从零搭建可运行的 Spring Boot 用户模块,覆盖分层设计、参数校验、MyBatis-Plus CRUD 与接口自测。
从 0 到 1 写一个 Spring Boot 用户模块(实战版)
/ Update
4 mins
707 words
Loading views
从 0 到 1 写一个 Spring Boot 用户模块(实战版)h1
这篇是怎么来的h2
这篇其实是我给自己补“工程手感”时写的。
前面我把注解、IOC、AOP 都看过一遍,但一到自己开项目就会卡:目录怎么分?校验放哪?查询接口怎么做分页才不乱?
所以我给自己定了个约束:不用追求大而全,只把 用户注册 + 用户列表 做到能联调、能自测、能讲清楚。
本文目标h2
完成一个可以联调的用户模块,覆盖:
- 分层结构:
Controller -> Service -> Mapper - DTO 入参校验:
@NotBlank、@Email - MyBatis-Plus 基础查询与写入
- 注册接口的重复邮箱拦截
- 查询接口的分页返回
技术栈h2
- Java 17
- Spring Boot 3
- MySQL 8
- MyBatis-Plus
- Lombok
先建表h2
CREATE TABLE `user` ( `id` BIGINT NOT NULL AUTO_INCREMENT, `username` VARCHAR(32) NOT NULL, `email` VARCHAR(64) NOT NULL, `password` VARCHAR(128) NOT NULL, `create_time` DATETIME NOT NULL, `update_time` DATETIME NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `uk_email` (`email`));项目结构h2
src/main/java/com/example/demo├── controller├── service│ └── impl├── mapper├── entity├── dto└── common第一步:先把主链路跑通h2
先定义四类核心代码:
User(实体)UserMapper extends BaseMapper<User>UserService/UserServiceImplUserController
Controller 先保留两个接口:
@RestController@RequestMapping("/user")public class UserController {
@Resource private UserService userService;
@PostMapping("/register") public Result<Long> register(@RequestBody @Valid UserRegisterDTO dto) { return Result.ok(userService.register(dto)); }
@GetMapping("/page") public Result<Page<UserVO>> page(@RequestParam Integer pageNum, @RequestParam Integer pageSize, @RequestParam(required = false) String keyword) { return Result.ok(userService.pageQuery(pageNum, pageSize, keyword)); }}第二步:DTO 校验入参h2
@Datapublic class UserRegisterDTO {
@NotBlank(message = "用户名不能为空") private String username;
@NotBlank(message = "邮箱不能为空") @Email(message = "邮箱格式不正确") private String email;
@NotBlank(message = "密码不能为空") @Size(min = 6, max = 20, message = "密码长度应为 6~20") private String password;}我当时改了两次目录,最后保留了这三条约定:
- 入参用 DTO
- 数据库映射用 Entity
- 返回给前端用 VO
第三步:实现注册逻辑h2
@Overridepublic Long register(UserRegisterDTO dto) { User exist = userMapper.selectOne( Wrappers.<User>lambdaQuery().eq(User::getEmail, dto.getEmail()) ); if (exist != null) { throw new BizException(40001, "邮箱已被注册"); }
User user = new User(); user.setUsername(dto.getUsername()); user.setEmail(dto.getEmail()); user.setPassword(passwordEncoder.encode(dto.getPassword())); user.setCreateTime(LocalDateTime.now()); user.setUpdateTime(LocalDateTime.now());
userMapper.insert(user); return user.getId();}这里至少要做两件事:
- 唯一性检查(避免重复注册)
- 密码加密(不要明文入库)
第四步:实现分页查询h2
@Overridepublic Page<UserVO> pageQuery(Integer pageNum, Integer pageSize, String keyword) { Page<User> page = new Page<>(pageNum, pageSize);
LambdaQueryWrapper<User> qw = Wrappers.lambdaQuery(User.class) .like(StringUtils.hasText(keyword), User::getUsername, keyword) .orderByDesc(User::getCreateTime);
Page<User> userPage = userMapper.selectPage(page, qw);
Page<UserVO> voPage = new Page<>(pageNum, pageSize, userPage.getTotal()); List<UserVO> records = userPage.getRecords().stream().map(this::toVO).toList(); voPage.setRecords(records); return voPage;}第五步:统一返回体(最小版本)h2
@Data@AllArgsConstructor@NoArgsConstructorpublic class Result<T> { private Integer code; private String message; private T data;
public static <T> Result<T> ok(T data) { return new Result<>(200, "success", data); }
public static <T> Result<T> fail(Integer code, String message) { return new Result<>(code, message, null); }}这篇只放“能跑通模块”的最小版本。
我交付前会过一遍h2
- 空用户名是否被拦截
- 非法邮箱是否被拦截
- 重复邮箱注册是否提示
- 密码是否加密后入库
- 分页查询是否按时间倒序
评论