前面我们做了很多开发的铺垫工作,终于到了来开发一个业务功能的时候了,我们就来写一个最简单的用户注册模块吧。
分配任务
先把要开发的任务分配下来,现在我们要开发一个后台的用户注册服务,用户以表单的形式提交post请求,提交的参数包括了用户名、密码。在后台,校验用户名和密码都不能为空,且校验用户名存在则不允许注册,写入用户表时密码要加密存储。
引入依赖
这里我们不再造轮子或者用一些基础加密工具来实现安全的加密逻辑,直接拿来主义,用spring安全框架给我们提供的工具。后续这个安全框架我们也要学习的,现在先用它著名的加密模块帮我们实现密码加密和验证需求。
dependencies {
...
implementation 'org.springframework.security:spring-security-core'
}
自定义异常
因为后台的校验我们是以抛出异常的形式来返回给上层调用的,为此我们自定义一个异常类:
package com.xiaojuan.boot.common.exception;
import ...
@Getter
public class BusinessException extends RuntimeException {
private String errCode;
@Nullable
private Object data;
public BusinessException(String msg) {
super(msg);
}
public BusinessException(String msg, String errCode) {
this(msg);
this.errCode = errCode;
}
public BusinessException(String msg, String errCode, Object data) {
this(msg, errCode);
this.data = data;
}
}
在这个异常类中,我们会包装错误信息、错误码以及可能返回的错误数据。注意它是从RuntimeException
继承的,在编译阶段不敏感,不受检查,上层调用无需捕获处理,只关心调用就行。
开发用户注册模块
UserRegisterDTO
这里我们将定义一个DTO来接受前端提交的注册表单数据,DTO可以在web层和Service层之间传输,在service层与DAO层之间与model进行相互转换,引入DTO就是为了让Service和DAO之间进行解耦。
package com.xiaojuan.boot.dto;
import ...
@Data
public class UserRegisterDTO {
private String username;
private String password;
}
UserService接口
package com.xiaojuan.boot.service;
import ...
public interface UserService {
void register(UserRegisterDTO userRegisterDTO) throws BusinessException;
}
接口实现逻辑:
package com.xiaojuan.boot.service.impl;
import ...
import static com.xiaojuan.boot.dao.generated.mapper.UserDynamicSqlSupport.*;
import static org.mybatis.dynamic.sql.SqlBuilder.*;
@Service
public class UserServiceImpl implements UserService {
private final UserMapper userMapper;
private final PasswordEncoder passwordEncoder;
public UserServiceImpl(UserMapper userMapper) {
this.userMapper = userMapper;
this.passwordEncoder = new BCryptPasswordEncoder();
}
@Override
public void register(UserRegisterDTO userRegisterDTO) throws BusinessException {
String username = userRegisterDTO.getUsername();
String password = userRegisterDTO.getPassword();
Assert.hasText(username, "用户名不能为空");
Assert.hasText(password, "密码不能为空");
// 先查询用户名是否存在
long userCount = userMapper.count(c -> c.where(UserDynamicSqlSupport.username, isEqualTo(username)));
if (userCount > 0) {
throw new BusinessException("用户名已存在");
}
// 密码加密
String encodedPwd = passwordEncoder.encode(password);
// 新用户入库
User user = new User();
user.setUsername(username);
user.setPassword(encodedPwd);
Date currDate = new Date();
user.setCreateTime(currDate);
user.setUpdateTime(currDate);
userMapper.insertSelective(user);
}
}
代码说明
在service实现类中我们注入了
UserMapper
接口,注意,这里更推荐用构造器注入的方式;同时,我们将密码加密器对象提升为成员变量,在无参构造中进行了初始化。因为我们的用户注册和将来的用户登录方法在处理密码时都要用到它。
register(userRegisterDTO)
方法中,只要逻辑校验失败就抛出异常。这里在判断字段不能为空时,我们建议使用spring提供的Assert
类。在执行user查询时我们并没有开发新的UserMapper
接口方法,这得益于mybatis dynamic sql模块给我们提供的灵活且通用的查询套路实现。
现在我们的开发任务结束了,该到验证逻辑是否ok的时候了,咱下一节再见!