本项目仅仅支持手机号注册
- 填入手机号,请求校验账号;
- 完善账号昵称和密码,发送验证码;
- 完成注册。
手机号校验
前端和后端都需要对手机号格式进行校验,同时后端也应该检查账户是否已经注册。
接口信息
定义Service
找到 UserInfoService,定义检查手机号方法:
UserInfoServicepublic interface UserInfoService extends IService<UserInfo> {
UserInfo findByPhone(String phone); }
|
在 UserServiceImpl 中实现该方法:
UserServiceImpl@Service public class UserServiceImpl extends ServiceImpl<UserInfoMapper, UserInfo> implements UserInfoService {
@Override public UserInfo findByPhone(String phone) { return getOne(Wrappers.<UserInfo>lambdaQuery().eq(UserInfo::getPhone, phone)); } }
|
定义Controller
在 UserInfoController 下定义接口
UserInfoController@RestController @RequestMapping("/users") public class UserInfoController {
private final UserInfoService userInfoService;
public UserInfoController(UserInfoService userInfoService) { this.userInfoService = userInfoService; }
@GetMapping("/phone/exists") public R<Boolean> checkExists(String phone){ return R.ok(userInfoService.findByPhone(phone) != null); } }
|
验证码服务
验证码服务需要提供如下服务:
首先生成验证码
然后将验证码保存到Redis中
调用三方接口向手机号发送验证码
校验验证码是否一致
验证码存储方案:
| 技术方案 |
效率 |
时效性 |
跨服务共享 |
| mysql |
一般 |
无 |
支持 |
| session |
高 |
有 |
不支持 |
| map |
高 |
无 |
不支持 |
| redis |
高 |
有 |
支持 |
验证码存入 Redis 的数据结构
- STRING: 直接就是 key/value 形式,操作简单,redis中外部key数量增加,会导致redis整体性能收到影响
- MAP:一个外部key,可以保存多个内部key/value键值对,可以避免外部key占用过多
- 单个map对象数据量过大 => 大key
- map 的时效性只针对外部key,无法针对内部key做过期时间
采用 STRING 结构存储验证码,key需要满足唯一性/可读性/扩展性
- USERS:REGIST:VERIFY_CODE:手机号
集成Redis
代码部分参考集成Redis部分
在模块trip-users-api的pom文件中添加Redis模块的依赖
<dependency> <groupId>com.swx</groupId> <artifactId>trip-common-redis</artifactId> </dependency>
|
接口信息
定义Service
新建 SmsService,定义发送短信方法:
SmsServicepublic interface SmsService {
void registerSmsSend(String phone); }
|
创建 SmsServiceImpl 并实现该方法:
SmsServiceImpl@Slf4j @Service public class SmsServiceImpl implements SmsService {
private final RedisService redisService;
public SmsServiceImpl(RedisService redisService) { this.redisService = redisService; }
@Override public void registerSmsSend(String phone) { String code = this.generateVerifyCode("LETTER", 4); UserRedisKeyPrefix keyPrefix = UserRedisKeyPrefix.USER_REGISTER_VERIFY_CODE_STRING; redisService.setCacheObject(keyPrefix.fullKey(phone), code, keyPrefix.getTimeout(), keyPrefix.getUnit()); }
private String generateVerifyCode(String type, int len) { String uuid = UUID.randomUUID().toString().replaceAll("-", ""); String code = uuid.substring(0, len); log.info("[短信服务] 生成验证码 ===== type={}, len={}, code={}", type, len, code); return code; } }
|
定义Controller
新建 SmsController
SmsController@RestController @RequestMapping("/sms") public class SmsController {
private final SmsService smsService;
public SmsController(SmsService smsService) { this.smsService = smsService; }
@PostMapping("register") public R<?> registerVerifyCode(String phone) { smsService.registerSmsSend(phone); return R.ok(); } }
|
账号注册
实现步骤:
- 基于手机号查询是否已经存在该手机号,如果存在则返回异常
- 从 redis 中获取验证码与前端传入的验证码进行校验是否一致,如果不一致则抛出异常
- 将验证码从 redis 中删除
- 创建用户对象,填入参数并补充其他默认值
- 对密码进行加密操作
- 保存用户对象到数据库
接口信息
请求参数
使用请求参数而非完整用户对象,可以避免接收多余参数,防止小人加入其他参数。
在 trip-users-api 模块中新建包 com.swx.user.vo,在该包下定义 RegisterRequest 类,用于接收请求参数
RegisterRequest
@Getter @Setter public class RegisterRequest { private String phone; private String nickname; private String password; private String verifyCode; }
|
定义Service
找到 UserInfoService,定义注册账号方法:
UserInfoService
void register(RegisterRequest req);
|
在 UserServiceImpl 中实现该方法:
UserServiceImpl@Service public class UserServiceImpl extends ServiceImpl<UserInfoMapper, UserInfo> implements UserInfoService {
private final RedisService redisService;
public UserServiceImpl(RedisService redisService) { this.redisService = redisService; }
@Override public void register(RegisterRequest req) { UserInfo byPhone = findByPhone(req.getPhone()); if (byPhone != null) { throw new BizException(R.CODE_REGISTER_ERROR, "手机号已存在,请不要重复注册"); } UserRedisKeyPrefix keyPrefix = UserRedisKeyPrefix.USER_REGISTER_VERIFY_CODE_STRING; String code = redisService.getCacheObject(keyPrefix.fullKey(req.getPhone())); if (!req.getVerifyCode().equalsIgnoreCase(code)) { throw new BizException(R.CODE_REGISTER_ERROR, "验证码错误"); } redisService.deleteObject(keyPrefix.fullKey(req.getPhone())); UserInfo userInfo = this.buildUserInfo(req); String encryptPassword = Md5Utils.getMD5(userInfo.getPassword() + userInfo.getPhone()); userInfo.setPassword(encryptPassword); super.save(userInfo); }
private UserInfo buildUserInfo(RegisterRequest req) { UserInfo userInfo = new UserInfo(); BeanUtils.copyProperties(req, userInfo); userInfo.setInfo("这个人很懒,什么都没写"); userInfo.setHeadImgUrl("/images/default.jpg"); return userInfo; } }
|
定义Controller
在 UserInfoController 下定义接口
UserInfoController@PostMapping("register") public R<?> register(RegisterRequest req) { userInfoService.register(req); return R.ok(); }
|