停止追踪specs,docs等目录文件
This commit is contained in:
@@ -1,517 +0,0 @@
|
||||
# Deploy Optimization Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** 将 label_backend 从 Spring Boot fat JAR 方式改造为薄 jar + Maven Assembly 分发包,并统一日志级别为 INFO。
|
||||
|
||||
**Architecture:** 移除 `spring-boot-maven-plugin`,改用 `maven-jar-plugin`(薄 jar)+ `maven-dependency-plugin`(复制依赖到 `target/libs/`)+ `maven-assembly-plugin`(组装 zip/tar.gz)。新增 `start.sh`(Docker/VM 双模式启动)和 `logback.xml`(60 MB 滚动)。Dockerfile 改为多阶段构建,从 `target/libs/` 和 `src/main/resources/` 复制构建产物。
|
||||
|
||||
**Tech Stack:** Maven 3.9, JDK 17, Spring Boot 3.2.5, maven-assembly-plugin 3.x, logback 1.4.x (Spring Boot 管理版本)
|
||||
|
||||
---
|
||||
|
||||
## 文件结构
|
||||
|
||||
| 操作 | 路径 | 说明 |
|
||||
|------|------|------|
|
||||
| 新建 | `src/main/resources/logback.xml` | INFO 级,60 MB 滚动日志配置 |
|
||||
| 新建 | `src/main/scripts/start.sh` | Docker/VM 双模式启动脚本 |
|
||||
| 新建 | `src/main/assembly/distribution.xml` | Assembly 描述符 |
|
||||
| 新建 | `src/main/assembly/empty-logs/.gitkeep` | logs/ 目录占位(Assembly 引用) |
|
||||
| 修改 | `pom.xml` | 替换 spring-boot-maven-plugin |
|
||||
| 修改 | `Dockerfile` | 多阶段构建,复制 etc/ + libs/ |
|
||||
| 批量修改 | 11 个 Service 类 | `log.debug` → `log.info`(21 处) |
|
||||
|
||||
---
|
||||
|
||||
## Task 1: 创建 logback.xml
|
||||
|
||||
**Files:**
|
||||
- Create: `src/main/resources/logback.xml`
|
||||
|
||||
- [ ] **Step 1: 创建 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>
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 验证文件存在**
|
||||
|
||||
```bash
|
||||
ls src/main/resources/logback.xml
|
||||
```
|
||||
|
||||
预期输出:`src/main/resources/logback.xml`
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add src/main/resources/logback.xml
|
||||
git commit -m "feat(deploy): 添加 logback.xml(INFO 级,60 MB 滚动)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 2: 创建 start.sh 启动脚本
|
||||
|
||||
**Files:**
|
||||
- Create: `src/main/scripts/start.sh`
|
||||
|
||||
- [ ] **Step 1: 创建目录并写入 start.sh**
|
||||
|
||||
```bash
|
||||
mkdir -p src/main/scripts
|
||||
```
|
||||
|
||||
文件内容 `src/main/scripts/start.sh`:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# label-backend 启动脚本
|
||||
# - Docker 环境(检测 /.dockerenv):exec 前台运行,保持容器进程存活
|
||||
# - 裸机 / VM:nohup 后台运行,日志追加至 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
|
||||
# 裸机 / VM:nohup 后台运行
|
||||
nohup java $JAVA_ARGS $MAIN_CLASS >> "$LOGDIR/startup.log" 2>&1 &
|
||||
echo "label-backend started, PID=$!"
|
||||
fi
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 验证文件存在**
|
||||
|
||||
```bash
|
||||
ls src/main/scripts/start.sh
|
||||
```
|
||||
|
||||
预期输出:`src/main/scripts/start.sh`
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add src/main/scripts/start.sh
|
||||
git commit -m "feat(deploy): 添加 start.sh(Docker exec / VM nohup 双模式)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 3: 创建 Assembly 描述符和目录占位
|
||||
|
||||
**Files:**
|
||||
- Create: `src/main/assembly/distribution.xml`
|
||||
- Create: `src/main/assembly/empty-logs/.gitkeep`
|
||||
|
||||
- [ ] **Step 1: 创建 assembly 目录结构**
|
||||
|
||||
```bash
|
||||
mkdir -p src/main/assembly/empty-logs
|
||||
touch src/main/assembly/empty-logs/.gitkeep
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 创建 distribution.xml**
|
||||
|
||||
文件内容 `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(0755 可执行) -->
|
||||
<files>
|
||||
<file>
|
||||
<source>src/main/scripts/start.sh</source>
|
||||
<outputDirectory>bin</outputDirectory>
|
||||
<fileMode>0755</fileMode>
|
||||
</file>
|
||||
</files>
|
||||
|
||||
<fileSets>
|
||||
<!-- etc/:application.yml + logback.xml -->
|
||||
<fileSet>
|
||||
<directory>src/main/resources</directory>
|
||||
<outputDirectory>etc</outputDirectory>
|
||||
<includes>
|
||||
<include>application.yml</include>
|
||||
<include>logback.xml</include>
|
||||
</includes>
|
||||
</fileSet>
|
||||
|
||||
<!-- libs/:薄 jar + 所有运行时依赖 -->
|
||||
<fileSet>
|
||||
<directory>${project.build.directory}/libs</directory>
|
||||
<outputDirectory>libs</outputDirectory>
|
||||
<includes>
|
||||
<include>**/*.jar</include>
|
||||
</includes>
|
||||
</fileSet>
|
||||
|
||||
<!-- logs/ 空目录占位 -->
|
||||
<fileSet>
|
||||
<directory>src/main/assembly/empty-logs</directory>
|
||||
<outputDirectory>logs</outputDirectory>
|
||||
</fileSet>
|
||||
</fileSets>
|
||||
|
||||
</assembly>
|
||||
```
|
||||
|
||||
- [ ] **Step 3: 验证文件存在**
|
||||
|
||||
```bash
|
||||
ls src/main/assembly/distribution.xml src/main/assembly/empty-logs/.gitkeep
|
||||
```
|
||||
|
||||
预期输出:两个文件路径均显示
|
||||
|
||||
- [ ] **Step 4: Commit**
|
||||
|
||||
```bash
|
||||
git add src/main/assembly/
|
||||
git commit -m "feat(deploy): 添加 Assembly 描述符 distribution.xml"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 4: 更新 pom.xml(替换 spring-boot-maven-plugin)
|
||||
|
||||
**Files:**
|
||||
- Modify: `pom.xml`
|
||||
|
||||
- [ ] **Step 1: 替换 `<build><plugins>` 段落**
|
||||
|
||||
将 `pom.xml` 的 `<build>` 段(当前仅含 spring-boot-maven-plugin)替换为:
|
||||
|
||||
```xml
|
||||
<build>
|
||||
<plugins>
|
||||
|
||||
<!-- 薄 jar:仅打包编译后的 class,输出到 target/libs/ -->
|
||||
<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>
|
||||
|
||||
<!-- 将所有运行时依赖复制到 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>
|
||||
<includeScope>runtime</includeScope>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<!-- 组装分发包(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>
|
||||
```
|
||||
|
||||
即用上述内容完整替换 `pom.xml` 中现有的 `<build>...</build>` 块(原内容为含 spring-boot-maven-plugin 的单插件配置)。
|
||||
|
||||
- [ ] **Step 2: 验证语法并试构建**
|
||||
|
||||
```bash
|
||||
mvn validate -q
|
||||
```
|
||||
|
||||
预期输出:无错误(只做 pom.xml 解析校验,不实际编译)
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add pom.xml
|
||||
git commit -m "feat(deploy): pom.xml 替换 fat JAR → 薄 jar + maven-dependency + maven-assembly"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 5: 更新 Dockerfile
|
||||
|
||||
**Files:**
|
||||
- Modify: `Dockerfile`
|
||||
|
||||
- [ ] **Step 1: 替换 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"]
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 验证 Dockerfile 语法**
|
||||
|
||||
```bash
|
||||
docker build --no-cache --dry-run . 2>/dev/null || echo "docker not available; syntax check skipped"
|
||||
```
|
||||
|
||||
预期:无语法错误(或 docker 不可用时跳过)
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add Dockerfile
|
||||
git commit -m "feat(deploy): Dockerfile 改为多阶段构建(薄 jar + start.sh)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 6: 批量替换 log.debug → log.info(11 文件,21 处)
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/main/java/com/label/module/video/service/VideoProcessService.java` (5 处)
|
||||
- Modify: `src/main/java/com/label/module/source/service/SourceService.java` (2 处)
|
||||
- Modify: `src/main/java/com/label/module/user/service/UserService.java` (3 处)
|
||||
- Modify: `src/main/java/com/label/module/user/service/AuthService.java` (2 处)
|
||||
- Modify: `src/main/java/com/label/module/config/service/SysConfigService.java` (2 处)
|
||||
- Modify: `src/main/java/com/label/module/annotation/service/ExtractionApprovedEventListener.java` (2 处)
|
||||
- Modify: `src/main/java/com/label/module/task/service/TaskService.java` (1 处)
|
||||
- Modify: `src/main/java/com/label/module/annotation/service/QaService.java` (1 处)
|
||||
- Modify: `src/main/java/com/label/module/export/service/FinetuneService.java` (1 处)
|
||||
- Modify: `src/main/java/com/label/module/task/service/TaskClaimService.java` (1 处)
|
||||
- Modify: `src/main/java/com/label/module/export/service/ExportService.java` (1 处)
|
||||
|
||||
- [ ] **Step 1: 批量替换**
|
||||
|
||||
```bash
|
||||
find src/main/java -name "*.java" \
|
||||
-exec sed -i 's/log\.debug(/log.info(/g' {} +
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 验证替换完整,无 log.debug 残留**
|
||||
|
||||
```bash
|
||||
grep -r "log\.debug" src/main/java && echo "FAIL: 仍有 log.debug 残留" || echo "PASS: 无 log.debug"
|
||||
```
|
||||
|
||||
预期输出:`PASS: 无 log.debug`
|
||||
|
||||
- [ ] **Step 3: 验证替换数量正确(应有 21 处 log.info 新增)**
|
||||
|
||||
```bash
|
||||
git diff --stat src/main/java | tail -1
|
||||
```
|
||||
|
||||
预期:显示 11 个文件,21 处改动(`21 insertions(+), 21 deletions(-)`)
|
||||
|
||||
- [ ] **Step 4: Commit**
|
||||
|
||||
```bash
|
||||
git add src/main/java/
|
||||
git commit -m "refactor(log): log.debug 全量替换为 log.info(11 文件,21 处)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 7: 全量构建验证
|
||||
|
||||
**Files:** 只读操作,不修改文件
|
||||
|
||||
- [ ] **Step 1: 执行完整构建(跳过测试)**
|
||||
|
||||
```bash
|
||||
mvn clean package -DskipTests
|
||||
```
|
||||
|
||||
预期:`BUILD SUCCESS`,无 ERROR 输出
|
||||
|
||||
- [ ] **Step 2: 验证 target/libs/ 目录存在且包含 jar**
|
||||
|
||||
```bash
|
||||
ls target/libs/*.jar | head -5
|
||||
ls target/libs/label-backend-1.0.0-SNAPSHOT.jar
|
||||
```
|
||||
|
||||
预期:显示薄 jar 及多个依赖 jar
|
||||
|
||||
- [ ] **Step 3: 验证分发包已生成**
|
||||
|
||||
```bash
|
||||
ls target/label-backend-1.0.0-SNAPSHOT.zip
|
||||
ls target/label-backend-1.0.0-SNAPSHOT.tar.gz
|
||||
```
|
||||
|
||||
预期:两个文件均存在
|
||||
|
||||
- [ ] **Step 4: 验证分发包内部结构**
|
||||
|
||||
```bash
|
||||
# 使用 unzip 检查 zip 内容(Windows Git Bash 或 Linux 均可用)
|
||||
unzip -l target/label-backend-1.0.0-SNAPSHOT.zip | grep -E "bin/|etc/|libs/|logs/"
|
||||
```
|
||||
|
||||
预期输出包含:
|
||||
```
|
||||
label-backend-1.0.0-SNAPSHOT/bin/start.sh
|
||||
label-backend-1.0.0-SNAPSHOT/etc/application.yml
|
||||
label-backend-1.0.0-SNAPSHOT/etc/logback.xml
|
||||
label-backend-1.0.0-SNAPSHOT/libs/label-backend-1.0.0-SNAPSHOT.jar
|
||||
label-backend-1.0.0-SNAPSHOT/logs/
|
||||
```
|
||||
|
||||
- [ ] **Step 5: 验证 logback.xml 已包含在 etc/ 内**
|
||||
|
||||
```bash
|
||||
unzip -l target/label-backend-1.0.0-SNAPSHOT.zip | grep logback
|
||||
```
|
||||
|
||||
预期:`label-backend-1.0.0-SNAPSHOT/etc/logback.xml`
|
||||
|
||||
- [ ] **Step 6: Commit 构建验证记录(如有必要则修正问题后提交)**
|
||||
|
||||
若 Step 1-5 全部通过,无需额外提交。若发现问题(如缺少文件、路径错误),修正对应 Task 后重新执行本 Task。
|
||||
|
||||
---
|
||||
|
||||
## 自检
|
||||
|
||||
### Spec 覆盖检查
|
||||
|
||||
| deploy.md 需求 | 对应任务 |
|
||||
|---------------|---------|
|
||||
| 1. 分发包结构 bin/etc/libs/logs | Task 3 (distribution.xml) + Task 7 验证 |
|
||||
| 2. start.sh nohup 后台启动 | Task 2 |
|
||||
| 3. logback.xml INFO + 60 MB 滚动 | Task 1 |
|
||||
| 4. logback.xml + application.yml 在 etc/ | Task 3 (distribution.xml fileSets) |
|
||||
| 5. maven-jar-plugin + maven-dependency-plugin | Task 4 |
|
||||
| 6. Maven Assembly Plugin → zip/tar.gz | Task 3 + Task 4 |
|
||||
| 7. Dockerfile 复制 etc/ + 调用 start.sh | Task 5 |
|
||||
| 8. log.debug → log.info | Task 6 |
|
||||
|
||||
所有 8 条需求均有对应任务。✅
|
||||
|
||||
### 类型一致性
|
||||
|
||||
- `MAIN_CLASS="com.label.LabelBackendApplication"` — 与 `src/main/java/com/label/LabelBackendApplication.java` 一致 ✅
|
||||
- `maven-jar-plugin` 输出路径 `target/libs/` — 与 Dockerfile `COPY --from=builder /app/target/libs/` 一致 ✅
|
||||
- `distribution.xml` `<directory>${project.build.directory}/libs</directory>` — 与 `maven-dependency-plugin` 的 `outputDirectory` 一致 ✅
|
||||
|
||||
## GSTACK REVIEW REPORT
|
||||
|
||||
| Review | Trigger | Why | Runs | Status | Findings |
|
||||
|--------|---------|-----|------|--------|----------|
|
||||
| CEO Review | `/plan-ceo-review` | Scope & strategy | 0 | — | — |
|
||||
| Codex Review | `/codex review` | Independent 2nd opinion | 0 | — | — |
|
||||
| Eng Review | `/plan-eng-review` | Architecture & tests (required) | 0 | — | — |
|
||||
| Design Review | `/plan-design-review` | UI/UX gaps | 0 | — | — |
|
||||
|
||||
**VERDICT:** NO REVIEWS YET — run `/autoplan` for full review pipeline, or individual reviews above.
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,66 +0,0 @@
|
||||
# Auth And Company Optimization Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** Replace the remaining Shiro authorization layer with project-owned Redis token authentication and add company CRUD APIs.
|
||||
|
||||
**Architecture:** Keep the existing UUID token, Redis session storage, and `CompanyContext` tenant injection. Add project-owned `@RequireAuth` and `@RequireRole` annotations plus a Spring MVC `AuthInterceptor`, then remove Shiro config/classes/dependencies. Add `CompanyService` and `CompanyController` for `sys_company` management.
|
||||
|
||||
**Tech Stack:** Java 21, Spring Boot 3.1.5, Spring MVC HandlerInterceptor, RedisTemplate, MyBatis-Plus, JUnit 5, Mockito, AssertJ.
|
||||
|
||||
---
|
||||
|
||||
### Task 1: Replace Shiro With Custom Auth Interceptor
|
||||
|
||||
**Files:**
|
||||
- Create: `src/main/java/com/label/annotation/RequireAuth.java`
|
||||
- Create: `src/main/java/com/label/annotation/RequireRole.java`
|
||||
- Create: `src/main/java/com/label/interceptor/AuthInterceptor.java`
|
||||
- Create: `src/main/java/com/label/common/auth/TokenPrincipal.java`
|
||||
- Create: `src/main/java/com/label/common/context/UserContext.java`
|
||||
- Modify: `src/main/java/com/label/config/ShiroConfig.java`
|
||||
- Modify: `src/main/java/com/label/common/shiro/TokenFilter.java`
|
||||
- Modify: `src/main/java/com/label/common/shiro/BearerToken.java`
|
||||
- Modify: `src/main/java/com/label/common/shiro/UserRealm.java`
|
||||
- Modify: `src/main/java/com/label/controller/*.java`
|
||||
- Modify: `src/main/java/com/label/service/*.java`
|
||||
- Modify: `pom.xml`
|
||||
- Test: `src/test/java/com/label/unit/AuthInterceptorTest.java`
|
||||
|
||||
- [x] Write failing tests for token loading, TTL refresh, role hierarchy, and context cleanup.
|
||||
- [x] Implement annotations, principal, context, and interceptor.
|
||||
- [x] Register the interceptor via Spring MVC config.
|
||||
- [x] Replace controller `@RequiresRoles` usage with `@RequireRole`.
|
||||
- [x] Remove Shiro-only classes, tests, dependencies, and exception handling.
|
||||
- [x] Run `mvn -q "-Dtest=AuthInterceptorTest,OpenApiAnnotationTest" test` and `mvn -q -DskipTests compile`.
|
||||
|
||||
### Task 2: Add Company Management
|
||||
|
||||
**Files:**
|
||||
- Create: `src/main/java/com/label/service/CompanyService.java`
|
||||
- Create: `src/main/java/com/label/controller/CompanyController.java`
|
||||
- Modify: `src/main/java/com/label/mapper/SysUserMapper.java`
|
||||
- Test: `src/test/java/com/label/unit/CompanyServiceTest.java`
|
||||
- Test: `src/test/java/com/label/unit/OpenApiAnnotationTest.java`
|
||||
|
||||
- [x] Write failing tests for create/list/update/status/delete behavior.
|
||||
- [x] Implement service validation and duplicate checks.
|
||||
- [x] Implement admin-only controller endpoints under `/api/companies`.
|
||||
- [x] Run `mvn -q "-Dtest=CompanyServiceTest,OpenApiAnnotationTest" test` and `mvn -q -DskipTests compile`.
|
||||
|
||||
### Task 3: Configuration And Verification
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/main/resources/application.yml`
|
||||
- Modify: `src/test/java/com/label/unit/ApplicationConfigTest.java`
|
||||
|
||||
- [x] Rename `shiro.auth.*` config to `auth.*`.
|
||||
- [x] Update safe defaults and type-aliases package.
|
||||
- [x] Run targeted unit tests and compile.
|
||||
- [x] Run `mvn clean test` once and record any external environment blockers.
|
||||
|
||||
### Verification Notes
|
||||
|
||||
- `mvn -q "-Dtest=LabelBackendApplicationTests,ApplicationConfigTest,AuthInterceptorTest,CompanyServiceTest,OpenApiAnnotationTest" test` passed.
|
||||
- `mvn -q -DskipTests compile` passed.
|
||||
- `mvn clean test` compiled main/test sources and passed unit tests, then failed only because 10 Testcontainers integration tests could not find a valid Docker environment.
|
||||
@@ -1,528 +0,0 @@
|
||||
# label_backend 标准目录扁平化 Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** 把 `com.label.module.*` 与 `com.label.common.aop/common.config` 迁移到规范要求的扁平目录,保证行为不变。
|
||||
|
||||
**Architecture:** 先写迁移守卫测试,再按 `基础设施 -> 数据层 -> 服务层 -> 控制层 -> 清理回归` 的顺序做 `git mv` 和导包修正,每阶段都用最小测试集验证。
|
||||
|
||||
**Tech Stack:** Java 21、Spring Boot 3.1.5、MyBatis-Plus、Shiro、JUnit 5、AssertJ、Maven
|
||||
|
||||
---
|
||||
|
||||
## 目标目录
|
||||
|
||||
- `src/main/java/com/label/annotation`
|
||||
- `src/main/java/com/label/aspect`
|
||||
- `src/main/java/com/label/config`
|
||||
- `src/main/java/com/label/controller`
|
||||
- `src/main/java/com/label/dto`
|
||||
- `src/main/java/com/label/entity`
|
||||
- `src/main/java/com/label/event`
|
||||
- `src/main/java/com/label/listener`
|
||||
- `src/main/java/com/label/mapper`
|
||||
- `src/main/java/com/label/service`
|
||||
- `src/test/java/com/label/unit/PackageStructureMigrationTest.java`
|
||||
|
||||
---
|
||||
|
||||
### Task 1: 锁定基础设施迁移目标
|
||||
|
||||
**Files:**
|
||||
- Create: `src/test/java/com/label/unit/PackageStructureMigrationTest.java`
|
||||
- Modify: `src/main/java/com/label/common/aop/OperationLog.java`
|
||||
- Modify: `src/main/java/com/label/common/aop/AuditAspect.java`
|
||||
- Modify: `src/main/java/com/label/common/config/MybatisPlusConfig.java`
|
||||
- Modify: `src/main/java/com/label/common/config/OpenApiConfig.java`
|
||||
- Modify: `src/main/java/com/label/common/config/RedisConfig.java`
|
||||
- Modify: `src/main/java/com/label/module/annotation/event/ExtractionApprovedEvent.java`
|
||||
- Modify: `src/main/java/com/label/module/annotation/service/ExtractionApprovedEventListener.java`
|
||||
- Modify: `src/main/java/com/label/module/annotation/service/ExtractionService.java`
|
||||
- Test: `src/test/java/com/label/unit/PackageStructureMigrationTest.java`
|
||||
|
||||
- [ ] **Step 1: 写失败测试**
|
||||
|
||||
```java
|
||||
package com.label.unit;
|
||||
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThatCode;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
|
||||
@DisplayName("标准目录扁平化迁移守卫测试")
|
||||
class PackageStructureMigrationTest {
|
||||
|
||||
@Test
|
||||
@DisplayName("基础设施类已迁移到目标目录")
|
||||
void infrastructureTypesMoved() {
|
||||
assertClassExists("com.label.annotation.OperationLog");
|
||||
assertClassExists("com.label.aspect.AuditAspect");
|
||||
assertClassExists("com.label.config.MybatisPlusConfig");
|
||||
assertClassExists("com.label.config.OpenApiConfig");
|
||||
assertClassExists("com.label.config.RedisConfig");
|
||||
assertClassExists("com.label.event.ExtractionApprovedEvent");
|
||||
assertClassExists("com.label.listener.ExtractionApprovedEventListener");
|
||||
|
||||
assertClassMissing("com.label.common.aop.OperationLog");
|
||||
assertClassMissing("com.label.common.aop.AuditAspect");
|
||||
assertClassMissing("com.label.common.config.MybatisPlusConfig");
|
||||
assertClassMissing("com.label.common.config.OpenApiConfig");
|
||||
assertClassMissing("com.label.common.config.RedisConfig");
|
||||
assertClassMissing("com.label.module.annotation.event.ExtractionApprovedEvent");
|
||||
assertClassMissing("com.label.module.annotation.service.ExtractionApprovedEventListener");
|
||||
}
|
||||
|
||||
private static void assertClassExists(String fqcn) {
|
||||
assertThatCode(() -> Class.forName(fqcn)).doesNotThrowAnyException();
|
||||
}
|
||||
|
||||
private static void assertClassMissing(String fqcn) {
|
||||
assertThatThrownBy(() -> Class.forName(fqcn)).isInstanceOf(ClassNotFoundException.class);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 跑红**
|
||||
|
||||
Run: `mvn -q "-Dtest=PackageStructureMigrationTest#infrastructureTypesMoved" test`
|
||||
|
||||
Expected: FAIL,提示新包类不存在。
|
||||
|
||||
- [ ] **Step 3: 最小实现**
|
||||
|
||||
先执行迁移:
|
||||
|
||||
```powershell
|
||||
git mv src/main/java/com/label/common/aop/OperationLog.java src/main/java/com/label/annotation/OperationLog.java
|
||||
git mv src/main/java/com/label/common/aop/AuditAspect.java src/main/java/com/label/aspect/AuditAspect.java
|
||||
git mv src/main/java/com/label/common/config/MybatisPlusConfig.java src/main/java/com/label/config/MybatisPlusConfig.java
|
||||
git mv src/main/java/com/label/common/config/OpenApiConfig.java src/main/java/com/label/config/OpenApiConfig.java
|
||||
git mv src/main/java/com/label/common/config/RedisConfig.java src/main/java/com/label/config/RedisConfig.java
|
||||
git mv src/main/java/com/label/module/annotation/event/ExtractionApprovedEvent.java src/main/java/com/label/event/ExtractionApprovedEvent.java
|
||||
git mv src/main/java/com/label/module/annotation/service/ExtractionApprovedEventListener.java src/main/java/com/label/listener/ExtractionApprovedEventListener.java
|
||||
```
|
||||
|
||||
再做精确替换:
|
||||
|
||||
```java
|
||||
// OperationLog.java
|
||||
package com.label.annotation;
|
||||
|
||||
// AuditAspect.java
|
||||
package com.label.aspect;
|
||||
import com.label.annotation.OperationLog;
|
||||
|
||||
// MybatisPlusConfig.java / OpenApiConfig.java / RedisConfig.java
|
||||
package com.label.config;
|
||||
|
||||
// ExtractionApprovedEvent.java
|
||||
package com.label.event;
|
||||
|
||||
// ExtractionApprovedEventListener.java
|
||||
package com.label.listener;
|
||||
import com.label.event.ExtractionApprovedEvent;
|
||||
import com.label.module.annotation.entity.TrainingDataset;
|
||||
import com.label.module.annotation.mapper.AnnotationResultMapper;
|
||||
import com.label.module.annotation.mapper.TrainingDatasetMapper;
|
||||
import com.label.module.source.entity.SourceData;
|
||||
import com.label.module.source.mapper.SourceDataMapper;
|
||||
import com.label.module.task.service.TaskService;
|
||||
```
|
||||
|
||||
并把 `src/main/java/com/label/module/annotation/service/ExtractionService.java` 中事件 import 改成 `com.label.event.ExtractionApprovedEvent`。
|
||||
|
||||
- [ ] **Step 4: 跑绿**
|
||||
|
||||
Run:
|
||||
- `mvn -q "-Dtest=PackageStructureMigrationTest#infrastructureTypesMoved" test`
|
||||
- `mvn -q -DskipTests compile`
|
||||
|
||||
Expected: 两条命令都 `BUILD SUCCESS`。
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```powershell
|
||||
git add src/test/java/com/label/unit/PackageStructureMigrationTest.java src/main/java/com/label/annotation src/main/java/com/label/aspect src/main/java/com/label/config src/main/java/com/label/event src/main/java/com/label/listener src/main/java/com/label/module/annotation/service/ExtractionService.java
|
||||
git commit -m "refactor: flatten infrastructure packages"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 2: 迁移 DTO、Entity、Mapper
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/test/java/com/label/unit/PackageStructureMigrationTest.java`
|
||||
- Modify: `src/test/java/com/label/unit/OpenApiAnnotationTest.java`
|
||||
- Modify: `src/test/java/com/label/integration/AuthIntegrationTest.java`
|
||||
- Modify: `src/test/java/com/label/integration/ExtractionApprovalIntegrationTest.java`
|
||||
- Modify: `src/test/java/com/label/integration/QaApprovalIntegrationTest.java`
|
||||
- Modify: `src/test/java/com/label/integration/UserManagementIntegrationTest.java`
|
||||
- Modify: `src/main/java/com/label/module/annotation/service/ExtractionService.java`
|
||||
- Modify: `src/main/java/com/label/module/annotation/service/QaService.java`
|
||||
- Modify: `src/main/java/com/label/module/config/service/SysConfigService.java`
|
||||
- Modify: `src/main/java/com/label/module/config/controller/SysConfigController.java`
|
||||
- Modify: `src/main/java/com/label/module/export/service/ExportService.java`
|
||||
- Modify: `src/main/java/com/label/module/export/service/FinetuneService.java`
|
||||
- Modify: `src/main/java/com/label/module/export/controller/ExportController.java`
|
||||
- Modify: `src/main/java/com/label/module/source/service/SourceService.java`
|
||||
- Modify: `src/main/java/com/label/module/source/controller/SourceController.java`
|
||||
- Modify: `src/main/java/com/label/module/task/service/TaskClaimService.java`
|
||||
- Modify: `src/main/java/com/label/module/task/service/TaskService.java`
|
||||
- Modify: `src/main/java/com/label/module/task/controller/TaskController.java`
|
||||
- Modify: `src/main/java/com/label/module/user/service/AuthService.java`
|
||||
- Modify: `src/main/java/com/label/module/user/service/UserService.java`
|
||||
- Modify: `src/main/java/com/label/module/user/controller/AuthController.java`
|
||||
- Modify: `src/main/java/com/label/module/user/controller/UserController.java`
|
||||
- Modify: `src/main/java/com/label/module/video/service/VideoProcessService.java`
|
||||
- Modify: `src/main/java/com/label/module/video/controller/VideoController.java`
|
||||
- Test: `src/test/java/com/label/unit/PackageStructureMigrationTest.java`
|
||||
|
||||
- [ ] **Step 1: 写失败测试**
|
||||
|
||||
在 `PackageStructureMigrationTest.java` 里追加:
|
||||
|
||||
```java
|
||||
@Test
|
||||
@DisplayName("DTO、实体、Mapper 已迁移到扁平数据层")
|
||||
void dataTypesMoved() {
|
||||
for (String fqcn : java.util.List.of(
|
||||
"com.label.dto.LoginRequest", "com.label.dto.LoginResponse", "com.label.dto.UserInfoResponse",
|
||||
"com.label.dto.TaskResponse", "com.label.dto.SourceResponse",
|
||||
"com.label.entity.AnnotationResult", "com.label.entity.TrainingDataset", "com.label.entity.SysConfig",
|
||||
"com.label.entity.ExportBatch", "com.label.entity.SourceData", "com.label.entity.AnnotationTask",
|
||||
"com.label.entity.AnnotationTaskHistory", "com.label.entity.SysCompany", "com.label.entity.SysUser",
|
||||
"com.label.entity.VideoProcessJob",
|
||||
"com.label.mapper.AnnotationResultMapper", "com.label.mapper.TrainingDatasetMapper",
|
||||
"com.label.mapper.SysConfigMapper", "com.label.mapper.ExportBatchMapper", "com.label.mapper.SourceDataMapper",
|
||||
"com.label.mapper.AnnotationTaskMapper", "com.label.mapper.TaskHistoryMapper",
|
||||
"com.label.mapper.SysCompanyMapper", "com.label.mapper.SysUserMapper", "com.label.mapper.VideoProcessJobMapper")) {
|
||||
assertClassExists(fqcn);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 跑红**
|
||||
|
||||
Run: `mvn -q "-Dtest=PackageStructureMigrationTest#dataTypesMoved" test`
|
||||
|
||||
Expected: FAIL,提示 `com.label.dto.LoginRequest` 等不存在。
|
||||
|
||||
- [ ] **Step 3: 最小实现**
|
||||
|
||||
执行迁移:
|
||||
|
||||
```powershell
|
||||
git mv src/main/java/com/label/module/user/dto/LoginRequest.java src/main/java/com/label/dto/LoginRequest.java
|
||||
git mv src/main/java/com/label/module/user/dto/LoginResponse.java src/main/java/com/label/dto/LoginResponse.java
|
||||
git mv src/main/java/com/label/module/user/dto/UserInfoResponse.java src/main/java/com/label/dto/UserInfoResponse.java
|
||||
git mv src/main/java/com/label/module/task/dto/TaskResponse.java src/main/java/com/label/dto/TaskResponse.java
|
||||
git mv src/main/java/com/label/module/source/dto/SourceResponse.java src/main/java/com/label/dto/SourceResponse.java
|
||||
git mv src/main/java/com/label/module/annotation/entity/AnnotationResult.java src/main/java/com/label/entity/AnnotationResult.java
|
||||
git mv src/main/java/com/label/module/annotation/entity/TrainingDataset.java src/main/java/com/label/entity/TrainingDataset.java
|
||||
git mv src/main/java/com/label/module/config/entity/SysConfig.java src/main/java/com/label/entity/SysConfig.java
|
||||
git mv src/main/java/com/label/module/export/entity/ExportBatch.java src/main/java/com/label/entity/ExportBatch.java
|
||||
git mv src/main/java/com/label/module/source/entity/SourceData.java src/main/java/com/label/entity/SourceData.java
|
||||
git mv src/main/java/com/label/module/task/entity/AnnotationTask.java src/main/java/com/label/entity/AnnotationTask.java
|
||||
git mv src/main/java/com/label/module/task/entity/AnnotationTaskHistory.java src/main/java/com/label/entity/AnnotationTaskHistory.java
|
||||
git mv src/main/java/com/label/module/user/entity/SysCompany.java src/main/java/com/label/entity/SysCompany.java
|
||||
git mv src/main/java/com/label/module/user/entity/SysUser.java src/main/java/com/label/entity/SysUser.java
|
||||
git mv src/main/java/com/label/module/video/entity/VideoProcessJob.java src/main/java/com/label/entity/VideoProcessJob.java
|
||||
git mv src/main/java/com/label/module/annotation/mapper/AnnotationResultMapper.java src/main/java/com/label/mapper/AnnotationResultMapper.java
|
||||
git mv src/main/java/com/label/module/annotation/mapper/TrainingDatasetMapper.java src/main/java/com/label/mapper/TrainingDatasetMapper.java
|
||||
git mv src/main/java/com/label/module/config/mapper/SysConfigMapper.java src/main/java/com/label/mapper/SysConfigMapper.java
|
||||
git mv src/main/java/com/label/module/export/mapper/ExportBatchMapper.java src/main/java/com/label/mapper/ExportBatchMapper.java
|
||||
git mv src/main/java/com/label/module/source/mapper/SourceDataMapper.java src/main/java/com/label/mapper/SourceDataMapper.java
|
||||
git mv src/main/java/com/label/module/task/mapper/AnnotationTaskMapper.java src/main/java/com/label/mapper/AnnotationTaskMapper.java
|
||||
git mv src/main/java/com/label/module/task/mapper/TaskHistoryMapper.java src/main/java/com/label/mapper/TaskHistoryMapper.java
|
||||
git mv src/main/java/com/label/module/user/mapper/SysCompanyMapper.java src/main/java/com/label/mapper/SysCompanyMapper.java
|
||||
git mv src/main/java/com/label/module/user/mapper/SysUserMapper.java src/main/java/com/label/mapper/SysUserMapper.java
|
||||
git mv src/main/java/com/label/module/video/mapper/VideoProcessJobMapper.java src/main/java/com/label/mapper/VideoProcessJobMapper.java
|
||||
```
|
||||
|
||||
统一替换包声明:
|
||||
|
||||
```java
|
||||
package com.label.dto;
|
||||
package com.label.entity;
|
||||
package com.label.mapper;
|
||||
```
|
||||
|
||||
然后把上面 `Files` 列表中的旧 `com.label.module.*.(dto|entity|mapper)` import 全部改到新包。
|
||||
|
||||
- [ ] **Step 4: 跑绿**
|
||||
|
||||
Run:
|
||||
- `mvn -q "-Dtest=PackageStructureMigrationTest#dataTypesMoved,OpenApiAnnotationTest,AuthIntegrationTest,ExtractionApprovalIntegrationTest,QaApprovalIntegrationTest,UserManagementIntegrationTest" test`
|
||||
|
||||
Expected: PASS。
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```powershell
|
||||
git add src/main/java/com/label/dto src/main/java/com/label/entity src/main/java/com/label/mapper src/test/java/com/label/unit/PackageStructureMigrationTest.java src/test/java/com/label/unit/OpenApiAnnotationTest.java src/test/java/com/label/integration/AuthIntegrationTest.java src/test/java/com/label/integration/ExtractionApprovalIntegrationTest.java src/test/java/com/label/integration/QaApprovalIntegrationTest.java src/test/java/com/label/integration/UserManagementIntegrationTest.java src/main/java/com/label/module
|
||||
git commit -m "refactor: flatten dto entity and mapper packages"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 3: 迁移业务服务层
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/test/java/com/label/unit/PackageStructureMigrationTest.java`
|
||||
- Modify: `src/main/java/com/label/module/annotation/controller/ExtractionController.java`
|
||||
- Modify: `src/main/java/com/label/module/annotation/controller/QaController.java`
|
||||
- Modify: `src/main/java/com/label/module/config/controller/SysConfigController.java`
|
||||
- Modify: `src/main/java/com/label/module/export/controller/ExportController.java`
|
||||
- Modify: `src/main/java/com/label/module/source/controller/SourceController.java`
|
||||
- Modify: `src/main/java/com/label/module/task/controller/TaskController.java`
|
||||
- Modify: `src/main/java/com/label/module/user/controller/AuthController.java`
|
||||
- Modify: `src/main/java/com/label/module/user/controller/UserController.java`
|
||||
- Modify: `src/main/java/com/label/module/video/controller/VideoController.java`
|
||||
- Test: `src/test/java/com/label/unit/PackageStructureMigrationTest.java`
|
||||
|
||||
- [ ] **Step 1: 写失败测试**
|
||||
|
||||
```java
|
||||
@Test
|
||||
@DisplayName("服务类已迁移到扁平 service 目录")
|
||||
void serviceTypesMoved() {
|
||||
for (String fqcn : java.util.List.of(
|
||||
"com.label.service.ExtractionService", "com.label.service.QaService",
|
||||
"com.label.service.SysConfigService", "com.label.service.ExportService",
|
||||
"com.label.service.FinetuneService", "com.label.service.SourceService",
|
||||
"com.label.service.TaskClaimService", "com.label.service.TaskService",
|
||||
"com.label.service.AuthService", "com.label.service.UserService",
|
||||
"com.label.service.VideoProcessService")) {
|
||||
assertClassExists(fqcn);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 跑红**
|
||||
|
||||
Run: `mvn -q "-Dtest=PackageStructureMigrationTest#serviceTypesMoved" test`
|
||||
|
||||
Expected: FAIL,提示 `com.label.service.*` 不存在。
|
||||
|
||||
- [ ] **Step 3: 最小实现**
|
||||
|
||||
执行迁移:
|
||||
|
||||
```powershell
|
||||
git mv src/main/java/com/label/module/annotation/service/ExtractionService.java src/main/java/com/label/service/ExtractionService.java
|
||||
git mv src/main/java/com/label/module/annotation/service/QaService.java src/main/java/com/label/service/QaService.java
|
||||
git mv src/main/java/com/label/module/config/service/SysConfigService.java src/main/java/com/label/service/SysConfigService.java
|
||||
git mv src/main/java/com/label/module/export/service/ExportService.java src/main/java/com/label/service/ExportService.java
|
||||
git mv src/main/java/com/label/module/export/service/FinetuneService.java src/main/java/com/label/service/FinetuneService.java
|
||||
git mv src/main/java/com/label/module/source/service/SourceService.java src/main/java/com/label/service/SourceService.java
|
||||
git mv src/main/java/com/label/module/task/service/TaskClaimService.java src/main/java/com/label/service/TaskClaimService.java
|
||||
git mv src/main/java/com/label/module/task/service/TaskService.java src/main/java/com/label/service/TaskService.java
|
||||
git mv src/main/java/com/label/module/user/service/AuthService.java src/main/java/com/label/service/AuthService.java
|
||||
git mv src/main/java/com/label/module/user/service/UserService.java src/main/java/com/label/service/UserService.java
|
||||
git mv src/main/java/com/label/module/video/service/VideoProcessService.java src/main/java/com/label/service/VideoProcessService.java
|
||||
```
|
||||
|
||||
统一替换:
|
||||
|
||||
```java
|
||||
package com.label.service;
|
||||
```
|
||||
|
||||
并把 `ExtractionController`、`QaController`、`SysConfigController`、`ExportController`、`SourceController`、`TaskController`、`AuthController`、`UserController`、`VideoController` 的服务导包改到 `com.label.service.*`。
|
||||
|
||||
- [ ] **Step 4: 跑绿**
|
||||
|
||||
Run:
|
||||
- `mvn -q "-Dtest=PackageStructureMigrationTest#serviceTypesMoved" test`
|
||||
- `mvn -q -DskipTests compile`
|
||||
|
||||
Expected: PASS。
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```powershell
|
||||
git add src/main/java/com/label/service src/main/java/com/label/module/annotation/controller/ExtractionController.java src/main/java/com/label/module/annotation/controller/QaController.java src/main/java/com/label/module/config/controller/SysConfigController.java src/main/java/com/label/module/export/controller/ExportController.java src/main/java/com/label/module/source/controller/SourceController.java src/main/java/com/label/module/task/controller/TaskController.java src/main/java/com/label/module/user/controller/AuthController.java src/main/java/com/label/module/user/controller/UserController.java src/main/java/com/label/module/video/controller/VideoController.java src/test/java/com/label/unit/PackageStructureMigrationTest.java
|
||||
git commit -m "refactor: flatten service packages"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 4: 迁移控制器并收敛 OpenAPI 测试
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/test/java/com/label/unit/PackageStructureMigrationTest.java`
|
||||
- Modify: `src/test/java/com/label/unit/OpenApiAnnotationTest.java`
|
||||
- Test: `src/test/java/com/label/unit/PackageStructureMigrationTest.java`
|
||||
- Test: `src/test/java/com/label/unit/OpenApiAnnotationTest.java`
|
||||
|
||||
- [ ] **Step 1: 写失败测试**
|
||||
|
||||
```java
|
||||
@Test
|
||||
@DisplayName("控制器已迁移到扁平 controller 目录")
|
||||
void controllerTypesMoved() {
|
||||
for (String fqcn : java.util.List.of(
|
||||
"com.label.controller.AuthController", "com.label.controller.UserController",
|
||||
"com.label.controller.SourceController", "com.label.controller.TaskController",
|
||||
"com.label.controller.ExtractionController", "com.label.controller.QaController",
|
||||
"com.label.controller.ExportController", "com.label.controller.SysConfigController",
|
||||
"com.label.controller.VideoController")) {
|
||||
assertClassExists(fqcn);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 跑红**
|
||||
|
||||
Run: `mvn -q "-Dtest=PackageStructureMigrationTest#controllerTypesMoved" test`
|
||||
|
||||
Expected: FAIL,提示 `com.label.controller.*` 不存在。
|
||||
|
||||
- [ ] **Step 3: 最小实现**
|
||||
|
||||
执行迁移:
|
||||
|
||||
```powershell
|
||||
git mv src/main/java/com/label/module/annotation/controller/ExtractionController.java src/main/java/com/label/controller/ExtractionController.java
|
||||
git mv src/main/java/com/label/module/annotation/controller/QaController.java src/main/java/com/label/controller/QaController.java
|
||||
git mv src/main/java/com/label/module/config/controller/SysConfigController.java src/main/java/com/label/controller/SysConfigController.java
|
||||
git mv src/main/java/com/label/module/export/controller/ExportController.java src/main/java/com/label/controller/ExportController.java
|
||||
git mv src/main/java/com/label/module/source/controller/SourceController.java src/main/java/com/label/controller/SourceController.java
|
||||
git mv src/main/java/com/label/module/task/controller/TaskController.java src/main/java/com/label/controller/TaskController.java
|
||||
git mv src/main/java/com/label/module/user/controller/AuthController.java src/main/java/com/label/controller/AuthController.java
|
||||
git mv src/main/java/com/label/module/user/controller/UserController.java src/main/java/com/label/controller/UserController.java
|
||||
git mv src/main/java/com/label/module/video/controller/VideoController.java src/main/java/com/label/controller/VideoController.java
|
||||
```
|
||||
|
||||
统一替换:
|
||||
|
||||
```java
|
||||
package com.label.controller;
|
||||
```
|
||||
|
||||
并把 `OpenApiAnnotationTest.java` 的 import 替换成:
|
||||
|
||||
```java
|
||||
import com.label.controller.AuthController;
|
||||
import com.label.controller.UserController;
|
||||
import com.label.controller.SourceController;
|
||||
import com.label.controller.TaskController;
|
||||
import com.label.controller.ExtractionController;
|
||||
import com.label.controller.QaController;
|
||||
import com.label.controller.ExportController;
|
||||
import com.label.controller.SysConfigController;
|
||||
import com.label.controller.VideoController;
|
||||
import com.label.dto.LoginRequest;
|
||||
import com.label.dto.LoginResponse;
|
||||
import com.label.dto.UserInfoResponse;
|
||||
import com.label.dto.TaskResponse;
|
||||
import com.label.dto.SourceResponse;
|
||||
```
|
||||
|
||||
- [ ] **Step 4: 跑绿**
|
||||
|
||||
Run: `mvn -q "-Dtest=PackageStructureMigrationTest#controllerTypesMoved,OpenApiAnnotationTest" test`
|
||||
|
||||
Expected: PASS。
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```powershell
|
||||
git add src/main/java/com/label/controller src/test/java/com/label/unit/PackageStructureMigrationTest.java src/test/java/com/label/unit/OpenApiAnnotationTest.java
|
||||
git commit -m "refactor: flatten controller packages"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 5: 清理旧包残留并做全量回归
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/test/java/com/label/unit/PackageStructureMigrationTest.java`
|
||||
- Test: `src/test/java/com/label/LabelBackendApplicationTests.java`
|
||||
- Test: `src/test/java/com/label/unit/ApplicationConfigTest.java`
|
||||
- Test: `src/test/java/com/label/unit/ShiroConfigTest.java`
|
||||
- Test: `src/test/java/com/label/unit/StateMachineTest.java`
|
||||
- Test: `src/test/java/com/label/unit/TokenFilterTest.java`
|
||||
- Test: `src/test/java/com/label/integration/AuthIntegrationTest.java`
|
||||
- Test: `src/test/java/com/label/integration/ExportIntegrationTest.java`
|
||||
- Test: `src/test/java/com/label/integration/ExtractionApprovalIntegrationTest.java`
|
||||
- Test: `src/test/java/com/label/integration/MultiTenantIsolationTest.java`
|
||||
- Test: `src/test/java/com/label/integration/QaApprovalIntegrationTest.java`
|
||||
- Test: `src/test/java/com/label/integration/ShiroFilterIntegrationTest.java`
|
||||
- Test: `src/test/java/com/label/integration/SourceIntegrationTest.java`
|
||||
- Test: `src/test/java/com/label/integration/SysConfigIntegrationTest.java`
|
||||
- Test: `src/test/java/com/label/integration/TaskClaimConcurrencyTest.java`
|
||||
- Test: `src/test/java/com/label/integration/UserManagementIntegrationTest.java`
|
||||
- Test: `src/test/java/com/label/integration/VideoCallbackIdempotencyTest.java`
|
||||
|
||||
- [ ] **Step 1: 写最终守卫测试**
|
||||
|
||||
在 `PackageStructureMigrationTest.java` 中补充:
|
||||
|
||||
```java
|
||||
@Test
|
||||
@DisplayName("源码中不再引用旧的 module、common.aop、common.config 包")
|
||||
void sourceTreeHasNoLegacyPackageReferences() throws Exception {
|
||||
try (java.util.stream.Stream<java.nio.file.Path> paths = java.nio.file.Files.walk(java.nio.file.Path.of("src"))) {
|
||||
java.util.List<String> violations = paths
|
||||
.filter(path -> path.toString().endsWith(".java"))
|
||||
.map(path -> {
|
||||
try {
|
||||
String text = java.nio.file.Files.readString(path);
|
||||
boolean legacy = text.contains("com.label.module.")
|
||||
|| text.contains("com.label.common.aop")
|
||||
|| text.contains("com.label.common.config");
|
||||
return legacy ? path.toString() : null;
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
})
|
||||
.filter(java.util.Objects::nonNull)
|
||||
.toList();
|
||||
|
||||
org.assertj.core.api.Assertions.assertThat(violations).isEmpty();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 跑红并定位残留**
|
||||
|
||||
Run:
|
||||
- `mvn -q "-Dtest=PackageStructureMigrationTest#sourceTreeHasNoLegacyPackageReferences" test`
|
||||
- `rg -n "com\.label\.module\.|com\.label\.common\.aop|com\.label\.common\.config" src/main/java src/test/java`
|
||||
|
||||
Expected: 初次 FAIL,并列出残留文件。
|
||||
|
||||
- [ ] **Step 3: 清理残留并确认旧目录为空**
|
||||
|
||||
Run:
|
||||
|
||||
```powershell
|
||||
Get-ChildItem -Path src/main/java/com/label/module -Recurse
|
||||
```
|
||||
|
||||
Expected: 没有 Java 文件残留;确认后删除空目录。
|
||||
|
||||
- [ ] **Step 4: 全量回归**
|
||||
|
||||
Run: `mvn clean test`
|
||||
|
||||
Expected: `BUILD SUCCESS`。
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```powershell
|
||||
git add src/main/java src/test/java
|
||||
git commit -m "refactor: complete backend directory flattening"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 自检
|
||||
|
||||
- 覆盖了 `annotation / aspect / config / event / listener / dto / entity / mapper / service / controller`
|
||||
- 没有引入 `service.impl`
|
||||
- 没有拆分 `dto/request`、`dto/response`
|
||||
- 最终门槛是 `mvn clean test`
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,244 +0,0 @@
|
||||
# label_backend 标准目录扁平化设计
|
||||
|
||||
**日期**: 2026-04-14
|
||||
**范围**: `label_backend` 主工程 Java 包结构调整
|
||||
**目标**: 将当前按业务域分层的 `com.label.module.*` 结构重组为符合《微服务开发规范文档》的扁平标准目录结构,同时保持现有业务行为不变。
|
||||
|
||||
---
|
||||
|
||||
## 1. 背景与现状
|
||||
|
||||
当前项目主代码位于 `src/main/java/com/label`,整体结构分为两部分:
|
||||
|
||||
- `com.label.common.*`:放置公共能力,如配置、异常、AOP、鉴权、Redis、状态机、存储与 AI 客户端
|
||||
- `com.label.module.*`:按业务域划分的模块目录,如 `user`、`task`、`source`、`annotation`、`export`、`config`、`video`
|
||||
|
||||
现有结构具备一定领域边界,但与《微服务开发规范文档》中规定的标准扁平目录结构不一致。后续如果继续沿用两套组织方式,会增加新代码接入成本,也会让公共能力与业务层级的归属变得不稳定。
|
||||
|
||||
---
|
||||
|
||||
## 2. 设计目标
|
||||
|
||||
本次调整目标如下:
|
||||
|
||||
- 将主代码目录统一调整为规范文档中的扁平结构
|
||||
- 保持包职责清晰,按“层级职责”而不是“业务模块”组织类
|
||||
- 在不改变业务逻辑和接口行为的前提下完成迁移
|
||||
- 规范包归属与少量命名,不引入新的技术分层
|
||||
- 为后续新增功能提供统一、可预测的目录组织方式
|
||||
|
||||
---
|
||||
|
||||
## 3. 明确不做的事情
|
||||
|
||||
为控制改造范围,本次不包含以下内容:
|
||||
|
||||
- 不新增 `service.impl`
|
||||
- 不将 `dto` 拆分为 `request`、`response`、`common`
|
||||
- 不主动修改接口 URL、接口契约或返回结构
|
||||
- 不进行与目录调整无关的业务重构
|
||||
- 不改造数据库结构、SQL 文件或资源文件布局
|
||||
|
||||
---
|
||||
|
||||
## 4. 目标目录结构
|
||||
|
||||
目标结构定义如下:
|
||||
|
||||
```text
|
||||
src/main/java/com/label/
|
||||
├── annotation/
|
||||
├── aspect/
|
||||
├── common/
|
||||
│ ├── ai/
|
||||
│ ├── context/
|
||||
│ ├── exception/
|
||||
│ ├── redis/
|
||||
│ ├── result/
|
||||
│ ├── shiro/
|
||||
│ ├── statemachine/
|
||||
│ └── storage/
|
||||
├── config/
|
||||
├── constant/
|
||||
├── controller/
|
||||
├── dto/
|
||||
├── entity/
|
||||
├── event/
|
||||
├── feign/
|
||||
├── listener/
|
||||
├── mapper/
|
||||
├── scheduled/
|
||||
├── service/
|
||||
├── typehandler/
|
||||
├── util/
|
||||
└── LabelBackendApplication.java
|
||||
```
|
||||
|
||||
说明:
|
||||
|
||||
- `common` 只保留真正跨业务复用的基础能力
|
||||
- `config` 从原 `common.config` 提升到顶层
|
||||
- `annotation` 与 `aspect` 从原 `common.aop` 中拆分
|
||||
- `event` 与 `listener` 分离,避免事件定义与监听逻辑混放
|
||||
- `controller`、`service`、`mapper`、`entity`、`dto` 全部收敛为顶层扁平结构
|
||||
|
||||
---
|
||||
|
||||
## 5. 包迁移规则
|
||||
|
||||
### 5.1 顶层迁移规则
|
||||
|
||||
- `com.label.module.*.controller` -> `com.label.controller`
|
||||
- `com.label.module.*.service` -> `com.label.service`
|
||||
- `com.label.module.*.mapper` -> `com.label.mapper`
|
||||
- `com.label.module.*.entity` -> `com.label.entity`
|
||||
- `com.label.module.*.dto` -> `com.label.dto`
|
||||
- `com.label.module.annotation.event` -> `com.label.event`
|
||||
- 事件监听类 -> `com.label.listener`
|
||||
- `com.label.common.config` -> `com.label.config`
|
||||
- `com.label.common.aop` 中注解类 -> `com.label.annotation`
|
||||
- `com.label.common.aop` 中切面类 -> `com.label.aspect`
|
||||
|
||||
### 5.2 典型映射示例
|
||||
|
||||
- `com.label.module.user.controller.AuthController` -> `com.label.controller.AuthController`
|
||||
- `com.label.module.user.service.UserService` -> `com.label.service.UserService`
|
||||
- `com.label.module.task.mapper.AnnotationTaskMapper` -> `com.label.mapper.AnnotationTaskMapper`
|
||||
- `com.label.module.source.entity.SourceData` -> `com.label.entity.SourceData`
|
||||
- `com.label.module.user.dto.LoginRequest` -> `com.label.dto.LoginRequest`
|
||||
- `com.label.common.aop.OperationLog` -> `com.label.annotation.OperationLog`
|
||||
- `com.label.common.aop.AuditAspect` -> `com.label.aspect.AuditAspect`
|
||||
- `com.label.module.annotation.event.ExtractionApprovedEvent` -> `com.label.event.ExtractionApprovedEvent`
|
||||
- `com.label.module.annotation.service.ExtractionApprovedEventListener` -> `com.label.listener.ExtractionApprovedEventListener`
|
||||
- `com.label.common.config.RedisConfig` -> `com.label.config.RedisConfig`
|
||||
|
||||
### 5.3 命名策略
|
||||
|
||||
- 以“最小必要变更”为原则,优先迁移包路径,不主动重命名类
|
||||
- 仅在类职责与包语义明显不匹配时做必要归位
|
||||
- 现阶段主代码类名不存在重名冲突,因此不需要为扁平化提前引入前缀或后缀
|
||||
|
||||
---
|
||||
|
||||
## 6. 实施顺序
|
||||
|
||||
为降低迁移风险,实际执行采用“目标一次性扁平化,操作分阶段迁移”的方式。
|
||||
|
||||
### 阶段 1:基础公共层归位
|
||||
|
||||
先迁移以下内容,建立新骨架:
|
||||
|
||||
- `annotation`
|
||||
- `aspect`
|
||||
- `config`
|
||||
- `event`
|
||||
- `listener`
|
||||
|
||||
目标是先完成最基础的结构纠偏,并尽早暴露切面、配置和事件扫描问题。
|
||||
|
||||
### 阶段 2:数据承载层迁移
|
||||
|
||||
再迁移下列包:
|
||||
|
||||
- `entity`
|
||||
- `dto`
|
||||
- `mapper`
|
||||
|
||||
这些类依赖通常较窄,适合优先扁平化,也能及早验证 MyBatis 相关扫描与引用是否正常。
|
||||
|
||||
### 阶段 3:业务服务层迁移
|
||||
|
||||
迁移所有业务服务类到 `service`,原则如下:
|
||||
|
||||
- 不新增实现层
|
||||
- 不调整现有业务编排
|
||||
- 只修正导包、注解引用和必要的包路径依赖
|
||||
|
||||
### 阶段 4:控制层迁移
|
||||
|
||||
最后迁移全部控制器到 `controller`。控制层依赖最广,放在后面可以减少中间态反复修改。
|
||||
|
||||
### 阶段 5:启动与扫描校正
|
||||
|
||||
统一检查并修正以下内容:
|
||||
|
||||
- `@MapperScan`
|
||||
- 组件扫描隐式路径
|
||||
- OpenAPI 相关配置
|
||||
- 事件监听与切面装配
|
||||
- 测试代码中的旧包引用
|
||||
|
||||
### 阶段 6:编译与回归验证
|
||||
|
||||
迁移完成后进行全量编译与测试回归,确认结构调整没有引入行为回归。
|
||||
|
||||
---
|
||||
|
||||
## 7. 风险与控制策略
|
||||
|
||||
### 风险 1:导包失效
|
||||
|
||||
大量类迁移后,主代码和测试代码中的 import 会同时失效。
|
||||
|
||||
控制策略:
|
||||
|
||||
- 按阶段迁移并同步修复引用
|
||||
- 每个阶段结束后至少执行一次编译检查
|
||||
|
||||
### 风险 2:Spring 扫描路径异常
|
||||
|
||||
若配置类、切面、监听器或 Mapper 的扫描路径与旧包结构耦合,迁移后可能导致 Bean 缺失。
|
||||
|
||||
控制策略:
|
||||
|
||||
- 显式检查启动类与配置类中的扫描配置
|
||||
- 将路径校正作为独立阶段处理
|
||||
|
||||
### 风险 3:事件监听失效
|
||||
|
||||
监听器从 `service` 拆到 `listener` 后,若注解或组件扫描不正确,会导致事件未被消费。
|
||||
|
||||
控制策略:
|
||||
|
||||
- 迁移时同步校验事件类与监听器依赖关系
|
||||
- 通过现有集成测试覆盖事件链路
|
||||
|
||||
### 风险 4:测试回归失败
|
||||
|
||||
测试代码同样引用旧包名,若只改主代码,会造成测试集整体失效。
|
||||
|
||||
控制策略:
|
||||
|
||||
- 测试代码与主代码同步迁移
|
||||
- 将 `mvn test` 作为最终验收门槛
|
||||
|
||||
---
|
||||
|
||||
## 8. 验收标准
|
||||
|
||||
本次结构调整完成后,应满足以下标准:
|
||||
|
||||
1. `src/main/java/com/label/module` 目录被完全移除
|
||||
2. 主代码包结构符合标准扁平目录规范
|
||||
3. 项目能够成功编译
|
||||
4. 现有测试能够通过,至少覆盖当前可运行的启动测试、单元测试和集成测试
|
||||
5. 调整范围内不存在残留旧包引用
|
||||
|
||||
---
|
||||
|
||||
## 9. 实现原则
|
||||
|
||||
执行本设计时应遵循以下原则:
|
||||
|
||||
- 先保证结构归位,再追求风格统一
|
||||
- 先保证行为不变,再做命名优化
|
||||
- 所有改动以“目录标准化”为中心,不引入额外架构决策
|
||||
- 每次修改都要让工程更接近规范,而不是制造新的混合结构
|
||||
|
||||
---
|
||||
|
||||
## 10. 结论
|
||||
|
||||
本次改造将 `label_backend` 从“按业务域分层 + 公共包混合”的现状,统一整理为规范文档定义的扁平标准目录结构。该调整不会改变系统能力边界,但会显著提升代码组织一致性、后续开发可预测性以及新成员理解成本。
|
||||
|
||||
实施时采用“目标结构一次确定、操作按阶段推进”的方式,以降低大规模包迁移带来的编译与装配风险。
|
||||
@@ -1,570 +0,0 @@
|
||||
# label-backend Swagger 接口黑盒测试用例
|
||||
|
||||
## 1. 文档说明
|
||||
|
||||
- 生成时间:2026-04-14
|
||||
- 适用项目:`label-backend`
|
||||
- 覆盖范围:当前 `controller` 中已开放的全部 Swagger/OpenAPI 接口
|
||||
- 生成依据:
|
||||
- `src/main/java/com/label/controller/**/*.java`
|
||||
- `src/main/java/com/label/service/**/*.java`
|
||||
- `src/main/resources/sql/init.sql`
|
||||
- `src/test/java/com/label/integration/**/*.java`
|
||||
- 说明:
|
||||
- 当前会话内未能直接访问 `http://127.0.0.1:8080/v3/api-docs`,本文档按代码、设计和现有集成测试反推生成。
|
||||
- 文中“预期结果”以黑盒测试应验证的业务契约为主;若当前实现存在契约空洞,用例应保留,用于发现缺陷。
|
||||
|
||||
## 2. 测试前提
|
||||
|
||||
### 2.1 基础环境
|
||||
|
||||
- Base URL:`http://127.0.0.1:8080`
|
||||
- OpenAPI:`GET /v3/api-docs`
|
||||
- Swagger UI:`GET /swagger-ui.html`
|
||||
- 默认返回体:`{ "code": "...", "message": "...", "data": ... }`
|
||||
|
||||
### 2.2 认证前提
|
||||
|
||||
- 若要执行认证、鉴权、越权、Token 失效类用例,建议运行时配置 `auth.enabled=true`。
|
||||
- 若当前运行环境仍为 `auth.enabled=false` 的 mock 模式,则以下用例中所有 `401/403` 校验需要在真实认证模式下执行。
|
||||
|
||||
### 2.3 种子数据建议
|
||||
|
||||
基于 `src/main/resources/sql/init.sql`,至少准备以下账号:
|
||||
|
||||
| 公司 | 用户名 | 密码 | 角色 |
|
||||
|---|---|---|---|
|
||||
| `DEMO` | `admin` | `admin123` | `ADMIN` |
|
||||
| `DEMO` | `reviewer01` | `review123` | `REVIEWER` |
|
||||
| `DEMO` | `annotator01` | `annot123` | `ANNOTATOR` |
|
||||
| `DEMO` | `uploader01` | `upload123` | `UPLOADER` |
|
||||
|
||||
额外建议准备:
|
||||
|
||||
- 第二家公司 `TESTB`
|
||||
- `TESTB` 下至少 1 个 `ADMIN` 账号
|
||||
- `DEMO`、`TESTB` 各自的资料、任务、配置、导出批次、视频任务样本
|
||||
|
||||
### 2.4 通用 Header
|
||||
|
||||
- JSON 接口:`Content-Type: application/json`
|
||||
- 文件上传:`multipart/form-data`
|
||||
- 受保护接口:`Authorization: Bearer <token>`
|
||||
- 视频回调启用密钥时:`X-Callback-Secret: <VIDEO_CALLBACK_SECRET>`
|
||||
|
||||
## 3. 通用黑盒用例
|
||||
|
||||
除 `POST /api/auth/login` 与 `POST /api/video/callback` 外,所有受保护接口均应复用以下通用用例。
|
||||
|
||||
| 用例ID | 适用范围 | 场景 | 操作 | 预期结果 |
|
||||
|---|---|---|---|---|
|
||||
| `G-AUTH-001` | 全部受保护接口 | 缺少 Token | 不传 `Authorization` | HTTP `401`,`code=UNAUTHORIZED` |
|
||||
| `G-AUTH-002` | 全部受保护接口 | Token 格式错误 | 传 `Authorization: BearerX xxx` 或 `Basic xxx` | HTTP `401`,`code=UNAUTHORIZED` |
|
||||
| `G-AUTH-003` | 全部受保护接口 | Token 无效或过期 | 传不存在/已失效 Token | HTTP `401`,`code=UNAUTHORIZED` |
|
||||
| `G-ROLE-001` | 有角色要求的接口 | 角色不足 | 用低权限账号访问高权限接口 | HTTP `403`,`code=FORBIDDEN` |
|
||||
| `G-TENANT-001` | 租户数据相关接口 | 跨租户访问数据 | 用 `TESTB` Token 访问 `DEMO` 数据 | 返回 `404`、空列表或业务拒绝;不能读到 `DEMO` 数据 |
|
||||
| `G-PAGE-001` | 分页接口 | 大页码限制 | 传超大 `pageSize` | 返回成功,`pageSize` 被限制到系统上限,不出现异常 |
|
||||
| `G-ERR-001` | 全部接口 | 非法请求不能打穿到 5xx | 传缺参/错参/非法状态 | 返回可解释的 `4xx` 或失败业务码,不应无意义 `500` |
|
||||
|
||||
## 4. 认证管理
|
||||
|
||||
### 4.1 `POST /api/auth/login`
|
||||
|
||||
| 用例ID | 场景 | 请求 | 预期结果 |
|
||||
|---|---|---|---|
|
||||
| `AUTH-LOGIN-001` | 正常登录 | `{"companyCode":"DEMO","username":"admin","password":"admin123"}` | HTTP `200`,`code=SUCCESS`,返回 `token/userId/username/role/expiresIn` |
|
||||
| `AUTH-LOGIN-002` | 密码错误 | `password=wrong` | HTTP `401`,`code=USER_NOT_FOUND` |
|
||||
| `AUTH-LOGIN-003` | 公司代码不存在 | `companyCode=NO_SUCH` | HTTP `401`,`code=USER_NOT_FOUND` |
|
||||
| `AUTH-LOGIN-004` | 账号被禁用 | 先将用户禁用,再登录 | HTTP `403`,`code=USER_DISABLED` |
|
||||
| `AUTH-LOGIN-005` | 缺少必填字段 | 缺 `companyCode`、`username` 或 `password` | 返回失败,不能生成有效 Token,不应出现无提示 `500` |
|
||||
|
||||
### 4.2 `POST /api/auth/logout`
|
||||
|
||||
| 用例ID | 场景 | 操作 | 预期结果 |
|
||||
|---|---|---|---|
|
||||
| `AUTH-LOGOUT-001` | 正常退出 | 用有效 Token 调用退出 | HTTP `200`,`code=SUCCESS` |
|
||||
| `AUTH-LOGOUT-002` | 退出后 Token 立即失效 | 退出后继续访问 `/api/auth/me` | HTTP `401`,`code=UNAUTHORIZED` |
|
||||
| `AUTH-LOGOUT-003` | 无 Token 退出 | 复用 `G-AUTH-001` | HTTP `401` |
|
||||
|
||||
### 4.3 `GET /api/auth/me`
|
||||
|
||||
| 用例ID | 场景 | 操作 | 预期结果 |
|
||||
|---|---|---|---|
|
||||
| `AUTH-ME-001` | 查询当前用户信息 | 用 `admin` Token 调用 | HTTP `200`,返回 `id/username/realName/role/companyId/companyName` |
|
||||
| `AUTH-ME-002` | 已退出 Token 查询自己 | 先登录再退出,再调 `/me` | HTTP `401`,`code=UNAUTHORIZED` |
|
||||
| `AUTH-ME-003` | 被禁用账号的旧 Token 查询自己 | 先登录,管理员禁用该用户,再调 `/me` | HTTP `401`,`code=UNAUTHORIZED` |
|
||||
|
||||
## 5. 公司管理
|
||||
|
||||
说明:本组接口复用 `G-AUTH-001~003`、`G-ROLE-001`。
|
||||
|
||||
### 5.1 `GET /api/companies`
|
||||
|
||||
| 用例ID | 场景 | 请求 | 预期结果 |
|
||||
|---|---|---|---|
|
||||
| `COMPANY-LIST-001` | 管理员分页查询公司 | `?page=1&pageSize=20` | HTTP `200`,返回分页结构 |
|
||||
| `COMPANY-LIST-002` | 按状态筛选 | `?status=ACTIVE` | 仅返回对应状态公司 |
|
||||
| `COMPANY-LIST-003` | 非管理员访问 | 用 `REVIEWER` 或 `UPLOADER` 调用 | HTTP `403`,`code=FORBIDDEN` |
|
||||
|
||||
### 5.2 `POST /api/companies`
|
||||
|
||||
| 用例ID | 场景 | 请求 | 预期结果 |
|
||||
|---|---|---|---|
|
||||
| `COMPANY-CREATE-001` | 创建公司成功 | `{"companyName":"测试公司B","companyCode":"TESTB"}` | HTTP `201`,创建成功,状态默认 `ACTIVE` |
|
||||
| `COMPANY-CREATE-002` | 公司代码重复 | 使用已存在 `companyCode` | HTTP `409`,`code=DUPLICATE_COMPANY_CODE` |
|
||||
| `COMPANY-CREATE-003` | 公司名称重复 | 使用已存在 `companyName` | HTTP `409`,`code=DUPLICATE_COMPANY_NAME` |
|
||||
| `COMPANY-CREATE-004` | 公司名为空 | `companyName` 空或空白 | HTTP `400`,`code=INVALID_COMPANY_FIELD` |
|
||||
| `COMPANY-CREATE-005` | 公司代码为空 | `companyCode` 空或空白 | HTTP `400`,`code=INVALID_COMPANY_FIELD` |
|
||||
|
||||
### 5.3 `PUT /api/companies/{id}`
|
||||
|
||||
| 用例ID | 场景 | 请求 | 预期结果 |
|
||||
|---|---|---|---|
|
||||
| `COMPANY-UPDATE-001` | 更新公司成功 | 修改 `companyName/companyCode` | HTTP `200`,字段更新成功 |
|
||||
| `COMPANY-UPDATE-002` | 更新不存在公司 | `id` 不存在 | HTTP `404`,`code=NOT_FOUND` |
|
||||
| `COMPANY-UPDATE-003` | 更新为重复代码 | `companyCode` 改成已存在值 | HTTP `409`,`code=DUPLICATE_COMPANY_CODE` |
|
||||
| `COMPANY-UPDATE-004` | 更新为重复名称 | `companyName` 改成已存在值 | HTTP `409`,`code=DUPLICATE_COMPANY_NAME` |
|
||||
|
||||
### 5.4 `PUT /api/companies/{id}/status`
|
||||
|
||||
| 用例ID | 场景 | 请求 | 预期结果 |
|
||||
|---|---|---|---|
|
||||
| `COMPANY-STATUS-001` | 禁用公司成功 | `{"status":"DISABLED"}` | HTTP `200`,公司状态变为 `DISABLED` |
|
||||
| `COMPANY-STATUS-002` | 恢复公司成功 | `{"status":"ACTIVE"}` | HTTP `200`,公司状态变为 `ACTIVE` |
|
||||
| `COMPANY-STATUS-003` | 非法状态值 | `{"status":"UNKNOWN"}` | HTTP `400`,`code=INVALID_COMPANY_STATUS` |
|
||||
| `COMPANY-STATUS-004` | 公司不存在 | 不存在 `id` | HTTP `404`,`code=NOT_FOUND` |
|
||||
|
||||
### 5.5 `DELETE /api/companies/{id}`
|
||||
|
||||
| 用例ID | 场景 | 操作 | 预期结果 |
|
||||
|---|---|---|---|
|
||||
| `COMPANY-DELETE-001` | 删除空公司成功 | 删除无用户的公司 | HTTP `200`,删除成功 |
|
||||
| `COMPANY-DELETE-002` | 公司下仍有用户 | 删除已有用户公司 | HTTP `409`,`code=COMPANY_HAS_USERS` |
|
||||
| `COMPANY-DELETE-003` | 删除不存在公司 | 不存在 `id` | HTTP `404`,`code=NOT_FOUND` |
|
||||
|
||||
## 6. 用户管理
|
||||
|
||||
说明:本组接口复用 `G-AUTH-001~003`、`G-ROLE-001`、`G-TENANT-001`。
|
||||
|
||||
### 6.1 `GET /api/users`
|
||||
|
||||
| 用例ID | 场景 | 请求 | 预期结果 |
|
||||
|---|---|---|---|
|
||||
| `USER-LIST-001` | 管理员查看本公司用户 | `?page=1&pageSize=20` | HTTP `200`,仅返回当前公司用户 |
|
||||
| `USER-LIST-002` | 跨租户隔离 | 用 `TESTB` 管理员查看列表 | 不出现 `DEMO` 用户 |
|
||||
| `USER-LIST-003` | 非管理员访问 | 用 `REVIEWER` 调用 | HTTP `403` |
|
||||
|
||||
### 6.2 `POST /api/users`
|
||||
|
||||
| 用例ID | 场景 | 请求 | 预期结果 |
|
||||
|---|---|---|---|
|
||||
| `USER-CREATE-001` | 创建用户成功 | `{"username":"qa01","password":"qa123456","realName":"测试员","role":"ANNOTATOR"}` | HTTP `200`,创建成功,状态默认为 `ACTIVE` |
|
||||
| `USER-CREATE-002` | 用户名重复 | 同公司创建同名用户 | HTTP `409`,`code=DUPLICATE_USERNAME` |
|
||||
| `USER-CREATE-003` | 角色非法 | `role=VIEWER` 或其他未支持角色 | HTTP `400`,`code=INVALID_ROLE` |
|
||||
| `USER-CREATE-004` | 跨租户污染校验 | `TESTB` 管理员创建用户后,`DEMO` 不可见 | 创建成功且仅属于当前公司 |
|
||||
|
||||
### 6.3 `PUT /api/users/{id}`
|
||||
|
||||
| 用例ID | 场景 | 请求 | 预期结果 |
|
||||
|---|---|---|---|
|
||||
| `USER-UPDATE-001` | 更新真实姓名成功 | `{"realName":"新名字"}` | HTTP `200`,真实姓名更新 |
|
||||
| `USER-UPDATE-002` | 更新密码成功 | `{"password":"newpass123"}`,再重新登录 | 新密码可登录,旧密码失效 |
|
||||
| `USER-UPDATE-003` | 更新不存在用户 | 不存在 `id` | HTTP `404`,`code=NOT_FOUND` |
|
||||
| `USER-UPDATE-004` | 更新他租户用户 | 用 `TESTB` 管理员修改 `DEMO` 用户 | HTTP `404` 或不可见 |
|
||||
|
||||
### 6.4 `PUT /api/users/{id}/status`
|
||||
|
||||
| 用例ID | 场景 | 请求 | 预期结果 |
|
||||
|---|---|---|---|
|
||||
| `USER-STATUS-001` | 禁用用户成功 | `{"status":"DISABLED"}` | HTTP `200`,用户状态更新成功 |
|
||||
| `USER-STATUS-002` | 禁用后旧 Token 失效 | 禁用后用该用户旧 Token 调 `/api/auth/me` | HTTP `401`,`code=UNAUTHORIZED` |
|
||||
| `USER-STATUS-003` | 恢复用户成功 | `{"status":"ACTIVE"}` | HTTP `200` |
|
||||
| `USER-STATUS-004` | 非法状态值 | `{"status":"LOCKED"}` | HTTP `400`,`code=INVALID_STATUS` |
|
||||
|
||||
### 6.5 `PUT /api/users/{id}/role`
|
||||
|
||||
| 用例ID | 场景 | 请求 | 预期结果 |
|
||||
|---|---|---|---|
|
||||
| `USER-ROLE-001` | 修改角色成功 | `{"role":"REVIEWER"}` | HTTP `200`,用户角色更新成功 |
|
||||
| `USER-ROLE-002` | 角色变更立即生效 | 同一 Token 变更前访问 reviewer 接口 `403`,变更后重试 | 变更后无需重新登录即可访问成功 |
|
||||
| `USER-ROLE-003` | 非法角色值 | `{"role":"VIEWER"}` | HTTP `400`,`code=INVALID_ROLE` |
|
||||
| `USER-ROLE-004` | 修改不存在用户 | 不存在 `id` | HTTP `404`,`code=NOT_FOUND` |
|
||||
|
||||
## 7. 资料管理
|
||||
|
||||
说明:本组接口复用 `G-AUTH-001~003`、`G-ROLE-001`、`G-TENANT-001`。
|
||||
|
||||
### 7.1 `POST /api/source/upload`
|
||||
|
||||
| 用例ID | 场景 | 请求 | 预期结果 |
|
||||
|---|---|---|---|
|
||||
| `SOURCE-UPLOAD-001` | 上传文本资料成功 | `multipart/form-data`,传 `file` + `dataType=TEXT` | HTTP `201`,返回资料 `id/fileName/dataType/status` |
|
||||
| `SOURCE-UPLOAD-002` | 上传图片资料成功 | `dataType=IMAGE` | HTTP `201` |
|
||||
| `SOURCE-UPLOAD-003` | 上传视频资料成功 | `dataType=VIDEO` | HTTP `201` |
|
||||
| `SOURCE-UPLOAD-004` | 空文件上传 | 文件为空 | HTTP `400`,`code=FILE_EMPTY` |
|
||||
| `SOURCE-UPLOAD-005` | 不支持的资料类型 | `dataType=PDF` | HTTP `400`,`code=INVALID_TYPE` |
|
||||
| `SOURCE-UPLOAD-006` | Swagger 文案兼容性检查 | `dataType=text` 小写 | 应与文档约定一致;若失败需修正文档或实现 |
|
||||
| `SOURCE-UPLOAD-007` | 低权限无权上传 | 用无上传权限账号访问 | HTTP `403` |
|
||||
|
||||
### 7.2 `GET /api/source/list`
|
||||
|
||||
| 用例ID | 场景 | 请求 | 预期结果 |
|
||||
|---|---|---|---|
|
||||
| `SOURCE-LIST-001` | 上传员查看自己资料 | 用 `uploader01` 调用 | 仅返回自己上传的数据 |
|
||||
| `SOURCE-LIST-002` | 管理员查看公司全部资料 | 用 `admin` 调用 | 返回公司内全部资料 |
|
||||
| `SOURCE-LIST-003` | 按类型筛选 | `?dataType=TEXT` | 仅返回对应类型 |
|
||||
| `SOURCE-LIST-004` | 按状态筛选 | `?status=PENDING` | 仅返回对应状态 |
|
||||
| `SOURCE-LIST-005` | 跨租户隔离 | `TESTB` 调用 | 看不到 `DEMO` 数据 |
|
||||
|
||||
### 7.3 `GET /api/source/{id}`
|
||||
|
||||
| 用例ID | 场景 | 操作 | 预期结果 |
|
||||
|---|---|---|---|
|
||||
| `SOURCE-DETAIL-001` | 查看资料详情成功 | 查询本公司资料 | HTTP `200`,返回详情及下载地址 |
|
||||
| `SOURCE-DETAIL-002` | 查询不存在资料 | `id` 不存在 | HTTP `404`,`code=NOT_FOUND` |
|
||||
| `SOURCE-DETAIL-003` | 跨租户查看详情 | 用 `TESTB` Token 查 `DEMO` 资料 | HTTP `404` 或不可见 |
|
||||
|
||||
### 7.4 `DELETE /api/source/{id}`
|
||||
|
||||
| 用例ID | 场景 | 操作 | 预期结果 |
|
||||
|---|---|---|---|
|
||||
| `SOURCE-DELETE-001` | 删除 `PENDING` 资料成功 | 删除未进入流水线资料 | HTTP `200` |
|
||||
| `SOURCE-DELETE-002` | 删除不存在资料 | `id` 不存在 | HTTP `404`,`code=NOT_FOUND` |
|
||||
| `SOURCE-DELETE-003` | 删除已进入流水线资料 | 状态为 `PREPROCESSING/EXTRACTING/QA_REVIEW/APPROVED` | HTTP `409`,`code=SOURCE_IN_PIPELINE` |
|
||||
| `SOURCE-DELETE-004` | 非管理员删除 | 用 `UPLOADER` 删除资料 | HTTP `403` |
|
||||
|
||||
## 8. 任务管理
|
||||
|
||||
说明:本组接口复用 `G-AUTH-001~003`、`G-ROLE-001`、`G-TENANT-001`。
|
||||
|
||||
### 8.1 `GET /api/tasks/pool`
|
||||
|
||||
| 用例ID | 场景 | 请求 | 预期结果 |
|
||||
|---|---|---|---|
|
||||
| `TASK-POOL-001` | 标注员查看任务池 | 用 `ANNOTATOR` 调用 | 仅返回 `EXTRACTION + UNCLAIMED` 任务 |
|
||||
| `TASK-POOL-002` | Reviewer/Admin 调用任务池 | 用 `REVIEWER` 或 `ADMIN` 调用 | 返回 `SUBMITTED` 待审任务 |
|
||||
| `TASK-POOL-003` | 跨租户隔离 | `TESTB` 调用 | 仅能看到本公司任务 |
|
||||
|
||||
### 8.2 `GET /api/tasks/mine`
|
||||
|
||||
| 用例ID | 场景 | 请求 | 预期结果 |
|
||||
|---|---|---|---|
|
||||
| `TASK-MINE-001` | 查询我的任务成功 | `?page=1&pageSize=20` | 返回当前用户的 `IN_PROGRESS/SUBMITTED/REJECTED` 任务 |
|
||||
| `TASK-MINE-002` | 按状态过滤 | `?status=REJECTED` | 仅返回对应状态 |
|
||||
| `TASK-MINE-003` | 未领取任务不应出现在 mine | 准备 `UNCLAIMED` 任务 | 列表中不出现该任务 |
|
||||
|
||||
### 8.3 `GET /api/tasks/pending-review`
|
||||
|
||||
| 用例ID | 场景 | 请求 | 预期结果 |
|
||||
|---|---|---|---|
|
||||
| `TASK-REVIEW-001` | Reviewer 查询待审批任务 | `?taskType=EXTRACTION` | 仅返回 `SUBMITTED` 且符合类型的任务 |
|
||||
| `TASK-REVIEW-002` | 非 Reviewer 访问 | 用 `ANNOTATOR` 调用 | HTTP `403` |
|
||||
| `TASK-REVIEW-003` | 跨租户隔离 | `TESTB` 调用 | 看不到 `DEMO` 待审任务 |
|
||||
|
||||
### 8.4 `GET /api/tasks`
|
||||
|
||||
| 用例ID | 场景 | 请求 | 预期结果 |
|
||||
|---|---|---|---|
|
||||
| `TASK-LIST-001` | 管理员查询全部任务 | `?status=SUBMITTED&taskType=QA_GENERATION` | 返回过滤后的本公司任务 |
|
||||
| `TASK-LIST-002` | 非管理员访问 | 用 `REVIEWER` 调用 | HTTP `403` |
|
||||
|
||||
### 8.5 `POST /api/tasks`
|
||||
|
||||
| 用例ID | 场景 | 请求 | 预期结果 |
|
||||
|---|---|---|---|
|
||||
| `TASK-CREATE-001` | 创建提取任务成功 | `{"sourceId":<id>,"taskType":"EXTRACTION"}` | HTTP `200`,返回新任务,状态 `UNCLAIMED` |
|
||||
| `TASK-CREATE-002` | 创建 QA 任务成功 | `{"sourceId":<id>,"taskType":"QA_GENERATION"}` | HTTP `200` |
|
||||
| `TASK-CREATE-003` | 缺少 `sourceId` | 仅传 `taskType` | 应返回明确失败,不应出现裸 `500` |
|
||||
| `TASK-CREATE-004` | 缺少 `taskType` | 仅传 `sourceId` | 应返回明确失败,不应出现裸 `500` |
|
||||
|
||||
### 8.6 `GET /api/tasks/{id}`
|
||||
|
||||
| 用例ID | 场景 | 操作 | 预期结果 |
|
||||
|---|---|---|---|
|
||||
| `TASK-DETAIL-001` | 查询任务详情成功 | 查询本公司任务 | HTTP `200`,返回任务详情 |
|
||||
| `TASK-DETAIL-002` | 查询不存在任务 | 不存在 `id` | HTTP `404`,`code=NOT_FOUND` |
|
||||
| `TASK-DETAIL-003` | 跨租户查看任务 | 用 `TESTB` 查询 `DEMO` 任务 | HTTP `404` 或不可见 |
|
||||
|
||||
### 8.7 `POST /api/tasks/{id}/claim`
|
||||
|
||||
| 用例ID | 场景 | 操作 | 预期结果 |
|
||||
|---|---|---|---|
|
||||
| `TASK-CLAIM-001` | 正常领取未领取任务 | `ANNOTATOR` 领取 `UNCLAIMED` 任务 | HTTP `200`,任务状态变为 `IN_PROGRESS` |
|
||||
| `TASK-CLAIM-002` | 并发抢任务 | 10 并发领取同一任务 | 恰好 1 个成功,其余 HTTP `409`,`code=TASK_CLAIMED` |
|
||||
| `TASK-CLAIM-003` | 重复领取已被占用任务 | 第二个用户再领取 | HTTP `409`,`code=TASK_CLAIMED` |
|
||||
|
||||
### 8.8 `POST /api/tasks/{id}/unclaim`
|
||||
|
||||
| 用例ID | 场景 | 操作 | 预期结果 |
|
||||
|---|---|---|---|
|
||||
| `TASK-UNCLAIM-001` | 放弃进行中任务成功 | 当前领取人放弃 `IN_PROGRESS` 任务 | HTTP `200`,状态回到 `UNCLAIMED` |
|
||||
| `TASK-UNCLAIM-002` | 非法状态放弃 | 对 `SUBMITTED/REJECTED/APPROVED` 任务调用 | HTTP `409`,`code=INVALID_STATE_TRANSITION` |
|
||||
| `TASK-UNCLAIM-003` | 非领取人放弃他人任务 | 用别的标注员调用 | 应被拒绝,不能让他人释放任务 |
|
||||
|
||||
### 8.9 `POST /api/tasks/{id}/reclaim`
|
||||
|
||||
| 用例ID | 场景 | 操作 | 预期结果 |
|
||||
|---|---|---|---|
|
||||
| `TASK-RECLAIM-001` | 原领取人重领被驳回任务 | 原领取人对 `REJECTED` 任务调用 | HTTP `200`,状态变为 `IN_PROGRESS` |
|
||||
| `TASK-RECLAIM-002` | 非原领取人重领 | 其他用户调用 | HTTP `403`,`code=FORBIDDEN` |
|
||||
| `TASK-RECLAIM-003` | 非驳回状态重领 | 对 `IN_PROGRESS` 任务调用 | HTTP `409`,`code=INVALID_STATE_TRANSITION` |
|
||||
|
||||
### 8.10 `PUT /api/tasks/{id}/reassign`
|
||||
|
||||
| 用例ID | 场景 | 请求 | 预期结果 |
|
||||
|---|---|---|---|
|
||||
| `TASK-REASSIGN-001` | 管理员强制指派成功 | `{"userId":<targetUserId>}` | HTTP `200`,任务归属变更到目标用户 |
|
||||
| `TASK-REASSIGN-002` | 指派不存在任务 | 不存在 `id` | HTTP `404`,`code=NOT_FOUND` |
|
||||
| `TASK-REASSIGN-003` | 缺少 `userId` | 空请求体或缺字段 | 应返回明确失败,不应裸 `500` |
|
||||
| `TASK-REASSIGN-004` | 指派后状态一致性 | 指派后查询任务 | 任务应处于可执行状态,归属人与时间被更新 |
|
||||
|
||||
## 9. 提取标注
|
||||
|
||||
说明:本组接口复用 `G-AUTH-001~003`、`G-ROLE-001`、`G-TENANT-001`。
|
||||
|
||||
### 9.1 `GET /api/extraction/{taskId}`
|
||||
|
||||
| 用例ID | 场景 | 操作 | 预期结果 |
|
||||
|---|---|---|---|
|
||||
| `EXT-GET-001` | 获取提取结果成功 | 查询本公司任务 | HTTP `200`,返回 `taskId/sourceType/sourceFilePath/isFinal/resultJson` |
|
||||
| `EXT-GET-002` | 查询不存在任务 | 不存在 `taskId` | HTTP `404`,`code=NOT_FOUND` |
|
||||
| `EXT-GET-003` | 跨租户查询结果 | 用 `TESTB` 查询 `DEMO` 任务 | HTTP `404` 或不可见 |
|
||||
|
||||
### 9.2 `PUT /api/extraction/{taskId}`
|
||||
|
||||
| 用例ID | 场景 | 请求体 | 预期结果 |
|
||||
|---|---|---|---|
|
||||
| `EXT-PUT-001` | 更新结果成功 | 传合法 JSON 字符串 | HTTP `200` |
|
||||
| `EXT-PUT-002` | 非法 JSON | 传坏 JSON | HTTP `400`,`code=INVALID_JSON` |
|
||||
| `EXT-PUT-003` | 任务不存在 | 不存在 `taskId` | HTTP `404`,`code=NOT_FOUND` |
|
||||
|
||||
### 9.3 `POST /api/extraction/{taskId}/submit`
|
||||
|
||||
| 用例ID | 场景 | 操作 | 预期结果 |
|
||||
|---|---|---|---|
|
||||
| `EXT-SUBMIT-001` | 提交成功 | `IN_PROGRESS` 任务提交 | HTTP `200`,任务变为 `SUBMITTED` |
|
||||
| `EXT-SUBMIT-002` | 非法状态提交 | `UNCLAIMED/REJECTED/APPROVED` 时提交 | HTTP `409`,`code=INVALID_STATE_TRANSITION` |
|
||||
|
||||
### 9.4 `POST /api/extraction/{taskId}/approve`
|
||||
|
||||
| 用例ID | 场景 | 操作 | 预期结果 |
|
||||
|---|---|---|---|
|
||||
| `EXT-APPROVE-001` | 审批通过成功 | Reviewer 审批 `SUBMITTED` 任务 | HTTP `200`,原任务 `APPROVED`,`isFinal=true` |
|
||||
| `EXT-APPROVE-002` | 审批通过触发后续链路 | 审批完成后查数据库/接口 | 自动创建 `QA_GENERATION` 任务,`source_data.status=QA_REVIEW`,创建 `training_dataset` |
|
||||
| `EXT-APPROVE-003` | 自审拦截 | 提交人与审批人相同 | HTTP `403`,`code=SELF_REVIEW_FORBIDDEN` |
|
||||
| `EXT-APPROVE-004` | 非法状态审批 | 未提交任务直接审批 | HTTP `409`,`code=INVALID_STATE_TRANSITION` |
|
||||
|
||||
### 9.5 `POST /api/extraction/{taskId}/reject`
|
||||
|
||||
| 用例ID | 场景 | 请求 | 预期结果 |
|
||||
|---|---|---|---|
|
||||
| `EXT-REJECT-001` | 驳回成功 | `{"reason":"实体识别有误"}` | HTTP `200`,任务变为 `REJECTED` |
|
||||
| `EXT-REJECT-002` | 驳回原因为空 | 空 body 或空 reason | HTTP `400`,`code=REASON_REQUIRED` |
|
||||
| `EXT-REJECT-003` | 自审驳回 | 提交人与驳回人相同 | HTTP `403`,`code=SELF_REVIEW_FORBIDDEN` |
|
||||
| `EXT-REJECT-004` | 驳回后可重领重提 | 驳回后原领取人调 `/reclaim` 再 `/submit` | 可重领,任务恢复到 `SUBMITTED` |
|
||||
|
||||
## 10. 问答生成
|
||||
|
||||
说明:本组接口复用 `G-AUTH-001~003`、`G-ROLE-001`、`G-TENANT-001`。
|
||||
|
||||
### 10.1 `GET /api/qa/{taskId}`
|
||||
|
||||
| 用例ID | 场景 | 操作 | 预期结果 |
|
||||
|---|---|---|---|
|
||||
| `QA-GET-001` | 获取候选问答对成功 | 查询本公司 QA 任务 | HTTP `200`,返回 `taskId/sourceType/items` |
|
||||
| `QA-GET-002` | 查询不存在任务 | 不存在 `taskId` | HTTP `404`,`code=NOT_FOUND` |
|
||||
| `QA-GET-003` | 跨租户查询 | 用 `TESTB` 查询 `DEMO` 任务 | HTTP `404` 或不可见 |
|
||||
|
||||
### 10.2 `PUT /api/qa/{taskId}`
|
||||
|
||||
| 用例ID | 场景 | 请求体 | 预期结果 |
|
||||
|---|---|---|---|
|
||||
| `QA-PUT-001` | 更新候选问答对成功 | `{"items":[...]}` | HTTP `200` |
|
||||
| `QA-PUT-002` | 非法 JSON | 传坏 JSON | HTTP `400`,`code=INVALID_JSON` |
|
||||
| `QA-PUT-003` | 缺失 items 字段 | 传空对象 `{}` | 不应报 5xx;若允许则生成空 conversations |
|
||||
|
||||
### 10.3 `POST /api/qa/{taskId}/submit`
|
||||
|
||||
| 用例ID | 场景 | 操作 | 预期结果 |
|
||||
|---|---|---|---|
|
||||
| `QA-SUBMIT-001` | 提交成功 | `IN_PROGRESS` 任务提交 | HTTP `200`,任务变为 `SUBMITTED` |
|
||||
| `QA-SUBMIT-002` | 非法状态提交 | 对 `UNCLAIMED/REJECTED/APPROVED` 任务提交 | HTTP `409`,`code=INVALID_STATE_TRANSITION` |
|
||||
|
||||
### 10.4 `POST /api/qa/{taskId}/approve`
|
||||
|
||||
| 用例ID | 场景 | 操作 | 预期结果 |
|
||||
|---|---|---|---|
|
||||
| `QA-APPROVE-001` | QA 审批通过成功 | Reviewer 审批 `SUBMITTED` QA 任务 | HTTP `200` |
|
||||
| `QA-APPROVE-002` | 审批通过完成整条流水线 | 审批完成后查状态 | `training_dataset.status=APPROVED`,任务 `APPROVED`,`source_data.status=APPROVED` |
|
||||
| `QA-APPROVE-003` | 自审拦截 | 提交人与审批人相同 | HTTP `403`,`code=SELF_REVIEW_FORBIDDEN` |
|
||||
|
||||
### 10.5 `POST /api/qa/{taskId}/reject`
|
||||
|
||||
| 用例ID | 场景 | 请求 | 预期结果 |
|
||||
|---|---|---|---|
|
||||
| `QA-REJECT-001` | 驳回成功 | `{"reason":"问题描述不准确"}` | HTTP `200`,任务变为 `REJECTED` |
|
||||
| `QA-REJECT-002` | 驳回原因为空 | 空 body 或空 reason | HTTP `400`,`code=REASON_REQUIRED` |
|
||||
| `QA-REJECT-003` | 驳回后候选数据删除 | 驳回后检查 `training_dataset` | 候选问答对被删除,`source_data` 维持 `QA_REVIEW` |
|
||||
| `QA-REJECT-004` | 驳回后可重领重提 | 原领取人调 `/reclaim` 再 `/submit` | 可重领,重新提交成功 |
|
||||
|
||||
## 11. 导出与微调
|
||||
|
||||
说明:本组接口复用 `G-AUTH-001~003`、`G-ROLE-001`、`G-TENANT-001`。
|
||||
|
||||
### 11.1 `GET /api/training/samples`
|
||||
|
||||
| 用例ID | 场景 | 请求 | 预期结果 |
|
||||
|---|---|---|---|
|
||||
| `EXPORT-SAMPLE-001` | 查询已审批样本成功 | `?page=1&pageSize=20` | HTTP `200`,返回 `APPROVED` 样本 |
|
||||
| `EXPORT-SAMPLE-002` | 仅看未导出样本 | `?exported=false` | 只返回 `export_batch_id` 为空的数据 |
|
||||
| `EXPORT-SAMPLE-003` | 按样本类型过滤 | `?sampleType=TEXT` | 仅返回对应类型 |
|
||||
| `EXPORT-SAMPLE-004` | 非管理员访问 | 用 `ANNOTATOR` 调用 | HTTP `403` |
|
||||
|
||||
### 11.2 `POST /api/export/batch`
|
||||
|
||||
| 用例ID | 场景 | 请求 | 预期结果 |
|
||||
|---|---|---|---|
|
||||
| `EXPORT-BATCH-001` | 创建导出批次成功 | `{"sampleIds":[<approvedId1>,<approvedId2>]}` | HTTP `201`,返回批次信息 |
|
||||
| `EXPORT-BATCH-002` | 样本列表为空 | `{"sampleIds":[]}` | HTTP `400`,`code=EMPTY_SAMPLES` |
|
||||
| `EXPORT-BATCH-003` | 包含未审批样本 | 混入 `PENDING_REVIEW/REJECTED` 样本 | HTTP `400`,`code=INVALID_SAMPLES` |
|
||||
| `EXPORT-BATCH-004` | 包含他租户样本 | 混入其他公司样本 ID | HTTP `400`,`code=INVALID_SAMPLES` |
|
||||
| `EXPORT-BATCH-005` | 批次创建后回写样本导出信息 | 创建成功后查询样本 | `exportBatchId/exportedAt` 已写入 |
|
||||
|
||||
### 11.3 `POST /api/export/{batchId}/finetune`
|
||||
|
||||
| 用例ID | 场景 | 操作 | 预期结果 |
|
||||
|---|---|---|---|
|
||||
| `FINETUNE-TRIGGER-001` | 首次触发微调成功 | 对 `NOT_STARTED` 批次调用 | HTTP `200`,返回 `glmJobId/finetuneStatus=RUNNING` |
|
||||
| `FINETUNE-TRIGGER-002` | 重复触发微调 | 对已启动批次再次调用 | HTTP `409`,`code=FINETUNE_ALREADY_STARTED` |
|
||||
| `FINETUNE-TRIGGER-003` | 触发不存在批次 | 不存在 `batchId` | HTTP `404`,`code=NOT_FOUND` |
|
||||
|
||||
### 11.4 `GET /api/export/{batchId}/status`
|
||||
|
||||
| 用例ID | 场景 | 操作 | 预期结果 |
|
||||
|---|---|---|---|
|
||||
| `FINETUNE-STATUS-001` | 未启动批次查询状态 | `glmJobId` 为空批次 | HTTP `200`,返回 `NOT_STARTED/progress=0` |
|
||||
| `FINETUNE-STATUS-002` | 已启动批次查询状态 | 对已提交微调任务批次调用 | HTTP `200`,返回 `batchId/glmJobId/finetuneStatus/progress/errorMessage` |
|
||||
| `FINETUNE-STATUS-003` | 跨租户状态隔离 | 用 `TESTB` 查 `DEMO` 批次 | HTTP `404` 或不可见 |
|
||||
|
||||
### 11.5 `GET /api/export/list`
|
||||
|
||||
| 用例ID | 场景 | 请求 | 预期结果 |
|
||||
|---|---|---|---|
|
||||
| `EXPORT-LIST-001` | 分页查询导出批次成功 | `?page=1&pageSize=20` | HTTP `200`,仅返回当前公司批次 |
|
||||
| `EXPORT-LIST-002` | 跨租户隔离 | `TESTB` 调用 | 看不到 `DEMO` 批次 |
|
||||
|
||||
## 12. 系统配置
|
||||
|
||||
说明:本组接口复用 `G-AUTH-001~003`、`G-ROLE-001`、`G-TENANT-001`。
|
||||
|
||||
### 12.1 `GET /api/config`
|
||||
|
||||
| 用例ID | 场景 | 操作 | 预期结果 |
|
||||
|---|---|---|---|
|
||||
| `CONFIG-LIST-001` | 查询合并配置成功 | 管理员查询 | HTTP `200`,返回当前公司可见配置列表 |
|
||||
| `CONFIG-LIST-002` | 公司配置覆盖全局默认 | 先写公司专属 `model_default`,再查询 | 返回 `scope=COMPANY` 且值为公司专属值 |
|
||||
| `CONFIG-LIST-003` | 未配置公司专属时回退全局 | 清除公司专属后查询 | 返回 `scope=GLOBAL` |
|
||||
| `CONFIG-LIST-004` | 跨租户隔离 | `DEMO` 配置不影响 `TESTB` | 另一公司仍看到自己的配置或全局默认 |
|
||||
|
||||
### 12.2 `PUT /api/config/{key}`
|
||||
|
||||
| 用例ID | 场景 | 请求 | 预期结果 |
|
||||
|---|---|---|---|
|
||||
| `CONFIG-UPSERT-001` | 新增公司专属配置成功 | `{"value":"glm-4-plus","description":"默认模型"}` | HTTP `200`,创建成功 |
|
||||
| `CONFIG-UPSERT-002` | 更新同键配置成功 | 同一个 `key` 连续两次 `PUT` | HTTP `200`,第二次为更新而不是重复插入 |
|
||||
| `CONFIG-UPSERT-003` | 未知配置键 | `PUT /api/config/unknown_key` | HTTP `400`,`code=UNKNOWN_CONFIG_KEY` |
|
||||
| `CONFIG-UPSERT-004` | 空配置值 | `{"value":""}` | HTTP `400`,`code=INVALID_CONFIG_VALUE` |
|
||||
|
||||
## 13. 视频处理
|
||||
|
||||
说明:
|
||||
|
||||
- `POST /api/video/callback` 为公开接口,不复用 `G-AUTH-001~003`
|
||||
- 其余视频接口复用 `G-AUTH-001~003`、`G-ROLE-001`、`G-TENANT-001`
|
||||
|
||||
### 13.1 `POST /api/video/process`
|
||||
|
||||
| 用例ID | 场景 | 请求 | 预期结果 |
|
||||
|---|---|---|---|
|
||||
| `VIDEO-CREATE-001` | 创建视频处理任务成功 | `{"sourceId":<videoSourceId>,"jobType":"FRAME_EXTRACT","params":"{\"frameInterval\":30}"}` | 创建成功,返回 job,资料状态进入 `PREPROCESSING` |
|
||||
| `VIDEO-CREATE-002` | 使用另一种任务类型成功 | `jobType=VIDEO_TO_TEXT` | 创建成功 |
|
||||
| `VIDEO-CREATE-003` | 缺少必要字段 | 缺 `sourceId` 或 `jobType` | 返回失败,`code=INVALID_PARAMS`,且不能创建 job |
|
||||
| `VIDEO-CREATE-004` | 非法任务类型 | `jobType=UNKNOWN` | HTTP `400`,`code=INVALID_JOB_TYPE` |
|
||||
| `VIDEO-CREATE-005` | 非视频资料创建视频任务 | 对非 `VIDEO` 资料调用 | 应返回明确失败,不应产生错误状态迁移 |
|
||||
| `VIDEO-CREATE-006` | 跨租户创建视频任务 | 用 `TESTB` 处理 `DEMO` 资料 | HTTP `404` 或不可见 |
|
||||
|
||||
### 13.2 `GET /api/video/jobs/{jobId}`
|
||||
|
||||
| 用例ID | 场景 | 操作 | 预期结果 |
|
||||
|---|---|---|---|
|
||||
| `VIDEO-GET-001` | 查询视频任务成功 | 查询本公司 `jobId` | HTTP `200`,返回任务状态、重试次数、输出路径等 |
|
||||
| `VIDEO-GET-002` | 查询不存在任务 | 不存在 `jobId` | HTTP `404`,`code=NOT_FOUND` |
|
||||
| `VIDEO-GET-003` | 跨租户查询 | 用 `TESTB` 查 `DEMO` 任务 | HTTP `404` 或不可见 |
|
||||
|
||||
### 13.3 `POST /api/video/jobs/{jobId}/reset`
|
||||
|
||||
| 用例ID | 场景 | 操作 | 预期结果 |
|
||||
|---|---|---|---|
|
||||
| `VIDEO-RESET-001` | 重置失败任务成功 | 对 `FAILED` 任务调用 | HTTP `200`,job 状态变为 `PENDING`,`retryCount=0` |
|
||||
| `VIDEO-RESET-002` | 非失败任务不允许重置 | 对 `PENDING/RETRYING/SUCCESS` 调用 | HTTP `400`,`code=INVALID_TRANSITION` |
|
||||
| `VIDEO-RESET-003` | 跨租户重置 | 用 `TESTB` 调 `DEMO` 任务 | HTTP `404` 或不可见 |
|
||||
|
||||
### 13.4 `POST /api/video/callback`
|
||||
|
||||
| 用例ID | 场景 | 请求 | 预期结果 |
|
||||
|---|---|---|---|
|
||||
| `VIDEO-CALLBACK-001` | SUCCESS 回调成功 | `{"jobId":123,"status":"SUCCESS","outputPath":"processed/frames.zip"}` | HTTP `200`,job 变 `SUCCESS`,`source_data.status=PENDING` |
|
||||
| `VIDEO-CALLBACK-002` | SUCCESS 回调幂等 | 对同一 `jobId` 连续两次发送相同 SUCCESS 回调 | 两次都成功,第二次不重复改状态、不重复产出副作用 |
|
||||
| `VIDEO-CALLBACK-003` | FAILED 回调触发重试 | 对未达重试上限 job 发 `FAILED` | job 变 `RETRYING`,`retryCount+1` |
|
||||
| `VIDEO-CALLBACK-004` | 超过最大重试次数 | `retryCount=maxRetries-1` 时再发 `FAILED` | job 变 `FAILED`,`source_data.status=PENDING` |
|
||||
| `VIDEO-CALLBACK-005` | 回调 job 不存在 | `jobId` 不存在 | 接口不应打穿服务;应安全返回,无脏数据写入 |
|
||||
| `VIDEO-CALLBACK-006` | 启用共享密钥时密钥错误 | 不传或传错 `X-Callback-Secret` | 返回失败,`code=UNAUTHORIZED` |
|
||||
| `VIDEO-CALLBACK-007` | 回调缺少 `jobId/status` | 缺关键字段 | 返回明确失败,不应裸 `500` |
|
||||
|
||||
## 14. 推荐执行顺序
|
||||
|
||||
建议按以下顺序执行,能更快定位问题来源:
|
||||
|
||||
1. 认证管理
|
||||
2. 公司管理
|
||||
3. 用户管理
|
||||
4. 资料管理
|
||||
5. 任务管理
|
||||
6. 提取标注
|
||||
7. 问答生成
|
||||
8. 导出与微调
|
||||
9. 系统配置
|
||||
10. 视频处理
|
||||
|
||||
## 15. 高风险回归点
|
||||
|
||||
每次版本回归至少覆盖以下高风险用例:
|
||||
|
||||
| 优先级 | 用例ID | 说明 |
|
||||
|---|---|---|
|
||||
| P0 | `AUTH-LOGIN-001` | 登录主链路 |
|
||||
| P0 | `AUTH-LOGOUT-002` | 退出后 Token 立即失效 |
|
||||
| P0 | `USER-STATUS-002` | 禁用账号后旧 Token 失效 |
|
||||
| P0 | `SOURCE-DELETE-003` | 资料进入流水线后不可删除 |
|
||||
| P0 | `TASK-CLAIM-002` | 并发抢任务只允许一个成功 |
|
||||
| P0 | `EXT-APPROVE-002` | 提取审批通过自动推进 QA 链路 |
|
||||
| P0 | `QA-APPROVE-002` | QA 审批通过完成整条流水线 |
|
||||
| P0 | `EXPORT-BATCH-003` | 非 APPROVED 样本不可导出 |
|
||||
| P0 | `CONFIG-LIST-004` | 配置跨租户隔离 |
|
||||
| P0 | `VIDEO-CALLBACK-002` | 视频回调幂等 |
|
||||
|
||||
## 16. 后续落地建议
|
||||
|
||||
本文档适合作为三类产物的源:
|
||||
|
||||
- Swagger 手工测试清单
|
||||
- Postman/Apifox/Newman 自动化用例集
|
||||
- `src/test/java/com/label/blackbox` 下的 API 黑盒自动化测试
|
||||
|
||||
若需要继续落地,建议优先把以下用例自动化:
|
||||
|
||||
- 认证与 Token 生命周期
|
||||
- 任务领取并发
|
||||
- 提取/问答审批状态流转
|
||||
- 导出样本校验
|
||||
- 视频回调幂等与重试
|
||||
Reference in New Issue
Block a user