一、准备工作
1.数据库的建立与连接
这里我们用主流数据库mysql,推荐使用docker建立数据库环境。
可视化,操作数据库的软件用DBeaver。

下面是数据库编辑器的代码:
sql
CREATE DATABASE big_event;
USE big_event;
CREATE TABLE user (
id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT COMMENT 'ID',
username VARCHAR(20) NOT NULL UNIQUE COMMENT '用户名',
password VARCHAR(32) COMMENT '密码',
nickname VARCHAR(10) DEFAULT '' COMMENT '昵称',
email VARCHAR(128) DEFAULT '' COMMENT '邮箱',
user_pic VARCHAR(128) DEFAULT '' COMMENT '头像',
create_time DATETIME NOT NULL COMMENT '创建时间',
update_time DATETIME NOT NULL COMMENT '修改时间'
) COMMENT '用户表';
这里定义了用户的基本属性,之后在java当中我们也要创建一个User类与这个表一一对应。
最后,在IDE当中的resources的文件夹下建立application.yml文件,输入配置信息:
sql
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/big_event
username: root
password: 你的密码
至此,数据库就算是接上spring了。
2.spring依赖
除了数据库,web和mybatis的依赖,我还加入了valibation和lombok依赖,它们提供了减少代码复杂度的注解,用到的时候我会解释。
sql
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.1.3</version>
</parent>
<groupId>org.example</groupId>
<artifactId>big-event</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>big-event</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- lombok 依赖-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.30</version>
</dependency>
<!--web依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--mybatis-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>3.0.0</version>
</dependency>
<!--数据库依赖 -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
</dependency>
<!--参数校验依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
</dependencies>
</project>
二、项目结构展示

