Java實(shí)現(xiàn)用戶短信驗(yàn)證碼登錄功能實(shí)例代碼
此處使用阿里提供的API解決方案
同時(shí)需要注意的是,此文章在Java項(xiàng)目操作上需要有一定的編程基礎(chǔ),因?yàn)椴幌肓_里吧嗦的一大堆,對于分層理解較差和基礎(chǔ)編程能力較低的小白不建議
1.前置阿里云操作
1.登錄阿里云后,搜索“短信服務(wù)”

2.點(diǎn)擊后進(jìn)入如下界面此處點(diǎn)擊“免費(fèi)開通”,

此處若項(xiàng)目必須要求有真實(shí)短信發(fā)送,則建議購買最便宜的先進(jìn)行測試即可

3.點(diǎn)擊“快速學(xué)習(xí)和測試”,依次根據(jù)提示,申請“資質(zhì)”,“簽名”,“模板”
此處三個(gè)都對應(yīng)個(gè)人和企業(yè),申請需要時(shí)間


4.模擬測試,在“快速學(xué)習(xí)和測試”界面的下方,有測試的模板可以使用,測試需要綁定測試手機(jī)號、申請自定義測試模板和自定義測試簽名

5.調(diào)用API發(fā)送短信
在此需要注意的是,VS Code和IEDA需要下載對應(yīng)的插件才能保證在后續(xù)自己的項(xiàng)目中能正常調(diào)用到短信發(fā)送的API接口

點(diǎn)擊SDK實(shí)例后能看到完整的調(diào)用代碼,此時(shí)建議使用V2.0,代碼包含java(異步)和java
,采用哪種方式都無所謂,只需將代碼全部復(fù)制即可

2.java項(xiàng)目操作
0.注意事項(xiàng)
在此之前,你需要準(zhǔn)備的東西如下
1.短信簽名名稱 SignName
2.短信模板Code TemplateCode
3.SDK實(shí)例代碼
4.對應(yīng)編譯器的插件必須安裝完畢
5.對應(yīng)的ALIBABA_CLOUD_ACCESS_KEY_ID和ALIBABA_CLOUD_ACCESS_KEY_SECRET
ALIBABA_CLOUD_ACCESS_KEY_ID和ALIBABA_CLOUD_ACCESS_KEY_SECRET可以在個(gè)人中心看到


創(chuàng)建對應(yīng)的AccessKey時(shí),需要保存好對應(yīng)的ALIBABA_CLOUD_ACCESS_KEY_ID和ALIBABA_CLOUD_ACCESS_KEY_SECRET,這是調(diào)用API時(shí)傳遞到阿里的憑證,十分重要!

