【华夏OA项目实战】3-30 利用Redis缓存Refresh与Code实现验证码机制

本课程开发软件与项目初始代码可以通过云盘下载,答疑服务可以关注视频片头的二维码获取。
https://www.123912.com/s/A75eVv-27mod,提取码:yton

教学视频

在华夏OA系统中密码是加密混淆之后存储在数据表中的,倘若用户忘记了自己的密码是无法找回的,只能重新设置密码。这节课我们用Redis缓存实现验证码机制,然后模拟发送验证码短信。如果你想要真实发送验证码短信,则可以对接阿里云或者腾讯云的相关接口,但是必须要先提供企业资质才可以。个人身份是无法使用运营商的短信接口的,以防有人群发诈骗短信。

视频中有代码讲解,大家认真观看视频,不要快进。有些操作只在视频中演示,只对着手册操作并不能完成案例,切记!

一、Refresh与Code缓存

在发送短信验证码的流程中,我们需要用到Refresh与Code两种缓存。Code缓存决定了短信验证码的有效期是多久,就是往Redis里面创建Code缓存的时候设置一个过期时间即可;Refresh缓存决定了在多长时间内系统不会再次发送验证码短信。比如用户每隔5秒钟(绕过后端防抖)点击一次发送验证码按钮,后端系统每次就真要发送短信吗?显而易见,肯定不行。毕竟发送短信是需要花钱的,我们不能无限制的为用户发送验证码短信,我们必须要降低发送短信的频率。其实只要加长发送短信的间隔时间,频率自然就降低了。假如我希望为用户发送验证码短信的间隔时间最少为两分钟,也就是说在两分钟内,即便用户没有收到验证码短信,系统也不会再为他发送短信。用户必须等待两分钟,在页面上点击发送验证码按钮,系统才会发送信息的验证码短信。这样做可以大幅降低发送短信的频率,短信开销也就节省下来了。
在这里插入图片描述

二、编写hxoa-oms子系统

1. 编写持久层

UserMapper.xml文件中,声明SQL语句根据手机号码查询用户ID。

<select id="searchIdByTel" parameterType="String" resultType="Long">
    SELECT id
    FROM tb_user
    WHERE tel = #{tel}
</select>

com.example.hxoa.cloud.oms.db.daoUserMapper.java接口中,声明抽象方法。

public interface UserMapper {
    ……
    public Long searchIdByTel(String tel);
}

2. 编写Dubbo层

com.example.hxoa.cloud.dubboUserApi.java接口中,声明抽象方法。

public interface UserApi {
    ……
    public R sendSmsCaptcha(String tel);
    public R verifySmsCaptcha(String tel, String captcha);
}

com.example.hxoa.cloud.oms.apiUserApiHandler.java类中,实现抽象方法。

@DubboService
@Slf4j
public class UserApiHandler implements UserApi {
    @Resource
    private RedissonClient redissonClient;
    
    ……
    @Override
    @SentinelResource("UserApiHandler.sendSmsCaptcha")
    public R sendSmsCaptcha(String tel) {
        Long userId = userMapper.searchIdByTel(tel);
        Map result;
        if (userId != null) {
            String key = "sms_captcha_refresh_" + tel;
            //检查该手机号码是否被暂时禁止刷新验证码
            RBucket rBucket = redissonClient.getBucket(key);
            if (rBucket.isExists()) {
                //如果存在Refresh缓存就不发送验证码短信
                result = new HashMap() {{
                    put("status", "FORBIDDEN");
                    put("message", "该手机号码暂时被禁止刷新验证码");
                }};
            }
            else {
                //生成6位数字验证码
                String captcha = RandomUtil.randomNumbers(6);
                System.out.println("验证码:" + captcha);

                //向Redis中写入Code缓存
                key = "sms_code_" + tel;
                RMap rmap = redissonClient.getMap(key);
                rmap.putAll(new HashMap() {{
                    put("captcha", captcha);
                    put("userId", userId);
                }});
                rmap.expire(Duration.of(5, ChronoUnit.MINUTES));

                // TODO 发送短信验证码

                //向Redis中写入Refresh缓存,2分钟内不再向该用户发送验证码短信
                key = "sms_captcha_refresh_" + tel;
                rBucket = redissonClient.getBucket(key);
                rBucket.set(captcha, Duration.of(2, ChronoUnit.MINUTES));
                result = new HashMap() {{
                    put("status", "SUCCESS");
                    put("message", "验证码发送成功");
                }};
            }

        }
        else {
            result = new HashMap() {{
                put("status", "FAILED");
                put("message", "手机号码不存在");
            }};
        }

        return R.ok().setAttributeAll(result);
    }

