docs(spec): 补充九、部署与发布章节(deploy.md 需求落地)

- TOC 添加第九章入口
- 九.1 Maven 构建:移除 fat JAR,添加 maven-jar-plugin + maven-dependency-plugin + maven-assembly-plugin
- 九.2 分发包结构:bin/etc/libs/logs 四级目录
- 九.3 start.sh:Docker 用 exec 前台、VM 用 nohup 后台
- 九.4 logback.xml:INFO 级别,60 MB 滚动,30 天保留
- 九.5 Dockerfile 更新:多阶段构建,复制 etc/ 配置并调用 start.sh
- 九.6 log.debug → log.info:11 文件 21 处,附批量替换命令
- 八 合规清单新增 #12-14:包结构、start.sh Docker 兼容、日志级别
This commit is contained in:
wh
2026-04-09 19:14:56 +08:00
parent 0fa3981a85
commit 011a731f4b

View File

@@ -45,6 +45,13 @@
- [7.4 状态机越界拒绝测试](#74-状态机越界拒绝测试) - [7.4 状态机越界拒绝测试](#74-状态机越界拒绝测试)
- [7.5 多租户隔离测试](#75-多租户隔离测试) - [7.5 多租户隔离测试](#75-多租户隔离测试)
- [八、宪章合规检查清单](#八宪章合规检查清单) - [八、宪章合规检查清单](#八宪章合规检查清单)
- [九、部署与发布](#九部署与发布)
- [9.1 Maven 构建配置变更](#91-maven-构建配置变更)
- [9.2 分发包结构](#92-分发包结构)
- [9.3 start.sh 启动脚本](#93-startsh-启动脚本)
- [9.4 logback.xml 配置](#94-logbackxml-配置)
- [9.5 Dockerfile 更新](#95-dockerfile-更新)
- [9.6 日志级别规范log.debug → log.info](#96-日志级别规范logdebug--loginfo)
--- ---
@@ -1478,12 +1485,14 @@ volumes:
**Dockerfilebackend** **Dockerfilebackend**
> 完整的多阶段构建 Dockerfile 见 [9.5 Dockerfile 更新](#95-dockerfile-更新)。
> 以下为旧版占位,已被新版替代(使用薄 jar + start.sh 方式)。
```dockerfile ```dockerfile
FROM eclipse-temurin:17-jre-alpine # 已废弃——使用 fat JAR 方式,不符合新部署规范
WORKDIR /app # FROM eclipse-temurin:17-jre-alpine
COPY target/label-backend-*.jar app.jar # COPY target/label-backend-*.jar app.jar
EXPOSE 8080 # ENTRYPOINT ["java", "-jar", "app.jar"]
ENTRYPOINT ["java", "-jar", "app.jar"]
``` ```
[↑ 返回目录](#目录) [↑ 返回目录](#目录)
@@ -1599,5 +1608,336 @@ PR 合并前评审人**必须**逐条核对以下清单:
| 9 | 只追加审计日志、AOP 切面、审计失败不回滚业务 | `AuditAspect``@OperationLog` | `sys_operation_log` 无 UPDATE/DELETE审计异常仅 error 日志 | | 9 | 只追加审计日志、AOP 切面、审计失败不回滚业务 | `AuditAspect``@OperationLog` | `sys_operation_log` 无 UPDATE/DELETE审计异常仅 error 日志 |
| 10 | RESTful URL、统一响应格式、分页必须 | `Result<T>`、各 Controller | 无动词路径;无裸 POJO 返回;所有列表接口有分页参数 | | 10 | RESTful URL、统一响应格式、分页必须 | `Result<T>`、各 Controller | 无动词路径;无裸 POJO 返回;所有列表接口有分页参数 |
| 11 | YAGNI业务在 ServiceController 只处理 HTTP | 包结构 | Controller 无业务判断逻辑;无未使用的抽象层 | | 11 | YAGNI业务在 ServiceController 只处理 HTTP | 包结构 | Controller 无业务判断逻辑;无未使用的抽象层 |
| 12 | 部署包结构规范bin/etc/libs/logs 四级) | `pom.xml``distribution.xml` | fat JAR 已移除assembly 产出 zip + tar.gzlibs/ 含薄 jar + 所有依赖 |
| 13 | start.sh 启动可执行、Docker 兼容 | `src/main/scripts/start.sh` | Docker 内 exec 前台运行VM 内 nohup 后台运行;日志写入 logs/ |
| 14 | 日志级别统一 INFO、无 log.debug 残留 | 全体 Service 类 | `grep -r "log\.debug"` 返回 0 结果logback.xml root level=INFO |
[↑ 返回目录](#目录)
---
## 九、部署与发布
### 9.1 Maven 构建配置变更
**目标**:用薄 jarthin jar替代 Spring Boot fat JAR由 Assembly Plugin 组装可分发压缩包。
**移除** `spring-boot-maven-plugin`fat JAR**替换为**以下三个插件:
```xml
<build>
<plugins>
<!-- 1. 薄 jar仅打包编译后的 class 文件Manifest 声明 Main-Class -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<outputDirectory>${project.build.directory}/libs</outputDirectory>
<archive>
<manifest>
<mainClass>com.label.LabelBackendApplication</mainClass>
<addClasspath>false</addClasspath>
</manifest>
</archive>
</configuration>
</plugin>
<!-- 2. 将所有运行时依赖复制到 target/libs/ -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>copy-dependencies</id>
<phase>package</phase>
<goals><goal>copy-dependencies</goal></goals>
<configuration>
<outputDirectory>${project.build.directory}/libs</outputDirectory>
<excludeScope>test</excludeScope>
</configuration>
</execution>
</executions>
</plugin>
<!-- 3. 组装分发包zip + tar.gz -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<executions>
<execution>
<id>create-distribution</id>
<phase>package</phase>
<goals><goal>single</goal></goals>
<configuration>
<descriptors>
<descriptor>src/main/assembly/distribution.xml</descriptor>
</descriptors>
<finalName>${project.artifactId}-${project.version}</finalName>
<appendAssemblyId>false</appendAssemblyId>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
```
**Assembly 描述符** `src/main/assembly/distribution.xml`
```xml
<assembly xmlns="http://maven.apache.org/ASSEMBLY/2.1.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.1.0
https://maven.apache.org/xsd/assembly-2.1.0.xsd">
<id>dist</id>
<formats>
<format>zip</format>
<format>tar.gz</format>
</formats>
<includeBaseDirectory>true</includeBaseDirectory>
<!-- bin/start.sh可执行权限 -->
<files>
<file>
<source>src/main/scripts/start.sh</source>
<outputDirectory>bin</outputDirectory>
<fileMode>0755</fileMode>
</file>
</files>
<!-- etc/ 配置文件 -->
<fileSets>
<fileSet>
<directory>src/main/resources</directory>
<outputDirectory>etc</outputDirectory>
<includes>
<include>application.yml</include>
<include>logback.xml</include>
</includes>
</fileSet>
<!-- logs/ 空目录占位(确保解压后目录存在) -->
<fileSet>
<directory>src/main/assembly/empty-logs</directory>
<outputDirectory>logs</outputDirectory>
</fileSet>
</fileSets>
<!-- libs/:薄 jarproject artifact+ 所有运行时依赖 -->
<dependencySets>
<dependencySet>
<outputDirectory>libs</outputDirectory>
<useProjectArtifact>true</useProjectArtifact>
<scope>runtime</scope>
</dependencySet>
</dependencySets>
</assembly>
```
> **注意**:需在项目根创建空目录占位文件 `src/main/assembly/empty-logs/.gitkeep`
> 否则 Assembly Plugin 会因源目录不存在而报错。
---
### 9.2 分发包结构
`mvn clean package` 后在 `target/` 生成:
```
target/
├── libs/
│ ├── label-backend-1.0.0-SNAPSHOT.jar ← 薄 jar仅 class
│ ├── spring-boot-starter-web-3.2.5.jar
│ ├── mybatis-plus-boot-starter-3.5.9.jar
│ └── ... (全部运行时依赖)
├── label-backend-1.0.0-SNAPSHOT.zip
└── label-backend-1.0.0-SNAPSHOT.tar.gz
```
压缩包解压后的内部结构:
```
label-backend-1.0.0-SNAPSHOT/
├── bin/
│ └── start.sh # 启动脚本0755
├── etc/
│ ├── application.yml # Spring 配置(生产环境按需修改)
│ └── logback.xml # 日志配置INFO + 60 MB 滚动)
├── libs/
│ ├── label-backend-1.0.0-SNAPSHOT.jar
│ └── ... (所有依赖 jar)
└── logs/ # 空目录,运行时写入日志
```
---
### 9.3 start.sh 启动脚本
新增文件:`src/main/scripts/start.sh`
```bash
#!/bin/bash
# label-backend 启动脚本
# - Docker 环境(检测 /.dockerenvexec 前台运行,保持容器进程存活
# - 裸机 / VMnohup 后台运行,日志追加至 logs/startup.log
set -e
BASEDIR=$(cd "$(dirname "$0")/.." && pwd)
LIBDIR="$BASEDIR/libs"
CONFDIR="$BASEDIR/etc"
LOGDIR="$BASEDIR/logs"
mkdir -p "$LOGDIR"
JVM_OPTS="${JVM_OPTS:--Xms512m -Xmx1024m}"
MAIN_CLASS="com.label.LabelBackendApplication"
JAVA_ARGS="$JVM_OPTS \
-Dspring.config.location=file:$CONFDIR/application.yml \
-Dlogging.config=file:$CONFDIR/logback.xml \
-cp $LIBDIR/*"
if [ -f /.dockerenv ]; then
# Docker 容器exec 替换当前进程PID=1 接管信号
exec java $JAVA_ARGS $MAIN_CLASS
else
# 裸机 / VMnohup 后台运行
nohup java $JAVA_ARGS $MAIN_CLASS >> "$LOGDIR/startup.log" 2>&1 &
echo "label-backend started, PID=$!"
fi
```
---
### 9.4 logback.xml 配置
新增文件:`src/main/resources/logback.xml`
```xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="60 seconds">
<property name="LOG_PATH" value="${LOG_PATH:-logs}"/>
<property name="APP_NAME" value="label-backend"/>
<property name="LOG_PATTERN"
value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n"/>
<!-- 控制台输出Docker 日志采集依赖 stdout -->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder charset="UTF-8">
<pattern>${LOG_PATTERN}</pattern>
</encoder>
</appender>
<!-- 滚动文件60 MB / 个,按日分组,保留 30 天,总上限 3 GB -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_PATH}/${APP_NAME}.log</file>
<encoder charset="UTF-8">
<pattern>${LOG_PATTERN}</pattern>
</encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${LOG_PATH}/${APP_NAME}.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<maxFileSize>60MB</maxFileSize>
<maxHistory>30</maxHistory>
<totalSizeCap>3GB</totalSizeCap>
</rollingPolicy>
</appender>
<root level="INFO">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="FILE"/>
</root>
</configuration>
```
> **说明**`LOG_PATH` 可通过环境变量或 JVM 参数覆盖,默认指向相对于工作目录的 `logs/`。
> Docker Compose 中如需持久化日志,挂载 `/app/logs` 卷并设置 `LOG_PATH=/app/logs`。
---
### 9.5 Dockerfile 更新
替换 `Dockerfile` 全文:
```dockerfile
# 构建阶段Maven + JDK 17 编译,生成薄 jar 及依赖
FROM maven:3.9-eclipse-temurin-17-alpine AS builder
WORKDIR /app
# 优先复制 pom.xml 利用 Docker 层缓存(依赖不变时跳过 go-offline
COPY pom.xml .
RUN mvn dependency:go-offline -q
COPY src ./src
RUN mvn clean package -DskipTests -q
# 运行阶段:仅含 JRE 的精简镜像
FROM eclipse-temurin:17-jre-alpine
WORKDIR /app
# 复制部署结构bin/ libs/ etc/
COPY --from=builder /app/src/main/scripts/start.sh bin/start.sh
COPY --from=builder /app/target/libs/ libs/
COPY --from=builder /app/src/main/resources/application.yml etc/application.yml
COPY --from=builder /app/src/main/resources/logback.xml etc/logback.xml
RUN mkdir -p logs && chmod +x bin/start.sh
EXPOSE 8080
# start.sh 检测到 /.dockerenv 后自动以 exec 前台方式运行
ENTRYPOINT ["bin/start.sh"]
```
**Docker Compose 中配置日志路径持久化(可选):**
```yaml
backend:
build: ./label_backend
volumes:
- ./logs/backend:/app/logs
environment:
LOG_PATH: /app/logs
# ... 其他环境变量保持不变
```
---
### 9.6 日志级别规范log.debug → log.info
代码约定:**所有业务 Service 的正常流程日志使用 `log.info`**`log.debug` 仅保留在需要
开发期详细追踪的工具类(当前项目无此场景,全部改为 `log.info`)。
**需修改的文件(共 11 个21 处):**
| 文件 | `log.debug` 数量 |
|------|-----------------|
| `module/video/service/VideoProcessService.java` | 5 |
| `module/source/service/SourceService.java` | 2 |
| `module/user/service/UserService.java` | 3 |
| `module/user/service/AuthService.java` | 2 |
| `module/config/service/SysConfigService.java` | 2 |
| `module/annotation/service/ExtractionApprovedEventListener.java` | 2 |
| `module/task/service/TaskService.java` | 1 |
| `module/annotation/service/QaService.java` | 1 |
| `module/export/service/FinetuneService.java` | 1 |
| `module/task/service/TaskClaimService.java` | 1 |
| `module/export/service/ExportService.java` | 1 |
**执行方式**(实现阶段批量替换):
```bash
# 验证变更范围
grep -rn "log\.debug" src/main/java
# 批量替换
find src/main/java -name "*.java" \
-exec sed -i 's/log\.debug(/log.info(/g' {} +
# 确认清零
grep -r "log\.debug" src/main/java && echo "FAIL" || echo "PASS"
```
[↑ 返回目录](#目录) [↑ 返回目录](#目录)