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