    @Override
    @SentinelResource("UserApiHandler.verifySmsCaptcha")
    public R verifySmsCaptcha(String tel, String captcha) {
        String key = "sms_code_" + tel;
        RMap rmap = redissonClient.getMap(key);
        //判断验证码是否已经过期
        if (!rmap.isExists()) {
            return R.ok().setAttribute("status", "OVERDUE");

        }
        //从Code缓存中取出验证码和用户ID
        String cacheCaptcha = rmap.get("captcha").toString();
        String cacheUserId = rmap.get("userId").toString();

        //比较用户提交的验证码和缓存的验证码是否一致
        if (cacheCaptcha.equals(captcha)) {
            //两个验证码一致就删除Code缓存和Refresh缓存
            rmap.delete();
            redissonClient.getMap("sms_captcha_refresh_" + tel).delete();
            return R.ok().setAttributeAll(new HashMap() {{
                put("status", "SUCCESS");
                put("userId", cacheUserId);
            }});
        }
        else {
            return R.ok().setAttribute("status", "DIFFERENT");
        }
    }
}

三、编写hxoa-mis子系统

1. 编写Dubbo层

com.example.hxoa.cloud.dubboUserApi.java接口中,声明抽象方法。

public interface UserApi {
    ……
    public R sendSmsCaptcha(String tel);
    public R verifySmsCaptcha(String tel, String captcha);
}

2. 编写业务层

com.example.hxoa.cloud.mis.serviceUserService.java接口中,声明抽象方法。

public interface UserService {
    ……
    public Map sendSmsCaptcha(String tel);
    public Map verifySmsCaptcha(String tel, String captcha);
}

com.example.hxoa.cloud.mis.service.implUserServiceImpl.java类中,实现抽象方法。

@Service
public class UserServiceImpl implements UserService {
    ……
    @Override
    public Map sendSmsCaptcha(String tel) {
        R r = userApi.sendSmsCaptcha(tel);
        Map result = r.getAttributeAll();
        return result;
    }

    @Override
    public Map verifySmsCaptcha(String tel, String captcha) {
        R r = userApi.verifySmsCaptcha(tel, captcha);
        Map result = r.getAttributeAll();
        return result;
    }
}

3. 编写Web层

com.example.hxoa.cloud.mis.controller.form包中,创建SendSmsCaptchaForm.java类。

@Data
public class SendSmsCaptchaForm {
    @NotBlank(message = "手机号不能为空")
    @Pattern(regexp = "^1[34578]\\d{9}$", message = "手机号格式不正确")
    private String tel;
}

com.example.hxoa.cloud.mis.controller.form包中,创建VerifySmsCaptchaForm.java类。

@Data
public class VerifySmsCaptchaForm {
    @NotBlank(message = "手机号不能为空")
    @Pattern(regexp = "^1[34578]\\d{9}$", message = "手机号格式不正确")
    private String tel;

    @NotBlank(message = "验证码不能为空")
    @Pattern(regexp = "^\\d{6}$", message = "验证码格式不正确")
    private String captcha;
}

com.example.hxoa.cloud.mis.controllerUserController.java类中,声明Web方法。

@RestController
@RequestMapping("/user")
public class UserController {
    ……
    @PostMapping("/send-sms-captcha")
    @SentinelResource("UserController.sendSmsCaptcha")
    public R sendSmsCaptcha(@RequestBody @Valid SendSmsCaptchaForm form) {
        Map result = userService.sendSmsCaptcha(form.getTel());
        return R.ok().setAttributeAll(result);
    }

    @PostMapping("/verify-sms-captcha")
    @SentinelResource("UserController.verifySmsCaptcha")
    public R verifySmsCaptcha(@RequestBody @Valid VerifySmsCaptchaForm form) {
        Map result = userService.verifySmsCaptcha(form.getTel(), form.getCaptcha());
        R r = R.ok();
        String status = MapUtil.getStr(result, "status");
        if ("SUCCESS".equals(status)) {
            Long userId = MapUtil.getLong(result, "userId");
            result.remove("userId");
            //重置密码的过程中,把用户先踢下线销毁现有令牌,防止他执行其他请求
            StpUtil.logout(userId, "Web");
            //因为重置密码的后端Web方法需要登录才能调用,所以重新执行登录。
            StpUtil.login(userId, "Web");
            String token = StpUtil.getTokenValueByLoginId(userId, "Web");
            //把新令牌返回给前端页面
            r.setAttribute("token", token);
        }
        r.setAttributeAll(result);
        return r;
    }
}

上面的Web方法并没有添加后端防抖@NoDuplicateSubmit注解,这是因为后端处理防抖的时候需要从请求头中提取Token然后解析出UserId。现在由于用户忘记密码而无法登录华夏OA系统,也就说明浏览器并没有缓存Token,所以我们不能给上面两个方法添加防抖注解,否则后端执行防抖的时候会因为拿不到Token而报错。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值