diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..399e818 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,9 @@ +FROM registry.bjzgzp.com:4433/library/eclipse-temurin:21-jdk-ubi10-minimal + +WORKDIR /app + +COPY ./label-backend-1.0.0-SNAPSHOT.jar /app/label-backend-1.0.0-SNAPSHOT.jar + +EXPOSE 18082 + +ENTRYPOINT ["java", "-Djava.net.preferIPv4Stack=true", "-jar", "/app/label-backend-1.0.0-SNAPSHOT.jar"] \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..c21b44f --- /dev/null +++ b/README.md @@ -0,0 +1,401 @@ +# label-backend + +## 项目简介 + +`label-backend` 是知识图谱智能标注平台的后端服务,负责资料上传、任务分发、提取标注、问答生成、训练样本导出、系统配置和视频预处理等核心流程。 + +系统采用 Spring Boot 3 + MyBatis-Plus + PostgreSQL + Redis 的技术组合,面向多租户标注场景设计,支持: + +- 公司级数据隔离 +- 基于 Redis Token 的认证鉴权 +- 提取与问答两阶段标注流程 +- 导出训练数据并对接微调任务 +- 视频预处理与异步回调 +- 审计日志与任务状态追踪 + +代码结构已按扁平标准目录整理,主包位于 `src/main/java/com/label`。 + +## 功能特性 + +- 认证鉴权 + - 使用 UUID Bearer Token + Redis 会话存储 + - 自定义 `@RequireAuth`、`@RequireRole` + - 角色分级:`ADMIN > REVIEWER > ANNOTATOR > UPLOADER` +- 多租户隔离 + - 基于 `CompanyContext` + `TenantLineInnerInterceptor` + - 对租户表自动追加 `company_id` 条件 + - 特殊表通过显式 `companyId` 参数校验 +- 公司与用户管理 + - 公司 CRUD + - 公司内用户创建、状态变更、角色变更 + - 角色变更和禁用后即时刷新或失效 Redis Token +- 资料管理 + - 支持文本、图片、视频三类原始资料 + - 上传到 RustFS,数据库保存元数据 + - 支持按角色查看、查询详情、删除 +- 任务管理 + - 任务池、我的任务、待审批队列、管理员全量视图 + - Redis 分布式锁 + 数据库原子更新保证任务领取并发安全 +- 提取标注 + - AI 预标注 + - 标注结果整体覆盖更新 + - 提交、审批通过、驳回 +- 问答生成 + - 基于提取审批通过事件生成候选问答对 + - 支持编辑、提交、审批、驳回 +- 训练数据导出 + - 查询已审批样本 + - 创建导出批次 + - 触发微调任务并查询状态 +- 系统配置 + - 支持公司专属配置覆盖全局默认配置 + - 配置项存储于 `sys_config` +- 视频处理 + - 支持触发视频预处理任务 + - 支持异步回调、失败重试、管理员重置 + +## 技术栈 + +- Java 21 +- Spring Boot 3.1.5 +- Spring MVC +- MyBatis-Plus 3.5.3.1 +- PostgreSQL +- Redis +- Spring AOP +- springdoc-openapi +- Testcontainers +- RustFS / S3 兼容对象存储 + +## 项目结构 + +项目根目录结构: + +```text +label_backend/ +├── assembly/ # 分发包描述与占位目录 +├── docs/ # 设计、计划、规范文档 +├── scripts/ # 启动脚本 +├── src/ +│ ├── main/ +│ │ ├── java/com/label/ # 主代码 +│ │ └── resources/ +│ │ ├── application.yml +│ │ ├── logback.xml +│ │ └── sql/ # 初始化 SQL,不打入构建产物 +│ └── test/ +│ ├── java/ # 单元测试与集成测试 +│ └── resources/db/init.sql # Testcontainers 测试初始化 SQL +├── docker-compose.yml +├── Dockerfile +├── pom.xml +└── README.md +``` + +Java 包结构: + +```text +com.label +├── annotation # 自定义注解,如 RequireAuth / RequireRole / OperationLog +├── aspect # AOP 审计切面 +├── common # 通用能力:auth、context、exception、result、storage、ai、statemachine +├── config # Spring 配置、MyBatis-Plus 配置、认证拦截器注册 +├── controller # 所有 REST 接口 +├── dto # DTO +├── entity # 实体 +├── event # 领域事件 +├── interceptor # 认证拦截器 +├── listener # 事件监听器 +├── mapper # MyBatis Mapper +├── service # 业务服务 +└── util # 工具类 +``` + +## 数据库表结构 + +初始化脚本位于 [init.sql](d:/workspace/label/label_backend/src/main/resources/sql/init.sql),包含 11 张核心表: + +- `sys_company` + - 租户公司表 +- `sys_user` + - 公司用户表,包含角色与状态 +- `source_data` + - 原始资料元数据,支持 `TEXT` / `IMAGE` / `VIDEO` +- `annotation_task` + - 标注任务表,支持 `EXTRACTION` / `QA_GENERATION` +- `annotation_result` + - 提取阶段 JSON 结果 +- `training_dataset` + - 训练样本数据,存储 GLM 格式 JSON +- `export_batch` + - 导出批次与微调任务状态 +- `sys_config` + - 全局与公司级配置 +- `sys_operation_log` + - 审计日志,只追加不更新 +- `annotation_task_history` + - 任务状态变更历史 +- `video_process_job` + - 视频预处理任务与回调状态 + +当前主要状态机: + +- `source_data.status` + - `PENDING` / `PREPROCESSING` / `EXTRACTING` / `QA_REVIEW` / `APPROVED` +- `annotation_task.status` + - `UNCLAIMED` / `IN_PROGRESS` / `SUBMITTED` / `APPROVED` / `REJECTED` +- `training_dataset.status` + - `PENDING_REVIEW` / `APPROVED` / `REJECTED` +- `video_process_job.status` + - `PENDING` / `RETRYING` / `SUCCESS` / `FAILED` + +## 配置说明 + +主配置文件位于 [application.yml](d:/workspace/label/label_backend/src/main/resources/application.yml)。 + +### 环境变量 + +| 变量名 | 说明 | +|---|---| +| `SPRING_DATASOURCE_URL` | PostgreSQL JDBC 地址 | +| `SPRING_DATASOURCE_USERNAME` | PostgreSQL 用户名 | +| `SPRING_DATASOURCE_PASSWORD` | PostgreSQL 密码 | +| `SPRING_DATA_REDIS_HOST` | Redis 主机 | +| `SPRING_DATA_REDIS_PORT` | Redis 端口 | +| `SPRING_DATA_REDIS_PASSWORD` | Redis 密码 | +| `RUSTFS_ENDPOINT` | RustFS / S3 兼容服务地址 | +| `RUSTFS_ACCESS_KEY` | RustFS Access Key | +| `RUSTFS_SECRET_KEY` | RustFS Secret Key | +| `AI_SERVICE_BASE_URL` | AI 服务地址 | +| `VIDEO_CALLBACK_SECRET` | 视频处理回调共享密钥 | + +### 关键配置项 + +- `auth.enabled` + - `true` 时启用真实 Token 鉴权 + - `false` 时使用 mock 身份,便于本地开发 +- `auth.mock-company-id` + - 开发模式下的模拟公司 ID +- `auth.mock-user-id` + - 开发模式下的模拟用户 ID +- `auth.mock-role` + - 开发模式下的模拟角色 +- `token.ttl-seconds` + - Token 有效期,默认 7200 秒 +- `springdoc.api-docs.path` + - OpenAPI 文档路径,默认 `/v3/api-docs` +- `springdoc.swagger-ui.path` + - Swagger UI 路径,默认 `/swagger-ui.html` + +## API接口 + +以下为当前主要接口分组。 + +### 1. 认证接口 + +- `POST /api/auth/login` + - 登录并返回 Bearer Token +- `POST /api/auth/logout` + - 登出并立即失效当前 Token +- `GET /api/auth/me` + - 获取当前登录用户信息 + +### 2. 公司管理 + +- `GET /api/companies` +- `POST /api/companies` +- `PUT /api/companies/{id}` +- `PUT /api/companies/{id}/status` +- `DELETE /api/companies/{id}` + +### 3. 用户管理 + +- `GET /api/users` +- `POST /api/users` +- `PUT /api/users/{id}` +- `PUT /api/users/{id}/status` +- `PUT /api/users/{id}/role` + +### 4. 资料管理 + +- `POST /api/source/upload` +- `GET /api/source/list` +- `GET /api/source/{id}` +- `DELETE /api/source/{id}` + +### 5. 任务管理 + +- `GET /api/tasks/pool` +- `GET /api/tasks/mine` +- `GET /api/tasks/pending-review` +- `GET /api/tasks` +- `POST /api/tasks` +- `GET /api/tasks/{id}` +- `POST /api/tasks/{id}/claim` +- `POST /api/tasks/{id}/unclaim` +- `POST /api/tasks/{id}/reclaim` +- `PUT /api/tasks/{id}/reassign` + +### 6. 提取标注 + +- `GET /api/extraction/{taskId}` +- `PUT /api/extraction/{taskId}` +- `POST /api/extraction/{taskId}/submit` +- `POST /api/extraction/{taskId}/approve` +- `POST /api/extraction/{taskId}/reject` + +### 7. 问答生成 + +- `GET /api/qa/{taskId}` +- `PUT /api/qa/{taskId}` +- `POST /api/qa/{taskId}/submit` +- `POST /api/qa/{taskId}/approve` +- `POST /api/qa/{taskId}/reject` + +### 8. 导出与微调 + +- `GET /api/training/samples` +- `POST /api/export/batch` +- `POST /api/export/{batchId}/finetune` +- `GET /api/export/{batchId}/status` +- `GET /api/export/list` + +### 9. 系统配置 + +- `GET /api/config` +- `PUT /api/config/{key}` + +### 10. 视频处理 + +- `POST /api/video/process` +- `GET /api/video/jobs/{jobId}` +- `POST /api/video/jobs/{jobId}/reset` +- `POST /api/video/callback` + +## 定时任务 + +当前项目中**没有启用 Spring `@Scheduled` 定时同步任务**。 + +现有异步能力主要通过以下方式完成: + +- 事务提交后事件监听 + - 提取审批通过后触发问答生成 +- 外部 AI 服务异步回调 + - 视频处理完成后回调 `/api/video/callback` +- Redis 分布式锁 + - 用于任务领取并发控制 + +如果后续需要周期性任务,建议单独引入明确的调度场景,不要复用当前业务链路中的事件机制。 + +## 部署说明 + +### 1. 数据库初始化 + +初始化 SQL 位于: + +- 开发/部署初始化脚本 + - [src/main/resources/sql/init.sql](d:/workspace/label/label_backend/src/main/resources/sql/init.sql) + +说明: + +- `src/main/resources/sql/init.sql` 会随源码保存,但**不会被打入 jar、target/classes 或分发包** +- `docker-compose.yml` 通过挂载该文件完成 PostgreSQL 初始化 + +### 2. 本地构建 + +```bash +mvn clean package -DskipTests +``` + +构建产物: +... +- `target/label-backend-1.0.0-SNAPSHOT.zip` +- `target/label-backend-1.0.0-SNAPSHOT.tar.gz` + +### 3. 分发包结构 + +分发包由 [distribution.xml](d:/workspace/label/label_backend/assembly/distribution.xml) 组装,解压后结构如下: + +```text +label-backend-/ +├── bin/ +│ └── start.sh +├── etc/ +│ ├── application.yml +│ └── logback.xml +├── libs/ +│ ├── label-backend-.jar +│ └── *.jar +└── logs/ +``` + +### 4. 启动脚本 + +启动脚本位于 [start.sh](d:/workspace/label/label_backend/scripts/start.sh)。 + +行为说明: + +- 在 Docker 容器中检测到 `/.dockerenv` 时,前台 `exec java ...` +- 在宿主机环境中使用 `nohup` 后台启动 +- 日志默认写入 `logs/startup.log` + +### 5. Docker Compose 启动 + +```bash +docker compose up -d +``` + +当前 `docker-compose.yml` 会启动: + +- PostgreSQL +- Redis +- RustFS(当前使用 MinIO 作为 S3 兼容替代) +- backend +- ai-service 占位服务 +- frontend 占位服务 + +### 6. Docker 镜像构建 + +```bash +docker build -t label-backend:latest . +``` + +`Dockerfile` 使用多阶段构建,并从项目根目录的 `scripts/start.sh` 复制启动脚本。 + +## 注意事项 + +1. 开发模式下 `auth.enabled=false` + - 此时会使用 mock 用户身份,不适合生产环境 + - 生产部署前必须显式启用真实鉴权 +2. 多租户隔离仍依赖 `CompanyContext` + `TenantLineInnerInterceptor` + - 租户表查询默认依赖租户拦截器 + - 个别特殊场景通过显式 `companyId` 参数校验 +3. `sys_config`、`sys_company`、`video_process_job` 属于特殊表 + - 其中部分表被排除出自动租户注入,需在服务层显式控制 +4. SQL 已迁移到 `src/main/resources/sql` + - 仅作为源码级初始化文件保留 + - 不会打进构建产物 +5. 集成测试依赖 Testcontainers + - 运行完整集成测试需要本机可用 Docker 环境 +6. 认证实现已移除 Shiro + - 当前使用自定义拦截器、注解与 Redis Token +7. 用户上下文 ThreadLocal 已移除 + - 当前只保留 `CompanyContext` + - 用户主体通过请求属性中的 `TokenPrincipal` 传递 + +## 开发规范 + +当前约束摘要: + +- 统一扁平目录结构,避免再次引入按业务域分层的旧目录 +- DTO 统一放在 `dto/`,不再拆分 `request/response` +- Service 统一放在 `service/`,不拆 `service/impl` +- 业务规则优先放在 Service,Controller 只负责 HTTP 协议层 +- 新增接口需同步补齐 Swagger 注解与测试 +- 所有对外接口参数必须在 Swagger 中明确体现名称、类型和含义 +- 固定结构请求体禁止继续使用匿名 `Map` 或 `Map`,必须定义 DTO 并补齐 `@Schema` 字段说明 +- 固定结构响应应优先使用明确 DTO,或至少为 Swagger 暴露对象补齐字段级 `@Schema` 注解 +- 路径参数、查询参数、请求体、分页包装和通用返回体都必须维护可读的 OpenAPI 文档说明 +- 需要保持历史兼容的原始 JSON 字符串请求体可以继续使用 `String`,但必须在 Swagger `@RequestBody` 中说明完整 JSON body 的提交方式和兼容原因 +- 修改 Controller 参数、请求 DTO、响应 DTO 或对外实体后,必须运行 `mvn -Dtest=OpenApiAnnotationTest test`,确保 Swagger 参数名称、类型和含义没有回退 +- 目录、配置、打包方式变化后,README、设计文档和部署说明必须同步更新 diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..35345fc --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,96 @@ +version: "3.9" + +services: + postgres: + image: postgres:16-alpine + environment: + POSTGRES_DB: label_db + POSTGRES_USER: label + POSTGRES_PASSWORD: label_password + ports: + - "5432:5432" + volumes: + - postgres_data:/var/lib/postgresql/data + - ./src/main/resources/sql/init.sql:/docker-entrypoint-initdb.d/init.sql + healthcheck: + test: ["CMD-SHELL", "pg_isready -U label -d label_db"] + interval: 10s + timeout: 5s + retries: 5 + + redis: + image: redis:7-alpine + command: redis-server --requirepass redis_password + ports: + - "6379:6379" + healthcheck: + test: ["CMD", "redis-cli", "-a", "redis_password", "ping"] + interval: 10s + timeout: 5s + retries: 5 + + # RustFS is an S3-compatible object storage service. + # Using MinIO as a drop-in S3 API substitute for development/testing. + # Replace with the actual RustFS image in production environments. + rustfs: + image: minio/minio:latest + command: server /data --console-address ":9001" + environment: + MINIO_ROOT_USER: minioadmin + MINIO_ROOT_PASSWORD: minioadmin + ports: + - "9000:9000" + - "9001:9001" + volumes: + - rustfs_data:/data + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"] + interval: 10s + timeout: 5s + retries: 5 + + backend: + build: . + ports: + - "8080:8080" + environment: + SPRING_DATASOURCE_URL: jdbc:postgresql://postgres:5432/label_db + SPRING_DATASOURCE_USERNAME: label + SPRING_DATASOURCE_PASSWORD: label_password + SPRING_DATA_REDIS_HOST: redis + SPRING_DATA_REDIS_PORT: 6379 + SPRING_DATA_REDIS_PASSWORD: redis_password + RUSTFS_ENDPOINT: http://rustfs:9000 + RUSTFS_ACCESS_KEY: minioadmin + RUSTFS_SECRET_KEY: minioadmin + AI_SERVICE_BASE_URL: http://ai-service:8000 + depends_on: + postgres: + condition: service_healthy + redis: + condition: service_healthy + rustfs: + condition: service_healthy + healthcheck: + test: ["CMD-SHELL", "wget -q --spider http://localhost:8080/actuator/health 2>/dev/null || exit 1"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 60s + + # Placeholder AI service — replace with the actual FastAPI image in production. + ai-service: + image: python:3.11-slim + command: ["python3", "-m", "http.server", "8000"] + ports: + - "8000:8000" + + # Placeholder frontend — replace with the actual Nginx + static build in production. + frontend: + image: nginx:alpine + ports: + - "80:80" + +volumes: + postgres_data: + rustfs_data: