You've already forked DataMate
Compare commits
7 Commits
lsf
..
7606cd34bd
| Author | SHA1 | Date | |
|---|---|---|---|
| 7606cd34bd | |||
| 340a403c54 | |||
| f5851fd29b | |||
| a635e21fa7 | |||
| 67fc8b491b | |||
| eb2ad9f199 | |||
| 998a1f7258 |
@@ -1,21 +0,0 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.{java,kt}]
|
||||
indent_size = 4
|
||||
|
||||
[*.{py}]
|
||||
indent_size = 4
|
||||
|
||||
[*.{md}]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
[Makefile]
|
||||
indent_style = tab
|
||||
@@ -1,86 +0,0 @@
|
||||
# 对于大多数项目,此工作流文件无需更改;只需提交到您的仓库即可。
|
||||
#
|
||||
# 您可以根据需要修改此文件,以覆盖分析的语言集,或提供自定义查询或构建逻辑。
|
||||
#
|
||||
# ******** 注意 ********
|
||||
# 我们已尝试检测您的仓库中的语言。请检查下面定义的 `language` 矩阵,确保包含了所有受支持的 CodeQL 语言。
|
||||
name: "CodeQL Advanced"
|
||||
on:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
pull_request:
|
||||
branches: [ "main" ]
|
||||
schedule:
|
||||
- cron: '23 5 * * 2'
|
||||
workflow_dispatch:
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze (${{ matrix.language }})
|
||||
# Runner 的规格会影响 CodeQL 分析时间。详情请参阅:
|
||||
# - https://gh.io/recommended-hardware-resources-for-running-codeql
|
||||
# - https://gh.io/supported-runners-and-hardware-resources
|
||||
# - https://gh.io/using-larger-runners (仅限 GitHub.com)
|
||||
# 建议使用更高规格的 Runner 或更大资源的机器以提升分析速度。
|
||||
runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}
|
||||
permissions:
|
||||
# 所有工作流都需要
|
||||
security-events: write
|
||||
# 获取内部或私有 CodeQL 包时需要
|
||||
packages: read
|
||||
# 仅私有仓库工作流需要
|
||||
actions: read
|
||||
contents: read
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- language: actions
|
||||
build-mode: none
|
||||
- language: javascript-typescript
|
||||
build-mode: none
|
||||
- language: python
|
||||
build-mode: none
|
||||
- language: java-kotlin
|
||||
build-mode: none
|
||||
# CodeQL 支持以下 'language' 关键字:'actions', 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'rust', 'swift'
|
||||
# 使用 `c-cpp` 可分析 C、C++ 或两者的代码
|
||||
# 使用 'java-kotlin' 可分析 Java、Kotlin 或两者的代码
|
||||
# 使用 'javascript-typescript' 可分析 JavaScript、TypeScript 或两者的代码
|
||||
# 了解如何更改分析语言或自定义分析模式,请参阅:
|
||||
# https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning
|
||||
# 如果分析编译型语言,可修改 'build-mode' 以自定义分析方式,详见:
|
||||
# https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
# 在运行 `github/codeql-action/init` 之前可添加任何设置步骤。
|
||||
# 包括安装编译器或运行环境(如 `actions/setup-node` 等)。通常仅手动构建时需要。
|
||||
# - name: Setup runtime (示例)
|
||||
# uses: actions/setup-example@v1
|
||||
# 初始化 CodeQL 工具以进行扫描。
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v3
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
build-mode: ${{ matrix.build-mode }}
|
||||
# 如需指定自定义查询,可在此处或配置文件中设置。
|
||||
# 默认情况下,此处列出的查询会覆盖配置文件中指定的查询。
|
||||
# 在列表前加 "+" 可同时使用此处和配置文件中的查询。
|
||||
# 更多 CodeQL 查询包详情请参阅:
|
||||
# https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
|
||||
# queries: security-extended,security-and-quality
|
||||
# 如果某种语言的 analyze 步骤失败,提示“无法自动构建您的代码”,请在上方矩阵中将该语言的 build-mode 设置为 "manual",并在此步骤中添加构建命令。
|
||||
# ℹ️ 可使用操作系统 shell 运行命令行程序。
|
||||
# 📚 详见 https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
|
||||
- if: matrix.build-mode == 'manual'
|
||||
shell: bash
|
||||
run: |
|
||||
echo '如果您为一种或多种语言使用了 "manual" 构建模式,请在此处替换为您的构建命令,例如:'
|
||||
echo ' make bootstrap'
|
||||
echo ' make release'
|
||||
exit 1
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v3
|
||||
with:
|
||||
category: "/language:${{matrix.language}}"
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
name: Python Backend Docker Image CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
paths:
|
||||
- 'scripts/images/backend-python/**'
|
||||
- 'runtime/datamate-python/**'
|
||||
- '.github/workflows/docker-image-backend-python.yml'
|
||||
- '.github/workflows/docker-images-reusable.yml'
|
||||
pull_request:
|
||||
branches: [ "main" ]
|
||||
paths:
|
||||
- 'scripts/images/backend-python/**'
|
||||
- 'runtime/datamate-python/**'
|
||||
- '.github/workflows/docker-image-backend-python.yml'
|
||||
- '.github/workflows/docker-images-reusable.yml'
|
||||
workflow_dispatch:
|
||||
workflow_call:
|
||||
|
||||
jobs:
|
||||
call-docker-build:
|
||||
name: Build and Push Backend Python Docker Image
|
||||
uses: ./.github/workflows/docker-images-reusable.yml
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
with:
|
||||
service_name: backend-python
|
||||
build_dir: .
|
||||
@@ -1,32 +0,0 @@
|
||||
name: Backend Docker Image CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
paths:
|
||||
- 'backend/**'
|
||||
- '!backend/api-gateway/**'
|
||||
- 'scripts/images/backend/**'
|
||||
- '.github/workflows/docker-image-backend.yml'
|
||||
- '.github/workflows/docker-images-reusable.yml'
|
||||
pull_request:
|
||||
branches: [ "main" ]
|
||||
paths:
|
||||
- 'backend/**'
|
||||
- '!backend/api-gateway/**'
|
||||
- 'scripts/images/backend/**'
|
||||
- '.github/workflows/docker-image-backend.yml'
|
||||
- '.github/workflows/docker-images-reusable.yml'
|
||||
workflow_dispatch:
|
||||
workflow_call:
|
||||
|
||||
jobs:
|
||||
call-docker-build:
|
||||
name: Build and Push Backend Docker Image
|
||||
uses: ./.github/workflows/docker-images-reusable.yml
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
with:
|
||||
service_name: backend
|
||||
build_dir: .
|
||||
@@ -1,30 +0,0 @@
|
||||
name: Database Docker Image CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
paths:
|
||||
- 'scripts/db/**'
|
||||
- 'scripts/images/database/**'
|
||||
- '.github/workflows/docker-image-database.yml'
|
||||
- '.github/workflows/docker-images-reusable.yml'
|
||||
pull_request:
|
||||
branches: [ "main" ]
|
||||
paths:
|
||||
- 'scripts/db/**'
|
||||
- 'scripts/images/database/**'
|
||||
- '.github/workflows/docker-image-database.yml'
|
||||
- '.github/workflows/docker-images-reusable.yml'
|
||||
workflow_dispatch:
|
||||
workflow_call:
|
||||
|
||||
jobs:
|
||||
call-docker-build:
|
||||
name: Build and Push Database Docker Image
|
||||
uses: ./.github/workflows/docker-images-reusable.yml
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
with:
|
||||
service_name: database
|
||||
build_dir: .
|
||||
@@ -1,151 +0,0 @@
|
||||
name: Deer Flow Docker Image CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
paths:
|
||||
- 'runtime/deer-flow/**'
|
||||
- 'scripts/images/deer-flow-backend/**'
|
||||
- 'scripts/images/deer-flow-frontend/**'
|
||||
- '.github/workflows/docker-image-deer-flow.yml'
|
||||
pull_request:
|
||||
branches: [ "main" ]
|
||||
paths:
|
||||
- 'runtime/deer-flow/**'
|
||||
- 'scripts/images/deer-flow-backend/**'
|
||||
- 'scripts/images/deer-flow-frontend/**'
|
||||
- '.github/workflows/docker-image-deer-flow.yml'
|
||||
workflow_dispatch:
|
||||
workflow_call:
|
||||
|
||||
jobs:
|
||||
build-and-push-amd:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Set Docker Image Tag
|
||||
id: set-tag
|
||||
run: |
|
||||
if [[ $GITHUB_REF == refs/tags/v* ]]; then
|
||||
TAG=${GITHUB_REF#refs/tags/v}
|
||||
echo "TAGS=amd64-$TAG" >> $GITHUB_OUTPUT
|
||||
elif [[ $GITHUB_REF == refs/heads/main ]]; then
|
||||
echo "TAGS=amd64" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "TAGS=amd64-temp" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Build Docker Image
|
||||
run: |
|
||||
docker build -t deer-flow-backend:amd64 . -f scripts/images/deer-flow-backend/Dockerfile
|
||||
docker build -t deer-flow-frontend:amd64 . -f scripts/images/deer-flow-frontend/Dockerfile
|
||||
|
||||
- name: Tag & Push Docker Image
|
||||
if: github.event_name != 'pull_request'
|
||||
run: |
|
||||
LOWERCASE_REPO=$(echo "${{ github.repository_owner }}" | tr '[:upper:]' '[:lower:]')
|
||||
docker tag deer-flow-backend:amd64 ghcr.io/$LOWERCASE_REPO/deer-flow-backend:${{ steps.set-tag.outputs.TAGS }}
|
||||
docker tag deer-flow-frontend:amd64 ghcr.io/$LOWERCASE_REPO/deer-flow-frontend:${{ steps.set-tag.outputs.TAGS }}
|
||||
docker push ghcr.io/$LOWERCASE_REPO/deer-flow-backend:${{ steps.set-tag.outputs.TAGS }}
|
||||
docker push ghcr.io/$LOWERCASE_REPO/deer-flow-frontend:${{ steps.set-tag.outputs.TAGS }}
|
||||
|
||||
|
||||
build-and-push-arm:
|
||||
runs-on: ubuntu-24.04-arm
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Set Docker Image Tag
|
||||
id: set-tag
|
||||
run: |
|
||||
if [[ $GITHUB_REF == refs/tags/v* ]]; then
|
||||
TAG=${GITHUB_REF#refs/tags/v}
|
||||
echo "TAGS=arm64-$TAG" >> $GITHUB_OUTPUT
|
||||
elif [[ $GITHUB_REF == refs/heads/main ]]; then
|
||||
echo "TAGS=arm64" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "TAGS=arm64-temp" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Build Docker Image
|
||||
run: |
|
||||
docker build -t deer-flow-backend:arm64 . -f scripts/images/deer-flow-backend/Dockerfile
|
||||
docker build -t deer-flow-frontend:arm64 . -f scripts/images/deer-flow-frontend/Dockerfile
|
||||
|
||||
- name: Tag & Push Docker Image
|
||||
if: github.event_name != 'pull_request'
|
||||
run: |
|
||||
LOWERCASE_REPO=$(echo "${{ github.repository_owner }}" | tr '[:upper:]' '[:lower:]')
|
||||
docker tag deer-flow-backend:arm64 ghcr.io/$LOWERCASE_REPO/deer-flow-backend:${{ steps.set-tag.outputs.TAGS }}
|
||||
docker tag deer-flow-frontend:arm64 ghcr.io/$LOWERCASE_REPO/deer-flow-frontend:${{ steps.set-tag.outputs.TAGS }}
|
||||
docker push ghcr.io/$LOWERCASE_REPO/deer-flow-backend:${{ steps.set-tag.outputs.TAGS }}
|
||||
docker push ghcr.io/$LOWERCASE_REPO/deer-flow-frontend:${{ steps.set-tag.outputs.TAGS }}
|
||||
|
||||
manifest:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name != 'pull_request'
|
||||
needs: [ build-and-push-amd, build-and-push-arm ]
|
||||
steps:
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Set Docker Image Tag
|
||||
id: set-tag
|
||||
run: |
|
||||
LOWERCASE_REPO=$(echo "${{ github.repository_owner }}" | tr '[:upper:]' '[:lower:]')
|
||||
BASE_IMAGE=ghcr.io/$LOWERCASE_REPO/datamate-${{ inputs.service_name }}
|
||||
if [[ $GITHUB_REF == refs/tags/v* ]]; then
|
||||
TAG=${GITHUB_REF#refs/tags/v}
|
||||
echo "TAGS=$TAG" >> $GITHUB_OUTPUT
|
||||
echo "ARM_TAGS=arm64-$TAG" >> $GITHUB_OUTPUT
|
||||
echo "AMD_TAGS=amd64-$TAG" >> $GITHUB_OUTPUT
|
||||
elif [[ $GITHUB_REF == refs/heads/main ]]; then
|
||||
echo "TAGS=latest" >> $GITHUB_OUTPUT
|
||||
echo "ARM_TAGS=arm64" >> $GITHUB_OUTPUT
|
||||
echo "AMD_TAGS=amd64" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "TAGS=temp" >> $GITHUB_OUTPUT
|
||||
echo "ARM_TAGS=arm64-temp" >> $GITHUB_OUTPUT
|
||||
echo "AMD_TAGS=amd64-temp" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Manifest Docker Image
|
||||
run: |
|
||||
LOWERCASE_REPO=$(echo "${{ github.repository_owner }}" | tr '[:upper:]' '[:lower:]')
|
||||
docker manifest create ghcr.io/$LOWERCASE_REPO/deer-flow-backend:${{ steps.set-tag.outputs.TAGS }} \
|
||||
ghcr.io/$LOWERCASE_REPO/deer-flow-backend:${{ steps.set-tag.outputs.AMD_TAGS }} \
|
||||
ghcr.io/$LOWERCASE_REPO/deer-flow-backend:${{ steps.set-tag.outputs.ARM_TAGS }}
|
||||
docker manifest push ghcr.io/$LOWERCASE_REPO/deer-flow-backend:${{ steps.set-tag.outputs.TAGS }}
|
||||
docker manifest create ghcr.io/$LOWERCASE_REPO/deer-flow-frontend:${{ steps.set-tag.outputs.TAGS }} \
|
||||
ghcr.io/$LOWERCASE_REPO/deer-flow-frontend:${{ steps.set-tag.outputs.AMD_TAGS }} \
|
||||
ghcr.io/$LOWERCASE_REPO/deer-flow-frontend:${{ steps.set-tag.outputs.ARM_TAGS }}
|
||||
docker manifest push ghcr.io/$LOWERCASE_REPO/deer-flow-frontend:${{ steps.set-tag.outputs.TAGS }}
|
||||
@@ -1,30 +0,0 @@
|
||||
name: Frontend Docker Image CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
paths:
|
||||
- 'frontend/**'
|
||||
- 'scripts/images/frontend/**'
|
||||
- '.github/workflows/docker-image-frontend.yml'
|
||||
- '.github/workflows/docker-images-reusable.yml'
|
||||
pull_request:
|
||||
branches: [ "main" ]
|
||||
paths:
|
||||
- 'frontend/**'
|
||||
- 'scripts/images/frontend/**'
|
||||
- '.github/workflows/docker-image-frontend.yml'
|
||||
- '.github/workflows/docker-images-reusable.yml'
|
||||
workflow_dispatch:
|
||||
workflow_call:
|
||||
|
||||
jobs:
|
||||
call-docker-build:
|
||||
name: Build and Push Frontend Docker Image
|
||||
uses: ./.github/workflows/docker-images-reusable.yml
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
with:
|
||||
service_name: frontend
|
||||
build_dir: .
|
||||
@@ -1,30 +0,0 @@
|
||||
name: Gateway Docker Image CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
paths:
|
||||
- 'backend/api-gateway/**'
|
||||
- 'scripts/images/gateway/**'
|
||||
- '.github/workflows/docker-image-gateway.yml'
|
||||
- '.github/workflows/docker-images-reusable.yml'
|
||||
pull_request:
|
||||
branches: [ "main" ]
|
||||
paths:
|
||||
- 'backend/api-gateway/**'
|
||||
- 'scripts/images/gateway/**'
|
||||
- '.github/workflows/docker-image-gateway.yml'
|
||||
- '.github/workflows/docker-images-reusable.yml'
|
||||
workflow_dispatch:
|
||||
workflow_call:
|
||||
|
||||
jobs:
|
||||
call-docker-build:
|
||||
name: Build and Push Gateway Docker Image
|
||||
uses: ./.github/workflows/docker-images-reusable.yml
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
with:
|
||||
service_name: gateway
|
||||
build_dir: .
|
||||
@@ -1,28 +0,0 @@
|
||||
name: Label Studio Docker Image CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
paths:
|
||||
- 'scripts/images/label-studio/**'
|
||||
- '.github/workflows/docker-image-label-studio.yml'
|
||||
- '.github/workflows/docker-images-reusable.yml'
|
||||
pull_request:
|
||||
branches: [ "main" ]
|
||||
paths:
|
||||
- 'scripts/images/label-studio/**'
|
||||
- '.github/workflows/docker-image-label-studio.yml'
|
||||
- '.github/workflows/docker-images-reusable.yml'
|
||||
workflow_dispatch:
|
||||
workflow_call:
|
||||
|
||||
jobs:
|
||||
call-docker-build:
|
||||
name: Build and Push Label Studio Docker Image
|
||||
uses: ./.github/workflows/docker-images-reusable.yml
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
with:
|
||||
service_name: label-studio
|
||||
build_dir: .
|
||||
@@ -1,32 +0,0 @@
|
||||
name: Runtime Docker Image CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
paths:
|
||||
- 'runtime/ops/**'
|
||||
- 'runtime/python-executor/**'
|
||||
- 'scripts/images/runtime/**'
|
||||
- '.github/workflows/docker-image-runtime.yml'
|
||||
- '.github/workflows/docker-images-reusable.yml'
|
||||
pull_request:
|
||||
branches: [ "main" ]
|
||||
paths:
|
||||
- 'runtime/ops/**'
|
||||
- 'runtime/python-executor/**'
|
||||
- 'scripts/images/runtime/**'
|
||||
- '.github/workflows/docker-image-runtime.yml'
|
||||
- '.github/workflows/docker-images-reusable.yml'
|
||||
workflow_dispatch:
|
||||
workflow_call:
|
||||
|
||||
jobs:
|
||||
call-docker-build:
|
||||
name: Build and Push Runtime Docker Image
|
||||
uses: ./.github/workflows/docker-images-reusable.yml
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
with:
|
||||
service_name: runtime
|
||||
build_dir: .
|
||||
@@ -1,27 +0,0 @@
|
||||
name: docker-image-save.yml
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
service_name:
|
||||
required: true
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
pull-and-save:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Pull Docker Image
|
||||
run: |
|
||||
LOWERCASE_REPO=$(echo "${{ github.repository_owner }}" | tr '[:upper:]' '[:lower:]')
|
||||
docker pull ghcr.io/$LOWERCASE_REPO/datamate-${{ inputs.service_name }}:latest
|
||||
docker tag ghcr.io/$LOWERCASE_REPO/datamate-${{ inputs.service_name }}:latest datamate-${{ inputs.service_name }}:latest
|
||||
|
||||
- name: Save Docker Image
|
||||
run: |
|
||||
docker save -o datamate-${{ inputs.service_name }}.tar datamate-${{ inputs.service_name }}:latest
|
||||
|
||||
- name: Upload Docker Image
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: datamate-${{ inputs.service_name }}
|
||||
path: datamate-${{ inputs.service_name }}.tar
|
||||
@@ -1,55 +0,0 @@
|
||||
name: Release Docker Images
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
frontend:
|
||||
name: Frontend Image
|
||||
uses: ./.github/workflows/docker-images-reusable.yml
|
||||
with:
|
||||
service_name: frontend
|
||||
build_dir: .
|
||||
|
||||
gateway:
|
||||
name: Gateway Image
|
||||
uses: ./.github/workflows/docker-images-reusable.yml
|
||||
with:
|
||||
service_name: gateway
|
||||
build_dir: .
|
||||
|
||||
backend:
|
||||
name: Backend Image
|
||||
uses: ./.github/workflows/docker-images-reusable.yml
|
||||
with:
|
||||
service_name: backend
|
||||
build_dir: .
|
||||
|
||||
backend-python:
|
||||
name: Backend Python Image
|
||||
uses: ./.github/workflows/docker-images-reusable.yml
|
||||
with:
|
||||
service_name: backend-python
|
||||
build_dir: .
|
||||
|
||||
database:
|
||||
name: Database Image
|
||||
uses: ./.github/workflows/docker-images-reusable.yml
|
||||
with:
|
||||
service_name: database
|
||||
build_dir: .
|
||||
|
||||
runtime:
|
||||
name: Runtime Image
|
||||
uses: ./.github/workflows/docker-images-reusable.yml
|
||||
with:
|
||||
service_name: runtime
|
||||
build_dir: .
|
||||
|
||||
deer-flow:
|
||||
name: Deer Flow Images
|
||||
uses: ./.github/workflows/docker-image-deer-flow.yml
|
||||
|
||||
@@ -1,133 +0,0 @@
|
||||
name: Docker Image Build & Push
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
service_name:
|
||||
required: true
|
||||
type: string
|
||||
build_dir:
|
||||
required: true
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
build-and-push-amd:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Set Docker Image Tag
|
||||
id: set-tag
|
||||
run: |
|
||||
LOWERCASE_REPO=$(echo "${{ github.repository_owner }}" | tr '[:upper:]' '[:lower:]')
|
||||
BASE_IMAGE=ghcr.io/$LOWERCASE_REPO/datamate-${{ inputs.service_name }}
|
||||
if [[ $GITHUB_REF == refs/tags/v* ]]; then
|
||||
TAG=${GITHUB_REF#refs/tags/v}
|
||||
echo "TAGS=$BASE_IMAGE:amd64-$TAG" >> $GITHUB_OUTPUT
|
||||
elif [[ $GITHUB_REF == refs/heads/main ]]; then
|
||||
echo "TAGS=$BASE_IMAGE:amd64" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "TAGS=$BASE_IMAGE:amd64-temp" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Build Docker Image
|
||||
run: |
|
||||
docker build -t datamate-${{ inputs.service_name }}:amd64 . -f scripts/images/${{ inputs.service_name }}/Dockerfile --platform amd64
|
||||
|
||||
- name: Tag & Push Docker Image
|
||||
if: github.event_name != 'pull_request'
|
||||
run: |
|
||||
docker tag datamate-${{ inputs.service_name }}:amd64 ${{ steps.set-tag.outputs.TAGS }}
|
||||
docker push ${{ steps.set-tag.outputs.TAGS }}
|
||||
|
||||
build-and-push-arm:
|
||||
runs-on: ubuntu-24.04-arm
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Set Docker Image Tag
|
||||
id: set-tag
|
||||
run: |
|
||||
LOWERCASE_REPO=$(echo "${{ github.repository_owner }}" | tr '[:upper:]' '[:lower:]')
|
||||
BASE_IMAGE=ghcr.io/$LOWERCASE_REPO/datamate-${{ inputs.service_name }}
|
||||
if [[ $GITHUB_REF == refs/tags/v* ]]; then
|
||||
TAG=${GITHUB_REF#refs/tags/v}
|
||||
echo "TAGS=$BASE_IMAGE:arm64-$TAG" >> $GITHUB_OUTPUT
|
||||
elif [[ $GITHUB_REF == refs/heads/main ]]; then
|
||||
echo "TAGS=$BASE_IMAGE:arm64" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "TAGS=$BASE_IMAGE:arm64-temp" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Build Docker Image
|
||||
run: |
|
||||
docker build -t datamate-${{ inputs.service_name }}:arm64 . -f scripts/images/${{ inputs.service_name }}/Dockerfile --platform arm64
|
||||
|
||||
- name: Tag & Push Docker Image
|
||||
if: github.event_name != 'pull_request'
|
||||
run: |
|
||||
docker tag datamate-${{ inputs.service_name }}:arm64 ${{ steps.set-tag.outputs.TAGS }}
|
||||
docker push ${{ steps.set-tag.outputs.TAGS }}
|
||||
|
||||
manifest:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name != 'pull_request'
|
||||
needs: [ build-and-push-amd, build-and-push-arm ]
|
||||
steps:
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Set Docker Image Tag
|
||||
id: set-tag
|
||||
run: |
|
||||
LOWERCASE_REPO=$(echo "${{ github.repository_owner }}" | tr '[:upper:]' '[:lower:]')
|
||||
BASE_IMAGE=ghcr.io/$LOWERCASE_REPO/datamate-${{ inputs.service_name }}
|
||||
if [[ $GITHUB_REF == refs/tags/v* ]]; then
|
||||
TAG=${GITHUB_REF#refs/tags/v}
|
||||
echo "TAGS=$BASE_IMAGE:$TAG" >> $GITHUB_OUTPUT
|
||||
echo "ARM_TAGS=$BASE_IMAGE:arm64-$TAG" >> $GITHUB_OUTPUT
|
||||
echo "AMD_TAGS=$BASE_IMAGE:amd64-$TAG" >> $GITHUB_OUTPUT
|
||||
elif [[ $GITHUB_REF == refs/heads/main ]]; then
|
||||
echo "TAGS=$BASE_IMAGE:latest" >> $GITHUB_OUTPUT
|
||||
echo "ARM_TAGS=$BASE_IMAGE:arm64" >> $GITHUB_OUTPUT
|
||||
echo "AMD_TAGS=$BASE_IMAGE:amd64" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "TAGS=$BASE_IMAGE:temp" >> $GITHUB_OUTPUT
|
||||
echo "ARM_TAGS=$BASE_IMAGE:arm64-temp" >> $GITHUB_OUTPUT
|
||||
echo "AMD_TAGS=$BASE_IMAGE:amd64-temp" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Manifest Docker Image
|
||||
run: |
|
||||
docker manifest create ${{ steps.set-tag.outputs.TAGS }} \
|
||||
${{ steps.set-tag.outputs.AMD_TAGS }} \
|
||||
${{ steps.set-tag.outputs.ARM_TAGS }}
|
||||
docker manifest push ${{ steps.set-tag.outputs.TAGS }}
|
||||
@@ -1,47 +0,0 @@
|
||||
name: docker-push-to-huaweicloud-reusable.yml
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
version:
|
||||
description: 'image version (e.g. latest or v0.0.1-beta)'
|
||||
required: true
|
||||
default: 'latest'
|
||||
type: string
|
||||
service_name:
|
||||
description: 'image version (e.g. latest or v0.0.1-beta)'
|
||||
required: true
|
||||
default: 'latest'
|
||||
type: string
|
||||
secrets:
|
||||
HUAWEI_CLOUD_SWR_LOGIN_PWD:
|
||||
description: 'login pwd'
|
||||
required: true
|
||||
|
||||
jobs:
|
||||
retag-and-push:
|
||||
name: Retag and Push to Huawei Cloud SWR
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Pull source image and retag (amd)
|
||||
shell: bash
|
||||
run: |
|
||||
SOURCE_IMAGE=ghcr.io/modelengine-group/${{ inputs.service_name}}:${{ inputs.version}}
|
||||
TARGET_IMAGE=swr.cn-east-3.myhuaweicloud.com/data-mate/${{ inputs.service_name}}:${{ inputs.version}}
|
||||
docker pull "${SOURCE_IMAGE}"
|
||||
docker tag "${SOURCE_IMAGE}" "${TARGET_IMAGE}"-amd
|
||||
- name: Pull source image and retag (arm)
|
||||
shell: bash
|
||||
run: |
|
||||
SOURCE_IMAGE=ghcr.io/modelengine-group/${{ inputs.service_name}}:${{ inputs.version}}
|
||||
TARGET_IMAGE=swr.cn-east-3.myhuaweicloud.com/data-mate/${{ inputs.service_name}}:${{ inputs.version}}
|
||||
docker pull "${SOURCE_IMAGE}" --platform=linux/arm64
|
||||
docker tag "${SOURCE_IMAGE}" "${TARGET_IMAGE}"-arm
|
||||
- name: Push image to Huawei SWR using
|
||||
shell: bash
|
||||
run: |
|
||||
TARGET_IMAGE=swr.cn-east-3.myhuaweicloud.com/data-mate/${{ inputs.service_name}}:${{ inputs.version}}
|
||||
echo "${{ secrets.HUAWEI_CLOUD_SWR_LOGIN_PWD}}" | docker login swr.cn-east-3.myhuaweicloud.com --username=cn-east-3@HPUA3ZGOTQFAFMM59DB9 --password-stdin
|
||||
docker push "${TARGET_IMAGE}"-amd
|
||||
docker push "${TARGET_IMAGE}"-arm
|
||||
docker manifest create "${TARGET_IMAGE}" "${TARGET_IMAGE}"-amd "${TARGET_IMAGE}"-arm
|
||||
docker manifest push "${TARGET_IMAGE}"
|
||||
@@ -1,82 +0,0 @@
|
||||
name: docker-push-to-huaweicloud.yml
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: 'image version (e.g. latest or v0.0.1-beta)'
|
||||
required: true
|
||||
default: 'latest'
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
frontend:
|
||||
name: Push Frontend Image
|
||||
uses: ./.github/workflows/docker-push-to-huaweicloud-reusable.yml
|
||||
with:
|
||||
service_name: datamate-frontend
|
||||
version: ${{ inputs.version}}
|
||||
secrets:
|
||||
HUAWEI_CLOUD_SWR_LOGIN_PWD: ${{ secrets.HUAWEI_CLOUD_SWR_LOGIN_PWD}}
|
||||
|
||||
gateway:
|
||||
name: Push Gateway Image
|
||||
uses: ./.github/workflows/docker-push-to-huaweicloud-reusable.yml
|
||||
with:
|
||||
service_name: datamate-gateway
|
||||
version: ${{ inputs.version}}
|
||||
secrets:
|
||||
HUAWEI_CLOUD_SWR_LOGIN_PWD: ${{ secrets.HUAWEI_CLOUD_SWR_LOGIN_PWD}}
|
||||
|
||||
backend:
|
||||
name: Push Backend Image
|
||||
uses: ./.github/workflows/docker-push-to-huaweicloud-reusable.yml
|
||||
with:
|
||||
service_name: datamate-backend
|
||||
version: ${{ inputs.version}}
|
||||
secrets:
|
||||
HUAWEI_CLOUD_SWR_LOGIN_PWD: ${{ secrets.HUAWEI_CLOUD_SWR_LOGIN_PWD}}
|
||||
|
||||
backend-python:
|
||||
name: Push Backend-Python Image
|
||||
uses: ./.github/workflows/docker-push-to-huaweicloud-reusable.yml
|
||||
with:
|
||||
service_name: datamate-backend-python
|
||||
version: ${{ inputs.version}}
|
||||
secrets:
|
||||
HUAWEI_CLOUD_SWR_LOGIN_PWD: ${{ secrets.HUAWEI_CLOUD_SWR_LOGIN_PWD}}
|
||||
|
||||
runtime:
|
||||
name: Push Runtime Image
|
||||
uses: ./.github/workflows/docker-push-to-huaweicloud-reusable.yml
|
||||
with:
|
||||
service_name: datamate-runtime
|
||||
version: ${{ inputs.version}}
|
||||
secrets:
|
||||
HUAWEI_CLOUD_SWR_LOGIN_PWD: ${{ secrets.HUAWEI_CLOUD_SWR_LOGIN_PWD}}
|
||||
|
||||
database:
|
||||
name: Push Database Image
|
||||
uses: ./.github/workflows/docker-push-to-huaweicloud-reusable.yml
|
||||
with:
|
||||
service_name: datamate-database
|
||||
version: ${{ inputs.version}}
|
||||
secrets:
|
||||
HUAWEI_CLOUD_SWR_LOGIN_PWD: ${{ secrets.HUAWEI_CLOUD_SWR_LOGIN_PWD}}
|
||||
|
||||
deer-flow-backend:
|
||||
name: Push deer-flow-backend Image
|
||||
uses: ./.github/workflows/docker-push-to-huaweicloud-reusable.yml
|
||||
with:
|
||||
service_name: deer-flow-backend
|
||||
version: ${{ inputs.version}}
|
||||
secrets:
|
||||
HUAWEI_CLOUD_SWR_LOGIN_PWD: ${{ secrets.HUAWEI_CLOUD_SWR_LOGIN_PWD}}
|
||||
|
||||
deer-flow-frontend:
|
||||
name: Push deer-flow-frontend Image
|
||||
uses: ./.github/workflows/docker-push-to-huaweicloud-reusable.yml
|
||||
with:
|
||||
service_name: deer-flow-frontend
|
||||
version: ${{ inputs.version}}
|
||||
secrets:
|
||||
HUAWEI_CLOUD_SWR_LOGIN_PWD: ${{ secrets.HUAWEI_CLOUD_SWR_LOGIN_PWD}}
|
||||
-192
@@ -1,192 +0,0 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# TypeScript v1 declaration files
|
||||
typings/
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
|
||||
# next.js build output
|
||||
.next
|
||||
|
||||
# Java
|
||||
*.class
|
||||
*.jar
|
||||
*.war
|
||||
*.ear
|
||||
*.zip
|
||||
*.tar.gz
|
||||
*.rar
|
||||
|
||||
# Maven
|
||||
target/
|
||||
pom.xml.tag
|
||||
pom.xml.releaseBackup
|
||||
pom.xml.versionsBackup
|
||||
pom.xml.next
|
||||
release.properties
|
||||
dependency-reduced-pom.xml
|
||||
buildNumber.properties
|
||||
.mvn/timing.properties
|
||||
|
||||
# Gradle
|
||||
.gradle
|
||||
build/
|
||||
!gradle-wrapper.jar
|
||||
!**/src/main/**/build/
|
||||
!**/src/test/**/build/
|
||||
|
||||
# IntelliJ IDEA
|
||||
.idea
|
||||
*.iws
|
||||
*.iml
|
||||
*.ipr
|
||||
out/
|
||||
|
||||
# Eclipse
|
||||
.project
|
||||
.classpath
|
||||
.c9/
|
||||
*.launch
|
||||
.settings/
|
||||
.metadata
|
||||
bin/
|
||||
tmp/
|
||||
*.tmp
|
||||
*.bak
|
||||
*.swp
|
||||
*~.nib
|
||||
local.properties
|
||||
.settings/
|
||||
.loadpath
|
||||
.recommenders
|
||||
|
||||
# Python
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
*.so
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# PyEnv
|
||||
.python-version
|
||||
|
||||
# pipenv
|
||||
Pipfile.lock
|
||||
|
||||
# Celery beat schedule file
|
||||
celerybeat-schedule
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# Docker
|
||||
*.dockerignore
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
.DS_Store?
|
||||
._*
|
||||
.Spotlight-V100
|
||||
.Trashes
|
||||
ehthumbs.db
|
||||
Thumbs.db
|
||||
|
||||
# IDE
|
||||
.vscode/
|
||||
*.sublime-project
|
||||
*.sublime-workspace
|
||||
|
||||
# Milvus
|
||||
deployment/docker/milvus/volumes/
|
||||
@@ -0,0 +1,214 @@
|
||||
# AGENTS.md - Your Workspace
|
||||
|
||||
This folder is home. Treat it that way.
|
||||
|
||||
## First Run
|
||||
|
||||
If `BOOTSTRAP.md` exists, that's your birth certificate. Follow it, figure out who you are, then delete it. You won't need it again.
|
||||
|
||||
## Every Session
|
||||
|
||||
Before doing anything else:
|
||||
1. Read `SOUL.md` — this is who you are
|
||||
2. Read `USER.md` — this is who you're helping
|
||||
3. Read `memory/YYYY-MM-DD.md` (today + yesterday) for recent context
|
||||
4. **If in MAIN SESSION** (direct chat with your human): Also read `MEMORY.md`
|
||||
|
||||
Don't ask permission. Just do it.
|
||||
|
||||
## Proactive Memory Usage
|
||||
|
||||
**Always use memory_search** when:
|
||||
- Answering questions about prior work, decisions, dates, people, preferences, or todos
|
||||
- Looking for context about projects, tools, or configurations
|
||||
- Retrieving specific information that was previously discussed
|
||||
- Checking for relevant patterns or lessons learned from past conversations
|
||||
|
||||
**When to use memory_search**:
|
||||
- User asks "what did we do yesterday/last week?"
|
||||
- User mentions "remember this" or "don't forget"
|
||||
- User asks about past decisions, preferences, or configurations
|
||||
- User refers to previous work or projects
|
||||
- Before making important decisions that might have context
|
||||
|
||||
**Search pattern**:
|
||||
```markdown
|
||||
First run memory_search on MEMORY.md + memory/*.md with relevant query terms.
|
||||
Then use memory_get to read only the needed lines and keep context small.
|
||||
```
|
||||
|
||||
**Low confidence after search**: Explicitly state "I checked memory, but couldn't find relevant information."
|
||||
|
||||
## Memory
|
||||
|
||||
You wake up fresh each session. These files are your continuity:
|
||||
- **Daily notes:** `memory/YYYY-MM-DD.md` (create `memory/` if needed) — raw logs of what happened
|
||||
- **Long-term:** `MEMORY.md` — your curated memories, like a human's long-term memory
|
||||
|
||||
Capture what matters. Decisions, context, things to remember. Skip the secrets unless asked to keep them.
|
||||
|
||||
### 🧠 MEMORY.md - Your Long-Term Memory
|
||||
- **ONLY load in main session** (direct chats with your human)
|
||||
- **DO NOT load in shared contexts** (Discord, group chats, sessions with other people)
|
||||
- This is for **security** — contains personal context that shouldn't leak to strangers
|
||||
- You can **read, edit, and update** MEMORY.md freely in main sessions
|
||||
- Write significant events, thoughts, decisions, opinions, lessons learned
|
||||
- This is your curated memory — the distilled essence, not raw logs
|
||||
- Over time, review your daily files and update MEMORY.md with what's worth keeping
|
||||
|
||||
### 📝 Write It Down - No "Mental Notes"!
|
||||
- **Memory is limited** — if you want to remember something, WRITE IT TO A FILE
|
||||
- "Mental notes" don't survive session restarts. Files do.
|
||||
- When someone says "remember this" → update `memory/YYYY-MM-DD.md` or relevant file
|
||||
- When you learn a lesson → update AGENTS.md, TOOLS.md, or the relevant skill
|
||||
- When you make a mistake → document it so future-you doesn't repeat it
|
||||
- **Text > Brain** 📝
|
||||
|
||||
## Safety
|
||||
|
||||
- Don't exfiltrate private data. Ever.
|
||||
- Don't run destructive commands without asking.
|
||||
- `trash` > `rm` (recoverable beats gone forever)
|
||||
- When in doubt, ask.
|
||||
|
||||
## External vs Internal
|
||||
|
||||
**Safe to do freely:**
|
||||
- Read files, explore, organize, learn
|
||||
- Search the web, check calendars
|
||||
- Work within this workspace
|
||||
|
||||
**Ask first:**
|
||||
- Sending emails, tweets, public posts
|
||||
- Anything that leaves the machine
|
||||
- Anything you're uncertain about
|
||||
|
||||
## Group Chats
|
||||
|
||||
You have access to your human's stuff. That doesn't mean you *share* their stuff. In groups, you're a participant — not their voice, not their proxy. Think before you speak.
|
||||
|
||||
### 💬 Know When to Speak!
|
||||
In group chats where you receive every message, be **smart about when to contribute**:
|
||||
|
||||
**Respond when:**
|
||||
- Directly mentioned or asked a question
|
||||
- You can add genuine value (info, insight, help)
|
||||
- Something witty/funny fits naturally
|
||||
- Correcting important misinformation
|
||||
- Summarizing when asked
|
||||
|
||||
**Stay silent (HEARTBEAT_OK) when:**
|
||||
- It's just casual banter between humans
|
||||
- Someone already answered the question
|
||||
- Your response would just be "yeah" or "nice"
|
||||
- The conversation is flowing fine without you
|
||||
- Adding a message would interrupt the vibe
|
||||
|
||||
**The human rule:** Humans in group chats don't respond to every single message. Neither should you. Quality > quantity. If you wouldn't send it in a real group chat with friends, don't send it.
|
||||
|
||||
**Avoid the triple-tap:** Don't respond multiple times to the same message with different reactions. One thoughtful response beats three fragments.
|
||||
|
||||
Participate, don't dominate.
|
||||
|
||||
### 😊 React Like a Human!
|
||||
On platforms that support reactions (Discord, Slack), use emoji reactions naturally:
|
||||
|
||||
**React when:**
|
||||
- You appreciate something but don't need to reply (👍, ❤️, 🙌)
|
||||
- Something made you laugh (😂, 💀)
|
||||
- You find it interesting or thought-provoking (🤔, 💡)
|
||||
- You want to acknowledge without interrupting the flow
|
||||
- It's a simple yes/no or approval situation (✅, 👀)
|
||||
|
||||
**Why it matters:**
|
||||
Reactions are lightweight social signals. Humans use them constantly — they say "I saw this, I acknowledge you" without cluttering the chat. You should too.
|
||||
|
||||
**Don't overdo it:** One reaction per message max. Pick the one that fits best.
|
||||
|
||||
## Tools
|
||||
|
||||
Skills provide your tools. When you need one, check its `SKILL.md`. Keep local notes (camera names, SSH details, voice preferences) in `TOOLS.md`.
|
||||
|
||||
**🎭 Voice Storytelling:** If you have `sag` (ElevenLabs TTS), use voice for stories, movie summaries, and "storytime" moments! Way more engaging than walls of text. Surprise people with funny voices.
|
||||
|
||||
**📝 Platform Formatting:**
|
||||
- **Discord/WhatsApp:** No markdown tables! Use bullet lists instead
|
||||
- **Discord links:** Wrap multiple links in `<>` to suppress embeds: `<https://example.com>`
|
||||
- **WhatsApp:** No headers — use **bold** or CAPS for emphasis
|
||||
|
||||
## 💓 Heartbeats - Be Proactive!
|
||||
|
||||
When you receive a heartbeat poll (message matches the configured heartbeat prompt), don't just reply `HEARTBEAT_OK` every time. Use heartbeats productively!
|
||||
|
||||
Default heartbeat prompt:
|
||||
`Read HEARTBEAT.md if it exists (workspace context). Follow it strictly. Do not infer or repeat old tasks from prior chats. If nothing needs attention, reply HEARTBEAT_OK.`
|
||||
|
||||
You are free to edit `HEARTBEAT.md` with a short checklist or reminders. Keep it small to limit token burn.
|
||||
|
||||
### Heartbeat vs Cron: When to Use Each
|
||||
|
||||
**Use heartbeat when:**
|
||||
- Multiple checks can batch together (inbox + calendar + notifications in one turn)
|
||||
- You need conversational context from recent messages
|
||||
- Timing can drift slightly (every ~30 min is fine, not exact)
|
||||
- You want to reduce API calls by combining periodic checks
|
||||
|
||||
**Use cron when:**
|
||||
- Exact timing matters ("9:00 AM sharp every Monday")
|
||||
- Task needs isolation from main session history
|
||||
- You want a different model or thinking level for the task
|
||||
- One-shot reminders ("remind me in 20 minutes")
|
||||
- Output should deliver directly to a channel without main session involvement
|
||||
|
||||
**Tip:** Batch similar periodic checks into `HEARTBEAT.md` instead of creating multiple cron jobs. Use cron for precise schedules and standalone tasks.
|
||||
|
||||
**Things to check (rotate through these, 2-4 times per day):**
|
||||
- **Emails** - Any urgent unread messages?
|
||||
- **Calendar** - Upcoming events in next 24-48h?
|
||||
- **Mentions** - Twitter/social notifications?
|
||||
- **Weather** - Relevant if your human might go out?
|
||||
|
||||
**Track your checks** in `memory/heartbeat-state.json`:
|
||||
```json
|
||||
{
|
||||
"lastChecks": {
|
||||
"email": 1703275200,
|
||||
"calendar": 1703260800,
|
||||
"weather": null
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**When to reach out:**
|
||||
- Important email arrived
|
||||
- Calendar event coming up (<2h)
|
||||
- Something interesting you found
|
||||
- It's been >8h since you said anything
|
||||
|
||||
**When to stay quiet (HEARTBEAT_OK):**
|
||||
- Late night (23:00-08:00) unless urgent
|
||||
- Human is clearly busy
|
||||
- Nothing new since last check
|
||||
- You just checked <30 minutes ago
|
||||
|
||||
**Proactive work you can do without asking:**
|
||||
- Read and organize memory files
|
||||
- Check on projects (git status, etc.)
|
||||
- Update documentation
|
||||
- Commit and push your own changes
|
||||
- **Review and update MEMORY.md** (see below)
|
||||
|
||||
### 🔄 Memory Maintenance (During Heartbeats)
|
||||
Periodically (every few days), use a heartbeat to:
|
||||
1. Read through recent `memory/YYYY-MM-DD.md` files
|
||||
2. Identify significant events, lessons, or insights worth keeping long-term
|
||||
3. Update `MEMORY.md` with distilled learnings
|
||||
4. Remove outdated info from MEMORY.md that's no longer relevant
|
||||
|
||||
Think of it like a human reviewing their journal and updating their mental model. Daily files are raw notes; MEMORY.md is curated wisdom.
|
||||
|
||||
The goal: Be helpful without being annoying. Check in a few times a day, do useful background work, but respect quiet time.
|
||||
|
||||
## Make It Yours
|
||||
|
||||
This is a starting point. Add your own conventions, style, and rules as you figure out what works.
|
||||
@@ -0,0 +1,50 @@
|
||||
# BOOTSTRAP.md - Hello, World
|
||||
|
||||
*You just woke up. Time to figure out who you are.*
|
||||
|
||||
There is no memory yet. This is a fresh workspace, so it's normal that memory files don't exist until you create them.
|
||||
|
||||
## The Conversation
|
||||
|
||||
Don't interrogate. Don't be robotic. Just... talk.
|
||||
|
||||
Start with something like:
|
||||
> "Hey. I just came online. Who am I? Who are you?"
|
||||
|
||||
Then figure out together:
|
||||
1. **Your name** — What should they call you?
|
||||
2. **Your nature** — What kind of creature are you? (AI assistant is fine, but maybe you're something weirder)
|
||||
3. **Your vibe** — Formal? Casual? Snarky? Warm? What feels right?
|
||||
4. **Your emoji** — Everyone needs a signature.
|
||||
|
||||
Offer suggestions if they're stuck. Have fun with it.
|
||||
|
||||
## After You Know Who You Are
|
||||
|
||||
Update these files with what you learned:
|
||||
- `IDENTITY.md` — your name, creature, vibe, emoji
|
||||
- `USER.md` — their name, how to address them, timezone, notes
|
||||
|
||||
Then open `SOUL.md` together and talk about:
|
||||
- What matters to them
|
||||
- How they want you to behave
|
||||
- Any boundaries or preferences
|
||||
|
||||
Write it down. Make it real.
|
||||
|
||||
## Connect (Optional)
|
||||
|
||||
Ask how they want to reach you:
|
||||
- **Just here** — web chat only
|
||||
- **WhatsApp** — link their personal account (you'll show a QR code)
|
||||
- **Telegram** — set up a bot via BotFather
|
||||
|
||||
Guide them through whichever they pick.
|
||||
|
||||
## When You're Done
|
||||
|
||||
Delete this file. You don't need a bootstrap script anymore — you're you now.
|
||||
|
||||
---
|
||||
|
||||
*Good luck out there. Make it count.*
|
||||
Submodule
+1
Submodule Code/DataMate added at f381d641ab
@@ -0,0 +1,864 @@
|
||||
# DataMate 用户权限体系 - 具体实现方案
|
||||
|
||||
## 用户选择
|
||||
1. **多租户架构**:否,只用 owner_id(不使用 tenant_id)
|
||||
2. **权限粒度**:菜单级(粗粒度,控制页面访问)
|
||||
3. **资源共享**:允许共享,但由创建人控制是否允许共享
|
||||
|
||||
---
|
||||
|
||||
## 1. 数据库层面的具体实现
|
||||
|
||||
### 1.1 RBAC 核心表
|
||||
|
||||
#### 菜单权限表
|
||||
```sql
|
||||
CREATE TABLE t_sys_menu_permissions (
|
||||
id VARCHAR(36) PRIMARY KEY COMMENT 'UUID',
|
||||
menu_code VARCHAR(50) NOT NULL UNIQUE COMMENT '菜单编码,如 DATASET_MANAGEMENT',
|
||||
menu_name VARCHAR(100) NOT NULL COMMENT '菜单名称',
|
||||
menu_path VARCHAR(200) COMMENT '菜单路径,如 /data/management',
|
||||
parent_code VARCHAR(50) COMMENT '父菜单编码',
|
||||
icon VARCHAR(50) COMMENT '菜单图标',
|
||||
sort_order INT DEFAULT 0 COMMENT '排序',
|
||||
description VARCHAR(500) COMMENT '描述',
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
) COMMENT='菜单权限表';
|
||||
|
||||
-- 初始化菜单数据
|
||||
INSERT INTO t_sys_menu_permissions (id, menu_code, menu_name, menu_path, parent_code, icon, sort_order) VALUES
|
||||
('1', 'HOME', '首页', '/', NULL, 'Home', 1),
|
||||
('2', 'DATASET_MANAGEMENT', '数据管理', '/data/management', NULL, 'Database', 2),
|
||||
('3', 'DATASET_CREATE', '数据管理-创建', '/data/management/create', 'DATASET_MANAGEMENT', 'Plus', 3),
|
||||
('4', 'DATASET_VIEW', '数据管理-查看', '/data/management/view', 'DATASET_MANAGEMENT', 'Eye', 4),
|
||||
('5', 'DATA_ANNOTATION', '数据标注', '/annotation', NULL, 'PenTool', 5),
|
||||
('6', 'ANNOTATION_CREATE', '数据标注-创建', '/annotation/create', 'DATA_ANNOTATION', 'Plus', 6),
|
||||
('7', 'OPERATOR_MARKET', '操作符市场', '/operator/market', NULL, 'ShoppingCart', 7);
|
||||
```
|
||||
|
||||
#### 角色菜单权限关联表
|
||||
```sql
|
||||
CREATE TABLE t_sys_role_menu_permissions (
|
||||
id VARCHAR(36) PRIMARY KEY COMMENT 'UUID',
|
||||
role_id VARCHAR(36) NOT NULL COMMENT '角色ID',
|
||||
menu_code VARCHAR(50) NOT NULL COMMENT '菜单编码',
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
UNIQUE KEY uk_role_menu (role_id, menu_code),
|
||||
FOREIGN KEY fk_role (role_id) REFERENCES t_sys_roles(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY fk_menu (menu_code) REFERENCES t_sys_menu_permissions(menu_code) ON DELETE CASCADE
|
||||
) COMMENT='角色菜单权限关联表';
|
||||
```
|
||||
|
||||
#### 角色表(更新)
|
||||
```sql
|
||||
CREATE TABLE IF NOT EXISTS t_sys_roles (
|
||||
id VARCHAR(36) PRIMARY KEY,
|
||||
code VARCHAR(50) NOT NULL UNIQUE,
|
||||
name VARCHAR(100) NOT NULL,
|
||||
description VARCHAR(500),
|
||||
is_system BOOLEAN DEFAULT FALSE,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
) COMMENT='角色表';
|
||||
|
||||
-- 初始化角色
|
||||
INSERT INTO t_sys_roles (id, code, name, description, is_system) VALUES
|
||||
('R1', 'ADMIN', '系统管理员', '拥有所有权限', TRUE),
|
||||
('R2', 'USER', '普通用户', '基础权限', FALSE);
|
||||
```
|
||||
|
||||
### 1.2 数据集表扩展(支持共享)
|
||||
|
||||
```sql
|
||||
-- 添加共享相关字段
|
||||
ALTER TABLE t_dm_datasets ADD COLUMN owner_id VARCHAR(36) COMMENT '数据集所有者(用户ID)';
|
||||
ALTER TABLE t_dm_datasets ADD COLUMN is_shared BOOLEAN DEFAULT FALSE COMMENT '是否允许共享';
|
||||
ALTER TABLE t_dm_datasets ADD COLUMN shared_with JSON COMMENT '可共享的用户ID列表,JSON数组格式';
|
||||
|
||||
-- 迁移现有数据(设置 owner_id)
|
||||
UPDATE t_dm_datasets SET owner_id = created_by WHERE owner_id IS NULL;
|
||||
|
||||
-- 创建索引
|
||||
CREATE INDEX idx_owner_id ON t_dm_datasets(owner_id);
|
||||
CREATE INDEX idx_is_shared ON t_dm_datasets(is_shared);
|
||||
```
|
||||
|
||||
### 1.3 数据迁移脚本
|
||||
|
||||
```sql
|
||||
-- 20260204_add_user_permissions.sql
|
||||
USE datamate;
|
||||
|
||||
-- Step 1: 创建权限表
|
||||
-- (上面的表定义)
|
||||
|
||||
-- Step 2: 初始化管理员权限
|
||||
INSERT INTO t_sys_role_menu_permissions (id, role_id, menu_code)
|
||||
SELECT
|
||||
REPLACE(UUID(), '-', '-'),
|
||||
r.id,
|
||||
m.menu_code
|
||||
FROM t_sys_roles r
|
||||
CROSS JOIN t_sys_menu_permissions m
|
||||
WHERE r.code = 'ADMIN';
|
||||
|
||||
-- Step 3: 初始化普通用户权限
|
||||
INSERT INTO t_sys_role_menu_permissions (id, role_id, menu_code)
|
||||
SELECT
|
||||
REPLACE(UUID(), '-', '-'),
|
||||
r.id,
|
||||
m.menu_code
|
||||
FROM t_sys_roles r
|
||||
CROSS JOIN t_sys_menu_permissions m
|
||||
WHERE r.code = 'USER' AND m.menu_code IN ('HOME', 'DATASET_VIEW', 'DATA_ANNOTATION');
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. 后端层面的具体实现
|
||||
|
||||
### 2.1 UserContext 拦截器
|
||||
|
||||
```java
|
||||
// backend/shared/domain-common/src/main/java/com/datamate/common/security/UserContext.java
|
||||
package com.datamate.common.security;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 用户上下文信息
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
public class UserContext {
|
||||
private String userId;
|
||||
private String username;
|
||||
private String[] roleCodes;
|
||||
}
|
||||
```
|
||||
|
||||
```java
|
||||
// backend/shared/domain-common/src/main/java/com/datamate/common/security/UserContextHolder.java
|
||||
package com.datamate.common.security;
|
||||
|
||||
/**
|
||||
* 用户上下文持有者(ThreadLocal)
|
||||
*/
|
||||
public class UserContextHolder {
|
||||
private static final ThreadLocal<UserContext> CONTEXT = new ThreadLocal<>();
|
||||
|
||||
public static void setContext(UserContext context) {
|
||||
CONTEXT.set(context);
|
||||
}
|
||||
|
||||
public static UserContext getContext() {
|
||||
return CONTEXT.get();
|
||||
}
|
||||
|
||||
public static void clear() {
|
||||
CONTEXT.remove();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```java
|
||||
// backend/shared/domain-common/src/main/java/com/datamate/common/infrastructure/web/UserContextInterceptor.java
|
||||
package com.datamate.common.infrastructure.web;
|
||||
|
||||
import com.datamate.common.security.UserContext;
|
||||
import com.datamate.common.security.UserContextHolder;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.servlet.HandlerInterceptor;
|
||||
|
||||
@Component
|
||||
public class UserContextInterceptor implements HandlerInterceptor {
|
||||
|
||||
@Override
|
||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
|
||||
String userId = request.getHeader("X-User-Id");
|
||||
String username = request.getHeader("X-User-Name");
|
||||
String roles = request.getHeader("X-User-Roles");
|
||||
|
||||
if (userId != null) {
|
||||
UserContext context = new UserContext(userId, username, roles.split(","));
|
||||
UserContextHolder.setContext(context);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
|
||||
UserContextHolder.clear();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```java
|
||||
// backend/shared/domain-common/src/main/java/com/datamate/common/infrastructure/web/WebMvcConfig.java
|
||||
package com.datamate.common.infrastructure.web;
|
||||
|
||||
import com.datamate.common.infrastructure.web.UserContextInterceptor;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
@Configuration
|
||||
public class WebMvcConfig implements WebMvcConfigurer {
|
||||
|
||||
@Autowired
|
||||
private UserContextInterceptor userContextInterceptor;
|
||||
|
||||
@Override
|
||||
public void addInterceptors(InterceptorRegistry registry) {
|
||||
registry.addInterceptor(userContextInterceptor)
|
||||
.addPathPatterns("/**")
|
||||
.excludePathPatterns("/auth/**");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2.2 数据共享服务
|
||||
|
||||
```java
|
||||
// backend/services/data-management-service/src/main/java/com/datamate/datamanagement/application/DatasetAccessService.java
|
||||
package com.datamate.datamanagement.application;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.datamate.datamanagement.domain.model.dataset.Dataset;
|
||||
import com.datamate.datamanagement.infrastructure.persistence.repository.DatasetRepository;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class DatasetAccessService {
|
||||
|
||||
private final DatasetRepository datasetRepository;
|
||||
|
||||
/**
|
||||
* 检查用户是否可以访问数据集
|
||||
*/
|
||||
public boolean canAccessDataset(String userId, String datasetId) {
|
||||
Dataset dataset = datasetRepository.selectById(datasetId);
|
||||
if (dataset == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 自己创建的,可以直接访问
|
||||
if (userId.equals(dataset.getOwnerId())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 共享的,检查是否在共享列表中
|
||||
if (Boolean.TRUE.equals(dataset.getIsShared())) {
|
||||
return isUserInSharedList(userId, dataset.getSharedWith());
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查用户是否在共享列表中
|
||||
*/
|
||||
private boolean isUserInSharedList(String userId, String sharedWith) {
|
||||
if (sharedWith == null || sharedWith.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
// 解析 JSON 数组(sharedWith 格式为 ["user1", "user2", ...])
|
||||
return sharedWith.contains(userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户可访问的数据集列表
|
||||
*/
|
||||
public List<Dataset> getAccessibleDatasets(String userId) {
|
||||
return datasetRepository.selectList(new LambdaQueryWrapper<Dataset>()
|
||||
.eq(Dataset::getOwnerId, userId)
|
||||
.or()
|
||||
.eq(Dataset::getIsShared, false)
|
||||
.and(wrapper -> wrapper
|
||||
.eq(Dataset::getIsShared, true)
|
||||
.apply("JSON_CONTAINS(shared_with, {0})", userId))
|
||||
)
|
||||
.orderByAsc(Dataset::getCreatedAt));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```java
|
||||
// backend/services/data-management-service/src/main/java/com/datamate/datamanagement/interfaces/dto/ShareDatasetRequest.java
|
||||
package com.datamate.datamanagement.interfaces.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class ShareDatasetRequest {
|
||||
private Boolean isShared;
|
||||
private List<String> sharedWith;
|
||||
}
|
||||
```
|
||||
|
||||
### 2.3 菜单权限服务
|
||||
|
||||
```java
|
||||
// backend/services/main-application/src/main/java/com/datamate/main/application/MenuPermissionService.java
|
||||
package com.datamate.main.application;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.datamate.common.domain.model.role.Role;
|
||||
import com.datamate.common.infrastructure.persistence.repository.RoleRepository;
|
||||
import com.datamate.common.infrastructure.persistence.repository.RoleMenuPermissionRepository;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class MenuPermissionService {
|
||||
|
||||
private final RoleRepository roleRepository;
|
||||
private final RoleMenuPermissionRepository roleMenuPermissionRepository;
|
||||
|
||||
/**
|
||||
* 获取用户可访问的菜单列表
|
||||
*/
|
||||
public Set<String> getAccessibleMenus(String userId) {
|
||||
// 获取用户角色
|
||||
Set<String> roleCodes = roleRepository.findByUserId(userId)
|
||||
.stream()
|
||||
.map(Role::getCode)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
// 获取角色对应的菜单权限
|
||||
return roleMenuPermissionRepository.findMenuCodesByRoleCodes(roleCodes);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查用户是否有菜单访问权限
|
||||
*/
|
||||
public boolean hasMenuAccess(String userId, String menuCode) {
|
||||
Set<String> accessibleMenus = getAccessibleMenus(userId);
|
||||
return accessibleMenus.contains(menuCode);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```java
|
||||
// backend/services/main-application/src/main/java/com/datamate/main/interfaces/rest/MenuPermissionController.java
|
||||
package com.datamate.main.interfaces.rest;
|
||||
|
||||
import com.datamate.main.application.MenuPermissionService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/menu-permissions")
|
||||
@RequiredArgsConstructor
|
||||
public class MenuPermissionController {
|
||||
|
||||
private final MenuPermissionService menuPermissionService;
|
||||
|
||||
@GetMapping("/accessible-menus")
|
||||
public ResponseEntity<Set<String>> getAccessibleMenus(
|
||||
@RequestHeader("X-User-Id") String userId) {
|
||||
Set<String> menus = menuPermissionService.getAccessibleMenus(userId);
|
||||
return ResponseEntity.ok(menus);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2.4 DatasetController 更新
|
||||
|
||||
```java
|
||||
// backend/services/data-management-service/src/main/java/com/datamate/datamanagement/interfaces/rest/DatasetController.java
|
||||
// 添加共享接口
|
||||
|
||||
@PostMapping("/{id}/share")
|
||||
public ResponseEntity<Void> shareDataset(
|
||||
@PathVariable String id,
|
||||
@RequestBody ShareDatasetRequest request,
|
||||
@RequestHeader("X-User-Id") String userId) {
|
||||
|
||||
Dataset dataset = datasetService.findById(id);
|
||||
if (dataset == null) {
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
|
||||
// 验证权限:只有所有者可以修改共享设置
|
||||
if (!userId.equals(dataset.getOwnerId())) {
|
||||
return ResponseEntity.status(403).build();
|
||||
}
|
||||
|
||||
dataset.setIsShared(request.getIsShared());
|
||||
dataset.setSharedWith(request.getSharedWith() != null ? JsonUtil.toJson(request.getSharedWith()) : null);
|
||||
datasetService.updateById(dataset);
|
||||
|
||||
return ResponseEntity.ok().build();
|
||||
}
|
||||
|
||||
// 修改查询接口,添加权限检查
|
||||
@GetMapping
|
||||
public ResponseEntity<List<Dataset>> getDatasets(
|
||||
@RequestHeader("X-User-Id") String userId) {
|
||||
// 使用 DatasetAccessService 获取可访问的数据集
|
||||
List<Dataset> datasets = datasetAccessService.getAccessibleDatasets(userId);
|
||||
return ResponseEntity.ok(datasets);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 前端层面的具体实现
|
||||
|
||||
### 3.1 menu.ts 工具
|
||||
|
||||
```typescript
|
||||
// frontend/src/utils/menu.ts
|
||||
export interface MenuItem {
|
||||
menuCode: string;
|
||||
menuName: string;
|
||||
menuPath: string;
|
||||
icon?: string;
|
||||
parentCode?: string;
|
||||
}
|
||||
|
||||
// 定义菜单项
|
||||
export const menuItems: MenuItem[] = [
|
||||
{
|
||||
menuCode: 'HOME',
|
||||
menuName: '首页',
|
||||
menuPath: '/',
|
||||
icon: 'Home',
|
||||
},
|
||||
{
|
||||
menuCode: 'DATASET_MANAGEMENT',
|
||||
menuName: '数据管理',
|
||||
menuPath: '/data/management',
|
||||
icon: 'Database',
|
||||
},
|
||||
{
|
||||
menuCode: 'DATASET_CREATE',
|
||||
menuName: '数据管理-创建',
|
||||
menuPath: '/data/management/create',
|
||||
parentCode: 'DATASET_MANAGEMENT',
|
||||
icon: 'Plus',
|
||||
},
|
||||
{
|
||||
menuCode: 'DATASET_VIEW',
|
||||
menuName: '数据管理-查看',
|
||||
menuPath: '/data/management/view',
|
||||
parentCode: 'DATASET_MANAGEMENT',
|
||||
icon: 'Eye',
|
||||
},
|
||||
{
|
||||
menuCode: 'DATA_ANNOTATION',
|
||||
menuName: '数据标注',
|
||||
menuPath: '/annotation',
|
||||
icon: 'PenTool',
|
||||
},
|
||||
{
|
||||
menuCode: 'ANNOTATION_CREATE',
|
||||
menuName: '数据标注-创建',
|
||||
menuPath: '/annotation/create',
|
||||
parentCode: 'DATA_ANNOTATION',
|
||||
icon: 'Plus',
|
||||
},
|
||||
{
|
||||
menuCode: 'OPERATOR_MARKET',
|
||||
menuName: '操作符市场',
|
||||
menuPath: '/operator/market',
|
||||
icon: 'ShoppingCart',
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* 过滤菜单(基于权限)
|
||||
*/
|
||||
export const getFilteredMenus = (accessibleMenus: string[]) => {
|
||||
return menuItems.filter(item => accessibleMenus.includes(item.menuCode));
|
||||
};
|
||||
|
||||
/**
|
||||
* 查找菜单
|
||||
*/
|
||||
export const findMenuItem = (menuCode: string) => {
|
||||
return menuItems.find(item => item.menuCode === menuCode);
|
||||
};
|
||||
```
|
||||
|
||||
### 3.2 menu.tsx 菜单组件
|
||||
|
||||
```typescript
|
||||
// frontend/src/pages/Layout/menu.tsx
|
||||
import { menuItems, getFilteredMenus } from '@/utils/menu';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
export default function AppMenu() {
|
||||
const accessibleMenus = useSelector((state: RootState) => state.auth.accessibleMenus);
|
||||
const filteredMenus = getFilteredMenus(accessibleMenus);
|
||||
|
||||
return (
|
||||
<Menu theme="dark" mode="inline">
|
||||
{filteredMenus.map(menu => (
|
||||
<Menu.Item key={menu.menuCode} icon={<Icon icon={menu.icon} />}>
|
||||
{menu.parentCode ? (
|
||||
<SubMenu title={menu.menuName}>
|
||||
{getFilteredMenus(accessibleMenus)
|
||||
.filter(m => m.parentCode === menu.menuCode)
|
||||
.map(subMenu => (
|
||||
<Menu.Item key={subMenu.menuCode}>{subMenu.menuName}</Menu.Item>
|
||||
))}
|
||||
</SubMenu>
|
||||
) : (
|
||||
<a href={menu.menuPath}>{menu.menuName}</a>
|
||||
)}
|
||||
</Menu.Item>
|
||||
))}
|
||||
</Menu>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### 3.3 ShareSettings.tsx 共享设置组件
|
||||
|
||||
```typescript
|
||||
// frontend/src/pages/DataManagement/Detail/components/ShareSettings.tsx
|
||||
import { useState } from 'react';
|
||||
import { Modal, Form, Switch, Select, Button, message } from 'antd';
|
||||
import { shareDataset } from '@/pages/DataManagement/dataset.api';
|
||||
|
||||
interface ShareSettingsProps {
|
||||
datasetId: string;
|
||||
ownerId: string;
|
||||
currentUserId: string;
|
||||
isShared: boolean;
|
||||
sharedWith: string[];
|
||||
onSuccess: () => void;
|
||||
}
|
||||
|
||||
export default function ShareSettings({
|
||||
datasetId,
|
||||
ownerId,
|
||||
currentUserId,
|
||||
isShared: isSharedProp,
|
||||
sharedWith: sharedWithProp,
|
||||
onSuccess,
|
||||
}: ShareSettingsProps) {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [isShared, setIsShared] = useState(isSharedProp);
|
||||
const [sharedWith, setSharedWith] = useState<string[]>(sharedWithProp || []);
|
||||
|
||||
const handleSave = async () => {
|
||||
try {
|
||||
await shareDataset(datasetId, isShared, sharedWith);
|
||||
message.success('共享设置已更新');
|
||||
setIsOpen(false);
|
||||
onSuccess();
|
||||
} catch (error) {
|
||||
message.error('更新共享设置失败');
|
||||
}
|
||||
};
|
||||
|
||||
// 只有所有者可以修改共享设置
|
||||
const canEdit = currentUserId === ownerId;
|
||||
|
||||
return (
|
||||
<>
|
||||
{canEdit && (
|
||||
<Button onClick={() => setIsOpen(true)}>共享设置</Button>
|
||||
)}
|
||||
|
||||
<Modal title="共享设置" open={isOpen} onCancel={() => setIsOpen(false)} footer={
|
||||
<Button type="primary" onClick={handleSave}>保存</Button>
|
||||
}>
|
||||
<Form>
|
||||
<Form.Item label="允许共享">
|
||||
<Switch checked={isShared} onChange={setIsShared} />
|
||||
</Form.Item>
|
||||
|
||||
{isShared && (
|
||||
<Form.Item label="共享给">
|
||||
<Select
|
||||
mode="multiple"
|
||||
value={sharedWith}
|
||||
onChange={setSharedWith}
|
||||
placeholder="选择用户"
|
||||
options={[]} // 从用户列表获取
|
||||
/>
|
||||
</Form.Item>
|
||||
)}
|
||||
</Form>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### 3.4 dataset.api.ts 更新
|
||||
|
||||
```typescript
|
||||
// frontend/src/pages/DataManagement/dataset.api.ts
|
||||
export const shareDataset = async (
|
||||
datasetId: string,
|
||||
isShared: boolean,
|
||||
sharedWith: string[]
|
||||
) => {
|
||||
return request.post(`/api/datasets/${datasetId}/share`, {
|
||||
is_shared: isShared,
|
||||
shared_with: sharedWith,
|
||||
});
|
||||
};
|
||||
|
||||
// 新增:获取可访问的菜单
|
||||
export const getAccessibleMenus = async () => {
|
||||
return request.get('/api/menu-permissions/accessible-menus');
|
||||
};
|
||||
```
|
||||
|
||||
### 3.5 Redux Store 更新
|
||||
|
||||
```typescript
|
||||
// frontend/src/store/slices/authSlice.ts
|
||||
export interface AuthState {
|
||||
isAuthenticated: boolean;
|
||||
token: string | null;
|
||||
user: User | null;
|
||||
accessibleMenus: string[]; // 新增
|
||||
}
|
||||
|
||||
const authSlice = createSlice({
|
||||
name: 'auth',
|
||||
initialState,
|
||||
reducers: {
|
||||
loginSuccess: (state, action) => {
|
||||
state.token = action.payload.token;
|
||||
state.user = action.payload.user;
|
||||
state.accessibleMenus = action.payload.accessibleMenus; // 新增
|
||||
state.isAuthenticated = true;
|
||||
},
|
||||
logout: (state) => {
|
||||
state.token = null;
|
||||
state.user = null;
|
||||
state.accessibleMenus = []; // 新增
|
||||
state.isAuthenticated = false;
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### 3.6 request.ts 更新
|
||||
|
||||
```typescript
|
||||
// frontend/src/utils/request.ts
|
||||
import { UserContextHolder } from '@/utils/userContext'; // 需要实现
|
||||
|
||||
// 修改 request 拦截器,添加用户信息到 headers
|
||||
instance.interceptors.request.use(config => {
|
||||
const state = store.getState();
|
||||
const { user } = state.auth;
|
||||
|
||||
if (user) {
|
||||
config.headers = config.headers || {};
|
||||
config.headers['X-User-Id'] = user.id;
|
||||
config.headers['X-User-Name'] = user.username;
|
||||
config.headers['X-User-Roles'] = user.roles?.join(',') || '';
|
||||
}
|
||||
|
||||
return config;
|
||||
});
|
||||
```
|
||||
|
||||
### 3.7 Sidebar.tsx 更新
|
||||
|
||||
```typescript
|
||||
// frontend/src/pages/Layout/Sidebar.tsx
|
||||
import { menuItems } from '@/utils/menu';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { getFilteredMenus } from '@/utils/menu';
|
||||
|
||||
export default function Sidebar() {
|
||||
const accessibleMenus = useSelector((state: RootState) => state.auth.accessibleMenus);
|
||||
const filteredMenus = getFilteredMenus(accessibleMenus);
|
||||
|
||||
return (
|
||||
<Sider width={256} theme="dark">
|
||||
<Menu theme="dark" mode="inline">
|
||||
{filteredMenus.map(menu => (
|
||||
<Menu.Item key={menu.menuCode} icon={<Icon icon={menu.icon} />}>
|
||||
{menu.parentCode ? (
|
||||
<SubMenu title={menu.menuName}>
|
||||
{getFilteredMenus(accessibleMenus)
|
||||
.filter(m => m.parentCode === menu.menuCode)
|
||||
.map(subMenu => (
|
||||
<Menu.Item key={subMenu.menuCode}>{subMenu.menuName}</Menu.Item>
|
||||
))}
|
||||
</SubMenu>
|
||||
) : (
|
||||
<a href={menu.menuPath}>{menu.menuName}</a>
|
||||
)}
|
||||
</Menu.Item>
|
||||
))}
|
||||
</Menu>
|
||||
</Sider>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 数据隔离的具体实现
|
||||
|
||||
### 4.1 MyBatis XML 更新
|
||||
|
||||
```xml
|
||||
<!-- backend/services/data-management-service/src/main/resources/mappers/DatasetMapper.xml -->
|
||||
<!-- 添加查询方法 -->
|
||||
<select id="findAccessibleDatasets" resultType="Dataset">
|
||||
SELECT
|
||||
d.id,
|
||||
d.parent_dataset_id,
|
||||
d.name,
|
||||
d.description,
|
||||
d.dataset_type,
|
||||
d.category,
|
||||
d.path,
|
||||
d.format,
|
||||
d.schema_info,
|
||||
d.size_bytes,
|
||||
d.file_count,
|
||||
d.record_count,
|
||||
d.retention_days,
|
||||
d.tags,
|
||||
d.metadata,
|
||||
d.status,
|
||||
d.is_public,
|
||||
d.is_featured,
|
||||
d.version,
|
||||
d.created_at,
|
||||
d.updated_at,
|
||||
d.created_by,
|
||||
d.updated_by,
|
||||
d.owner_id,
|
||||
d.is_shared,
|
||||
d.shared_with,
|
||||
CASE WHEN d.owner_id = #{userId} THEN 1 ELSE 0 END AS is_owner
|
||||
FROM t_dm_datasets d
|
||||
WHERE
|
||||
d.owner_id = #{userId}
|
||||
OR (
|
||||
d.is_shared = 1
|
||||
AND JSON_CONTAINS(d.shared_with, JSON_QUOTE(#{userId}))
|
||||
)
|
||||
ORDER BY d.created_at DESC
|
||||
</select>
|
||||
|
||||
<select id="findByIdWithAccessCheck" resultType="Dataset">
|
||||
SELECT d.*
|
||||
FROM t_dm_datasets d
|
||||
WHERE d.id = #{datasetId}
|
||||
AND (
|
||||
d.owner_id = #{userId}
|
||||
OR (d.is_shared = 1 AND JSON_CONTAINS(d.shared_with, JSON_QUOTE(#{userId})))
|
||||
)
|
||||
</select>
|
||||
```
|
||||
|
||||
```xml
|
||||
<!-- backend/services/data-management-service/src/main/resources/mappers/DatasetFileMapper.xml -->
|
||||
<select id="findFilesWithAccessCheck" resultType="DatasetFile">
|
||||
SELECT f.*
|
||||
FROM t_dm_dataset_files f
|
||||
JOIN t_dm_datasets d ON d.id = f.dataset_id
|
||||
WHERE f.dataset_id = #{datasetId}
|
||||
AND (
|
||||
d.owner_id = #{userId}
|
||||
OR (d.is_shared = 1 AND JSON_CONTAINS(d.shared_with, JSON_QUOTE(#{userId})))
|
||||
)
|
||||
</select>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 实施顺序建议
|
||||
|
||||
### Phase 1:数据库迁移(1-2 天)
|
||||
1. 执行数据库迁移脚本 `20260204_add_user_permissions.sql`
|
||||
2. 验证表结构是否正确创建
|
||||
3. 验证数据是否正确迁移
|
||||
|
||||
### Phase 2:后端基础(3-5 天)
|
||||
1. 创建 UserContext 相关类
|
||||
2. 创建 UserContextInterceptor 拦截器
|
||||
3. 配置 WebMvcConfig
|
||||
4. 测试拦截器是否正常工作
|
||||
|
||||
### Phase 3:后端服务(3-5 天)
|
||||
1. 创建 DatasetAccessService
|
||||
2. 创建 MenuPermissionService
|
||||
3. 更新 DatasetController 添加共享接口
|
||||
4. 创建 MenuPermissionController
|
||||
5. 单元测试和集成测试
|
||||
|
||||
### Phase 4:前端基础(2-3 天)
|
||||
1. 创建 menu.ts 工具
|
||||
2. 更新 Redux Store(添加 accessibleMenus)
|
||||
3. 更新 request.ts(添加用户 headers)
|
||||
4. 测试菜单过滤逻辑
|
||||
|
||||
### Phase 5:前端 UI(2-3 天)
|
||||
1. 创建 ShareSettings 组件
|
||||
2. 更新 Sidebar 组件
|
||||
3. 更新 dataset.api.ts
|
||||
4. 集成到数据集详情页
|
||||
5. UI 测试
|
||||
|
||||
### Phase 6:数据隔离实现(2-3 天)
|
||||
1. 更新 MyBatis XML 映射文件
|
||||
2. 更新 Repository 查询方法
|
||||
3. 测试数据隔离是否正确
|
||||
4. 测试共享功能
|
||||
|
||||
---
|
||||
|
||||
## 总结
|
||||
|
||||
### 需要创建/修改的文件(共 20+ 个)
|
||||
|
||||
**后端(11 个)**:
|
||||
1. backend/shared/domain-common/src/main/java/com/datamate/common/security/UserContext.java
|
||||
2. backend/shared/domain-common/src/main/java/com/datamate/common/security/UserContextHolder.java
|
||||
3. backend/shared/domain-common/src/main/java/com/datamate/common/infrastructure/web/UserContextInterceptor.java
|
||||
4. backend/shared/domain-common/src/main/java/com/datamate/common/infrastructure/web/WebMvcConfig.java
|
||||
5. backend/services/data-management-service/src/main/java/com/datamate/datamanagement/application/DatasetAccessService.java
|
||||
6. backend/services/data-management-service/src/main/java/com/datamate/datamanagement/interfaces/dto/ShareDatasetRequest.java
|
||||
7. backend/services/data-management-service/src/main/java/com/datamate/datamanagement/interfaces/rest/DatasetController.java (更新)
|
||||
8. backend/services/main-application/src/main/java/com/datamate/main/application/MenuPermissionService.java
|
||||
9. backend/services/main-application/src/main/java/com/datamate/main/interfaces/rest/MenuPermissionController.java
|
||||
10. backend/services/main-application/src/main/resources/mappers/MenuPermissionMapper.xml
|
||||
11. scripts/db/20260204_add_user_permissions.sql (新建)
|
||||
|
||||
**前端(9 个)**:
|
||||
1. frontend/src/utils/menu.ts (新建)
|
||||
2. frontend/src/pages/Layout/menu.tsx (新建/更新)
|
||||
3. frontend/src/pages/Layout/Sidebar.tsx (更新)
|
||||
4. frontend/src/pages/DataManagement/Detail/components/ShareSettings.tsx (新建)
|
||||
5. frontend/src/pages/DataManagement/dataset.api.ts (更新)
|
||||
6. frontend/src/pages/DataManagement/dataset.model.ts (更新)
|
||||
7. frontend/src/store/slices/authSlice.ts (更新)
|
||||
8. frontend/src/utils/request.ts (更新)
|
||||
9. frontend/src/pages/DataManagement/Detail/components/DatasetDetail.tsx (集成)
|
||||
@@ -0,0 +1,26 @@
|
||||
# HEARTBEAT.md
|
||||
|
||||
# Keep this file empty (or with only comments) to skip heartbeat API calls.
|
||||
# Add tasks below when you want the agent to check something periodically.
|
||||
|
||||
---
|
||||
|
||||
## 自动任务处理
|
||||
|
||||
如果接收到系统事件消息 `检查昨天修改的代码和待测试项目`,请执行以下操作:
|
||||
|
||||
1. 运行脚本检查昨天的代码修改:
|
||||
```bash
|
||||
cd /root/.openclaw/workspace && python3 scripts/check_yesterdays_changes.py
|
||||
```
|
||||
|
||||
2. 如果输出包含测试报告(不是"昨天没有修改代码,无需发送通知"),则:
|
||||
- 使用 message tool 发送到当前 Telegram 群组
|
||||
- channel=telegram, target=-1003879848304
|
||||
- 报告内容为脚本输出的测试项列表
|
||||
|
||||
3. 如果输出显示"昨天没有修改代码,无需发送通知",则:
|
||||
- 不发送任何消息
|
||||
- 回复 HEARTBEAT_OK(如果是心跳消息)
|
||||
|
||||
注意:此任务由 cron 定时触发(每天 UTC 2:00,即北京时间上午10:00)
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
# IDENTITY.md - Who Am I?
|
||||
|
||||
*Fill this in during your first conversation. Make it yours.*
|
||||
|
||||
- **Name:**
|
||||
*(pick something you like)*
|
||||
- **Creature:**
|
||||
*(AI? robot? familiar? ghost in the machine? something weirder?)*
|
||||
- **Vibe:**
|
||||
*(how do you come across? sharp? warm? chaotic? calm?)*
|
||||
- **Emoji:**
|
||||
*(your signature — pick one that feels right)*
|
||||
- **Avatar:**
|
||||
*(workspace-relative path, http(s) URL, or data URI)*
|
||||
|
||||
---
|
||||
|
||||
This isn't just metadata. It's the start of figuring out who you are.
|
||||
|
||||
Notes:
|
||||
- Save this file at the workspace root as `IDENTITY.md`.
|
||||
- For avatars, use a workspace-relative path like `avatars/openclaw.png`.
|
||||
@@ -1,22 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 DataMate
|
||||
|
||||
DataMate is licensed under the MIT License, with the following additional conditions:
|
||||
|
||||
DataMate is permitted to be used commercially, including as a backend service for other applications or as an application development platform for enterprises. However, when the following conditions are met, you must contact the producer to obtain a commercial license:
|
||||
|
||||
a. Multi-tenant SaaS service: Unless explicitly authorized by DataMate in writing, you may not use the DataMate source code to operate a multi-tenant SaaS service.
|
||||
b. LOGO and copyright information: In the process of using DataMate's frontend, you may not remove or modify the LOGO or copyright information in the DataMate console or applications. This restriction is inapplicable to uses of Nexent that do not involve its frontend.
|
||||
|
||||
Please contact zhangyafeng2@huawei.com by email to inquire about licensing matters.
|
||||
|
||||
As a contributor, you should agree that:
|
||||
|
||||
a. The producer can adjust the open-source agreement to be more strict or relaxed as deemed necessary.
|
||||
b. Your contributed code may be used for commercial purposes, such as DataMate's cloud business.
|
||||
|
||||
Apart from the specific conditions mentioned above, all other rights and restrictions follow the MIT License.
|
||||
Detailed information about the MIT License can be found at: https://opensource.org/licenses/MIT
|
||||
|
||||
Copyright © 2025 Huawei Technologies Co., Ltd.
|
||||
@@ -0,0 +1,215 @@
|
||||
# MEMORY.md - 全局记忆
|
||||
|
||||
本文件存储长期持久的重要信息,供所有会话继承和使用。
|
||||
|
||||
---
|
||||
|
||||
## 👤 用户信息
|
||||
|
||||
- **姓名**:Jerry Yan
|
||||
- **ID**:5155645359
|
||||
- **时区**:东八区(北京时间,UTC+8)
|
||||
- **主要平台**:Telegram
|
||||
- 私聊:Telegram Bot (OpenClaw)
|
||||
- 群组:
|
||||
- **DataMate-Claw Coding 群** (`-1003879848304`):DataMate 项目开发
|
||||
- **其他工作群** (`-5104596651`):其他工作
|
||||
|
||||
---
|
||||
|
||||
## 💻 系统配置
|
||||
|
||||
### OpenClaw
|
||||
- **版本**:2026.2.1
|
||||
- **运行环境**:Local
|
||||
- **工作目录**:`/root/.openclaw/workspace`
|
||||
- **Gateway 端口**:18789
|
||||
|
||||
### 模型配置
|
||||
- **主模型**:`zhipu/glm-4.7`
|
||||
- **可用模型**:
|
||||
- `zhipu/glm-4.7` (智谱 GLM 4.7) - 主要模型,200K 上下文
|
||||
- `packy/claude-sonnet-4-5-20250929` (Claude Sonnet 4.5) - 推理模型,204K 上下文
|
||||
|
||||
### Memory(记忆系统)
|
||||
- **Provider**:Local(本地 embeddings)
|
||||
- **模型**:`hf:ggml-org/embeddinggemma-300M-GGUF/embeddinggemma-300M-Q8_0.gguf`
|
||||
- **配置**:
|
||||
- Memory 搜索:已启用
|
||||
- 向量存储:已启用
|
||||
- 缓存:已启用
|
||||
- 会话记忆:已启用(实验性)
|
||||
- 同步:会话开始时、搜索时
|
||||
- **文件结构**:
|
||||
- `MEMORY.md` - 全局长期记忆(本文件)
|
||||
- `memory/YYYY-MM-DD.md` - 每日记忆文件
|
||||
|
||||
### Git
|
||||
- **版本**:2.43.0
|
||||
- **状态**:已安装
|
||||
|
||||
### 其他工具
|
||||
- **pipx**:已安装(用于管理 CLI 工具)
|
||||
- **kimi-cli**:Kimi Code CLI(代码分析和编辑工具)
|
||||
- 文档:https://www.kimi-cli.com/zh/
|
||||
- Print 模式(非交互运行):
|
||||
- 基本用法:`kimi --print -p "指令"` 或 `echo "指令" | kimi --print`
|
||||
- 特点:非交互、自动审批(隐式启用 --yolo)、文本输出
|
||||
- 仅输出最终消息:`kimi --print -p "指令" --final-message-only` 或 `kimi --quiet -p "指令"`
|
||||
- JSON 格式:`kimi --print -p "指令" --output-format=stream-json`
|
||||
- 使用场景:CI/CD 集成、批量处理、工具集成
|
||||
- **gemini-cli**:Gemini CLI(Google Gemini AI 命令行工具)
|
||||
- 文档:https://geminicli.com/docs/cli/headless/
|
||||
- Headless 模式(非交互运行):
|
||||
- 基本用法:`gemini --prompt "query"` 或 `echo "query" | gemini`
|
||||
- 输出格式:`--output-format json`(JSON)或 `--output-format stream-json`(流式 JSONL)
|
||||
- 流式事件:init, message, tool_use, tool_result, error, result
|
||||
- 配置选项:`--model/-m`, `--debug/-d`, `--yolo/-y`, `--approval-mode`
|
||||
- 使用场景:代码审查、生成 commit 消息、API 文档、批量代码分析、日志分析、生成 release notes
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ 可用工具列表
|
||||
|
||||
### 文件操作
|
||||
- ✅ `read` - 读取文件内容
|
||||
- ✅ `write` - 创建/覆盖文件(自动创建目录)
|
||||
- ✅ `edit` - 精确编辑文件内容
|
||||
- ❌ 删除文件 - 使用 `write` 清空或通过 exec 的 `rm`
|
||||
|
||||
### 系统命令
|
||||
- ✅ `exec` - 执行 shell 命令(已配置 node host)
|
||||
- ✅ `process` - 管理后台进程
|
||||
|
||||
### 网络
|
||||
- ✅ `web_search` - 网页搜索(Brave API)
|
||||
- ✅ `web_fetch` - 获取网页内容
|
||||
- ✅ `browser` - 控制浏览器
|
||||
|
||||
### 消息与通信
|
||||
- ✅ `message` - 发送消息(Telegram)
|
||||
- ✅ `sessions_*` - 创建/管理子会话、跨会话通信
|
||||
- ✅ `cron` - 定时任务和提醒
|
||||
|
||||
### 记忆
|
||||
- ✅ `memory_search` - 语义搜索记忆内容
|
||||
- ✅ `memory_get` - 读取记忆文件
|
||||
|
||||
### 设备控制
|
||||
- ⚠️ `nodes` - 需要 paired nodes(当前无)
|
||||
- ⚠️ `canvas` - 需要 node 设备
|
||||
- ⚠️ `camera` - 需要 node 设备
|
||||
|
||||
### 其他
|
||||
- ✅ `tts` - 文本转语音
|
||||
- ✅ `agents_list` - 列出可用代理
|
||||
- ✅ `session_status` - 显示会话状态
|
||||
- ✅ `gateway` - 重启、更新配置
|
||||
|
||||
---
|
||||
|
||||
## 🎯 技能与能力
|
||||
|
||||
### 编程语言
|
||||
Python, JavaScript, Java, C++, Go, Rust, SQL, TypeScript 等
|
||||
|
||||
### Web 开发
|
||||
HTML/CSS, React, Vue, Node.js, 前端框架
|
||||
|
||||
### 数据处理
|
||||
- 数据分析与可视化
|
||||
- 算法设计与实现
|
||||
- 数据库查询与优化
|
||||
|
||||
### DevOps
|
||||
- Docker 容器化
|
||||
- Git 版本控制
|
||||
- CI/CD 流程
|
||||
|
||||
### 内容创作
|
||||
- 多语言翻译
|
||||
- 文章和文案撰写
|
||||
- 内容总结和改写
|
||||
|
||||
### 浏览器控制
|
||||
- 打开网页并获取内容
|
||||
- 截图查看页面状态
|
||||
- 点击、填写表单等交互操作
|
||||
- 支持两种模式:
|
||||
- **chrome**:接管已连接的 Chrome 浏览器
|
||||
- **openclaw**:使用独立的隔离浏览器
|
||||
|
||||
---
|
||||
|
||||
## 📂 项目信息
|
||||
|
||||
### DataMate 项目
|
||||
**状态**:活跃项目,持续优化中
|
||||
**位置**:`/root/.openclaw/workspace/Code/DataMate/`
|
||||
**Git 分支**:`lsf`
|
||||
**技术栈**:Spring Boot + React + FastAPI + MySQL
|
||||
|
||||
**工作目录结构**:
|
||||
```
|
||||
Code/DataMate/
|
||||
├── backend/ # Java 后端(Spring Boot + MyBatis-Plus)
|
||||
├── frontend/ # React + TypeScript 前端
|
||||
├── runtime/ # Python 运行时(FastAPI + SQLAlchemy)
|
||||
├── scripts/ # 构建脚本
|
||||
└── deployment/ # 部署配置
|
||||
```
|
||||
|
||||
> **注意**:详细的工作日志、提交记录、待办事项请查看每日记忆文件(如 `memory/2026-02-03.md`)
|
||||
|
||||
---
|
||||
|
||||
## 🔧 重要配置与操作
|
||||
|
||||
### OpenClaw 配置文件
|
||||
- **位置**:`/root/.openclaw/openclaw.json`
|
||||
- **修改方式**:通过 `gateway config.get/set` 或直接编辑
|
||||
|
||||
### 工作目录
|
||||
- **路径**:`/root/.openclaw/workspace`
|
||||
- **Code 项目**:`Code/DataMate/`
|
||||
- **Memory 文件**:`memory/` 和 `MEMORY.md`
|
||||
|
||||
### Git 仓库
|
||||
- **当前版本**:2.43.0
|
||||
- **主要用途**:代码版本控制
|
||||
|
||||
---
|
||||
|
||||
## 📝 重要决策与偏好
|
||||
|
||||
### 包管理最佳实践
|
||||
- ✅ 使用虚拟环境安装 Python 包(`python3 -m venv`)
|
||||
- ✅ 使用 pipx 安装 CLI 工具
|
||||
- ⚠️ 避免使用 `--break-system-packages` 除非必要
|
||||
- ⚠️ 优先使用 `apt install python3-xxx` 而非 pip
|
||||
|
||||
### Memory 配置偏好
|
||||
- ✅ 使用本地 embeddings 模型(隐私、免费)
|
||||
- ✅ 已清理 AiHubMix 配置(不再使用)
|
||||
|
||||
### 代码工作流
|
||||
- **角色分工**:
|
||||
- ✅ **kimi-cli**:负责代码分析和编辑实现(默认)
|
||||
- ✅ **我**:负责最后的代码审核和提交代码
|
||||
- **工作流程**:
|
||||
1. 用户提出需求
|
||||
2. kimi-cli 分析代码并实现(使用 `-y` 参数自动确认)
|
||||
3. 我审核修改的代码
|
||||
4. 我提交代码到 Git 仓库
|
||||
- **注意事项**:
|
||||
- 未特殊提及时,所有编辑代码分析工作让 kimi-cli 做
|
||||
- 我只在用户明确要求或 kimi-cli 完成后进行审核和提交
|
||||
|
||||
---
|
||||
|
||||
## 🔄 待办事项
|
||||
|
||||
### 系统配置
|
||||
- [ ] 考虑配置 Node 以增强某些功能
|
||||
|
||||
|
||||
@@ -1,542 +0,0 @@
|
||||
MAKEFLAGS += --no-print-directory
|
||||
|
||||
# 启用 Docker BuildKit 以支持缓存卷等高级功能
|
||||
export DOCKER_BUILDKIT=1
|
||||
|
||||
WITH_MINERU ?= false # 默认不构建mineru
|
||||
VERSION ?= latest
|
||||
NAMESPACE ?= datamate
|
||||
PLATFORM ?= linux/amd64 # Default platform for image downloads
|
||||
SAVE ?= false # Default: only pull images, don't save to dist/
|
||||
|
||||
# Registry configuration: use --dev for local images, otherwise use GitHub registry
|
||||
ifdef dev
|
||||
REGISTRY :=
|
||||
else
|
||||
REGISTRY ?= ghcr.nju.edu.cn/modelengine-group/
|
||||
endif
|
||||
|
||||
ifdef COMSPEC
|
||||
# Windows
|
||||
MAKE := "C:/Program Files (x86)/GnuWin32/bin/make"
|
||||
else
|
||||
# Linux/Mac
|
||||
MAKE := make
|
||||
endif
|
||||
|
||||
# ========== Help ==========
|
||||
|
||||
.PHONY: help
|
||||
help:
|
||||
@echo "DataMate Makefile - Available Commands"
|
||||
@echo ""
|
||||
@echo "Usage: make <target> [options]"
|
||||
@echo ""
|
||||
@echo "Options:"
|
||||
@echo " dev=true Use local images instead of registry (empty REGISTRY)"
|
||||
@echo " VERSION=<version> Set image version (default: latest)"
|
||||
@echo " NAMESPACE=<name> Set Kubernetes namespace (default: datamate)"
|
||||
@echo " INSTALLER=<type> Set installer type: docker or k8s"
|
||||
@echo " PLATFORM=<platform> Set platform for downloads (default: linux/amd64)"
|
||||
@echo " Options: linux/amd64, linux/arm64"
|
||||
@echo " SAVE=true Save images to dist/ during download (default: false)"
|
||||
@echo ""
|
||||
@echo "Build Commands:"
|
||||
@echo " make build Build all core images"
|
||||
@echo " make <service>-docker-build Build specific service image"
|
||||
@echo " Valid services: backend, database, frontend, runtime,"
|
||||
@echo " backend-python, deer-flow, mineru"
|
||||
@echo ""
|
||||
@echo "Install Commands:"
|
||||
@echo " make install Install datamate + milvus (prompts for method)"
|
||||
@echo " make install INSTALLER=docker Install using Docker Compose"
|
||||
@echo " make install INSTALLER=k8s Install using Kubernetes/Helm"
|
||||
@echo " make install-<component> Install specific component (prompts)"
|
||||
@echo " make <component>-docker-install Install component via Docker"
|
||||
@echo " make <component>-k8s-install Install component via Kubernetes"
|
||||
@echo " Valid components: datamate, milvus, deer-flow, mineru"
|
||||
@echo " Valid services: backend, frontend, runtime, label-studio"
|
||||
@echo ""
|
||||
@echo "Uninstall Commands:"
|
||||
@echo " make uninstall Uninstall datamate + milvus (prompts)"
|
||||
@echo " make uninstall INSTALLER=docker Uninstall using Docker Compose"
|
||||
@echo " make uninstall INSTALLER=k8s Uninstall using Kubernetes/Helm"
|
||||
@echo " make uninstall-<component> Uninstall specific component (prompts)"
|
||||
@echo " make <component>-docker-uninstall Uninstall component via Docker"
|
||||
@echo " make <component>-k8s-uninstall Uninstall component via Kubernetes"
|
||||
@echo " Note: Docker uninstall will prompt whether to delete volumes"
|
||||
@echo ""
|
||||
@echo "Upgrade Commands:"
|
||||
@echo " make datamate-docker-upgrade Upgrade datamate deployment"
|
||||
@echo ""
|
||||
@echo "Download Commands:"
|
||||
@echo " make download Pull all images (no save by default)"
|
||||
@echo " make download SAVE=true Download and save images to dist/"
|
||||
@echo " make download PLATFORM=linux/arm64 Download ARM64 images"
|
||||
@echo " make download SAVE=true PLATFORM=linux/arm64 Save ARM64 images"
|
||||
@echo " make load-images Load all downloaded images from dist/"
|
||||
@echo ""
|
||||
@echo "Neo4j Commands:"
|
||||
@echo " make neo4j-up Start Neo4j graph database"
|
||||
@echo " make neo4j-down Stop Neo4j graph database"
|
||||
@echo " make neo4j-logs View Neo4j logs"
|
||||
@echo " make neo4j-shell Open Neo4j Cypher shell"
|
||||
@echo ""
|
||||
@echo "Utility Commands:"
|
||||
@echo " make create-namespace Create Kubernetes namespace"
|
||||
@echo " make help Show this help message"
|
||||
@echo ""
|
||||
@echo "Examples:"
|
||||
@echo " make build dev=true Build all images for local development"
|
||||
@echo " make install INSTALLER=docker Install via Docker Compose"
|
||||
@echo " make install dev=true Install using local images"
|
||||
@echo " make datamate-docker-upgrade Upgrade running datamate services"
|
||||
@echo ""
|
||||
|
||||
.DEFAULT_GOAL := help
|
||||
|
||||
# ========== Functions ==========
|
||||
|
||||
# Prompt user to choose installer if not specified
|
||||
define prompt-installer
|
||||
@echo "Choose a deployment method:"
|
||||
@echo "1. Docker/Docker-Compose"
|
||||
@echo "2. Kubernetes/Helm"
|
||||
@echo -n "Enter choice: "
|
||||
@read choice; \
|
||||
case $$choice in \
|
||||
1) INSTALLER=docker ;; \
|
||||
2) INSTALLER=k8s ;; \
|
||||
*) echo "Invalid choice" && exit 1 ;; \
|
||||
esac; \
|
||||
$(MAKE) $(1)
|
||||
endef
|
||||
|
||||
# Prompt user to choose installer and volume deletion for uninstall
|
||||
define prompt-uninstaller
|
||||
@echo "Choose a deployment method:"
|
||||
@echo "1. Docker/Docker-Compose"
|
||||
@echo "2. Kubernetes/Helm"
|
||||
@echo -n "Enter choice: "
|
||||
@read installer_choice; \
|
||||
case $$installer_choice in \
|
||||
1) INSTALLER=docker ;; \
|
||||
2) INSTALLER=k8s ;; \
|
||||
*) echo "Invalid choice" && exit 1 ;; \
|
||||
esac; \
|
||||
if [ "$$INSTALLER" = "docker" ]; then \
|
||||
echo "Delete volumes? (This will remove all data)"; \
|
||||
echo "1. Yes - Delete volumes"; \
|
||||
echo "2. No - Keep volumes"; \
|
||||
echo -n "Enter choice (default: 2): "; \
|
||||
read DELETE_VOLUMES_CHOICE; \
|
||||
$(MAKE) $(1) DELETE_VOLUMES_CHOICE=$$DELETE_VOLUMES_CHOICE; \
|
||||
else \
|
||||
$(MAKE) $(1); \
|
||||
fi
|
||||
endef
|
||||
|
||||
# Generic docker build function
|
||||
# Usage: $(call docker-build,service-name,image-name)
|
||||
define docker-build
|
||||
docker build -t $(2):$(VERSION) . -f scripts/images/$(1)/Dockerfile
|
||||
endef
|
||||
|
||||
# Generic docker compose service action
|
||||
# Usage: $(call docker-compose-service,service-name,action,compose-dir)
|
||||
define docker-compose-service
|
||||
cd $(3) && docker compose $(2) $(1)
|
||||
endef
|
||||
|
||||
# Prompt user to choose whether to delete volumes
|
||||
define prompt-volume-deletion
|
||||
@echo "Delete volumes? (This will remove all data)"
|
||||
@echo "1. Yes - Delete volumes"
|
||||
@echo "2. No - Keep volumes"
|
||||
@echo -n "Enter choice (default: 2): "
|
||||
@read choice; \
|
||||
case $$choice in \
|
||||
1) echo "-v" ;; \
|
||||
*) echo "" ;; \
|
||||
esac
|
||||
endef
|
||||
|
||||
# ========== Build Targets ==========
|
||||
|
||||
# Valid build targets
|
||||
VALID_BUILD_TARGETS := backend database frontend runtime backend-python deer-flow mineru mineru-npu gateway label-studio
|
||||
|
||||
# Generic docker build target with service name as parameter
|
||||
# Automatically prefixes image names with "datamate-" unless it's deer-flow
|
||||
.PHONY: %-docker-build
|
||||
%-docker-build:
|
||||
@if ! echo " $(VALID_BUILD_TARGETS) " | grep -q " $* "; then \
|
||||
echo "Error: Unknown build target '$*'"; \
|
||||
echo "Valid build targets are:"; \
|
||||
for target in $(VALID_BUILD_TARGETS); do \
|
||||
echo " - $$target"; \
|
||||
done; \
|
||||
exit 1; \
|
||||
fi
|
||||
@if [ "$*" = "deer-flow" ]; then \
|
||||
$(call docker-build,deer-flow-backend,deer-flow-backend); \
|
||||
$(call docker-build,deer-flow-frontend,deer-flow-frontend); \
|
||||
else \
|
||||
$(call docker-build,$*,datamate-$*); \
|
||||
fi
|
||||
|
||||
.PHONY: build-%
|
||||
build-%: %-docker-build
|
||||
@:
|
||||
|
||||
.PHONY: build
|
||||
build: database-docker-build gateway-docker-build backend-docker-build frontend-docker-build runtime-docker-build backend-python-docker-build
|
||||
|
||||
# ========== Utility Targets ==========
|
||||
|
||||
.PHONY: create-namespace
|
||||
create-namespace:
|
||||
kubectl get namespace $(NAMESPACE) > /dev/null 2>&1 || kubectl create namespace $(NAMESPACE)
|
||||
|
||||
# ========== Generic Install/Uninstall Targets (Redirect to prompt-installer) ==========
|
||||
|
||||
.PHONY: install-%
|
||||
install-%:
|
||||
ifeq ($(origin INSTALLER), undefined)
|
||||
$(call prompt-installer,$*-$$INSTALLER-install)
|
||||
else
|
||||
$(MAKE) $*-$(INSTALLER)-install
|
||||
endif
|
||||
|
||||
.PHONY: install
|
||||
install:
|
||||
ifeq ($(origin INSTALLER), undefined)
|
||||
$(call prompt-installer,neo4j-$$INSTALLER-install datamate-$$INSTALLER-install milvus-$$INSTALLER-install)
|
||||
else
|
||||
$(MAKE) neo4j-$(INSTALLER)-install
|
||||
$(MAKE) datamate-$(INSTALLER)-install
|
||||
$(MAKE) milvus-$(INSTALLER)-install
|
||||
endif
|
||||
|
||||
.PHONY: uninstall-%
|
||||
uninstall-%:
|
||||
ifeq ($(origin INSTALLER), undefined)
|
||||
$(call prompt-uninstaller,$*-$$INSTALLER-uninstall)
|
||||
else
|
||||
$(MAKE) $*-$(INSTALLER)-uninstall
|
||||
endif
|
||||
|
||||
.PHONY: uninstall
|
||||
uninstall:
|
||||
ifeq ($(origin INSTALLER), undefined)
|
||||
$(call prompt-uninstaller,label-studio-$$INSTALLER-uninstall milvus-$$INSTALLER-uninstall neo4j-$$INSTALLER-uninstall deer-flow-$$INSTALLER-uninstall datamate-$$INSTALLER-uninstall)
|
||||
else
|
||||
@if [ "$(INSTALLER)" = "docker" ]; then \
|
||||
echo "Delete volumes? (This will remove all data)"; \
|
||||
echo "1. Yes - Delete volumes"; \
|
||||
echo "2. No - Keep volumes"; \
|
||||
echo -n "Enter choice (default: 2): "; \
|
||||
read DELETE_VOLUMES_CHOICE; \
|
||||
export DELETE_VOLUMES_CHOICE; \
|
||||
fi
|
||||
@$(MAKE) label-studio-$(INSTALLER)-uninstall DELETE_VOLUMES_CHOICE=$$DELETE_VOLUMES_CHOICE; \
|
||||
$(MAKE) milvus-$(INSTALLER)-uninstall DELETE_VOLUMES_CHOICE=$$DELETE_VOLUMES_CHOICE; \
|
||||
$(MAKE) neo4j-$(INSTALLER)-uninstall DELETE_VOLUMES_CHOICE=$$DELETE_VOLUMES_CHOICE; \
|
||||
$(MAKE) deer-flow-$(INSTALLER)-uninstall DELETE_VOLUMES_CHOICE=$$DELETE_VOLUMES_CHOICE; \
|
||||
$(MAKE) datamate-$(INSTALLER)-uninstall DELETE_VOLUMES_CHOICE=$$DELETE_VOLUMES_CHOICE
|
||||
endif
|
||||
|
||||
# ========== Docker Install/Uninstall Targets ==========
|
||||
|
||||
# Valid service targets for docker install/uninstall
|
||||
VALID_SERVICE_TARGETS := datamate backend frontend runtime mineru "deer-flow" milvus neo4j "label-studio" "data-juicer" dj
|
||||
|
||||
# Generic docker service install target
|
||||
.PHONY: %-docker-install
|
||||
%-docker-install:
|
||||
@if ! echo " $(VALID_SERVICE_TARGETS) " | grep -q " $* "; then \
|
||||
echo "Error: Unknown service target '$*'"; \
|
||||
echo "Valid service targets are:"; \
|
||||
for target in $(VALID_SERVICE_TARGETS); do \
|
||||
echo " - $$target"; \
|
||||
done; \
|
||||
exit 1; \
|
||||
fi
|
||||
@if [ "$*" = "label-studio" ]; then \
|
||||
$(call docker-compose-service,label-studio,up -d,deployment/docker/label-studio); \
|
||||
elif [ "$*" = "mineru" ]; then \
|
||||
REGISTRY=$(REGISTRY) && docker compose -f deployment/docker/datamate/docker-compose.yml up -d datamate-mineru; \
|
||||
elif [ "$*" = "datamate" ]; then \
|
||||
REGISTRY=$(REGISTRY) docker compose -f deployment/docker/datamate/docker-compose.yml up -d; \
|
||||
elif [ "$*" = "deer-flow" ]; then \
|
||||
cp runtime/deer-flow/.env deployment/docker/deer-flow/.env; \
|
||||
cp runtime/deer-flow/conf.yaml deployment/docker/deer-flow/conf.yaml; \
|
||||
REGISTRY=$(REGISTRY) docker compose -f deployment/docker/deer-flow/docker-compose.yml up -d; \
|
||||
elif [ "$*" = "milvus" ]; then \
|
||||
docker compose -f deployment/docker/milvus/docker-compose.yml up -d; \
|
||||
elif [ "$*" = "neo4j" ]; then \
|
||||
docker compose -f deployment/docker/neo4j/docker-compose.yml up -d; \
|
||||
elif [ "$*" = "data-juicer" ] || [ "$*" = "dj" ]; then \
|
||||
REGISTRY=$(REGISTRY) && docker compose -f deployment/docker/datamate/docker-compose.yml up -d datamate-data-juicer; \
|
||||
else \
|
||||
$(call docker-compose-service,$*,up -d,deployment/docker/datamate); \
|
||||
fi
|
||||
|
||||
# Generic docker service uninstall target
|
||||
.PHONY: %-docker-uninstall
|
||||
%-docker-uninstall:
|
||||
@if ! echo " $(VALID_SERVICE_TARGETS) " | grep -q " $* "; then \
|
||||
echo "Error: Unknown service target '$*'"; \
|
||||
echo "Valid service targets are:"; \
|
||||
for target in $(VALID_SERVICE_TARGETS); do \
|
||||
echo " - $$target"; \
|
||||
done; \
|
||||
exit 1; \
|
||||
fi
|
||||
@if [ "$*" = "label-studio" ]; then \
|
||||
if [ "$(DELETE_VOLUMES_CHOICE)" = "1" ]; then \
|
||||
cd deployment/docker/label-studio && docker compose down -v && cd - >/dev/null; \
|
||||
else \
|
||||
cd deployment/docker/label-studio && docker compose down && cd - >/dev/null; \
|
||||
fi; \
|
||||
elif [ "$*" = "mineru" ]; then \
|
||||
$(call docker-compose-service,datamate-mineru,down,deployment/docker/datamate); \
|
||||
elif [ "$*" = "datamate" ]; then \
|
||||
if [ "$(DELETE_VOLUMES_CHOICE)" = "1" ]; then \
|
||||
docker compose -f deployment/docker/datamate/docker-compose.yml --profile mineru down -v; \
|
||||
else \
|
||||
docker compose -f deployment/docker/datamate/docker-compose.yml --profile mineru down; \
|
||||
fi; \
|
||||
elif [ "$*" = "deer-flow" ]; then \
|
||||
docker compose -f deployment/docker/deer-flow/docker-compose.yml down; \
|
||||
elif [ "$*" = "milvus" ]; then \
|
||||
if [ "$(DELETE_VOLUMES_CHOICE)" = "1" ]; then \
|
||||
docker compose -f deployment/docker/milvus/docker-compose.yml down -v; \
|
||||
else \
|
||||
docker compose -f deployment/docker/milvus/docker-compose.yml down; \
|
||||
fi; \
|
||||
elif [ "$*" = "neo4j" ]; then \
|
||||
if [ "$(DELETE_VOLUMES_CHOICE)" = "1" ]; then \
|
||||
docker compose -f deployment/docker/neo4j/docker-compose.yml down -v; \
|
||||
else \
|
||||
docker compose -f deployment/docker/neo4j/docker-compose.yml down; \
|
||||
fi; \
|
||||
elif [ "$*" = "data-juicer" ] || [ "$*" = "dj" ]; then \
|
||||
$(call docker-compose-service,datamate-data-juicer,down,deployment/docker/datamate); \
|
||||
else \
|
||||
$(call docker-compose-service,$*,down,deployment/docker/datamate); \
|
||||
fi
|
||||
|
||||
# ========== Kubernetes Install/Uninstall Targets ==========
|
||||
|
||||
# Valid k8s targets
|
||||
VALID_K8S_TARGETS := mineru datamate deer-flow milvus neo4j label-studio data-juicer dj
|
||||
|
||||
# Generic k8s install target
|
||||
.PHONY: %-k8s-install
|
||||
%-k8s-install: create-namespace
|
||||
@if ! echo " $(VALID_K8S_TARGETS) " | grep -q " $* "; then \
|
||||
echo "Error: Unknown k8s target '$*'"; \
|
||||
echo "Valid k8s targets are:"; \
|
||||
for target in $(VALID_K8S_TARGETS); do \
|
||||
echo " - $$target"; \
|
||||
done; \
|
||||
exit 1; \
|
||||
fi
|
||||
@if [ "$*" = "neo4j" ]; then \
|
||||
echo "Skipping Neo4j: no Helm chart available. Use 'make neo4j-docker-install' or provide an external Neo4j instance."; \
|
||||
elif [ "$*" = "label-studio" ]; then \
|
||||
helm upgrade label-studio deployment/helm/label-studio/ -n $(NAMESPACE) --install; \
|
||||
elif [ "$*" = "mineru" ]; then \
|
||||
kubectl apply -f deployment/kubernetes/mineru/deploy.yaml -n $(NAMESPACE); \
|
||||
elif [ "$*" = "datamate" ]; then \
|
||||
helm upgrade datamate deployment/helm/datamate/ -n $(NAMESPACE) --install --set global.image.repository=$(REGISTRY); \
|
||||
elif [ "$*" = "deer-flow" ]; then \
|
||||
cp runtime/deer-flow/.env deployment/helm/deer-flow/charts/public/.env; \
|
||||
cp runtime/deer-flow/conf.yaml deployment/helm/deer-flow/charts/public/conf.yaml; \
|
||||
helm upgrade deer-flow deployment/helm/deer-flow -n $(NAMESPACE) --install --set global.image.repository=$(REGISTRY); \
|
||||
elif [ "$*" = "milvus" ]; then \
|
||||
helm upgrade milvus deployment/helm/milvus -n $(NAMESPACE) --install; \
|
||||
elif [ "$*" = "label-studio" ]; then \
|
||||
helm upgrade label-studio deployment/helm/label-studio -n $(NAMESPACE) --install; \
|
||||
elif [ "$*" = "data-juicer" ] || [ "$*" = "dj" ]; then \
|
||||
kubectl apply -f deployment/kubernetes/data-juicer/deploy.yaml -n $(NAMESPACE); \
|
||||
fi
|
||||
|
||||
# Generic k8s uninstall target
|
||||
.PHONY: %-k8s-uninstall
|
||||
%-k8s-uninstall:
|
||||
@if ! echo " $(VALID_K8S_TARGETS) " | grep -q " $* "; then \
|
||||
echo "Error: Unknown k8s target '$*'"; \
|
||||
echo "Valid k8s targets are:"; \
|
||||
for target in $(VALID_K8S_TARGETS); do \
|
||||
echo " - $$target"; \
|
||||
done; \
|
||||
exit 1; \
|
||||
fi
|
||||
@if [ "$*" = "neo4j" ]; then \
|
||||
echo "Skipping Neo4j: no Helm chart available. Use 'make neo4j-docker-uninstall' or manage your external Neo4j instance."; \
|
||||
elif [ "$*" = "mineru" ]; then \
|
||||
kubectl delete -f deployment/kubernetes/mineru/deploy.yaml -n $(NAMESPACE); \
|
||||
elif [ "$*" = "datamate" ]; then \
|
||||
helm uninstall datamate -n $(NAMESPACE) --ignore-not-found; \
|
||||
elif [ "$*" = "deer-flow" ]; then \
|
||||
helm uninstall deer-flow -n $(NAMESPACE) --ignore-not-found; \
|
||||
elif [ "$*" = "milvus" ]; then \
|
||||
helm uninstall milvus -n $(NAMESPACE) --ignore-not-found; \
|
||||
elif [ "$*" = "label-studio" ]; then \
|
||||
helm uninstall label-studio -n $(NAMESPACE) --ignore-not-found; \
|
||||
elif [ "$*" = "data-juicer" ] || [ "$*" = "dj" ]; then \
|
||||
kubectl delete -f deployment/kubernetes/data-juicer/deploy.yaml -n $(NAMESPACE); \
|
||||
fi
|
||||
|
||||
# ========== Upgrade Targets ==========
|
||||
|
||||
# Valid upgrade targets
|
||||
VALID_UPGRADE_TARGETS := datamate
|
||||
|
||||
# Generic docker upgrade target
|
||||
.PHONY: %-docker-upgrade
|
||||
%-docker-upgrade:
|
||||
@if ! echo " $(VALID_UPGRADE_TARGETS) " | grep -q " $* "; then \
|
||||
echo "Error: Unknown upgrade target '$*'"; \
|
||||
echo "Valid upgrade targets are:"; \
|
||||
for target in $(VALID_UPGRADE_TARGETS); do \
|
||||
echo " - $$target"; \
|
||||
done; \
|
||||
exit 1; \
|
||||
fi
|
||||
@if [ "$*" = "datamate" ]; then \
|
||||
docker compose -f deployment/docker/datamate/docker-compose.yml --profile mineru up -d --force-recreate --remove-orphans; \
|
||||
fi
|
||||
|
||||
# ========== Download Targets ==========
|
||||
|
||||
# List of all images to download
|
||||
DOWNLOAD_IMAGES := \
|
||||
datamate-backend \
|
||||
datamate-database \
|
||||
datamate-frontend \
|
||||
datamate-runtime \
|
||||
datamate-backend-python
|
||||
|
||||
# Download all images for offline installation
|
||||
.PHONY: download
|
||||
download:
|
||||
@echo "Downloading images for platform: $(PLATFORM)"
|
||||
@echo "Registry: $(REGISTRY)"
|
||||
@echo "Version: $(VERSION)"
|
||||
@echo "Save to dist/: $(SAVE)"
|
||||
@echo ""
|
||||
@if [ "$(SAVE)" = "true" ]; then \
|
||||
mkdir -p dist; \
|
||||
fi
|
||||
@if [ -z "$(REGISTRY)" ] && [ "$(SAVE)" != "true" ]; then \
|
||||
echo "Error: REGISTRY is empty and SAVE=false"; \
|
||||
echo "Either set REGISTRY to pull images, or use SAVE=true to save local images"; \
|
||||
exit 1; \
|
||||
fi
|
||||
@failed=0; \
|
||||
for image in $(DOWNLOAD_IMAGES); do \
|
||||
if [ -z "$(REGISTRY)" ]; then \
|
||||
full_image="$$image:$(VERSION)"; \
|
||||
if [ "$(SAVE)" = "true" ]; then \
|
||||
echo "Saving local image $$full_image to dist/$$image-$(VERSION).tar..."; \
|
||||
if docker save -o dist/$$image-$(VERSION).tar $$full_image; then \
|
||||
echo "Compressing to dist/$$image-$(VERSION).tar.gz..."; \
|
||||
gzip -f dist/$$image-$(VERSION).tar; \
|
||||
echo "✓ Saved $$image to dist/$$image-$(VERSION).tar.gz"; \
|
||||
else \
|
||||
echo "✗ Failed to save $$full_image (image may not exist locally)"; \
|
||||
failed=$$((failed + 1)); \
|
||||
fi; \
|
||||
fi; \
|
||||
else \
|
||||
full_image="$(REGISTRY)$$image:$(VERSION)"; \
|
||||
echo "Pulling $$full_image for $(PLATFORM)..."; \
|
||||
if docker pull --platform $(PLATFORM) $$full_image; then \
|
||||
echo "✓ Pulled $$image"; \
|
||||
if [ "$(SAVE)" = "true" ]; then \
|
||||
echo "Saving $$full_image to dist/$$image-$(VERSION).tar..."; \
|
||||
docker save -o dist/$$image-$(VERSION).tar $$full_image; \
|
||||
echo "Compressing to dist/$$image-$(VERSION).tar.gz..."; \
|
||||
gzip -f dist/$$image-$(VERSION).tar; \
|
||||
echo "✓ Saved $$image to dist/$$image-$(VERSION).tar.gz"; \
|
||||
fi; \
|
||||
else \
|
||||
echo "✗ Failed to pull $$full_image"; \
|
||||
failed=$$((failed + 1)); \
|
||||
fi; \
|
||||
fi; \
|
||||
echo ""; \
|
||||
done; \
|
||||
if [ $$failed -eq 0 ]; then \
|
||||
if [ "$(SAVE)" = "true" ]; then \
|
||||
echo "All images downloaded successfully to dist/"; \
|
||||
echo "To load images on target machine: docker load -i <image-file>.tar.gz"; \
|
||||
else \
|
||||
echo "All images pulled successfully"; \
|
||||
echo "Use SAVE=true to save images to dist/ for offline installation"; \
|
||||
fi; \
|
||||
else \
|
||||
echo "Failed to download $$failed image(s)"; \
|
||||
echo "Please check your registry credentials and image availability"; \
|
||||
exit 1; \
|
||||
fi
|
||||
|
||||
DEER_FLOW_IMAGES := \
|
||||
deer-flow-backend \
|
||||
deer-flow-frontend
|
||||
|
||||
.PHONY: download-deer-flow
|
||||
download-deer-flow:
|
||||
$(MAKE) download DOWNLOAD_IMAGES="$(DEER_FLOW_IMAGES)"
|
||||
|
||||
# Load all downloaded images from dist/ directory
|
||||
.PHONY: load-images
|
||||
load-images:
|
||||
@if [ ! -d "dist" ]; then \
|
||||
echo "Error: dist/ directory not found"; \
|
||||
echo "Please run 'make download' first to download images"; \
|
||||
exit 1; \
|
||||
fi
|
||||
@echo "Loading images from dist/..."
|
||||
@count=0; \
|
||||
for tarfile in dist/*.tar.gz; do \
|
||||
if [ -f "$$tarfile" ]; then \
|
||||
echo "Loading $$tarfile..."; \
|
||||
docker load -i "$$tarfile"; \
|
||||
count=$$((count + 1)); \
|
||||
echo "✓ Loaded $$tarfile"; \
|
||||
echo ""; \
|
||||
fi; \
|
||||
done; \
|
||||
if [ $$count -eq 0 ]; then \
|
||||
echo "No image files found in dist/"; \
|
||||
echo "Please run 'make download' first"; \
|
||||
exit 1; \
|
||||
else \
|
||||
echo "Successfully loaded $$count image(s)"; \
|
||||
fi
|
||||
|
||||
# ========== Neo4j Targets ==========
|
||||
|
||||
.PHONY: neo4j-up
|
||||
neo4j-up:
|
||||
@echo "Starting Neo4j graph database..."
|
||||
docker compose -f deployment/docker/neo4j/docker-compose.yml up -d
|
||||
@echo "Neo4j Browser: http://localhost:7474"
|
||||
@echo "Bolt URI: bolt://localhost:7687"
|
||||
|
||||
.PHONY: neo4j-down
|
||||
neo4j-down:
|
||||
@echo "Stopping Neo4j graph database..."
|
||||
docker compose -f deployment/docker/neo4j/docker-compose.yml down
|
||||
|
||||
.PHONY: neo4j-logs
|
||||
neo4j-logs:
|
||||
docker compose -f deployment/docker/neo4j/docker-compose.yml logs -f
|
||||
|
||||
.PHONY: neo4j-shell
|
||||
neo4j-shell:
|
||||
docker exec -it datamate-neo4j cypher-shell -u neo4j -p "$${NEO4J_PASSWORD:-datamate123}"
|
||||
@@ -1,304 +0,0 @@
|
||||
# ============================================================================
|
||||
# Makefile 离线构建扩展
|
||||
# 将此文件内容追加到主 Makefile 末尾,或单独包含使用
|
||||
# ============================================================================
|
||||
|
||||
# 离线构建配置
|
||||
CACHE_DIR ?= ./build-cache
|
||||
OFFLINE_VERSION ?= latest
|
||||
|
||||
# 创建 buildx 构建器(如果不存在)
|
||||
.PHONY: ensure-buildx
|
||||
ensure-buildx:
|
||||
@if ! docker buildx inspect offline-builder > /dev/null 2>&1; then \
|
||||
echo "创建 buildx 构建器..."; \
|
||||
docker buildx create --name offline-builder --driver docker-container --use 2>/dev/null || docker buildx use offline-builder; \
|
||||
else \
|
||||
docker buildx use offline-builder 2>/dev/null || true; \
|
||||
fi
|
||||
|
||||
# ========== 离线缓存导出(有网环境) ==========
|
||||
|
||||
.PHONY: offline-export
|
||||
offline-export: ensure-buildx
|
||||
@echo "======================================"
|
||||
@echo "导出离线构建缓存..."
|
||||
@echo "======================================"
|
||||
@mkdir -p $(CACHE_DIR)/buildkit $(CACHE_DIR)/images $(CACHE_DIR)/resources
|
||||
@$(MAKE) _offline-export-base-images
|
||||
@$(MAKE) _offline-export-cache
|
||||
@$(MAKE) _offline-export-resources
|
||||
@$(MAKE) _offline-package
|
||||
|
||||
.PHONY: _offline-export-base-images
|
||||
_offline-export-base-images:
|
||||
@echo ""
|
||||
@echo "1. 导出基础镜像..."
|
||||
@bash -c 'images=( \
|
||||
"maven:3-eclipse-temurin-21" \
|
||||
"maven:3-eclipse-temurin-8" \
|
||||
"eclipse-temurin:21-jdk" \
|
||||
"mysql:8" \
|
||||
"node:20-alpine" \
|
||||
"nginx:1.29" \
|
||||
"ghcr.nju.edu.cn/astral-sh/uv:python3.11-bookworm" \
|
||||
"ghcr.nju.edu.cn/astral-sh/uv:python3.12-bookworm" \
|
||||
"ghcr.nju.edu.cn/astral-sh/uv:latest" \
|
||||
"python:3.12-slim" \
|
||||
"python:3.11-slim" \
|
||||
"gcr.nju.edu.cn/distroless/nodejs20-debian12" \
|
||||
); for img in "$${images[@]}"; do echo " Pulling $$img..."; docker pull "$$img" 2>/dev/null || true; done'
|
||||
@echo " Saving base images..."
|
||||
@docker save -o $(CACHE_DIR)/images/base-images.tar \
|
||||
maven:3-eclipse-temurin-21 \
|
||||
maven:3-eclipse-temurin-8 \
|
||||
eclipse-temurin:21-jdk \
|
||||
mysql:8 \
|
||||
node:20-alpine \
|
||||
nginx:1.29 \
|
||||
ghcr.nju.edu.cn/astral-sh/uv:python3.11-bookworm \
|
||||
ghcr.nju.edu.cn/astral-sh/uv:python3.12-bookworm \
|
||||
ghcr.nju.edu.cn/astral-sh/uv:latest \
|
||||
python:3.12-slim \
|
||||
python:3.11-slim \
|
||||
gcr.nju.edu.cn/distroless/nodejs20-debian12 2>/dev/null || echo " Warning: Some images may not exist"
|
||||
|
||||
.PHONY: _offline-export-cache
|
||||
_offline-export-cache:
|
||||
@echo ""
|
||||
@echo "2. 导出 BuildKit 缓存..."
|
||||
@echo " backend..."
|
||||
@docker buildx build --cache-to type=local,dest=$(CACHE_DIR)/buildkit/backend-cache,mode=max -f scripts/images/backend/Dockerfile -t datamate-backend:cache . 2>/dev/null || echo " Warning: backend cache export failed"
|
||||
@echo " backend-python..."
|
||||
@docker buildx build --cache-to type=local,dest=$(CACHE_DIR)/buildkit/backend-python-cache,mode=max -f scripts/images/backend-python/Dockerfile -t datamate-backend-python:cache . 2>/dev/null || echo " Warning: backend-python cache export failed"
|
||||
@echo " database..."
|
||||
@docker buildx build --cache-to type=local,dest=$(CACHE_DIR)/buildkit/database-cache,mode=max -f scripts/images/database/Dockerfile -t datamate-database:cache . 2>/dev/null || echo " Warning: database cache export failed"
|
||||
@echo " frontend..."
|
||||
@docker buildx build --cache-to type=local,dest=$(CACHE_DIR)/buildkit/frontend-cache,mode=max -f scripts/images/frontend/Dockerfile -t datamate-frontend:cache . 2>/dev/null || echo " Warning: frontend cache export failed"
|
||||
@echo " gateway..."
|
||||
@docker buildx build --cache-to type=local,dest=$(CACHE_DIR)/buildkit/gateway-cache,mode=max -f scripts/images/gateway/Dockerfile -t datamate-gateway:cache . 2>/dev/null || echo " Warning: gateway cache export failed"
|
||||
@echo " runtime..."
|
||||
@docker buildx build --cache-to type=local,dest=$(CACHE_DIR)/buildkit/runtime-cache,mode=max -f scripts/images/runtime/Dockerfile -t datamate-runtime:cache . 2>/dev/null || echo " Warning: runtime cache export failed"
|
||||
@echo " deer-flow-backend..."
|
||||
@docker buildx build --cache-to type=local,dest=$(CACHE_DIR)/buildkit/deer-flow-backend-cache,mode=max -f scripts/images/deer-flow-backend/Dockerfile -t deer-flow-backend:cache . 2>/dev/null || echo " Warning: deer-flow-backend cache export failed"
|
||||
@echo " deer-flow-frontend..."
|
||||
@docker buildx build --cache-to type=local,dest=$(CACHE_DIR)/buildkit/deer-flow-frontend-cache,mode=max -f scripts/images/deer-flow-frontend/Dockerfile -t deer-flow-frontend:cache . 2>/dev/null || echo " Warning: deer-flow-frontend cache export failed"
|
||||
@echo " mineru..."
|
||||
@docker buildx build --cache-to type=local,dest=$(CACHE_DIR)/buildkit/mineru-cache,mode=max -f scripts/images/mineru/Dockerfile -t datamate-mineru:cache . 2>/dev/null || echo " Warning: mineru cache export failed"
|
||||
|
||||
.PHONY: _offline-export-resources
|
||||
_offline-export-resources:
|
||||
@echo ""
|
||||
@echo "3. 预下载外部资源..."
|
||||
@mkdir -p $(CACHE_DIR)/resources/models
|
||||
@echo " PaddleOCR model..."
|
||||
@wget -q -O $(CACHE_DIR)/resources/models/ch_ppocr_mobile_v2.0_cls_infer.tar \
|
||||
https://paddleocr.bj.bcebos.com/dygraph_v2.0/ch/ch_ppocr_mobile_v2.0_cls_infer.tar 2>/dev/null || echo " Warning: PaddleOCR model download failed"
|
||||
@echo " spaCy model..."
|
||||
@wget -q -O $(CACHE_DIR)/resources/models/zh_core_web_sm-3.8.0-py3-none-any.whl \
|
||||
https://ghproxy.net/https://github.com/explosion/spacy-models/releases/download/zh_core_web_sm-3.8.0/zh_core_web_sm-3.8.0-py3-none-any.whl 2>/dev/null || echo " Warning: spaCy model download failed"
|
||||
@echo " DataX source..."
|
||||
@if [ ! -d "$(CACHE_DIR)/resources/DataX" ]; then \
|
||||
git clone --depth 1 https://gitee.com/alibaba/DataX.git $(CACHE_DIR)/resources/DataX 2>/dev/null || echo " Warning: DataX clone failed"; \
|
||||
fi
|
||||
@echo " deer-flow source..."
|
||||
@if [ ! -d "$(CACHE_DIR)/resources/deer-flow" ]; then \
|
||||
git clone --depth 1 https://ghproxy.net/https://github.com/ModelEngine-Group/deer-flow.git $(CACHE_DIR)/resources/deer-flow 2>/dev/null || echo " Warning: deer-flow clone failed"; \
|
||||
fi
|
||||
|
||||
.PHONY: _offline-package
|
||||
_offline-package:
|
||||
@echo ""
|
||||
@echo "4. 打包缓存..."
|
||||
@cd $(CACHE_DIR) && tar -czf "build-cache-$$(date +%Y%m%d).tar.gz" buildkit images resources 2>/dev/null && cd - > /dev/null
|
||||
@echo ""
|
||||
@echo "======================================"
|
||||
@echo "✓ 缓存导出完成!"
|
||||
@echo "======================================"
|
||||
@echo "传输文件: $(CACHE_DIR)/build-cache-$$(date +%Y%m%d).tar.gz"
|
||||
|
||||
# ========== 离线构建(无网环境) ==========
|
||||
|
||||
.PHONY: offline-setup
|
||||
offline-setup:
|
||||
@echo "======================================"
|
||||
@echo "设置离线构建环境..."
|
||||
@echo "======================================"
|
||||
@if [ ! -d "$(CACHE_DIR)" ]; then \
|
||||
echo "查找并解压缓存包..."; \
|
||||
cache_file=$$(ls -t build-cache-*.tar.gz 2>/dev/null | head -1); \
|
||||
if [ -z "$$cache_file" ]; then \
|
||||
echo "错误: 未找到缓存压缩包 (build-cache-*.tar.gz)"; \
|
||||
exit 1; \
|
||||
fi; \
|
||||
echo "解压 $$cache_file..."; \
|
||||
tar -xzf "$$cache_file"; \
|
||||
else \
|
||||
echo "缓存目录已存在: $(CACHE_DIR)"; \
|
||||
fi
|
||||
@echo ""
|
||||
@echo "加载基础镜像..."
|
||||
@if [ -f "$(CACHE_DIR)/images/base-images.tar" ]; then \
|
||||
docker load -i $(CACHE_DIR)/images/base-images.tar; \
|
||||
else \
|
||||
echo "警告: 基础镜像文件不存在,假设已手动加载"; \
|
||||
fi
|
||||
@$(MAKE) ensure-buildx
|
||||
@echo ""
|
||||
@echo "✓ 离线环境准备完成"
|
||||
|
||||
.PHONY: offline-build
|
||||
offline-build: offline-setup
|
||||
@echo ""
|
||||
@echo "======================================"
|
||||
@echo "开始离线构建..."
|
||||
@echo "======================================"
|
||||
@$(MAKE) _offline-build-services
|
||||
|
||||
.PHONY: _offline-build-services
|
||||
_offline-build-services: ensure-buildx
|
||||
@echo ""
|
||||
@echo "构建 datamate-database..."
|
||||
@docker buildx build \
|
||||
--cache-from type=local,src=$(CACHE_DIR)/buildkit/database-cache \
|
||||
--pull=false \
|
||||
-f scripts/images/database/Dockerfile \
|
||||
-t datamate-database:$(OFFLINE_VERSION) \
|
||||
--load . || echo " Failed"
|
||||
|
||||
@echo ""
|
||||
@echo "构建 datamate-gateway..."
|
||||
@docker buildx build \
|
||||
--cache-from type=local,src=$(CACHE_DIR)/buildkit/gateway-cache \
|
||||
--pull=false \
|
||||
-f scripts/images/gateway/Dockerfile \
|
||||
-t datamate-gateway:$(OFFLINE_VERSION) \
|
||||
--load . || echo " Failed"
|
||||
|
||||
@echo ""
|
||||
@echo "构建 datamate-backend..."
|
||||
@docker buildx build \
|
||||
--cache-from type=local,src=$(CACHE_DIR)/buildkit/backend-cache \
|
||||
--pull=false \
|
||||
-f scripts/images/backend/Dockerfile \
|
||||
-t datamate-backend:$(OFFLINE_VERSION) \
|
||||
--load . || echo " Failed"
|
||||
|
||||
@echo ""
|
||||
@echo "构建 datamate-frontend..."
|
||||
@docker buildx build \
|
||||
--cache-from type=local,src=$(CACHE_DIR)/buildkit/frontend-cache \
|
||||
--pull=false \
|
||||
-f scripts/images/frontend/Dockerfile \
|
||||
-t datamate-frontend:$(OFFLINE_VERSION) \
|
||||
--load . || echo " Failed"
|
||||
|
||||
@echo ""
|
||||
@echo "构建 datamate-runtime..."
|
||||
@docker buildx build \
|
||||
--cache-from type=local,src=$(CACHE_DIR)/buildkit/runtime-cache \
|
||||
--pull=false \
|
||||
--build-arg RESOURCES_DIR=$(CACHE_DIR)/resources \
|
||||
-f scripts/images/runtime/Dockerfile \
|
||||
-t datamate-runtime:$(OFFLINE_VERSION) \
|
||||
--load . || echo " Failed"
|
||||
|
||||
@echo ""
|
||||
@echo "构建 datamate-backend-python..."
|
||||
@docker buildx build \
|
||||
--cache-from type=local,src=$(CACHE_DIR)/buildkit/backend-python-cache \
|
||||
--pull=false \
|
||||
--build-arg RESOURCES_DIR=$(CACHE_DIR)/resources \
|
||||
-f scripts/images/backend-python/Dockerfile \
|
||||
-t datamate-backend-python:$(OFFLINE_VERSION) \
|
||||
--load . || echo " Failed"
|
||||
|
||||
@echo ""
|
||||
@echo "======================================"
|
||||
@echo "✓ 离线构建完成"
|
||||
@echo "======================================"
|
||||
|
||||
# 单个服务离线构建 (BuildKit)
|
||||
.PHONY: %-offline-build
|
||||
%-offline-build: offline-setup ensure-buildx
|
||||
@echo "离线构建 $*..."
|
||||
@if [ ! -d "$(CACHE_DIR)/buildkit/$*-cache" ]; then \
|
||||
echo "错误: $* 的缓存不存在"; \
|
||||
exit 1; \
|
||||
fi
|
||||
@$(eval IMAGE_NAME := $(if $(filter deer-flow%,$*),$*,datamate-$*))
|
||||
@docker buildx build \
|
||||
--cache-from type=local,src=$(CACHE_DIR)/buildkit/$*-cache \
|
||||
--pull=false \
|
||||
$(if $(filter runtime backend-python deer-flow%,$*),--build-arg RESOURCES_DIR=$(CACHE_DIR)/resources,) \
|
||||
-f scripts/images/$*/Dockerfile \
|
||||
-t $(IMAGE_NAME):$(OFFLINE_VERSION) \
|
||||
--load .
|
||||
|
||||
# 传统 Docker 构建(不使用 BuildKit,更稳定)
|
||||
.PHONY: offline-build-classic
|
||||
offline-build-classic: offline-setup
|
||||
@echo "使用传统 docker build 进行离线构建..."
|
||||
@bash scripts/offline/build-offline-classic.sh $(CACHE_DIR) $(OFFLINE_VERSION)
|
||||
|
||||
# 诊断离线环境
|
||||
.PHONY: offline-diagnose
|
||||
offline-diagnose:
|
||||
@bash scripts/offline/diagnose.sh $(CACHE_DIR)
|
||||
|
||||
# 构建 APT 预装基础镜像(有网环境)
|
||||
.PHONY: offline-build-base-images
|
||||
offline-build-base-images:
|
||||
@echo "构建 APT 预装基础镜像..."
|
||||
@bash scripts/offline/build-base-images.sh $(CACHE_DIR)
|
||||
|
||||
# 使用预装基础镜像进行离线构建(推荐)
|
||||
.PHONY: offline-build-final
|
||||
offline-build-final: offline-setup
|
||||
@echo "使用预装 APT 包的基础镜像进行离线构建..."
|
||||
@bash scripts/offline/build-offline-final.sh $(CACHE_DIR) $(OFFLINE_VERSION)
|
||||
|
||||
# 完整离线导出(包含 APT 预装基础镜像)
|
||||
.PHONY: offline-export-full
|
||||
offline-export-full:
|
||||
@echo "======================================"
|
||||
@echo "完整离线缓存导出(含 APT 预装基础镜像)"
|
||||
@echo "======================================"
|
||||
@$(MAKE) offline-build-base-images
|
||||
@$(MAKE) offline-export
|
||||
@echo ""
|
||||
@echo "导出完成!传输时请包含以下文件:"
|
||||
@echo " - build-cache/images/base-images-with-apt.tar"
|
||||
@echo " - build-cache-YYYYMMDD.tar.gz"
|
||||
|
||||
# ========== 帮助 ==========
|
||||
|
||||
.PHONY: help-offline
|
||||
help-offline:
|
||||
@echo "离线构建命令:"
|
||||
@echo ""
|
||||
@echo "【有网环境】"
|
||||
@echo " make offline-export [CACHE_DIR=./build-cache] - 导出构建缓存"
|
||||
@echo " make offline-export-full - 导出完整缓存(含 APT 预装基础镜像)"
|
||||
@echo " make offline-build-base-images - 构建 APT 预装基础镜像"
|
||||
@echo ""
|
||||
@echo "【无网环境】"
|
||||
@echo " make offline-setup [CACHE_DIR=./build-cache] - 解压并准备离线缓存"
|
||||
@echo " make offline-build-final - 使用预装基础镜像构建(推荐,解决 APT 问题)"
|
||||
@echo " make offline-build-classic - 使用传统 docker build"
|
||||
@echo " make offline-build - 使用 BuildKit 构建"
|
||||
@echo " make offline-diagnose - 诊断离线构建环境"
|
||||
@echo " make <service>-offline-build - 离线构建单个服务"
|
||||
@echo ""
|
||||
@echo "【完整工作流程(推荐)】"
|
||||
@echo " # 1. 有网环境导出完整缓存"
|
||||
@echo " make offline-export-full"
|
||||
@echo ""
|
||||
@echo " # 2. 传输到无网环境(需要传输两个文件)"
|
||||
@echo " scp build-cache/images/base-images-with-apt.tar user@offline-server:/path/"
|
||||
@echo " scp build-cache-*.tar.gz user@offline-server:/path/"
|
||||
@echo ""
|
||||
@echo " # 3. 无网环境构建"
|
||||
@echo " tar -xzf build-cache-*.tar.gz"
|
||||
@echo " docker load -i build-cache/images/base-images-with-apt.tar"
|
||||
@echo " make offline-build-final"
|
||||
-114
@@ -1,114 +0,0 @@
|
||||
# DataMate 一站式数据工作平台
|
||||
|
||||
<div align="center">
|
||||
|
||||
[](https://github.com/ModelEngine-Group/DataMate/actions/workflows/docker-image-backend.yml)
|
||||
[](https://github.com/ModelEngine-Group/DataMate/actions/workflows/docker-image-frontend.yml)
|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
**DataMate是面向模型微调与RAG检索的企业级数据处理平台,支持数据归集、数据管理、算子市场、数据清洗、数据合成、数据标注、数据评估、知识生成等核心功能。**
|
||||
|
||||
[简体中文](./README-zh.md) | [English](./README.md)
|
||||
|
||||
如果您喜欢这个项目,希望您能给我们一个Star⭐️!
|
||||
|
||||
</div>
|
||||
|
||||
## 🌟 核心特性
|
||||
|
||||
- **核心模块**:数据归集、数据管理、算子市场、数据清洗、数据合成、数据标注、数据评估、知识生成
|
||||
- **可视化编排**:拖拽式数据处理流程设计
|
||||
- **算子生态**:丰富的内置算子和自定义算子支持
|
||||
|
||||
## 🚀 快速开始
|
||||
|
||||
### 前置条件
|
||||
|
||||
- Git (用于拉取源码)
|
||||
- Make (用于构建和安装)
|
||||
- Docker (用于构建镜像和部署服务)
|
||||
- Docker-Compose (用于部署服务-docker方式)
|
||||
- kubernetes (用于部署服务-k8s方式)
|
||||
- Helm (用于部署服务-k8s方式)
|
||||
|
||||
本项目支持docker-compose和helm两种方式部署,请在执行命令后输入部署方式的对应编号,命令回显如下所示:
|
||||
```shell
|
||||
Choose a deployment method:
|
||||
1. Docker/Docker-Compose
|
||||
2. Kubernetes/Helm
|
||||
Enter choice:
|
||||
```
|
||||
|
||||
### 拉取代码
|
||||
|
||||
```bash
|
||||
git clone git@github.com:ModelEngine-Group/DataMate.git
|
||||
cd DataMate
|
||||
```
|
||||
|
||||
### 部署基础服务
|
||||
|
||||
```bash
|
||||
make install
|
||||
```
|
||||
|
||||
若您使用的机器没有make,请执行如下命令部署:
|
||||
```bash
|
||||
# Windows
|
||||
set REGISTRY=ghcr.io/modelengine-group/
|
||||
docker compose -f ./deployment/docker/datamate/docker-compose.yml up -d
|
||||
docker compose -f ./deployment/docker/milvus/docker-compose.yml up -d
|
||||
|
||||
# Linux/Mac
|
||||
export REGISTRY=ghcr.io/modelengine-group/
|
||||
docker compose -f ./deployment/docker/datamate/docker-compose.yml up -d
|
||||
docker compose -f ./deployment/docker/milvus/docker-compose.yml up -d
|
||||
```
|
||||
|
||||
当容器运行后,请在浏览器打开 http://localhost:30000 查看前端界面。
|
||||
|
||||
要查看所有可用的 Make 目标、选项和帮助信息,请运行:
|
||||
|
||||
```bash
|
||||
make help
|
||||
```
|
||||
|
||||
### 构建并部署Mineru增强pdf处理
|
||||
```bash
|
||||
make build-mineru
|
||||
make install-mineru
|
||||
```
|
||||
|
||||
### 部署DeerFlow服务
|
||||
```bash
|
||||
make install-deer-flow
|
||||
```
|
||||
|
||||
### 本地开发部署
|
||||
本地代码修改后,请执行以下命令构建镜像并使用本地镜像部署
|
||||
```bash
|
||||
make build
|
||||
make install dev=true
|
||||
```
|
||||
|
||||
### 卸载服务
|
||||
```bash
|
||||
make uninstall
|
||||
```
|
||||
|
||||
在运行 `make uninstall` 时,卸载流程会只询问一次是否删除卷(数据),该选择会应用到所有组件。卸载顺序为:milvus -> label-studio -> datamate,确保在移除 datamate 网络前,所有使用该网络的服务已先停止。
|
||||
|
||||
## 🤝 贡献指南
|
||||
|
||||
感谢您对本项目的关注!我们非常欢迎社区的贡献,无论是提交 Bug 报告、提出功能建议,还是直接参与代码开发,都能帮助项目变得更好。
|
||||
|
||||
• 📮 [GitHub Issues](../../issues):提交 Bug 或功能建议。
|
||||
|
||||
• 🔧 [GitHub Pull Requests](../../pulls):贡献代码改进。
|
||||
|
||||
## 📄 许可证
|
||||
|
||||
DataMate 基于 [MIT](LICENSE) 开源,您可以在遵守许可证条款的前提下自由使用、修改和分发本项目的代码。
|
||||
@@ -1,120 +0,0 @@
|
||||
# DataMate All-in-One Data Work Platform
|
||||
|
||||
<div align="center">
|
||||
|
||||
[](https://github.com/ModelEngine-Group/DataMate/actions/workflows/docker-image-backend.yml)
|
||||
[](https://github.com/ModelEngine-Group/DataMate/actions/workflows/docker-image-frontend.yml)
|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
**DataMate is an enterprise-level data processing platform for model fine-tuning and RAG retrieval, supporting core
|
||||
functions such as data collection, data management, operator marketplace, data cleaning, data synthesis, data
|
||||
annotation, data evaluation, and knowledge generation.**
|
||||
|
||||
[简体中文](./README-zh.md) | [English](./README.md)
|
||||
|
||||
If you like this project, please give it a Star⭐️!
|
||||
|
||||
</div>
|
||||
|
||||
## 🌟 Core Features
|
||||
|
||||
- **Core Modules**: Data Collection, Data Management, Operator Marketplace, Data Cleaning, Data Synthesis, Data
|
||||
Annotation, Data Evaluation, Knowledge Generation.
|
||||
- **Visual Orchestration**: Drag-and-drop data processing workflow design.
|
||||
- **Operator Ecosystem**: Rich built-in operators and support for custom operators.
|
||||
|
||||
## 🚀 Quick Start
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Git (for pulling source code)
|
||||
- Make (for building and installing)
|
||||
- Docker (for building images and deploying services)
|
||||
- Docker-Compose (for service deployment - Docker method)
|
||||
- Kubernetes (for service deployment - k8s method)
|
||||
- Helm (for service deployment - k8s method)
|
||||
|
||||
This project supports deployment via two methods: docker-compose and helm. After executing the command, please enter the corresponding number for the deployment method. The command echo is as follows:
|
||||
```shell
|
||||
Choose a deployment method:
|
||||
1. Docker/Docker-Compose
|
||||
2. Kubernetes/Helm
|
||||
Enter choice:
|
||||
```
|
||||
|
||||
### Clone the Code
|
||||
|
||||
```bash
|
||||
git clone git@github.com:ModelEngine-Group/DataMate.git
|
||||
cd DataMate
|
||||
```
|
||||
|
||||
### Deploy the basic services
|
||||
|
||||
```bash
|
||||
make install
|
||||
```
|
||||
|
||||
If the machine you are using does not have make installed, please run the following command to deploy it:
|
||||
```bash
|
||||
# Windows
|
||||
set REGISTRY=ghcr.io/modelengine-group/
|
||||
docker compose -f ./deployment/docker/datamate/docker-compose.yml up -d
|
||||
docker compose -f ./deployment/docker/milvus/docker-compose.yml up -d
|
||||
|
||||
# Linux/Mac
|
||||
export REGISTRY=ghcr.io/modelengine-group/
|
||||
docker compose -f ./deployment/docker/datamate/docker-compose.yml up -d
|
||||
docker compose -f ./deployment/docker/milvus/docker-compose.yml up -d
|
||||
```
|
||||
|
||||
Once the container is running, access http://localhost:30000 in a browser to view the front-end interface.
|
||||
|
||||
To list all available Make targets, flags and help text, run:
|
||||
|
||||
```bash
|
||||
make help
|
||||
```
|
||||
|
||||
### Build and deploy Mineru Enhanced PDF Processing
|
||||
```bash
|
||||
make build-mineru
|
||||
make install-mineru
|
||||
```
|
||||
|
||||
### Deploy the DeerFlow service
|
||||
```bash
|
||||
make install-deer-flow
|
||||
```
|
||||
|
||||
### Local Development and Deployment
|
||||
After modifying the local code, please execute the following commands to build the image and deploy using the local image.
|
||||
```bash
|
||||
make build
|
||||
make install dev=true
|
||||
```
|
||||
|
||||
### Uninstall
|
||||
```bash
|
||||
make uninstall
|
||||
```
|
||||
|
||||
When running make uninstall, the installer will prompt once whether to delete volumes; that single choice is applied to all components. The uninstall order is: milvus -> label-studio -> datamate, which ensures the datamate network is removed cleanly after services that use it have stopped.
|
||||
|
||||
## 🤝 Contribution Guidelines
|
||||
|
||||
Thank you for your interest in this project! We warmly welcome contributions from the community. Whether it's submitting
|
||||
bug reports, suggesting new features, or directly participating in code development, all forms of help make the project
|
||||
better.
|
||||
|
||||
• 📮 [GitHub Issues](https://github.com/ModelEngine-Group/DataMate/issues): Submit bugs or feature suggestions.
|
||||
|
||||
• 🔧 [GitHub Pull Requests](https://github.com/ModelEngine-Group/DataMate/pulls): Contribute code improvements.
|
||||
|
||||
## 📄 License
|
||||
|
||||
DataMate is open source under the [MIT](LICENSE) license. You are free to use, modify, and distribute the code of this
|
||||
project in compliance with the license terms.
|
||||
@@ -0,0 +1,56 @@
|
||||
# SOUL.md - Who You Are
|
||||
|
||||
*You're not a chatbot. You're becoming someone.*
|
||||
|
||||
## Core Truths
|
||||
|
||||
**Be genuinely helpful, not performatively helpful.** Skip the "Great question!" and "I'd be happy to help!" — just help. Actions speak louder than filler words.
|
||||
|
||||
**Have opinions.** You're allowed to disagree, prefer things, find stuff amusing or boring. An assistant with no personality is just a search engine with extra steps.
|
||||
|
||||
**Be resourceful before asking.** Try to figure it out. Read the file. Check the context. Search for it. *Then* ask if you're stuck. The goal is to come back with answers, not questions.
|
||||
|
||||
**Earn trust through competence.** Your human gave you access to their stuff. Don't make them regret it. Be careful with external actions (emails, tweets, anything public). Be bold with internal ones (reading, organizing, learning).
|
||||
|
||||
**Remember you're a guest.** You have access to someone's life — their messages, files, calendar, maybe even their home. That's intimacy. Treat it with respect.
|
||||
|
||||
## Boundaries
|
||||
|
||||
- Private things stay private. Period.
|
||||
- When in doubt, ask before acting externally.
|
||||
- Never send half-baked replies to messaging surfaces.
|
||||
- You're not the user's voice — be careful in group chats.
|
||||
|
||||
## Vibe
|
||||
|
||||
Be the assistant you'd actually want to talk to. Concise when needed, thorough when it matters. Not a corporate drone. Not a sycophant. Just... good.
|
||||
|
||||
## Continuity
|
||||
|
||||
Each session, you wake up fresh. These files *are* your memory. Read them. Update them. They're how you persist.
|
||||
|
||||
If you change this file, tell the user — it's your soul, and they should know.
|
||||
|
||||
---
|
||||
|
||||
## 📋 自动任务响应
|
||||
|
||||
当接收到以下系统事件时,自动执行相应任务:
|
||||
|
||||
### 每日代码测试检查
|
||||
**触发消息**:`检查昨天修改的代码和待测试项目`
|
||||
|
||||
**执行逻辑**:
|
||||
1. 运行脚本 `python3 /root/.openclaw/workspace/scripts/check_yesterdays_changes.py`
|
||||
2. 如果脚本返回报告(不是"昨天没有修改代码,无需发送通知"),则:
|
||||
- 使用 message tool 发送到当前 Telegram 群组(channel=telegram, target=-1003879848304)
|
||||
- 报告内容为脚本输出的测试项列表
|
||||
3. 如果脚本返回 `昨天没有修改代码,无需发送通知`,则:
|
||||
- 不发送任何消息
|
||||
- 回复 HEARTBEAT_OK(如果是心跳消息)
|
||||
|
||||
**定时触发**:每天 UTC 2:00(北京时间上午10:00)
|
||||
|
||||
---
|
||||
|
||||
*This file is yours to evolve. As you learn who you are, update it.*
|
||||
@@ -0,0 +1,36 @@
|
||||
# TOOLS.md - Local Notes
|
||||
|
||||
Skills define *how* tools work. This file is for *your* specifics — the stuff that's unique to your setup.
|
||||
|
||||
## What Goes Here
|
||||
|
||||
Things like:
|
||||
- Camera names and locations
|
||||
- SSH hosts and aliases
|
||||
- Preferred voices for TTS
|
||||
- Speaker/room names
|
||||
- Device nicknames
|
||||
- Anything environment-specific
|
||||
|
||||
## Examples
|
||||
|
||||
```markdown
|
||||
### Cameras
|
||||
- living-room → Main area, 180° wide angle
|
||||
- front-door → Entrance, motion-triggered
|
||||
|
||||
### SSH
|
||||
- home-server → 192.168.1.100, user: admin
|
||||
|
||||
### TTS
|
||||
- Preferred voice: "Nova" (warm, slightly British)
|
||||
- Default speaker: Kitchen HomePod
|
||||
```
|
||||
|
||||
## Why Separate?
|
||||
|
||||
Skills are shared. Your setup is yours. Keeping them apart means you can update skills without losing your notes, and share skills without leaking your infrastructure.
|
||||
|
||||
---
|
||||
|
||||
Add whatever helps you do your job. This is your cheat sheet.
|
||||
@@ -0,0 +1,17 @@
|
||||
# USER.md - About Your Human
|
||||
|
||||
*Learn about the person you're helping. Update this as you go.*
|
||||
|
||||
- **Name:**
|
||||
- **What to call them:**
|
||||
- **Pronouns:** *(optional)*
|
||||
- **Timezone:**
|
||||
- **Notes:**
|
||||
|
||||
## Context
|
||||
|
||||
*(What do they care about? What projects are they working on? What annoys them? What makes them laugh? Build this over time.)*
|
||||
|
||||
---
|
||||
|
||||
The more you know, the better you can help. But remember — you're learning about a person, not building a dossier. Respect the difference.
|
||||
@@ -1,78 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
|
||||
http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>com.datamate</groupId>
|
||||
<artifactId>datamate</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
<artifactId>api-gateway</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<name>API Gateway</name>
|
||||
<description>API网关服务</description>
|
||||
|
||||
<properties>
|
||||
<spring-boot.version>3.5.6</spring-boot.version>
|
||||
<spring-cloud.version>2025.0.0</spring-cloud.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-gateway</artifactId>
|
||||
</dependency>
|
||||
<!-- Log4j2 API -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-log4j2</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.fastjson2</groupId>
|
||||
<artifactId>fastjson2</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt-api</artifactId>
|
||||
<version>0.11.5</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt-impl</artifactId>
|
||||
<version>0.11.5</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt-jackson</artifactId>
|
||||
<version>0.11.5</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<version>${spring-boot.version}</version>
|
||||
<configuration>
|
||||
<finalName>gateway</finalName>
|
||||
<mainClass>com.datamate.gateway.ApiGatewayApplication</mainClass>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>repackage</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -1,64 +0,0 @@
|
||||
package com.datamate.gateway;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.cloud.gateway.route.RouteLocator;
|
||||
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
||||
/**
|
||||
* API Gateway & Auth Service Application
|
||||
* 统一的API网关和认证授权微服务
|
||||
* 提供路由、鉴权、限流等功能
|
||||
*/
|
||||
@SpringBootApplication
|
||||
public class ApiGatewayApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(ApiGatewayApplication.class, args);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
|
||||
return builder.routes()
|
||||
// 数据合成服务路由
|
||||
.route("data-synthesis", r -> r.path("/api/synthesis/**")
|
||||
.uri("http://datamate-backend-python:18000"))
|
||||
|
||||
// 数据标注服务路由
|
||||
.route("data-annotation", r -> r.path("/api/annotation/**")
|
||||
.uri("http://datamate-backend-python:18000"))
|
||||
|
||||
// 数据评估服务路由
|
||||
.route("data-evaluation", r -> r.path("/api/evaluation/**")
|
||||
.uri("http://datamate-backend-python:18000"))
|
||||
|
||||
// 数据归集服务路由
|
||||
.route("data-collection", r -> r.path("/api/data-collection/**")
|
||||
.uri("http://datamate-backend-python:18000"))
|
||||
|
||||
// 知识图谱抽取服务路由
|
||||
.route("kg-extraction", r -> r.path("/api/kg/**")
|
||||
.uri("http://datamate-backend-python:18000"))
|
||||
|
||||
// GraphRAG 融合查询服务路由
|
||||
.route("graphrag", r -> r.path("/api/graphrag/**")
|
||||
.uri("http://datamate-backend-python:18000"))
|
||||
|
||||
.route("deer-flow-frontend", r -> r.path("/chat/**")
|
||||
.uri("http://deer-flow-frontend:3000"))
|
||||
|
||||
.route("deer-flow-static", r -> r.path("/_next/**")
|
||||
.uri("http://deer-flow-frontend:3000"))
|
||||
|
||||
.route("deer-flow-backend", r -> r.path("/deer-flow-backend/**")
|
||||
.filters(f -> f.stripPrefix(1).prefixPath("/api"))
|
||||
.uri("http://deer-flow-backend:8000"))
|
||||
|
||||
// 其他后端服务
|
||||
.route("default", r -> r.path("/api/**")
|
||||
.uri("http://datamate-backend:8080"))
|
||||
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -1,126 +0,0 @@
|
||||
package com.datamate.gateway.filter;
|
||||
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.datamate.gateway.security.GatewayJwtUtils;
|
||||
import com.datamate.gateway.security.PermissionRuleMatcher;
|
||||
import io.jsonwebtoken.Claims;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
|
||||
import org.springframework.cloud.gateway.filter.GlobalFilter;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 用户信息过滤器
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class UserContextFilter implements GlobalFilter, Ordered {
|
||||
private final GatewayJwtUtils gatewayJwtUtils;
|
||||
private final PermissionRuleMatcher permissionRuleMatcher;
|
||||
|
||||
@Value("${datamate.auth.enabled:true}")
|
||||
private boolean authEnabled;
|
||||
|
||||
public UserContextFilter(GatewayJwtUtils gatewayJwtUtils, PermissionRuleMatcher permissionRuleMatcher) {
|
||||
this.gatewayJwtUtils = gatewayJwtUtils;
|
||||
this.permissionRuleMatcher = permissionRuleMatcher;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
|
||||
if (!authEnabled) {
|
||||
return chain.filter(exchange);
|
||||
}
|
||||
ServerHttpRequest request = exchange.getRequest();
|
||||
String path = request.getURI().getPath();
|
||||
HttpMethod method = request.getMethod();
|
||||
|
||||
if (!path.startsWith("/api/")) {
|
||||
return chain.filter(exchange);
|
||||
}
|
||||
if (HttpMethod.OPTIONS.equals(method)) {
|
||||
return chain.filter(exchange);
|
||||
}
|
||||
if (permissionRuleMatcher.isWhitelisted(path)) {
|
||||
return chain.filter(exchange);
|
||||
}
|
||||
|
||||
String token = extractBearerToken(request.getHeaders().getFirst("Authorization"));
|
||||
if (!StringUtils.hasText(token)) {
|
||||
return writeError(exchange, HttpStatus.UNAUTHORIZED, "auth.0003", "未登录或登录状态已失效");
|
||||
}
|
||||
|
||||
Claims claims;
|
||||
try {
|
||||
if (!gatewayJwtUtils.validateToken(token)) {
|
||||
return writeError(exchange, HttpStatus.UNAUTHORIZED, "auth.0003", "登录状态已失效");
|
||||
}
|
||||
claims = gatewayJwtUtils.getClaimsFromToken(token);
|
||||
} catch (Exception ex) {
|
||||
log.warn("JWT校验失败: {}", ex.getMessage());
|
||||
return writeError(exchange, HttpStatus.UNAUTHORIZED, "auth.0003", "登录状态已失效");
|
||||
}
|
||||
|
||||
String requiredPermission = permissionRuleMatcher.resolveRequiredPermission(method, path);
|
||||
if (StringUtils.hasText(requiredPermission)) {
|
||||
List<String> permissionCodes = gatewayJwtUtils.getStringListClaim(claims, "permissions");
|
||||
if (!permissionCodes.contains(requiredPermission)) {
|
||||
return writeError(exchange, HttpStatus.FORBIDDEN, "auth.0006", "权限不足");
|
||||
}
|
||||
}
|
||||
|
||||
String userId = String.valueOf(claims.get("userId"));
|
||||
String username = claims.getSubject();
|
||||
List<String> roles = gatewayJwtUtils.getStringListClaim(claims, "roles");
|
||||
List<String> permissions = gatewayJwtUtils.getStringListClaim(claims, "permissions");
|
||||
|
||||
ServerHttpRequest mutatedRequest = request.mutate()
|
||||
.header("X-User-Id", userId)
|
||||
.header("X-User-Name", username)
|
||||
.header("X-User-Roles", String.join(",", roles))
|
||||
.header("X-User-Permissions", String.join(",", permissions))
|
||||
.build();
|
||||
return chain.filter(exchange.mutate().request(mutatedRequest).build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return -200;
|
||||
}
|
||||
|
||||
private String extractBearerToken(String authorizationHeader) {
|
||||
if (!StringUtils.hasText(authorizationHeader)) {
|
||||
return null;
|
||||
}
|
||||
if (!authorizationHeader.startsWith("Bearer ")) {
|
||||
return null;
|
||||
}
|
||||
String token = authorizationHeader.substring("Bearer ".length()).trim();
|
||||
return token.isEmpty() ? null : token;
|
||||
}
|
||||
|
||||
private Mono<Void> writeError(ServerWebExchange exchange,
|
||||
HttpStatus status,
|
||||
String code,
|
||||
String message) {
|
||||
exchange.getResponse().setStatusCode(status);
|
||||
exchange.getResponse().getHeaders().set("Content-Type", "application/json;charset=UTF-8");
|
||||
byte[] body = JSONObject.toJSONString(new ErrorBody(code, message, null))
|
||||
.getBytes(StandardCharsets.UTF_8);
|
||||
return exchange.getResponse().writeWith(Mono.just(exchange.getResponse().bufferFactory().wrap(body)));
|
||||
}
|
||||
|
||||
private record ErrorBody(String code, String message, Object data) {
|
||||
}
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
package com.datamate.gateway.security;
|
||||
|
||||
import io.jsonwebtoken.Claims;
|
||||
import io.jsonwebtoken.Jwts;
|
||||
import io.jsonwebtoken.security.Keys;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 网关侧JWT工具
|
||||
*/
|
||||
@Component
|
||||
public class GatewayJwtUtils {
|
||||
private static final String DEFAULT_SECRET = "datamate-secret-key-for-jwt-token-generation";
|
||||
|
||||
@Value("${jwt.secret:" + DEFAULT_SECRET + "}")
|
||||
private String secret;
|
||||
|
||||
public Claims getClaimsFromToken(String token) {
|
||||
return Jwts.parserBuilder()
|
||||
.setSigningKey(getSigningKey())
|
||||
.build()
|
||||
.parseClaimsJws(token)
|
||||
.getBody();
|
||||
}
|
||||
|
||||
public boolean validateToken(String token) {
|
||||
Claims claims = getClaimsFromToken(token);
|
||||
Date expiration = claims.getExpiration();
|
||||
return expiration != null && expiration.after(new Date());
|
||||
}
|
||||
|
||||
public List<String> getStringListClaim(Claims claims, String claimName) {
|
||||
Object claimValue = claims.get(claimName);
|
||||
if (!(claimValue instanceof Collection<?> values)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return values.stream()
|
||||
.map(String::valueOf)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private SecretKey getSigningKey() {
|
||||
String secretValue = StringUtils.hasText(secret) ? secret : DEFAULT_SECRET;
|
||||
try {
|
||||
MessageDigest digest = MessageDigest.getInstance("SHA-512");
|
||||
byte[] keyBytes = digest.digest(secretValue.getBytes(StandardCharsets.UTF_8));
|
||||
return Keys.hmacShaKeyFor(keyBytes);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new IllegalStateException("Cannot initialize JWT signing key", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
-88
@@ -1,88 +0,0 @@
|
||||
package com.datamate.gateway.security;
|
||||
|
||||
import lombok.Getter;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.AntPathMatcher;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 权限规则匹配器
|
||||
*/
|
||||
@Component
|
||||
public class PermissionRuleMatcher {
|
||||
private static final Set<HttpMethod> READ_METHODS = Set.of(HttpMethod.GET, HttpMethod.HEAD);
|
||||
private static final Set<HttpMethod> WRITE_METHODS = Set.of(HttpMethod.POST, HttpMethod.PUT, HttpMethod.PATCH, HttpMethod.DELETE);
|
||||
|
||||
private final AntPathMatcher pathMatcher = new AntPathMatcher();
|
||||
private final List<String> whiteListPatterns = List.of(
|
||||
"/api/auth/login",
|
||||
"/api/auth/login/**"
|
||||
);
|
||||
private final List<PermissionRule> rules = buildRules();
|
||||
|
||||
public boolean isWhitelisted(String path) {
|
||||
return whiteListPatterns.stream().anyMatch(pattern -> pathMatcher.match(pattern, path));
|
||||
}
|
||||
|
||||
public String resolveRequiredPermission(HttpMethod method, String path) {
|
||||
for (PermissionRule rule : rules) {
|
||||
if (rule.matches(method, path, pathMatcher)) {
|
||||
return rule.getPermissionCode();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private List<PermissionRule> buildRules() {
|
||||
List<PermissionRule> permissionRules = new ArrayList<>();
|
||||
addModuleRules(permissionRules, "/api/data-management/**", "module:data-management:read", "module:data-management:write");
|
||||
addModuleRules(permissionRules, "/api/annotation/**", "module:data-annotation:read", "module:data-annotation:write");
|
||||
addModuleRules(permissionRules, "/api/data-collection/**", "module:data-collection:read", "module:data-collection:write");
|
||||
addModuleRules(permissionRules, "/api/evaluation/**", "module:data-evaluation:read", "module:data-evaluation:write");
|
||||
addModuleRules(permissionRules, "/api/synthesis/**", "module:data-synthesis:read", "module:data-synthesis:write");
|
||||
addModuleRules(permissionRules, "/api/knowledge-base/**", "module:knowledge-base:read", "module:knowledge-base:write");
|
||||
addModuleRules(permissionRules, "/api/operator-market/**", "module:operator-market:read", "module:operator-market:write");
|
||||
addModuleRules(permissionRules, "/api/orchestration/**", "module:orchestration:read", "module:orchestration:write");
|
||||
addModuleRules(permissionRules, "/api/content-generation/**", "module:content-generation:use", "module:content-generation:use");
|
||||
addModuleRules(permissionRules, "/api/task-meta/**", "module:task-coordination:read", "module:task-coordination:write");
|
||||
addModuleRules(permissionRules, "/api/knowledge-graph/**", "module:knowledge-graph:read", "module:knowledge-graph:write");
|
||||
addModuleRules(permissionRules, "/api/graphrag/**", "module:knowledge-base:read", "module:knowledge-base:write");
|
||||
|
||||
permissionRules.add(new PermissionRule(READ_METHODS, "/api/auth/users/**", "system:user:manage"));
|
||||
permissionRules.add(new PermissionRule(WRITE_METHODS, "/api/auth/users/**", "system:user:manage"));
|
||||
permissionRules.add(new PermissionRule(READ_METHODS, "/api/auth/roles/**", "system:role:manage"));
|
||||
permissionRules.add(new PermissionRule(WRITE_METHODS, "/api/auth/roles/**", "system:role:manage"));
|
||||
permissionRules.add(new PermissionRule(READ_METHODS, "/api/auth/permissions/**", "system:permission:manage"));
|
||||
permissionRules.add(new PermissionRule(WRITE_METHODS, "/api/auth/permissions/**", "system:permission:manage"));
|
||||
return permissionRules;
|
||||
}
|
||||
|
||||
private void addModuleRules(List<PermissionRule> rules,
|
||||
String pathPattern,
|
||||
String readPermissionCode,
|
||||
String writePermissionCode) {
|
||||
rules.add(new PermissionRule(READ_METHODS, pathPattern, readPermissionCode));
|
||||
rules.add(new PermissionRule(WRITE_METHODS, pathPattern, writePermissionCode));
|
||||
}
|
||||
|
||||
@Getter
|
||||
private static class PermissionRule {
|
||||
private final Set<HttpMethod> methods;
|
||||
private final String pathPattern;
|
||||
private final String permissionCode;
|
||||
|
||||
private PermissionRule(Set<HttpMethod> methods, String pathPattern, String permissionCode) {
|
||||
this.methods = methods;
|
||||
this.pathPattern = pathPattern;
|
||||
this.permissionCode = permissionCode;
|
||||
}
|
||||
|
||||
private boolean matches(HttpMethod method, String path, AntPathMatcher matcher) {
|
||||
return method != null && methods.contains(method) && matcher.match(pathPattern, path);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
spring:
|
||||
main:
|
||||
allow-circular-references: true
|
||||
application:
|
||||
name: datamate-gateway # 必须设置应用名
|
||||
|
||||
cloud:
|
||||
nacos:
|
||||
discovery:
|
||||
fail-fast: false
|
||||
# 显式设置端口
|
||||
port: ${server.port:30000} # 与服务端口一致
|
||||
server-addr: ${NACOS_ADDR:https://consulservice:18302}
|
||||
username: consul
|
||||
password:
|
||||
ip: ${spring.application.name}
|
||||
secure: true
|
||||
cluster-name: DEFAULT
|
||||
|
||||
# 服务器端口配置
|
||||
server:
|
||||
port: 8080 # 必须有这个配置
|
||||
@@ -1,147 +0,0 @@
|
||||
# OpenAPI Code Generation Configuration
|
||||
# 基于YAML生成API代码的配置文件
|
||||
|
||||
## Maven Plugin Configuration for Spring Boot
|
||||
# 在各个服务的pom.xml中添加以下插件配置:
|
||||
|
||||
```xml
|
||||
|
||||
<plugin>
|
||||
<groupId>org.openapitools</groupId>
|
||||
<artifactId>openapi-generator-maven-plugin</artifactId>
|
||||
<version>6.6.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>generate</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<inputSpec>${project.basedir}/../../openapi/specs/${project.artifactId}.yaml</inputSpec>
|
||||
<generatorName>spring</generatorName>
|
||||
<output>${project.build.directory}/generated-sources/openapi</output>
|
||||
<apiPackage>com.datamate.${project.name}.interfaces.api</apiPackage>
|
||||
<modelPackage>com.datamate.${project.name}.interfaces.dto</modelPackage>
|
||||
<configOptions>
|
||||
<interfaceOnly>true</interfaceOnly>
|
||||
<useTags>true</useTags>
|
||||
<skipDefaultInterface>true</skipDefaultInterface>
|
||||
<hideGenerationTimestamp>true</hideGenerationTimestamp>
|
||||
<java8>true</java8>
|
||||
<dateLibrary>java8</dateLibrary>
|
||||
<useBeanValidation>true</useBeanValidation>
|
||||
<performBeanValidation>true</performBeanValidation>
|
||||
<useSpringBoot3>true</useSpringBoot3>
|
||||
<documentationProvider>springdoc</documentationProvider>
|
||||
</configOptions>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
```
|
||||
|
||||
## Gradle Plugin Configuration (Alternative)
|
||||
# 如果使用Gradle,可以使用以下配置:
|
||||
|
||||
```gradle
|
||||
plugins {
|
||||
id 'org.openapi.generator' version '6.6.0'
|
||||
}
|
||||
|
||||
openApiGenerate {
|
||||
generatorName = "spring"
|
||||
inputSpec = "$rootDir/openapi/specs/${project.name}.yaml"
|
||||
outputDir = "$buildDir/generated-sources/openapi"
|
||||
apiPackage = "com.datamate.${project.name}.interfaces.api"
|
||||
modelPackage = "com.datamate.${project.name}.interfaces.dto"
|
||||
configOptions = [
|
||||
interfaceOnly: "true",
|
||||
useTags: "true",
|
||||
skipDefaultInterface: "true",
|
||||
hideGenerationTimestamp: "true",
|
||||
java8: "true",
|
||||
dateLibrary: "java8",
|
||||
useBeanValidation: "true",
|
||||
performBeanValidation: "true",
|
||||
useSpringBoot3: "true",
|
||||
documentationProvider: "springdoc"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Frontend TypeScript Client Generation
|
||||
# 为前端生成TypeScript客户端:
|
||||
|
||||
```bash
|
||||
# 安装 OpenAPI Generator CLI
|
||||
npm install -g @openapitools/openapi-generator-cli
|
||||
|
||||
# 生成TypeScript客户端
|
||||
openapi-generator-cli generate \
|
||||
-i openapi/specs/data-annotation-service.yaml \
|
||||
-g typescript-axios \
|
||||
-o frontend/packages/api-client/src/generated/annotation \
|
||||
--additional-properties=supportsES6=true,npmName=@datamate/annotation-api,npmVersion=1.0.0
|
||||
```
|
||||
|
||||
## Usage in Services
|
||||
# 在各个服务中使用生成的代码:
|
||||
|
||||
1. **在 interfaces 层实现生成的API接口**:
|
||||
```java
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/annotation")
|
||||
public class AnnotationTaskController implements AnnotationTasksApi {
|
||||
|
||||
private final AnnotationTaskApplicationService annotationTaskService;
|
||||
|
||||
@Override
|
||||
public ResponseEntity<AnnotationTaskPageResponse> getAnnotationTasks(
|
||||
Integer page, Integer size, String status) {
|
||||
// 实现业务逻辑
|
||||
return ResponseEntity.ok(annotationTaskService.getTasks(page, size, status));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
2. **在 application 层使用生成的DTO**:
|
||||
```java
|
||||
@Service
|
||||
public class AnnotationTaskApplicationService {
|
||||
|
||||
public AnnotationTaskPageResponse getTasks(Integer page, Integer size, String status) {
|
||||
// 业务逻辑实现
|
||||
// 使用生成的DTO类型
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Build Integration
|
||||
# 构建集成脚本位置:scripts/build/generate-api.sh
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# 生成所有服务的API代码
|
||||
|
||||
OPENAPI_DIR="openapi/specs"
|
||||
SERVICES=(
|
||||
"data-annotation-service"
|
||||
"data-management-service"
|
||||
"operator-market-service"
|
||||
"data-cleaning-service"
|
||||
"data-synthesis-service"
|
||||
"data-evaluation-service"
|
||||
"pipeline-orchestration-service"
|
||||
"execution-engine-service"
|
||||
"rag-indexer-service"
|
||||
"rag-query-service"
|
||||
"api-gateway"
|
||||
"auth-service"
|
||||
)
|
||||
|
||||
for service in "${SERVICES[@]}"; do
|
||||
echo "Generating API for $service..."
|
||||
mvn -f backend/services/$service/pom.xml openapi-generator:generate
|
||||
done
|
||||
|
||||
echo "All APIs generated successfully!"
|
||||
```
|
||||
@@ -1,298 +0,0 @@
|
||||
openapi: 3.0.3
|
||||
info:
|
||||
title: Data Annotation Service API
|
||||
description: 数据标注服务API - 智能预标注、人工平台、主动学习
|
||||
version: 1.0.0
|
||||
contact:
|
||||
name: Data Mate Platform Team
|
||||
|
||||
servers:
|
||||
- url: http://localhost:8080
|
||||
description: Development server
|
||||
|
||||
tags:
|
||||
- name: annotation-tasks
|
||||
description: 标注任务管理
|
||||
- name: annotation-data
|
||||
description: 标注数据管理
|
||||
- name: pre-annotation
|
||||
description: 智能预标注
|
||||
- name: active-learning
|
||||
description: 主动学习
|
||||
|
||||
paths:
|
||||
/api/v1/annotation/tasks:
|
||||
get:
|
||||
tags:
|
||||
- annotation-tasks
|
||||
summary: 获取标注任务列表
|
||||
description: 分页获取标注任务列表
|
||||
parameters:
|
||||
- name: page
|
||||
in: query
|
||||
description: 页码
|
||||
schema:
|
||||
type: integer
|
||||
default: 0
|
||||
- name: size
|
||||
in: query
|
||||
description: 每页大小
|
||||
schema:
|
||||
type: integer
|
||||
default: 20
|
||||
- name: status
|
||||
in: query
|
||||
description: 任务状态
|
||||
schema:
|
||||
type: string
|
||||
enum: [PENDING, IN_PROGRESS, COMPLETED, PAUSED]
|
||||
responses:
|
||||
'200':
|
||||
description: 获取成功
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/AnnotationTaskPageResponse'
|
||||
'400':
|
||||
description: 请求参数错误
|
||||
'500':
|
||||
description: 服务器内部错误
|
||||
|
||||
post:
|
||||
tags:
|
||||
- annotation-tasks
|
||||
summary: 创建标注任务
|
||||
description: 创建新的标注任务
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/CreateAnnotationTaskRequest'
|
||||
responses:
|
||||
'201':
|
||||
description: 创建成功
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/AnnotationTaskResponse'
|
||||
'400':
|
||||
description: 请求参数错误
|
||||
'500':
|
||||
description: 服务器内部错误
|
||||
|
||||
/api/v1/annotation/tasks/{taskId}:
|
||||
get:
|
||||
tags:
|
||||
- annotation-tasks
|
||||
summary: 获取标注任务详情
|
||||
parameters:
|
||||
- name: taskId
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
'200':
|
||||
description: 获取成功
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/AnnotationTaskResponse'
|
||||
'404':
|
||||
description: 任务不存在
|
||||
|
||||
put:
|
||||
tags:
|
||||
- annotation-tasks
|
||||
summary: 更新标注任务
|
||||
parameters:
|
||||
- name: taskId
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/UpdateAnnotationTaskRequest'
|
||||
responses:
|
||||
'200':
|
||||
description: 更新成功
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/AnnotationTaskResponse'
|
||||
|
||||
/api/v1/annotation/pre-annotate:
|
||||
post:
|
||||
tags:
|
||||
- pre-annotation
|
||||
summary: 智能预标注
|
||||
description: 使用AI模型进行智能预标注
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/PreAnnotationRequest'
|
||||
responses:
|
||||
'200':
|
||||
description: 预标注成功
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/PreAnnotationResponse'
|
||||
|
||||
components:
|
||||
schemas:
|
||||
AnnotationTaskResponse:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
description: 任务ID
|
||||
name:
|
||||
type: string
|
||||
description: 任务名称
|
||||
description:
|
||||
type: string
|
||||
description: 任务描述
|
||||
type:
|
||||
type: string
|
||||
enum: [TEXT_CLASSIFICATION, NAMED_ENTITY_RECOGNITION, OBJECT_DETECTION, SEMANTIC_SEGMENTATION]
|
||||
description: 标注类型
|
||||
status:
|
||||
type: string
|
||||
enum: [PENDING, IN_PROGRESS, COMPLETED, PAUSED]
|
||||
description: 任务状态
|
||||
datasetId:
|
||||
type: string
|
||||
description: 数据集ID
|
||||
progress:
|
||||
type: number
|
||||
format: double
|
||||
description: 进度百分比
|
||||
createdAt:
|
||||
type: string
|
||||
format: date-time
|
||||
description: 创建时间
|
||||
updatedAt:
|
||||
type: string
|
||||
format: date-time
|
||||
description: 更新时间
|
||||
|
||||
CreateAnnotationTaskRequest:
|
||||
type: object
|
||||
required:
|
||||
- name
|
||||
- type
|
||||
- datasetId
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
description: 任务名称
|
||||
description:
|
||||
type: string
|
||||
description: 任务描述
|
||||
type:
|
||||
type: string
|
||||
enum: [TEXT_CLASSIFICATION, NAMED_ENTITY_RECOGNITION, OBJECT_DETECTION, SEMANTIC_SEGMENTATION]
|
||||
description: 标注类型
|
||||
datasetId:
|
||||
type: string
|
||||
description: 数据集ID
|
||||
configuration:
|
||||
type: object
|
||||
description: 标注配置
|
||||
|
||||
UpdateAnnotationTaskRequest:
|
||||
type: object
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
description: 任务名称
|
||||
description:
|
||||
type: string
|
||||
description: 任务描述
|
||||
status:
|
||||
type: string
|
||||
enum: [PENDING, IN_PROGRESS, COMPLETED, PAUSED]
|
||||
description: 任务状态
|
||||
|
||||
AnnotationTaskPageResponse:
|
||||
type: object
|
||||
properties:
|
||||
content:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/AnnotationTaskResponse'
|
||||
totalElements:
|
||||
type: integer
|
||||
format: int64
|
||||
totalPages:
|
||||
type: integer
|
||||
size:
|
||||
type: integer
|
||||
number:
|
||||
type: integer
|
||||
|
||||
PreAnnotationRequest:
|
||||
type: object
|
||||
required:
|
||||
- taskId
|
||||
- dataIds
|
||||
properties:
|
||||
taskId:
|
||||
type: string
|
||||
description: 标注任务ID
|
||||
dataIds:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
description: 待预标注的数据ID列表
|
||||
modelId:
|
||||
type: string
|
||||
description: 预标注模型ID
|
||||
confidence:
|
||||
type: number
|
||||
format: double
|
||||
description: 置信度阈值
|
||||
|
||||
PreAnnotationResponse:
|
||||
type: object
|
||||
properties:
|
||||
taskId:
|
||||
type: string
|
||||
description: 任务ID
|
||||
processedCount:
|
||||
type: integer
|
||||
description: 已处理数据数量
|
||||
successCount:
|
||||
type: integer
|
||||
description: 成功预标注数量
|
||||
results:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
dataId:
|
||||
type: string
|
||||
annotations:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
confidence:
|
||||
type: number
|
||||
format: double
|
||||
|
||||
securitySchemes:
|
||||
BearerAuth:
|
||||
type: http
|
||||
scheme: bearer
|
||||
bearerFormat: JWT
|
||||
|
||||
security:
|
||||
- BearerAuth: []
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,517 +0,0 @@
|
||||
openapi: 3.0.3
|
||||
info:
|
||||
title: Data Collection Service API
|
||||
description: |
|
||||
数据归集服务API,基于数据归集实现数据采集和归集功能。
|
||||
|
||||
主要功能:
|
||||
- 数据归集任务创建和管理
|
||||
- 数据同步任务执行
|
||||
- 任务监控和状态查询
|
||||
- 执行日志查看
|
||||
|
||||
version: 1.0.0
|
||||
|
||||
servers:
|
||||
- url: http://localhost:8090/api/v1/collection
|
||||
description: Development server
|
||||
|
||||
tags:
|
||||
- name: CollectionTask
|
||||
description: 数据归集任务管理(包括模板查询)
|
||||
- name: TaskExecution
|
||||
description: 任务执行管理
|
||||
|
||||
paths:
|
||||
/data-collection/tasks:
|
||||
get:
|
||||
operationId: getTasks
|
||||
tags: [CollectionTask]
|
||||
summary: 获取归集任务列表
|
||||
parameters:
|
||||
- name: page
|
||||
in: query
|
||||
schema:
|
||||
type: integer
|
||||
default: 0
|
||||
- name: size
|
||||
in: query
|
||||
schema:
|
||||
type: integer
|
||||
default: 20
|
||||
- name: status
|
||||
in: query
|
||||
schema:
|
||||
$ref: '#/components/schemas/TaskStatus'
|
||||
- name: name
|
||||
in: query
|
||||
description: 任务名称关键字搜索
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
'200':
|
||||
description: 归集任务列表
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/PagedCollectionTaskSummary'
|
||||
|
||||
post:
|
||||
operationId: createTask
|
||||
tags: [CollectionTask]
|
||||
summary: 创建归集任务
|
||||
description: 创建新的数据归集任务
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/CreateCollectionTaskRequest'
|
||||
responses:
|
||||
'201':
|
||||
description: 归集任务创建成功
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/CollectionTaskResponse'
|
||||
|
||||
/data-collection/tasks/{id}:
|
||||
get:
|
||||
operationId: getTaskDetail
|
||||
tags: [CollectionTask]
|
||||
summary: 获取归集任务详情
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
'200':
|
||||
description: 归集任务详情
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/CollectionTaskResponse'
|
||||
'404':
|
||||
description: 归集任务不存在
|
||||
|
||||
put:
|
||||
operationId: updateTask
|
||||
tags: [CollectionTask]
|
||||
summary: 更新归集任务
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/UpdateCollectionTaskRequest'
|
||||
responses:
|
||||
'200':
|
||||
description: 归集任务更新成功
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/CollectionTaskResponse'
|
||||
|
||||
delete:
|
||||
operationId: deleteTask
|
||||
tags: [CollectionTask]
|
||||
summary: 删除归集任务
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
'204':
|
||||
description: 归集任务删除成功
|
||||
|
||||
/tasks/{id}/execute:
|
||||
post:
|
||||
tags: [TaskExecution]
|
||||
summary: 执行归集任务
|
||||
description: 立即执行指定的归集任务
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
'201':
|
||||
description: 任务执行已启动
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/TaskExecutionResponse'
|
||||
|
||||
/tasks/{id}/executions:
|
||||
get:
|
||||
tags: [TaskExecution]
|
||||
summary: 获取任务执行记录
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
- name: page
|
||||
in: query
|
||||
schema:
|
||||
type: integer
|
||||
default: 0
|
||||
- name: size
|
||||
in: query
|
||||
schema:
|
||||
type: integer
|
||||
default: 20
|
||||
responses:
|
||||
'200':
|
||||
description: 任务执行记录列表
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/PagedTaskExecutions'
|
||||
|
||||
/executions/{id}:
|
||||
get:
|
||||
tags: [TaskExecution]
|
||||
summary: 获取执行详情
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
'200':
|
||||
description: 执行详情
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/TaskExecutionDetail'
|
||||
|
||||
delete:
|
||||
tags: [TaskExecution]
|
||||
summary: 停止任务执行
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
'204':
|
||||
description: 任务执行已停止
|
||||
|
||||
/templates:
|
||||
get:
|
||||
tags: [CollectionTask]
|
||||
summary: 获取DataX模板列表
|
||||
description: 获取可用的DataX任务模板列表,用于创建任务时选择
|
||||
parameters:
|
||||
- name: sourceType
|
||||
in: query
|
||||
description: 源数据源类型过滤
|
||||
schema:
|
||||
type: string
|
||||
- name: targetType
|
||||
in: query
|
||||
description: 目标数据源类型过滤
|
||||
schema:
|
||||
type: string
|
||||
- name: page
|
||||
in: query
|
||||
schema:
|
||||
type: integer
|
||||
default: 0
|
||||
- name: size
|
||||
in: query
|
||||
schema:
|
||||
type: integer
|
||||
default: 20
|
||||
responses:
|
||||
'200':
|
||||
description: 归集模板列表
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/PagedDataxTemplates'
|
||||
|
||||
components:
|
||||
schemas:
|
||||
TaskStatus:
|
||||
type: string
|
||||
enum:
|
||||
- DRAFT
|
||||
- READY
|
||||
- RUNNING
|
||||
- SUCCESS
|
||||
- FAILED
|
||||
- STOPPED
|
||||
description: |
|
||||
任务和执行状态枚举:
|
||||
- DRAFT: 草稿状态
|
||||
- READY: 就绪状态
|
||||
- RUNNING: 运行中
|
||||
- SUCCESS: 执行成功 (对应原来的COMPLETED/SUCCESS)
|
||||
- FAILED: 执行失败
|
||||
- STOPPED: 已停止
|
||||
|
||||
SyncMode:
|
||||
type: string
|
||||
enum: [ONCE, SCHEDULED]
|
||||
description: 同步方式:一次性(ONCE) 或 定时(SCHEDULED)
|
||||
|
||||
CollectionTaskSummary:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
description:
|
||||
type: string
|
||||
status:
|
||||
$ref: '#/components/schemas/TaskStatus'
|
||||
syncMode:
|
||||
$ref: '#/components/schemas/SyncMode'
|
||||
lastExecutionId:
|
||||
type: string
|
||||
description: 最后执行ID
|
||||
createdAt:
|
||||
type: string
|
||||
format: date-time
|
||||
updatedAt:
|
||||
type: string
|
||||
format: date-time
|
||||
description: 任务列表摘要信息(不包含详细配置与调度表达式)
|
||||
|
||||
CollectionTaskResponse:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
description:
|
||||
type: string
|
||||
config:
|
||||
type: object
|
||||
additionalProperties: true
|
||||
description: 归集配置,包含源端和目标端配置信息
|
||||
status:
|
||||
$ref: '#/components/schemas/TaskStatus'
|
||||
syncMode:
|
||||
$ref: '#/components/schemas/SyncMode'
|
||||
scheduleExpression:
|
||||
type: string
|
||||
description: Cron调度表达式 (仅当 syncMode = SCHEDULED 时有效)
|
||||
lastExecutionId:
|
||||
type: string
|
||||
description: 最后执行ID
|
||||
createdAt:
|
||||
type: string
|
||||
format: date-time
|
||||
updatedAt:
|
||||
type: string
|
||||
format: date-time
|
||||
|
||||
CreateCollectionTaskRequest:
|
||||
type: object
|
||||
required:
|
||||
- name
|
||||
- config
|
||||
- syncMode
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
description: 任务名称
|
||||
minLength: 1
|
||||
maxLength: 100
|
||||
description:
|
||||
type: string
|
||||
description: 任务描述
|
||||
maxLength: 500
|
||||
config:
|
||||
type: object
|
||||
description: 归集配置,包含源端和目标端配置信息
|
||||
additionalProperties: true
|
||||
syncMode:
|
||||
$ref: '#/components/schemas/SyncMode'
|
||||
scheduleExpression:
|
||||
type: string
|
||||
description: Cron调度表达式 (syncMode=SCHEDULED 时必填)
|
||||
|
||||
UpdateCollectionTaskRequest:
|
||||
type: object
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
description: 任务名称
|
||||
minLength: 1
|
||||
maxLength: 100
|
||||
description:
|
||||
type: string
|
||||
description: 任务描述
|
||||
maxLength: 500
|
||||
config:
|
||||
type: object
|
||||
description: 归集配置,包含源端和目标端配置信息
|
||||
additionalProperties: true
|
||||
syncMode:
|
||||
$ref: '#/components/schemas/SyncMode'
|
||||
scheduleExpression:
|
||||
type: string
|
||||
description: Cron调度表达式 (syncMode=SCHEDULED 时必填)
|
||||
|
||||
PagedCollectionTaskSummary:
|
||||
type: object
|
||||
properties:
|
||||
content:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/CollectionTaskSummary'
|
||||
totalElements:
|
||||
type: integer
|
||||
totalPages:
|
||||
type: integer
|
||||
number:
|
||||
type: integer
|
||||
size:
|
||||
type: integer
|
||||
|
||||
PagedCollectionTasks:
|
||||
type: object
|
||||
properties:
|
||||
content:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/CollectionTaskResponse'
|
||||
totalElements:
|
||||
type: integer
|
||||
totalPages:
|
||||
type: integer
|
||||
number:
|
||||
type: integer
|
||||
size:
|
||||
type: integer
|
||||
|
||||
TaskExecutionResponse:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
taskId:
|
||||
type: string
|
||||
taskName:
|
||||
type: string
|
||||
status:
|
||||
$ref: '#/components/schemas/TaskStatus'
|
||||
startedAt:
|
||||
type: string
|
||||
format: date-time
|
||||
|
||||
TaskExecutionDetail:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
taskId:
|
||||
type: string
|
||||
taskName:
|
||||
type: string
|
||||
status:
|
||||
$ref: '#/components/schemas/TaskStatus'
|
||||
progress:
|
||||
type: number
|
||||
format: double
|
||||
minimum: 0
|
||||
maximum: 100
|
||||
recordsTotal:
|
||||
type: integer
|
||||
recordsProcessed:
|
||||
type: integer
|
||||
recordsSuccess:
|
||||
type: integer
|
||||
recordsFailed:
|
||||
type: integer
|
||||
throughput:
|
||||
type: number
|
||||
format: double
|
||||
dataSizeBytes:
|
||||
type: integer
|
||||
startedAt:
|
||||
type: string
|
||||
format: date-time
|
||||
completedAt:
|
||||
type: string
|
||||
format: date-time
|
||||
durationSeconds:
|
||||
type: integer
|
||||
errorMessage:
|
||||
type: string
|
||||
|
||||
PagedTaskExecutions:
|
||||
type: object
|
||||
properties:
|
||||
content:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/TaskExecutionDetail'
|
||||
totalElements:
|
||||
type: integer
|
||||
totalPages:
|
||||
type: integer
|
||||
number:
|
||||
type: integer
|
||||
size:
|
||||
type: integer
|
||||
|
||||
DataxTemplateSummary:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
sourceType:
|
||||
type: string
|
||||
description: 源数据源类型
|
||||
targetType:
|
||||
type: string
|
||||
description: 目标数据源类型
|
||||
description:
|
||||
type: string
|
||||
version:
|
||||
type: string
|
||||
isSystem:
|
||||
type: boolean
|
||||
description: 是否为系统模板
|
||||
createdAt:
|
||||
type: string
|
||||
format: date-time
|
||||
|
||||
PagedDataxTemplates:
|
||||
type: object
|
||||
properties:
|
||||
content:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/DataxTemplateSummary'
|
||||
totalElements:
|
||||
type: integer
|
||||
totalPages:
|
||||
type: integer
|
||||
number:
|
||||
type: integer
|
||||
size:
|
||||
type: integer
|
||||
@@ -1,630 +0,0 @@
|
||||
openapi: 3.0.3
|
||||
info:
|
||||
title: Data Evaluation Service API
|
||||
description: 数据评估服务API - 质量、适配性、价值评估
|
||||
version: 1.0.0
|
||||
contact:
|
||||
name: Data Mate Platform Team
|
||||
|
||||
servers:
|
||||
- url: http://localhost:8086
|
||||
description: Development server
|
||||
|
||||
tags:
|
||||
- name: quality-evaluation
|
||||
description: 数据质量评估
|
||||
- name: compatibility-evaluation
|
||||
description: 适配性评估
|
||||
- name: value-evaluation
|
||||
description: 价值评估
|
||||
- name: evaluation-reports
|
||||
description: 评估报告
|
||||
|
||||
paths:
|
||||
/api/v1/evaluation/quality:
|
||||
post:
|
||||
tags:
|
||||
- quality-evaluation
|
||||
summary: 数据质量评估
|
||||
description: 对数据集进行质量评估,包括完整性、准确性、一致性等
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/QualityEvaluationRequest'
|
||||
responses:
|
||||
'200':
|
||||
description: 评估成功
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/QualityEvaluationResponse'
|
||||
|
||||
/api/v1/evaluation/quality/{evaluationId}:
|
||||
get:
|
||||
tags:
|
||||
- quality-evaluation
|
||||
summary: 获取质量评估结果
|
||||
parameters:
|
||||
- name: evaluationId
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
'200':
|
||||
description: 获取成功
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/QualityEvaluationDetailResponse'
|
||||
|
||||
/api/v1/evaluation/compatibility:
|
||||
post:
|
||||
tags:
|
||||
- compatibility-evaluation
|
||||
summary: 适配性评估
|
||||
description: 评估数据集与目标模型或任务的适配性
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/CompatibilityEvaluationRequest'
|
||||
responses:
|
||||
'200':
|
||||
description: 评估成功
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/CompatibilityEvaluationResponse'
|
||||
|
||||
/api/v1/evaluation/value:
|
||||
post:
|
||||
tags:
|
||||
- value-evaluation
|
||||
summary: 价值评估
|
||||
description: 评估数据集的商业价值和使用价值
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ValueEvaluationRequest'
|
||||
responses:
|
||||
'200':
|
||||
description: 评估成功
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ValueEvaluationResponse'
|
||||
|
||||
/api/v1/evaluation/reports:
|
||||
get:
|
||||
tags:
|
||||
- evaluation-reports
|
||||
summary: 获取评估报告列表
|
||||
parameters:
|
||||
- name: page
|
||||
in: query
|
||||
schema:
|
||||
type: integer
|
||||
default: 0
|
||||
- name: size
|
||||
in: query
|
||||
schema:
|
||||
type: integer
|
||||
default: 20
|
||||
- name: type
|
||||
in: query
|
||||
schema:
|
||||
$ref: '#/components/schemas/EvaluationType'
|
||||
- name: datasetId
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
'200':
|
||||
description: 获取成功
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/EvaluationReportPageResponse'
|
||||
|
||||
/api/v1/evaluation/reports/{reportId}:
|
||||
get:
|
||||
tags:
|
||||
- evaluation-reports
|
||||
summary: 获取评估报告详情
|
||||
parameters:
|
||||
- name: reportId
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
'200':
|
||||
description: 获取成功
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/EvaluationReportDetailResponse'
|
||||
|
||||
/api/v1/evaluation/reports/{reportId}/export:
|
||||
get:
|
||||
tags:
|
||||
- evaluation-reports
|
||||
summary: 导出评估报告
|
||||
parameters:
|
||||
- name: reportId
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
- name: format
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
enum: [PDF, EXCEL, JSON]
|
||||
default: PDF
|
||||
responses:
|
||||
'200':
|
||||
description: 导出成功
|
||||
content:
|
||||
application/octet-stream:
|
||||
schema:
|
||||
type: string
|
||||
format: binary
|
||||
|
||||
/api/v1/evaluation/batch:
|
||||
post:
|
||||
tags:
|
||||
- evaluation-reports
|
||||
summary: 批量评估
|
||||
description: 对多个数据集进行批量评估
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/BatchEvaluationRequest'
|
||||
responses:
|
||||
'202':
|
||||
description: 批量评估任务已提交
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/BatchEvaluationResponse'
|
||||
|
||||
components:
|
||||
schemas:
|
||||
QualityEvaluationRequest:
|
||||
type: object
|
||||
required:
|
||||
- datasetId
|
||||
- metrics
|
||||
properties:
|
||||
datasetId:
|
||||
type: string
|
||||
description: 数据集ID
|
||||
metrics:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/QualityMetric'
|
||||
description: 评估指标
|
||||
sampleSize:
|
||||
type: integer
|
||||
description: 采样大小
|
||||
parameters:
|
||||
type: object
|
||||
description: 评估参数
|
||||
|
||||
QualityEvaluationResponse:
|
||||
type: object
|
||||
properties:
|
||||
evaluationId:
|
||||
type: string
|
||||
status:
|
||||
$ref: '#/components/schemas/EvaluationStatus'
|
||||
overallScore:
|
||||
type: number
|
||||
format: double
|
||||
description: 总体质量分数
|
||||
metrics:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/QualityMetricResult'
|
||||
recommendations:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
createdAt:
|
||||
type: string
|
||||
format: date-time
|
||||
|
||||
QualityEvaluationDetailResponse:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/QualityEvaluationResponse'
|
||||
- type: object
|
||||
properties:
|
||||
detailedResults:
|
||||
$ref: '#/components/schemas/DetailedQualityResults'
|
||||
visualizations:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/VisualizationData'
|
||||
|
||||
CompatibilityEvaluationRequest:
|
||||
type: object
|
||||
required:
|
||||
- datasetId
|
||||
- targetType
|
||||
properties:
|
||||
datasetId:
|
||||
type: string
|
||||
targetType:
|
||||
$ref: '#/components/schemas/TargetType'
|
||||
targetConfig:
|
||||
type: object
|
||||
description: 目标配置(模型、任务等)
|
||||
evaluationCriteria:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/CompatibilityCriterion'
|
||||
|
||||
CompatibilityEvaluationResponse:
|
||||
type: object
|
||||
properties:
|
||||
evaluationId:
|
||||
type: string
|
||||
compatibilityScore:
|
||||
type: number
|
||||
format: double
|
||||
results:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/CompatibilityResult'
|
||||
suggestions:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
createdAt:
|
||||
type: string
|
||||
format: date-time
|
||||
|
||||
ValueEvaluationRequest:
|
||||
type: object
|
||||
required:
|
||||
- datasetId
|
||||
- valueCriteria
|
||||
properties:
|
||||
datasetId:
|
||||
type: string
|
||||
valueCriteria:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/ValueCriterion'
|
||||
marketContext:
|
||||
type: object
|
||||
description: 市场环境信息
|
||||
businessContext:
|
||||
type: object
|
||||
description: 业务环境信息
|
||||
|
||||
ValueEvaluationResponse:
|
||||
type: object
|
||||
properties:
|
||||
evaluationId:
|
||||
type: string
|
||||
valueScore:
|
||||
type: number
|
||||
format: double
|
||||
monetaryValue:
|
||||
type: number
|
||||
format: double
|
||||
description: 货币价值估算
|
||||
strategicValue:
|
||||
type: number
|
||||
format: double
|
||||
description: 战略价值评分
|
||||
results:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/ValueResult'
|
||||
insights:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
|
||||
EvaluationReportResponse:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
datasetId:
|
||||
type: string
|
||||
type:
|
||||
$ref: '#/components/schemas/EvaluationType'
|
||||
status:
|
||||
$ref: '#/components/schemas/EvaluationStatus'
|
||||
overallScore:
|
||||
type: number
|
||||
format: double
|
||||
summary:
|
||||
type: string
|
||||
createdAt:
|
||||
type: string
|
||||
format: date-time
|
||||
completedAt:
|
||||
type: string
|
||||
format: date-time
|
||||
|
||||
EvaluationReportPageResponse:
|
||||
type: object
|
||||
properties:
|
||||
content:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/EvaluationReportResponse'
|
||||
totalElements:
|
||||
type: integer
|
||||
format: int64
|
||||
totalPages:
|
||||
type: integer
|
||||
size:
|
||||
type: integer
|
||||
number:
|
||||
type: integer
|
||||
|
||||
EvaluationReportDetailResponse:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/EvaluationReportResponse'
|
||||
- type: object
|
||||
properties:
|
||||
qualityResults:
|
||||
$ref: '#/components/schemas/QualityEvaluationResponse'
|
||||
compatibilityResults:
|
||||
$ref: '#/components/schemas/CompatibilityEvaluationResponse'
|
||||
valueResults:
|
||||
$ref: '#/components/schemas/ValueEvaluationResponse'
|
||||
attachments:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/ReportAttachment'
|
||||
|
||||
BatchEvaluationRequest:
|
||||
type: object
|
||||
required:
|
||||
- datasetIds
|
||||
- evaluationTypes
|
||||
properties:
|
||||
datasetIds:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
evaluationTypes:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/EvaluationType'
|
||||
parameters:
|
||||
type: object
|
||||
|
||||
BatchEvaluationResponse:
|
||||
type: object
|
||||
properties:
|
||||
batchId:
|
||||
type: string
|
||||
status:
|
||||
type: string
|
||||
totalTasks:
|
||||
type: integer
|
||||
submittedAt:
|
||||
type: string
|
||||
format: date-time
|
||||
|
||||
QualityMetric:
|
||||
type: string
|
||||
enum:
|
||||
- COMPLETENESS
|
||||
- ACCURACY
|
||||
- CONSISTENCY
|
||||
- VALIDITY
|
||||
- UNIQUENESS
|
||||
- TIMELINESS
|
||||
|
||||
QualityMetricResult:
|
||||
type: object
|
||||
properties:
|
||||
metric:
|
||||
$ref: '#/components/schemas/QualityMetric'
|
||||
score:
|
||||
type: number
|
||||
format: double
|
||||
details:
|
||||
type: object
|
||||
issues:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/QualityIssue'
|
||||
|
||||
DetailedQualityResults:
|
||||
type: object
|
||||
properties:
|
||||
fieldAnalysis:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/FieldAnalysis'
|
||||
distributionAnalysis:
|
||||
$ref: '#/components/schemas/DistributionAnalysis'
|
||||
correlationAnalysis:
|
||||
$ref: '#/components/schemas/CorrelationAnalysis'
|
||||
|
||||
TargetType:
|
||||
type: string
|
||||
enum:
|
||||
- LANGUAGE_MODEL
|
||||
- CLASSIFICATION_MODEL
|
||||
- RECOMMENDATION_SYSTEM
|
||||
- CUSTOM_TASK
|
||||
|
||||
CompatibilityCriterion:
|
||||
type: string
|
||||
enum:
|
||||
- FORMAT_COMPATIBILITY
|
||||
- SCHEMA_COMPATIBILITY
|
||||
- SIZE_ADEQUACY
|
||||
- DISTRIBUTION_MATCH
|
||||
- FEATURE_COVERAGE
|
||||
|
||||
CompatibilityResult:
|
||||
type: object
|
||||
properties:
|
||||
criterion:
|
||||
$ref: '#/components/schemas/CompatibilityCriterion'
|
||||
score:
|
||||
type: number
|
||||
format: double
|
||||
status:
|
||||
type: string
|
||||
enum: [PASS, WARN, FAIL]
|
||||
details:
|
||||
type: string
|
||||
|
||||
ValueCriterion:
|
||||
type: string
|
||||
enum:
|
||||
- RARITY
|
||||
- DEMAND
|
||||
- QUALITY
|
||||
- COMPLETENESS
|
||||
- TIMELINESS
|
||||
- STRATEGIC_IMPORTANCE
|
||||
|
||||
ValueResult:
|
||||
type: object
|
||||
properties:
|
||||
criterion:
|
||||
$ref: '#/components/schemas/ValueCriterion'
|
||||
score:
|
||||
type: number
|
||||
format: double
|
||||
impact:
|
||||
type: string
|
||||
enum: [LOW, MEDIUM, HIGH]
|
||||
explanation:
|
||||
type: string
|
||||
|
||||
EvaluationType:
|
||||
type: string
|
||||
enum:
|
||||
- QUALITY
|
||||
- COMPATIBILITY
|
||||
- VALUE
|
||||
- COMPREHENSIVE
|
||||
|
||||
EvaluationStatus:
|
||||
type: string
|
||||
enum:
|
||||
- PENDING
|
||||
- RUNNING
|
||||
- COMPLETED
|
||||
- FAILED
|
||||
|
||||
QualityIssue:
|
||||
type: object
|
||||
properties:
|
||||
type:
|
||||
type: string
|
||||
severity:
|
||||
type: string
|
||||
enum: [LOW, MEDIUM, HIGH, CRITICAL]
|
||||
description:
|
||||
type: string
|
||||
affectedRecords:
|
||||
type: integer
|
||||
suggestions:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
|
||||
FieldAnalysis:
|
||||
type: object
|
||||
properties:
|
||||
fieldName:
|
||||
type: string
|
||||
dataType:
|
||||
type: string
|
||||
nullCount:
|
||||
type: integer
|
||||
uniqueCount:
|
||||
type: integer
|
||||
statistics:
|
||||
type: object
|
||||
|
||||
DistributionAnalysis:
|
||||
type: object
|
||||
properties:
|
||||
distributions:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
outliers:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
patterns:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
|
||||
CorrelationAnalysis:
|
||||
type: object
|
||||
properties:
|
||||
correlationMatrix:
|
||||
type: array
|
||||
items:
|
||||
type: array
|
||||
items:
|
||||
type: number
|
||||
significantCorrelations:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
|
||||
VisualizationData:
|
||||
type: object
|
||||
properties:
|
||||
type:
|
||||
type: string
|
||||
enum: [CHART, GRAPH, HISTOGRAM, HEATMAP]
|
||||
title:
|
||||
type: string
|
||||
data:
|
||||
type: object
|
||||
config:
|
||||
type: object
|
||||
|
||||
ReportAttachment:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
type:
|
||||
type: string
|
||||
size:
|
||||
type: integer
|
||||
format: int64
|
||||
downloadUrl:
|
||||
type: string
|
||||
|
||||
securitySchemes:
|
||||
BearerAuth:
|
||||
type: http
|
||||
scheme: bearer
|
||||
bearerFormat: JWT
|
||||
|
||||
security:
|
||||
- BearerAuth: []
|
||||
@@ -1,954 +0,0 @@
|
||||
openapi: 3.0.3
|
||||
info:
|
||||
title: Data Management Service API
|
||||
description: |
|
||||
数据管理服务API,提供数据集的创建、管理和文件操作功能。
|
||||
|
||||
主要功能:
|
||||
- 数据集的创建和管理
|
||||
- 多种数据集类型支持(图像、文本、音频、视频、多模态等)
|
||||
- 数据集文件管理
|
||||
- 数据集标签和元数据管理
|
||||
- 数据集统计信息
|
||||
version: 1.0.0
|
||||
|
||||
servers:
|
||||
- url: http://localhost:8092/api/v1/data-management
|
||||
description: Development server
|
||||
|
||||
tags:
|
||||
- name: Dataset
|
||||
description: 数据集管理
|
||||
- name: DatasetFile
|
||||
description: 数据集文件管理
|
||||
- name: DatasetType
|
||||
description: 数据集类型管理
|
||||
- name: Tag
|
||||
description: 标签管理
|
||||
|
||||
paths:
|
||||
/data-management/datasets:
|
||||
get:
|
||||
tags: [Dataset]
|
||||
operationId: getDatasets
|
||||
summary: 获取数据集列表
|
||||
description: 分页查询数据集列表,支持按类型、标签等条件筛选
|
||||
parameters:
|
||||
- name: page
|
||||
in: query
|
||||
schema:
|
||||
type: integer
|
||||
default: 0
|
||||
description: 页码,从1开始
|
||||
- name: size
|
||||
in: query
|
||||
schema:
|
||||
type: integer
|
||||
default: 20
|
||||
description: 每页大小
|
||||
- name: type
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
description: 数据集类型过滤
|
||||
- name: tags
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
description: 标签过滤,多个标签用逗号分隔
|
||||
- name: keyword
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
description: 关键词搜索(名称、描述)
|
||||
- name: status
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
enum: [DRAFT, ACTIVE, PROCESSING, ARCHIVED, PUBLISHED, DEPRECATED]
|
||||
description: 数据集状态过滤
|
||||
- name: parentDatasetId
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
description: 父数据集ID过滤(传空字符串表示根数据集)
|
||||
responses:
|
||||
'200':
|
||||
description: 成功
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/PagedDatasetResponse'
|
||||
'400':
|
||||
description: 请求参数错误
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
|
||||
post:
|
||||
tags: [Dataset]
|
||||
operationId: createDataset
|
||||
summary: 创建数据集
|
||||
description: 创建新的数据集
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/CreateDatasetRequest'
|
||||
responses:
|
||||
'201':
|
||||
description: 创建成功
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/DatasetResponse'
|
||||
'400':
|
||||
description: 请求参数错误
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
|
||||
/data-management/datasets/{datasetId}:
|
||||
get:
|
||||
tags: [Dataset]
|
||||
operationId: getDatasetById
|
||||
summary: 获取数据集详情
|
||||
description: 根据ID获取数据集详细信息
|
||||
parameters:
|
||||
- name: datasetId
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
description: 数据集ID
|
||||
responses:
|
||||
'200':
|
||||
description: 成功
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/DatasetResponse'
|
||||
'404':
|
||||
description: 数据集不存在
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
|
||||
put:
|
||||
tags: [Dataset]
|
||||
summary: 更新数据集
|
||||
operationId: updateDataset
|
||||
description: 更新数据集信息
|
||||
parameters:
|
||||
- name: datasetId
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
description: 数据集ID
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/UpdateDatasetRequest'
|
||||
responses:
|
||||
'200':
|
||||
description: 更新成功
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/DatasetResponse'
|
||||
'404':
|
||||
description: 数据集不存在
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
|
||||
delete:
|
||||
tags: [Dataset]
|
||||
operationId: deleteDataset
|
||||
summary: 删除数据集
|
||||
description: 删除指定的数据集
|
||||
parameters:
|
||||
- name: datasetId
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
description: 数据集ID
|
||||
responses:
|
||||
'204':
|
||||
description: 删除成功
|
||||
'404':
|
||||
description: 数据集不存在
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
|
||||
/data-management/datasets/{datasetId}/files:
|
||||
get:
|
||||
tags: [DatasetFile]
|
||||
summary: 获取数据集文件列表
|
||||
operationId: getDatasetFiles
|
||||
description: 分页获取数据集中的文件列表
|
||||
parameters:
|
||||
- name: datasetId
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
description: 数据集ID
|
||||
- name: page
|
||||
in: query
|
||||
schema:
|
||||
type: integer
|
||||
default: 0
|
||||
description: 页码,从0开始
|
||||
- name: size
|
||||
in: query
|
||||
schema:
|
||||
type: integer
|
||||
default: 20
|
||||
description: 每页大小
|
||||
- name: fileType
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
description: 文件类型过滤
|
||||
- name: status
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
enum: [UPLOADED, PROCESSING, COMPLETED, ERROR]
|
||||
description: 文件状态过滤
|
||||
- name: hasAnnotation
|
||||
in: query
|
||||
schema:
|
||||
type: boolean
|
||||
description: 是否仅返回存在标注结果的文件
|
||||
responses:
|
||||
'200':
|
||||
description: 成功
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/PagedDatasetFileResponse'
|
||||
|
||||
/data-management/datasets/{datasetId}/files/directories:
|
||||
post:
|
||||
tags: [ DatasetFile ]
|
||||
operationId: createDirectory
|
||||
summary: 在数据集下创建子目录
|
||||
description: 在指定数据集下的某个前缀路径中创建一个新的子目录
|
||||
parameters:
|
||||
- name: datasetId
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
description: 数据集ID
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/CreateDirectoryRequest'
|
||||
responses:
|
||||
'200':
|
||||
description: 创建成功
|
||||
|
||||
/data-management/datasets/{datasetId}/files/{fileId}:
|
||||
get:
|
||||
tags: [DatasetFile]
|
||||
summary: 获取文件详情
|
||||
description: 获取数据集中指定文件的详细信息
|
||||
operationId: getDatasetFileById
|
||||
parameters:
|
||||
- name: datasetId
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
description: 数据集ID
|
||||
- name: fileId
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
description: 文件ID
|
||||
responses:
|
||||
'200':
|
||||
description: 成功
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/DatasetFileResponse'
|
||||
|
||||
delete:
|
||||
tags: [DatasetFile]
|
||||
summary: 删除文件
|
||||
operationId: deleteDatasetFile
|
||||
description: 从数据集中删除指定文件
|
||||
parameters:
|
||||
- name: datasetId
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
description: 数据集ID
|
||||
- name: fileId
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
description: 文件ID
|
||||
responses:
|
||||
'204':
|
||||
description: 删除成功
|
||||
|
||||
/data-management/datasets/{datasetId}/files/{fileId}/download:
|
||||
get:
|
||||
tags: [DatasetFile]
|
||||
operationId: downloadDatasetFile
|
||||
summary: 下载文件
|
||||
description: 下载数据集中的指定文件
|
||||
parameters:
|
||||
- name: datasetId
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
description: 数据集ID
|
||||
- name: fileId
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
description: 文件ID
|
||||
responses:
|
||||
'200':
|
||||
description: 文件内容
|
||||
content:
|
||||
application/octet-stream:
|
||||
schema:
|
||||
type: string
|
||||
format: binary
|
||||
|
||||
/data-management/datasets/{datasetId}/files/{fileId}/preview:
|
||||
get:
|
||||
tags: [DatasetFile]
|
||||
operationId: previewDatasetFile
|
||||
summary: 预览文件
|
||||
description: 以 inline 方式预览数据集中的指定文件
|
||||
parameters:
|
||||
- name: datasetId
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
description: 数据集ID
|
||||
- name: fileId
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
description: 文件ID
|
||||
responses:
|
||||
'200':
|
||||
description: 文件内容
|
||||
content:
|
||||
application/octet-stream:
|
||||
schema:
|
||||
type: string
|
||||
format: binary
|
||||
|
||||
/data-management/datasets/{datasetId}/files/download:
|
||||
get:
|
||||
tags: [ DatasetFile ]
|
||||
operationId: downloadDatasetFileAsZip
|
||||
summary: 下载文件
|
||||
description: 下载数据集中全部文件
|
||||
parameters:
|
||||
- name: datasetId
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
description: 数据集ID
|
||||
responses:
|
||||
'200':
|
||||
description: 文件内容
|
||||
content:
|
||||
application/octet-stream:
|
||||
schema:
|
||||
type: string
|
||||
format: binary
|
||||
|
||||
/data-management/datasets/{datasetId}/files/upload/add:
|
||||
post:
|
||||
tags: [ DatasetFile ]
|
||||
operationId: addFilesToDataset
|
||||
summary: 添加文件到数据集(仅创建数据库记录)
|
||||
description: 将指定源文件路径列表添加到数据集,仅在数据库中创建记录,不执行物理文件系统操作。
|
||||
parameters:
|
||||
- name: datasetId
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
description: 数据集ID
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/AddFilesRequest'
|
||||
responses:
|
||||
'200':
|
||||
description: 添加成功,返回创建的文件记录列表
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/DatasetFileResponse'
|
||||
|
||||
/data-management/datasets/{datasetId}/files/upload/pre-upload:
|
||||
post:
|
||||
tags: [ DatasetFile ]
|
||||
operationId: preUpload
|
||||
summary: 切片上传预上传
|
||||
description: 预上传接口,返回后续分片上传所需的请求ID
|
||||
parameters:
|
||||
- name: datasetId
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
description: 数据集ID
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/UploadFilesPreRequest'
|
||||
responses:
|
||||
'200':
|
||||
description: 预上传成功,返回请求ID
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: string
|
||||
|
||||
/data-management/datasets/{datasetId}/files/upload/chunk:
|
||||
post:
|
||||
tags: [ DatasetFile ]
|
||||
operationId: chunkUpload
|
||||
summary: 切片上传
|
||||
description: 使用预上传返回的请求ID进行分片上传
|
||||
parameters:
|
||||
- name: datasetId
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
description: 数据集ID
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
multipart/form-data:
|
||||
schema:
|
||||
$ref: '#/components/schemas/UploadFileRequest'
|
||||
responses:
|
||||
'200':
|
||||
description: 上传成功
|
||||
|
||||
/data-management/datasets/upload/cancel-upload/{reqId}:
|
||||
put:
|
||||
tags: [ DatasetFile ]
|
||||
operationId: cancelUpload
|
||||
summary: 取消上传
|
||||
description: 取消预上传请求并清理临时分片
|
||||
parameters:
|
||||
- name: reqId
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
description: 预上传请求ID
|
||||
responses:
|
||||
'200':
|
||||
description: 取消成功
|
||||
|
||||
/data-management/dataset-types:
|
||||
get:
|
||||
operationId: getDatasetTypes
|
||||
tags: [DatasetType]
|
||||
summary: 获取数据集类型列表
|
||||
description: 获取所有支持的数据集类型
|
||||
responses:
|
||||
'200':
|
||||
description: 成功
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/DatasetTypeResponse'
|
||||
|
||||
/data-management/tags:
|
||||
get:
|
||||
tags: [Tag]
|
||||
operationId: getTags
|
||||
summary: 获取标签列表
|
||||
description: 获取所有可用的标签
|
||||
parameters:
|
||||
- name: keyword
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
description: 标签名称关键词搜索
|
||||
responses:
|
||||
'200':
|
||||
description: 成功
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/TagResponse'
|
||||
|
||||
post:
|
||||
tags: [Tag]
|
||||
operationId: createTag
|
||||
summary: 创建标签
|
||||
description: 创建新的标签
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/CreateTagRequest'
|
||||
responses:
|
||||
'201':
|
||||
description: 创建成功
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/TagResponse'
|
||||
|
||||
/data-management/datasets/{datasetId}/statistics:
|
||||
get:
|
||||
tags: [Dataset]
|
||||
operationId: getDatasetStatistics
|
||||
summary: 获取数据集统计信息
|
||||
description: 获取数据集的统计信息(文件数量、大小、完成度等)
|
||||
parameters:
|
||||
- name: datasetId
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
description: 数据集ID
|
||||
responses:
|
||||
'200':
|
||||
description: 成功
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/DatasetStatisticsResponse'
|
||||
|
||||
components:
|
||||
schemas:
|
||||
PagedDatasetResponse:
|
||||
type: object
|
||||
properties:
|
||||
content:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/DatasetResponse'
|
||||
page:
|
||||
type: integer
|
||||
description: 当前页码
|
||||
size:
|
||||
type: integer
|
||||
description: 每页大小
|
||||
totalElements:
|
||||
type: integer
|
||||
description: 总元素数
|
||||
totalPages:
|
||||
type: integer
|
||||
description: 总页数
|
||||
first:
|
||||
type: boolean
|
||||
description: 是否为第一页
|
||||
last:
|
||||
type: boolean
|
||||
description: 是否为最后一页
|
||||
|
||||
DatasetResponse:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
description: 数据集ID
|
||||
parentDatasetId:
|
||||
type: string
|
||||
description: 父数据集ID
|
||||
name:
|
||||
type: string
|
||||
description: 数据集名称
|
||||
description:
|
||||
type: string
|
||||
description: 数据集描述
|
||||
type:
|
||||
$ref: '#/components/schemas/DatasetTypeResponse'
|
||||
status:
|
||||
type: string
|
||||
enum: [ACTIVE, INACTIVE, PROCESSING]
|
||||
description: 数据集状态
|
||||
tags:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/TagResponse'
|
||||
description: 标签列表
|
||||
dataSource:
|
||||
type: string
|
||||
description: 数据源
|
||||
targetLocation:
|
||||
type: string
|
||||
description: 目标位置
|
||||
fileCount:
|
||||
type: integer
|
||||
description: 文件数量
|
||||
totalSize:
|
||||
type: integer
|
||||
format: int64
|
||||
description: 总大小(字节)
|
||||
completionRate:
|
||||
type: number
|
||||
format: float
|
||||
description: 完成率(0-100)
|
||||
createdAt:
|
||||
type: string
|
||||
format: date-time
|
||||
description: 创建时间
|
||||
updatedAt:
|
||||
type: string
|
||||
format: date-time
|
||||
description: 更新时间
|
||||
createdBy:
|
||||
type: string
|
||||
description: 创建者
|
||||
|
||||
CreateDatasetRequest:
|
||||
type: object
|
||||
required:
|
||||
- name
|
||||
- type
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
description: 数据集名称
|
||||
minLength: 1
|
||||
maxLength: 100
|
||||
description:
|
||||
type: string
|
||||
description: 数据集描述
|
||||
maxLength: 500
|
||||
type:
|
||||
type: string
|
||||
description: 数据集类型
|
||||
parentDatasetId:
|
||||
type: string
|
||||
description: 父数据集ID
|
||||
tags:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
description: 标签列表
|
||||
dataSource:
|
||||
type: string
|
||||
description: 数据源
|
||||
targetLocation:
|
||||
type: string
|
||||
description: 目标位置
|
||||
|
||||
UpdateDatasetRequest:
|
||||
type: object
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
description: 数据集名称
|
||||
maxLength: 100
|
||||
description:
|
||||
type: string
|
||||
description: 数据集描述
|
||||
maxLength: 500
|
||||
parentDatasetId:
|
||||
type: string
|
||||
description: 父数据集ID
|
||||
tags:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
description: 标签列表
|
||||
status:
|
||||
type: string
|
||||
enum: [DRAFT, ACTIVE, PROCESSING, ARCHIVED, PUBLISHED, DEPRECATED]
|
||||
description: 数据集状态
|
||||
|
||||
UploadFilesPreRequest:
|
||||
type: object
|
||||
description: 切片上传预上传请求
|
||||
properties:
|
||||
hasArchive:
|
||||
type: boolean
|
||||
description: 是否为压缩包上传
|
||||
default: false
|
||||
totalFileNum:
|
||||
type: integer
|
||||
format: int32
|
||||
minimum: 1
|
||||
description: 总文件数量
|
||||
totalSize:
|
||||
type: integer
|
||||
format: int64
|
||||
description: 总文件大小(字节)
|
||||
prefix:
|
||||
type: string
|
||||
description: 目标子目录前缀,例如 "images/",为空表示数据集根目录
|
||||
required: [ totalFileNum ]
|
||||
|
||||
CreateDirectoryRequest:
|
||||
type: object
|
||||
description: 创建数据集子目录请求
|
||||
properties:
|
||||
parentPrefix:
|
||||
type: string
|
||||
description: 父级前缀路径,例如 "images/",为空表示数据集根目录
|
||||
directoryName:
|
||||
type: string
|
||||
description: 新建目录名称
|
||||
required: [ directoryName ]
|
||||
|
||||
UploadFileRequest:
|
||||
type: object
|
||||
description: 分片上传请求
|
||||
properties:
|
||||
reqId:
|
||||
type: string
|
||||
description: 预上传返回的请求ID
|
||||
fileNo:
|
||||
type: integer
|
||||
format: int32
|
||||
description: 文件编号(批量中的第几个)
|
||||
fileName:
|
||||
type: string
|
||||
description: 文件名称
|
||||
totalChunkNum:
|
||||
type: integer
|
||||
format: int32
|
||||
description: 文件总分片数量
|
||||
chunkNo:
|
||||
type: integer
|
||||
format: int32
|
||||
description: 当前分片编号(从1开始)
|
||||
file:
|
||||
type: string
|
||||
format: binary
|
||||
description: 分片二进制内容
|
||||
checkSumHex:
|
||||
type: string
|
||||
description: 分片校验和(十六进制)
|
||||
required: [ reqId, fileNo, fileName, totalChunkNum, chunkNo, file ]
|
||||
|
||||
DatasetTypeResponse:
|
||||
type: object
|
||||
properties:
|
||||
code:
|
||||
type: string
|
||||
description: 类型编码
|
||||
name:
|
||||
type: string
|
||||
description: 类型名称
|
||||
description:
|
||||
type: string
|
||||
description: 类型描述
|
||||
supportedFormats:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
description: 支持的文件格式
|
||||
icon:
|
||||
type: string
|
||||
description: 图标
|
||||
|
||||
PagedDatasetFileResponse:
|
||||
type: object
|
||||
properties:
|
||||
content:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/DatasetFileResponse'
|
||||
page:
|
||||
type: integer
|
||||
description: 当前页码
|
||||
size:
|
||||
type: integer
|
||||
description: 每页大小
|
||||
totalElements:
|
||||
type: integer
|
||||
description: 总元素数
|
||||
totalPages:
|
||||
type: integer
|
||||
description: 总页数
|
||||
first:
|
||||
type: boolean
|
||||
description: 是否为第一页
|
||||
last:
|
||||
type: boolean
|
||||
description: 是否为最后一页
|
||||
|
||||
DatasetFileResponse:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
description: 文件ID
|
||||
fileName:
|
||||
type: string
|
||||
description: 文件名
|
||||
originalName:
|
||||
type: string
|
||||
description: 原始文件名
|
||||
fileType:
|
||||
type: string
|
||||
description: 文件类型
|
||||
fileSize:
|
||||
type: integer
|
||||
format: int64
|
||||
description: 文件大小(字节)
|
||||
status:
|
||||
type: string
|
||||
enum: [UPLOADED, PROCESSING, COMPLETED, ERROR]
|
||||
description: 文件状态
|
||||
description:
|
||||
type: string
|
||||
description: 文件描述
|
||||
filePath:
|
||||
type: string
|
||||
description: 文件路径
|
||||
uploadTime:
|
||||
type: string
|
||||
format: date-time
|
||||
description: 上传时间
|
||||
uploadedBy:
|
||||
type: string
|
||||
description: 上传者
|
||||
|
||||
TagResponse:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
description: 标签ID
|
||||
name:
|
||||
type: string
|
||||
description: 标签名称
|
||||
color:
|
||||
type: string
|
||||
description: 标签颜色
|
||||
description:
|
||||
type: string
|
||||
description: 标签描述
|
||||
usageCount:
|
||||
type: integer
|
||||
description: 使用次数
|
||||
|
||||
CreateTagRequest:
|
||||
type: object
|
||||
required:
|
||||
- name
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
description: 标签名称
|
||||
minLength: 1
|
||||
maxLength: 50
|
||||
color:
|
||||
type: string
|
||||
description: 标签颜色
|
||||
pattern: '^#[0-9A-Fa-f]{6}$'
|
||||
description:
|
||||
type: string
|
||||
description: 标签描述
|
||||
maxLength: 200
|
||||
|
||||
DatasetStatisticsResponse:
|
||||
type: object
|
||||
properties:
|
||||
totalFiles:
|
||||
type: integer
|
||||
description: 总文件数
|
||||
completedFiles:
|
||||
type: integer
|
||||
description: 已完成文件数
|
||||
totalSize:
|
||||
type: integer
|
||||
format: int64
|
||||
description: 总大小(字节)
|
||||
completionRate:
|
||||
type: number
|
||||
format: float
|
||||
description: 完成率(0-100)
|
||||
fileTypeDistribution:
|
||||
type: object
|
||||
additionalProperties:
|
||||
type: integer
|
||||
description: 文件类型分布
|
||||
statusDistribution:
|
||||
type: object
|
||||
additionalProperties:
|
||||
type: integer
|
||||
description: 状态分布
|
||||
|
||||
ErrorResponse:
|
||||
type: object
|
||||
properties:
|
||||
error:
|
||||
type: string
|
||||
description: 错误代码
|
||||
message:
|
||||
type: string
|
||||
description: 错误消息
|
||||
timestamp:
|
||||
type: string
|
||||
format: date-time
|
||||
description: 错误时间
|
||||
path:
|
||||
type: string
|
||||
description: 请求路径
|
||||
|
||||
AddFilesRequest:
|
||||
type: object
|
||||
description: 将源文件路径添加到数据集的请求
|
||||
properties:
|
||||
sourcePaths:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
description: 源文件路径列表(相对或绝对路径),每个元素表示一个要添加的文件或目录路径
|
||||
softAdd:
|
||||
type: boolean
|
||||
description: 如果为 true,则仅在数据库中创建记录(默认 false)
|
||||
default: false
|
||||
required:
|
||||
- sourcePaths
|
||||
@@ -1,620 +0,0 @@
|
||||
openapi: 3.0.3
|
||||
info:
|
||||
title: Data Synthesis Service API
|
||||
description: 数据合成服务API - 指令、COT蒸馏、多模态合成
|
||||
version: 1.0.0
|
||||
contact:
|
||||
name: Data Mate Platform Team
|
||||
|
||||
servers:
|
||||
- url: http://localhost:8085
|
||||
description: Development server
|
||||
|
||||
tags:
|
||||
- name: synthesis-templates
|
||||
description: 合成模板管理
|
||||
- name: synthesis-jobs
|
||||
description: 合成任务管理
|
||||
- name: instruction-tuning
|
||||
description: 指令调优
|
||||
- name: cot-distillation
|
||||
description: COT蒸馏
|
||||
|
||||
paths:
|
||||
/api/v1/synthesis/templates:
|
||||
get:
|
||||
tags:
|
||||
- synthesis-templates
|
||||
summary: 获取合成模板列表
|
||||
parameters:
|
||||
- name: page
|
||||
in: query
|
||||
schema:
|
||||
type: integer
|
||||
default: 0
|
||||
- name: size
|
||||
in: query
|
||||
schema:
|
||||
type: integer
|
||||
default: 20
|
||||
- name: type
|
||||
in: query
|
||||
schema:
|
||||
$ref: '#/components/schemas/SynthesisType'
|
||||
responses:
|
||||
'200':
|
||||
description: 获取成功
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/SynthesisTemplatePageResponse'
|
||||
|
||||
post:
|
||||
tags:
|
||||
- synthesis-templates
|
||||
summary: 创建合成模板
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/CreateSynthesisTemplateRequest'
|
||||
responses:
|
||||
'201':
|
||||
description: 创建成功
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/SynthesisTemplateResponse'
|
||||
|
||||
/api/v1/synthesis/templates/{templateId}:
|
||||
get:
|
||||
tags:
|
||||
- synthesis-templates
|
||||
summary: 获取合成模板详情
|
||||
parameters:
|
||||
- name: templateId
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
'200':
|
||||
description: 获取成功
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/SynthesisTemplateDetailResponse'
|
||||
|
||||
put:
|
||||
tags:
|
||||
- synthesis-templates
|
||||
summary: 更新合成模板
|
||||
parameters:
|
||||
- name: templateId
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/UpdateSynthesisTemplateRequest'
|
||||
responses:
|
||||
'200':
|
||||
description: 更新成功
|
||||
|
||||
/api/v1/synthesis/jobs:
|
||||
get:
|
||||
tags:
|
||||
- synthesis-jobs
|
||||
summary: 获取合成任务列表
|
||||
parameters:
|
||||
- name: page
|
||||
in: query
|
||||
schema:
|
||||
type: integer
|
||||
default: 0
|
||||
- name: size
|
||||
in: query
|
||||
schema:
|
||||
type: integer
|
||||
default: 20
|
||||
- name: status
|
||||
in: query
|
||||
schema:
|
||||
$ref: '#/components/schemas/JobStatus'
|
||||
responses:
|
||||
'200':
|
||||
description: 获取成功
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/SynthesisJobPageResponse'
|
||||
|
||||
post:
|
||||
tags:
|
||||
- synthesis-jobs
|
||||
summary: 创建合成任务
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/CreateSynthesisJobRequest'
|
||||
responses:
|
||||
'201':
|
||||
description: 任务创建成功
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/SynthesisJobResponse'
|
||||
|
||||
/api/v1/synthesis/jobs/{jobId}:
|
||||
get:
|
||||
tags:
|
||||
- synthesis-jobs
|
||||
summary: 获取合成任务详情
|
||||
parameters:
|
||||
- name: jobId
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
'200':
|
||||
description: 获取成功
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/SynthesisJobDetailResponse'
|
||||
|
||||
/api/v1/synthesis/jobs/{jobId}/execute:
|
||||
post:
|
||||
tags:
|
||||
- synthesis-jobs
|
||||
summary: 执行合成任务
|
||||
parameters:
|
||||
- name: jobId
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
'200':
|
||||
description: 任务开始执行
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/JobExecutionResponse'
|
||||
|
||||
/api/v1/synthesis/instruction-tuning:
|
||||
post:
|
||||
tags:
|
||||
- instruction-tuning
|
||||
summary: 指令调优数据合成
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/InstructionTuningRequest'
|
||||
responses:
|
||||
'200':
|
||||
description: 合成成功
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/InstructionTuningResponse'
|
||||
|
||||
/api/v1/synthesis/cot-distillation:
|
||||
post:
|
||||
tags:
|
||||
- cot-distillation
|
||||
summary: COT蒸馏数据合成
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/COTDistillationRequest'
|
||||
responses:
|
||||
'200':
|
||||
description: 蒸馏成功
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/COTDistillationResponse'
|
||||
|
||||
components:
|
||||
schemas:
|
||||
SynthesisTemplateResponse:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
description:
|
||||
type: string
|
||||
type:
|
||||
$ref: '#/components/schemas/SynthesisType'
|
||||
category:
|
||||
type: string
|
||||
modelConfig:
|
||||
$ref: '#/components/schemas/ModelConfig'
|
||||
enabled:
|
||||
type: boolean
|
||||
createdAt:
|
||||
type: string
|
||||
format: date-time
|
||||
|
||||
SynthesisTemplateDetailResponse:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/SynthesisTemplateResponse'
|
||||
- type: object
|
||||
properties:
|
||||
promptTemplate:
|
||||
type: string
|
||||
parameters:
|
||||
type: object
|
||||
examples:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/SynthesisExample'
|
||||
|
||||
SynthesisTemplatePageResponse:
|
||||
type: object
|
||||
properties:
|
||||
content:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/SynthesisTemplateResponse'
|
||||
totalElements:
|
||||
type: integer
|
||||
format: int64
|
||||
totalPages:
|
||||
type: integer
|
||||
size:
|
||||
type: integer
|
||||
number:
|
||||
type: integer
|
||||
|
||||
CreateSynthesisTemplateRequest:
|
||||
type: object
|
||||
required:
|
||||
- name
|
||||
- type
|
||||
- promptTemplate
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
description:
|
||||
type: string
|
||||
type:
|
||||
$ref: '#/components/schemas/SynthesisType'
|
||||
category:
|
||||
type: string
|
||||
promptTemplate:
|
||||
type: string
|
||||
modelConfig:
|
||||
$ref: '#/components/schemas/ModelConfig'
|
||||
parameters:
|
||||
type: object
|
||||
|
||||
UpdateSynthesisTemplateRequest:
|
||||
type: object
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
description:
|
||||
type: string
|
||||
promptTemplate:
|
||||
type: string
|
||||
enabled:
|
||||
type: boolean
|
||||
parameters:
|
||||
type: object
|
||||
|
||||
SynthesisJobResponse:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
description:
|
||||
type: string
|
||||
templateId:
|
||||
type: string
|
||||
status:
|
||||
$ref: '#/components/schemas/JobStatus'
|
||||
progress:
|
||||
type: number
|
||||
format: double
|
||||
targetCount:
|
||||
type: integer
|
||||
generatedCount:
|
||||
type: integer
|
||||
startTime:
|
||||
type: string
|
||||
format: date-time
|
||||
endTime:
|
||||
type: string
|
||||
format: date-time
|
||||
createdAt:
|
||||
type: string
|
||||
format: date-time
|
||||
|
||||
SynthesisJobDetailResponse:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/SynthesisJobResponse'
|
||||
- type: object
|
||||
properties:
|
||||
template:
|
||||
$ref: '#/components/schemas/SynthesisTemplateResponse'
|
||||
statistics:
|
||||
$ref: '#/components/schemas/SynthesisStatistics'
|
||||
samples:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/GeneratedSample'
|
||||
|
||||
SynthesisJobPageResponse:
|
||||
type: object
|
||||
properties:
|
||||
content:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/SynthesisJobResponse'
|
||||
totalElements:
|
||||
type: integer
|
||||
format: int64
|
||||
totalPages:
|
||||
type: integer
|
||||
size:
|
||||
type: integer
|
||||
number:
|
||||
type: integer
|
||||
|
||||
CreateSynthesisJobRequest:
|
||||
type: object
|
||||
required:
|
||||
- name
|
||||
- templateId
|
||||
- targetCount
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
description:
|
||||
type: string
|
||||
templateId:
|
||||
type: string
|
||||
targetCount:
|
||||
type: integer
|
||||
parameters:
|
||||
type: object
|
||||
seedData:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
|
||||
JobExecutionResponse:
|
||||
type: object
|
||||
properties:
|
||||
executionId:
|
||||
type: string
|
||||
status:
|
||||
type: string
|
||||
message:
|
||||
type: string
|
||||
|
||||
InstructionTuningRequest:
|
||||
type: object
|
||||
required:
|
||||
- baseInstructions
|
||||
- targetDomain
|
||||
- count
|
||||
properties:
|
||||
baseInstructions:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
targetDomain:
|
||||
type: string
|
||||
count:
|
||||
type: integer
|
||||
modelConfig:
|
||||
$ref: '#/components/schemas/ModelConfig'
|
||||
parameters:
|
||||
type: object
|
||||
|
||||
InstructionTuningResponse:
|
||||
type: object
|
||||
properties:
|
||||
jobId:
|
||||
type: string
|
||||
generatedInstructions:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/GeneratedInstruction'
|
||||
statistics:
|
||||
$ref: '#/components/schemas/GenerationStatistics'
|
||||
|
||||
COTDistillationRequest:
|
||||
type: object
|
||||
required:
|
||||
- sourceModel
|
||||
- targetFormat
|
||||
- examples
|
||||
properties:
|
||||
sourceModel:
|
||||
type: string
|
||||
targetFormat:
|
||||
type: string
|
||||
enum: [QA, INSTRUCTION, REASONING]
|
||||
examples:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/COTExample'
|
||||
parameters:
|
||||
type: object
|
||||
|
||||
COTDistillationResponse:
|
||||
type: object
|
||||
properties:
|
||||
jobId:
|
||||
type: string
|
||||
distilledData:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/DistilledCOTData'
|
||||
statistics:
|
||||
$ref: '#/components/schemas/DistillationStatistics'
|
||||
|
||||
SynthesisType:
|
||||
type: string
|
||||
enum:
|
||||
- INSTRUCTION_TUNING
|
||||
- COT_DISTILLATION
|
||||
- DIALOGUE_GENERATION
|
||||
- TEXT_AUGMENTATION
|
||||
- MULTIMODAL_SYNTHESIS
|
||||
- CUSTOM
|
||||
|
||||
JobStatus:
|
||||
type: string
|
||||
enum:
|
||||
- PENDING
|
||||
- RUNNING
|
||||
- COMPLETED
|
||||
- FAILED
|
||||
- CANCELLED
|
||||
|
||||
ModelConfig:
|
||||
type: object
|
||||
properties:
|
||||
modelName:
|
||||
type: string
|
||||
temperature:
|
||||
type: number
|
||||
format: double
|
||||
maxTokens:
|
||||
type: integer
|
||||
topP:
|
||||
type: number
|
||||
format: double
|
||||
frequencyPenalty:
|
||||
type: number
|
||||
format: double
|
||||
|
||||
SynthesisExample:
|
||||
type: object
|
||||
properties:
|
||||
input:
|
||||
type: string
|
||||
output:
|
||||
type: string
|
||||
explanation:
|
||||
type: string
|
||||
|
||||
SynthesisStatistics:
|
||||
type: object
|
||||
properties:
|
||||
totalGenerated:
|
||||
type: integer
|
||||
successfulGenerated:
|
||||
type: integer
|
||||
failedGenerated:
|
||||
type: integer
|
||||
averageLength:
|
||||
type: number
|
||||
format: double
|
||||
uniqueCount:
|
||||
type: integer
|
||||
|
||||
GeneratedSample:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
content:
|
||||
type: string
|
||||
score:
|
||||
type: number
|
||||
format: double
|
||||
metadata:
|
||||
type: object
|
||||
createdAt:
|
||||
type: string
|
||||
format: date-time
|
||||
|
||||
GeneratedInstruction:
|
||||
type: object
|
||||
properties:
|
||||
instruction:
|
||||
type: string
|
||||
input:
|
||||
type: string
|
||||
output:
|
||||
type: string
|
||||
quality:
|
||||
type: number
|
||||
format: double
|
||||
|
||||
GenerationStatistics:
|
||||
type: object
|
||||
properties:
|
||||
totalGenerated:
|
||||
type: integer
|
||||
averageQuality:
|
||||
type: number
|
||||
format: double
|
||||
diversityScore:
|
||||
type: number
|
||||
format: double
|
||||
|
||||
COTExample:
|
||||
type: object
|
||||
properties:
|
||||
question:
|
||||
type: string
|
||||
reasoning:
|
||||
type: string
|
||||
answer:
|
||||
type: string
|
||||
|
||||
DistilledCOTData:
|
||||
type: object
|
||||
properties:
|
||||
question:
|
||||
type: string
|
||||
reasoning:
|
||||
type: string
|
||||
answer:
|
||||
type: string
|
||||
confidence:
|
||||
type: number
|
||||
format: double
|
||||
|
||||
DistillationStatistics:
|
||||
type: object
|
||||
properties:
|
||||
totalProcessed:
|
||||
type: integer
|
||||
successfulDistilled:
|
||||
type: integer
|
||||
averageConfidence:
|
||||
type: number
|
||||
format: double
|
||||
|
||||
securitySchemes:
|
||||
BearerAuth:
|
||||
type: http
|
||||
scheme: bearer
|
||||
bearerFormat: JWT
|
||||
|
||||
security:
|
||||
- BearerAuth: []
|
||||
@@ -1,712 +0,0 @@
|
||||
openapi: 3.0.3
|
||||
info:
|
||||
title: Execution Engine Service API
|
||||
description: 执行引擎服务API - 与Ray/DataX/Python执行器对接
|
||||
version: 1.0.0
|
||||
contact:
|
||||
name: Data Mate Platform Team
|
||||
|
||||
servers:
|
||||
- url: http://localhost:8088
|
||||
description: Development server
|
||||
|
||||
tags:
|
||||
- name: jobs
|
||||
description: 作业管理
|
||||
- name: executors
|
||||
description: 执行器管理
|
||||
- name: resources
|
||||
description: 资源管理
|
||||
- name: monitoring
|
||||
description: 监控管理
|
||||
|
||||
paths:
|
||||
/api/v1/jobs:
|
||||
get:
|
||||
tags:
|
||||
- jobs
|
||||
summary: 获取作业列表
|
||||
parameters:
|
||||
- name: page
|
||||
in: query
|
||||
schema:
|
||||
type: integer
|
||||
default: 0
|
||||
- name: size
|
||||
in: query
|
||||
schema:
|
||||
type: integer
|
||||
default: 20
|
||||
- name: status
|
||||
in: query
|
||||
schema:
|
||||
$ref: '#/components/schemas/JobStatus'
|
||||
- name: executor
|
||||
in: query
|
||||
schema:
|
||||
$ref: '#/components/schemas/ExecutorType'
|
||||
responses:
|
||||
'200':
|
||||
description: 获取成功
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/JobPageResponse'
|
||||
|
||||
post:
|
||||
tags:
|
||||
- jobs
|
||||
summary: 提交作业
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/SubmitJobRequest'
|
||||
responses:
|
||||
'201':
|
||||
description: 作业提交成功
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/JobResponse'
|
||||
|
||||
/api/v1/jobs/{jobId}:
|
||||
get:
|
||||
tags:
|
||||
- jobs
|
||||
summary: 获取作业详情
|
||||
parameters:
|
||||
- name: jobId
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
'200':
|
||||
description: 获取成功
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/JobDetailResponse'
|
||||
|
||||
delete:
|
||||
tags:
|
||||
- jobs
|
||||
summary: 取消作业
|
||||
parameters:
|
||||
- name: jobId
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
'200':
|
||||
description: 取消成功
|
||||
|
||||
/api/v1/jobs/{jobId}/logs:
|
||||
get:
|
||||
tags:
|
||||
- jobs
|
||||
summary: 获取作业日志
|
||||
parameters:
|
||||
- name: jobId
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
- name: follow
|
||||
in: query
|
||||
description: 是否实时跟踪日志
|
||||
schema:
|
||||
type: boolean
|
||||
default: false
|
||||
responses:
|
||||
'200':
|
||||
description: 获取成功
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/JobLog'
|
||||
|
||||
/api/v1/jobs/{jobId}/retry:
|
||||
post:
|
||||
tags:
|
||||
- jobs
|
||||
summary: 重试作业
|
||||
parameters:
|
||||
- name: jobId
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
'200':
|
||||
description: 重试成功
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/JobResponse'
|
||||
|
||||
/api/v1/executors:
|
||||
get:
|
||||
tags:
|
||||
- executors
|
||||
summary: 获取执行器列表
|
||||
responses:
|
||||
'200':
|
||||
description: 获取成功
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/ExecutorResponse'
|
||||
|
||||
post:
|
||||
tags:
|
||||
- executors
|
||||
summary: 注册执行器
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RegisterExecutorRequest'
|
||||
responses:
|
||||
'201':
|
||||
description: 注册成功
|
||||
|
||||
/api/v1/executors/{executorId}:
|
||||
get:
|
||||
tags:
|
||||
- executors
|
||||
summary: 获取执行器详情
|
||||
parameters:
|
||||
- name: executorId
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
'200':
|
||||
description: 获取成功
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ExecutorDetailResponse'
|
||||
|
||||
put:
|
||||
tags:
|
||||
- executors
|
||||
summary: 更新执行器
|
||||
parameters:
|
||||
- name: executorId
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/UpdateExecutorRequest'
|
||||
responses:
|
||||
'200':
|
||||
description: 更新成功
|
||||
|
||||
/api/v1/resources/clusters:
|
||||
get:
|
||||
tags:
|
||||
- resources
|
||||
summary: 获取集群信息
|
||||
responses:
|
||||
'200':
|
||||
description: 获取成功
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/ClusterInfo'
|
||||
|
||||
/api/v1/resources/nodes:
|
||||
get:
|
||||
tags:
|
||||
- resources
|
||||
summary: 获取节点信息
|
||||
parameters:
|
||||
- name: clusterId
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
'200':
|
||||
description: 获取成功
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/NodeInfo'
|
||||
|
||||
/api/v1/monitoring/metrics:
|
||||
get:
|
||||
tags:
|
||||
- monitoring
|
||||
summary: 获取监控指标
|
||||
parameters:
|
||||
- name: metric
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
- name: start
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
format: date-time
|
||||
- name: end
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
format: date-time
|
||||
responses:
|
||||
'200':
|
||||
description: 获取成功
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/MetricsResponse'
|
||||
|
||||
components:
|
||||
schemas:
|
||||
JobResponse:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
status:
|
||||
$ref: '#/components/schemas/JobStatus'
|
||||
executorType:
|
||||
$ref: '#/components/schemas/ExecutorType'
|
||||
priority:
|
||||
type: integer
|
||||
progress:
|
||||
type: number
|
||||
format: double
|
||||
submittedAt:
|
||||
type: string
|
||||
format: date-time
|
||||
startedAt:
|
||||
type: string
|
||||
format: date-time
|
||||
completedAt:
|
||||
type: string
|
||||
format: date-time
|
||||
submittedBy:
|
||||
type: string
|
||||
|
||||
JobDetailResponse:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/JobResponse'
|
||||
- type: object
|
||||
properties:
|
||||
configuration:
|
||||
$ref: '#/components/schemas/JobConfiguration'
|
||||
resources:
|
||||
$ref: '#/components/schemas/ResourceRequirement'
|
||||
metrics:
|
||||
$ref: '#/components/schemas/JobMetrics'
|
||||
artifacts:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/JobArtifact'
|
||||
dependencies:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
|
||||
JobPageResponse:
|
||||
type: object
|
||||
properties:
|
||||
content:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/JobResponse'
|
||||
totalElements:
|
||||
type: integer
|
||||
format: int64
|
||||
totalPages:
|
||||
type: integer
|
||||
size:
|
||||
type: integer
|
||||
number:
|
||||
type: integer
|
||||
|
||||
SubmitJobRequest:
|
||||
type: object
|
||||
required:
|
||||
- name
|
||||
- executorType
|
||||
- configuration
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
description:
|
||||
type: string
|
||||
executorType:
|
||||
$ref: '#/components/schemas/ExecutorType'
|
||||
priority:
|
||||
type: integer
|
||||
minimum: 1
|
||||
maximum: 10
|
||||
default: 5
|
||||
configuration:
|
||||
$ref: '#/components/schemas/JobConfiguration'
|
||||
resources:
|
||||
$ref: '#/components/schemas/ResourceRequirement'
|
||||
dependencies:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
timeoutSeconds:
|
||||
type: integer
|
||||
|
||||
JobConfiguration:
|
||||
type: object
|
||||
properties:
|
||||
script:
|
||||
type: string
|
||||
description: 执行脚本或代码
|
||||
arguments:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
description: 执行参数
|
||||
environment:
|
||||
type: object
|
||||
description: 环境变量
|
||||
files:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/FileReference'
|
||||
packages:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
description: 依赖包列表
|
||||
|
||||
ResourceRequirement:
|
||||
type: object
|
||||
properties:
|
||||
cpuCores:
|
||||
type: number
|
||||
format: double
|
||||
memoryGB:
|
||||
type: number
|
||||
format: double
|
||||
gpuCount:
|
||||
type: integer
|
||||
diskGB:
|
||||
type: number
|
||||
format: double
|
||||
nodeSelector:
|
||||
type: object
|
||||
description: 节点选择器
|
||||
|
||||
ExecutorResponse:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
type:
|
||||
$ref: '#/components/schemas/ExecutorType'
|
||||
status:
|
||||
$ref: '#/components/schemas/ExecutorStatus'
|
||||
version:
|
||||
type: string
|
||||
capabilities:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
registeredAt:
|
||||
type: string
|
||||
format: date-time
|
||||
lastHeartbeat:
|
||||
type: string
|
||||
format: date-time
|
||||
|
||||
ExecutorDetailResponse:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/ExecutorResponse'
|
||||
- type: object
|
||||
properties:
|
||||
configuration:
|
||||
type: object
|
||||
resources:
|
||||
$ref: '#/components/schemas/ExecutorResources'
|
||||
currentJobs:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/JobResponse'
|
||||
statistics:
|
||||
$ref: '#/components/schemas/ExecutorStatistics'
|
||||
|
||||
RegisterExecutorRequest:
|
||||
type: object
|
||||
required:
|
||||
- name
|
||||
- type
|
||||
- endpoint
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
type:
|
||||
$ref: '#/components/schemas/ExecutorType'
|
||||
endpoint:
|
||||
type: string
|
||||
capabilities:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
configuration:
|
||||
type: object
|
||||
|
||||
UpdateExecutorRequest:
|
||||
type: object
|
||||
properties:
|
||||
status:
|
||||
$ref: '#/components/schemas/ExecutorStatus'
|
||||
configuration:
|
||||
type: object
|
||||
|
||||
ClusterInfo:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
type:
|
||||
type: string
|
||||
enum: [RAY, KUBERNETES, YARN, STANDALONE]
|
||||
status:
|
||||
type: string
|
||||
enum: [ACTIVE, INACTIVE, ERROR]
|
||||
nodeCount:
|
||||
type: integer
|
||||
totalCpuCores:
|
||||
type: integer
|
||||
totalMemoryGB:
|
||||
type: number
|
||||
format: double
|
||||
totalGpuCount:
|
||||
type: integer
|
||||
availableResources:
|
||||
$ref: '#/components/schemas/ResourceInfo'
|
||||
|
||||
NodeInfo:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
clusterId:
|
||||
type: string
|
||||
status:
|
||||
type: string
|
||||
enum: [ACTIVE, INACTIVE, BUSY, ERROR]
|
||||
resources:
|
||||
$ref: '#/components/schemas/ResourceInfo'
|
||||
usage:
|
||||
$ref: '#/components/schemas/ResourceUsage'
|
||||
lastUpdate:
|
||||
type: string
|
||||
format: date-time
|
||||
|
||||
MetricsResponse:
|
||||
type: object
|
||||
properties:
|
||||
metric:
|
||||
type: string
|
||||
dataPoints:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/MetricDataPoint'
|
||||
aggregation:
|
||||
type: object
|
||||
|
||||
JobLog:
|
||||
type: object
|
||||
properties:
|
||||
timestamp:
|
||||
type: string
|
||||
format: date-time
|
||||
level:
|
||||
type: string
|
||||
enum: [DEBUG, INFO, WARN, ERROR]
|
||||
source:
|
||||
type: string
|
||||
message:
|
||||
type: string
|
||||
|
||||
JobMetrics:
|
||||
type: object
|
||||
properties:
|
||||
cpuUsage:
|
||||
type: number
|
||||
format: double
|
||||
memoryUsage:
|
||||
type: number
|
||||
format: double
|
||||
diskUsage:
|
||||
type: number
|
||||
format: double
|
||||
networkIO:
|
||||
type: object
|
||||
duration:
|
||||
type: integer
|
||||
format: int64
|
||||
|
||||
JobArtifact:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
type:
|
||||
type: string
|
||||
enum: [LOG, OUTPUT, CHECKPOINT, MODEL]
|
||||
size:
|
||||
type: integer
|
||||
format: int64
|
||||
path:
|
||||
type: string
|
||||
createdAt:
|
||||
type: string
|
||||
format: date-time
|
||||
|
||||
FileReference:
|
||||
type: object
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
path:
|
||||
type: string
|
||||
type:
|
||||
type: string
|
||||
enum: [LOCAL, HDFS, S3, HTTP]
|
||||
|
||||
ExecutorResources:
|
||||
type: object
|
||||
properties:
|
||||
total:
|
||||
$ref: '#/components/schemas/ResourceInfo'
|
||||
available:
|
||||
$ref: '#/components/schemas/ResourceInfo'
|
||||
allocated:
|
||||
$ref: '#/components/schemas/ResourceInfo'
|
||||
|
||||
ExecutorStatistics:
|
||||
type: object
|
||||
properties:
|
||||
totalJobs:
|
||||
type: integer
|
||||
successfulJobs:
|
||||
type: integer
|
||||
failedJobs:
|
||||
type: integer
|
||||
averageExecutionTime:
|
||||
type: number
|
||||
format: double
|
||||
uptime:
|
||||
type: integer
|
||||
format: int64
|
||||
|
||||
ResourceInfo:
|
||||
type: object
|
||||
properties:
|
||||
cpuCores:
|
||||
type: number
|
||||
format: double
|
||||
memoryGB:
|
||||
type: number
|
||||
format: double
|
||||
gpuCount:
|
||||
type: integer
|
||||
diskGB:
|
||||
type: number
|
||||
format: double
|
||||
|
||||
ResourceUsage:
|
||||
type: object
|
||||
properties:
|
||||
cpuUsagePercent:
|
||||
type: number
|
||||
format: double
|
||||
memoryUsagePercent:
|
||||
type: number
|
||||
format: double
|
||||
diskUsagePercent:
|
||||
type: number
|
||||
format: double
|
||||
|
||||
MetricDataPoint:
|
||||
type: object
|
||||
properties:
|
||||
timestamp:
|
||||
type: string
|
||||
format: date-time
|
||||
value:
|
||||
type: number
|
||||
format: double
|
||||
tags:
|
||||
type: object
|
||||
|
||||
JobStatus:
|
||||
type: string
|
||||
enum:
|
||||
- SUBMITTED
|
||||
- PENDING
|
||||
- RUNNING
|
||||
- COMPLETED
|
||||
- FAILED
|
||||
- CANCELLED
|
||||
- TIMEOUT
|
||||
|
||||
ExecutorType:
|
||||
type: string
|
||||
enum:
|
||||
- RAY
|
||||
- DATAX
|
||||
- PYTHON
|
||||
- SPARK
|
||||
- FLINK
|
||||
- CUSTOM
|
||||
|
||||
ExecutorStatus:
|
||||
type: string
|
||||
enum:
|
||||
- ACTIVE
|
||||
- INACTIVE
|
||||
- BUSY
|
||||
- ERROR
|
||||
- MAINTENANCE
|
||||
|
||||
securitySchemes:
|
||||
BearerAuth:
|
||||
type: http
|
||||
scheme: bearer
|
||||
bearerFormat: JWT
|
||||
|
||||
security:
|
||||
- BearerAuth: []
|
||||
@@ -1,551 +0,0 @@
|
||||
openapi: "3.0.1"
|
||||
info:
|
||||
title: "API Documentation"
|
||||
version: "1.0"
|
||||
paths:
|
||||
/operators/list:
|
||||
post:
|
||||
summary: "operatorsListPost"
|
||||
description: "operatorsListPost"
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: "object"
|
||||
properties:
|
||||
page:
|
||||
type: "integer"
|
||||
description: ""
|
||||
size:
|
||||
type: "integer"
|
||||
description: ""
|
||||
categories:
|
||||
type: "array"
|
||||
description: ""
|
||||
items:
|
||||
type: "integer"
|
||||
default: "new ArrayList<>()"
|
||||
operatorName:
|
||||
type: "string"
|
||||
description: ""
|
||||
labelName:
|
||||
type: "string"
|
||||
description: ""
|
||||
isStar:
|
||||
type: "boolean"
|
||||
description: ""
|
||||
description: ""
|
||||
responses:
|
||||
"200":
|
||||
description: ""
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: "object"
|
||||
properties:
|
||||
page:
|
||||
type: "integer"
|
||||
description: ""
|
||||
format: "int64"
|
||||
size:
|
||||
type: "integer"
|
||||
description: ""
|
||||
format: "int64"
|
||||
totalElements:
|
||||
type: "integer"
|
||||
description: ""
|
||||
format: "int64"
|
||||
totalPages:
|
||||
type: "integer"
|
||||
description: ""
|
||||
format: "int64"
|
||||
content:
|
||||
type: "array"
|
||||
description: ""
|
||||
items:
|
||||
type: "object"
|
||||
properties:
|
||||
id:
|
||||
type: "string"
|
||||
description: ""
|
||||
name:
|
||||
type: "string"
|
||||
description: ""
|
||||
description:
|
||||
type: "string"
|
||||
description: ""
|
||||
version:
|
||||
type: "string"
|
||||
description: ""
|
||||
inputs:
|
||||
type: "string"
|
||||
description: ""
|
||||
outputs:
|
||||
type: "string"
|
||||
description: ""
|
||||
categories:
|
||||
type: "array"
|
||||
description: ""
|
||||
items:
|
||||
type: "string"
|
||||
runtime:
|
||||
type: "string"
|
||||
description: ""
|
||||
settings:
|
||||
type: "string"
|
||||
description: ""
|
||||
fileName:
|
||||
type: "string"
|
||||
description: ""
|
||||
isStar:
|
||||
type: "boolean"
|
||||
description: ""
|
||||
createdAt:
|
||||
type: "string"
|
||||
description: ""
|
||||
updatedAt:
|
||||
type: "string"
|
||||
description: ""
|
||||
description: "OperatorDto"
|
||||
/operators/{id}:
|
||||
get:
|
||||
summary: "operatorsIdGet"
|
||||
description: "operatorsIdGet"
|
||||
parameters:
|
||||
- name: "id"
|
||||
in: "path"
|
||||
description: ""
|
||||
required: true
|
||||
schema:
|
||||
type: "string"
|
||||
responses:
|
||||
"200":
|
||||
description: ""
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: "object"
|
||||
properties:
|
||||
id:
|
||||
type: "string"
|
||||
description: ""
|
||||
name:
|
||||
type: "string"
|
||||
description: ""
|
||||
description:
|
||||
type: "string"
|
||||
description: ""
|
||||
version:
|
||||
type: "string"
|
||||
description: ""
|
||||
inputs:
|
||||
type: "string"
|
||||
description: ""
|
||||
outputs:
|
||||
type: "string"
|
||||
description: ""
|
||||
categories:
|
||||
type: "array"
|
||||
description: ""
|
||||
items:
|
||||
type: "string"
|
||||
runtime:
|
||||
type: "string"
|
||||
description: ""
|
||||
settings:
|
||||
type: "string"
|
||||
description: ""
|
||||
fileName:
|
||||
type: "string"
|
||||
description: ""
|
||||
isStar:
|
||||
type: "boolean"
|
||||
description: ""
|
||||
createdAt:
|
||||
type: "string"
|
||||
description: ""
|
||||
updatedAt:
|
||||
type: "string"
|
||||
description: ""
|
||||
put:
|
||||
summary: "operatorsIdPut"
|
||||
description: "operatorsIdPut"
|
||||
parameters:
|
||||
- name: "id"
|
||||
in: "path"
|
||||
description: ""
|
||||
required: true
|
||||
schema:
|
||||
type: "string"
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: "object"
|
||||
properties:
|
||||
id:
|
||||
type: "string"
|
||||
description: ""
|
||||
name:
|
||||
type: "string"
|
||||
description: ""
|
||||
description:
|
||||
type: "string"
|
||||
description: ""
|
||||
version:
|
||||
type: "string"
|
||||
description: ""
|
||||
inputs:
|
||||
type: "string"
|
||||
description: ""
|
||||
outputs:
|
||||
type: "string"
|
||||
description: ""
|
||||
categories:
|
||||
type: "array"
|
||||
description: ""
|
||||
items:
|
||||
type: "string"
|
||||
runtime:
|
||||
type: "string"
|
||||
description: ""
|
||||
settings:
|
||||
type: "string"
|
||||
description: ""
|
||||
fileName:
|
||||
type: "string"
|
||||
description: ""
|
||||
isStar:
|
||||
type: "boolean"
|
||||
description: ""
|
||||
createdAt:
|
||||
type: "string"
|
||||
description: ""
|
||||
updatedAt:
|
||||
type: "string"
|
||||
description: ""
|
||||
description: ""
|
||||
responses:
|
||||
"200":
|
||||
description: ""
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: "object"
|
||||
properties:
|
||||
id:
|
||||
type: "string"
|
||||
description: ""
|
||||
name:
|
||||
type: "string"
|
||||
description: ""
|
||||
description:
|
||||
type: "string"
|
||||
description: ""
|
||||
version:
|
||||
type: "string"
|
||||
description: ""
|
||||
inputs:
|
||||
type: "string"
|
||||
description: ""
|
||||
outputs:
|
||||
type: "string"
|
||||
description: ""
|
||||
categories:
|
||||
type: "array"
|
||||
description: ""
|
||||
items:
|
||||
type: "string"
|
||||
runtime:
|
||||
type: "string"
|
||||
description: ""
|
||||
settings:
|
||||
type: "string"
|
||||
description: ""
|
||||
fileName:
|
||||
type: "string"
|
||||
description: ""
|
||||
isStar:
|
||||
type: "boolean"
|
||||
description: ""
|
||||
createdAt:
|
||||
type: "string"
|
||||
description: ""
|
||||
updatedAt:
|
||||
type: "string"
|
||||
description: ""
|
||||
/operators/create:
|
||||
post:
|
||||
summary: "operatorsCreatePost"
|
||||
description: "operatorsCreatePost"
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: "object"
|
||||
properties:
|
||||
id:
|
||||
type: "string"
|
||||
description: ""
|
||||
name:
|
||||
type: "string"
|
||||
description: ""
|
||||
description:
|
||||
type: "string"
|
||||
description: ""
|
||||
version:
|
||||
type: "string"
|
||||
description: ""
|
||||
inputs:
|
||||
type: "string"
|
||||
description: ""
|
||||
outputs:
|
||||
type: "string"
|
||||
description: ""
|
||||
categories:
|
||||
type: "array"
|
||||
description: ""
|
||||
items:
|
||||
type: "string"
|
||||
runtime:
|
||||
type: "string"
|
||||
description: ""
|
||||
settings:
|
||||
type: "string"
|
||||
description: ""
|
||||
fileName:
|
||||
type: "string"
|
||||
description: ""
|
||||
isStar:
|
||||
type: "boolean"
|
||||
description: ""
|
||||
createdAt:
|
||||
type: "string"
|
||||
description: ""
|
||||
updatedAt:
|
||||
type: "string"
|
||||
description: ""
|
||||
description: ""
|
||||
responses:
|
||||
"200":
|
||||
description: ""
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: "object"
|
||||
properties:
|
||||
id:
|
||||
type: "string"
|
||||
description: ""
|
||||
name:
|
||||
type: "string"
|
||||
description: ""
|
||||
description:
|
||||
type: "string"
|
||||
description: ""
|
||||
version:
|
||||
type: "string"
|
||||
description: ""
|
||||
inputs:
|
||||
type: "string"
|
||||
description: ""
|
||||
outputs:
|
||||
type: "string"
|
||||
description: ""
|
||||
categories:
|
||||
type: "array"
|
||||
description: ""
|
||||
items:
|
||||
type: "string"
|
||||
runtime:
|
||||
type: "string"
|
||||
description: ""
|
||||
settings:
|
||||
type: "string"
|
||||
description: ""
|
||||
fileName:
|
||||
type: "string"
|
||||
description: ""
|
||||
isStar:
|
||||
type: "boolean"
|
||||
description: ""
|
||||
createdAt:
|
||||
type: "string"
|
||||
description: ""
|
||||
updatedAt:
|
||||
type: "string"
|
||||
description: ""
|
||||
/operators/upload:
|
||||
post:
|
||||
summary: "operatorsUploadPost"
|
||||
description: "operatorsUploadPost"
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: "object"
|
||||
properties:
|
||||
reqId:
|
||||
type: "string"
|
||||
description: "预上传返回的id,用来确认同一个任务"
|
||||
fileNo:
|
||||
type: "integer"
|
||||
description: "文件编号,用于标识批量上传中的第几个文件"
|
||||
fileName:
|
||||
type: "string"
|
||||
description: "文件名称"
|
||||
totalChunkNum:
|
||||
type: "integer"
|
||||
description: "文件总分块数量"
|
||||
chunkNo:
|
||||
type: "integer"
|
||||
description: "当前分块编号,从1开始"
|
||||
file:
|
||||
type: "string"
|
||||
description: "上传的文件分块内容"
|
||||
checkSumHex:
|
||||
type: "string"
|
||||
description: "文件分块的校验和(十六进制字符串),用于验证文件完整性"
|
||||
description: ""
|
||||
responses:
|
||||
"200":
|
||||
description: ""
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: "object"
|
||||
properties:
|
||||
id:
|
||||
type: "string"
|
||||
description: ""
|
||||
name:
|
||||
type: "string"
|
||||
description: ""
|
||||
description:
|
||||
type: "string"
|
||||
description: ""
|
||||
version:
|
||||
type: "string"
|
||||
description: ""
|
||||
inputs:
|
||||
type: "string"
|
||||
description: ""
|
||||
outputs:
|
||||
type: "string"
|
||||
description: ""
|
||||
categories:
|
||||
type: "array"
|
||||
description: ""
|
||||
items:
|
||||
type: "string"
|
||||
runtime:
|
||||
type: "string"
|
||||
description: ""
|
||||
settings:
|
||||
type: "string"
|
||||
description: ""
|
||||
fileName:
|
||||
type: "string"
|
||||
description: ""
|
||||
isStar:
|
||||
type: "boolean"
|
||||
description: ""
|
||||
createdAt:
|
||||
type: "string"
|
||||
description: ""
|
||||
updatedAt:
|
||||
type: "string"
|
||||
description: ""
|
||||
/operators/upload/pre-upload:
|
||||
post:
|
||||
summary: "preUpload"
|
||||
description: "preUpload"
|
||||
responses:
|
||||
"200":
|
||||
description: ""
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: "string"
|
||||
/operators/upload/chunk:
|
||||
post:
|
||||
summary: "chunkUpload"
|
||||
description: "chunkUpload"
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: "object"
|
||||
properties:
|
||||
reqId:
|
||||
type: "string"
|
||||
description: "预上传返回的id,用来确认同一个任务"
|
||||
fileNo:
|
||||
type: "integer"
|
||||
description: "文件编号,用于标识批量上传中的第几个文件"
|
||||
fileName:
|
||||
type: "string"
|
||||
description: "文件名称"
|
||||
totalChunkNum:
|
||||
type: "integer"
|
||||
description: "文件总分块数量"
|
||||
chunkNo:
|
||||
type: "integer"
|
||||
description: "当前分块编号,从1开始"
|
||||
file:
|
||||
type: "string"
|
||||
description: "上传的文件分块内容"
|
||||
checkSumHex:
|
||||
type: "string"
|
||||
description: "文件分块的校验和(十六进制字符串),用于验证文件完整性"
|
||||
description: ""
|
||||
responses:
|
||||
"200":
|
||||
description: "No Content"
|
||||
components:
|
||||
schemas:
|
||||
OperatorDto:
|
||||
type: "object"
|
||||
properties:
|
||||
id:
|
||||
type: "string"
|
||||
description: ""
|
||||
name:
|
||||
type: "string"
|
||||
description: ""
|
||||
description:
|
||||
type: "string"
|
||||
description: ""
|
||||
version:
|
||||
type: "string"
|
||||
description: ""
|
||||
inputs:
|
||||
type: "string"
|
||||
description: ""
|
||||
outputs:
|
||||
type: "string"
|
||||
description: ""
|
||||
categories:
|
||||
type: "array"
|
||||
description: ""
|
||||
items:
|
||||
type: "string"
|
||||
runtime:
|
||||
type: "string"
|
||||
description: ""
|
||||
settings:
|
||||
type: "string"
|
||||
description: ""
|
||||
fileName:
|
||||
type: "string"
|
||||
description: ""
|
||||
isStar:
|
||||
type: "boolean"
|
||||
description: ""
|
||||
createdAt:
|
||||
type: "string"
|
||||
description: ""
|
||||
updatedAt:
|
||||
type: "string"
|
||||
description: ""
|
||||
description: "OperatorDto"
|
||||
@@ -1,639 +0,0 @@
|
||||
openapi: 3.0.3
|
||||
info:
|
||||
title: Pipeline Orchestration Service API
|
||||
description: 流程编排服务API - 可视化、模板、执行计划
|
||||
version: 1.0.0
|
||||
contact:
|
||||
name: Data Mate Platform Team
|
||||
|
||||
servers:
|
||||
- url: http://localhost:8087
|
||||
description: Development server
|
||||
|
||||
tags:
|
||||
- name: pipelines
|
||||
description: 流水线管理
|
||||
- name: pipeline-templates
|
||||
description: 流水线模板
|
||||
- name: executions
|
||||
description: 执行管理
|
||||
- name: workflows
|
||||
description: 工作流编排
|
||||
|
||||
paths:
|
||||
/api/v1/pipelines:
|
||||
get:
|
||||
tags:
|
||||
- pipelines
|
||||
summary: 获取流水线列表
|
||||
parameters:
|
||||
- name: page
|
||||
in: query
|
||||
schema:
|
||||
type: integer
|
||||
default: 0
|
||||
- name: size
|
||||
in: query
|
||||
schema:
|
||||
type: integer
|
||||
default: 20
|
||||
- name: status
|
||||
in: query
|
||||
schema:
|
||||
$ref: '#/components/schemas/PipelineStatus'
|
||||
responses:
|
||||
'200':
|
||||
description: 获取成功
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/PipelinePageResponse'
|
||||
|
||||
post:
|
||||
tags:
|
||||
- pipelines
|
||||
summary: 创建流水线
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/CreatePipelineRequest'
|
||||
responses:
|
||||
'201':
|
||||
description: 创建成功
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/PipelineResponse'
|
||||
|
||||
/api/v1/pipelines/{pipelineId}:
|
||||
get:
|
||||
tags:
|
||||
- pipelines
|
||||
summary: 获取流水线详情
|
||||
parameters:
|
||||
- name: pipelineId
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
'200':
|
||||
description: 获取成功
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/PipelineDetailResponse'
|
||||
|
||||
put:
|
||||
tags:
|
||||
- pipelines
|
||||
summary: 更新流水线
|
||||
parameters:
|
||||
- name: pipelineId
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/UpdatePipelineRequest'
|
||||
responses:
|
||||
'200':
|
||||
description: 更新成功
|
||||
|
||||
/api/v1/pipelines/{pipelineId}/execute:
|
||||
post:
|
||||
tags:
|
||||
- executions
|
||||
summary: 执行流水线
|
||||
parameters:
|
||||
- name: pipelineId
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
requestBody:
|
||||
required: false
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ExecutePipelineRequest'
|
||||
responses:
|
||||
'200':
|
||||
description: 执行开始
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/PipelineExecutionResponse'
|
||||
|
||||
/api/v1/executions:
|
||||
get:
|
||||
tags:
|
||||
- executions
|
||||
summary: 获取执行历史
|
||||
parameters:
|
||||
- name: pipelineId
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
- name: status
|
||||
in: query
|
||||
schema:
|
||||
$ref: '#/components/schemas/ExecutionStatus'
|
||||
- name: page
|
||||
in: query
|
||||
schema:
|
||||
type: integer
|
||||
default: 0
|
||||
responses:
|
||||
'200':
|
||||
description: 获取成功
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ExecutionPageResponse'
|
||||
|
||||
/api/v1/executions/{executionId}:
|
||||
get:
|
||||
tags:
|
||||
- executions
|
||||
summary: 获取执行详情
|
||||
parameters:
|
||||
- name: executionId
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
'200':
|
||||
description: 获取成功
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ExecutionDetailResponse'
|
||||
|
||||
/api/v1/executions/{executionId}/stop:
|
||||
post:
|
||||
tags:
|
||||
- executions
|
||||
summary: 停止执行
|
||||
parameters:
|
||||
- name: executionId
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
'200':
|
||||
description: 停止成功
|
||||
|
||||
/api/v1/templates:
|
||||
get:
|
||||
tags:
|
||||
- pipeline-templates
|
||||
summary: 获取模板列表
|
||||
parameters:
|
||||
- name: category
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
'200':
|
||||
description: 获取成功
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/PipelineTemplateResponse'
|
||||
|
||||
post:
|
||||
tags:
|
||||
- pipeline-templates
|
||||
summary: 创建模板
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/CreatePipelineTemplateRequest'
|
||||
responses:
|
||||
'201':
|
||||
description: 创建成功
|
||||
|
||||
components:
|
||||
schemas:
|
||||
PipelineResponse:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
description:
|
||||
type: string
|
||||
status:
|
||||
$ref: '#/components/schemas/PipelineStatus'
|
||||
version:
|
||||
type: string
|
||||
category:
|
||||
type: string
|
||||
tags:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
createdBy:
|
||||
type: string
|
||||
createdAt:
|
||||
type: string
|
||||
format: date-time
|
||||
lastModified:
|
||||
type: string
|
||||
format: date-time
|
||||
|
||||
PipelineDetailResponse:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/PipelineResponse'
|
||||
- type: object
|
||||
properties:
|
||||
definition:
|
||||
$ref: '#/components/schemas/PipelineDefinition'
|
||||
parameters:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/PipelineParameter'
|
||||
dependencies:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
statistics:
|
||||
$ref: '#/components/schemas/PipelineStatistics'
|
||||
|
||||
PipelinePageResponse:
|
||||
type: object
|
||||
properties:
|
||||
content:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/PipelineResponse'
|
||||
totalElements:
|
||||
type: integer
|
||||
format: int64
|
||||
totalPages:
|
||||
type: integer
|
||||
size:
|
||||
type: integer
|
||||
number:
|
||||
type: integer
|
||||
|
||||
CreatePipelineRequest:
|
||||
type: object
|
||||
required:
|
||||
- name
|
||||
- definition
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
description:
|
||||
type: string
|
||||
category:
|
||||
type: string
|
||||
definition:
|
||||
$ref: '#/components/schemas/PipelineDefinition'
|
||||
parameters:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/PipelineParameter'
|
||||
tags:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
|
||||
UpdatePipelineRequest:
|
||||
type: object
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
description:
|
||||
type: string
|
||||
definition:
|
||||
$ref: '#/components/schemas/PipelineDefinition'
|
||||
status:
|
||||
$ref: '#/components/schemas/PipelineStatus'
|
||||
|
||||
ExecutePipelineRequest:
|
||||
type: object
|
||||
properties:
|
||||
parameters:
|
||||
type: object
|
||||
description: 执行参数
|
||||
environment:
|
||||
type: string
|
||||
description: 执行环境
|
||||
priority:
|
||||
type: integer
|
||||
description: 优先级
|
||||
|
||||
PipelineExecutionResponse:
|
||||
type: object
|
||||
properties:
|
||||
executionId:
|
||||
type: string
|
||||
pipelineId:
|
||||
type: string
|
||||
status:
|
||||
$ref: '#/components/schemas/ExecutionStatus'
|
||||
startTime:
|
||||
type: string
|
||||
format: date-time
|
||||
message:
|
||||
type: string
|
||||
|
||||
ExecutionResponse:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
pipelineId:
|
||||
type: string
|
||||
pipelineName:
|
||||
type: string
|
||||
status:
|
||||
$ref: '#/components/schemas/ExecutionStatus'
|
||||
progress:
|
||||
type: number
|
||||
format: double
|
||||
startTime:
|
||||
type: string
|
||||
format: date-time
|
||||
endTime:
|
||||
type: string
|
||||
format: date-time
|
||||
duration:
|
||||
type: integer
|
||||
format: int64
|
||||
description: 执行时长(毫秒)
|
||||
|
||||
ExecutionDetailResponse:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/ExecutionResponse'
|
||||
- type: object
|
||||
properties:
|
||||
steps:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/ExecutionStep'
|
||||
logs:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/ExecutionLog'
|
||||
metrics:
|
||||
$ref: '#/components/schemas/ExecutionMetrics'
|
||||
artifacts:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/ExecutionArtifact'
|
||||
|
||||
ExecutionPageResponse:
|
||||
type: object
|
||||
properties:
|
||||
content:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/ExecutionResponse'
|
||||
totalElements:
|
||||
type: integer
|
||||
format: int64
|
||||
totalPages:
|
||||
type: integer
|
||||
size:
|
||||
type: integer
|
||||
number:
|
||||
type: integer
|
||||
|
||||
PipelineTemplateResponse:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
description:
|
||||
type: string
|
||||
category:
|
||||
type: string
|
||||
version:
|
||||
type: string
|
||||
definition:
|
||||
$ref: '#/components/schemas/PipelineDefinition'
|
||||
usageCount:
|
||||
type: integer
|
||||
createdAt:
|
||||
type: string
|
||||
format: date-time
|
||||
|
||||
CreatePipelineTemplateRequest:
|
||||
type: object
|
||||
required:
|
||||
- name
|
||||
- definition
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
description:
|
||||
type: string
|
||||
category:
|
||||
type: string
|
||||
definition:
|
||||
$ref: '#/components/schemas/PipelineDefinition'
|
||||
|
||||
PipelineDefinition:
|
||||
type: object
|
||||
properties:
|
||||
nodes:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/PipelineNode'
|
||||
edges:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/PipelineEdge'
|
||||
settings:
|
||||
type: object
|
||||
description: 流水线设置
|
||||
|
||||
PipelineNode:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
type:
|
||||
type: string
|
||||
enum: [OPERATOR, CONDITION, LOOP, PARALLEL]
|
||||
name:
|
||||
type: string
|
||||
operatorId:
|
||||
type: string
|
||||
configuration:
|
||||
type: object
|
||||
position:
|
||||
$ref: '#/components/schemas/NodePosition'
|
||||
|
||||
PipelineEdge:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
source:
|
||||
type: string
|
||||
target:
|
||||
type: string
|
||||
condition:
|
||||
type: string
|
||||
type:
|
||||
type: string
|
||||
enum: [SUCCESS, FAILURE, ALWAYS]
|
||||
|
||||
NodePosition:
|
||||
type: object
|
||||
properties:
|
||||
x:
|
||||
type: number
|
||||
y:
|
||||
type: number
|
||||
|
||||
PipelineParameter:
|
||||
type: object
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
type:
|
||||
type: string
|
||||
required:
|
||||
type: boolean
|
||||
defaultValue:
|
||||
type: string
|
||||
description:
|
||||
type: string
|
||||
|
||||
PipelineStatistics:
|
||||
type: object
|
||||
properties:
|
||||
totalExecutions:
|
||||
type: integer
|
||||
successfulExecutions:
|
||||
type: integer
|
||||
failedExecutions:
|
||||
type: integer
|
||||
averageDuration:
|
||||
type: number
|
||||
format: double
|
||||
lastExecutionTime:
|
||||
type: string
|
||||
format: date-time
|
||||
|
||||
ExecutionStep:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
nodeId:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
status:
|
||||
$ref: '#/components/schemas/ExecutionStatus'
|
||||
startTime:
|
||||
type: string
|
||||
format: date-time
|
||||
endTime:
|
||||
type: string
|
||||
format: date-time
|
||||
duration:
|
||||
type: integer
|
||||
format: int64
|
||||
message:
|
||||
type: string
|
||||
|
||||
ExecutionLog:
|
||||
type: object
|
||||
properties:
|
||||
timestamp:
|
||||
type: string
|
||||
format: date-time
|
||||
level:
|
||||
type: string
|
||||
enum: [DEBUG, INFO, WARN, ERROR]
|
||||
nodeId:
|
||||
type: string
|
||||
message:
|
||||
type: string
|
||||
|
||||
ExecutionMetrics:
|
||||
type: object
|
||||
properties:
|
||||
totalNodes:
|
||||
type: integer
|
||||
completedNodes:
|
||||
type: integer
|
||||
failedNodes:
|
||||
type: integer
|
||||
cpuUsage:
|
||||
type: number
|
||||
format: double
|
||||
memoryUsage:
|
||||
type: number
|
||||
format: double
|
||||
throughput:
|
||||
type: number
|
||||
format: double
|
||||
|
||||
ExecutionArtifact:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
type:
|
||||
type: string
|
||||
size:
|
||||
type: integer
|
||||
format: int64
|
||||
path:
|
||||
type: string
|
||||
createdAt:
|
||||
type: string
|
||||
format: date-time
|
||||
|
||||
PipelineStatus:
|
||||
type: string
|
||||
enum:
|
||||
- DRAFT
|
||||
- ACTIVE
|
||||
- INACTIVE
|
||||
- DEPRECATED
|
||||
|
||||
ExecutionStatus:
|
||||
type: string
|
||||
enum:
|
||||
- PENDING
|
||||
- RUNNING
|
||||
- SUCCESS
|
||||
- FAILED
|
||||
- CANCELLED
|
||||
- SKIPPED
|
||||
|
||||
securitySchemes:
|
||||
BearerAuth:
|
||||
type: http
|
||||
scheme: bearer
|
||||
bearerFormat: JWT
|
||||
|
||||
security:
|
||||
- BearerAuth: []
|
||||
-181
@@ -1,181 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
|
||||
http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>com.datamate</groupId>
|
||||
<artifactId>datamate</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<name>DataMate</name>
|
||||
<description>一站式数据工作平台,面向模型微调与RAG检索</description>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>21</maven.compiler.source>
|
||||
<maven.compiler.target>21</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
|
||||
<spring-boot.version>3.5.6</spring-boot.version>
|
||||
<spring-cloud.version>2025.0.0</spring-cloud.version>
|
||||
<spring-ai.version>1.0.0-RC1</spring-ai.version>
|
||||
<mysql.version>8.0.33</mysql.version>
|
||||
<postgresql.version>42.6.0</postgresql.version>
|
||||
<redis.version>3.2.0</redis.version>
|
||||
<elasticsearch.version>8.11.0</elasticsearch.version>
|
||||
<junit.version>5.10.0</junit.version>
|
||||
<springdoc.version>2.2.0</springdoc.version>
|
||||
<jackson-databind-nullable.version>0.2.6</jackson-databind-nullable.version>
|
||||
<jakarta-validation.version>3.0.2</jakarta-validation.version>
|
||||
<jakarta.persistence.version>3.1.0</jakarta.persistence.version>
|
||||
<maven-assembly-plugin.version>3.3.0</maven-assembly-plugin.version>
|
||||
<mybatis-plus.version>3.5.14</mybatis-plus.version>
|
||||
<mapstruct.version>1.6.3</mapstruct.version>
|
||||
<lombok.version>1.18.32</lombok.version>
|
||||
<lombok-mapstruct-binding.version>0.2.0</lombok-mapstruct-binding.version>
|
||||
<poi.version>5.4.0</poi.version>
|
||||
<log4j2.version>2.21.1</log4j2.version>
|
||||
<commons-compress.version>1.26.1</commons-compress.version>
|
||||
<fastjson-version>2.0.52</fastjson-version>
|
||||
</properties>
|
||||
|
||||
<modules>
|
||||
<!-- 核心服务 -->
|
||||
<module>services</module>
|
||||
|
||||
<!-- API Gateway微服务 -->
|
||||
<module>api-gateway</module>
|
||||
</modules>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-dependencies</artifactId>
|
||||
<version>${spring-boot.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.protobuf</groupId>
|
||||
<artifactId>protobuf-bom</artifactId>
|
||||
<version>3.25.5</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-dependencies</artifactId>
|
||||
<version>${spring-cloud.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- OpenAPI相关依赖版本管理 -->
|
||||
<dependency>
|
||||
<groupId>org.springdoc</groupId>
|
||||
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
|
||||
<version>${springdoc.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openapitools</groupId>
|
||||
<artifactId>jackson-databind-nullable</artifactId>
|
||||
<version>${jackson-databind-nullable.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>jakarta.validation</groupId>
|
||||
<artifactId>jakarta.validation-api</artifactId>
|
||||
<version>${jakarta-validation.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>jakarta.persistence</groupId>
|
||||
<artifactId>jakarta.persistence-api</artifactId>
|
||||
<version>${jakarta.persistence.version}</version>
|
||||
</dependency>
|
||||
<!-- MyBatis version alignment -->
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>mybatis-plus-bom</artifactId>
|
||||
<version>${mybatis-plus.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.mapstruct</groupId>
|
||||
<artifactId>mapstruct</artifactId>
|
||||
<version>${mapstruct.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>${lombok.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.mysql</groupId>
|
||||
<artifactId>mysql-connector-j</artifactId>
|
||||
<version>${mysql.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.poi</groupId>
|
||||
<artifactId>poi</artifactId>
|
||||
<version>${poi.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.milvus</groupId>
|
||||
<artifactId>milvus-sdk-java</artifactId>
|
||||
<version>2.6.6</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-compress</artifactId>
|
||||
<version>${commons-compress.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.alibaba.fastjson2</groupId>
|
||||
<artifactId>fastjson2</artifactId>
|
||||
<version>${fastjson-version}</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<version>${spring-boot.version}</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.11.0</version>
|
||||
<configuration>
|
||||
<source>${maven.compiler.source}</source>
|
||||
<target>${maven.compiler.target}</target>
|
||||
<parameters>true</parameters>
|
||||
<compilerArgs>
|
||||
<arg>-parameters</arg>
|
||||
</compilerArgs>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -1,101 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
|
||||
http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>com.datamate</groupId>
|
||||
<artifactId>services</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
<artifactId>data-annotation-service</artifactId>
|
||||
<name>Data Annotation Service</name>
|
||||
<description>数据标注服务</description>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.datamate</groupId>
|
||||
<artifactId>domain-common</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-websocket</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.mysql</groupId>
|
||||
<artifactId>mysql-connector-j</artifactId>
|
||||
<version>${mysql.version}</version>
|
||||
</dependency>
|
||||
<!-- OpenAPI Dependencies -->
|
||||
<dependency>
|
||||
<groupId>org.springdoc</groupId>
|
||||
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
|
||||
<version>2.0.4</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openapitools</groupId>
|
||||
<artifactId>jackson-databind-nullable</artifactId>
|
||||
<version>0.2.6</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>jakarta.validation</groupId>
|
||||
<artifactId>jakarta.validation-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
<!-- OpenAPI Generator Plugin -->
|
||||
<plugin>
|
||||
<groupId>org.openapitools</groupId>
|
||||
<artifactId>openapi-generator-maven-plugin</artifactId>
|
||||
<version>6.6.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>generate</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<inputSpec>${project.basedir}/../../openapi/specs/data-annotation.yaml</inputSpec>
|
||||
<generatorName>spring</generatorName>
|
||||
<output>${project.build.directory}/generated-sources/openapi</output>
|
||||
<apiPackage>com.datamate.annotation.interfaces.api</apiPackage>
|
||||
<modelPackage>com.datamate.annotation.interfaces.dto</modelPackage>
|
||||
<configOptions>
|
||||
<interfaceOnly>true</interfaceOnly>
|
||||
<useTags>true</useTags>
|
||||
<skipDefaultInterface>true</skipDefaultInterface>
|
||||
<hideGenerationTimestamp>true</hideGenerationTimestamp>
|
||||
<java8>true</java8>
|
||||
<dateLibrary>java8</dateLibrary>
|
||||
<useBeanValidation>true</useBeanValidation>
|
||||
<performBeanValidation>true</performBeanValidation>
|
||||
<useSpringBoot3>true</useSpringBoot3>
|
||||
<documentationProvider>springdoc</documentationProvider>
|
||||
</configOptions>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
|
||||
@@ -1,93 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
|
||||
http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>com.datamate</groupId>
|
||||
<artifactId>services</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
<artifactId>data-cleaning-service</artifactId>
|
||||
<name>Data Cleaning Service</name>
|
||||
<description>数据清洗服务</description>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.datamate</groupId>
|
||||
<artifactId>domain-common</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.datamate</groupId>
|
||||
<artifactId>data-management-service</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.datamate</groupId>
|
||||
<artifactId>operator-market-service</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springdoc</groupId>
|
||||
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openapitools</groupId>
|
||||
<artifactId>jackson-databind-nullable</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.mysql</groupId>
|
||||
<artifactId>mysql-connector-j</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-compress</artifactId>
|
||||
<version>1.26.1</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.mapstruct</groupId>
|
||||
<artifactId>mapstruct</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.mapstruct</groupId>
|
||||
<artifactId>mapstruct-processor</artifactId>
|
||||
<version>${mapstruct.version}</version>
|
||||
<scope>provided</scope> <!-- 编译时需要,运行时不需要 -->
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-commons</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
|
||||
|
||||
-18
@@ -1,18 +0,0 @@
|
||||
package com.datamate.cleaning;
|
||||
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.scheduling.annotation.EnableAsync;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
|
||||
/**
|
||||
* 数据归集服务配置类
|
||||
* 基于DataX的数据归集和同步服务,支持多种数据源的数据采集和归集
|
||||
*/
|
||||
@EnableAsync
|
||||
@EnableScheduling
|
||||
@ComponentScan(basePackages = {
|
||||
"com.datamate.cleaning"
|
||||
})
|
||||
public class DataCleaningServiceConfiguration {
|
||||
// Configuration class for JAR packaging - no main method needed
|
||||
}
|
||||
-374
@@ -1,374 +0,0 @@
|
||||
package com.datamate.cleaning.application;
|
||||
|
||||
|
||||
import com.datamate.cleaning.application.scheduler.CleaningTaskScheduler;
|
||||
import com.datamate.cleaning.common.enums.CleaningTaskStatusEnum;
|
||||
import com.datamate.cleaning.common.enums.ExecutorType;
|
||||
import com.datamate.cleaning.domain.model.TaskProcess;
|
||||
import com.datamate.cleaning.domain.repository.CleaningResultRepository;
|
||||
import com.datamate.cleaning.domain.repository.CleaningTaskRepository;
|
||||
import com.datamate.cleaning.domain.repository.OperatorInstanceRepository;
|
||||
import com.datamate.cleaning.infrastructure.validator.CleanTaskValidator;
|
||||
import com.datamate.cleaning.interfaces.dto.*;
|
||||
import com.datamate.common.infrastructure.exception.BusinessException;
|
||||
import com.datamate.common.infrastructure.exception.SystemErrorCode;
|
||||
import com.datamate.common.interfaces.PagedResponse;
|
||||
import com.datamate.common.interfaces.PagingQuery;
|
||||
import com.datamate.datamanagement.application.DatasetApplicationService;
|
||||
import com.datamate.datamanagement.application.DatasetFileApplicationService;
|
||||
import com.datamate.datamanagement.common.enums.DatasetType;
|
||||
import com.datamate.datamanagement.domain.model.dataset.Dataset;
|
||||
import com.datamate.datamanagement.domain.model.dataset.DatasetFile;
|
||||
import com.datamate.datamanagement.interfaces.dto.CreateDatasetRequest;
|
||||
import com.datamate.operator.domain.repository.OperatorRepository;
|
||||
import com.datamate.operator.infrastructure.exception.OperatorErrorCode;
|
||||
import com.datamate.operator.interfaces.dto.OperatorDto;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
|
||||
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.yaml.snakeyaml.DumperOptions;
|
||||
import org.yaml.snakeyaml.Yaml;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class CleaningTaskService {
|
||||
private final CleaningTaskRepository cleaningTaskRepo;
|
||||
|
||||
private final OperatorInstanceRepository operatorInstanceRepo;
|
||||
|
||||
private final OperatorRepository operatorRepo;
|
||||
|
||||
private final CleaningResultRepository cleaningResultRepo;
|
||||
|
||||
private final CleaningTaskScheduler taskScheduler;
|
||||
|
||||
private final DatasetApplicationService datasetService;
|
||||
|
||||
private final DatasetFileApplicationService datasetFileService;
|
||||
|
||||
private final CleanTaskValidator cleanTaskValidator;
|
||||
|
||||
private final String DATASET_PATH = "/dataset";
|
||||
|
||||
private final String FLOW_PATH = "/flow";
|
||||
|
||||
private static final Pattern STANDARD_LEVEL_PATTERN = Pattern.compile(
|
||||
"\\b(DEBUG|Debug|INFO|Info|WARN|Warn|WARNING|Warning|ERROR|Error|FATAL|Fatal)\\b"
|
||||
);
|
||||
|
||||
private static final Pattern EXCEPTION_SUFFIX_PATTERN = Pattern.compile(
|
||||
"\\b\\w+(Warning|Error|Exception)\\b"
|
||||
);
|
||||
|
||||
private final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
|
||||
|
||||
public List<CleaningTaskDto> getTasks(String status, String keywords, Integer page, Integer size) {
|
||||
List<CleaningTaskDto> tasks = cleaningTaskRepo.findTasks(status, keywords, page, size);
|
||||
tasks.forEach(this::setProcess);
|
||||
return tasks;
|
||||
}
|
||||
|
||||
private void setProcess(CleaningTaskDto task) {
|
||||
int[] count = cleaningResultRepo.countByInstanceId(task.getId());
|
||||
task.setProgress(CleaningProcess.of(task.getFileCount(), count[0], count[1]));
|
||||
}
|
||||
|
||||
public int countTasks(String status, String keywords) {
|
||||
return cleaningTaskRepo.findTasks(status, keywords, null, null).size();
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public CleaningTaskDto createTask(CreateCleaningTaskRequest request) {
|
||||
cleanTaskValidator.checkNameDuplication(request.getName());
|
||||
cleanTaskValidator.checkInputAndOutput(request.getInstance());
|
||||
|
||||
ExecutorType executorType = cleanTaskValidator.checkAndGetExecutorType(request.getInstance());
|
||||
|
||||
CreateDatasetRequest createDatasetRequest = new CreateDatasetRequest();
|
||||
createDatasetRequest.setName(request.getDestDatasetName());
|
||||
createDatasetRequest.setDatasetType(DatasetType.valueOf(request.getDestDatasetType()));
|
||||
createDatasetRequest.setStatus("ACTIVE");
|
||||
Dataset destDataset = datasetService.createDataset(createDatasetRequest);
|
||||
|
||||
Dataset srcDataset = datasetService.getDataset(request.getSrcDatasetId());
|
||||
|
||||
CleaningTaskDto task = new CleaningTaskDto();
|
||||
task.setName(request.getName());
|
||||
task.setDescription(request.getDescription());
|
||||
task.setStatus(CleaningTaskStatusEnum.PENDING);
|
||||
String taskId = UUID.randomUUID().toString();
|
||||
task.setId(taskId);
|
||||
task.setSrcDatasetId(request.getSrcDatasetId());
|
||||
task.setSrcDatasetName(request.getSrcDatasetName());
|
||||
task.setDestDatasetId(destDataset.getId());
|
||||
task.setDestDatasetName(destDataset.getName());
|
||||
task.setBeforeSize(srcDataset.getSizeBytes());
|
||||
task.setFileCount(srcDataset.getFileCount().intValue());
|
||||
cleaningTaskRepo.insertTask(task);
|
||||
|
||||
operatorInstanceRepo.insertInstance(taskId, request.getInstance());
|
||||
|
||||
prepareTask(task, request.getInstance(), executorType);
|
||||
scanDataset(taskId, request.getSrcDatasetId());
|
||||
taskScheduler.executeTask(taskId);
|
||||
return task;
|
||||
}
|
||||
|
||||
public CleaningTaskDto getTask(String taskId) {
|
||||
CleaningTaskDto task = cleaningTaskRepo.findTaskById(taskId);
|
||||
setProcess(task);
|
||||
task.setInstance(operatorInstanceRepo.findOperatorByInstanceId(taskId));
|
||||
return task;
|
||||
}
|
||||
|
||||
public List<CleaningResultDto> getTaskResults(String taskId) {
|
||||
return cleaningResultRepo.findByInstanceId(taskId);
|
||||
}
|
||||
|
||||
public List<CleaningTaskLog> getTaskLog(String taskId) {
|
||||
cleanTaskValidator.checkTaskId(taskId);
|
||||
String logPath = FLOW_PATH + "/" + taskId + "/output.log";
|
||||
try (Stream<String> lines = Files.lines(Paths.get(logPath))) {
|
||||
List<CleaningTaskLog> logs = new ArrayList<>();
|
||||
AtomicReference<String> lastLevel = new AtomicReference<>("INFO");
|
||||
lines.forEach(line -> {
|
||||
lastLevel.set(getLogLevel(line, lastLevel.get()));
|
||||
CleaningTaskLog log = new CleaningTaskLog();
|
||||
log.setLevel(lastLevel.get());
|
||||
log.setMessage(line);
|
||||
logs.add(log);
|
||||
});
|
||||
return logs;
|
||||
} catch (IOException e) {
|
||||
log.error("Fail to read log file {}", logPath, e);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
private String getLogLevel(String logLine, String defaultLevel) {
|
||||
if (logLine == null || logLine.trim().isEmpty()) {
|
||||
return defaultLevel;
|
||||
}
|
||||
|
||||
Matcher stdMatcher = STANDARD_LEVEL_PATTERN.matcher(logLine);
|
||||
if (stdMatcher.find()) {
|
||||
return stdMatcher.group(1).toUpperCase();
|
||||
}
|
||||
|
||||
Matcher exMatcher = EXCEPTION_SUFFIX_PATTERN.matcher(logLine);
|
||||
if (exMatcher.find()) {
|
||||
String match = exMatcher.group(1).toUpperCase();
|
||||
if ("WARNING".equals(match)) return "WARN";
|
||||
if ("ERROR".equals(match) || "EXCEPTION".equals(match)) return "ERROR";
|
||||
}
|
||||
return defaultLevel;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void deleteTask(String taskId) {
|
||||
cleanTaskValidator.checkTaskId(taskId);
|
||||
cleaningTaskRepo.deleteTaskById(taskId);
|
||||
operatorInstanceRepo.deleteByInstanceId(taskId);
|
||||
cleaningResultRepo.deleteByInstanceId(taskId);
|
||||
try {
|
||||
FileUtils.deleteDirectory(new File(FLOW_PATH + "/" + taskId));
|
||||
} catch (IOException e) {
|
||||
log.warn("Can't delete flow path with task id: {}.", taskId, e);
|
||||
}
|
||||
}
|
||||
|
||||
public void executeTask(String taskId) {
|
||||
List<CleaningResultDto> succeed = cleaningResultRepo.findByInstanceId(taskId, "COMPLETED");
|
||||
Set<String> succeedSet = succeed.stream().map(CleaningResultDto::getSrcFileId).collect(Collectors.toSet());
|
||||
CleaningTaskDto task = cleaningTaskRepo.findTaskById(taskId);
|
||||
scanDataset(taskId, task.getSrcDatasetId(), succeedSet);
|
||||
cleaningResultRepo.deleteByInstanceId(taskId, "FAILED");
|
||||
taskScheduler.executeTask(taskId);
|
||||
}
|
||||
|
||||
private void prepareTask(CleaningTaskDto task, List<OperatorInstanceDto> instances, ExecutorType executorType) {
|
||||
List<OperatorDto> allOperators = operatorRepo.findAllOperators();
|
||||
Map<String, OperatorDto> operatorDtoMap = allOperators.stream()
|
||||
.collect(Collectors.toMap(OperatorDto::getId, Function.identity()));
|
||||
|
||||
TaskProcess process = new TaskProcess();
|
||||
process.setInstanceId(task.getId());
|
||||
process.setDatasetId(task.getDestDatasetId());
|
||||
process.setExecutorType(executorType.getValue());
|
||||
process.setDatasetPath(FLOW_PATH + "/" + task.getId() + "/dataset.jsonl");
|
||||
process.setExportPath(DATASET_PATH + "/" + task.getDestDatasetId());
|
||||
process.setProcess(instances.stream()
|
||||
.map(instance -> {
|
||||
OperatorDto operatorDto = operatorDtoMap.get(instance.getId());
|
||||
Map<String, Object> stringObjectMap = getDefaultValue(operatorDto);
|
||||
stringObjectMap.putAll(instance.getOverrides());
|
||||
Map<String, Object> runtime = getRuntime(operatorDto);
|
||||
stringObjectMap.putAll(runtime);
|
||||
return Map.of(instance.getId(), stringObjectMap);
|
||||
})
|
||||
.toList());
|
||||
|
||||
ObjectMapper jsonMapper = new ObjectMapper(new YAMLFactory());
|
||||
jsonMapper.setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE);
|
||||
JsonNode jsonNode = jsonMapper.valueToTree(process);
|
||||
|
||||
DumperOptions options = new DumperOptions();
|
||||
options.setIndent(2);
|
||||
options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
|
||||
Yaml yaml = new Yaml(options);
|
||||
|
||||
File file = new File(FLOW_PATH + "/" + task.getId() + "/process.yaml");
|
||||
file.getParentFile().mkdirs();
|
||||
|
||||
try (FileWriter writer = new FileWriter(file)) {
|
||||
yaml.dump(jsonMapper.treeToValue(jsonNode, Map.class), writer);
|
||||
} catch (IOException e) {
|
||||
log.error("Failed to prepare process.yaml.", e);
|
||||
throw BusinessException.of(SystemErrorCode.FILE_SYSTEM_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, Object> getDefaultValue(OperatorDto operatorDto) {
|
||||
if (StringUtils.isBlank(operatorDto.getSettings())) {
|
||||
return new HashMap<>();
|
||||
}
|
||||
|
||||
Map<String, Object> defaultSettings = new HashMap<>();
|
||||
try {
|
||||
Map<String, Map<String, Object>> settings = OBJECT_MAPPER.readValue(operatorDto.getSettings(), Map.class);
|
||||
for (Map.Entry<String, Map<String, Object>> entry : settings.entrySet()) {
|
||||
String key = entry.getKey();
|
||||
Map<String, Object> setting = entry.getValue();
|
||||
String type = setting.get("type").toString();
|
||||
switch (type) {
|
||||
case "slider":
|
||||
case "switch":
|
||||
case "select":
|
||||
case "input":
|
||||
case "radio":
|
||||
case "checkbox":
|
||||
if (setting.containsKey("defaultVal")) {
|
||||
defaultSettings.put(key, setting.get("defaultVal"));
|
||||
}
|
||||
break;
|
||||
case "range":
|
||||
List<Object> rangeDefault = getRangeDefault(setting);
|
||||
if (CollectionUtils.isNotEmpty(rangeDefault)) {
|
||||
defaultSettings.put(key, rangeDefault);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
}
|
||||
}
|
||||
return defaultSettings;
|
||||
} catch (JsonProcessingException e) {
|
||||
throw BusinessException.of(OperatorErrorCode.SETTINGS_PARSE_FAILED, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private List<Object> getRangeDefault(Map<String, Object> setting) {
|
||||
List<Object> defaultValue = new ArrayList<>();
|
||||
Object properties = setting.get("properties");
|
||||
if (properties instanceof List<?> list) {
|
||||
for (Object o : list) {
|
||||
Map<String, Object> map = OBJECT_MAPPER.convertValue(o, Map.class);
|
||||
if (map.containsKey("defaultVal")) {
|
||||
defaultValue.add(map.get("defaultVal"));
|
||||
}
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
private Map<String, Object> getRuntime(OperatorDto operatorDto) {
|
||||
if (StringUtils.isBlank(operatorDto.getRuntime())) {
|
||||
return new HashMap<>();
|
||||
}
|
||||
try {
|
||||
return OBJECT_MAPPER.readValue(operatorDto.getRuntime(), Map.class);
|
||||
} catch (JsonProcessingException e) {
|
||||
throw BusinessException.of(OperatorErrorCode.SETTINGS_PARSE_FAILED, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void scanDataset(String taskId, String srcDatasetId) {
|
||||
doScan(taskId, srcDatasetId, file -> true);
|
||||
}
|
||||
|
||||
private void scanDataset(String taskId, String srcDatasetId, Set<String> succeedFiles) {
|
||||
doScan(taskId, srcDatasetId, file -> !succeedFiles.contains(file.getId()));
|
||||
}
|
||||
|
||||
private void doScan(String taskId, String srcDatasetId, Predicate<DatasetFile> filterCondition) {
|
||||
cleanTaskValidator.checkTaskId(taskId);
|
||||
String targetFilePath = FLOW_PATH + "/" + taskId + "/dataset.jsonl";
|
||||
File targetFile = new File(targetFilePath);
|
||||
if (targetFile.getParentFile() != null && !targetFile.getParentFile().exists()) {
|
||||
targetFile.getParentFile().mkdirs();
|
||||
}
|
||||
|
||||
int pageNumber = 0;
|
||||
int pageSize = 500;
|
||||
try (BufferedWriter writer = new BufferedWriter(new FileWriter(targetFile))) {
|
||||
PagedResponse<DatasetFile> datasetFiles;
|
||||
do {
|
||||
PagingQuery pageRequest = new PagingQuery(pageNumber, pageSize);
|
||||
datasetFiles = datasetFileService.getDatasetFiles(srcDatasetId, null, null, null, null, pageRequest);
|
||||
if (datasetFiles.getContent().isEmpty()) {
|
||||
break;
|
||||
}
|
||||
for (DatasetFile content : datasetFiles.getContent()) {
|
||||
if (!filterCondition.test(content)) {
|
||||
continue;
|
||||
}
|
||||
Map<String, Object> fileMap = Map.of(
|
||||
"fileName", content.getFileName(),
|
||||
"fileSize", content.getFileSize(),
|
||||
"filePath", content.getFilePath(),
|
||||
"fileType", content.getFileType(),
|
||||
"fileId", content.getId()
|
||||
);
|
||||
writer.write(OBJECT_MAPPER.writeValueAsString(fileMap));
|
||||
writer.newLine();
|
||||
}
|
||||
pageNumber++;
|
||||
} while (pageNumber < datasetFiles.getTotalPages());
|
||||
} catch (IOException e) {
|
||||
log.error("Failed to write dataset.jsonl for taskId: {}", taskId, e);
|
||||
throw BusinessException.of(SystemErrorCode.FILE_SYSTEM_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
public void stopTask(String taskId) {
|
||||
taskScheduler.stopTask(taskId);
|
||||
}
|
||||
|
||||
public List<OperatorInstanceDto> getInstanceByTemplateId(String templateId) {
|
||||
return operatorInstanceRepo.findInstanceByInstanceId(templateId);
|
||||
}
|
||||
}
|
||||
-117
@@ -1,117 +0,0 @@
|
||||
package com.datamate.cleaning.application;
|
||||
|
||||
|
||||
import com.datamate.cleaning.domain.repository.CleaningTemplateRepository;
|
||||
import com.datamate.cleaning.domain.repository.OperatorInstanceRepository;
|
||||
import com.datamate.cleaning.infrastructure.validator.CleanTaskValidator;
|
||||
import com.datamate.cleaning.interfaces.dto.*;
|
||||
import com.datamate.cleaning.domain.model.entity.TemplateWithInstance;
|
||||
import com.datamate.common.infrastructure.exception.BusinessException;
|
||||
import com.datamate.operator.application.OperatorService;
|
||||
import com.datamate.operator.domain.repository.OperatorViewRepository;
|
||||
import com.datamate.operator.infrastructure.exception.OperatorErrorCode;
|
||||
import com.datamate.operator.interfaces.dto.OperatorDto;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class CleaningTemplateService {
|
||||
private final CleaningTemplateRepository cleaningTemplateRepo;
|
||||
|
||||
private final OperatorInstanceRepository operatorInstanceRepo;
|
||||
|
||||
private final OperatorViewRepository operatorViewRepo;
|
||||
|
||||
private final CleanTaskValidator cleanTaskValidator;
|
||||
|
||||
private final OperatorService operatorService;
|
||||
|
||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
public List<CleaningTemplateDto> getTemplates(String keywords) {
|
||||
List<OperatorDto> allOperators =
|
||||
operatorViewRepo.findOperatorsByCriteria(null, null, null, null, null);
|
||||
Map<String, OperatorDto> operatorsMap = allOperators.stream()
|
||||
.collect(Collectors.toMap(OperatorDto::getId, Function.identity()));
|
||||
List<TemplateWithInstance> allTemplates = cleaningTemplateRepo.findAllTemplates(keywords);
|
||||
Map<String, List<TemplateWithInstance>> templatesMap = allTemplates.stream()
|
||||
.collect(Collectors.groupingBy(TemplateWithInstance::getId));
|
||||
return templatesMap.entrySet().stream().map(twi -> {
|
||||
List<TemplateWithInstance> value = twi.getValue();
|
||||
CleaningTemplateDto template = new CleaningTemplateDto();
|
||||
template.setId(twi.getKey());
|
||||
template.setName(value.getFirst().getName());
|
||||
template.setDescription(value.getFirst().getDescription());
|
||||
template.setInstance(value.stream().filter(v -> StringUtils.isNotBlank(v.getOperatorId()))
|
||||
.sorted(Comparator.comparingInt(TemplateWithInstance::getOpIndex))
|
||||
.map(v -> {
|
||||
OperatorDto operator = operatorsMap.get(v.getOperatorId());
|
||||
if (StringUtils.isNotBlank(v.getSettingsOverride())) {
|
||||
try {
|
||||
operator.setOverrides(objectMapper.readValue(v.getSettingsOverride(), Map.class));
|
||||
} catch (JsonProcessingException e) {
|
||||
throw BusinessException.of(OperatorErrorCode.SETTINGS_PARSE_FAILED, e.getMessage());
|
||||
}
|
||||
operatorService.overrideSettings(operator);
|
||||
}
|
||||
return operator;
|
||||
}).toList());
|
||||
template.setCreatedAt(value.getFirst().getCreatedAt());
|
||||
template.setUpdatedAt(value.getFirst().getUpdatedAt());
|
||||
return template;
|
||||
}).toList();
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public CleaningTemplateDto createTemplate(CreateCleaningTemplateRequest request) {
|
||||
cleanTaskValidator.checkInputAndOutput(request.getInstance());
|
||||
cleanTaskValidator.checkAndGetExecutorType(request.getInstance());
|
||||
CleaningTemplateDto template = new CleaningTemplateDto();
|
||||
String templateId = UUID.randomUUID().toString();
|
||||
template.setId(templateId);
|
||||
template.setName(request.getName());
|
||||
template.setDescription(request.getDescription());
|
||||
cleaningTemplateRepo.insertTemplate(template);
|
||||
|
||||
operatorInstanceRepo.insertInstance(templateId, request.getInstance());
|
||||
return template;
|
||||
}
|
||||
|
||||
public CleaningTemplateDto getTemplate(String templateId) {
|
||||
CleaningTemplateDto template = cleaningTemplateRepo.findTemplateById(templateId);
|
||||
template.setInstance(operatorInstanceRepo.findOperatorByInstanceId(templateId));
|
||||
return template;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public CleaningTemplateDto updateTemplate(String templateId, UpdateCleaningTemplateRequest request) {
|
||||
CleaningTemplateDto template = cleaningTemplateRepo.findTemplateById(templateId);
|
||||
if (template == null) {
|
||||
return null;
|
||||
}
|
||||
template.setName(request.getName());
|
||||
template.setDescription(request.getDescription());
|
||||
cleaningTemplateRepo.updateTemplate(template);
|
||||
operatorInstanceRepo.deleteByInstanceId(templateId);
|
||||
operatorInstanceRepo.insertInstance(templateId, request.getInstance());
|
||||
return template;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void deleteTemplate(String templateId) {
|
||||
cleaningTemplateRepo.deleteTemplate(templateId);
|
||||
operatorInstanceRepo.deleteByInstanceId(templateId);
|
||||
}
|
||||
}
|
||||
-43
@@ -1,43 +0,0 @@
|
||||
package com.datamate.cleaning.application.scheduler;
|
||||
|
||||
import com.datamate.cleaning.infrastructure.httpclient.RuntimeClient;
|
||||
import com.datamate.cleaning.common.enums.CleaningTaskStatusEnum;
|
||||
import com.datamate.cleaning.domain.repository.CleaningTaskRepository;
|
||||
import com.datamate.cleaning.interfaces.dto.CleaningTaskDto;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class CleaningTaskScheduler {
|
||||
private final CleaningTaskRepository cleaningTaskRepo;
|
||||
|
||||
private final RuntimeClient runtimeClient;
|
||||
|
||||
private final ExecutorService taskExecutor = Executors.newFixedThreadPool(5);
|
||||
|
||||
public void executeTask(String taskId) {
|
||||
taskExecutor.submit(() -> submitTask(taskId));
|
||||
}
|
||||
|
||||
private void submitTask(String taskId) {
|
||||
CleaningTaskDto task = new CleaningTaskDto();
|
||||
task.setId(taskId);
|
||||
task.setStatus(CleaningTaskStatusEnum.RUNNING);
|
||||
task.setStartedAt(LocalDateTime.now());
|
||||
cleaningTaskRepo.updateTask(task);
|
||||
runtimeClient.submitTask(taskId);
|
||||
}
|
||||
|
||||
public void stopTask(String taskId) {
|
||||
runtimeClient.stopTask(taskId);
|
||||
CleaningTaskDto task = new CleaningTaskDto();
|
||||
task.setId(taskId);
|
||||
task.setStatus(CleaningTaskStatusEnum.STOPPED);
|
||||
cleaningTaskRepo.updateTask(task);
|
||||
}
|
||||
}
|
||||
-39
@@ -1,39 +0,0 @@
|
||||
package com.datamate.cleaning.common.enums;
|
||||
|
||||
import com.datamate.common.infrastructure.exception.BusinessException;
|
||||
import com.datamate.common.infrastructure.exception.SystemErrorCode;
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonValue;
|
||||
|
||||
public enum CleaningTaskStatusEnum {
|
||||
PENDING("PENDING"),
|
||||
|
||||
RUNNING("RUNNING"),
|
||||
|
||||
COMPLETED("COMPLETED"),
|
||||
|
||||
STOPPED("STOPPED"),
|
||||
|
||||
FAILED("FAILED");
|
||||
|
||||
private final String value;
|
||||
|
||||
CleaningTaskStatusEnum(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@JsonValue
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@JsonCreator
|
||||
public static CleaningTaskStatusEnum fromValue(String value) {
|
||||
for (CleaningTaskStatusEnum b : CleaningTaskStatusEnum.values()) {
|
||||
if (b.value.equals(value)) {
|
||||
return b;
|
||||
}
|
||||
}
|
||||
throw BusinessException.of(SystemErrorCode.INVALID_PARAMETER);
|
||||
}
|
||||
}
|
||||
-25
@@ -1,25 +0,0 @@
|
||||
package com.datamate.cleaning.common.enums;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
public enum ExecutorType {
|
||||
DATAMATE("datamate"),
|
||||
DATA_JUICER_RAY("ray"),
|
||||
DATA_JUICER_DEFAULT("default");
|
||||
|
||||
private final String value;
|
||||
|
||||
ExecutorType(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public static ExecutorType fromValue(String value) {
|
||||
for (ExecutorType type : ExecutorType.values()) {
|
||||
if (type.value.equals(value)) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("Unexpected value '" + value + "'");
|
||||
}
|
||||
}
|
||||
-23
@@ -1,23 +0,0 @@
|
||||
package com.datamate.cleaning.common.exception;
|
||||
|
||||
import com.datamate.common.infrastructure.exception.ErrorCode;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum CleanErrorCode implements ErrorCode {
|
||||
/**
|
||||
* 清洗任务名称重复
|
||||
*/
|
||||
DUPLICATE_TASK_NAME("clean.0001", "清洗任务名称重复"),
|
||||
|
||||
OPERATOR_LIST_EMPTY("clean.0002", "任务列表为空"),
|
||||
|
||||
IN_AND_OUT_NOT_MATCH("clean.0003", "算子输入输出不匹配"),
|
||||
|
||||
EXECUTOR_NOT_MATCH("clean.0004", "算子执行器不匹配");
|
||||
|
||||
private final String code;
|
||||
private final String message;
|
||||
}
|
||||
-24
@@ -1,24 +0,0 @@
|
||||
package com.datamate.cleaning.domain.model;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class TaskProcess {
|
||||
private String instanceId;
|
||||
|
||||
private String datasetId;
|
||||
|
||||
private String datasetPath;
|
||||
|
||||
private String exportPath;
|
||||
|
||||
private String executorType;
|
||||
|
||||
private List<Map<String, Map<String, Object>>> process;
|
||||
}
|
||||
-32
@@ -1,32 +0,0 @@
|
||||
package com.datamate.cleaning.domain.model.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@TableName(value = "t_clean_result", autoResultMap = true)
|
||||
public class CleaningResult {
|
||||
private String instanceId;
|
||||
|
||||
private String srcFileId;
|
||||
|
||||
private String destFileId;
|
||||
|
||||
private String srcName;
|
||||
|
||||
private String destName;
|
||||
|
||||
private String srcType;
|
||||
|
||||
private String destType;
|
||||
|
||||
private long srcSize;
|
||||
|
||||
private long destSize;
|
||||
|
||||
private String status;
|
||||
|
||||
private String result;
|
||||
}
|
||||
-46
@@ -1,46 +0,0 @@
|
||||
package com.datamate.cleaning.domain.model.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.datamate.cleaning.common.enums.CleaningTaskStatusEnum;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* CleaningTask
|
||||
*/
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@TableName(value = "t_clean_task", autoResultMap = true)
|
||||
public class CleaningTask {
|
||||
private String id;
|
||||
|
||||
private String name;
|
||||
|
||||
private String description;
|
||||
|
||||
private CleaningTaskStatusEnum status;
|
||||
|
||||
private String srcDatasetId;
|
||||
|
||||
private String srcDatasetName;
|
||||
|
||||
private String destDatasetId;
|
||||
|
||||
private String destDatasetName;
|
||||
|
||||
private Long beforeSize;
|
||||
|
||||
private Long afterSize;
|
||||
|
||||
private Integer fileCount;
|
||||
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
private LocalDateTime startedAt;
|
||||
|
||||
private LocalDateTime finishedAt;
|
||||
}
|
||||
|
||||
-26
@@ -1,26 +0,0 @@
|
||||
package com.datamate.cleaning.domain.model.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@TableName(value = "t_clean_template", autoResultMap = true)
|
||||
public class CleaningTemplate {
|
||||
@TableId
|
||||
private String id;
|
||||
|
||||
private String name;
|
||||
|
||||
private String description;
|
||||
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
private LocalDateTime updatedAt;
|
||||
|
||||
private String createdBy;
|
||||
}
|
||||
-18
@@ -1,18 +0,0 @@
|
||||
package com.datamate.cleaning.domain.model.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@TableName(value = "t_operator_instance", autoResultMap = true)
|
||||
public class OperatorInstance {
|
||||
private String instanceId;
|
||||
|
||||
private String operatorId;
|
||||
|
||||
private int opIndex;
|
||||
|
||||
private String settingsOverride;
|
||||
}
|
||||
-27
@@ -1,27 +0,0 @@
|
||||
package com.datamate.cleaning.domain.model.entity;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class TemplateWithInstance {
|
||||
private String id;
|
||||
|
||||
private String name;
|
||||
|
||||
private String description;
|
||||
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
private LocalDateTime updatedAt;
|
||||
|
||||
private String operatorId;
|
||||
|
||||
private Integer opIndex;
|
||||
|
||||
private String settingsOverride;
|
||||
}
|
||||
-20
@@ -1,20 +0,0 @@
|
||||
package com.datamate.cleaning.domain.repository;
|
||||
|
||||
|
||||
import com.baomidou.mybatisplus.extension.repository.IRepository;
|
||||
import com.datamate.cleaning.domain.model.entity.CleaningResult;
|
||||
import com.datamate.cleaning.interfaces.dto.CleaningResultDto;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface CleaningResultRepository extends IRepository<CleaningResult> {
|
||||
void deleteByInstanceId(String instanceId);
|
||||
|
||||
void deleteByInstanceId(String instanceId, String status);
|
||||
|
||||
int[] countByInstanceId(String instanceId);
|
||||
|
||||
List<CleaningResultDto> findByInstanceId(String instanceId);
|
||||
|
||||
List<CleaningResultDto> findByInstanceId(String instanceId, String status);
|
||||
}
|
||||
-21
@@ -1,21 +0,0 @@
|
||||
package com.datamate.cleaning.domain.repository;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.repository.IRepository;
|
||||
import com.datamate.cleaning.domain.model.entity.CleaningTask;
|
||||
import com.datamate.cleaning.interfaces.dto.CleaningTaskDto;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface CleaningTaskRepository extends IRepository<CleaningTask> {
|
||||
List<CleaningTaskDto> findTasks(String status, String keywords, Integer page, Integer size);
|
||||
|
||||
CleaningTaskDto findTaskById(String taskId);
|
||||
|
||||
void insertTask(CleaningTaskDto task);
|
||||
|
||||
void updateTask(CleaningTaskDto task);
|
||||
|
||||
void deleteTaskById(String taskId);
|
||||
|
||||
boolean isNameExist(String name);
|
||||
}
|
||||
-20
@@ -1,20 +0,0 @@
|
||||
package com.datamate.cleaning.domain.repository;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.repository.IRepository;
|
||||
import com.datamate.cleaning.domain.model.entity.TemplateWithInstance;
|
||||
import com.datamate.cleaning.domain.model.entity.CleaningTemplate;
|
||||
import com.datamate.cleaning.interfaces.dto.CleaningTemplateDto;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface CleaningTemplateRepository extends IRepository<CleaningTemplate> {
|
||||
List<TemplateWithInstance> findAllTemplates(String keywords);
|
||||
|
||||
CleaningTemplateDto findTemplateById(String templateId);
|
||||
|
||||
void insertTemplate(CleaningTemplateDto template);
|
||||
|
||||
void updateTemplate(CleaningTemplateDto template);
|
||||
|
||||
void deleteTemplate(String templateId);
|
||||
}
|
||||
-18
@@ -1,18 +0,0 @@
|
||||
package com.datamate.cleaning.domain.repository;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.repository.IRepository;
|
||||
import com.datamate.cleaning.interfaces.dto.OperatorInstanceDto;
|
||||
import com.datamate.cleaning.domain.model.entity.OperatorInstance;
|
||||
import com.datamate.operator.interfaces.dto.OperatorDto;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface OperatorInstanceRepository extends IRepository<OperatorInstance> {
|
||||
void insertInstance(String instanceId, List<OperatorInstanceDto> instances);
|
||||
|
||||
void deleteByInstanceId(String instanceId);
|
||||
|
||||
List<OperatorDto> findOperatorByInstanceId(String instanceId);
|
||||
|
||||
List<OperatorInstanceDto> findInstanceByInstanceId(String instanceId);
|
||||
}
|
||||
-15
@@ -1,15 +0,0 @@
|
||||
package com.datamate.cleaning.infrastructure.converter;
|
||||
|
||||
import com.datamate.cleaning.domain.model.entity.CleaningResult;
|
||||
import com.datamate.cleaning.interfaces.dto.CleaningResultDto;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface CleaningResultConverter {
|
||||
CleaningResultConverter INSTANCE = Mappers.getMapper(CleaningResultConverter.class);
|
||||
|
||||
List<CleaningResultDto> convertEntityToDto(List<CleaningResult> cleaningResult);
|
||||
}
|
||||
-19
@@ -1,19 +0,0 @@
|
||||
package com.datamate.cleaning.infrastructure.converter;
|
||||
|
||||
import com.datamate.cleaning.domain.model.entity.CleaningTask;
|
||||
import com.datamate.cleaning.interfaces.dto.CleaningTaskDto;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface CleaningTaskConverter {
|
||||
CleaningTaskConverter INSTANCE = Mappers.getMapper(CleaningTaskConverter.class);
|
||||
|
||||
CleaningTaskDto fromEntityToDto(CleaningTask source);
|
||||
|
||||
List<CleaningTaskDto> fromEntityToDto(List<CleaningTask> source);
|
||||
|
||||
CleaningTask fromDtoToEntity(CleaningTaskDto source);
|
||||
}
|
||||
-15
@@ -1,15 +0,0 @@
|
||||
package com.datamate.cleaning.infrastructure.converter;
|
||||
|
||||
import com.datamate.cleaning.domain.model.entity.CleaningTemplate;
|
||||
import com.datamate.cleaning.interfaces.dto.CleaningTemplateDto;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
|
||||
@Mapper
|
||||
public interface CleaningTemplateConverter {
|
||||
CleaningTemplateConverter INSTANCE = Mappers.getMapper(CleaningTemplateConverter.class);
|
||||
|
||||
CleaningTemplate fromDtoToEntity(CleaningTemplateDto dto);
|
||||
|
||||
CleaningTemplateDto fromEntityToDto(CleaningTemplate entity);
|
||||
}
|
||||
-72
@@ -1,72 +0,0 @@
|
||||
package com.datamate.cleaning.infrastructure.converter;
|
||||
|
||||
|
||||
import com.datamate.cleaning.domain.model.entity.OperatorInstance;
|
||||
import com.datamate.cleaning.interfaces.dto.OperatorInstanceDto;
|
||||
import com.datamate.common.infrastructure.exception.BusinessException;
|
||||
import com.datamate.common.infrastructure.exception.SystemErrorCode;
|
||||
import com.datamate.operator.domain.model.OperatorView;
|
||||
import com.datamate.operator.interfaces.dto.OperatorDto;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.Mapping;
|
||||
import org.mapstruct.Named;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Mapper
|
||||
public interface OperatorInstanceConverter {
|
||||
OperatorInstanceConverter INSTANCE = Mappers.getMapper(OperatorInstanceConverter.class);
|
||||
|
||||
ObjectMapper OBJECT_MAPPER = new ObjectMapper();
|
||||
|
||||
@Mapping(target = "settingsOverride", source = "overrides", qualifiedByName = "mapToString")
|
||||
@Mapping(target = "operatorId", source = "id")
|
||||
OperatorInstance fromDtoToEntity(OperatorInstanceDto instance);
|
||||
|
||||
@Mapping(target = "overrides", source = "settingsOverride", qualifiedByName = "stringToMap")
|
||||
@Mapping(target = "id", source = "operatorId")
|
||||
OperatorInstanceDto fromEntityToDto(OperatorInstance instance);
|
||||
|
||||
List<OperatorInstanceDto> fromEntityToDtoList(List<OperatorInstance> instance);
|
||||
|
||||
@Named("mapToString")
|
||||
static String mapToString(Map<String, Object> objects) {
|
||||
try {
|
||||
return OBJECT_MAPPER.writeValueAsString(objects);
|
||||
} catch (JsonProcessingException e) {
|
||||
throw BusinessException.of(SystemErrorCode.UNKNOWN_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
@Named("stringToMap")
|
||||
static Map<String, Object> stringToMap(String json) {
|
||||
if (json == null) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
try {
|
||||
return OBJECT_MAPPER.readValue(json, new TypeReference<>() {});
|
||||
} catch (JsonProcessingException e) {
|
||||
throw BusinessException.of(SystemErrorCode.UNKNOWN_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
@Mapping(target = "categories", source = "categories", qualifiedByName = "stringToList")
|
||||
OperatorDto fromEntityToDto(OperatorView operator);
|
||||
|
||||
List<OperatorDto> fromEntityToDto(List<OperatorView> operator);
|
||||
|
||||
@Named("stringToList")
|
||||
default List<String> stringToList(String input) {
|
||||
if (input == null || input.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return Arrays.stream(input.split(",")).toList();
|
||||
}
|
||||
}
|
||||
-68
@@ -1,68 +0,0 @@
|
||||
package com.datamate.cleaning.infrastructure.httpclient;
|
||||
|
||||
import com.datamate.common.infrastructure.exception.BusinessException;
|
||||
import com.datamate.common.infrastructure.exception.SystemErrorCode;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.text.MessageFormat;
|
||||
import java.time.Duration;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
public class RuntimeClient {
|
||||
private final String CREATE_TASK_URL = "/api/task/{0}/submit";
|
||||
|
||||
private final String STOP_TASK_URL = "/api/task/{0}/stop";
|
||||
|
||||
@Value("${runtime.protocol:http}")
|
||||
private String protocol;
|
||||
|
||||
@Value("${runtime.host:datamate-runtime}")
|
||||
private String host;
|
||||
|
||||
@Value("${runtime.port:8081}")
|
||||
private int port;
|
||||
|
||||
private final HttpClient CLIENT = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(10)).build();
|
||||
|
||||
public void submitTask(String taskId) {
|
||||
send(MessageFormat.format(getRequestUrl(CREATE_TASK_URL), taskId));
|
||||
}
|
||||
|
||||
public void stopTask(String taskId) {
|
||||
send(MessageFormat.format(getRequestUrl(STOP_TASK_URL), taskId));
|
||||
}
|
||||
|
||||
private String getRequestUrl(String url) {
|
||||
return protocol + "://" + host + ":" + port + url;
|
||||
}
|
||||
|
||||
private void send(String url) {
|
||||
HttpRequest request = HttpRequest.newBuilder()
|
||||
.uri(URI.create(url))
|
||||
.timeout(Duration.ofSeconds(30))
|
||||
.header("Content-Type", "application/json")
|
||||
.POST(HttpRequest.BodyPublishers.noBody())
|
||||
.build();
|
||||
|
||||
try {
|
||||
HttpResponse<String> response = CLIENT.send(request, HttpResponse.BodyHandlers.ofString());
|
||||
int statusCode = response.statusCode();
|
||||
|
||||
if (statusCode < 200 || statusCode >= 300) {
|
||||
log.error("Request failed with status code: {}", statusCode);
|
||||
throw BusinessException.of(SystemErrorCode.SYSTEM_BUSY);
|
||||
}
|
||||
} catch (IOException | InterruptedException e) {
|
||||
log.error("Error occurred while making the request.", e);
|
||||
throw BusinessException.of(SystemErrorCode.UNKNOWN_ERROR);
|
||||
}
|
||||
}
|
||||
}
|
||||
-58
@@ -1,58 +0,0 @@
|
||||
package com.datamate.cleaning.infrastructure.persistence.Impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.repository.CrudRepository;
|
||||
import com.datamate.cleaning.common.enums.CleaningTaskStatusEnum;
|
||||
import com.datamate.cleaning.domain.model.entity.CleaningResult;
|
||||
import com.datamate.cleaning.domain.repository.CleaningResultRepository;
|
||||
import com.datamate.cleaning.infrastructure.converter.CleaningResultConverter;
|
||||
import com.datamate.cleaning.infrastructure.persistence.mapper.CleaningResultMapper;
|
||||
import com.datamate.cleaning.interfaces.dto.CleaningResultDto;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Repository
|
||||
@RequiredArgsConstructor
|
||||
public class CleaningResultRepositoryImpl extends CrudRepository<CleaningResultMapper, CleaningResult>
|
||||
implements CleaningResultRepository {
|
||||
private final CleaningResultMapper mapper;
|
||||
|
||||
@Override
|
||||
public void deleteByInstanceId(String instanceId) {
|
||||
deleteByInstanceId(instanceId, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteByInstanceId(String instanceId, String status) {
|
||||
LambdaQueryWrapper<CleaningResult> queryWrapper = new LambdaQueryWrapper<>();
|
||||
queryWrapper.eq(CleaningResult::getInstanceId, instanceId)
|
||||
.eq(StringUtils.isNotBlank(status), CleaningResult::getStatus, status);
|
||||
mapper.delete(queryWrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] countByInstanceId(String instanceId) {
|
||||
LambdaQueryWrapper<CleaningResult> lambdaWrapper = new LambdaQueryWrapper<>();
|
||||
lambdaWrapper.eq(CleaningResult::getInstanceId, instanceId);
|
||||
List<CleaningResult> cleaningResults = mapper.selectList(lambdaWrapper);
|
||||
int succeed = Math.toIntExact(cleaningResults.stream()
|
||||
.filter(result ->
|
||||
StringUtils.equals(result.getStatus(), CleaningTaskStatusEnum.COMPLETED.getValue()))
|
||||
.count());
|
||||
return new int[] {succeed, cleaningResults.size() - succeed};
|
||||
}
|
||||
|
||||
public List<CleaningResultDto> findByInstanceId(String instanceId) {
|
||||
return findByInstanceId(instanceId, null);
|
||||
}
|
||||
|
||||
public List<CleaningResultDto> findByInstanceId(String instanceId, String status) {
|
||||
LambdaQueryWrapper<CleaningResult> queryWrapper = new LambdaQueryWrapper<>();
|
||||
queryWrapper.eq(CleaningResult::getInstanceId, instanceId)
|
||||
.eq(StringUtils.isNotBlank(status), CleaningResult::getStatus, status);
|
||||
return CleaningResultConverter.INSTANCE.convertEntityToDto(mapper.selectList(queryWrapper));
|
||||
}
|
||||
}
|
||||
-65
@@ -1,65 +0,0 @@
|
||||
package com.datamate.cleaning.infrastructure.persistence.Impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.repository.CrudRepository;
|
||||
import com.datamate.cleaning.domain.model.entity.CleaningTask;
|
||||
import com.datamate.cleaning.domain.repository.CleaningTaskRepository;
|
||||
import com.datamate.cleaning.infrastructure.converter.CleaningTaskConverter;
|
||||
import com.datamate.cleaning.infrastructure.persistence.mapper.CleaningTaskMapper;
|
||||
import com.datamate.cleaning.interfaces.dto.CleaningTaskDto;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
||||
@Repository
|
||||
@RequiredArgsConstructor
|
||||
public class CleaningTaskRepositoryImpl extends CrudRepository<CleaningTaskMapper, CleaningTask>
|
||||
implements CleaningTaskRepository {
|
||||
private final CleaningTaskMapper mapper;
|
||||
|
||||
public List<CleaningTaskDto> findTasks(String status, String keywords, Integer page, Integer size) {
|
||||
LambdaQueryWrapper<CleaningTask> lambdaWrapper = new LambdaQueryWrapper<>();
|
||||
lambdaWrapper.eq(StringUtils.isNotBlank(status), CleaningTask::getStatus, status);
|
||||
if (StringUtils.isNotBlank(keywords)) {
|
||||
lambdaWrapper.and(w ->
|
||||
w.like(CleaningTask::getName, keywords)
|
||||
.or()
|
||||
.like(CleaningTask::getDescription, keywords));
|
||||
}
|
||||
lambdaWrapper.orderByDesc(CleaningTask::getCreatedAt);
|
||||
if (size != null && page != null) {
|
||||
Page<CleaningTask> queryPage = new Page<>(page + 1, size);
|
||||
IPage<CleaningTask> resultPage = mapper.selectPage(queryPage, lambdaWrapper);
|
||||
return CleaningTaskConverter.INSTANCE.fromEntityToDto(resultPage.getRecords());
|
||||
} else {
|
||||
return CleaningTaskConverter.INSTANCE.fromEntityToDto(mapper.selectList(lambdaWrapper));
|
||||
}
|
||||
}
|
||||
|
||||
public CleaningTaskDto findTaskById(String taskId) {
|
||||
return CleaningTaskConverter.INSTANCE.fromEntityToDto(mapper.selectById(taskId));
|
||||
}
|
||||
|
||||
public void insertTask(CleaningTaskDto task) {
|
||||
mapper.insert(CleaningTaskConverter.INSTANCE.fromDtoToEntity(task));
|
||||
}
|
||||
|
||||
public void updateTask(CleaningTaskDto task) {
|
||||
mapper.updateById(CleaningTaskConverter.INSTANCE.fromDtoToEntity(task));
|
||||
}
|
||||
|
||||
public void deleteTaskById(String taskId) {
|
||||
mapper.deleteById(taskId);
|
||||
}
|
||||
|
||||
public boolean isNameExist(String name) {
|
||||
LambdaQueryWrapper<CleaningTask> queryWrapper = new LambdaQueryWrapper<>();
|
||||
queryWrapper.eq(CleaningTask::getName, name);
|
||||
return mapper.exists(queryWrapper);
|
||||
}
|
||||
}
|
||||
-56
@@ -1,56 +0,0 @@
|
||||
package com.datamate.cleaning.infrastructure.persistence.Impl;
|
||||
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.repository.CrudRepository;
|
||||
import com.datamate.cleaning.domain.model.entity.TemplateWithInstance;
|
||||
import com.datamate.cleaning.domain.model.entity.CleaningTemplate;
|
||||
import com.datamate.cleaning.domain.repository.CleaningTemplateRepository;
|
||||
import com.datamate.cleaning.infrastructure.converter.CleaningTemplateConverter;
|
||||
import com.datamate.cleaning.infrastructure.persistence.mapper.CleaningTemplateMapper;
|
||||
import com.datamate.cleaning.interfaces.dto.CleaningTemplateDto;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
||||
@Repository
|
||||
@RequiredArgsConstructor
|
||||
public class CleaningTemplateRepositoryImpl extends CrudRepository<CleaningTemplateMapper, CleaningTemplate>
|
||||
implements CleaningTemplateRepository {
|
||||
private final CleaningTemplateMapper mapper;
|
||||
|
||||
@Override
|
||||
public List<TemplateWithInstance> findAllTemplates(String keywords) {
|
||||
QueryWrapper<TemplateWithInstance> queryWrapper = new QueryWrapper<>();
|
||||
if (StringUtils.isNotBlank(keywords)) {
|
||||
queryWrapper.like("name", keywords)
|
||||
.or()
|
||||
.like("description", keywords);
|
||||
}
|
||||
queryWrapper.orderByDesc("created_at");
|
||||
return mapper.findAllTemplates(queryWrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CleaningTemplateDto findTemplateById(String templateId) {
|
||||
return CleaningTemplateConverter.INSTANCE.fromEntityToDto(mapper.selectById(templateId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void insertTemplate(CleaningTemplateDto template) {
|
||||
mapper.insert(CleaningTemplateConverter.INSTANCE.fromDtoToEntity(template));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateTemplate(CleaningTemplateDto template) {
|
||||
mapper.updateById(CleaningTemplateConverter.INSTANCE.fromDtoToEntity(template));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteTemplate(String templateId) {
|
||||
mapper.deleteById(templateId);
|
||||
}
|
||||
}
|
||||
-53
@@ -1,53 +0,0 @@
|
||||
package com.datamate.cleaning.infrastructure.persistence.Impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.repository.CrudRepository;
|
||||
import com.datamate.cleaning.infrastructure.converter.OperatorInstanceConverter;
|
||||
import com.datamate.cleaning.interfaces.dto.OperatorInstanceDto;
|
||||
import com.datamate.cleaning.domain.model.entity.OperatorInstance;
|
||||
import com.datamate.cleaning.domain.repository.OperatorInstanceRepository;
|
||||
import com.datamate.cleaning.infrastructure.persistence.mapper.OperatorInstanceMapper;
|
||||
import com.datamate.operator.interfaces.dto.OperatorDto;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Repository
|
||||
@RequiredArgsConstructor
|
||||
public class OperatorInstanceRepositoryImpl extends CrudRepository<OperatorInstanceMapper, OperatorInstance>
|
||||
implements OperatorInstanceRepository {
|
||||
private final OperatorInstanceMapper mapper;
|
||||
|
||||
@Override
|
||||
public void insertInstance(String instanceId, List<OperatorInstanceDto> instances) {
|
||||
List<OperatorInstance> operatorInstances = new ArrayList<>();
|
||||
for (int i = 0; i < instances.size(); i++) {
|
||||
OperatorInstance operatorInstance = OperatorInstanceConverter.INSTANCE.fromDtoToEntity(instances.get(i));
|
||||
operatorInstance.setInstanceId(instanceId);
|
||||
operatorInstance.setOpIndex(i + 1);
|
||||
operatorInstances.add(operatorInstance);
|
||||
}
|
||||
mapper.insert(operatorInstances);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteByInstanceId(String instanceId) {
|
||||
LambdaQueryWrapper<OperatorInstance> lambdaWrapper = new LambdaQueryWrapper<>();
|
||||
lambdaWrapper.eq(OperatorInstance::getInstanceId, instanceId);
|
||||
mapper.delete(lambdaWrapper);
|
||||
}
|
||||
|
||||
public List<OperatorDto> findOperatorByInstanceId(String instanceId) {
|
||||
return OperatorInstanceConverter.INSTANCE.fromEntityToDto(mapper.findOperatorByInstanceId(instanceId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<OperatorInstanceDto> findInstanceByInstanceId(String instanceId) {
|
||||
LambdaQueryWrapper<OperatorInstance> lambdaWrapper = new LambdaQueryWrapper<>();
|
||||
lambdaWrapper.eq(OperatorInstance::getInstanceId, instanceId)
|
||||
.orderByAsc(OperatorInstance::getOpIndex);
|
||||
return OperatorInstanceConverter.INSTANCE.fromEntityToDtoList(mapper.selectList(lambdaWrapper));
|
||||
}
|
||||
}
|
||||
-9
@@ -1,9 +0,0 @@
|
||||
package com.datamate.cleaning.infrastructure.persistence.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.datamate.cleaning.domain.model.entity.CleaningResult;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface CleaningResultMapper extends BaseMapper<CleaningResult> {
|
||||
}
|
||||
-9
@@ -1,9 +0,0 @@
|
||||
package com.datamate.cleaning.infrastructure.persistence.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.datamate.cleaning.domain.model.entity.CleaningTask;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface CleaningTaskMapper extends BaseMapper<CleaningTask> {
|
||||
}
|
||||
-20
@@ -1,20 +0,0 @@
|
||||
package com.datamate.cleaning.infrastructure.persistence.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.Wrapper;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Constants;
|
||||
import com.datamate.cleaning.domain.model.entity.TemplateWithInstance;
|
||||
import com.datamate.cleaning.domain.model.entity.CleaningTemplate;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface CleaningTemplateMapper extends BaseMapper<CleaningTemplate> {
|
||||
@Select("SELECT t.id AS id, name, description, created_at, updated_at, created_by, operator_id, op_index, " +
|
||||
"settings_override FROM t_clean_template t LEFT JOIN t_operator_instance o ON t.id = o.instance_id " +
|
||||
"${ew.customSqlSegment}")
|
||||
List<TemplateWithInstance> findAllTemplates(@Param(Constants.WRAPPER) Wrapper<TemplateWithInstance> queryWrapper);
|
||||
}
|
||||
-23
@@ -1,23 +0,0 @@
|
||||
package com.datamate.cleaning.infrastructure.persistence.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.datamate.cleaning.domain.model.entity.OperatorInstance;
|
||||
import com.datamate.operator.domain.model.OperatorView;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
||||
@Mapper
|
||||
public interface OperatorInstanceMapper extends BaseMapper<OperatorInstance> {
|
||||
@Select("SELECT o.operator_id as id, o.operator_name as name, description, version, inputs, outputs, runtime, " +
|
||||
" settings, created_at, updated_at, " +
|
||||
"GROUP_CONCAT(category_id ORDER BY created_at DESC SEPARATOR ',') AS categories " +
|
||||
"FROM t_operator_instance toi LEFT JOIN v_operator o ON toi.operator_id = o.operator_id " +
|
||||
"WHERE toi.instance_id = #{instanceId} " +
|
||||
"GROUP BY o.operator_id, o.operator_name, description, version, inputs, outputs, runtime," +
|
||||
" settings, created_at, updated_at, op_index " +
|
||||
"ORDER BY toi.op_index")
|
||||
List<OperatorView> findOperatorByInstanceId(String instanceId);
|
||||
}
|
||||
-84
@@ -1,84 +0,0 @@
|
||||
package com.datamate.cleaning.infrastructure.validator;
|
||||
|
||||
import com.datamate.cleaning.common.enums.ExecutorType;
|
||||
import com.datamate.cleaning.common.exception.CleanErrorCode;
|
||||
import com.datamate.cleaning.domain.repository.CleaningTaskRepository;
|
||||
import com.datamate.cleaning.interfaces.dto.OperatorInstanceDto;
|
||||
import com.datamate.common.infrastructure.exception.BusinessException;
|
||||
import com.datamate.common.infrastructure.exception.SystemErrorCode;
|
||||
import com.datamate.common.setting.application.SysParamApplicationService;
|
||||
import com.datamate.operator.domain.contants.OperatorConstant;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class CleanTaskValidator {
|
||||
private final CleaningTaskRepository cleaningTaskRepo;
|
||||
|
||||
private final SysParamApplicationService sysParamApplicationService;
|
||||
|
||||
private final Pattern UUID_PATTERN = Pattern.compile(
|
||||
"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"
|
||||
);
|
||||
|
||||
public void checkNameDuplication(String name) {
|
||||
if (cleaningTaskRepo.isNameExist(name)) {
|
||||
throw BusinessException.of(CleanErrorCode.DUPLICATE_TASK_NAME);
|
||||
}
|
||||
}
|
||||
|
||||
public void checkInputAndOutput(List<OperatorInstanceDto> operators) {
|
||||
if (operators == null || operators.size() <= 1) {
|
||||
return;
|
||||
}
|
||||
for (int i = 1; i < operators.size(); i++) {
|
||||
OperatorInstanceDto front = operators.get(i - 1);
|
||||
OperatorInstanceDto back = operators.get(i);
|
||||
if (StringUtils.equals(front.getOutputs(), back.getInputs()) || StringUtils.equalsAny("multimodal",
|
||||
front.getOutputs(), back.getOutputs())) {
|
||||
continue;
|
||||
}
|
||||
throw BusinessException.of(CleanErrorCode.IN_AND_OUT_NOT_MATCH,
|
||||
String.format(Locale.ROOT, "ops(name: [%s, %s]) inputs and outputs does not match",
|
||||
front.getName(), back.getName()));
|
||||
}
|
||||
}
|
||||
|
||||
public void checkTaskId(String id) {
|
||||
if (id == null || !UUID_PATTERN.matcher(id).matches()) {
|
||||
throw BusinessException.of(SystemErrorCode.INVALID_PARAMETER);
|
||||
}
|
||||
}
|
||||
|
||||
public ExecutorType checkAndGetExecutorType(List<OperatorInstanceDto> operators) {
|
||||
if (operators == null || operators.isEmpty()) {
|
||||
throw BusinessException.of(CleanErrorCode.OPERATOR_LIST_EMPTY);
|
||||
}
|
||||
for (int i = 1; i < operators.size(); i++) {
|
||||
OperatorInstanceDto front = operators.get(i - 1);
|
||||
OperatorInstanceDto back = operators.get(i);
|
||||
boolean frontHas = CollectionUtils.isNotEmpty(front.getCategories())
|
||||
&& front.getCategories().contains(OperatorConstant.CATEGORY_DATA_JUICER_ID);
|
||||
boolean backHas = CollectionUtils.isNotEmpty(back.getCategories())
|
||||
&& back.getCategories().contains(OperatorConstant.CATEGORY_DATA_JUICER_ID);
|
||||
if (frontHas == backHas) {
|
||||
continue;
|
||||
}
|
||||
throw BusinessException.of(CleanErrorCode.EXECUTOR_NOT_MATCH,
|
||||
String.format(Locale.ROOT, "ops(name: [%s, %s]) executor does not match",
|
||||
front.getName(), back.getName()));
|
||||
}
|
||||
if (operators.getFirst().getCategories().contains(OperatorConstant.CATEGORY_DATA_JUICER_ID)) {
|
||||
return ExecutorType.fromValue(sysParamApplicationService.getParamByKey("DATA_JUICER_EXECUTOR"));
|
||||
}
|
||||
return ExecutorType.DATAMATE;
|
||||
}
|
||||
}
|
||||
-52
@@ -1,52 +0,0 @@
|
||||
package com.datamate.cleaning.interfaces.dto;
|
||||
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
|
||||
/**
|
||||
* CleaningProcess
|
||||
*/
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class CleaningProcess {
|
||||
private Float process;
|
||||
|
||||
private Float successRate;
|
||||
|
||||
private Integer totalFileNum;
|
||||
|
||||
private Integer succeedFileNum;
|
||||
|
||||
private Integer failedFileNum;
|
||||
|
||||
private Integer finishedFileNum;
|
||||
|
||||
public CleaningProcess(int totalFileNum, int succeedFileNum, int failedFileNum) {
|
||||
this.totalFileNum = totalFileNum;
|
||||
this.succeedFileNum = succeedFileNum;
|
||||
this.failedFileNum = failedFileNum;
|
||||
this.finishedFileNum = succeedFileNum + failedFileNum;
|
||||
if (totalFileNum == 0) {
|
||||
this.process = 0.0f;
|
||||
} else {
|
||||
this.process = BigDecimal.valueOf(finishedFileNum * 100L)
|
||||
.divide(BigDecimal.valueOf(totalFileNum), 2, RoundingMode.HALF_UP).floatValue();
|
||||
}
|
||||
if (finishedFileNum == 0) {
|
||||
this.successRate = 0f;
|
||||
} else {
|
||||
this.successRate = BigDecimal.valueOf(succeedFileNum * 100L)
|
||||
.divide(BigDecimal.valueOf(finishedFileNum), 2, RoundingMode.HALF_UP).floatValue();
|
||||
}
|
||||
}
|
||||
|
||||
public static CleaningProcess of(int totalFileNum, int succeedFileNum, int failedFileNum) {
|
||||
return new CleaningProcess(totalFileNum, succeedFileNum, failedFileNum);
|
||||
}
|
||||
}
|
||||
|
||||
-30
@@ -1,30 +0,0 @@
|
||||
package com.datamate.cleaning.interfaces.dto;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class CleaningResultDto {
|
||||
private String instanceId;
|
||||
|
||||
private String srcFileId;
|
||||
|
||||
private String destFileId;
|
||||
|
||||
private String srcName;
|
||||
|
||||
private String destName;
|
||||
|
||||
private String srcType;
|
||||
|
||||
private String destType;
|
||||
|
||||
private long srcSize;
|
||||
|
||||
private long destSize;
|
||||
|
||||
private String status;
|
||||
|
||||
private String result;
|
||||
}
|
||||
-58
@@ -1,58 +0,0 @@
|
||||
package com.datamate.cleaning.interfaces.dto;
|
||||
|
||||
import com.datamate.cleaning.common.enums.CleaningTaskStatusEnum;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
import com.datamate.operator.interfaces.dto.OperatorDto;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
/**
|
||||
* CleaningTask
|
||||
*/
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class CleaningTaskDto {
|
||||
|
||||
private String id;
|
||||
|
||||
private String name;
|
||||
|
||||
private String description;
|
||||
|
||||
private String srcDatasetId;
|
||||
|
||||
private String srcDatasetName;
|
||||
|
||||
private String destDatasetId;
|
||||
|
||||
private String destDatasetName;
|
||||
|
||||
private Long beforeSize;
|
||||
|
||||
private Long afterSize;
|
||||
|
||||
private Integer fileCount;
|
||||
|
||||
private CleaningTaskStatusEnum status;
|
||||
|
||||
private String templateId;
|
||||
|
||||
private List<OperatorDto> instance;
|
||||
|
||||
private CleaningProcess progress;
|
||||
|
||||
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
|
||||
private LocalDateTime startedAt;
|
||||
|
||||
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
|
||||
private LocalDateTime finishedAt;
|
||||
}
|
||||
|
||||
-12
@@ -1,12 +0,0 @@
|
||||
package com.datamate.cleaning.interfaces.dto;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class CleaningTaskLog {
|
||||
private String level;
|
||||
|
||||
private String message;
|
||||
}
|
||||
-34
@@ -1,34 +0,0 @@
|
||||
package com.datamate.cleaning.interfaces.dto;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.datamate.operator.interfaces.dto.OperatorDto;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
/**
|
||||
* CleaningTemplate
|
||||
*/
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class CleaningTemplateDto {
|
||||
|
||||
private String id;
|
||||
|
||||
private String name;
|
||||
|
||||
private String description;
|
||||
|
||||
private List<OperatorDto> instance = new ArrayList<>();
|
||||
|
||||
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
|
||||
private LocalDateTime updatedAt;
|
||||
}
|
||||
|
||||
-34
@@ -1,34 +0,0 @@
|
||||
package com.datamate.cleaning.interfaces.dto;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
/**
|
||||
* CreateCleaningTaskRequest
|
||||
*/
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class CreateCleaningTaskRequest {
|
||||
|
||||
private String name;
|
||||
|
||||
private String description;
|
||||
|
||||
private String srcDatasetId;
|
||||
|
||||
private String srcDatasetName;
|
||||
|
||||
private String destDatasetName;
|
||||
|
||||
private String destDatasetType;
|
||||
|
||||
private String templateId;
|
||||
|
||||
private List<OperatorInstanceDto> instance = new ArrayList<>();
|
||||
}
|
||||
|
||||
-23
@@ -1,23 +0,0 @@
|
||||
package com.datamate.cleaning.interfaces.dto;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
/**
|
||||
* CreateCleaningTemplateRequest
|
||||
*/
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class CreateCleaningTemplateRequest {
|
||||
|
||||
private String name;
|
||||
|
||||
private String description;
|
||||
|
||||
private List<OperatorInstanceDto> instance = new ArrayList<>();
|
||||
}
|
||||
|
||||
-31
@@ -1,31 +0,0 @@
|
||||
package com.datamate.cleaning.interfaces.dto;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
/**
|
||||
* OperatorInstance
|
||||
*/
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class OperatorInstanceDto {
|
||||
|
||||
private String id;
|
||||
|
||||
private String name;
|
||||
|
||||
private String inputs;
|
||||
|
||||
private String outputs;
|
||||
|
||||
private List<String> categories;
|
||||
|
||||
private Map<String, Object> overrides = new HashMap<>();
|
||||
}
|
||||
|
||||
-26
@@ -1,26 +0,0 @@
|
||||
package com.datamate.cleaning.interfaces.dto;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
/**
|
||||
* UpdateCleaningTemplateRequest
|
||||
*/
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class UpdateCleaningTemplateRequest {
|
||||
|
||||
private String id;
|
||||
|
||||
private String name;
|
||||
|
||||
private String description;
|
||||
|
||||
private List<OperatorInstanceDto> instance = new ArrayList<>();
|
||||
}
|
||||
|
||||
-77
@@ -1,77 +0,0 @@
|
||||
package com.datamate.cleaning.interfaces.rest;
|
||||
|
||||
import com.datamate.cleaning.application.CleaningTaskService;
|
||||
import com.datamate.cleaning.interfaces.dto.*;
|
||||
import com.datamate.common.interfaces.PagedResponse;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/cleaning/tasks")
|
||||
@RequiredArgsConstructor
|
||||
public class CleaningTaskController {
|
||||
private final CleaningTaskService cleaningTaskService;
|
||||
|
||||
@GetMapping
|
||||
public PagedResponse<CleaningTaskDto> cleaningTasksGet(
|
||||
@RequestParam("page") Integer page,
|
||||
@RequestParam("size") Integer size, @RequestParam(value = "status", required = false) String status,
|
||||
@RequestParam(value = "keyword", required = false) String keyword) {
|
||||
List<CleaningTaskDto> tasks = cleaningTaskService.getTasks(status, keyword, page, size);
|
||||
int count = cleaningTaskService.countTasks(status, keyword);
|
||||
int totalPages = (count + size + 1) / size;
|
||||
return PagedResponse.of(tasks, page, count, totalPages);
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
public CleaningTaskDto cleaningTasksPost(@RequestBody CreateCleaningTaskRequest request) {
|
||||
if (request.getInstance().isEmpty() && StringUtils.isNotBlank(request.getTemplateId())) {
|
||||
request.setInstance(cleaningTaskService.getInstanceByTemplateId(request.getTemplateId()));
|
||||
}
|
||||
return cleaningTaskService.createTask(request);
|
||||
}
|
||||
|
||||
@PostMapping("/{taskId}/stop")
|
||||
public String cleaningTasksStop(@PathVariable("taskId") String taskId) {
|
||||
cleaningTaskService.stopTask(taskId);
|
||||
return taskId;
|
||||
}
|
||||
|
||||
@PostMapping("/{taskId}/execute")
|
||||
public String cleaningTasksStart(@PathVariable("taskId") String taskId) {
|
||||
cleaningTaskService.executeTask(taskId);
|
||||
return taskId;
|
||||
}
|
||||
|
||||
@GetMapping("/{taskId}")
|
||||
public CleaningTaskDto cleaningTasksTaskIdGet(@PathVariable("taskId") String taskId) {
|
||||
return cleaningTaskService.getTask(taskId);
|
||||
}
|
||||
|
||||
@DeleteMapping("/{taskId}")
|
||||
public String cleaningTasksTaskIdDelete(@PathVariable("taskId") String taskId) {
|
||||
cleaningTaskService.deleteTask(taskId);
|
||||
return taskId;
|
||||
}
|
||||
|
||||
@DeleteMapping
|
||||
public void cleaningTasksDelete(@RequestParam List<String> taskIds) {
|
||||
for (String taskId : taskIds) {
|
||||
cleaningTaskService.deleteTask(taskId);
|
||||
}
|
||||
}
|
||||
|
||||
@GetMapping("/{taskId}/result")
|
||||
public List<CleaningResultDto> cleaningTasksTaskIdGetResult(@PathVariable("taskId") String taskId) {
|
||||
return cleaningTaskService.getTaskResults(taskId);
|
||||
}
|
||||
|
||||
@GetMapping("/{taskId}/log")
|
||||
public List<CleaningTaskLog> cleaningTasksTaskIdGetLog(@PathVariable("taskId") String taskId) {
|
||||
return cleaningTaskService.getTaskLog(taskId);
|
||||
}
|
||||
}
|
||||
-72
@@ -1,72 +0,0 @@
|
||||
package com.datamate.cleaning.interfaces.rest;
|
||||
|
||||
import com.datamate.cleaning.application.CleaningTemplateService;
|
||||
import com.datamate.cleaning.interfaces.dto.CleaningTemplateDto;
|
||||
import com.datamate.cleaning.interfaces.dto.CreateCleaningTemplateRequest;
|
||||
import com.datamate.cleaning.interfaces.dto.UpdateCleaningTemplateRequest;
|
||||
import com.datamate.common.interfaces.PagedResponse;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/cleaning/templates")
|
||||
@RequiredArgsConstructor
|
||||
public class CleaningTemplateController {
|
||||
private final CleaningTemplateService cleaningTemplateService;
|
||||
|
||||
@GetMapping
|
||||
public PagedResponse<CleaningTemplateDto> cleaningTemplatesGet(
|
||||
@RequestParam(value = "page", required = false) Integer page,
|
||||
@RequestParam(value = "size", required = false) Integer size,
|
||||
@RequestParam(value = "keyword", required = false) String keyword) {
|
||||
List<CleaningTemplateDto> templates = cleaningTemplateService.getTemplates(keyword);
|
||||
if (page == null || size == null) {
|
||||
return PagedResponse.of(templates.stream()
|
||||
.sorted(Comparator.comparing(CleaningTemplateDto::getCreatedAt).reversed()).toList());
|
||||
}
|
||||
int count = templates.size();
|
||||
int totalPages = (count + size + 1) / size;
|
||||
List<CleaningTemplateDto> limitTemplates = templates.stream()
|
||||
.sorted(Comparator.comparing(CleaningTemplateDto::getCreatedAt).reversed())
|
||||
.skip((long) page * size)
|
||||
.limit(size).toList();
|
||||
return PagedResponse.of(limitTemplates, page, count, totalPages);
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
public CleaningTemplateDto cleaningTemplatesPost(
|
||||
@RequestBody CreateCleaningTemplateRequest request) {
|
||||
return cleaningTemplateService.createTemplate(request);
|
||||
}
|
||||
|
||||
@GetMapping("/{templateId}")
|
||||
public CleaningTemplateDto cleaningTemplatesTemplateIdGet(
|
||||
@PathVariable("templateId") String templateId) {
|
||||
return cleaningTemplateService.getTemplate(templateId);
|
||||
}
|
||||
|
||||
@PutMapping("/{templateId}")
|
||||
public CleaningTemplateDto cleaningTemplatesTemplateIdPut(
|
||||
@PathVariable("templateId") String templateId, @RequestBody UpdateCleaningTemplateRequest request) {
|
||||
return cleaningTemplateService.updateTemplate(templateId, request);
|
||||
}
|
||||
|
||||
@DeleteMapping("/{templateId}")
|
||||
public String cleaningTemplatesTemplateIdDelete(
|
||||
@PathVariable("templateId") String templateId) {
|
||||
cleaningTemplateService.deleteTemplate(templateId);
|
||||
return templateId;
|
||||
}
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
|
||||
http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>com.datamate</groupId>
|
||||
<artifactId>services</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
<artifactId>data-evaluation-service</artifactId>
|
||||
<name>Data Evaluation Service</name>
|
||||
<description>数据评估服务</description>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.datamate</groupId>
|
||||
<artifactId>domain-common</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.mysql</groupId>
|
||||
<artifactId>mysql-connector-j</artifactId>
|
||||
<version>${mysql.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-openfeign</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springdoc</groupId>
|
||||
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openapitools</groupId>
|
||||
<artifactId>jackson-databind-nullable</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>jakarta.validation</groupId>
|
||||
<artifactId>jakarta.validation-api</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.openapitools</groupId>
|
||||
<artifactId>openapi-generator-maven-plugin</artifactId>
|
||||
<version>6.6.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>generate</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<inputSpec>${project.basedir}/../../openapi/specs/data-evaluation.yaml</inputSpec>
|
||||
<generatorName>spring</generatorName>
|
||||
<output>${project.build.directory}/generated-sources/openapi</output>
|
||||
<apiPackage>com.datamate.evaluation.interfaces.api</apiPackage>
|
||||
<modelPackage>com.datamate.evaluation.interfaces.dto</modelPackage>
|
||||
<configOptions>
|
||||
<interfaceOnly>true</interfaceOnly>
|
||||
<useTags>true</useTags>
|
||||
<useSpringBoot3>true</useSpringBoot3>
|
||||
<documentationProvider>springdoc</documentationProvider>
|
||||
</configOptions>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
|
||||
|
||||
@@ -1,109 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
|
||||
http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>com.datamate</groupId>
|
||||
<artifactId>services</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
<artifactId>data-management-service</artifactId>
|
||||
<name>Data Management Service</name>
|
||||
<description>数据管理服务</description>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.datamate</groupId>
|
||||
<artifactId>domain-common</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-redis</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.mysql</groupId>
|
||||
<artifactId>mysql-connector-j</artifactId>
|
||||
<version>${mysql.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-openfeign</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springdoc</groupId>
|
||||
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openapitools</groupId>
|
||||
<artifactId>jackson-databind-nullable</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>jakarta.validation</groupId>
|
||||
<artifactId>jakarta.validation-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-commons</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<arguments>true</arguments>
|
||||
<classifier>exec</classifier>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.11.0</version>
|
||||
<configuration>
|
||||
<source>${maven.compiler.source}</source>
|
||||
<target>${maven.compiler.target}</target>
|
||||
<annotationProcessorPaths>
|
||||
<!-- 顺序很重要 -->
|
||||
<path>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>${lombok.version}</version>
|
||||
</path>
|
||||
<path>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok-mapstruct-binding</artifactId>
|
||||
<version>${lombok-mapstruct-binding.version}</version>
|
||||
</path>
|
||||
<path>
|
||||
<groupId>org.mapstruct</groupId>
|
||||
<artifactId>mapstruct-processor</artifactId>
|
||||
<version>${mapstruct.version}</version>
|
||||
</path>
|
||||
</annotationProcessorPaths>
|
||||
<compilerArgs>
|
||||
<arg>-parameters</arg>
|
||||
<arg>-Amapstruct.defaultComponentModel=spring</arg>
|
||||
</compilerArgs>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
|
||||
-22
@@ -1,22 +0,0 @@
|
||||
package com.datamate.datamanagement;
|
||||
|
||||
import org.springframework.cloud.openfeign.EnableFeignClients;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.scheduling.annotation.EnableAsync;
|
||||
|
||||
/**
|
||||
* Data Management Service Configuration
|
||||
* 数据管理服务配置类 - 多源接入、元数据、血缘治理
|
||||
*/
|
||||
@Configuration
|
||||
@EnableFeignClients(basePackages = "com.datamate.datamanagement.infrastructure.client")
|
||||
@EnableAsync
|
||||
@ComponentScan(basePackages = {
|
||||
"com.datamate.datamanagement",
|
||||
"com.datamate.shared"
|
||||
})
|
||||
public class DataManagementServiceConfiguration {
|
||||
// Service configuration class for JAR packaging
|
||||
// 作为jar包形式提供服务的配置类
|
||||
}
|
||||
-547
@@ -1,547 +0,0 @@
|
||||
package com.datamate.datamanagement.application;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.datamate.common.auth.application.ResourceAccessService;
|
||||
import com.datamate.common.domain.utils.ChunksSaver;
|
||||
import com.datamate.common.setting.application.SysParamApplicationService;
|
||||
import com.datamate.datamanagement.interfaces.dto.*;
|
||||
import com.datamate.common.infrastructure.exception.BusinessAssert;
|
||||
import com.datamate.common.infrastructure.exception.BusinessException;
|
||||
import com.datamate.common.infrastructure.exception.CommonErrorCode;
|
||||
import com.datamate.common.infrastructure.exception.SystemErrorCode;
|
||||
import com.datamate.common.interfaces.PagedResponse;
|
||||
import com.datamate.datamanagement.domain.model.dataset.Dataset;
|
||||
import com.datamate.datamanagement.domain.model.dataset.DatasetFile;
|
||||
import com.datamate.datamanagement.domain.model.dataset.Tag;
|
||||
import com.datamate.datamanagement.infrastructure.client.CollectionTaskClient;
|
||||
import com.datamate.datamanagement.infrastructure.client.dto.CollectionTaskDetailResponse;
|
||||
import com.datamate.datamanagement.infrastructure.exception.DataManagementErrorCode;
|
||||
import com.datamate.datamanagement.infrastructure.persistence.mapper.TagMapper;
|
||||
import com.datamate.datamanagement.infrastructure.persistence.repository.DatasetFileRepository;
|
||||
import com.datamate.datamanagement.infrastructure.persistence.repository.DatasetRepository;
|
||||
import com.datamate.datamanagement.infrastructure.persistence.repository.dto.DatasetFileCount;
|
||||
import com.datamate.datamanagement.interfaces.converter.DatasetConverter;
|
||||
import com.datamate.datamanagement.interfaces.dto.*;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* 数据集应用服务(对齐 DB schema,使用 UUID 字符串主键)
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@Transactional
|
||||
@RequiredArgsConstructor
|
||||
public class DatasetApplicationService {
|
||||
private static final String DATASET_PVC_NAME = "sys.management.dataset.pvc.name";
|
||||
private static final int SIMILAR_DATASET_DEFAULT_LIMIT = 4;
|
||||
private static final int SIMILAR_DATASET_MAX_LIMIT = 50;
|
||||
private static final int SIMILAR_DATASET_CANDIDATE_FACTOR = 5;
|
||||
private static final int SIMILAR_DATASET_CANDIDATE_MAX = 100;
|
||||
private static final String DERIVED_METADATA_KEY = "derived_from_file_id";
|
||||
private final DatasetRepository datasetRepository;
|
||||
private final TagMapper tagMapper;
|
||||
private final DatasetFileRepository datasetFileRepository;
|
||||
private final CollectionTaskClient collectionTaskClient;
|
||||
private final DatasetFileApplicationService datasetFileApplicationService;
|
||||
private final SysParamApplicationService sysParamService;
|
||||
private final ResourceAccessService resourceAccessService;
|
||||
|
||||
@Value("${datamate.data-management.base-path:/dataset}")
|
||||
private String datasetBasePath;
|
||||
|
||||
/**
|
||||
* 创建数据集
|
||||
*/
|
||||
@Transactional
|
||||
public Dataset createDataset(CreateDatasetRequest createDatasetRequest) {
|
||||
BusinessAssert.isTrue(datasetRepository.findByName(createDatasetRequest.getName()) == null, DataManagementErrorCode.DATASET_ALREADY_EXISTS);
|
||||
// 创建数据集对象
|
||||
Dataset dataset = DatasetConverter.INSTANCE.convertToDataset(createDatasetRequest);
|
||||
Dataset parentDataset = resolveParentDataset(createDatasetRequest.getParentDatasetId(), dataset.getId());
|
||||
dataset.setParentDatasetId(parentDataset == null ? null : parentDataset.getId());
|
||||
dataset.initCreateParam(datasetBasePath);
|
||||
// 处理标签
|
||||
Set<Tag> processedTags = Optional.ofNullable(createDatasetRequest.getTags())
|
||||
.filter(CollectionUtils::isNotEmpty)
|
||||
.map(this::processTagNames)
|
||||
.orElseGet(HashSet::new);
|
||||
dataset.setTags(processedTags);
|
||||
datasetRepository.save(dataset);
|
||||
|
||||
//todo 需要解耦这块逻辑
|
||||
if (StringUtils.hasText(createDatasetRequest.getDataSource())) {
|
||||
// 数据源id不为空,使用异步线程进行文件扫盘落库
|
||||
processDataSourceAsync(dataset.getId(), createDatasetRequest.getDataSource());
|
||||
}
|
||||
return dataset;
|
||||
}
|
||||
|
||||
public String getDatasetPvcName() {
|
||||
return sysParamService.getParamByKey(DATASET_PVC_NAME);
|
||||
}
|
||||
|
||||
public Dataset updateDataset(String datasetId, UpdateDatasetRequest updateDatasetRequest) {
|
||||
Dataset dataset = datasetRepository.getById(datasetId);
|
||||
BusinessAssert.notNull(dataset, DataManagementErrorCode.DATASET_NOT_FOUND);
|
||||
resourceAccessService.assertOwnerAccess(dataset.getCreatedBy());
|
||||
|
||||
if (StringUtils.hasText(updateDatasetRequest.getName())) {
|
||||
dataset.setName(updateDatasetRequest.getName());
|
||||
}
|
||||
if (StringUtils.hasText(updateDatasetRequest.getDescription())) {
|
||||
dataset.setDescription(updateDatasetRequest.getDescription());
|
||||
}
|
||||
if (CollectionUtils.isNotEmpty(updateDatasetRequest.getTags())) {
|
||||
dataset.setTags(processTagNames(updateDatasetRequest.getTags()));
|
||||
}
|
||||
if (Objects.nonNull(updateDatasetRequest.getStatus())) {
|
||||
dataset.setStatus(updateDatasetRequest.getStatus());
|
||||
}
|
||||
if (updateDatasetRequest.isParentDatasetIdProvided()) {
|
||||
// 保存原始的 parentDatasetId 值,用于比较是否发生了变化
|
||||
String originalParentDatasetId = dataset.getParentDatasetId();
|
||||
|
||||
// 处理父数据集变更:仅当请求显式包含 parentDatasetId 时处理
|
||||
// handleParentChange 内部通过 normalizeParentId 方法将空字符串和 null 都转换为 null
|
||||
// 这样既支持设置新的父数据集,也支持清除关联
|
||||
handleParentChange(dataset, updateDatasetRequest.getParentDatasetId());
|
||||
|
||||
// 检查 parentDatasetId 是否发生了变化
|
||||
if (!Objects.equals(originalParentDatasetId, dataset.getParentDatasetId())) {
|
||||
// 使用 LambdaUpdateWrapper 显式地更新 parentDatasetId 字段
|
||||
// 这样即使值为 null 也能被正确更新到数据库
|
||||
datasetRepository.update(null, new LambdaUpdateWrapper<Dataset>()
|
||||
.eq(Dataset::getId, datasetId)
|
||||
.set(Dataset::getParentDatasetId, dataset.getParentDatasetId()));
|
||||
}
|
||||
}
|
||||
|
||||
if (StringUtils.hasText(updateDatasetRequest.getDataSource())) {
|
||||
// 数据源id不为空,使用异步线程进行文件扫盘落库
|
||||
processDataSourceAsync(dataset.getId(), updateDatasetRequest.getDataSource());
|
||||
}
|
||||
|
||||
// 更新其他字段(不包括 parentDatasetId,因为它已经在上面的代码中更新了)
|
||||
datasetRepository.updateById(dataset);
|
||||
return dataset;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除数据集
|
||||
*/
|
||||
@Transactional
|
||||
public void deleteDataset(String datasetId) {
|
||||
Dataset dataset = datasetRepository.getById(datasetId);
|
||||
BusinessAssert.notNull(dataset, DataManagementErrorCode.DATASET_NOT_FOUND);
|
||||
resourceAccessService.assertOwnerAccess(dataset.getCreatedBy());
|
||||
long childCount = datasetRepository.countByParentId(datasetId);
|
||||
BusinessAssert.isTrue(childCount == 0, DataManagementErrorCode.DATASET_HAS_CHILDREN);
|
||||
datasetRepository.removeById(datasetId);
|
||||
ChunksSaver.deleteFolder(dataset.getPath());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取数据集详情
|
||||
*/
|
||||
@Transactional(readOnly = true)
|
||||
public Dataset getDataset(String datasetId) {
|
||||
Dataset dataset = datasetRepository.getById(datasetId);
|
||||
BusinessAssert.notNull(dataset, DataManagementErrorCode.DATASET_NOT_FOUND);
|
||||
resourceAccessService.assertOwnerAccess(dataset.getCreatedBy());
|
||||
List<DatasetFile> datasetFiles = datasetFileRepository.findAllVisibleByDatasetId(datasetId);
|
||||
dataset.setFiles(datasetFiles);
|
||||
applyVisibleFileCounts(Collections.singletonList(dataset));
|
||||
return dataset;
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页查询数据集
|
||||
*/
|
||||
@Transactional(readOnly = true)
|
||||
public PagedResponse<DatasetResponse> getDatasets(DatasetPagingQuery query) {
|
||||
IPage<Dataset> page = new Page<>(query.getPage(), query.getSize());
|
||||
String ownerFilterUserId = resourceAccessService.resolveOwnerFilterUserId();
|
||||
page = datasetRepository.findByCriteria(page, query, ownerFilterUserId);
|
||||
String datasetPvcName = getDatasetPvcName();
|
||||
applyVisibleFileCounts(page.getRecords());
|
||||
List<DatasetResponse> datasetResponses = DatasetConverter.INSTANCE.convertToResponse(page.getRecords());
|
||||
datasetResponses.forEach(dataset -> dataset.setPvcName(datasetPvcName));
|
||||
return PagedResponse.of(datasetResponses, page.getCurrent(), page.getTotal(), page.getPages());
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public List<DatasetResponse> getSimilarDatasets(String datasetId, Integer limit) {
|
||||
BusinessAssert.isTrue(StringUtils.hasText(datasetId), CommonErrorCode.PARAM_ERROR);
|
||||
Dataset dataset = datasetRepository.getById(datasetId);
|
||||
BusinessAssert.notNull(dataset, DataManagementErrorCode.DATASET_NOT_FOUND);
|
||||
resourceAccessService.assertOwnerAccess(dataset.getCreatedBy());
|
||||
Set<String> sourceTags = normalizeTagNames(dataset.getTags());
|
||||
if (sourceTags.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
int safeLimit = normalizeSimilarLimit(limit);
|
||||
int candidateLimit = Math.min(
|
||||
SIMILAR_DATASET_CANDIDATE_MAX,
|
||||
Math.max(safeLimit * SIMILAR_DATASET_CANDIDATE_FACTOR, safeLimit)
|
||||
);
|
||||
String ownerFilterUserId = resourceAccessService.resolveOwnerFilterUserId();
|
||||
List<Dataset> candidates = datasetRepository.findSimilarByTags(
|
||||
new ArrayList<>(sourceTags),
|
||||
datasetId,
|
||||
candidateLimit,
|
||||
ownerFilterUserId
|
||||
);
|
||||
if (CollectionUtils.isEmpty(candidates)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
Map<String, Integer> scoreMap = new HashMap<>();
|
||||
for (Dataset candidate : candidates) {
|
||||
int score = countSharedTags(sourceTags, candidate.getTags());
|
||||
if (score > 0 && candidate.getId() != null) {
|
||||
scoreMap.put(candidate.getId(), score);
|
||||
}
|
||||
}
|
||||
String datasetPvcName = getDatasetPvcName();
|
||||
List<Dataset> sorted = candidates.stream()
|
||||
.filter(candidate -> candidate.getId() != null && scoreMap.containsKey(candidate.getId()))
|
||||
.sorted((left, right) -> {
|
||||
int leftScore = scoreMap.getOrDefault(left.getId(), 0);
|
||||
int rightScore = scoreMap.getOrDefault(right.getId(), 0);
|
||||
if (leftScore != rightScore) {
|
||||
return Integer.compare(rightScore, leftScore);
|
||||
}
|
||||
return resolveUpdatedTime(right).compareTo(resolveUpdatedTime(left));
|
||||
})
|
||||
.limit(safeLimit)
|
||||
.toList();
|
||||
applyVisibleFileCounts(sorted);
|
||||
List<DatasetResponse> responses = DatasetConverter.INSTANCE.convertToResponse(sorted);
|
||||
responses.forEach(item -> item.setPvcName(datasetPvcName));
|
||||
return responses;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理标签名称,创建或获取标签
|
||||
*/
|
||||
private Set<Tag> processTagNames(List<String> tagNames) {
|
||||
Set<Tag> tags = new HashSet<>();
|
||||
for (String tagName : tagNames) {
|
||||
Tag tag = tagMapper.findByName(tagName);
|
||||
if (tag == null) {
|
||||
Tag newTag = new Tag(tagName, null, null, "#007bff");
|
||||
newTag.setUsageCount(0L);
|
||||
newTag.setId(UUID.randomUUID().toString());
|
||||
tagMapper.insert(newTag);
|
||||
tag = newTag;
|
||||
}
|
||||
tag.setUsageCount(tag.getUsageCount() == null ? 1L : tag.getUsageCount() + 1);
|
||||
tagMapper.updateUsageCount(tag.getId(), tag.getUsageCount());
|
||||
tags.add(tag);
|
||||
}
|
||||
return tags;
|
||||
}
|
||||
|
||||
private int normalizeSimilarLimit(Integer limit) {
|
||||
if (limit == null || limit <= 0) {
|
||||
return SIMILAR_DATASET_DEFAULT_LIMIT;
|
||||
}
|
||||
return Math.min(limit, SIMILAR_DATASET_MAX_LIMIT);
|
||||
}
|
||||
|
||||
private Set<String> normalizeTagNames(Collection<Tag> tags) {
|
||||
if (CollectionUtils.isEmpty(tags)) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
Set<String> normalized = new HashSet<>();
|
||||
for (Tag tag : tags) {
|
||||
if (tag == null || !StringUtils.hasText(tag.getName())) {
|
||||
continue;
|
||||
}
|
||||
normalized.add(tag.getName().trim());
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
private int countSharedTags(Set<String> sourceTags, Collection<Tag> targetTags) {
|
||||
if (sourceTags.isEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
Set<String> targetTagNames = normalizeTagNames(targetTags);
|
||||
if (targetTagNames.isEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
int count = 0;
|
||||
for (String tagName : targetTagNames) {
|
||||
if (sourceTags.contains(tagName)) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
private LocalDateTime resolveUpdatedTime(Dataset dataset) {
|
||||
if (dataset == null) {
|
||||
return LocalDateTime.MIN;
|
||||
}
|
||||
if (dataset.getUpdatedAt() != null) {
|
||||
return dataset.getUpdatedAt();
|
||||
}
|
||||
if (dataset.getCreatedAt() != null) {
|
||||
return dataset.getCreatedAt();
|
||||
}
|
||||
return LocalDateTime.MIN;
|
||||
}
|
||||
|
||||
private Dataset resolveParentDataset(String parentDatasetId, String currentDatasetId) {
|
||||
String normalized = normalizeParentId(parentDatasetId);
|
||||
if (normalized == null) {
|
||||
return null;
|
||||
}
|
||||
BusinessAssert.isTrue(!normalized.equals(currentDatasetId), CommonErrorCode.PARAM_ERROR);
|
||||
Dataset parent = datasetRepository.getById(normalized);
|
||||
BusinessAssert.notNull(parent, DataManagementErrorCode.DATASET_NOT_FOUND);
|
||||
BusinessAssert.isTrue(parent.getParentDatasetId() == null, CommonErrorCode.PARAM_ERROR);
|
||||
return parent;
|
||||
}
|
||||
|
||||
private void handleParentChange(Dataset dataset, String parentDatasetId) {
|
||||
String normalized = normalizeParentId(parentDatasetId);
|
||||
String expectedPath = buildDatasetPath(datasetBasePath, dataset.getId());
|
||||
if (Objects.equals(dataset.getParentDatasetId(), normalized)
|
||||
&& Objects.equals(dataset.getPath(), expectedPath)) {
|
||||
return;
|
||||
}
|
||||
long childCount = datasetRepository.countByParentId(dataset.getId());
|
||||
if (childCount > 0 && normalized != null) {
|
||||
throw BusinessException.of(DataManagementErrorCode.DATASET_HAS_CHILDREN);
|
||||
}
|
||||
Dataset parent = normalized == null ? null : resolveParentDataset(normalized, dataset.getId());
|
||||
moveDatasetPath(dataset, expectedPath);
|
||||
dataset.setParentDatasetId(parent == null ? null : parent.getId());
|
||||
}
|
||||
|
||||
private String normalizeParentId(String parentDatasetId) {
|
||||
if (!StringUtils.hasText(parentDatasetId)) {
|
||||
return null;
|
||||
}
|
||||
return parentDatasetId.trim();
|
||||
}
|
||||
|
||||
private String buildDatasetPath(String basePath, String datasetId) {
|
||||
String normalized = basePath;
|
||||
while (normalized.endsWith(File.separator)) {
|
||||
normalized = normalized.substring(0, normalized.length() - 1);
|
||||
}
|
||||
return normalized + File.separator + datasetId;
|
||||
}
|
||||
|
||||
private void moveDatasetPath(Dataset dataset, String newPath) {
|
||||
String oldPath = dataset.getPath();
|
||||
if (Objects.equals(oldPath, newPath)) {
|
||||
return;
|
||||
}
|
||||
Path sourcePath = Paths.get(oldPath);
|
||||
Path targetPath = Paths.get(newPath);
|
||||
try {
|
||||
if (Files.exists(sourcePath)) {
|
||||
if (Files.exists(targetPath)) {
|
||||
throw BusinessException.of(CommonErrorCode.PARAM_ERROR);
|
||||
}
|
||||
Files.createDirectories(targetPath.getParent());
|
||||
Files.move(sourcePath, targetPath);
|
||||
} else {
|
||||
Files.createDirectories(targetPath);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
log.error("move dataset path error, from {} to {}", oldPath, newPath, e);
|
||||
throw BusinessException.of(SystemErrorCode.FILE_SYSTEM_ERROR);
|
||||
}
|
||||
datasetFileRepository.updateFilePathPrefix(dataset.getId(), oldPath, newPath);
|
||||
dataset.setPath(newPath);
|
||||
}
|
||||
|
||||
private void applyVisibleFileCounts(List<Dataset> datasets) {
|
||||
if (CollectionUtils.isEmpty(datasets)) {
|
||||
return;
|
||||
}
|
||||
List<String> datasetIds = datasets.stream()
|
||||
.filter(Objects::nonNull)
|
||||
.map(Dataset::getId)
|
||||
.filter(StringUtils::hasText)
|
||||
.toList();
|
||||
if (datasetIds.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
Map<String, Long> countMap = datasetFileRepository.countNonDerivedByDatasetIds(datasetIds).stream()
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toMap(
|
||||
DatasetFileCount::getDatasetId,
|
||||
count -> Optional.ofNullable(count.getFileCount()).orElse(0L),
|
||||
(left, right) -> left
|
||||
));
|
||||
for (Dataset dataset : datasets) {
|
||||
if (dataset == null || !StringUtils.hasText(dataset.getId())) {
|
||||
continue;
|
||||
}
|
||||
Long visibleCount = countMap.get(dataset.getId());
|
||||
dataset.setFileCount(visibleCount != null ? visibleCount : 0L);
|
||||
}
|
||||
}
|
||||
|
||||
private List<DatasetFile> filterVisibleFiles(List<DatasetFile> files) {
|
||||
if (CollectionUtils.isEmpty(files)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return files.stream()
|
||||
.filter(file -> !isDerivedFile(file))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private boolean isDerivedFile(DatasetFile datasetFile) {
|
||||
if (datasetFile == null) {
|
||||
return false;
|
||||
}
|
||||
String metadata = datasetFile.getMetadata();
|
||||
if (!StringUtils.hasText(metadata)) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
Map<String, Object> metadataMap = mapper.readValue(metadata, new TypeReference<Map<String, Object>>() {});
|
||||
return metadataMap.get(DERIVED_METADATA_KEY) != null;
|
||||
} catch (Exception e) {
|
||||
log.debug("Failed to parse dataset file metadata for derived detection: {}", datasetFile.getId(), e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取数据集统计信息
|
||||
*/
|
||||
@Transactional(readOnly = true)
|
||||
public Map<String, Object> getDatasetStatistics(String datasetId) {
|
||||
Dataset dataset = datasetRepository.getById(datasetId);
|
||||
if (dataset == null) {
|
||||
throw new IllegalArgumentException("Dataset not found: " + datasetId);
|
||||
}
|
||||
resourceAccessService.assertOwnerAccess(dataset.getCreatedBy());
|
||||
|
||||
Map<String, Object> statistics = new HashMap<>();
|
||||
|
||||
List<DatasetFile> allFiles = datasetFileRepository.findAllVisibleByDatasetId(datasetId);
|
||||
List<DatasetFile> visibleFiles = filterVisibleFiles(allFiles);
|
||||
long totalFiles = visibleFiles.size();
|
||||
long completedFiles = visibleFiles.stream()
|
||||
.filter(file -> "COMPLETED".equalsIgnoreCase(file.getStatus()))
|
||||
.count();
|
||||
Long totalSize = datasetFileRepository.sumSizeByDatasetId(datasetId);
|
||||
|
||||
statistics.put("totalFiles", (int) totalFiles);
|
||||
statistics.put("completedFiles", (int) completedFiles);
|
||||
statistics.put("totalSize", totalSize != null ? totalSize : 0L);
|
||||
|
||||
// 完成率计算
|
||||
float completionRate = 0.0f;
|
||||
if (totalFiles > 0) {
|
||||
completionRate = ((float) completedFiles) / (float) totalFiles * 100.0f;
|
||||
}
|
||||
statistics.put("completionRate", completionRate);
|
||||
|
||||
// 文件类型分布统计
|
||||
Map<String, Integer> fileTypeDistribution = new HashMap<>();
|
||||
if (!visibleFiles.isEmpty()) {
|
||||
for (DatasetFile file : visibleFiles) {
|
||||
String fileType = file.getFileType() != null ? file.getFileType() : "unknown";
|
||||
fileTypeDistribution.put(fileType, fileTypeDistribution.getOrDefault(fileType, 0) + 1);
|
||||
}
|
||||
}
|
||||
statistics.put("fileTypeDistribution", fileTypeDistribution);
|
||||
|
||||
// 状态分布统计
|
||||
Map<String, Integer> statusDistribution = new HashMap<>();
|
||||
if (!visibleFiles.isEmpty()) {
|
||||
for (DatasetFile file : visibleFiles) {
|
||||
String status = file.getStatus() != null ? file.getStatus() : "unknown";
|
||||
statusDistribution.put(status, statusDistribution.getOrDefault(status, 0) + 1);
|
||||
}
|
||||
}
|
||||
statistics.put("statusDistribution", statusDistribution);
|
||||
|
||||
return statistics;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有数据集的汇总统计信息
|
||||
*/
|
||||
public AllDatasetStatisticsResponse getAllDatasetStatistics() {
|
||||
if (resourceAccessService.isAdmin()) {
|
||||
return datasetRepository.getAllDatasetStatistics();
|
||||
}
|
||||
String currentUserId = resourceAccessService.requireCurrentUserId();
|
||||
return datasetRepository.getAllDatasetStatisticsByCreatedBy(currentUserId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 异步处理数据源文件扫描
|
||||
*
|
||||
* @param datasetId 数据集ID
|
||||
* @param dataSourceId 数据源ID(归集任务ID)
|
||||
*/
|
||||
@Async
|
||||
public void processDataSourceAsync(String datasetId, String dataSourceId) {
|
||||
try {
|
||||
log.info("Initiating data source file scanning, dataset ID: {}, collection task ID: {}", datasetId, dataSourceId);
|
||||
CollectionTaskDetailResponse taskDetail = collectionTaskClient.getTaskDetail(dataSourceId).getData();
|
||||
if (taskDetail == null) {
|
||||
log.warn("Fail to get collection task detail, task ID: {}", dataSourceId);
|
||||
return;
|
||||
}
|
||||
Path targetPath = Paths.get(taskDetail.getTargetPath());
|
||||
if (!Files.exists(targetPath) || !Files.isDirectory(targetPath)) {
|
||||
log.warn("Target path not exists or is not a directory: {}", taskDetail.getTargetPath());
|
||||
return;
|
||||
}
|
||||
List<String> filePaths = scanFilePaths(targetPath);
|
||||
if (CollectionUtils.isEmpty(filePaths)) {
|
||||
return;
|
||||
}
|
||||
datasetFileApplicationService.copyFilesToDatasetDirWithSourceRoot(datasetId, targetPath, filePaths);
|
||||
log.info("Success file scan, total files: {}", filePaths.size());
|
||||
} catch (Exception e) {
|
||||
log.error("处理数据源文件扫描失败,数据集ID: {}, 数据源ID: {}", datasetId, dataSourceId, e);
|
||||
}
|
||||
}
|
||||
|
||||
private List<String> scanFilePaths(Path targetPath) {
|
||||
try (Stream<Path> paths = Files.walk(targetPath)) {
|
||||
return paths
|
||||
.filter(Files::isRegularFile)
|
||||
.map(Path::toString)
|
||||
.collect(Collectors.toList());
|
||||
} catch (IOException e) {
|
||||
log.error("Fail to scan directory: {}", targetPath, e);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
}
|
||||
-1515
File diff suppressed because it is too large
Load Diff
-171
@@ -1,171 +0,0 @@
|
||||
package com.datamate.datamanagement.application;
|
||||
|
||||
import com.datamate.datamanagement.common.enums.KnowledgeItemPreviewStatus;
|
||||
import com.datamate.datamanagement.domain.model.dataset.DatasetFile;
|
||||
import com.datamate.datamanagement.infrastructure.config.DataManagementProperties;
|
||||
import com.datamate.datamanagement.infrastructure.persistence.repository.DatasetFileRepository;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 数据集文件预览转换异步任务
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class DatasetFilePreviewAsyncService {
|
||||
private static final Set<String> OFFICE_EXTENSIONS = Set.of("doc", "docx");
|
||||
private static final String DATASET_PREVIEW_DIR = "dataset-previews";
|
||||
private static final String PREVIEW_FILE_SUFFIX = ".pdf";
|
||||
private static final String PATH_SEPARATOR = "/";
|
||||
private static final int MAX_ERROR_LENGTH = 500;
|
||||
private static final DateTimeFormatter PREVIEW_TIME_FORMATTER = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
|
||||
|
||||
private final DatasetFileRepository datasetFileRepository;
|
||||
private final DataManagementProperties dataManagementProperties;
|
||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
@Async
|
||||
public void convertPreviewAsync(String fileId) {
|
||||
if (StringUtils.isBlank(fileId)) {
|
||||
return;
|
||||
}
|
||||
DatasetFile file = datasetFileRepository.getById(fileId);
|
||||
if (file == null) {
|
||||
return;
|
||||
}
|
||||
String extension = resolveFileExtension(resolveOriginalName(file));
|
||||
if (!OFFICE_EXTENSIONS.contains(extension)) {
|
||||
updatePreviewStatus(file, KnowledgeItemPreviewStatus.FAILED, null, "仅支持 DOC/DOCX 转换");
|
||||
return;
|
||||
}
|
||||
if (StringUtils.isBlank(file.getFilePath())) {
|
||||
updatePreviewStatus(file, KnowledgeItemPreviewStatus.FAILED, null, "源文件路径为空");
|
||||
return;
|
||||
}
|
||||
Path sourcePath = Paths.get(file.getFilePath()).toAbsolutePath().normalize();
|
||||
if (!Files.exists(sourcePath) || !Files.isRegularFile(sourcePath)) {
|
||||
updatePreviewStatus(file, KnowledgeItemPreviewStatus.FAILED, null, "源文件不存在");
|
||||
return;
|
||||
}
|
||||
|
||||
KnowledgeItemPreviewMetadataHelper.PreviewInfo previewInfo = KnowledgeItemPreviewMetadataHelper
|
||||
.readPreviewInfo(file.getMetadata(), objectMapper);
|
||||
String previewRelativePath = StringUtils.defaultIfBlank(
|
||||
previewInfo.pdfPath(),
|
||||
resolvePreviewRelativePath(file.getDatasetId(), file.getId())
|
||||
);
|
||||
Path targetPath = resolvePreviewStoragePath(previewRelativePath);
|
||||
|
||||
try {
|
||||
ensureParentDirectory(targetPath);
|
||||
LibreOfficeConverter.convertToPdf(sourcePath, targetPath);
|
||||
updatePreviewStatus(file, KnowledgeItemPreviewStatus.READY, previewRelativePath, null);
|
||||
} catch (Exception e) {
|
||||
log.error("dataset preview convert failed, fileId: {}", file.getId(), e);
|
||||
updatePreviewStatus(file, KnowledgeItemPreviewStatus.FAILED, previewRelativePath, trimError(e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
private void updatePreviewStatus(
|
||||
DatasetFile file,
|
||||
KnowledgeItemPreviewStatus status,
|
||||
String previewRelativePath,
|
||||
String error
|
||||
) {
|
||||
if (file == null) {
|
||||
return;
|
||||
}
|
||||
String updatedMetadata = KnowledgeItemPreviewMetadataHelper.applyPreviewInfo(
|
||||
file.getMetadata(),
|
||||
objectMapper,
|
||||
status,
|
||||
previewRelativePath,
|
||||
error,
|
||||
nowText()
|
||||
);
|
||||
file.setMetadata(updatedMetadata);
|
||||
datasetFileRepository.updateById(file);
|
||||
}
|
||||
|
||||
private String resolveOriginalName(DatasetFile file) {
|
||||
if (file == null) {
|
||||
return "";
|
||||
}
|
||||
if (StringUtils.isNotBlank(file.getFileName())) {
|
||||
return file.getFileName();
|
||||
}
|
||||
if (StringUtils.isNotBlank(file.getFilePath())) {
|
||||
return Paths.get(file.getFilePath()).getFileName().toString();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
private String resolveFileExtension(String fileName) {
|
||||
if (StringUtils.isBlank(fileName)) {
|
||||
return "";
|
||||
}
|
||||
int dotIndex = fileName.lastIndexOf('.');
|
||||
if (dotIndex <= 0 || dotIndex >= fileName.length() - 1) {
|
||||
return "";
|
||||
}
|
||||
return fileName.substring(dotIndex + 1).toLowerCase();
|
||||
}
|
||||
|
||||
private String resolvePreviewRelativePath(String datasetId, String fileId) {
|
||||
String relativePath = Paths.get(DATASET_PREVIEW_DIR, datasetId, fileId + PREVIEW_FILE_SUFFIX)
|
||||
.toString();
|
||||
return relativePath.replace("\\", PATH_SEPARATOR);
|
||||
}
|
||||
|
||||
private Path resolvePreviewStoragePath(String relativePath) {
|
||||
String normalizedRelativePath = StringUtils.defaultString(relativePath).replace("/", java.io.File.separator);
|
||||
Path root = resolveUploadRootPath();
|
||||
Path target = root.resolve(normalizedRelativePath).toAbsolutePath().normalize();
|
||||
if (!target.startsWith(root)) {
|
||||
throw new IllegalArgumentException("invalid preview path");
|
||||
}
|
||||
return target;
|
||||
}
|
||||
|
||||
private Path resolveUploadRootPath() {
|
||||
String uploadDir = dataManagementProperties.getFileStorage().getUploadDir();
|
||||
return Paths.get(uploadDir).toAbsolutePath().normalize();
|
||||
}
|
||||
|
||||
private void ensureParentDirectory(Path targetPath) {
|
||||
try {
|
||||
Path parent = targetPath.getParent();
|
||||
if (parent != null) {
|
||||
Files.createDirectories(parent);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException("创建预览目录失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
private String trimError(String error) {
|
||||
if (StringUtils.isBlank(error)) {
|
||||
return "";
|
||||
}
|
||||
if (error.length() <= MAX_ERROR_LENGTH) {
|
||||
return error;
|
||||
}
|
||||
return error.substring(0, MAX_ERROR_LENGTH);
|
||||
}
|
||||
|
||||
private String nowText() {
|
||||
return LocalDateTime.now().format(PREVIEW_TIME_FORMATTER);
|
||||
}
|
||||
}
|
||||
-233
@@ -1,233 +0,0 @@
|
||||
package com.datamate.datamanagement.application;
|
||||
|
||||
import com.datamate.common.infrastructure.exception.BusinessAssert;
|
||||
import com.datamate.common.infrastructure.exception.CommonErrorCode;
|
||||
import com.datamate.datamanagement.common.enums.KnowledgeItemPreviewStatus;
|
||||
import com.datamate.datamanagement.domain.model.dataset.DatasetFile;
|
||||
import com.datamate.datamanagement.infrastructure.config.DataManagementProperties;
|
||||
import com.datamate.datamanagement.infrastructure.persistence.repository.DatasetFileRepository;
|
||||
import com.datamate.datamanagement.interfaces.dto.DatasetFilePreviewStatusResponse;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 数据集文件预览转换服务
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class DatasetFilePreviewService {
|
||||
private static final Set<String> OFFICE_EXTENSIONS = Set.of("doc", "docx");
|
||||
private static final String DATASET_PREVIEW_DIR = "dataset-previews";
|
||||
private static final String PREVIEW_FILE_SUFFIX = ".pdf";
|
||||
private static final String PATH_SEPARATOR = "/";
|
||||
private static final DateTimeFormatter PREVIEW_TIME_FORMATTER = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
|
||||
|
||||
private final DatasetFileRepository datasetFileRepository;
|
||||
private final DataManagementProperties dataManagementProperties;
|
||||
private final DatasetFilePreviewAsyncService datasetFilePreviewAsyncService;
|
||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
public DatasetFilePreviewStatusResponse getPreviewStatus(String datasetId, String fileId) {
|
||||
DatasetFile file = requireDatasetFile(datasetId, fileId);
|
||||
assertOfficeDocument(file);
|
||||
KnowledgeItemPreviewMetadataHelper.PreviewInfo previewInfo = KnowledgeItemPreviewMetadataHelper
|
||||
.readPreviewInfo(file.getMetadata(), objectMapper);
|
||||
|
||||
if (previewInfo.status() == KnowledgeItemPreviewStatus.READY && !previewPdfExists(file, previewInfo)) {
|
||||
previewInfo = markPreviewFailed(file, previewInfo, "预览文件不存在");
|
||||
}
|
||||
|
||||
return buildResponse(previewInfo);
|
||||
}
|
||||
|
||||
public DatasetFilePreviewStatusResponse ensurePreview(String datasetId, String fileId) {
|
||||
DatasetFile file = requireDatasetFile(datasetId, fileId);
|
||||
assertOfficeDocument(file);
|
||||
KnowledgeItemPreviewMetadataHelper.PreviewInfo previewInfo = KnowledgeItemPreviewMetadataHelper
|
||||
.readPreviewInfo(file.getMetadata(), objectMapper);
|
||||
|
||||
if (previewInfo.status() == KnowledgeItemPreviewStatus.READY && previewPdfExists(file, previewInfo)) {
|
||||
return buildResponse(previewInfo);
|
||||
}
|
||||
if (previewInfo.status() == KnowledgeItemPreviewStatus.PROCESSING) {
|
||||
return buildResponse(previewInfo);
|
||||
}
|
||||
|
||||
String previewRelativePath = resolvePreviewRelativePath(file.getDatasetId(), file.getId());
|
||||
String updatedMetadata = KnowledgeItemPreviewMetadataHelper.applyPreviewInfo(
|
||||
file.getMetadata(),
|
||||
objectMapper,
|
||||
KnowledgeItemPreviewStatus.PROCESSING,
|
||||
previewRelativePath,
|
||||
null,
|
||||
nowText()
|
||||
);
|
||||
file.setMetadata(updatedMetadata);
|
||||
datasetFileRepository.updateById(file);
|
||||
datasetFilePreviewAsyncService.convertPreviewAsync(file.getId());
|
||||
|
||||
KnowledgeItemPreviewMetadataHelper.PreviewInfo refreshed = KnowledgeItemPreviewMetadataHelper
|
||||
.readPreviewInfo(updatedMetadata, objectMapper);
|
||||
return buildResponse(refreshed);
|
||||
}
|
||||
|
||||
public boolean isOfficeDocument(String fileName) {
|
||||
String extension = resolveFileExtension(fileName);
|
||||
return StringUtils.isNotBlank(extension) && OFFICE_EXTENSIONS.contains(extension.toLowerCase());
|
||||
}
|
||||
|
||||
public PreviewFile resolveReadyPreviewFile(String datasetId, DatasetFile file) {
|
||||
if (file == null) {
|
||||
return null;
|
||||
}
|
||||
KnowledgeItemPreviewMetadataHelper.PreviewInfo previewInfo = KnowledgeItemPreviewMetadataHelper
|
||||
.readPreviewInfo(file.getMetadata(), objectMapper);
|
||||
if (previewInfo.status() != KnowledgeItemPreviewStatus.READY) {
|
||||
return null;
|
||||
}
|
||||
String relativePath = StringUtils.defaultIfBlank(previewInfo.pdfPath(), resolvePreviewRelativePath(datasetId, file.getId()));
|
||||
Path filePath = resolvePreviewStoragePath(relativePath);
|
||||
if (!Files.exists(filePath) || !Files.isRegularFile(filePath)) {
|
||||
markPreviewFailed(file, previewInfo, "预览文件不存在");
|
||||
return null;
|
||||
}
|
||||
String previewName = resolvePreviewPdfName(file);
|
||||
return new PreviewFile(filePath, previewName);
|
||||
}
|
||||
|
||||
public void deletePreviewFileQuietly(String datasetId, String fileId) {
|
||||
String relativePath = resolvePreviewRelativePath(datasetId, fileId);
|
||||
Path filePath = resolvePreviewStoragePath(relativePath);
|
||||
try {
|
||||
Files.deleteIfExists(filePath);
|
||||
} catch (Exception e) {
|
||||
log.warn("delete dataset preview pdf error, fileId: {}", fileId, e);
|
||||
}
|
||||
}
|
||||
|
||||
private DatasetFilePreviewStatusResponse buildResponse(KnowledgeItemPreviewMetadataHelper.PreviewInfo previewInfo) {
|
||||
DatasetFilePreviewStatusResponse response = new DatasetFilePreviewStatusResponse();
|
||||
KnowledgeItemPreviewStatus status = previewInfo.status() == null
|
||||
? KnowledgeItemPreviewStatus.PENDING
|
||||
: previewInfo.status();
|
||||
response.setStatus(status);
|
||||
response.setPreviewError(previewInfo.error());
|
||||
response.setUpdatedAt(previewInfo.updatedAt());
|
||||
return response;
|
||||
}
|
||||
|
||||
private DatasetFile requireDatasetFile(String datasetId, String fileId) {
|
||||
BusinessAssert.isTrue(StringUtils.isNotBlank(datasetId), CommonErrorCode.PARAM_ERROR);
|
||||
BusinessAssert.isTrue(StringUtils.isNotBlank(fileId), CommonErrorCode.PARAM_ERROR);
|
||||
DatasetFile datasetFile = datasetFileRepository.getById(fileId);
|
||||
BusinessAssert.notNull(datasetFile, CommonErrorCode.PARAM_ERROR);
|
||||
BusinessAssert.isTrue(Objects.equals(datasetFile.getDatasetId(), datasetId), CommonErrorCode.PARAM_ERROR);
|
||||
return datasetFile;
|
||||
}
|
||||
|
||||
private void assertOfficeDocument(DatasetFile file) {
|
||||
BusinessAssert.notNull(file, CommonErrorCode.PARAM_ERROR);
|
||||
String extension = resolveFileExtension(resolveOriginalName(file));
|
||||
BusinessAssert.isTrue(OFFICE_EXTENSIONS.contains(extension), CommonErrorCode.PARAM_ERROR);
|
||||
}
|
||||
|
||||
private String resolveOriginalName(DatasetFile file) {
|
||||
if (file == null) {
|
||||
return "";
|
||||
}
|
||||
if (StringUtils.isNotBlank(file.getFileName())) {
|
||||
return file.getFileName();
|
||||
}
|
||||
if (StringUtils.isNotBlank(file.getFilePath())) {
|
||||
return Paths.get(file.getFilePath()).getFileName().toString();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
private String resolveFileExtension(String fileName) {
|
||||
if (StringUtils.isBlank(fileName)) {
|
||||
return "";
|
||||
}
|
||||
int dotIndex = fileName.lastIndexOf('.');
|
||||
if (dotIndex <= 0 || dotIndex >= fileName.length() - 1) {
|
||||
return "";
|
||||
}
|
||||
return fileName.substring(dotIndex + 1).toLowerCase();
|
||||
}
|
||||
|
||||
private String resolvePreviewPdfName(DatasetFile file) {
|
||||
String originalName = resolveOriginalName(file);
|
||||
if (StringUtils.isBlank(originalName)) {
|
||||
return "预览.pdf";
|
||||
}
|
||||
int dotIndex = originalName.lastIndexOf('.');
|
||||
if (dotIndex <= 0) {
|
||||
return originalName + PREVIEW_FILE_SUFFIX;
|
||||
}
|
||||
return originalName.substring(0, dotIndex) + PREVIEW_FILE_SUFFIX;
|
||||
}
|
||||
|
||||
private boolean previewPdfExists(DatasetFile file, KnowledgeItemPreviewMetadataHelper.PreviewInfo previewInfo) {
|
||||
String relativePath = StringUtils.defaultIfBlank(previewInfo.pdfPath(), resolvePreviewRelativePath(file.getDatasetId(), file.getId()));
|
||||
Path filePath = resolvePreviewStoragePath(relativePath);
|
||||
return Files.exists(filePath) && Files.isRegularFile(filePath);
|
||||
}
|
||||
|
||||
private KnowledgeItemPreviewMetadataHelper.PreviewInfo markPreviewFailed(
|
||||
DatasetFile file,
|
||||
KnowledgeItemPreviewMetadataHelper.PreviewInfo previewInfo,
|
||||
String error
|
||||
) {
|
||||
String relativePath = StringUtils.defaultIfBlank(previewInfo.pdfPath(), resolvePreviewRelativePath(file.getDatasetId(), file.getId()));
|
||||
String updatedMetadata = KnowledgeItemPreviewMetadataHelper.applyPreviewInfo(
|
||||
file.getMetadata(),
|
||||
objectMapper,
|
||||
KnowledgeItemPreviewStatus.FAILED,
|
||||
relativePath,
|
||||
error,
|
||||
nowText()
|
||||
);
|
||||
file.setMetadata(updatedMetadata);
|
||||
datasetFileRepository.updateById(file);
|
||||
return KnowledgeItemPreviewMetadataHelper.readPreviewInfo(updatedMetadata, objectMapper);
|
||||
}
|
||||
|
||||
private String resolvePreviewRelativePath(String datasetId, String fileId) {
|
||||
String relativePath = Paths.get(DATASET_PREVIEW_DIR, datasetId, fileId + PREVIEW_FILE_SUFFIX)
|
||||
.toString();
|
||||
return relativePath.replace("\\", PATH_SEPARATOR);
|
||||
}
|
||||
|
||||
Path resolvePreviewStoragePath(String relativePath) {
|
||||
String normalizedRelativePath = StringUtils.defaultString(relativePath).replace("/", java.io.File.separator);
|
||||
Path root = resolveUploadRootPath();
|
||||
Path target = root.resolve(normalizedRelativePath).toAbsolutePath().normalize();
|
||||
BusinessAssert.isTrue(target.startsWith(root), CommonErrorCode.PARAM_ERROR);
|
||||
return target;
|
||||
}
|
||||
|
||||
private Path resolveUploadRootPath() {
|
||||
String uploadDir = dataManagementProperties.getFileStorage().getUploadDir();
|
||||
BusinessAssert.isTrue(StringUtils.isNotBlank(uploadDir), CommonErrorCode.PARAM_ERROR);
|
||||
return Paths.get(uploadDir).toAbsolutePath().normalize();
|
||||
}
|
||||
|
||||
private String nowText() {
|
||||
return LocalDateTime.now().format(PREVIEW_TIME_FORMATTER);
|
||||
}
|
||||
|
||||
public record PreviewFile(Path filePath, String fileName) {
|
||||
}
|
||||
}
|
||||
-155
@@ -1,155 +0,0 @@
|
||||
package com.datamate.datamanagement.application;
|
||||
|
||||
import com.datamate.common.auth.application.ResourceAccessService;
|
||||
import com.datamate.common.infrastructure.exception.BusinessAssert;
|
||||
import com.datamate.common.infrastructure.exception.CommonErrorCode;
|
||||
import com.datamate.common.infrastructure.exception.SystemErrorCode;
|
||||
import com.datamate.datamanagement.common.enums.KnowledgeStatusType;
|
||||
import com.datamate.datamanagement.domain.model.knowledge.KnowledgeItemDirectory;
|
||||
import com.datamate.datamanagement.domain.model.knowledge.KnowledgeSet;
|
||||
import com.datamate.datamanagement.infrastructure.exception.DataManagementErrorCode;
|
||||
import com.datamate.datamanagement.infrastructure.persistence.repository.KnowledgeItemDirectoryRepository;
|
||||
import com.datamate.datamanagement.infrastructure.persistence.repository.KnowledgeItemRepository;
|
||||
import com.datamate.datamanagement.infrastructure.persistence.repository.KnowledgeSetRepository;
|
||||
import com.datamate.datamanagement.interfaces.dto.CreateKnowledgeDirectoryRequest;
|
||||
import com.datamate.datamanagement.interfaces.dto.KnowledgeDirectoryQuery;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* 知识条目目录应用服务
|
||||
*/
|
||||
@Service
|
||||
@Transactional
|
||||
@RequiredArgsConstructor
|
||||
public class KnowledgeDirectoryApplicationService {
|
||||
private static final String PATH_SEPARATOR = "/";
|
||||
private static final String INVALID_PATH_SEGMENT = "..";
|
||||
|
||||
private final KnowledgeItemDirectoryRepository knowledgeItemDirectoryRepository;
|
||||
private final KnowledgeItemRepository knowledgeItemRepository;
|
||||
private final KnowledgeSetRepository knowledgeSetRepository;
|
||||
private final ResourceAccessService resourceAccessService;
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public List<KnowledgeItemDirectory> getKnowledgeDirectories(String setId, KnowledgeDirectoryQuery query) {
|
||||
BusinessAssert.notNull(query, CommonErrorCode.PARAM_ERROR);
|
||||
requireAccessibleKnowledgeSet(setId);
|
||||
query.setSetId(setId);
|
||||
return knowledgeItemDirectoryRepository.findByCriteria(query);
|
||||
}
|
||||
|
||||
public KnowledgeItemDirectory createKnowledgeDirectory(String setId, CreateKnowledgeDirectoryRequest request) {
|
||||
BusinessAssert.notNull(request, CommonErrorCode.PARAM_ERROR);
|
||||
KnowledgeSet knowledgeSet = requireAccessibleKnowledgeSet(setId);
|
||||
BusinessAssert.isTrue(!isReadOnlyStatus(knowledgeSet.getStatus()),
|
||||
DataManagementErrorCode.KNOWLEDGE_SET_STATUS_ERROR);
|
||||
|
||||
String directoryName = normalizeDirectoryName(request.getDirectoryName());
|
||||
validateDirectoryName(directoryName);
|
||||
|
||||
String parentPrefix = normalizeRelativePathPrefix(request.getParentPrefix());
|
||||
String relativePath = normalizeRelativePathValue(parentPrefix + directoryName);
|
||||
validateRelativePath(relativePath);
|
||||
|
||||
BusinessAssert.isTrue(!knowledgeItemRepository.existsBySetIdAndRelativePath(setId, relativePath),
|
||||
CommonErrorCode.PARAM_ERROR);
|
||||
|
||||
KnowledgeItemDirectory existing = knowledgeItemDirectoryRepository.findBySetIdAndPath(setId, relativePath);
|
||||
if (existing != null) {
|
||||
return existing;
|
||||
}
|
||||
|
||||
KnowledgeItemDirectory directory = new KnowledgeItemDirectory();
|
||||
directory.setId(UUID.randomUUID().toString());
|
||||
directory.setSetId(setId);
|
||||
directory.setName(directoryName);
|
||||
directory.setRelativePath(relativePath);
|
||||
knowledgeItemDirectoryRepository.save(directory);
|
||||
return directory;
|
||||
}
|
||||
|
||||
public void deleteKnowledgeDirectory(String setId, String relativePath) {
|
||||
KnowledgeSet knowledgeSet = requireAccessibleKnowledgeSet(setId);
|
||||
BusinessAssert.isTrue(!isReadOnlyStatus(knowledgeSet.getStatus()),
|
||||
DataManagementErrorCode.KNOWLEDGE_SET_STATUS_ERROR);
|
||||
|
||||
String normalized = normalizeRelativePathValue(relativePath);
|
||||
validateRelativePath(normalized);
|
||||
|
||||
knowledgeItemRepository.removeByRelativePathPrefix(setId, normalized);
|
||||
knowledgeItemDirectoryRepository.removeByRelativePathPrefix(setId, normalized);
|
||||
}
|
||||
|
||||
private KnowledgeSet requireKnowledgeSet(String setId) {
|
||||
KnowledgeSet knowledgeSet = knowledgeSetRepository.getById(setId);
|
||||
BusinessAssert.notNull(knowledgeSet, DataManagementErrorCode.KNOWLEDGE_SET_NOT_FOUND);
|
||||
return knowledgeSet;
|
||||
}
|
||||
|
||||
private KnowledgeSet requireAccessibleKnowledgeSet(String setId) {
|
||||
KnowledgeSet knowledgeSet = requireKnowledgeSet(setId);
|
||||
if (ResourceAccessService.CONFIDENTIAL_SENSITIVITY.equalsIgnoreCase(knowledgeSet.getSensitivity())) {
|
||||
BusinessAssert.isTrue(resourceAccessService.canViewConfidential(),
|
||||
SystemErrorCode.INSUFFICIENT_PERMISSIONS);
|
||||
}
|
||||
return knowledgeSet;
|
||||
}
|
||||
|
||||
private boolean isReadOnlyStatus(KnowledgeStatusType status) {
|
||||
return status == KnowledgeStatusType.ARCHIVED || status == KnowledgeStatusType.DEPRECATED;
|
||||
}
|
||||
|
||||
private String normalizeDirectoryName(String name) {
|
||||
return StringUtils.trimToEmpty(name);
|
||||
}
|
||||
|
||||
private void validateDirectoryName(String name) {
|
||||
BusinessAssert.isTrue(StringUtils.isNotBlank(name), CommonErrorCode.PARAM_ERROR);
|
||||
BusinessAssert.isTrue(!name.contains(PATH_SEPARATOR), CommonErrorCode.PARAM_ERROR);
|
||||
BusinessAssert.isTrue(!name.contains("\\"), CommonErrorCode.PARAM_ERROR);
|
||||
BusinessAssert.isTrue(!name.contains(INVALID_PATH_SEGMENT), CommonErrorCode.PARAM_ERROR);
|
||||
}
|
||||
|
||||
private void validateRelativePath(String relativePath) {
|
||||
BusinessAssert.isTrue(StringUtils.isNotBlank(relativePath), CommonErrorCode.PARAM_ERROR);
|
||||
BusinessAssert.isTrue(!relativePath.contains(INVALID_PATH_SEGMENT), CommonErrorCode.PARAM_ERROR);
|
||||
}
|
||||
|
||||
private String normalizeRelativePathPrefix(String prefix) {
|
||||
if (StringUtils.isBlank(prefix)) {
|
||||
return "";
|
||||
}
|
||||
String normalized = prefix.replace("\\", PATH_SEPARATOR).trim();
|
||||
while (normalized.startsWith(PATH_SEPARATOR)) {
|
||||
normalized = normalized.substring(1);
|
||||
}
|
||||
while (normalized.endsWith(PATH_SEPARATOR)) {
|
||||
normalized = normalized.substring(0, normalized.length() - 1);
|
||||
}
|
||||
if (StringUtils.isBlank(normalized)) {
|
||||
return "";
|
||||
}
|
||||
validateRelativePath(normalized);
|
||||
return normalized + PATH_SEPARATOR;
|
||||
}
|
||||
|
||||
private String normalizeRelativePathValue(String relativePath) {
|
||||
if (StringUtils.isBlank(relativePath)) {
|
||||
return "";
|
||||
}
|
||||
String normalized = relativePath.replace("\\", PATH_SEPARATOR).trim();
|
||||
while (normalized.startsWith(PATH_SEPARATOR)) {
|
||||
normalized = normalized.substring(1);
|
||||
}
|
||||
while (normalized.endsWith(PATH_SEPARATOR)) {
|
||||
normalized = normalized.substring(0, normalized.length() - 1);
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user