使用Redis的采用阿里云短信服务的短信登录注册

一 、设计流程图

        

二、引入所需要的依赖

这里的注入依赖可能不全

redis

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

hutool

        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.8.18</version>
        </dependency>

json解析

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>2.0.32</version>
        </dependency>

        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
        </dependency>

mybaits-plus

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.3.1</version>
        </dependency>

三、配置数据库

向数据库添加属性

四、配置Redis连接

在application.yml中添加Redis的连接信息

spring:
  redis:
    host: 192.168.150.101
    port: 6379
    password: 123
    lettuce:
      pool:
        max-active: 8
        max-idle: 8
        min-idle: 0
        max-wait: 100

五、搭好三层框架

Controller层

@RestController
@Slf4j
public class PhoneEnrollController {
    
}

Service层

public interface SendStudentPhoneService extends IService<Student> {
}

@Service
public class SendStudentPhoneServiceImpl extends ServiceImpl<SendStudentPhoneMapper, Student> implements SendStudentPhoneService {
}

Mapper层

@Mapper
public interface SendStudentPhoneMapper extends BaseMapper<Student> {
}

六、编写短信验证码发送信息代码

@RestController
@Slf4j
@RequiredArgsConstructor
public class PhoneEnrollController {

    private final SendStudentPhoneServiceImpl sendStudentPhoneService;

    @PostMapping("/student/phone")
    public Result SendCode(String phone){
        return sendStudentPhoneService.sendCode(phone);
    }
}
@Slf4j
@Service
@RequiredArgsConstructor
public class SendStudentPhoneServiceImpl extends ServiceImpl<SendStudentPhoneMapper, Student> implements SendStudentPhoneService {

    private final StringRedisTemplate stringRedisTemplate;

    @Override
    public Result sendCode(String phone) {
        //判断手机是否合法
        if (RegexUtils.isPhoneInvalid(phone)) {
            return Result.error("手机格式不合法");
        }
        //生成随机验证码
        String code = RandomUtil.randomNumbers(6);
        //保存到Redis中
        stringRedisTemplate.opsForValue().set(LOGIN_CODE_KEY+phone,code,LOGIN_CODE_SSL, TimeUnit.MINUTES);
        //发送验证码
        log.info("发送验证码成功,{}" ,code);
        return Result.success();
    }
}

这里先采用log来代替发送信息

其中采用的手机格式是否合法是采用的RedexPatterns和RedexUtils

import cn.hutool.core.util.StrUtil;

/**
 * @author 虎哥
 */
public class RegexUtils {
    /**
     * 是否是无效手机格式
     * @param phone 要校验的手机号
     * @return true:符合,false:不符合
     */
    public static boolean isPhoneInvalid(String phone){
        return mismatch(phone, RegexPatterns.PHONE_REGEX);
    }
    /**
     * 是否是无效邮箱格式
     * @param email 要校验的邮箱
     * @return true:符合,false:不符合
     */
    public static boolean isEmailInvalid(String email){
        return mismatch(email, RegexPatterns.EMAIL_REGEX);
    }

    /**
     * 是否是无效验证码格式
     * @param code 要校验的验证码
     * @return true:符合,false:不符合
     */
    public static boolean isCodeInvalid(String code){
        return mismatch(code, RegexPatterns.VERIFY_CODE_REGEX);
    }

    // 校验是否不符合正则格式
    private static boolean mismatch(String str, String regex){
        if (StrUtil.isBlank(str)) {
            return true;
        }
        return !str.matches(regex);
    }
}
/**
 * @author 虎哥
 */
public abstract class RegexPatterns {
    /**
     * 手机号正则
     */
    public static final String PHONE_REGEX = "^1([38][0-9]|4[579]|5[0-3,5-9]|6[6]|7[0135678]|9[89])\\d{8}$";
    /**
     * 邮箱正则
     */
    public static final String EMAIL_REGEX = "^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\\.[a-zA-Z0-9_-]+)+$";
    /**
     * 密码正则。4~32位的字母、数字、下划线
     */
    public static final String PASSWORD_REGEX = "^\\w{4,32}$";
    /**
     * 验证码正则, 6位数字或字母
     */
    public static final String VERIFY_CODE_REGEX = "^[a-zA-Z\\d]{6}$";

}

