前言 我花了大约 3 周时间完成了我的第一个完整后端项目——用户中心系统。作为一个大二学生,这个项目让我从”只会写 Hello World”到能够独立完成一个前后端分离的系统。虽然项目还没部署,代码也还有很多可以优化的地方,但这个过程让我学到了很多。今天想和大家分享一下我为什么选择这个项目,以及整个开发过程中的思考和收获。
为什么选择用户中心项目? 1. 从”看到”到”想做” 其实最开始我并没有明确的项目方向。那段时间在网上刷到很多类似的项目教程,比如”从零开始搭建用户系统”、”Spring Boot 实战项目”之类的。看多了之后,我发现用户中心项目有几个特点特别适合我这种初学者:
功能明确 :注册、登录、信息管理,这些功能我作为用户每天都在用,理解起来不费劲
技术栈常见 :Spring Boot + MySQL + Redis,这些技术网上资料多,遇到问题容易找到解决方案
复杂度适中 :不会太简单(比如只做个计算器),也不会太复杂(比如电商系统),正好适合练手
所以我就决定:就做这个了!
2. 软件工程导论的启发 这学期我们开了《软件工程导论》这门课,虽然理论居多,但让我知道了做项目不是直接写代码,而是要先分析需求、设计架构、画数据库表、定义接口…这些步骤。
所以我想按照课程里学到的方法,一步步来:
需求分析 :先想清楚要做什么功能
技术选型 :选什么框架、什么数据库
架构设计 :系统怎么分层,模块怎么划分
数据库设计 :表结构怎么设计,字段怎么定义
API 设计 :接口路径、参数、返回值
编码实现 :把设计变成代码
测试优化 :跑通功能,优化性能
虽然最后做的时候还是踩了很多坑,但有了这个思路,至少不会完全不知道从哪开始。
项目流程回顾 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 , "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) { String token = request.getHeader("Authorization" ); UserDTO user = tokenUtils.verifyToken(token); if (user == null ) { throw new BusinessException ("未登录" ); } if (request.getRequestURI().startsWith("/api/admin" )) { if (!"admin" .equals(user.getUserRole())) { throw new BusinessException ("无权限" ); } } 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; 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; public String generateToken (Long userId) { return "USER_TOKEN:" + UUID.randomUUID().toString().replace("-" , "" ); } public void storeToken (String token, UserDTO user) { redisTemplate.opsForValue().set( token, JSON.toJSONString(user), 2 , TimeUnit.HOURS ); } 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(); } }
收获与反思 学到的技术
Spring Boot 核心功能 :注解、配置、拦截器、异常处理
MyBatis-Plus :简化数据库操作,自动生成代码
Redis :缓存、Token 存储、分布式支持
前后端分离开发 :接口设计、跨域处理、Token 传递
数据库设计 :表结构设计、索引优化、逻辑删除
不足与改进
测试覆盖不够 :只写了核心接口的测试,边界情况考虑不全
代码复用性 :部分逻辑存在冗余,可以提取成公共方法
接口限流 :没有实现限流,高并发场景下可能出问题
日志记录 :日志记录不够完善,出问题时排查困难
安全性 :虽然用了 BCrypt 和 Token,但还有很多安全措施没做(比如接口限流、防 SQL 注入等)
后续计划 虽然项目基本功能都实现了,但还有很多可以改进的地方:
功能扩展 :
接入短信验证码,增强注册安全性
实现用户头像上传功能
添加用户登录日志,方便分析异常登录
技术提升 :
学习 Docker,把项目容器化
学习 Nginx,做反向代理和负载均衡
把项目部署到云服务器,让其他人也能访问
代码优化 :
提高测试覆盖率
优化代码结构,提高复用性
接入接口限流,防止恶意请求
写在最后 作为第一个完整项目,过程虽然磕磕绊绊,但收获远超预期。从最初连数据库连接都配置不好,到现在能独立解决加密、登录态、跨域等问题,真切感受到了”实践”的力量。
这个项目让我明白,理论很重要,但实践更关键 。看再多教程,不如自己动手做一遍。遇到问题、解决问题、总结经验,这个过程让我成长了很多。
虽然项目还没部署,代码也还有很多可以优化的地方,但至少我迈出了第一步。路还很长,继续加油!
相关文章 这个用户中心项目系列的其他文章: