前言

我花了大约 3 周时间完成了我的第一个完整后端项目——用户中心系统。作为一个大二学生,这个项目让我从”只会写 Hello World”到能够独立完成一个前后端分离的系统。虽然项目还没部署,代码也还有很多可以优化的地方,但这个过程让我学到了很多。今天想和大家分享一下我为什么选择这个项目,以及整个开发过程中的思考和收获。

为什么选择用户中心项目?

1. 从”看到”到”想做”

其实最开始我并没有明确的项目方向。那段时间在网上刷到很多类似的项目教程,比如”从零开始搭建用户系统”、”Spring Boot 实战项目”之类的。看多了之后,我发现用户中心项目有几个特点特别适合我这种初学者:

  • 功能明确:注册、登录、信息管理,这些功能我作为用户每天都在用,理解起来不费劲
  • 技术栈常见:Spring Boot + MySQL + Redis,这些技术网上资料多,遇到问题容易找到解决方案
  • 复杂度适中:不会太简单(比如只做个计算器),也不会太复杂(比如电商系统),正好适合练手

所以我就决定:就做这个了!

2. 软件工程导论的启发

这学期我们开了《软件工程导论》这门课,虽然理论居多,但让我知道了做项目不是直接写代码,而是要先分析需求、设计架构、画数据库表、定义接口…这些步骤。

所以我想按照课程里学到的方法,一步步来:

  1. 需求分析:先想清楚要做什么功能
  2. 技术选型:选什么框架、什么数据库
  3. 架构设计:系统怎么分层,模块怎么划分
  4. 数据库设计:表结构怎么设计,字段怎么定义
  5. API 设计:接口路径、参数、返回值
  6. 编码实现:把设计变成代码
  7. 测试优化:跑通功能,优化性能

虽然最后做的时候还是踩了很多坑,但有了这个思路,至少不会完全不知道从哪开始。

项目流程回顾

1. 需求分析阶段

最开始我花了两天时间梳理需求。虽然用户中心听起来简单,但真要写下来,还是有不少细节要考虑:

核心功能:

  • 用户注册:账户密码校验、重复检查(用户名、邮箱、手机号不能重复)
  • 用户登录:身份验证、登录态管理(Token 机制)
  • 用户信息管理:查看、修改个人信息
  • 用户管理(管理员功能):查询用户列表、修改用户信息、删除用户
  • 权限控制:区分普通用户和管理员,不同角色能访问的接口不同

非功能需求:

  • 密码要加密存储(不能明文)
  • 登录态要支持多端(PC、手机都能登录)
  • 接口要统一返回格式,方便前端处理
  • 异常要统一处理,不能直接返回堆栈信息

2. 技术选型阶段

选技术的时候我主要考虑两个因素:学习成本和资料丰富度。毕竟我是自学,如果还选个冷门技术,遇到问题找不到资料就很麻烦。

模块 技术选择 理由
后端框架 Spring Boot 上手快,生态完善,网上教程多
数据库 MySQL 关系型数据库,适合存储用户信息,学习成本低
ORM MyBatis-Plus 简化 CRUD 操作,自动生成代码,减少重复工作
登录态管理 Redis + Token 支持分布式,可主动失效,比 Session 灵活
前端 React + Ant Design 组件丰富,适合做管理系统界面
密码加密 BCrypt 安全性高,Spring Security 内置支持

3. 架构设计阶段

虽然项目不大,但我还是简单画了一下架构图(用 draw.io 画的,比较简单):

系统架构示意图

分层设计:

  • Controller 层:接收 HTTP 请求,参数校验,返回统一格式的响应
  • Service 层:业务逻辑处理,比如登录验证、密码加密、Token 生成
  • Mapper 层:数据库 CRUD 操作,使用 MyBatis-Plus 简化

关键组件:

  • 拦截器:统一处理 Token 验证、权限校验
  • 全局异常处理器:统一处理异常,返回友好错误信息
  • 工具类:Token 生成、密码加密、用户上下文(ThreadLocal)

4. 数据库设计阶段

数据库设计我花了不少时间,因为这是第一次自己设计表结构。我参考了一些开源项目的设计,也看了很多数据库设计的文章(后来我还专门写了一篇《用户中心项目的数据库设计:常见问题与解决方案》来总结)。

