当前位置: 首页 > news >正文

Spring Boot 用户注册接口(含事务 + 参数校验)

一、前言

当我们把查询接口跑通之后,下一步最自然的就是:

实现一个真正的用户注册接口

很多人一开始写注册接口,会直接在 Controller 里一把梭:

  • 接参数
  • 判空
  • 查数据库
  • 插数据
  • 返回结果

这样能跑,但很快就会乱。

这篇文章,我带你用一个更规范、更接近企业项目的方式,完成一个最小可运行版本的用户注册接口,并且加上两个关键能力:

  • 参数校验
  • 事务控制

二、最终目标

实现接口:

POST /user/register

请求体:

{ "username": "admin", "password": "123456" }

返回:

{ "code": 0, "message": "成功", "data": "注册成功" }

三、先说最终结构

这篇文章采用最小工程化结构:

controller service mapper entity dto common

四、数据库准备

表结构如下:

CREATE TABLE user ( id BIGINT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(64) NOT NULL, password VARCHAR(128) NOT NULL, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP );

五、准备 DTO(接收前端参数)

路径:

dto/UserRegisterDTO.java

代码:

package org.example.arkbackend.dto; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Size; import lombok.Data; @Data public class UserRegisterDTO { @NotBlank(message = "用户名不能为空") @Size(min = 3, max = 20, message = "用户名长度必须在3到20之间") private String username; @NotBlank(message = "密码不能为空") @Size(min = 6, max = 20, message = "密码长度必须在6到20之间") private String password; }

六、准备实体类

路径:

entity/User.java

代码:

package org.example.arkbackend.entity; import lombok.Data; import java.time.LocalDateTime; @Data public class User { private Long id; private String username; private String password; private LocalDateTime createTime; }

七、统一返回体

路径:

common/Result.java

代码:

package org.example.arkbackend.common; import lombok.Data; @Data public class Result<T> { private Integer code; private String message; private T data; public static <T> Result<T> success(T data) { Result<T> r = new Result<>(); r.setCode(0); r.setMessage("成功"); r.setData(data); return r; } public static <T> Result<T> fail(String message) { Result<T> r = new Result<>(); r.setCode(1); r.setMessage(message); r.setData(null); return r; } }

八、编写 Mapper 接口

路径:

mapper/UserMapper.java

代码:

package org.example.arkbackend.mapper; import org.apache.ibatis.annotations.Mapper; import org.example.arkbackend.entity.User; @Mapper public interface UserMapper { User selectByUsername(String username); int insert(User user); }

九、编写 XML

路径:

resources/mapper/UserMapper.xml

代码:

<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="org.example.arkbackend.mapper.UserMapper"> <select id="selectByUsername" resultType="User"> select id, username, password, create_time from user where username = #{username} limit 1 </select> <insert id="insert" useGeneratedKeys="true" keyProperty="id"> insert into user(username, password) values(#{username}, #{password}) </insert> </mapper>

十、编写 Service

路径:

service/UserService.java

代码:

package org.example.arkbackend.service; import org.example.arkbackend.dto.UserRegisterDTO; public interface UserService { String register(UserRegisterDTO dto); }

路径:

service/impl/UserServiceImpl.java

代码:

package org.example.arkbackend.service.impl; import lombok.RequiredArgsConstructor; import org.example.arkbackend.dto.UserRegisterDTO; import org.example.arkbackend.entity.User; import org.example.arkbackend.mapper.UserMapper; import org.example.arkbackend.service.UserService; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service @RequiredArgsConstructor public class UserServiceImpl implements UserService { private final UserMapper userMapper; @Override @Transactional public String register(UserRegisterDTO dto) { User existUser = userMapper.selectByUsername(dto.getUsername()); if (existUser != null) { throw new RuntimeException("用户名已存在"); } User user = new User(); user.setUsername(dto.getUsername()); user.setPassword(dto.getPassword()); int rows = userMapper.insert(user); if (rows != 1) { throw new RuntimeException("注册失败"); } return "注册成功"; } }

十一、编写 Controller

路径:

controller/UserController.java

代码:

package org.example.arkbackend.controller; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.example.arkbackend.common.Result; import org.example.arkbackend.dto.UserRegisterDTO; import org.example.arkbackend.service.UserService; import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/user") @RequiredArgsConstructor public class UserController { private final UserService userService; @PostMapping("/register") public Result<String> register(@RequestBody @Valid UserRegisterDTO dto) { return Result.success(userService.register(dto)); } }

十二、为什么要加 @Valid?

这一句非常关键:

@RequestBody @Valid UserRegisterDTO dto

意思是:

在进入 Controller 方法之前,先对 DTO 做参数校验

比如:

  • 用户名为空
  • 密码太短

这些问题,根本不该进入业务层。


十三、为什么要加 @Transactional?

这一句也非常关键:

@Transactional

它的意义是:

register()方法里的数据库操作,处于同一个事务中

也就是说:

  • 如果都成功 → 提交
  • 如果中途报错 → 回滚

虽然你现在注册逻辑只有一条 insert,看起来事务好像“没必要”。

但你要形成工程思维:

只要一个业务动作涉及数据库修改,就应该开始建立事务意识