在Redis中可以查看到code信息

七、编写验证码登录注册的代码

    public Result login(String phone, String code)  {
        //判断手机号是否合法
        if (RegexUtils.isPhoneInvalid(phone)) {
            return Result.error("手机格式不合法");
        }
        //从Redis中获取code对比
        String cacheCode = stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEY + phone);
        if (cacheCode == null || !cacheCode.equals(code)) {
            return Result.error("验证码错误");
        }
        //从数据库中搜索用户,判断用户是否注册过
        Student student = query().eq("phone", phone).one();

        if(student == null){
            //如果不存在就保存到数据中
            Student stu = new Student();
            stu.setUsername(RandomUtil.randomString(10));
            stu.setName("***");
            stu.setIsVaild((short) 1);
            stu.setPhone(phone);
            stu.setCreateTime(LocalDateTime.now());
            stu.setUpdateTime(LocalDateTime.now());
            save(stu);
            //生成Jwt令牌作token
            try {
                jwt = makeJwt(stu);
            }catch (Exception e)
            {
                System.out.println(e);
            }
        }
        try {
            jwt = makeJwt(student);
        }catch (Exception e)
        {
            System.out.println(e);
        }

        return Result.success(jwt);

    }

    private String makeJwt(Student student) throws JsonProcessingException {
        Map<String,Object> claims = new HashMap<>();
        claims.put("id",student.getId());
        claims.put("username",student.getUsername());
        claims.put("name",student.getName());
        String jwt = JwtUtils.generateJwt(claims);
        //json数据 不能传递时间信息所以要拷贝到一个 新的对象
        //将对象转为json
        String json = mapper.writeValueAsString(BeanUtil.copyProperties(student,People.class));


        //存储到Redis中
        stringRedisTemplate.opsForValue().set(LOGIN_TOKEN_KEY+jwt,json,LOGIN_TOKEN_SSL,TimeUnit.MINUTES);
        return jwt;

    }

这里使用了JwtUtils工具类来生成JWT令牌,这里不建议使用JWT令牌了,因为redis的token会改变导致多次登录有多个key 的情况,这里建议使用一个简单的唯一的随机生成

public class JwtUtils {

    private static String signKey = "mmznb";
    private static Long expire = 43200000L;//12h

    /**
     * 生成JWT令牌
     * @param claims JWT第二部分负载 payload 中存储的内容
     * @return
     */
    public static String generateJwt(Map<String, Object> claims){
        String jwt = Jwts.builder()
                .addClaims(claims)
                .signWith(SignatureAlgorithm.HS256, signKey)
                .setExpiration(new Date(System.currentTimeMillis() + expire))
                .compact();
        return jwt;
    }

    /**
     * 解析JWT令牌
     * @param jwt JWT令牌
     * @return JWT第二部分负载 payload 中存储的内容
     */
    public static Claims parseJWT(String jwt){
        Claims claims = Jwts.parser()
                .setSigningKey(signKey)
                .parseClaimsJws(jwt)
                .getBody();
        return claims;
    }
}

可以看到成功的信息

八、编写拦截器的代码

这里采用的是两层拦截器

一层是刷新Redis的TTL

一层是对没有token的进行拦截

@Slf4j
@Component
public class LoginCheckInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        if (GetTokenId.getUserId() == null) {
            response.setStatus(401);
            return false;
        }
        return true;
    }
@Slf4j
@Component
@RequiredArgsConstructor
public class RefreshTokenInterceptor implements HandlerInterceptor {

    private StringRedisTemplate stringRedisTemplate;
    public RefreshTokenInterceptor (StringRedisTemplate stringRedisTemplate){
        this.stringRedisTemplate = stringRedisTemplate;
    }

    private static final ObjectMapper mapper = new ObjectMapper();

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String url = request.getRequestURL().toString();
        log.info("请求的url:{}", url);
        //获取Token中jwt
        String jwt = request.getHeader("token");
        //从redis中找到相应的键值对
        String Key = LOGIN_TOKEN_KEY+jwt;
        String jsonUser = stringRedisTemplate.opsForValue().get(Key);
        //如果为空就放行
        if (jsonUser == null) {
            return true;
        }
        People people = mapper.readValue(jsonUser,People.class);
        GetTokenId.setUserId(people.getId());
        //不为空就刷新有效期
        stringRedisTemplate.expire(Key,LOGIN_CODE_SSL, TimeUnit.MINUTES);
        return true;

    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

