前言
毕业设计做了一个基于 Spring Boot 3 + React 19 的前后端分离在线书店系统,功能涵盖用户认证、购物车、订单、支付、AI 推荐等完整电商链路。本地开发没问题,但要真正部署到服务器上,还是踩了不少坑。
本文记录从零开始,将项目通过 Docker Compose 部署到阿里云 ECS(1.6GB 内存)的完整过程。
技术架构
| 层级 | 技术栈 |
|---|---|
| 后端 | Spring Boot 3.2.2 + Java 21 + MySQL 8.0 + JPA |
| 前端 | React 19 + TypeScript + Vite 7 + Tailwind CSS + Ant Design |
| 部署 | Docker Compose + Nginx 反向代理 |
| 服务器 | 阿里云 ECS Debian 13, 1.6GB RAM, 10GB 磁盘 |
Docker Compose 架构
三个容器,各司其职:
用户浏览器 → :8088 (Nginx 容器)
├── / → React SPA (静态文件)
├── /api/* → Spring Boot 后端 (:8080)
├── /swagger/* → Swagger UI
└── /uploads/* → 图片资源
↕
MySQL 8.0 容器 (:3306 仅本地)部署步骤
1. 准备 Dockerfile
后端 Dockerfile — 多阶段构建,Maven 编译 + JRE 运行:
# 构建阶段
FROM maven:3.9-eclipse-temurin-21 AS builder
WORKDIR /app
COPY pom.xml .
RUN mvn dependency:go-offline -B
COPY src ./src
RUN mvn clean package -DskipTests -B
# 运行阶段
FROM eclipse-temurin:21-jre-alpine
WORKDIR /app
COPY --from=builder /app/target/*.jar app.jar
EXPOSE 8080
CMD ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]前端 Dockerfile — Node 构建 + Nginx 托管:
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf2. 编写 docker-compose.yml
services:
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
MYSQL_DATABASE: online_bookstore
ports:
- "127.0.0.1:3306:3306"
volumes:
- mysql_data:/var/lib/mysql
backend:
build: ./backend
depends_on:
mysql:
condition: service_healthy
environment:
DB_URL: jdbc:mysql://mysql:3306/online_bookstore
JWT_SECRET: ${JWT_SECRET}
SPRING_PROFILES_ACTIVE: dev
EMAIL_MOCK: "true"
JAVA_OPTS: -Xms256m -Xmx512m
volumes:
- uploads_data:/app/uploads
frontend:
build: ./frontend
ports:
- "8088:80"
depends_on:
- backend3. 一键启动
cd /opt/project_library
docker compose up -d --build首次构建约 5-10 分钟(Maven 下载依赖 + npm install)。启动后 DataInitializer 自动填充 49 本图书、10 个用户、9 个示例订单。
踩坑记录与 Bug 修复
Bug 1:首页板块返回 401
现象:未登录用户访问 /api/home/* 全部返回 401 Unauthorized。
原因:SecurityConfig 中没有放行 /api/home/**,被 .anyRequest().authenticated() 拦截。
修复:在 SecurityConfig.java 的 filterChain 中添加:
.requestMatchers(HttpMethod.GET, "/api/home/**").permitAll()Bug 2:删除优惠券 FK 约束报错
现象:管理员删除已领取的优惠券时,MySQL 报外键约束冲突。
原因:user_coupons 表引用 coupons,没有级联删除。
修复:在 CouponService.deleteCoupon() 中,先删关联记录再删优惠券:
userCouponRepository.deleteByCouponId(id);
couponRepository.deleteById(id);Bug 3:OpenAPI 文档重定向丢失端口
现象:/v3/api-docs 301 重定向到 http://localhost/v3/api-docs/(丢失 :8088 端口)。
原因:Spring Boot 的 Tomcat 重定向使用 Host 头构造 URL,经过 Nginx 反代后端口信息丢失。
修复:两个改动 —
- 后端
application.properties添加spring.mvc.forward-headers-strategy=native - 前端 nginx.conf 添加精确匹配:
location = /v3/api-docs { proxy_pass ... }
Bug 4:DataInitializer 重启崩溃循环
现象:后端不断重启,日志报 Duplicate entry for key 'reviews.uk_review_user_book'。
原因:DataInitializer 每次重启都插入 reviews,但没有防重复检查(orders 和 coupons 都有 count() == 0 检查,reviews 漏了)。
修复:添加 long existingReviewCount = reviewRepository.count(); 守卫条件。
Bug 5:购物车序列化崩溃
现象:购物车接口返回 500,Hibernate 懒加载代理无法序列化。
修复:在 CartItem.java 的 @JsonIgnoreProperties 中添加 "hibernateLazyInitializer"。
Bug 6:图片上传 404
现象:头像和封面图片返回 404。
原因:nginx 的正则 ~* \.(png|jpg|...)$ 优先级高于前缀 location /uploads/。
修复:给 /uploads/ 加 ^~ 优先匹配。
Mock 模式测试
系统支持三种 Mock 模式,答辩演示时无需真实第三方服务:
| 功能 | Mock ON | Mock OFF |
|---|---|---|
| 邮箱验证码 | 返回固定验证码 123456 | 需要 SMTP 配置 |
| AI 推荐 | 返回预设推荐结果 | 需要 OpenRouter API Key |
| 支付宝支付 | 模拟支付成功 | 需要沙箱密钥 |
通过环境变量 EMAIL_MOCK=true/false 切换,重启后端即可生效。
最终验证结果
全面测试 90+ 个 API 端点,通过率 99%:
- ✅ 认证(登录/注册/验证码)
- ✅ 图书 CRUD + 搜索 + 分类
- ✅ 购物车 + 订单 + 支付
- ✅ 收藏 + 评价 + 浏览历史
- ✅ 积分签到 + 优惠券
- ✅ 管理后台仪表盘 + 数据导出
- ✅ 首页板块公开访问
- ✅ Swagger / OpenAPI 文档
- ✅ 权限控制(401/403)
总结
1.6GB 内存的 VPS 完全可以跑起前后端分离项目,关键是 Docker Compose 让部署变得可重复、可维护。最大的收获不是部署本身,而是在过程中发现并修复了 6 个系统 Bug — 这些在本地开发时完全没暴露出来。