React + Vite 前端 + Spring Boot (Java 17, MyBatis) 后端 + MySQL 项目,提供一个完整的、零错误的部署方案。方案将:
- 使用 Docker Compose 的 build 指令直接构建前端(Nginx)和后端(Spring Boot)镜像,省去手动 docker build。
- 保留 MyBatis 配置和 ./mysql/db.sql:/docker-entrypoint-initdb.d/db.sql 初始化。
- 确保部署后直接通过浏览器访问 http://localhost(Nginx 默认 80 端口)。
- 支持云部署,通用且只需修改少量配置。
项目结构
假设项目结构如下(基于 C:\full-stack\admin-react,调整路径如果不同):
text
admin-react/
├── frontend/ # React + Vite 前端
│ ├── vite.config.js
│ ├── package.json
│ ├── src/
│ └── ... (其他文件)
├── backend/ # Spring Boot 后端 (MyBatis)
│ ├── pom.xml
│ ├── src/
│ │ └── main/
│ │ ├── java/ # Java 代码和 MyBatis Mapper 接口
│ │ └── resources/
│ │ ├── application.yml
│ │ └── mapper/ # MyBatis XML Mapper 文件(可选)
│ └── ... (其他文件)
├── mysql/ # MySQL 初始化脚本
│ └── db.sql # 数据库初始化 SQL
├── nginx/ # Nginx 配置文件
│ └── nginx.conf
├── docker-compose.yml # Docker Compose 配置
├── Dockerfile.frontend # 前端 Nginx 构建
├── Dockerfile.backend # 后端 Spring Boot 构建
└── README.md # 可选文档
步骤 1: 项目配置
前端 (React + Vite)
-
vite.config.js (frontend/vite.config.js):
js
import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; export default defineConfig({ plugins: [react()], server: { proxy: { '/api': 'http://localhost:8080', // 开发时代理到后端 }, }, build: { outDir: 'dist', }, });
-
package.json (frontend/package.json):
json
"scripts": { "build": "vite build" }
后端 (Spring Boot + MyBatis)
-
application.yml (backend/src/main/resources/application.yml):
yaml
server: port: ${SERVER_PORT:8080} spring: datasource: url: jdbc:mysql://${DB_HOST:localhost}:${DB_PORT:3306}/${DB_NAME:mydatabase}?useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true username: ${DB_USERNAME:root} password: ${DB_PASSWORD:password} driver-class-name: com.mysql.cj.jdbc.Driver mybatis: mapper-locations: classpath:mapper/*.xml # 调整为您的 XML Mapper 路径(若无则移除) type-aliases-package: com.example.entity # 调整为您的实体包路径 configuration: map-underscore-to-camel-case: true logging: level: root: INFO com.example: DEBUG # 调整为您的包路径
- 注意:替换 com.example 为实际包路径。若使用 MyBatis 注解(@Mapper),移除 mapper-locations。
-
pom.xml (backend/pom.xml):
xml
<properties> <java.version>17</java.version> <mybatis.spring-boot.version>3.0.3</mybatis.spring-boot.version> </properties> <dependencies> <dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>${mybatis.spring-boot.version}</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </build>
-
db.sql (mysql/db.sql):
sql
CREATE DATABASE IF NOT EXISTS mydatabase; USE mydatabase; CREATE TABLE IF NOT EXISTS users ( id BIGINT AUTO_INCREMENT PRIMARY KEY, username VARCHAR(50) NOT NULL UNIQUE, email VARCHAR(100) NOT NULL ); INSERT IGNORE INTO users (username, email) VALUES ('admin', 'admin@example.com');
- 注意:IF NOT EXISTS 和 INSERT IGNORE 防止重复执行错误。
-
MyBatis Mapper :
-
如果用 XML,放置在 backend/src/main/resources/mapper/(e.g., UserMapper.xml)。
-
如果用注解: java
package com.example.mapper; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Select; @Mapper public interface UserMapper { @Select("SELECT * FROM users WHERE id = #{id}") User findById(Long id); }
-
主类添加 @MapperScan: java
import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication @MapperScan("com.example.mapper") public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
-
Nginx 配置
nginx.conf (nginx/nginx.conf):
nginx
server {
listen 80;
server_name localhost;
root /usr/share/nginx/html;
index index.html;
# React 单页应用路由
location / {
try_files $uri $uri/ /index.html;
}
# 代理 API 到后端
location /api/ {
proxy_pass http://backend:8080/api/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
# 静态资源缓存
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot) {
expires 1y;
access_log off;
add_header Cache-Control "public";
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
步骤 2: Dockerfiles
前端 (Nginx)
Dockerfile.frontend (C:\full-stack\admin-react\Dockerfile.frontend):
dockerfile
# 构建 React 前端
FROM node:20-alpine AS build
WORKDIR /app
COPY frontend/package*.json ./
RUN npm install
COPY frontend/ ./
RUN npm run build
# 运行 Nginx
FROM nginx:1.25-alpine
COPY --from=build /app/dist /usr/share/nginx/html
COPY nginx/nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
后端 (Spring Boot)
Dockerfile.backend (C:\full-stack\admin-react\Dockerfile.backend):
dockerfile
FROM maven:3.8.6-amazoncorretto-17 AS build
WORKDIR /app
COPY backend/pom.xml ./
COPY backend/src ./src
RUN mvn clean package -DskipTests
FROM amazoncorretto:17-alpine
WORKDIR /app
COPY --from=build /app/target/*.jar app.jar
EXPOSE 8080
HEALTHCHECK --interval=30s --timeout=3s CMD wget --quiet --tries=1 --spider http://localhost:8080/actuator/health || exit 1
ENTRYPOINT ["java", "-jar", "/app.jar"]
步骤 3: Docker Compose
docker-compose.yml (C:\full-stack\admin-react\docker-compose.yml):
yaml
services:
frontend:
build:
context: .
dockerfile: Dockerfile.frontend
ports:
- "80:80"
depends_on:
backend:
condition: service_healthy
restart: always
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost"]
interval: 30s
timeout: 3s
retries: 3
backend:
build:
context: .
dockerfile: Dockerfile.backend
ports:
- "8080:8080"
environment:
- SERVER_PORT=8080
- DB_HOST=db
- DB_PORT=3306
- DB_NAME=mydatabase
- DB_USERNAME=root
- DB_PASSWORD=password
depends_on:
db:
condition: service_healthy
restart: always
healthcheck:
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:8080/actuator/health"]
interval: 30s
timeout: 3s
retries: 3
db:
image: mysql:8.0
ports:
- "3306:3306"
environment:
- MYSQL_ROOT_PASSWORD=password
- MYSQL_DATABASE=mydatabase
volumes:
- db-data:/var/lib/mysql
- ./mysql/db.sql:/docker-entrypoint-initdb.d/db.sql
restart: always
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 10s
timeout: 5s
retries: 5
volumes:
db-data:
- 关键点 :
- 移除 image: my-app-frontend:latest 和 image: my-app-backend:latest,直接用 build 指令,Docker Compose 自动构建镜像,无需手动 docker build。
- frontend 暴露 80 端口,浏览器访问 http://localhost。
- backend 不暴露端口,Nginx 通过 proxy_pass http://backend:8080/api/ 访问(backend 是服务名)。
- db 保留 db.sql 初始化。
步骤 4: 本地部署与访问
-
运行 Docker Compose :
bash
cd C:\full-stack\admin-react docker-compose up --build
- --build 强制构建 frontend 和 backend 镜像(从 Dockerfile.frontend 和 Dockerfile.backend)。
- 自动拉取 mysql:8.0(若网络问题,参考下方调试)。
-
浏览器访问 :
-
打开浏览器,输入: text
http://localhost
- Nginx serve React 页面(/usr/share/nginx/html)。
- API 请求(/api/)代理到后端。
-
如果 80 端口被占用,修改 docker-compose.yml 的 frontend 端口(e.g., 8080:80),访问 http://localhost:8080。
-
-
验证 :
-
检查容器:docker-compose ps(确保 frontend, backend, db 状态为 Up)。
-
查看日志: bash
docker logs admin-react-frontend-1 docker logs admin-react-backend-1 docker logs admin-react-db-1
-
确认 MySQL 初始化: bash
docker exec -it admin-react-db-1 mysql -uroot -ppassword -e "use mydatabase; show tables;"
-
步骤 5: 云部署
推送镜像(可选)
如果云部署需要预构建镜像:
-
构建: bash
docker-compose build
-
打标签并推送: bash
docker tag admin-react-frontend yourusername/my-app-frontend:latest docker tag admin-react-backend yourusername/my-app-backend:latest docker push yourusername/my-app-frontend:latest docker push yourusername/my-app-backend:latest
AWS ECS 示例
-
推送镜像到 ECR(若不推送,可在云端用 docker-compose.yml 构建)。
-
Task Definitions :
-
Frontend: json
{ "containerDefinitions": [ { "name": "frontend", "image": "your-ecr-repo/my-app-frontend:latest", "portMappings": [{ "containerPort": 80 }], "dependsOn": [ { "containerName": "backend", "condition": "HEALTHY" } ] } ] }
-
Backend: json
{ "containerDefinitions": [ { "name": "backend", "image": "your-ecr-repo/my-app-backend:latest", "environment": [ { "name": "SERVER_PORT", "value": "8080" }, { "name": "DB_HOST", "value": "your-rds-endpoint" }, { "name": "DB_PORT", "value": "3306" }, { "name": "DB_NAME", "value": "mydatabase" }, { "name": "DB_USERNAME", "value": "admin" } ], "secrets": [ { "name": "DB_PASSWORD", "valueFrom": "arn:aws:ssm:region:account-id:parameter/db-password" } ] } ] }
-
-
Service:Fargate,ALB 绑定到 frontend 的 80 端口。
-
RDS:手动运行 db.sql 初始化。
其他云(GKE/AKS)
- 用 Kubernetes Deployment/Service,frontend 暴露 80 端口(LoadBalancer)。
- 数据库:Cloud SQL 或 Azure MySQL,手动初始化 db.sql。
步骤 6: 调试与注意事项
- 无法访问 http://localhost :
- 检查端口:netstat -aon | findstr :80.
- 确认 Nginx 日志:docker logs admin-react-frontend-1.
- 确保 frontend/dist 构建成功:dir frontend\dist.
- API 请求失败 :
- 验证 Nginx proxy_pass http://backend:8080/api/(backend 是服务名)。
- 检查后端日志:docker logs admin-react-backend-1.
- MySQL 初始化失败 :
- 确认 db.sql 语法:docker exec -it admin-react-db-1 mysql -uroot -ppassword -e "use mydatabase; show tables;".
- 检查 MyBatis 连接:后端日志是否有 SQL 错误。
- Docker Hub 拉取失败 (mysql:8.0):
-
配置镜像源(中国用户): json
// C:\ProgramData\Docker\config\daemon.json { "registry-mirrors": ["https://registry.docker-cn.com"] }
-
重启 Docker:net stop com.docker.service && net start com.docker.service.
-
重试:docker-compose up --build.
-
- 配置修改 :
- 数据库名/密码:改 docker-compose.yml 或云 env vars 的 DB_NAME、DB_PASSWORD.
- 端口:改 frontend 的 ports(e.g., 8080:80)。
总结
- 改进:移除手动 docker build,Docker Compose 的 build 指令自动构建 frontend 和 backend 镜像。前端由 Nginx 提供,省去复制 dist 的步骤。
- 访问:运行 docker-compose up --build,浏览器访问 http://localhost(或 http://localhost:8080 如果改端口)。
- 云部署:推送镜像或直接用 docker-compose.yml 构建,RDS 初始化 db.sql。
- 零错误:健康检查、IF NOT EXISTS、日志调试确保无误。
如果遇到问题(e.g., 页面空白、API 404),分享 docker logs 输出或错误信息,我可进一步协助!