        GetTokenId.shutdown();
    }
}

在WebConfig中注册

@Configuration//配置类
public class WebConfig implements WebMvcConfigurer {

    @Resource
    private StringRedisTemplate stringRedisTemplate;


    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginCheckInterceptor())
                .addPathPatterns("/**")
                .excludePathPatterns("/*/login","/*/activation"
                        ,"/*/create","/*/sendEmail","/*/updatePassword"
                        ,"/*/password","/*/findNewId","/*/getCarousel"
                        ,"/*/newDetail","/*/phone","/*/loginPhone").order(1);
        registry.addInterceptor( new RefreshTokenInterceptor(stringRedisTemplate)).addPathPatterns("/**").order(0);
    }
}

九、编写发送短信的功能

这里采用的是阿里云的短信功能

搜索短信服务,在发送测试中绑定测试手机号

注册一个RAM访问控制

在国内消息中的资质管理审核一下

然后是注入依赖

        <dependency>
            <groupId>com.aliyun</groupId>
            <artifactId>dysmsapi20170525</artifactId>
            <version>3.0.0</version>
        </dependency>

这里我不想再系统环境中配置所以就在yml中配置了

@Data
@Component
@ConfigurationProperties(prefix = "aliyun.phone")
public class AliPhoneProperties {

    private String endpoint;
    private String signName;
    private String accessKeyId;
    private String accessKeySecret;
    private String templateCode;
}

需要在yml文件中配置上面几个东东

@Slf4j
@Component
public class PhoneUtils {

    @Autowired
    private AliPhoneProperties aliPhoneProperties;

    public com.aliyun.dysmsapi20170525.Client createClient(String AccessKeyId,String AccessKeySecret,String endpoint) throws Exception {

        com.aliyun.teaopenapi.models.Config config = new com.aliyun.teaopenapi.models.Config()
                .setAccessKeyId(AccessKeyId)
                .setAccessKeySecret(AccessKeySecret);
        config.endpoint = endpoint;
        return new com.aliyun.dysmsapi20170525.Client(config);
    }

    public void sendPhone(String phone,String code) throws Exception {
        com.aliyun.dysmsapi20170525.Client client = new PhoneUtils().createClient(aliPhoneProperties.getAccessKeyId(), aliPhoneProperties.getAccessKeySecret(), aliPhoneProperties.getEndpoint());
        com.aliyun.dysmsapi20170525.models.SendSmsRequest sendSmsRequest = new com.aliyun.dysmsapi20170525.models.SendSmsRequest()
                .setSignName(aliPhoneProperties.getSignName())
                .setTemplateCode(aliPhoneProperties.getTemplateCode())
                .setPhoneNumbers(phone)
                .setTemplateParam(String.format("{\"code\":\"%s\"}", code));
        com.aliyun.teautil.models.RuntimeOptions runtime = new com.aliyun.teautil.models.RuntimeOptions();
        try {
            // 复制代码运行请自行打印 API 的返回值
            client.sendSmsWithOptions(sendSmsRequest, runtime);
        } catch (TeaException error) {
            // 此处仅做打印展示,请谨慎对待异常处理,在工程项目中切勿直接忽略异常。
            // 错误 message
            System.out.println(error.getMessage());
            // 诊断地址
            System.out.println(error.getData().get("Recommend"));
            com.aliyun.teautil.Common.assertAsString(error.message);
        } catch (Exception _error) {
            TeaException error = new TeaException(_error.getMessage(), _error);
            // 此处仅做打印展示,请谨慎对待异常处理,在工程项目中切勿直接忽略异常。
            // 错误 message
            System.out.println(error.getMessage());
            // 诊断地址
            System.out.println(error.getData().get("Recommend"));
            com.aliyun.teautil.Common.assertAsString(error.message);
        }
    }

}

这里是从阿里云帮助文档中修改后的工具类

最后修改log.info发送验证码 为 

phoneUtils.sendPhone(phone,code);

别忘了还要注入phoneUtils

private final PhoneUtils phoneUtils;

  • 9
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值