个人项目:交际支付项目(小老板)

作者:三哥(j3code.cn)

文档体系:admire.j3code.cn/note

上篇咱们花了 44 块钱完成了一个发送短信的工具类,那么这篇就要让其派上用场了,来编写一个经过短信验证码办法完成的用户注册与登录。

开端之前呢,你们需求先搭建一个根本的 SpringBoot 项目,便利后续的代码编写(不过多赘述)。

1、表字段剖析

首先咱们来剖析一下用户表的相关字段:

用户id

电话:phone

暗码:password

而后面或许需求一些扫码登录,所以预留扫码登录的一些字段

openid

session_key

unionid

另外,再适当个性化的加一些字段,标识用户

头像:avatar_url

昵称:nick_name

性别:sex

简介:intro

创立时刻:create_time

修正时刻:update_time

ok,那么用户表的创立 SQL 如下:

CREATE TABLE `sb_user` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `phone` varchar(15) COLLATE utf8mb4_german2_ci DEFAULT NULL COMMENT '电话',
  `password` varchar(200) COLLATE utf8mb4_german2_ci DEFAULT NULL COMMENT '暗码',
  `avatar_url` varchar(500) COLLATE utf8mb4_german2_ci DEFAULT 'https://thirdwx.qlogo.cn/mmopen/vi_32/POgEwh4mIHO4nibH0KlMECNjjGxQUq24ZEaGT4poC6icRiccVGKSyXwibcPq4BWmiaIGuG1icwxaQX6grC9VemZoJ8rg/132' COMMENT '头像',
  `nick_name` varchar(50) COLLATE utf8mb4_german2_ci DEFAULT '默认用户' COMMENT '昵称',
  `openid` varchar(200) COLLATE utf8mb4_german2_ci DEFAULT NULL,
  `session_key` varchar(200) COLLATE utf8mb4_german2_ci DEFAULT NULL,
  `unionid` varchar(200) COLLATE utf8mb4_german2_ci DEFAULT NULL,
  `sex` tinyint(1) DEFAULT '1' COMMENT '性别,0女1男',
  `intro` varchar(256) COLLATE utf8mb4_german2_ci DEFAULT NULL COMMENT '简介',
  `create_time` datetime DEFAULT NULL,
  `update_time` datetime DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `un_1` (`phone`),
  UNIQUE KEY `un_2` (`openid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_german2_ci

用户表规划好了之后,那么开端咱们的短信记载表的规划吧!

为啥要有这张表,我觉得最主要的一个原因仍是便利运营人员计算短信的发放数目,便利后续的运营策略。当然这对于咱们开发人员来说也是一样,记载就相当于一个日志,假如出现啥问题,开发人员也好回查,所以做一个短信记载的表是很有意义的。

这张表不必记太多字段,有如下这些就行:

id

电话:phone

发送状态:status

补白:remark

创立时刻:create_time

对映的表 SQL 如下:

CREATE TABLE `sb_sms_log` (
    `id` bigint(20) NOT NULL AUTO_INCREMENT,
    `phone` varchar(11) COLLATE utf8_unicode_ci NOT NULL COMMENT '号码',
    `status` int(11) NOT NULL COMMENT '发送状态',
    `remark` varchar(200) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '补白',
    `create_time` datetime DEFAULT NULL COMMENT '发送时刻',
    PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci

在数据库中创立表之后,咱们经过 IDEA 的插件 MyBatisX 插件来生成对应的 entity、service、mapper 等类或 xml 文件。

1)安装 MyBatisX 插件(我想大家都会)

2)项目衔接 MySQL 数据库

  • 个人就别搞短信登录注册功能了,没用

    开端衔接你的数据库

    个人就别搞短信登录注册功能了,没用

3)使用插件,生成代码

  • 只能一个表一个表的生成代码,过程如图:

    个人就别搞短信登录注册功能了,没用

2、短信验证码注册剖析

现在,发挥你们大脑的时刻到了,假如让你来考虑一下用户注册的流程,你会如何做呢!

由于咱们这是短信验证码办法注册,所以肯定要考虑如下的问题:

  1. 短信不能马马虎虎的发送
  2. 发送短信前,需求经过严格的校验
  3. 做好短信发送记载

开端剖析每一步的流程,这儿仅仅大致的流程,详细的细节要到代码中去考量。

1、注册页面回显一个图形验证码,该验证码能够经过按钮触发替换

2、用户获取短信验证码,有必要带着电话 + 图形验证码,才可获取短信验证码

3、注册时需提交如下信息:

  • 电话
  • 短信验证码
  • 暗码

4、一系列的校验流程,成功之后才干进入后续过程

5、生成用户信息,注册成功

大致的流程出来了,那么为了加深一下形象,咱们再来画个时序图:

个人就别搞短信登录注册功能了,没用

现在清晰多了,那么开端编码。

3、编码完成注册功用

3.1 获取图形验证码

别嫌我啰嗦,获取图形验证码的流程咱们仍是要再来剖析剖析。

先搞清楚一点,为啥需求这个。

意图:避免用户刷发送短信接口,只要发送短信接口中带着的图形验证码正确才干触发后续的短信发送功用,不然一律不发送验证码。

现在知道这个图形验证码作用了吧!

那下面我画一个代码完成的根本流程图,保证代码的每一步都清清楚楚:

个人就别搞短信登录注册功能了,没用

编码完成:

条件:先创立好如下几个类:

  1. SmsAuthController:短信认证相关控制器
  2. AuthService 和 AuthServiceImpl:认证事务接口和完成类

1)controller 接口办法编写

@Slf4j
@ResponseResult
@AllArgsConstructor
@RestController
@RequestMapping(UrlPrefixConstants.V1 + "/sms/auth")
public class SmsAuthController {
    private final AuthService service;
    /**
     * 用户登录或许注册获取图形验证码
     *
     * @return 图片 Base64 字符串
     */
    @GetMapping("/getCaptcha")
    public String getCaptcha() {
        return service.getCaptcha();
    }
}

2)service 办法完成

public interface AuthService {
    String getCaptcha();
}
@Slf4j
@Service
@AllArgsConstructor
public class AuthServiceImpl implements AuthService {
    private final RedisTemplate<String, Object> redisTemplate;
    @Override
    public String getCaptcha() {
        // 生成 图形验证码
        RandomGenerator randomGenerator = new RandomGenerator("abcdefghjkmnopqrstuvwxyz023456789", 4);
        LineCaptcha lineCaptcha = CaptchaUtil.createLineCaptcha(100, 42, 4, 60);
        lineCaptcha.setGenerator(randomGenerator);
        lineCaptcha.createCode();
        // 获取 base64 字符串 和 code 值
        String base64 = "data:image/png;base64," + lineCaptcha.getImageBase64();
        String code = lineCaptcha.getCode();
        log.info("图形验证码内容:{}", code);
        log.debug("图形验证码base64:{}", base64);
        // 依据 code ,生成 redis 的 key,登录或注册时需求验证
        String key = SbUtil.captchaRedisKey(code);
        // 存入 redis(key,value,超时时刻)
        redisTemplate.opsForValue().set(key, code, ServerNameConstants.CAPTCHA_KEY_EXPIRED_TIME);
        return base64;
    }
}

注:SbUtil 和 ServerNameConstants 类是体系封装的工具类和常量类,代码就不贴了,你们能够依据事务灵敏完成。

3.2 获取短信验证码

这节就来完成如何发送短信验证码了。

那仍是来剖析一下,这个流程需求考虑那些问题:

  1. 校验恳求是否合格,不合格的一律不发送短信验证码
  2. 需存储发送记载

考虑的问题尽管不多,可是校验这块却是本次的重点,只要校验做的好,升职加薪少不了。

写代码之前,仍是来画画流程图:

个人就别搞短信登录注册功能了,没用

注意图中的几个校验,其中有一个当地会存在并发安全问题,不知道你们会不会发现?

编码完成:

1)controller 编写

@Slf4j
@ResponseResult
@AllArgsConstructor
@RestController
@RequestMapping(UrlPrefixConstants.V1 + "/sms/auth")
public class SmsAuthController {
    private final AuthService service;
    /**
     * 发送短信验证码
     *
     * @param phone 电话号码
     * @param code  图形验证码 code
     */
    @GetMapping("/sendSms")
    public void sendSms(@RequestParam("phone") String phone, @RequestParam("code") String code) {
        service.sendSms(phone, code);
    }
}

2)service 办法完成

public interface AuthService {
    void sendSms(String phone, String code);
}
@Slf4j
@Service
@AllArgsConstructor
public class AuthServiceImpl implements AuthService {
    private final UserService userService;
    private final RedisTemplate<String, Object> redisTemplate;
    private final ApplicationContext applicationContext;
    private final SendSmsConfig sendSmsConfig;
    private final SmsLogService smsLogService;
    @Override
    public void sendSms(String phone, String code) {
        // 校验
        checkPhoneAndRegisterAndKey(phone, SbUtil.captchaRedisKey(code));
        // 开端发送短信验证码
        // 生成 6 位数字验证码
        String smsCode = RandomUtil.randomNumbers(6);
        // 记载
        SmsLog smsLog = new SmsLog()
            .setPhone(phone)
            .setRemark(String.format("用户注册短信验证码发送:%s", smsCode));
        // 封装短信发送参数
        final var smsRequest = new SendSmsUtil.SendSmsRequest()
            .setSmsSdkAppId(sendSmsConfig.getSmsSdkAppId())
            .setSecretId(sendSmsConfig.getSecretId())
            .setSecretKey(sendSmsConfig.getSecretKey())
            .setSignName(sendSmsConfig.getSignName())
            .setTemplateId(sendSmsConfig.getTemplateId())
            .setPhone(phone)
            .setTemplateParamSet(new String[]{smsCode, "" + ServerNameConstants.SMS_KEY_EXPIRED_TIME.get(ChronoUnit.SECONDS) / 60});
        // 发送短信验证码并获取成果
        smsLog.setStatus(SendSmsUtil.sendSms(smsRequest) ? SuccessFailStatusEnum.SUCCESS : SuccessFailStatusEnum.FAIL);
        try {
            // 保存发送日志
            smsLogService.save(smsLog);
        } catch (Exception e) {
            log.error("保存短信发送记载失利:", e);
        }
        if (SuccessFailStatusEnum.FAIL.equals(smsLog.getStatus())) {
            throw new SendSmsAuthException("短信发送失利!");
        }
        // 存入 redis,校验用户的注册逻辑
        redisTemplate.opsForValue().set(SbUtil.smsRedisKey(phone + ":" + smsCode), smsCode, ServerNameConstants.SMS_KEY_EXPIRED_TIME);
    }
    /**
     * 加分布式锁,保证此功用原子
     *
     * @param key code 再 redis 中对应的 key
     */
    @DistributedLock(key = "check-code-{1}", lockFail = true, failMessage = "恳求频繁,稍后重试!")
    public void checkey(String key) {
        // 校验图形验证码是否正确
        if (Boolean.FALSE.equals(redisTemplate.hasKey(key))) {
            throw new SendSmsAuthException("验证码不正确或已过期!");
        }
        // 证码成功,删除 key,避免屡次提交,屡次发送短信
        redisTemplate.delete(key);
    }
    /**
     * 校验电话号码是否合格,是否现已注册,key 是否正确
     *
     * @param phone
     * @param key
     */
    public void checkPhoneAndRegisterAndKey(String phone, String key) {
        // 校验电话号码
        if (Boolean.FALSE.equals(SbUtil.checkPhone(phone))) {
            throw new SendSmsAuthException("请正确输入电话号码!");
        }
        // 是否注册
        User user = userService.lambdaQuery()
            .eq(User::getPhone, phone)
            .one();
        if (Objects.nonNull(user)) {
            throw new RegisterAuthException("电话号码已存在,请直接登录!");
        }
        // key 是否存在
        applicationContext.getBean(AuthServiceImpl.class).checkey(key);
    }
}

SendSmsConfig 装备代码:

@Slf4j
@Data
@Configuration
@ConfigurationProperties(prefix = "send.sms")
public class SendSmsConfig {
    /**
     * 短信签名内容,有必要填写已审阅经过的签名
     */
    private String signName;
    /**
     * 模板 ID: 有必要填写已审阅经过的模板 ID
     */
    private String templateId;
    /**
     * 应用id
     */
    private String smsSdkAppId;
    /**
     * api密钥中的secretId
     */
    private String secretId;
    /**
     * api密钥中的应用密钥
     */
    private String secretKey;
}

剖析一下上面的代码:

  1. 先做了三个校验:校验电话号码、校验号码是否现已注册、校验图形验证码是否正确

    这儿要重点说一下:校验图形验证码是否正确

    大家试想一下,假如咱们从 Redis 中校验了图形验证码合格,然后不去删除此验证码,是不是在验证码过期之内,用户有或许一直带着这个有效验证码进行刷接口,导致咱们的短信不停的发送啊!

    所以咱们这儿的做法肯定是两步:

    1. 校验验证码是否合格
    2. 合格,移除验证码

    可是新的问题又来了,假如用户一次性进行很多的恳求(并发)都打到第一步,而第一步同样是符合要求任然会进入到第二步,导致发送多条短信,所以有必要让 1、2 两步原子的履行。

    处理:

    1. 分布式锁

      引荐我的手写分布式锁视频:www.bilibili.com/video/BV1d4…

    2. Lua 脚本履行 redis

    那么,剖析到这儿大家就知道我为啥使用了一个分布式锁了。至于 Lua 脚本,后续我也会在优化版本中将代码写出来。

  2. 生成 6 位短信验证码,封装发送短信参数,短信发送

  3. 保存短信记载

  4. 短信验证码存入 Redis,用户注册时进行校验

3.3 短信验证码完成注册

经过了上面两个过程,终于是要真实的开端注册用户数据了,那流程走到这儿,咱们要明确下面这几件事:

  1. 给用户发过短信了,注册时需带着此短信验证码
  2. 让用户输入暗码,后续能够经过短信登录或许电话 + 暗码办法登录

ok,再来画一画此功用的消息流程图:

个人就别搞短信登录注册功能了,没用

好,现在开端编码。

1)controller 编写

@Slf4j
@ResponseResult
@AllArgsConstructor
@RestController
@RequestMapping(UrlPrefixConstants.V1 + "/sms/auth")
public class SmsAuthController {
    private final AuthService service;
    /**
     * 用户注册
     *
     * @param request 恳求数据
     */
    @PostMapping("/register")
    public void register(@Validated @RequestBody RegisterRequest request) {
        service.register(request);
    }
}

RegisterRequest 恳求体:

@Data
public class RegisterRequest {
    /**
     * 电话
     */
    @NotBlank(message = "电话号码不为空")
    private String phone;
    /**
     * 短信验证码
     */
    @NotBlank(message = "短信验证码不为空")
    private String smsCode;
    /**
     * 暗码
     */
    @NotBlank(message = "暗码不为空")
    private String password;
}

2)service 编写

public interface AuthService {
    void register(RegisterRequest request);
}
@Slf4j
@Service
@AllArgsConstructor
public class AuthServiceImpl implements AuthService {
    private final UserService userService;
    private final RedisTemplate<String, Object> redisTemplate;
    private final ApplicationContext applicationContext;
    private final SendSmsConfig sendSmsConfig;
    private final SmsLogService smsLogService;
    @Override
    public void register(RegisterRequest request) {
        // 校验
        checkPhoneAndRegisterAndKey(request.getPhone(), SbUtil.smsRedisKey(request.getPhone() + ":" + request.getSmsCode()));
        // 创立并保存用户目标
        boolean isSave = userService.save(new User()
                                          .setPhone(request.getPhone())
                                          .setPassword(request.getPassword()));
        if (Boolean.FALSE.equals(isSave)) {
            throw new RegisterAuthException("注册失利,请联络管理员!");
        }
    }
}

注:有些办法在 3.2 节中现已写过了,就不贴出来了

接着,咱们对 User 目标内部做了一些调整,供给了下面两个办法:

/**
 * 设置暗码,自动加密
 * @param password
 * @return
 */
public User setPassword(String password) {
    if (StringUtils.isBlank(password)) {
        throw new PasswordException("暗码不能为空!");
    }
    this.password = MD5.create().digestHex(password);
    return this;
}
/**
 * 判别当前目标暗码与传入暗码是否持平
 * @param password
 * @return
 */
public Boolean passwordEqual(String password) {
    if (StringUtils.isBlank(password)){
        return Boolean.FALSE;
    }
    /**
         * 这儿,进行了两次 MD5,由于存入的时分对原值 MD5 了一次,
         * 然后取出来调用 set 的时分又 MD5 了一次,所以要进行两次 MD5 进行比较
         */
    String hex = MD5.create().digestHex(password);
    String hex02 = MD5.create().digestHex(hex);
    return StringUtils.isBlank(this.password) ? Boolean.FALSE : this.password.equals(hex02);
}

这样做的意图是为了高内聚,由于暗码是用户的东西,所以关于暗码的操作都内聚到用户类中。

4、编码完成登录功用

依据注册功用的完成,咱们知道体系中现已有了用户的电话和暗码,所以咱们应该要供给两种登录的办法:短信登录、电话 + 暗码登录。

4.1 短信验证码登录

仔细剖析,咱们能发现,这个功用的完成流程和注册功用相似,也是有下面几步:

  1. 获取图形验证码
  2. 获取短信验证码
  3. 短信验证码完成登录

所以,这儿为了不重复编写代码,那么就对上面的 3.2 小节的功用完成做一些小小的改动。

3.1 不必动,由于功用一摸一样,只不过 3.2 会有发送记载,和短信发送模板需求改动,所以咱们只改 3.2 节

3.2 调整如下:

1、增加短信发送类型枚举,有了这个,后续就能够灵敏的增加短信发送类型了

@Getter
public enum SendSmsTypeEnum {
    LOGIN("LOGIN", "登录", "1847057"),
    REGISTER("REGISTER", "注册", "1843226"),
    ;
    private String value;
    private String description;
    /**
     * 短信模板id
     */
    private String templateId;
    SendSmsTypeEnum(String value, String description, String templateId) {
        this.value = value;
        this.description = description;
        this.templateId = templateId;
    }
}

2、修正 controller 的发送短信接口界说

/**
 * 发送短信验证码
 *
 * @param request 发送短信恳求参数
 */
@PostMapping("/sendSms")
public void sendSms(@Validated @RequestBody SendSmsRequest request) {
    service.sendSms(request);
}

3、界说 SendSmsRequest 恳求体

@Data
public class SendSmsRequest {
    /**
     * 电话
     */
    @NotBlank(message = "电话不为空")
    private String phone;
    /**
     * 验证码
     */
    @NotBlank(message = "验证码不为空")
    private String code;
    /**
     * 类型
     */
    @NotNull(message = "类型不为空")
    private SendSmsTypeEnum sendSmsType;
}

4、修正 service 中的发送短信办法

@Override
public void sendSms(SendSmsRequest request) {
    if (SendSmsTypeEnum.REGISTER.equals(request.getSendSmsType())) {
        // 校验注册
        checkPhoneAndRegisterAndKey(request.getPhone(), SbUtil.captchaRedisKey(request.getCode()));
    } else if (SendSmsTypeEnum.LOGIN.equals(request.getSendSmsType())) {
        // 校验登录
        checkPhoneAndLoginAndKey(request.getPhone(), SbUtil.captchaRedisKey(request.getCode()));
    }
    // 开端发送短信验证码
    // 生成 6 位数字验证码
    String smsCode = RandomUtil.randomNumbers(6);
    // 记载
    SmsLog smsLog = new SmsLog()
        .setPhone(request.getPhone())
        .setRemark(String.format("用户" + request.getSendSmsType().getDescription() + "短信验证码发送:%s", smsCode));
    // 封装短信发送参数
    final var smsRequest = new SendSmsUtil.SendSmsRequest()
        .setSmsSdkAppId(sendSmsConfig.getSmsSdkAppId())
        .setSecretId(sendSmsConfig.getSecretId())
        .setSecretKey(sendSmsConfig.getSecretKey())
        .setSignName(sendSmsConfig.getSignName())
        .setTemplateId(request.getSendSmsType().getTemplateId())
        .setPhone(request.getPhone())
        // 设置验证码、过期时刻,由于短信模板有2个占位符
        .setTemplateParamSet(new String[]{smsCode, "" + ServerNameConstants.SMS_KEY_EXPIRED_TIME.get(ChronoUnit.SECONDS) / 60});
    // 发送短信验证码并获取成果
    smsLog.setStatus(SendSmsUtil.sendSms(smsRequest) ? SuccessFailStatusEnum.SUCCESS : SuccessFailStatusEnum.FAIL);
    try {
        // 保存发送日志
        smsLogService.save(smsLog);
    } catch (Exception e) {
        log.error("保存短信发送记载失利:", e);
    }
    if (SuccessFailStatusEnum.FAIL.equals(smsLog.getStatus())) {
        throw new SendSmsAuthException("短信发送失利!");
    }
    // 存入 redis,校验用户的注册逻辑
    redisTemplate.opsForValue().set(SbUtil.smsRedisKey(request.getPhone() + ":" + smsCode), smsCode, ServerNameConstants.SMS_KEY_EXPIRED_TIME);
}
/**
     * 校验电话号码是否合格,是否现已注册,key 是否正确
     *
     * @param phone
     * @param key
     */
public void checkPhoneAndRegisterAndKey(String phone, String key) {
    // 校验电话号码
    if (Boolean.FALSE.equals(SbUtil.checkPhone(phone))) {
        throw new SendSmsAuthException("请正确输入电话号码!");
    }
    // 是否注册
    User user = userService.lambdaQuery()
        .eq(User::getPhone, phone)
        .one();
    if (Objects.nonNull(user)) {
        throw new RegisterAuthException("电话号码已存在,请直接登录!");
    }
    // key 是否存在
    applicationContext.getBean(AuthServiceImpl.class).checkey(key);
}
/**
     * 校验电话号码是否合格,是否未注册,key 是否正确
     *
     * @param phone
     * @param key
     */
public void checkPhoneAndLoginAndKey(String phone, String key) {
    // 校验电话号码
    if (Boolean.FALSE.equals(SbUtil.checkPhone(phone))) {
        throw new SendSmsAuthException("请正确输入电话号码!");
    }
    // 是否注册
    User user = userService.lambdaQuery()
        .eq(User::getPhone, phone)
        .one();
    if (Objects.isNull(user)) {
        throw new LoginAuthException("电话号码不存在,请注册!");
    }
    // key 是否存在
    applicationContext.getBean(AuthServiceImpl.class).checkey(key);
}

自此,咱们的发送短信功用改造就完成了,现在它适用于 SendSmsTypeEnum 枚举中界说的任何一种短信发送。现在,开端完成咱们的短信登录功用。

4.1.1 短信登录

对于短信登录,咱们也先确定好一些先前条件:

  1. 带着电话 + 短信验证码

嗯,就这一个,剩下的就是后端校验验证码是否正确,用户是否存在一类的了。接着仍是老样子,画一下短信登录的流程图:

个人就别搞短信登录注册功能了,没用

开端编写代码:

1)contoller 完成

/**
 * 短信登录
 *
 * @param phone   电话
 * @param smsCode 短信验证码
 * @return token
 */
@GetMapping("/loginBySms")
public String loginBySms(@RequestParam("phone") String phone, @RequestParam("smsCode") String smsCode) {
    return service.loginBySms(phone, smsCode);
}

2)编写 service

public interface AuthService {
    String loginBySms(String phone, String smsCode);
}
@Slf4j
@Service
@AllArgsConstructor
public class AuthServiceImpl implements AuthService {
    private final UserService userService;
    private final RedisTemplate<String, Object> redisTemplate;
    private final ApplicationContext applicationContext;
    @Override
    public String loginBySms(String phone, String smsCode) {
        // 校验电话号码
        if (Boolean.FALSE.equals(SbUtil.checkPhone(phone))) {
            throw new SendSmsAuthException("请正确输入电话号码!");
        }
        // 是否注册
        User user = userService.lambdaQuery()
            .eq(User::getPhone, phone)
            .one();
        if (Objects.isNull(user)) {
            throw new LoginAuthException("电话号码不存在,请注册!");
        }
        // key 是否存在
        applicationContext.getBean(AuthServiceImpl.class).checkey(SbUtil.smsRedisKey(phone + ":" + smsCode));
        // 生成 token,并存入 redis(带过期时刻)
        String token = SbUtil.getTokenStr();
        redisTemplate.opsForValue().set(SbUtil.loginRedisKey(token), JSON.toJSONString(user), ServerNameConstants.AUTH_KEY_EXPIRED_TIME);
        // 回来 token
        return token;
    }
}

至此呢,一个短信登录功用就做好了,当然我相信这个都处理了,那么下面的功用就复制粘贴微改一下就 ok 了,看我操作。

4.2 电话 + 暗码登录

这个功用的条件:

  1. 前端带着 电话 + 暗码进行登录

流程图我就不画了,和短信登录相似,我就直接开端撸代码。

1)controller 编写

/**
 * 暗码登录
 *
 * @param phone    电话
 * @param password 暗码
 * @return token
 */
@GetMapping("/loginByPassword")
public String loginByPassword(@RequestParam("phone") String phone, @RequestParam("password") String password) {
    return service.loginByPassword(phone, password);
}

2)service 编写

public interface AuthService {
    String loginByPassword(String phone, String password);
}
@Slf4j
@Service
@AllArgsConstructor
public class AuthServiceImpl implements AuthService {
    private final UserService userService;
    private final RedisTemplate<String, Object> redisTemplate;
    private final ApplicationContext applicationContext;
    @Override
    public String loginByPassword(String phone, String password) {
        // 校验电话号码
        if (Boolean.FALSE.equals(SbUtil.checkPhone(phone))) {
            throw new SendSmsAuthException("请正确输入电话号码!");
        }
        // 是否注册
        User user = userService.lambdaQuery()
            .eq(User::getPhone, phone)
            .one();
        if (Objects.isNull(user)) {
            throw new LoginAuthException("电话号码不存在,请注册!");
        }
        // 校验暗码
        if (Boolean.FALSE.equals(user.passwordEqual(password))){
            throw new LoginAuthException("暗码不正确,请重新输入!");
        }
        // 生成 token,并存入 redis(带过期时刻)
        String token = SbUtil.getTokenStr();
        redisTemplate.opsForValue().set(SbUtil.loginRedisKey(token), JSON.toJSONString(user), ServerNameConstants.AUTH_KEY_EXPIRED_TIME);
        // 回来 token
        return token;
    }
}

自此,咱们的登录相关功用就现已做完了。或许有人会说咋没有退出登录呢,这个别急,做这个功用之前呢,有一些优先功用要完成,之后才干开发退出登录功用。

这儿先卖个关子,咱们把功用留到下篇完成。

好了,今天内容就完毕了,关注我,精彩文章立刻到来。