以上準(zhǔn)備完畢后,就可以在java項(xiàng)目中嵌入對應(yīng)的API實(shí)現(xiàn)驗(yàn)證碼的發(fā)送
1.創(chuàng)建兩個(gè)接口,一個(gè)是獲取驗(yàn)證碼,一個(gè)是攜帶驗(yàn)證碼登錄
在此方案下,采用了將驗(yàn)證碼存儲到Redis中,此處存入Redis后,可設(shè)置驗(yàn)證碼的過期時(shí)間,減少對底層的訪問壓力,也能實(shí)現(xiàn)驗(yàn)證碼限時(shí)的操作,另外此出也可加入對應(yīng)的手機(jī)號在固定時(shí)間內(nèi)對于獲取驗(yàn)證碼接口的訪問次數(shù)限制,避免惡意訪問造成服務(wù)器壓力過大。
Controller
package com.ruoyi.controller;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.ruoyi.common.constant.ReturnConstants;
import com.ruoyi.common.constant.UserConstants;
import com.ruoyi.domain.dto.*;
import com.ruoyi.domain.entity.User;
import com.ruoyi.pojo.vo.CurrentPrincipal;
import com.ruoyi.security.center.RequestLimit;
import com.ruoyi.service.WeChatLoginService;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.bean.result.WxMpOAuth2AccessToken;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.*;
import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.service.IUserService;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.common.core.page.TableDataInfo;
@Slf4j
@RestController
@RequestMapping("/v1/user")
public class UserController extends BaseController
{
@Autowired
private IUserService userService;
@Autowired
private WxMpService wxService;
@Autowired
private WeChatLoginService weChatLoginService;
/**
* - 手機(jī)號格式錯(cuò)誤
* - 為空
* - 不符合手機(jī)號
* - 手機(jī)號未注冊
* - 用戶被禁用
* - 在此處直接驗(yàn)證,以減少發(fā)送短信的成本
* 驗(yàn)證碼存儲到redis中,在五分鐘內(nèi)可以通過驗(yàn)證碼登錄
* @param userVerifyDTO
* @return
* @throws Exception
*/
@RequestLimit
@PostMapping("verify")
public AjaxResult verify(@RequestBody UserVerifyDTO userVerifyDTO) throws Exception {
log.debug("處理驗(yàn)證碼獲取");
log.debug("驗(yàn)證信息:{}", userVerifyDTO);
return AjaxResult.success(userService.verify(userVerifyDTO));
}
/**
* 登錄請求,匹配redis中的驗(yàn)證碼和數(shù)據(jù)庫中的信息
* @param userLoginDTO
* @param request
* @return
* @throws SocketException
* @throws UnknownHostException
*/
@RequestLimit
@PostMapping("login")
public Object login(@RequestBody UserLoginDTO userLoginDTO, HttpServletRequest request) throws SocketException, UnknownHostException {
log.debug("處理登錄請求-攜帶驗(yàn)證碼");
log.debug("登錄信息:{}", userLoginDTO);
return userService.login(userLoginDTO, request);
}
}2.編寫service的實(shí)現(xiàn),
ServiceImpl
package com.ruoyi.service.impl;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.time.Duration;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import cn.hutool.core.bean.BeanUtil;
import com.alibaba.fastjson2.JSON;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.ruoyi.common.constant.ReturnConstants;
import com.ruoyi.common.constant.UserConstants;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.utils.ServletUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.ip.AddressUtils;
import com.ruoyi.common.utils.ip.IpUtils;
import com.ruoyi.common.utils.uuid.UUID;
import com.ruoyi.constans.JwtConstans;
import com.ruoyi.domain.bo.UserInsertBo;
import com.ruoyi.domain.dto.*;
import com.ruoyi.domain.entity.Addr;
import com.ruoyi.domain.entity.Logininfor;
import com.ruoyi.domain.entity.Loginlogs;
import com.ruoyi.domain.entity.User;
import com.ruoyi.domain.param.UserLoginInfoVO;
import com.ruoyi.domain.vo.UserLoginResultVO;
import com.ruoyi.mapper.LoginlogsMapper;
import com.ruoyi.pojo.vo.CurrentPrincipal;
import com.ruoyi.pojo.vo.PageData;
import com.ruoyi.pojo.vo.UserCachePO;
import com.ruoyi.service.IUserService;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.utils.*;
import eu.bitwalker.useragentutils.UserAgent;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.annotation.Cacheable;
import com.ruoyi.common.core.redis.RedisCache;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.stereotype.Service;
import com.ruoyi.mapper.UserMapper;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import static com.ruoyi.common.utils.PageUtils.startPage;
@Slf4j
@Service
public class UserServiceImpl implements IUserService, JwtConstans
{
@Value("${token.secret}")
private String secretKey;
@Value("${token.expireTime}")
private Integer expireTime;
@Value("${wkzr.redis.test}")
private String secret;
/**
* 驗(yàn)證碼過期時(shí)間
*/
@Value("${wkzr.redis.verificationExpirationTime}")
private Integer verificationExpirationTime;
@Autowired
private StringRedisTemplate redisTemplate;
@Autowired
private LoginlogsMapper loginlogsMapper;
@Autowired
private UserMapper userMapper;
@Autowired
private RedisCache redisCache;
//設(shè)置初始密碼屬性
@Value("${userPassword.initPassword}")
private String initPassword;
/**
* - 手機(jī)號格式錯(cuò)誤
* - 為空
* - 不符合手機(jī)號
* - 手機(jī)號未注冊
* - 用戶被禁用
* - 在此處直接驗(yàn)證,以減少發(fā)送短信的成本
* 驗(yàn)證碼存儲到redis中,在五分鐘內(nèi)可以通過驗(yàn)證碼登錄
* @param userVerifyDTO
* @return
* @throws Exception
*/
@Override
public String verify(UserVerifyDTO userVerifyDTO) throws Exception {
String phoneNumber = userVerifyDTO.getPhoneNumber();
// 格式錯(cuò)誤
if (phoneNumber.length() != 11) {
throw new AccessDeniedException(ReturnConstants.PHONENUMBER_FORMAT_ERROR);
}
// 不能為空
if (StringUtils.isEmpty(phoneNumber)) {
throw new AccessDeniedException(ReturnConstants.PHONENUMBER_NOT_EMPTY);
}
User user = userMapper.selectUserByPhone(phoneNumber);
// 用戶不存在
if (user == null) {
throw new AccessDeniedException(ReturnConstants.ACCOUNT_NOT_EXIST);
}
// 未啟用
if (user.getEnabled() != null && user.getEnabled() == 0) {
throw new AccessDeniedException(ReturnConstants.USER_IS_UNENABLED);
}
// 生成隨機(jī)驗(yàn)證碼
// String verificationCode = SendCodeUtils.generateVerificationCode();
String verificationCode = "000000";
System.out.println("驗(yàn)證碼:" + verificationCode);
// 發(fā)送驗(yàn)證碼
log.info("發(fā)送驗(yàn)證碼!");
SendCodeUtils.verify(phoneNumber, verificationCode);
// 存儲驗(yàn)證碼到Redis,設(shè)置有效期為5分鐘
String rediskey = secret + phoneNumber;
redisTemplate.opsForValue().set(rediskey, verificationCode, verificationExpirationTime, TimeUnit.MINUTES); // 單位為分鐘
return ReturnConstants.CAPTCHA_SEND_SUCCESS;
}
/**
* 從redis中獲取驗(yàn)證碼
* 匹配
* - 未通過
* - 不存在或過期
* - 存在且通過
* @param userLoginDTO
* @param request
* @return
* @throws SocketException
* @throws UnknownHostException
*/
@Override
public Object login(UserLoginDTO userLoginDTO, HttpServletRequest request) throws SocketException, UnknownHostException {
log.info("request:{}", request);
String phoneNumber = userLoginDTO.getPhoneNumber();
String remoteAddr = IpUtils.getIpAddr();// ip地址
String macaddr = GetMacAddr.getLocalMac(remoteAddr);//mac地址
//獲取瀏覽器
UserAgent userAgent = UserAgent.parseUserAgentString(ServletUtils.getRequest().getHeader("User-Agent"));
String browser = userAgent.getBrowser().getName();
//獲取操作系統(tǒng)
String os = userAgent.getOperatingSystem().getName();
//獲取操作地點(diǎn)
String location = AddressUtils.getRealAddressByIP(remoteAddr);
log.info("remoteAddr:{}", remoteAddr);
log.info("userAgent:{}", userAgent);
// 從Redis中獲取存儲的驗(yàn)證碼
String redisKey = secret + phoneNumber;
String storedCode = redisTemplate.opsForValue().get(redisKey);
if (storedCode == null) {
return AjaxResult.forbidden(ReturnConstants.CAPTCHA_NOT_EXIT);
}
if (!userLoginDTO.getVerificationCode().equals(storedCode)) {
return AjaxResult.forbidden(ReturnConstants.CAPTCHA_IS_ERROR);
}
User user = userMapper.selectUserByPhone(phoneNumber);
log.info("user信息:{}", user);
if (user == null) {
return AjaxResult.forbidden(ReturnConstants.USER_NOT_EXIST);
}
if (user.getEnabled() == 0){
return AjaxResult.forbidden(ReturnConstants.USER_IS_UNENABLED);
}
// 獲取用戶信息
Integer userId = user.getUserId();
String userAccount = user.getUserAccount();
String userName = user.getUsername();
Logininfor logininfor = Logininfor.builder()
.userAccount(userAccount)
.userName(userName)
.ipaddr(remoteAddr)
.macAddr(macaddr)
.browser(browser)
.os(os)
.loginLocation(location)
.loginTime(new Date())
.phoneNumber(user.getPhoneNumber())
.enable(1)
.wxBind(user.getWxBind())
.status(1)
.build();
loginlogsMapper.insertTrinvLoginlogs(logininfor);
// 生成token
// JWT
Map<String, Object> claims = new HashMap<>();
String uuid = UUID.randomUUID().toString();
claims.put(CLAIM_USER_ID, userId);
claims.put(CLAIM_UUID, uuid);
claims.put(CLAIM_PHONE_NUMBER, phoneNumber);
claims.put(CLAIM_USER_ACCOUNT, userAccount);
claims.put(CLAIM_USER_NAME, userName);
claims.put(CLAIM_USER_AGENT, userAgent); // mac
claims.put(CLAIM_REMOTE_ADDR, remoteAddr); // ip
claims.put(CLAIM_OS, os); // os操作系統(tǒng)
claims.put(CLAIM_MAC, macaddr); // mac地址
claims.put(CLAIM_BROWSER, browser); // 瀏覽器名稱
String jwt = JwtUtils.createJWT(claims, secretKey);
log.info("生成用戶的JWT數(shù)據(jù):{}", jwt);
UserLoginInfoVO userLoginInfoVO = userMapper.getLoginInfoByUsername(userName);
log.info("userLoginInfoVO:{}", userLoginInfoVO);
List<GrantedAuthority> authorities = new ArrayList<>();
// 獲取角色關(guān)鍵字 用于后續(xù)權(quán)限判斷
List<String> rolekeys = userLoginInfoVO.getRolekeys();
for (String rolekey : rolekeys) {
authorities.add(new SimpleGrantedAuthority(rolekey));
}
String authoritiesJsonString = JSON.toJSONString(authorities);
UserCachePO userCachePO = new UserCachePO();
userCachePO.setEnable(userLoginInfoVO.getEnable());
userCachePO.setAuthoritiesJsonString(authoritiesJsonString);
userCachePO.setToken(jwt);
// 轉(zhuǎn)換hash數(shù)據(jù)類型,存入redis
String jwtRedisKey = "JWT_Token:" + uuid;// 鍵
HashOperations<String, Object, Object> opsForHash = redisTemplate.opsForHash();
Map<String, Object> userLoginInfoMap = BeanUtil.beanToMap(userCachePO);
opsForHash.putAll(jwtRedisKey, userLoginInfoMap);
redisTemplate.expire(jwtRedisKey, 86400, TimeUnit.MINUTES);// 過期時(shí)間
log.info("向緩存中存入用戶狀態(tài)數(shù)據(jù):{}", userCachePO);
// 返回登錄結(jié)果VO
UserLoginResultVO userLoginResultVO = new UserLoginResultVO()
.setUserId(userId)
.setUsername(userName)
.setToken(jwt)
.setAuthorities(rolekeys);
return AjaxResult.success(userLoginResultVO);
}
}3.發(fā)送短信的API
此處代碼有兩個(gè)工具類
// 生成隨機(jī)驗(yàn)證碼
String verificationCode = SendCodeUtils.generateVerificationCode();
System.out.println("驗(yàn)證碼:" + verificationCode);
// 發(fā)送驗(yàn)證碼
log.info("發(fā)送驗(yàn)證碼!");
SendCodeUtils.verify(phoneNumber, verificationCode);package com.ruoyi.utils;
import com.aliyun.tea.TeaException;
import java.util.Random;
public class SendCodeUtils {
public static String generateVerificationCode() {
// 設(shè)置驗(yàn)證碼長度為6
int length = 6;
// 驗(yàn)證碼字符集
String digits = "0123456789";
Random random = new Random();
StringBuilder sb = new StringBuilder();
// 生成六位數(shù)驗(yàn)證碼
for (int i = 0; i < length; i++) {
int index = random.nextInt(digits.length());
sb.append(digits.charAt(index));
}
return sb.toString();
}
/**
* <b>description</b> :
* <p>使用AK&SK初始化賬號Client</p>
* @return Client
*
* @throws Exception
*/
public static com.aliyun.dysmsapi20170525.Client createClient() throws Exception {
// 工程代碼泄露可能會導(dǎo)致 AccessKey 泄露,并威脅賬號下所有資源的安全性。以下代碼示例僅供參考。
// 建議使用更安全的 STS 方式,更多鑒權(quán)訪問方式請參見:https://help.aliyun.com/document_detail/378657.html。
com.aliyun.teaopenapi.models.Config config = new com.aliyun.teaopenapi.models.Config()
// 必填,請確保代碼運(yùn)行環(huán)境設(shè)置了環(huán)境變量 ALIBABA_CLOUD_ACCESS_KEY_ID。
.setAccessKeyId("ALIBABA_CLOUD_ACCESS_KEY_ID")
// 必填,請確保代碼運(yùn)行環(huán)境設(shè)置了環(huán)境變量 ALIBABA_CLOUD_ACCESS_KEY_SECRET。
.setAccessKeySecret("ALIBABA_CLOUD_ACCESS_KEY_SECRET");
// Endpoint 請參考 https://api.aliyun.com/product/Dysmsapi
config.endpoint = "dysmsapi.aliyuncs.com";
return new com.aliyun.dysmsapi20170525.Client(config);
}
public static String verify(String phoneNumber, String verificationCode) throws Exception {
// java.util.List<String> args = java.util.Arrays.asList(args_);
com.aliyun.dysmsapi20170525.Client client = SendCodeUtils.createClient();
com.aliyun.dysmsapi20170525.models.SendSmsRequest sendSmsRequest = new com.aliyun.dysmsapi20170525.models.SendSmsRequest()
.setPhoneNumbers(phoneNumber)
.setSignName("簽名名稱")
.setTemplateCode("模板Code")
.setTemplateParam("{\"code\":\"" + verificationCode + "\"}");
try {
// 復(fù)制代碼運(yùn)行請自行打印 API 的返回值
client.sendSmsWithOptions(sendSmsRequest, new com.aliyun.teautil.models.RuntimeOptions());
return verificationCode;
} catch (TeaException error) {
// 此處僅做打印展示,請謹(jǐn)慎對待異常處理,在工程項(xiàng)目中切勿直接忽略異常。
// 錯(cuò)誤 message
System.out.println(error.getMessage());
// 診斷地址
System.out.println(error.getData().get("Recommend"));
com.aliyun.teautil.Common.assertAsString(error.message);
return null;
} catch (Exception _error) {
TeaException error = new TeaException(_error.getMessage(), _error);
// 此處僅做打印展示,請謹(jǐn)慎對待異常處理,在工程項(xiàng)目中切勿直接忽略異常。
// 錯(cuò)誤 message
System.out.println(error.getMessage());
// 診斷地址
System.out.println(error.getData().get("Recommend"));
com.aliyun.teautil.Common.assertAsString(error.message);
return null;
}
}
}
之前需要的幾個(gè)關(guān)鍵信息可以在此發(fā)揮用處
此處若是公司內(nèi)部代碼可將ALIBABA_CLOUD_ACCESS_KEY_ID和ALIBABA_CLOUD_ACCESS_KEY_SECRET的值直接填入對應(yīng)位置
但若是代碼可能會泄露,則還是建議在對應(yīng)的環(huán)境中部署環(huán)境變量,代碼運(yùn)行時(shí)獲取環(huán)境變量自動(dòng)填入,涉及的Windows和Linux環(huán)境下的環(huán)境變量設(shè)置在后續(xù)文章中可找到,此處不多贅述。
// 必填,請確保代碼運(yùn)行環(huán)境設(shè)置了環(huán)境變量 ALIBABA_CLOUD_ACCESS_KEY_ID。 .setAccessKeyId(System.getenv("ALIBABA_CLOUD_ACCESS_KEY_ID")) // 必填,請確保代碼運(yùn)行環(huán)境設(shè)置了環(huán)境變量 ALIBABA_CLOUD_ACCESS_KEY_SECRET。 .setAccessKeySecret(System.getenv("ALIBABA_CLOUD_ACCESS_KEY_SECRET"));