可以先根据截图将类和接口都创建好。
1. Controller:接请求
负责接收前端传来的用户名和密码,并调用业务层。
2. Service:管业务
负责决定这件事该怎么做,比如先查用户,再决定是否注册。
3. Mapper:查数据库
负责执行 SQL,把查询和插入操作真正落到数据库上。
4. Result:统一返回格式
不管成功还是失败,都返回统一结构,方便前后端交互。
5. Md5Util:处理密码
让密码不要以明文形式直接存入数据库。
6. GlobalExceptionHandler:全局异常处理器
接收异常,处理异常。
三、User类
java
package org.example.pojo;
import lombok.Data;
import java.time.LocalDateTime;
@Data
public class User {
private Integer id;//主键ID
private String username;//用户名
private String password;//密码
private String nickname;//昵称
private String email;//邮箱
private String userPic;//用户头像地址
private LocalDateTime createTime;//创建时间
private LocalDateTime updateTime;//更新时间
}
定义user类,属性要和数据库中一致,这里@Data是lombok提供的注解,运行程序后可以把这个类的所以属性都写上set和get方法。非常方便。
四、接口层
sql
package org.example.controller;
import jakarta.validation.constraints.Pattern;
import org.example.pojo.Result;
import org.example.pojo.User;
import org.example.service.UserService;
import org.example.utils.Md5Util;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/user")
@Validated
public class UserController {
@Autowired
private UserService userService;
@PostMapping("/register")
public Result register(@Pattern(regexp = "^\\S{5,16}$") String username,@Pattern(regexp = "^\\S{5,16}$") String password){
//查询用户
User u= userService.findByUserName(username);
if(u==null){
//注册
userService.register(username,password);
return Result.success();
}else{
return Result.error("用户已被占用");
}
}
}
1.代码解释
常规注解解释:
@RestController:说明这是一个控制器类,返回的数据会直接变成 JSON
@RequestMapping("/user"):说明这个类里的接口统一以 /user 开头,避免代码耦合
@PostMapping("/register"):说明这是一个 POST 请求,对应地址是 /user/register
@Autowired:spring boot的关键设计之一,自动注入功能,接口层只负责接受,业务逻辑需要在Service层实现,所以这个需要借助注释来注入一个Service对象。
参数校验部分注解解释:
前端传过来的参数,后端不能直接信。需要通过判断逻辑来检查。
当然,你可以直接使用if else判断语句来驳回违法参数,但这样做会让代码变得复杂且不美观,通常,我们会使用注解来实现参数校验
@Validated:validated依赖提供的注解,该类开启参数校验功能。
@Pattern:限制用户名和密码必须符合指定规则。@Pattern(regexp = "^\\S{5,16}$")的意思就是用户名和密码必须是 5 到 16 位,并且不能包含空格等空白字符。
逻辑解释:
这段代码的逻辑十分简单,User u= userService.findByUserName(username);通过Service层提供的方法来判断用户名有没有被占用,具体的实现方法都写在Service和Mapper当中。
2.Controller 层的职责:
Controller 层一般只做三件事:
- 接收前端请求
- 接收参数并做基础校验
- 调用 Service,最后把结果返回给前端
也就是说,Controller 更像一个"前台接待":
- 用户来办事,它先接待
- 检查提交材料是否符合最基本要求
- 然后把真正的业务交给后面的 Service 去处理
Controller 一般不要写太多业务逻辑。
五、Service层
service接口
java
package org.example.service;
import org.example.pojo.User;
public interface UserService {
User findByUserName(String username);
//注册
void register(String username,String password);
}
service类
java
package org.example.service.impl;
import org.example.mapper.UserMapper;
import org.example.pojo.User;
import org.example.service.UserService;
import org.example.utils.Md5Util;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserserviceImpl implements UserService {
@Autowired
private UserMapper userMapper;
@Override
public User findByUserName(String username) {
User u= userMapper.findByUserName(username);
return u;
}
@Override
public void register(String username, String password) {
//加密
String md5String = Md5Util.getMD5String(password);
//添加
userMapper.add(username,md5String);
}
}
1.代码解释
findByUserName方法实现了根据用户名查找用户的功能,但是service不能直接操作数据库,需要借助mapper中的方法。
register提供了注册方法,这里要注意,密码不能直接存到数据库中,要经过加密处理,这里用的Md5的加密方法,不用理解,直接复制即可。将数据添加到数据库当中,也需要操纵数据库,所以任然需要mapper中的方法。
java
package org.example.utils;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class Md5Util {
/**
* 默认的密码字符串组合,用来将字节转换成 16 进制表示的字符,apache校验下载的文件的正确性用的就是默认的这个组合
*/
protected static char hexDigits[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
protected static MessageDigest messagedigest = null;
static {
try {
messagedigest = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException nsaex) {
System.err.println(Md5Util.class.getName() + "初始化失败,MessageDigest不支持MD5Util。");
nsaex.printStackTrace();
}
}
/**
* 生成字符串的md5校验值
*
* @param s
* @return
*/
public static String getMD5String(String s) {
return getMD5String(s.getBytes());
}
/**
* 判断字符串的md5校验码是否与一个已知的md5码相匹配
*
* @param password 要校验的字符串
* @param md5PwdStr 已知的md5校验码
* @return
*/
public static boolean checkPassword(String password, String md5PwdStr) {
String s = getMD5String(password);
return s.equals(md5PwdStr);
}
public static String getMD5String(byte[] bytes) {
messagedigest.update(bytes);
return bufferToHex(messagedigest.digest());
}
private static String bufferToHex(byte bytes[]) {
return bufferToHex(bytes, 0, bytes.length);
}
private static String bufferToHex(byte bytes[], int m, int n) {
StringBuffer stringbuffer = new StringBuffer(2 * n);
int k = m + n;
for (int l = m; l < k; l++) {
appendHexPair(bytes[l], stringbuffer);
}
return stringbuffer.toString();
}
private static void appendHexPair(byte bt, StringBuffer stringbuffer) {
char c0 = hexDigits[(bt & 0xf0) >> 4];// 取字节中高 4 位的数字转换, >>>
// 为逻辑右移,将符号位一起右移,此处未发现两种符号有何不同
char c1 = hexDigits[bt & 0xf];// 取字节中低 4 位的数字转换
stringbuffer.append(c0);
stringbuffer.append(c1);
}
}
2.Service 层是干什么的
Service 层就是"业务层"。
它和 Controller 的最大区别是:
- Controller 关心"请求来了怎么办"
- Service 关心"这件事到底该怎么做"
比如注册功能,业务逻辑可能包括:
- 先查用户名是否存在
- 如果不存在,再对密码加密
- 再调用 Mapper 插入数据库
- 以后也许还要发欢迎消息、记录日志、注册送积分......
这些都属于业务逻辑,最适合放在 Service。
Service层的业务逻辑很多,但操作数据库的实际上是mapper。
六、mapper层
java
package org.example.mapper;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import org.example.pojo.User;
@Mapper
public interface UserMapper {
//根据用户名查询用户
@Select("select * from user where username=#{username}")
User findByUserName(String username);
//添加
@Insert("insert into user(username,password,create_time,update_time)"+"values(#{username},#{md5String},now(),now())")
void add(String username,String md5String);
}
1.代码解释
这里用到的是mybatis提供的注解,简单来说mybatis就是实现了在java中编写sql代码,实现操作数据库的功能。
@Mapper就是在告诉spring boot这是个mapper接口,请帮我生成实现类,你虽然只写了接口,但运行时 MyBatis 会帮你创建代理对象,所以你可以像调用普通方法一样调用它。
@Select因为这里是查找用户所以是select,括号里面写的是具体的查找方法
@Insert表示的是插入数据,除了要用户名和密码外还需要输入创建时间和更新时间,获取当前时间的方法是now()。
2.Mapper 层是干什么的
Mapper 层就是:
专门负责操作数据库。
在这段代码里,它做了两件事:
findByUserName:去数据库查用户add:把新用户插入数据库
为什么数据库操作要单独放 Mapper
因为 SQL 和业务逻辑不是一回事。
例如:
- "用户名是否允许重复"是业务规则
- "怎么查 user 表"是数据库操作
所以 Service 负责业务判断,Mapper 负责数据库执行,这样分工清楚,不容易乱。
七、统一响应类Result
java
@Data
public class Result<T> {
private Integer code;//业务状态码 0-成功 1-失败
private String message;//提示信息
private T data;//响应数据
public Result(Integer i, String m, T d) {
this.code=i;
this.message=m;
this.data=d;
}
public static <E> Result<E> success(E data) {
return new Result<>(0, "操作成功", data);
}
public static Result success() {
return new Result(0, "操作成功", null);
}
public static Result error(String message) {
return new Result(1, message, null);
}
}
这个类的作用很简单,就是为你返回的数据套上一层壳,统一返回数据的格式。
为什么要统一格式
如果不统一,今天你返回字符串,明天返回对象,后天返回布尔值,前端就会很难处理。
统一格式后,前端只需要约定:
code == 0表示成功code == 1表示失败message用来看提示data用来放真正的数据
这样整个项目风格就统一了。
泛型 <T> 是干什么的
Result<T> 中的 T 表示:
data 里存的数据类型是不固定的。
比如:
- 注册成功,可能没有数据,
data = null - 查询用户信息时,
data可以是User - 查询用户列表时,
data可以是List<User>
所以泛型的作用是:
让 Result 这个类可以适配不同类型的数据,而不用为每种返回值都重新写一个类。
八、全局异常处理器
java
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(Exception.class)
public Result handleException(Exception e){
e.printStackTrace();
return Result.error(
Boolean.parseBoolean(String.valueOf((e.getMessage()==null)))
? "操作失败"
: e.getMessage()
);
}
}
这是一个兜底的设计。
作用就是统一处理项目中抛出的异常,避免报错时直接把一大堆错误信息甩给前端。
@ExceptionHandler(Exception.class)
这句表示:
只要出现 Exception 类型的异常,就执行这个方法。
然后在方法里返回 Result.error(...),这样前端收到的仍然是统一 JSON,而不是一堆不友好的报错。
九、总结
对于初学者来说,Spring Boot 的注册功能是一个非常好的入门案例。因为它虽然简单,却几乎把后端开发里最常见的几个核心点都串起来了:接口设计、三层架构、参数校验、统一返回、数据库操作、密码处理、异常处理。
当你把这个小功能真正吃透之后,后面再去学登录、JWT、拦截器、权限控制、Redis 缓存,其实都会更顺,因为你已经理解了后端接口最基本的运行方式。
这个案例真正教你的不是"怎么写一个注册接口",而是:
一个 Spring Boot 接口,从接收请求到返回结果,中间到底经历了什么。