接口定义

地址:POST /user/login 入参:

{
    "phone": "18011119108", // 手机号
    "code": "218603", // 登录验证码,验证码登录时,需要填写
    "password": "xx", // 密码登录时,需要填写
    "type": 1 // 登录类型,1表示手机号验证码登录;2表示账号密码登录
}

出参:

{
	"success": true,
	"message": null,
	"errorCode": null,
	"data": "xxxxx" // 登录成功后,返回 Token 令牌
}

登录请求VO:

public class UserLoginPhoneVOReq {  
    @NotBlank(message = "手机号不能为空")  
    @PhoneNumber  
    private String phone;  
  
  
    private String verificationCode;  
    private String password;  
  
    @NotNull(message = "登录类型不能为空")  
    private Integer type;  
}

登录类型也创建一个枚举记录:

@Getter  
@AllArgsConstructor  
public enum LoginTypeEnum {  
    // 验证码 1
    VERIFICATION_CODE(1),  
    // 密码 2
    PASSWORD(2);  
  
    private final Integer value;  
  
    public static LoginTypeEnum getEnumByValue(Integer code) {  
        for (LoginTypeEnum loginTypeEnum : LoginTypeEnum.values()) {  
            if (Objects.equals(code, loginTypeEnum.getValue())) {  
                return loginTypeEnum;  
            }  
        }  
        return null;  
    }  
}

实现

验证码部分之前已经实现过了,所以用户登录时 redis 中已经有了一个 phonNumber:123456 的单值对,直接拿过来比较即可。

  • UserService 中声明:
Response<String> loginAndRegister(UserLoginPhoneVOReq userLoginPhoneVOReq);
  • UserServiceImpl 中写逻辑:
@ApiOperationLog(description = "登录与注册")
public Response<String> loginAndRegister(UserLoginPhoneVOReq userLoginPhoneVOReq) {
	String phone = userLoginPhoneVOReq.getPhone();
	LoginTypeEnum loginTypeEnum = LoginTypeEnum.getEnumByValue(userLoginPhoneVOReq.getType());
 
	switch (loginTypeEnum) {
		case VERIFICATION_CODE:
			String code = userLoginPhoneVOReq.getVerificationCode();
 
			if(code == null)
				return Response.fail(ResponseCodeEnum.PARAM_NOT_VALID.getErrorCode(), "验证码不能为空,VerificationCode");
 
			// 查之前生成的 redis 验证码
			RBucket<String> correctCode = redissonClient.getBucket(RedisKeyConstants.buildVerificationCodeKey(phone));
 
			if (correctCode == null || correctCode.get() == null) {
				return Response.fail(ResponseCodeEnum.PARAM_NOT_VALID.getErrorCode(), "验证码已过期");
			}
 
			log.info("correctCode: {}, code: {}", correctCode.get(), code);
			if (!code.equals(correctCode.get())) {
				return Response.fail(ResponseCodeEnum.PARAM_NOT_VALID.getErrorCode(), "验证码错误");
			}
 
			// 通过手机号查该用户的类
			LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
			queryWrapper.eq(User::getPhone, phone);
			User user = this.baseMapper.selectOne(queryWrapper);
 
			if (user == null) {
				// 注册 TODO
				userId = this.userRegisterByPhone(phone);  
			}else {  
				userId = user.getId();  
			}
			break;
		case PASSWORD:
			// TODO
			break;
		default:
			break;
	}
 
	// Sa-Token 登录,并返回 Token 令牌存储到 Redis
	StpUtil.login(userId);
  
	return Response.success(StpUtil.getTokenValue());
}

其中注册部分实现:

@Resource  
private ITUserRoleRelService userRoleRelService;  
  
/**  
 * 系统自动注册用户  
 * @param phone  
 * @return  
 */  
@Transactional(rollbackFor = Exception.class)  
public Long userRegisterByPhone(String phone) {  
    // 获得全局自增的用户编号  
    Long ojId = redissonClient.getAtomicLong(RedisKeyConstants.ONLINEJUDGE_ID_GENERATOR).incrementAndGet();  
  
    // 新建用户实例  
    User user = User.builder()  
            .userAccount(phone)  
            .userPassword(DigestUtils.md5DigestAsHex((SALT + phone).getBytes()))  
            .userName("用户" + ojId)  
            .phone(phone)  
            .userState(UserStateEnum.LOGOUT.getValue())  
            .userRole("common_user")  
            .ojId(ojId)  
            .build();  
  
    // 插入到数据库  
    this.save(user);  
  
    // 获取刚刚插入的用户的数据库id  
    Long userId = this.baseMapper.selectOne(new LambdaQueryWrapper<User>().eq(User::getOjId, ojId)).getId();  
  
    // 分配角色  
    TUserRoleRel userRoleRel = TUserRoleRel.builder()  
            .userId(userId)  
            .roleId(RoleConstants.COMMON_USER_ROLE_ID)  
            .createTime(LocalDateTime.now())  
            .updateTime(LocalDateTime.now())  
            .isDeleted(DeletedEnum.NOT_DELETED.getValue())  
            .build();  
    userRoleRelService.save(userRoleRel);  
  
  
    // 角色信息存入 redis
    List<Long> roles = Lists.newArrayList(RoleConstants.COMMON_USER_ROLE_ID);  
    String redisRolesKey = RedisKeyConstants.buildUserRoleKey(ojId);  
    redissonClient.getBucket(redisRolesKey).set(JsonUtil.toJsonString(roles));  
  
    return userId;  
}

里面的 ONLINEJUDGE_ID_GENERATOR 是 202502231839 Redis 获得全局自增的编号

数据库操作参考 202502231746 Mybatis-plus

其中事务处理不能简单使用 @Transactional(rollbackFor = Exception.class) 会出现不完全回滚的问题,解决方法:202502232036 SpringBoot 声明式注解事务失效