登录注册是管理系统类毕设里几乎躲不开的模块,看上去只是两张表单,真正卡人的地方集中在三处:密码怎么存、登录态怎么带、前后端字段怎么对齐。这篇按「表设计 → 密码 → 会话 → 接口约定 → 联调」的顺序给做法和取舍,讲的是判断依据,不是可以直接套用的项目代码。
适用范围:Spring Boot 3.x + MyBatis-Plus + MySQL 8,前端不限框架。
一、表先定下来:两张表够用
很多人一上来就画五张表(用户、角色、权限、用户角色、角色权限),做到一半发现业务里根本没有多角色,余下四张表全是空转。判断依据很简单:如果你的系统只有「管理员」和「普通用户」两种身份,权限写进用户表的一个字段就够,不必拆角色表。
用户表的字段可以这样定:
CREATE TABLE sys_user (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键',
username VARCHAR(32) NOT NULL COMMENT '登录名',
password_hash VARCHAR(100) NOT NULL COMMENT 'BCrypt 结果约 60 位,留余量',
nickname VARCHAR(32) DEFAULT NULL COMMENT '显示名',
role VARCHAR(16) NOT NULL DEFAULT 'USER' COMMENT 'ADMIN / USER',
status TINYINT NOT NULL DEFAULT 1 COMMENT '1 正常 0 停用',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (id),
UNIQUE KEY uk_username (username)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';
username 上的 uk_username 别省。注册接口靠它兜住重复注册,否则并发下两个人能注册出同一个登录名,后面登录时「查到两条」的异常很难查。password_hash 长度给到 100,是因为 BCrypt 输出 60 位,换 Argon2 会更长,留余量比事后改表省事。
二、密码存储:差别不在「高级」,在验证成本
| 明文 | 全部账号直接暴露 | 任何场景都不该出现 |
| MD5 + 固定盐 | 可被彩虹表批量还原 | MD5 计算太快,反而方便离线爆破 |
| BCrypt / Argon2 | 逐个爆破的成本被抬高 | 内置随机盐,可调 cost |
用 Spring Security 的 crypto 模块就够,不必引入整个安全框架:
// 依赖:org.springframework.security:spring-security-crypto
PasswordEncoder encoder = new BCryptPasswordEncoder();
// 注册:只存哈希
String hash = encoder.encode("123456");
// 登录:用 matches 比对,不要自己拼字符串比
boolean ok = encoder.matches("123456", hash);
注意 BCrypt 每次 encode 结果都不一样(盐随机),所以不能用等号比较两次哈希值,这一点是初学者最常见的误判。
三、登录态:Session 与 Token 的取舍
| 服务端是否存状态 | 存 | 不存 |
| 前后端分离 / 跨域 | 要配 Cookie 与 CORS | 放请求头即可 |
| 强制失效 | 删 Session 即可 | 需要额外黑名单或短有效期 |
| 实现成本 | 低(单机) | 略高,但联调省心 |
学生项目大多是单机部署、前端跑在另一个端口,走 JWT 更省事;代价是无法即时强制失效,只能靠短有效期加刷新。如果系统里有「管理员封禁账号」这类需求,封禁后还得在拦截器里查一次用户状态,光靠 token 自证是不够的。
四、接口清单与四条约定
| 注册 | POST | /api/auth/register | 校验重名,返回新用户 id |
| 登录 | POST | /api/auth/login | 返回 token |
| 当前用户 | GET | /api/auth/me | 靠 token 解析出身份 |
| 退出 | POST | /api/auth/logout | 服务端清缓存或前端删 token |
四条约定,联调阶段能省掉大量返工:
public record Result<T>(int code, String message, T data) {
public static <T> Result<T> ok(T data) { return new Result<>(0, "ok", data); }
public static <T> Result<T> fail(int code, String message) { return new Result<>(code, message, null); }
}
五、联调最常卡的三处
收尾一句:这个模块的价值不在「能登录」,而在「错了能定位」。把返回体、错误码、请求头三样先统一,后面的模块都是同一套模板。
(本文由 AI 辅助整理框架与措辞,技术要点与命令均经本人实测核对。)
如果卡在同样的问题上,可以留言说清报错和版本,我按技术问题帮你判断。