4.以上操作和數(shù)據(jù)庫都完成后,即可實(shí)現(xiàn)短信驗(yàn)證碼的發(fā)送,登錄時(shí)要求用戶攜帶驗(yàn)證碼,并與Redis中存儲的驗(yàn)證碼匹配即可通過校驗(yàn)。
到此這篇關(guān)于Java實(shí)現(xiàn)用戶短信驗(yàn)證碼登錄功能的文章就介紹到這了,更多相關(guān)Java用戶短信驗(yàn)證碼登錄內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
解析Java中的定時(shí)器及使用定時(shí)器制作彈彈球游戲的示例
這篇文章主要介紹了Java中的定時(shí)器及使用定時(shí)器制作彈彈球游戲的示例,文中同時(shí)也分析了定時(shí)器timer的缺點(diǎn)及相關(guān)替代方案,需要的朋友可以參考下2016-02-02
SpringBoot中將@Bean方法解析為BeanDefinition詳解
這篇文章主要介紹了SpringBoot中將@Bean方法解析為BeanDefinition詳解,得到的BeanDefinition是ConfigurationClassBeanDefinition類型,會為BeanDefinition設(shè)置factoryMethodName,這意味著當(dāng)實(shí)例化這個(gè)bean的時(shí)候?qū)⒉捎霉S方法,需要的朋友可以參考下2023-12-12
基于idea解決springweb項(xiàng)目的Java文件無法執(zhí)行問題
這篇文章給大家介紹了基于idea解決springweb項(xiàng)目的Java文件無法執(zhí)行問題,文中通過圖文結(jié)合的方式給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作有一定的幫助,需要的朋友可以參考下2024-02-02
Java中構(gòu)造函數(shù),set/get方法和toString方法使用及注意說明
這篇文章主要介紹了Java中構(gòu)造函數(shù),set/get方法和toString方法的使用及注意說明,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-01-01
SpringBoot?@Configuration與@Bean注解使用介紹
這篇文章主要介紹了SpringBoot中的@Configuration與@Bean注解,在進(jìn)行項(xiàng)目編寫前,我們還需要知道一個(gè)東西,就是SpringBoot對我們的SpringMVC還做了哪些配置,包括如何擴(kuò)展,如何定制,只有把這些都搞清楚了,我們在之后使用才會更加得心應(yīng)手2022-10-10
SSM框架下如何實(shí)現(xiàn)數(shù)據(jù)從后臺傳輸?shù)角芭_
這篇文章主要介紹了SSM框架下如何實(shí)現(xiàn)數(shù)據(jù)從后臺傳輸?shù)角芭_,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-05-05
劍指Offer之Java算法習(xí)題精講二叉樹的構(gòu)造和遍歷
跟著思路走,之后從簡單題入手,反復(fù)去看,做過之后可能會忘記,之后再做一次,記不住就反復(fù)做,反復(fù)尋求思路和規(guī)律,慢慢積累就會發(fā)現(xiàn)質(zhì)的變化2022-03-03

