- 流程一览(类似伪代码)
-
- 一、获取图形验证码,加入redis
- 二、判断图形验证码正确后,发送手机验证码
- 三、判断手机验证码正确后,成功注册
- 四、登录相关
- ... 、详情看代码,细节
-
- 各种依赖不再提供,仅提供思路根据自己的情况自己定义即可。
一、第一步:获取图形验证码
@GetMapping("captcha")
public void getCaptcha(HttpServletRequest request, HttpServletResponse response){
String captchaText = captchaProducer.createText();
log.info("验证码内容:{}",captchaText);
redisTemplate.opsForValue().set(getCaptchaKey(request),captchaText,CAPTCHA_CODE_EXPIRED, TimeUnit.MILLISECONDS);
BufferedImage bufferedImage = captchaProducer.createImage(captchaText);
try (ServletOutputStream outputStream = response.getOutputStream()){
ImageIO.write(bufferedImage,"jpg",outputStream);
outputStream.flush();
} catch (IOException e) {
log.error("获取流出错:{}",e.getMessage());
}
} 根据ip等构造key
private String getCaptchaKey(HttpServletRequest request){
String ip = CommonUtil.getIpAddr(request);
String userAgent = request.getHeader("User-Agent");
String key = "account-service:captcha:"+CommonUtil.MD5(ip+userAgent);
log.info("验证码key:{}",key);
return key;
} 二、发送手机验证码
/**
* 发送短信验证码
* @return
*/
@PostMapping("send_code")
public JsonData sendCode(@RequestBody SendCodeRequest sendCodeRequest,HttpServletRequest request){
String key = getCaptchaKey(request);
String cacheCaptcha = redisTemplate.opsForValue().get(key);
String captcha = sendCodeRequest.getCaptcha();
if(captcha!=null && cacheCaptcha !=null && cacheCaptcha.equalsIgnoreCase(captcha)){
//成功
redisTemplate.delete(key);
JsonData jsonData = notifyService.sendCode(SendCodeEnum.USER_REGISTER,sendCodeRequest.getTo());
return jsonData;
}else {
return JsonData.buildResult(BizCodeEnum.CODE_CAPTCHA_ERROR);
}
} @Override
public JsonData sendCode(SendCodeEnum sendCodeEnum, String to) {
String cacheKey = String.format(RedisKey.CHECK_CODE_KEY,sendCodeEnum.name(),to);
String cacheValue = redisTemplate.opsForValue().get(cacheKey);
//如果不为空,再判断是否是60秒内重复发送 0122_232131321314132
if(StringUtils.isNotBlank(cacheValue)){
long ttl = Long.parseLong(cacheKey.split("_")[1]);
//当前时间戳-验证码发送的时间戳,如果小于60秒,则不给重复发送
long leftTime = CommonUtil.getCurrentTimestamp() - ttl;
if( leftTime < (1000*60)){
log.info("重复发送短信验证码,时间间隔:{}秒",leftTime);
return JsonData.buildResult(BizCodeEnum.CODE_LIMITED);
}
}
String code = CommonUtil.getRandomCode(6);
//生成拼接好验证码
String value = code+"_"+CommonUtil.getCurrentTimestamp();
redisTemplate.opsForValue().set(cacheKey,value,CODE_EXPIRED,TimeUnit.MILLISECONDS);
if(CheckUtil.isEmail(to)){
//发送邮箱验证码 TODO
}else if(CheckUtil.isPhone(to)){
//发送手机验证码
smsComponent.send(to,smsConfig.getTemplateId(),code);
}
return JsonData.buildSuccess();
} 三、用户注册
/**
* 用户注册
* @param registerRequest
* @return
*/
@PostMapping("register")
public JsonData register(@RequestBody AccountRegisterRequest registerRequest){
JsonData jsonData = accountService.register(registerRequest);
return jsonData;
} /**
* 手机验证码验证
* 密码加密(TODO)
* 账号唯一性检查(TODO)
* 插入数据库
* 新注册用户福利发放(TODO)
*
* @param registerRequest
* @return
*/
@Override
public JsonData register(AccountRegisterRequest registerRequest) {
boolean checkCode = false;
//判断验证码
if (StringUtils.isNotBlank(registerRequest.getPhone())) {
checkCode = notifyService.checkCode(SendCodeEnum.USER_REGISTER, registerRequest.getPhone(), registerRequest.getCode());
}
//验证码错误
if (!checkCode) {
return JsonData.buildResult(BizCodeEnum.CODE_ERROR);
}
AccountDO accountDO = new AccountDO();
BeanUtils.copyProperties(registerRequest, accountDO);
//认证级别
accountDO.setAuth(AuthTypeEnum.DEFAULT.name());
//设置密码 秘钥 盐
accountDO.setSecret("$1$" + CommonUtil.getStringNumRandom(8));
String cryptPwd = Md5Crypt.md5Crypt(registerRequest.getPwd().getBytes(), accountDO.getSecret());
accountDO.setPwd(cryptPwd);
int rows = accountManager.insert(accountDO);
log.info("rows:{},注册成功:{}", rows, accountDO);
//用户注册成功,发放福利
userRegisterInitTask(accountDO);
return JsonData.buildSuccess();
} /**
* 验证码校验逻辑
* @param sendCodeEnum
* @param to
* @param code
* @return
*/
@Override
public boolean checkCode(SendCodeEnum sendCodeEnum, String to, String code) {
String cacheKey = String.format(RedisKey.CHECK_CODE_KEY,sendCodeEnum.name(),to);
String cacheValue = redisTemplate.opsForValue().get(cacheKey);
if(StringUtils.isNotBlank(cacheValue)){
String cacheCode = cacheValue.split("_")[0];
if(cacheCode.equalsIgnoreCase(code)){
//删除验证码
redisTemplate.delete(code);
return true;
}
}
return false;
} 四、登录
/**
* 用户登录
* @param request
* @return
*/
@PostMapping("login")
public JsonData login(@RequestBody AccountLoginRequest request){
JsonData jsonData = accountService.login(request);
return jsonData;
} /**
* 1、根据手机号去找
* 2、有的话,则用秘钥+用户传递的明文密码,进行加密,再和数据库的密文进行匹配
*
* @param request
* @return
*/
@Override
public JsonData login(AccountLoginRequest request) {
List<AccountDO> accountDOList = accountManager.findByPhone(request.getPhone());
if (accountDOList != null && accountDOList.size() == 1) {
AccountDO accountDO = accountDOList.get(0);
String md5Crypt = Md5Crypt.md5Crypt(request.getPwd().getBytes(), accountDO.getSecret());
if (md5Crypt.equalsIgnoreCase(accountDO.getPwd())) {
LoginUser loginUser = LoginUser.builder().build();
BeanUtils.copyProperties(accountDO, loginUser);
// 生成token之后返回
String token = JWTUtil.geneJsonWebTokne(loginUser);
return JsonData.buildSuccess(token);
} else {
return JsonData.buildResult(BizCodeEnum.ACCOUNT_PWD_ERROR);
}
} else {
return JsonData.buildResult(BizCodeEnum.ACCOUNT_UNREGISTER);
}
} 额外相关——JWT封装(JWTUtil)
package net.dbc.utils;
/**
* @author DBC
* @version 1.0.0
* @date 2022年10月02日 10:42:15
* @website dbc655.top
*/
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.extern.slf4j.Slf4j;
import net.dbc.model.LoginUser;
import java.util.Date;
@Slf4j
public class JWTUtil {
/**
* 主题
*/
private static final String SUBJECT = "dbc";
/**
* 加密密钥
*/
private static final String SECRET = "dbc655.top";
/**
* 令牌前缀
*/
private static final String TOKNE_PREFIX = "my-cloud";
/**
* token过期时间,7天
*/
private static final long EXPIRED = 1000 * 60 * 60 * 24 * 7;
/**
* 生成token
*
* @param loginUser
* @return
*/
public static String geneJsonWebTokne(LoginUser loginUser) {
if (loginUser == null) {
throw new NullPointerException("对象为空");
}
String token = Jwts.builder().setSubject(SUBJECT)
//配置payload
.claim("head_img", loginUser.getHeadImg())
.claim("account_no", loginUser.getAccountNo())
.claim("username", loginUser.getUsername())
.claim("mail", loginUser.getMail())
.claim("phone", loginUser.getPhone())
.claim("auth", loginUser.getAuth())
.setIssuedAt(new Date())
.setExpiration(new Date(CommonUtil.getCurrentTimestamp() + EXPIRED))
.signWith(SignatureAlgorithm.HS256, SECRET).compact();
token = TOKNE_PREFIX + token;
return token;
}
/**
* 解密jwt
* @param token
* @return
*/
public static Claims checkJWT(String token) {
try {
final Claims claims = Jwts.parser().setSigningKey(SECRET)
.parseClaimsJws(token.replace(TOKNE_PREFIX, "")).getBody();
return claims;
} catch (Exception e) {
log.error("jwt 解密失败");
return null;
}
}
} 额外相关——拦截器(InterceptorConfig)
package net.dbc.config;
import lombok.extern.slf4j.Slf4j;
import net.dbc.interceptor.LoginInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* @author DBC
* @version 1.0.0
* @date 2022年10月02日 11:40:55
* @website dbc655.top
*/
@Configuration
@Slf4j
public class InterceptorConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor())
//添加拦截的路径
.addPathPatterns("/api/account/*/**", "/api/traffic/*/**")
//排除不拦截
.excludePathPatterns(
"/api/account/*/register","/api/account/*/upload","/api/account/*/login",
"/api/notify/v1/captcha","/api/notify/*/send_code");
}
} 本文作者为DBC,转载请注明。