系统开发毕设实战:从零构建高可用微服务架构的完整路径
摘要:许多计算机专业学生在完成系统开发毕设时,常陷入“功能堆砌、架构混乱、部署困难”的困境。本文以真实毕设项目为蓝本,详解如何基于 Spring Boot + Vue + Docker 技术栈,设计具备解耦、幂等性和可观测性的微服务系统。读者将掌握模块划分原则、API 设计规范、容器化部署流程,并通过压测验证系统吞吐量与稳定性,显著提升毕设工程深度与答辩竞争力。
1. 毕设常见痛点:为什么“能跑就行”不再够用
本科毕设评审正悄悄从“功能可用”转向“工程可信”。下面三条是答辩老师最爱追问、也是最容易翻车的点:
- 单体臃肿:所有代码塞进一个 Maven 模块,修改一行代码要全量重启,本地调试 5 分钟一次,老师现场演示直接“社死”。
- 无日志监控:异常靠
System.out.println,服务器 404 了还要借老师电脑看控制台,现场尴尬值 +10086。 - 并发处理缺失:演示用 Postman 单线程点两下没问题,老师一喊“同学们一起点”,数据库连接池瞬间打满,直接 5xx。
一句话总结:业务代码能跑只是“及格线”,高并发、可观测、易回滚才是“优秀线”。
2. 技术选型对比:把“熟悉”升级成“经得起问”
| 维度 | 选型 A(熟悉但易踩坑) | 选型 B(学习成本略高但答辩更稳) | 建议 |
|---|---|---|---|
| 后端框架 | Django + REST framework | Spring Boot 2.7.x | 若团队对 Kotlin/Java 有基础,直接 Spring Boot;IoC、AOP、 actuator 端点老师都认识。 |
| 数据库 | MySQL 5.7 | PostgreSQL 14 | MySQL 在 Windows 下大小写不敏感易埋雷,PG 的 JSONB 还能直接存日志,减少字段设计。 |
| 缓存 | 无 | Redis 6.2 | 缓存击穿、雪崩是答辩加分项,用 Redis 至少能讲出“互斥锁 + 随机过期”套路。 |
| 前端 | JSP/Thymeleaf 单体渲染 | Vue3 + ElementPlus | 前后端分离后,可以独立部署,演示时前端挂掉不影响后端成绩。 |
| 容器 | 手动打包 war | Docker + Docker Compose | 现场换电脑 3 分钟起服务,老师体感“专业”。 |
结论:Spring Boot + PostgreSQL + Redis + Vue + Docker这套栈,文档全、生态坑少,最关键是——老师都听过,不会抓瞎。
3. 核心实现细节:让代码自己“会说话”
3.1 模块划分:先画圈再写码
采用DDD 最简版思路,把“用户、商品、订单”拆成三个微服务(毕设规模下可共库,但包路径隔离):
- user-service:
com.graduation.user - product-service:
com.graduation.product - order-service:
com.graduation.order
每个服务只暴露Feign 客户端接口,Controller 禁止跨模块调表,数据库层物理隔离留给后续迭代,答辩时讲清楚边界即可。
3.2 API 设计规范
统一 REST 风格 + 版本号:
GET /api/v1/orders/{id} POST /api/v1/orders返回体包装:
{ "code": 0, "msg": "success", "data": { ... } }code 非 0 时走全局异常处理器,老师一眼就能定位问题。
3.3 JWT 鉴权 & 刷新策略
access-token 有效期 15 min,refresh-token 7 天,Redis 记录刷新链防止重复回滚。关键代码(Spring Security 过滤器片段):
// UsernamePasswordAuthenticationFilter 之前 if (jwtUtil.isExpiringSoon(token)) { String newToken = jwtUtil.refreshToken(token); response.setHeader("Access-Token", newToken); }技巧:在响应头里直接返新 token,前端无感刷新,演示时老师看不到登录页跳转,体验丝滑。
3.4 Redis 缓存防击穿
典型getProduct场景:
- 查询缓存,命中直接返回;
- 未命中尝试获取Redis 分布式锁(SET NX EX);
- 拿到锁回源库加载,写缓存再删锁;
- 其他请求自旋 50 ms 重试,防止雪崩。
String lockKey = "lock:product:" + id; String val = UUIDUtil.fastUUID(); try { if (Boolean.TRUE.equals(redisTemplate.opsForValue() .setIfAbsent(lockKey, val, 5, TimeUnit.SECONDS))) { Product p = productMapper.selectById(id); redisTemplate.opsForValue().set("product:" + id, p, 300, TimeUnit.SECONDS); return p; } else { Thread.sleep(50); return getProduct(id); // 递归重试 } } finally { // Lua 保证原子删除 String lua = "if redis.call('get', KEYS[1]) == ARGV[1] then " + "return redis.call('del', KEYS[1]) else return 0 end"; redisTemplate.execute(new DefaultRedisScript<>(lua, Long.class), Collections.singletonList(lockKey), val); }3.5 接口幂等性设计
订单创建最怕F5 刷新重复扣款。采用Token 桶机制:
- 下单前调
/api/v1/orders/token获取一次性幂等 Token(UUID),Redis 存 60 s; - 创建订单 Header 附带
Idempotency-Key: UUID; - 服务端 Lua 脚本GET+DEL判断,原子性保证仅执行一次。
老师若问“为什么不用数据库唯一索引”,答:“库表解耦,幂等逻辑上浮到网关层,减少存储压力。”——答辩加分。
4. 架构图 & 关键代码结构
目录示例:
graduation-project/ ├─ gateway/ # Spring Cloud Gateway ├─ user-service/ │ ├─ src/main/java/com/graduation/user/ │ │ ├─ controller/ │ │ ├─ service/ │ │ ├─ mapper/ │ │ └─ config/ # Redis、MyBatis、Swagger ├─ product-service/ # 同上 ├─ order-service/ ├─ vue-admin/ # Vue3 + Vite ├─ docker-compose.yml └─ jmeter/ # 压测脚本Clean Code 要点:
- 所有函数不超过 30 行;
- 魔法值一律提为
static final;- 日志使用
Slf4j + {}占位,拒绝字符串拼接。
5. 性能测试 & 安全加固
5.1 JMeter 压测结果
- 场景:4C8G 笔记本 Docker 限 1G 内存;
- 线程组:500 并发,Ramp-up 5s,循环 20 次;
- 指标:
- QPS ≈ 1100(订单创建接口)
- Avg RT ≈ 450 ms
- 99% RT < 900 ms
- 错误率 0%(幂等 Token 无重复)
若老师追问“再高一点就崩怎么办?”——把Undertow 替换 Tomcat,默认线程池 8→32,QPS 可再抬升 20%。
5.2 安全考量
- SQL 注入:MyBatis-Plus
#{}预编译,拒绝${}拼接; - CORS:Gateway 统一配置,只允许
https://localhost:5173,拒绝*通配; - XSS:Vue 自带 v-text 转义,后端 Jackson 开启
SerializationFeature.WRITE_CHAR_ARRAYS_AS_JSON_ARRAYS; - 敏感信息:actuator 只开放
health/info,禁止暴露 env、heapdump。
6. 生产环境避坑指南
- 数据库连接池
- 默认 HikariCP 最大 10,Docker 内跑压测直接打满;按 2C 4G 容器建议:maxPoolSize=20,minimumIdle=5,connectionTimeout=30s。
- Docker 镜像体积
- 使用
openjdk:17-jre-slim基础镜像,分层构建 + .dockerignore把target/*.jar以外全部排除,体积从 380 MB 降到 120 MB。
- 使用
- Git 分支管理
- 采用GitFlow 简化版:master / dev / feature/xx,禁止直接 push master;答辩演示前打 Tag
v1.0.0,回滚分分钟。
- 采用GitFlow 简化版:master / dev / feature/xx,禁止直接 push master;答辩演示前打 Tag
- 日志落盘
- Docker 默认 json-file 驱动,单容器 100 MB 封顶,超出后
docker logs卡死;挂载 volume 到宿主 + logback 滚动,保留 7 天,老师要看现场日志随时less。
- Docker 默认 json-file 驱动,单容器 100 MB 封顶,超出后
- 冷启动加速
- Spring Boot 3 原生编译 GraalVM 太折腾,用 AppCDS(Application Class-Data Sharing),把类元数据 dump 后映射,启动时间从 8s 降到 4s,演示不尴尬。
7. 结语:把“能跑”升级成“能战”
毕设不是“交差”,而是一次把课堂知识塞进真实机器的低成本演练。本文给出的模块拆分、幂等设计、缓存防击穿、压测指标,每一条都是答辩现场的高频提问点。建议你立即动手:
- 抽出当前单体项目里最耗时的接口,先加 Redis 缓存;
- 把登录态从 Session 迁到 JWT,用 Postman 并发 200 线程自测;
- 写一套 Docker Compose,让同组同学电脑 3 分钟跑起来;
- 跑 JMeter,把 QPS、RT、错误率截屏贴进论文——图表比文字更能打。
最后留一道思考题:在服务器资源只有 2C4G、时间只剩两周的前提下,你会优先保功能完整,还是优先保系统健壮?
答案没有标准,但评审老师更想听的是“在哪些节点我做了什么取舍,以及为什么”——讲得出权衡,才证明你真的懂了系统开发毕设。祝你答辩顺利,代码不挂!