以后注册逻辑很可能变成:

  • 插入用户表
  • 插入用户角色表
  • 发送注册消息

这时候事务就非常重要了。


十四、全局异常处理(推荐补上)

如果参数校验失败,默认报错会比较丑。
我们可以统一拦一下。

路径:

common/GlobalExceptionHandler.java

代码:

package org.example.arkbackend.common; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; @RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(RuntimeException.class) public Result<String> handleRuntimeException(RuntimeException e) { return Result.fail(e.getMessage()); } @ExceptionHandler(MethodArgumentNotValidException.class) public Result<String> handleValidException(MethodArgumentNotValidException e) { String message = e.getBindingResult() .getFieldError() .getDefaultMessage(); return Result.fail(message); } }

十五、测试一下

1. 正常请求

{ "username": "admin123", "password": "123456" }

返回:

{ "code": 0, "message": "成功", "data": "注册成功" }

2. 用户名为空

{ "username": "", "password": "123456" }

返回:

{ "code": 1, "message": "用户名不能为空", "data": null }

3. 用户名重复

返回:

{ "code": 1, "message": "用户名已存在", "data": null }

十六、这一版代码的优点

这套注册接口虽然简单,但已经具备了企业项目的基本味道:

  • DTO 和 Entity 分离
  • Controller 只接请求,不写业务
  • Service 负责核心逻辑
  • MyBatis 负责数据库访问
  • @Transactional保障事务
  • @Valid保障参数校验
  • 统一返回体
  • 全局异常处理

十七、一句话总结

❗ 一个合格的注册接口,不只是“能插入数据库”,
而是要同时考虑:

  • 参数是否合法

  • 事务是否安全

  • 返回是否统一

  • 异常是否可控


十八、下一篇预告

下一篇可以继续写:

《Spring Boot 用户登录接口(JWT + 拦截器 + 登录状态校验)》

这就能把“注册 + 登录”两个最基础的用户系统闭环跑起来了。

http://www.jsqmd.com/news/663522/

相关文章:

  • RDKit终极指南:从零开始掌握化学信息学与药物设计
  • STM32实战:DAC电压输出与ADC自校准闭环系统
  • 嘎嘎降AI和PaperRR哪个适合留学论文:Turnitin达标效果对比
  • 为什么92%的AGI系统在监管沙盒中因“解释失败”被一票否决?——基于17个真实审计案例的穿透式复盘
  • 黎阳之光核工厂202应急管控平台|全域实景孪生,筑牢核安全最后一道防线
  • 别再手动算了!用PyTorch Hook一键统计你的CNN模型参数量与FLOPs(附完整代码)
  • 别再只输密码了!手把手带你用Wireshark抓包,亲手‘看见’WPA2的四次握手过程(含过滤技巧)
  • 如何用RL4CO构建智能决策引擎:5分钟掌握强化学习组合优化
  • OP-TEE安全存储深度解析(一):密钥层级与文件加密流程
  • 别再折腾环境了!Win10+GTX1060保姆级YOLOv4训练环境配置(CUDA10.1/CUDNN8.0.3/OpenCV4.4.0)
  • 从零手搓SM3国密算法:用C++一步步实现哈希函数(附完整可运行代码)
  • 解锁MATLAB算力:GPU并行计算实战指南
  • 如何用 filter 过滤数组中不符合业务条件的冗余数据
  • 从CH344Q出发:打造高性能USB转4路TTL串口模块的设计实践
  • 软件测试核心概念实战解析:从理论到习题的深度贯通
  • 别再让VAE学废了!手把手教你诊断和修复‘后验坍塌’(附PyTorch代码)
  • 从滤波到优化:手把手拆解VIO算法核心,看懂OpenVINS的MSCKF和ORB-SLAM3的BA到底差在哪
  • AI代码配额=新型IT预算?2026奇点大会披露:头部企业已将配额消耗纳入DevOps成本中心KPI(含真实财务映射表)
  • 最新 AI 论文盘点(2026-04-12):5 篇新作看长时记忆、推理微调、可审计医疗抽取、端侧个性化与分层 RAG
  • 从IoU到EIoU:目标检测边界框回归损失函数的演进与实战解析
  • 用周立功CAN分析仪抓包解析电动汽车充电握手(附真实报文数据)
  • 从原理到代码:手把手教你用C语言和OpenSSL实现RSA分段加密与验签(附完整项目)
  • ABR 会将自身所在区域内的路由(包括直连网段)通过 Type 3 LSA 通告到其他区域,但不会通告回本区域
  • Multi-Agent产品策略:从功能堆砌到智能工作流的重构
  • MT7916芯片深度解析:从拆机中兴E1630看MTK首款AX3000方案
  • Zotero-OCR插件:3步实现PDF文献智能识别与可搜索文本层添加
  • 【雷达成像】基于二维ADMM的稀度驱动ISAR成像附Matlab复现含文献
  • X.509数字证书实战解析:从结构到应用
  • 别再只读SOC了!MAX17048电量计的高级玩法:休眠管理、报警阈值设置与电量跳变修复
  • MATLAB条形图进阶:从基础bar函数到数据可视化实战