Files
smartClean/docs/自动化部署方案.md
xqzp2026 8373460096 feat: 添加自动化部署方案(Docker + 远程服务器两套方案)
- 新增 deploy/docker/:Docker 本机模拟部署,含 Dockerfile、docker-compose、deploy.sh 一键脚本
- 新增 deploy/remote/:远程服务器部署,含 SSH 自动上传、重启、回滚脚本
- 新增 deploy/README.md:完整使用手册,含现状分析、落地调整工作清单、命令速查
- 新增 build.sh/start.sh:本地构建和启动脚本(含飞书通知)
- 新增前端 .env.docker 环境配置,API 指向测试服务器
- 前端 package.json 新增 build-docker 命令
- 更新 .gitignore:排除 IDE 配置、SQL 数据、Docker 敏感文件
- 前端 UI 样式优化(多个页面组件)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 18:41:15 +09:30

648 lines
19 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# SmartClean 本地模拟自动化打包部署方案
## 一、目标
在本机用 Docker 模拟真实服务器环境,实现 **一键构建 → 镜像打包 → 部署 → 健康检查 → 回滚 → 飞书通知** 全流程自动化,为后续迁移到远程服务器 / CI/CD 打基础。
## 二、整体架构
```
开发者本机
├── 源码 (smartclean/)
├── deploy.sh 一键触发
│ ├── 1. Maven/npm 构建产物
│ ├── 2. Docker 镜像打包(带版本 tag
│ ├── 3. Docker Compose 部署(替换容器)
│ ├── 4. 健康检查(轮询接口)
│ ├── 5. 失败自动回滚(切回上一版本镜像)
│ └── 6. 飞书 Webhook 通知结果
└── Docker 容器组(模拟生产服务器)
┌──────────────────────────────────────────────┐
│ smartclean-front (Nginx, 端口 80) │
│ smartclean-web (Tomcat 8.5, 端口 18095) │
│ smartclean-task (Spring Boot, 端口 18097) │
│ smartclean-mysql (MySQL 5.7, 端口 3307) │
│ smartclean-redis (Redis 6, 端口 6380) │
└──────────────────────────────────────────────┘
```
> 端口刻意与本机开发环境错开,两套环境完全独立、互不干扰。
## 三、新增文件结构
```
smartclean/
└── deploy/
├── docker-compose.yml # 服务编排定义
├── .env # 环境变量(密码等敏感信息,不入 git
├── Dockerfile.web # Web 服务镜像Tomcat + ROOT.war
├── Dockerfile.task # Task 服务镜像JRE + JAR
├── Dockerfile.front # 前端镜像Nginx + 静态文件)
├── conf/
│ ├── nginx.conf # Nginx 反代配置(前端 → 后端)
│ ├── application-docker.yml # Web 服务 Docker 专用 Spring 配置
│ └── application-task-docker.yml # Task 服务 Docker 专用 Spring 配置
├── init-sql/
│ └── init.sh # 首次启动时自动建库导数据
├── deploy.sh # 一键部署脚本(主入口)
└── rollback.sh # 一键回滚脚本
```
## 四、各组件详细设计
### 4.1 Docker Compose 编排
```yaml
# deploy/docker-compose.yml
version: '3.8'
services:
# ==================== 基础设施 ====================
mysql:
image: mysql:5.7
container_name: smartclean-mysql
ports:
- "3307:3306"
environment:
MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
volumes:
- mysql_data:/var/lib/mysql
- ../sql:/docker-entrypoint-initdb.d # 首次启动自动导入 SQL
command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 10s
retries: 5
networks:
- smartclean
redis:
image: redis:6-alpine
container_name: smartclean-redis
ports:
- "6380:6379"
command: redis-server --requirepass ${REDIS_PASSWORD}
volumes:
- redis_data:/data
healthcheck:
test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD}", "ping"]
interval: 10s
retries: 3
networks:
- smartclean
# ==================== 应用服务 ====================
web:
image: smartclean-web:${VERSION:-latest}
container_name: smartclean-web
build:
context: ..
dockerfile: deploy/Dockerfile.web
ports:
- "18095:8095"
depends_on:
mysql:
condition: service_healthy
redis:
condition: service_healthy
environment:
SPRING_PROFILES_ACTIVE: docker
volumes:
- ./conf/application-docker.yml:/app/config/application-docker.yml
- web_logs:/app/logs
healthcheck:
test: ["CMD", "curl", "-sf", "http://localhost:8095/dropDown/districtTree"]
interval: 15s
timeout: 5s
retries: 5
start_period: 60s
restart: on-failure:3
networks:
- smartclean
task:
image: smartclean-task:${VERSION:-latest}
container_name: smartclean-task
build:
context: ..
dockerfile: deploy/Dockerfile.task
ports:
- "18097:8097"
depends_on:
mysql:
condition: service_healthy
redis:
condition: service_healthy
environment:
SPRING_PROFILES_ACTIVE: docker
volumes:
- ./conf/application-task-docker.yml:/app/config/application-task-docker.yml
- task_logs:/app/logs
restart: on-failure:3
networks:
- smartclean
frontend:
image: smartclean-front:${VERSION:-latest}
container_name: smartclean-front
build:
context: ..
dockerfile: deploy/Dockerfile.front
ports:
- "80:80"
depends_on:
- web
volumes:
- ./conf/nginx.conf:/etc/nginx/conf.d/default.conf
healthcheck:
test: ["CMD", "curl", "-sf", "http://localhost/"]
interval: 10s
retries: 3
restart: on-failure:3
networks:
- smartclean
volumes:
mysql_data:
redis_data:
web_logs:
task_logs:
networks:
smartclean:
driver: bridge
```
### 4.2 Dockerfile
#### Web 服务Tomcat + WAR
```dockerfile
# deploy/Dockerfile.web
FROM maven:3.8-openjdk-8 AS builder
WORKDIR /src
COPY backend/xiaoqu-intellectual-public/ xiaoqu-intellectual-public/
COPY backend/xiaoqu-intellectual-web/ xiaoqu-intellectual-web/
RUN cd xiaoqu-intellectual-public && mvn clean install -q -DskipTests \
&& cd ../xiaoqu-intellectual-web && mvn clean package -q -DskipTests
FROM tomcat:8.5-jdk8-temurin
RUN rm -rf /usr/local/tomcat/webapps/*
COPY --from=builder /src/xiaoqu-intellectual-web/target/ROOT.war /usr/local/tomcat/webapps/
# 注入外部配置目录
ENV SPRING_CONFIG_ADDITIONAL_LOCATION=/app/config/
RUN mkdir -p /app/config /app/logs
EXPOSE 8095
```
#### Task 服务Spring Boot JAR
```dockerfile
# deploy/Dockerfile.task
FROM maven:3.8-openjdk-8 AS builder
WORKDIR /src
COPY backend/xiaoqu-intellectual-public/ xiaoqu-intellectual-public/
COPY backend/xiaoqu-intellectual-task/ xiaoqu-intellectual-task/
RUN cd xiaoqu-intellectual-public && mvn clean install -q -DskipTests \
&& cd ../xiaoqu-intellectual-task && mvn clean package -q -DskipTests
FROM openjdk:8-jre-slim
WORKDIR /app
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
COPY --from=builder /src/xiaoqu-intellectual-task/target/*.jar app.jar
RUN mkdir -p /app/config /app/logs
EXPOSE 8097
ENTRYPOINT ["java", "-jar", "app.jar", \
"--spring.config.additional-location=/app/config/"]
```
#### 前端(多阶段构建 → Nginx
```dockerfile
# deploy/Dockerfile.front
FROM node:16-alpine AS builder
WORKDIR /app
COPY frontend/witcleansystem/package*.json ./
RUN npm ci --registry=https://registry.npmmirror.com
COPY frontend/witcleansystem/ ./
RUN npm run build
FROM nginx:1.24-alpine
RUN rm -rf /usr/share/nginx/html/*
COPY --from=builder /app/dist /usr/share/nginx/html
EXPOSE 80
```
### 4.3 Nginx 反代配置
```nginx
# deploy/conf/nginx.conf
server {
listen 80;
server_name localhost;
# 前端静态文件
location / {
root /usr/share/nginx/html;
index index.html;
try_files $uri $uri/ /index.html; # SPA history fallback
}
# API 反代到后端 Web 服务
location /api/ {
proxy_pass http://web:8095/;
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|woff2?)$ {
root /usr/share/nginx/html;
expires 7d;
add_header Cache-Control "public, immutable";
}
}
```
### 4.4 Docker 专用 Spring 配置
```yaml
# deploy/conf/application-docker.yml
# 覆盖 application-test.yml 中的远程地址,指向 Docker 容器
server:
port: 8095
redis:
host: redis # Docker 容器名
port: 6379
password: ${REDIS_PASSWORD:kaixinjiuhao}
spring:
datasource:
db1:
url: jdbc:mysql://mysql:3306/xiaoqu_comples_d?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai&allowMultiQueries=true&allowPublicKeyRetrieval=true
username: root
password: ${DB_PASSWORD:kaixinjiuhao}
db2:
url: jdbc:mysql://mysql:3306/xiaoqu_intellectual_d?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai&allowMultiQueries=true&allowPublicKeyRetrieval=true
username: root
password: ${DB_PASSWORD:kaixinjiuhao}
xxl:
job:
admin:
addresses: http://xxl-job-admin:8080/xxl-job-admin # 可选,本地模拟可留空
```
### 4.5 环境变量文件
```bash
# deploy/.env不入 git
DB_PASSWORD=kaixinjiuhao
REDIS_PASSWORD=kaixinjiuhao
VERSION=latest
FEISHU_WEBHOOK=https://open.feishu.cn/open-apis/bot/v2/hook/5703e8cc-6998-46a6-af9d-8c5102cc8c1e
```
## 五、一键部署脚本
```bash
# deploy/deploy.sh
#!/bin/bash
#
# SmartClean 一键部署脚本(本地 Docker 模拟)
#
# 用法:
# ./deploy.sh # 部署所有服务
# ./deploy.sh web # 仅重建部署 Web 服务
# ./deploy.sh task # 仅重建部署 Task 服务
# ./deploy.sh front # 仅重建部署前端
# ./deploy.sh rollback # 回滚到上一版本
set -e
DEPLOY_DIR="$(cd "$(dirname "$0")" && pwd)"
ROOT_DIR="$(dirname "$DEPLOY_DIR")"
source "$DEPLOY_DIR/.env"
# ===== 版本号:日期-git短hash =====
GIT_HASH=$(cd "$ROOT_DIR" && git rev-parse --short HEAD)
VERSION="v$(date +%Y%m%d)-${GIT_HASH}"
BACKUP_FILE="$DEPLOY_DIR/.last-version"
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
log_info() { echo -e "${GREEN}[INFO]${NC} $1"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
# ===== 飞书通知 =====
notify_feishu() {
local title="$1" content="$2" color="$3"
curl -s -X POST "$FEISHU_WEBHOOK" \
-H "Content-Type: application/json" \
-d "{
\"msg_type\": \"interactive\",
\"card\": {
\"header\": {\"title\":{\"tag\":\"plain_text\",\"content\":\"$title\"},\"template\":\"$color\"},
\"elements\": [{\"tag\":\"markdown\",\"content\":\"$content\"}]
}
}" > /dev/null 2>&1
}
# ===== 备份当前版本号 =====
backup_version() {
if [ -f "$DEPLOY_DIR/.current-version" ]; then
cp "$DEPLOY_DIR/.current-version" "$BACKUP_FILE"
log_info "已备份当前版本: $(cat "$BACKUP_FILE")"
fi
}
# ===== 构建镜像 =====
build_images() {
local target="${1:-all}"
local start_time=$(date +%s)
cd "$DEPLOY_DIR"
case "$target" in
web)
log_info "构建 Web 镜像 ($VERSION)..."
docker compose build web
;;
task)
log_info "构建 Task 镜像 ($VERSION)..."
docker compose build task
;;
front)
log_info "构建前端镜像 ($VERSION)..."
docker compose build frontend
;;
all)
log_info "构建全部镜像 ($VERSION)..."
docker compose build web task frontend
;;
esac
local elapsed=$(( $(date +%s) - start_time ))
log_info "镜像构建完成 (${elapsed}s)"
}
# ===== 部署服务 =====
deploy_services() {
local target="${1:-all}"
cd "$DEPLOY_DIR"
export VERSION
# 确保基础设施先启动
log_info "确保 MySQL + Redis 运行中..."
docker compose up -d mysql redis
log_info "等待数据库就绪..."
docker compose exec mysql mysqladmin ping -h localhost --wait=30 --silent 2>/dev/null
case "$target" in
web) docker compose up -d --no-deps web ;;
task) docker compose up -d --no-deps task ;;
front) docker compose up -d --no-deps frontend ;;
all) docker compose up -d web task frontend ;;
esac
log_info "容器已启动,开始健康检查..."
}
# ===== 健康检查 =====
healthcheck() {
local target="${1:-all}"
local max_retries=20
local interval=5
# 检查 Web 服务
if [ "$target" = "all" ] || [ "$target" = "web" ]; then
log_info "检查 Web 服务..."
for i in $(seq 1 $max_retries); do
if curl -sf http://localhost:18095/dropDown/districtTree > /dev/null 2>&1; then
log_info "✅ Web 服务健康 (第${i}次检查)"
break
fi
if [ $i -eq $max_retries ]; then
log_error "❌ Web 服务健康检查失败"
return 1
fi
sleep $interval
done
fi
# 检查前端
if [ "$target" = "all" ] || [ "$target" = "front" ]; then
log_info "检查前端服务..."
for i in $(seq 1 $max_retries); do
if curl -sf http://localhost:80/ > /dev/null 2>&1; then
log_info "✅ 前端服务健康 (第${i}次检查)"
break
fi
if [ $i -eq $max_retries ]; then
log_error "❌ 前端服务健康检查失败"
return 1
fi
sleep $interval
done
fi
return 0
}
# ===== 回滚 =====
rollback() {
if [ ! -f "$BACKUP_FILE" ]; then
log_error "没有可回滚的版本"
exit 1
fi
local old_version=$(cat "$BACKUP_FILE")
log_warn "回滚到版本: $old_version"
export VERSION="$old_version"
cd "$DEPLOY_DIR"
docker compose up -d web task frontend
echo "$old_version" > "$DEPLOY_DIR/.current-version"
log_info "回滚完成"
notify_feishu "⚠️ SmartClean 已回滚" \
"**回滚版本:** $old_version\\n**触发原因:** 健康检查失败" \
"yellow"
}
# ===== 主流程 =====
TARGET="${1:-all}"
DEPLOY_START=$(date +%s)
BRANCH=$(cd "$ROOT_DIR" && git rev-parse --abbrev-ref HEAD)
COMMIT=$(cd "$ROOT_DIR" && git log -1 --format='%h %s')
if [ "$TARGET" = "rollback" ]; then
rollback
exit 0
fi
log_info "=============================="
log_info " SmartClean 自动化部署"
log_info " 版本: $VERSION"
log_info " 分支: $BRANCH"
log_info " 目标: $TARGET"
log_info "=============================="
# 1. 备份当前版本
backup_version
# 2. 构建镜像
build_images "$TARGET"
# 3. 部署
deploy_services "$TARGET"
# 4. 健康检查
if healthcheck "$TARGET"; then
# 记录当前版本
echo "$VERSION" > "$DEPLOY_DIR/.current-version"
ELAPSED=$(( $(date +%s) - DEPLOY_START ))
log_info "=============================="
log_info " ✅ 部署成功!耗时 ${ELAPSED}s"
log_info " 前端: http://localhost"
log_info " Web: http://localhost:18095"
log_info " Task: http://localhost:18097"
log_info "=============================="
notify_feishu "✅ SmartClean 部署成功" \
"**版本:** $VERSION\\n**分支:** $BRANCH\\n**提交:** $COMMIT\\n**目标:** $TARGET\\n**耗时:** ${ELAPSED}s" \
"green"
else
log_error "健康检查失败,自动回滚..."
rollback
ELAPSED=$(( $(date +%s) - DEPLOY_START ))
notify_feishu "❌ SmartClean 部署失败(已回滚)" \
"**版本:** $VERSION\\n**分支:** $BRANCH\\n**提交:** $COMMIT\\n**耗时:** ${ELAPSED}s\\n**状态:** 已自动回滚" \
"red"
exit 1
fi
```
## 六、回滚脚本
```bash
# deploy/rollback.sh
#!/bin/bash
# 快捷回滚入口
DEPLOY_DIR="$(cd "$(dirname "$0")" && pwd)"
exec "$DEPLOY_DIR/deploy.sh" rollback
```
## 七、使用方式
### 首次启动
```bash
cd deploy/
# 1. 创建 .env 文件(设置密码等)
cp .env.example .env
# 2. 一键构建 + 部署全部服务
./deploy.sh
```
### 日常使用
```bash
# 修改了后端代码,只重建部署 Web 服务
./deploy.sh web
# 修改了前端代码,只重建部署前端
./deploy.sh front
# 全量部署
./deploy.sh
# 出问题了,一键回滚
./deploy.sh rollback
# 或
./rollback.sh
```
### 查看日志
```bash
# 查看所有服务日志
docker compose -f deploy/docker-compose.yml logs -f
# 只看 Web 服务
docker compose -f deploy/docker-compose.yml logs -f web
# 查看容器状态
docker compose -f deploy/docker-compose.yml ps
```
### 停止 / 清理
```bash
# 停止所有容器(数据保留)
docker compose -f deploy/docker-compose.yml down
# 停止并删除数据卷(慎用,会丢数据库数据)
docker compose -f deploy/docker-compose.yml down -v
```
## 八、端口映射总览
| 服务 | 容器内端口 | 宿主机端口 | 用途 |
|------|-----------|-----------|------|
| 前端 (Nginx) | 80 | **80** | 浏览器访问入口 |
| Web 后端 | 8095 | **18095** | API 接口(也可通过 Nginx `/api` 访问) |
| Task 后端 | 8097 | **18097** | 定时任务服务 |
| MySQL | 3306 | **3307** | 本机连接调试用 |
| Redis | 6379 | **6380** | 本机连接调试用 |
> 与本机开发环境 (8079/8095/3306/6379) 完全隔离。
## 九、与本机开发环境的对比
```
┌─────────────────────────────┐ ┌─────────────────────────────┐
│ 本机开发环境(已有) │ │ Docker 模拟部署(新增) │
│ │ │ │
│ npm run dev → :8079 │ │ Nginx → :80 │
│ mvn spring-boot:run → :8095│ │ Tomcat 容器 → :18095 │
│ 本机 MySQL → :3306 │ │ MySQL 容器 → :3307 │
│ 本机 Redis → :6379 │ │ Redis 容器 → :6380 │
│ │ │ │
│ 用途: 开发调试、热重载 │ │ 用途: 模拟生产部署流程 │
└─────────────────────────────┘ └─────────────────────────────┘
```
## 十、后续演进路径
本方案在本机验证通过后,可以低成本迁移到真实环境:
| 阶段 | 动作 | 改动量 |
|------|------|--------|
| **当前** | 本机 Docker 模拟 | 本方案 |
| **阶段 2** | 远程服务器部署 | deploy.sh 加 SSH 远程执行,或把镜像 push 到私有 Registry |
| **阶段 3** | Gitea Actions CI/CD | 把 deploy.sh 的逻辑搬到 `.gitea/workflows/deploy.yml`push 自动触发 |
| **阶段 4** | 多环境 | docker-compose.prod.yml 覆盖生产配置,同一套镜像部署到不同环境 |
每个阶段都是增量改动,核心的 Dockerfile 和 docker-compose.yml 不需要重写。