- 流程一览(类似伪代码)
-
- 一、获取图形验证码,加入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,转载请注明。