核心表设计:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
-- 用户表(核心表)
CREATE TABLE `user` (
`user_id` INT PRIMARY KEY AUTO_INCREMENT COMMENT '用户ID',
`username` VARCHAR(50) UNIQUE COMMENT '用户名',
`email` VARCHAR(100) UNIQUE COMMENT '邮箱',
`phone` VARCHAR(20) UNIQUE COMMENT '手机号',
`password_hash` CHAR(60) NOT NULL COMMENT '密码哈希(bcrypt)',
`user_role` ENUM('user', 'admin') DEFAULT 'user' COMMENT '角色',
`status` ENUM('active', 'inactive', 'banned') DEFAULT 'inactive' COMMENT '状态',
`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`is_delete` TINYINT DEFAULT 0 COMMENT '逻辑删除标记',
INDEX `idx_email` (`email`),
INDEX `idx_phone` (`phone`)
) COMMENT='用户基本信息表';

设计考虑:

  • 用 UNIQUE 保证用户名、邮箱、手机号唯一
  • 给邮箱、手机号建索引,登录时查询快
  • 用 ENUM 限制角色和状态的值,避免脏数据
  • 用逻辑删除(is_delete)而不是物理删除,数据可以恢复

5. API 设计阶段

1
2
3
4
5
6
7
8
9
10
11
12
用户相关接口:
POST /api/user/register # 用户注册
POST /api/user/login # 用户登录
GET /api/user/getInfo # 获取当前用户信息
POST /api/user/updateInfo # 更新用户信息
POST /api/user/logout # 退出登录

管理员接口:
GET /api/admin/getUserList # 获取用户列表
GET /api/admin/getUserById # 获取用户详情
POST /api/admin/updateUser # 更新用户信息
POST /api/admin/deleteUser # 删除用户

现在回头看,接口设计确实有很多问题:

  • 路径命名不统一:有些用层级结构(/api/user/register),有些用动词+名词(/api/admin/getUserList)
  • HTTP 方法用得不对:更新和删除应该用 PUT 和 DELETE,但我都用 POST 了
  • 参数传递方式混乱:getUserById 用查询参数传 ID,updateUser 和 deleteUser 又在请求体里传 userId,一点都不统一
  • GET 请求传参不规范:getUserById 应该用路径参数 /api/admin/user/{id} 更规范

不过当时能跑起来就行,后面我应该会写一篇关于学到的 RESTful API 设计方法。

统一返回格式:
唯一做得比较规范的是统一返回格式,所有接口都返回 Result<T> 格式,这样前端处理起来方便:

1
2
3
4
5
{
"code": 0, // 0 表示成功,非 0 表示失败
"data": {...}, // 数据
"message": "ok" // 消息
}

6. 开发实现阶段

这个阶段是最痛苦的,也是收获最多的。我遇到了很多问题,也解决了很多问题:

问题 1:密码安全存储

最开始我直接把密码存到数据库,后来意识到安全问题,改成了 BCrypt 加密。这个过程我专门写了一篇文章记录(见《用户中心项目踩坑记 1:密码加密那些事儿》)。

问题 2:登录态管理

最开始用 Session,结果刷新页面就丢失,多端也无法共享。后来改成 Redis + Token 方案,解决了这些问题。这个过程我也写了文章(见《用户中心项目踩坑记 2:登录态管理的那些坑》)。

问题 3:跨域问题

前后端分离开发,前端运行在 localhost:3000,后端运行在 localhost:8080,浏览器会报跨域错误。我通过配置 CORS 解决了:

1
2
3
4
5
6
7
8
9
10
11
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins("http://localhost:3000")
.allowedMethods("GET", "POST", "PUT", "DELETE")
.allowedHeaders("*")
.allowCredentials(true);
}
}

问题 4:权限控制

普通用户和管理员能访问的接口不同,我通过拦截器实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@Component
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) {
// 1. 验证 Token
String token = request.getHeader("Authorization");
UserDTO user = tokenUtils.verifyToken(token);
if (user == null) {
throw new BusinessException("未登录");
}

// 2. 权限校验(管理员接口需要 admin 角色)
if (request.getRequestURI().startsWith("/api/admin")) {
if (!"admin".equals(user.getUserRole())) {
throw new BusinessException("无权限");
}
}

// 3. 保存用户信息到 ThreadLocal
UserContext.set(user);
return true;
}
}

7. 测试与优化阶段

单元测试:
虽然测试写得不多,但至少给核心接口写了测试用例,比如登录、注册:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@SpringBootTest
class UserControllerTest {
@Autowired
private UserController userController;

@Test
void testLogin() {
LoginRequest request = new LoginRequest();
request.setAccount("test@example.com");
request.setPassword("123456");

Result<LoginResponse> result = userController.login(request);
assertEquals(0, result.getCode());
assertNotNull(result.getData().getToken());
}
}

性能优化:

  • 给用户表的邮箱、手机号字段建了索引,登录查询快了很多
  • 登录成功后把用户信息存到 Redis,减少数据库查询

异常处理:
写了全局异常处理器,统一处理异常:

1
2
3
4
5
6
7
8
9
10
11
12
13
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(BusinessException.class)
public Result<Void> handleBusinessException(BusinessException e) {
return Result.error(e.getMessage());
}

@ExceptionHandler(Exception.class)
public Result<Void> handleException(Exception e) {
log.error("系统异常", e);
return Result.error("系统繁忙,请稍后再试");
}
}

核心技术实现

1. 通用返回结果封装

所有接口都返回统一的 Result<T> 格式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Data
public class Result<T> {
private int code; // 业务状态码:0 表示成功,非 0 表示失败
private T data; // 数据
private String message; // 消息

// 成功返回
public static <T> Result<T> success(T data) {
Result<T> result = new Result<>();
result.setCode(0);
result.setData(data);
result.setMessage("ok");
return result;
}

// 错误返回
public static <T> Result<T> error(String message) {
Result<T> result = new Result<>();
result.setCode(500);
result.setData(null);
result.setMessage(message);
return result;
}
}

2. Token 工具类

基于 Redis 的 Token 管理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@Component
public class TokenUtils {
@Autowired
private StringRedisTemplate redisTemplate;

// 生成 Token
public String generateToken(Long userId) {
return "USER_TOKEN:" + UUID.randomUUID().toString().replace("-", "");
}

// 存储 Token 和用户信息(2小时过期)
public void storeToken(String token, UserDTO user) {
redisTemplate.opsForValue().set(
token,
JSON.toJSONString(user),
2,
TimeUnit.HOURS
);
}

// 验证 Token 并自动续期
public UserDTO verifyToken(String token) {
String userJson = redisTemplate.opsForValue().get(token);
if (userJson == null) {
return null;
}
// 验证成功后自动续期
redisTemplate.expire(token, 2, TimeUnit.HOURS);
return JSON.parseObject(userJson, UserDTO.class);
}
}

3. 用户上下文(ThreadLocal)

方便在 Service 层获取当前登录用户:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class UserContext {
private static final ThreadLocal<UserDTO> userHolder = new ThreadLocal<>();

public static void set(UserDTO user) {
userHolder.set(user);
}

public static UserDTO get() {
return userHolder.get();
}

public static void clear() {
userHolder.remove();
}
}

收获与反思

学到的技术

  1. Spring Boot 核心功能:注解、配置、拦截器、异常处理
  2. MyBatis-Plus:简化数据库操作,自动生成代码
  3. Redis:缓存、Token 存储、分布式支持
  4. 前后端分离开发:接口设计、跨域处理、Token 传递
  5. 数据库设计:表结构设计、索引优化、逻辑删除

不足与改进

  1. 测试覆盖不够:只写了核心接口的测试,边界情况考虑不全
  2. 代码复用性:部分逻辑存在冗余,可以提取成公共方法
  3. 接口限流:没有实现限流,高并发场景下可能出问题
  4. 日志记录:日志记录不够完善,出问题时排查困难
  5. 安全性:虽然用了 BCrypt 和 Token,但还有很多安全措施没做(比如接口限流、防 SQL 注入等)

后续计划

虽然项目基本功能都实现了,但还有很多可以改进的地方:

  1. 功能扩展:

    • 接入短信验证码,增强注册安全性
    • 实现用户头像上传功能
    • 添加用户登录日志,方便分析异常登录
  2. 技术提升:

    • 学习 Docker,把项目容器化
    • 学习 Nginx,做反向代理和负载均衡
    • 把项目部署到云服务器,让其他人也能访问
  3. 代码优化:

    • 提高测试覆盖率
    • 优化代码结构,提高复用性
    • 接入接口限流,防止恶意请求

写在最后

作为第一个完整项目,过程虽然磕磕绊绊,但收获远超预期。从最初连数据库连接都配置不好,到现在能独立解决加密、登录态、跨域等问题,真切感受到了”实践”的力量。

这个项目让我明白,理论很重要,但实践更关键。看再多教程,不如自己动手做一遍。遇到问题、解决问题、总结经验,这个过程让我成长了很多。

虽然项目还没部署,代码也还有很多可以优化的地方,但至少我迈出了第一步。路还很长,继续加油!


相关文章

这个用户中心项目系列的其他文章: