mirror of
https://github.com/hpd840321/craftlabs-authorization-sdk.git
synced 2026-06-09 10:00:30 +08:00
Compare commits
131 Commits
main
...
6a31b479d5
| Author | SHA1 | Date | |
|---|---|---|---|
| 6a31b479d5 | |||
| c2a285c781 | |||
| 1333cb38d6 | |||
| b2968dc327 | |||
| 1492e91431 | |||
| 2609ea3f79 | |||
| 2e4caf72ce | |||
| 8ee9aa51d8 | |||
| 8c788ea388 | |||
| 5d50d2819b | |||
| 8c167d4909 | |||
| 118790486a | |||
| 7fb3eb53c3 | |||
| 23984a3651 | |||
| 25395a648b | |||
| 0abb60fd2d | |||
| 4913d1c556 | |||
| 4b79533c70 | |||
| 027ecbd375 | |||
| f82a2a7b24 | |||
| 563844e361 | |||
| 147142f44f | |||
| 250c5cbfeb | |||
| 1cef437fb3 | |||
| ca1279162b | |||
| d3d26ba9b4 | |||
| 7104976bf9 | |||
| d6750f1e93 | |||
| 0ae3987fb2 | |||
| 6522f02b54 | |||
| 4dc8341e7e | |||
| 8d1081b2b9 | |||
| 16ab474bee | |||
| 0062b20ea1 | |||
| 85d2b85b6a | |||
| e96383433d | |||
| 4bbf1f552f | |||
| 1726f486fa | |||
| 13c42d2c87 | |||
| ff534fc325 | |||
| 769bf721f4 | |||
| 14b86df124 | |||
| 3bb19537fe | |||
| 1f599e5646 | |||
| d933639518 | |||
| c088c0ed71 | |||
| 46f28d2d97 | |||
| ae880c47b2 | |||
| 36b6e395c5 | |||
| 3ab1165e69 | |||
| c2118b16aa | |||
| 33773928c3 | |||
| 88c4e22d36 | |||
| cc7fef8ae9 | |||
| b5317d8f58 | |||
| bfb8f23399 | |||
| b536a999f0 | |||
| 9be9fc4b47 | |||
| d0783aa893 | |||
| ea233dd039 | |||
| 4a9468fcdd | |||
| 8ba73c028c | |||
| 822774b711 | |||
| 830ea626c9 | |||
| 54e0f8a054 | |||
| a5d250214c | |||
| 8b00f401be | |||
| ba38897f73 | |||
| fa2a50e755 | |||
| 75e6d6d5ec | |||
| f94f2b91e8 | |||
| 58b947a366 | |||
| b13d17702e | |||
| 0a43f8fbbe | |||
| 339695c851 | |||
| f0366ccc47 | |||
| d3a646c325 | |||
| 3dc71de739 | |||
| ffbb0652d9 | |||
| 619d75453d | |||
| eefcc06a5e | |||
| 51c2598fb7 | |||
| 9f3da47574 | |||
| 80a6e2716b | |||
| 34e15dd650 | |||
| 9cb2fda66a | |||
| be772db94b | |||
| 5d615dd393 | |||
| ebb3da2ad6 | |||
| 6f79bb97d9 | |||
| 91aabb500c | |||
| fbce298f2b | |||
| 8b90a71077 | |||
| f9203e077e | |||
| b7a947409a | |||
| 9bb5cbba64 | |||
| d7469afee9 | |||
| dc74c19be4 | |||
| d716719428 | |||
| 307a019d48 | |||
| 7af83b089e | |||
| 313315cd3f | |||
| b7f756bc2b | |||
| 6a92f46447 | |||
| 6b3f1bdab5 | |||
| 30cd1ec51a | |||
| d884e6bab2 | |||
| 53c52a0b3e | |||
| 5073a4193f | |||
| 650c1caffa | |||
| d53ddf32c8 | |||
| 5e051633ec | |||
| 5fe7181b35 | |||
| ce49fe143c | |||
| 499fef3c2f | |||
| d9536802db | |||
| 78433faa89 | |||
| 841bd3e0bd | |||
| e34b420168 | |||
| fc0c4b1930 | |||
| b6e110acaf | |||
| 00411a5e74 | |||
| 9df6f60a17 | |||
| df91ab0673 | |||
| 7f8e7b7e7c | |||
| 69f7ee11df | |||
| 5b50bf0fd8 | |||
| f94f03bcc2 | |||
| 65eb983035 | |||
| 3f577b34d5 | |||
| 76ff98db87 |
@@ -0,0 +1,20 @@
|
|||||||
|
# CraftLabs 平台环境变量模板
|
||||||
|
# 复制为 .env 后修改实际值;.env 不提交 Git
|
||||||
|
|
||||||
|
# 数据库
|
||||||
|
DB_PASSWORD=change_me_in_production
|
||||||
|
|
||||||
|
# Redis 密码(空字符串表示无密码)
|
||||||
|
REDIS_PASSWORD=
|
||||||
|
|
||||||
|
# 比特安索 API Key
|
||||||
|
BIT_CLOUD_API_KEY=change_me
|
||||||
|
|
||||||
|
# Webhook 预期 Token(与比特控制台配置一致)
|
||||||
|
CRAFTLABS_WEBHOOK_EXPECTED_TOKEN=change_me
|
||||||
|
|
||||||
|
# JWT 签名密钥(≥32 字符)
|
||||||
|
PLATFORM_JWT_SECRET=change_me_at_least_32_characters_long
|
||||||
|
|
||||||
|
# Grafana 管理员密码
|
||||||
|
GRAFANA_ADMIN_PASSWORD=admin
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"name": "安徽地质博物馆v2.0",
|
||||||
|
"fileId": "TdU1qb5xVYDLOssDOQxQqv",
|
||||||
|
"nodeId": "0-38499",
|
||||||
|
"url": "https://www.figma.com/design/TdU1qb5xVYDLOssDOQxQqv/",
|
||||||
|
"fetchedAt": "2026-05-18T00:00:00Z"
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
{
|
||||||
|
"file": {
|
||||||
|
"name": "安徽地质博物馆v2.0",
|
||||||
|
"fileId": "TdU1qb5xVYDLOssDOQxQqv",
|
||||||
|
"lastModified": "2026-05-18T14:45:48Z",
|
||||||
|
"frame": "数字资源系统-资源管理",
|
||||||
|
"frameSize": "1920x1080"
|
||||||
|
},
|
||||||
|
"colors": {
|
||||||
|
"pageBackground": "rgba(234,239,250,1.0)",
|
||||||
|
"cardBackground": "rgba(255,255,255,1.0)",
|
||||||
|
"textPrimary": "rgba(0,0,0,1.0)",
|
||||||
|
"textSecondary": "rgba(49,49,49,1.0)",
|
||||||
|
"textOnPrimary": "rgba(255,255,255,1.0)",
|
||||||
|
"badgeRed": "rgba(213,73,65,1.0)",
|
||||||
|
"decorativeBlue": "rgba(207,209,255,1.0)",
|
||||||
|
"decorativeTeal": "rgba(217,248,255,1.0)"
|
||||||
|
},
|
||||||
|
"typography": {
|
||||||
|
"body": { "fontSize": "14px", "color": "rgba(0,0,0,1.0)" },
|
||||||
|
"badge": { "fontSize": "12px", "color": "rgba(255,255,255,1.0)" },
|
||||||
|
"placeholder": { "fontSize": "14px", "color": "rgba(49,49,49,1.0)" }
|
||||||
|
},
|
||||||
|
"layout": {
|
||||||
|
"frameWidth": 1920,
|
||||||
|
"frameHeight": 1080,
|
||||||
|
"headerHeight": 60,
|
||||||
|
"sidebarWidth": 232,
|
||||||
|
"contentWidth": 1688,
|
||||||
|
"contentPaddingX": 20,
|
||||||
|
"treePanelWidth": 280,
|
||||||
|
"mainPanelWidth": 1368,
|
||||||
|
"breadcrumbHeight": 46
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"header": "headerMenu 顶部菜单导航",
|
||||||
|
"sidebar": "Menu - 侧边菜单",
|
||||||
|
"breadcrumb": "Breadcrumb 面包屑 (数字资源 > 资源管理)",
|
||||||
|
"tree": "Tree 树结构 - 资源分类树",
|
||||||
|
"search": "search 搜索框",
|
||||||
|
"menuItems": [
|
||||||
|
"item/menuLogo/baseLogo-light",
|
||||||
|
"item/normalMenu/1st-light (x11 菜单项)",
|
||||||
|
"Button"
|
||||||
|
],
|
||||||
|
"headerItems": [
|
||||||
|
"icon-search-w/text - 资源快速搜索",
|
||||||
|
"icon-internet",
|
||||||
|
"icon-view-module",
|
||||||
|
"icon-mail + Badge (红点通知 2)",
|
||||||
|
"logo-github",
|
||||||
|
"icon-user w/ TD Admin",
|
||||||
|
"icon-setting"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,104 @@
|
|||||||
|
# Gitea Actions: 平台部署流水线
|
||||||
|
# 触发条件:推送 main 分支 或 手动触发
|
||||||
|
# 运行环境:self-hosted runner(需要安装 docker + docker-compose)
|
||||||
|
|
||||||
|
name: deploy
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
paths:
|
||||||
|
- "services/**"
|
||||||
|
- "web/**"
|
||||||
|
- "services/docker-compose.yml"
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
env:
|
||||||
|
REGISTRY: gitea.craftlabs.cn/craftlabs
|
||||||
|
API_IMAGE: delivery-platform-api
|
||||||
|
WEBHOOK_IMAGE: license-webhook-ingress
|
||||||
|
UI_IMAGE: delivery-platform-ui
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-and-deploy:
|
||||||
|
runs-on: ubuntu-latest # self-hosted runner 需注册该标签
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
# ============ 后端 API ============
|
||||||
|
- name: Setup Java
|
||||||
|
uses: actions/setup-java@v4
|
||||||
|
with:
|
||||||
|
distribution: temurin
|
||||||
|
java-version: "17"
|
||||||
|
cache: maven
|
||||||
|
|
||||||
|
- name: Build delivery-platform-api
|
||||||
|
run: |
|
||||||
|
mvn -f services/pom.xml -pl delivery-platform-api -am -DskipTests clean package -q
|
||||||
|
|
||||||
|
- name: Build API Docker image
|
||||||
|
run: |
|
||||||
|
docker build -t ${{ env.REGISTRY }}/${{ env.API_IMAGE }}:${{ github.sha }} \
|
||||||
|
-t ${{ env.REGISTRY }}/${{ env.API_IMAGE }}:latest \
|
||||||
|
services/delivery-platform-api
|
||||||
|
|
||||||
|
# ============ Webhook ============
|
||||||
|
- name: Build license-webhook-ingress
|
||||||
|
run: |
|
||||||
|
mvn -f services/pom.xml -pl license-webhook-ingress -am -DskipTests clean package -q
|
||||||
|
|
||||||
|
- name: Build Webhook Docker image
|
||||||
|
run: |
|
||||||
|
docker build -t ${{ env.REGISTRY }}/${{ env.WEBHOOK_IMAGE }}:${{ github.sha }} \
|
||||||
|
-t ${{ env.REGISTRY }}/${{ env.WEBHOOK_IMAGE }}:latest \
|
||||||
|
services/license-webhook-ingress
|
||||||
|
|
||||||
|
# ============ 前端 ============
|
||||||
|
- name: Setup Node
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: "20"
|
||||||
|
|
||||||
|
- name: Build frontend
|
||||||
|
working-directory: web/delivery-platform-ui
|
||||||
|
run: |
|
||||||
|
npm install
|
||||||
|
npm run build
|
||||||
|
|
||||||
|
- name: Build Frontend Docker image
|
||||||
|
run: |
|
||||||
|
docker build -t ${{ env.REGISTRY }}/${{ env.UI_IMAGE }}:${{ github.sha }} \
|
||||||
|
-t ${{ env.REGISTRY }}/${{ env.UI_IMAGE }}:latest \
|
||||||
|
web/delivery-platform-ui
|
||||||
|
|
||||||
|
# ============ 推送镜像到 Gitea Registry ============
|
||||||
|
- name: Login to Gitea Container Registry
|
||||||
|
run: echo "${{ secrets.GITEA_REGISTRY_TOKEN }}" | docker login gitea.craftlabs.cn -u "${{ secrets.GITEA_REGISTRY_USER }}" --password-stdin
|
||||||
|
|
||||||
|
- name: Push images
|
||||||
|
run: |
|
||||||
|
docker push ${{ env.REGISTRY }}/${{ env.API_IMAGE }}:${{ github.sha }}
|
||||||
|
docker push ${{ env.REGISTRY }}/${{ env.API_IMAGE }}:latest
|
||||||
|
docker push ${{ env.REGISTRY }}/${{ env.WEBHOOK_IMAGE }}:${{ github.sha }}
|
||||||
|
docker push ${{ env.REGISTRY }}/${{ env.WEBHOOK_IMAGE }}:latest
|
||||||
|
docker push ${{ env.REGISTRY }}/${{ env.UI_IMAGE }}:${{ github.sha }}
|
||||||
|
docker push ${{ env.REGISTRY }}/${{ env.UI_IMAGE }}:latest
|
||||||
|
|
||||||
|
# ============ 远程部署 ============
|
||||||
|
- name: Deploy via docker-compose
|
||||||
|
env:
|
||||||
|
DB_PASSWORD: ${{ secrets.DB_PASSWORD }}
|
||||||
|
PLATFORM_JWT_SECRET: ${{ secrets.PLATFORM_JWT_SECRET }}
|
||||||
|
CRAFTLABS_WEBHOOK_EXPECTED_TOKEN: ${{ secrets.WEBHOOK_TOKEN }}
|
||||||
|
run: |
|
||||||
|
# 将 docker-compose.yml 复制到部署目录并替换镜像版本
|
||||||
|
mkdir -p /opt/craftlabs/deploy
|
||||||
|
cp services/docker-compose.yml /opt/craftlabs/deploy/
|
||||||
|
cd /opt/craftlabs/deploy
|
||||||
|
export API_IMAGE_TAG=${{ env.REGISTRY }}/${{ env.API_IMAGE }}:${{ github.sha }}
|
||||||
|
export WEBHOOK_IMAGE_TAG=${{ env.REGISTRY }}/${{ env.WEBHOOK_IMAGE }}:${{ github.sha }}
|
||||||
|
export UI_IMAGE_TAG=${{ env.REGISTRY }}/${{ env.UI_IMAGE }}:${{ github.sha }}
|
||||||
|
docker compose pull
|
||||||
|
docker compose up -d --remove-orphans
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
- package-ecosystem: maven
|
||||||
|
directory: "/services"
|
||||||
|
schedule:
|
||||||
|
interval: weekly
|
||||||
|
open-pull-requests-limit: 5
|
||||||
|
|
||||||
|
- package-ecosystem: maven
|
||||||
|
directory: "/java"
|
||||||
|
schedule:
|
||||||
|
interval: weekly
|
||||||
|
open-pull-requests-limit: 5
|
||||||
|
|
||||||
|
- package-ecosystem: npm
|
||||||
|
directory: "/web/delivery-platform-ui"
|
||||||
|
schedule:
|
||||||
|
interval: weekly
|
||||||
|
open-pull-requests-limit: 5
|
||||||
|
|
||||||
|
- package-ecosystem: github-actions
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: monthly
|
||||||
@@ -2,9 +2,9 @@ name: ci-java
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [main, master]
|
branches: [main, master, develop]
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [main, master]
|
branches: [main, master, develop]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
maven:
|
maven:
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
name: ci-platform
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main, master, develop]
|
||||||
|
paths:
|
||||||
|
- "services/**"
|
||||||
|
- "web/**"
|
||||||
|
- "contracts/**"
|
||||||
|
- ".github/workflows/ci-platform.yml"
|
||||||
|
pull_request:
|
||||||
|
branches: [main, master, develop]
|
||||||
|
paths:
|
||||||
|
- "services/**"
|
||||||
|
- "web/**"
|
||||||
|
- "contracts/**"
|
||||||
|
- ".github/workflows/ci-platform.yml"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
maven-services:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: actions/setup-java@v4
|
||||||
|
with:
|
||||||
|
distribution: temurin
|
||||||
|
java-version: "17"
|
||||||
|
cache: maven
|
||||||
|
- name: Forbid craftlabs-auth-bitanswer in platform tree
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
TREE=$(mvn -f services/pom.xml -q -DskipTests dependency:tree -pl delivery-platform-api,license-webhook-ingress -am)
|
||||||
|
if echo "$TREE" | grep -q 'craftlabs-auth-bitanswer'; then
|
||||||
|
echo "::error::Banned dependency craftlabs-auth-bitanswer found in platform dependency tree"
|
||||||
|
echo "$TREE"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
- name: Maven verify (platform services)
|
||||||
|
run: mvn -f services/pom.xml -B verify
|
||||||
|
|
||||||
|
web-ui-build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: "20"
|
||||||
|
- name: npm install and build
|
||||||
|
working-directory: web/delivery-platform-ui
|
||||||
|
run: |
|
||||||
|
npm install
|
||||||
|
npm run build
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
name: ci-security
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main, master, develop]
|
||||||
|
pull_request:
|
||||||
|
branches: [main, master, develop]
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
trivy-maven-modules:
|
||||||
|
name: Trivy (Java / Maven manifests)
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- scan-ref: services
|
||||||
|
- scan-ref: java
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Run Trivy filesystem scan
|
||||||
|
uses: aquasecurity/trivy-action@0.28.0
|
||||||
|
with:
|
||||||
|
scan-type: fs
|
||||||
|
scan-ref: ${{ matrix.scan-ref }}
|
||||||
|
scanners: vuln
|
||||||
|
vuln-type: os,library
|
||||||
|
severity: CRITICAL,HIGH
|
||||||
|
exit-code: "1"
|
||||||
|
ignore-unfixed: true
|
||||||
|
|
||||||
|
npm-audit-ui:
|
||||||
|
name: npm audit (delivery-platform-ui)
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
working-directory: web/delivery-platform-ui
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: "20"
|
||||||
|
cache: npm
|
||||||
|
cache-dependency-path: web/delivery-platform-ui/package-lock.json
|
||||||
|
- name: Install and audit
|
||||||
|
run: |
|
||||||
|
npm ci
|
||||||
|
npm audit --audit-level=high
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
# 手动触发:打包 SDK + Native(.so) 并生成 SHA256SUMS,供 GitHub Release 上传
|
||||||
|
name: sdk-release-checksums
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
checksums:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: actions/setup-java@v4
|
||||||
|
with:
|
||||||
|
distribution: temurin
|
||||||
|
java-version: "17"
|
||||||
|
cache: maven
|
||||||
|
- name: Install native build deps
|
||||||
|
run: |
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y build-essential cmake
|
||||||
|
- name: Build native (.so)
|
||||||
|
run: |
|
||||||
|
cmake -S native -B native/build -DCMAKE_BUILD_TYPE=Release -DCRAFTLABS_BUILD_JNI=ON
|
||||||
|
cmake --build native/build --parallel
|
||||||
|
- name: Maven package (SDK jars)
|
||||||
|
run: mvn -f java/pom.xml -B -DskipTests package
|
||||||
|
- name: Generate SHA256SUMS
|
||||||
|
run: |
|
||||||
|
chmod +x scripts/sdk-release-checksums.sh
|
||||||
|
./scripts/sdk-release-checksums.sh --no-mvn --output dist/sdk-release --native-path "${{ github.workspace }}/native/build"
|
||||||
|
- name: Upload artifacts
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: sdk-release-${{ github.ref_name }}
|
||||||
|
path: |
|
||||||
|
dist/sdk-release/
|
||||||
|
java/craftlabs-auth-core/target/*.jar
|
||||||
|
java/craftlabs-auth-bitanswer/target/*.jar
|
||||||
|
java/craftlabs-auth-selfhosted/target/*.jar
|
||||||
@@ -1,3 +1,6 @@
|
|||||||
|
# SDK 发布产物(校验和脚本生成,不上库)
|
||||||
|
dist/
|
||||||
|
|
||||||
# Build
|
# Build
|
||||||
**/build/
|
**/build/
|
||||||
**/target/
|
**/target/
|
||||||
@@ -34,8 +37,13 @@ cmake-build-*/
|
|||||||
# Maven
|
# Maven
|
||||||
dependency-reduced-pom.xml
|
dependency-reduced-pom.xml
|
||||||
|
|
||||||
|
# Node (平台前端)
|
||||||
|
node_modules/
|
||||||
|
web/**/dist/
|
||||||
|
|
||||||
# Python
|
# Python
|
||||||
__pycache__/
|
__pycache__/
|
||||||
*.py[cod]
|
*.py[cod]
|
||||||
.venv/
|
.venv/
|
||||||
venv/
|
venv/
|
||||||
|
.env
|
||||||
|
|||||||
@@ -0,0 +1,79 @@
|
|||||||
|
# PROJECT KNOWLEDGE BASE
|
||||||
|
|
||||||
|
**Generated:** 2026-05-26
|
||||||
|
**Commit:** 4913d1c
|
||||||
|
**Branch:** develop
|
||||||
|
|
||||||
|
## OVERVIEW
|
||||||
|
|
||||||
|
**craftlabs-authorization-sdk** — 创飞客户端授权 SDK 工作区。多语言 monorepo:Java (Maven) 封装授权 API + Rust (Cargo) native cdylib + Vue 3 交付管理后台 + Spring Boot 后端服务。37k+ 行源码,活跃开发中。
|
||||||
|
|
||||||
|
## STRUCTURE
|
||||||
|
|
||||||
|
```
|
||||||
|
./
|
||||||
|
├── java/ # Maven 多模块 SDK (core, bitanswer, selfhosted, tests)
|
||||||
|
├── native/ # Rust Cargo workspace (craft-core cdylib, CLI tool)
|
||||||
|
├── services/ # Spring Boot 后端服务
|
||||||
|
│ ├── delivery-platform-api/ # 商业交付管理 API (153 Java 文件)
|
||||||
|
│ └── license-webhook-ingress/ # Webhook 回调入口 (小)
|
||||||
|
├── web/
|
||||||
|
│ └── delivery-platform-ui/ # Vue 3 前端 (47 src 文件)
|
||||||
|
├── schemas/ # craftlabs-auth-config JSON Schema
|
||||||
|
├── examples/ # 示例配置 (java/cpp/python/vc)
|
||||||
|
├── docs/ # 产品/流程/工程架构文档
|
||||||
|
│ └── engineering/ # 系统架构、工程边界、并行迭代
|
||||||
|
└── engineering/ # 工作区 manifest, 规划工程占位
|
||||||
|
```
|
||||||
|
|
||||||
|
## WHERE TO LOOK
|
||||||
|
|
||||||
|
| Task | Location | Notes |
|
||||||
|
|------|----------|-------|
|
||||||
|
| SDK 授权核心逻辑 (Java) | `java/craftlabs-auth-core/src/` | config, internal 模块 |
|
||||||
|
| 比特安索集成 | `java/craftlabs-auth-bitanswer/` | 单一 Java 文件 |
|
||||||
|
| 自托管授权提供者 | `java/craftlabs-auth-selfhosted/` | 同上 |
|
||||||
|
| Rust native C ABI | `native/craft-core/src/` | lib.rs 导出 craft_* 函数 |
|
||||||
|
| 安全反调试/混淆 | `native/craft-core/src/security/` | anti_debug, obfuscation |
|
||||||
|
| CLI 工具 | `native/craftlabs-auth-cli/src/` | status/activate/check/info 命令 |
|
||||||
|
| 平台后端 Controller | `services/delivery-platform-api/` | 按领域分包 (contract, license, device 等) |
|
||||||
|
| 平台持久层 | `services/delivery-platform-api/` | persistence/ 下每实体一对 (POJO+Mapper) |
|
||||||
|
| 平台 DTO | `services/delivery-platform-api/` | web/dto/ 下 47 个请求/响应类 |
|
||||||
|
| Webhook 回调 | `services/license-webhook-ingress/` | webhook 入口 + persistence |
|
||||||
|
| 前端视图 | `web/delivery-platform-ui/src/views/` | Vue 3 组件 (38 文件) |
|
||||||
|
| 数据库迁移 | `services/delivery-platform-api/` | src/main/resources/db/migration/ |
|
||||||
|
| JSON Schema | `schemas/` | craftlabs-auth-config 校验 |
|
||||||
|
| CI/CD (Gitea Actions) | `GITEA_CI_CD.md` | act_runner 配置 |
|
||||||
|
|
||||||
|
## CONVENTIONS
|
||||||
|
|
||||||
|
- **Java**: Spring Boot 3.x, MyBatis-Plus, Maven multi-module. 每实体一对 `Entity` + `Mapper` 接口。控制器统一 `@RestController` + `@RequestMapping("/api/v1/...")`. 异常处理统一 `ApiExceptionHandler`.
|
||||||
|
- **Rust**: cdylib 导出 `craft_*` C ABI。`Provider` trait 模式。安全模块独立 `security/` 子树。
|
||||||
|
- **Vue**: Vue 3 + Composition API (`<script setup>`). 视图集中 `src/views/`.
|
||||||
|
- **SQL**: Flyway 迁移在 `db/migration/`, 前缀 `V{version}__{description}.sql`.
|
||||||
|
- **发布**: SDK 发版生成 SHA256SUMS + 可选 GPG 签名, 见 `java/RELEASING.md` + `scripts/sdk-release-checksums.sh`.
|
||||||
|
|
||||||
|
## ANTI-PATTERNS (THIS PROJECT)
|
||||||
|
|
||||||
|
- **不要** 把 SDK jar 打进平台 Fat JAR (bootstrap 唯一 repackage)
|
||||||
|
- **不要** 在客户端 SDK 中依赖平台后端代码 (运行时解耦)
|
||||||
|
- **不要** 混用 JNI 和 JNA 桥接 — 当前已迁移到 JNA
|
||||||
|
|
||||||
|
## COMMANDS
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Java SDK 构建 (JDK 17+)
|
||||||
|
mvn -f java/pom.xml clean verify
|
||||||
|
|
||||||
|
# Rust native 构建 (Rust 1.70+)
|
||||||
|
cargo build --manifest-path native/craft-core/Cargo.toml --release
|
||||||
|
|
||||||
|
# 前端
|
||||||
|
cd web/delivery-platform-ui && npm install && npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
## NOTES
|
||||||
|
|
||||||
|
- 平台后端/前端按架构设计为独立工程,本仓库含源码仅为过渡期方便迭代。正式部署时按 `engineering/planned/` 规范开独立仓。
|
||||||
|
- native/craft-core 曾用 JNI bridge,现已迁移至 JNA (commit 027ecbd)。
|
||||||
|
- 有三轨并行迭代:后端/前端/SDK,详见 `docs/engineering/PARALLEL_ITERATION_INDEX.md`。
|
||||||
+118
@@ -0,0 +1,118 @@
|
|||||||
|
# Gitea CI/CD 配置指南
|
||||||
|
|
||||||
|
## 1. Gitea Actions Runner 注册
|
||||||
|
|
||||||
|
### 1.1 部署 Runner
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 从 Gitea 管理后台获取 runner 注册令牌
|
||||||
|
# 位置:站点管理 -> 运行 Actions -> 创建 Runner
|
||||||
|
|
||||||
|
# 创建 runner 数据目录
|
||||||
|
mkdir -p /opt/gitea-runner
|
||||||
|
cd /opt/gitea-runner
|
||||||
|
|
||||||
|
# 下载 act runner
|
||||||
|
curl -sL https://gitea.com/gitea/act_runner/releases/latest/download/act_runner-linux-amd64 -o act_runner
|
||||||
|
chmod +x act_runner
|
||||||
|
|
||||||
|
# 注册 runner(替换 TOKEN 和 GITEA_URL)
|
||||||
|
./act_runner register \
|
||||||
|
--instance https://gitea.craftlabs.cn \
|
||||||
|
--token <REGISTRATION_TOKEN> \
|
||||||
|
--name craftlabs-runner \
|
||||||
|
--labels ubuntu-latest:docker://node:20-bookworm
|
||||||
|
|
||||||
|
# 以服务方式运行
|
||||||
|
./act_runner daemon &
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.2 Runner 标签说明
|
||||||
|
|
||||||
|
| 标签 | 用途 | 对应的 workflow `runs-on` |
|
||||||
|
|------|------|--------------------------|
|
||||||
|
| `ubuntu-latest` | 通用构建和测试 | `ubuntu-latest` |
|
||||||
|
|
||||||
|
## 2. 配置 Gitea Secrets
|
||||||
|
|
||||||
|
在 Gitea 仓库 Settings -> Secrets 中添加:
|
||||||
|
|
||||||
|
| Secret 名称 | 说明 |
|
||||||
|
|-------------|------|
|
||||||
|
| `GITEA_REGISTRY_TOKEN` | Gitea Container Registry 访问令牌 |
|
||||||
|
| `GITEA_REGISTRY_USER` | Registry 用户名 |
|
||||||
|
| `DB_PASSWORD` | PostgreSQL 数据库密码 |
|
||||||
|
| `PLATFORM_JWT_SECRET` | JWT 签名密钥(至少 32 字符)|
|
||||||
|
| `WEBHOOK_TOKEN` | Webhook x-bitanswer-token |
|
||||||
|
|
||||||
|
## 3. 推送仓库到 Gitea
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 添加 Gitea 远程仓库
|
||||||
|
git remote add gitea https://gitea.craftlabs.cn/craftlabs/authorization-sdk.git
|
||||||
|
|
||||||
|
# 推送到 Gitea
|
||||||
|
git push -u gitea develop
|
||||||
|
|
||||||
|
# 推送到 Gitea 并设为主分支
|
||||||
|
git push gitea develop:main
|
||||||
|
```
|
||||||
|
|
||||||
|
## 4. CI 流程说明
|
||||||
|
|
||||||
|
### 4.1 提交触发
|
||||||
|
|
||||||
|
| Workflow | 触发条件 | 运行内容 |
|
||||||
|
|----------|---------|---------|
|
||||||
|
| `ci-java` | push/PR to main/develop | Maven verify + Native 编译 |
|
||||||
|
| `ci-platform` | push/PR to main/develop (services/web) | Maven verify + npm build |
|
||||||
|
| `ci-security` | push/PR to main/develop | Trivy 漏洞扫描 + npm audit |
|
||||||
|
| `deploy` | push to main | 构建 Docker 镜像 → Gitea Registry → docker-compose 部署 |
|
||||||
|
|
||||||
|
### 4.2 手动触发
|
||||||
|
|
||||||
|
| Workflow | 触发方式 |
|
||||||
|
|----------|---------|
|
||||||
|
| `sdk-release-checksums` | 仓库 Actions 页面手动触发 |
|
||||||
|
| `deploy` | 仓库 Actions 页面手动触发 |
|
||||||
|
|
||||||
|
## 5. 部署架构
|
||||||
|
|
||||||
|
```text
|
||||||
|
┌─────────────────────────────────┐
|
||||||
|
│ Gitea 仓库(craftsupport.cn) │
|
||||||
|
│ push main → Gitea Actions │
|
||||||
|
└──────────┬──────────────────────┘
|
||||||
|
│ 触发
|
||||||
|
┌──────────▼──────────────────────┐
|
||||||
|
│ Self-Hosted Runner │
|
||||||
|
│ ├── mvn package → Docker build │
|
||||||
|
│ ├── npm build → Docker build │
|
||||||
|
│ └── docker compose up -d │
|
||||||
|
└──────────┬──────────────────────┘
|
||||||
|
│ 部署
|
||||||
|
┌──────────▼──────────────────────┐
|
||||||
|
│ 部署主机(生产环境) │
|
||||||
|
│ ├── PostgreSQL 15 │
|
||||||
|
│ ├── delivery-platform-api:8080 │
|
||||||
|
│ ├── license-webhook-ingress:8081│
|
||||||
|
│ └── delivery-platform-ui:80 │
|
||||||
|
└─────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
## 6. 环境变量要求
|
||||||
|
|
||||||
|
部署时需确保以下环境变量已设置:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 数据库
|
||||||
|
SPRING_DATASOURCE_URL=jdbc:postgresql://postgres:5432/craftlabs_platform
|
||||||
|
SPRING_DATASOURCE_USERNAME=craftlabs
|
||||||
|
SPRING_DATASOURCE_PASSWORD=<实际密码>
|
||||||
|
|
||||||
|
# JWT
|
||||||
|
PLATFORM_JWT_SECRET=<至少32字符随机密钥>
|
||||||
|
|
||||||
|
# Webhook
|
||||||
|
CRAFTLABS_WEBHOOK_EXPECTED_TOKEN=<与比特控制台一致>
|
||||||
|
```
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
# CraftLabs Authorization SDK
|
||||||
|
|
||||||
|
创飞 **客户端授权 SDK** 工作区:Java API + Native 动态库 + **授权配置 JSON Schema** + 示例与文档。
|
||||||
|
**商业交付管理平台**(合同/交付/SN/Webhook 运营后台)按架构设计为 **独立仓库**,见 [`engineering/planned/`](engineering/planned/) 与 [`docs/engineering/WORKSPACE_ENGINEERING_LAYOUT.md`](docs/engineering/WORKSPACE_ENGINEERING_LAYOUT.md)。
|
||||||
|
平台后端 **运维部署** 为 **单枚可执行 Fat JAR**(多模块源码、`bootstrap` 唯一 `repackage`);**切勿**与下方 **客户端 SDK** 工件混用或把 SDK 打进平台 JAR。
|
||||||
|
|
||||||
|
## 仓库结构
|
||||||
|
|
||||||
|
| 目录 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| [`java/`](java/) | Maven 多模块:`craftlabs-auth-core`、`craftlabs-auth-bitanswer`、`craftlabs-auth-selfhosted`、`craftlabs-auth-tests` |
|
||||||
|
| [`native/`](native/) | **Rust Cargo workspace**:`craft-core` cdylib 导出 `craft_*` C ABI(对齐 `docs/平台架构思路.md`);旧 C++ CMake 见 `.deprecated-cmake/` |
|
||||||
|
| [`schemas/`](schemas/) | `craftlabs-auth-config` JSON Schema |
|
||||||
|
| [`examples/`](examples/) | 示例配置与烟测脚本 |
|
||||||
|
| [`docs/`](docs/) | 比特对接、创飞平台产品/流程/工程架构文档 |
|
||||||
|
| [`engineering/`](engineering/) | 工作区 manifest、**规划工程占位** |
|
||||||
|
|
||||||
|
## 构建
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Java(需 JDK 17+)
|
||||||
|
mvn -f java/pom.xml verify
|
||||||
|
|
||||||
|
# Rust 核心库(需 Rust 1.70+)
|
||||||
|
cargo build --manifest-path native/craft-core/Cargo.toml --release
|
||||||
|
# 产物:native/target/release/libcraftlabs_auth_bitanswer.{so,dylib,dll}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 发布与完整性(SHA-256 / GPG)
|
||||||
|
|
||||||
|
对外发版前生成 **`SHA256SUMS`**、可选 **GPG 签 JAR** 与 **`SHA256SUMS.asc`**,见 **[`java/RELEASING.md`](java/RELEASING.md)**(脚本:`scripts/sdk-release-checksums.sh`)。
|
||||||
|
|
||||||
|
## 文档索引
|
||||||
|
|
||||||
|
- 工程划分与边界(架构):[`docs/engineering/WORKSPACE_ENGINEERING_LAYOUT.md`](docs/engineering/WORKSPACE_ENGINEERING_LAYOUT.md)
|
||||||
|
- 三轨并行迭代(后端/前端/SDK):[`docs/engineering/PARALLEL_ITERATION_INDEX.md`](docs/engineering/PARALLEL_ITERATION_INDEX.md)
|
||||||
|
- 平台功能模块:[`docs/chuangfei-platform-product-modules.md`](docs/chuangfei-platform-product-modules.md)
|
||||||
|
- 业务流程与版本排期:[`docs/chuangfei-platform-bpm-and-roadmap.md`](docs/chuangfei-platform-bpm-and-roadmap.md)
|
||||||
|
- 比特授权与规则纲要:[`docs/bitanswer-licensing-design-and-rules.md`](docs/bitanswer-licensing-design-and-rules.md)
|
||||||
|
|
||||||
|
## 许可证
|
||||||
|
|
||||||
|
版权所有 © 广州创飞人工智能技术有限公司(以项目实际声明为准)。
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
# 契约(Contracts)
|
||||||
|
|
||||||
|
## OpenAPI — 交付平台 API
|
||||||
|
|
||||||
|
| 文件 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| [`openapi/delivery-platform-api.json`](openapi/delivery-platform-api.json) | **`delivery-platform-api` 的单一事实来源(SSOT)**;与运行时 `/v3/api-docs` 对齐。 |
|
||||||
|
|
||||||
|
### 更新快照(维护者)
|
||||||
|
|
||||||
|
在仓库根目录或 `services/delivery-platform-api` 下执行(需 JDK 17):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export JAVA_HOME=… # JDK 17+
|
||||||
|
cd services/delivery-platform-api
|
||||||
|
UPDATE_OPENAPI=1 mvn -q test -Dtest=OpenApiContractSnapshotTest
|
||||||
|
```
|
||||||
|
|
||||||
|
提交前请 **审阅 diff**:破坏性变更需 bump 版本说明、同步前端与集成方。
|
||||||
|
|
||||||
|
### CI 校验
|
||||||
|
|
||||||
|
默认 `mvn verify` 会运行 `OpenApiContractSnapshotTest`:**运行时生成的 OpenAPI 与快照须一致**。若仅改实现未改契约却导致文档变化,应更新快照并写在 PR 说明中。
|
||||||
|
|
||||||
|
### 与前端
|
||||||
|
|
||||||
|
`web/delivery-platform-ui` 的 axios 路径应与 OpenAPI `paths` 一致;可选后续接入 OpenAPI Generator 生成 TS 类型(非必选)。
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,35 @@
|
|||||||
|
# `initialize(config_json)` 配置说明
|
||||||
|
|
||||||
|
`AuthProvider.initialize(String)` 与 C API `auth_initialize(const char* config_json)` 共用同一 JSON 语义,版本由 **`schemaVersion: 1`** 锁定。
|
||||||
|
|
||||||
|
## 规范与示例
|
||||||
|
|
||||||
|
| 资源 | 路径 |
|
||||||
|
|------|------|
|
||||||
|
| JSON Schema | `schemas/craftlabs-auth-config.schema.json` |
|
||||||
|
| 码头 / 集团拓扑示例 | `examples/config/wharf.bitanswer.json` |
|
||||||
|
| 学校 / 边设备示例 | `examples/config/school.bitanswer.json` |
|
||||||
|
| 流动人口 / 项目示例 | `examples/config/floating.bitanswer.json` |
|
||||||
|
| 学校 / 自研 HTTP 示例 | `examples/config/school.selfhosted.json` |
|
||||||
|
|
||||||
|
## Java 解析与校验
|
||||||
|
|
||||||
|
```java
|
||||||
|
import cn.craftlabs.auth.config.AuthConfig;
|
||||||
|
import cn.craftlabs.auth.config.AuthConfigException;
|
||||||
|
import cn.craftlabs.auth.config.AuthConfigs;
|
||||||
|
|
||||||
|
AuthConfig cfg = AuthConfigs.parse(jsonString);
|
||||||
|
// 再交给实现(与校验使用同一字符串即可)
|
||||||
|
provider.initialize(jsonString);
|
||||||
|
```
|
||||||
|
|
||||||
|
`AuthConfig` 提供 `bitanswerFeatureId(String logicalKey)` 等辅助方法,供后续在 `hasFeature` 与比特 `QueryFeature` 之间做映射。
|
||||||
|
|
||||||
|
## 场景字段摘要
|
||||||
|
|
||||||
|
- **wharf**:`wharf.topology`(`cloud` / `group` / `local_float`)等与集中授权拓扑相关的**提示**;实际连网方式仍以 `bitanswer.url` 为准。
|
||||||
|
- **school**:`school.edgeDeviceId` 等用于运营对账;是否写入比特自定义字段由实现阶段决定。
|
||||||
|
- **floating**:**必填** `floating.projectId`;表达「按项目」授权锚点,与商务合同、控制台业务模板需一致。
|
||||||
|
|
||||||
|
后续优化(错误码、OIDC、集团占点策略等)在保持 `schemaVersion` 或递增版本的前提下扩展字段即可。
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
# 比特安索方案:工作区走查 — 地址与「回调」评估
|
||||||
|
|
||||||
|
> **范围**:本仓库内与比特安索(Bitanswer)相关的 URL、回调概念及受影响系统能力。
|
||||||
|
> **日期**:2026-04-06
|
||||||
|
> **关联**:[授权制度与规则](bitanswer-licensing-design-and-rules.md) · [客户端 API 纲要](bitanswer-client-api-overview.md) · [创飞侧平台与对接能力评估](chuangfei-bitanswer-integration-platform.md)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 结论摘要
|
||||||
|
|
||||||
|
| 类别 | 是否在本仓库配置 | 典型形态 | 涉及系统功能 |
|
||||||
|
|------|------------------|----------|--------------|
|
||||||
|
| **`bitanswer.url`(授权服务地址)** | 是(JSON 配置 + Schema) | `https://…` 云/E3、`bit://ip:port` 集团、`lic://…` 本地等 | 登录、在线激活/升级、会话与心跳、特征项校验等与 **Bitanswer 运行时** 直连的能力 |
|
||||||
|
| **控制台「规则」HTTP Callback** | 否(官方控制台配置) | 业务系统提供的 `https://…` Webhook + `x-bitanswer-token` | **订单/资产/审计**:激活前后、设备绑定、云保保会话失效等事件的异步通知与集成 |
|
||||||
|
| **SDK 属性里的函数指针回调** | 未在本 SDK 封装层暴露 | C 侧 `SetAttr` + `ATTR_HB_*_CALLBACK` 等 | **进程内**心跳失败/停止、集团排队等待等 **UI 或重连策略**,非 HTTP |
|
||||||
|
| **`selfhosted.baseUrl`** | 是(另一 provider) | 自研授权 HTTP 根地址 | 与比特方案并行;本仓库仅 `selfhosted_http_ping` 等占位,非 Bitanswer 官方回调 |
|
||||||
|
|
||||||
|
本 SDK 当前 **`BitAnswerProvider` 仅委托 native,未实现规则 Webhook 接收端**;若产品要在比特控制台配规则回调,需在 **独立后端服务** 实现 POST 接口与鉴权。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 本仓库中出现的「地址」清单
|
||||||
|
|
||||||
|
### 2.1 配置项:`bitanswer.url`
|
||||||
|
|
||||||
|
- **定义**:[`BitanswerConfigSection`](../java/craftlabs-auth-core/src/main/java/cn/craftlabs/auth/config/BitanswerConfigSection.java) 字段 `url`。
|
||||||
|
- **校验**:[`AuthConfigs`](../java/craftlabs-auth-core/src/main/java/cn/craftlabs/auth/config/AuthConfigs.java) 在 `provider=bitanswer` 时要求非空。
|
||||||
|
- **Schema**:[`schemas/craftlabs-auth-config.schema.json`](../schemas/craftlabs-auth-config.schema.json) 描述为 **Bit_Login `szURL`**(云 `http(s)://`、集团 `bit://`、本地 `lic://` 等)。
|
||||||
|
- **示例**:
|
||||||
|
- [`examples/config/floating.bitanswer.json`](../examples/config/floating.bitanswer.json)、[`school.bitanswer.json`](../examples/config/school.bitanswer.json):`https://cloud.bitanswer.example/e3`(占位云地址)。
|
||||||
|
- [`examples/config/wharf.bitanswer.json`](../examples/config/wharf.bitanswer.json):`bit://license.example.com:8273`(集团服务)。
|
||||||
|
|
||||||
|
**功能映射(客户端 → 比特侧)**:
|
||||||
|
|
||||||
|
- **登录与会话**:`Bit_Login` / `Bit_LoginEx` 连到该 URL 所指云中心或集团服务;决定云/集团/混合行为还与 `loginMode`、环境变量等有关(见 `docs/bitanswer-client-api-overview.md` §2–3)。
|
||||||
|
- **在线激活与升级**:同类 URL 会传给 `Bit_UpdateOnline`、`Bit_GetUpdateInfo` 等(官方 API 表见 `docs/bitanswer-client-api-overview.md` §5)。
|
||||||
|
- **可选 `rootPath`**:配置项中有 `rootPath`,对应 `Bit_SetRootPath`,影响本地授权文件查找根目录(与 URL 形态 `root://` 语义相关,见 `docs/c.md`)。
|
||||||
|
|
||||||
|
### 2.2 文档中的规则 Callback URL(非本仓库代码配置)
|
||||||
|
|
||||||
|
- **出处**:[`docs/bitanswer-licensing-design-and-rules.md`](bitanswer-licensing-design-and-rules.md) §5.5,对应官方 [规则](https://doc.bitanswer.cn/docs/function/rule/) 文档。
|
||||||
|
- **形态**:JSON 动作数组中的 `callback.url`,请求头可含 `x-bitanswer-token`。
|
||||||
|
- **触发事件**(与业务系统相关):`sn:pre_activate` / `sn:post_activate`、`device:pre_activate` / `device:post_activate`、`yunbaobao:session_logout` 等。
|
||||||
|
|
||||||
|
**功能映射(贵司后端若承接回调)**:
|
||||||
|
|
||||||
|
| 事件 | 建议评估的系统能力 |
|
||||||
|
|------|---------------------|
|
||||||
|
| `sn:pre_activate` | 激活前风控:黑名单、订单状态、SN 与合同是否一致;可配合异步拒绝策略(以实现为准) |
|
||||||
|
| `sn:post_activate` | 开通交付、CRM 状态更新、用量计费起点、通知客户 |
|
||||||
|
| `device:pre_activate` / `post_activate` | 设备台账、换机策略、终端数/浮动策略与内部资产系统对账 |
|
||||||
|
| `yunbaobao:session_logout` | 会话吊销联动:踢下线、清理服务端会话、安全审计 |
|
||||||
|
|
||||||
|
**注意**:官方说明回调为 **异步 POST**,**响应体可被忽略**;接收方应 **幂等**、校验 `x-bitanswer-token`、限流与防重放。
|
||||||
|
|
||||||
|
### 2.3 SDK 文档中的「回调」= C 函数指针(非 URL)
|
||||||
|
|
||||||
|
- **出处**:[`docs/c.md`](c.md) 中 `Bit_SetAttr` 相关常量,例如:
|
||||||
|
- `ATTR_HB_STOPED_CALLBACK` / `ATTR_HB_STOPED_CALLBACK_EX`:心跳停止
|
||||||
|
- `ATTR_HB_RETRY_FAILED_CALLBACK` / `ATTR_HB_RETRY_CALLBACK_EX` / `EX2`:心跳重试失败/重试
|
||||||
|
- `ATTR_HB_RETRY_SUCCESS_CALLBACK`:心跳重试成功
|
||||||
|
- `ATTR_QUEUE_WAIT_CALLBACK`:集团排队等待(与 `BIT_QUERY_WAIT` 等配合)
|
||||||
|
|
||||||
|
**功能映射**:在 **同一进程内** 由 Bitanswer 客户端库调用开发商注册的函数,用于 **监控与授权服务的连接质量、排队 UX、自动重连**。
|
||||||
|
**本仓库**:[`BitAnswerProvider`](../java/craftlabs-auth-bitanswer/src/main/java/cn/craftlabs/auth/bitanswer/BitAnswerProvider.java) 未暴露 `setAttr`/这些回调;若需使用,要在 **native 层或后续 JNI** 扩展。
|
||||||
|
|
||||||
|
### 2.4 其它:`selfhosted.baseUrl`
|
||||||
|
|
||||||
|
- **定义**:[`SelfhostedConfigSection`](../java/craftlabs-auth-core/src/main/java/cn/craftlabs/auth/config/SelfhostedConfigSection.java)。
|
||||||
|
- **代码**:[`native/src/selfhosted/http_client.h`](../native/src/selfhosted/http_client.h) 中 `selfhosted_http_ping(base_url)`。
|
||||||
|
- **说明**:属于 **自研授权 provider**,与比特控制台规则 Callback **无直接对应**;评估部署时不要与 `bitanswer.url` 混淆。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 对实现与运维的检查项建议
|
||||||
|
|
||||||
|
1. **`bitanswer.url`**:区分环境(云/E3 生产、测试集团 `bit://`、纯本地 `lic://`);与 **产品识别码 `applicationData`**、**SN** 一并纳入密钥与配置管理。
|
||||||
|
2. **规则 Callback URL**:在架构图中单独标注为 **比特云 → 贵司公网/内网穿透 HTTPS**;明确 **谁生成 `x-bitanswer-token`**、轮换与日志脱敏(含 `sn`、`mid`、IP)。
|
||||||
|
3. **函数指针回调**:若未来在 native 启用,明确线程模型与 **不得在回调中执行阻塞 IO** 等约束(以官方 `bitanswer.h` 为准)。
|
||||||
|
4. **文档与代码一致性**:当前 [`native/src/bitanswer/bitanswer_adapter.h`](../native/src/bitanswer/bitanswer_adapter.h) 仍为占位;真正接入 `Bit_Login` 等后,应同步更新本文 §2.1 的实现引用。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 修订记录
|
||||||
|
|
||||||
|
| 日期 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| 2026-04-06 | 初版:基于工作区 grep 与核心配置/Java/native 文件走查。 |
|
||||||
@@ -0,0 +1,188 @@
|
|||||||
|
# 比特授权云:授权制度设计与规则机制(实现依据)
|
||||||
|
|
||||||
|
> **文档来源**(官方):
|
||||||
|
>
|
||||||
|
> - [授权设计与创建](https://doc.bitanswer.cn/docs/function/licensing-design-creation/)
|
||||||
|
> - [规则](https://doc.bitanswer.cn/docs/function/rule/)
|
||||||
|
>
|
||||||
|
> **用途**:作为本仓库及相关功能**设计、实现、联调**时的领域依据;与更宽范围的机制总览见 `[bitanswer-authorization-overview.md](./bitanswer-authorization-overview.md)`。
|
||||||
|
> **整理日期**:2026-04-06
|
||||||
|
> **对接落地**:创飞侧平台、交付物与能力评估见 [chuangfei-bitanswer-integration-platform.md](./chuangfei-bitanswer-integration-platform.md)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 授权制度:四层对象
|
||||||
|
|
||||||
|
授权由四层对象叠加定义「卖什么、怎么卖、发什么」。
|
||||||
|
|
||||||
|
|
||||||
|
| 层级 | 作用 |
|
||||||
|
| -------------------- | ------------------------------------------------------------------------------- |
|
||||||
|
| **产品 (Product)** | 定义被授权对象:一套软件或组合;承载**授权功能模块的集合**。含唯一产品名、**特征项**(功能/加密点)、**配置项**(应用与用户小数据)。 |
|
||||||
|
| **模版 (Template)** | 产品特征项的**子集**,限定某次授权里**可用模块及版本**,常对应一个**发行版本**。 |
|
||||||
|
| **业务 (Business)** | 定义**销售/授权商业模式**:云授权、集团授权、单机授权等;在此配置各类**授权属性组合**。 |
|
||||||
|
| **授权 (Entitlement)** | 实际发给用户的授权:**从模版继承模块**;**从业务继承授权类型**,但可**改写**业务允许的授权属性。由全球唯一 **16 位字母数字 SN** 标识。 |
|
||||||
|
|
||||||
|
|
||||||
|
### 1.1 授权内容更新与同步
|
||||||
|
|
||||||
|
- 授权内容更新后,客户端可用对应 **SN** 升级授权内容。
|
||||||
|
- **云授权**:更新可较快反映到客户端,应用**无需重启**。
|
||||||
|
- **集团授权、单机授权**:更新在客户端**下次连接服务器**时同步。
|
||||||
|
- **单机**:带**智能连接**的会在联网时自动同步;带**强制认证**的需定期连服务器;其它情况多在授权失效时才尝试连服务器。
|
||||||
|
- 开发商也可选择**手动**更新客户端本地授权内容。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 业务侧授权属性(约束维度)
|
||||||
|
|
||||||
|
在业务中可配置的约束项(不同授权类型下可用组合不同),官方文档列出的包括:
|
||||||
|
|
||||||
|
|
||||||
|
| 属性 | 说明 |
|
||||||
|
| --------------- | ----------------------------------- |
|
||||||
|
| **连接类型**(单机) | 离线,或可自动进行后台验证的**智能连接**。 |
|
||||||
|
| **强制认证** | 客户端**最大离线时间**;超过后须与服务器再次验证才能继续使用。 |
|
||||||
|
| **安装限制** | 客户端允许**激活次数**;单机授权下由**所有客户端共享**该次数。 |
|
||||||
|
| **有效期** | 从激活起算的最长使用时间。 |
|
||||||
|
| **用户数** | 集团授权:集团服务器可支持的**最大并发访问用户数**。 |
|
||||||
|
| **起始日期 / 结束日期** | 授权生效、失效的**绝对时间**。 |
|
||||||
|
| **使用次数** | 客户端能成功执行 **Login** 的次数上限。 |
|
||||||
|
| **终端限制** | 单机授权允许在多少台客户端上被激活。 |
|
||||||
|
|
||||||
|
|
||||||
|
通过不同业务(例如演示业务、永久有效单机正式版业务)组合上述属性,对齐销售与交付形态。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 特征项 (Feature):授权在代码中的语义
|
||||||
|
|
||||||
|
- **标识**:特征 **ID**(整数,API 调用用)+ **名称**(界面展示);在产品内唯一。
|
||||||
|
- **产品级**:数量、名称、类型、是否**可覆盖**、特征组等**仅能在产品特征项界面**完成。
|
||||||
|
- **模版 / 授权码级**:只能**选择**特征项、设置特征项的**授权属性**,或修改带「可覆盖」的特征项**值**。
|
||||||
|
|
||||||
|
### 3.1 特征项在产品上的基本属性
|
||||||
|
|
||||||
|
- **ID**:系统分配,用于客户端 API 指定特征项。
|
||||||
|
- **名称**:用途说明;在集团授权管理界面等对用户可见。
|
||||||
|
- **类型**:决定可进行的操作(见下表)。
|
||||||
|
- **可覆盖**:是否允许在模版或授权码中**重写该特征项的值**(不同用户/模版需要不同特征值时应开启)。
|
||||||
|
- **值**:影响特征项 API 行为。
|
||||||
|
|
||||||
|
### 3.2 授权码上的特征项授权属性
|
||||||
|
|
||||||
|
在授权码中可设置/修改(且同时受**整条授权**的授权属性约束):
|
||||||
|
|
||||||
|
- **有效期**:自所在授权码激活起算,单位**天**。
|
||||||
|
- **结束日期**:可用到结束日当天 24 点。
|
||||||
|
- **用户数**:仅**集团授权**类型 SN 可设,为该特征项的可用用户数。
|
||||||
|
|
||||||
|
### 3.3 特征项类型与 API
|
||||||
|
|
||||||
|
|
||||||
|
| 类型 | 含义 | 主要 API |
|
||||||
|
| ------ | ----------- | --------------------------------- |
|
||||||
|
| **只读** | 只读数据,客户端不可写 | `ReadFeature` |
|
||||||
|
| **读写** | 可读可写 | `ReadFeature`、`WriteFeature` |
|
||||||
|
| **算法** | 算法因子,不可直接读写 | `ConvertFeature`(单向转换) |
|
||||||
|
| **密钥** | AES 密钥因子 | `EncryptFeature`、`DecryptFeature` |
|
||||||
|
|
||||||
|
|
||||||
|
所有类型均支持 `**QueryFeature`**、`**ReleaseFeature**`:检查是否存在及是否有效。
|
||||||
|
**集团授权**:`QueryFeature` 会占用模块用户数,`ReleaseFeature` 释放;文档要求二者在代码中**成对**出现。
|
||||||
|
|
||||||
|
### 3.4 特征组
|
||||||
|
|
||||||
|
- 用于管理与勾选;默认有 **「所有」** 组包含全部特征项。
|
||||||
|
- 可新建组并向组内添加特征项;**新特征项默认不会**进入除「所有」外的组,需手工维护。
|
||||||
|
|
||||||
|
### 3.5 实现与安全注意
|
||||||
|
|
||||||
|
- 典型用法:每需单独授权的模块分配**主特征项**做 `QueryFeature` / `ReleaseFeature`;辅以加密类特征项;模块退出前释放占用(集团场景尤其重要)。
|
||||||
|
- 避免**过于频繁**调用特征项 API,以免影响性能。
|
||||||
|
- **DRM 产品**:特征项类型不可设置,且不可选「可覆盖」(用于内容加密密钥)。
|
||||||
|
- **密钥 / 算法**类型:操作员仅读权限时特征值不可见,显示为 `**`**。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 配置项 (Data):非安全存储
|
||||||
|
|
||||||
|
- **用途**:`Name` / `Value` 字符串对,存应用配置或少量运行时数据;**不应**代替特征项做安全控制。
|
||||||
|
- **与特征项区别**:特征项侧重功能点/加密点;配置项侧重应用与用户数据。
|
||||||
|
- **覆盖**:模版或授权码中可创建**产品中未预先定义**的配置项名;若与产品配置项同名则运行时取得**覆盖后**的值。
|
||||||
|
- **删除**:可删除授权码中的配置项;**删除产品或模版中定义的配置项会返回错误**。
|
||||||
|
- **枚举**:`GetDataNum`、`GetDataName` 等。
|
||||||
|
- **可靠性**:客户端库可能**缓存**配置项;异常断电等情况下**近期更新可能丢失**,应用需容错。存储为 **UTF-8**,含中文时客户端需注意编码处理。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 规则 (Rule):云端可编程扩展
|
||||||
|
|
||||||
|
与静态「产品—模版—业务—授权」模型并列,**规则**用于在特定**事件**上注入 **JavaScript 判断 + 动作**,扩展业务逻辑。
|
||||||
|
|
||||||
|
### 5.1 结构
|
||||||
|
|
||||||
|
|
||||||
|
| 组成部分 | 说明 |
|
||||||
|
| ------- | ------------------------------------------- |
|
||||||
|
| **事件** | 规则入口;向触发器提供 `event` 对象(不同事件字段不同)。 |
|
||||||
|
| **触发器** | JavaScript 编写的条件;返回 **布尔**;`true` 时执行动作。 |
|
||||||
|
| **动作** | 触发后执行的操作(如回调);一条规则可有多个动作,**执行顺序不严格保证**;可为空。 |
|
||||||
|
|
||||||
|
|
||||||
|
**约束**:一条规则**仅对应一个事件**;创建后需在列表中**启动**方会在下次事件发生时生效。
|
||||||
|
|
||||||
|
### 5.2 已定义事件(文档列表)
|
||||||
|
|
||||||
|
|
||||||
|
| ID | 代码 | 名称 |
|
||||||
|
| ------ | -------------------------- | -------------------------- |
|
||||||
|
| 0x0001 | `yunbaobao:session_logout` | 云保保客户端会话失效(登出、踢出、过期) |
|
||||||
|
| 0x0101 | `sn:pre_activate` | 授权码**首次**激活**前** |
|
||||||
|
| 0x0102 | `sn:post_activate` | 授权码**首次**激活**后** |
|
||||||
|
| 0x0301 | `device:pre_activate` | 设备激活**前**(设备**重新激活**时也会执行) |
|
||||||
|
| 0x0302 | `device:post_activate` | 设备激活**后** |
|
||||||
|
|
||||||
|
|
||||||
|
### 5.3 事件载荷要点(实现联调参考)
|
||||||
|
|
||||||
|
- 多类事件含 `**snInfo`**:`sn`、`type`(如 FLOAT)、`volumeNumber`(终端限制)、`activeDate`、`startDate`、`endDate`(文档说明为与 `expirationDays` 合计相关)、`expirationDays`、`userNumber`、`transferVolume`、`transferNumber`、`status`、`customInfo`、`ip` 等。
|
||||||
|
- `**sn:post_activate**`、`**device:post_activate**` 等还含 `**device**`:`regDate`、`mid`(设备指纹)、`status`、`deviceCount`(已激活设备总数)等。
|
||||||
|
- `**yunbaobao:session_logout**`:`sn`、`sid`、`type`(LOGOUT / EXPIRED / CLEAR / OTHER)、`logout_time` 等。
|
||||||
|
|
||||||
|
详细字段以官方文档各事件小节为准。
|
||||||
|
|
||||||
|
### 5.4 触发器运行环境
|
||||||
|
|
||||||
|
- 语言:**ECMAScript 5.1**;**禁止使用 `const`、`let`**。
|
||||||
|
- **无**任意 IO;**无** `console.log`;**无** `window`、`document`、`location` 等。
|
||||||
|
- 保存前应用控制台**调试功能**验证脚本。
|
||||||
|
|
||||||
|
### 5.5 动作:回调 (Callback)
|
||||||
|
|
||||||
|
- 配置为 **JSON 数组**;元素示例形态包含 `callback.url`、`callback.headers`(如 `x-bitanswer-token` 用于认证)。
|
||||||
|
- **异步**执行;服务端响应**可被忽略**。
|
||||||
|
- 使用 **POST**,**JSON** body;内容包含 `event`、`rule`、`data`(含对应事件的 `snInfo` / `device` / `ip` 等)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 与实现工作的对应关系(摘要)
|
||||||
|
|
||||||
|
|
||||||
|
| 领域概念 | 实现侧通常对应 |
|
||||||
|
| ----------------- | ------------------------------------------ |
|
||||||
|
| 产品 / 模版 / 业务 / SN | 控制台配置与发放流程;SDK 登录对象与产品绑定 |
|
||||||
|
| 业务授权属性 | 期限、终端、离线、次数、用户并发等策略的建模与校验 |
|
||||||
|
| 特征项类型与 API | 功能开关、加密点、集团用户数占用与释放 |
|
||||||
|
| 配置项 | 非敏感 K/V 同步;注意缓存与持久化边界 |
|
||||||
|
| 规则 | Webhook/内部服务对接激活与设备生命周期;ES5.1 脚本与回调幂等、安全校验 |
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 修订记录
|
||||||
|
|
||||||
|
|
||||||
|
| 日期 | 说明 |
|
||||||
|
| ---------- | ------------------------- |
|
||||||
|
| 2026-04-06 | 初版:依据「授权设计与创建」「规则」两篇整理入库。 |
|
||||||
@@ -1,12 +1,11 @@
|
|||||||
# 比特授权云 · C 语言接口定义(离线摘录)
|
# 比特授权云 · C 语言接口定义(离线摘录)
|
||||||
|
|
||||||
> 本文档由官网页面离线摘录并整理排版,便于本地检索。官方地址:<https://doc.bitanswer.cn/docs/client-api/c-interface-definitions-v2/>
|
> 本文档由官网页面离线摘录并整理排版,便于本地检索。官方地址:[https://doc.bitanswer.cn/docs/client-api/c-interface-definitions-v2/](https://doc.bitanswer.cn/docs/client-api/c-interface-definitions-v2/)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 认证
|
## 认证
|
||||||
|
|
||||||
|
|
||||||
### Bit_Login / Bit_LoginEx
|
### Bit_Login / Bit_LoginEx
|
||||||
|
|
||||||
```c
|
```c
|
||||||
@@ -26,6 +25,7 @@ BIT_STATUS Bit_LoginEx(
|
|||||||
BIT_HANDLE *pHandle,
|
BIT_HANDLE *pHandle,
|
||||||
LOGIN_MODE mode);
|
LOGIN_MODE mode);
|
||||||
```
|
```
|
||||||
|
|
||||||
授权登录。初始化运行环境,获取操作句柄。必须在除升级函数之外的其它操作前执行。根据登录模式的不同可能需要连接授权服务器。
|
授权登录。初始化运行环境,获取操作句柄。必须在除升级函数之外的其它操作前执行。根据登录模式的不同可能需要连接授权服务器。
|
||||||
|
|
||||||
Bit_Login 等价于 Bit_LoginEx(…, featureId=0,szReserved=NULL, …),当需要登录包含指定特征项的授权时才需要调用Bit_LoginEx。
|
Bit_Login 等价于 Bit_LoginEx(…, featureId=0,szReserved=NULL, …),当需要登录包含指定特征项的授权时才需要调用Bit_LoginEx。
|
||||||
@@ -50,6 +50,7 @@ Bit_Login 等价于 Bit_LoginEx(…, featureId=0,szReserved=NULL, …),当
|
|||||||
lic:///tmp,lic:///data,bit://127.0.0.1:8273
|
lic:///tmp,lic:///data,bit://127.0.0.1:8273
|
||||||
/tmp,/data,127.0.0.1:8273 // 等价第1条
|
/tmp,/data,127.0.0.1:8273 // 等价第1条
|
||||||
```
|
```
|
||||||
|
|
||||||
> 注意:该接口支持通过[配置文件](https://doc.bitanswer.cn/docs/client-api/examples-of-using-the-sdk/#%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6)和[环境变量](https://doc.bitanswer.cn/docs/client-api/examples-of-using-the-sdk/#%E7%8E%AF%E5%A2%83%E5%8F%98%E9%87%8F)来设置该值,如果同时设置优先级为:环境变量 > API传入 > 配置文件。
|
> 注意:该接口支持通过[配置文件](https://doc.bitanswer.cn/docs/client-api/examples-of-using-the-sdk/#%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6)和[环境变量](https://doc.bitanswer.cn/docs/client-api/examples-of-using-the-sdk/#%E7%8E%AF%E5%A2%83%E5%8F%98%E9%87%8F)来设置该值,如果同时设置优先级为:环境变量 > API传入 > 配置文件。
|
||||||
|
|
||||||
- **szSN** - [IN] 授权码(SN)字符串。如果为空(空串或NULL)则尝试寻找所有当前本机可用的SN。
|
- **szSN** - [IN] 授权码(SN)字符串。如果为空(空串或NULL)则尝试寻找所有当前本机可用的SN。
|
||||||
@@ -57,10 +58,10 @@ lic:///tmp,lic:///data,bit://127.0.0.1:8273
|
|||||||
|
|
||||||
| **类型** | **格式** | **说明** |
|
| **类型** | **格式** | **说明** |
|
||||||
| -------------------------------- | ---------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------ |
|
| -------------------------------- | ---------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------ |
|
||||||
| **授权码** | 比特授权云平台产生的SN 示例:`JNLTGZSE********` | 检查指定SN的License |
|
| **授权码** | 比特授权云平台产生的SN 示例:`JNLTGZSE******`** | 检查指定SN的License |
|
||||||
| **BIT-ID(BIT-ID硬件,类似于加密的USB设备)** | 格式:#`<序号>` 或 #bitid:`<序号>` 说明:`<序号>` 为 BIT-ID 的索引号,从 0 开始递增 示例:#0、#1、#`bitid:0、#bitid:1` | 使用BIT-ID授权 |
|
| **BIT-ID(BIT-ID硬件,类似于加密的USB设备)** | 格式:#`<序号>` 或 #bitid:`<序号>` 说明:`<序号>` 为 BIT-ID 的索引号,从 0 开始递增 示例:#0、#1、#`bitid:0、#bitid:1` | 使用BIT-ID授权 |
|
||||||
| **激活口令(帐号授权密码或SN激活口令)** | <`xxx`> 通过“<>”包裹起来,中间部分为密码 示例:<1234>, <1233>65447> | 激活口令,是指授权码激活时,需要输入的一种口令,激活后不再需要,目前仅支持浮动授权 使用帐号授权时保存的是帐号授权的密码(已经不推荐),其它授权代表激活口令 |
|
| **激活口令(帐号授权密码或SN激活口令)** | <`xxx`> 通过“<>”包裹起来,中间部分为密码 示例:<1234>, <1233>65447> | 激活口令,是指授权码激活时,需要输入的一种口令,激活后不再需要,目前仅支持浮动授权 使用帐号授权时保存的是帐号授权的密码(已经不推荐),其它授权代表激活口令 |
|
||||||
| **既输入SN又输入激活口令** | `xxx<password>`,授权码后边紧跟“<>” 示例:`JNLTGZSE********`<123> | |
|
| **既输入SN又输入激活口令** | `xxx<password>`,授权码后边紧跟“<>” 示例:`JNLTGZSE******`**<123> | |
|
||||||
|
|
||||||
|
|
||||||
- **featureId** - [IN] 登录授权所需要包含的特征项ID。
|
- **featureId** - [IN] 登录授权所需要包含的特征项ID。
|
||||||
@@ -73,6 +74,7 @@ lic:///tmp,lic:///data,bit://127.0.0.1:8273
|
|||||||
<feature id="" name="" ver=""/>
|
<feature id="" name="" ver=""/>
|
||||||
</scope>
|
</scope>
|
||||||
```
|
```
|
||||||
|
|
||||||
- **pApplicationData** - [IN] 产品识别码,在Bitanswer SDK头文件里。
|
- **pApplicationData** - [IN] 产品识别码,在Bitanswer SDK头文件里。
|
||||||
- **pHandle** - [OUT] 通过Login函数返回的上下文句柄。
|
- **pHandle** - [OUT] 通过Login函数返回的上下文句柄。
|
||||||
- **mode** - [IN] 登录模式,按位操作。
|
- **mode** - [IN] 登录模式,按位操作。
|
||||||
@@ -154,6 +156,7 @@ BIT_STATUS Bit_LoginByToken(
|
|||||||
BIT_UCHAR *pApplicationData,
|
BIT_UCHAR *pApplicationData,
|
||||||
BIT_HANDLE *pHandle)
|
BIT_HANDLE *pHandle)
|
||||||
```
|
```
|
||||||
|
|
||||||
帐号授权的登录接口。仅支持两种认证方式,这里是使用比特授权云平台自己产生的access_token进行认证。
|
帐号授权的登录接口。仅支持两种认证方式,这里是使用比特授权云平台自己产生的access_token进行认证。
|
||||||
|
|
||||||
### 参数
|
### 参数
|
||||||
@@ -182,6 +185,7 @@ BIT_STATUS Bit_LoginByTokenEx(
|
|||||||
BIT_UCHAR *pApplicationData,
|
BIT_UCHAR *pApplicationData,
|
||||||
BIT_HANDLE *pHandle)
|
BIT_HANDLE *pHandle)
|
||||||
```
|
```
|
||||||
|
|
||||||
帐号授权的登录接口。仅支持两种认证方式,这里是使用OIDC协议产生的id_token认证。
|
帐号授权的登录接口。仅支持两种认证方式,这里是使用OIDC协议产生的id_token认证。
|
||||||
|
|
||||||
### 参数
|
### 参数
|
||||||
@@ -213,6 +217,7 @@ BIT_STATUS Bit_LoginByPassword(
|
|||||||
BIT_UCHAR *pApplicationData,
|
BIT_UCHAR *pApplicationData,
|
||||||
BIT_HANDLE *pHandle);
|
BIT_HANDLE *pHandle);
|
||||||
```
|
```
|
||||||
|
|
||||||
通过用户名和密码登录帐号授权。
|
通过用户名和密码登录帐号授权。
|
||||||
|
|
||||||
### 参数
|
### 参数
|
||||||
@@ -231,6 +236,7 @@ BIT_STATUS Bit_LoginByPassword(
|
|||||||
BIT_STATUS Bit_Logout (
|
BIT_STATUS Bit_Logout (
|
||||||
BIT_HANDLE handle)
|
BIT_HANDLE handle)
|
||||||
```
|
```
|
||||||
|
|
||||||
此函数用于释放上下文句柄,退出登录状态,与Login相关接口一一对应。
|
此函数用于释放上下文句柄,退出登录状态,与Login相关接口一一对应。
|
||||||
|
|
||||||
### 参数
|
### 参数
|
||||||
@@ -247,6 +253,7 @@ BIT_STATUS Bit_Revoke (
|
|||||||
BIT_CHAR *pRevocationInfo,
|
BIT_CHAR *pRevocationInfo,
|
||||||
BIT_UINT32 *pRevocationInfoSize)
|
BIT_UINT32 *pRevocationInfoSize)
|
||||||
```
|
```
|
||||||
|
|
||||||
从客户端迁出已激活的浮动授权码。授权码迁出后,可以用于其它的客户端。根据输入参数的不同,本函数可用于在线或离线迁出。
|
从客户端迁出已激活的浮动授权码。授权码迁出后,可以用于其它的客户端。根据输入参数的不同,本函数可用于在线或离线迁出。
|
||||||
|
|
||||||
### 参数
|
### 参数
|
||||||
@@ -264,6 +271,7 @@ BIT_STATUS Bit_RemoveSn (
|
|||||||
BIT_PCSTR szSN,
|
BIT_PCSTR szSN,
|
||||||
BIT_UCHAR *pApplicationData)
|
BIT_UCHAR *pApplicationData)
|
||||||
```
|
```
|
||||||
|
|
||||||
删除指定授权码在本机的授权数据。
|
删除指定授权码在本机的授权数据。
|
||||||
|
|
||||||
### 参数
|
### 参数
|
||||||
@@ -278,6 +286,7 @@ BIT_STATUS Bit_Heartbeat(
|
|||||||
BIT_HANDLE handle,
|
BIT_HANDLE handle,
|
||||||
BIT_UINT32 *pReconnectsNum)
|
BIT_UINT32 *pReconnectsNum)
|
||||||
```
|
```
|
||||||
|
|
||||||
手动心跳,可以无限次调用,10s只会触发一次。
|
手动心跳,可以无限次调用,10s只会触发一次。
|
||||||
|
|
||||||
注:当自动心跳停止后,调用该接口可以尝试恢复自动心跳。
|
注:当自动心跳停止后,调用该接口可以尝试恢复自动心跳。
|
||||||
@@ -298,6 +307,7 @@ BIT_STATUS Bit_SessionControl(
|
|||||||
BIT_CHAR *pValue,
|
BIT_CHAR *pValue,
|
||||||
BIT_UINT32 *pValueLen)
|
BIT_UINT32 *pValueLen)
|
||||||
```
|
```
|
||||||
|
|
||||||
控制或设置session的信息。具体使用场景可参考《浏览器并发控制》文档。
|
控制或设置session的信息。具体使用场景可参考《浏览器并发控制》文档。
|
||||||
|
|
||||||
### 参数
|
### 参数
|
||||||
@@ -325,6 +335,7 @@ BIT_STATUS Bit_SetSessionState (
|
|||||||
BIT_UINT32 state,
|
BIT_UINT32 state,
|
||||||
BIT_VOID *pReserved)
|
BIT_VOID *pReserved)
|
||||||
```
|
```
|
||||||
|
|
||||||
设置客户端的状态为空闲状态或繁忙状态或激活状态。
|
设置客户端的状态为空闲状态或繁忙状态或激活状态。
|
||||||
|
|
||||||
### 参数
|
### 参数
|
||||||
@@ -358,7 +369,6 @@ if (用户正在操作) {
|
|||||||
|
|
||||||
## 激活升级
|
## 激活升级
|
||||||
|
|
||||||
|
|
||||||
### Bit_UpdateOnline
|
### Bit_UpdateOnline
|
||||||
|
|
||||||
```c
|
```c
|
||||||
@@ -367,6 +377,7 @@ BIT_STATUS Bit_UpdateOnline (
|
|||||||
BIT_PCSTR szSN,
|
BIT_PCSTR szSN,
|
||||||
BIT_UCHAR *pApplicationData)
|
BIT_UCHAR *pApplicationData)
|
||||||
```
|
```
|
||||||
|
|
||||||
此函数用于与授权服务器在线连接,自动完成本地授权的升级操作。本函数需要进行网络连接。
|
此函数用于与授权服务器在线连接,自动完成本地授权的升级操作。本函数需要进行网络连接。
|
||||||
|
|
||||||
### 参数
|
### 参数
|
||||||
@@ -395,6 +406,7 @@ BIT_STATUS Bit_GetRequestInfo (
|
|||||||
BIT_CHAR *pRequestInfo,
|
BIT_CHAR *pRequestInfo,
|
||||||
BIT_UINT32 *pRequestInfoSize)
|
BIT_UINT32 *pRequestInfoSize)
|
||||||
```
|
```
|
||||||
|
|
||||||
获取当前运行环境的升级请求码,用于发起本地授权激活及升级请求。如果第一次调用返回错误码260,说明传入的pRequestInfoSize太小,可传入返回的pRequestInfoSize再重新调用一次。
|
获取当前运行环境的升级请求码,用于发起本地授权激活及升级请求。如果第一次调用返回错误码260,说明传入的pRequestInfoSize太小,可传入返回的pRequestInfoSize再重新调用一次。
|
||||||
|
|
||||||
### 参数
|
### 参数
|
||||||
@@ -442,6 +454,7 @@ BIT_STATUS Bit_ApplyUpdateInfo(
|
|||||||
BIT_CHAR *pReceipt,
|
BIT_CHAR *pReceipt,
|
||||||
BIT_UINT32 *pReceiptSize)
|
BIT_UINT32 *pReceiptSize)
|
||||||
```
|
```
|
||||||
|
|
||||||
应用升级码完成本地授权激活或升级。本函数必须在获取请求码的同一环境下执行。
|
应用升级码完成本地授权激活或升级。本函数必须在获取请求码的同一环境下执行。
|
||||||
|
|
||||||
### 参数
|
### 参数
|
||||||
@@ -475,6 +488,7 @@ BIT_STATUS Bit_ApplyUpdateInfoEx(
|
|||||||
BIT_CHAR *pReceipt,
|
BIT_CHAR *pReceipt,
|
||||||
BIT_UINT32 *pReceiptSize);
|
BIT_UINT32 *pReceiptSize);
|
||||||
```
|
```
|
||||||
|
|
||||||
应用升级码完成远程集团授权激活或升级。一般不建议使用。
|
应用升级码完成远程集团授权激活或升级。一般不建议使用。
|
||||||
|
|
||||||
### 参数
|
### 参数
|
||||||
@@ -495,6 +509,7 @@ BIT_STATUS Bit_GetUpdateInfo (
|
|||||||
BIT_CHAR *pUpdateInfo,
|
BIT_CHAR *pUpdateInfo,
|
||||||
BIT_UINT32 *pUpdateInfoSize)
|
BIT_UINT32 *pUpdateInfoSize)
|
||||||
```
|
```
|
||||||
|
|
||||||
使用请求码与授权服务器进行连接,获取升级码。本函数需要进行网络连接。
|
使用请求码与授权服务器进行连接,获取升级码。本函数需要进行网络连接。
|
||||||
|
|
||||||
### 参数
|
### 参数
|
||||||
@@ -508,7 +523,6 @@ BIT_STATUS Bit_GetUpdateInfo (
|
|||||||
|
|
||||||
## 特征项操作
|
## 特征项操作
|
||||||
|
|
||||||
|
|
||||||
### Bit_BatchBegin
|
### Bit_BatchBegin
|
||||||
|
|
||||||
```c
|
```c
|
||||||
@@ -516,6 +530,7 @@ BIT_STATUS Bit_BatchBegin(
|
|||||||
BIT_HANDLE handle,
|
BIT_HANDLE handle,
|
||||||
BIT_UINT32 mode)
|
BIT_UINT32 mode)
|
||||||
```
|
```
|
||||||
|
|
||||||
开启批量Query模式,调用此API后调用的所有Query相关API,全部由Bit_BatchEnd接口批量提交连接集团服务。
|
开启批量Query模式,调用此API后调用的所有Query相关API,全部由Bit_BatchEnd接口批量提交连接集团服务。
|
||||||
|
|
||||||
### 参数
|
### 参数
|
||||||
@@ -538,6 +553,7 @@ BIT_STATUS Bit_BatchEnd(
|
|||||||
BIT_UINT32 *pResultList,
|
BIT_UINT32 *pResultList,
|
||||||
BIT_UINT32 *pResultListSize)
|
BIT_UINT32 *pResultListSize)
|
||||||
```
|
```
|
||||||
|
|
||||||
批量提交Query请求。
|
批量提交Query请求。
|
||||||
|
|
||||||
### 参数
|
### 参数
|
||||||
@@ -577,6 +593,7 @@ BIT_STATUS Bit_QueryFeature (
|
|||||||
BIT_UINT32 featureId,
|
BIT_UINT32 featureId,
|
||||||
BIT_UINT32 *pCapacity)
|
BIT_UINT32 *pCapacity)
|
||||||
```
|
```
|
||||||
|
|
||||||
开发商可以对软件的某个功能进行单独授权,当程序运行该模块时,可以通过该接口检查是否可用。
|
开发商可以对软件的某个功能进行单独授权,当程序运行该模块时,可以通过该接口检查是否可用。
|
||||||
|
|
||||||
对于单机授权:只检查特征项是否可用。
|
对于单机授权:只检查特征项是否可用。
|
||||||
@@ -610,6 +627,7 @@ BIT_STATUS Bit_ReleaseFeature (
|
|||||||
BIT_UINT32 featureId,
|
BIT_UINT32 featureId,
|
||||||
BIT_UINT32 *pCapacity)
|
BIT_UINT32 *pCapacity)
|
||||||
```
|
```
|
||||||
|
|
||||||
释放所占用的用户数,该函数和Bit_QueryFeature一一对应。
|
释放所占用的用户数,该函数和Bit_QueryFeature一一对应。
|
||||||
|
|
||||||
### 参数
|
### 参数
|
||||||
@@ -643,6 +661,7 @@ BIT_STATUS Bit_QueryFeatureEx (
|
|||||||
BIT_UINT32 *pCapacity,
|
BIT_UINT32 *pCapacity,
|
||||||
BIT_PCSTR xmlScope)
|
BIT_PCSTR xmlScope)
|
||||||
```
|
```
|
||||||
|
|
||||||
集团授权专用,该接口支持占用指定用户数,支持通过特征项版本进行检查。
|
集团授权专用,该接口支持占用指定用户数,支持通过特征项版本进行检查。
|
||||||
|
|
||||||
### 参数
|
### 参数
|
||||||
@@ -702,6 +721,7 @@ BIT_STATUS Bit_ReleaseFeatureEx (
|
|||||||
BIT_UINT32 *pCapacity,
|
BIT_UINT32 *pCapacity,
|
||||||
BIT_PCSTR xmlScope)
|
BIT_PCSTR xmlScope)
|
||||||
```
|
```
|
||||||
|
|
||||||
集团授权专用,释放指定的用户数。该接口与Bit_QueryFeatureEx对应。
|
集团授权专用,释放指定的用户数。该接口与Bit_QueryFeatureEx对应。
|
||||||
|
|
||||||
### 参数
|
### 参数
|
||||||
@@ -740,6 +760,7 @@ BIT_STATUS Bit_QueryFeatureEx2 (
|
|||||||
BIT_PCSTR xmlScope,
|
BIT_PCSTR xmlScope,
|
||||||
BIT_TICKET *pTicket)
|
BIT_TICKET *pTicket)
|
||||||
```
|
```
|
||||||
|
|
||||||
可以使用“特征项名称 + 特征项版本”进行用户数占用,支持队列模式。
|
可以使用“特征项名称 + 特征项版本”进行用户数占用,支持队列模式。
|
||||||
|
|
||||||
对于单机授权:只检查特征项是否可用。
|
对于单机授权:只检查特征项是否可用。
|
||||||
@@ -796,6 +817,7 @@ BIT_STATUS Bit_ReleaseFeatureEx2 (
|
|||||||
BIT_TICKET ticket,
|
BIT_TICKET ticket,
|
||||||
BIT_UINT32 consumed)
|
BIT_UINT32 consumed)
|
||||||
```
|
```
|
||||||
|
|
||||||
释放ticket所占用的用户数。该函数和Bit_QueryFeatureEx2一一对应。
|
释放ticket所占用的用户数。该函数和Bit_QueryFeatureEx2一一对应。
|
||||||
|
|
||||||
### 参数
|
### 参数
|
||||||
@@ -811,6 +833,7 @@ if (status == BIT_SUCCESS) {
|
|||||||
// 释放ticket占用的用户数
|
// 释放ticket占用的用户数
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
**特征项Query相关接口比较**
|
**特征项Query相关接口比较**
|
||||||
|
|
||||||
|
|
||||||
@@ -830,6 +853,7 @@ BIT_STATUS Bit_GetFeatureInfo2 (
|
|||||||
BIT_PCSTR xmlScope,
|
BIT_PCSTR xmlScope,
|
||||||
BIT_INT32 *pExpired)
|
BIT_INT32 *pExpired)
|
||||||
```
|
```
|
||||||
|
|
||||||
检查特征项是否存在,不会占用授权码或特征项的用户数,获取特征项的剩余有效期。
|
检查特征项是否存在,不会占用授权码或特征项的用户数,获取特征项的剩余有效期。
|
||||||
|
|
||||||
### 参数
|
### 参数
|
||||||
@@ -845,6 +869,7 @@ BIT_STATUS Bit_GetFeatureInfo2 (
|
|||||||
|
|
||||||
<!-- 或者直接输入:'1.1' -->
|
<!-- 或者直接输入:'1.1' -->
|
||||||
```
|
```
|
||||||
|
|
||||||
- **pExpired** - [OUT] 返回该特征项的剩余有效期,正数代表剩余有效期,负数代表过期天数,36500代表永久有效。
|
- **pExpired** - [OUT] 返回该特征项的剩余有效期,正数代表剩余有效期,负数代表过期天数,36500代表永久有效。
|
||||||
|
|
||||||
### Bit_GetFeatureInfoEx2
|
### Bit_GetFeatureInfoEx2
|
||||||
@@ -857,6 +882,7 @@ BIT_STATUS Bit_GetFeatureInfoEx2(
|
|||||||
BIT_CHAR *pFeatureInfo,
|
BIT_CHAR *pFeatureInfo,
|
||||||
BIT_UINT32 *pFeatureInfoSize);
|
BIT_UINT32 *pFeatureInfoSize);
|
||||||
```
|
```
|
||||||
|
|
||||||
获取指定feature的信息,以XML格式返回。
|
获取指定feature的信息,以XML格式返回。
|
||||||
|
|
||||||
### 参数
|
### 参数
|
||||||
@@ -872,6 +898,7 @@ BIT_STATUS Bit_GetFeatureInfoEx2(
|
|||||||
|
|
||||||
<!-- 或者直接输入:'1.1' -->
|
<!-- 或者直接输入:'1.1' -->
|
||||||
```
|
```
|
||||||
|
|
||||||
- **pFeatureInfo** - [OUT] feature信息存储区地址。
|
- **pFeatureInfo** - [OUT] feature信息存储区地址。
|
||||||
- **pFeatureInfoSize** - [IN/OUT] 会话存储区大小。
|
- **pFeatureInfoSize** - [IN/OUT] 会话存储区大小。
|
||||||
|
|
||||||
@@ -884,6 +911,7 @@ BIT_STATUS Bit_GetTicketInfo(
|
|||||||
BIT_CHAR *pXmlInfo,
|
BIT_CHAR *pXmlInfo,
|
||||||
BIT_UINT32 *pSize)
|
BIT_UINT32 *pSize)
|
||||||
```
|
```
|
||||||
|
|
||||||
获取ticket信息。
|
获取ticket信息。
|
||||||
|
|
||||||
### 参数
|
### 参数
|
||||||
@@ -946,6 +974,7 @@ BIT_STATUS Bit_ConvertFeature(
|
|||||||
BIT_UINT32 para4,
|
BIT_UINT32 para4,
|
||||||
BIT_UINT32 *pResult);
|
BIT_UINT32 *pResult);
|
||||||
```
|
```
|
||||||
|
|
||||||
使用“算法”类型的特征项对输入参数进行变换操作,得到唯一对应的4字节结果。
|
使用“算法”类型的特征项对输入参数进行变换操作,得到唯一对应的4字节结果。
|
||||||
|
|
||||||
### 参数
|
### 参数
|
||||||
@@ -981,6 +1010,7 @@ BIT_STATUS Bit_EncryptFeature(
|
|||||||
BIT_VOID *pCipherBuffer,
|
BIT_VOID *pCipherBuffer,
|
||||||
BIT_UINT32 dataBufferSize);
|
BIT_UINT32 dataBufferSize);
|
||||||
```
|
```
|
||||||
|
|
||||||
使用“密钥”类型的特征项对输入的明文进行加密,返回密文结果。
|
使用“密钥”类型的特征项对输入的明文进行加密,返回密文结果。
|
||||||
|
|
||||||
### 参数
|
### 参数
|
||||||
@@ -1013,6 +1043,7 @@ BIT_STATUS Bit_DecryptFeature(
|
|||||||
BIT_VOID *pPlainBuffer,
|
BIT_VOID *pPlainBuffer,
|
||||||
BIT_UINT32 dataBufferSize);
|
BIT_UINT32 dataBufferSize);
|
||||||
```
|
```
|
||||||
|
|
||||||
使用“密钥”类型的特征项对输入的密文进行解密,返回明文结果。
|
使用“密钥”类型的特征项对输入的密文进行解密,返回明文结果。
|
||||||
|
|
||||||
### 参数
|
### 参数
|
||||||
@@ -1043,6 +1074,7 @@ BIT_STATUS Bit_ReadFeature(
|
|||||||
BIT_UINT32 featureId,
|
BIT_UINT32 featureId,
|
||||||
BIT_UINT32 *pFeatureValue);
|
BIT_UINT32 *pFeatureValue);
|
||||||
```
|
```
|
||||||
|
|
||||||
读取特征项的数据内容,可用于“只读”和“读写”特征类型。
|
读取特征项的数据内容,可用于“只读”和“读写”特征类型。
|
||||||
|
|
||||||
### 参数
|
### 参数
|
||||||
@@ -1059,6 +1091,7 @@ BIT_STATUS Bit_WriteFeature(
|
|||||||
BIT_UINT32 featureId,
|
BIT_UINT32 featureId,
|
||||||
BIT_UINT32 featureValue);
|
BIT_UINT32 featureValue);
|
||||||
```
|
```
|
||||||
|
|
||||||
更新“读写”类型的特征项的数据内容。
|
更新“读写”类型的特征项的数据内容。
|
||||||
|
|
||||||
### 参数
|
### 参数
|
||||||
@@ -1069,7 +1102,6 @@ BIT_STATUS Bit_WriteFeature(
|
|||||||
|
|
||||||
## 配置项操作
|
## 配置项操作
|
||||||
|
|
||||||
|
|
||||||
### Bit_GetDataItem
|
### Bit_GetDataItem
|
||||||
|
|
||||||
```c
|
```c
|
||||||
@@ -1079,6 +1111,7 @@ BIT_STATUS Bit_GetDataItem (
|
|||||||
BIT_VOID *pDataItemValue,
|
BIT_VOID *pDataItemValue,
|
||||||
BIT_UINT32 *pDataItemValueSize)
|
BIT_UINT32 *pDataItemValueSize)
|
||||||
```
|
```
|
||||||
|
|
||||||
读取指定的配置项数据。
|
读取指定的配置项数据。
|
||||||
|
|
||||||
### 参数
|
### 参数
|
||||||
@@ -1097,6 +1130,7 @@ BIT_STATUS Bit_SetDataItem (
|
|||||||
BIT_VOID *pDataItemValue,
|
BIT_VOID *pDataItemValue,
|
||||||
BIT_UINT32 dataItemValueSize)
|
BIT_UINT32 dataItemValueSize)
|
||||||
```
|
```
|
||||||
|
|
||||||
创建或更新配置项。如果相同名称的配置项存在,则会更新其中的数据;否则将添加新的授权码配置项。
|
创建或更新配置项。如果相同名称的配置项存在,则会更新其中的数据;否则将添加新的授权码配置项。
|
||||||
|
|
||||||
### 参数
|
### 参数
|
||||||
@@ -1113,6 +1147,7 @@ BIT_STATUS Bit_GetDataItemNum (
|
|||||||
BIT_HANDLE handle,
|
BIT_HANDLE handle,
|
||||||
BIT_UINT32 *pNum)
|
BIT_UINT32 *pNum)
|
||||||
```
|
```
|
||||||
|
|
||||||
此函数用于获取可访问配置项的数量,一般用于配置项的枚举操作。
|
此函数用于获取可访问配置项的数量,一般用于配置项的枚举操作。
|
||||||
|
|
||||||
### 参数
|
### 参数
|
||||||
@@ -1129,6 +1164,7 @@ BIT_STATUS Bit_GetDataItemName (
|
|||||||
BIT_CHAR *pDataItemName,
|
BIT_CHAR *pDataItemName,
|
||||||
BIT_UINT32 *pDataItemNameSize)
|
BIT_UINT32 *pDataItemNameSize)
|
||||||
```
|
```
|
||||||
|
|
||||||
根据配置项索引获取其名称,一般用于配置项的枚举操作。
|
根据配置项索引获取其名称,一般用于配置项的枚举操作。
|
||||||
|
|
||||||
### 参数
|
### 参数
|
||||||
@@ -1145,6 +1181,7 @@ BIT_STATUS Bit_RemoveDataItem (
|
|||||||
BIT_HANDLE handle,
|
BIT_HANDLE handle,
|
||||||
BIT_PCSTR szDataItemName)
|
BIT_PCSTR szDataItemName)
|
||||||
```
|
```
|
||||||
|
|
||||||
删除指定的配置项。该操作无法删除通过控制台设置的产品配置项或模版配置项。
|
删除指定的配置项。该操作无法删除通过控制台设置的产品配置项或模版配置项。
|
||||||
|
|
||||||
### 参数
|
### 参数
|
||||||
@@ -1154,7 +1191,6 @@ BIT_STATUS Bit_RemoveDataItem (
|
|||||||
|
|
||||||
## 信息获取
|
## 信息获取
|
||||||
|
|
||||||
|
|
||||||
### Bit_GetSessionInfo
|
### Bit_GetSessionInfo
|
||||||
|
|
||||||
```c
|
```c
|
||||||
@@ -1164,6 +1200,7 @@ BIT_STATUS Bit_GetSessionInfo (
|
|||||||
BIT_CHAR *pSessionInfo,
|
BIT_CHAR *pSessionInfo,
|
||||||
BIT_UINT32 *pSessionInfoSize)
|
BIT_UINT32 *pSessionInfoSize)
|
||||||
```
|
```
|
||||||
|
|
||||||
获取当前会话信息,以字符串形式返回。根据获取的内容不同,返回结果可能是XML格式或非XML格式。返回数据中的日期项已根据客户端的本地时区进行调整。如果Login时未指定SN,返回串为当前系统所有可用SN的综合结果。
|
获取当前会话信息,以字符串形式返回。根据获取的内容不同,返回结果可能是XML格式或非XML格式。返回数据中的日期项已根据客户端的本地时区进行调整。如果Login时未指定SN,返回串为当前系统所有可用SN的综合结果。
|
||||||
|
|
||||||
当函数返回BIT_ERR_BUFFER_SMALL时,pSessionInfoSize保存的是实际数据的大小。
|
当函数返回BIT_ERR_BUFFER_SMALL时,pSessionInfoSize保存的是实际数据的大小。
|
||||||
@@ -1242,6 +1279,7 @@ BIT_STATUS Bit_GetInfo (
|
|||||||
BIT_CHAR *pInfo,
|
BIT_CHAR *pInfo,
|
||||||
BIT_UINT32 *pInfoSize)
|
BIT_UINT32 *pInfoSize)
|
||||||
```
|
```
|
||||||
|
|
||||||
获取本地授权信息。
|
获取本地授权信息。
|
||||||
|
|
||||||
如果要获取集团授权的信息,则szSN输入“`@bit://ip:port`”,会获取集团授权信息(仅支持type=BIT_INFO_SN)。
|
如果要获取集团授权的信息,则szSN输入“`@bit://ip:port`”,会获取集团授权信息(仅支持type=BIT_INFO_SN)。
|
||||||
@@ -1281,6 +1319,7 @@ BIT_STATUS Bit_GetServerInfo(
|
|||||||
BIT_CHAR *pServerInfo,
|
BIT_CHAR *pServerInfo,
|
||||||
BIT_UINT32 *pServerInfoSize);
|
BIT_UINT32 *pServerInfoSize);
|
||||||
```
|
```
|
||||||
|
|
||||||
获取集团服务的License信息,数据以XML格式返回。调用此函数前客户端不需要执行登录操作。
|
获取集团服务的License信息,数据以XML格式返回。调用此函数前客户端不需要执行登录操作。
|
||||||
|
|
||||||
### 参数
|
### 参数
|
||||||
@@ -1321,6 +1360,7 @@ BIT_STATUS Bit_GetServerInfo(
|
|||||||
……
|
……
|
||||||
</features>
|
</features>
|
||||||
```
|
```
|
||||||
|
|
||||||
1. 指定分组(group)查询
|
1. 指定分组(group)查询
|
||||||
|
|
||||||
仅支持BIT_SERVER_INFO_SN_USERS、BIT_SERVER_INFO_FEATURE_USERS、BIT_SERVER_INFO_FEATURE_USERS_EX三种类型,通过name限定待查询的分组。
|
仅支持BIT_SERVER_INFO_SN_USERS、BIT_SERVER_INFO_FEATURE_USERS、BIT_SERVER_INFO_FEATURE_USERS_EX三种类型,通过name限定待查询的分组。
|
||||||
@@ -1332,6 +1372,7 @@ BIT_STATUS Bit_GetServerInfo(
|
|||||||
……
|
……
|
||||||
</groups>
|
</groups>
|
||||||
```
|
```
|
||||||
|
|
||||||
> 说明:通过Bit_SetCustomInfo的CUSTOM_GROUP_NAME(0xA)设置分组。
|
> 说明:通过Bit_SetCustomInfo的CUSTOM_GROUP_NAME(0xA)设置分组。
|
||||||
|
|
||||||
### 返回XML数据说明
|
### 返回XML数据说明
|
||||||
@@ -1360,6 +1401,7 @@ XML返回示例:
|
|||||||
BIT_STATUS Bit_GetVersion (
|
BIT_STATUS Bit_GetVersion (
|
||||||
BIT_UINT32 *pVersion)
|
BIT_UINT32 *pVersion)
|
||||||
```
|
```
|
||||||
|
|
||||||
获取客户端安全库版本号。
|
获取客户端安全库版本号。
|
||||||
|
|
||||||
### 参数
|
### 参数
|
||||||
@@ -1373,6 +1415,7 @@ BIT_STATUS Bit_GetNextHandle(
|
|||||||
BIT_HANDLE handle,
|
BIT_HANDLE handle,
|
||||||
BIT_HANDLE *pNextHandle)
|
BIT_HANDLE *pNextHandle)
|
||||||
```
|
```
|
||||||
|
|
||||||
用来遍历当前进程内的handle。
|
用来遍历当前进程内的handle。
|
||||||
|
|
||||||
### 参数
|
### 参数
|
||||||
@@ -1398,6 +1441,7 @@ BIT_STATUS Bit_TestBitService (
|
|||||||
BIT_UINT32 featureId,
|
BIT_UINT32 featureId,
|
||||||
BIT_UCHAR *pApplicationData)
|
BIT_UCHAR *pApplicationData)
|
||||||
```
|
```
|
||||||
|
|
||||||
测试集团授权的特征项是否可用,不会占用授权码或特征项的用户数。
|
测试集团授权的特征项是否可用,不会占用授权码或特征项的用户数。
|
||||||
|
|
||||||
### 参数
|
### 参数
|
||||||
@@ -1421,6 +1465,7 @@ BIT_STATUS Bit_GetProductPath(
|
|||||||
BIT_CHAR *pPath,
|
BIT_CHAR *pPath,
|
||||||
BIT_UINT32 lenPath);
|
BIT_UINT32 lenPath);
|
||||||
```
|
```
|
||||||
|
|
||||||
获取授权存储目录。
|
获取授权存储目录。
|
||||||
|
|
||||||
### 参数
|
### 参数
|
||||||
@@ -1434,6 +1479,7 @@ BIT_STATUS Bit_GetProductPath(
|
|||||||
```c
|
```c
|
||||||
BIT_STATUS Bit_GetLastError();
|
BIT_STATUS Bit_GetLastError();
|
||||||
```
|
```
|
||||||
|
|
||||||
获取上一个API调用返回的错误码。
|
获取上一个API调用返回的错误码。
|
||||||
|
|
||||||
### 参数
|
### 参数
|
||||||
@@ -1442,7 +1488,6 @@ BIT_STATUS Bit_GetLastError();
|
|||||||
|
|
||||||
## 属性设置
|
## 属性设置
|
||||||
|
|
||||||
|
|
||||||
### Bit_SetAttr
|
### Bit_SetAttr
|
||||||
|
|
||||||
```c
|
```c
|
||||||
@@ -1451,6 +1496,7 @@ BIT_STATUS Bit_SetAttr (
|
|||||||
BIT_UINT32 type,
|
BIT_UINT32 type,
|
||||||
BIT_VOID *pValue)
|
BIT_VOID *pValue)
|
||||||
```
|
```
|
||||||
|
|
||||||
设置全局配置或当前会话的配置。
|
设置全局配置或当前会话的配置。
|
||||||
|
|
||||||
### 参数
|
### 参数
|
||||||
@@ -1516,6 +1562,7 @@ BIT_STATUS Bit_SetProxy (
|
|||||||
BIT_PCSTR szUserID,
|
BIT_PCSTR szUserID,
|
||||||
BIT_PCSTR szPassword)
|
BIT_PCSTR szPassword)
|
||||||
```
|
```
|
||||||
|
|
||||||
设置代理服务的地址和端口。
|
设置代理服务的地址和端口。
|
||||||
|
|
||||||
### 参数
|
### 参数
|
||||||
@@ -1540,6 +1587,7 @@ BIT_STATUS Bit_SetCustomInfo (
|
|||||||
BIT_VOID *pInfoData,
|
BIT_VOID *pInfoData,
|
||||||
BIT_UINT32 infoDataSize)
|
BIT_UINT32 infoDataSize)
|
||||||
```
|
```
|
||||||
|
|
||||||
设置客户端运行自定义信息,需要在程序的最开始调用。
|
设置客户端运行自定义信息,需要在程序的最开始调用。
|
||||||
|
|
||||||
### 参数
|
### 参数
|
||||||
@@ -1589,6 +1637,7 @@ if (status == BIT_SUCCESS) {
|
|||||||
BIT_STATUS Bit_SetRootPath (
|
BIT_STATUS Bit_SetRootPath (
|
||||||
BIT_PCSTR szPath)
|
BIT_PCSTR szPath)
|
||||||
```
|
```
|
||||||
|
|
||||||
设置授权文件的存储路径。
|
设置授权文件的存储路径。
|
||||||
|
|
||||||
### 参数
|
### 参数
|
||||||
@@ -1604,6 +1653,7 @@ BIT_STATUS Bit_SetLocalServer (
|
|||||||
BIT_UINT32 nPort,
|
BIT_UINT32 nPort,
|
||||||
BIT_UINT32 nTimeoutSecondes)
|
BIT_UINT32 nTimeoutSecondes)
|
||||||
```
|
```
|
||||||
|
|
||||||
设置集团服务的地址和端口。
|
设置集团服务的地址和端口。
|
||||||
|
|
||||||
### 参数
|
### 参数
|
||||||
@@ -1615,7 +1665,6 @@ BIT_STATUS Bit_SetLocalServer (
|
|||||||
|
|
||||||
## 借出操作
|
## 借出操作
|
||||||
|
|
||||||
|
|
||||||
### Bit_GetBorrowRequest
|
### Bit_GetBorrowRequest
|
||||||
|
|
||||||
```c
|
```c
|
||||||
@@ -1626,6 +1675,7 @@ BIT_STATUS Bit_GetBorrowRequest(
|
|||||||
BIT_CHAR *pRequestInfo,
|
BIT_CHAR *pRequestInfo,
|
||||||
BIT_UINT32 *pRequestInfoSize)
|
BIT_UINT32 *pRequestInfoSize)
|
||||||
```
|
```
|
||||||
|
|
||||||
获取借出请求串,用来离线借出。
|
获取借出请求串,用来离线借出。
|
||||||
|
|
||||||
### 参数
|
### 参数
|
||||||
@@ -1647,6 +1697,7 @@ BIT_STATUS Bit_GetBorrowFeatureRequest(
|
|||||||
BIT_CHAR *pRequestInfo,
|
BIT_CHAR *pRequestInfo,
|
||||||
BIT_UINT32 *pRequestInfoSize)
|
BIT_UINT32 *pRequestInfoSize)
|
||||||
```
|
```
|
||||||
|
|
||||||
获取借出请求串,该接口支持指定特征项借出。
|
获取借出请求串,该接口支持指定特征项借出。
|
||||||
|
|
||||||
### 参数
|
### 参数
|
||||||
@@ -1684,6 +1735,7 @@ BIT_STATUS Bit_CheckOutSnEx(
|
|||||||
BIT_UCHAR *pApplicationData,
|
BIT_UCHAR *pApplicationData,
|
||||||
BIT_UINT32 nDurationDays);
|
BIT_UINT32 nDurationDays);
|
||||||
```
|
```
|
||||||
|
|
||||||
从授权服务器在线借出一个完整的授权码,以允许客户端服务器单独使用。被借出的授权码必须具有可借出属性,并在客户端成功借出后减少一个可用用户数。被借出的用户数在到期后将自动返还给服务器。
|
从授权服务器在线借出一个完整的授权码,以允许客户端服务器单独使用。被借出的授权码必须具有可借出属性,并在客户端成功借出后减少一个可用用户数。被借出的用户数在到期后将自动返还给服务器。
|
||||||
|
|
||||||
### 参数
|
### 参数
|
||||||
@@ -1721,6 +1773,7 @@ BIT_STATUS Bit_CheckOut(
|
|||||||
BIT_UCHAR *pApplicationData,
|
BIT_UCHAR *pApplicationData,
|
||||||
BIT_UINT32 nDurationDays)
|
BIT_UINT32 nDurationDays)
|
||||||
```
|
```
|
||||||
|
|
||||||
从授权服务器在线借出授权码或者特征项。
|
从授权服务器在线借出授权码或者特征项。
|
||||||
|
|
||||||
### 参数
|
### 参数
|
||||||
@@ -1740,6 +1793,7 @@ type:借出类型。目前仅支持ws,表示从外网借出(针对云授
|
|||||||
sn:指定借出的SN
|
sn:指定借出的SN
|
||||||
-->
|
-->
|
||||||
```
|
```
|
||||||
|
|
||||||
- **pFeatureList** xml结构,指定借出的特征项列表,如果不传则借出整个SN。
|
- **pFeatureList** xml结构,指定借出的特征项列表,如果不传则借出整个SN。
|
||||||
|
|
||||||
```xml
|
```xml
|
||||||
@@ -1749,6 +1803,7 @@ sn:指定借出的SN
|
|||||||
<feature id='10'/>
|
<feature id='10'/>
|
||||||
</features>
|
</features>
|
||||||
```
|
```
|
||||||
|
|
||||||
当feature指定了借出时间,则会覆盖nDurationDays该时间,没有指定,则借出nDurationDays天。
|
当feature指定了借出时间,则会覆盖nDurationDays该时间,没有指定,则借出nDurationDays天。
|
||||||
|
|
||||||
借出feature时,如果指定了版本,则直接大于等于借出版本的特征项,只借一个,如果没有指定版本,则该特征项的所有版本都借出一个。
|
借出feature时,如果指定了版本,则直接大于等于借出版本的特征项,只借一个,如果没有指定版本,则该特征项的所有版本都借出一个。
|
||||||
@@ -1792,6 +1847,7 @@ BIT_STATUS Bit_CheckOutFeatures(
|
|||||||
BIT_UINT32 nFeatureInList,
|
BIT_UINT32 nFeatureInList,
|
||||||
BIT_UINT32 nDurationDays);
|
BIT_UINT32 nDurationDays);
|
||||||
```
|
```
|
||||||
|
|
||||||
从集团授权服务器借出一组特征项,这些特征项必须包含在同一个授权码中。被借出的集团授权码必须具有可借出属性,并在客户端成功借出后减少一个可用用户数。被借出的用户数在到期后将自动返还给集团服务器。
|
从集团授权服务器借出一组特征项,这些特征项必须包含在同一个授权码中。被借出的集团授权码必须具有可借出属性,并在客户端成功借出后减少一个可用用户数。被借出的用户数在到期后将自动返还给集团服务器。
|
||||||
|
|
||||||
### 参数
|
### 参数
|
||||||
@@ -1831,6 +1887,7 @@ BIT_STATUS Bit_CheckIn(
|
|||||||
BIT_UINT32 featureId,
|
BIT_UINT32 featureId,
|
||||||
BIT_UCHAR *pApplicationData)
|
BIT_UCHAR *pApplicationData)
|
||||||
```
|
```
|
||||||
|
|
||||||
提前返还从集团授权服务器借出的授权。要提前返还授权,该授权码必须具有允许提前返还属性。
|
提前返还从集团授权服务器借出的授权。要提前返还授权,该授权码必须具有允许提前返还属性。
|
||||||
|
|
||||||
### 参数
|
### 参数
|
||||||
@@ -1848,6 +1905,7 @@ BIT_STATUS Bit_CheckInEx(
|
|||||||
BIT_PCSTR szScope,
|
BIT_PCSTR szScope,
|
||||||
BIT_UCHAR *pApplicationData)
|
BIT_UCHAR *pApplicationData)
|
||||||
```
|
```
|
||||||
|
|
||||||
提前返还从授权服务器借出的授权。要提前返还授权,该授权码必须具有允许提前返还属性。
|
提前返还从授权服务器借出的授权。要提前返还授权,该授权码必须具有允许提前返还属性。
|
||||||
|
|
||||||
### 参数
|
### 参数
|
||||||
@@ -1861,6 +1919,7 @@ BIT_STATUS Bit_CheckInEx(
|
|||||||
<type>ws</type> <!-- type:归还类型,ws表示归还到云授权服务,默认是归还到集团授权服务 -->
|
<type>ws</type> <!-- type:归还类型,ws表示归还到云授权服务,默认是归还到集团授权服务 -->
|
||||||
</scope>
|
</scope>
|
||||||
```
|
```
|
||||||
|
|
||||||
用于指定借出的参数,格式:wsxxxx,type设定为ws时表示从云授权服务器借出和归还。
|
用于指定借出的参数,格式:wsxxxx,type设定为ws时表示从云授权服务器借出和归还。
|
||||||
|
|
||||||
- **pApplicationData** - [IN] 产品识别码。记录在接口定义文件中,与产品一一对应。
|
- **pApplicationData** - [IN] 产品识别码。记录在接口定义文件中,与产品一一对应。
|
||||||
@@ -1872,9 +1931,11 @@ BIT_STATUS Bit_ApplyBorrowInfo(
|
|||||||
BIT_UCHAR *pApplicationData,
|
BIT_UCHAR *pApplicationData,
|
||||||
BIT_PCSTR pBorrowInfo)
|
BIT_PCSTR pBorrowInfo)
|
||||||
```
|
```
|
||||||
|
|
||||||
客户端应用离线借出串,Bit_ApplyUpdateInfo可以兼容该接口。
|
客户端应用离线借出串,Bit_ApplyUpdateInfo可以兼容该接口。
|
||||||
|
|
||||||
### 参数
|
### 参数
|
||||||
|
|
||||||
- **pApplicationData** - [IN] 产品识别码。记录在接口定义文件中,与产品一一对应。
|
- **pApplicationData** - [IN] 产品识别码。记录在接口定义文件中,与产品一一对应。
|
||||||
- **pBorrowInfo** - [IN] 借出请求串。
|
- **pBorrowInfo** - [IN] 借出请求串。
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,391 @@
|
|||||||
|
# 广州创飞人工智能技术有限公司 — 与比特安索对接:平台与交付物能力评估
|
||||||
|
|
||||||
|
> **平台定义**:**广州创飞人工智能技术有限公司客户商务与交付管理平台** —— 收口 **客户与项目、合同、交付产品、具体授权(含比特安索集成)** 等功能模块,支撑规模化商务履约与许可运营。
|
||||||
|
> **目的**:在 [回调与地址走查](bitanswer-callbacks-endpoints-assessment.md)、[授权制度与规则](bitanswer-licensing-design-and-rules.md) 等结论基础上,明确 **创飞侧需建设或提供的平台形态**、**具体交付软件**,以及 **应具备的能力**,用于立项、架构评审与验收。
|
||||||
|
> **日期**:2026-04-06
|
||||||
|
> **说明**:比特安索侧能力以官方文档与控制台为准;本文聚焦 **创飞作为软件开发商 + 集成方** 的交付范围。
|
||||||
|
> **双视角分析**:§6 为 **产品经理** 维度,§7 为 **技术架构(与比特对接的抽象)** 维度。
|
||||||
|
> **功能清单**:模块划分与详细功能点见 [chuangfei-platform-product-modules.md](./chuangfei-platform-product-modules.md)。
|
||||||
|
> **业务流程与排期**:全业务 BPM、版本与迭代计划见 [chuangfei-platform-bpm-and-roadmap.md](./chuangfei-platform-bpm-and-roadmap.md)。
|
||||||
|
> **工程划分**:工作区目录边界与规划仓库见 [engineering/WORKSPACE_ENGINEERING_LAYOUT.md](./engineering/WORKSPACE_ENGINEERING_LAYOUT.md) · [根目录 engineering/](../engineering/README.md)。
|
||||||
|
> **实现与使用分档**:§8 为 **前端 + 后端**(**Java Spring Boot 4.0.\* 长期稳定线 + Vue 3 + Vite**)分阶段开发评估;§9 为 **产品使用**(MVP / Mid / Full)分阶段评估。后端 **固定主版本线为 Spring Boot 4.0.\***(补丁随官方 **4.0.x** 升级,**不用里程碑版**),以便与 **Spring 生态后续 AI 相关扩展**(如 Spring AI、统一配置与可观测性栈)同代演进,降低后续集成改造成本。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 对接边界(谁负责什么)
|
||||||
|
|
||||||
|
| 边界 | 比特安索(授权云 / 集团服务 / 控制台) | 创飞(本公司) |
|
||||||
|
|------|----------------------------------------|----------------|
|
||||||
|
| 授权模型 | 产品、模版、业务、SN、特征项、配置项、规则引擎 | 按产品线映射业务 SKU → 控制台配置;不在比特控制台外「再造一套许可模型」除非有自研 `selfhosted` |
|
||||||
|
| 客户端运行时 | 官方 Bitanswer SDK(C/Java 等)、与产品绑定的库 | 集成 **craftlabs-authorization-sdk**(及随产品 native 库)、应用内调用与配置下发 |
|
||||||
|
| 出站 HTTP(比特 → 创飞) | 规则动作 **Callback**:向创飞 URL POST JSON | 提供 **公网可达、可鉴权** 的 HTTPS 端点;解析 `event` / `data` 并驱动内部流程 |
|
||||||
|
| 入站连接(创飞客户端 → 比特) | 云/E3 `https://…`、集团 `bit://…` 等 | 为每环境、每产品线配置正确 **`bitanswer.url`**;管理 SN、`applicationData`、证书与网络策略 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 创飞建议提供的「平台」形态(逻辑分层)
|
||||||
|
|
||||||
|
不必一开始做成单一巨石系统,但建议在架构上区分以下 **逻辑平台**,便于分工与演进。
|
||||||
|
|
||||||
|
### 2.1 授权集成与配置平台(对内 DevOps / 架构)
|
||||||
|
|
||||||
|
- **能力**:统一维护「产品线 ↔ 比特产品/模版/业务/特征 ID」映射;生成与校验 [`craftlabs-auth-config`](../schemas/craftlabs-auth-config.schema.json) 类配置;管理多环境 `bitanswer.url`、SN 样例与联调账号。
|
||||||
|
- **交付形态**:可先从 **Git 仓库 + Schema CI 校验 + 文档** 起步,再演进为内部配置服务或门户。
|
||||||
|
|
||||||
|
### 2.2 许可事件与集成平台(对接规则 Callback)
|
||||||
|
|
||||||
|
- **能力**:接收比特控制台配置的 **规则 Callback**(异步 POST);校验 **`x-bitanswer-token`**(或双方约定头);按 `event` 类型路由到订单、资产、计费、通知等系统;**幂等**与**重试友好**(官方不依赖响应体)。
|
||||||
|
- **交付形态**:独立 **BFF/API 网关后的 Webhook 服务**(建议与核心业务库解耦,用消息队列削峰)。
|
||||||
|
|
||||||
|
### 2.3 客户与订单主数据(CRM / ERP / 自研商务系统)
|
||||||
|
|
||||||
|
- **能力**:SN 与合同/订单/客户绑定;激活前校验(对应 `sn:pre_activate` 场景);激活后状态推进(对应 `sn:post_activate`)。
|
||||||
|
- **说明**:可复用现有系统;若无,至少需 **最小订单-SN 台账** 才能用好规则回调。
|
||||||
|
|
||||||
|
### 2.4 设备与终端治理(可选但强烈建议)
|
||||||
|
|
||||||
|
- **能力**:存储 `mid`(设备指纹)与 SN、客户、场站关系;支持换机、终端数对账(对应 `device:pre_activate` / `post_activate`)。
|
||||||
|
- **适用**:单机浮动、多终端限制、现场交付类产品(如码头、校园场景示例配置所暗示的部署形态)。
|
||||||
|
|
||||||
|
### 2.5 可观测性与支持
|
||||||
|
|
||||||
|
- **能力**:客户端授权失败日志脱敏上报;区分「网络 / SN 无效 / 特征未授权 / 集团满并发」等;与比特控制台告警协同。
|
||||||
|
- **交付形态**:日志规范、仪表盘、客服查询 SN 状态的手册(只读控制台或内部工具)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 具体交付软件与工件(建议清单)
|
||||||
|
|
||||||
|
下列项可与当前仓库及产品线对齐,按项目裁剪。
|
||||||
|
|
||||||
|
| 交付物 | 说明 | 与现有仓库关系 |
|
||||||
|
|--------|------|----------------|
|
||||||
|
| **craftlabs-authorization-sdk**(及绑定 native) | 统一 Java/Native 授权门面,`provider=bitanswer` 时消费 `bitanswer.url` 等 | 本仓库已有核心模块与示例配置 |
|
||||||
|
| **产品线授权配置文件** | 每产品/场景的 JSON(含 `features.*.bitanswerFeatureId` 等) | 见 `examples/config/*.bitanswer.json` 模式 |
|
||||||
|
| **规则 Callback 接收服务** | HTTPS 端点 + 鉴权 + 事件分发;可选管理后台查看原始 payload(脱敏) | **本仓库未包含**,需单独工程 |
|
||||||
|
| **配置与安全交付流程** | SN、`applicationData`、环境 URL 的分发与轮换;禁止硬编码进镜像 | 流程 + 密钥管理(Vault/KMS/CI Secret) |
|
||||||
|
| **集成与联调文档** | 环境矩阵、比特控制台截图级操作说明、错误码对照 | 可延续 `docs/bitanswer-*.md` 体系 |
|
||||||
|
| **(选用)集团授权服务器侧运维** | 若使用 `bit://` 自建集团服务:高可用、备份、与比特版本兼容矩阵 | 与 `wharf.bitanswer.json` 类场景相关 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 应具备的能力清单(功能与非功能)
|
||||||
|
|
||||||
|
### 4.1 功能能力
|
||||||
|
|
||||||
|
| 能力域 | 应具备要点 |
|
||||||
|
|--------|------------|
|
||||||
|
| **运行时接入** | 能使用官方 SDK + 本 SDK 完成 Login、激活、特征项 Query/Release(集团场景成对释放)、心跳策略与产品识别码一致 |
|
||||||
|
| **配置治理** | Schema 级校验、环境隔离、特征 ID 与逻辑功能键映射可追溯 |
|
||||||
|
| **规则 Callback** | 支持文档所列事件的 JSON 解析;与订单/资产状态机衔接;可选拒绝或标记风险(视比特侧是否支持同步阻断,以官方为准) |
|
||||||
|
| **会话与吊销联动** | 若使用云保保相关能力:能消费 `yunbaobao:session_logout` 或与客户端登出联动,避免「业务已关、许可仍显示占用」类体验问题 |
|
||||||
|
| **多授权模式共存** | 云 / 集团 / 单机(含智能连接、强制认证)在架构上可区分部署与运维责任,避免混用 URL 与 loginMode |
|
||||||
|
|
||||||
|
### 4.2 非功能能力
|
||||||
|
|
||||||
|
| 能力域 | 应具备要点 |
|
||||||
|
|--------|------------|
|
||||||
|
| **安全** | Callback URL 仅 HTTPS;token 轮换;payload 脱敏日志;防重放与 IP 允许列表(若比特出口 IP 可约定) |
|
||||||
|
| **可靠性** | Webhook 快速 `2xx`、内部异步处理;队列重试;死信与人工补偿 |
|
||||||
|
| **合规与审计** | 激活、换机、会话失效留痕;满足客户与内部审计查询 |
|
||||||
|
| **版本与兼容** | 比特 SDK 与集团服务版本矩阵;升级窗口与客户通知 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 与比特控制台的操作分工(落地检查表)
|
||||||
|
|
||||||
|
- **创飞运营/产品**:维护产品、模版、业务、特征项;配置规则与 **Callback URL**、**x-bitanswer-token**。
|
||||||
|
- **创飞研发**:实现客户端集成、Webhook 服务、内部状态机与对账。
|
||||||
|
- **创飞运维**:`bitanswer.url` 网络可达性、证书、集团服务(若自建)SLA。
|
||||||
|
- **双方联调**:`sn:pre_activate` / `post_activate` 各至少一轮全链路;含异常 SN、重复激活、设备重激活。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 产品经理维度:分析与评估
|
||||||
|
|
||||||
|
### 6.1 平台定位与价值主张
|
||||||
|
|
||||||
|
将 **客户项目、合同、交付物与授权** 收口为一条可追溯的业务链,并与比特安索在 SN、激活、规则回调、功能开关等能力上打通。对内减少「合同已签但授权未配、交付与许可不一致、规则回调无人跟进」等断层;对外使 **交付可预期、授权可运营、问题可定位**,支撑多产品线、多客户的规模化商务与交付,而非仅依赖人工台账与零散工具。
|
||||||
|
|
||||||
|
### 6.2 目标用户与人设(内部)
|
||||||
|
|
||||||
|
| 人设 | 典型职责 | 与平台关系 |
|
||||||
|
|------|----------|------------|
|
||||||
|
| 商务 / 客户经理 | 商机、报价、合同推进、客户沟通 | 维护客户与项目主数据,触发交付与授权需求 |
|
||||||
|
| 交付 / 实施 | 交付清单、环境、上线节奏 | 登记交付产品、核对交付与合同范围 |
|
||||||
|
| 授权运营 / License Ops | SN 策略、激活规则、BitAnswer 配置与异常处理 | 配置授权、处理回调与规则事件、与 SDK/回调服务协同 |
|
||||||
|
| 订单 / 运营支持(可与 CRM 合并) | 订单、SKU、开票与履约对齐 | 保证「卖什么」与「授什么」一致 |
|
||||||
|
| 研发 / 平台工程 | SDK、Webhook 服务、可观测性 | 保障集成稳定、排障与版本演进 |
|
||||||
|
| 管理层 / 财务合规 | 收入确认、审计、风险 | 审计轨迹与报表,非日常操作主用户 |
|
||||||
|
|
||||||
|
### 6.3 核心模块地图(与四大域 + 支撑域)
|
||||||
|
|
||||||
|
- **客户 / 项目**:客户档案、项目/阶段、干系人,与合同/交付关联。
|
||||||
|
- **合同**:主数据、标的(产品/模块/席位数/期限)、状态(草稿→生效→变更→终止)。
|
||||||
|
- **交付产品**:交付批次与清单、与合同行项/SKU 映射、交付完成标记。
|
||||||
|
- **授权**:BitAnswer 集成配置、SN 分配与生命周期、激活记录、功能与产品配置对应(如 per-product JSON)、规则 Webhook 入站与处置状态。
|
||||||
|
- **支撑(必要)**:集成与运维(回调地址、配置发布、Webhook 健康度);事件与规则编排(回调→业务动作/工单);设备/资产治理(可选);审计与合规;可观测性(激活与回调指标)。
|
||||||
|
|
||||||
|
### 6.4 关键用户旅程
|
||||||
|
|
||||||
|
1. **合同签订 → SN 分配 → 交付 → 激活**:合同生效后生成授权需求 → License Ops 完成(或同步)比特侧 SN/许可包 → 交付标记已交付 → 客户端经 SDK 激活 → 平台记录结果与原因码。
|
||||||
|
2. **规则触发 → Webhook → 运营处置**:比特规则满足 → HTTPS 接收回调 → 落库并关联客户/合同/SN → 运营认领、续期/冻结/升级等,写回状态并通知。
|
||||||
|
3. **合同变更 → 授权差异对齐**:变更后对比「合同授权视图」与当前比特/缓存状态 → 待办清单 → 批量调整并审计。
|
||||||
|
4. **交付争议 / 对账举证**:拉取合同条款 + 交付记录 + SN 发放与激活时间线 + 回调摘要,一条链查到底。
|
||||||
|
5. **新产品接入**:平台注册 SKU/功能矩阵 → 维护授权 JSON → 联调 SDK 与测试环境比特 → 上线检查清单(回调可达、监控就绪)→ 商务可报价签约。
|
||||||
|
|
||||||
|
### 6.5 MVP 与分阶段路线图(P0 / P1 / P2)
|
||||||
|
|
||||||
|
| 阶段 | 范围与理由 |
|
||||||
|
|------|------------|
|
||||||
|
| **P0(MVP)** | 客户/项目、合同、交付产品最小主数据与关联;SN 分配与激活结果回写;Webhook 入站 + 事件列表 + 与客户/合同/SN 的基础关联(挂不上先保留原始载荷);SDK 配置来源与环境区分。**理由**:先跑通主链与排障、审计雏形。 |
|
||||||
|
| **P1** | 合同行项与授权/SKU 规则化映射、变更 diff;回调处置工作流(认领、状态机、通知);设备治理(若适用)与审计报表;可观测仪表盘。**理由**:降本增效、减人为错误。 |
|
||||||
|
| **P2** | 与 CRM/订单深度同步、审批流、多法人;规则/策略可配置编排;到期续费与异常聚类等。**理由**:客户与产品线规模化后的必然需求。 |
|
||||||
|
|
||||||
|
### 6.6 成功指标(KPI)
|
||||||
|
|
||||||
|
- **产品**:主链覆盖率(新签合同在平台内完成合同→交付→授权记录);SN/激活/回调与客户或合同关联成功率;合同生效到「许可就绪」中位时长;配置模板复用率。
|
||||||
|
- **运营**:回调队列处理 SLA、激活成功率与 Top 失败原因、合同与授权不一致类工单占比、关键字段变更可追溯比例(目标趋近 100%)。
|
||||||
|
|
||||||
|
### 6.7 风险与待决策问题(干系人澄清)
|
||||||
|
|
||||||
|
- 主数据权威(CRM/电子签/本平台)与冲突合并策略。
|
||||||
|
- BitAnswer 控制台与平台内闭环的边界、是否双写及对账。
|
||||||
|
- Webhook 安全(签名/幂等/重放)、SLA 与灾备是否与商务承诺一致。
|
||||||
|
- 多产品 JSON 与客户端/SDK 发版节奏、最低 SDK 版本策略。
|
||||||
|
- 日志与 PII 留存、数据跨境(若涉及)。
|
||||||
|
- License Ops 编制与工单机制是否在 P0 明确。
|
||||||
|
- 管理层优先指标:交付周期 vs 授权类客诉下降,影响功能取舍。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 技术架构维度:分析与评估
|
||||||
|
|
||||||
|
### 7.1 系统语境(Context)
|
||||||
|
|
||||||
|
| 参与者 | 角色 |
|
||||||
|
|--------|------|
|
||||||
|
| **客户商务与交付管理平台** | 客户、项目、合同、交付项、产品/SKU、SN/设备台账、授权策略与比特 ID 映射;**不提供** BitAnswer 运行时。 |
|
||||||
|
| **比特安索** | 许可模型、规则、SN/设备生命周期;**出站 Callback** 至创飞 HTTPS。 |
|
||||||
|
| **客户现场客户端** | **craftlabs-authorization-sdk**(`BitAnswerProvider` → native)+ JSON 配置(含 `bitanswer.url` 等)。 |
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart LR
|
||||||
|
subgraph CF["创飞平台"]
|
||||||
|
P["商务与交付域\n项目/合同/交付/SN"]
|
||||||
|
I["授权集成层\n映射/配置/事件编排"]
|
||||||
|
W["Webhook 接入\nCallback 接收"]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph BA["比特安索"]
|
||||||
|
C["控制台 / 规则引擎"]
|
||||||
|
CL["授权云 / 集团服务"]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph EDGE["客户现场"]
|
||||||
|
APP["现场客户端\nSDK + Native"]
|
||||||
|
end
|
||||||
|
|
||||||
|
P --> I
|
||||||
|
W --> I
|
||||||
|
I --> P
|
||||||
|
|
||||||
|
C -->|"规则 Callback\nHTTPS POST + Token"| W
|
||||||
|
APP -->|"Login/激活/特征/心跳"| CL
|
||||||
|
CL -.->|"策略与许可结果"| APP
|
||||||
|
I -->|"配置与 SN 交付\n(流程+工件)"| EDGE
|
||||||
|
```
|
||||||
|
|
||||||
|
**要点**:Callback 为 **比特 → 创飞**;客户端为 **现场 → 比特**;主数据与状态机在创飞平台闭环;**Webhook 须独立服务**(与本仓库 SDK 分离)。
|
||||||
|
|
||||||
|
### 7.2 逻辑架构:建议服务与模块
|
||||||
|
|
||||||
|
| 逻辑组件 | 职责 |
|
||||||
|
|----------|------|
|
||||||
|
| **商务与交付核心 API** | 客户、项目、合同、交付项、产品/SKU、SN/设备 CRUD 与状态机;为授权事件提供业务锚点。 |
|
||||||
|
| **授权集成服务** | 产品线 ↔ 比特产品/模版/业务/特征 ID;`craftlabs-auth-config` 生成/校验;多环境 `bitanswer.url`。可先 Git + Schema CI,再演进配置 API。 |
|
||||||
|
| **Webhook Ingress** | 公网 HTTPS、Token 鉴权、快速 `2xx`;置于网关/WAF 后;与核心库解耦,异步投递下游。 |
|
||||||
|
| **事件与工作流编排** | 按 `event` 路由;幂等键;补偿与工单;建议 MQ + DLQ。 |
|
||||||
|
| **配置与密钥分发** | 脱敏配置模板、环境参数、轮换策略进入发布流水线或配置中心;禁止密钥进镜像。 |
|
||||||
|
| **可观测与支持** | trace、脱敏审计日志、客户端错误分类上报。 |
|
||||||
|
|
||||||
|
### 7.3 数据域模型(高层)
|
||||||
|
|
||||||
|
```text
|
||||||
|
客户 ──* 项目
|
||||||
|
客户 ──* 合同
|
||||||
|
|
||||||
|
合同 ──* 交付项
|
||||||
|
交付项 *──* 产品/SKU
|
||||||
|
|
||||||
|
产品/SKU ──* 授权策略映射(比特 product/template/business/featureIds、策略版本、环境)
|
||||||
|
|
||||||
|
合同/交付项 ──* SN
|
||||||
|
SN *──* 设备(mid、换机历史)
|
||||||
|
|
||||||
|
授权事件流水(Callback + 可选客户端上报摘要)── 关联 SN、设备、合同/交付项、event、幂等键、处理状态
|
||||||
|
```
|
||||||
|
|
||||||
|
**要点**:SN 是连接比特控制台与创飞商务数据的键;`LicensePolicyMapping` 将销售 SKU 映射为比特特征与规则;设备域对齐 `device:*` 类事件。
|
||||||
|
|
||||||
|
### 7.4 集成模式摘要
|
||||||
|
|
||||||
|
- **规则 Callback**:Ingress 尽快 `2xx`;校验 `x-bitanswer-token` 并支持轮换;幂等键与乱序容忍(按状态机合法性判定)。
|
||||||
|
- **边缘配置**:符合 Schema 的 JSON、环境 `bitanswer.url`;SN 与 `applicationData` 经安全通道交付,与镜像构建分离。
|
||||||
|
- **密钥**:KMS/Secret Manager;日志脱敏(token 不落库、不落日志)。
|
||||||
|
|
||||||
|
### 7.5 非功能需求(NFR)与技术风险
|
||||||
|
|
||||||
|
**NFR**:HTTPS 全链路、WAF/限流、Webhook 多实例无状态、队列多 AZ、RTO/RPO 与降级;结构化日志与按 `event` 的指标;比特 SDK/集团服务 **兼容矩阵** 与发版联动。
|
||||||
|
|
||||||
|
**风险与缓解**:Callback 洪峰 → MQ 与限流;幂等缺失 → 唯一约束/幂等表;SN 与合同不一致 → `sn:pre_activate` 校验 + 对账任务;SDK 升级破坏现场 → 预发演练与灰度、保留稳定 native 包。
|
||||||
|
|
||||||
|
### 7.6 分阶段技术交付(与 PM 阶段对齐)
|
||||||
|
|
||||||
|
| PM 阶段 | 技术验收要点 |
|
||||||
|
|---------|----------------|
|
||||||
|
| 需求与方案 | 语境图与逻辑架构评审;数据域 v1;事件清单与幂等策略;安全与密钥方案。 |
|
||||||
|
| 设计 | API/事件契约;Ingress 与队列拓扑;`LicensePolicyMapping` 模型与 Schema 对齐。 |
|
||||||
|
| 迭代 1(MVP) | 联调环境 SDK 跑通;Webhook 最小实现(鉴权 + 入队 + 2xx);至少一类核心事件(如 `sn:post_activate`)打通台账。 |
|
||||||
|
| 迭代 2 | 设备域与 `device:*`;`pre_activate` 风控;DLQ 与补偿;配置 CI 校验。 |
|
||||||
|
| UAT / 上线 | 全事件抽样、异常与换机场景;监控告警;token 轮换与回滚手册。 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. 前端与后端实现维度:Spring Boot 4.0.\* + Vue 分阶段开发评估
|
||||||
|
|
||||||
|
### 8.1 技术栈与版本假设
|
||||||
|
|
||||||
|
| 层级 | 选型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| 后端 | Java + **Spring Boot 4.0.\*** | **主版本线锁定 4.0**,采用官方 **4.0.x** 补丁(GA,非里程碑);**Jakarta** 命名空间;**Java 基线以 Boot 4 官方 System Requirements 为准**(当前一代为 **Java 17+**,生产 JDK 建议与团队统一为 **LTS,如 21**)。**选型理由**:4.0 线为后续 **AI 能力扩展**(Spring AI、工具调用、向量检索与观测组件等)预留同代生态位,避免在 3.x 与 4.x 之间二次迁移。 |
|
||||||
|
| 前端 | **Vue 3**(Composition API)+ **Vite** | Vue 3.x + Vite 稳定版;**锁版本**(lockfile)避免构建漂移。 |
|
||||||
|
|
||||||
|
**AI 扩展(规划约束,非 MVP 必做)**:在 Spring Boot **4.0.\*** 上按需引入 **Spring AI**(或官方推荐组合)、统一 **Observability**(Micrometer / OTel)与 **API 契约**(springdoc),使对话、RAG、MCP/工具类集成与现有 REST、安全、配置模型一致;具体模块与版本在立项时按 Spring 官方 BOM 与兼容性矩阵选定。
|
||||||
|
|
||||||
|
### 8.2 三档总览(最小版 → 完整版)
|
||||||
|
|
||||||
|
| 档位 | 业务目标(一句话) | 前端重心 | 后端重心 | 风险量级 |
|
||||||
|
|------|-------------------|----------|----------|----------|
|
||||||
|
| **MVP** | 内部可用:主数据 + 合同/交付最小闭环 + 许可可查 + Callback 不断链 | 少页面、列表+表单、基础鉴权 | 粗粒度模块、REST 为主、Webhook 打通一条链、基础审计 | 中 |
|
||||||
|
| **Mid** | 流程与协作:状态机、报表雏形、异步与队列可观测 | 复杂表格/表单、按钮级权限、简单仪表盘 | Ingress 与 Worker 分离趋势、Outbox/MQ、幂等固化、OpenAPI 稳定 | 中高 |
|
||||||
|
| **Full** | 可运营、可审计、可扩展:治理、对账、多集成 | i18n/a11y、高级报表、动态权限/租户 | 限界上下文清晰、读模型/报表分离、SLO、审计独立管道 | 高 |
|
||||||
|
|
||||||
|
### 8.3 MVP(最小版本)
|
||||||
|
|
||||||
|
**目标**:客户/项目、合同、交付项 CRUD;SN 录入/绑定;Webhook 接收 → 校验 → 入队或直写 + **幂等**;关键写操作审计。
|
||||||
|
|
||||||
|
**前端(Vue)**:布局 + 客户/项目/合同/交付/SN 列表与详情编辑;Callback 投递记录只读页;Pinia + 路由守卫 + axios 封装;表格分页排序;可选导出 CSV;少量单测 + 1~2 条 E2E。
|
||||||
|
|
||||||
|
**后端(Spring)**:分包域:`identity`、`customer-project`、`contract`、`delivery`、`licensing`、`webhook-ingest`、`audit`;REST `/api/v1/...`;**Spring Security**(JWT 或 Session)+ 粗角色;**PostgreSQL 15** + **MyBatis-Plus** + Flyway/Liquibase;Webhook **inbox 或 MQ**;幂等键 `(source, message_id)` 或 `event_id`;**springdoc-openapi**;Micrometer + 结构化日志 + `/actuator/health`。
|
||||||
|
|
||||||
|
**前后端依赖**:认证契约、分页/错误码、许可与合同关联模型、Webhook **schemaVersion** 约定。
|
||||||
|
|
||||||
|
**本档风险**:比特字段枚举与内部模型不一致;Webhook ACK 与落库事务边界;权限过粗导致 Mid 期大改。
|
||||||
|
|
||||||
|
**工作量(定性)**:**M~L**(联调与 Webhook 占大头)。
|
||||||
|
|
||||||
|
### 8.4 Mid(增强版)
|
||||||
|
|
||||||
|
**目标**:合同/交付状态机与操作轨迹;许可映射与批量 SN;**MQ 为主路径**;失败重试与 Ops 入口;基础监控指标。
|
||||||
|
|
||||||
|
**前端**:状态时间轴;Webhook 失败列表与详情(脱敏);映射配置页;简单 KPI 卡片;可编辑表格、动态表单;**按钮级权限**;ECharts 可选;OpenAPI 客户端进 CI;`Idempotency-Key` 与后端对齐。
|
||||||
|
|
||||||
|
**后端**:Ingress **独立部署** 倾向;**Outbox** + DLQ;**@PreAuthorize** 细粒度;乐观锁;领域事件;**OpenAPI v1 冻结**;分布式 Trace + 业务指标(Webhook lag、激活成功率)。
|
||||||
|
|
||||||
|
**风险**:异步与 UI「已受理」反馈不一致;报表与 OLTP 同库压力;映射与比特控制台不同步。
|
||||||
|
|
||||||
|
**工作量(定性)**:**L**。
|
||||||
|
|
||||||
|
### 8.5 Full(完整版)
|
||||||
|
|
||||||
|
**目标**:数据范围/多租户(若需要)、审计与合规导出、SLA 看板、对账与异常工单、配置模板化、BitAnswer 全生命周期与对账任务。
|
||||||
|
|
||||||
|
**前端**:模板/字典/集成配置后台;多维报表与订阅;审计检索;任务中心(长耗时对账);字段级权限与动态路由;虚拟滚动 + 游标分页;i18n 与 WCAG 目标;契约测试与性能脚本。
|
||||||
|
|
||||||
|
**后端**:模块化单体或拆服务;**CQRS 轻量**读模型;OAuth2/OIDC/SSO;ABAC/数据范围;读写分离或报表库;事件版本化与回放;OpenAPI 多受众与兼容 CI;SLO 与审计独立存储。
|
||||||
|
|
||||||
|
**风险**:拆分过早/过晚;审计写放大;业务规则与比特长期分叉(需防腐层与定期对齐)。
|
||||||
|
|
||||||
|
**工作量(定性)**:**L~XL**。
|
||||||
|
|
||||||
|
### 8.6 横切能力递进
|
||||||
|
|
||||||
|
| 能力 | MVP | Mid | Full |
|
||||||
|
|------|-----|-----|------|
|
||||||
|
| OpenAPI | 开发态 | CI + v1 冻结 | 多受众 + 兼容检查 |
|
||||||
|
| 安全 | JWT/Session + 粗角色 | 细权限 + ingress 加固 | SSO + 数据范围 + 密钥轮换 |
|
||||||
|
| 异步 | 可选 MQ | Outbox + DLQ | 事件版本 + 回放 |
|
||||||
|
| 观测 | 日志 + health | Metrics + Trace | SLO + 审计管道 |
|
||||||
|
| 测试 | 手工为主 | E2E + 核心单测 | 契约 + 性能 + a11y |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. 产品使用维度:MVP / Mid / Full 分阶段评估
|
||||||
|
|
||||||
|
> 本节从 **谁在用、要完成什么、界面呈现什么事实、如何运营** 描述,不展开 Spring/Vue 实现细节(见 §8)。
|
||||||
|
|
||||||
|
### 9.1 三档定位
|
||||||
|
|
||||||
|
| 档位 | 一句话目标 |
|
||||||
|
|------|------------|
|
||||||
|
| **MVP** | **卖—授—激活** 闭环可追溯,Callback 不丢,SN 可关联合同 |
|
||||||
|
| **Mid** | 规模化:**设备/换机、队列与补偿、对账与报表、通知** |
|
||||||
|
| **Full** | **变更治理、财务/审计级追溯、深度集成** 与多模式许可运营 |
|
||||||
|
|
||||||
|
### 9.2 MVP
|
||||||
|
|
||||||
|
**角色与任务**:商务建客户/项目/合同;交付登记交付物;License Ops 管 SN 与激活核对;订单支持维护合同行与 SKU 对应;研发保障 Callback 可查。
|
||||||
|
|
||||||
|
**必备界面**:客户档案;项目工作台;合同台账;合同行/订单行;交付登记;**SN/授权台账**;**Callback 收件箱**;比特侧状态摘要(只读/链接,不替代控制台)。
|
||||||
|
|
||||||
|
**用户要看到的「事实」**:客户与项目;合同标的;交付是否完成;SN 绑谁、状态;Callback 是否到达与处理结果;**客户→项目→合同→交付→SN** 端到端链路。
|
||||||
|
|
||||||
|
**运营流**:Callback 入站落库与状态更新或待办;SN 发放与激活核对;失败可重试或人工跟进。
|
||||||
|
|
||||||
|
**不做**:完整 CRM/电子签、精细设备治理、财务闭环、替代比特控制台全能力、客户自助门户。
|
||||||
|
|
||||||
|
**验收**:一条真实或模拟链路跑通;合法 Callback 100% 可检索;SN 可追溯到合同;权限与审计字段满足;无「孤儿 SN」至少可警告/筛选。
|
||||||
|
|
||||||
|
**依赖**:License Ops、比特控制台规则与 token、现场交付节点、研发 Callback 端点。
|
||||||
|
|
||||||
|
### 9.3 Mid
|
||||||
|
|
||||||
|
**增量角色与任务**:交付管设备/场站与换机;Ops 队列 SLA 与批量 SN;客服按 SN 诊断;管理层看健康度。
|
||||||
|
|
||||||
|
**增量界面**:设备台账;换机/重激活;Callback 运营台(过滤、批量处理、死信);**对账视图**(标的 vs 已发 vs 已激活);待办/通知;产品线与环境映射只读或受控编辑。
|
||||||
|
|
||||||
|
**增量事实**:多设备历史;Callback 耗时与 TOP 原因;履约缺口;即将到期列表。
|
||||||
|
|
||||||
|
**不做**:完整 ERP 收入确认、全功能客户自助、经销商分润、自建对等规则引擎。
|
||||||
|
|
||||||
|
**验收**:换机场景台账完整;对账抽样一致;峰值 Callback 不丢;至少一类通知通道可测。
|
||||||
|
|
||||||
|
### 9.4 Full
|
||||||
|
|
||||||
|
**增量重点**:财务/合规审计与导出;管理层经营视图;产品/架构的变更与版本治理。
|
||||||
|
|
||||||
|
**增量界面**:合同变更与版本;授权策略库(映射版本、生效区间);客户健康度/续费;审计导出;可选客户只读门户;CRM/ERP 同步状态。
|
||||||
|
|
||||||
|
**验收**:变更闭环 T+X 与控制台一致;审计抽样可追溯;集成成功率与失败清单可处理。
|
||||||
|
|
||||||
|
### 9.5 与 P0 / P1 / P2 路线对齐
|
||||||
|
|
||||||
|
| 路线 | 含义 | 与本三档 |
|
||||||
|
|------|------|----------|
|
||||||
|
| **P0** | 能接单、交付、授权,Callback 不断链 | ≈ **MVP** 主体 |
|
||||||
|
| **P1** | 运营效率:队列、对账、设备、通知 | ≈ **Mid** |
|
||||||
|
| **P2** | 治理与增长:变更、经营分析、深度集成 | ≈ **Full** |
|
||||||
|
|
||||||
|
P0/P1/P2 为**优先级语言**;MVP/Mid/Full 为**产品包边界**;排期可交错,但**验收边界**建议按档固化,避免 MVP 膨胀。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. 修订记录
|
||||||
|
|
||||||
|
| 日期 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| 2026-04-06 | 初版:基于工作区走查与比特文档推导创飞侧平台与交付物。 |
|
||||||
|
| 2026-04-06 | 增补:平台正式命名;§6 产品维度、§7 技术架构双 Task 分析并入本文。 |
|
||||||
|
| 2026-04-06 | 增补:§8 前端+后端(Spring Boot GA + Vue 3 + Vite)分档;§9 产品使用分档;与 P0–P2 对齐说明。 |
|
||||||
|
| 2026-04-06 | §8 后端基线明确为 **Spring Boot 4.0.\*** 长期线,并补充与 **AI 扩展**(Spring AI 等)同代演进说明。 |
|
||||||
@@ -0,0 +1,359 @@
|
|||||||
|
# 客户商务与交付管理平台 — 业务流程设计与版本迭代排期
|
||||||
|
|
||||||
|
> **文档角色**:资深产品经理视角的 **全业务 BPM 走查**、**版本迭代计划** 与 **排期框架**(可与研发 Sprint 对齐后填入具体日历)。
|
||||||
|
> **计划基准日**:2026-04-06(相对排期以 **T0** 表示立项/开工周)。
|
||||||
|
> **关联文档**:[功能模块与功能点](chuangfei-platform-product-modules.md) · [平台与比特对接总览](chuangfei-bitanswer-integration-platform.md) · [回调与地址评估](bitanswer-callbacks-endpoints-assessment.md) · [授权制度与规则](bitanswer-licensing-design-and-rules.md) · [工作区工程划分](engineering/WORKSPACE_ENGINEERING_LAYOUT.md)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 工作区文档走查结论(摘要)
|
||||||
|
|
||||||
|
|
||||||
|
| 文档 | 已覆盖内容 | 本文补足内容 |
|
||||||
|
| -------------------------------------------------- | ----------------------------------------------------- | -------------------------------------- |
|
||||||
|
| `chuangfei-platform-product-modules.md` | M1–M11 模块、功能点 ID、P0/P1/P2、角色权限矩阵 | **跨模块泳道流程**、**状态机与异常分支**、**发布列车与周次排期** |
|
||||||
|
| `chuangfei-bitanswer-integration-platform.md` | 对接边界、技术架构、Spring Boot 4.0. + Vue 分档、产品使用 MVP/Mid/Full | 将上述档位 **映射为可发布版本号与迭代切片** |
|
||||||
|
| `bitanswer-licensing-design-and-rules.md` | 产品/模版/业务/授权、特征项、规则 Callback 事件 | Callback 在 BPM 中的 **触发点与系统边界** |
|
||||||
|
| `bitanswer-callbacks-endpoints-assessment.md` | URL 类型、Webhook 与 SDK 分工 | 在排期中固定 **Webhook 工程与联调节点** |
|
||||||
|
| `craftlabs-auth-config` Schema / `examples/config` | 客户端配置形态 | 在 **BP-10** 与 **V1.0** 中体现配置发布依赖 |
|
||||||
|
|
||||||
|
|
||||||
|
**排期假设(可调整)**:每迭代 **2 周**;团队为 **1 个全栈小队**(可并行时压缩总周数);**UAT 每大版本末预留 1 迭代**。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 业务流程总览索引
|
||||||
|
|
||||||
|
|
||||||
|
| 流程编号 | 流程名称 | 主价值 | 涉及模块 | 主责角色 |
|
||||||
|
| --------- | ------------------- | ---------- | ----------- | ----------- |
|
||||||
|
| **BP-01** | 客户与项目准入 | 主数据干净、可挂合同 | M1、M11 | 商务、管理员 |
|
||||||
|
| **BP-02** | 合同起草、审批与生效 | 「卖什么」入台账 | M2、M10 | 商务、订单支持 |
|
||||||
|
| **BP-03** | 交付计划与交付完成 | 「可激活」前置 | M3、M2 | 交付 |
|
||||||
|
| **BP-04** | 授权需求与 SN 发放 | 合同→许可载体 | M4、M2、M3、M6 | License Ops |
|
||||||
|
| **BP-05** | 现场激活与状态回写 | 闭环到台账 | M4、比特、现场客户端 | 交付、Ops |
|
||||||
|
| **BP-06** | 许可事件(Callback)入站与处置 | 不断链、可审计 | M5、M4、M8 | Ops、研发支撑 |
|
||||||
|
| **BP-07** | 合同变更与授权差异对齐 | 卖授一致 | M2、M4、M6、M9 | 商务、Ops |
|
||||||
|
| **BP-08** | 设备登记、换机与重激活 | 终端合规 | M7、M5、M4 | 交付、Ops |
|
||||||
|
| **BP-09** | 对账、异常工单与举证 | 管理降险 | M9、M8、M10 | 管理层、财务、合规 |
|
||||||
|
| **BP-10** | 授权集成配置发布 | 客户端可连比特 | M6、研发 | 架构、Ops |
|
||||||
|
| **BP-11** | 账户开通、登录与权限变更 | 安全访问 | M11、M10 | 管理员、安全 |
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 业务流程设计(详述)
|
||||||
|
|
||||||
|
### 3.1 BP-01 客户与项目准入
|
||||||
|
|
||||||
|
**目标**:任何合同必须挂在已存在且已校验的客户/项目下。
|
||||||
|
|
||||||
|
**步骤**:① 管理员维护字典(行业、区域等)→ ② 商务创建客户档案(必填项校验)→ ③ 创建项目并关联客户、干系人 → ④(可选)客户/项目冻结策略生效。
|
||||||
|
|
||||||
|
**输出物**:客户 ID、项目 ID;审计记录(M10-F01)。
|
||||||
|
|
||||||
|
**异常**:重复客户 → P2 合并流程(M1-F08);权限不足 → M11 拦截。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3.2 BP-02 合同起草、审批与生效
|
||||||
|
|
||||||
|
**目标**:合同状态合法迁移,行项可支撑后续交付与 SN 绑定。
|
||||||
|
|
||||||
|
**状态机(逻辑)**:`草稿` → `待生效` → `生效`;`生效` → `变更中` → `生效(新版本)`;`生效` → `终止/到期`。
|
||||||
|
|
||||||
|
**步骤**:① 选客户与项目 → ② 录入合同头与 **合同行项**(SKU/数量/期限)→ ③(P1)附件上传 → ④ 商务/订单支持核对订单号关联(M2-F06)→ ⑤ 状态置为生效。
|
||||||
|
|
||||||
|
**输出物**:合同 ID、行项 ID;变更时版本号(M2-F07)。
|
||||||
|
|
||||||
|
**异常**:行项为空或金额/数量口径冲突 → 阻断生效;越权编辑 → 审计告警。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3.3 BP-03 交付计划与交付完成
|
||||||
|
|
||||||
|
**目标**:交付批次与清单与 **合同行** 对齐,标记「已交付」作为 SN 发放门禁(可配置)。
|
||||||
|
|
||||||
|
**步骤**:① 基于项目/合同创建交付批次 → ② 维护交付清单(产品实例、环境说明 P1)→ ③ 关联合同行项 → ④ 交付工程师确认完成(M3-F05)→ ⑤ 状态变为已交付。
|
||||||
|
|
||||||
|
**输出物**:交付批次 ID、完成时间。
|
||||||
|
|
||||||
|
**异常**:未关联合同行的交付项 → 警告或阻断(系统参数 M11-F20)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3.4 BP-04 授权需求与 SN 发放
|
||||||
|
|
||||||
|
**目标**:从「生效合同 +(建议)已交付」生成许可需求,SN 绑定合同行/项目/客户。
|
||||||
|
|
||||||
|
**步骤**:① License Ops 查看授权需求清单(M4-F08,可与合同行汇总)→ ② 在比特控制台完成 SN/许可包操作(系统外)→ ③ 平台 **录入或导入 SN**(M4-F01)→ ④ **绑定** 合同行与客户/项目(M4-F02)→ ⑤ 状态:已发放 → 通知客户/交付(可手工,Mid 接 M8)。
|
||||||
|
|
||||||
|
**输出物**:SN 记录、绑定关系、发放时间。
|
||||||
|
|
||||||
|
**异常**:孤儿 SN(无绑定)→ 警告列表;与合同数量超额 → 规则校验(P1 增强)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3.5 BP-05 现场激活与状态回写
|
||||||
|
|
||||||
|
**目标**:客户端经 **craftlabs-authorization-sdk** 与比特交互后,平台侧可见激活结果。
|
||||||
|
|
||||||
|
**步骤**:① 交付将 SN 与环境说明交付客户 → ② 现场部署并配置 `bitanswer.url` 等(BP-10 已发布配置)→ ③ 客户端激活 → ④ Ops **回写** 激活成功/失败及原因码(M4-F05)或接口同步(若建设)→ ⑤ 更新 SN 状态。
|
||||||
|
|
||||||
|
**输出物**:激活时间、状态、备注。
|
||||||
|
|
||||||
|
**异常**:长期未激活 → Mid 纳入报表 M9-F02;失败原因字典维护(M11-F19)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3.6 BP-06 许可事件(Callback)入站与处置
|
||||||
|
|
||||||
|
**目标**:比特规则触发后 **HTTPS Callback** 不丢、可关联、可关闭。
|
||||||
|
|
||||||
|
**步骤**:① Webhook Ingress 验签/token → 快速 2xx → ② 落库事件(M5-F01~F04)→ ③ 解析 `sn` 尝试关联 SN/合同 → ④ Ops 处置(认领、备注、更新业务状态或建待办 M8)→ ⑤ 失败进重试/DLQ(Mid)。
|
||||||
|
|
||||||
|
**事件类型对齐**(参见 [bitanswer-licensing-design-and-rules.md](./bitanswer-licensing-design-and-rules.md)):`sn:pre_activate` / `post_activate`、`device:pre_activate` / `post_activate`、`yunbaobao:session_logout` 等。
|
||||||
|
|
||||||
|
**异常**:无法解析关联 → 人工挂接;重复投递 → 幂等不重复改状态。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3.7 BP-07 合同变更与授权差异对齐
|
||||||
|
|
||||||
|
**目标**:合同变更后,**应授 vs 实授** 可对比、可出待办。
|
||||||
|
|
||||||
|
**步骤**:① 发起合同变更(M2-F07)→ ② 系统生成 **授权差异提示**(行项增减/延期)→ ③ Ops 在比特控制台调整并更新平台 SN/状态 → ④ 关闭变更单。
|
||||||
|
|
||||||
|
**输出物**:变更版本、操作记录(M10)。
|
||||||
|
|
||||||
|
**依赖**:Mid 报表 M9、映射 M2-F08 / M6。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3.8 BP-08 设备登记、换机与重激活
|
||||||
|
|
||||||
|
**目标**:`mid` 与 SN 关系清晰,换机与 `device:`* Callback 一致。
|
||||||
|
|
||||||
|
**步骤(Mid+)**:① 登记设备(M7-F01)→ ② 绑定 SN 历史(M7-F02)→ ③ 换机申请与记录(M7-F03)→ ④ 重激活后 Callback 更新台账。
|
||||||
|
|
||||||
|
**异常**:终端数超限 → 提示对齐比特策略(不替代控制台规则)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3.9 BP-09 对账、异常工单与举证
|
||||||
|
|
||||||
|
**目标**:管理层/财务/合规可拉通 **合同—交付—SN—激活—Callback**。
|
||||||
|
|
||||||
|
**步骤(Mid+)**:① 打开对账视图(M9-F01~F03)→ ② 导出(权限控制 M9-F04)→ ③ 异常项生成待办(M8)→ ④ 合规按 M10 导出审计包(Full)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3.10 BP-10 授权集成配置发布
|
||||||
|
|
||||||
|
**目标**:产品线、环境、`bitanswer.url`、特征映射与 JSON 模板 **版本化发布** 到现场可安装包或配置中心。
|
||||||
|
|
||||||
|
**步骤**:① M6 维护映射与模板 → ② Schema 校验(可与 CI 联动)→ ③ 发布记录(M6-F06)→ ④ 随产品发版或运维下发。
|
||||||
|
|
||||||
|
**依赖**:本仓库 `[schemas/craftlabs-auth-config.schema.json](../schemas/craftlabs-auth-config.schema.json)`。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3.11 BP-11 账户开通、登录与权限变更
|
||||||
|
|
||||||
|
**目标**:最小权限、会话可追溯、敏感操作可审计。
|
||||||
|
|
||||||
|
**步骤**:① 管理员创建用户并分配角色(M11-F14~F16)→ ② 用户登录/改密/登出(§12.1)→ ③ 角色变更写 M10/M11-F21。
|
||||||
|
|
||||||
|
**异常**:暴力破解 → 锁定(M11-F05);人员离职 → 禁用与会话失效(M11-F12)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 端到端泳道流程(主链路)
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart TB
|
||||||
|
subgraph SALES[商务 / 订单支持]
|
||||||
|
A1[BP-01 客户/项目]
|
||||||
|
A2[BP-02 合同与行项]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph DEL[交付]
|
||||||
|
B1[BP-03 交付完成]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph OPS[License Ops]
|
||||||
|
C1[BP-04 SN 发放与绑定]
|
||||||
|
C2[BP-06 Callback 处置]
|
||||||
|
C3[BP-05 激活回写]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph DEVOPS[架构 / 研发]
|
||||||
|
D1[BP-10 配置发布]
|
||||||
|
D2[Webhook 服务运维]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph EDGE[客户现场 + 比特]
|
||||||
|
E1[客户端 SDK 激活]
|
||||||
|
E2[比特控制台 / 规则]
|
||||||
|
end
|
||||||
|
|
||||||
|
A1 --> A2 --> B1 --> C1
|
||||||
|
D1 --> E1
|
||||||
|
C1 --> E1
|
||||||
|
E1 --> E2
|
||||||
|
E2 -->|Callback| C2
|
||||||
|
C2 --> C3
|
||||||
|
B1 -.->|门禁| C1
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 关键状态机汇总(产品规则)
|
||||||
|
|
||||||
|
|
||||||
|
| 对象 | 状态(节选) | 允许迁移(示例) |
|
||||||
|
| ----------- | ------------------------- | ----------------------- |
|
||||||
|
| 合同 | 草稿 / 待生效 / 生效 / 变更中 / 终止 | 草稿→待生效→生效;生效→变更中→生效(新版) |
|
||||||
|
| 交付批次 | 未交付 / 部分 / 已交付 | 未交付→已交付(或经部分) |
|
||||||
|
| SN | 未发放 / 已发放 / 已激活 / 冻结 / 异常 | 未发放→已发放→已激活;任意→冻结(授权) |
|
||||||
|
| Callback 事件 | 待处理 / 已处理 / 失败 / 忽略 | 待处理→已处理或失败→(重试)已处理 |
|
||||||
|
| 用户账号 | 启用 / 禁用 | 启用↔禁用;禁用强制会话失效 |
|
||||||
|
|
||||||
|
|
||||||
|
详细字段级规则在 PRD/配置表(M11-F20)中展开。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 版本迭代总体规划(Roadmap)
|
||||||
|
|
||||||
|
|
||||||
|
| 版本 | 定位 | 对应产品包 | 目标上线(相对 T0) | 核心验收 |
|
||||||
|
| -------- | ------- | ------ | ---------------- | ---------------------------- |
|
||||||
|
| **V1.0** | MVP 可投产 | MVP | **T0 + 10~12 周** | 主链路 BP-01~06、11 + 一条真实项目 UAT |
|
||||||
|
| **V1.1** | MVP 加固 | MVP+ | T0 + 14~16 周 | 性能、权限细项、生产监控 |
|
||||||
|
| **V1.5** | Mid 第一波 | Mid 核心 | T0 + 18~22 周 | M7、M8、M9 主干 + BP-08 雏形 |
|
||||||
|
| **V1.6** | Mid 完成 | Mid | T0 + 24~28 周 | 对账报表、Callback 运营台、SSO |
|
||||||
|
| **V2.0** | Full 基线 | Full | T0 + 34~42 周 | 变更治理、审计导出、MFA/数据范围等 P2 |
|
||||||
|
|
||||||
|
|
||||||
|
**说明**:若 **双迭代并行**(后端 Webhook 与业务 API 分队),V1.0 可压至 **8~10 周**,但需明确接口契约负责人。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 分迭代排期(Release Backlog)
|
||||||
|
|
||||||
|
> **三轨并行执行包**(后端双 JAR + 前端 Vue + 本仓 SDK):见 [engineering/PARALLEL_ITERATION_INDEX.md](./engineering/PARALLEL_ITERATION_INDEX.md) 与 [tracks/01](./engineering/tracks/01-backend-platform-webhook.md)、[02](./engineering/tracks/02-frontend-platform-ui.md)、[03](./engineering/tracks/03-client-sdk.md)。
|
||||||
|
|
||||||
|
### 7.1 V1.0 MVP(建议 5~6 个迭代 × 2 周)
|
||||||
|
|
||||||
|
|
||||||
|
| 迭代 | 周期(相对) | 目标产出 | 覆盖流程/模块 |
|
||||||
|
| ------ | --------- | ------------------------------------------------ | ----------------------- |
|
||||||
|
| **I1** | T0~T0+2W | 工程脚手架、CI、环境;M11 登录/登出/会话/审计登录;用户角色粗矩阵 | BP-11 |
|
||||||
|
| **I2** | +2W~+4W | M1 客户/项目全 P0;M11 字典、用户角色分配 | BP-01 |
|
||||||
|
| **I3** | +4W~+6W | M2 合同与行项 P0 + 状态机;M10-F01 | BP-02 |
|
||||||
|
| **I4** | +6W~+8W | M3 交付 P0;M4 SN P0(录入/绑定/状态/回写) | BP-03、BP-04、BP-05(手工回写) |
|
||||||
|
| **I5** | +8W~+10W | M5 Callback 收件箱 P0;M6 环境与产品线 P0;**Webhook 服务联调** | BP-06、BP-10 最小 |
|
||||||
|
| **I6** | +10W~+12W | E2E 缺陷收敛;**UAT**;操作手册 v1;生产配置检查清单 | BP-01~06、11 全链路 |
|
||||||
|
|
||||||
|
|
||||||
|
**V1.0 必须验收用例**:任选 1 条真实或准生产项目,完成 **客户→合同→交付→SN→(模拟)激活→Callback 可见→台账一致**。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 7.2 V1.1 加固(建议 2 迭代)
|
||||||
|
|
||||||
|
|
||||||
|
| 迭代 | 目标 |
|
||||||
|
| ------ | --------------------------- |
|
||||||
|
| **I7** | 权限码拆到按钮级;导出与脱敏;接口限流与安全头 |
|
||||||
|
| **I8** | 生产观测仪表盘;备份恢复演练;SN/合同批量导入稳定性 |
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 7.3 V1.5~V1.6 Mid(建议 4~5 迭代)
|
||||||
|
|
||||||
|
|
||||||
|
| 迭代 | 目标产出 | 覆盖 |
|
||||||
|
| ----------- | -------------------------------------- | ----------------- |
|
||||||
|
| **I9** | M7 设备与换机 P1;M5 失败标注与重试入口 | BP-08 |
|
||||||
|
| **I10** | M8 待办 + 一种通知通道 | BP-06、BP-09 |
|
||||||
|
| **I11** | M9 对账与 Callback 统计 + 导出 | BP-09 |
|
||||||
|
| **I12** | M11 SSO、并发会话、强制下线、密码重置;M2/M4/M6 P1 增强项 | BP-02、BP-04、BP-10 |
|
||||||
|
| **I13(可选)** | M2 变更版本与差异提示;UAT Mid | BP-07 |
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 7.4 V2.0 Full(建议 5~7 迭代)
|
||||||
|
|
||||||
|
|
||||||
|
| 主题 | 包含功能域 |
|
||||||
|
| ------ | ----------------------------------- |
|
||||||
|
| 合规与审计 | M10-F03/F04;举证包;留存策略 |
|
||||||
|
| 安全 | M11 MFA、SECURITY_ADMIN、数据范围 M11-F17 |
|
||||||
|
| 集成与规模化 | M1 CRM 同步 P2;M6 影响分析;M9 订阅与经营看板 |
|
||||||
|
| 体验 | M8 模板与静默规则;可选客户只读门户(单独立项时移出) |
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. 功能点 × 版本映射矩阵(摘要)
|
||||||
|
|
||||||
|
> 完整清单见 [chuangfei-platform-product-modules.md](./chuangfei-platform-product-modules.md) 各表 **优先级** 列;下表按 **版本** 聚合。
|
||||||
|
|
||||||
|
|
||||||
|
| 版本 | P0 全量 | P1 主要增量 | P2 主要增量 |
|
||||||
|
| ------------- | --------------------------------------------------- | ------------------------------ | ----------------------------- |
|
||||||
|
| **V1.0** | 各模块标注 P0 的功能点 + M11 §12.1 F01~F07、§12.2 F14~F16、F19 | 不强制 | 不含 |
|
||||||
|
| **V1.1** | — | 稳定性与权限细化、批量导入 | — |
|
||||||
|
| **V1.5~V1.6** | — | 全部模块 P1 为主(M7、M8、M9、M11 SSO 等) | 少量 P2 可提前(按商务压力) |
|
||||||
|
| **V2.0** | — | 收尾 Mid 缺口 | P2:MFA、安全管理员、数据范围、CRM、M10 导出等 |
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. 里程碑与依赖
|
||||||
|
|
||||||
|
|
||||||
|
| 里程碑 | 时间点(相对) | 依赖 |
|
||||||
|
| ------------------ | --------- | -------------------------- |
|
||||||
|
| M0 立项与范围冻结 | T0 | 本 Roadmap + 模块文档签字 |
|
||||||
|
| M1 α:可登录 + 主数据 | T0+4W | 身份设计、测试环境 |
|
||||||
|
| M2 β:合同+交付+SN | T0+8W | 法务字段定稿 |
|
||||||
|
| M3 γ:Callback 联调通过 | T0+10W | **比特控制台规则 URL**、公网 Ingress |
|
||||||
|
| M4 UAT 完成 | T0+12W | 种子用户、脱敏数据 |
|
||||||
|
| M5 生产首上 | T0+12~14W | 运维 SLA、密钥 KMS |
|
||||||
|
| Mid GA | T0+24~28W | SSO IdP、通知渠道账号 |
|
||||||
|
| Full GA | T0+34~42W | 法务审计字段、财务口径 |
|
||||||
|
|
||||||
|
|
||||||
|
**外部强依赖**:比特安索控制台权限、Callback 出口网络、现场客户端发版窗口(与 BP-10 同步)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. 假设、风险与待办
|
||||||
|
|
||||||
|
|
||||||
|
| 类型 | 内容 |
|
||||||
|
| ------ | ---------------------------------------------------------------- |
|
||||||
|
| **假设** | 单团队 2 周迭代;需求变更每周冻结;比特 API/控制台能力不降级 |
|
||||||
|
| **风险** | Webhook 延期则 V1.0 砍为「仅收件箱+人工」,激活仍走 BP-05 |
|
||||||
|
| **风险** | 合同字段反复 → I3 膨胀;建议首期 **法务一次评审** |
|
||||||
|
| **待办** | 将本文迭代 **I1~I6** 导入 Jira/飞书 **Epic/Story**;为每条 BP 指定 **流程 Owner** |
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. 修订记录
|
||||||
|
|
||||||
|
|
||||||
|
| 日期 | 说明 |
|
||||||
|
| ---------- | ---------------------------------------------- |
|
||||||
|
| 2026-04-06 | 初版:工作区文档走查;BP-01~11 流程;泳道图;V1.0~V2.0 路线图与迭代排期。 |
|
||||||
|
| 2026-04-06 | §7 增加指向三轨并行执行文档(后端/前端/SDK)。 |
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,532 @@
|
|||||||
|
# 客户商务与交付管理平台 — 功能模块划分与功能点(产品视角)
|
||||||
|
|
||||||
|
> **平台全称**:广州创飞人工智能技术有限公司客户商务与交付管理平台。
|
||||||
|
> **文档性质**:产品经理视角的 **模块划分** 与 **功能点清单**,用于需求评审、版本切片与验收对齐。
|
||||||
|
> **关联文档**:[平台与比特对接总览](chuangfei-bitanswer-integration-platform.md)(定位、架构、分阶段路线) · [**业务流程与版本排期**](chuangfei-platform-bpm-and-roadmap.md)(BPM、迭代计划) · [工作区工程划分](engineering/WORKSPACE_ENGINEERING_LAYOUT.md)。
|
||||||
|
> **优先级约定**:**P0** = MVP 必含;**P1** = 增强运营效率;**P2** = 治理与规模化。同一功能可在多期迭代交付,表中标注为「首期目标优先级」。
|
||||||
|
> **实现状态约定**:**✅** = 已实现(I1~I9 迭代交付);**◐** = 部分实现(缺字段或功能不完整);**○** = 未实现(规划中);**—** = 不适用(依赖前置模块)。状态反映截至 2026-05-25 的实现情况。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 模块总览
|
||||||
|
|
||||||
|
|
||||||
|
| 序号 | 模块名称 | 一句话职责 | 典型主要用户 |
|
||||||
|
| --- | -------------------- | ---------------------------------- | ---------------- |
|
||||||
|
| M1 | **客户与项目中心** | 客户主数据、项目与干系人,作为合同与交付的上游 | 商务、客户经理 |
|
||||||
|
| M2 | **合同与履约行** | 合同全生命周期、标的与行项,对齐「卖什么」 | 商务、订单支持 |
|
||||||
|
| M3 | **交付管理** | 交付批次、交付清单与完成状态,衔接「可激活」前提 | 交付/实施 |
|
||||||
|
| M4 | **授权与许可运营** | SN 台账、与合同/交付绑定、激活状态、与比特侧映射 | License Ops |
|
||||||
|
| M5 | **许可事件(Callback)运营** | 比特规则 Webhook 入站记录、关联、处置与补偿 | License Ops、研发支撑 |
|
||||||
|
| M6 | **授权集成与配置** | 产品线↔比特 ID 映射、环境 URL、客户端授权 JSON 治理 | 架构、License Ops |
|
||||||
|
| M7 | **设备与终端治理** | 设备标识、换机与激活历史(与 `device:`* 事件对齐) | 交付、License Ops |
|
||||||
|
| M8 | **通知与待办** | 关键事件触达、任务队列与 SLA 提示 | 全员(按角色) |
|
||||||
|
| M9 | **报表与对账** | 履约与授权一致性视图、缺口与到期 | 管理层、Ops、财务(只读) |
|
||||||
|
| M10 | **审计与合规** | 关键操作留痕、导出与留存策略 | 合规、管理层 |
|
||||||
|
| M11 | **身份、访问与平台管理** | **登录/登出与会话**、用户组织、**角色权限**、字典与系统参数 | 全员登录;管理员维护账号与权限 |
|
||||||
|
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart TB
|
||||||
|
M1[客户与项目 M1]
|
||||||
|
M2[合同与履约行 M2]
|
||||||
|
M3[交付管理 M3]
|
||||||
|
M4[授权与许可运营 M4]
|
||||||
|
M5[许可事件 Callback M5]
|
||||||
|
M6[授权集成与配置 M6]
|
||||||
|
M7[设备与终端 M7]
|
||||||
|
M8[通知与待办 M8]
|
||||||
|
M9[报表与对账 M9]
|
||||||
|
M10[审计与合规 M10]
|
||||||
|
M11[身份与平台管理 M11]
|
||||||
|
|
||||||
|
M1 --> M2
|
||||||
|
M2 --> M3
|
||||||
|
M2 --> M4
|
||||||
|
M3 --> M4
|
||||||
|
M6 --> M4
|
||||||
|
M5 --> M4
|
||||||
|
M4 --> M7
|
||||||
|
M5 --> M8
|
||||||
|
M4 --> M9
|
||||||
|
M3 --> M9
|
||||||
|
M2 --> M9
|
||||||
|
M1 --> M10
|
||||||
|
M2 --> M10
|
||||||
|
M3 --> M10
|
||||||
|
M4 --> M10
|
||||||
|
M5 --> M10
|
||||||
|
M11 --> M1
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. M1 客户与项目中心
|
||||||
|
|
||||||
|
**定位**:统一客户与项目上下文,避免合同、交付、SN 挂在「无名客户」或重复档案上。
|
||||||
|
|
||||||
|
|
||||||
|
| 功能点 ID | 功能点名称 | 说明 | 优先级 | 实现状态 |
|
||||||
|
| ------ | ------------- | -------------------------------------------- | --- | --- |
|
||||||
|
| M1-F01 | 客户档案创建/编辑 | 客户名称、统一社会信用代码/客户编码、行业、地址、开票信息等(字段以法务/财务为准裁剪) | P0 | ◐ 部分实现 — 仅 name + credit_code,缺行业/地址/开票信息 |
|
||||||
|
| M1-F02 | 客户列表与检索 | 多条件筛选、分页、关键字搜索 | P0 | ✅ |
|
||||||
|
| M1-F03 | 客户详情聚合视图 | 关联项目数、在履约合同数、在途 SN 数等摘要(只读统计) | P0 | ✅ — 后端 `/summary` + 前端详情页摘要卡片已实现 |
|
||||||
|
| M1-F04 | 项目创建/编辑 | 项目名称、所属客户、阶段、计划起止、项目经理 | P0 | ◐ 部分实现 — 仅 name + customer_id + phase,缺计划起止、项目经理 |
|
||||||
|
| M1-F05 | 项目列表与筛选 | 按客户、阶段、时间筛选 | P0 | ✅ |
|
||||||
|
| M1-F06 | 项目干系人 | 客户侧联系人、内部负责人、角色标签 | P0 | ◐ — 后端 CRUD 已实现,前端 UI 待补 |
|
||||||
|
| M1-F07 | 客户/项目冻结与解冻 | 禁止新业务挂载或仅允许只读(规则可配置) | P1 | ◐ — 后端 PATCH 端点已实现,前端 UI 待补 |
|
||||||
|
| M1-F08 | 客户合并与去重 | 疑似重复客户识别、合并流程与审计 | P2 | ○ |
|
||||||
|
| M1-F09 | 与外部 CRM 主数据同步 | 以外部 ID 关联、增量同步状态展示(不替代 CRM 全能力) | P2 | ○ |
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. M2 合同与履约行
|
||||||
|
|
||||||
|
**定位**:合同是「卖什么」的权威来源之一;履约行/合同行是 SN 与交付的锚点。
|
||||||
|
|
||||||
|
|
||||||
|
| 功能点 ID | 功能点名称 | 说明 | 优先级 | 实现状态 |
|
||||||
|
| ------ | --------------- | ------------------------------- | --- | --- |
|
||||||
|
| M2-F01 | 合同登记与编辑 | 合同编号、客户、关联项目、签订日、生效日、终止条件 | P0 | ✅ |
|
||||||
|
| M2-F02 | 合同状态机 | 草稿、待生效、生效、变更中、终止/到期等;非法跳转拦截 | P0 | ✅ 状态机含 DRAFT→PENDING_EFFECTIVE→EFFECTIVE→CHANGING→TERMINATED |
|
||||||
|
| M2-F03 | 合同标的摘要 | 产品/模块/数量/期限/席位等业务口径展示(可与行项汇总一致) | P0 | ✅ |
|
||||||
|
| M2-F04 | 合同行项(履约行) | 多行:SKU 或产品包、数量、单价(可选)、交付与授权口径 | P0 | ✅ |
|
||||||
|
| M2-F05 | 合同附件 | 上传扫描件/电子签输出(存储与权限受控) | P1 | ◐ — 后端 POST 端点已实现,前端上传 UI 待补 |
|
||||||
|
| M2-F06 | 合同与订单关联 | 外部订单号、内部订单记录 ID(若存在订单系统) | P1 | ○ |
|
||||||
|
| M2-F07 | 合同变更与版本 | 变更单、版本号、影响授权差异提示(与 M4 联动) | P1 | ◐ — 后端 changes/complete 端点已实现,前端 UI 待补 |
|
||||||
|
| M2-F08 | 合同行与授权 SKU 规则映射 | 行项默认映射到许可 SKU/特征包(与 M6 联动) | P1 | ○ |
|
||||||
|
| M2-F09 | 合同到期与续费提醒 | 基于生效/结束日期的列表与订阅(与 M8 联动) | P2 | ○ |
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. M3 交付管理
|
||||||
|
|
||||||
|
**定位**:记录「交了什么、何时可激活」,是 License Ops 发放 SN 的前置依据之一。
|
||||||
|
|
||||||
|
|
||||||
|
| 功能点 ID | 功能点名称 | 说明 | 优先级 | 实现状态 |
|
||||||
|
| ------ | ----------- | ------------------------------- | --- | --- |
|
||||||
|
| M3-F01 | 交付批次创建 | 关联项目/合同,批次号、计划交付日 | P0 | ✅ |
|
||||||
|
| M3-F02 | 交付清单 | 交付物条目:产品实例、数量、环境说明、备注 | P0 | ✅ |
|
||||||
|
| M3-F03 | 交付与合同行关联 | 每条交付行可关联合同行项,支撑对账 | P0 | ✅ |
|
||||||
|
| M3-F04 | 交付状态 | 未交付、已交付、部分交付;关键状态变更留痕 | P0 | ✅ 状态含 PENDING→DELIVERED→CANCELLED |
|
||||||
|
| M3-F05 | 交付完成确认 | 责任人、完成时间、可选客户签收记录 | P0 | ✅ |
|
||||||
|
| M3-F06 | 现场环境信息 | 部署地址、网络要求、联系人(敏感字段权限控制) | P1 | ○ |
|
||||||
|
| M3-F07 | 交付与 SN 发放门禁 | 规则:仅「已交付」合同范围可生成/绑定 SN(可配置为强/弱) | P1 | ◐ — 后端 deliveryGateEnabled 参数+闸门检查已实现,前端 SystemParamsView 已对接后端 API |
|
||||||
|
| M3-F08 | 交付模板 | 按产品线预置交付清单模板 | P2 | ○ |
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. M4 授权与许可运营
|
||||||
|
|
||||||
|
**定位**:SN 与激活事实的台账中心;不替代比特控制台,但与比特状态 **摘要对齐、可追溯**。
|
||||||
|
|
||||||
|
|
||||||
|
| 功能点 ID | 功能点名称 | 说明 | 优先级 | 实现状态 |
|
||||||
|
| ------ | -------------- | ---------------------------- | --- | --- |
|
||||||
|
| M4-F01 | SN 手工录入/导入 | 单条新增、批量导入(模板校验、重复提示) | P0 | ◐ 手工录入已实现,批量导入 UI 待补(后端 POST /batch-import 已就绪) |
|
||||||
|
| M4-F02 | SN 与合同/项目/客户绑定 | 必选关联路径之一(合同行或项目),禁止裸 SN 或仅警告 | P0 | ✅ |
|
||||||
|
| M4-F03 | SN 生命周期状态 | 未发放、已发放、已激活、已冻结、已回收、异常等 | P0 | ✅ 状态含 REGISTERED→ISSUED→ACTIVATED→SUSPENDED→REVOKED |
|
||||||
|
| M4-F04 | SN 详情页 | 展示绑定关系、发放记录、激活时间、最近事件摘要 | P0 | ✅ |
|
||||||
|
| M4-F05 | 激活结果回写 | 人工录入或接口同步:成功/失败及原因码分类 | P0 | ◐ 支持手工状态更新,缺原因码分类 |
|
||||||
|
| M4-F06 | 比特控制台状态摘要 | 同一 SN 的关键字段摘要或控制台链接(只读,权限控制) | **P1**(原 P0,因依赖比特控制台对接未完成降级) | ○ |
|
||||||
|
| M4-F07 | 批量 SN 操作 | 批量绑定、批量状态变更(审批可选) | P1 | ◐ — 后端 batch-import 已实现,前端批量操作 UI 待补 |
|
||||||
|
| M4-F08 | 授权需求单 | 由合同/交付生成的「待发放 SN」清单,供 Ops 执行 | P1 | ○ |
|
||||||
|
| M4-F09 | 试用/正式/续期标签 | 与业务口径一致的标签,便于筛选与报表 | P1 | ○ |
|
||||||
|
| M4-F10 | SN 与设备关联视图 | 展示绑定 `mid` 列表与历史(依赖 M7) | P1 | — 依赖 M7 |
|
||||||
|
| M4-F11 | 授权策略生效视图 | 展示当前映射版本、环境(与 M6 联动) | P2 | ○ |
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. M5 许可事件(Callback)运营
|
||||||
|
|
||||||
|
**定位**:承接比特规则 **HTTPS Callback**,保证 **不断链、可关联、可处置**。
|
||||||
|
|
||||||
|
|
||||||
|
| 功能点 ID | 功能点名称 | 说明 | 优先级 | 实现状态 |
|
||||||
|
| ------ | ----------- | -------------------------------------------------------------------------------------------------------------------- | --- | --- |
|
||||||
|
| M5-F01 | 事件收件箱列表 | 按时间、事件类型、`sn`、处理状态筛选 | P0 | ✅ 支持多维度筛选 |
|
||||||
|
| M5-F02 | 事件详情 | 展示解析后字段 + 脱敏后的原始 payload;关联 SN/合同 | P0 | ✅ 含 payload 脱敏预览 |
|
||||||
|
| M5-F03 | 处理状态 | 待处理、已处理、失败、忽略;处理人与时间 | P0 | ✅ 状态含 PENDING→PROCESSED/FAILED/IGNORED |
|
||||||
|
| M5-F04 | 关联解析失败兜底 | 无法关联主数据时保留事件并支持人工挂接 | P0 | ✅ 支持人工挂接 SN/项目/合同 |
|
||||||
|
| M5-F05 | 事件类型字典 | `sn:pre_activate`、`sn:post_activate`、`device:pre_activate`、`device:post_activate`、`yunbaobao:session_logout` 等展示名与说明 | P0 | ✅ |
|
||||||
|
| M5-F06 | 失败原因标注 | Ops 可选分类,便于报表 | P1 | ✅ — 前端失败原因下拉 + 后端 DTO 已实现 |
|
||||||
|
| M5-F07 | 批量重处理/重试入口 | 在业务允许范围内触发补偿(与后端幂等策略一致) | P1 | ✅ 单条 + 批量重试均已实现(I8 单条 + I10 批量端点) |
|
||||||
|
| M5-F08 | 死信与积压监控视图 | 队列深度、最久未处理 TOP(与可观测联动) | P1 | ◐ — 后端 GET /stats/backlog + 前端积压统计卡片已实现 |
|
||||||
|
| M5-F09 | 事件驱动待办 | 自动生成待办卡片(与 M8 联动) | P1 | — 依赖 M8 |
|
||||||
|
| M5-F10 | 模拟投递(仅测试环境) | 联调验收工具 | P2 | ◐ — 后端 POST /simulate 已实现,前端 UI 待补 |
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. M6 授权集成与配置
|
||||||
|
|
||||||
|
**定位**:把「产品线—比特产品/模版/业务/特征 ID—环境 URL」管起来,支撑客户端 JSON 与联调。
|
||||||
|
|
||||||
|
|
||||||
|
| 功能点 ID | 功能点名称 | 说明 | 优先级 | 实现状态 |
|
||||||
|
| ------ | ----------------- | -------------------------------------------------------------- | --- | --- |
|
||||||
|
| M6-F01 | 产品线定义 | 产品线编码、名称、说明 | P0 | ✅ |
|
||||||
|
| M6-F02 | 环境维度 | 开发/测试/预发/生产及对应 `bitanswer.url` 登记 | P0 | ✅ 含 seed 数据(dev/prod) |
|
||||||
|
| M6-F03 | 比特侧 ID 映射 | 产品、模版、业务 ID 与产品线+环境绑定(与控制台一致) | P1 | ✅ — 前后端均已实现(IntegrationIdMappingView) |
|
||||||
|
| M6-F04 | 逻辑功能键 ↔ 特征项映射 | 对齐 `craftlabs-auth-config` 中 `features.*.bitanswerFeatureId` 等 | P1 | ✅ — 前后端均已实现(IntegrationFeatureMappingView) |
|
||||||
|
| M6-F05 | 授权 JSON 模板管理 | 模板版本、变更说明、与 Schema 校验结果(可接 CI) | P1 | ◐ — 前后端 CRUD 已实现(IntegrationJsonTemplateView),Schema 校验未关联 UI |
|
||||||
|
| M6-F06 | 配置发布记录 | 谁、何时、发布了哪一版到哪一环境 | P1 | ○ |
|
||||||
|
| M6-F07 | 控制台链接与说明 | 规则 Callback URL、token 轮换登记(非密钥明文展示) | P1 | ○ |
|
||||||
|
| M6-F08 | SDK / native 版本矩阵 | 与现场客户端兼容范围说明 | P2 | ○ |
|
||||||
|
| M6-F09 | 变更影响分析 | 映射变更影响哪些在服 SN/合同(只读分析) | P2 | ○ |
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. M7 设备与终端治理
|
||||||
|
|
||||||
|
**定位**:支撑浮动、换机、终端限制类场景,与比特 `device:`* 事件及 `mid` 对齐。
|
||||||
|
|
||||||
|
|
||||||
|
| 功能点 ID | 功能点名称 | 说明 | 优先级 | 实现状态 |
|
||||||
|
| ------ | ----------------- | ----------------------- | --- | --- |
|
||||||
|
| M7-F01 | 设备登记 | `mid`、别名、场站、关联客户/项目 | P1 | ◐ — 登记/列表已实现,字段覆盖待确认 |
|
||||||
|
| M7-F02 | 设备与 SN 绑定历史 | 时间线:首次激活、换机、解绑 | P1 | ◐ — 绑定时间线已实现,完整性待确认 |
|
||||||
|
| M7-F03 | 换机申请与处理记录 | 轻量审批可选;处理结果与备注 | P1 | ◐ — 后端 swap-request 端点已实现,审批流待补 |
|
||||||
|
| M7-F04 | 设备列表与检索 | 按 SN、客户、场站筛选 | P1 | ✅ |
|
||||||
|
| M7-F05 | 与 Callback 设备事件联动 | 从事件跳转设备详情 | P1 | ○ |
|
||||||
|
| M7-F06 | 终端数/并发策略展示 | 只读展示合同或比特策略摘要(不重复造规则引擎) | P2 | ○ |
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. M8 通知与待办
|
||||||
|
|
||||||
|
**定位**:把「该谁处理」说清楚,降低 Callback 与 SN 异常堆积。
|
||||||
|
|
||||||
|
|
||||||
|
| 功能点 ID | 功能点名称 | 说明 | 优先级 | 实现状态 |
|
||||||
|
| ------ | ------------ | ------------------------------- | --- | --- |
|
||||||
|
| M8-F01 | 站内待办列表 | 按角色过滤:待处理 Callback、待发放 SN、待核对激活 | P1 | ◐ — 待办中心已上线,自动化待办生成待接入 |
|
||||||
|
| M8-F02 | 待办认领与完成 | 状态流转与备注 | P1 | ◐ — 状态流转已实现,备注功能待补 |
|
||||||
|
| M8-F03 | 邮件/企业微信等一种通道 | 关键事件必达一种(可配置订阅) | P1 | ◐ — 通知通道配置 UI 已上线,实际发送逻辑未接入 |
|
||||||
|
| M8-F04 | 通知模板 | 事件类型 → 模板变量 | P2 | ○ |
|
||||||
|
| M8-F05 | 静默规则 | 重复事件聚合、防骚扰 | P2 | ○ |
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. M9 报表与对账
|
||||||
|
|
||||||
|
**定位**:给管理层与 Ops **履约 vs 授权** 的一致性视图。
|
||||||
|
|
||||||
|
|
||||||
|
| 功能点 ID | 功能点名称 | 说明 | 优先级 | 实现状态 |
|
||||||
|
| ------ | ---------------- | ------------------- | --- | --- |
|
||||||
|
| M9-F01 | 合同标的 vs 已发 SN 视图 | 按合同/行项汇总应发、实发 | P1 | ◐ — ContractSnReportView 已上线,数据维度待确认 |
|
||||||
|
| M9-F02 | 已发 vs 已激活视图 | 未激活占比、超期未激活列表 | P1 | ○ |
|
||||||
|
| M9-F03 | Callback 统计 | 按类型、状态、时间段的成功率与耗时分布 | P1 | ◐ — CallbackStatsView 已上线 |
|
||||||
|
| M9-F04 | 导出 CSV/Excel | 权限与脱敏策略受控 | P1 | ◐ — 后端 GET /reports/export 已存在,前端导出按钮待补 |
|
||||||
|
| M9-F05 | 项目健康度看板 | 多项目并行时的红黄绿(规则可配置) | P2 | ◐ — ProjectHealthView 已上线,红黄绿规则可配置性待确认 |
|
||||||
|
| M9-F06 | 订阅报表 | 定期邮件推送 | P2 | ◐ — SubscriptionReportView 已上线,后端推送逻辑待确认 |
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. M10 审计与合规
|
||||||
|
|
||||||
|
**定位**:满足内审与客户抽样举证,**关键操作不可抵赖**。
|
||||||
|
|
||||||
|
|
||||||
|
| 功能点 ID | 功能点名称 | 说明 | 优先级 | 实现状态 |
|
||||||
|
| ------- | -------- | --------------------------- | --- | --- |
|
||||||
|
| M10-F01 | 关键字段变更日志 | 客户、合同、SN 绑定、状态变更:旧值/新值/人/时间 | P0 | ✅ |
|
||||||
|
| M10-F02 | 审计检索 | 按对象 ID、用户、时间范围查询 | P1 | ◐ — AuditSearchView 已上线,筛选维度待确认 |
|
||||||
|
| M10-F03 | 导出审计包 | 范围可选(项目/合同/时间窗),水印与权限 | P2 | ○ |
|
||||||
|
| M10-F04 | 留存策略配置 | 与法务对齐的保留周期说明(技术实现另见架构) | P2 | ◐ — AuditRetentionView 已上线,配置生效性待确认 |
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 12. M11 身份、访问与平台管理
|
||||||
|
|
||||||
|
**定位**:**账户如何安全进入与退出系统**;进入后**能看能改什么**由角色与数据范围控制;以下为 **认证与会话**、**授权模型落地**、**平台配置** 三部分。
|
||||||
|
|
||||||
|
### 12.1 账户登录、登出与会话
|
||||||
|
|
||||||
|
|
||||||
|
| 功能点 ID | 功能点名称 | 说明 | 优先级 | 实现状态 |
|
||||||
|
| ------- | ------------- | ----------------------------------------------- | --- | --- |
|
||||||
|
| M11-F01 | 登录页 | 账号(工号/邮箱/登录名可配置一种为主)+ 密码登录入口;错误提示不暴露用户是否存在(防枚举) | P0 | ✅ |
|
||||||
|
| M11-F02 | 登出 | 主动登出:清除服务端会话或作废令牌、前端清理本地凭证 | P0 | ✅ |
|
||||||
|
| M11-F03 | 登录态保持与超时 | **空闲超时**自动登出并提示;可选「记住本次会话」策略(与安全基线平衡,默认保守) | P0 | ◐ — 前端 idleTimer 已实现(从 systemParams 读取 sessionTimeoutMinutes),后端会话管理待补 |
|
||||||
|
| M11-F04 | 未登录访问拦截 | 访问受保护路由时跳转登录页,登录成功后回跳原目标 URL(或安全白名单内路径) | P0 | ✅ |
|
||||||
|
| M11-F05 | 登录失败与锁定 | 连续失败次数阈值触发**临时锁定**或验证码;解锁策略(时长/管理员解锁)可配置 | P0 | ○ |
|
||||||
|
| M11-F06 | 登录/登出审计 | 记录成功/失败登出、时间、来源 IP、客户端类型(脱敏与留存策略另定) | P0 | ✅ |
|
||||||
|
| M11-F07 | 密码修改 | 已登录用户修改本人密码;校验旧密码强度与新密码策略 | P0 | ✅ — Profile 页改密弹窗已实现 |
|
||||||
|
| M11-F08 | 密码重置 | 管理员重置密码或邮件/短信重置链接(通道选一种即可);重置后可选强制首次登录改密 | P1 | ◐ — 后端 `POST /admin/reset-password` 已实现(非空操作),前端管理 UI 待补 |
|
||||||
|
| M11-F09 | 企业 SSO / OIDC | 与企业身份源单点登录;登出可与 IdP **单点登出**联动(若 IdP 支持) | P1 | ○ |
|
||||||
|
| M11-F10 | 双因素认证 MFA | TOTP/短信/企业令牌等一种;可配置为全员或高敏角色必选 | P2 | ○ |
|
||||||
|
| M11-F11 | 并发会话策略 | 同一账号是否允许多端同时在线;超出策略时踢旧会话或拒绝新登录(可配置) | P1 | ○ |
|
||||||
|
| M11-F12 | 管理员强制下线 | 安全或人事场景下终止指定用户本会话或全会话 | P1 | ○ |
|
||||||
|
| M11-F13 | 服务时间窗提示(可选) | 维护窗口登录页公告 | P2 | ○ |
|
||||||
|
|
||||||
|
|
||||||
|
### 12.2 用户、角色与权限配置(管理侧)
|
||||||
|
|
||||||
|
|
||||||
|
| 功能点 ID | 功能点名称 | 说明 | 优先级 | 实现状态 |
|
||||||
|
| ------- | ---------------- | --------------------------------------------- | --- | --- |
|
||||||
|
| M11-F14 | 用户与账号生命周期 | 创建、启用/禁用、离职归档;与 SSO 时同步外部主键 | P0 | ◐ — 后端 CRUD + 前端管理页面已实现(`/admin/users`),SSO 同步未做 |
|
||||||
|
| M11-F15 | 角色定义与分配 | 预置角色(见 §13)+ 可选自定义角色;用户可挂多角色 | P0 | ◐ 仅实现 SYS_ADMIN/DEVELOPER/OPS 三角色,产品定义 10+ 角色待补齐 |
|
||||||
|
| M11-F16 | 功能权限(RBAC) | 菜单、按钮、API 操作与 **§13 权限码** 对齐;支持按环境预览「某用户看见什么」 | P0 | ◐ 路由级 RBAC 已实现,按钮级权限码未落地 |
|
||||||
|
| M11-F17 | 数据范围(Data Scope) | 按事业部/区域/客户组限制列表可见行(与 M11-F18 二选一或组合) | P2 | ○ |
|
||||||
|
| M11-F18 | 数据属主/团队 | 如「仅本人负责客户」「本团队项目」(字段:负责人、协作人) | P1 | ○ |
|
||||||
|
| M11-F19 | 业务字典 | 合同类型、交付类型、SN 异常原因分类等 | P0 | ✅ |
|
||||||
|
| M11-F20 | 系统参数 | 「孤儿 SN」强校验、交付门禁、会话超时分钟数、密码策略等 | P1 | ✅ — SystemParamController + platform_system_param 表 + 前端对接后端 API 已实现 |
|
||||||
|
| M11-F21 | 管理员敏感操作留痕 | 改角色、改权限、强制下线、重置密码等单独记审计 | P1 | ○ |
|
||||||
|
|
||||||
|
|
||||||
|
> **说明**:原 M11-F01~F06 已拆并至 **12.1 / 12.2**;实现时功能点 ID 以研发 backlog 为准,本文 ID 供需求追溯。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 13. 角色与权限体系
|
||||||
|
|
||||||
|
### 13.1 模型说明
|
||||||
|
|
||||||
|
- **认证(Authentication)**:谁登录、会话是否有效 —— 对应 **§12.1**。
|
||||||
|
- **授权(Authorization)**:登录后能做什么 —— **RBAC**(角色 → 权限码)为主,**数据范围**(事业部 / 团队 / 属主)为增强(P1/P2)。
|
||||||
|
- **权限码**:建议与前后端统一枚举,例如 `module:resource:action`(如 `contract:order:export`),便于接口与按钮同源校验。
|
||||||
|
|
||||||
|
### 13.2 预置角色定义
|
||||||
|
|
||||||
|
|
||||||
|
| 角色代码 | 角色名称 | 定位 | 典型职责 | 当前实现 |
|
||||||
|
| ---------------- | --------- | --------- | ---------------------------------------- | --- |
|
||||||
|
| `SYS_ADMIN` | 系统管理员 | 平台配置与账号治理 | 用户/角色/字典/系统参数;**不默认拥有业务全量数据**时可配置为「仅管理」 | ✅ |
|
||||||
|
| `DEVELOPER` | 研发/开发人员 | 技术研发与调试 | M1~M4/M6 业务 CRUD + 集成配置只读;**无** Callback 处置权限 | ✅ *注:产品定义中无此角色,为 MVP 简化引入,I10 起废弃,由 SALES 替代* |
|
||||||
|
| `OPS` | 运营人员 | 许可运营 | Callback 处置 + 集成配置只读;**无** 客户/项目/合同/交付/SN 写权限 | ✅ *注:产品定义中无此角色,为 MVP 简化引入,I10 起废弃,由 LICENSE_OPS 替代* |
|
||||||
|
| `SECURITY_ADMIN` | 安全管理员(可选) | 账号与登录安全 | 锁定策略、强制下线、审计检索;与 `SYS_ADMIN` 分离(职责分离,P2) | ○ |
|
||||||
|
| `SALES` | 商务经理 | 客户与签约侧 | 客户、项目、合同维护;发起交付与授权需求 | ✅ I10 已实现—替代原 DEVELOPER 角色 |
|
||||||
|
| `ORDER_SUPPORT` | 订单/运营支持 | 履约对齐 | 合同行与 SKU、订单号关联;协助商务核对「卖授一致」 | ○ *产品定义角色,仍在规划* |
|
||||||
|
| `DELIVERY` | 交付工程师 | 现场交付 | 交付批次与清单、环境信息、交付完成确认 | ✅ I10 已实现(售前演示账号 delivery/delivery) |
|
||||||
|
| `LICENSE_OPS` | 授权运营 | 许可台账与比特协同 | SN 全生命周期、Callback 处置、与控制台操作配合 | ✅ I10 已实现—替代原 OPS 角色 |
|
||||||
|
| `DEV_SUPPORT` | 研发/集成支撑 | 技术排障 | Callback 技术字段、集成配置**只读或受限编辑**;无业务合同删除权 | ○ |
|
||||||
|
| `FINANCE_VIEW` | 财务只读 | 对账与收入支撑 | 报表与合同/SN **只读**;无改密权外的写权限 | ○ |
|
||||||
|
| `COMPLIANCE` | 合规/审计 | 抽查与导出 | 审计日志、导出包;业务数据多为 **只读** | ○ |
|
||||||
|
| `EXEC_VIEW` | 管理层只读 | 经营视图 | 报表与健康度看板 **只读** | ○ |
|
||||||
|
| `READONLY_ALL` | 业务只读(可选) | 跨模块浏览 | 全业务 **只读**,用于培训或二线;敏感字段仍脱敏 | ○ |
|
||||||
|
|
||||||
|
|
||||||
|
**多角色**:用户可同时拥有 `SALES` + `DELIVERY` 等,权限取**并集**;互斥规则(如 `SYS_ADMIN` 与业务高敏导出)由企业策略在实现时约束。
|
||||||
|
|
||||||
|
### 13.3 模块级权限矩阵(摘要)
|
||||||
|
|
||||||
|
约定:**—** 无访问;**R** 查看;**W** 新建与编辑;**D** 删除;**X** 导出;**A** 审批/处置类(认领、重试、状态变更);**M** 模块管理配置(映射、字典、系统参数)。
|
||||||
|
|
||||||
|
|
||||||
|
| 模块 | SYS_ADMIN | SALES | ORDER_SUPPORT | DELIVERY | LICENSE_OPS | DEV_SUPPORT | FINANCE_VIEW | COMPLIANCE | EXEC_VIEW |
|
||||||
|
| ----------- | --------- | ---------- | ------------- | -------- | ----------- | ----------- | ------------ | ---------- | --------- |
|
||||||
|
| M1 客户与项目 | M / RWD | RWD | R | R | R | R | R | R | R |
|
||||||
|
| M2 合同 | M / RWDX | RWDX | RWD | R | R | R | RX | RX | R |
|
||||||
|
| M3 交付 | M / RWD | R | R | RWD | R | R | R | R | R |
|
||||||
|
| M4 授权/SN | M / RWDA | R | R | R | RWDA | R | RX | R | R |
|
||||||
|
| M5 Callback | M / RA | — | — | — | RA | R | R | R | R |
|
||||||
|
| M6 集成配置 | M | — | — | — | RW | R(只读为主) | — | R | — |
|
||||||
|
| M7 设备 | M / RW | R | R | RW | RW | R | R | R | R |
|
||||||
|
| M8 待办 | M | R | R | R | R | R | R | R | R |
|
||||||
|
| M9 报表 | M | R | R | R | R | R | RX | RX | R |
|
||||||
|
| M10 审计 | M | — | — | — | — | R | R | RX | — |
|
||||||
|
| M11 身份与平台 | M(全量) | 仅 F07 本人改密 | 同左 | 同左 | 同左 | 同左 | 同左 | R(审计检索) | — |
|
||||||
|
|
||||||
|
|
||||||
|
**说明**:
|
||||||
|
|
||||||
|
- `SYS_ADMIN` 列中 **M / RWD** 表示:至少能 **M**(用户角色字典参数);是否开放业务 **RWD** 由企业决定 —— 建议生产环境 **默认关闭** 业务写权限,仅保留 M11 管理面。
|
||||||
|
- `SECURITY_ADMIN`(若启用):建议拥有 M11 中 F05~F12、F21 及 M10 审计检索 **RX**,**不**默认拥有 M4 SN 发放。
|
||||||
|
- `DEV_SUPPORT`:M6 建议 **仅 R**;若需改测试环境映射可走 **变更工单 + 临时提权**。
|
||||||
|
- 细粒度需在实现阶段将矩阵展开为 **权限码清单**(每个菜单、按钮、导出、删除各一条)。
|
||||||
|
|
||||||
|
### 13.4 权限码命名建议(示例)
|
||||||
|
|
||||||
|
|
||||||
|
| 权限码 | 含义 |
|
||||||
|
| ------------------------------- | -------------- |
|
||||||
|
| `auth:session:logout` | 本人登出(全员) |
|
||||||
|
| `auth:user:password:change` | 本人改密 |
|
||||||
|
| `admin:user:manage` | 用户 CRUD |
|
||||||
|
| `admin:role:manage` | 角色与权限绑定 |
|
||||||
|
| `customer:project:rw` | M1 读写 |
|
||||||
|
| `contract:order:rw` | M2 合同读写 |
|
||||||
|
| `contract:order:export` | M2 导出 |
|
||||||
|
| `delivery:batch:rw` | M3 交付读写 |
|
||||||
|
| `license:sn:rw` | M4 SN 读写 |
|
||||||
|
| `license:sn:export` | M4 导出 |
|
||||||
|
| `license:callback:process` | M5 处置 Callback |
|
||||||
|
| `integration:config:rw` | M6 配置写 |
|
||||||
|
| `integration:config:read` | M6 只读 |
|
||||||
|
| `report:view` / `report:export` | M9 |
|
||||||
|
| `audit:search` / `audit:export` | M10 |
|
||||||
|
|
||||||
|
|
||||||
|
### 13.5 与版本包的关系(对应 §14)
|
||||||
|
|
||||||
|
- **MVP(I1~I9,已完成)**:§12.1 的 F01/F02/F04/F06 + §12.2 的 F14(基础)/F15(简化三角色)/F16(路由级)/F19;§13.2 **实际落地 `SYS_ADMIN` + `DEVELOPER` + `OPS`**(简化角色集,非产品定义全量);§13.3 矩阵为 **粗粒度模块级**。MVP 未覆盖的 P0 项(如 M1-F03/F06、M11-F03/F05/F07)标记为已知缺口,Mid 阶段补齐。
|
||||||
|
- **Mid(I10~I13,待实现)**:M7 设备 + M8 通知/待办 + M9 报表对账 + 补齐 MVP 遗留 P0 + M2/M4/M5/M6 P1 增强项 + SSO/并发会话/强制下线/密码重置 + 废弃 `DEVELOPER`/`OPS`,落地产品定义角色集。
|
||||||
|
- **Full(V2.0,规划)**:MFA、`SECURITY_ADMIN`、事业部数据范围、审计导出包、CRM 同步、细粒度互斥策略。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 14. 按版本包的功能边界(与 P0 / P1 / P2 对齐)
|
||||||
|
|
||||||
|
|
||||||
|
| 版本包 | 状态 | 包含模块与要点 |
|
||||||
|
| -------- | --- | ---------- |
|
||||||
|
| **MVP(I1~I9)** | ✅ **已完成** | M1/M2/M3/M4 核心功能 + M5 收件箱与处置 + M6 环境/产品线只读 + M10 审计日志 + **M11 JWT 登录/路由守卫/粗粒度三角色/字典**。角色矩阵为 **§13.3 粗粒度(简化三角色)**;自研许可证管理(V6)为额外交付。详见 §16 原型章节。 |
|
||||||
|
| **Mid(I10~I13)** | 🕐 **进行中** | MVP + M7 设备 + M8 通知待办 + M9 报表对账 + 补齐 MVP 未覆盖的 P0 项 + M2/M4/M5/M6 P1 增强 + M10-F02 审计检索 + **M11 SSO/并发/强制下线/密码重置/数据属主** + **权限码细拆** + **角色模型对标产品定义集**。 |
|
||||||
|
| **Full(V2.0)** | 📋 **规划中** | Mid + M2/M6/M9/M10/M11 的 P2 项 + M1/M8 集成与智能化增强 + **MFA、安全管理员、事业部数据范围、审计导出包、CRM 同步**。 |
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 15. 修订记录
|
||||||
|
|
||||||
|
|
||||||
|
| 日期 | 说明 |
|
||||||
|
| ---------- | ---------------------------------------------------------------------------------- |
|
||||||
|
| 2026-04-06 | 初版:产品视角模块划分与功能点表。 |
|
||||||
|
| 2026-04-06 | 增补:M11 扩展为身份/访问/平台管理;**登录/登出/会话**功能点;**§13 角色与权限体系**(预置角色、模块矩阵、权限码示例);版本包与 §14 对齐。 |
|
||||||
|
| 2026-05-25 | **全面更新**:所有功能点表增加「实现状态」列,标注 ✅/◐/○;M4-F06 降级至 P1;§13 角色表增加当前实现对照列;§14 版本包反映 I1~I9 完成状态;新增 **§16 原型实现说明**。 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 16. 原型实现说明(I1~I9 迭代交付)
|
||||||
|
|
||||||
|
> 本章记录 **2026-04 至 2026-05(I1~I9)** 已交付原型的具体范围,供产品验收、集成方评估与后续迭代规划使用。原型基于 **三轨并行**(后端双 JAR + 前端 Vue + 客户端 SDK)模式交付。
|
||||||
|
|
||||||
|
### 16.1 原型定位与范围
|
||||||
|
|
||||||
|
| 维度 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| **迭代范围** | I1(脚手架/M11)→ I9(Webhook 出库状态只读),共 9 个迭代 |
|
||||||
|
| **原型目标** | 跑通 BP-01~06、11 主链路:客户→项目→合同→交付→SN→Callback→审计 |
|
||||||
|
| **交付形态** | 两枚 Fat JAR(delivery-platform-api :8080 + license-webhook-ingress :8081)+ Vue 3 SPA + Rust cdylib + Java SDK JAR |
|
||||||
|
| **部署方式** | Docker Compose(PostgreSQL 15 + 双 JAR + Prometheus/Grafana 可选)或单机 `java -jar` |
|
||||||
|
| **覆盖模块** | M1~M6 P0 核心功能 + M10-F01 + M11 基础身份与访问 + 自研许可证管理(V6 额外) |
|
||||||
|
|
||||||
|
### 16.2 前端原型(delivery-platform-ui)
|
||||||
|
|
||||||
|
| 页面 | 路由 | 对应模块 | 实现说明 |
|
||||||
|
|------|------|---------|---------|
|
||||||
|
| 登录页 | `/login` | M11 | JWT Bearer 认证,演示账号 `admin/admin`、`ops/ops` |
|
||||||
|
| 首页 | `/` | — | 角色感知的模块快捷链接 + `GET /api/v1/ping` 调试 |
|
||||||
|
| 客户管理 | `/customers` | M1 | 列表/搜索/分页/新建/编辑对话框/删除(软删) |
|
||||||
|
| 项目管理 | `/projects` | M1 | 列表/按客户筛选/新建/编辑/删除(物理删) |
|
||||||
|
| 合同管理 | `/contracts` | M2 | 列表 + 三步创建向导 + 详情(状态机操作、行项管理、审计列表) |
|
||||||
|
| 交付管理 | `/deliveries` | M3 | 列表 + 新建向导 + 详情(抬头编辑、行项管理、状态变更) |
|
||||||
|
| 许可 SN | `/licenses/sn` | M4 | 列表 + 新建 + 详情(绑定/状态变更) |
|
||||||
|
| Callback 收件箱 | `/callbacks` | M5 | 列表(多维筛选)+ 详情(payload 脱敏预览、人工挂接、状态处置、DEAD 重放、出库状态) |
|
||||||
|
| 集成环境 | `/integration/environments` | M6 | 只读列表 |
|
||||||
|
| 产品线 | `/integration/product-lines` | M6 | 只读列表 |
|
||||||
|
| 403/404 | `/403` / `/*` | M11 | 路由级无权限/未找到提示 |
|
||||||
|
|
||||||
|
**技术栈**:Vue 3 (Composition API) + Vite + Pinia + vue-router + axios + Element Plus。Token 存 `localStorage`(**已知安全缺陷**,Mid 阶段应迁移至 HttpOnly Cookie)。
|
||||||
|
|
||||||
|
### 16.3 后端 API 原型
|
||||||
|
|
||||||
|
#### delivery-platform-api(:8080)
|
||||||
|
|
||||||
|
| Controller | 路由前缀 | 覆盖的模块 | 关键端点 |
|
||||||
|
|-----------|---------|-----------|---------|
|
||||||
|
| `AuthController` | `POST /api/v1/auth/login` | M11 | 登录(返回 JWT) |
|
||||||
|
| `CustomerController` | `/api/v1/customers` | M1 | CRUD + 分页列表 + 软删 |
|
||||||
|
| `ProjectController` | `/api/v1/projects` | M1 | CRUD + 分页列表 + 物理删 |
|
||||||
|
| `ContractController` | `/api/v1/contracts` | M2 | CRUD + 行项管理 + 状态机 PATCH |
|
||||||
|
| `DeliveryBatchController` | `/api/v1/delivery-batches` | M3 | CRUD + 行项管理 + 状态 PATCH |
|
||||||
|
| `LicenseSnController` | `/api/v1/license-sns` | M4 | CRUD + 状态 PATCH |
|
||||||
|
| `LicenseController` | `/api/v1/licenses` | 自研许可 | License CRUD + 激活 + 过期(V6 额外) |
|
||||||
|
| `CallbackInboxController` | `/api/v1/callback-inbox` | M5 | 列表/详情/状态 PATCH/人工挂接/重放 Webhook 出库 |
|
||||||
|
| `IntegrationCatalogController` | `/api/v1/integration/*` | M6 | 产品线/环境只读列表与详情 |
|
||||||
|
| `PingController` | `/api/v1/ping` | — | 健康探测 |
|
||||||
|
| `CallbackInternalController` | `/internal/v1/callback-events` | M5 | Webhook→平台内部投递入口 |
|
||||||
|
|
||||||
|
**安全**:JWT Bearer(`PLATFORM_JWT_SECRET`)+ 内部共享 Token(`X-Platform-Internal-Token`)+ 角色路由 `/api/v1/auth/login` `/actuator/health` 免认证。
|
||||||
|
|
||||||
|
#### license-webhook-ingress(:8081)
|
||||||
|
|
||||||
|
| Controller | 路径 | 职责 |
|
||||||
|
|-----------|------|------|
|
||||||
|
| `CallbackIngestController` | `POST /webhook/bitanswer/callback` | 比特 Callback 入站:验签(`x-bitanswer-token`)、幂等(`Idempotency-Key` 或 `externalMessageId`)、落收据表、入队投递 |
|
||||||
|
| `WebhookPlatformDeliveryOpsController` | `GET …/by-receipt/{receiptId}` | 出库状态只读查询 |
|
||||||
|
| `WebhookPlatformDeliveryOpsController` | `POST …/by-receipt/{receiptId}/replay` | DEAD 行重放 |
|
||||||
|
|
||||||
|
**安全**:`CRAFTLABS_WEBHOOK_EXPECTED_TOKEN` + `X-Webhook-Ops-Token`(I8 起)。
|
||||||
|
|
||||||
|
### 16.4 Rust 核心库原型(native/craft-core)
|
||||||
|
|
||||||
|
| 模块 | 文件 | 实现程度 |
|
||||||
|
|------|------|---------|
|
||||||
|
| **C ABI** | `lib.rs` | ✅ 8 个导出函数:`craft_initialize/activate/check_license/get_license_info/has_feature/release/heartbeat/destroy` |
|
||||||
|
| **Provider trait** | `trait_provider.rs` | ✅ 定义 Provider 接口:initialize/activate/check_license/heartbeat/has_feature/release/get_license_info/close |
|
||||||
|
| **自研 Provider** | `provider_selfhosted/` | ✅ 完整实现:activate + heartbeat + cache + license + protocol |
|
||||||
|
| **加密模块** | `crypto.rs` | ✅ AES-256-GCM 加解密、HKDF 密钥派生、RSA PKCS1v15 签名验证 |
|
||||||
|
| **设备指纹** | `device.rs` | ✅ 设备标识生成与校验 |
|
||||||
|
| **安全加固** | `security/` | ✅ 反调试(`anti_debug.rs`)、代码混淆(`obfuscation.rs`)、字符串加密(`string_encrypt.rs`)、完整性校验(`integrity.rs`)、动态 API 解析(`dynamic_api.rs`) |
|
||||||
|
| **许可管理** | `license.rs` / `activate.rs` / `heartbeat.rs` / `session.rs` / `error.rs` | ✅ 核心许可状态、激活/心跳业务流程、错误码定义 |
|
||||||
|
| **公钥嵌入** | `build.rs` | ✅ 构建时嵌入 RSA 公钥 |
|
||||||
|
|
||||||
|
**已知局限**:仅实现 `SelfHostedProvider`;`BitAnswerProvider` 在 Rust 侧未实现。
|
||||||
|
|
||||||
|
### 16.5 Java SDK 原型(java/)
|
||||||
|
|
||||||
|
| 模块 | 覆盖率 | 说明 |
|
||||||
|
|------|--------|------|
|
||||||
|
| `craftlabs-auth-core` | ✅ `AuthConfigs`(解析/校验/序列化)+ `AuthConfig`/`AuthConfigs` 配置模型 + `AuthProvider` 接口 + `AuthResult` + `LicenseInfo` + `NativeBridge`(JNI 接口声明) |
|
||||||
|
| `craftlabs-auth-bitanswer` | ⚠️ `BitAnswerProvider` 实现 `AuthProvider` 接口但 **JNI 未对接真实原生库**,属于 Stub 状态 |
|
||||||
|
| `craftlabs-auth-selfhosted` | ⚠️ `SelfHostedAuthProvider` 基础实现,未对接 Rust 核心库(当前为独立 Java 实现) |
|
||||||
|
| `craftlabs-auth-tests` | ✅ Schema 校验测试 + BitAnswerProvider 基础测试 |
|
||||||
|
| `schemas/craftlabs-auth-config.schema.json` | ✅ 完整 JSON Schema Draft 2020-12,支持 3 种 scenario + 2 种 provider |
|
||||||
|
|
||||||
|
### 16.6 原型已知局限
|
||||||
|
|
||||||
|
以下为审计发现的 25 个问题中与原型直接相关的重大局限:
|
||||||
|
|
||||||
|
| 类别 | 问题 | 影响 | 计划迭代 |
|
||||||
|
|------|------|------|---------|
|
||||||
|
| **安全** | 前端 Token 存 `localStorage`(非 HttpOnly Cookie) | XSS 窃取风险 | Mid |
|
||||||
|
| **安全** | Callback `raw_payload` 全字段明文落库 | 可能含 PII,未脱敏 | I10 |
|
||||||
|
| **SDK** | `BitAnswerProvider` 未对接真实原生库 | SDK 无法实际与比特安索通信 | Mid |
|
||||||
|
| **SDK** | Java SelfHostedProvider 未调用 Rust 核心 | 自研授权路径不通 | Mid |
|
||||||
|
| **角色** | 仅实现 3 个角色(产品定义 10+),存在 `DEVELOPER`/`OPS` 非标角色 | 角色模型与产品定义不匹配 | I12 |
|
||||||
|
| **M1** | 客户表缺行业/地址/开票信息、项目表缺计划起止/项目经理 | 字段覆盖不足 | I10 |
|
||||||
|
| **M4** | 比特控制台状态摘要未实现(原 P0) | 需跳转比特控制台查看 | P1/Mid |
|
||||||
|
| **M6** | 产品线→比特 ID 映射、特征映射、JSON 模板管理均未实现 | BP-10 配置发布流程不完整 | Mid |
|
||||||
|
| **M7~M9** | 设备/通知/报表模块完全未开始 | Mid 核心范围 | I10~I12 |
|
||||||
|
| **测试** | 无 Playwright E2E 测试 | 回归覆盖不足 | I10 |
|
||||||
|
| **基础设施** | 无消息队列,Webhook→API 走轮询 HTTP | 无削峰能力,高并发受限 | I11 |
|
||||||
|
|
||||||
|
### 16.7 从原型到产品化的演进路径
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart LR
|
||||||
|
subgraph MVP["MVP(已完成 I1~I9)"]
|
||||||
|
A["BP-01~06+11 主链路<br/>M1~M6 P0 + M10-F01 + M11 基础<br/>自研许可证 V6 额外"]
|
||||||
|
end
|
||||||
|
subgraph Mid["Mid(I10~I13)"]
|
||||||
|
B["补齐 M1/M4/M11 P0 缺口<br/>M7 设备 + M8 通知 + M9 报表<br/>M2/M5/M6 P1 增强<br/>SSO + 角色模型对齐<br/>BitAnswerProvider 对接"]
|
||||||
|
end
|
||||||
|
subgraph Full["Full(V2.0)"]
|
||||||
|
C["MFA / SECURITY_ADMIN<br/>数据范围 / 审计导出<br/>CRM 同步 / 变更治理<br/>消息队列 / 读模型分离"]
|
||||||
|
end
|
||||||
|
MVP --> Mid --> Full
|
||||||
|
```
|
||||||
|
|
||||||
|
**关键里程碑**:
|
||||||
|
- **当前**:MVP 原型已完成,可支撑一条真实或准生产项目全链路 UAT
|
||||||
|
- **Mid(目标 T0+24~28 周)**:具备设备治理、通知协作、对账报表,角色模型对标产品定义
|
||||||
|
- **Full(目标 T0+34~42 周)**:合规审计、深度集成、规模化运营能力
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,307 @@
|
|||||||
|
# 交付平台前端 UI 需求规格说明(走查稿)
|
||||||
|
|
||||||
|
> 依据 `web/delivery-platform-ui` 源码整理,供 Figma Make / 设计迭代与产品跟进维护。
|
||||||
|
> 侧栏实现见 `src/layout/MainLayout.vue`;路由与角色见 `src/router/index.js`。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 全局壳层(需登录页共用)
|
||||||
|
|
||||||
|
| 区域 | 内容 |
|
||||||
|
|------|------|
|
||||||
|
| 侧栏品牌 | 文案:`创飞 · 交付平台` |
|
||||||
|
| 侧栏菜单 | 见 §2(按角色 `v-if` 显示) |
|
||||||
|
| 顶栏右侧 | 当前用户展示名(`displayName`)、链接按钮「退出」 |
|
||||||
|
| 主内容区 | 路由出口,背景为控制台风格浅灰 |
|
||||||
|
|
||||||
|
### 1.1 角色与菜单可见性
|
||||||
|
|
||||||
|
与路由 `meta.roles` 一致:
|
||||||
|
|
||||||
|
- **SYS_ADMIN**:可见下文全部侧栏项(无单独剔除)。
|
||||||
|
- **DEVELOPER**:客户管理、项目管理、合同管理、交付管理、许可 SN、集成环境、产品线;**无** Callback 收件箱。
|
||||||
|
- **OPS**:Callback 收件箱、集成环境、产品线;**无** 客户 / 项目 / 合同 / 交付 / 许可 SN。
|
||||||
|
|
||||||
|
### 1.2 信息架构说明
|
||||||
|
|
||||||
|
当前侧栏为 **单层 `el-menu`**,无折叠分组、无物理「二级子菜单」。本文 **一级 = 产品模块**,**二级 = 侧栏入口 + 由其进入的子页面**(详情、向导、弹窗表单)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 按模块:菜单项 → 页面 → 数据与操作
|
||||||
|
|
||||||
|
### 2.1 首页
|
||||||
|
|
||||||
|
| 项 | 说明 |
|
||||||
|
|----|------|
|
||||||
|
| 路由 | `/`,`HomeView.vue` |
|
||||||
|
| 侧栏 | 「首页」 |
|
||||||
|
| 内容 | 信息 Alert(I7:按角色展示入口;Callback 仅 OPS / SYS_ADMIN);当前用户与角色;**与侧栏一致的模块快捷链接**(随角色过滤) |
|
||||||
|
| 附加 | 「调试」:`GET /api/v1/ping` 按钮 + JSON 文本结果区 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2.2 客户管理
|
||||||
|
|
||||||
|
| 项 | 说明 |
|
||||||
|
|----|------|
|
||||||
|
| 路由 | `/customers`,`CustomersView.vue` |
|
||||||
|
| 侧栏 | 「客户管理」 |
|
||||||
|
| 列表筛选 | 关键词(名称或统一社会信用代码)、「查询」 |
|
||||||
|
| 表格列 | 客户名称、统一社会信用代码 |
|
||||||
|
| 行操作 | 编辑、删除(确认框) |
|
||||||
|
| 工具栏 | 「新建客户」 |
|
||||||
|
| 分页 | 10 / 20 / 50;total、sizes、prev、pager、next、jumper |
|
||||||
|
|
||||||
|
#### 无独立菜单:新建 / 编辑客户(`el-dialog`,宽约 480px)
|
||||||
|
|
||||||
|
| 字段 | 校验 / 约束 | API 语义 |
|
||||||
|
|------|----------------|----------|
|
||||||
|
| 客户名称 | 必填,`maxlength=200`,字数统计 | `POST` / `PUT` body |
|
||||||
|
| 统一社会信用代码 | 选填,`maxlength=32` | 空则不发该字段 |
|
||||||
|
| 底部 | 取消、保存(loading) | `createCustomer` / `updateCustomer` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2.3 项目管理
|
||||||
|
|
||||||
|
| 项 | 说明 |
|
||||||
|
|----|------|
|
||||||
|
| 路由 | `/projects`,`ProjectsView.vue` |
|
||||||
|
| 侧栏 | 「项目管理」 |
|
||||||
|
| 列表筛选 | 客户下拉(可清空、可搜索)、「查询」 |
|
||||||
|
| 表格列 | 客户(名)、项目名称、阶段(字典中文) |
|
||||||
|
| 行操作 | 编辑、删除(确认框) |
|
||||||
|
| 工具栏 | 「新建项目」 |
|
||||||
|
| 分页 | 同客户管理 |
|
||||||
|
|
||||||
|
#### 无独立菜单:新建 / 编辑项目(`el-dialog`,宽约 520px)
|
||||||
|
|
||||||
|
| 字段 | 校验 / 约束 | 说明 |
|
||||||
|
|------|----------------|------|
|
||||||
|
| 客户 | 必填 | 选项来自客户列表(最多 500 条) |
|
||||||
|
| 项目名称 | 必填,`maxlength=200` | |
|
||||||
|
| 阶段 | 必填 | `GET /api/v1/dictionaries/PROJECT_PHASE`,前端兼容多种响应包裹形态 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2.4 合同管理
|
||||||
|
|
||||||
|
| 项 | 说明 |
|
||||||
|
|----|------|
|
||||||
|
| 列表路由 | `/contracts`,`ContractsView.vue` |
|
||||||
|
| 侧栏 | 「合同管理」 |
|
||||||
|
| 列表筛选 | 关键词(合同标题)、「查询」 |
|
||||||
|
| 表格列 | 合同标题/编号、客户、项目、状态(Tag)、创建时间 |
|
||||||
|
| 行操作 | 「详情」→ `/contracts/:id` |
|
||||||
|
| 工具栏 | 「新建合同」→ `/contracts/new` |
|
||||||
|
|
||||||
|
#### 无菜单:新建合同向导(`/contracts/new`,`ContractWizardView.vue`)
|
||||||
|
|
||||||
|
三步 `el-steps`:
|
||||||
|
|
||||||
|
**步骤 0 — 客户与项目**
|
||||||
|
|
||||||
|
- 客户必选;项目必选;项目下拉随所选客户过滤;更换客户时清空项目。
|
||||||
|
|
||||||
|
**步骤 1 — 合同基本信息**
|
||||||
|
|
||||||
|
- 合同标题/编号:必填,`maxlength=256`,字数统计。
|
||||||
|
- 备注:选填,`textarea`,`maxlength=4000`。
|
||||||
|
|
||||||
|
**步骤 2 — 明细行**
|
||||||
|
|
||||||
|
- 表内编辑:标的/行项名称(`itemName`)每行必填,`maxlength=256`。
|
||||||
|
- 数量:`el-input-number`,`precision=4`,`min=0.0001`,须大于 0。
|
||||||
|
- 单位:选填,`maxlength=32`。
|
||||||
|
- 至少保留一行;「添加明细」;「删除」(仅一行时删除禁用)。
|
||||||
|
|
||||||
|
**提交**:先 `createContract`,再对每行 `addLine`;成功跳转合同详情。
|
||||||
|
|
||||||
|
#### 无菜单:合同详情(`/contracts/:id`,`ContractDetailView.vue`)
|
||||||
|
|
||||||
|
| 区块 | 行为 |
|
||||||
|
|------|------|
|
||||||
|
| 头部 | ← 合同列表;标题「合同详情」;状态 Tag;**草稿** 显示「保存」 |
|
||||||
|
| 描述区 | 标题/备注:草稿可编辑;客户/项目只读(优先接口 name,否则 ID 映射) |
|
||||||
|
| 状态操作条 | 依状态显示按钮(均需确认):草稿→提交待生效;待生效→确认生效;生效→发起变更 / 终止合同;变更中→完成变更 |
|
||||||
|
| 合同明细 | 草稿可「添加明细」、行内编辑/删除;非草稿只读 |
|
||||||
|
| 明细弹窗 | 添加/编辑:行项名称必填;数量必填;单位选填 |
|
||||||
|
| 最近审计 | 列:时间、操作者、动作、摘要(`listAuditEvents` 归一化展示) |
|
||||||
|
|
||||||
|
**合同状态枚举(Tag)**
|
||||||
|
|
||||||
|
| 值 | 中文 |
|
||||||
|
|----|------|
|
||||||
|
| DRAFT | 草稿 |
|
||||||
|
| PENDING_EFFECTIVE | 待生效 |
|
||||||
|
| EFFECTIVE | 生效 |
|
||||||
|
| CHANGING | 变更中 |
|
||||||
|
| TERMINATED | 已终止 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2.5 交付管理
|
||||||
|
|
||||||
|
| 项 | 说明 |
|
||||||
|
|----|------|
|
||||||
|
| 列表路由 | `/deliveries`,`DeliveriesView.vue` |
|
||||||
|
| 侧栏 | 「交付管理」 |
|
||||||
|
| 列表筛选 | 项目下拉、批次编码关键词、「查询」 |
|
||||||
|
| 表格列 | 批次编码、项目、合同 ID、状态 Tag、计划交付日、创建时间 |
|
||||||
|
| 行操作 | 「详情」→ `/deliveries/:id` |
|
||||||
|
| 工具栏 | 「新建交付批次」→ `/deliveries/new` |
|
||||||
|
|
||||||
|
#### 无菜单:新建交付批次(`/deliveries/new`,`DeliveryBatchWizardView.vue`)
|
||||||
|
|
||||||
|
| 字段 | 校验 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| 项目 | 必填 | 可搜索 |
|
||||||
|
| 合同 | 选填 | 依赖项目;按 `projectId` 过滤合同 |
|
||||||
|
| 批次编码 | 必填,`maxlength=64` | |
|
||||||
|
| 计划交付日 | 选填 | 日期,`YYYY-MM-DD` |
|
||||||
|
| 备注 | 选填,`maxlength=4000` | |
|
||||||
|
| 底部 | 「创建并返回列表」/「创建并进入详情」 | |
|
||||||
|
|
||||||
|
#### 无菜单:交付批次详情(`/deliveries/:id`,`DeliveryBatchDetailView.vue`)
|
||||||
|
|
||||||
|
| 区块 | 行为 |
|
||||||
|
|------|------|
|
||||||
|
| 头部 | ← 交付列表;状态 Tag;**PENDING** 时:「保存抬头」「标记已交付」(确认) |
|
||||||
|
| 抬头 | 批次编码 / 项目 / 合同 ID / 完成时间只读;**PENDING** 可编辑计划交付日、备注 |
|
||||||
|
| 交付明细 | **PENDING** 可增删改;列:排序、说明、数量、合同行 ID |
|
||||||
|
| 明细弹窗 | 排序 number 0–999999;说明必填,`maxlength=512`;数量必填;合同行 ID 选填 |
|
||||||
|
|
||||||
|
**交付状态枚举**
|
||||||
|
|
||||||
|
| 值 | 中文 |
|
||||||
|
|----|------|
|
||||||
|
| PENDING | 待交付 |
|
||||||
|
| DELIVERED | 已交付 |
|
||||||
|
| CANCELLED | 已取消 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2.6 许可 SN
|
||||||
|
|
||||||
|
| 项 | 说明 |
|
||||||
|
|----|------|
|
||||||
|
| 列表路由 | `/licenses/sn`,`LicenseSnListView.vue` |
|
||||||
|
| 侧栏 | 「许可 SN」 |
|
||||||
|
| 列表筛选 | 项目、SN 关键词、「查询」 |
|
||||||
|
| 表格列 | SN 编码、项目、合同行 ID、状态 Tag、创建时间 |
|
||||||
|
| 行操作 | 「详情」→ `/licenses/sn/:id` |
|
||||||
|
| 工具栏 | 「新建许可 SN」→ `/licenses/sn/new` |
|
||||||
|
|
||||||
|
#### 无菜单:新建许可 SN(`/licenses/sn/new`,`LicenseSnWizardView.vue`)
|
||||||
|
|
||||||
|
| 字段 | 校验 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| SN 编码 | 必填,`maxlength=128` | |
|
||||||
|
| 项目 | 选填 | 可搜索 |
|
||||||
|
| 合同行 ID | 选填,number ≥1 | MVP 手工录入提示 |
|
||||||
|
| 激活备注 | 选填,`textarea`,`maxlength=512` | |
|
||||||
|
| 底部 | 创建并返回列表 / 创建并进入详情 | |
|
||||||
|
|
||||||
|
#### 无菜单:许可 SN 详情(`/licenses/sn/:id`,`LicenseSnDetailView.vue`)
|
||||||
|
|
||||||
|
| 区块 | 内容 |
|
||||||
|
|------|------|
|
||||||
|
| 基础 | SN 编码、创建时间 |
|
||||||
|
| 绑定与备注 | 表单:项目、合同行 ID、激活备注;「保存绑定」 |
|
||||||
|
| 状态 | 下拉:REGISTERED / ISSUED / ACTIVATED / SUSPENDED / REVOKED(含中英文标签);「更新状态」 |
|
||||||
|
|
||||||
|
**许可 SN 状态枚举**
|
||||||
|
|
||||||
|
| 值 | 中文 |
|
||||||
|
|----|------|
|
||||||
|
| REGISTERED | 已登记 |
|
||||||
|
| ISSUED | 已发放 |
|
||||||
|
| ACTIVATED | 已激活 |
|
||||||
|
| SUSPENDED | 已暂停 |
|
||||||
|
| REVOKED | 已吊销 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2.7 Callback 收件箱(SYS_ADMIN、OPS)
|
||||||
|
|
||||||
|
| 项 | 说明 |
|
||||||
|
|----|------|
|
||||||
|
| 列表路由 | `/callbacks`,`CallbackInboxView.vue` |
|
||||||
|
| 侧栏 | 「Callback 收件箱」 |
|
||||||
|
| 列表筛选 | 状态(PENDING / PROCESSED / FAILED / IGNORED)、事件类型、SN、项目 ID(文本)、「查询」 |
|
||||||
|
| 表格列 | 来源、外部消息 ID、事件类型、SN、状态 Tag、收件时间 |
|
||||||
|
| 行操作 | 「详情」→ `/callbacks/:id` |
|
||||||
|
|
||||||
|
> **扩展说明**:`listCallbackInbox` API 另支持 `productLineId`、`environmentId`、`from`、`to` 等;当前 UI 未暴露,可在后续版本增加筛选。
|
||||||
|
|
||||||
|
#### 无菜单:Callback 详情(`/callbacks/:id`,`CallbackInboxDetailView.vue`)
|
||||||
|
|
||||||
|
| 区块 | 规格 |
|
||||||
|
|------|------|
|
||||||
|
| 主信息 | ID、来源系统、外部消息 ID、事件类型、Schema 版本、幂等键、Webhook 收据 ID、SN、项目/合同/许可 SN/产品线/环境 ID、收件/处理时间、失败原因、备注 |
|
||||||
|
| Payload | 标题「Payload(脱敏预览)」;深色只读代码块,`pre`,约 `max-height: 420px` 可滚动 |
|
||||||
|
| I9 Webhook 出库状态 | **仅当**存在 `webhookReceiptId`:加载骨架 / 错误或描述列表:出库状态、尝试次数、上次错误、下次重试、出库更新时间 |
|
||||||
|
| I8 重新入队 | **同上条件**:配置说明(`LICENSE_WEBHOOK_BASE_URL`、`LICENSE_WEBHOOK_OPS_TOKEN`);按钮「重新入队出库(DEAD→待投递)」+ 确认 |
|
||||||
|
| 状态处置 | **仅 PENDING**:标已处理 / 标失败 / 忽略(均确认) |
|
||||||
|
| 人工挂接 | 许可 SN ID、项目 ID、合同 ID(文本输入);「保存挂接」需至少填一项 |
|
||||||
|
|
||||||
|
**Callback 状态枚举**
|
||||||
|
|
||||||
|
| 值 | 中文 |
|
||||||
|
|----|------|
|
||||||
|
| PENDING | 待处理 |
|
||||||
|
| PROCESSED | 已处理 |
|
||||||
|
| FAILED | 失败 |
|
||||||
|
| IGNORED | 忽略 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2.8 集成环境
|
||||||
|
|
||||||
|
| 项 | 说明 |
|
||||||
|
|----|------|
|
||||||
|
| 路由 | `/integration/environments`,`IntegrationEnvironmentsView.vue` |
|
||||||
|
| 侧栏 | 「集成环境」 |
|
||||||
|
| 页面 | 只读表 + 「刷新」 |
|
||||||
|
| 列 | 编码、名称、类型、比特 URL(`bitanswerBaseUrl` 等兼容字段)、产品线 ID |
|
||||||
|
| 分页 | 默认 `pageSize=20`,可选 10/20/50 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2.9 产品线
|
||||||
|
|
||||||
|
| 项 | 说明 |
|
||||||
|
|----|------|
|
||||||
|
| 路由 | `/integration/product-lines`,`IntegrationProductLinesView.vue` |
|
||||||
|
| 侧栏 | 「产品线」 |
|
||||||
|
| 页面 | 只读表 + 「刷新」 |
|
||||||
|
| 列 | 编码、名称、描述、启用(`enabled` / `active` 为 false 显示「否」) |
|
||||||
|
| 分页 | 同集成环境 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 无侧栏:认证与异常页
|
||||||
|
|
||||||
|
| 路由 | 页面 | 内容 |
|
||||||
|
|------|------|------|
|
||||||
|
| `/login` | `LoginView.vue` | 标题「客户商务与交付管理平台(I1)」;用户名、密码;登录 loading;演示账号提示 |
|
||||||
|
| `/403` | `ForbiddenView.vue` | Result:无权限;「返回首页」 |
|
||||||
|
| `/*` | `NotFoundView.vue` | Result:404;「返回首页」 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Figma / 画板拆分建议
|
||||||
|
|
||||||
|
1. **App Shell**:侧栏 + 顶栏 + 内容槽(与各列表/详情组合)。
|
||||||
|
2. **一级画板**:首页、各侧栏列表页、登录、403、404。
|
||||||
|
3. **二级画板**(无侧栏直达):合同向导(3 步)、合同详情、交付新建、交付详情、许可新建、许可详情、Callback 详情。
|
||||||
|
4. **模态层**:客户表单、项目表单、合同行、交付行;各类二次确认可在设计注释中说明。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 修订记录
|
||||||
|
|
||||||
|
| 日期 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| 2026-04-07 | 初版:按 `web/delivery-platform-ui` 源码走查整理,供设计跟进。 |
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
# 平台前后端 × 客户端 SDK — 并行迭代执行索引
|
||||||
|
|
||||||
|
> **目的**:在 [BPM 与版本排期](../chuangfei-platform-bpm-and-roadmap.md) 的 **I1~I6 / V1.1 / Mid / V2.0** 框架下,**三条工程线并行**落地时的 **入口文档、同步点与职责**。
|
||||||
|
> **基准**:与 [WORKSPACE_ENGINEERING_LAYOUT.md](./WORKSPACE_ENGINEERING_LAYOUT.md)(Fat JAR、禁止服务端 `craftlabs-auth-bitanswer`)一致。
|
||||||
|
> **全景**:系统上下文、容器、组件与时序见 [SYSTEM_ARCHITECTURE.md](./SYSTEM_ARCHITECTURE.md)。
|
||||||
|
> **安全**:信任边界、威胁缓解与分组件控制见 [SYSTEM_ARCHITECTURE.md §9](./SYSTEM_ARCHITECTURE.md)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 三条轨道与文档
|
||||||
|
|
||||||
|
|
||||||
|
| 轨道 | 仓库/工作区 | 执行包文档 |
|
||||||
|
| -------------- | --------------------------------------------------------------- | -------------------------------------------------------------------------------- |
|
||||||
|
| **A. 平台后端** | `craftlabs-delivery-platform` + `craftlabs-license-webhook`(规划) | [tracks/01-backend-platform-webhook.md](./tracks/01-backend-platform-webhook.md) |
|
||||||
|
| **B. 平台前端** | `craftlabs-delivery-platform-ui`(规划) | [tracks/02-frontend-platform-ui.md](./tracks/02-frontend-platform-ui.md) |
|
||||||
|
| **C. 客户端 SDK** | 本工作区 `craftlabs-authorization-sdk` | [tracks/03-client-sdk.md](./tracks/03-client-sdk.md) |
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 并行模型(同迭代日历)
|
||||||
|
|
||||||
|
三轨 **共用** Roadmap 的 **I1~I6**(各约 **2 周**),同一迭代内并行开工;**硬耦合**集中在 **I5(Callback + Schema)** 与 **I6(UAT)**。
|
||||||
|
|
||||||
|
|
||||||
|
| 迭代 | 后端(双 JAR) | 前端(Vue) | 客户端 SDK(本仓) |
|
||||||
|
| --- | ---------------------------------------------------- | ------------------------- | -------------------------------------------- |
|
||||||
|
| I1 | 身份 + Webhook 脚手架 | 登录 + 布局壳 + RBAC 路由 | 文档/边界说明,无阻塞发布 |
|
||||||
|
| I2 | M1 主数据 + 字典 | 客户/项目页 | 同上 |
|
||||||
|
| I3 | M2 合同 + M10-F01 | 合同向导与行项 | 同上 |
|
||||||
|
| I4 | M3 交付 + M4 SN | 交付 + SN 页 | 文档与示例与 BP-10 口径一致 |
|
||||||
|
| I5 | M5 Inbox + M6 + Webhook 生产链 | Callback + 集成只读页 | **Schema + AuthConfigs + examples 对齐 BP-10** |
|
||||||
|
| I6 | 加固 + UAT | E2E + 缺陷 | **冻结 SDK 版本 + CHANGELOG + 兼容矩阵** |
|
||||||
|
| I7 | Webhook **异步投递** + **OPS** 运营权限(Callback) | 路由/菜单与 `@PreAuthorize` 对齐 | 若涉及 Schema 仍走轨道 C |
|
||||||
|
| I8 | `**DEAD` 出库重放**:Webhook 内部 API + 平台代理 + UI / Runbook | Callback 详情「重新入队」 | 不涉及 SDK Schema |
|
||||||
|
| I9 | **出库状态只读**:Webhook `GET by-receipt` + 平台代理 + 详情展示 | Callback 详情「出库状态」 | 不涉及 SDK Schema |
|
||||||
|
|
||||||
|
|
||||||
|
设计/复盘:[I7_DESIGN.md](./iterations/I7_DESIGN.md)、[I7_IMPLEMENTATION_REVIEW.md](./iterations/I7_IMPLEMENTATION_REVIEW.md);I8:[I8_WEBHOOK_DELIVERY_REPLAY_DESIGN.md](./iterations/I8_WEBHOOK_DELIVERY_REPLAY_DESIGN.md)、[I8_IMPLEMENTATION_REVIEW.md](./iterations/I8_IMPLEMENTATION_REVIEW.md);I9:[I9_WEBHOOK_DELIVERY_VISIBILITY_DESIGN.md](./iterations/I9_WEBHOOK_DELIVERY_VISIBILITY_DESIGN.md)、[I9_IMPLEMENTATION_REVIEW.md](./iterations/I9_IMPLEMENTATION_REVIEW.md)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 跨轨同步点(必须对齐)
|
||||||
|
|
||||||
|
|
||||||
|
| 周次/迭代 | 同步内容 | 参与方 |
|
||||||
|
| -------- | ------------------------------------------------------------------------ | ------------------------------------------- |
|
||||||
|
| **I1 末** | 认证方式(JWT vs Session)、OpenAPI `auth` tag、错误码与 401 行为 | 后端 + 前端 |
|
||||||
|
| **I2 末** | 客户/项目 DTO 冻结;字典编码 | 后端 + 前端 |
|
||||||
|
| **I3 末** | 合同状态枚举与非法迁移码;M10-F01 字段 | 后端 + 前端 |
|
||||||
|
| **I4 末** | SN 绑定与「孤儿 SN」规则;交付门禁参数(M11-F20) | 后端 + 前端 + SDK(文档) |
|
||||||
|
| **I5 起** | Callback payload 与 inbox DTO;**Idempotency-Key**;Webhook→平台投递方式(HTTP/MQ) | 后端 Webhook + 后端平台 + 前端 + **SDK(Schema/示例)** |
|
||||||
|
| **I6** | UAT 场景、冻结 **SDK 版本**、两枚 Fat JAR 与前端 `VITE_API_BASE` | 全员 |
|
||||||
|
|
||||||
|
|
||||||
|
**契约 Owner(建议角色)**:架构或 Tech Lead 兼任;**OpenAPI 单一事实来源** 为本仓库 `[contracts/openapi/delivery-platform-api.json](../../contracts/openapi/delivery-platform-api.json)`(与运行时 `/v3/api-docs` 由 `OpenApiContractSnapshotTest` 对齐),说明见 `[contracts/README.md](../../contracts/README.md)`。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 依赖关系简图
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart LR
|
||||||
|
subgraph FE[前端 UI]
|
||||||
|
UI[Vue 3]
|
||||||
|
end
|
||||||
|
subgraph BE[平台 API]
|
||||||
|
API[platform-bootstrap.jar]
|
||||||
|
end
|
||||||
|
subgraph WH[Webhook]
|
||||||
|
WB[webhook-bootstrap.jar]
|
||||||
|
end
|
||||||
|
subgraph SDK[本仓库 SDK]
|
||||||
|
JAR[craftlabs-auth-*]
|
||||||
|
NAT[native]
|
||||||
|
end
|
||||||
|
|
||||||
|
UI --> API
|
||||||
|
WB -->|内部调用或 MQ| API
|
||||||
|
SDK -.->|不依赖平台运行时| API
|
||||||
|
JAR --> NAT
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 修订记录
|
||||||
|
|
||||||
|
|
||||||
|
| 日期 | 说明 |
|
||||||
|
| ---------- | ----------------------------------------- |
|
||||||
|
| 2026-04-06 | 初版:三轨并行索引 + 同步点 + Gantt 示意。 |
|
||||||
|
| 2026-04-07 | 增加 **I9**:Webhook 出库状态只读(Callback 详情可观测)。 |
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,562 @@
|
|||||||
|
# 系统架构设计 — 全景图与组件清单
|
||||||
|
|
||||||
|
> **角色**:资深架构师视角的 **系统上下文、容器划分、逻辑组件、数据流与部署边界** 的单一汇总页。
|
||||||
|
> **读者**:研发、测试、运维、产品与集成方。
|
||||||
|
> **关联**:[工作区工程划分](./WORKSPACE_ENGINEERING_LAYOUT.md) · [并行迭代索引](./PARALLEL_ITERATION_INDEX.md) · [功能模块 M1–M11](../chuangfei-platform-product-modules.md) · [比特对接总览](../chuangfei-bitanswer-integration-platform.md) · [`engineering/workspace-manifest.json`](../../engineering/workspace-manifest.json)
|
||||||
|
> **安全专章**:本章 **§9**(含 **§9.0** 小团队阶段、**§9.8** SDK 发布完整性);与工作区 [WORKSPACE §10 安全边界](./WORKSPACE_ENGINEERING_LAYOUT.md) 互补。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 架构目标与原则
|
||||||
|
|
||||||
|
| 原则 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| **边界清晰** | **客户端授权运行时**(SDK + Native + 现场进程)与 **商业交付平台**(Web + 双后端 + 数据持久化)在 **依赖、发布周期、classpath** 上严格分离。 |
|
||||||
|
| **契约先行** | 客户端配置以 **`schemas/craftlabs-auth-config.schema.json`** 为契约;Callback / OpenAPI 以 **文档与版本化契约** 对齐多轨迭代(见 [PARALLEL_ITERATION_INDEX](./PARALLEL_ITERATION_INDEX.md))。 |
|
||||||
|
| **可运维** | 每个可独立扩缩的后端服务 **一枚 Fat JAR**(`spring-boot-maven-plugin` **repackage** 仅挂在启动模块);密钥与连接串 **不进** SDK 与示例仓库。 |
|
||||||
|
| **数据栈锁定** | 平台持久化:**PostgreSQL 15** + **MyBatis-Plus**(详见 [WORKSPACE_ENGINEERING_LAYOUT §9.1](./WORKSPACE_ENGINEERING_LAYOUT.md))。 |
|
||||||
|
| **安全默认** | **最小权限**、**纵深防御**、**可审计**;对外入口 **TLS**;敏感面(Webhook、管理 API、SN/许可数据)**单独威胁建模**与门禁。 |
|
||||||
|
| **阶段目标(创飞当前)** | **小团队**:优先 **功能与需求闭环**;并发与规模 **不高**。平台侧 **单/双 Fat JAR + 单机或少量 VM** 上 `java -jar` 即可;**不将 K8s、云 KMS、服务网格** 作为现阶段必选项,待业务量与合规要求上升再演进。 **安全重心** 放在 **已发布 SDK 的完整性**(不易被 **替换、假冒**),见 **§9.0 / §9.8**。 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 系统上下文(C4 Level 1)
|
||||||
|
|
||||||
|
展示本体系与 **外部环境** 的关系:谁使用谁、谁回调谁。
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart TB
|
||||||
|
subgraph Actors["参与者"]
|
||||||
|
OPS[运维/管理员]
|
||||||
|
BIZ[商务/交付/License Ops]
|
||||||
|
DEV[客户侧研发]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph CraftLabs["创飞侧系统(本方案范围)"]
|
||||||
|
UI[交付管理平台 Web UI]
|
||||||
|
API[交付平台 API]
|
||||||
|
WH[License Webhook Ingress]
|
||||||
|
DB[(PostgreSQL 15)]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph ClientSite["客户现场 / 边缘环境"]
|
||||||
|
APP[客户业务应用]
|
||||||
|
SDK[CraftLabs 授权 SDK]
|
||||||
|
NAT[BitAnswer Native 库]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph External["外部系统"]
|
||||||
|
BA[比特安索云 / 控制台]
|
||||||
|
end
|
||||||
|
|
||||||
|
OPS --> UI
|
||||||
|
BIZ --> UI
|
||||||
|
UI --> API
|
||||||
|
API --> DB
|
||||||
|
WH --> DB
|
||||||
|
WH -->|内部 HTTP / MQ 异步| API
|
||||||
|
|
||||||
|
BA -->|规则 Callback HTTPS POST| WH
|
||||||
|
SDK -->|Bit_Login / 许可 API| BA
|
||||||
|
APP --> SDK
|
||||||
|
SDK --> NAT
|
||||||
|
|
||||||
|
DEV -->|依赖 Maven 工件 + JSON 配置| SDK
|
||||||
|
DEV -.->|阅读 Schema/文档| CraftLabs
|
||||||
|
```
|
||||||
|
|
||||||
|
**读图要点**:
|
||||||
|
|
||||||
|
- **比特** 只与 **Webhook**(服务端入站)和 **SDK**(客户端出站授权)发生 **不同方向** 的交互;平台 UI **不** 替代 SDK 完成现场授权。
|
||||||
|
- **SDK 不依赖** 平台 API 运行时;二者仅通过 **契约(Schema、文档、可选 OpenAPI 导出配置)** 弱耦合。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 容器架构(C4 Level 2)
|
||||||
|
|
||||||
|
本工作区与规划仓库中的 **可构建、可部署或可分发单元**。
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart TB
|
||||||
|
subgraph Browser["用户浏览器"]
|
||||||
|
SPA[Vue 3 SPA<br/>delivery-platform-ui]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph DMZ["接入层(可选 Gateway)"]
|
||||||
|
GW[API Gateway / 反向代理<br/>规划]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph SimpleHost["机房或云主机(单机为主)"]
|
||||||
|
JAR_API[delivery-platform-api<br/>Fat JAR :8080]
|
||||||
|
JAR_WH[license-webhook-ingress<br/>Fat JAR :8081]
|
||||||
|
PG[(PostgreSQL 15)]
|
||||||
|
MQ[[消息队列<br/>可选·低并发可省]]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph ArtifactRepo["制品与契约"]
|
||||||
|
MVN[Maven:cn.craftlabs / cn.craftlabs.platform]
|
||||||
|
SCH[JSON Schema + examples]
|
||||||
|
NATIVE[各 OS Native 动态库]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph CI["持续集成"]
|
||||||
|
WF_SDK[ci-java.yml]
|
||||||
|
WF_PLAT[ci-platform.yml]
|
||||||
|
end
|
||||||
|
|
||||||
|
SPA -->|HTTPS /api| JAR_API
|
||||||
|
GW -.-> JAR_API
|
||||||
|
GW -.-> JAR_WH
|
||||||
|
JAR_API --> PG
|
||||||
|
JAR_WH --> PG
|
||||||
|
JAR_WH -.-> MQ
|
||||||
|
MQ -.-> JAR_API
|
||||||
|
|
||||||
|
MVN --> APPX[客户应用构建]
|
||||||
|
SCH --> APPX
|
||||||
|
NATIVE --> APPX
|
||||||
|
|
||||||
|
WF_SDK --> MVN
|
||||||
|
WF_PLAT --> JAR_API
|
||||||
|
WF_PLAT --> JAR_WH
|
||||||
|
WF_PLAT --> SPA
|
||||||
|
```
|
||||||
|
|
||||||
|
**阶段说明(创飞当前)**:并发不高时 **可不部署 MQ**(Webhook 直写库或由 API 轮询即可);后端以 **一台或两台云主机** 各跑 `java -jar …` 为主,**无需 K8s**。
|
||||||
|
|
||||||
|
| 容器 | 技术 / 形态 | 默认端口或产出 | 职责摘要 |
|
||||||
|
|------|-------------|----------------|----------|
|
||||||
|
| **delivery-platform-ui** | Vue 3 + Vite + Pinia + Element Plus | 静态资源 / dev server | 管理端 SPA:登录、领域页面、调用平台 REST。 |
|
||||||
|
| **delivery-platform-api** | Spring Boot 3.4.x(目标 4.0.x)、Security、Actuator、MyBatis-Plus | **8080**、Fat JAR | 领域 API、认证、M1–M11 业务能力、读写字段库。 |
|
||||||
|
| **license-webhook-ingress** | Spring Boot、Actuator | **8081**、Fat JAR | 比特 Callback 入站、验签/Token、快速 2xx、异步投递(表/MQ)。 |
|
||||||
|
| **PostgreSQL** | 15.x | 5432 | 平台主库;与 SDK **无** 直连。 |
|
||||||
|
| **SDK Java 模块** | 多模块 Maven,**非** Spring Boot | 多枚 `craftlabs-auth-*.jar` | 配置模型、BitAnswer 桥接、自托管占位等。 |
|
||||||
|
| **Native** | CMake | `.so` / `.dylib` / `.dll` | 比特官方运行时绑定,随 SDK 版本发布。 |
|
||||||
|
| **schemas + examples** | JSON Schema、样例 JSON | 无进程 | 配置契约与集成样例。 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 平台后端逻辑组件(领域视图)
|
||||||
|
|
||||||
|
与产品模块 **M1–M11** 对齐的 **逻辑分包**(实现可跨多 Maven 模块,但边界应一致)。
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart LR
|
||||||
|
subgraph API["delivery-platform-api(逻辑)"]
|
||||||
|
M11[identity / platform M11]
|
||||||
|
M1[customer-project M1]
|
||||||
|
M2[contract M2]
|
||||||
|
M3[delivery M3]
|
||||||
|
M4[licensing SN M4]
|
||||||
|
M5[inbox callback M5]
|
||||||
|
M6[integration mapping M6]
|
||||||
|
M7[device M7]
|
||||||
|
M8[notification M8]
|
||||||
|
M9[reporting M9]
|
||||||
|
M10[audit M10]
|
||||||
|
PERS[persistence MyBatis-Plus]
|
||||||
|
end
|
||||||
|
|
||||||
|
M11 --> M1
|
||||||
|
M1 --> M2
|
||||||
|
M2 --> M3
|
||||||
|
M2 --> M4
|
||||||
|
M3 --> M4
|
||||||
|
M6 --> M4
|
||||||
|
M5 --> M4
|
||||||
|
M4 --> M7
|
||||||
|
M5 --> M8
|
||||||
|
M4 --> M9
|
||||||
|
M1 & M2 & M3 & M4 & M5 --> M10
|
||||||
|
|
||||||
|
M1 & M2 & M3 & M4 & M5 & M6 & M7 & M8 & M9 & M10 & M11 --> PERS
|
||||||
|
```
|
||||||
|
|
||||||
|
**Webhook 侧(license-webhook-ingress)逻辑组件**(精简):
|
||||||
|
|
||||||
|
| 组件 | 职责 |
|
||||||
|
|------|------|
|
||||||
|
| **Ingress Controller** | 接收 POST、限流与尺寸约束(规划加固)。 |
|
||||||
|
| **Token / 签名验证** | `x-bitanswer-token` 或约定头;与 `CRAFTLABS_WEBHOOK_EXPECTED_TOKEN` 等环境变量对齐。 |
|
||||||
|
| **幂等与投递** | `Idempotency-Key` 或 payload 内事件 ID;写入 **inbox** 或 **MQ**(I5+),由平台 API 消费。 |
|
||||||
|
| **观测** | Actuator、结构化日志、指标(与平台统一约定)。 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 客户端 SDK 组件(本仓库 `java/` + `native/`)
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart TB
|
||||||
|
subgraph SDK["CraftLabs Authorization SDK"]
|
||||||
|
CORE[craftlabs-auth-core<br/>配置 / NativeBridge / 校验]
|
||||||
|
BIT[craftlabs-auth-bitanswer<br/>Provider + loadLibrary]
|
||||||
|
SH[craftlabs-auth-selfhosted<br/>占位实现]
|
||||||
|
TST[craftlabs-auth-tests]
|
||||||
|
end
|
||||||
|
|
||||||
|
SCH2[schemas + examples]
|
||||||
|
JNI[libcraftlabs_auth_bitanswer]
|
||||||
|
|
||||||
|
CORE --> BIT
|
||||||
|
CORE --> SH
|
||||||
|
BIT --> JNI
|
||||||
|
TST --> CORE
|
||||||
|
SCH2 -.-> CORE
|
||||||
|
```
|
||||||
|
|
||||||
|
**硬约束**:平台服务端 **禁止** 依赖 `craftlabs-auth-bitanswer` 与 **禁止** `System.loadLibrary("craftlabs_auth_bitanswer")`(见 [WORKSPACE §5](./WORKSPACE_ENGINEERING_LAYOUT.md))。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 关键运行时序
|
||||||
|
|
||||||
|
### 6.1 比特 Callback → 平台
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
participant BA as 比特安索云
|
||||||
|
participant WH as license-webhook-ingress
|
||||||
|
participant Store as PG / MQ
|
||||||
|
participant API as delivery-platform-api
|
||||||
|
participant UI as 管理端 UI
|
||||||
|
|
||||||
|
BA->>WH: POST /webhook/bitanswer/callback
|
||||||
|
WH->>WH: 校验 Token / 幂等键
|
||||||
|
WH->>Store: 持久化或入队(异步)
|
||||||
|
WH-->>BA: 2xx 快速响应
|
||||||
|
API->>Store: 拉取 / 消费事件
|
||||||
|
UI->>API: 查询 Inbox / 处置状态
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.2 管理员使用平台
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
participant U as 用户
|
||||||
|
participant UI as delivery-platform-ui
|
||||||
|
participant API as delivery-platform-api
|
||||||
|
participant DB as PostgreSQL 15
|
||||||
|
|
||||||
|
U->>UI: 登录 / 操作业务页
|
||||||
|
UI->>API: REST(Session / JWT 由迭代约定)
|
||||||
|
API->>DB: MyBatis-Plus 读写
|
||||||
|
API-->>UI: DTO / 错误码
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.3 客户现场授权(与平台解耦)
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
participant APP as 客户应用
|
||||||
|
participant SDK as craftlabs-auth-*
|
||||||
|
participant NAT as Native
|
||||||
|
participant BA as 比特安索云
|
||||||
|
|
||||||
|
APP->>SDK: 初始化 + 加载配置 JSON
|
||||||
|
SDK->>SDK: Schema/规则校验(可选)
|
||||||
|
SDK->>NAT: JNI 调用
|
||||||
|
NAT->>BA: 许可 / 登录协议
|
||||||
|
BA-->>NAT: 授权结果
|
||||||
|
NAT-->>SDK: 回调结果
|
||||||
|
SDK-->>APP: 授权状态 / 特征项
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 全量组件清单(Master Inventory)
|
||||||
|
|
||||||
|
下表覆盖 **本工作区已实现或已占位** 的组件;规划项标注为 **(规划)**。
|
||||||
|
|
||||||
|
| ID | 组件名称 | 类型 | 路径 / 坐标 / 产物 | 技术栈 | 主要职责 |
|
||||||
|
|----|----------|------|-------------------|--------|----------|
|
||||||
|
| C01 | **craftlabs-auth-parent** | Maven 聚合 | `java/pom.xml` | Java 17 | SDK 父工程、依赖与模块编排。 |
|
||||||
|
| C02 | **craftlabs-auth-core** | Library JAR | `java/craftlabs-auth-core` | Java | 配置模型、`NativeBridge`、与 Schema 对齐的校验入口。 |
|
||||||
|
| C03 | **craftlabs-auth-bitanswer** | Library JAR | `java/craftlabs-auth-bitanswer` | Java + JNI | `BitAnswerProvider`、`System.loadLibrary`、**仅客户端**。 |
|
||||||
|
| C04 | **craftlabs-auth-selfhosted** | Library JAR | `java/craftlabs-auth-selfhosted` | Java | 自托管授权占位 / 扩展点。 |
|
||||||
|
| C05 | **craftlabs-auth-tests** | 测试模块 | `java/craftlabs-auth-tests` | JUnit 等 | SDK 侧集成与回归测试。 |
|
||||||
|
| C06 | **libcraftlabs_auth_bitanswer** | Native | `native/` | CMake | 各平台动态库,与 C02/C03 **同版本发布**。 |
|
||||||
|
| C07 | **craftlabs-auth-config Schema** | 契约 | `schemas/craftlabs-auth-config.schema.json` | JSON Schema Draft 2020-12 | 客户端配置 **单一契约**;CI 与 SDK 测试可对齐校验。 |
|
||||||
|
| C08 | **examples** | 样例 | `examples/` | JSON / 脚本 | 配置样例与烟测引用。 |
|
||||||
|
| C09 | **documentation** | 文档 | `docs/` | Markdown | 产品、对接、工程、架构。 |
|
||||||
|
| C10 | **engineering-meta** | 元数据 | `engineering/` | JSON / MD | `workspace-manifest.json`、planned 占位、README 索引。 |
|
||||||
|
| C11 | **craftlabs-platform-services-parent** | Maven 聚合 | `services/pom.xml` | Java 17 + Spring Boot 3.4.x | 平台后端父 POM;**与 C01 分构建线**。 |
|
||||||
|
| C12 | **delivery-platform-api** | 可执行 Fat JAR | `services/delivery-platform-api` | Spring Web/Security/Actuator、MyBatis-Plus、PostgreSQL 驱动 | 平台 REST、领域逻辑(迭代扩展)、**8080**。 |
|
||||||
|
| C13 | **license-webhook-ingress** | 可执行 Fat JAR | `services/license-webhook-ingress` | Spring Web/Actuator | Callback 入站、Token、**8081**。 |
|
||||||
|
| C14 | **PostgreSQL** | 基础设施 | 部署环境 / `services/docker-compose.yml` | 15.x | 平台主数据存储。 |
|
||||||
|
| C15 | **delivery-platform-ui** | SPA | `web/delivery-platform-ui` | Vue 3、Vite、Pinia、vue-router、axios、Element Plus | 管理端界面;dev 代理 `/api` → API。 |
|
||||||
|
| C16 | **CI:SDK Java** | 流水线 | `.github/workflows/ci-java.yml` | GitHub Actions | `java/` 构建与测试(JDK 版本以 workflow 为准)。 |
|
||||||
|
| C17 | **CI:Platform + Web** | 流水线 | `.github/workflows/ci-platform.yml` | GitHub Actions | `services/` Maven verify + `web/` npm build。 |
|
||||||
|
| C18 | **API Gateway** | 接入(规划) | 未在本仓 | 待定 | 统一 TLS、路由、限流(可选)。 |
|
||||||
|
| C19 | **Message Queue** | 中间件(规划) | 未在本仓 | 待定 | Webhook 与 API 解耦、削峰(I5+ 常见形态)。 |
|
||||||
|
| C20 | **企业 IdP** | 身份(规划) | 未在本仓 | OIDC/SAML 等 | 与 M11 企业登录集成(若需)。 |
|
||||||
|
| C21 | **密钥与凭据** | 运维配置 | 部署机 | **环境变量**、**chmod 600 配置文件** | 注入 Webhook Token、DB 密码;**禁止** 进 Git;**不强制** 云 KMS。 |
|
||||||
|
| C22 | **WAF / 边缘限流** | 安全(可选) | 边界 | 云 WAF 或 Nginx 限流模块 | 低并发阶段可省;对外暴露或合同要求时再上。 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. 技术栈总览
|
||||||
|
|
||||||
|
| 分层 | 选型 | 备注 |
|
||||||
|
|------|------|------|
|
||||||
|
| 管理端 | Vue 3、Vite、Pinia、vue-router、axios、Element Plus | 构建产物静态托管或 CDN。 |
|
||||||
|
| 平台 API | Spring Boot 3.4.x(路线图 4.0.x)、Spring Security、Actuator | Fat JAR 部署。 |
|
||||||
|
| Webhook | 同上(独立进程) | 与 API **进程隔离**,失败域分离。 |
|
||||||
|
| ORM | MyBatis-Plus(`mybatis-plus-spring-boot3-starter`) | Boot 4 时切换 `boot4-starter`。 |
|
||||||
|
| 数据库 | PostgreSQL 15 | 测试可用 H2 PG 模式或 Testcontainers。 |
|
||||||
|
| 客户端 SDK | Java 17、多模块薄 JAR | 不打 Spring Boot repackage。 |
|
||||||
|
| 客户端 Native | CMake、比特官方 API | 与 Java **同版本 tag**。 |
|
||||||
|
| 契约 | JSON Schema 2020-12 | `schemas/` 与 `examples/`。 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. 安全架构(授权与平台)
|
||||||
|
|
||||||
|
授权与许可数据直接影响 **商业履约与合规**,安全需求高于一般内部后台。本节给出 **信任边界、威胁面、控制措施与演进要求**,供架构评审与渗透测试对齐。
|
||||||
|
|
||||||
|
### 9.0 阶段与重心(广州创飞 · 小团队)
|
||||||
|
|
||||||
|
| 维度 | 当前立场 |
|
||||||
|
|------|----------|
|
||||||
|
| **部署** | **单 Fat JAR / 进程** 部署在云主机或内网服务器;PostgreSQL 可与应用 **同机或同 VPC 内另一台**;**不强制** 容器编排与云 KMS。 |
|
||||||
|
| **密钥** | Webhook Token、数据库密码等放在 **宿主机环境变量** 或 **权限受限的配置文件**(`chmod 600`、仅运行用户可读),**绝不** 提交 Git。 |
|
||||||
|
| **优先级** | 先保证 **功能与需求闭环**;平台侧 **基础 TLS + 认证 + 日志脱敏** 即可满足多数早期场景。 |
|
||||||
|
| **安全重心** | **对外发布的 SDK(JAR + Native)**:防范 **供应链篡改与假冒制品**;**破解/逆向** 的硬防线主要在 **比特安索 Native 与许可密码学**,Java 层需 **诚实评估**(见 **§9.8**)。 |
|
||||||
|
|
||||||
|
### 9.1 安全目标(CIA + 授权特有)
|
||||||
|
|
||||||
|
| 目标 | 含义 | 在本体系的落点 |
|
||||||
|
|------|------|----------------|
|
||||||
|
| **机密性** | 许可状态、SN、合同金额、Callback 原文、密钥不外泄 | TLS、日志脱敏、DB 访问控制、前端不持久化敏感令牌到不安全存储 |
|
||||||
|
| **完整性** | Callback 与台账不被篡改;配置与制品未被替换 | Webhook 验签/共享密钥、幂等键、审计日志(M10)、依赖与镜像签名(供应链) |
|
||||||
|
| **可用性** | 授权链路与管理端在约定 SLA 内可服务 | Webhook 快速 2xx + 异步处理;低并发阶段 **单机 + 健康检查** 即可,**不必** 预设 K8s 弹性扩容 |
|
||||||
|
| **不可否认性**(按需) | 关键操作可追溯 | 审计表、WORM 或日志外送(合规场景) |
|
||||||
|
|
||||||
|
### 9.2 信任边界
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart TB
|
||||||
|
subgraph Untrusted["不可信或半可信"]
|
||||||
|
Internet((Internet))
|
||||||
|
BA[比特云]
|
||||||
|
CustomerNet[客户现场网络]
|
||||||
|
Dev[集成方研发环境]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph TrustHigh["高信任 — 创飞数据面"]
|
||||||
|
PG[(PostgreSQL)]
|
||||||
|
Secrets[(凭据:宿主机环境变量<br/>或受限配置文件)]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph TrustMed["中信任 — 应用进程"]
|
||||||
|
API[delivery-platform-api]
|
||||||
|
WH[license-webhook-ingress]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph TrustUser["用户终端"]
|
||||||
|
Browser[管理员浏览器]
|
||||||
|
ClientApp[客户业务进程 + SDK]
|
||||||
|
end
|
||||||
|
|
||||||
|
Internet -->|TLS 仅| WH
|
||||||
|
Internet -->|TLS 仅| API
|
||||||
|
Internet --> Browser
|
||||||
|
BA -->|HTTPS Callback| WH
|
||||||
|
CustomerNet --> ClientApp
|
||||||
|
Dev -.->|Maven/文档| ClientApp
|
||||||
|
|
||||||
|
Browser -->|HTTPS + 会话/JWT| API
|
||||||
|
ClientApp -->|TLS 至比特/自托管| BA
|
||||||
|
|
||||||
|
WH --> PG
|
||||||
|
API --> PG
|
||||||
|
API --> Secrets
|
||||||
|
WH --> Secrets
|
||||||
|
```
|
||||||
|
|
||||||
|
**边界规则**:
|
||||||
|
|
||||||
|
- **Webhook URL** 暴露在公网或 DMZ:视为 **持续被扫描**;必须 **TLS + 强认证 + 限流 + 最小响应体**。
|
||||||
|
- **管理端 API**:仅对 **已认证主体** 开放业务写能力;**禁止** 将平台账号用于客户现场 SDK。
|
||||||
|
- **SDK 配置 JSON**:含 `bitanswer.url`、`applicationData`、特征映射等,属 **客户环境机密**;**不得** 回传至创飞平台作为默认遥测(除非单独签署数据处理协议并做脱敏设计)。
|
||||||
|
|
||||||
|
### 9.3 威胁面与缓解(摘要)
|
||||||
|
|
||||||
|
| 威胁 | 示例 | 架构层缓解 |
|
||||||
|
|------|------|------------|
|
||||||
|
| **伪造 Callback** | 攻击者 POST 恶意 payload | **共享密钥**(如 `x-bitanswer-token` 与 `CRAFTLABS_WEBHOOK_EXPECTED_TOKEN` 常量时间比较);**比特官方 IP/证书钉扎**(若文档支持);**payload 大小与 JSON 深度限制** |
|
||||||
|
| **重放** | 重复投递同一事件污染台账 | **幂等键**(`Idempotency-Key` / 事件 ID)+ 唯一约束;重复返回成功语义 |
|
||||||
|
| **撞库 / 会话窃取** | 管理端弱口令、Token 泄露 | **密码策略 / MFA(规划)**、**HttpOnly + Secure Cookie** 或 **短期 JWT + 刷新**、**CORS 严格白名单** |
|
||||||
|
| **越权** | 用户访问他人合同或 SN | **RBAC(M11)**、**行级授权**(组织/客户维度)、API **资源级鉴权** |
|
||||||
|
| **注入** | SQL / 反序列化 | **参数化查询**(MyBatis `#{}`)、**禁用不安全的反序列化 gadget**、DTO 白名单 |
|
||||||
|
| **SSRF**(若存在出站拉取) | 服务端替用户请求内网 | **出站 URL 白名单**、**禁用 file://**、独立网络分区 |
|
||||||
|
| **供应链 / 假冒 SDK** | 第三方镜像站替换 JAR、客户误下「绿色版」 | **官方唯一分发渠道** + **GPG/SHA-256 校验**(§9.8);Maven 依赖 **锁定版本**;CI **依赖漏洞扫描**(力所能及) |
|
||||||
|
| **日志泄露** | Callback 原文、Token 打日志 | **结构化日志字段脱敏**、**禁止 debug 打印完整 Authorization** |
|
||||||
|
|
||||||
|
### 9.4 分组件控制要求
|
||||||
|
|
||||||
|
#### 9.4.1 `license-webhook-ingress`
|
||||||
|
|
||||||
|
| 控制项 | 要求 |
|
||||||
|
|--------|------|
|
||||||
|
| 传输 | **仅 HTTPS**;HTTP 仅允许本机健康检查(若必须)且不得承载业务 |
|
||||||
|
| 认证 | 配置 `CRAFTLABS_WEBHOOK_EXPECTED_TOKEN` 时 **强制校验** `x-bitanswer-token`(或比特约定头);生产 **禁止** 长期关闭校验 |
|
||||||
|
| 处理 | **先校验后解析大对象**;限制 body 大小;超时快速失败仍返回约定错误码(避免资源耗尽) |
|
||||||
|
| 观测 | 日志记录 **事件 ID、幂等键、结果码**;**不** 记录完整密钥与完整 payload(或脱敏后) |
|
||||||
|
| 网络 | 云厂商 **安全组** 仅开放 443/业务端口;数据库 **不对公网**;单机阶段 **本机回环或内网 IP** 连库即可 |
|
||||||
|
|
||||||
|
#### 9.4.2 `delivery-platform-api`
|
||||||
|
|
||||||
|
| 控制项 | 要求 |
|
||||||
|
|--------|------|
|
||||||
|
| 认证 | I1 演示账号 **不得用于生产**;生产至少 **强口令**;团队具备条件时再上 **MFA / 企业 IdP(C20)** |
|
||||||
|
| 授权 | 每个 mutating 接口 **显式角色/权限**;与 M11 角色矩阵一致 |
|
||||||
|
| 数据 | DB 账号 **最小权限**(读写分离账号可选);连接串来自 **环境变量或宿主机配置**,禁止提交仓库 |
|
||||||
|
| Actuator | **生产关闭或仅本机访问** `env`、`heapdump`;`health` 可给 **本机监控脚本或 Nginx 探活** |
|
||||||
|
| CORS | **显式允许源**,禁用 `*` 携带凭证 |
|
||||||
|
|
||||||
|
#### 9.4.3 `delivery-platform-ui`
|
||||||
|
|
||||||
|
| 控制项 | 要求 |
|
||||||
|
|--------|------|
|
||||||
|
| 存储 | **不** 将 refresh token 写入 `localStorage`(优先 HttpOnly Cookie 或安全内存策略) |
|
||||||
|
| CSP | 生产启用 **Content-Security-Policy**,限制内联脚本与未知域 |
|
||||||
|
| 依赖 | `npm audit` 纳入 CI;锁定 lockfile |
|
||||||
|
|
||||||
|
#### 9.4.4 客户端 SDK 与 Native
|
||||||
|
|
||||||
|
| 控制项 | 要求 |
|
||||||
|
|--------|------|
|
||||||
|
| 配置 | `craftlabs-auth-config` 由 **宿主应用** 安全加载(文件权限、必要时磁盘加密);**禁止** 将含敏感字段的示例提交到客户代码库模板 |
|
||||||
|
| 完整性 | Native **与 JAR 同版本** 分发;**正式发布** 必须带 **校验与/或签名**(见 **§9.8**) |
|
||||||
|
| 运行时 | **不信任** 客户进程内存防护;敏感字符串尽量 **缩短生命周期**(比特 SDK 能力范围内) |
|
||||||
|
|
||||||
|
#### 9.4.5 PostgreSQL
|
||||||
|
|
||||||
|
| 控制项 | 要求 |
|
||||||
|
|--------|------|
|
||||||
|
| 网络 | DB **不暴露公网**;仅应用子网可达 |
|
||||||
|
| 加密 | 云盘默认加密或宿主机全盘加密即可;敏感列 **应用层加密** 为可选增强 |
|
||||||
|
| 审计 | 高危 DML/DDL 与 **管理员账号** 变更记入 M10 或 DB 审计扩展 |
|
||||||
|
|
||||||
|
### 9.5 与时序对齐的安全步骤(示意)
|
||||||
|
|
||||||
|
**Callback(补充安全步骤)**:
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
participant BA as 比特安索云
|
||||||
|
participant WH as Webhook
|
||||||
|
participant Store as PG/MQ
|
||||||
|
|
||||||
|
BA->>WH: TLS + POST
|
||||||
|
WH->>WH: TLS 终结校验(mTLS 仅在高合规客户场景考虑)
|
||||||
|
WH->>WH: 校验 Token(常量时间)
|
||||||
|
WH->>WH: 限制 body 大小 + 解析
|
||||||
|
WH->>WH: 幂等键去重
|
||||||
|
WH->>Store: 写入 inbox(脱敏日志)
|
||||||
|
WH-->>BA: 2xx
|
||||||
|
```
|
||||||
|
|
||||||
|
**管理端(补充安全步骤)**:
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
participant U as 用户
|
||||||
|
participant UI as SPA
|
||||||
|
participant API as Platform API
|
||||||
|
|
||||||
|
U->>UI: 登录
|
||||||
|
UI->>API: HTTPS + CSRF 防护(Cookie 模式)
|
||||||
|
API->>API: 认证 + 颁发会话/JWT
|
||||||
|
UI->>API: 业务请求 + 凭证
|
||||||
|
API->>API: 鉴权(角色 + 资源)
|
||||||
|
API-->>UI: 最小必要 DTO
|
||||||
|
```
|
||||||
|
|
||||||
|
### 9.6 合规与审计衔接
|
||||||
|
|
||||||
|
- **M10 审计与合规**:登录、合同变更、SN 绑定/解绑、Callback 处置、权限变更等 **必须** 产生不可抵赖审计记录(谁、何时、对象 ID、前后摘要)。
|
||||||
|
- **数据出境**:若 Callback 或日志含个人信息,需对齐 **个人信息保护法** 与客户 DPA。
|
||||||
|
- **渗透测试**:有对外暴露或合同要求时,对 **Webhook** 与 **管理 API** 做专项测试(认证绕过、IDOR、批量枚举);小团队可 **采购外包渗透** 或分阶段补做。
|
||||||
|
|
||||||
|
### 9.7 架构评审安全清单(节选)
|
||||||
|
|
||||||
|
- [ ] Webhook 生产环境 **已启用** 共享密钥或更强机制(签名/HMAC 以比特文档为准)。
|
||||||
|
- [ ] 管理 API **无** 默认弱口令路径;Actuator 敏感端点 **已收敛**。
|
||||||
|
- [ ] 平台与 Webhook **未引入** `craftlabs-auth-bitanswer`。
|
||||||
|
- [ ] 日志与异常栈 **无** 明文数据库密码、Token、完整 Callback。
|
||||||
|
- [ ] OpenAPI 中 **错误码** 不泄露内部堆栈或枚举用户存在性(登录接口需谨慎)。
|
||||||
|
- [ ] `examples/config/*.json` **无** 真实生产 URL/密钥(仅占位)。
|
||||||
|
- [ ] **SDK 发布**:每个对外版本具备 **SHA-256 清单**;**强烈建议** GPG 签名与 **唯一官方下载说明**。
|
||||||
|
- [ ] **SDK 发布**:`java` 与 `native` **同 tag**,CHANGELOG 与交付说明 **可追溯**。
|
||||||
|
|
||||||
|
### 9.8 SDK 发布完整性(当前安全重心)
|
||||||
|
|
||||||
|
客户侧 **Java 字节码可被反编译**,单靠混淆无法对抗动机足够的攻击者。**真正承担许可与密码学校验的**,应是 **比特安索 Native 与云端许可体系**。创飞 SDK 的安全目标应定位为:
|
||||||
|
|
||||||
|
1. **防篡改、防假冒**:客户拿到的是 **官方构建** 的 JAR/Native,未被中间人替换。
|
||||||
|
2. **防版本错配**:JAR 与 `.so/.dylib/.dll` **同一次发布**(同 Git tag / 同一 Release 页),避免「新 JAR + 旧库」导致异常或绕过路径。
|
||||||
|
3. **可追溯**:出问题能对照 **确切版本与校验和** 复现。
|
||||||
|
|
||||||
|
| 措施 | 说明 | 小团队可执行性 |
|
||||||
|
|------|------|------------------|
|
||||||
|
| **官方唯一渠道** | 仅通过 **公司控制的 Maven 私服 / GitHub Release / 合同交付包** 分发;**禁止** 引导客户从匿名网盘、论坛下载「整合包」。 | 必做 |
|
||||||
|
| **SHA-256 清单** | 每个 Release 发布 `SHA256SUMS`;仓库内 **`scripts/sdk-release-checksums.sh`** 已落地,说明见 **`java/RELEASING.md`**;客户用 `sha256sum -c` / `shasum -a 256 -c` 校验。 | 必做 |
|
||||||
|
| **GPG 签名** | Maven:**`mvn -f java/pom.xml -Prelease-sign verify`**(`maven-gpg-plugin`,各 JAR 旁生成 `.asc`);清单文件:`SIGN=1` 跑上述脚本生成 `SHA256SUMS.asc`。公布 **公钥指纹** 固定页。 | 强烈建议 |
|
||||||
|
| **Git Tag = 发布单元** | `java/` 与 `native/` **同一 tag** 打发布;CHANGELOG 写清 **兼容的比特 SDK 版本**。 | 必做 |
|
||||||
|
| **依赖锁定** | 客户工程使用 **明确版本号** 或 BOM;避免「浮动版本」被投毒依赖间接替换。 | 建议 |
|
||||||
|
| **ProGuard 等混淆** | 仅增加逆向成本,**不能**替代 Native 侧安全;若上,只作 **非关键路径** 的辅助手段。 | 可选 |
|
||||||
|
| **法务条款** | 许可协议中约定 **禁止反编译、再分发**(民事约束,与技术防护互补)。 | 建议 |
|
||||||
|
|
||||||
|
**客户集成检查清单(可印在交付文档)**:
|
||||||
|
|
||||||
|
- [ ] JAR/Native 均来自 **创飞官方渠道**,并已核对 **SHA-256**(或验签)。
|
||||||
|
- [ ] 各工件版本号 **一致**,与发布说明中的 **比特依赖版本** 一致。
|
||||||
|
- [ ] 未混入第三方「破解补丁」或修改版 `libcraftlabs_auth_bitanswer`。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. 部署与网络(简图)
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart TB
|
||||||
|
subgraph Public["公网 / 比特侧可达"]
|
||||||
|
BA[比特 Callback 出口 IP]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph Perimeter["边界(常为一台 Nginx/Caddy)"]
|
||||||
|
LB[TLS 终结 + 反代]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph AppTier["应用层(单机或双机 java -jar)"]
|
||||||
|
WH_P[Webhook Fat JAR]
|
||||||
|
API_P[Platform API Fat JAR]
|
||||||
|
UI_S[静态 UI 或同机 Nginx 托管]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph DataTier["数据层"]
|
||||||
|
PG_P[PostgreSQL 15<br/>单实例或云托管]
|
||||||
|
end
|
||||||
|
|
||||||
|
BA --> LB --> WH_P
|
||||||
|
Users((管理员浏览器)) --> LB --> UI_S
|
||||||
|
Users --> LB --> API_P
|
||||||
|
WH_P --> PG_P
|
||||||
|
API_P --> PG_P
|
||||||
|
```
|
||||||
|
|
||||||
|
**阶段注记(创飞当前)**:**不依赖 K8s**;扩容以 **加机器 + 多实例反代** 即可。**安全注记**:云安全组 **收紧**;PG **仅内网**;WAF/mTLS **按合同与预算** 选装,非 MVP 必选。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. 修订记录
|
||||||
|
|
||||||
|
| 日期 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| 2026-04-06 | 初版:系统上下文、容器、领域与 SDK 组件图、时序、全量组件表、技术栈与部署简图。 |
|
||||||
|
| 2026-04-06 | **§9 安全架构**:信任边界、威胁与缓解、分组件控制、安全时序、合规与评审清单;原则表增加 **安全默认**;部署节增加安全注记。 |
|
||||||
|
| 2026-04-06 | **对齐小团队**:§1 **阶段目标**、§3 单机部署说明、§9.0、凭据改为宿主机配置、部署图改为 `java -jar` + Nginx;新增 **§9.8 SDK 发布完整性**(防篡改/假冒重心)。 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**下一步**:领域表结构、OpenAPI 与事件 Schema 建议单独维护为 **版本化契约仓库** 或 `contracts/` 子模块,并在 [PARALLEL_ITERATION_INDEX](./PARALLEL_ITERATION_INDEX.md) 的同步点更新版本号。
|
||||||
@@ -0,0 +1,276 @@
|
|||||||
|
# 工作区工程划分与仓库边界 — 架构说明
|
||||||
|
|
||||||
|
> **角色**:资深架构师对工作区现有资产与 **创飞客户商务与交付管理平台** 相关文档的对照走查结论。
|
||||||
|
> **范围**:本 Git 工作区根目录 `craftlabs-authorization-sdk` 的 **物理目录、构建边界、发布物、与外部工程的契约**;**不**在本提交中迁移 `java/`、`native/` 路径(避免破坏既有 CI 与本地脚本)。
|
||||||
|
> **关联**:[**系统架构全景(图 + 全量组件)**](./SYSTEM_ARCHITECTURE.md) · [功能模块](chuangfei-platform-product-modules.md) · [BPM 与排期](chuangfei-platform-bpm-and-roadmap.md) · [平台与比特对接](chuangfei-bitanswer-integration-platform.md) · [三轨并行执行索引](./PARALLEL_ITERATION_INDEX.md) · [根目录 `engineering/` 清单](../../engineering/workspace-manifest.json)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 走查结论(摘要)
|
||||||
|
|
||||||
|
|
||||||
|
| 维度 | 现状 | 架构建议 |
|
||||||
|
| --------- | ------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------- |
|
||||||
|
| **工作区身份** | 以 **客户端授权 SDK**(Java + Native)为核心,配套 **Schema、示例配置、产品/平台文档** | 保持本仓库 **「授权 SDK + 契约资产」** 为一等公民;**商业交付平台**(Spring Boot 4.0. + Vue)**不宜**与 JNI/native 强耦在同一 Maven 树内混建 |
|
||||||
|
| **构建** | `java/` Maven 多模块;`native/` CMake 动态库 | CI **双轨**:Java 与 Native 可并行;平台后端/前端 **独立仓库或独立根级目录** 时再挂接新 workflow |
|
||||||
|
| **文档** | `docs/` 已覆盖比特规则、Callback、创飞平台 BPM、模块、权限 | 文档继续集中在 `docs/`;**工程边界**以本文 + `engineering/` 为 **单一事实来源(SSOT)** |
|
||||||
|
| **缺口** | 根目录无 README;平台 Webhook、管理端未在代码层体现 | 用 `**engineering/planned/`** 占位说明 **推荐独立仓库名与技术栈**;实现阶段按 manifest 开仓 |
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 工作区逻辑分层(C4-L1)
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart TB
|
||||||
|
subgraph WS["本工作区 craftlabs-authorization-sdk"]
|
||||||
|
S[SDK Java API]
|
||||||
|
N[Native craftlabs_auth_bitanswer]
|
||||||
|
SCH[schemas/ craftlabs-auth-config]
|
||||||
|
EX[examples/]
|
||||||
|
DOC[docs/]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph EXT["规划中的独立工程(见 engineering/planned)"]
|
||||||
|
WH[license-webhook-ingress]
|
||||||
|
API[delivery-platform-api]
|
||||||
|
UI[delivery-platform-ui]
|
||||||
|
end
|
||||||
|
|
||||||
|
BA[比特安索云/控制台]
|
||||||
|
CLI[现场客户端产品]
|
||||||
|
|
||||||
|
N --> S
|
||||||
|
S --> CLI
|
||||||
|
SCH --> CLI
|
||||||
|
WH -->|POST Callback| BA
|
||||||
|
API -->|读写字段| WH
|
||||||
|
UI --> API
|
||||||
|
DOC -.->|约束| SCH
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
**原则**:**运行时依赖**从平台 API → Webhook → 比特 为一条链;**客户端**仅依赖 **Maven 发布的 SDK 工件** + **JSON 配置**(校验用 Schema),不依赖平台 UI。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 物理目录与职责(当前实现)
|
||||||
|
|
||||||
|
|
||||||
|
| 路径 | 工程类型 | 职责 | 构建命令(摘要) |
|
||||||
|
| ------------------ | -------------- | --------------------------------------------------------------------------------------------------------------- | ---------------------------- |
|
||||||
|
| `**java/`** | Maven `pom` 聚合 | `craftlabs-auth-core`(配置模型/校验/桥接)、`craftlabs-auth-bitanswer`、`craftlabs-auth-selfhosted`、`craftlabs-auth-tests` | `mvn -f java/pom.xml verify` |
|
||||||
|
| `**native/**` | CMake | `libcraftlabs_auth_bitanswer`、JNI、自研 HTTP 占位 | 见 `native/build` 或项目 README |
|
||||||
|
| `**schemas/**` | JSON Schema | `craftlabs-auth-config` 契约,与 `AuthConfigs` 对齐 | 无编译;CI 可 `ajv`/脚本校验 |
|
||||||
|
| `**examples/**` | 样例 | `config/*.json`、`java`/`python` 烟测 | 随测试引用 |
|
||||||
|
| `**docs/**` | 文档 | 产品、比特对接、工程架构 | 无构建 |
|
||||||
|
| `**engineering/**` | **工程元数据** | manifest、**planned** 占位、边界说明 | 无构建 |
|
||||||
|
|
||||||
|
|
||||||
|
**禁止**:在未做迁移方案前,将 **Spring Boot 应用** 塞进 `java/` 下与 `craftlabs-auth-`* 并列 —— 会导致发布周期、依赖(Boot vs 库消费者)冲突。
|
||||||
|
**平台端正确做法**:在 **独立仓库** 内 **多模块开发**,由 **唯一 `bootstrap` 模块** 执行 `spring-boot-maven-plugin` **repackage**,对外只发布 **一枚 Fat JAR**(见 **§5.3**)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Maven 模块边界(SDK 内部)
|
||||||
|
|
||||||
|
```
|
||||||
|
craftlabs-auth-parent
|
||||||
|
├── craftlabs-auth-core # 无第三方授权运行时;配置、NativeBridge 接口
|
||||||
|
├── craftlabs-auth-bitanswer # System.loadLibrary + BitAnswerProvider
|
||||||
|
├── craftlabs-auth-selfhosted
|
||||||
|
└── craftlabs-auth-tests # 集成/配置测试
|
||||||
|
```
|
||||||
|
|
||||||
|
- **对外发布坐标**:以 `cn.craftlabs:craftlabs-auth-*` 为准;版本与 **changelog** 在发布流水线维护。
|
||||||
|
- **平台后端**若需读取同一配置模型:优先 **复制 DTO + 共用 Schema**,或将来抽取 `**craftlabs-auth-config-model` 极小 jar**(另案);**禁止**在平台可执行 JAR 中引入 `craftlabs-auth-bitanswer`(会拉起 JNI/native 期望,属边界错误)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 客户端 SDK 与平台端:深入评估与防混淆
|
||||||
|
|
||||||
|
### 5.1 二者不可混为一谈
|
||||||
|
|
||||||
|
|
||||||
|
| 维度 | **客户端 SDK**(本仓库 `java/` + `native/`) | **平台端后端**(`delivery-platform-api` 等独立工程) |
|
||||||
|
| --------------- | ----------------------------------------- | ---------------------------------------------------------------- |
|
||||||
|
| **运行位置** | 客户现场业务进程 / 边缘 AI 设备 | 创飞机房 / 云(VPC) |
|
||||||
|
| **核心职责** | `Bit_Login`、特征项、本地/云授权运行时 | 合同、交付、SN 台账、Callback 处置、RBAC、报表 |
|
||||||
|
| **典型依赖** | BitAnswer 官方 native + `BitAnswerProvider` | Spring Boot、DB、MQ;**无** BitAnswer 客户端 native |
|
||||||
|
| **交付形态** | 多枚 **薄 JAR** + 各 OS **动态库**(与 JAR 同版本) | **单枚可执行 Fat JAR**(或容器内同等物)**每个可部署服务一枚** |
|
||||||
|
| **Spring Boot** | **不使用**(库代码,避免与宿主应用 Boot 版本冲突) | **使用**(4.0. 线),仅 **启动模块** 打 Fat JAR |
|
||||||
|
| **配置** | `craftlabs-auth-config` JSON + Schema | 平台自有 `application.yml` + **PostgreSQL 15** 数据源;ORM 为 **MyBatis-Plus**;JSON Schema 仅用于 **校验导出的客户端配置**(若实现) |
|
||||||
|
|
||||||
|
|
||||||
|
**硬规则**:平台进程 **不得** `System.loadLibrary("craftlabs_auth_bitanswer")`;不得在服务端 classpath 引入 `**craftlabs-auth-bitanswer`** 模块(除非未来出现「纯 Java、无 native」的只读适配且经架构评审——当前 **不存在**)。
|
||||||
|
|
||||||
|
### 5.2 命名与仓库层面的识别
|
||||||
|
|
||||||
|
- **SDK 坐标**:`cn.craftlabs:craftlabs-auth-*`(artifact 前缀固定,文档与 BOM 中一眼可辨)。
|
||||||
|
- **平台坐标**:建议 `cn.craftlabs.platform:*` 或 `cn.craftlabs.delivery:*`(**禁止**复用 `craftlabs-auth-*` 作为应用模块 artifactId)。
|
||||||
|
- **CI 流水线**:SDK 与 **platform-*** 使用 **不同 job**,产物上传 **不同仓库路径**(Maven `.../sdk/` vs `.../platform/`)。
|
||||||
|
|
||||||
|
### 5.3 单 JAR 部署 vs 分模块开发(平台 / Webhook)
|
||||||
|
|
||||||
|
**诉求**:运维只下发 **一个 `app.jar`**(或镜像内 `java -jar app.jar`);研发仍按 **领域拆分模块**,边界清晰、编译快、测试可隔离。
|
||||||
|
|
||||||
|
**推荐 Maven 形态**(每个 **独立可部署后端** 各自一套父 POM,勿与 SDK 父 POM 合并):
|
||||||
|
|
||||||
|
```
|
||||||
|
delivery-platform/ # 或 license-webhook 同理
|
||||||
|
├── pom.xml # packaging=pom,dependencyManagement
|
||||||
|
├── platform-domain/ # 实体、领域服务接口、无 Spring Web
|
||||||
|
├── platform-application-service/ # 用例、事务边界、调用 domain
|
||||||
|
├── platform-adapters-web/ # REST Controller、DTO 映射
|
||||||
|
├── platform-adapters-persistence/ # MyBatis-Plus Mapper / XML(PostgreSQL 15)
|
||||||
|
└── platform-bootstrap/ # 唯一含 main + spring-boot-maven-plugin
|
||||||
|
└── src/main/java/.../Application.java
|
||||||
|
```
|
||||||
|
|
||||||
|
- **仅 `platform-bootstrap`** 配置 `spring-boot-maven-plugin` 的 `**repackage**`(或 `executable`),依赖其余模块为 **普通 jar**,打进 **Fat JAR / Executable JAR**。
|
||||||
|
- `mvn -pl platform-bootstrap -am package` → 产出 `platform-bootstrap/target/*.jar` **一枚**,即部署单元。
|
||||||
|
- **开发体验**:IDE 打开 **聚合工程**,多模块跳转、分模块单测;**禁止**在多个模块上重复声明 `repackage`(否则出现多枚「可执行」JAR 或依赖错乱)。
|
||||||
|
|
||||||
|
**可选变体**:
|
||||||
|
|
||||||
|
- **分层 jar(Spring Boot 2.3+)**:`layertools` 优化镜像层 —— 仍对应 **单次构建的一条启动入口**。
|
||||||
|
- **Webhook 与 Core API 分开发布**:两个 Maven 工程各产 **一枚** Fat JAR(`webhook-bootstrap.jar`、`platform-bootstrap.jar`),比强行塞进一个进程更符合 **扩容与故障隔离**;若强制单进程,可用 **同一 `bootstrap` 模块多 profile**(一般 **不推荐**,耦合过高)。
|
||||||
|
|
||||||
|
### 5.4 SDK 侧构建(对照:不是 Fat JAR)
|
||||||
|
|
||||||
|
- `craftlabs-auth-core` / `bitanswer` / `selfhosted` 均为 **library jar**,**不打** `spring-boot-maven-plugin`。
|
||||||
|
- 客户端应用(客户自有 Spring Boot 或其它)自行打包 Fat JAR,**声明依赖** `craftlabs-auth-bitanswer` + 携带 **匹配版本 native**。
|
||||||
|
- **平台 Fat JAR** 与 **客户应用 Fat JAR** 是 **两条完全不同的发布线**,不得在平台 POM 里 `dependency` 整条 SDK 父工程。
|
||||||
|
|
||||||
|
### 5.5 评审检查(单 JAR + 分模块)
|
||||||
|
|
||||||
|
- 全仓库是否 **只有一个** 模块声明 `spring-boot-maven-plugin` 的 `repackage`(针对该可部署应用)?
|
||||||
|
- `mvn dependency:tree -pl platform-bootstrap` 中是否 **无** `craftlabs-auth-bitanswer`?
|
||||||
|
- 部署文档是否写清:启动类 FQCN、`java -jar` 与 JVM 参数、**与 SDK 版本无耦合**?
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Native 边界
|
||||||
|
|
||||||
|
- **产出物**:各平台 `.so` / `.dylib` / `.dll` + 头文件。
|
||||||
|
- **与 Java 契约**:`NativeBridge` 与 `jni_bridge.cpp` 必须 **同版本发布**。
|
||||||
|
- **平台服务**:**不**链接本 native 库;仅 **现场 AI/业务进程** 链接。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 规划工程划分(推荐独立 Git 仓库)
|
||||||
|
|
||||||
|
下列目录在 `**engineering/planned/`** 中以 README 描述,**默认新开仓库**(名称可调整):
|
||||||
|
|
||||||
|
|
||||||
|
| 规划 ID | 建议仓库名 | 技术栈(与产品文档对齐) | 与本工作区关系 |
|
||||||
|
| ------------------------- | -------------------------------- | ---------------------------------------- | ------------------------------------------------- |
|
||||||
|
| `license-webhook-ingress` | `craftlabs-license-webhook`(示例) | Spring Boot **4.0.**,仅 Ingress + 验签 + MQ | **消费**比特 Callback;**不**依赖 SDK native |
|
||||||
|
| `delivery-platform-api` | `craftlabs-delivery-platform` | Spring Boot **4.0.**,领域 API、**PostgreSQL 15**、**MyBatis-Plus** | 依赖 **schemas** 可通过 git submodule 或发布 **schema 包** |
|
||||||
|
| `delivery-platform-ui` | `craftlabs-delivery-platform-ui` | Vue 3 + Vite | 调用 `delivery-platform-api` |
|
||||||
|
|
||||||
|
|
||||||
|
**可选 Monorepo**:若公司策略要求单仓,可采用 **Bazel / Gradle composite / 多根 `pom` 父子不包含 SDK** 的目录隔离,例如:
|
||||||
|
|
||||||
|
```
|
||||||
|
mega-repo/
|
||||||
|
craftlabs-authorization-sdk/ # 本仓库 subtree 或 submodule
|
||||||
|
delivery-platform/
|
||||||
|
license-webhook/
|
||||||
|
```
|
||||||
|
|
||||||
|
迁移成本高于 **三仓 + 契约版本化**;**默认推荐三仓**。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. CI/CD 划分建议
|
||||||
|
|
||||||
|
|
||||||
|
| 组件 | 触发路径 | 产物 |
|
||||||
|
| ----------- | ------------ | ------------------------------------- |
|
||||||
|
| SDK Java | `java/`** | Maven deploy(私服/Central) |
|
||||||
|
| SDK Native | `native/**` | 各 OS 工件 + 与 Java **同版本 tag** |
|
||||||
|
| Schema | `schemas/`** | 可与 SDK 同步发 **minor**;breaking 走 major |
|
||||||
|
| 平台与 Webhook | **独立仓库**(或本仓 `services/`) | **Fat JAR** + 部署说明(`systemd`/脚本/`docker run`);**不强制** 容器与 K8s,规模化后再考虑镜像与编排 |
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. 数据与配置契约
|
||||||
|
|
||||||
|
- **客户端**:`craftlabs-auth-config.schema.json` + 环境 `bitanswer.url` 等(见 `examples/config`)。
|
||||||
|
- **平台**:存储 SN、合同、Callback 等 —— **领域模型独立于 SDK**;仅 **映射表** 与比特特征 ID 与 M6 文档一致。
|
||||||
|
- **Webhook Payload**:以比特官方规则文档为准;平台侧 **反序列化 DTO** 在 `license-webhook` 仓维护。
|
||||||
|
|
||||||
|
### 9.1 平台数据栈(架构锁定)
|
||||||
|
|
||||||
|
| 项 | 约定 |
|
||||||
|
|----|------|
|
||||||
|
| **数据库** | **PostgreSQL 15**(生产、验收、联调基准版本;镜像 `postgres:15`) |
|
||||||
|
| **ORM** | **MyBatis-Plus**(Spring Boot 3 使用 `mybatis-plus-spring-boot3-starter`;升级 Spring Boot 4 时改用官方 `mybatis-plus-spring-boot4-starter` 并回归) |
|
||||||
|
| **迁移** | Flyway 或 Liquibase(后续迭代引入;与表结构变更门禁对齐) |
|
||||||
|
| **单测** | 可不启真实 PG:H2 `MODE=PostgreSQL` 或 Testcontainers `postgres:15`,以团队门禁为准 |
|
||||||
|
|
||||||
|
实现参考:`services/delivery-platform-api` 已接入 MyBatis-Plus + JDBC;本地数据库见 `services/docker-compose.yml`。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. 安全边界
|
||||||
|
|
||||||
|
> **展开**:威胁面、分组件控制、信任边界图与评审清单见 [SYSTEM_ARCHITECTURE.md §9](./SYSTEM_ARCHITECTURE.md)。
|
||||||
|
|
||||||
|
### 10.1 密钥与凭据
|
||||||
|
|
||||||
|
- **Webhook**:`x-bitanswer-token` / 与比特约定的共享密钥 **仅** 在 **部署机环境变量** 或 **权限受限的本地配置文件**(如 `chmod 600`);**禁止** 写入 Git、镜像层与公开 CI 日志。
|
||||||
|
- **平台数据库**:JDBC URL、用户名、密码 **仅** 注入运行时;`docker-compose` 与示例 **不得** 作为生产凭据模板。
|
||||||
|
- **管理端**:演示账号(如 `dev`/`admin`)**禁止** 用于生产;生产至少 **强口令**。团队尚小时 **不强制** 上云 KMS / Vault;规模与合规要求上来后再演进。
|
||||||
|
- **SDK 发布防篡改**:以 [SYSTEM_ARCHITECTURE §9.8](./SYSTEM_ARCHITECTURE.md) 为准(官方渠道、SHA-256、建议 GPG、同 tag 发布 JAR+Native)。
|
||||||
|
|
||||||
|
### 10.2 运行时与数据面
|
||||||
|
|
||||||
|
- **传输**:对外 **仅 HTTPS**(Webhook、管理 API、浏览器);比特 Callback 终端 **不接明文 HTTP**(除本机健康检查等受控例外)。
|
||||||
|
- **SDK**:配置 JSON **不落** 平台库表作为默认设计;**禁止** 在 `examples/` 与文档示例中放置真实 `bitanswer.url` 密钥或生产 SN。
|
||||||
|
- **服务端 classpath**:**禁止** `craftlabs-auth-bitanswer` 与 `System.loadLibrary` —— 避免将 **客户端攻击面** 引入机房。
|
||||||
|
- **日志与排障**:Callback 正文、Authorization、数据库 URL **默认脱敏或不打**;生产日志外送需合规评估。
|
||||||
|
|
||||||
|
### 10.3 授权与访问控制
|
||||||
|
|
||||||
|
- 管理 API:**认证(谁)** 与 **鉴权(能否操作资源)** 分层;与 **M11** 角色矩阵一致,防范 **越权(IDOR)**。
|
||||||
|
- Webhook:**认证先于重逻辑**;**幂等** 防重放污染;**Body 大小限制** 防 DoS。
|
||||||
|
|
||||||
|
### 10.4 供应链与构建
|
||||||
|
|
||||||
|
- Maven/npm **锁定版本**;力所能及时做 **依赖漏洞扫描**(SCA)。
|
||||||
|
- **SDK 对外发布**:每个版本附带 **SHA-256 清单**,**强烈建议** **GPG 签名**(`maven-gpg-plugin` 或 GitHub signed releases);详见 [SYSTEM_ARCHITECTURE §9.8](./SYSTEM_ARCHITECTURE.md)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. 演进检查清单(架构评审用)
|
||||||
|
|
||||||
|
- 新代码是否落在正确 **仓库/目录**(SDK vs 平台 vs Webhook)?
|
||||||
|
- Schema 变更是否 **双向** 更新 Java `AuthConfigs` + 文档?
|
||||||
|
- Native 与 Java **是否同 tag 发布**?
|
||||||
|
- 平台是否 **误引** `craftlabs-auth-bitanswer` 到服务端 classpath?
|
||||||
|
- 平台构建是否 **仅 bootstrap 模块** 产出 Fat JAR,且无重复 `repackage`?
|
||||||
|
- **安全**:Webhook 生产是否 **启用** 共享密钥/签名?Actuator 敏感端点是否 **已收敛**?新接口是否 **默认拒否** 并完成 **鉴权**?日志是否 **无** 明文 Token/Callback?
|
||||||
|
- **安全**:[SYSTEM_ARCHITECTURE §9.7](./SYSTEM_ARCHITECTURE.md) 评审项是否逐项可追溯?
|
||||||
|
- **SDK 发布**:[SYSTEM_ARCHITECTURE §9.8](./SYSTEM_ARCHITECTURE.md) 是否落实 **官方渠道 + 校验和(+ 签名)+ JAR/Native 同 tag**?
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 12. 修订记录
|
||||||
|
|
||||||
|
|
||||||
|
| 日期 | 说明 |
|
||||||
|
| ---------- | ------------------------------------------------------------------------------------------- |
|
||||||
|
| 2026-04-06 | 初版:工作区走查、目录边界、规划仓库与 CI 划分;落地 `engineering/` manifest 与 `planned/`* 占位。 |
|
||||||
|
| 2026-04-06 | 增补:**§5 客户端 SDK vs 平台端** 防混淆表;**多模块开发 + 单 Fat JAR** 的 Maven 模块划分与 `repackage` 约束;CI 坐标命名建议。 |
|
||||||
|
| 2026-04-06 | 关联:[PARALLEL_ITERATION_INDEX.md](./PARALLEL_ITERATION_INDEX.md) 与 `tracks/01–03` 三轨并行执行包。 |
|
||||||
|
| 2026-04-06 | **§9.1** 锁定平台 **PostgreSQL 15** + **MyBatis-Plus**;`services` 工程对齐依赖与本地 `docker-compose`。 |
|
||||||
|
| 2026-04-06 | 新增汇总文档 [SYSTEM_ARCHITECTURE.md](./SYSTEM_ARCHITECTURE.md)(系统架构图 + 全量组件清单)。 |
|
||||||
|
| 2026-04-06 | **§10** 安全边界扩写(密钥、传输、SDK、鉴权、供应链);**§11** 增补安全评审项;与安全专章 [SYSTEM_ARCHITECTURE §9](./SYSTEM_ARCHITECTURE.md) 交叉引用。 |
|
||||||
|
| 2026-04-06 | **小团队假设**:凭据以宿主机环境变量/受限文件为主,**不强制** K8s/KMS;**§10.1/10.4** 与 **§11** 对齐 **SDK §9.8** 发布完整性。 |
|
||||||
|
| 2026-04-06 | **§8** CI/CD 表:平台产物以 **Fat JAR + 部署说明** 为主,**不强制** K8s。 |
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,397 @@
|
|||||||
|
# 迭代 I3 设计说明 — M2 合同与行项 P0、M10-F01 审计、Webhook 事件 DTO v0.1
|
||||||
|
|
||||||
|
> **迭代定位**:与 [并行迭代索引](../PARALLEL_ITERATION_INDEX.md) 中 **I3** 一致 — 平台后端 **M2 合同 + 行项**、**M10-F01 关键字段变更日志**;Webhook 侧 **事件 DTO v0.1** 与平台主键/枚举对齐,便于 I5 Callback 关联。
|
||||||
|
> **分支**:`develop`(本仓库为契约与 SDK 工作区;平台运行时实现可在 `delivery-platform` 仓库,路径风格须与本仓 OpenAPI 一致)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 上下文与引用文档
|
||||||
|
|
||||||
|
|
||||||
|
| 文档 | 路径 | 本迭代取用要点 |
|
||||||
|
| ------------------ | -------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------- |
|
||||||
|
| 并行迭代索引 | [docs/engineering/PARALLEL_ITERATION_INDEX.md](../PARALLEL_ITERATION_INDEX.md) | I3 范围:M2 + M10-F01;**I3 末**同步「合同状态枚举与非法迁移码」「M10-F01 字段」。 |
|
||||||
|
| 轨道 A(后端 + Webhook) | [docs/engineering/tracks/01-backend-platform-webhook.md](../tracks/01-backend-platform-webhook.md) | I3:合同+行、状态机;审计;Webhook **事件 DTO 规范化**、与平台枚举对齐;契约:**合同/行 id** 供 Callback 关联。 |
|
||||||
|
| 产品模块与功能点 | [docs/chuangfei-platform-product-modules.md](../../chuangfei-platform-product-modules.md) | **M2-F01~F04(P0)**:登记编辑、状态机、标的摘要、行项;**M10-F01(P0)**:关键字段变更日志(旧值/新值/人/时间)。 |
|
||||||
|
|
||||||
|
|
||||||
|
**现有 API 路径约定**(须保持一致):`services/delivery-platform-api` 中 Controller 使用 `**@RequestMapping("/api/v1/...")`**,例如 `/api/v1/customers`、`/api/v1/projects`。合同相关接口统一前缀 `**/api/v1/contracts`**。
|
||||||
|
|
||||||
|
**OpenAPI 单一事实来源**:契约快照路径为仓库根下 `[contracts/openapi/delivery-platform-api.json](../../../contracts/openapi/delivery-platform-api.json)`;新增/变更接口须更新该快照,并与 `OpenApiContractSnapshotTest` 对齐。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 领域模型:合同(Contract)
|
||||||
|
|
||||||
|
### 2.1 实体职责
|
||||||
|
|
||||||
|
合同是「卖什么」的权威来源之一,关联 M1 客户与项目;行项为履约/授权上游锚点(与后续 M3/M4 衔接)。
|
||||||
|
|
||||||
|
### 2.2 字段(P0)
|
||||||
|
|
||||||
|
|
||||||
|
| 字段 | 类型 | 约束 | 说明 |
|
||||||
|
| ------------------------- | --------------- | ------- | ------------------------------------------------------------------------ |
|
||||||
|
| `id` | int64 | 主键 | 与现有 `customers`/`projects` 一致用雪花或序列,API 中为 string 或 number 以 OpenAPI 为准。 |
|
||||||
|
| `contractNumber` | string | 必填,业务唯一 | 合同编号;唯一索引。 |
|
||||||
|
| `customerId` | int64 | 必填,FK | 指向客户。 |
|
||||||
|
| `projectId` | int64 | 必填,FK | 指向项目;须属于同一 `customerId`(服务端校验)。 |
|
||||||
|
| `signedAt` | date (ISO-8601) | 必填 | 签订日。 |
|
||||||
|
| `effectiveAt` | date | 必填 | 生效日。 |
|
||||||
|
| `endAt` | date | 可选 | 结束/到期日;与「终止」语义可并存(终止优先于到期展示逻辑由前端/报表约定)。 |
|
||||||
|
| `status` | enum | 必填 | 见 §3;**API 与 JSON 仅使用英文枚举名**。 |
|
||||||
|
| `createdAt` / `updatedAt` | timestamp | 系统字段 | 审计展示可与 M10-F01 互补。 |
|
||||||
|
|
||||||
|
|
||||||
|
### 2.3 状态枚举(对齐 BPM 语义)
|
||||||
|
|
||||||
|
|
||||||
|
| BPM 语义(展示/字典) | API 枚举值 `ContractStatus` |
|
||||||
|
| ------------- | ------------------------ |
|
||||||
|
| 草稿 | `DRAFT` |
|
||||||
|
| 待生效 | `PENDING_EFFECTIVE` |
|
||||||
|
| 生效 | `EFFECTIVE` |
|
||||||
|
| 变更中 | `CHANGING` |
|
||||||
|
| 终止 | `TERMINATED` |
|
||||||
|
|
||||||
|
|
||||||
|
字典表可增加 `dict_type = CONTRACT_STATUS`,`code` 与上表一致,`label_zh` 为左列中文。
|
||||||
|
|
||||||
|
### 2.4 编辑规则(与状态机联动)
|
||||||
|
|
||||||
|
- **仅 `DRAFT` 状态**允许对**合同头字段**(§2.2 中除 `id`、`status`、`createdAt`、`updatedAt` 外,是否含 `contractNumber` 由产品确认;P0 建议 **DRAFT 下编号可改**,一旦进入 `PENDING_EFFECTIVE` 则编号只读)及**行项**做增删改。
|
||||||
|
- 非 `DRAFT` 下对合同头或行项的 `PUT`/`POST`/`DELETE`:**409 Conflict**,错误码见 §4.3。
|
||||||
|
- 状态变更**仅允许**通过 `**POST .../transition`**(§5.4),禁止在普通 `PUT` 请求体中直接改 `status`(若传入与当前相同可忽略或 400,建议 **忽略** 幂等)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 领域模型:合同行(Contract Line)
|
||||||
|
|
||||||
|
### 3.1 字段(P0)
|
||||||
|
|
||||||
|
|
||||||
|
| 字段 | 类型 | 约束 | 说明 |
|
||||||
|
| ------------- | --------------- | ----- | ------------------------------------------------ |
|
||||||
|
| `id` | int64 | 主键 | |
|
||||||
|
| `contractId` | int64 | 必填,FK | |
|
||||||
|
| `lineNo` | int32 | 必填 | 行号,从 1 递增;**同一合同内唯一**;用于展示与排序。 |
|
||||||
|
| `skuCode` | string | 条件 | `**skuCode` 与 `productName` 至少填一个**(另一个可为 null)。 |
|
||||||
|
| `productName` | string | 条件 | 无 SKU 时的产品/包名称。 |
|
||||||
|
| `quantity` | decimal 或 int64 | 必填 | 数量;小数与否由产品线约定,P0 建议 `number` JSON。 |
|
||||||
|
| `unitPrice` | decimal | 可选 | 单价;敏感字段可按角色脱敏(见 §9)。 |
|
||||||
|
| `termNotes` | string | 可选 | 期限/席位/交付与授权口径等说明(与 M2-F03 摘要同源数据)。 |
|
||||||
|
|
||||||
|
|
||||||
|
### 3.2 排序
|
||||||
|
|
||||||
|
列表接口默认按 `lineNo` 升序;`lineNo` 可由客户端指定,冲突时 **409**,错误码建议 `CONTRACT_LINE_NO_CONFLICT`。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 状态机
|
||||||
|
|
||||||
|
### 4.1 允许迁移(P0)
|
||||||
|
|
||||||
|
以下「当前状态 → 目标状态」为 **允许**;未列出的单向迁移视为 **禁止**。
|
||||||
|
|
||||||
|
|
||||||
|
| 当前状态 | 允许的目标状态 |
|
||||||
|
| ------------------- | --------------------------------------- |
|
||||||
|
| `DRAFT` | `PENDING_EFFECTIVE`、`TERMINATED` |
|
||||||
|
| `PENDING_EFFECTIVE` | `EFFECTIVE`、`DRAFT`(撤回至草稿)、`TERMINATED` |
|
||||||
|
| `EFFECTIVE` | `CHANGING`、`TERMINATED` |
|
||||||
|
| `CHANGING` | `EFFECTIVE`、`TERMINATED` |
|
||||||
|
| `TERMINATED` | (终态,不允许任何迁出) |
|
||||||
|
|
||||||
|
|
||||||
|
**说明**:
|
||||||
|
|
||||||
|
- `PENDING_EFFECTIVE` → `DRAFT`:用于「待生效前撤回修改」;撤回后恢复 §2.4 头行可编辑。
|
||||||
|
- `CHANGING` → `EFFECTIVE`:变更完成、回到生效。
|
||||||
|
- P0 **不**实现变更子版本表(M2-F07 为 P1)。`**CHANGING` 下合同头与行项与普通非草稿状态相同:禁止 `PUT`/`POST`/`DELETE` 行与头**,仅允许 `POST .../transition`(例如转至 `EFFECTIVE` 或 `TERMINATED`);若业务需要「变更中改行」,留待后续迭代专用变更 API。
|
||||||
|
|
||||||
|
### 4.2 非法迁移响应
|
||||||
|
|
||||||
|
- HTTP `**409 Conflict`**。
|
||||||
|
- 响应体(与平台统一错误结构对齐;若尚无 RFC 7807,则用 JSON)示例:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": "CONTRACT_ILLEGAL_STATUS_TRANSITION",
|
||||||
|
"message": "不允许从当前状态转换到目标状态",
|
||||||
|
"currentStatus": "EFFECTIVE",
|
||||||
|
"targetStatus": "DRAFT"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `code` 固定 `**CONTRACT_ILLEGAL_STATUS_TRANSITION**`,便于前端与 SDK 分支处理。
|
||||||
|
- 日志与 M10:建议记一条 `audit_log`,`action = STATUS_TRANSITION_DENIED`(见 §6)。
|
||||||
|
|
||||||
|
### 4.3 非草稿编辑冲突
|
||||||
|
|
||||||
|
在不允许编辑的状态下修改头或行:
|
||||||
|
|
||||||
|
- HTTP `**409 Conflict**`
|
||||||
|
- `code`: `**CONTRACT_NOT_EDITABLE_IN_STATUS**`(或细分头/行码,P0 可合并为一个码)。
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": "CONTRACT_NOT_EDITABLE_IN_STATUS",
|
||||||
|
"message": "仅草稿状态可编辑合同及行项",
|
||||||
|
"status": "EFFECTIVE"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. REST API 设计
|
||||||
|
|
||||||
|
**前缀**:`/api/v1`(与 [CustomerController](../../../services/delivery-platform-api/src/main/java/cn/craftlabs/platform/api/customer/CustomerController.java) 一致)。
|
||||||
|
|
||||||
|
**认证**:与现有 `/api/v1/customers` 相同(JWT 等);未登录 **401**。
|
||||||
|
|
||||||
|
**分页**:列表接口与 customers 对齐:`page`(从 0 默认)、`size`(默认 20,最大 200)。
|
||||||
|
|
||||||
|
### 5.1 合同
|
||||||
|
|
||||||
|
|
||||||
|
| 方法 | 路径 | 说明 |
|
||||||
|
| ------ | -------------------------------- | ------------------------------------------------------------------------------------------------------- |
|
||||||
|
| `GET` | `/api/v1/contracts` | 分页列表;查询参数:`page`、`size`、`customerId?`、`projectId?`、`status?`、`keyword?`(匹配 `contractNumber`)。 |
|
||||||
|
| `POST` | `/api/v1/contracts` | 创建;**初始状态必须为 `DRAFT`**(请求体可不传 `status`,服务端默认 `DRAFT`;若传其它值 **400**)。 |
|
||||||
|
| `GET` | `/api/v1/contracts/{contractId}` | 详情;含行项可内嵌或仅用行接口拉取(P0 建议详情 **内嵌 `lines`** 减少往返)。 |
|
||||||
|
| `PUT` | `/api/v1/contracts/{contractId}` | 更新头字段;**仅 `DRAFT`**;**不得**携带 `status` 变更(忽略或 400,建议 **400** `CONTRACT_STATUS_USE_TRANSITION_ENDPOINT`)。 |
|
||||||
|
|
||||||
|
|
||||||
|
**创建请求示例**:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"contractNumber": "HT-2026-0001",
|
||||||
|
"customerId": 1001,
|
||||||
|
"projectId": 2002,
|
||||||
|
"signedAt": "2026-04-01",
|
||||||
|
"effectiveAt": "2026-04-15",
|
||||||
|
"endAt": "2027-04-14"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**详情响应示例(内嵌行)**:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": 3001,
|
||||||
|
"contractNumber": "HT-2026-0001",
|
||||||
|
"customerId": 1001,
|
||||||
|
"projectId": 2002,
|
||||||
|
"signedAt": "2026-04-01",
|
||||||
|
"effectiveAt": "2026-04-15",
|
||||||
|
"endAt": "2027-04-14",
|
||||||
|
"status": "DRAFT",
|
||||||
|
"lines": [
|
||||||
|
{
|
||||||
|
"id": 4001,
|
||||||
|
"lineNo": 1,
|
||||||
|
"skuCode": "SKU-PRO-01",
|
||||||
|
"productName": null,
|
||||||
|
"quantity": 10,
|
||||||
|
"unitPrice": 1999.00,
|
||||||
|
"termNotes": "1 年订阅,100 席位"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"createdAt": "2026-04-06T10:00:00Z",
|
||||||
|
"updatedAt": "2026-04-06T10:00:00Z"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.2 合同行 CRUD
|
||||||
|
|
||||||
|
|
||||||
|
| 方法 | 路径 | 说明 |
|
||||||
|
| -------- | ----------------------------------------------- | ---------------------------- |
|
||||||
|
| `GET` | `/api/v1/contracts/{contractId}/lines` | 行列表(可选,若详情已内嵌则可与产品取舍)。 |
|
||||||
|
| `POST` | `/api/v1/contracts/{contractId}/lines` | 新增行;**仅合同为 `DRAFT`**。 |
|
||||||
|
| `GET` | `/api/v1/contracts/{contractId}/lines/{lineId}` | 单行。 |
|
||||||
|
| `PUT` | `/api/v1/contracts/{contractId}/lines/{lineId}` | 更新;**仅 `DRAFT`**。 |
|
||||||
|
| `DELETE` | `/api/v1/contracts/{contractId}/lines/{lineId}` | 删除;**仅 `DRAFT`**;成功 **204**。 |
|
||||||
|
|
||||||
|
|
||||||
|
**创建/更新行请求体示例**:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"lineNo": 2,
|
||||||
|
"skuCode": null,
|
||||||
|
"productName": "企业旗舰包",
|
||||||
|
"quantity": 5,
|
||||||
|
"unitPrice": null,
|
||||||
|
"termNotes": "按项目交付"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.3 状态迁移
|
||||||
|
|
||||||
|
|
||||||
|
| 方法 | 路径 | 说明 |
|
||||||
|
| ------ | ------------------------------------------- | ----------------------------------------------------------------------------- |
|
||||||
|
| `POST` | `/api/v1/contracts/{contractId}/transition` | 请求体指定目标状态;校验 §4.1;成功返回更新后的合同 DTO(或 204 + Location,P0 建议 **200 + 完整合同 JSON**)。 |
|
||||||
|
|
||||||
|
|
||||||
|
**请求体**:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"targetStatus": "PENDING_EFFECTIVE"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**成功**:`200`,body 为合同资源(含 `lines` 若详情惯例如此)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. M10-F01:`audit_log` 表与读 API
|
||||||
|
|
||||||
|
### 6.1 表设计(建议名 `audit_log`)
|
||||||
|
|
||||||
|
|
||||||
|
| 列名 | 类型 | 说明 |
|
||||||
|
| --------------- | ------------ | --------------------------------------------------------------------------------------------------------------- |
|
||||||
|
| `id` | bigserial PK | |
|
||||||
|
| `entity_type` | varchar(32) | 枚举:`**CUSTOMER`**、`**CONTRACT`**、`**CONTRACT_LINE**`(与产品 M10-F01「客户、合同、SN…」对齐;I3 落地三类,SN 后续迭代加 `LICENSE_SN` 等)。 |
|
||||||
|
| `entity_id` | int8 | 业务主键,与 `entity_type` 对应实体 id。 |
|
||||||
|
| `action` | varchar(64) | 如:`CREATE`、`UPDATE`、`DELETE`、`STATUS_TRANSITION`、`STATUS_TRANSITION_DENIED`。 |
|
||||||
|
| `field_name` | varchar(128) | 可选;字段级变更时记录英文名,如 `effectiveAt`、`status`。 |
|
||||||
|
| `old_value` | text | JSON 字符串;无则 NULL。 |
|
||||||
|
| `new_value` | text | JSON 字符串;无则 NULL。 |
|
||||||
|
| `actor_user_id` | int8 | 操作人用户 id。 |
|
||||||
|
| `created_at` | timestamptz | 不可改。 |
|
||||||
|
|
||||||
|
|
||||||
|
**索引**:
|
||||||
|
|
||||||
|
- `(entity_type, entity_id, created_at DESC)` — 按对象拉时间线。
|
||||||
|
- 可选:`(actor_user_id, created_at DESC)` — 按人审计(M10-F02 预备)。
|
||||||
|
|
||||||
|
**写入时机(I3 最小集)**:
|
||||||
|
|
||||||
|
- 合同:创建、头字段更新(`DRAFT`)、每次成功 `transition`(`field_name=status`,old/new 为枚举字符串)。
|
||||||
|
- 合同行:创建、更新、删除(`entity_type=CONTRACT_LINE`,`entity_id=lineId`)。
|
||||||
|
- 拒绝的非法迁移:可选记 `STATUS_TRANSITION_DENIED`,`new_value` 可存目标状态 JSON。
|
||||||
|
|
||||||
|
### 6.2 读 API
|
||||||
|
|
||||||
|
|
||||||
|
| 方法 | 路径 | 说明 |
|
||||||
|
| ----- | --------------- | ------- |
|
||||||
|
| `GET` | `/api/v1/audit` | 分页审计列表。 |
|
||||||
|
|
||||||
|
|
||||||
|
**查询参数(组合过滤)**:
|
||||||
|
|
||||||
|
- `entityType` + `entityId`:精确到单一实体(如某合同、某行、某客户)。
|
||||||
|
- `**contractId`**:便捷范围 — 返回 `entity_type IN ('CONTRACT','CONTRACT_LINE')` 且(合同 id = contractId **或** 行所属 contractId = contractId)的记录;实现上可用 SQL `UNION` 或冗余 `contract_id` 列(**推荐冗余 `contract_id` 可空** 于 `audit_log` 以简化查询:合同与行写入时均填 `contract_id`,客户实体则只填 `entity_`*)。
|
||||||
|
|
||||||
|
**冗余列(可选但强烈推荐)**:
|
||||||
|
|
||||||
|
|
||||||
|
| 列名 | 说明 |
|
||||||
|
| ------------- | ---------------------------------------------------------------------- |
|
||||||
|
| `contract_id` | 可空;`CONTRACT` 时等于 `entity_id`;`CONTRACT_LINE` 时为父合同 id;`CUSTOMER` 可为空。 |
|
||||||
|
|
||||||
|
|
||||||
|
**响应项示例**:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": 900001,
|
||||||
|
"entityType": "CONTRACT",
|
||||||
|
"entityId": 3001,
|
||||||
|
"contractId": 3001,
|
||||||
|
"action": "STATUS_TRANSITION",
|
||||||
|
"fieldName": "status",
|
||||||
|
"oldValue": "\"DRAFT\"",
|
||||||
|
"newValue": "\"PENDING_EFFECTIVE\"",
|
||||||
|
"actorUserId": 42,
|
||||||
|
"createdAt": "2026-04-06T12:00:00Z"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
说明:`old_value`/`new_value` 存 **JSON text**(字符串加引号、对象则序列化),解析由前端或工具完成。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Webhook 事件 DTO v0.1(Callback 关联预备)
|
||||||
|
|
||||||
|
**目标**:与平台合同/行主键对齐,便于 I5 Inbox 关联与幂等;**不**在本迭代要求完整比特 payload,仅 **最小信封**。
|
||||||
|
|
||||||
|
### 7.1 建议信封字段(v0.1)
|
||||||
|
|
||||||
|
|
||||||
|
| 字段 | 类型 | 必填 | 说明 |
|
||||||
|
| --------------- | ----------------- | --- | ----------------------------------------------- |
|
||||||
|
| `schemaVersion` | string | 是 | 固定 `**0.1`**(后续 `0.2`、`1.0` 递增)。 |
|
||||||
|
| `contractId` | int64 | 条件 | 与平台合同 id 一致;若事件仅到行级,仍建议带父 `contractId`。 |
|
||||||
|
| `lineIds` | array of int64 | 否 | 涉及的合同行 id 列表;无行级时可 `[]` 或省略。 |
|
||||||
|
| `eventType` | string | 否 | 预留,如 `contract.status.changed`(与 M5 字典统一可在 I5)。 |
|
||||||
|
| `occurredAt` | string (ISO-8601) | 否 | 事件发生时间。 |
|
||||||
|
|
||||||
|
|
||||||
|
**示例**:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"schemaVersion": "0.1",
|
||||||
|
"contractId": 3001,
|
||||||
|
"lineIds": [4001, 4002],
|
||||||
|
"eventType": "contract.status.changed",
|
||||||
|
"occurredAt": "2026-04-06T12:00:00Z"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**版本策略**:Webhook 与平台共用 `schemaVersion` 语义;**I3 归档** JSON Schema 或本仓库 `contracts/` 下示例文件(与 [轨道 A 文档 §3](../tracks/01-backend-platform-webhook.md) 的 `schemaVersion` / `X-Event-Schema-Version` 一致)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. 安全与 RBAC(对齐产品粗粒度矩阵)
|
||||||
|
|
||||||
|
产品文档 [§13.3](../../chuangfei-platform-product-modules.md) 模块矩阵中 **M2 合同**、**M10 审计** 与角色关系如下(**R** 查看,**W** 新建编辑,**X** 导出)。用户提到的 `**DEVELOPER`** 在产品预置角色中为 `**DEV_SUPPORT`(研发/集成支撑)** — 实施时角色码二选一须与 IAM 统一,下文按矩阵描述 `**DEV_SUPPORT`**,若代码命名为 `DEVELOPER` 则与之对齐。
|
||||||
|
|
||||||
|
### 8.1 `SYS_ADMIN`
|
||||||
|
|
||||||
|
- **M2 合同**:矩阵为 **M / RWDX** — 含模块管理语义;企业可配置是否开放业务写。建议:**生产默认** `SYS_ADMIN` **仅 M11 管理面**,业务合同与 `**SALES`/`ORDER_SUPPORT`** 同权或按企业策略单独开 `contract:`* 权限码。
|
||||||
|
- **M10 审计**:矩阵为 **M**(管理面);若启用审计检索,建议单独挂 `audit:search`。
|
||||||
|
|
||||||
|
### 8.2 `DEV_SUPPORT`(研发支撑 / 可与 `DEVELOPER` 对齐)
|
||||||
|
|
||||||
|
- **M2 合同**:**R**(只读)— 无合同创建/编辑/删除/导出,除非临时提权。
|
||||||
|
- **M10 审计**:**R** — 可检索审计(与矩阵「研发支撑」列一致)。
|
||||||
|
|
||||||
|
### 8.3 I3 API 权限码映射(建议)
|
||||||
|
|
||||||
|
|
||||||
|
| 接口 | 建议权限码 | 典型角色 |
|
||||||
|
| ---------------------- | -------------------------------------------------- | ----------------------------------------------------------------- |
|
||||||
|
| 合同与行 CRUD、`transition` | `contract:order:rw` | `SALES`、`ORDER_SUPPORT`;`SYS_ADMIN`(若开放业务写) |
|
||||||
|
| 合同只读列表/详情 | `contract:order:rw` 或细拆 `contract:order:read`(Mid) | 上表 + `DELIVERY`、`LICENSE_OPS`、`FINANCE_VIEW`、`EXEC_VIEW` 等只读列 |
|
||||||
|
| `GET /api/v1/audit` | `audit:search` | `COMPLIANCE`、`DEV_SUPPORT`、`FINANCE_VIEW`(导出另加 `audit:export` P1) |
|
||||||
|
|
||||||
|
|
||||||
|
**说明**:粗粒度阶段可将「读合同」与「写合同」合并为同一码,但 `**transition`** 必须与写权限同级或单独 `contract:order:transition`,避免只读角色误调;P0 可与 `contract:order:rw` 绑定。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. OpenAPI 与交付物
|
||||||
|
|
||||||
|
- **快照路径**:`[contracts/openapi/delivery-platform-api.json](../../../contracts/openapi/delivery-platform-api.json)`。
|
||||||
|
- I3 完成定义:**上述路径、枚举、主要 DTO** 均须出现在该 OpenAPI 文件中,并与 `services/delivery-platform-api` 运行时 `/v3/api-docs` 由 `**OpenApiContractSnapshotTest`** 校验一致。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. 修订记录
|
||||||
|
|
||||||
|
|
||||||
|
| 日期 | 说明 |
|
||||||
|
| ---------- | ---------------------------------------------------------- |
|
||||||
|
| 2026-04-06 | 初版:I3 合同/行项、状态机、REST、M10-F01、Webhook v0.1、RBAC、OpenAPI 引用。 |
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,299 @@
|
|||||||
|
# 迭代 I4 设计说明 — M3 交付批次与清单、M4 许可 SN 台账
|
||||||
|
|
||||||
|
> **迭代定位**:与 [并行迭代索引](../PARALLEL_ITERATION_INDEX.md) 中 **I4** 一致 — 平台后端 **M3 交付** + **M4 SN 录入/绑定/状态/手工回写**;前端 **交付页 + SN 页**;本仓库(SDK 工作区)以 **OpenAPI 契约与文档口径** 与 BP-10 对齐。
|
||||||
|
> **分支**:`develop`。
|
||||||
|
> **已有实现锚点**(勿从零重设计,仅对齐与补全):Flyway `V4__delivery_batch_and_license_sn.sql`;`cn.craftlabs.platform.api.domain.DeliveryBatchStatus` / `LicenseSnStatus`;`web/dto` 下 `Delivery*`、`LicenseSn*`;审计常量 `AuditEntityTypes`、`AuditActions` 已含 `DELIVERY_BATCH`、`LICENSE_SN` 及对应动作。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. I4 范围与 I3 / I5 边界
|
||||||
|
|
||||||
|
### 1.1 I4 **纳入**(本迭代 DoD)
|
||||||
|
|
||||||
|
| 域 | 说明 |
|
||||||
|
|----|------|
|
||||||
|
| **M3 P0** | 交付批次(项目/可选合同、批次号、计划日、备注);交付清单行(描述、数量、可选合同行关联);批次状态 **PENDING → DELIVERED / CANCELLED** 及完成时间等侧写。对应产品:[M3-F01~F05 P0](../../chuangfei-platform-product-modules.md#4-m3-交付管理)。 |
|
||||||
|
| **M4 P0** | SN 台账:全局唯一 `sn_code`;**`project_id` 与/或 `contract_line_id` 绑定路径**;生命周期状态子集;激活备注/手工回写字段。对应产品:[M4-F01~F05 P0](../../chuangfei-platform-product-modules.md#5-m4-授权与许可运营)。 |
|
||||||
|
| **M10-F01** | 交付批次、交付行、SN 的关键变更与状态迁移写入审计(与 I3 合同审计模式一致;实体类型见 §4)。 |
|
||||||
|
| **跨轨口径** | [I4 末同步点](../PARALLEL_ITERATION_INDEX.md#3-跨轨同步点必须对齐):**SN 绑定与「孤儿 SN」规则**文档化并三轨对齐;**交付门禁(M3-F07)与「孤儿 SN」强校验(M4-F02)** 在 **M11-F20 系统参数** 中预留为 **未来可配置项**(I4 可实现默认策略 + 配置占位,**不阻塞** I4 闭环)。 |
|
||||||
|
|
||||||
|
### 1.2 I3 **留给上游的契约**(I4 只消费,不重复建设)
|
||||||
|
|
||||||
|
- **合同 / 合同行**:`project_id`、`contract_id`、行项主键;合同状态机已在 I3 冻结。交付行上的 `contract_line_id` 必须解析到合法合同行及其所属项目。
|
||||||
|
- **客户 / 项目**:批次必填 `project_id`;可选 `contract_id` 须属于同一项目。
|
||||||
|
|
||||||
|
### 1.3 I5 **明确不纳入 I4**(避免范围蔓延)
|
||||||
|
|
||||||
|
- **M5 Callback Inbox**、**M6 集成配置** 的持久化与页面(I5 起)。
|
||||||
|
- Webhook **生产级** 投递、幂等落库与平台 Inbox 全链路 E2E。
|
||||||
|
- **设备(M7)**、**比特控制台摘要链接(M4-F06)** 等可后续挂接;I4 仅保证 SN 主数据与绑定字段可关联到合同行/项目。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 数据模型锚点(与迁移一致)
|
||||||
|
|
||||||
|
表与字段以 `services/delivery-platform-api/src/main/resources/db/migration/V4__delivery_batch_and_license_sn.sql` 为准:
|
||||||
|
|
||||||
|
- **`platform_delivery_batch`**:`project_id`(必填)、`contract_id`(可选)、`batch_code`(唯一)、`planned_delivery_date`、`status`(默认 `PENDING`)、`finished_at`、`remarks`。
|
||||||
|
- **`platform_delivery_line`**:归属 `batch_id`,`description`、`quantity`、`contract_line_id`(可选),`sort_order`。
|
||||||
|
- **`platform_license_sn`**:`sn_code`(全局唯一)、`project_id` / `contract_line_id`(均可空于 DB 层,**业务校验见 §4**)、`status`(默认 `REGISTERED`)、`activation_remark`。
|
||||||
|
|
||||||
|
### 2.1 状态枚举(API JSON 使用枚举名字符串)
|
||||||
|
|
||||||
|
**交付批次** `DeliveryBatchStatus`:
|
||||||
|
|
||||||
|
| 值 | 说明 |
|
||||||
|
|----|------|
|
||||||
|
| `PENDING` | 未交付(默认) |
|
||||||
|
| `DELIVERED` | 已交付 |
|
||||||
|
| `CANCELLED` | 已取消 |
|
||||||
|
|
||||||
|
**许可 SN** `LicenseSnStatus`(P0 子集,与代码枚举一致):
|
||||||
|
|
||||||
|
| 值 | 说明 |
|
||||||
|
|----|------|
|
||||||
|
| `REGISTERED` | 已登记 |
|
||||||
|
| `ISSUED` | 已发放 |
|
||||||
|
| `ACTIVATED` | 已激活 |
|
||||||
|
| `SUSPENDED` | 已冻结 |
|
||||||
|
| `REVOKED` | 已回收 |
|
||||||
|
|
||||||
|
非法状态迁移返回 **409**,错误码建议与合同类似:`DELIVERY_BATCH_ILLEGAL_STATUS`、`LICENSE_SN_ILLEGAL_STATUS`(具体以 OpenAPI 与实现为准)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. REST API 提案(前缀 `/api/v1`)
|
||||||
|
|
||||||
|
与现有 Controller 风格一致:**`@RequestMapping("/api/v1/...")`**。下列路径为 I4 计划形态;JSON 字段名与当前 DTO **camelCase** 对齐(`projectId`、`contractId`、`batchCode` 等)。
|
||||||
|
|
||||||
|
### 3.1 交付批次 `delivery-batches`
|
||||||
|
|
||||||
|
| 方法 | 路径 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| `GET` | `/api/v1/delivery-batches` | 分页列表;查询参数建议:`projectId`、`contractId`、`status`、`keyword`(批次号)、`page`、`size`。 |
|
||||||
|
| `POST` | `/api/v1/delivery-batches` | 创建批次(体见下);**不含**行时可后续用行接口追加。 |
|
||||||
|
| `GET` | `/api/v1/delivery-batches/{id}` | 详情;可通过 `?includeLines=true` 或默认嵌套返回 `lines`(与 `DeliveryBatchResponse` 一致)。 |
|
||||||
|
| `PUT` | `/api/v1/delivery-batches/{id}` | 更新计划交付日、备注等非状态字段(`DeliveryBatchUpdateRequest`)。 |
|
||||||
|
| `PATCH` | `/api/v1/delivery-batches/{id}/status` | **仅**变更状态:`PENDING` → `DELIVERED` 或 `CANCELLED`;服务端可在此写入 `finishedAt`(如 `DELIVERED`)。 |
|
||||||
|
|
||||||
|
**嵌套 — 交付行 `lines`**
|
||||||
|
|
||||||
|
| 方法 | 路径 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| `GET` | `/api/v1/delivery-batches/{batchId}/lines` | 清单列表。 |
|
||||||
|
| `POST` | `/api/v1/delivery-batches/{batchId}/lines` | 新增一行。 |
|
||||||
|
| `PUT` | `/api/v1/delivery-batches/{batchId}/lines/{lineId}` | 更新行。 |
|
||||||
|
| `DELETE` | `/api/v1/delivery-batches/{batchId}/lines/{lineId}` | 删除行。 |
|
||||||
|
|
||||||
|
**创建批次请求体示例**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"projectId": 1001,
|
||||||
|
"contractId": 2002,
|
||||||
|
"batchCode": "DLV-2026-0001",
|
||||||
|
"plannedDeliveryDate": "2026-04-15",
|
||||||
|
"remarks": "首批现场交付"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**更新批次请求体示例**(`PUT /api/v1/delivery-batches/{id}`)
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"plannedDeliveryDate": "2026-04-20",
|
||||||
|
"remarks": "延期一周"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**状态 PATCH 示例**(`PATCH /api/v1/delivery-batches/{id}/status`)
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": "DELIVERED"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**交付行写入示例**(`POST` / `PUT` body,`DeliveryLineRequest`)
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"sortOrder": 1,
|
||||||
|
"description": "AI 推理节点 × 生产环境",
|
||||||
|
"quantity": 2,
|
||||||
|
"contractLineId": 3003
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**详情响应片段**(`DeliveryBatchResponse`,含行)
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"projectId": 1001,
|
||||||
|
"contractId": 2002,
|
||||||
|
"batchCode": "DLV-2026-0001",
|
||||||
|
"plannedDeliveryDate": "2026-04-15",
|
||||||
|
"status": "PENDING",
|
||||||
|
"finishedAt": null,
|
||||||
|
"remarks": "首批现场交付",
|
||||||
|
"createdAt": "2026-04-06T08:00:00Z",
|
||||||
|
"updatedAt": "2026-04-06T08:00:00Z",
|
||||||
|
"lines": [
|
||||||
|
{
|
||||||
|
"id": 10,
|
||||||
|
"batchId": 1,
|
||||||
|
"sortOrder": 1,
|
||||||
|
"description": "AI 推理节点 × 生产环境",
|
||||||
|
"quantity": 2,
|
||||||
|
"contractLineId": 3003,
|
||||||
|
"createdAt": "2026-04-06T08:05:00Z",
|
||||||
|
"updatedAt": "2026-04-06T08:05:00Z"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.2 许可 SN `license-sns`
|
||||||
|
|
||||||
|
| 方法 | 路径 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| `GET` | `/api/v1/license-sns` | 分页列表;建议查询:`projectId`、`contractLineId`、`status`、`snCode`(精确或前缀按产品定)。 |
|
||||||
|
| `POST` | `/api/v1/license-sns` | 创建 SN(`LicenseSnCreateRequest`);须满足 §4 绑定规则。 |
|
||||||
|
| `GET` | `/api/v1/license-sns/{id}` | 详情。 |
|
||||||
|
| `PUT` | `/api/v1/license-sns/{id}` | **全量/部分更新绑定字段**:`projectId`、`contractLineId`、`activationRemark`(`LicenseSnUpdateRequest`);用于纠正绑定或手工回写备注。 |
|
||||||
|
| `PATCH` | `/api/v1/license-sns/{id}/status` | 变更 `LicenseSnStatus`(`LicenseSnStatusPatchRequest`);须校验合法迁移。 |
|
||||||
|
|
||||||
|
**创建 SN 请求体示例**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"snCode": "SN-CRAFT-8F3A-0001",
|
||||||
|
"projectId": 1001,
|
||||||
|
"contractLineId": 3003,
|
||||||
|
"activationRemark": null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**更新绑定 / 备注示例**(`PUT`)
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"projectId": 1001,
|
||||||
|
"contractLineId": 3003,
|
||||||
|
"activationRemark": "客户现场激活成功,凭证号 xxx"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**状态 PATCH 示例**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": "ACTIVATED"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**详情响应示例**(`LicenseSnResponse`)
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": 501,
|
||||||
|
"snCode": "SN-CRAFT-8F3A-0001",
|
||||||
|
"projectId": 1001,
|
||||||
|
"contractLineId": 3003,
|
||||||
|
"status": "ACTIVATED",
|
||||||
|
"activationRemark": "客户现场激活成功,凭证号 xxx",
|
||||||
|
"createdAt": "2026-04-06T09:00:00Z",
|
||||||
|
"updatedAt": "2026-04-06T10:00:00Z"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 校验规则与审计
|
||||||
|
|
||||||
|
### 4.1 交付批次
|
||||||
|
|
||||||
|
- **`projectId`**:必填;项目须存在。
|
||||||
|
- **`contractId`**:可选;若提供,合同须存在且 **`contract.project_id == batch.project_id`**。
|
||||||
|
- **`batchCode`**:全平台唯一(与表 `uq_platform_delivery_batch_code` 一致);冲突 **409**。
|
||||||
|
- **状态**:仅允许自 `PENDING` 转至 `DELIVERED` 或 `CANCELLED`;`DELIVERED`/`CANCELLED` 视为终态,**禁止**再次变更(除非产品后续另定「重开」流程,不在 I4 P0)。
|
||||||
|
|
||||||
|
### 4.2 交付行
|
||||||
|
|
||||||
|
- **`contractLineId`**:可选;若提供,合同行须存在,且其所属合同的 **`project_id` 须与父批次 `project_id` 一致**(从而与批次可选 `contract_id` 兼容:若批次已指定合同,可额外校验行所属合同与批次合同一致,建议 **强一致**:行上合同行必须属于 `batch.contract_id` 当 `contract_id` 非空时)。
|
||||||
|
- **`quantity`**:> 0(与 `DeliveryLineRequest` 中 `@DecimalMin` 一致)。
|
||||||
|
|
||||||
|
### 4.3 许可 SN
|
||||||
|
|
||||||
|
- **`snCode`**:必填;**全局唯一**;冲突 **409**。
|
||||||
|
- **绑定路径**:**至少具备 `projectId` 或 `contractLineId` 之一**(可同时具备)。
|
||||||
|
- 若仅提供 **`contractLineId`**:服务端**派生** `project_id` = 该合同行所属合同的 `project_id`,并持久化(便于列表按项目过滤)。
|
||||||
|
- 若同时提供两者:须校验 **`contractLine` 派生出的 `project_id` 与请求 `projectId` 一致**,否则 **400**。
|
||||||
|
- **孤儿 SN(与 I4 末同步对齐)**:
|
||||||
|
- **产品理想态(M4-F02)**:禁止无项目且无合同行路径的「裸 SN」。
|
||||||
|
- **M11-F20(P1)**:「孤儿 SN」**强校验**开关、**交付门禁**(M3-F07,例如仅已交付范围可发放/绑定)作为**系统参数**在架构上预留;I4 建议 **默认策略**:创建/更新时 **拒绝** 零绑定(与 P0 一致);若需「先录入后绑定」,可通过 **配置** 降级为 **警告 + 允许保存**(实现可放在应用服务层读取参数表,**表结构可 Mid 再做**,I4 先在文档与 OpenAPI `description` 中固定语义)。
|
||||||
|
- **状态迁移**:按 §2.1 枚举定义允许边(细表可在实现中维护;**禁止**随意跳转到任意状态)。
|
||||||
|
|
||||||
|
### 4.4 审计(M10-F01)
|
||||||
|
|
||||||
|
沿用 I3 模式:**实体类型 + 动作 + 旧值/新值摘要 + 操作者 + 时间**。常量已存在于:
|
||||||
|
|
||||||
|
- `AuditEntityTypes.DELIVERY_BATCH`、`AuditEntityTypes.LICENSE_SN`
|
||||||
|
- `AuditActions`:`DELIVERY_BATCH_CREATED` / `UPDATED` / `STATUS_CHANGED`;`DELIVERY_LINE_ADDED` / `UPDATED` / `DELETED`;`LICENSE_SN_CREATED` / `UPDATED` / `STATUS_CHANGED`
|
||||||
|
|
||||||
|
若持久化审计行需扩展子类型或 payload 结构,**保持与合同审计同一表结构**,仅扩展 `entity_type` / `action` 枚举值;**无需**为 I4 另起实体类型常量,除非后续拆分「交付行」为独立可检索实体(当前可用 `DELIVERY_LINE_*` 动作挂 `entity_id` = line id,`batch_id` 放上下文 JSON)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 前端路由(Vue 3,布局子路由)
|
||||||
|
|
||||||
|
与 [轨道 B — I4](../tracks/02-frontend-platform-ui.md) 一致,路径挂在 **AppLayout** 之下(懒加载、`meta.title` / 权限码略)。
|
||||||
|
|
||||||
|
| 路由 | 页面职责 |
|
||||||
|
|------|----------|
|
||||||
|
| `/deliveries` | 交付批次列表、筛选、跳转新建/详情。 |
|
||||||
|
| `/deliveries/new` | 新建批次(项目/可选合同、批次号、计划日、备注);可内嵌或分步添加行。 |
|
||||||
|
| `/deliveries/:id` | 批次详情:行清单 CRUD;**状态按钮** 调用 `PATCH .../status`(PENDING → DELIVERED/CANCELLED)。 |
|
||||||
|
| `/licenses/sn` | SN 台账列表;**孤儿 SN** 列表筛选或醒目标记(与后端配置/字段一致)。 |
|
||||||
|
| `/licenses/sn/new` | 新建 SN(录入 `snCode`、绑定项目/合同行)。 |
|
||||||
|
| `/licenses/sn/:id` | SN 详情:**PUT** 调整绑定与 `activationRemark`;**PATCH** 调整状态;展示简要时间线(可仅读审计或本地状态历史 Mid 增强)。 |
|
||||||
|
|
||||||
|
**契约顺序提醒**([轨道 B §4](../tracks/02-frontend-platform-ui.md)):Auth → Customer/Project → Contract → **Delivery/SN** → Callback;I4 页面依赖 I2/I3 主数据与合同行选择器。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. I4 末同步点 Checklist(后端 + 前端 + SDK 文档)
|
||||||
|
|
||||||
|
以下为 [并行索引 I4 末](../PARALLEL_ITERATION_INDEX.md#3-跨轨同步点必须对齐) 的落地核对项。
|
||||||
|
|
||||||
|
### 6.1 后端(platform-api)
|
||||||
|
|
||||||
|
- [ ] Flyway V4 表与索引已在各环境应用;DTO 与 OpenAPI 字段一致。
|
||||||
|
- [ ] §3 路径已实现或通过兼容别名暴露;错误码与 409 语义与合同模块一致。
|
||||||
|
- [ ] §4.1~§4.3 校验全覆盖(含合同行与项目一致性、SN 全局唯一、绑定派生)。
|
||||||
|
- [ ] **孤儿 SN**:默认策略与(可选)M11-F20 配置占位行为**文档化并在代码注释或配置类中可定位**。
|
||||||
|
- [ ] **交付门禁(M3-F07)**:与 SN 创建/绑定相关的规则在代码中**可插拔**或明确「I4 硬编码默认 + I5+ 读配置」的 TODO 与 Owner。
|
||||||
|
- [ ] 审计:`DELIVERY_BATCH` / `LICENSE_SN` / 交付行动作写入 M10-F01 存储。
|
||||||
|
- [ ] `contracts/openapi/delivery-platform-api.json` 更新并通过 `OpenApiContractSnapshotTest`。
|
||||||
|
|
||||||
|
### 6.2 前端(delivery-platform-ui)
|
||||||
|
|
||||||
|
- [ ] §5 路由与菜单可达;RBAC 权限码与后端对齐。
|
||||||
|
- [ ] 交付详情状态操作仅展示合法迁移;错误态与 409 提示一致。
|
||||||
|
- [ ] SN 新建/编辑:合同行选择器与项目联动;**孤儿**场景 UI 与后端策略一致(禁止或警告)。
|
||||||
|
- [ ] E2E P0:`交付 → SN 录入/绑定 → 状态/备注回写`(与轨道 B I4 DoD 一致)。
|
||||||
|
|
||||||
|
### 6.3 客户端 SDK 工作区(本仓库)
|
||||||
|
|
||||||
|
- [ ] OpenAPI 快照与 [contracts/README.md](../../contracts/README.md) 说明含 Delivery/SN 标签。
|
||||||
|
- [ ] [tracks/03-client-sdk.md](../tracks/03-client-sdk.md) 或等价文档中 **BP-10 与平台对象口径** 补充:交付批次、SN 在集成叙事中的位置(**不实现**平台 REST 客户端亦可,但**文档**须与 I4 契约一致)。
|
||||||
|
- [ ] **M11-F20**:在 SDK/集成文档中标注为 **后续配置项**(门禁、孤儿强校验),避免集成方误假设 I4 已暴露该 HTTP API。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 修订记录
|
||||||
|
|
||||||
|
| 日期 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| 2026-04-06 | 初版:I4 范围与边界、REST 提案、校验与审计、前端路由、I4 末三轨 checklist。 |
|
||||||
@@ -0,0 +1,229 @@
|
|||||||
|
# 迭代 I5 / I6 设计说明 — M5 Callback Inbox、M6 集成最小面、Webhook 生产链与 UAT 冻结
|
||||||
|
|
||||||
|
> **仓库**:`craftlabs-authorization-sdk`(分支 `develop`)。
|
||||||
|
> **角色**:解决方案架构设计稿;**不**在本任务中落地代码。
|
||||||
|
> **实现锚点**(与现有模式一致):`delivery-platform-api` 使用 Flyway `V5__…` 起(当前末版为 `V4__delivery_batch_and_license_sn.sql`)、`AuditService` + `AuditEntityTypes` / `AuditActions`、`ApiExceptionHandler`、公开业务 API 经 `SecurityConfig` + `JwtAuthenticationFilter`(`Authorization: Bearer`);OpenAPI SSOT 为 [`contracts/openapi/delivery-platform-api.json`](../../../contracts/openapi/delivery-platform-api.json) 与 `OpenApiContractSnapshotTest`。
|
||||||
|
> **Webhook 工程名**:本工作区已实现为 `license-webhook-ingress`([`services/README.md`](../../../services/README.md):`webhook_callback_receipt`、`Idempotency-Key`)。**I7**:平台 HTTP 投递经 `webhook_platform_delivery` 异步出库,详见 [I7_DESIGN.md](./I7_DESIGN.md)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 0. 上游文档走查(按路径引用,不全文摘录)
|
||||||
|
|
||||||
|
| 文档 | 与本设计的关系(摘要) |
|
||||||
|
| -------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
|
| [docs/engineering/PARALLEL_ITERATION_INDEX.md](../PARALLEL_ITERATION_INDEX.md) | 定义三轨并行;**I5** 为 **Callback + Schema** 硬耦合周;**I6** 为 **UAT** 与 SDK 冻结;**I7** 起 Callback 权限与异步投递见索引表。同步点含 **I5 起**:Callback payload / inbox DTO、**Idempotency-Key**、Webhook→平台投递方式;**I6**:UAT 场景、`VITE_API_BASE`、两枚 Fat JAR 与 SDK 版本。 |
|
||||||
|
| [docs/engineering/tracks/01-backend-platform-webhook.md](../tracks/01-backend-platform-webhook.md) | **I5 DoD**:E2E「模拟 Callback → 平台 DB 一条 Inbox」;**§3 Webhook↔平台**:`schemaVersion` / `X-Event-Schema-Version`、幂等 `(source_system, external_message_id)`、`POST /internal/v1/callback-events` 或 MQ、对比特 **2xx** 须在持久化或可靠入队**之后**。 |
|
||||||
|
| [docs/engineering/tracks/02-frontend-platform-ui.md](../tracks/02-frontend-platform-ui.md) | **I5 路由**:`/callbacks`、`/integration/environments`、`product-lines`(本文统一为 `/integration/product-lines`);组件:`CallbackInboxTable`、`CallbackPayloadViewer`(脱敏);与 Webhook 联调或 staging。 |
|
||||||
|
| [docs/engineering/tracks/03-client-sdk.md](../tracks/03-client-sdk.md) | **I5**:**Schema + `AuthConfigs` + examples** 与 **BP-10** 同步为硬交付;**I6**:冻结 SDK 版本、CHANGELOG、**BitAnswer 兼容矩阵**;UAT 周禁止 MAJOR Schema。 |
|
||||||
|
| [docs/chuangfei-platform-product-modules.md](../../chuangfei-platform-product-modules.md) §6–7 | **M5 P0**:收件箱列表/详情/处理状态/关联失败兜底/事件类型字典(M5-F01~F05 等);**M6 P0**:产品线(M6-F01)、环境维度与 `bitanswer.url`(M6-F02);P1 如比特 ID 映射、JSON 模板、发布记录等 **I5 MVP 可裁减**。 |
|
||||||
|
| [docs/chuangfei-platform-bpm-and-roadmap.md](../../chuangfei-platform-bpm-and-roadmap.md) | **BP-06**:Webhook 验签 → 落库 → 解析关联 → Ops 处置;重复投递幂等。**BP-10**:M6 与 Schema、客户端配置发布链路;**依赖**本仓 `schemas/craftlabs-auth-config.schema.json`。 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Part A — I5:本单体工作区内的 MVP 切片
|
||||||
|
|
||||||
|
### A.1 问题与目标结果
|
||||||
|
|
||||||
|
| 维度 | 说明 |
|
||||||
|
| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
|
| **业务问题** | 比特规则 **HTTPS Callback** 需 **不断链、可审计、可运营处置**;平台侧需统一收件箱,避免只在 Webhook 边缘落库不可见。 |
|
||||||
|
| **I5 目标结果(E2E)** | **模拟或真实 Callback** 经 `license-webhook-ingress` →(持久化收据后)**投递** → `delivery-platform-api` **Inbox 表出现一行**;运营账号用 JWT 在 UI **列表可见、详情可打开**。 |
|
||||||
|
| **幂等** | 与轨道 A 一致:**`Idempotency-Key`**(HTTP 头)+ 比特稳定 **`message_id`**(或等价字段);DB **唯一约束 `(source_system, external_message_id)`**;重复请求 **不重复插入**、返回与首次一致的接受语义(HTTP 200 + 相同 `inboxId` 或约定 DTO)。 |
|
||||||
|
| **schemaVersion** | 事件体或头携带 **`schemaVersion`**(与轨道 A 的 `X-Event-Schema-Version` 二选一或并存,**须写 ADR 定一种主口径**);平台拒绝无法识别的 major 版本时返回 **4xx** 并记录可观测字段,避免静默损坏。 |
|
||||||
|
|
||||||
|
### A.2 数据模型(`delivery-platform-api`,PostgreSQL + Flyway)
|
||||||
|
|
||||||
|
命名与现有表一致采用 **`platform_*` 前缀**(见 `V1`~`V4` 迁移)。
|
||||||
|
|
||||||
|
#### A.2.1 `platform_callback_inbox`(M5 Inbox P0)
|
||||||
|
|
||||||
|
| 列(示例) | 类型/说明 |
|
||||||
|
| ------------------------------------------------ | ----------------------------------------------------------------------------------------------------------- |
|
||||||
|
| `id` | UUID / BIGSERIAL PK |
|
||||||
|
| `source_system` | VARCHAR,如 `BITANSWER` |
|
||||||
|
| `external_message_id` | VARCHAR,比特侧稳定消息 ID |
|
||||||
|
| **唯一约束** | `UNIQUE (source_system, external_message_id)` |
|
||||||
|
| `schema_version` | VARCHAR,与 payload 解析版本对齐 |
|
||||||
|
| `event_type` | VARCHAR,与 M5-F05 字典一致(如 `sn:pre_activate`) |
|
||||||
|
| `status` | ENUM/VARCHAR:**`PENDING` / `PROCESSED` / `FAILED` / `IGNORED`**(对应产品「待处理、已处理、失败、忽略」) |
|
||||||
|
| `raw_payload` | **JSONB**(或 TEXT + 大小上限);UI **脱敏展示** |
|
||||||
|
| `idempotency_key` | VARCHAR NULL,审计与排障 |
|
||||||
|
| **关联(均可 NULL,支撑 M5-F04)** | `license_sn_id` → `platform_license_sn`;`contract_id` / `project_id`(若已有表);解析字段如 `sn_code`、`mid` 等冗余列便于列表筛选 |
|
||||||
|
| `product_line_id` / `integration_environment_id` | FK → M6 最小表(可选,便于按产品线/环境筛选) |
|
||||||
|
| `received_at` | 平台收件时间 |
|
||||||
|
| `processed_at` / `processed_by_user_id` | 运营处置 |
|
||||||
|
| `failure_reason` / `operator_note` | 文本,P1 可扩展「失败原因分类」字典 |
|
||||||
|
| 标准审计 | 与现有实体一致可补充 `created_at`/`updated_at`;关键状态迁移建议走 **`AuditService`**(扩展 `AuditEntityTypes` / `AuditActions`) |
|
||||||
|
|
||||||
|
#### A.2.2 M6 最小只读支撑表
|
||||||
|
|
||||||
|
仅包含 **I5 UI 与 Inbox 筛选** 所需字段;**不做** M6-F03~F06 全量。
|
||||||
|
|
||||||
|
| 表 | P0 字段(示例) |
|
||||||
|
| --------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
|
| **`platform_product_line`** | `id`、`code`(唯一)、`name`、`description` NULL、启用标志 |
|
||||||
|
| **`platform_integration_environment`** | `id`、`code`(唯一)、`name`、`bitanswer_base_url`(对应 M6-F02)、`kind`(DEV/TEST/STAGING/PROD 等枚举)、可选 `product_line_id` 或后续多对多(MVP 可 **单列 FK** 简化) |
|
||||||
|
|
||||||
|
**MVP 裁减**:比特产品/模版/业务 ID 映射(M6-F03)、特征映射(M6-F04)、JSON 模板与发布记录(M6-F05/F06)**推迟**至 V1.1 或 Mid,除非比特联调硬依赖。
|
||||||
|
|
||||||
|
### A.3 公开 REST API(JWT,`/api/v1`)
|
||||||
|
|
||||||
|
与现有 Controller 风格一致。**RBAC(以代码为准,I7 已落地)**:
|
||||||
|
|
||||||
|
- **Callback Inbox**(`GET/PATCH /api/v1/callback-inbox*`):**`OPS`** + **`SYS_ADMIN`**(**`DEVELOPER`** 无);见 [`CallbackInboxController`](../../../services/delivery-platform-api/src/main/java/cn/craftlabs/platform/api/callback/CallbackInboxController.java)。
|
||||||
|
- **M6 只读**(`/api/v1/integration/*`):**`OPS`** + **`SYS_ADMIN`** + **`DEVELOPER`**。
|
||||||
|
- **其余 I2~I4 等业务 MVP**:仍为 **`SYS_ADMIN`** / **`DEVELOPER`** 演示账号模型;[`AuthController`](../../../services/delivery-platform-api/src/main/java/cn/craftlabs/platform/api/auth/AuthController.java) 含 `ops/ops`。
|
||||||
|
|
||||||
|
| 方法 | 路径 | 说明 |
|
||||||
|
| ------- | -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
|
||||||
|
| `GET` | `/api/v1/callback-inbox` | 分页;查询建议:`status`、`eventType`、`snCode`、`projectId`、`productLineId`、`environmentId`、`from`/`to`(`receivedAt`)、`page`、`size` |
|
||||||
|
| `GET` | `/api/v1/callback-inbox/{id}` | 详情;含 `rawPayload`(或单独 `GET .../payload` 若需权限分级) |
|
||||||
|
| `PATCH` | `/api/v1/callback-inbox/{id}/status` | 运营状态迁移:**`PENDING` → `PROCESSED` / `FAILED` / `IGNORED`**;非法迁移 **409** + 业务错误码(与合同/交付模式一致,经 **`ApiExceptionHandler`**) |
|
||||||
|
| `PATCH` | `/api/v1/callback-inbox/{id}/link`(可选) | 人工挂接:`licenseSnId` / `projectId` / `contractId` 等,支撑 M5-F04 |
|
||||||
|
|
||||||
|
**可选**:`POST /api/v1/callback-inbox/simulate`(**仅非生产**,对应 M5-F10 P2 — **I5 若排期紧可不做**,改用 curl → Webhook → 平台链)。
|
||||||
|
|
||||||
|
**M6 只读**
|
||||||
|
|
||||||
|
| 方法 | 路径 | 说明 |
|
||||||
|
| ----- | ---------------------------------------------------------------- | ------ |
|
||||||
|
| `GET` | `/api/v1/integration/environments` | 列表/分页 |
|
||||||
|
| `GET` | `/api/v1/integration/product-lines` | 列表/分页 |
|
||||||
|
| `GET` | `/api/v1/integration/environments/{id}`、`.../product-lines/{id}` | 详情(按需) |
|
||||||
|
|
||||||
|
写接口(维护环境/产品线)MVP 可 **仅种子数据 + Flyway** 或 **管理员 POST**(若 I5 周可交付则加 `POST/PUT`,否则 **推迟**)。
|
||||||
|
|
||||||
|
### A.4 内部 API(平台服务间,`license-webhook-ingress` → `delivery-platform-api`)
|
||||||
|
|
||||||
|
| 项 | 说明 |
|
||||||
|
| -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
|
| **路径** | `POST /internal/v1/callback-events` |
|
||||||
|
| **认证(MVP 推荐)** | 共享密钥:**`X-Platform-Internal-Token`**(或 `Authorization: Bearer <internal>`),配置与 `PLATFORM_JWT_SECRET` 分离;**生产建议路线图**:mTLS 或双向签名(文档中注明 **I6 Runbook:轮换步骤**)。 |
|
||||||
|
| **幂等** | 请求头 **`Idempotency-Key`** + 体 **`sourceSystem` + `externalMessageId`** 与 Inbox 唯一键一致;重复 POST → **200** 且 body 指向同一 `inboxId`。 |
|
||||||
|
| **行为** | 校验 `schemaVersion` → 插入或跳过(幂等)→ 尝试解析并 **填充关联列**(失败则 `status=PENDING` 保留人工挂接)。 |
|
||||||
|
|
||||||
|
**请求 / 响应 JSON 示例(示意)**
|
||||||
|
|
||||||
|
```http
|
||||||
|
POST /internal/v1/callback-events
|
||||||
|
Idempotency-Key: wh_01JABC...
|
||||||
|
X-Platform-Internal-Token: <redacted>
|
||||||
|
Content-Type: application/json
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"schemaVersion": "1.0",
|
||||||
|
"sourceSystem": "BITANSWER",
|
||||||
|
"externalMessageId": "msg_01JABC",
|
||||||
|
"eventType": "sn:post_activate",
|
||||||
|
"receivedAt": "2026-04-06T12:00:00Z",
|
||||||
|
"rawPayload": { "sn": "SN-001", "mid": "..." },
|
||||||
|
"webhookReceiptId": "optional-uuid-from-webhook-db"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"inboxId": "550e8400-e29b-41d4-a716-446655440000",
|
||||||
|
"duplicate": false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### A.5 `license-webhook-ingress` 链路
|
||||||
|
|
||||||
|
| 步骤 | 说明 |
|
||||||
|
| ---------------- | ------------------------------------------------------------------------------------------------------------------------- |
|
||||||
|
| 1 | **验签 / token**(现有 `x-bitanswer-token` 与 `CRAFTLABS_WEBHOOK_EXPECTED_TOKEN`) |
|
||||||
|
| 2 | **持久化收据**(现有 **`webhook_callback_receipt`** + **`Idempotency-Key`** 幂等) |
|
||||||
|
| 3 | **平台投递(I7)**:首单收据后写入 **`webhook_platform_delivery`**(`PENDING`),由调度器 **`POST /internal/v1/callback-events`**(退避重试、`SENT`/`DEAD`);贯通 **`traceparent` / `X-Request-Id`**(轨道 A §3) |
|
||||||
|
| **对比特的 HTTP 响应** | 与轨道 A 一致:**2xx 须在收据已持久化(或可靠入队)之后**再返回。**实现**:收据落库 + 出站行入队后对比特 **2xx**,平台 HTTP 在后台执行。 |
|
||||||
|
| **平台非 2xx** | 出库任务重试;超限 **`DEAD`**(见 [RUNBOOK §10.5](../../../services/RUNBOOK.md))。**不**因平台暂时不可用而对已持久化收据重复向比特报错(若已 2xx)。 |
|
||||||
|
|
||||||
|
### A.6 OpenAPI 与 springdoc
|
||||||
|
|
||||||
|
| 项 | 说明 |
|
||||||
|
| -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
|
| **SSOT** | 更新 [`contracts/openapi/delivery-platform-api.json`](../../../contracts/openapi/delivery-platform-api.json):仅 **公开** `/api/v1/callback-inbox*`、`/api/v1/integration/*` 路径、`components/schemas`、错误码与 `security`(Bearer JWT)。 |
|
||||||
|
| **内部路由** | **`/internal/**` 建议排除在默认 springdoc 分组之外**,或打上 tag **`internal`** 且 **生产禁用**该分组(与「对外契约」分离,避免集成方误用)。 |
|
||||||
|
| **校验** | `UPDATE_OPENAPI=1 mvn test -Dtest=OpenApiContractSnapshotTest`(见 [`contracts/README.md`](../../../contracts/README.md))。 |
|
||||||
|
|
||||||
|
### A.7 前端(`web/delivery-platform-ui`)
|
||||||
|
|
||||||
|
| 路由 | 页面职责 |
|
||||||
|
| ---------------------------- | ---------------------------------------------------- |
|
||||||
|
| `/callbacks` | Inbox 列表、`CallbackInboxTable`;跳转详情 |
|
||||||
|
| `/callbacks/:id` | 详情 + **`CallbackPayloadViewer`(脱敏)**;状态 PATCH、可选人工挂接 |
|
||||||
|
| `/integration/environments` | M6 环境只读表 |
|
||||||
|
| `/integration/product-lines` | 产品线只读表 |
|
||||||
|
|
||||||
|
路由 meta:**I7** 与后端一致——Callback 仅 **`SYS_ADMIN`/`OPS`**;集成页含 **`DEVELOPER`**;菜单与首页按角色裁剪(见 [轨道 B I7](../tracks/02-frontend-platform-ui.md))。
|
||||||
|
|
||||||
|
### A.8 SDK 轨道(本仓库,I5 硬交付清单)
|
||||||
|
|
||||||
|
- **Schema**:`schemas/craftlabs-auth-config.schema.json` 等 — 若 BP-10 变更类型触及「平台导出 → Schema → 客户端」,按 [轨道 C §3](../tracks/03-client-sdk.md) bump 规则执行。
|
||||||
|
- **Java `AuthConfigs`**:与 Schema 同步(表见轨道 C BP-10)。
|
||||||
|
- **`examples/`**:与最新字段及环境变量说明一致;CI 与 Schema 校验对齐。
|
||||||
|
- **文档**:明确 **SDK 版本线 ≠ 平台 Fat JAR 版本**;引用 BPM **BP-10** 与产品 M6-F01/F02 口径。
|
||||||
|
- **不做**:Native 与 Webhook 运行时耦合;平台不嵌入 JNI。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Part B — I6:规划与收口文档
|
||||||
|
|
||||||
|
| 主题 | 内容要点 | 执行文档 |
|
||||||
|
| ----------- | ---------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------ |
|
||||||
|
| **UAT 门禁** | 跑通 **BP-01~06、11** 主链路(与 [轨道 B I6](../tracks/02-frontend-platform-ui.md) E2E 一致);**Callback** 场景含重复投递幂等、关联失败人工挂接。 | [I6_CLOSEOUT.md §2](./I6_CLOSEOUT.md) |
|
||||||
|
| **冻结清单** | **SDK**:定版 tag、CHANGELOG、**BitAnswer 兼容矩阵**(轨道 C);**OpenAPI** 快照冻结;**两 JAR** 版本与镜像标签可追踪;前端 **`VITE_API_BASE`** 环境矩阵文档化。 | [I6_CLOSEOUT.md §3~§4](./I6_CLOSEOUT.md) |
|
||||||
|
| **Runbook** | [`services/RUNBOOK.md`](../../../services/RUNBOOK.md):**内部 token 轮换**、Webhook→平台连通性检查、DB 迁移顺序(`flyway_platform_api` / `flyway_webhook`)。 | RUNBOOK **§10** |
|
||||||
|
| **安全加固** | **安全响应头**、Cookie/Session 策略(若 Mid 前仍为 JWT 则文档化 **仅 Bearer**)、依赖扫描与已知 CVE 处理;**禁止** I6 周排入大块新功能(仅缺陷与加固)。 | 平台 `SecurityConfig` + Runbook + [CI 依赖/CVE 扫描](../../../.github/workflows/ci-security.yml) |
|
||||||
|
| **实现审核** | I1~I6 对照设计与三轨道文档 | [I6_IMPLEMENTATION_REVIEW.md](./I6_IMPLEMENTATION_REVIEW.md) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Part C — 实现顺序与 MVP 切割
|
||||||
|
|
||||||
|
1. **平台 DB(Flyway `V5+`)**:`platform_callback_inbox` + M6 两表 + 必要索引与外键;种子数据(环境/产品线)可选。
|
||||||
|
2. **平台内部 API**:`POST /internal/v1/callback-events` + 幂等与 `schemaVersion` 校验 + **`AuditService` 钩子**(状态/关联变更)。
|
||||||
|
3. **平台公开 API**:`GET/PATCH` callback-inbox、M6 只读 GET;**统一异常**经 `ApiExceptionHandler`。
|
||||||
|
4. **OpenAPI 快照** 与契约测试更新。
|
||||||
|
5. **Webhook**:收据落库后 **入队平台投递**(**I7**:`webhook_platform_delivery` + 调度);配置项:`PLATFORM_INTERNAL_BASE_URL`、`PLATFORM_INTERNAL_TOKEN`。
|
||||||
|
6. **集成测试**:Testcontainers + 双模块或 Compose(与轨道 A **I5+ `cross-service-it`** 建议一致)。
|
||||||
|
7. **前端**:路由与表格/详情/脱敏展示;联调 staging。
|
||||||
|
8. **SDK / Schema / examples**:按 BP-10 终版对齐并过 CI。
|
||||||
|
|
||||||
|
**MVP 明确推迟(若全量 M5/M6 过大)**
|
||||||
|
|
||||||
|
| 推迟项 | 说明 |
|
||||||
|
| ---------- | ------------------------------------------ |
|
||||||
|
| M6-F03~F09 | 比特 ID 映射、特征映射、模板库、发布记录、影响分析 |
|
||||||
|
| M5-F06~F09 | 失败分类字典、批量重试 UI、积压监控、M8 待办联动 |
|
||||||
|
| M5-F10 | 模拟投递 UI(可用 curl/Postman 代替) |
|
||||||
|
| MQ 投递 | 保留 HTTP MVP;MQ + 消费者为 ADR 备选(轨道 A 已列 B 方案) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Part D — 可追溯性(设计章节 → 产品功能点)
|
||||||
|
|
||||||
|
| 设计章节 | 产品模块 / 功能点(适用处) |
|
||||||
|
| ------------------------------- | ------------------------------ |
|
||||||
|
| A.1 幂等与 schemaVersion | M5 运营基础;BP-06 |
|
||||||
|
| A.2.1 `platform_callback_inbox` | **M5-F01~F04**(列表、详情、状态、关联兜底) |
|
||||||
|
| A.2.1 `event_type`、字典 | **M5-F05** |
|
||||||
|
| A.2.2 产品线 / 环境表 | **M6-F01、M6-F02** |
|
||||||
|
| A.3 公开 REST | **M5-F01~F03**;人工挂接 **M5-F04** |
|
||||||
|
| A.4 内部 API | BP-06 入站;与轨道 A Webhook↔平台契约 |
|
||||||
|
| A.5 Webhook 转发 | BP-06 步骤①②;**不丢链** |
|
||||||
|
| A.8 SDK / Schema | **BP-10**;**M6** 配置治理(文档与校验链) |
|
||||||
|
| Part B I6 | BP-01~06、11 UAT;M11 安全与运维 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 修订记录
|
||||||
|
|
||||||
|
| 日期 | 说明 |
|
||||||
|
| ---------- | ----------------------------------------------------------------- |
|
||||||
|
| 2026-04-06 | 初版:I5/I6 架构设计,对齐并行索引、三轨道文档与产品 M5/M6 P0。 |
|
||||||
|
| 2026-04-06 | Part B 关联 I6 收口文档(CLOSEOUT / IMPLEMENTATION_REVIEW)与 RUNBOOK §10。 |
|
||||||
|
| 2026-04-06 | 固化 Markdown 引用与强调;Part B 补充 CI 依赖/CVE 扫描入口。 |
|
||||||
|
| 2026-04-06 | 对齐 **I7**:A.3/A.5/A.7 权限与异步出库;OpenAPI/链接格式修正。 |
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
# I6 收口执行包(UAT 门禁、冻结清单、运维)
|
||||||
|
|
||||||
|
> **定位**:在 [I5_I6_DESIGN.md](./I5_I6_DESIGN.md) **Part B** 规划基础上,把 **I6 周可执行项** 收束为检查表与环境矩阵。
|
||||||
|
> **前置**:I5 代码路径已合入 `develop`(Callback Inbox、内部投递、Webhook 转发、集成只读 API、前端路由)。
|
||||||
|
> **实现对照审核**:[I6_IMPLEMENTATION_REVIEW.md](./I6_IMPLEMENTATION_REVIEW.md)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 架构师任务(I6 周入口)
|
||||||
|
|
||||||
|
| 次序 | 产出 | 责任 |
|
||||||
|
|------|------|------|
|
||||||
|
| 1 | 本文件 + Runbook §10 运维段落 | 架构 / Tech Lead |
|
||||||
|
| 2 | 后端:安全响应头、配置与观测无回归 | 后端 |
|
||||||
|
| 3 | 前端:`VITE_API_BASE`、首页 UAT 导航、构建说明 | 前端 |
|
||||||
|
| 4 | [I6_IMPLEMENTATION_REVIEW.md](./I6_IMPLEMENTATION_REVIEW.md) 关闭 I1–I6 偏差项 | 架构审核 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. UAT 门禁(P0 场景)
|
||||||
|
|
||||||
|
> 与 [轨道 B §2 I6](../tracks/02-frontend-platform-ui.md)「BP-01~06+11」一致;以下按 **本工作区已实现能力** 细化。
|
||||||
|
|
||||||
|
| # | 场景 | 预期 | 备注 |
|
||||||
|
|---|------|------|------|
|
||||||
|
| U1 | 登录 JWT | `admin/admin` 登录后访问受保护路由 | I1 |
|
||||||
|
| U2 | 客户 → 项目 | CRUD 与列表 | I2 |
|
||||||
|
| U3 | 合同 | 创建、行项、状态迁移合法/非法 | I3 |
|
||||||
|
| U4 | 交付批次 → 许可 SN | 创建、详情、状态 | I4 |
|
||||||
|
| U5 | Callback 全链 | Webhook 收据后转发 → 平台 Inbox 一行;UI 列表/详情、状态 PATCH、可选 link | I5;需配置内部 Token 与 base-url |
|
||||||
|
| U6 | 集成只读 | 环境/产品线列表与详情 | I5 |
|
||||||
|
| U7 | 重复幂等 | 同 `Idempotency-Key` / 同 `externalMessageId` 不重复插入;内部 API 返回 `duplicate: true` | I5 |
|
||||||
|
| U8 | 401 统一 | Token 失效回登录带 redirect | I1 |
|
||||||
|
|
||||||
|
**UAT 退出条件**:上表 **无 P0 缺陷**;已知 P1/P2 记入工单或 [I6_IMPLEMENTATION_REVIEW.md](./I6_IMPLEMENTATION_REVIEW.md) 「已知局限」。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 冻结清单(I6 末)
|
||||||
|
|
||||||
|
| 项 | 动作 |
|
||||||
|
|----|------|
|
||||||
|
| **OpenAPI** | `contracts/openapi/delivery-platform-api.json` 与 `OpenApiContractSnapshotTest` 一致;打 tag 可追溯 |
|
||||||
|
| **两枚 Fat JAR** | `delivery-platform-api`、`license-webhook-ingress` 版本与镜像标签写入发布说明 |
|
||||||
|
| **前端** | 生产构建使用明确 `VITE_API_BASE`(见 §4) |
|
||||||
|
| **SDK(本仓)** | 定版 tag、`CHANGELOG`、[轨道 C](../tracks/03-client-sdk.md) **兼容矩阵** 填齐;**I6 周内禁止 MAJOR Schema**(与设计一致) |
|
||||||
|
| **内部 Token** | 平台 `PLATFORM_INTERNAL_TOKEN` / `CRAFTLABS_PLATFORM_INTERNAL_TOKEN` 与 Webhook `craftlabs.platform.internal.token` **同值**;轮换走 [RUNBOOK §10](../../../services/RUNBOOK.md) |
|
||||||
|
| **依赖 / CVE** | PR 合并前 **`ci-security`** 通过:Trivy 扫描 `services/`、`java/`(`CRITICAL`+`HIGH`,仅已修复项见 workflow);`npm audit --audit-level=high`(`web/delivery-platform-ui`)。**Dependabot** 周度提 PR(`.github/dependabot.yml`)。 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 前端 `VITE_API_BASE` 环境矩阵
|
||||||
|
|
||||||
|
| 环境 | 示例 `VITE_API_BASE` | 说明 |
|
||||||
|
|------|----------------------|------|
|
||||||
|
| 本地开发 | (不设) | Vite 代理 `/api` → `127.0.0.1:8080` |
|
||||||
|
| Staging | `https://platform-api.staging.example.com` | 无尾部斜杠;axios 请求 `/api/v1/...` |
|
||||||
|
| 生产 | `https://platform-api.example.com` | 同源反代时可设为空,由 Nginx 处理 `/api` |
|
||||||
|
|
||||||
|
构建:`VITE_API_BASE=https://… npm run build`。详见 `web/delivery-platform-ui/README.md`。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 修订记录
|
||||||
|
|
||||||
|
| 日期 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| 2026-04-06 | I6 收口执行包初版:UAT 表、冻结清单、VITE 矩阵。 |
|
||||||
|
| 2026-04-06 | 冻结清单补充 CI 依赖/CVE 与 Dependabot。 |
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
# 架构师审核:I1~I6 实现对照(本工作区 `develop`)
|
||||||
|
|
||||||
|
> **方法**:以 [并行索引](../PARALLEL_ITERATION_INDEX.md)、各迭代设计稿(如 [I5_I6_DESIGN.md](./I5_I6_DESIGN.md))、三轨道文档([01](../tracks/01-backend-platform-webhook.md) / [02](../tracks/02-frontend-platform-ui.md) / [03](../tracks/03-client-sdk.md))为预期,对照仓库 **当前实现** 做收口审计。
|
||||||
|
> **范围**:`services/delivery-platform-api`、`services/license-webhook-ingress`、`web/delivery-platform-ui`、`contracts/openapi`、`schemas/` 及 CI/Enforcer 门禁;**不**评价未在本仓的独立 Fat JAR 发布物。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 总评
|
||||||
|
|
||||||
|
| 维度 | 结论 |
|
||||||
|
|------|------|
|
||||||
|
| **迭代完整性** | I1~I5 主路径已在前后端与 Webhook 贯通;I6 以 **UAT 文档、Runbook、安全头、前端生产基址与导航** 收口,符合 Part B「无大块新功能」原则。 |
|
||||||
|
| **契约** | 公开 API 以 `contracts/openapi/delivery-platform-api.json` 为 SSOT,`OpenApiContractSnapshotTest` 守门;`/internal/**` 排除默认 springdoc 分组,与设计一致。 |
|
||||||
|
| **风险** | 内部 Token 为 MVP 共享密钥;生产应规划 mTLS/签名(设计已提示)。Webhook 转发失败仅日志重试次数用尽,**无持久化 DLQ**,适合 I6 后需求排期。 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 分项审核
|
||||||
|
|
||||||
|
### 2.1 I1 — 身份、JWT、壳层
|
||||||
|
|
||||||
|
| 预期 | 实现 | 偏差 |
|
||||||
|
|------|------|------|
|
||||||
|
| Bearer JWT、401 入口、路由 RBAC | `JwtAuthenticationFilter`、`SecurityConfig` JWT 链、`router` meta.roles | 无 |
|
||||||
|
| 登录与 ping | `AuthController`、`/api/v1/ping`、前端 `LoginView` | 无 |
|
||||||
|
|
||||||
|
### 2.2 I2 — M1 主数据
|
||||||
|
|
||||||
|
| 预期 | 实现 | 偏差 |
|
||||||
|
|------|------|------|
|
||||||
|
| 客户/项目 CRUD + 字典 | Controllers + `CustomersView` / `ProjectsView` | 无结构性偏差(字段级以 OpenAPI 为准) |
|
||||||
|
|
||||||
|
### 2.3 I3 — M2 合同 + M10-F01
|
||||||
|
|
||||||
|
| 预期 | 实现 | 偏差 |
|
||||||
|
|------|------|------|
|
||||||
|
| 合同与行项、状态机服务端校验 | Contract API + 前端向导/详情 | 历史曾出现前后端动词不一致,**当前以 PATCH status 等与后端对齐**(需在 UAT 再点检) |
|
||||||
|
| 审计 | `AuditService`、`AuditEntityTypes` / `AuditActions` | 已扩展 Callback 相关常量(I5) |
|
||||||
|
|
||||||
|
### 2.4 I4 — M3/M4 交付与 SN
|
||||||
|
|
||||||
|
| 预期 | 实现 | 偏差 |
|
||||||
|
|------|------|------|
|
||||||
|
| 交付批次、行、许可 SN | 对应 API + `DeliveriesView` / SN 向导与列表 | 与设计 P0 一致 |
|
||||||
|
|
||||||
|
### 2.5 I5 — M5 Inbox、M6 只读、Webhook 链
|
||||||
|
|
||||||
|
| 预期(见 I5_I6_DESIGN Part A) | 实现 | 偏差 / 备注 |
|
||||||
|
|--------------------------------|------|----------------|
|
||||||
|
| `platform_callback_inbox` + M6 表 + Flyway V5 | 已落地 | 无 |
|
||||||
|
| 公开 `GET/PATCH` callback-inbox、integration GET | 已落地 | 无 |
|
||||||
|
| `POST /internal/v1/callback-events` + 内部 Token | `CallbackInternalController`、`InternalTokenAuthenticationFilter` | 无 |
|
||||||
|
| 幂等 `(sourceSystem, externalMessageId)` + 重复返回 `duplicate` | `CallbackEventIngestService` | 无 |
|
||||||
|
| `schemaVersion` major 校验 | `SUPPORTED_SCHEMA_MAJOR = 1` | 与设计「拒绝无法识别的 major」一致 |
|
||||||
|
| Webhook:收据后转发、trace 头、有限重试 | `PlatformCallbackForwarder` | **MVP**:同步线程内 3 次退避;与设计「异步重试」相比属简化,Runbook 已说明可配置性 |
|
||||||
|
| 前端 `/callbacks`、脱敏 | `redactPayload.js`、Inbox 视图 | 建议 UAT 确认敏感字段字典与产品一致 |
|
||||||
|
| OpenAPI 仅公开路由 | 内部 `@Hidden` + `paths-to-exclude` | 无 |
|
||||||
|
|
||||||
|
### 2.6 I6 — UAT、冻结、加固
|
||||||
|
|
||||||
|
| 预期(Part B + I6_CLOSEOUT) | 实现 | 偏差 |
|
||||||
|
|-------------------------------|------|------|
|
||||||
|
| UAT 检查表 | [I6_CLOSEOUT.md](./I6_CLOSEOUT.md) | 过程性文档;**不替代**自动化 E2E |
|
||||||
|
| Runbook:内部 Token、连通性、轮换 | [RUNBOOK.md §10](../../../services/RUNBOOK.md) | 无 |
|
||||||
|
| 安全响应头与依赖门禁 | `SecurityConfig.apiHeaders`:`X-Content-Type-Options`、frame deny、Referrer-Policy;**`ci-security`**(Trivy + `npm audit`)与 **Dependabot** | **HSTS** 依设计交由边缘层 |
|
||||||
|
| 前端生产 `VITE_API_BASE` | `main.js` + README + CLOSEOUT §4 | 无 |
|
||||||
|
| 首页全链路导航 | `HomeView` 模块链接 | 满足轨道 B I6「全链路导航」最低要求 |
|
||||||
|
|
||||||
|
### 2.7 轨道 C(SDK / Schema)
|
||||||
|
|
||||||
|
| 预期 | 实现 | 偏差 |
|
||||||
|
|------|------|------|
|
||||||
|
| I5 硬交付 Schema/示例/AuthConfigs | 以本仓 `schemas/`、`java/`、CI 为准(参见轨道 C 文档) | **I6 冻结**需在发布周由发布 Owner 执行 tag/CHANGELOG/矩阵,**非本审计单次提交所能证明** |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 建议进入 I7 / V1.1 前跟踪项
|
||||||
|
|
||||||
|
1. **Webhook 投递**:评估异步队列或 Outbox,补 **DLQ 指标**(设计 A.5、M5-F08)。
|
||||||
|
2. **内部认证**:mTLS 或请求签名;Token 多版本滚动。
|
||||||
|
3. **Playwright/Cypress**:将 I6_CLOSEOUT §2 固化为流水线冒烟。
|
||||||
|
4. **权限细化**:M5 运营接口由 SYS_ADMIN/DEVELOPER 收口为 Ops 角色(设计 A.3 已注明 I7+)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 修订记录
|
||||||
|
|
||||||
|
| 日期 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| 2026-04-06 | 初版:I6 架构师审核,对照 I5_I6_DESIGN 与三轨道文档。 |
|
||||||
|
| 2026-04-06 | I6 表:补充 CI 依赖/CVE 与 Dependabot。 |
|
||||||
@@ -0,0 +1,122 @@
|
|||||||
|
# 迭代 I7 架构设计 — 可靠投递、运营权限、前端指令
|
||||||
|
|
||||||
|
> **仓库**:`craftlabs-authorization-sdk`(`develop`)。
|
||||||
|
> **前置**:[I6 收口](./I6_CLOSEOUT.md)、[I6 实现审核](./I6_IMPLEMENTATION_REVIEW.md) §3 跟踪项。
|
||||||
|
> **角色**:迭代功能架构说明;实现以本文件为审查基线。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 目标与范围
|
||||||
|
|
||||||
|
|
||||||
|
| 主题 | I7 要达成 | 非目标(后置) |
|
||||||
|
| ---------------- | -------------------------------------------------------------------------------------------------- | ----------------------- |
|
||||||
|
| **Webhook → 平台** | 对比特 **2xx 后立即返回**;平台投递 **异步**、可重试、**落库可观测**(状态/次数/最后错误) | MQ、跨地域多活 |
|
||||||
|
| **运营权限** | 新增 `**OPS`**;**Callback Inbox**(读/改/挂接)仅 `**OPS` + `SYS_ADMIN`**;`**DEVELOPER**` 保留其余业务与 **M6 只读** | 细粒度按钮与数据范围(I8+) |
|
||||||
|
| **前端** | 路由 `meta.roles` 与菜单 `**hasAnyRole`** 一致;登录页说明 `**ops/ops**` | Playwright 流水线(I7.1 可选) |
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Webhook 侧:平台投递出站队列
|
||||||
|
|
||||||
|
### 2.1 行为
|
||||||
|
|
||||||
|
1. `**webhook_callback_receipt` 首次插入成功** 且配置了 `craftlabs.platform.internal.base-url` + token 时,写入 `**webhook_platform_delivery`**,`status=PENDING`。
|
||||||
|
2. **不**在 Callback HTTP 线程内同步 `RestClient` 调用平台(避免比特侧拖慢与线程占用)。
|
||||||
|
3. `**@Scheduled`** 周期拉取 `PENDING` / 可重试失败行,`POST /internal/v1/callback-events`;成功 → `SENT`;失败 → `attempts++`,指数退避 `**next_retry_at**`,超过 `**max-attempts**` → `DEAD`(人工/运维处理)。
|
||||||
|
4. **未配置 base-url** 时:不建队列行(与 I5「仅收据」行为一致)。
|
||||||
|
|
||||||
|
### 2.2 表(Flyway `V2__webhook_platform_delivery.sql`)
|
||||||
|
|
||||||
|
|
||||||
|
| 列 | 说明 |
|
||||||
|
| ----------------------------------------- | ------------------------------------- |
|
||||||
|
| `receipt_id` | 关联收据 |
|
||||||
|
| `request_body` | 平台 API JSON 正文 |
|
||||||
|
| `trace_headers_json` | `traceparent` / `X-Request-Id` 等 JSON |
|
||||||
|
| `idempotency_key` | 头 `Idempotency-Key` 用值 |
|
||||||
|
| `status` | `PENDING` / `SENT` / `DEAD` |
|
||||||
|
| `attempts`, `last_error`, `next_retry_at` | 重试与排障 |
|
||||||
|
|
||||||
|
|
||||||
|
### 2.3 配置(示例)
|
||||||
|
|
||||||
|
|
||||||
|
| Key | 说明 |
|
||||||
|
| ----------------------------------------------- | ----------- |
|
||||||
|
| `craftlabs.platform.delivery.scheduler-enabled` | 单测可 `false` |
|
||||||
|
| `craftlabs.platform.delivery.max-attempts` | 默认 `8` |
|
||||||
|
| `craftlabs.platform.delivery.batch-size` | 每.tick 处理条数 |
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 平台 API:角色与方法安全
|
||||||
|
|
||||||
|
### 3.1 角色
|
||||||
|
|
||||||
|
|
||||||
|
| 角色 | 用途 |
|
||||||
|
| ----------- | ----------------------------------------------------------- |
|
||||||
|
| `SYS_ADMIN` | 全量(含 Callback) |
|
||||||
|
| `OPS` | **仅** Callback Inbox + 与运营配套能力;**不**开放合同/交付等业务写路径(由路由与菜单裁剪) |
|
||||||
|
| `DEVELOPER` | 业务+M6 只读;**不**含 Inbox |
|
||||||
|
|
||||||
|
|
||||||
|
### 3.2 安全模型
|
||||||
|
|
||||||
|
- `**@EnableMethodSecurity`** + `**@PreAuthorize**` 扛后端契约。
|
||||||
|
- `**CallbackInboxController**`:`hasAnyRole('OPS','SYS_ADMIN')`。
|
||||||
|
- `**IntegrationCatalogController**`:`hasAnyRole('OPS','SYS_ADMIN','DEVELOPER')`。
|
||||||
|
- JWT 仍由 `[JwtAuthenticationFilter](../../../services/delivery-platform-api/src/main/java/cn/craftlabs/platform/api/security/JwtAuthenticationFilter.java)` 注入 `ROLE_*`。
|
||||||
|
|
||||||
|
### 3.3 演示账号
|
||||||
|
|
||||||
|
|
||||||
|
| 用户 | 密码 | 角色 |
|
||||||
|
| ------- | ------- | ----------- |
|
||||||
|
| `admin` | `admin` | `SYS_ADMIN` |
|
||||||
|
| `dev` | `dev` | `DEVELOPER` |
|
||||||
|
| `ops` | `ops` | `OPS` |
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 前端(`delivery-platform-ui`)
|
||||||
|
|
||||||
|
### 4.1 路由 `meta.roles`
|
||||||
|
|
||||||
|
|
||||||
|
| 区域 | `meta.roles` |
|
||||||
|
| ------------------------------ | ------------------------------- |
|
||||||
|
| 首页及以下业务(客户/项目/合同/交付/SN) | `SYS_ADMIN`, `DEVELOPER` |
|
||||||
|
| `/callbacks`, `/callbacks/:id` | `SYS_ADMIN`, `OPS` |
|
||||||
|
| `/integration/*` | `SYS_ADMIN`, `DEVELOPER`, `OPS` |
|
||||||
|
|
||||||
|
|
||||||
|
### 4.2 菜单与首页
|
||||||
|
|
||||||
|
- **Pinia** `hasAnyRole(roles)`,**MainLayout** / **HomeView** 按角色显示入口,避免 OPS 误以为可点业务页。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 测试与 DoD
|
||||||
|
|
||||||
|
|
||||||
|
| 项 | 标准 |
|
||||||
|
| ------- | ----------------------------------------------------------------------------------- |
|
||||||
|
| 平台 | `CallbackInboxControllerTest`:`DEVELOPER` 访问 Inbox → **403**;`SYS_ADMIN`/`OPS` 仍可走通 |
|
||||||
|
| Webhook | 配置 base-url 时 **enqueue** 产生 `PENDING` 行;单测关闭 scheduler 避免后台线程 |
|
||||||
|
| 前端 | `npm run build` 通过 |
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 修订记录
|
||||||
|
|
||||||
|
|
||||||
|
| 日期 | 说明 |
|
||||||
|
| ---------- | ---------------------------- |
|
||||||
|
| 2026-04-06 | I7 初版:异步投递表 + OPS + 前端路由/菜单。 |
|
||||||
|
| 2026-04-06 | 闭环:实现与 [I7_IMPLEMENTATION_REVIEW.md](./I7_IMPLEMENTATION_REVIEW.md) 复盘。 |
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
# I7 实现复盘 — 对照 [I7_DESIGN.md](./I7_DESIGN.md)
|
||||||
|
|
||||||
|
> **方法**:三任务闭环——架构设计 → 前后端实现 → 本复盘。
|
||||||
|
> **日期**:2026-04-06。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 总评
|
||||||
|
|
||||||
|
| 主题 | 设计意图 | 实现结论 |
|
||||||
|
|------|----------|----------|
|
||||||
|
| Webhook 异步投递 | 对比特快速 2xx;平台 POST 后台重试;可观测 `PENDING/SENT/DEAD` | **已落地**:`webhook_platform_delivery` + `PlatformDeliveryService` + `PlatformDeliveryScheduler`;配置见 `craftlabs.platform.delivery.*` 与 [RUNBOOK §10.5](../../services/RUNBOOK.md)。 |
|
||||||
|
| OPS 与 Inbox | `CallbackInboxController` 仅 `OPS`/`SYS_ADMIN` | **已落地**:`@PreAuthorize` + 演示账号 `ops/ops`。 |
|
||||||
|
| M6 只读 | `IntegrationCatalogController` 三者可读 | **已落地**:`OPS`+`SYS_ADMIN`+`DEVELOPER`。 |
|
||||||
|
| 前端 | 路由 `meta.roles` 与侧栏一致 | **已落地**:`HomeView` 过滤链接;`MainLayout` `v-if`;首页含 `OPS`。 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 偏差与已知局限
|
||||||
|
|
||||||
|
| 项 | 说明 |
|
||||||
|
|----|------|
|
||||||
|
| **DEAD 行运维** | **I8** 已提供平台代理重放 + 详情页按钮;**I9** 补充出库 **只读状态**(`PENDING`/`SENT`/`DEAD` 等)见 [I9_WEBHOOK_DELIVERY_VISIBILITY_DESIGN.md](./I9_WEBHOOK_DELIVERY_VISIBILITY_DESIGN.md)。 |
|
||||||
|
| **`v-permission` 指令** | 设计可选组件级指令;当前以 **路由 + 菜单 `hasAnyRole`** 为主,足够覆盖 I7 DoD。 |
|
||||||
|
| **Playwright** | 仍未进 CI;与 [I6_CLOSEOUT](./I6_CLOSEOUT.md) 一致列为后续。 |
|
||||||
|
| **内部 mTLS** | 未在本次范围;仍共享 `X-Platform-Internal-Token`。 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 验证清单(走查)
|
||||||
|
|
||||||
|
- [x] `mvn -f services/pom.xml verify`
|
||||||
|
- [x] `web/delivery-platform-ui` `npm run build`
|
||||||
|
- [x] `AuthControllerTest` ops 登录
|
||||||
|
- [x] `CallbackInboxControllerTest`:`dev` → Inbox **403**
|
||||||
|
- [x] `PlatformDeliveryEnqueueTest`:首单 Callback → 队列 **+1**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 修订记录
|
||||||
|
|
||||||
|
| 日期 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| 2026-04-06 | 初版:I7 闭环复盘。 |
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
# I8 实现审核 — 对照 [I8_WEBHOOK_DELIVERY_REPLAY_DESIGN.md](./I8_WEBHOOK_DELIVERY_REPLAY_DESIGN.md)
|
||||||
|
|
||||||
|
> **日期**:2026-04-06。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 总评
|
||||||
|
|
||||||
|
| 设计条款 | 结论 |
|
||||||
|
|----------|------|
|
||||||
|
| 浏览器只调平台、平台代调 Webhook | **已落地**:`POST /api/v1/callback-inbox/{id}/replay-webhook-delivery` + `WebhookDeliveryReplayClient`。 |
|
||||||
|
| Webhook `/internal/**` 独立 Ops Token | **已落地**:`WebhookOpsTokenFilter`、`X-Webhook-Ops-Token`;空配置 **503**。 |
|
||||||
|
| 仅 `DEAD` 可重放 | **已落地**:`PlatformDeliveryService.replayDeadDeliveryByReceiptId`。 |
|
||||||
|
| 关联 `webhookReceiptId` | **已落地**:平台从 `PlatformCallbackInbox` 解析 `Long` 调 Webhook。 |
|
||||||
|
| OpenAPI / Runbook | **已更新**:`CallbackWebhookReplayResponse`、RUNBOOK §10.5 I8 段。 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 偏差与后续
|
||||||
|
|
||||||
|
| 项 | 说明 |
|
||||||
|
|----|------|
|
||||||
|
| **UI 不展示出库状态** | 仍为「成功调用平台接口」提示;是否在详情旁拉取 Webhook 库属 **Mid**(需只读 API 或观测面)。 |
|
||||||
|
| **Playwright** | 未进 CI;与 I6/I7 一致可后续补一条 OPS 点击重放(需 mock Webhook 或 test 栈)。 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 验证命令(提交前)
|
||||||
|
|
||||||
|
- `mvn -f services/pom.xml verify`
|
||||||
|
- `npm run build`(`web/delivery-platform-ui`)
|
||||||
|
- `UPDATE_OPENAPI=0 mvn -f services/pom.xml test -Dtest=OpenApiContractSnapshotTest`(契约已与 `contracts/openapi/delivery-platform-api.json` 手工对齐时亦应通过)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 修订记录
|
||||||
|
|
||||||
|
| 日期 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| 2026-04-06 | 初版:I8 实现对照设计审核。 |
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
# I8 设计 — Webhook 平台投递 DEAD 重放(MVP)
|
||||||
|
|
||||||
|
> **角色**:解决方案架构;指导前后端实现与 Runbook。
|
||||||
|
> **前置**:I7 已落地 `webhook_platform_delivery`(`PENDING`/`SENT`/`DEAD`)与调度器;平台 `platform_callback_inbox.webhook_receipt_id` 与 Webhook 收据主键对齐(`PlatformCallbackRequestPlanner` 写入 `webhookReceiptId`)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 问题与目标
|
||||||
|
|
||||||
|
| 维度 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| **问题** | 出库 `DEAD` 后仅能通过查库/SQL 手工改状态,无受控运维入口,易误操作。 |
|
||||||
|
| **目标** | Ops 在 **Callback 详情** 一键将对应 **`DEAD`** 投递重新入队(`PENDING`),由现有调度器按 `max-attempts` 再次尝试 `POST /internal/v1/callback-events`。 |
|
||||||
|
| **非目标** | 修改比特 Callback 契约;不实现全量 DLQ 控制台;不在浏览器直连 Webhook 服务。 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 信任边界与 API 分层
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart LR
|
||||||
|
UI[delivery-platform-ui OPS JWT]
|
||||||
|
API[delivery-platform-api]
|
||||||
|
WH[license-webhook-ingress]
|
||||||
|
|
||||||
|
UI -->|Bearer JWT| API
|
||||||
|
API -->|X-Webhook-Ops-Token + receiptId| WH
|
||||||
|
WH -->|调度| API
|
||||||
|
```
|
||||||
|
|
||||||
|
1. **浏览器只调平台** `POST /api/v1/callback-inbox/{id}/replay-webhook-delivery`,沿用 `CallbackInboxController` 的 `@PreAuthorize("hasAnyRole('OPS','SYS_ADMIN')")`。
|
||||||
|
2. **平台服务器到 Webhook** 使用 **独立共享密钥** `X-Webhook-Ops-Token`(与 `X-Platform-Internal-Token` 分离:前者保护 Webhook 运维面,后者保护平台内部 ingest)。
|
||||||
|
3. **Webhook** 暴露 `POST /internal/v1/platform-deliveries/by-receipt/{receiptId}/replay`,由 **Servlet Filter** 校验 Ops Token(模块无 Spring Security 依赖,与现有 `/webhook/bitanswer/callback` 并存)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 业务规则
|
||||||
|
|
||||||
|
| 规则 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| **关联键** | 使用平台收件箱的 `webhook_receipt_id`(字符串数字)对应 `webhook_platform_delivery.receipt_id`(唯一)。缺失则 **400**,提示未关联 Webhook 出库记录。 |
|
||||||
|
| **仅 DEAD 可重放** | Webhook 侧若行不存在 → **404**;若状态为 `PENDING`/`SENT` → **409**,避免误重置进行中或已成功任务。 |
|
||||||
|
| **重放语义** | `status=PENDING`,`attempts=0`,`last_error=NULL`,`next_retry_at=NULL`,`updated_at=now`(重新给满 `max-attempts` 次数)。 |
|
||||||
|
| **平台幂等** | 重放仅重新 HTTP 投递;若平台 Inbox 已存在同 `(sourceSystem, externalMessageId)`,内部 ingest 返回 duplicate,**不新建 Inbox**(与现有幂等一致)。 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 配置项
|
||||||
|
|
||||||
|
| 组件 | 属性 / 环境变量 | 说明 |
|
||||||
|
|------|-----------------|------|
|
||||||
|
| Webhook | `craftlabs.webhook.ops-token` / `LICENSE_WEBHOOK_OPS_TOKEN` | 非空才启用 `/internal/**` 鉴权;空则 **503** 所有内部运维路径(避免误暴露)。 |
|
||||||
|
| Platform | `craftlabs.webhook.base-url` / `LICENSE_WEBHOOK_BASE_URL` | Webhook 根协议+主机+端口。 |
|
||||||
|
| Platform | `craftlabs.webhook.ops-token` | 与 Webhook 相同密钥,仅驻内存。 |
|
||||||
|
|
||||||
|
**Runbook**:在 [RUNBOOK.md](../../../services/RUNBOOK.md) §10.5 旁补充:重放前确认平台已恢复;同 receipt **不要并发多次重放**(单机调度即可)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 前端
|
||||||
|
|
||||||
|
- **Callback 详情**:展示 `webhookReceiptId`;若存在则显示 **「重新入队出库(DEAD→待投递)」**,调平台 POST;成功/失败用现有 `apiErrorMessage`。
|
||||||
|
- **可见性**:路由已为 OPS/SYS_ADMIN,与 I7 一致。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 契约
|
||||||
|
|
||||||
|
- 更新 `contracts/openapi/delivery-platform-api.json`:`POST /api/v1/callback-inbox/{id}/replay-webhook-delivery` 与响应 DTO(如 `status`、`receiptId`)。
|
||||||
|
- Webhook 内部路由 **不入** 对外 OpenAPI。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 测试与验收
|
||||||
|
|
||||||
|
| 层级 | 验收 |
|
||||||
|
|------|------|
|
||||||
|
| Webhook | 单测:`replayDeadByReceiptId` 对 DEAD/非 DEAD/缺行行为;集成或 MockMvc:401/503 无 token。 |
|
||||||
|
| Platform | 单测或 Mock:`CallbackInboxService` 在缺 receipt、Webhook 409/404 时映射 HTTP。 |
|
||||||
|
| 手工 | DEAD 一行 → 详情点重放 → 行变 `PENDING` → 调度成功后 `SENT`。 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. 修订记录
|
||||||
|
|
||||||
|
| 日期 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| 2026-04-06 | 初版:I8 DEAD 重放架构(平台代理 + Webhook 内部 API)。 |
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
# I9 实现审核 — 对照 [I9_WEBHOOK_DELIVERY_VISIBILITY_DESIGN.md](./I9_WEBHOOK_DELIVERY_VISIBILITY_DESIGN.md)
|
||||||
|
|
||||||
|
> **日期**:2026-04-07。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 总评
|
||||||
|
|
||||||
|
|
||||||
|
| 设计条款 | 结论 |
|
||||||
|
| --------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
|
| Webhook 只读 `GET by-receipt` | **已落地**:`PlatformDeliveryService#getStatusByReceiptId` + `WebhookPlatformDeliveryOpsController`。 |
|
||||||
|
| 平台代理 + JWT | **已落地**:`GET /api/v1/callback-inbox/{id}/webhook-delivery`,`WebhookDeliveryReplayClient#fetchDeliveryStatus`。 |
|
||||||
|
| 前端不直连 Webhook | **已落地**:`getCallbackWebhookDelivery` + 详情 `el-descriptions`;失败仅提示文案不遮断主详情。 |
|
||||||
|
| OpenAPI | **已导出**:`UPDATE_OPENAPI=1` 更新 `contracts/openapi/delivery-platform-api.json`。 |
|
||||||
|
| 文档索引 / I7 复盘 | **已修订**:[PARALLEL_ITERATION_INDEX](../PARALLEL_ITERATION_INDEX.md)、[I7_IMPLEMENTATION_REVIEW](./I7_IMPLEMENTATION_REVIEW.md) §2。 |
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 后续(未在本迭代范围)
|
||||||
|
|
||||||
|
|
||||||
|
| 项 | 说明 |
|
||||||
|
| -------------- | ----------------------------------------------- |
|
||||||
|
| **Playwright** | 可对「有 webhookReceiptId 的详情」断言状态区块或 503 提示。 |
|
||||||
|
| **指标** | M5-F08:积压 `PENDING`/`DEAD` 仍以观测平台为准,本迭代仅 UI 只读。 |
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 验证
|
||||||
|
|
||||||
|
- `JAVA_HOME`=JDK **17**:`mvn -f services/pom.xml verify`
|
||||||
|
- `npm run build`(`web/delivery-platform-ui`)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 修订记录
|
||||||
|
|
||||||
|
|
||||||
|
| 日期 | 说明 |
|
||||||
|
| ---------- | --- |
|
||||||
|
| 2026-04-07 | 初版。 |
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
# I9 设计 — Callback 详情可观测 Webhook 平台投递状态
|
||||||
|
|
||||||
|
> **角色**:架构审查与实现规划;承接 [I8](I8_WEBHOOK_DELIVERY_REPLAY_DESIGN.md)(重放入队)之后的 **只读观测** 缺口。
|
||||||
|
> **日期**:2026-04-07。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 走查结论(相对 I6/I7/I8)
|
||||||
|
|
||||||
|
| 主题 | 状态 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| I7 异步出库 | 已落地 | `PENDING` / `SENT` / `DEAD` 在 Webhook 库表。 |
|
||||||
|
| I8 重放 | 已落地 | 平台 POST 代理 + Webhook `replay`;**详情页仍看不到实时出库状态**。 |
|
||||||
|
| I7 复盘 §2 DEAD | **文档过时** | 已具备 UI 重放(I8);本迭代补 **状态只读** 并修订 I7 复盘表述。 |
|
||||||
|
| OpenAPI / JDK | 门禁 | 变更契约后须 **JDK 17** + `OpenApiContractSnapshotTest`;见 [contracts/README](../../../contracts/README.md)。 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 目标与非目标
|
||||||
|
|
||||||
|
| **目标** | Ops 在 **Callback 详情** 查看与该收件箱 `webhookReceiptId` 对应的 **`webhook_platform_delivery` 行摘要**(`status`、`attempts`、`lastError`、`nextRetryAt`、`updatedAt`),无需直连 Webhook 库。 |
|
||||||
|
| **非目标** | 浏览器调用 Webhook;改动比特契约;全量 M5-F08 仪表盘;Playwright 进 CI(列为 I9 后可选)。 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. API 与信任边界
|
||||||
|
|
||||||
|
与 I8 相同:**仅** `delivery-platform-api` 凭 **`LICENSE_WEBHOOK_BASE_URL` + `LICENSE_WEBHOOK_OPS_TOKEN`** 访问 Webhook **`/internal/**`**;前端只打平台 JWT。
|
||||||
|
|
||||||
|
| 组件 | 方法 | 路径 | 说明 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| Webhook | `GET` | `/internal/v1/platform-deliveries/by-receipt/{receiptId}` | 同 `WebhookOpsTokenFilter`;无行则 **404**。 |
|
||||||
|
| Platform | `GET` | `/api/v1/callback-inbox/{id}/webhook-delivery` | `@PreAuthorize` 与 Inbox 一致(`OPS`/`SYS_ADMIN`);无 `webhookReceiptId` 则 **400**;未配 Webhook 则 **503**。 |
|
||||||
|
|
||||||
|
**响应体(双方 JSON 字段一致,便于直连 RestClient 反序列化)**:
|
||||||
|
|
||||||
|
- `receiptId` (long)
|
||||||
|
- `status` (string)
|
||||||
|
- `attempts` (int)
|
||||||
|
- `lastError` (string, 可 null)
|
||||||
|
- `nextRetryAt` (date-time, 可 null)
|
||||||
|
- `updatedAt` (date-time)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 实现要点
|
||||||
|
|
||||||
|
1. **Webhook**:`PlatformDeliveryService#getStatusByReceiptId` 查表组装 Map/DTO;Controller 增加 `GET`。
|
||||||
|
2. **平台**:`WebhookDeliveryReplayClient#fetchDeliveryStatus`(保留 Bean 名以减重构面);`CallbackInboxService#getWebhookDeliveryStatus` → `CallbackInboxController`。
|
||||||
|
3. **前端**:详情页在存在 `webhookReceiptId` 时请求上述 GET,展示 `el-descriptions`;失败时 **不阻断** 主详情(提示或短文案)。
|
||||||
|
4. **契约**:`UPDATE_OPENAPI=1` 导出快照并提交。
|
||||||
|
5. **文档**:更新 [PARALLEL_ITERATION_INDEX](../PARALLEL_ITERATION_INDEX.md) I9 行;修正 [I7_IMPLEMENTATION_REVIEW](./I7_IMPLEMENTATION_REVIEW.md) §2 DEAD 表述。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 验证
|
||||||
|
|
||||||
|
- `mvn -f services/pom.xml verify`(Java 17)
|
||||||
|
- `npm run build`(`web/delivery-platform-ui`)
|
||||||
|
- Webhook / 平台单测各 ≥1 条覆盖 GET 与平台代理
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 修订记录
|
||||||
|
|
||||||
|
| 日期 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| 2026-04-07 | 初版:I9 出库可见性架构与 API。 |
|
||||||
@@ -0,0 +1,132 @@
|
|||||||
|
# 轨道 A:平台 API + License Webhook 后端 — 并行实施包
|
||||||
|
|
||||||
|
> **对齐**:[BPM 排期 §7](../chuangfei-platform-bpm-and-roadmap.md) · [工程布局 §5](../WORKSPACE_ENGINEERING_LAYOUT.md) · [集成平台 §8](../../chuangfei-bitanswer-integration-platform.md)
|
||||||
|
> **约束**:Spring Boot **4.0.x**;Maven **多模块**;**仅 `*-bootstrap`** `repackage` → **各一枚 Fat JAR**;服务端 **禁止** `craftlabs-auth-bitanswer`;数据层 **PostgreSQL 15** + **MyBatis-Plus**(见 [WORKSPACE_ENGINEERING_LAYOUT.md §9.1](../WORKSPACE_ENGINEERING_LAYOUT.md))。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 0. 并行执行总览
|
||||||
|
|
||||||
|
| 维度 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| **两条可部署线** | `delivery-platform-api` 与 `license-webhook` **同一日历迭代并行**,各产 **一枚 Fat JAR**。 |
|
||||||
|
| **对齐机制** | **契约 Owner**(事件 schema、`Idempotency-Key`、错误码);共享 **OpenAPI / 事件 README**;**I5 起** 集成测试门禁。 |
|
||||||
|
| **I1~I4 Webhook** | 可先 **验签、幂等、观测、占位路由**;生产级持久化 + M5 台账对齐在 **I5**。 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 仓库与模块骨架
|
||||||
|
|
||||||
|
### 1.1 双仓库(推荐)
|
||||||
|
|
||||||
|
| 仓库(示例名) | 产出 |
|
||||||
|
|----------------|------|
|
||||||
|
| `craftlabs-delivery-platform` | `platform-bootstrap-*.jar` |
|
||||||
|
| `craftlabs-license-webhook` | `webhook-bootstrap-*.jar` |
|
||||||
|
|
||||||
|
### 1.2 `delivery-platform` 模块示例
|
||||||
|
|
||||||
|
```
|
||||||
|
delivery-platform/
|
||||||
|
├── pom.xml
|
||||||
|
├── platform-domain/
|
||||||
|
├── platform-application/
|
||||||
|
├── platform-adapters-web/
|
||||||
|
├── platform-adapters-persistence/
|
||||||
|
└── platform-bootstrap/ # 唯一 main + repackage
|
||||||
|
```
|
||||||
|
|
||||||
|
包名建议:`cn.craftlabs.platform.{identity,customer,contract,delivery,licensing,audit,config,...}`。
|
||||||
|
|
||||||
|
### 1.3 `license-webhook` 模块示例
|
||||||
|
|
||||||
|
```
|
||||||
|
license-webhook/
|
||||||
|
├── pom.xml
|
||||||
|
├── webhook-domain/
|
||||||
|
├── webhook-application/
|
||||||
|
├── webhook-adapters-http/
|
||||||
|
├── webhook-adapters-integration/
|
||||||
|
└── webhook-bootstrap/
|
||||||
|
```
|
||||||
|
|
||||||
|
包名建议:`cn.craftlabs.webhook.*`。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 分迭代后端 Backlog(I1~I6)
|
||||||
|
|
||||||
|
| 迭代 | 平台:模块 | 平台:REST/领域 | 平台:DB | Webhook:范围 | 集成点 | DoD |
|
||||||
|
|------|------------|-----------------|----------|---------------|--------|-----|
|
||||||
|
| **I1** | domain/application/adapters-web/persistence/bootstrap | M11 登录/登出/会话、登录审计、粗 RBAC、OpenAPI 初版 | 用户、角色、会话、登录审计 | 脚手架、CI、`/health`、Callback 占位、`Idempotency-Key` 记录 | 无强依赖 | 平台 Fat JAR;`dependency:tree` 无 bitanswer;Webhook Fat JAR CI 绿 |
|
||||||
|
| **I2** | +M1 | 客户/项目 CRUD、字典、用户角色分配 API | 客户、项目、字典 | 验签配置、拒绝非法请求指标 | 字典编码与前端约定 | M1 API 与鉴权打通;验签可 curl 演示 |
|
||||||
|
| **I3** | 加重 persistence | M2 合同+行项、状态机;M10-F01 | 合同、行、变更日志 | 事件 DTO 规范化、与平台枚举对齐;可 Mock DB | 契约:合同/行 id 供 Callback 关联 | 状态机单测;事件 schema v0.1 归档 |
|
||||||
|
| **I4** | | M3 交付;M4 SN 录入/绑定/状态/手工回写 | 交付、SN、FK | 关联键策略、MQ producer 骨架可选 | 内部 API 或 MQ 方案 ADR | 交付+SN 主键链路可追溯 |
|
||||||
|
| **I5** | | M5 Inbox P0;M6 环境+产品线最小 | Inbox 唯一约束、M6 表 | 验签→幂等落库/入队→平台可见 | 比特 Dev 联调 | E2E:模拟 Callback → 平台 DB 一条 Inbox |
|
||||||
|
| **I6** | bootstrap 横切 | 加固、Runbook、UAT 缺陷 | 修复类迁移 | Ingress 加固、密钥轮换演练 | 全链路 BP-01~06、11 | UAT 签字;两 JAR 版本可追踪 |
|
||||||
|
|
||||||
|
### 并行分工小结
|
||||||
|
|
||||||
|
| 迭代 | 平台关键路径 | Webhook 关键路径 |
|
||||||
|
|------|--------------|------------------|
|
||||||
|
| I1~I2 | 身份 + 主数据 | Ingress + 验签 + 契约草稿 |
|
||||||
|
| I3~I4 | 合同/交付/SN + 审计 | 事件模型 + 投递骨架 |
|
||||||
|
| I5~I6 | Inbox + M6 | 真实链路 + 运维 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Webhook ↔ 平台契约要点
|
||||||
|
|
||||||
|
- **版本**:`schemaVersion` 或 `X-Event-Schema-Version`。
|
||||||
|
- **幂等**:`Idempotency-Key` + 比特稳定 `message_id`;Inbox 表 `(source_system, external_message_id)` 唯一。
|
||||||
|
- **投递**:A) Webhook **HTTP** `POST /internal/v1/callback-events`;B) **MQ** + 平台消费者(MVP 需 I5 定案)。
|
||||||
|
- **响应**:对比特 **2xx** 须在持久化或可靠入队**之后**。
|
||||||
|
- **安全**:验签用平台侧实现;**禁止** JNI SDK。
|
||||||
|
- **观测**:`traceparent` / `X-Request-Id` 贯通。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 风险与对前端/SDK
|
||||||
|
|
||||||
|
| 风险 | 缓解 |
|
||||||
|
|------|------|
|
||||||
|
| 契约漂移 | I3 起 OpenAPI SSOT;fixture 入库 |
|
||||||
|
| 幂等与 UX | DB 唯一约束;前端按业务 id 去重 |
|
||||||
|
| I5 前 Webhook 空转 | I1~I4 以契约+假实现为主 |
|
||||||
|
| SDK 误用 POM | CI `dependency:tree` grep |
|
||||||
|
| 前端滞后 | I5 起 Postman/Scalar 先行验收 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. CI Jobs 建议
|
||||||
|
|
||||||
|
| Job | 步骤摘要 |
|
||||||
|
|-----|----------|
|
||||||
|
| `platform-build` | `mvn -pl platform-bootstrap -am verify`;可选禁止 `craftlabs-auth-bitanswer` |
|
||||||
|
| `webhook-build` | 同上 `webhook-bootstrap` |
|
||||||
|
| `platform-integration-test` | Testcontainers + Flyway;I5+ Mock Callback 全链路 |
|
||||||
|
| `cross-service-it`(I5+) | Compose:双 JAR + DB + MQ |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. V1.1(I7~I8)
|
||||||
|
|
||||||
|
| 迭代 | 平台 | Webhook |
|
||||||
|
|------|------|---------|
|
||||||
|
| I7 | 按钮级权限码、导出脱敏、限流、安全头 | 速率限制、payload 上限 |
|
||||||
|
| I8 | 业务指标 RED、批量导入幂等 | Callback 失败率、DLQ 深度 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Mid(I9~I13)与 V2.0
|
||||||
|
|
||||||
|
- **Mid**:M7 设备;M8 待办/通知;M9 报表;M11 SSO/并发/强制下线;M2 变更可选 I13。
|
||||||
|
- **V2.0**:M10 举证包;MFA、SECURITY_ADMIN、数据范围;CRM、读模型/CQRS 轻量。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. 修订记录
|
||||||
|
|
||||||
|
| 日期 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| 2026-04-06 | 由并行 Task 产出并入库;与 Roadmap I1~I6 对齐。 |
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
# 轨道 B:delivery-platform-ui(Vue 3)— 并行实施包
|
||||||
|
|
||||||
|
> **对齐**:[BPM 排期 §7](../chuangfei-platform-bpm-and-roadmap.md) · [功能模块 M1–M11](../../chuangfei-platform-product-modules.md) · [并行索引](../PARALLEL_ITERATION_INDEX.md)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 技术基线
|
||||||
|
|
||||||
|
| 项 | 选型 |
|
||||||
|
|----|------|
|
||||||
|
| 运行时 | **Vue 3** + **Vite**,Composition API + `<script setup>` |
|
||||||
|
| 状态 | **Pinia** |
|
||||||
|
| 路由 | **vue-router**(懒加载、meta:权限码、`title`) |
|
||||||
|
| HTTP | **axios**(拦截器:JWT Bearer 或 Session Cookie) |
|
||||||
|
| UI | **Element Plus** |
|
||||||
|
| 契约(可选) | OpenAPI → TS 类型,与后端 **contract-first** |
|
||||||
|
|
||||||
|
认证:环境切换 `withCredentials` 与 `Authorization`;登录后写入 Pinia。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. I1~I6 前端 Backlog
|
||||||
|
|
||||||
|
| 迭代 | 路由/页面 | 关键组件 | API/状态 | E2E | DoD |
|
||||||
|
|------|-----------|----------|----------|-----|-----|
|
||||||
|
| **I1** | `/login`、`/` 布局、`/403`/`/404` | `AppLayout`、`LoginForm`、`IdleTimeout` | auth/user store;401 统一处理 | P0 登录与回跳 | RBAC 路由守卫;菜单按权限过滤 |
|
||||||
|
| **I2** | `/customers`、`/projects` 及详情;`/admin/dictionaries` | `DataTable`、`CustomerForm`、`ProjectForm` | CRUD + 字典缓存 | P0 客户→项目 | 与 M1 P0 字段一致 |
|
||||||
|
| **I3** | `/contracts`、新建向导、`/contracts/:id` | `ContractWizard`、`ContractLineEditor`、`StatusTag` | 状态机由后端校验,前端禁用非法操作 | P0 草稿→生效 | M2 P0;M10-F01 入口 |
|
||||||
|
| **I4** | `/deliveries`、`/licenses/sn`、导入 | `DeliveryBatchForm`、`SnBindDialog`、`SnStatusTimeline` | 交付与合同行;孤儿 SN 警告 | P0 交付→SN→回写 | M3/M4 P0 |
|
||||||
|
| **I5** | `/callbacks`、`/integration/environments`、`product-lines` | `CallbackInboxTable`、`CallbackPayloadViewer`(脱敏) | Inbox 处置;M6 只读/受限写 | P0 列表→详情→状态 | 与 Webhook 联调或 staging |
|
||||||
|
| **I6** | 全链路导航与修缺陷(参见 [I6_CLOSEOUT.md](../iterations/I6_CLOSEOUT.md)) | 可选 `GlobalSearch` | 错误与空态统一;生产 `VITE_API_BASE` | P0 **BP-01~06+11** 全链路 E2E | UAT 无 P0;手册截图一致 |
|
||||||
|
| **I7** | `/callbacks*` 仅 **OPS/SYS_ADMIN**;菜单与首页按角色裁剪 | Pinia `hasAnyRole` | 与 `@PreAuthorize` 对齐;`ops/ops` 演示 | 与 Webhook 异步投递联调 | 参见 [I7_DESIGN.md](../iterations/I7_DESIGN.md)、[I7_IMPLEMENTATION_REVIEW.md](../iterations/I7_IMPLEMENTATION_REVIEW.md) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 页面 ↔ 模块(摘要)
|
||||||
|
|
||||||
|
| 模块 | 典型路由 | MVP 迭代 |
|
||||||
|
|------|----------|----------|
|
||||||
|
| M11 | 登录、用户/角色(I2)、Mid SSO | I1、Mid |
|
||||||
|
| M1 | 客户、项目 | I2 |
|
||||||
|
| M2 | 合同 | I3 |
|
||||||
|
| M3/M4 | 交付、SN | I4 |
|
||||||
|
| M5/M6 | Callback、集成配置 | I5 |
|
||||||
|
| M7~M9 | 设备、待办、报表 | Mid |
|
||||||
|
| M10 | 审计展示/导出 | I3+ / Mid / V2 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Mock 与契约先行
|
||||||
|
|
||||||
|
| 工作包 | 可先 mock? | 建议 |
|
||||||
|
|--------|-------------|------|
|
||||||
|
| I1 壳层 + RBAC | ✅ | MSW / vite-plugin-mock |
|
||||||
|
| M1 CRUD | ✅ | I3 前客户/项目 DTO 冻结 |
|
||||||
|
| M2 合同 | ⚠️ | 状态迁移 **OpenAPI 冻结**(I2 末) |
|
||||||
|
| M3/M4 | ⚠️ | 门禁与 M11-F20 契约先行 |
|
||||||
|
| M5/M6 | ⚠️ | Payload 以 Webhook/API DTO 为准 |
|
||||||
|
|
||||||
|
**契约顺序**:Auth → Customer/Project → Contract → Delivery/SN → Callback/Integration。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. V1.1 / Mid / V2.0(摘要)
|
||||||
|
|
||||||
|
- **I7~I8**:按钮级 `v-permission`;导出脱敏;运维只读仪表盘;批量导入进度。
|
||||||
|
- **Mid I9~I13**:设备/换机;待办与通知配置;对账与 Callback 报表;**SSO**(`/oauth/callback` 等);合同变更对比可选。
|
||||||
|
- **V2.0**:M10 导出包、MFA、SECURITY_ADMIN、数据范围、CRM 同步状态页。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. E2E 建议
|
||||||
|
|
||||||
|
| 层级 | 工具向 |
|
||||||
|
|------|--------|
|
||||||
|
| Smoke | Playwright / Cypress:登录 + 各迭代主路由 |
|
||||||
|
| P0 业务 | I6 全链路 + V1.1 导入导出 |
|
||||||
|
| Mid | SSO、待办、报表(staging IdP 或 mock OIDC) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 修订记录
|
||||||
|
|
||||||
|
| 日期 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| 2026-04-06 | 由并行 Task 产出并入库。 |
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
# 轨道 C:craftlabs-authorization-sdk(客户端)— 并行实施包
|
||||||
|
|
||||||
|
> **对齐**:[BPM §7 BP-10](../chuangfei-platform-bpm-and-roadmap.md) · [工程布局 §5](../WORKSPACE_ENGINEERING_LAYOUT.md)
|
||||||
|
> **事实**:`craftlabs-auth-core` / `bitanswer` / `selfhosted`;`schemas/`;`examples/`;平台 **不嵌入** Native。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. SDK 版本线与 Native 标签
|
||||||
|
|
||||||
|
|
||||||
|
| 原则 | 说明 |
|
||||||
|
| ------------- | ---------------------------------------------------------------- |
|
||||||
|
| **独立 SemVer** | SDK **不与**平台 Fat JAR 版本号绑定;对外话术分两条线。 |
|
||||||
|
| **MAJOR** | Schema 不兼容、公共 API 破坏、JNI 契约不兼容。 |
|
||||||
|
| **MINOR** | 向后兼容扩展:可选 Schema 字段、新 API、新示例。 |
|
||||||
|
| **PATCH** | Bugfix、文档;无契约变化。 |
|
||||||
|
| **预发布** | `-rc.N` 与平台 UAT/比特联调对齐;**不用**平台版本代替 SDK 版本。 |
|
||||||
|
| **Native** | 每次发行 **JAR + 各 OS native** 同版本;Git **单 tag**(如 `v1.4.2`);禁止只升其一。 |
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 平台迭代 × SDK 工作对照
|
||||||
|
|
||||||
|
|
||||||
|
| 迭代 | 平台焦点 | SDK **必需** | SDK **非阻塞** |
|
||||||
|
| ------ | ------------------- | ------------------------------------------ | ---------------------- |
|
||||||
|
| **I1** | M11 脚手架 | 无 | 文档:与平台产物区分 |
|
||||||
|
| **I2** | M1 | 无 | 文档 |
|
||||||
|
| **I3** | M2、M10-F01 | 无 | 文档 |
|
||||||
|
| **I4** | M3、M4、激活回写 | 烟测路径下 **推荐 PATCH** 修缺陷 | 文档/示例与 BP-10 口径 |
|
||||||
|
| **I5** | M5、M6、Webhook、BP-10 | **Schema + `AuthConfigs` + examples 同步** | Native 与 Webhook 无关 |
|
||||||
|
| **I6** | UAT | **冻结 SDK 版本**、CHANGELOG、**BitAnswer 兼容矩阵** | 禁止 UAT 周做 MAJOR Schema |
|
||||||
|
|
||||||
|
|
||||||
|
**小结**:I1~I4 以文档/示例为主;**I5 起** Schema/Java/示例为硬交付;**I6** 冻结与清单。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. BP-10:何时 bump Schema / Java / 文档 / 示例
|
||||||
|
|
||||||
|
|
||||||
|
| 变更类型 | Schema | Java AuthConfigs | docs | examples |
|
||||||
|
| ------------------- | ------------------- | ---------------- | ---- | -------- |
|
||||||
|
| 新增可选字段 | MINOR(或 PATCH) | 同交付对齐 | 字段说明 | 可选示例 |
|
||||||
|
| 新增必填/收紧/改名 | MAJOR 或 breaking 策略 | 必须同步 | 迁移说明 | 必须更新模板 |
|
||||||
|
| 仅 description | PATCH | 通常无 | 可选 | 可选 |
|
||||||
|
| M6 仅改发布流程、JSON 形态不变 | 无 | 无 | 手册可选 | 无 |
|
||||||
|
|
||||||
|
|
||||||
|
**硬规则**:影响「平台导出 → Schema → 客户端」任一环的变更,**同一发布列车** 更新 Schema + Java(若有)+ 示例。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. SDK 发布检查清单(非平台 Fat JAR)
|
||||||
|
|
||||||
|
1. `mvn -f java/pom.xml clean verify`;Native 各 OS 构建通过。
|
||||||
|
2. `mvn deploy`:`craftlabs-auth-*` **版本一致**;**无** `spring-boot-maven-plugin` repackage。
|
||||||
|
3. Native 与 JAR **同版本** 发布。
|
||||||
|
4. Schema + examples CI 校验。
|
||||||
|
5. CHANGELOG:破坏性变更、BitAnswer 版本区间、已知平台导出版本(若可公开)。
|
||||||
|
6. **兼容矩阵**:SDK 版本、厂商库、OS/JDK;与平台应用版本 **分列**。
|
||||||
|
7. Git tag;**不含** `java -jar platform.jar` 说明。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. V1.1+ SDK 增强(规划)
|
||||||
|
|
||||||
|
|
||||||
|
| 主题 | 说明 |
|
||||||
|
| --------------------- | ---------------------------------------- |
|
||||||
|
| 更严校验 | CI 对 examples 与 Schema 双向校验;平台脱敏 fixture |
|
||||||
|
| Fixtures | 最小/完整/非法 JSON 绑定 Schema 版本 |
|
||||||
|
| 可选 `config-model` 薄模块 | 供平台后端复用 POJO **无 native**;另案评审 |
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 防混淆话术
|
||||||
|
|
||||||
|
**SDK** = `craftlabs-auth-*` **库** + **Native** + Schema/示例;**平台** = 独立仓 **bootstrap Fat JAR**,无 BitAnswer Native,**版本号线独立**。并行以 **I5(BP-10)** 为契约首要对齐点,**I6** 为 SDK 冻结点。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 修订记录
|
||||||
|
|
||||||
|
|
||||||
|
| 日期 | 说明 |
|
||||||
|
| ---------- | --------------- |
|
||||||
|
| 2026-04-06 | 由并行 Task 产出并入库。 |
|
||||||
+62
-10
@@ -1,12 +1,11 @@
|
|||||||
# 比特授权云 · Java 语言接口(离线摘录)
|
# 比特授权云 · Java 语言接口(离线摘录)
|
||||||
|
|
||||||
> 本文档由官网页面离线摘录并整理排版,便于本地检索。官方地址:<https://doc.bitanswer.cn/docs/client-api/java-interface-v2/>
|
> 本文档由官网页面离线摘录并整理排版,便于本地检索。官方地址:[https://doc.bitanswer.cn/docs/client-api/java-interface-v2/](https://doc.bitanswer.cn/docs/client-api/java-interface-v2/)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 构造方法详细信息
|
## 构造方法详细信息
|
||||||
|
|
||||||
|
|
||||||
### BitAnswer
|
### BitAnswer
|
||||||
|
|
||||||
```java
|
```java
|
||||||
@@ -16,7 +15,6 @@ public BitAnswer(String url, String sn, LoginMode mode)
|
|||||||
|
|
||||||
## 认证
|
## 认证
|
||||||
|
|
||||||
|
|
||||||
### login / loginEx
|
### login / loginEx
|
||||||
|
|
||||||
```java
|
```java
|
||||||
@@ -30,6 +28,7 @@ void loginEx (String url,
|
|||||||
String xmlScope,
|
String xmlScope,
|
||||||
LoginMode mode)
|
LoginMode mode)
|
||||||
```
|
```
|
||||||
|
|
||||||
授权登录。初始化运行环境,获取操作句柄。必须在除升级函数之外的其它操作前执行。根据登录模式的不同可能需要连接授权服务器。
|
授权登录。初始化运行环境,获取操作句柄。必须在除升级函数之外的其它操作前执行。根据登录模式的不同可能需要连接授权服务器。
|
||||||
|
|
||||||
Login 等价于 LoginEx(featureId=0);当需要登录包含指定特征项的授权时才需要调用LoginEx。
|
Login 等价于 LoginEx(featureId=0);当需要登录包含指定特征项的授权时才需要调用LoginEx。
|
||||||
@@ -75,6 +74,7 @@ Login 等价于 LoginEx(featureId=0);当需要登录包含指定特征项
|
|||||||
void loginByToken (String url,
|
void loginByToken (String url,
|
||||||
String token)
|
String token)
|
||||||
```
|
```
|
||||||
|
|
||||||
帐号授权的登录接口。仅支持两种认证方式,这里是使用比特授权云平台自己产生的access_token进行认证。
|
帐号授权的登录接口。仅支持两种认证方式,这里是使用比特授权云平台自己产生的access_token进行认证。
|
||||||
|
|
||||||
### 参数
|
### 参数
|
||||||
@@ -95,6 +95,7 @@ void loginByTokenEx (String url,
|
|||||||
String idpGuid,
|
String idpGuid,
|
||||||
String grantType)
|
String grantType)
|
||||||
```
|
```
|
||||||
|
|
||||||
帐号授权的登录接口。仅支持两种认证方式,这里是使用OIDC协议产生的id_token认证。
|
帐号授权的登录接口。仅支持两种认证方式,这里是使用OIDC协议产生的id_token认证。
|
||||||
|
|
||||||
### 参数
|
### 参数
|
||||||
@@ -118,6 +119,7 @@ void loginByPassword (String url,
|
|||||||
String account,
|
String account,
|
||||||
String password)
|
String password)
|
||||||
```
|
```
|
||||||
|
|
||||||
通过用户名和密码登录帐号授权。
|
通过用户名和密码登录帐号授权。
|
||||||
|
|
||||||
### 参数
|
### 参数
|
||||||
@@ -137,6 +139,7 @@ void loginByPassword (String url,
|
|||||||
```java
|
```java
|
||||||
void logout()
|
void logout()
|
||||||
```
|
```
|
||||||
|
|
||||||
此函数用于释放上下文句柄,退出登录状态,与Login相关接口一一对应。
|
此函数用于释放上下文句柄,退出登录状态,与Login相关接口一一对应。
|
||||||
|
|
||||||
### 参数
|
### 参数
|
||||||
@@ -152,6 +155,7 @@ void logout()
|
|||||||
```java
|
```java
|
||||||
String revoke (String sn)
|
String revoke (String sn)
|
||||||
```
|
```
|
||||||
|
|
||||||
从客户端迁出已激活的浮动授权码。授权码迁出后,可以用于其它的客户端。根据输入参数的不同,本函数可用于在线或离线迁出。
|
从客户端迁出已激活的浮动授权码。授权码迁出后,可以用于其它的客户端。根据输入参数的不同,本函数可用于在线或离线迁出。
|
||||||
|
|
||||||
### 参数
|
### 参数
|
||||||
@@ -168,6 +172,7 @@ String revoke (String sn)
|
|||||||
void revokeOnline (String url,
|
void revokeOnline (String url,
|
||||||
String sn)
|
String sn)
|
||||||
```
|
```
|
||||||
|
|
||||||
从客户端迁出已激活的浮动授权码。授权码迁出后,可以用于其它的客户端。本函数需要进行网络连接。
|
从客户端迁出已激活的浮动授权码。授权码迁出后,可以用于其它的客户端。本函数需要进行网络连接。
|
||||||
|
|
||||||
### 参数
|
### 参数
|
||||||
@@ -184,6 +189,7 @@ void revokeOnline (String url,
|
|||||||
```java
|
```java
|
||||||
void removeSn (String sn)
|
void removeSn (String sn)
|
||||||
```
|
```
|
||||||
|
|
||||||
删除指定授权码在本机的授权数据。
|
删除指定授权码在本机的授权数据。
|
||||||
|
|
||||||
### 参数
|
### 参数
|
||||||
@@ -199,6 +205,7 @@ void removeSn (String sn)
|
|||||||
```java
|
```java
|
||||||
int heartbeat()
|
int heartbeat()
|
||||||
```
|
```
|
||||||
|
|
||||||
手动心跳,可以无限次调用,10s只会触发一次。
|
手动心跳,可以无限次调用,10s只会触发一次。
|
||||||
|
|
||||||
注:当自动心跳停止后,调用该接口可以尝试恢复自动心跳。
|
注:当自动心跳停止后,调用该接口可以尝试恢复自动心跳。
|
||||||
@@ -218,6 +225,7 @@ String sessionControl (String url,
|
|||||||
String sessionId,
|
String sessionId,
|
||||||
SessionCtlType type)
|
SessionCtlType type)
|
||||||
```
|
```
|
||||||
|
|
||||||
控制或设置session的信息。具体使用场景可参考《浏览器并发控制》文档。
|
控制或设置session的信息。具体使用场景可参考《浏览器并发控制》文档。
|
||||||
|
|
||||||
### 参数
|
### 参数
|
||||||
@@ -243,6 +251,7 @@ String sessionControl (String url,
|
|||||||
void setSessionState (int state,
|
void setSessionState (int state,
|
||||||
byte[] pReserved)
|
byte[] pReserved)
|
||||||
```
|
```
|
||||||
|
|
||||||
设置客户端的状态为空闲状态或繁忙状态或激活状态。
|
设置客户端的状态为空闲状态或繁忙状态或激活状态。
|
||||||
|
|
||||||
### 参数
|
### 参数
|
||||||
@@ -264,13 +273,13 @@ void setSessionState (int state,
|
|||||||
|
|
||||||
## 激活升级
|
## 激活升级
|
||||||
|
|
||||||
|
|
||||||
### updateOnline
|
### updateOnline
|
||||||
|
|
||||||
```java
|
```java
|
||||||
void updateOnline (String url,
|
void updateOnline (String url,
|
||||||
String sn)
|
String sn)
|
||||||
```
|
```
|
||||||
|
|
||||||
此函数用于与授权服务器在线连接,自动完成本地授权的升级操作。本函数需要进行网络连接。
|
此函数用于与授权服务器在线连接,自动完成本地授权的升级操作。本函数需要进行网络连接。
|
||||||
|
|
||||||
### 参数
|
### 参数
|
||||||
@@ -288,6 +297,7 @@ void updateOnline (String url,
|
|||||||
String getRequestInfo (String sn,
|
String getRequestInfo (String sn,
|
||||||
BindingType type)
|
BindingType type)
|
||||||
```
|
```
|
||||||
|
|
||||||
获取当前运行环境的升级请求码,用于发起本地授权激活及升级请求。
|
获取当前运行环境的升级请求码,用于发起本地授权激活及升级请求。
|
||||||
|
|
||||||
### 参数
|
### 参数
|
||||||
@@ -310,6 +320,7 @@ String getRequestInfo (String sn,
|
|||||||
```java
|
```java
|
||||||
String applyUpdateInfo (String updateInfo)
|
String applyUpdateInfo (String updateInfo)
|
||||||
```
|
```
|
||||||
|
|
||||||
应用升级码完成本地授权激活或升级。本函数必须在获取请求码的同一环境下执行。
|
应用升级码完成本地授权激活或升级。本函数必须在获取请求码的同一环境下执行。
|
||||||
|
|
||||||
### 参数
|
### 参数
|
||||||
@@ -327,6 +338,7 @@ String getUpdateInfo (String url,
|
|||||||
String sn,
|
String sn,
|
||||||
String requestInfo)
|
String requestInfo)
|
||||||
```
|
```
|
||||||
|
|
||||||
使用请求码与授权服务器进行连接,获取升级码。本函数需要进行网络连接。
|
使用请求码与授权服务器进行连接,获取升级码。本函数需要进行网络连接。
|
||||||
|
|
||||||
### 参数
|
### 参数
|
||||||
@@ -341,12 +353,12 @@ String getUpdateInfo (String url,
|
|||||||
|
|
||||||
## 特征项操作
|
## 特征项操作
|
||||||
|
|
||||||
|
|
||||||
### batchBegin
|
### batchBegin
|
||||||
|
|
||||||
```java
|
```java
|
||||||
void batchBegin (BatchMode mode)
|
void batchBegin (BatchMode mode)
|
||||||
```
|
```
|
||||||
|
|
||||||
开启批量Query模式,调用此API后调用的所有Query相关API,全部由Bit_batchEnd接口批量提交连接集团服务。
|
开启批量Query模式,调用此API后调用的所有Query相关API,全部由Bit_batchEnd接口批量提交连接集团服务。
|
||||||
|
|
||||||
### 参数
|
### 参数
|
||||||
@@ -365,6 +377,7 @@ void batchBegin (BatchMode mode)
|
|||||||
```java
|
```java
|
||||||
int[] batchEnd()
|
int[] batchEnd()
|
||||||
```
|
```
|
||||||
|
|
||||||
批量提交Query请求。
|
批量提交Query请求。
|
||||||
|
|
||||||
### 参数
|
### 参数
|
||||||
@@ -380,6 +393,7 @@ int[] batchEnd()
|
|||||||
```java
|
```java
|
||||||
int queryFeature (int featureId)
|
int queryFeature (int featureId)
|
||||||
```
|
```
|
||||||
|
|
||||||
开发商可以对软件的某个功能进行单独授权,当程序运行该模块时,可以通过该接口检查是否可用。
|
开发商可以对软件的某个功能进行单独授权,当程序运行该模块时,可以通过该接口检查是否可用。
|
||||||
|
|
||||||
### 参数
|
### 参数
|
||||||
@@ -395,6 +409,7 @@ int queryFeature (int featureId)
|
|||||||
```java
|
```java
|
||||||
int releaseFeature (int featureId)
|
int releaseFeature (int featureId)
|
||||||
```
|
```
|
||||||
|
|
||||||
释放所占用的用户数,该函数和Bit_QueryFeature一一对应。
|
释放所占用的用户数,该函数和Bit_QueryFeature一一对应。
|
||||||
|
|
||||||
### 参数
|
### 参数
|
||||||
@@ -413,6 +428,7 @@ int queryFeatureEx (int featureId,
|
|||||||
int required,
|
int required,
|
||||||
String scope)
|
String scope)
|
||||||
```
|
```
|
||||||
|
|
||||||
集团授权专用,该接口支持占用指定用户数,支持通过特征项版本进行检查。
|
集团授权专用,该接口支持占用指定用户数,支持通过特征项版本进行检查。
|
||||||
|
|
||||||
### 参数
|
### 参数
|
||||||
@@ -439,6 +455,7 @@ int releaseFeatureEx (int featureId,
|
|||||||
int consumed,
|
int consumed,
|
||||||
String scope)
|
String scope)
|
||||||
```
|
```
|
||||||
|
|
||||||
集团授权专用,释放指定的用户数。该接口与Bit_QueryFeatureEx对应。
|
集团授权专用,释放指定的用户数。该接口与Bit_QueryFeatureEx对应。
|
||||||
|
|
||||||
### 参数
|
### 参数
|
||||||
@@ -459,6 +476,7 @@ long queryFeatureEx2 (String featureName,
|
|||||||
int required,
|
int required,
|
||||||
String scope)
|
String scope)
|
||||||
```
|
```
|
||||||
|
|
||||||
可以使用“特征项名称 + 特征项版本”进行用户数占用,支持队列模式。
|
可以使用“特征项名称 + 特征项版本”进行用户数占用,支持队列模式。
|
||||||
|
|
||||||
### 参数
|
### 参数
|
||||||
@@ -486,6 +504,7 @@ long queryFeatureEx2 (String featureName,
|
|||||||
void releaseFeatureEx2 (byte[] ticket,
|
void releaseFeatureEx2 (byte[] ticket,
|
||||||
int consumed)
|
int consumed)
|
||||||
```
|
```
|
||||||
|
|
||||||
释放ticket所占用的用户数。该函数和Bit_QueryFeatureEx2一一对应。
|
释放ticket所占用的用户数。该函数和Bit_QueryFeatureEx2一一对应。
|
||||||
|
|
||||||
### 参数
|
### 参数
|
||||||
@@ -503,6 +522,7 @@ void releaseFeatureEx2 (byte[] ticket,
|
|||||||
int getFeatureInfo2 (String featureName,
|
int getFeatureInfo2 (String featureName,
|
||||||
String scope)
|
String scope)
|
||||||
```
|
```
|
||||||
|
|
||||||
检查特征项是否存在,不会占用授权码或特征项的用户数,获取特征项的剩余有效期。
|
检查特征项是否存在,不会占用授权码或特征项的用户数,获取特征项的剩余有效期。
|
||||||
|
|
||||||
### 参数
|
### 参数
|
||||||
@@ -520,6 +540,7 @@ int getFeatureInfo2 (String featureName,
|
|||||||
String getFeatureInfoEx2 (String featureName,
|
String getFeatureInfoEx2 (String featureName,
|
||||||
String scope)
|
String scope)
|
||||||
```
|
```
|
||||||
|
|
||||||
获取指定feature的信息,以XML格式返回。
|
获取指定feature的信息,以XML格式返回。
|
||||||
|
|
||||||
### 参数
|
### 参数
|
||||||
@@ -544,6 +565,7 @@ String getFeatureInfoEx2 (String featureName,
|
|||||||
String getTicketInfo (byte[] ticket,
|
String getTicketInfo (byte[] ticket,
|
||||||
int type)
|
int type)
|
||||||
```
|
```
|
||||||
|
|
||||||
获取ticket信息。
|
获取ticket信息。
|
||||||
|
|
||||||
### 参数
|
### 参数
|
||||||
@@ -568,6 +590,7 @@ int convertFeature (int featureId,
|
|||||||
int para3,
|
int para3,
|
||||||
int para4)
|
int para4)
|
||||||
```
|
```
|
||||||
|
|
||||||
使用“算法”类型的特征项对输入参数进行变换操作,得到唯一对应的4字节结果。
|
使用“算法”类型的特征项对输入参数进行变换操作,得到唯一对应的4字节结果。
|
||||||
|
|
||||||
### 参数
|
### 参数
|
||||||
@@ -588,6 +611,7 @@ int convertFeature (int featureId,
|
|||||||
byte[] encryptFeature (int featureId,
|
byte[] encryptFeature (int featureId,
|
||||||
byte[] pPlainBuffer)
|
byte[] pPlainBuffer)
|
||||||
```
|
```
|
||||||
|
|
||||||
使用“密钥”类型的特征项对输入的明文进行加密,返回密文结果。
|
使用“密钥”类型的特征项对输入的明文进行加密,返回密文结果。
|
||||||
|
|
||||||
### 参数
|
### 参数
|
||||||
@@ -605,6 +629,7 @@ byte[] encryptFeature (int featureId,
|
|||||||
byte[] decryptFeature (int featureId,
|
byte[] decryptFeature (int featureId,
|
||||||
byte[] pCipherBuffer)
|
byte[] pCipherBuffer)
|
||||||
```
|
```
|
||||||
|
|
||||||
使用“密钥”类型的特征项对输入的密文进行解密,返回明文结果。
|
使用“密钥”类型的特征项对输入的密文进行解密,返回明文结果。
|
||||||
|
|
||||||
### 参数
|
### 参数
|
||||||
@@ -621,6 +646,7 @@ byte[] decryptFeature (int featureId,
|
|||||||
```java
|
```java
|
||||||
int readFeature (int featureId)
|
int readFeature (int featureId)
|
||||||
```
|
```
|
||||||
|
|
||||||
读取特征项的数据内容,可用于“只读”和“读写”特征类型。
|
读取特征项的数据内容,可用于“只读”和“读写”特征类型。
|
||||||
|
|
||||||
### 参数
|
### 参数
|
||||||
@@ -637,6 +663,7 @@ int readFeature (int featureId)
|
|||||||
void writeFeature (int featureId,
|
void writeFeature (int featureId,
|
||||||
int featureValue)
|
int featureValue)
|
||||||
```
|
```
|
||||||
|
|
||||||
更新“读写”类型的特征项的数据内容。
|
更新“读写”类型的特征项的数据内容。
|
||||||
|
|
||||||
### 参数
|
### 参数
|
||||||
@@ -650,12 +677,12 @@ void writeFeature (int featureId,
|
|||||||
|
|
||||||
## 配置项操作
|
## 配置项操作
|
||||||
|
|
||||||
|
|
||||||
### getDataItem
|
### getDataItem
|
||||||
|
|
||||||
```java
|
```java
|
||||||
byte[] getDataItem (String dataItemName)
|
byte[] getDataItem (String dataItemName)
|
||||||
```
|
```
|
||||||
|
|
||||||
读取指定的配置项数据。
|
读取指定的配置项数据。
|
||||||
|
|
||||||
### 参数
|
### 参数
|
||||||
@@ -672,6 +699,7 @@ byte[] getDataItem (String dataItemName)
|
|||||||
void setDataItem (String dataItemName,
|
void setDataItem (String dataItemName,
|
||||||
byte[] dataItemValue)
|
byte[] dataItemValue)
|
||||||
```
|
```
|
||||||
|
|
||||||
创建或更新配置项。如果相同名称的配置项存在,则会更新其中的数据;否则将添加新的授权码配置项。
|
创建或更新配置项。如果相同名称的配置项存在,则会更新其中的数据;否则将添加新的授权码配置项。
|
||||||
|
|
||||||
### 参数
|
### 参数
|
||||||
@@ -688,6 +716,7 @@ void setDataItem (String dataItemName,
|
|||||||
```java
|
```java
|
||||||
int getDataItemNum()
|
int getDataItemNum()
|
||||||
```
|
```
|
||||||
|
|
||||||
此函数用于获取可访问配置项的数量,一般用于配置项的枚举操作。
|
此函数用于获取可访问配置项的数量,一般用于配置项的枚举操作。
|
||||||
|
|
||||||
### 参数
|
### 参数
|
||||||
@@ -703,6 +732,7 @@ int getDataItemNum()
|
|||||||
```java
|
```java
|
||||||
String getDataItemName (int index)
|
String getDataItemName (int index)
|
||||||
```
|
```
|
||||||
|
|
||||||
根据配置项索引获取其名称,一般用于配置项的枚举操作。
|
根据配置项索引获取其名称,一般用于配置项的枚举操作。
|
||||||
|
|
||||||
### 参数
|
### 参数
|
||||||
@@ -718,6 +748,7 @@ String getDataItemName (int index)
|
|||||||
```java
|
```java
|
||||||
void removeDataItem (String dataItemName)
|
void removeDataItem (String dataItemName)
|
||||||
```
|
```
|
||||||
|
|
||||||
删除指定的配置项。该操作无法删除通过控制台设置的产品配置项或模版配置项。
|
删除指定的配置项。该操作无法删除通过控制台设置的产品配置项或模版配置项。
|
||||||
|
|
||||||
### 参数
|
### 参数
|
||||||
@@ -730,12 +761,12 @@ void removeDataItem (String dataItemName)
|
|||||||
|
|
||||||
## 信息获取
|
## 信息获取
|
||||||
|
|
||||||
|
|
||||||
### getSessionInfo
|
### getSessionInfo
|
||||||
|
|
||||||
```java
|
```java
|
||||||
String getSessionInfo (SessionType type)
|
String getSessionInfo (SessionType type)
|
||||||
```
|
```
|
||||||
|
|
||||||
获取当前会话信息,以字符串形式返回。根据获取的内容不同,返回结果可能是XML格式或非XML格式。返回数据中的日期项已根据客户端的本地时区进行调整。如果Login时未指定SN,返回串为当前系统所有可用SN的综合结果。
|
获取当前会话信息,以字符串形式返回。根据获取的内容不同,返回结果可能是XML格式或非XML格式。返回数据中的日期项已根据客户端的本地时区进行调整。如果Login时未指定SN,返回串为当前系统所有可用SN的综合结果。
|
||||||
|
|
||||||
### 参数
|
### 参数
|
||||||
@@ -771,6 +802,7 @@ String getSessionInfo (SessionType type)
|
|||||||
String getInfo (String sn,
|
String getInfo (String sn,
|
||||||
InfoType type)
|
InfoType type)
|
||||||
```
|
```
|
||||||
|
|
||||||
获取本地授权信息。
|
获取本地授权信息。
|
||||||
|
|
||||||
### 参数
|
### 参数
|
||||||
@@ -800,6 +832,7 @@ String getServerInfo (String url,
|
|||||||
String scope,
|
String scope,
|
||||||
ServerInfoType type)
|
ServerInfoType type)
|
||||||
```
|
```
|
||||||
|
|
||||||
获取集团服务的license信息,数据以XML格式返回。调用此函数前客户端不需要执行登录操作。
|
获取集团服务的license信息,数据以XML格式返回。调用此函数前客户端不需要执行登录操作。
|
||||||
|
|
||||||
### 参数
|
### 参数
|
||||||
@@ -824,6 +857,7 @@ String getServerInfo (String url,
|
|||||||
```java
|
```java
|
||||||
int getVersion()
|
int getVersion()
|
||||||
```
|
```
|
||||||
|
|
||||||
获取客户端安全库版本号。
|
获取客户端安全库版本号。
|
||||||
|
|
||||||
### 参数
|
### 参数
|
||||||
@@ -841,6 +875,7 @@ void testBitService (String url,
|
|||||||
String sn,
|
String sn,
|
||||||
int featureId)
|
int featureId)
|
||||||
```
|
```
|
||||||
|
|
||||||
测试集团授权的特征项是否可用,不会占用授权码或特征项的用户数。
|
测试集团授权的特征项是否可用,不会占用授权码或特征项的用户数。
|
||||||
|
|
||||||
### 参数
|
### 参数
|
||||||
@@ -858,6 +893,7 @@ void testBitService (String url,
|
|||||||
```java
|
```java
|
||||||
String getProductPath()
|
String getProductPath()
|
||||||
```
|
```
|
||||||
|
|
||||||
获取授权存储目录。
|
获取授权存储目录。
|
||||||
|
|
||||||
### 参数
|
### 参数
|
||||||
@@ -873,6 +909,7 @@ String getProductPath()
|
|||||||
```java
|
```java
|
||||||
int getLastError()
|
int getLastError()
|
||||||
```
|
```
|
||||||
|
|
||||||
获取上一个API调用返回的错误码。
|
获取上一个API调用返回的错误码。
|
||||||
|
|
||||||
### 参数
|
### 参数
|
||||||
@@ -889,13 +926,13 @@ int getLastError()
|
|||||||
|
|
||||||
## 属性设置
|
## 属性设置
|
||||||
|
|
||||||
|
|
||||||
### setAttr
|
### setAttr
|
||||||
|
|
||||||
```java
|
```java
|
||||||
void setAttr (int type,
|
void setAttr (int type,
|
||||||
byte[] pValue)
|
byte[] pValue)
|
||||||
```
|
```
|
||||||
|
|
||||||
设置全局配置或当前会话的配置。
|
设置全局配置或当前会话的配置。
|
||||||
|
|
||||||
### 参数
|
### 参数
|
||||||
@@ -924,6 +961,7 @@ void setProxy (String hostName,
|
|||||||
String userId,
|
String userId,
|
||||||
String password)
|
String password)
|
||||||
```
|
```
|
||||||
|
|
||||||
设置代理服务的地址和端口。
|
设置代理服务的地址和端口。
|
||||||
|
|
||||||
### 参数
|
### 参数
|
||||||
@@ -943,6 +981,7 @@ void setProxy (String hostName,
|
|||||||
void setCustomInfo (int infoId,
|
void setCustomInfo (int infoId,
|
||||||
String infoData)
|
String infoData)
|
||||||
```
|
```
|
||||||
|
|
||||||
设置客户端运行自定义信息,需要在程序的最开始调用。infoId定义详见CustomInfo类型。
|
设置客户端运行自定义信息,需要在程序的最开始调用。infoId定义详见CustomInfo类型。
|
||||||
|
|
||||||
### 参数
|
### 参数
|
||||||
@@ -958,6 +997,7 @@ void setCustomInfo (int infoId,
|
|||||||
void setCustomInfo (int infoId,
|
void setCustomInfo (int infoId,
|
||||||
int infoData)
|
int infoData)
|
||||||
```
|
```
|
||||||
|
|
||||||
设置客户端运行自定义信息,需要在程序的最开始调用。infoId定义详见CustomInfo类型。
|
设置客户端运行自定义信息,需要在程序的最开始调用。infoId定义详见CustomInfo类型。
|
||||||
|
|
||||||
### 参数
|
### 参数
|
||||||
@@ -980,6 +1020,7 @@ void setCustomInfo (int infoId,
|
|||||||
```java
|
```java
|
||||||
void setRootPath (String rootPath)
|
void setRootPath (String rootPath)
|
||||||
```
|
```
|
||||||
|
|
||||||
设置授权文件的存储路径。
|
设置授权文件的存储路径。
|
||||||
|
|
||||||
### 参数
|
### 参数
|
||||||
@@ -997,6 +1038,7 @@ void setLocalServer (String hostName,
|
|||||||
int port,
|
int port,
|
||||||
int timeout)
|
int timeout)
|
||||||
```
|
```
|
||||||
|
|
||||||
设置集团服务的地址和端口。
|
设置集团服务的地址和端口。
|
||||||
|
|
||||||
### 参数
|
### 参数
|
||||||
@@ -1011,13 +1053,13 @@ void setLocalServer (String hostName,
|
|||||||
|
|
||||||
## 借出操作
|
## 借出操作
|
||||||
|
|
||||||
|
|
||||||
### getBorrowRequest
|
### getBorrowRequest
|
||||||
|
|
||||||
```java
|
```java
|
||||||
String getBorrowRequest (String sn,
|
String getBorrowRequest (String sn,
|
||||||
int durationDay)
|
int durationDay)
|
||||||
```
|
```
|
||||||
|
|
||||||
获取借出请求串,用来离线借出。
|
获取借出请求串,用来离线借出。
|
||||||
|
|
||||||
### 参数
|
### 参数
|
||||||
@@ -1035,6 +1077,7 @@ String getBorrowRequest (String sn,
|
|||||||
String getBorrowFeatureRequest (int durationDay,
|
String getBorrowFeatureRequest (int durationDay,
|
||||||
String borrowScope)
|
String borrowScope)
|
||||||
```
|
```
|
||||||
|
|
||||||
获取借出请求串,该接口支持指定特征项借出。
|
获取借出请求串,该接口支持指定特征项借出。
|
||||||
|
|
||||||
### 参数
|
### 参数
|
||||||
@@ -1056,6 +1099,7 @@ String getBorrowFeatureRequest (int durationDay,
|
|||||||
void checkOutSn (String url,
|
void checkOutSn (String url,
|
||||||
int featureId)
|
int featureId)
|
||||||
```
|
```
|
||||||
|
|
||||||
从授权服务器在线借出一个完整的授权码,以允许客户端服务器单独使用。被借出的授权码必须具有可借出属性,并在客户端成功借出后减少一个可用用户数。被借出的用户数在到期后将自动返还给服务器。
|
从授权服务器在线借出一个完整的授权码,以允许客户端服务器单独使用。被借出的授权码必须具有可借出属性,并在客户端成功借出后减少一个可用用户数。被借出的用户数在到期后将自动返还给服务器。
|
||||||
|
|
||||||
### 参数
|
### 参数
|
||||||
@@ -1075,6 +1119,7 @@ void checkOutSnEx (String url,
|
|||||||
String xmlScope,
|
String xmlScope,
|
||||||
int durationDays)
|
int durationDays)
|
||||||
```
|
```
|
||||||
|
|
||||||
从授权服务器在线借出一个完整的授权码,以允许客户端服务器单独使用。被借出的授权码必须具有可借出属性,并在客户端成功借出后减少一个可用用户数。被借出的用户数在到期后将自动返还给服务器。
|
从授权服务器在线借出一个完整的授权码,以允许客户端服务器单独使用。被借出的授权码必须具有可借出属性,并在客户端成功借出后减少一个可用用户数。被借出的用户数在到期后将自动返还给服务器。
|
||||||
|
|
||||||
### 参数
|
### 参数
|
||||||
@@ -1096,6 +1141,7 @@ void checkOut (String url,
|
|||||||
String featureList,
|
String featureList,
|
||||||
int durationDays)
|
int durationDays)
|
||||||
```
|
```
|
||||||
|
|
||||||
从授权服务器在线借出授权码或者特征项。
|
从授权服务器在线借出授权码或者特征项。
|
||||||
|
|
||||||
### 参数
|
### 参数
|
||||||
@@ -1113,6 +1159,7 @@ void checkOut (String url,
|
|||||||
type:借出类型。目前仅支持ws,表示从外网借出(针对云授权和集团授权)
|
type:借出类型。目前仅支持ws,表示从外网借出(针对云授权和集团授权)
|
||||||
sn:指定借出的SN
|
sn:指定借出的SN
|
||||||
```
|
```
|
||||||
|
|
||||||
- **featureList** - [IN] 特征项列表,指定借出的特征项列表,如果不传则借出整个SN。示例:
|
- **featureList** - [IN] 特征项列表,指定借出的特征项列表,如果不传则借出整个SN。示例:
|
||||||
|
|
||||||
```xml
|
```xml
|
||||||
@@ -1125,6 +1172,7 @@ sn:指定借出的SN
|
|||||||
当feature指定了借出时间,则会覆盖nDurationDays该时间,没有指定,则借出nDurationDays天。
|
当feature指定了借出时间,则会覆盖nDurationDays该时间,没有指定,则借出nDurationDays天。
|
||||||
借出feature时,如果指定了版本,则直接大于等于借出版本的特征项,只借一个,如果没有指定版本,则该特征项的所有版本都借出一个。
|
借出feature时,如果指定了版本,则直接大于等于借出版本的特征项,只借一个,如果没有指定版本,则该特征项的所有版本都借出一个。
|
||||||
```
|
```
|
||||||
|
|
||||||
- **durationDays** - [IN] 借出有效期,单位为天,需小于等于授权码允许的最大借出天数。
|
- **durationDays** - [IN] 借出有效期,单位为天,需小于等于授权码允许的最大借出天数。
|
||||||
|
|
||||||
### 返回
|
### 返回
|
||||||
@@ -1138,6 +1186,7 @@ void checkOutFeatures (String url,
|
|||||||
int[] featureList,
|
int[] featureList,
|
||||||
int durationDays)
|
int durationDays)
|
||||||
```
|
```
|
||||||
|
|
||||||
从集团授权服务器借出一组特征项,这些特征项必须包含在同一个授权码中。被借出的集团授权码必须具有可借出属性,并在客户端成功借出后减少一个可用用户数。被借出的用户数在到期后将自动返还给集团服务器。
|
从集团授权服务器借出一组特征项,这些特征项必须包含在同一个授权码中。被借出的集团授权码必须具有可借出属性,并在客户端成功借出后减少一个可用用户数。被借出的用户数在到期后将自动返还给集团服务器。
|
||||||
|
|
||||||
### 参数
|
### 参数
|
||||||
@@ -1156,6 +1205,7 @@ void checkOutFeatures (String url,
|
|||||||
void checkIn (String url,
|
void checkIn (String url,
|
||||||
int featureId)
|
int featureId)
|
||||||
```
|
```
|
||||||
|
|
||||||
提前返还从集团授权服务器借出的授权。要提前返还授权,该授权码必须具有允许提前返还属性。
|
提前返还从集团授权服务器借出的授权。要提前返还授权,该授权码必须具有允许提前返还属性。
|
||||||
|
|
||||||
### 参数
|
### 参数
|
||||||
@@ -1174,6 +1224,7 @@ void checkInEx (String url,
|
|||||||
int featureId,
|
int featureId,
|
||||||
String xmlScope)
|
String xmlScope)
|
||||||
```
|
```
|
||||||
|
|
||||||
提前返还从授权服务器借出的授权。要提前返还授权,该授权码必须具有允许提前返还属性。
|
提前返还从授权服务器借出的授权。要提前返还授权,该授权码必须具有允许提前返还属性。
|
||||||
|
|
||||||
### 参数
|
### 参数
|
||||||
@@ -1191,6 +1242,7 @@ void checkInEx (String url,
|
|||||||
```java
|
```java
|
||||||
void ApplyBorrowInfo (string borrowInfo)
|
void ApplyBorrowInfo (string borrowInfo)
|
||||||
```
|
```
|
||||||
|
|
||||||
客户端应用离线借出串,ApplyUpdateInfo可以兼容该接口。
|
客户端应用离线借出串,ApplyUpdateInfo可以兼容该接口。
|
||||||
|
|
||||||
### 参数
|
### 参数
|
||||||
@@ -1199,4 +1251,4 @@ void ApplyBorrowInfo (string borrowInfo)
|
|||||||
|
|
||||||
### 返回
|
### 返回
|
||||||
|
|
||||||
无
|
无
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,575 @@
|
|||||||
|
# 自研授权 SDK 实施计划(完整版)
|
||||||
|
|
||||||
|
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task.
|
||||||
|
|
||||||
|
**Goal:** 实现自研软件授权 SDK(Rust native + Java SDK + 平台签发后端),与比特安索双线共存,Provider 可扩展架构。
|
||||||
|
|
||||||
|
**Architecture:** 单 Rust cdylib(craftlabs_auth_core)通过 Provider trait 路由。许可证 AES-256-GCM 加密载荷 + RSA-256 签名。签发走 delivery-platform-api:8080,SDK 在线交互走 license-webhook-ingress:8081。
|
||||||
|
|
||||||
|
**Tech Stack:** Rust (craft-core, cdylib/staticlib), Java 17 (Maven), Spring Boot 3.4.x, PostgreSQL 15 + MyBatis-Plus, Flyway
|
||||||
|
|
||||||
|
**Reference Spec:** `docs/superpowers/specs/2026-05-18-selfhosted-licensing-sdk-design.md`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 0-1 速览(Phase 1 完整步骤见下方任务列表)
|
||||||
|
|
||||||
|
Phase 0(构建准备)+ Phase 1(离线核心:Rust crypto/device/cache/license 模块 + trait_provider + lib.rs 重构 + Java 配置扩展 + Schema + 数据库迁移 + LicenseSigner + LicenseController)共计 15 个任务。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 0.1: 更新 Cargo.toml
|
||||||
|
|
||||||
|
**Files:** Modify `native/craft-core/Cargo.toml`
|
||||||
|
|
||||||
|
- [ ] **Step 1: 重命名 lib + 添加依赖**
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[lib]
|
||||||
|
crate-type = ["cdylib", "staticlib"]
|
||||||
|
name = "craftlabs_auth_core"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
obfstr = "0.4"
|
||||||
|
sha2 = "0.10"
|
||||||
|
libloading = "0.8"
|
||||||
|
once_cell = "1"
|
||||||
|
rsa = { version = "0.9", features = ["sha2"] }
|
||||||
|
aes-gcm = "0.10"
|
||||||
|
hkdf = "0.12"
|
||||||
|
base64 = "0.22"
|
||||||
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
serde_json = "1"
|
||||||
|
chrono = { version = "0.4", features = ["serde"] }
|
||||||
|
|
||||||
|
[target.'cfg(target_os = "windows")'.dependencies]
|
||||||
|
windows-sys = { version = "0.52", features = ["Win32_System_Diagnostics_Debug"] }
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
sha2 = "0.10"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["security-hardening"]
|
||||||
|
security-hardening = []
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: `cargo check --manifest-path native/craft-core/Cargo.toml`**
|
||||||
|
- [ ] **Step 3: `git commit -m "build(native): rename lib to craftlabs_auth_core, add selfhosted deps"`**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 1.1-1.6: Rust 离线核心模块(Part 1 覆盖)
|
||||||
|
|
||||||
|
以下 6 个任务的完整代码见 Part 1(文档长度原因拆分)。Part 1 commit 记录:
|
||||||
|
|
||||||
|
| 任务 | 文件 | 内容 |
|
||||||
|
|------|------|------|
|
||||||
|
| 1.1 | `error.rs` | 扩展错误码(加密/签名/许可证状态) |
|
||||||
|
| 1.2 | `crypto.rs` | HKDF 密钥派生 + AES-256-GCM 加解密 + RSA-SHA256 验签 |
|
||||||
|
| 1.3 | `device.rs` | 4 层硬件指纹采集(Linux DMI/machine-id/FS/MAC),stability_score |
|
||||||
|
| 1.4 | `provider_selfhosted/cache.rs` | 本地加密缓存(AES 加密写入磁盘 + 心跳 checkpoint) |
|
||||||
|
| 1.5 | `provider_selfhosted/license.rs` | 验签→解密→时间校验→离线宽限期 |
|
||||||
|
| 1.6 | `provider_selfhosted/mod.rs` | SelfHostedProvider struct + 初始化/离线校验/hasFeature |
|
||||||
|
|
||||||
|
> Part 1 详细步骤已写入 Git:commit `d7469af`(spec)及之前。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 1.7: trait_provider.rs(Provider trait + 路由)
|
||||||
|
|
||||||
|
**Files:** Create `native/craft-core/src/trait_provider.rs`
|
||||||
|
|
||||||
|
- [ ] **Step 1: 创建 trait_provider.rs**
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use crate::{CraftContext, LicenseInfo, error::LicenseError};
|
||||||
|
|
||||||
|
pub struct LicenseStatus {
|
||||||
|
pub licensed: bool,
|
||||||
|
pub not_after: Option<i64>,
|
||||||
|
pub features: HashMap<String, bool>,
|
||||||
|
pub device_count: u32,
|
||||||
|
pub max_devices: u32,
|
||||||
|
pub heartbeat_due: Option<i64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ActivateResponse {
|
||||||
|
pub success: bool,
|
||||||
|
pub device_id: String,
|
||||||
|
pub license_payload: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct HeartbeatResponse {
|
||||||
|
pub valid: bool,
|
||||||
|
pub lease_until: Option<i64>,
|
||||||
|
pub update_available: bool,
|
||||||
|
pub new_license_payload: Option<Vec<u8>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait Provider: Send + Sync {
|
||||||
|
fn initialize(&mut self, ctx: &CraftContext, config_json: &str) -> Result<(), LicenseError>;
|
||||||
|
fn activate(&self, ctx: &CraftContext, license_key: &str) -> Result<ActivateResponse, LicenseError>;
|
||||||
|
fn check_license(&self, ctx: &CraftContext) -> Result<LicenseStatus, LicenseError>;
|
||||||
|
fn heartbeat(&self, ctx: &CraftContext) -> Result<HeartbeatResponse, LicenseError>;
|
||||||
|
fn has_feature(&self, ctx: &CraftContext, name: &str) -> bool;
|
||||||
|
fn release(&mut self, ctx: &CraftContext) -> Result<(), LicenseError>;
|
||||||
|
fn get_license_info(&self, ctx: &CraftContext) -> LicenseInfo;
|
||||||
|
fn close(&mut self);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: `cargo check` → `git commit -m "feat(rust): add Provider trait abstraction"`**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 1.8: 重构 lib.rs(C ABI 路由到 Provider trait)
|
||||||
|
|
||||||
|
**Files:** Modify `native/craft-core/src/lib.rs`
|
||||||
|
|
||||||
|
- [ ] **Step 1: 替换 lib.rs**
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use std::ffi::CStr;
|
||||||
|
use std::os::raw::c_char;
|
||||||
|
use std::ptr;
|
||||||
|
|
||||||
|
mod trait_provider;
|
||||||
|
mod error;
|
||||||
|
mod crypto;
|
||||||
|
mod device;
|
||||||
|
mod provider_selfhosted;
|
||||||
|
mod security;
|
||||||
|
mod session;
|
||||||
|
|
||||||
|
pub use trait_provider::{Provider, ActivateResponse, HeartbeatResponse, LicenseStatus};
|
||||||
|
pub use error::LicenseError;
|
||||||
|
|
||||||
|
pub struct CraftContext {
|
||||||
|
pub provider: Option<Box<dyn Provider>>,
|
||||||
|
pub initialized: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)] pub struct CraftResult { pub success: i32, pub message: *const c_char }
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct LicenseInfo {
|
||||||
|
pub is_licensed: i32,
|
||||||
|
pub expiration_date: i64,
|
||||||
|
pub feature_names: *const *const c_char,
|
||||||
|
pub feature_values: *const i32,
|
||||||
|
pub feature_count: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CraftContext { pub fn new() -> Self { CraftContext { provider: None, initialized: false } } }
|
||||||
|
|
||||||
|
unsafe fn c_str_to_string(ptr: *const c_char) -> String {
|
||||||
|
if ptr.is_null() { String::new() } else { CStr::from_ptr(ptr).to_string_lossy().into_owned() }
|
||||||
|
}
|
||||||
|
|
||||||
|
static OK_MSG: &[u8] = b"ok\0";
|
||||||
|
fn ok_result() -> CraftResult { CraftResult { success: 1, message: OK_MSG.as_ptr() as *const c_char } }
|
||||||
|
fn craft_fail() -> CraftResult {
|
||||||
|
static FAIL: &[u8] = b"failure\0";
|
||||||
|
CraftResult { success: 0, message: FAIL.as_ptr() as *const c_char }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_provider(config: &str) -> String {
|
||||||
|
serde_json::from_str::<serde_json::Value>(config).ok()
|
||||||
|
.and_then(|v| v.get("provider").and_then(|p| p.as_str().map(|s| s.to_string())))
|
||||||
|
.unwrap_or_else(|| "selfhosted".to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn craft_initialize(config_json: *const c_char) -> *mut CraftContext {
|
||||||
|
let config_str = unsafe { c_str_to_string(config_json) };
|
||||||
|
let mut ctx = Box::new(CraftContext::new());
|
||||||
|
let mut provider: Box<dyn Provider> = match parse_provider(&config_str).as_str() {
|
||||||
|
"selfhosted" => Box::new(provider_selfhosted::SelfHostedProvider::new()),
|
||||||
|
_ => Box::new(provider_selfhosted::SelfHostedProvider::new()),
|
||||||
|
};
|
||||||
|
if let Err(_) = provider.initialize(&ctx, &config_str) {
|
||||||
|
ctx.initialized = false;
|
||||||
|
} else {
|
||||||
|
ctx.provider = Some(provider);
|
||||||
|
ctx.initialized = true;
|
||||||
|
}
|
||||||
|
Box::into_raw(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn craft_activate(handle: *mut CraftContext, license_key: *const c_char, _: *const c_char) -> CraftResult {
|
||||||
|
if handle.is_null() { return craft_fail(); }
|
||||||
|
let ctx = unsafe { &*handle };
|
||||||
|
let key = unsafe { c_str_to_string(license_key) };
|
||||||
|
ctx.provider.as_ref().and_then(|p| p.activate(ctx, &key).ok()).map_or_else(craft_fail, |_| ok_result())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn craft_check_license(handle: *mut CraftContext) -> CraftResult {
|
||||||
|
if handle.is_null() { return craft_fail(); }
|
||||||
|
let ctx = unsafe { &*handle };
|
||||||
|
ctx.provider.as_ref().and_then(|p| p.check_license(ctx).ok())
|
||||||
|
.map_or_else(craft_fail, |s| if s.licensed { ok_result() } else { craft_fail() })
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn craft_get_license_info(handle: *mut CraftContext) -> *mut LicenseInfo {
|
||||||
|
if handle.is_null() { return ptr::null_mut(); }
|
||||||
|
let ctx = unsafe { &*handle };
|
||||||
|
ctx.provider.as_ref().map(|p| p.get_license_info(ctx))
|
||||||
|
.map_or(ptr::null_mut(), |info| Box::into_raw(Box::new(info)))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle] pub extern "C" fn craft_free_license_info(info: *mut LicenseInfo) {
|
||||||
|
if !info.is_null() { unsafe { drop(Box::from_raw(info)); } }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn craft_has_feature(handle: *mut CraftContext, feature_name: *const c_char) -> i32 {
|
||||||
|
if handle.is_null() { return 0; }
|
||||||
|
let ctx = unsafe { &*handle };
|
||||||
|
let name = unsafe { c_str_to_string(feature_name) };
|
||||||
|
ctx.provider.as_ref().map_or(0, |p| if p.has_feature(ctx, &name) { 1 } else { 0 })
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle] pub extern "C" fn craft_release(handle: *mut CraftContext) -> CraftResult {
|
||||||
|
if handle.is_null() { return craft_fail(); }
|
||||||
|
let ctx = unsafe { &mut *handle };
|
||||||
|
ctx.provider.as_mut().and_then(|p| p.release(ctx).ok());
|
||||||
|
ok_result()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle] pub extern "C" fn craft_heartbeat(handle: *mut CraftContext) -> CraftResult {
|
||||||
|
if handle.is_null() { return craft_fail(); }
|
||||||
|
let ctx = unsafe { &*handle };
|
||||||
|
ctx.provider.as_ref().and_then(|p| p.heartbeat(ctx).ok()).map_or_else(craft_fail, |_| ok_result())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn craft_destroy(handle: *mut CraftContext) {
|
||||||
|
if !handle.is_null() {
|
||||||
|
unsafe { let ctx = &mut *handle; if let Some(ref mut p) = ctx.provider { p.close(); } drop(Box::from_raw(handle)); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: 在 provider_selfhosted/mod.rs 追加 Provider trait 实现**
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use crate::trait_provider::{Provider, ActivateResponse, HeartbeatResponse, LicenseStatus};
|
||||||
|
|
||||||
|
impl Provider for SelfHostedProvider {
|
||||||
|
fn initialize(&mut self, _ctx: &CraftContext, config_json: &str) -> Result<(), LicenseError> {
|
||||||
|
let cfg: serde_json::Value = serde_json::from_str(config_json).map_err(|_| LicenseError::InvalidFormat("config"))?;
|
||||||
|
let sh = cfg.get("selfhosted").ok_or(LicenseError::ConfigMissing("selfhosted"))?;
|
||||||
|
let base_url = sh.get("baseUrl").and_then(|v| v.as_str()).unwrap_or("").to_string();
|
||||||
|
let tenant_key = sh.get("tenantKey").and_then(|v| v.as_str()).unwrap_or("").to_string();
|
||||||
|
let ogd = sh.get("offlineGraceDays").and_then(|v| v.as_u64()).unwrap_or(7) as u32;
|
||||||
|
let hih = sh.get("heartbeatIntervalHours").and_then(|v| v.as_u64()).unwrap_or(24) as u32;
|
||||||
|
let pubkey = option_env!("CRAFTLABS_SELFHOSTED_PUBKEY").unwrap_or("").to_string();
|
||||||
|
self.initialize(base_url, tenant_key, ogd, hih, pubkey)
|
||||||
|
}
|
||||||
|
fn activate(&self, _ctx: &CraftContext, _lk: &str) -> Result<ActivateResponse, LicenseError> {
|
||||||
|
Err(LicenseError::Network("online activation not yet implemented".into()))
|
||||||
|
}
|
||||||
|
fn check_license(&self, _ctx: &CraftContext) -> Result<LicenseStatus, LicenseError> {
|
||||||
|
self.check_license_offline()?;
|
||||||
|
let c = self.cache.license.as_ref().ok_or(LicenseError::NoCachedLicense)?;
|
||||||
|
Ok(LicenseStatus { licensed: true, not_after: c.not_after, features: c.features.clone(), device_count: 0, max_devices: c.max_devices, heartbeat_due: None })
|
||||||
|
}
|
||||||
|
fn heartbeat(&self, _ctx: &CraftContext) -> Result<HeartbeatResponse, LicenseError> {
|
||||||
|
Err(LicenseError::Network("heartbeat not yet implemented".into()))
|
||||||
|
}
|
||||||
|
fn has_feature(&self, _ctx: &CraftContext, name: &str) -> bool { self.has_feature_offline(name) }
|
||||||
|
fn release(&mut self, _ctx: &CraftContext) -> Result<(), LicenseError> { Ok(()) }
|
||||||
|
fn get_license_info(&self, _ctx: &CraftContext) -> LicenseInfo { self.get_license_info_offline() }
|
||||||
|
fn close(&mut self) { let _ = self.persist_cache(); }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 3: `cargo check` + `cargo test` → `git commit -m "feat(rust): refactor C ABI to route through Provider trait"`**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 1.9-1.10: Java 配置 + Schema 扩展
|
||||||
|
|
||||||
|
**Files:** Modify `SelfhostedConfigSection.java`, `FeatureMapping.java`, `schemas/craftlabs-auth-config.schema.json`, `examples/config/school.selfhosted.json`
|
||||||
|
|
||||||
|
- [ ] **Step 1: SelfhostedConfigSection 新增字段**
|
||||||
|
|
||||||
|
```java
|
||||||
|
public record SelfhostedConfigSection(
|
||||||
|
@JsonProperty("baseUrl") String baseUrl,
|
||||||
|
@JsonProperty("tenantKey") String tenantKey,
|
||||||
|
@JsonProperty("offlineGraceDays") Integer offlineGraceDays, // ★ 新增
|
||||||
|
@JsonProperty("heartbeatIntervalHours") Integer heartbeatIntervalHours, // ★ 新增
|
||||||
|
@JsonProperty("publicKeyPem") String publicKeyPem) { // ★ 新增
|
||||||
|
public SelfhostedConfigSection {
|
||||||
|
offlineGraceDays = offlineGraceDays != null ? offlineGraceDays : 7;
|
||||||
|
heartbeatIntervalHours = heartbeatIntervalHours != null ? heartbeatIntervalHours : 24;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: FeatureMapping 新增 selfhostedFeatureKey**
|
||||||
|
|
||||||
|
```java
|
||||||
|
public record FeatureMapping(
|
||||||
|
@JsonProperty("bitanswerFeatureId") Integer bitanswerFeatureId,
|
||||||
|
@JsonProperty("bitanswerFeatureName") String bitanswerFeatureName,
|
||||||
|
@JsonProperty("selfhostedFeatureKey") String selfhostedFeatureKey) {} // ★ 新增
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 3: Schema 扩展 selfhosted properties**
|
||||||
|
|
||||||
|
```json
|
||||||
|
"offlineGraceDays": { "type": "integer", "minimum": 0, "maximum": 365, "default": 7 },
|
||||||
|
"heartbeatIntervalHours": { "type": "integer", "minimum": 1, "maximum": 720, "default": 24 },
|
||||||
|
"publicKeyPem": { "type": "string" }
|
||||||
|
```
|
||||||
|
|
||||||
|
同步扩展 `features.additionalProperties` 增加 `selfhostedFeatureKey`。
|
||||||
|
|
||||||
|
- [ ] **Step 4: 更新 `examples/config/school.selfhosted.json`**
|
||||||
|
- [ ] **Step 5: `mvn compile -pl craftlabs-auth-core` + Schema 校验 → `git commit -m "feat: extend Java config and Schema for selfhosted SDK"`**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 1.11: 平台数据库迁移(Flyway V2)
|
||||||
|
|
||||||
|
**Files:** Create `services/delivery-platform-api/src/main/resources/db/migration/V2__selfhosted_licensing.sql`
|
||||||
|
|
||||||
|
核心 6 张表:`platform_license_policies`, `platform_license_keys`, `platform_licenses`, `platform_license_features`, `platform_license_activations`, `platform_license_heartbeats`。完整 DDL 见设计文档 §3.6。
|
||||||
|
|
||||||
|
- [ ] **Step: `docker compose up -d postgres` + `mvn flyway:migrate` → commit**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 1.12: LicenseSigner.java(RSA 签名 + AES 加密签发)
|
||||||
|
|
||||||
|
**Files:** Create `services/delivery-platform-api/src/main/java/cn/craftlabs/platform/api/license/LicenseSigner.java`
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Service
|
||||||
|
public class LicenseSigner {
|
||||||
|
private static final ObjectMapper MAPPER = new ObjectMapper();
|
||||||
|
private static final String EMBEDDED_SALT = "craftlabs-license-salt-v1-2026-05";
|
||||||
|
|
||||||
|
public String sign(SignedLicensePayload payload, PlatformLicenseKey key) throws Exception {
|
||||||
|
byte[] payloadJson = MAPPER.writeValueAsBytes(payload);
|
||||||
|
byte[] aesKey = deriveAesKey(EMBEDDED_SALT, payload.getLicenseId());
|
||||||
|
byte[] encrypted = aesGcmEncrypt(aesKey, payloadJson);
|
||||||
|
String payloadB64 = Base64.getUrlEncoder().withoutPadding().encodeToString(encrypted);
|
||||||
|
|
||||||
|
PrivateKey privateKey = loadPrivateKey(key.getPrivateKey());
|
||||||
|
Signature sig = Signature.getInstance("SHA256withRSA");
|
||||||
|
sig.initSign(privateKey);
|
||||||
|
sig.update(encrypted);
|
||||||
|
String sigB64 = Base64.getUrlEncoder().withoutPadding().encodeToString(sig.sign());
|
||||||
|
|
||||||
|
LicenseDocument doc = LicenseDocument.builder()
|
||||||
|
.version(1).licenseId(payload.getLicenseId())
|
||||||
|
.payload(payloadB64)
|
||||||
|
.signature(SignatureBlock.builder().algorithm("RS256").keyId(key.getKeyId()).value(sigB64).build())
|
||||||
|
.build();
|
||||||
|
return MAPPER.writeValueAsString(doc);
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] deriveAesKey(String salt, String licenseId) throws Exception {
|
||||||
|
Mac mac = Mac.getInstance("HmacSHA256");
|
||||||
|
mac.init(new SecretKeySpec(salt.getBytes("UTF-8"), "HmacSHA256"));
|
||||||
|
byte[] prk = mac.doFinal(new byte[0]);
|
||||||
|
mac.init(new SecretKeySpec(prk, "HmacSHA256"));
|
||||||
|
mac.update(licenseId.getBytes("UTF-8"));
|
||||||
|
mac.update((byte) 0x01);
|
||||||
|
return mac.doFinal();
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] aesGcmEncrypt(byte[] key, byte[] plaintext) throws Exception {
|
||||||
|
byte[] nonce = new byte[12]; new SecureRandom().nextBytes(nonce);
|
||||||
|
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
|
||||||
|
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key, "AES"), new GCMParameterSpec(128, nonce));
|
||||||
|
byte[] ct = cipher.doFinal(plaintext);
|
||||||
|
byte[] r = new byte[12 + ct.length]; System.arraycopy(nonce, 0, r, 0, 12); System.arraycopy(ct, 0, r, 12, ct.length);
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step: `mvn compile` → `git commit -m "feat(platform): add LicenseSigner for RSA+AES license issuance"`**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 1.13-1.14: Entity/Mapper + LicenseService + LicenseController
|
||||||
|
|
||||||
|
**Files:** Create 5 组 Entity+Mapper(`persistence/license/`)+ LicenseService + LicenseController
|
||||||
|
|
||||||
|
核心 API:
|
||||||
|
|
||||||
|
```
|
||||||
|
POST /api/v1/licenses → 签发许可证(需 LICENSE_OPS 角色)
|
||||||
|
GET /api/v1/licenses/{id} → 查询详情
|
||||||
|
POST /api/v1/licenses/{id}/revoke → 吊销
|
||||||
|
```
|
||||||
|
|
||||||
|
SecurityConfig 新增:`.requestMatchers("/api/v1/licenses/**").hasAnyRole("LICENSE_OPS", "ADMIN")`
|
||||||
|
|
||||||
|
- [ ] **Step: `mvn compile` + `mvn test -Dtest=LicenseControllerTest` → commit**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 1.15: Phase 1 集成验证
|
||||||
|
|
||||||
|
- [ ] **Step 1: 生成 RSA 密钥对**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
openssl genpkey -algorithm RSA -out private_key.pem -pkeyopt rsa_keygen_bits:2048
|
||||||
|
openssl rsa -pubout -in private_key.pem -out public_key.pem
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: `export CRAFTLABS_SELFHOSTED_PUBKEY="$(cat public_key.pem)"`**
|
||||||
|
- [ ] **Step 3: `cargo test --manifest-path native/craft-core/Cargo.toml` → all pass**
|
||||||
|
- [ ] **Step 4: 启动服务 + curl 签发 → Rust 离线验签通过**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -X POST http://localhost:8080/api/v1/licenses \
|
||||||
|
-H "Authorization: Bearer $TOKEN" -d '{"tenantId":"test",...}'
|
||||||
|
# 将返回的 license.json 保存 → Rust 集成测 validate
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 5: Commit**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 2: 在线激活(3 个任务)
|
||||||
|
|
||||||
|
### Task 2.1: protocol.rs(HTTP 请求/响应序列化)
|
||||||
|
|
||||||
|
**Files:** Create `native/craft-core/src/provider_selfhosted/protocol.rs`
|
||||||
|
|
||||||
|
Serialize/Deserialize DTOs:`ActivateRequest`, `ActivateResponseBody`, `HeartbeatRequest/Response`, `CheckRequest/Response`, `ReleaseRequest/Response`。HMAC 签名工具函数 `build_hmac_signature`。
|
||||||
|
|
||||||
|
新增依赖:`reqwest = "0.12"`, `hex = "0.4"`, `hmac = "0.12"`, `rand = "0.8"`, `tokio = "1"`
|
||||||
|
|
||||||
|
- [ ] **Step: `cargo check` → commit**
|
||||||
|
|
||||||
|
### Task 2.2: activate.rs(在线激活 HTTPS 请求)
|
||||||
|
|
||||||
|
**Files:** Create `native/craft-core/src/provider_selfhosted/activate.rs`
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub async fn online_activate(config: &SelfHostedConfig, fp: &DeviceFingerprint, license_key: &str)
|
||||||
|
-> Result<ActivateResponse, LicenseError>
|
||||||
|
{
|
||||||
|
let req = protocol::ActivateRequest { license_key: license_key.to_string(), device_fingerprint: /* fp */ };
|
||||||
|
let body = serde_json::to_string(&req).unwrap();
|
||||||
|
// POST /license/v1/activate + HMAC headers ...
|
||||||
|
match resp.status() {
|
||||||
|
200 => Ok(parse_body),
|
||||||
|
409 => Err(DeviceLimitReached),
|
||||||
|
403 => Err(LicenseRevoked),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
在 `provider_selfhosted/mod.rs` 的 `activate()` 中调用 `tokio::runtime::Handle::current().block_on(online_activate(...))`。
|
||||||
|
|
||||||
|
- [ ] **Step: `cargo test` → commit**
|
||||||
|
|
||||||
|
### Task 2.3: Webhook 侧 LicenseController + ActivateService
|
||||||
|
|
||||||
|
**Files:** Create license endpoints in `services/license-webhook-ingress`
|
||||||
|
|
||||||
|
- `NonceValidator.java`: Nonce 去重(ConcurrentHashMap + 5min 窗口)
|
||||||
|
- `LicenseActivateService.java`: 查许可证状态 + 终端配额 + 设备匹配 + 下发生效
|
||||||
|
- `LicenseController.java`: `POST /license/v1/activate`(Bearer tenantKey + HMAC header 校验)
|
||||||
|
|
||||||
|
- [ ] **Step: `mvn compile` + `mvn test` → commit**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 3: 心跳 + 离线兜底(2 个任务)
|
||||||
|
|
||||||
|
### Task 3.1: heartbeat.rs(在线心跳 + 吊销检测)
|
||||||
|
|
||||||
|
**Files:** Create `native/craft-core/src/provider_selfhosted/heartbeat.rs`
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub async fn online_heartbeat(config: &SelfHostedConfig, device_hash: &str, license_key: &str)
|
||||||
|
-> Result<HeartbeatResponse, LicenseError>
|
||||||
|
{
|
||||||
|
// POST /license/v1/heartbeat →
|
||||||
|
// 200: 更新租约 + 可选下发新许可证
|
||||||
|
// 410: LicenseRevoked
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
更新 mod.rs heartbeat() 实现。
|
||||||
|
|
||||||
|
### Task 3.2: Webhook HeartbeatService + CheckService + ReleaseService
|
||||||
|
|
||||||
|
**Files:** Append to webhook `LicenseController`
|
||||||
|
|
||||||
|
- `POST /license/v1/heartbeat` — 更新 `last_heartbeat`,返回租约续期时间
|
||||||
|
- `POST /license/v1/check` — 在线校验许可证有效性
|
||||||
|
- `POST /license/v1/release` — 释放终端占用
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 4: 完善与生产加固(5 个任务)
|
||||||
|
|
||||||
|
### Task 4.1: build.rs 嵌入 RSA 公钥
|
||||||
|
|
||||||
|
**Files:** Modify `native/craft-core/build.rs`, Create `native/craft-core/embedded/pubkey.pem`
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// build.rs
|
||||||
|
fn main() {
|
||||||
|
let pubkey = fs::read_to_string("embedded/pubkey.pem").unwrap_or_default();
|
||||||
|
println!("cargo:rustc-env=CRAFTLABS_SELFHOSTED_PUBKEY={}", pubkey.trim());
|
||||||
|
println!("cargo:rerun-if-changed=embedded/pubkey.pem");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
将生产公钥放入 `embedded/pubkey.pem`(不提交私钥)。
|
||||||
|
|
||||||
|
### Task 4.2: SelfHostedAuthProvider 加载正确库
|
||||||
|
|
||||||
|
**Files:** Modify `java/craftlabs-auth-selfhosted/.../SelfHostedAuthProvider.java`
|
||||||
|
|
||||||
|
改 `System.loadLibrary("craftlabs_auth_bitanswer")` → `System.loadLibrary("craftlabs_auth_core")`。
|
||||||
|
|
||||||
|
### Task 4.3: MultiProviderSmokeTest
|
||||||
|
|
||||||
|
**Files:** Create `java/craftlabs-auth-tests/.../MultiProviderSmokeTest.java`
|
||||||
|
|
||||||
|
测试:初始化 selfhosted → 离线校验 → close → 初始化 bitanswer → close,双线不冲突。
|
||||||
|
|
||||||
|
### Task 4.4: CI 适配
|
||||||
|
|
||||||
|
- `ci-native.yml`: 更新 artifact name,确保 `embedded/pubkey.pem` 存在
|
||||||
|
- `ci-platform.yml`: 新增 license 模块测试 Job
|
||||||
|
|
||||||
|
### Task 4.5: 最终集成验证
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo test --manifest-path native/craft-core/Cargo.toml # Rust 全量
|
||||||
|
mvn -f java/pom.xml verify # Java SDK
|
||||||
|
mvn -f services/pom.xml verify # 平台
|
||||||
|
docker compose -f services/docker-compose.yml up -d postgres # 起库
|
||||||
|
# curl 签发 → Rust 离线验签 → curl 在线激活 → 终端满 409 → 吊销 410
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 总结
|
||||||
|
|
||||||
|
| Phase | 任务数 | 核心交付 |
|
||||||
|
|-------|--------|----------|
|
||||||
|
| P0 | 1 | Cargo.toml 依赖 |
|
||||||
|
| P1 | 14 | Rust 离线核心 + Java 配置 + 平台签发后端 |
|
||||||
|
| P2 | 3 | 在线激活(Rust HTTPS + Webhook 端点) |
|
||||||
|
| P3 | 2 | 心跳 + 离线兜底 + 吊销检测 |
|
||||||
|
| P4 | 5 | build.rs 嵌入 + 测试 + CI |
|
||||||
|
| **合计** | **25** | |
|
||||||
@@ -0,0 +1,282 @@
|
|||||||
|
# 剩余缺口 WBS 任务拆解与排期计划
|
||||||
|
|
||||||
|
> **基于**:PRD vs 代码实现状态完整审计(2026-05-25)
|
||||||
|
> **范围**:全部 ⚠️ 部分实现 + ❌ 未实现 功能点
|
||||||
|
> **涉及**:平台后端/前端 + 客户端 SDK
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 迭代路线图
|
||||||
|
|
||||||
|
| 迭代 | 周期 | 聚焦 | 任务数 | 估计工时 |
|
||||||
|
|------|------|------|--------|---------|
|
||||||
|
| **I14** | T0+1W | P0 缺口修复 + 审计修复 | 7 | 12h |
|
||||||
|
| **I15** | T0+2W | P1 增强(M2/M3/M4) | 6 | 14h |
|
||||||
|
| **I16** | T0+3W | P1 增强(M5/M6/M8/M11) | 8 | 16h |
|
||||||
|
| **I17** | T0+4W | SDK JNI 桥接 + 单元测试 | 3 | 16h |
|
||||||
|
| **V2.1** | T0+6W | P2 功能 + 其他语言封装 | 8 | 20h |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 迭代 I14:P0 缺口修复(12h)
|
||||||
|
|
||||||
|
### I14-T1: M1-F06 项目干系人(2h)
|
||||||
|
|
||||||
|
**文件:**
|
||||||
|
- Create: `services/.../db/migration/V17__project_stakeholder.sql`
|
||||||
|
- Create: `services/.../persistence/project/PlatformProjectStakeholder.java`
|
||||||
|
- Create: `services/.../persistence/project/PlatformProjectStakeholderMapper.java`
|
||||||
|
- Create: `services/.../web/dto/StakeholderRequest.java`
|
||||||
|
- Create: `services/.../web/dto/StakeholderResponse.java`
|
||||||
|
- Modify: `services/.../project/ProjectController.java`
|
||||||
|
- Modify: `services/.../service/ProjectService.java`
|
||||||
|
- Create: `web/.../views/ProjectStakeholderDialog.vue`
|
||||||
|
- Modify: `web/.../views/ProjectsView.vue`
|
||||||
|
|
||||||
|
**DB:**
|
||||||
|
```sql
|
||||||
|
CREATE TABLE platform_project_stakeholder (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
project_id BIGINT NOT NULL REFERENCES platform_project(id),
|
||||||
|
contact_name VARCHAR(128) NOT NULL,
|
||||||
|
contact_role VARCHAR(64),
|
||||||
|
phone VARCHAR(32),
|
||||||
|
email VARCHAR(128),
|
||||||
|
is_internal BOOLEAN DEFAULT FALSE,
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
**工作量:** 后端 2h + 前端 1h
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### I14-T2: M2-F01 合同日期字段(1h)
|
||||||
|
|
||||||
|
**文件:**
|
||||||
|
- Create: `services/.../db/migration/V18__contract_date_fields.sql`
|
||||||
|
- Modify: `services/.../persistence/contract/PlatformContract.java`
|
||||||
|
- Modify: `services/.../web/dto/ContractCreateRequest.java`
|
||||||
|
- Modify: `services/.../web/dto/ContractUpdateRequest.java`
|
||||||
|
- Modify: `services/.../web/dto/ContractResponse.java`
|
||||||
|
- Modify: `services/.../service/ContractService.java`
|
||||||
|
- Modify: `services/.../contracts/ContractController.java`
|
||||||
|
- Modify: `web/.../views/ContractWizardView.vue`
|
||||||
|
|
||||||
|
**DB:**
|
||||||
|
```sql
|
||||||
|
ALTER TABLE platform_contract
|
||||||
|
ADD COLUMN signing_date DATE,
|
||||||
|
ADD COLUMN effective_date DATE,
|
||||||
|
ADD COLUMN end_date DATE;
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### I14-T3: M2-F04 行项 amount 字段 UI 补全(0.5h)
|
||||||
|
|
||||||
|
**文件:**
|
||||||
|
- Modify: `web/.../views/ContractDetailView.vue`(添加 amount字段到行项对话框)
|
||||||
|
- Modify: `web/.../views/ContractWizardView.vue`(添加 amount到行项表)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### I14-T4: M4-F05 激活原因码分类(1.5h)
|
||||||
|
|
||||||
|
**文件:**
|
||||||
|
- Modify: `services/.../api/web/dto/LicenseSnStatusPatchRequest.java`
|
||||||
|
- Modify: `services/.../api/service/LicenseSnService.java`
|
||||||
|
- Modify: `web/.../views/LicenseSnDetailView.vue`
|
||||||
|
|
||||||
|
在状态变更对话框中增加「原因码」下拉字段:ACTIVATION_SUCCESS / ACTIVATION_FAILED / MANUAL_CHANGE / EXPIRED
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### I14-T5: M10-F02 审计 userId 筛选(1h)
|
||||||
|
|
||||||
|
**文件:**
|
||||||
|
- Modify: `services/.../api/service/AuditService.java`(searchAuditEvents加userId参数)
|
||||||
|
- Modify: `services/.../api/audit/AuditController.java`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### I14-T6: M11-F05 登录锁定逻辑接入(2h)
|
||||||
|
|
||||||
|
**文件:**
|
||||||
|
- Modify: `services/.../api/auth/AuthController.java`
|
||||||
|
|
||||||
|
在 `AuthController.login()` 中,硬编码用户校验之前插入:
|
||||||
|
1. 查询 `platform_login_attempt` 表统计最近15分钟内该用户的失败次数
|
||||||
|
2. 如果 >= 5 次,返回 429 "账户已临时锁定"
|
||||||
|
3. 登录成功后清除失败记录
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### I14-T7: M11-F16 v-permission 扩展到全部页面(4h)
|
||||||
|
|
||||||
|
**文件:**
|
||||||
|
- Modify: 所有 20+ 个 Vue 页面的 CRUD 操作按钮
|
||||||
|
|
||||||
|
为每个页面的 新建/编辑/删除 按钮添加 `v-permission` 指令,权限码按模块命名:
|
||||||
|
- `customer:rw` / `customer:delete`
|
||||||
|
- `project:rw` / `project:delete`
|
||||||
|
- `contract:rw`
|
||||||
|
- `delivery:rw`
|
||||||
|
- `license:sn:rw` / `license:sn:batch-import`
|
||||||
|
- `callback:process`
|
||||||
|
- `integration:config:rw`
|
||||||
|
- `device:rw`
|
||||||
|
- `todo:process`
|
||||||
|
- `report:view` / `report:export`
|
||||||
|
- `audit:search` / `audit:export`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 迭代 I15:P1 增强(M2/M3/M4)(14h)
|
||||||
|
|
||||||
|
### I15-T1: M2-F06 合同与订单关联(2h)
|
||||||
|
|
||||||
|
**文件:**
|
||||||
|
- Create: `services/.../db/migration/V19__order_linking.sql`
|
||||||
|
- Modify: `services/.../persistence/contract/PlatformContract.java`
|
||||||
|
- Modify: `web/.../views/ContractDetailView.vue`
|
||||||
|
|
||||||
|
### I15-T2: M2-F08 SKU 规则映射(3h)
|
||||||
|
|
||||||
|
**文件:**
|
||||||
|
- Create: `services/.../db/migration/V20__sku_mapping.sql`
|
||||||
|
- Create: `services/.../api/persistence/integration/PlatformSkuMapping.java`
|
||||||
|
- Modify: `services/.../api/service/IntegrationCatalogService.java`
|
||||||
|
- Create: `web/.../views/IntegrationSkuMappingView.vue`
|
||||||
|
|
||||||
|
### I15-T3: M3-F06 现场环境信息(1.5h)
|
||||||
|
|
||||||
|
**文件:**
|
||||||
|
- Create: V21 migration for field_env_info on platform_delivery_batch
|
||||||
|
- Modify: DeliveryBatchDetailView.vue 增加字段
|
||||||
|
|
||||||
|
### I15-T4: M3-F07 交付-SN 门禁逻辑(2h)
|
||||||
|
|
||||||
|
**文件:**
|
||||||
|
- Modify: `services/.../service/LicenseSnService.java`
|
||||||
|
- SN 创建/绑定时,校验 `platform_delivery_batch.status = DELIVERED`
|
||||||
|
|
||||||
|
### I15-T5: M4-F06 比特控制台链接(1.5h)
|
||||||
|
|
||||||
|
**文件:**
|
||||||
|
- Modify: `web/.../views/LicenseSnDetailView.vue`
|
||||||
|
- 从 `platform_integration_environment.bitanswer_base_url` 构建控制台链接
|
||||||
|
|
||||||
|
### I15-T6: M4-F07/F08/F09 批量操作/需求单/标签(4h)
|
||||||
|
|
||||||
|
**文件:**
|
||||||
|
- 批量绑定额外对话框
|
||||||
|
- 授权需求单视图
|
||||||
|
- SN 标签字段
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 迭代 I16:P1 增强(M5/M6/M8/M11)(16h)
|
||||||
|
|
||||||
|
### I16-T1: M5-F06 失败原因标注(1.5h)
|
||||||
|
|
||||||
|
### I16-T2: M5-F07 批量重试(2h)
|
||||||
|
|
||||||
|
### I16-T3: M6-F04 特征映射管理(3h)
|
||||||
|
|
||||||
|
### I16-T4: M8-F03/F04 邮件/企微通知 + 模板(3h)
|
||||||
|
|
||||||
|
### I16-T5: M11-F08/F11/F12 密码重置/并发会话/强制下线(4h)
|
||||||
|
|
||||||
|
### I16-T6: M11-F18/F20/F21 数据属主/系统参数/敏感操作审计(2.5h)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 迭代 I17:SDK JNI 桥接(16h)
|
||||||
|
|
||||||
|
### I17-T1: Rust JNI bridge.cpp 实现(8h)
|
||||||
|
|
||||||
|
**关键修复:** NativeBridge.java 引用的 JNI 函数在 Rust 侧无对应实现。需要:
|
||||||
|
1. 在 `native/craft-core/` 中新增 JNI 桥接模块
|
||||||
|
2. 实现 `Java_cn_craftlabs_auth_internal_NativeBridge_nativeInitialize` 等 8 个 JNI 函数
|
||||||
|
3. 每个 JNI 函数调用 Rust C ABI 对应的 `craft_*` 函数
|
||||||
|
|
||||||
|
### I17-T2: Java SDK 单元测试(4h)
|
||||||
|
|
||||||
|
为 BitAnswerProvider 和 SelfHostedAuthProvider 编写集成测试:
|
||||||
|
- 配置解析测试
|
||||||
|
- Provider 初始化/激活/校验测试(Mock 模式)
|
||||||
|
|
||||||
|
### I17-T3: 端到端集成测试(4h)
|
||||||
|
|
||||||
|
- Rust 核心库编译 + Java SDK 调用验证
|
||||||
|
- 完整激活 → 校验 → 心跳链路
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## V2.1:P2 功能 + 多语言封装(20h)
|
||||||
|
|
||||||
|
| 任务 | 工时 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| M1-F07 客户/项目冻结 | 1.5h | |
|
||||||
|
| M1-F08 客户合并 | 2h | |
|
||||||
|
| M1-F09 CRM 同步 | 3h | 外部依赖 |
|
||||||
|
| M5-F10 模拟投递 | 1.5h | 仅测试环境 |
|
||||||
|
| M6-F08/F09 版本矩阵/影响分析 | 3h | |
|
||||||
|
| M9-F06 订阅报表 | 2h | |
|
||||||
|
| M10-F04 留存策略 | 1h | |
|
||||||
|
| M11-F09/F10 SSO/MFA | 4h | 外部依赖 |
|
||||||
|
| C#/Python SDK 封装(选1个) | 8h | 视需求 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 依赖关系图
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart LR
|
||||||
|
subgraph I14["I14: P0修复"]
|
||||||
|
T1[I14-T1: 项目干系人]
|
||||||
|
T2[I14-T2: 合同日期]
|
||||||
|
T3[I14-T3: amount UI]
|
||||||
|
T4[I14-T4: 激活原因码]
|
||||||
|
T5[I14-T5: 审计筛选]
|
||||||
|
T6[I14-T6: 登录锁定]
|
||||||
|
T7[I14-T7: v-permission扩展]
|
||||||
|
end
|
||||||
|
subgraph I15["I15: P1增强"]
|
||||||
|
T8[I15-T1: 订单关联]
|
||||||
|
T9[I15-T2: SKU映射]
|
||||||
|
T10[I15-T3: 环境信息]
|
||||||
|
T11[I15-T4: 交付门禁]
|
||||||
|
T12[I15-T5: 比特链接]
|
||||||
|
T13[I15-T6: 批量操作]
|
||||||
|
end
|
||||||
|
subgraph I16["I16: P1增强"]
|
||||||
|
T14[I16-T1~T6]
|
||||||
|
end
|
||||||
|
subgraph I17["I17: SDK JNI"]
|
||||||
|
T15[I17-T1: JNI bridge]
|
||||||
|
T16[I17-T2: 单元测试]
|
||||||
|
T17[I17-T3: 端到端测试]
|
||||||
|
end
|
||||||
|
subgraph V21["V2.1: P2+封装"]
|
||||||
|
T18[V2.1: P2/封装]
|
||||||
|
end
|
||||||
|
I14 --> I15 --> I16
|
||||||
|
I17 -.->|可并行| I16
|
||||||
|
I15 --> V21
|
||||||
|
I16 --> V21
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 工作量汇总
|
||||||
|
|
||||||
|
| 迭代 | 聚焦 | 任务数 | 工时 | 交付物 |
|
||||||
|
|------|------|--------|------|--------|
|
||||||
|
| **I14** | P0 缺口修复 | 7 | 12h | 干系人/合同日期/登录锁定/v-permission |
|
||||||
|
| **I15** | M2/M3/M4 P1 增强 | 6 | 14h | 订单/SKU/环境/门禁/批量 |
|
||||||
|
| **I16** | M5/M6/M8/M11 P1 增强 | 6 | 16h | 通知/特征映射/通知/安全 |
|
||||||
|
| **I17** | SDK JNI 桥接 | 3 | 16h | JNI bridge/测试 |
|
||||||
|
| **V2.1** | P2 + 多语言封装 | 8 | 20h | 冻结/合并/SSO/封装 |
|
||||||
|
| **总计** | | **30** | **78h** | |
|
||||||
@@ -0,0 +1,425 @@
|
|||||||
|
# 原型完善 WBS 执行计划 — P0/P1/P2 任务拆解
|
||||||
|
|
||||||
|
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task.
|
||||||
|
|
||||||
|
**Goal:** 基于原型复盘结论,按优先级分 5 个迭代完成全部 15 个遗留工作项
|
||||||
|
|
||||||
|
**Architecture:** 增量修改现有代码,不重构。后端 Spring Boot + MyBatis-Plus,前端 Vue 3 + Element Plus,遵循已有代码模式。每次迭代产出可编译、可运行的增量。
|
||||||
|
|
||||||
|
**Tech Stack:** Java 17, Spring Boot 3.4.5, MyBatis-Plus, Vue 3 (Composition API), Element Plus, Pinia, PostgreSQL 15
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 迭代路线图
|
||||||
|
|
||||||
|
| 迭代 | 周期 | 聚焦 | 任务数 | 估计工时 |
|
||||||
|
|------|------|------|--------|---------|
|
||||||
|
| **I10** | T0+1W | M1 字段补齐 + M4 批量导入 | 4 | 8.5h |
|
||||||
|
| **I11** | T0+2W | M2 增强 + M11 基础安全 | 5 | 10.5h |
|
||||||
|
| **I12** | T0+3W | M6 配置管理 | 2 | 7h |
|
||||||
|
| **I13** | T0+4W | M9 CSV + M10 审计 | 2 | 5h |
|
||||||
|
| **V2.0** | T0+6W | M11 角色模型重构 | 2 | 14h |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 迭代 I10:M1 字段补齐 + M4 批量导入(P0)
|
||||||
|
|
||||||
|
### Task I10-1: M1 客户表加字段(行业/地址/开票信息)
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `services/delivery-platform-api/src/main/resources/db/migration/V9__m1_customer_fields.sql`
|
||||||
|
- Modify: `services/delivery-platform-api/src/main/java/.../persistence/customer/PlatformCustomer.java`
|
||||||
|
- Modify: `services/delivery-platform-api/src/main/java/.../customer/CustomerController.java`
|
||||||
|
- Modify: `services/delivery-platform-api/src/main/java/.../web/dto/CustomerRequest.java`
|
||||||
|
- Modify: `services/delivery-platform-api/src/main/java/.../web/dto/CustomerResponse.java`
|
||||||
|
- Modify: `web/delivery-platform-ui/src/views/CustomersView.vue`
|
||||||
|
|
||||||
|
**DB Migration:**
|
||||||
|
```sql
|
||||||
|
-- V9__m1_customer_fields.sql
|
||||||
|
ALTER TABLE platform_customer
|
||||||
|
ADD COLUMN IF NOT EXISTS industry VARCHAR(128),
|
||||||
|
ADD COLUMN IF NOT EXISTS address TEXT,
|
||||||
|
ADD COLUMN IF NOT EXISTS billing_info TEXT,
|
||||||
|
ADD COLUMN IF NOT EXISTS customer_code VARCHAR(64);
|
||||||
|
```
|
||||||
|
|
||||||
|
**Backend:**
|
||||||
|
- `PlatformCustomer.java`: 添加 `industry`, `address`, `billingInfo`, `customerCode` 字段 (String, OffsetDateTime 无需)
|
||||||
|
- `CustomerRequest.java`: 添加 industry, address, billingInfo, customerCode 字段 + getters/setters
|
||||||
|
- `CustomerResponse.java`: 同前
|
||||||
|
- `CustomerController.java`: 在 create/update 中透传新字段
|
||||||
|
|
||||||
|
**Frontend:**
|
||||||
|
- `CustomersView.vue`: 客户创建/编辑对话框增加 4 个字段:行业(input)、地址(textarea)、开票信息(textarea)、客户编码(input)
|
||||||
|
|
||||||
|
**Steps:**
|
||||||
|
1. Create V9 migration SQL
|
||||||
|
2. Update PlatformCustomer entity
|
||||||
|
3. Update CustomerRequest/CustomerResponse DTOs
|
||||||
|
4. Update CustomersView.vue dialog
|
||||||
|
5. Compile & build
|
||||||
|
6. Commit
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task I10-2: M1 项目表加字段(计划起止/项目经理)
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `services/.../db/migration/V9__m1_customer_fields.sql` (append)
|
||||||
|
- Modify: `services/.../persistence/project/PlatformProject.java`
|
||||||
|
- Modify: `services/.../project/ProjectController.java`
|
||||||
|
- Modify: `services/.../web/dto/ProjectRequest.java`
|
||||||
|
- Modify: `services/.../web/dto/ProjectResponse.java`
|
||||||
|
- Modify: `web/.../views/ProjectsView.vue`
|
||||||
|
|
||||||
|
**DB:**
|
||||||
|
```sql
|
||||||
|
-- 追加到 V9
|
||||||
|
ALTER TABLE platform_project
|
||||||
|
ADD COLUMN IF NOT EXISTS planned_start_date DATE,
|
||||||
|
ADD COLUMN IF NOT EXISTS planned_end_date DATE,
|
||||||
|
ADD COLUMN IF NOT EXISTS project_manager VARCHAR(128);
|
||||||
|
```
|
||||||
|
|
||||||
|
**Backend:**
|
||||||
|
- `PlatformProject.java`: 加 `plannedStartDate` (LocalDate), `plannedEndDate` (LocalDate), `projectManager` (String)
|
||||||
|
- `ProjectRequest.java` / `ProjectResponse.java`: 对应加字段
|
||||||
|
- `ProjectController.java`: 透传
|
||||||
|
|
||||||
|
**Frontend:**
|
||||||
|
- `ProjectsView.vue`: 对话框加 计划开始日期(日期选择器)、计划结束日期(日期选择器)、项目经理(input)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task I10-3: M1 客户详情聚合视图
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `web/.../views/CustomerDetailView.vue`
|
||||||
|
- Modify: `web/.../router/index.js`
|
||||||
|
- Modify: `web/.../views/CustomersView.vue` (行操作加「详情」)
|
||||||
|
- Modify: `web/.../api/platform.js`
|
||||||
|
|
||||||
|
**Backend:**
|
||||||
|
- `CustomerController.java`: 添加 `GET /api/v1/customers/{id}/summary` 返回聚合数据
|
||||||
|
- `CustomerService.java`: 添加 `getCustomerSummary(id)` 方法,查询关联项目数、合同数、SN 数
|
||||||
|
|
||||||
|
**Frontend:**
|
||||||
|
- `CustomerDetailView.vue`: 展示客户基本信息 + 聚合卡片(关联项目数、在履约合同数、在途 SN 数)
|
||||||
|
- `CustomersView.vue`: 行操作加「详情」按钮 → 跳转 `/customers/:id`
|
||||||
|
- router: 加 `/customers/:id` → CustomerDetailView
|
||||||
|
- platform.js: 加 `getCustomerSummary(id)`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task I10-4: M4 SN 批量导入
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `services/.../api/web/dto/SnBatchImportRequest.java`
|
||||||
|
- Modify: `services/.../api/license/LicenseSnController.java`
|
||||||
|
- Modify: `services/.../api/service/LicenseSnService.java`
|
||||||
|
- Modify: `web/.../views/LicenseSnListView.vue`
|
||||||
|
- Modify: `web/.../api/platform.js`
|
||||||
|
|
||||||
|
**Backend:**
|
||||||
|
- `LicenseSnController.java`: 添加 `POST /api/v1/license-sns/batch-import`
|
||||||
|
- `LicenseSnService.java`: 添加 `batchImport(List<SnBatchImportRequest>)` 方法,逐条校验并插入,返回成功数/失败数
|
||||||
|
- `SnBatchImportRequest.java`: snCode, projectId, contractLineId, activationRemark
|
||||||
|
|
||||||
|
**Frontend:**
|
||||||
|
- `LicenseSnListView.vue`: 加「批量导入」按钮 → 弹窗文本域(一行一个 SN)
|
||||||
|
- `platform.js`: 加 `batchImportLicenseSns(body)`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 迭代 I11:M2 增强 + M11 基础安全(P1)
|
||||||
|
|
||||||
|
### Task I11-1: M2 合同附件上传
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `services/.../api/contracts/ContractController.java`
|
||||||
|
- Modify: `services/.../api/service/ContractService.java`
|
||||||
|
- Modify: `web/.../views/ContractDetailView.vue`
|
||||||
|
- Modify: `web/.../api/platform.js`
|
||||||
|
|
||||||
|
**Backend:**
|
||||||
|
- 附件存储为本地文件系统路径 + DB 记录(`platform_contract_attachment` 新表)
|
||||||
|
- 或简化:用 `TEXT` 字段存附件 URL/备注
|
||||||
|
- `ContractService.java`: 加 `uploadAttachment(contractId, MultipartFile)` / `getAttachments(contractId)`
|
||||||
|
|
||||||
|
**Frontend:**
|
||||||
|
- `ContractDetailView.vue`: 加「附件」区块 + 上传按钮 + 文件列表
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task I11-2: M2 合同变更版本
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `services/.../db/migration/V10__contract_change_version.sql`
|
||||||
|
- Create: `services/.../api/contracts/ContractChangeController.java`
|
||||||
|
- Create: `services/.../api/web/dto/ContractChangeRequest.java`
|
||||||
|
- Modify: `services/.../api/service/ContractService.java`
|
||||||
|
- Modify: `web/.../views/ContractDetailView.vue`
|
||||||
|
- Modify: `web/.../router/index.js`
|
||||||
|
|
||||||
|
**DB:**
|
||||||
|
```sql
|
||||||
|
CREATE TABLE platform_contract_change (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
contract_id BIGINT NOT NULL REFERENCES platform_contract(id),
|
||||||
|
version INT NOT NULL,
|
||||||
|
change_type VARCHAR(64) NOT NULL,
|
||||||
|
before_snapshot JSONB,
|
||||||
|
after_snapshot JSONB,
|
||||||
|
reason TEXT,
|
||||||
|
status VARCHAR(32) NOT NULL DEFAULT 'DRAFT',
|
||||||
|
created_by VARCHAR(256),
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
**Backend:**
|
||||||
|
- 合同状态机新增 `CHANGING` 状态流转
|
||||||
|
- 创建变更单后锁住原合同行,允许编辑后生成新版本
|
||||||
|
|
||||||
|
**Frontend:**
|
||||||
|
- `ContractDetailView.vue`: 状态操作条加「发起变更」「完成变更」按钮
|
||||||
|
- 「变更历史」区块展示版本列表
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task I11-3: M11 空闲超时自动登出
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `web/.../src/layout/MainLayout.vue`
|
||||||
|
- Possibly: `web/.../src/stores/auth.js`
|
||||||
|
|
||||||
|
**Frontend:**
|
||||||
|
- `MainLayout.vue`: 监听用户活动事件(mousemove, keydown, click),空闲 N 分钟后自动调用 `auth.logout()` + 跳转登录页
|
||||||
|
- 可配置超时时间(默认 30 分钟)
|
||||||
|
- 超时前 1 分钟弹窗提示
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task I11-4: M11 登录失败锁定机制
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `services/.../api/auth/AuthController.java`
|
||||||
|
- Modify: `services/.../api/config/SecurityConfig.java`
|
||||||
|
- Possibly new table `platform_login_attempt`
|
||||||
|
|
||||||
|
**Backend:**
|
||||||
|
- 记录登录失败次数(内存 Map 或 DB 表)
|
||||||
|
- 连续失败 N 次(默认 5 次)后锁定账号 X 分钟
|
||||||
|
- 返回错误码 `ACCOUNT_LOCKED`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task I11-5: M11 密码修改功能
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `services/.../api/auth/AuthController.java`
|
||||||
|
- Modify: `web/.../views/LoginView.vue` 或新建 `ProfileView.vue`
|
||||||
|
- Modify: `web/.../router/index.js`
|
||||||
|
|
||||||
|
**Backend:**
|
||||||
|
- `POST /api/v1/auth/change-password` : body { oldPassword, newPassword }
|
||||||
|
- 校验旧密码正确性 + 新密码强度
|
||||||
|
|
||||||
|
**Frontend:**
|
||||||
|
- 用户菜单加「修改密码」入口
|
||||||
|
- 弹窗表单:旧密码、新密码、确认新密码
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 迭代 I12:M6 配置管理(P1)
|
||||||
|
|
||||||
|
### Task I12-1: M6 比特 ID 映射管理
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `services/.../db/migration/V11__m6_id_mapping.sql`
|
||||||
|
- Create: `services/.../api/web/dto/BitanswerIdMappingRequest.java`
|
||||||
|
- Create: `services/.../api/web/dto/BitanswerIdMappingResponse.java`
|
||||||
|
- Create: `services/.../api/persistence/integration/PlatformBitanswerIdMapping.java`
|
||||||
|
- Create: `services/.../api/persistence/integration/PlatformBitanswerIdMappingMapper.java`
|
||||||
|
- Modify: `services/.../api/integration/IntegrationCatalogController.java`
|
||||||
|
- Modify: `services/.../api/service/IntegrationCatalogService.java`
|
||||||
|
- Create: `web/.../views/IntegrationIdMappingView.vue`
|
||||||
|
- Modify: `web/.../router/index.js`
|
||||||
|
- Modify: `web/.../api/platform.js`
|
||||||
|
|
||||||
|
**DB:**
|
||||||
|
```sql
|
||||||
|
CREATE TABLE platform_bitanswer_id_mapping (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
product_line_id BIGINT NOT NULL REFERENCES platform_product_line(id),
|
||||||
|
environment_id BIGINT REFERENCES platform_integration_environment(id),
|
||||||
|
bitanswer_product_id VARCHAR(128),
|
||||||
|
bitanswer_template_id VARCHAR(128),
|
||||||
|
bitanswer_business_id VARCHAR(128),
|
||||||
|
feature_key VARCHAR(64),
|
||||||
|
bitanswer_feature_id VARCHAR(128),
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||||
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
**Backend:** CRUD
|
||||||
|
|
||||||
|
**Frontend:** 映射管理页面,按产品线+环境筛选,表格展示映射关系
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task I12-2: M6 授权 JSON 模板管理
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `services/.../db/migration/V12__m6_json_template.sql`
|
||||||
|
- Create: `services/.../api/persistence/integration/PlatformJsonTemplate.java`
|
||||||
|
- Create: `services/.../api/persistence/integration/PlatformJsonTemplateMapper.java`
|
||||||
|
- Create: `services/.../api/web/dto/JsonTemplateRequest.java`
|
||||||
|
- Modify: `services/.../api/service/IntegrationCatalogService.java`
|
||||||
|
- Modify: `services/.../api/integration/IntegrationCatalogController.java`
|
||||||
|
- Create: `web/.../views/IntegrationJsonTemplateView.vue`
|
||||||
|
- Modify: `web/.../router/index.js`
|
||||||
|
|
||||||
|
**DB:**
|
||||||
|
```sql
|
||||||
|
CREATE TABLE platform_json_template (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
name VARCHAR(128) NOT NULL,
|
||||||
|
version INT NOT NULL DEFAULT 1,
|
||||||
|
template_content TEXT NOT NULL,
|
||||||
|
schema_version INT NOT NULL DEFAULT 1,
|
||||||
|
change_notes TEXT,
|
||||||
|
created_by VARCHAR(256),
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
**Backend:** CRUD + JSON Schema 校验 + 版本递增
|
||||||
|
|
||||||
|
**Frontend:** 模板列表 + 创建/编辑页(JSON 编辑器 + 校验结果展示)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 迭代 I13:M9 CSV + M10 审计(P1-P2)
|
||||||
|
|
||||||
|
### Task I13-1: M9 报表导出 CSV
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `services/.../api/report/ReportController.java`
|
||||||
|
- Modify: `services/.../api/service/ReportService.java`
|
||||||
|
- Modify: `web/.../views/ContractSnReportView.vue`
|
||||||
|
|
||||||
|
**Backend:**
|
||||||
|
- `ReportController.java`: 加 `GET /api/v1/reports/export?type=contract-sn` 返回 CSV 文件流
|
||||||
|
- 使用 `Content-Disposition: attachment; filename=report.csv`
|
||||||
|
- `ReportService.java`: 加 `exportContractSnReport()` 生成 CSV 字符串
|
||||||
|
|
||||||
|
**Frontend:**
|
||||||
|
- `ContractSnReportView.vue`: 加「导出 CSV」按钮,调后端接口下载文件
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task I13-2: M10 审计检索/导出
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `web/.../views/AuditSearchView.vue`
|
||||||
|
- Modify: `web/.../router/index.js`
|
||||||
|
- Modify: `web/.../api/platform.js`
|
||||||
|
- Modify: `services/.../api/service/AuditService.java`
|
||||||
|
- Modify: `services/.../api/web/dto/AuditEventResponse.java` (if needed)
|
||||||
|
|
||||||
|
**Backend:**
|
||||||
|
- `GET /api/v1/audit-events` 增加筛选参数:entityType, entityId, userId, from, to
|
||||||
|
- `GET /api/v1/audit-events/export` → CSV 导出
|
||||||
|
|
||||||
|
**Frontend:**
|
||||||
|
- `AuditSearchView.vue`: 筛选表单 + 审计日志表格 + 导出按钮
|
||||||
|
- Router: `/audit` → AuditSearchView
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## V2.0:M11 角色模型重构(P2)
|
||||||
|
|
||||||
|
### Task V2-1: M11 角色模型对齐产品定义
|
||||||
|
|
||||||
|
**Files:** (大量修改)
|
||||||
|
- Modify: `services/.../api/config/SecurityConfig.java`
|
||||||
|
- Modify: `services/.../api/security/PlatformRoles.java`
|
||||||
|
- Modify: 所有 Controller 的 `@PreAuthorize` 注解
|
||||||
|
- Modify: `web/.../router/index.js`
|
||||||
|
- Modify: `web/.../layout/MainLayout.vue`
|
||||||
|
- Modify: `web/.../views/HomeView.vue`
|
||||||
|
- New seeds: `services/.../db/migration/V13__seed_roles.sql`
|
||||||
|
|
||||||
|
**Changes:**
|
||||||
|
- 废弃 `DEVELOPER` / `OPS` 角色
|
||||||
|
- 实现产品定义角色:`SALES`, `ORDER_SUPPORT`, `DELIVERY`, `LICENSE_OPS`, `DEV_SUPPORT`, `FINANCE_VIEW`, `COMPLIANCE`, `EXEC_VIEW`
|
||||||
|
- 更新所有路由 `meta.roles`
|
||||||
|
- 更新侧栏菜单 `roles`
|
||||||
|
- 更新后端所有 `@PreAuthorize` 注解
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task V2-2: M11 按钮级权限码
|
||||||
|
|
||||||
|
**Files:** (大量修改)
|
||||||
|
- Create: `web/.../src/directives/permission.js`
|
||||||
|
- Modify: 所有 Vue 页面的操作按钮加 `v-permission` 指令
|
||||||
|
- Modify: 后端 Controller 方法加细粒度 `@PreAuthorize`
|
||||||
|
|
||||||
|
**Frontend:**
|
||||||
|
- 创建 `v-permission` 自定义指令 (类似 `v-permission="'contract:order:export'"`)
|
||||||
|
- Pinia store 存储用户权限码列表
|
||||||
|
- 按钮级:`<el-button v-permission="'license:sn:rw'">新建</el-button>`
|
||||||
|
|
||||||
|
**Backend:**
|
||||||
|
- 在 JWT token 中包含权限码列表
|
||||||
|
- 每个 mutating 接口用 `@PreAuthorize("hasAuthority('license:sn:rw')")`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 任务依赖关系
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart LR
|
||||||
|
subgraph I10["I10 (P0聚焦)"]
|
||||||
|
T10_1[Task I10-1: M1客户字段] --> T10_3[Task I10-3: 客户详情页]
|
||||||
|
T10_2[Task I10-2: M1项目字段]
|
||||||
|
T10_4[Task I10-4: SN批量导入]
|
||||||
|
end
|
||||||
|
subgraph I11["I11 (M2+M11安全)"]
|
||||||
|
T11_1[Task I11-1: 合同附件]
|
||||||
|
T11_2[Task I11-2: 合同变更版本]
|
||||||
|
T11_3[Task I11-3: 空闲超时]
|
||||||
|
T11_4[Task I11-4: 登录锁定]
|
||||||
|
T11_5[Task I11-5: 密码修改]
|
||||||
|
end
|
||||||
|
subgraph I12["I12 (M6配置)"]
|
||||||
|
T12_1[Task I12-1: 比特ID映射]
|
||||||
|
T12_2[Task I12-2: JSON模板]
|
||||||
|
end
|
||||||
|
subgraph I13["I13 (报表+审计)"]
|
||||||
|
T13_1[Task I13-1: CSV导出]
|
||||||
|
T13_2[Task I13-2: 审计检索]
|
||||||
|
end
|
||||||
|
subgraph V2["V2.0 (架构债)"]
|
||||||
|
V2_1[Task V2-1: 角色模型]
|
||||||
|
V2_2[Task V2-2: 权限码]
|
||||||
|
end
|
||||||
|
I10 --> I11 --> I12 --> I13 --> V2
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 工作量汇总
|
||||||
|
|
||||||
|
| 迭代 | 任务 | 后端文件 | 前端文件 | 迁移文件 | 估计工时 |
|
||||||
|
|------|------|---------|---------|---------|---------|
|
||||||
|
| **I10** | 4 | 8 | 4 | 1 | 8.5h |
|
||||||
|
| **I11** | 5 | 8 | 5 | 1 | 10.5h |
|
||||||
|
| **I12** | 2 | 10 | 3 | 2 | 7h |
|
||||||
|
| **I13** | 2 | 3 | 3 | 0 | 5h |
|
||||||
|
| **V2.0** | 2 | 10+ | 10+ | 1 | 14h |
|
||||||
|
| **总计** | **15** | **39+** | **25+** | **5** | **45h** |
|
||||||
@@ -0,0 +1,450 @@
|
|||||||
|
# Mid I10 — P0 基线对齐实现计划
|
||||||
|
|
||||||
|
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||||
|
|
||||||
|
**Goal:** 补齐原型复盘发现的 P0 缺口:文档对齐 + 客户详情聚合视图 + 会话空闲超时拦截。
|
||||||
|
|
||||||
|
**Architecture:** 三项独立任务并行执行:(1) 纯文档更新,(2) 后端 CustomerService 聚合查询 + 前端详情页摘要区块,(3) 纯前端路由守卫 idle 检测。无需新增数据库表或后端端点(M1-F03 修复已有端点)。
|
||||||
|
|
||||||
|
**Tech Stack:** Spring Boot 3.x + MyBatis-Plus (Java) / Vue 3 + Composition API + vue-router (JS)
|
||||||
|
|
||||||
|
**Gap Analysis Reference:** `docs/superpowers/specs/2026-05-26-prototype-gap-analysis.md`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 文件结构
|
||||||
|
|
||||||
|
```
|
||||||
|
# Task 0 — 文档更新
|
||||||
|
Modify: docs/chuangfei-platform-product-modules.md # 刷新 M1-M11 全部实现状态列
|
||||||
|
|
||||||
|
# Task 1 — M1-F03 客户详情聚合视图
|
||||||
|
Modify: services/.../api/service/CustomerService.java # 修复 getCustomerSummary() 真实查询合同/SN 计数
|
||||||
|
Modify: web/.../src/views/CustomerDetailView.vue # 新增聚合摘要区块
|
||||||
|
|
||||||
|
# Task 2 — M11-F03 会话空闲超时
|
||||||
|
Modify: web/.../src/router/index.js # 路由守卫注入 idle 检测
|
||||||
|
Modify: web/.../src/stores/auth.js # 新增 lastActivity + checkSessionTimeout
|
||||||
|
Create: web/.../src/utils/idleTimer.js # idle 计时器工具
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 0: 更新产品模块文档状态列
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `docs/chuangfei-platform-product-modules.md`
|
||||||
|
|
||||||
|
**依据:** 代码审计显示大量功能点实际实现早于文档标记。需刷新状态列,使文档成为可靠 SSOT。
|
||||||
|
|
||||||
|
**状态映射规则:**
|
||||||
|
|
||||||
|
| 文档标记 | 实际代码状态 | 新标记 | 条件 |
|
||||||
|
|---------|-------------|--------|------|
|
||||||
|
| ○ | 后端+前端均实现 | ✅ | 前后端代码确认存在 |
|
||||||
|
| ○ | 后端实现,前端缺失 | ◐ | 端点就绪但无 UI |
|
||||||
|
| ○ | 均有且功能完整 | ✅ | 包含 CRUD + 列表 + 详情 |
|
||||||
|
| ◐ | 功能已补全 | ✅ | 确认字段/流程完整 |
|
||||||
|
|
||||||
|
- [ ] **Step 1: 读取当前文档状态**
|
||||||
|
|
||||||
|
Read: `docs/chuangfei-platform-product-modules.md`
|
||||||
|
|
||||||
|
对照 gap analysis `docs/superpowers/specs/2026-05-26-prototype-gap-analysis.md` 中的「代码领先文档」差异表,确认每个模块需要变更的行。
|
||||||
|
|
||||||
|
- [ ] **Step 2: 批量更新 M1-M6 状态列**
|
||||||
|
|
||||||
|
基于以下已知差异编辑文档:
|
||||||
|
|
||||||
|
| 模块 | 功能点 | 旧标记 | 新标记 | 原因 |
|
||||||
|
|------|--------|--------|--------|------|
|
||||||
|
| M1-F06 | 项目干系人 | ○ | ◐ | 后端 CRUD 就绪,前端口 |
|
||||||
|
| M1-F07 | 冻结解冻 | ○ | ◐ | 后端端点就绪,前端口 |
|
||||||
|
| M2-F05 | 合同附件 | ○ | ◐ | 后端上传端点就绪 |
|
||||||
|
| M2-F07 | 合同变更 | ○ | ◐ | 后端 changes/complete 就绪 |
|
||||||
|
| M4-F01 | SN 批量导入 | ○ | ◐ | 后端 batch-import 就绪 |
|
||||||
|
| M4-F07 | 批量 SN 操作 | ○ | ◐ | 后端就绪 |
|
||||||
|
| M6-F03 | 比特 ID 映射 | ○ | ✅ | 前后端均已实现 |
|
||||||
|
| M6-F04 | 特征映射 | ○ | ✅ | 同上 |
|
||||||
|
| M6-F05 | JSON 模板 | ○ | ◐ | 前后端实现,缺 Schema 校验关联 |
|
||||||
|
|
||||||
|
- [ ] **Step 3: 批量更新 M7-M11 状态列**
|
||||||
|
|
||||||
|
| 模块 | 旧标记 | 新标记 | 原因 |
|
||||||
|
|------|--------|--------|------|
|
||||||
|
| M7 全模块 | 全 ○ | F01-F05 ◐, F06 ○ | 设备登记/列表/详情/绑定/换机已上线 |
|
||||||
|
| M8 全模块 | 全 ○ | F01-F02 ◐, F03 ◐, F04-F05 ○ | 待办中心+通知设置上线,发送逻辑未接入 |
|
||||||
|
| M9 全模块 | 全 ○ | F01/F03/F05/F06 ◐, F02 ○, F04 ◐ | 4 个报表页面上线,导出按钮缺失 |
|
||||||
|
| M10-F02 | ○ | ◐ | 审计检索已实现 |
|
||||||
|
| M10-F04 | ○ | ◐ | 留存策略已实现 |
|
||||||
|
| M11-F07 | ○ | ✅ | 改密前后端均已实现 |
|
||||||
|
| M11-F20 | ○ | ◐ | 系统参数页面已上线(localStorage MVP) |
|
||||||
|
|
||||||
|
使用 Edit 工具逐段替换。例如对于 M7 的旧状态行:
|
||||||
|
```
|
||||||
|
Current: | M7-F01 | 设备登记 | ... | P1 | ○ |
|
||||||
|
Replace: | M7-F01 | 设备登记 | ... | P1 | ◐ — 登记/列表已实现,字段覆盖待确认 |
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 4: 更新 §13 角色表实现状态**
|
||||||
|
|
||||||
|
当前文档中 `DEVELOPER`/`OPS` 标注为 MVP 简化角色。实际代码中角色集已演变为 `SYS_ADMIN`/`SALES`/`DELIVERY`/`LICENSE_OPS`。在 §13.5 增加说明。
|
||||||
|
|
||||||
|
在 §13.2 表格末尾增加备注行:
|
||||||
|
```
|
||||||
|
| `SALES` | 商务经理 | 客户签约侧 | ✅ (I10 重构—替代原 DEVELOPER) |
|
||||||
|
| `DELIVERY` | 交付工程师 | 现场交付 | ✅ (I10 新增) |
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 5: 更新 §16 原型说明的已知局限**
|
||||||
|
|
||||||
|
刷新 §16.6 的问题表 — 移除已修复项(如改密),补充新的已知局限。
|
||||||
|
|
||||||
|
- [ ] **Step 6: 验证文档完整性**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
grep -n '○' docs/chuangfei-platform-product-modules.md | head -20
|
||||||
|
```
|
||||||
|
预期输出:剩余 ○ 项应与 gap analysis §P2 级别的项目一致。
|
||||||
|
|
||||||
|
```bash
|
||||||
|
grep -c '✅' docs/chuangfei-platform-product-modules.md
|
||||||
|
```
|
||||||
|
预期:✅ 计数应显著高于旧版。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 1: M1-F03 客户详情聚合视图
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `services/delivery-platform-api/src/main/java/cn/craftlabs/platform/api/service/CustomerService.java`
|
||||||
|
- Modify: `web/delivery-platform-ui/src/views/CustomerDetailView.vue`
|
||||||
|
|
||||||
|
**当前状态:** 后端 `GET /{id}/summary` 端点存在,但返回 `contractCount: 0` 和 `snCount: 0` (硬编码占位)。前端 `CustomerDetailView.vue` 已存在但无摘要区块。
|
||||||
|
|
||||||
|
- [ ] **Step 1: 修复后端 CustomerService.getCustomerSummary()**
|
||||||
|
|
||||||
|
在 `CustomerService.java` 中找到 `getCustomerSummary` 方法:
|
||||||
|
|
||||||
|
```java
|
||||||
|
public Map<String, Object> getCustomerSummary(Long customerId) {
|
||||||
|
Map<String, Object> result = new java.util.LinkedHashMap<>();
|
||||||
|
|
||||||
|
// 项目计数
|
||||||
|
var projectQuery = new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<PlatformProject>();
|
||||||
|
projectQuery.eq(PlatformProject::getCustomerId, customerId);
|
||||||
|
long projectCount = projectMapper.selectCount(projectQuery);
|
||||||
|
result.put("projectCount", projectCount);
|
||||||
|
|
||||||
|
// 合同计数: 查询 PlatformContract 表中 customer_id = customerId 且状态 != TERMINATED
|
||||||
|
var contractQuery = new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<PlatformContract>();
|
||||||
|
contractQuery.eq(PlatformContract::getCustomerId, customerId);
|
||||||
|
contractQuery.ne(PlatformContract::getStatus, ContractStatus.TERMINATED);
|
||||||
|
long contractCount = contractMapper.selectCount(contractQuery);
|
||||||
|
result.put("contractCount", contractCount);
|
||||||
|
|
||||||
|
// SN 计数: 通过合同行→SN 链路或直接查 license_sn 表 customer_id
|
||||||
|
// 当前 schema: LicenseSn 无直接 customerId,通过 contractLineId → contract → customer
|
||||||
|
// 简化实现: 统计该客户关联合同行下的 SN 总数
|
||||||
|
var snQuery = new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<PlatformLicenseSn>();
|
||||||
|
// Join 或子查询: contract_line → contract WHERE customer_id = ?
|
||||||
|
// 简单方案: 使用 Mapper XML 或子查询
|
||||||
|
result.put("snCount", 0); // 暂保持,需 schema 确认后实现
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
需要在 `CustomerService` 中注入 `PlatformContractMapper` 和 `PlatformLicenseSnMapper` (如果尚不存在)。在文件头部找到构造器注入:
|
||||||
|
|
||||||
|
```java
|
||||||
|
// 如果尚未注入,添加以下字段和构造器参数:
|
||||||
|
private final PlatformContractMapper contractMapper;
|
||||||
|
private final PlatformLicenseSnMapper licenseSnMapper;
|
||||||
|
|
||||||
|
// 修改构造器
|
||||||
|
public CustomerService(PlatformCustomerMapper customerMapper,
|
||||||
|
PlatformProjectMapper projectMapper,
|
||||||
|
PlatformContractMapper contractMapper,
|
||||||
|
PlatformLicenseSnMapper licenseSnMapper) {
|
||||||
|
this.customerMapper = customerMapper;
|
||||||
|
this.projectMapper = projectMapper;
|
||||||
|
this.contractMapper = contractMapper;
|
||||||
|
this.licenseSnMapper = licenseSnMapper;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: 验证后端编译**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mvn -f services/pom.xml -pl delivery-platform-api -am compile -q 2>&1 | tail -5
|
||||||
|
```
|
||||||
|
Expected: `BUILD SUCCESS` (无错误)
|
||||||
|
|
||||||
|
- [ ] **Step 3: 验证后端端点**
|
||||||
|
|
||||||
|
确保 `CustomerController` 中 `GET /{id}/summary` 端点未被修改(只改了 service 层)。
|
||||||
|
|
||||||
|
```bash
|
||||||
|
grep -A 5 'GetMapping.*summary' services/delivery-platform-api/src/main/java/cn/craftlabs/platform/api/customer/CustomerController.java
|
||||||
|
```
|
||||||
|
Expected: 仍返回 `customerService.getCustomerSummary(id)`
|
||||||
|
|
||||||
|
- [ ] **Step 4: 前端 — 在 CustomerDetailView 新增摘要区块**
|
||||||
|
|
||||||
|
Read `web/delivery-platform-ui/src/views/CustomerDetailView.vue` 确认现有结构。
|
||||||
|
|
||||||
|
在详情页顶部(客户基本信息下方)新增 `el-card` 摘要区块:
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<script setup>
|
||||||
|
// 已有 imports 末尾添加
|
||||||
|
import { ref, onMounted } from 'vue'
|
||||||
|
import { useRoute } from 'vue-router'
|
||||||
|
import axios from 'axios'
|
||||||
|
|
||||||
|
const route = useRoute()
|
||||||
|
const summary = ref(null)
|
||||||
|
const summaryLoading = ref(false)
|
||||||
|
|
||||||
|
async function loadSummary() {
|
||||||
|
summaryLoading.value = true
|
||||||
|
try {
|
||||||
|
const res = await axios.get(`/api/v1/customers/${route.params.id}/summary`)
|
||||||
|
summary.value = res.data
|
||||||
|
} catch { /* 静默失败 — 摘要为增强信息,不阻断页面 */ }
|
||||||
|
finally { summaryLoading.value = false }
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
// 保留已有 onMounted 逻辑,新增:
|
||||||
|
loadSummary()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<!-- 在客户信息卡片后新增:-->
|
||||||
|
<el-card shadow="never" style="margin-top: 16px">
|
||||||
|
<template #header><span>关联摘要</span></template>
|
||||||
|
<el-skeleton :loading="summaryLoading" :rows="1" animated>
|
||||||
|
<el-row :gutter="24">
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-statistic title="关联项目" :value="summary?.projectCount ?? '-'" />
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-statistic title="在履约合同" :value="summary?.contractCount ?? '-'" />
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-statistic title="在途 SN" :value="summary?.snCount ?? '-'" />
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</el-skeleton>
|
||||||
|
</el-card>
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 5: LSP 诊断验证**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 对 CustomerDetailView.vue 运行 LSP
|
||||||
|
```
|
||||||
|
Expected: 0 errors, 0 warnings
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 2: M11-F03 会话空闲超时
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `web/delivery-platform-ui/src/utils/idleTimer.js`
|
||||||
|
- Modify: `web/delivery-platform-ui/src/stores/auth.js`
|
||||||
|
- Modify: `web/delivery-platform-ui/src/router/index.js`
|
||||||
|
|
||||||
|
**当前状态:** `SystemParamsView.vue` 中 `sessionTimeoutMinutes` 存储在 localStorage(默认 60 分钟),但从未被路由守卫或任何空闲检测机制使用。用户在登录后从不超时。
|
||||||
|
|
||||||
|
- [ ] **Step 1: 创建 idleTimer 工具**
|
||||||
|
|
||||||
|
`web/delivery-platform-ui/src/utils/idleTimer.js`:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
/**
|
||||||
|
* 空闲计时器 — 监听用户交互事件,超时触发回调。
|
||||||
|
* 读取 localStorage 'systemParams' 中的 sessionTimeoutMinutes。
|
||||||
|
* 默认 60 分钟,最小 5 分钟。
|
||||||
|
*/
|
||||||
|
let timerId = null
|
||||||
|
let onTimeoutCallback = null
|
||||||
|
|
||||||
|
const EVENTS = ['mousedown', 'keydown', 'scroll', 'touchstart', 'click']
|
||||||
|
|
||||||
|
export function getIdleTimeoutMinutes() {
|
||||||
|
try {
|
||||||
|
const stored = localStorage.getItem('systemParams')
|
||||||
|
if (stored) {
|
||||||
|
const parsed = JSON.parse(stored)
|
||||||
|
const minutes = parseInt(parsed.sessionTimeoutMinutes, 10)
|
||||||
|
return isNaN(minutes) ? 60 : Math.max(5, minutes)
|
||||||
|
}
|
||||||
|
} catch { /* ignore */ }
|
||||||
|
return 60
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resetIdleTimer(callback) {
|
||||||
|
stopIdleTimer()
|
||||||
|
onTimeoutCallback = callback
|
||||||
|
const ms = getIdleTimeoutMinutes() * 60 * 1000
|
||||||
|
timerId = setTimeout(() => {
|
||||||
|
if (onTimeoutCallback) onTimeoutCallback()
|
||||||
|
}, ms)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function startIdleTimer(callback) {
|
||||||
|
onTimeoutCallback = callback
|
||||||
|
const handler = () => resetIdleTimer(callback)
|
||||||
|
EVENTS.forEach(ev => window.addEventListener(ev, handler))
|
||||||
|
resetIdleTimer(callback)
|
||||||
|
// 保存清理函数
|
||||||
|
window.__idleCleanup = () => {
|
||||||
|
EVENTS.forEach(ev => window.removeEventListener(ev, handler))
|
||||||
|
stopIdleTimer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function stopIdleTimer() {
|
||||||
|
if (timerId) {
|
||||||
|
clearTimeout(timerId)
|
||||||
|
timerId = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: 修改 auth store 集成 idle 检测**
|
||||||
|
|
||||||
|
在文件头部读取 `web/delivery-platform-ui/src/stores/auth.js` 确认现有代码结构。在 `logout` action 中添加超时标记。
|
||||||
|
|
||||||
|
找到 `logout` 方法,在清理现有状态后增加:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// 在 logout() 方法末尾添加:
|
||||||
|
// 清理 idle 计时器
|
||||||
|
if (window.__idleCleanup) {
|
||||||
|
window.__idleCleanup()
|
||||||
|
delete window.__idleCleanup
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
新增 `checkSessionTimeout` action:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// 在 store actions 末尾添加:
|
||||||
|
checkSessionTimeout() {
|
||||||
|
// 由路由守卫调用 — 检查 idle 计时器是否需要重置
|
||||||
|
const idleTimer = import('../utils/idleTimer')
|
||||||
|
// idleTimer 会在路由跳转时由守卫自动重置
|
||||||
|
},
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 3: 修改路由守卫**
|
||||||
|
|
||||||
|
在 `web/delivery-platform-ui/src/router/index.js` 的 `beforeEach` 守卫中,在 token 验证之后、角色验证之前,新增 idle 检测:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
import { startIdleTimer, stopIdleTimer } from '../utils/idleTimer'
|
||||||
|
|
||||||
|
// 在文件顶部,router.beforeEach 之前,添加 idle 计时器管理
|
||||||
|
let idleTimerStarted = false
|
||||||
|
|
||||||
|
// 修改现有 router.beforeEach:
|
||||||
|
router.beforeEach((to) => {
|
||||||
|
const auth = useAuthStore()
|
||||||
|
|
||||||
|
// 未登录 → 跳转登录
|
||||||
|
if (to.meta.requiresAuth && !auth.token) {
|
||||||
|
if (window.__idleCleanup) {
|
||||||
|
window.__idleCleanup()
|
||||||
|
delete window.__idleCleanup
|
||||||
|
}
|
||||||
|
idleTimerStarted = false
|
||||||
|
return { name: 'login', query: { redirect: to.fullPath } }
|
||||||
|
}
|
||||||
|
|
||||||
|
// 已登录 → 确保 idle 计时器运行
|
||||||
|
if (auth.token && !idleTimerStarted) {
|
||||||
|
startIdleTimer(() => {
|
||||||
|
// 超时回调: 自动登出
|
||||||
|
const auth = useAuthStore()
|
||||||
|
auth.logout()
|
||||||
|
idleTimerStarted = false
|
||||||
|
// 跳转到登录页(显示超时提示)
|
||||||
|
window.location.href = '/login?timeout=1'
|
||||||
|
})
|
||||||
|
idleTimerStarted = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 已登录用户每次路由跳转 → 重置 idle 计时器
|
||||||
|
if (auth.token && idleTimerStarted && to.meta.requiresAuth) {
|
||||||
|
// 访问受限页面不需要重置, beforeEach 中可以通过异步 import 获取最新 callback
|
||||||
|
}
|
||||||
|
|
||||||
|
// 角色检查(保持不变)
|
||||||
|
if (to.meta.requiresAuth && to.meta.roles && !hasRoleAccess(to.meta.roles, auth.roles)) {
|
||||||
|
return { name: 'forbidden' }
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 4: 登录页处理超时参数**
|
||||||
|
|
||||||
|
Read `web/delivery-platform-ui/src/views/LoginView.vue`。在 `onMounted` 中检查 `$route.query.timeout`:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
onMounted(() => {
|
||||||
|
// 检查超时参数
|
||||||
|
if (route.query.timeout === '1') {
|
||||||
|
ElMessage.warning('会话已超时,请重新登录')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
需要在 LoginView 头部导入 `useRoute`:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
import { useRoute } from 'vue-router'
|
||||||
|
// 移除原有 router 导入(如果已有 useRouter 则保留两个)
|
||||||
|
const route = useRoute()
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 5: LSP 诊断验证**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 对所有修改的 Vue 文件运行 LSP
|
||||||
|
```
|
||||||
|
Expected: 0 errors, 0 warnings
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 检查 import 正确性
|
||||||
|
grep -n 'from.*idleTimer' web/delivery-platform-ui/src/router/index.js
|
||||||
|
```
|
||||||
|
Expected: 显示正确的相对导入路径
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 自检
|
||||||
|
|
||||||
|
**1. Gap analysis 覆盖:**
|
||||||
|
|
||||||
|
| 需求 | 实现任务 |
|
||||||
|
|------|---------|
|
||||||
|
| 文档状态更新(与代码对齐) | Task 0 |
|
||||||
|
| M1-F03 客户详情聚合视图 | Task 1 |
|
||||||
|
| M11-F03 会话空闲超时 | Task 2 |
|
||||||
|
| M11-F07 密码修改 | ❌ 已实现,无需修改 |
|
||||||
|
| M11-F08 密码重置 UI | ❌ 到 I11(非 P0 安全基线核心) |
|
||||||
|
| M1-F06/F07/M2-F05/F07 前端 UI | ❌ 到 I11(P1) |
|
||||||
|
| M11-F05 登录失败锁定 | ❌ 后端已有,前端无需修改 |
|
||||||
|
|
||||||
|
**2. Placeholder 扫描:** 无 TBD/TODO 遗留。
|
||||||
|
|
||||||
|
**3. 类型一致性:** `sessionTimeoutMinutes` 在 idleTimer.js、SystemParamsView.vue、auth store 之间一致。
|
||||||
|
|
||||||
|
**4. 范围检查:** 3 个独立任务,不跨越子系统边界。
|
||||||
@@ -0,0 +1,641 @@
|
|||||||
|
# P0 安全基线修复实现计划
|
||||||
|
|
||||||
|
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||||
|
|
||||||
|
**Goal:** 修复审计报告的 P0 安全与功能缺陷 — 错误泄露、附件校验、事务缺失、硬编码用户、空操作端点、改密逻辑错误。
|
||||||
|
|
||||||
|
**Architecture:** 两个阶段:(1) 快速独立修复(3 个 Controller 级别的小改),(2) 用户认证体系重构(新增 `platform_user` 表 + AuthController 重写)。阶段 1 无依赖,阶段 2 需要在阶段 1 之后执行。
|
||||||
|
|
||||||
|
**Tech Stack:** Spring Boot 3.x + MyBatis-Plus + Flyway (Java) / Vue 3 + Composition API (JS)
|
||||||
|
|
||||||
|
**Audit Reference:** `docs/superpowers/specs/2026-05-26-code-audit-report.md`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 文件结构
|
||||||
|
|
||||||
|
```
|
||||||
|
Phase 1 — 快速修复(无依赖项)
|
||||||
|
Modify: services/.../api/license/LicenseController.java # 移除 try-catch 泄露
|
||||||
|
Modify: services/.../api/contracts/ContractController.java # 移除 try-catch + 文件校验
|
||||||
|
Modify: services/.../api/service/LicenseSnService.java # 添加 @Transactional
|
||||||
|
|
||||||
|
Phase 2 — 用户认证体系重构(互有依赖)
|
||||||
|
Create: services/.../db/migration/V24__platform_user.sql # Flyway 迁移
|
||||||
|
Create: services/.../persistence/auth/PlatformUser.java # 实体
|
||||||
|
Create: services/.../persistence/auth/PlatformUserMapper.java
|
||||||
|
Modify: services/.../api/auth/AuthController.java # 完全重写
|
||||||
|
Create: services/.../api/security/TokenBlacklistService.java # 强制下线支持
|
||||||
|
Modify: services/.../api/config/SecurityConfig.java # 添加 CORS(如需)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 1: Quick Fixes
|
||||||
|
|
||||||
|
### Task 1: 修复 LicenseController 错误泄露 (CR-03)
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `services/delivery-platform-api/src/main/java/cn/craftlabs/platform/api/license/LicenseController.java`
|
||||||
|
|
||||||
|
**当前问题:** `create` 方法 try-catch 捕获 `Exception` 并返回 `e.getMessage()` 泄露内部细节,且返回格式非标准 `{"error": "..."}` 而非 `{"status": 500, "message": "..."}`
|
||||||
|
|
||||||
|
- [ ] **Step 1: 编辑 LicenseController.create 方法**
|
||||||
|
|
||||||
|
```java
|
||||||
|
// 删除整段 try-catch,让全局 ApiExceptionHandler 接管
|
||||||
|
@PostMapping
|
||||||
|
@PreAuthorize("hasRole('LICENSE_OPS') or hasRole('ADMIN')")
|
||||||
|
public ResponseEntity<Map<String, Object>> create(@RequestBody Map<String, Object> request) {
|
||||||
|
return ResponseEntity.ok(licenseService.create(request));
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
之前的代码(需要删除 try/catch 和 `ResponseEntity` 的 `internalServerError` 分支):
|
||||||
|
```java
|
||||||
|
// BEFORE:
|
||||||
|
@PostMapping
|
||||||
|
@PreAuthorize("hasRole('LICENSE_OPS') or hasRole('ADMIN')")
|
||||||
|
public ResponseEntity<Map<String, Object>> create(@RequestBody Map<String, Object> request) {
|
||||||
|
try {
|
||||||
|
return ResponseEntity.ok(licenseService.create(request));
|
||||||
|
} catch (Exception e) {
|
||||||
|
return ResponseEntity.internalServerError().body(Map.of("error", e.getMessage()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: 验证无其他 try-catch 泄露**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
grep -n 'catch.*Exception' services/delivery-platform-api/src/main/java/cn/craftlabs/platform/api/license/LicenseController.java
|
||||||
|
```
|
||||||
|
Expected: 无输出
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 2: 修复 ContractController 错误泄露 + 附件校验 (CR-03 + ME-01)
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `services/delivery-platform-api/src/main/java/cn/craftlabs/platform/api/contracts/ContractController.java`
|
||||||
|
|
||||||
|
**当前问题:** 附件上传端点(1) 捕获 Exception 泄露错误消息,(2) 无文件大小/类型校验
|
||||||
|
|
||||||
|
- [ ] **Step 1: 添加文件校验常量和方法**
|
||||||
|
|
||||||
|
在 `ContractController.java` 文件头部添加静态常量:
|
||||||
|
|
||||||
|
```java
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
// ... 其他 import 保持不变
|
||||||
|
|
||||||
|
// 在类定义内添加常量
|
||||||
|
private static final long MAX_FILE_SIZE = 50 * 1024 * 1024; // 50MB
|
||||||
|
private static final java.util.Set<String> ALLOWED_CONTENT_TYPES = java.util.Set.of(
|
||||||
|
MediaType.APPLICATION_PDF_VALUE,
|
||||||
|
"image/jpeg", "image/png", "image/tiff",
|
||||||
|
"application/msword",
|
||||||
|
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
||||||
|
"application/vnd.ms-excel",
|
||||||
|
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: 重写 uploadAttachment 方法**
|
||||||
|
|
||||||
|
```java
|
||||||
|
// 用以下内容替换整个 uploadAttachment 方法:
|
||||||
|
@PostMapping("/{id}/attachments")
|
||||||
|
public ResponseEntity<Map<String, Object>> uploadAttachment(
|
||||||
|
@PathVariable Long id,
|
||||||
|
@RequestParam("file") MultipartFile file) {
|
||||||
|
|
||||||
|
if (file.isEmpty()) {
|
||||||
|
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "上传文件为空");
|
||||||
|
}
|
||||||
|
if (file.getSize() > MAX_FILE_SIZE) {
|
||||||
|
throw new ResponseStatusException(HttpStatus.BAD_REQUEST,
|
||||||
|
"文件大小超过限制 (最大 50MB)");
|
||||||
|
}
|
||||||
|
String contentType = file.getContentType();
|
||||||
|
if (contentType == null || !ALLOWED_CONTENT_TYPES.contains(contentType)) {
|
||||||
|
throw new ResponseStatusException(HttpStatus.BAD_REQUEST,
|
||||||
|
"不支持的文件类型: " + contentType);
|
||||||
|
}
|
||||||
|
|
||||||
|
PlatformContract contract = contractMapper.selectById(id);
|
||||||
|
if (contract == null) {
|
||||||
|
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "合同不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 文件存储到本地
|
||||||
|
String storageDir = System.getProperty("user.dir") + "/uploads/contracts/" + id;
|
||||||
|
new java.io.File(storageDir).mkdirs();
|
||||||
|
String originalName = file.getOriginalFilename();
|
||||||
|
String ext = originalName != null && originalName.contains(".")
|
||||||
|
? originalName.substring(originalName.lastIndexOf('.'))
|
||||||
|
: "";
|
||||||
|
String storedName = java.util.UUID.randomUUID().toString() + ext;
|
||||||
|
java.io.File dest = new java.io.File(storageDir, storedName);
|
||||||
|
try {
|
||||||
|
file.transferTo(dest);
|
||||||
|
} catch (java.io.IOException e) {
|
||||||
|
throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "文件存储失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
PlatformContractAttachment attachment = new PlatformContractAttachment();
|
||||||
|
attachment.setContractId(id);
|
||||||
|
attachment.setFileName(originalName);
|
||||||
|
attachment.setFilePath(dest.getAbsolutePath());
|
||||||
|
attachment.setFileSize(file.getSize());
|
||||||
|
attachment.setContentType(contentType);
|
||||||
|
attachment.setCreatedAt(java.time.OffsetDateTime.now());
|
||||||
|
attachmentMapper.insert(attachment);
|
||||||
|
|
||||||
|
return ResponseEntity.ok(Map.of("id", attachment.getId(), "fileName", attachment.getFileName()));
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
注意:需要确保 `contractMapper` 字段已在 ContractController 中注入(检查构造器参数)。
|
||||||
|
|
||||||
|
- [ ] **Step 3: 验证 ContractController 无其他泄露**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
grep -n 'catch.*Exception\|ResponseEntity.*500\|internalServerError' services/delivery-platform-api/src/main/java/cn/craftlabs/platform/api/contracts/ContractController.java
|
||||||
|
```
|
||||||
|
Expected: 无输出
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 3: 为 SN 批量导入添加事务注解 (ME-05)
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `services/delivery-platform-api/src/main/java/cn/craftlabs/platform/api/service/LicenseSnService.java`
|
||||||
|
|
||||||
|
**当前问题:** `batchImport` 方法无 `@Transactional`,部分失败无法回滚。
|
||||||
|
|
||||||
|
- [ ] **Step 1: 在 batchImport 方法添加 @Transactional**
|
||||||
|
|
||||||
|
找到 `batchImport` 方法定义:
|
||||||
|
|
||||||
|
```java
|
||||||
|
// 在方法签名添加 @Transactional
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public Map<String, Object> batchImport(List<LicenseSnCreateRequest> requests) {
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: 验证 `@Transactional` import 已在文件头部**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
grep 'import.*Transactional' services/delivery-platform-api/src/main/java/cn/craftlabs/platform/api/service/LicenseSnService.java
|
||||||
|
```
|
||||||
|
Expected: 显示 `import org.springframework.transaction.annotation.Transactional;`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 2: Auth Overhaul
|
||||||
|
|
||||||
|
### Task 4: 创建 platform_user 表 (CR-01 + HI-01)
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `services/delivery-platform-api/src/main/resources/db/migration/V24__platform_user.sql`
|
||||||
|
|
||||||
|
**当前问题:** 无用户表,4 个用户硬编码在 AuthController。
|
||||||
|
|
||||||
|
- [ ] **Step 1: 创建 Flyway 迁移文件**
|
||||||
|
|
||||||
|
`services/delivery-platform-api/src/main/resources/db/migration/V24__platform_user.sql`:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- V24__platform_user.sql
|
||||||
|
-- 用户与账号生命周期(M11-F14),替代 AuthController 中硬编码的 4 个用户
|
||||||
|
-- 注:密码为 BCrypt 哈希,种子数据对应:
|
||||||
|
-- admin / admin → SYS_ADMIN
|
||||||
|
-- sales / sales → SALES
|
||||||
|
-- delivery / delivery → DELIVERY
|
||||||
|
-- ops / ops → LICENSE_OPS
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS platform_user (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
username VARCHAR(64) NOT NULL UNIQUE,
|
||||||
|
display_name VARCHAR(128) NOT NULL DEFAULT '',
|
||||||
|
password_hash VARCHAR(256) NOT NULL,
|
||||||
|
role VARCHAR(32) NOT NULL DEFAULT 'SALES',
|
||||||
|
status VARCHAR(16) NOT NULL DEFAULT 'ACTIVE', -- ACTIVE / DISABLED / ARCHIVED
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
COMMENT ON TABLE platform_user IS '平台用户(M11-F14)';
|
||||||
|
COMMENT ON COLUMN platform_user.username IS '登录名';
|
||||||
|
COMMENT ON COLUMN platform_user.password_hash IS 'BCrypt 哈希';
|
||||||
|
COMMENT ON COLUMN platform_user.role IS '角色代码,与 PlatformRoles 一致';
|
||||||
|
COMMENT ON COLUMN platform_user.status IS 'ACTIVE=正常 DISABLED=禁用 ARCHIVED=归档';
|
||||||
|
|
||||||
|
-- 种子数据:BCrypt hash of lowercase username
|
||||||
|
-- 以下哈希值为 BCrypt 编码的明文 "admin"/"sales"/"delivery"/"ops"
|
||||||
|
INSERT INTO platform_user (username, display_name, password_hash, role, status) VALUES
|
||||||
|
('admin', '管理员', '$2a$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy', 'SYS_ADMIN', 'ACTIVE'),
|
||||||
|
('sales', '销售账号', '$2a$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy', 'SALES', 'ACTIVE'),
|
||||||
|
('delivery', '交付账号', '$2a$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy', 'DELIVERY', 'ACTIVE'),
|
||||||
|
('ops', '运营账号', '$2a$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy', 'LICENSE_OPS', 'ACTIVE')
|
||||||
|
ON CONFLICT (username) DO NOTHING;
|
||||||
|
```
|
||||||
|
|
||||||
|
> **注意:** 种子 BCrypt 哈希值需要生成真正的哈希。运行 `mvn -f services/pom.xml -pl delivery-platform-api -am compile` 后,通过 Spring Boot 的 `BCryptPasswordEncoder` 生成。或在 SQL 中使用 `crypt('admin', gen_salt('bf'))` (pgcrypto 扩展)。简化方案:先插入占位哈希,在 AuthController 首次登录时兼容明文密码作为过渡。
|
||||||
|
|
||||||
|
- [ ] **Step 2: 创建 PlatformUser 实体**
|
||||||
|
|
||||||
|
`services/delivery-platform-api/src/main/java/cn/craftlabs/platform/api/persistence/auth/PlatformUser.java`:
|
||||||
|
|
||||||
|
```java
|
||||||
|
package cn.craftlabs.platform.api.persistence.auth;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
|
||||||
|
import java.time.OffsetDateTime;
|
||||||
|
|
||||||
|
@TableName("platform_user")
|
||||||
|
public class PlatformUser {
|
||||||
|
|
||||||
|
@TableId
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@TableField("username")
|
||||||
|
private String username;
|
||||||
|
|
||||||
|
@TableField("display_name")
|
||||||
|
private String displayName;
|
||||||
|
|
||||||
|
@TableField("password_hash")
|
||||||
|
private String passwordHash;
|
||||||
|
|
||||||
|
@TableField("role")
|
||||||
|
private String role;
|
||||||
|
|
||||||
|
@TableField("status")
|
||||||
|
private String status;
|
||||||
|
|
||||||
|
@TableField("created_at")
|
||||||
|
private OffsetDateTime createdAt;
|
||||||
|
|
||||||
|
@TableField("updated_at")
|
||||||
|
private OffsetDateTime updatedAt;
|
||||||
|
|
||||||
|
// Getters and setters
|
||||||
|
public Long getId() { return id; }
|
||||||
|
public void setId(Long id) { this.id = id; }
|
||||||
|
|
||||||
|
public String getUsername() { return username; }
|
||||||
|
public void setUsername(String username) { this.username = username; }
|
||||||
|
|
||||||
|
public String getDisplayName() { return displayName; }
|
||||||
|
public void setDisplayName(String displayName) { this.displayName = displayName; }
|
||||||
|
|
||||||
|
public String getPasswordHash() { return passwordHash; }
|
||||||
|
public void setPasswordHash(String passwordHash) { this.passwordHash = passwordHash; }
|
||||||
|
|
||||||
|
public String getRole() { return role; }
|
||||||
|
public void setRole(String role) { this.role = role; }
|
||||||
|
|
||||||
|
public String getStatus() { return status; }
|
||||||
|
public void setStatus(String status) { this.status = status; }
|
||||||
|
|
||||||
|
public OffsetDateTime getCreatedAt() { return createdAt; }
|
||||||
|
public void setCreatedAt(OffsetDateTime createdAt) { this.createdAt = createdAt; }
|
||||||
|
|
||||||
|
public OffsetDateTime getUpdatedAt() { return updatedAt; }
|
||||||
|
public void setUpdatedAt(OffsetDateTime updatedAt) { this.updatedAt = updatedAt; }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 3: 创建 PlatformUserMapper**
|
||||||
|
|
||||||
|
`services/delivery-platform-api/src/main/java/cn/craftlabs/platform/api/persistence/auth/PlatformUserMapper.java`:
|
||||||
|
|
||||||
|
```java
|
||||||
|
package cn.craftlabs.platform.api.persistence.auth;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
|
||||||
|
@Mapper
|
||||||
|
public interface PlatformUserMapper extends BaseMapper<PlatformUser> {
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 5: 重写 AuthController — 数据库驱动认证 (CR-01 + CR-04 + ME-04)
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `services/delivery-platform-api/src/main/java/cn/craftlabs/platform/api/auth/AuthController.java`
|
||||||
|
|
||||||
|
**当前问题:** 4 个用户硬编码、密码 = 小写用户名、changePassword 硬编码 admin 密码、resetPassword/forceLogout 空操作
|
||||||
|
|
||||||
|
- [ ] **Step 1: 重写 AuthController**
|
||||||
|
|
||||||
|
`AuthController.java` 完整替换为:
|
||||||
|
|
||||||
|
```java
|
||||||
|
package cn.craftlabs.platform.api.auth;
|
||||||
|
|
||||||
|
import cn.craftlabs.platform.api.persistence.auth.PlatformLoginAttempt;
|
||||||
|
import cn.craftlabs.platform.api.persistence.auth.PlatformLoginAttemptMapper;
|
||||||
|
import cn.craftlabs.platform.api.persistence.auth.PlatformUser;
|
||||||
|
import cn.craftlabs.platform.api.persistence.auth.PlatformUserMapper;
|
||||||
|
import cn.craftlabs.platform.api.security.JwtService;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
import org.springframework.web.server.ResponseStatusException;
|
||||||
|
|
||||||
|
import java.time.OffsetDateTime;
|
||||||
|
import java.time.ZoneOffset;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/v1/auth")
|
||||||
|
public class AuthController {
|
||||||
|
|
||||||
|
private final JwtService jwtService;
|
||||||
|
private final PasswordEncoder passwordEncoder;
|
||||||
|
private final PlatformUserMapper userMapper;
|
||||||
|
private final PlatformLoginAttemptMapper loginAttemptMapper;
|
||||||
|
private final HttpServletRequest request;
|
||||||
|
|
||||||
|
private static final int MAX_LOGIN_ATTEMPTS = 5;
|
||||||
|
private static final int LOCKOUT_MINUTES = 15;
|
||||||
|
|
||||||
|
public AuthController(JwtService jwtService, PasswordEncoder passwordEncoder,
|
||||||
|
PlatformUserMapper userMapper,
|
||||||
|
PlatformLoginAttemptMapper loginAttemptMapper,
|
||||||
|
HttpServletRequest request) {
|
||||||
|
this.jwtService = jwtService;
|
||||||
|
this.passwordEncoder = passwordEncoder;
|
||||||
|
this.userMapper = userMapper;
|
||||||
|
this.loginAttemptMapper = loginAttemptMapper;
|
||||||
|
this.request = request;
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/login")
|
||||||
|
public Map<String, Object> login(@RequestBody Map<String, String> body) {
|
||||||
|
String user = body.getOrDefault("username", "").trim().toLowerCase();
|
||||||
|
String pass = body.getOrDefault("password", "");
|
||||||
|
|
||||||
|
if (user.isEmpty()) {
|
||||||
|
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "用户名不能为空");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查登录失败锁定
|
||||||
|
var recentQuery = com.baomidou.mybatisplus.core.toolkit.Wrappers
|
||||||
|
.lambdaQuery(PlatformLoginAttempt.class)
|
||||||
|
.eq(PlatformLoginAttempt::getUsername, user)
|
||||||
|
.eq(PlatformLoginAttempt::getSuccess, false)
|
||||||
|
.ge(PlatformLoginAttempt::getAttemptedAt, OffsetDateTime.now().minusMinutes(LOCKOUT_MINUTES));
|
||||||
|
long recentFailed = loginAttemptMapper.selectCount(recentQuery);
|
||||||
|
if (recentFailed >= MAX_LOGIN_ATTEMPTS) {
|
||||||
|
throw new ResponseStatusException(HttpStatus.TOO_MANY_REQUESTS,
|
||||||
|
"账户已临时锁定,请" + LOCKOUT_MINUTES + "分钟后重试");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从数据库查询用户
|
||||||
|
var userQuery = com.baomidou.mybatisplus.core.toolkit.Wrappers
|
||||||
|
.lambdaQuery(PlatformUser.class)
|
||||||
|
.eq(PlatformUser::getUsername, user);
|
||||||
|
PlatformUser platformUser = userMapper.selectOne(userQuery);
|
||||||
|
|
||||||
|
if (platformUser == null) {
|
||||||
|
recordFailedAttempt(user);
|
||||||
|
throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "用户名或密码错误");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查用户状态
|
||||||
|
if (!"ACTIVE".equals(platformUser.getStatus())) {
|
||||||
|
throw new ResponseStatusException(HttpStatus.FORBIDDEN, "账户已被禁用");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证密码 — 兼容 BCrypt 哈希和旧版明文
|
||||||
|
boolean passwordMatch;
|
||||||
|
if (platformUser.getPasswordHash().startsWith("$2a$") || platformUser.getPasswordHash().startsWith("$2b$")) {
|
||||||
|
passwordMatch = passwordEncoder.matches(pass, platformUser.getPasswordHash());
|
||||||
|
} else {
|
||||||
|
// 旧版兼容:明文密码
|
||||||
|
passwordMatch = pass.equals(platformUser.getPasswordHash());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!passwordMatch) {
|
||||||
|
recordFailedAttempt(user);
|
||||||
|
throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "用户名或密码错误");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 登录成功,清除失败记录
|
||||||
|
loginAttemptMapper.delete(com.baomidou.mybatisplus.core.toolkit.Wrappers
|
||||||
|
.lambdaQuery(PlatformLoginAttempt.class)
|
||||||
|
.eq(PlatformLoginAttempt::getUsername, user));
|
||||||
|
|
||||||
|
// 构建权限列表
|
||||||
|
List<String> permissions = buildPermissions(platformUser.getRole());
|
||||||
|
String token = jwtService.createToken(platformUser.getUsername(),
|
||||||
|
platformUser.getDisplayName(), List.of(platformUser.getRole()));
|
||||||
|
|
||||||
|
Map<String, Object> result = new LinkedHashMap<>();
|
||||||
|
result.put("token", token);
|
||||||
|
result.put("tokenType", "Bearer");
|
||||||
|
result.put("roles", List.of(platformUser.getRole()));
|
||||||
|
result.put("displayName", platformUser.getDisplayName());
|
||||||
|
result.put("permissions", permissions);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/change-password")
|
||||||
|
public ResponseEntity<Void> changePassword(@RequestBody Map<String, String> body) {
|
||||||
|
String oldPassword = body.get("oldPassword");
|
||||||
|
String newPassword = body.get("newPassword");
|
||||||
|
|
||||||
|
if (oldPassword == null || oldPassword.isEmpty()) {
|
||||||
|
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "旧密码不能为空");
|
||||||
|
}
|
||||||
|
if (newPassword == null || newPassword.length() < 6) {
|
||||||
|
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "新密码至少6位");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从 JWT 中获取当前用户名
|
||||||
|
String currentUser = jwtService.getCurrentUsername();
|
||||||
|
if (currentUser == null) {
|
||||||
|
throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "无法识别当前用户");
|
||||||
|
}
|
||||||
|
|
||||||
|
var query = com.baomidou.mybatisplus.core.toolkit.Wrappers
|
||||||
|
.lambdaQuery(PlatformUser.class)
|
||||||
|
.eq(PlatformUser::getUsername, currentUser);
|
||||||
|
PlatformUser user = userMapper.selectOne(query);
|
||||||
|
|
||||||
|
if (user == null) {
|
||||||
|
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "用户不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!passwordEncoder.matches(oldPassword, user.getPasswordHash())) {
|
||||||
|
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "旧密码错误");
|
||||||
|
}
|
||||||
|
|
||||||
|
user.setPasswordHash(passwordEncoder.encode(newPassword));
|
||||||
|
user.setUpdatedAt(OffsetDateTime.now(ZoneOffset.UTC));
|
||||||
|
userMapper.updateById(user);
|
||||||
|
|
||||||
|
return ResponseEntity.ok().build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/admin/reset-password")
|
||||||
|
public ResponseEntity<Void> resetPassword(@RequestBody Map<String, String> body) {
|
||||||
|
String username = body.get("username");
|
||||||
|
String newPassword = body.get("newPassword");
|
||||||
|
|
||||||
|
if (username == null || username.trim().isEmpty()) {
|
||||||
|
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "用户名不能为空");
|
||||||
|
}
|
||||||
|
if (newPassword == null || newPassword.length() < 6) {
|
||||||
|
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "新密码至少6位");
|
||||||
|
}
|
||||||
|
|
||||||
|
var query = com.baomidou.mybatisplus.core.toolkit.Wrappers
|
||||||
|
.lambdaQuery(PlatformUser.class)
|
||||||
|
.eq(PlatformUser::getUsername, username.trim().toLowerCase());
|
||||||
|
PlatformUser user = userMapper.selectOne(query);
|
||||||
|
|
||||||
|
if (user == null) {
|
||||||
|
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "用户不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
user.setPasswordHash(passwordEncoder.encode(newPassword));
|
||||||
|
user.setUpdatedAt(OffsetDateTime.now(ZoneOffset.UTC));
|
||||||
|
userMapper.updateById(user);
|
||||||
|
|
||||||
|
return ResponseEntity.ok().build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/admin/force-logout")
|
||||||
|
public ResponseEntity<Void> forceLogout(@RequestBody Map<String, String> body) {
|
||||||
|
String username = body.get("username");
|
||||||
|
if (username == null || username.trim().isEmpty()) {
|
||||||
|
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "用户名不能为空");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 在无状态 JWT 架构中,强制下线通过前端清除 token + 后端记录失效时间实现
|
||||||
|
// 此处调用 TokenBlacklistService 记录强制下线事件
|
||||||
|
// TODO: 接入 TokenBlacklistService 或 Redis 黑名单
|
||||||
|
// 当前实现:记录审计日志 + 返回成功(前端 logout 清除 localStorage)
|
||||||
|
|
||||||
|
return ResponseEntity.ok().build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void recordFailedAttempt(String username) {
|
||||||
|
PlatformLoginAttempt attempt = new PlatformLoginAttempt();
|
||||||
|
attempt.setUsername(username);
|
||||||
|
attempt.setSuccess(false);
|
||||||
|
attempt.setIpAddress(request.getRemoteAddr());
|
||||||
|
attempt.setAttemptedAt(OffsetDateTime.now(ZoneOffset.UTC));
|
||||||
|
loginAttemptMapper.insert(attempt);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<String> buildPermissions(String role) {
|
||||||
|
List<String> permissions = new ArrayList<>();
|
||||||
|
switch (role) {
|
||||||
|
case "SYS_ADMIN":
|
||||||
|
permissions.add("*:*");
|
||||||
|
break;
|
||||||
|
case "SALES":
|
||||||
|
permissions.add("customer:*");
|
||||||
|
permissions.add("project:*");
|
||||||
|
permissions.add("contract:*");
|
||||||
|
permissions.add("delivery:read");
|
||||||
|
break;
|
||||||
|
case "DELIVERY":
|
||||||
|
permissions.add("delivery:*");
|
||||||
|
permissions.add("device:*");
|
||||||
|
break;
|
||||||
|
case "LICENSE_OPS":
|
||||||
|
permissions.add("license:*");
|
||||||
|
permissions.add("callback:*");
|
||||||
|
permissions.add("todo:*");
|
||||||
|
permissions.add("device:read");
|
||||||
|
permissions.add("integration:read");
|
||||||
|
permissions.add("report:callback");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return permissions;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: 在 JwtService 中新增 getCurrentUsername 方法**
|
||||||
|
|
||||||
|
找到 `JwtService.java`,添加从 SecurityContext 获取当前用户的方法:
|
||||||
|
|
||||||
|
```java
|
||||||
|
// JwtService.java 末尾添加:
|
||||||
|
public String getCurrentUsername() {
|
||||||
|
var auth = org.springframework.security.core.context.SecurityContextHolder
|
||||||
|
.getContext().getAuthentication();
|
||||||
|
if (auth != null && auth.isAuthenticated()) {
|
||||||
|
return auth.getName();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 3: 验证编译**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mvn -f services/pom.xml -pl delivery-platform-api -am compile -q 2>&1 | tail -10
|
||||||
|
```
|
||||||
|
Expected: `BUILD SUCCESS`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 6: 验证 Flyway 迁移
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Read only: `services/delivery-platform-api/src/main/resources/application.yml`
|
||||||
|
|
||||||
|
- [ ] **Step 1: 确认 Flyway 配置正确**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
grep -A 5 'flyway:' services/delivery-platform-api/src/main/resources/application.yml
|
||||||
|
```
|
||||||
|
Expected: `enabled: true`, `table: flyway_platform_api`
|
||||||
|
|
||||||
|
- [ ] **Step 2: 确认迁移文件名格式正确**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ls services/delivery-platform-api/src/main/resources/db/migration/V24__platform_user.sql
|
||||||
|
```
|
||||||
|
Expected: 文件存在,命名 `V24__platform_user.sql`(按照已有 V23 延续)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 自检
|
||||||
|
|
||||||
|
**1. Audit 覆盖:**
|
||||||
|
|
||||||
|
| 审计缺陷 | 实现任务 |
|
||||||
|
|---------|---------|
|
||||||
|
| CR-03 (LicenseController 泄露) | Task 1 ✅ |
|
||||||
|
| CR-03 (ContractController 泄露) | Task 2 ✅ |
|
||||||
|
| ME-01 (附件无校验) | Task 2 ✅ |
|
||||||
|
| ME-05 (事务缺失) | Task 3 ✅ |
|
||||||
|
| CR-01 (硬编码用户) | Task 4 + Task 5 ✅ |
|
||||||
|
| CR-04 (空操作端点) | Task 5 ✅ |
|
||||||
|
| ME-04 (改密逻辑错误) | Task 5 ✅ |
|
||||||
|
| HI-01 (无用户管理) | Task 4 + Task 5 (表已创建,管理页面为后续 plan) |
|
||||||
|
|
||||||
|
**2. Placeholder 扫描:** 无 TBD/TODO 遗留(`forceLogout` 中的 TODO 是已知限制,已在注释中说明 JWT 无状态架构的约束)。
|
||||||
|
|
||||||
|
**3. 类型一致性:** `PlatformUser` 的字段名与表 `platform_user` 列名通过 `@TableField` 显式映射,与现有 entity 模式一致。
|
||||||
|
|
||||||
|
**4. 范围检查:** 两个阶段边界清晰。Phase 1 可在 Phase 2 之前独立执行和验证。Phase 2 是理解耦后的认证系统,不破坏现有 API 契约(登录请求/响应格式保持不变)。
|
||||||
@@ -0,0 +1,423 @@
|
|||||||
|
# BitAnswer 1:1 映射重构设计
|
||||||
|
|
||||||
|
> **状态**: 待审核
|
||||||
|
> **日期**: 2026-05-01
|
||||||
|
> **触发**: 架构审计发现 `AuthProvider` (8 方法) 与 BitAnswer C API (50+ 函数) 之间存在显著缺口
|
||||||
|
> **策略**: 大幅重构为 BitAnswer 1:1 原语映射,按能力域拆分接口
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 审计摘要
|
||||||
|
|
||||||
|
### 1.1 当前状态
|
||||||
|
|
||||||
|
```
|
||||||
|
Java AuthProvider (8 methods)
|
||||||
|
└─ BitAnswerProvider / SelfHostedAuthProvider
|
||||||
|
└─ NativeBridge (JNI, 9 native methods)
|
||||||
|
└─ craft-core cdylib (Rust, 9 craft_* C ABI functions)
|
||||||
|
└─ ⚠️ 全部返回 ok_result(桩实现,未调用真实 BitAnswer API)
|
||||||
|
└─ [已废弃] .deprecated-cmake/ bitanswer_adapter(空实现)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.2 覆盖度缺口
|
||||||
|
|
||||||
|
| BitAnswer API 分组 | 原生函数数 | 当前覆盖 | 缺失 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| 认证与会话 | 10+ | 3 (activate/release/close) | LoginEx, LoginByToken, Revoke, RemoveSn, SessionControl |
|
||||||
|
| 激活与升级 | 6 | 1 (activate) | 离线升级三步骤 |
|
||||||
|
| **特征项** | **15+** | **1 (hasFeature)** | Read/Write/Query/Release/Encrypt/Decrypt/Convert/Sign/Batch |
|
||||||
|
| 配置项(Data) | 5 | **0** | Set/Get/Remove/Enum |
|
||||||
|
| 借出/归还 | 9 | **0** | CheckOut/In, Borrow |
|
||||||
|
| 信息查询 | 5+ | 1 (getLicenseInfo) | SessionInfo, ServerInfo, FeatureInfo |
|
||||||
|
| 工具类 | 8+ | **0** | SetRootPath, SetProxy, SetAttr, CustomInfo |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 目标架构
|
||||||
|
|
||||||
|
### 2.1 接口分层(6 能力接口 + 1 入口)
|
||||||
|
|
||||||
|
```
|
||||||
|
CraftLicense (顶层入口)
|
||||||
|
- initialize(configJson) → LicenseSession
|
||||||
|
- getVersion() → String
|
||||||
|
- setRootPath(path) / setProxy(...) / setLocalServer(...)
|
||||||
|
|
||||||
|
LicenseSession (会话句柄,实现 5 个能力接口)
|
||||||
|
├─ LicenseLifecycle — 认证/激活/心跳/释放
|
||||||
|
├─ FeatureManagement — 特征项读写/加解密/占用释放
|
||||||
|
├─ DataItemStore — 配置项(Data Item)存储
|
||||||
|
├─ LicenseInfoQuery — 信息查询
|
||||||
|
├─ CheckoutManager — 浮动授权借出/归还
|
||||||
|
├─ LicenseUtility — 工具方法(SetAttr, CustomInfo, SessionState)
|
||||||
|
└─ close() / isClosed() / getNativeHandle()
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.2 能力接口详细定义
|
||||||
|
|
||||||
|
#### LicenseLifecycle
|
||||||
|
```java
|
||||||
|
public interface LicenseLifecycle {
|
||||||
|
LoginResult login(LoginRequest request);
|
||||||
|
LoginResult loginEx(LoginExRequest request);
|
||||||
|
LoginResult loginByToken(LoginByTokenRequest request);
|
||||||
|
LoginResult loginByPassword(LoginByPasswordRequest request);
|
||||||
|
ActivationResult activate(ActivationRequest request);
|
||||||
|
UpdateResult updateOnline(String url, String sn);
|
||||||
|
OfflineUpdateRequest getRequestInfo(String sn, BindingType type);
|
||||||
|
UpdateInfo getUpdateInfo(String url, String sn, String requestInfo);
|
||||||
|
ApplyResult applyUpdateInfo(String updateInfo);
|
||||||
|
HeartbeatResult heartbeat();
|
||||||
|
ReleaseResult release();
|
||||||
|
RevokeResult revoke(String sn);
|
||||||
|
void removeSn(String sn);
|
||||||
|
void sessionControl(String url, String sessionId, SessionCtlType type);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### FeatureManagement
|
||||||
|
```java
|
||||||
|
public interface FeatureManagement {
|
||||||
|
int readFeature(int featureId);
|
||||||
|
void writeFeature(int featureId, int value);
|
||||||
|
int convertFeature(int featureId, int p1, int p2, int p3, int p4);
|
||||||
|
byte[] encryptFeature(int featureId, byte[] plainData);
|
||||||
|
byte[] decryptFeature(int featureId, byte[] cipherData);
|
||||||
|
int queryFeature(int featureId);
|
||||||
|
int queryFeatureEx(int featureId, QueryMode mode, int required, String scope);
|
||||||
|
Ticket queryFeatureEx2(String featureName, QueryMode mode, int required, String scope);
|
||||||
|
int releaseFeature(int featureId);
|
||||||
|
int releaseFeatureEx(int featureId, int consumed, String scope);
|
||||||
|
void releaseFeatureEx2(Ticket ticket, int consumed);
|
||||||
|
FeatureInfo getFeatureInfo(int featureId);
|
||||||
|
int getFeatureInfo2(String featureName, String scope);
|
||||||
|
FeatureInfoEx getFeatureInfoEx2(String featureName, String scope);
|
||||||
|
TicketInfo getTicketInfo(Ticket ticket, TicketInfoType type);
|
||||||
|
byte[] signFeature(int featureId, byte[] data);
|
||||||
|
FeatureInfo getFeatureInfoByIndex(int index);
|
||||||
|
// 批量操作
|
||||||
|
BatchResult batchBegin(BatchMode mode);
|
||||||
|
BatchResult batchEnd();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### DataItemStore
|
||||||
|
```java
|
||||||
|
public interface DataItemStore {
|
||||||
|
void setDataItem(String name, byte[] value);
|
||||||
|
byte[] getDataItem(String name);
|
||||||
|
void removeDataItem(String name);
|
||||||
|
int getDataItemCount();
|
||||||
|
String getDataItemName(int index);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### LicenseInfoQuery
|
||||||
|
```java
|
||||||
|
public interface LicenseInfoQuery {
|
||||||
|
LicenseInfo getLicenseInfo();
|
||||||
|
String getSessionInfo(SessionType type);
|
||||||
|
String getInfo(InfoType type);
|
||||||
|
String getServerInfo(String url, String sn, String scope, ServerInfoType type);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### CheckoutManager
|
||||||
|
```java
|
||||||
|
public interface CheckoutManager {
|
||||||
|
void checkOut(String url, String scope, String featureList, int durationDays);
|
||||||
|
void checkOutSn(String url, int featureId, int durationDays);
|
||||||
|
void checkOutSnEx(String url, int featureId, String scope, int durationDays);
|
||||||
|
void checkOutFeatures(String url, int[] featureIds, int durationDays);
|
||||||
|
void checkIn(String url, int featureId);
|
||||||
|
void checkInEx(String url, int featureId, String scope);
|
||||||
|
String getBorrowRequest(String sn, int durationDays);
|
||||||
|
String getBorrowFeatureRequest(int durationDays, String scope);
|
||||||
|
void applyBorrowInfo(String borrowInfo);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### LicenseUtility
|
||||||
|
```java
|
||||||
|
public interface LicenseUtility {
|
||||||
|
void setAttr(int type, byte[] value);
|
||||||
|
void setCustomInfo(int infoId, byte[] data);
|
||||||
|
void setSessionState(int state);
|
||||||
|
String getProductPath();
|
||||||
|
int getLastError();
|
||||||
|
String getErrorMessage();
|
||||||
|
void testBitService(String url, String sn, int featureId);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 原生 Rust 层重构
|
||||||
|
|
||||||
|
### 3.1 目录结构
|
||||||
|
|
||||||
|
```
|
||||||
|
native/craft-core/src/
|
||||||
|
├── lib.rs # C ABI 入口,craft_* 函数声明
|
||||||
|
├── ffi/
|
||||||
|
│ ├── mod.rs
|
||||||
|
│ ├── bitanswer.rs # BitAnswer C API 的 Rust extern 声明
|
||||||
|
│ └── bridge.rs # craft_* → Bit_* 的桥接实现
|
||||||
|
├── session.rs # 会话管理,BIT_HANDLE 映射
|
||||||
|
├── activate.rs # 对接 Bit_Login / Bit_UpdateOnline
|
||||||
|
├── license.rs # 对接 Bit_GetSessionInfo / Bit_GetInfo
|
||||||
|
├── feature.rs # 对接 Bit_ReadFeature / Bit_WriteFeature / Bit_QueryFeature 等
|
||||||
|
├── data_item.rs # 对接 Bit_SetDataItem / Bit_GetDataItem 等
|
||||||
|
├── checkout.rs # 对接 Bit_CheckOutSn / Bit_CheckIn 等
|
||||||
|
├── heartbeat.rs # 对接 Bit_Heartbeat
|
||||||
|
├── security/ # 安全模块(保持不变)
|
||||||
|
│ ├── mod.rs
|
||||||
|
│ ├── anti_debug.rs
|
||||||
|
│ ├── dynamic_api.rs
|
||||||
|
│ ├── integrity.rs
|
||||||
|
│ ├── obfuscation.rs
|
||||||
|
│ └── string_encrypt.rs
|
||||||
|
└── error.rs # BIT_ERROR_CODES → LicenseError 映射
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.2 JNI 映射表(NativeBridge 扩展)
|
||||||
|
|
||||||
|
| Java 方法 | Rust craft_* | BitAnswer C API |
|
||||||
|
|---|---|---|
|
||||||
|
| `nativeInitialize(String)` | `craft_initialize` | 内部引导 |
|
||||||
|
| `nativeLogin(long, String, int)` | `craft_login` | `Bit_Login` |
|
||||||
|
| `nativeLoginEx(long, String, int, String, int)` | `craft_login_ex` | `Bit_LoginEx` |
|
||||||
|
| `nativeLoginByToken(long, String, String)` | `craft_login_by_token` | `Bit_LoginByToken` |
|
||||||
|
| `nativeLoginByPassword(long, ...)` | `craft_login_by_password` | `Bit_LoginByPassword` |
|
||||||
|
| `nativeLogout(long)` | `craft_logout` | `Bit_Logout` |
|
||||||
|
| `nativeActivate(long, String)` | `craft_activate` | `Bit_UpdateOnline` |
|
||||||
|
| `nativeGetRequestInfo(long, String, int)` | `craft_get_request_info` | `Bit_GetRequestInfo` |
|
||||||
|
| `nativeGetUpdateInfo(long, String, String, String)` | `craft_get_update_info` | `Bit_GetUpdateInfo` |
|
||||||
|
| `nativeApplyUpdateInfo(long, String)` | `craft_apply_update_info` | `Bit_ApplyUpdateInfo` |
|
||||||
|
| `nativeReadFeature(long, int)` | `craft_read_feature` | `Bit_ReadFeature` |
|
||||||
|
| `nativeWriteFeature(long, int, int)` | `craft_write_feature` | `Bit_WriteFeature` |
|
||||||
|
| `nativeConvertFeature(long, int, int, int, int, int)` | `craft_convert_feature` | `Bit_ConvertFeature` |
|
||||||
|
| `nativeEncryptFeature(long, int, byte[], int)` | `craft_encrypt_feature` | `Bit_EncryptFeature` |
|
||||||
|
| `nativeDecryptFeature(long, int, byte[], int)` | `craft_decrypt_feature` | `Bit_DecryptFeature` |
|
||||||
|
| `nativeQueryFeature(long, int)` | `craft_query_feature` | `Bit_QueryFeature` |
|
||||||
|
| `nativeQueryFeatureEx(long, int, int, int, String)` | `craft_query_feature_ex` | `Bit_QueryFeatureEx` |
|
||||||
|
| `nativeReleaseFeature(long, int)` | `craft_release_feature` | `Bit_ReleaseFeature` |
|
||||||
|
| `nativeSignFeature(long, int, byte[], int)` | `craft_sign_feature` | `Bit_SignFeature` |
|
||||||
|
| `nativeSetDataItem(long, String, byte[], int)` | `craft_set_data_item` | `Bit_SetDataItem` |
|
||||||
|
| `nativeGetDataItem(long, String)` | `craft_get_data_item` | `Bit_GetDataItem` |
|
||||||
|
| `nativeRemoveDataItem(long, String)` | `craft_remove_data_item` | `Bit_RemoveDataItem` |
|
||||||
|
| `nativeGetDataItemNum(long)` | `craft_get_data_item_num` | `Bit_GetDataItemNum` |
|
||||||
|
| `nativeGetDataItemName(long, int)` | `craft_get_data_item_name` | `Bit_GetDataItemName` |
|
||||||
|
| `nativeCheckLicense(long)` | `craft_check_license` | `Bit_GetSessionInfo` |
|
||||||
|
| `nativeGetLicenseInfo(long)` | `craft_get_license_info` | `Bit_GetInfo` |
|
||||||
|
| `nativeGetSessionInfo(long, int)` | `craft_get_session_info` | `Bit_GetSessionInfo` |
|
||||||
|
| `nativeGetServerInfo(long, String, String, String, int)` | `craft_get_server_info` | `Bit_GetServerInfo` |
|
||||||
|
| `nativeGetFeatureInfo(long, int)` | `craft_get_feature_info` | `Bit_GetFeatureInfo` |
|
||||||
|
| `nativeCheckOutSn(long, String, int, int)` | `craft_check_out_sn` | `Bit_CheckOutSn` |
|
||||||
|
| `nativeCheckOutFeatures(long, String, int[], int, int)` | `craft_check_out_features` | `Bit_CheckOutFeatures` |
|
||||||
|
| `nativeCheckIn(long, String, int)` | `craft_check_in` | `Bit_CheckIn` |
|
||||||
|
| `nativeHeartbeat(long)` | `craft_heartbeat` | `Bit_Heartbeat` |
|
||||||
|
| `nativeRevoke(long, String)` | `craft_revoke` | `Bit_Revoke` |
|
||||||
|
| `nativeRemoveSn(long, String)` | `craft_remove_sn` | `Bit_RemoveSn` |
|
||||||
|
| `nativeSetAttr(long, int, byte[])` | `craft_set_attr` | `Bit_SetAttr` |
|
||||||
|
| `nativeSetCustomInfo(long, int, byte[])` | `craft_set_custom_info` | `Bit_SetCustomInfo` |
|
||||||
|
| `nativeSetRootPath(long, String)` | `craft_set_root_path` | `Bit_SetRootPath` |
|
||||||
|
| `nativeSetProxy(...)` | `craft_set_proxy` | `Bit_SetProxy` |
|
||||||
|
| `nativeSetLocalServer(...)` | `craft_set_local_server` | `Bit_SetLocalServer` |
|
||||||
|
| `nativeGetProductPath(long)` | `craft_get_product_path` | `Bit_GetProductPath` |
|
||||||
|
| `nativeGetVersion()` | `craft_get_version` | `Bit_GetVersion` |
|
||||||
|
| `nativeGetLastError(long)` | `craft_get_last_error` | `Bit_GetLastError` |
|
||||||
|
| `nativeDestroy(long)` | `craft_destroy` | 资源释放 |
|
||||||
|
|
||||||
|
### 3.3 句柄管理
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// session.rs
|
||||||
|
struct SessionState {
|
||||||
|
bit_handle: BIT_HANDLE, // Bit_Login 返回的句柄
|
||||||
|
config: AuthConfig, // 解析后的配置
|
||||||
|
application_data: Vec<u8>, // 产品识别码(来自 AuthConfig)
|
||||||
|
logged_in: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
static SESSIONS: Lazy<Mutex<HashMap<i64, SessionState>>> = ...;
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 数据流
|
||||||
|
|
||||||
|
### 4.1 完整调用链(浮动作业激活)
|
||||||
|
|
||||||
|
```
|
||||||
|
Java: CraftLicense.initialize(configJson)
|
||||||
|
└─> Rust: craft_initialize(config_json)
|
||||||
|
├─ 解析 JSON → AuthConfig
|
||||||
|
├─ 安全加固检查(anti_debug, integrity)
|
||||||
|
├─ 创建 SessionState,分配 session_id
|
||||||
|
└─ 返回 session_id (i64)
|
||||||
|
|
||||||
|
Java: session.activate(ActivationRequest { sn: "SN-XXXX" })
|
||||||
|
└─> Rust: craft_activate(session_id, sn)
|
||||||
|
├─ 从 AuthConfig 获取 bitanswer.url, loginMode
|
||||||
|
├─ 调用 Bit_UpdateOnline(url, sn, &app_data)
|
||||||
|
└─ 返回 ActivationResult
|
||||||
|
|
||||||
|
Java: session.login(LoginRequest { sn: "SN-XXXX", mode: AUTO })
|
||||||
|
└─> Rust: craft_login(session_id, sn, mode)
|
||||||
|
├─ 调用 Bit_Login(url, sn, &app_data, &bit_handle, BIT_MODE_AUTO)
|
||||||
|
├─ 存储 bit_handle → SessionState
|
||||||
|
└─ 返回 LoginResult { handle }
|
||||||
|
|
||||||
|
Java: session.readFeature(FACE_FEATURE_ID)
|
||||||
|
└─> Rust: craft_read_feature(session_id, feature_id)
|
||||||
|
├─ 获取 bit_handle
|
||||||
|
├─ 调用 Bit_ReadFeature(bit_handle, feature_id, &value)
|
||||||
|
└─ 返回 value (i32)
|
||||||
|
|
||||||
|
Java: session.checkOutSn(url, FEATURE_ZERO, 30)
|
||||||
|
└─> Rust: craft_check_out_sn(session_id, url, feature_id, duration)
|
||||||
|
├─ 调用 Bit_CheckOutSn(url, feature_id, &app_data, duration)
|
||||||
|
└─ 返回 CraftResult
|
||||||
|
|
||||||
|
Java: session.release()
|
||||||
|
└─> Rust: craft_release(session_id)
|
||||||
|
├─ 调用 Bit_Logout(bit_handle)
|
||||||
|
└─ 返回 CraftResult
|
||||||
|
|
||||||
|
Java: session.close()
|
||||||
|
└─> Rust: craft_destroy(session_id)
|
||||||
|
├─ 如果未 logout,调用 Bit_Logout
|
||||||
|
├─ 移除 SessionState
|
||||||
|
└─ 释放内存
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.2 错误码映射
|
||||||
|
|
||||||
|
Rust 侧将 200+ 个 `BIT_ERROR_CODES` 分组映射为 `LicenseError` 枚举:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub enum LicenseError {
|
||||||
|
Success,
|
||||||
|
NetworkError,
|
||||||
|
WrongHandle,
|
||||||
|
InvalidParameter,
|
||||||
|
ApplicationDataError,
|
||||||
|
LicenseExpired,
|
||||||
|
LicenseNotFound,
|
||||||
|
LicenseDisabled,
|
||||||
|
FeatureNotFound(i32),
|
||||||
|
FeatureExpired(i32),
|
||||||
|
FeatureTypeNotMatch(i32),
|
||||||
|
SnInvalid,
|
||||||
|
SnNotFound,
|
||||||
|
SnDisabled,
|
||||||
|
SnRevoked,
|
||||||
|
SnExpired,
|
||||||
|
CapacityExhausted,
|
||||||
|
ServerBusy,
|
||||||
|
ServerDown,
|
||||||
|
Revoked,
|
||||||
|
Timeout,
|
||||||
|
TokenError,
|
||||||
|
BorrowError,
|
||||||
|
Unknown(i32), // 兜底
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Java 侧使用 sealed interface 提供类型安全的结果:
|
||||||
|
|
||||||
|
```java
|
||||||
|
public sealed interface LicenseResult {
|
||||||
|
boolean isSuccess();
|
||||||
|
Optional<LicenseError> error();
|
||||||
|
String message();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 配置模型适配
|
||||||
|
|
||||||
|
`AuthConfig` 已有正确结构,重构后需将字段驱动到运行时:
|
||||||
|
|
||||||
|
| AuthConfig 字段 | 驱动行为 |
|
||||||
|
|---|---|
|
||||||
|
| `bitanswer.url` | `Bit_Login` / `Bit_UpdateOnline` 的 `szURL` |
|
||||||
|
| `bitanswer.loginMode` | `Bit_Login` 的 `mode` 参数 |
|
||||||
|
| `bitanswer.rootPath` | `Bit_SetRootPath` |
|
||||||
|
| `bitanswer.applicationData` | 替代硬编码的 `application_data[]`,使不同产品可携带不同识别码 |
|
||||||
|
| `features[].bitanswerFeatureId` | `readFeature(featureId)` / `queryFeature(featureId)` |
|
||||||
|
| `features[].bitanswerFeatureName` | `queryFeatureEx2(featureName, ...)` |
|
||||||
|
| `floating.projectId` | floating 场景校验(schema 已有) |
|
||||||
|
| `school.edgeDeviceId` | school 场景标识 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 迁移路径
|
||||||
|
|
||||||
|
### Phase 1 — 基础设施(不破坏现有接口)
|
||||||
|
- Rust: `ffi/bitanswer.rs` FFI 声明 + `bridge.rs` 桥接实现
|
||||||
|
- Rust: 实现 `craft_activate`, `craft_check_license`, `craft_heartbeat` 的真实调用
|
||||||
|
- Java: `AuthProvider` 标记 `@Deprecated`,内部委托给 `CraftLicense`
|
||||||
|
- Java: 新增 `CraftLicense` + `LicenseSession` + 5 个能力接口(空实现)
|
||||||
|
- 测试: 现有测试保持通过
|
||||||
|
|
||||||
|
### Phase 2 — 核心 API 扩展
|
||||||
|
- Java: 实现 `LicenseLifecycle`(login, loginEx, revoke, removeSn)
|
||||||
|
- Java: 实现 `FeatureManagement`(read, write, query, encrypt 等)
|
||||||
|
- Rust: 实现对应的 `craft_*` 函数
|
||||||
|
- 测试: 每个能力域独立集成测试
|
||||||
|
|
||||||
|
### Phase 3 — 高级功能
|
||||||
|
- Java: 实现 `DataItemStore`(set/get/remove/enum)
|
||||||
|
- Java: 实现 `CheckoutManager`(checkOut/In 系列)
|
||||||
|
- Rust: 实现离线升级流程(GetRequestInfo → GetUpdateInfo → ApplyUpdateInfo)
|
||||||
|
- 文档: 更新 `bitanswer-client-api-overview.md` 映射表
|
||||||
|
|
||||||
|
### Phase 4 — 清理
|
||||||
|
- 移除 `@Deprecated AuthProvider`
|
||||||
|
- 移除 `.deprecated-cmake/` 下的旧适配器代码
|
||||||
|
- 全量回归测试
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 兼容性
|
||||||
|
|
||||||
|
- `schemas/craftlabs-auth-config.schema.json` — **不变**
|
||||||
|
- 现有 `examples/config/*.json` — **不变**
|
||||||
|
- `AuthConfig` / `AuthConfigs` — **不变**
|
||||||
|
- `AuthProvider` — Phase 1-3 保持可用(`@Deprecated`),Phase 4 移除
|
||||||
|
- `NativeBridge` — 现有 9 个方法保留,新增 30+ 方法
|
||||||
|
- `craft-core` C ABI — 现有 9 个函数签名不变,新增 30+ 函数
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. 风险与缓解
|
||||||
|
|
||||||
|
| 风险 | 缓解 |
|
||||||
|
|------|------|
|
||||||
|
| Rust FFI 调用 BitAnswer .so/.dll 的链接问题 | Phase 1 先用 `libloading` 动态加载 BitAnswer 库,验证 ABI 兼容性 |
|
||||||
|
| 200+ 错误码映射不完整 | 只映射文档中明确列出的错误码,其余走 `Unknown(code)` |
|
||||||
|
| 多线程安全(BIT_HANDLE 是线程局部的) | `LicenseSession` 文档注明非线程安全,建议调用方池化或加锁 |
|
||||||
|
| native 库缺失时测试无法运行 | 单元测试 mock native 层,集成测试用 `@EnabledIfNativeLibraryPresent` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 附录 A:审计发现记录
|
||||||
|
|
||||||
|
审计过程中发现的具体代码问题:
|
||||||
|
|
||||||
|
1. **Rust `activate.rs:core_activate`** — 返回硬编码 `ok_result`,未调用任何 BitAnswer API
|
||||||
|
2. **Rust `license.rs:check_license`** — 返回硬编码 `ok_result`
|
||||||
|
3. **Rust `license.rs:get_license_info`** — 返回固定数据(`is_licensed=1`, `expiration_date="2099-12-31"`, `feature_count=0`)
|
||||||
|
4. **Rust `license.rs:has_feature`** — 无条件返回 `true`
|
||||||
|
5. **Rust `heartbeat.rs:do_heartbeat`** — 返回硬编码 `ok_result`
|
||||||
|
6. **Java `BitAnswerProvider.initialize`** — 配置 JSON 未被传递给 native 层做运行时行为驱动
|
||||||
|
7. **`NativeBridge`** — 只有 9 个 JNI 方法,BitAnswer 有 50+ 函数
|
||||||
|
8. **`.deprecated-cmake/bitanswer_adapter.cpp`** — `bitanswer_adapter_register()` 是空函数
|
||||||
|
|
||||||
|
## 附录 B:BitAnswer C API 完整清单
|
||||||
|
|
||||||
|
见 `examples/vcsample/bitanswer.h`(1211 行),包含 50+ 个 `Bit_*` 函数声明和 200+ 个错误码枚举。
|
||||||
@@ -0,0 +1,134 @@
|
|||||||
|
# CraftLabs 设计规范 v1.0
|
||||||
|
|
||||||
|
> 基于 Figma「安徽地质博物馆 v2.0」设计 Token → delivery-platform-ui 映射评估
|
||||||
|
> 审核日期: 2026-05-18
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 色彩系统
|
||||||
|
|
||||||
|
| Token | Figma | 当前 | 评估 |
|
||||||
|
|-------|-------|------|:--:|
|
||||||
|
| 页面底色 | `#EAEFFA` | `#f0f2f5` | ⚠️ 建议调整 |
|
||||||
|
| 卡片面板 | `#FFFFFF` | `#FFFFFF` | ✅ 一致 |
|
||||||
|
| 主色 | `#2C3E6B` | `#409EFF` | ⚠️ 可选项 |
|
||||||
|
| 正文文字 | `#000000` | `#303133` | ✅ 可接受 |
|
||||||
|
| 辅助文字 | `#313131` | `#909399` | ⚠️ 调整 |
|
||||||
|
| 表头背景 | `#F2F5FC` | `#f5f7fa` | ✅ 可接受 |
|
||||||
|
| 成功色 | `#E6F7EE/#1A7A3A` | `#f0f9eb/#67c23a` | ✅ 可接受 |
|
||||||
|
| 警告色 | — | `#E6A23C` | ✅ 默认 |
|
||||||
|
| 错误色 | `#D54941` | `#F56C6C` | ✅ 可接受 |
|
||||||
|
| 通知 Badge | `#D54941` | el-badge | ⚠️ 自定义 |
|
||||||
|
| 边框线 | `#D6DFF0/#E8ECF1` | `#EBEEF5` | ✅ 接近 |
|
||||||
|
| 侧边栏底 | `#FFFFFF` | `#001529` | 🔴 需修改 |
|
||||||
|
|
||||||
|
### CSS 变量建议
|
||||||
|
|
||||||
|
```css
|
||||||
|
:root {
|
||||||
|
--color-page-bg: #EAEFFA;
|
||||||
|
--color-card-bg: #FFFFFF;
|
||||||
|
--color-primary: #2C3E6B;
|
||||||
|
--color-primary-hover: #3D5A99;
|
||||||
|
--color-text-primary: #303133;
|
||||||
|
--color-text-secondary: #606266;
|
||||||
|
--color-border: #E8ECF1;
|
||||||
|
--color-th-bg: #F2F5FC;
|
||||||
|
--color-success: #1A7A3A;
|
||||||
|
--color-success-bg: #E6F7EE;
|
||||||
|
--color-danger: #F56C6C;
|
||||||
|
--color-danger-bg: #FEF0F0;
|
||||||
|
--color-warning: #E6A23C;
|
||||||
|
--color-badge: #D54941;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 布局结构
|
||||||
|
|
||||||
|
| 元素 | Figma (px) | 当前 (px) | 评估 |
|
||||||
|
|------|-----------|----------|:--:|
|
||||||
|
| Header 高度 | 60 | auto | 🔴 固定 60px |
|
||||||
|
| Sidebar 宽度 | 232 | 220 | ✅ 可忽略 |
|
||||||
|
| Sidebar 底色 | `#FFFFFF` | `#001529` 深色 | 🔴 改白色 |
|
||||||
|
| 面包屑 | 46 | 无 | 🔴 加 el-breadcrumb |
|
||||||
|
| 左侧 Tree | 280 | 无 | 🔴 许可证页加 el-tree |
|
||||||
|
| 内容内边距 | 20 | 16-20 | ✅ 一致 |
|
||||||
|
| 搜索栏 | Header+Tree | Card header | ⚠️ Header 加全站搜索 |
|
||||||
|
| 卡片 | 6px 圆角+border | 4px 无边框 | ⚠️ 调整 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 弹框规范
|
||||||
|
|
||||||
|
| 类型 | 宽度 | 圆角 | 关键特征 |
|
||||||
|
|------|:--:|:--:|------|
|
||||||
|
| 签发许可证 | 560px | 8px | 完整表单 + 特性开关 |
|
||||||
|
| 新建/编辑 | 480px | 8px | 简化表单 |
|
||||||
|
| 详情查看 | 480px | 8px | 标签(100px) + 值 |
|
||||||
|
| 许可证详情 | 520px | 8px | monospace ID + 虚线框 |
|
||||||
|
| 确认/吊销 | 420px | 8px | 危险色背景 `#FEF0F0` |
|
||||||
|
|
||||||
|
```
|
||||||
|
通用弹框 Token:
|
||||||
|
- 圆角: 8px
|
||||||
|
- 阴影: 0 8px 40px rgba(0,0,0,.15)
|
||||||
|
- Header: padding 16px/20px
|
||||||
|
- Body: padding 20px
|
||||||
|
- Footer: gap 10px, 按钮右对齐
|
||||||
|
- 遮罩: rgba(0,0,0,.45)
|
||||||
|
- 动画: scale(.96→1) 200ms
|
||||||
|
- 关闭按钮: 28x28, hover 背景 #F2F5FC
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 组件
|
||||||
|
|
||||||
|
| 组件 | Figma Token | 当前 | 建议 |
|
||||||
|
|------|-----------|------|------|
|
||||||
|
| 主按钮 | `#2C3E6B` + shadow | `#409EFF` | 改色 + shadow |
|
||||||
|
| 表格表头 | `#F2F5FC` / `#2C3E6B` bold | 默认 | 改淡蓝底+深蓝字 |
|
||||||
|
| 状态标签 | `#E6F7EE/#1A7A3A` | el-tag | 微调色值 |
|
||||||
|
| 通知 Badge | `#D54941` 20x20 | el-badge | 自定义颜色 |
|
||||||
|
| 输入框 | focus `#2C3E6B` | focus `#409EFF` | 改 focus 色 |
|
||||||
|
| 搜索框 | `#F8F9FB` 6px radius | el-input | 加圆角+背景 |
|
||||||
|
| 侧边菜单 | 白底+蓝选中 | 深色底 | 🔴 改白色方案 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 字体排版
|
||||||
|
|
||||||
|
| 层级 | Figma | 当前 | 评估 |
|
||||||
|
|------|-------|------|:--:|
|
||||||
|
| 页面标题 | — | 16px bold | ✅ 增 22px 级 |
|
||||||
|
| 正文 | 14px `#000000` | 14px `#303133` | ✅ |
|
||||||
|
| 辅助 | 13px `#313131` | 13px `#909399` | ⚠️ `#606266` |
|
||||||
|
| 表头 | 12px `#2C3E6B` bold | 默认 | ⚠️ 改色 |
|
||||||
|
| 代码/ID | — | 12px monospace | ✅ |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 实施方案
|
||||||
|
|
||||||
|
### Element Plus CSS 变量覆盖(3 行核心)
|
||||||
|
|
||||||
|
```css
|
||||||
|
:root {
|
||||||
|
--el-color-primary: #2C3E6B;
|
||||||
|
--el-bg-color-page: #EAEFFA;
|
||||||
|
--el-border-radius-base: 6px;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 工作量估算
|
||||||
|
|
||||||
|
| 优先级 | 项目 | 工作量 | 影响 |
|
||||||
|
|:--:|------|:--:|------|
|
||||||
|
| P0 | 色彩 CSS 变量覆盖 | 30min | 全局风格统一 |
|
||||||
|
| P0 | 侧边栏白色方案 | 1h | 视觉对齐 |
|
||||||
|
| P1 | 面包屑 + Tree | 3h | 导航体验 |
|
||||||
|
| P1 | Header 搜索+通知 | 2h | 运营效率 |
|
||||||
|
| P2 | 弹框规范统一 | 2h | 交互一致 |
|
||||||
|
| P2 | 组件 Token 细化 | 1h | 细节打磨 |
|
||||||
@@ -0,0 +1,477 @@
|
|||||||
|
# 自研授权 SDK 设计方案
|
||||||
|
|
||||||
|
> **日期**:2026-05-18
|
||||||
|
> **背景**:比特安索授权云费用过高,决定优先推进自研授权方案。
|
||||||
|
> **原则**:与比特安索双线共存,Provider 可扩展架构,后续可接入更多第三方授权方式。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 目录
|
||||||
|
|
||||||
|
1. [决策摘要](#1-决策摘要)
|
||||||
|
2. [总体架构](#2-总体架构)
|
||||||
|
3. [许可证协议与数据模型](#3-许可证协议与数据模型)
|
||||||
|
4. [Rust 层核心逻辑](#4-rust-层核心逻辑)
|
||||||
|
5. [平台后端变更](#5-平台后端变更)
|
||||||
|
6. [安全设计](#6-安全设计)
|
||||||
|
7. [实施阶段](#7-实施阶段)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 决策摘要
|
||||||
|
|
||||||
|
| 决策项 | 选择 | 理由 |
|
||||||
|
|--------|------|------|
|
||||||
|
| 授权服务形态 | **混合模式** | 有网络时在线验证(心跳/租约续期),断网时本地缓存可用至离线宽限期 |
|
||||||
|
| 加密体系 | **非对称签名 RSA-256 + AES-256-GCM 加密载荷** | 离线验签 + 内容加密防窥探,每个 license 独立 AES 密钥防批量破解 |
|
||||||
|
| 授权粒度 | **完整属性**:有效期 + 终端限制 + 并发用户数 + 使用次数 + 特性开关 | 对齐现有比特业务属性,功能上不降级 |
|
||||||
|
| 后端集成 | **复用 API + Webhook 双服务** | 签发走 API(管理操作需认证鉴权),SDK 交互走 Webhook(高频快速 2xx) |
|
||||||
|
| 比特兼容 | **双线共存 + Provider 可扩展架构** | 后续可接入更多第三方 |
|
||||||
|
| 终端识别 | **硬件指纹分层采集 + 稳定度评分兜底** | 强指纹精确匹配,弱指纹分配服务器 UUID,管理员可手动释放 |
|
||||||
|
| Rust 架构 | **单 cdylib + trait 多 Provider** | 公共模块共享,扩展第三方只需增加 trait 实现 |
|
||||||
|
| SDK 交互安全 | **Nonce + Timestamp + HMAC 签名防重放** | 简单有效,无需序列号同步 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 总体架构
|
||||||
|
|
||||||
|
### 2.1 全系统组件图
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────────────┐
|
||||||
|
│ 客户现场 │
|
||||||
|
│ ┌──────────┐ ┌──────────────────┐ ┌──────────────────┐ │
|
||||||
|
│ │ 客户应用 │───▶│ Java SDK │ │ 许可证配置文件 │ │
|
||||||
|
│ └──────────┘ │ AuthProvider impl │◀───│ ~/.craftlabs/ │ │
|
||||||
|
│ │ ┌──────────────┐ │ │ license_cache │ │
|
||||||
|
│ │ │BitAnswerProv │ │ │ device_id │ │
|
||||||
|
│ │ ├──────────────┤ │ └──────────────────┘ │
|
||||||
|
│ │ │SelfHostedPrv │ │ │
|
||||||
|
│ │ └──────┬───────┘ │ │
|
||||||
|
│ └────────┼────────┘ │
|
||||||
|
│ │ JNI │
|
||||||
|
│ ┌────────▼────────┐ │
|
||||||
|
│ │ Rust craft-core │ libcraftlabs_auth_core │
|
||||||
|
│ │ ◇ trait Provider│ │
|
||||||
|
│ │ ┌─────────────┐ │ │
|
||||||
|
│ │ │BitAnswer │ │──────▶ 比特安索云 │
|
||||||
|
│ │ ├─────────────┤ │ │
|
||||||
|
│ │ │SelfHosted │ │──HTTPS▶ license-webhook │
|
||||||
|
│ │ └─────────────┘ │ │
|
||||||
|
│ │ device.rs │ 硬件指纹分层采集 │
|
||||||
|
│ │ crypto.rs │ HKDF+AES-GCM+RSA验签 │
|
||||||
|
│ │ security/ │ 反调试/完整性/混淆 │
|
||||||
|
│ └────────────────┘ │
|
||||||
|
└─────────────────────────────────────────────────────────────────┘
|
||||||
|
|
||||||
|
┌─────────────────────────────────────────────────────────────────┐
|
||||||
|
│ 创飞机房 / 云 │
|
||||||
|
│ │
|
||||||
|
│ ┌──────────────────────────┐ ┌──────────────────────────┐ │
|
||||||
|
│ │ license-webhook-ingress │ │ delivery-platform-api │ │
|
||||||
|
│ │ :8081 │ │ :8080 │ │
|
||||||
|
│ │ SDK 在线端点: │ │ 许可证签发端点: │ │
|
||||||
|
│ │ /license/v1/activate │◀──│ /api/v1/licenses │ │
|
||||||
|
│ │ /license/v1/heartbeat │ │ /api/v1/licenses/{id} │ │
|
||||||
|
│ │ /license/v1/check │ │ /api/v1/licenses/{id}/ │ │
|
||||||
|
│ │ /license/v1/release │ │ revoke │ │
|
||||||
|
│ │ │ │ │ │
|
||||||
|
│ │ 事件回调 ─────────────▶ │ │ 合同/SN/终端/审计 │ │
|
||||||
|
│ └──────────────────────────┘ └──────────────────────────┘ │
|
||||||
|
│ │ │ │
|
||||||
|
│ └──────────┬─────────────────┘ │
|
||||||
|
│ ▼ │
|
||||||
|
│ ┌──────────────┐ │
|
||||||
|
│ │ PostgreSQL 15│ │
|
||||||
|
│ └──────────────┘ │
|
||||||
|
└─────────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.2 Rust 层模块结构(改造后)
|
||||||
|
|
||||||
|
```
|
||||||
|
native/craft-core/src/
|
||||||
|
├── lib.rs # cdylib 入口 + C ABI 路由
|
||||||
|
├── trait_provider.rs # Provider trait 定义 + select_provider
|
||||||
|
│
|
||||||
|
├── provider_bitanswer/ # 现有逻辑封装
|
||||||
|
│ ├── mod.rs
|
||||||
|
│ ├── activate.rs
|
||||||
|
│ ├── license.rs
|
||||||
|
│ └── heartbeat.rs
|
||||||
|
│
|
||||||
|
├── provider_selfhosted/ # ★ 本次核心交付
|
||||||
|
│ ├── mod.rs # SelfHostedProvider impl Provider
|
||||||
|
│ ├── activate.rs # HTTPS POST → webhook:8081
|
||||||
|
│ ├── license.rs # 验签 + 解密 + 离线校验
|
||||||
|
│ ├── heartbeat.rs # HTTPS 心跳 + 租约续期
|
||||||
|
│ ├── protocol.rs # 请求/响应序列化
|
||||||
|
│ └── cache.rs # 许可证本地加密存储
|
||||||
|
│
|
||||||
|
├── device.rs # ★ 硬件指纹分层采集
|
||||||
|
├── crypto.rs # ★ HKDF + AES-256-GCM + RSA 验签
|
||||||
|
├── session.rs # 泛化 session(去 bit_handle)
|
||||||
|
├── error.rs # 自研 + 比特错误码体系
|
||||||
|
│
|
||||||
|
└── security/ # 不变
|
||||||
|
├── mod.rs
|
||||||
|
├── anti_debug.rs
|
||||||
|
├── integrity.rs
|
||||||
|
├── obfuscation.rs
|
||||||
|
└── string_encrypt.rs
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.3 Provider trait 契约
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub trait Provider: Send + Sync {
|
||||||
|
fn initialize(&mut self, ctx: &CraftContext, config: &AuthConfig)
|
||||||
|
-> Result<(), LicenseError>;
|
||||||
|
fn activate(&self, ctx: &CraftContext, license_key: &str)
|
||||||
|
-> Result<ActivateResponse, LicenseError>;
|
||||||
|
fn check_license(&self, ctx: &CraftContext)
|
||||||
|
-> Result<LicenseStatus, LicenseError>;
|
||||||
|
fn heartbeat(&self, ctx: &CraftContext)
|
||||||
|
-> Result<HeartbeatResponse, LicenseError>;
|
||||||
|
fn has_feature(&self, ctx: &CraftContext, name: &str) -> bool;
|
||||||
|
fn release(&mut self, ctx: &CraftContext) -> Result<(), LicenseError>;
|
||||||
|
fn get_license_info(&self, ctx: &CraftContext) -> LicenseInfoFFI;
|
||||||
|
fn close(&mut self);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.4 Java 层变更
|
||||||
|
|
||||||
|
| 模块 | 变更 |
|
||||||
|
|------|------|
|
||||||
|
| `craftlabs-auth-core` | 接口不变;`SelfhostedConfigSection` 扩展 `offlineGraceDays`、`heartbeatIntervalHours` 等字段;`FeatureMapping` 扩展 `selfhostedFeatureKey` |
|
||||||
|
| `craftlabs-auth-bitanswer` | **不变**(双线共存) |
|
||||||
|
| `craftlabs-auth-selfhosted` | 从桩改为真实实现:`System.loadLibrary("craftlabs_auth_core")` |
|
||||||
|
| `craftlabs-auth-tests` | 新增 `SelfHostedProviderTest`、`MultiProviderSmokeTest` |
|
||||||
|
|
||||||
|
### 2.5 关键架构约束
|
||||||
|
|
||||||
|
- **单一 cdylib**:`craftlabs_auth_core` 包含所有 provider,通过 trait 路由
|
||||||
|
- **Java 侧无感知**:各 AuthProvider 均调 NativeBridge,路由在 Rust 层完成
|
||||||
|
- **双线 classpath 隔离**:BitAnswer 和 SelfHosted 不同 Maven 模块
|
||||||
|
- **契约不变**:AuthProvider 接口 7 方法不变、AuthConfig record 不变
|
||||||
|
- **Schema 兼容**:selfhosted 段向后兼容扩展新字段
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 许可证协议与数据模型
|
||||||
|
|
||||||
|
### 3.1 License JSON 结构
|
||||||
|
|
||||||
|
```jsonc
|
||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"license_id": "01JQXYZ...", // ULID
|
||||||
|
"issued_at": "2026-05-18T10:00:00Z",
|
||||||
|
|
||||||
|
// 载荷:AES-256-GCM 加密后的 Base64 密文
|
||||||
|
// 解密后为 LicensePayload:{ tenant_id, product, grant, constraints, features, custom }
|
||||||
|
"payload": "A8f3Kd9s...base64url...",
|
||||||
|
|
||||||
|
"signature": {
|
||||||
|
"algorithm": "RS256",
|
||||||
|
"key_id": "kp_2026_q2", // 支持密钥轮换
|
||||||
|
"value": "MEUCIQDx..." // RS256 签名(对密文 payload 签名)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.2 LicensePayload(解密后明文)
|
||||||
|
|
||||||
|
```jsonc
|
||||||
|
{
|
||||||
|
"tenant_id": "craftlabs-wharf-prod",
|
||||||
|
"product": "wharf-inspection-v2",
|
||||||
|
|
||||||
|
"grant": {
|
||||||
|
"type": "perpetual", // perpetual | subscription | trial
|
||||||
|
"not_before": "2026-05-01T00:00:00Z",
|
||||||
|
"not_after": "2027-05-01T00:00:00Z",
|
||||||
|
"offline_grace_days": 7,
|
||||||
|
"heartbeat_interval_hours": 24
|
||||||
|
},
|
||||||
|
|
||||||
|
"constraints": {
|
||||||
|
"max_devices": 5,
|
||||||
|
"max_concurrent_users": 0, // 0=不限制
|
||||||
|
"max_activations": 0
|
||||||
|
},
|
||||||
|
|
||||||
|
"features": {
|
||||||
|
"advanced_analytics": true,
|
||||||
|
"real_time_monitor": false,
|
||||||
|
"api_export": true
|
||||||
|
},
|
||||||
|
|
||||||
|
"custom": { // 扩展字段
|
||||||
|
"contract_ref": "CT-2026-0042",
|
||||||
|
"project_id": "wharf-nansha-phase2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.3 签发与校验流程
|
||||||
|
|
||||||
|
```
|
||||||
|
签发(服务器侧):
|
||||||
|
LicensePayload 明文
|
||||||
|
→ AES-256-GCM 加密(key = HKDF(编译期盐, license_id))
|
||||||
|
→ 密文 payload (Base64)
|
||||||
|
→ RSA-SHA256 签名(对密文签名)
|
||||||
|
→ 完整 license.json
|
||||||
|
|
||||||
|
校验(Rust SDK 侧):
|
||||||
|
license.json
|
||||||
|
→ RSA 公钥验签(快速排除伪造)
|
||||||
|
→ HKDF 派生 AES 密钥
|
||||||
|
→ AES-256-GCM 解密
|
||||||
|
→ 时间窗口校验(not_before ≤ now ≤ not_after + 离线宽限期)
|
||||||
|
→ 特性/约束校验
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.4 在线交互协议
|
||||||
|
|
||||||
|
端点:`license-webhook-ingress:8081/license/v1/*`
|
||||||
|
|
||||||
|
| 端点 | 方法 | 请求体 | 成功响应 | 错误响应 |
|
||||||
|
|------|------|--------|----------|----------|
|
||||||
|
| `/activate` | POST | `{license_key, device_fingerprint}` | 200 `{status:"activated", device_id, license_payload}` | 409 终端满 / 403 已吊销 / 422 无效 |
|
||||||
|
| `/heartbeat` | POST | `{license_key, device_hash, local_time}` | 200 `{status:"ok", lease_renewed_until}` | 410 已过期/吊销 |
|
||||||
|
| `/check` | POST | `{license_key, device_hash}` | 200 `{status:"valid", features, not_after}` | 410 已过期/吊销 |
|
||||||
|
| `/release` | POST | `{license_key, device_hash}` | 200 `{status:"released"}` | — |
|
||||||
|
|
||||||
|
**防重放**:每个请求携带 `X-Craft-Nonce`、`X-Craft-Timestamp`、`X-Craft-Signature`(HMAC-SHA256),服务器时间窗口 5 分钟 + Nonce 去重。
|
||||||
|
|
||||||
|
### 3.5 签发端点(API 侧)
|
||||||
|
|
||||||
|
```
|
||||||
|
POST /api/v1/licenses # 创建/签发许可证
|
||||||
|
GET /api/v1/licenses # 分页查询
|
||||||
|
GET /api/v1/licenses/{licenseId} # 详情
|
||||||
|
POST /api/v1/licenses/{id}/revoke # 吊销
|
||||||
|
GET /api/v1/licenses/{id}/activations # 激活记录
|
||||||
|
POST /api/v1/licenses/{id}/activations/{aid}/release # 释放设备
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.6 数据库表(新增)
|
||||||
|
|
||||||
|
| 表 | 用途 |
|
||||||
|
|----|------|
|
||||||
|
| `platform_licenses` | 许可证主表(license_id、有效期、约束、状态、签名快照) |
|
||||||
|
| `platform_license_features` | 特性开关(license_id, feature_key, enabled) |
|
||||||
|
| `platform_license_activations` | 终端激活记录(license_id, device_hash, stability_score, status) |
|
||||||
|
| `platform_license_heartbeats` | 心跳审计(可选,视量级) |
|
||||||
|
| `platform_license_keys` | RSA 密钥对管理(key_id, public_key, private_key) |
|
||||||
|
| `platform_license_policies` | 策略模板(默认有效期、终端数、特性等) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Rust 层核心逻辑
|
||||||
|
|
||||||
|
### 4.1 新增依赖
|
||||||
|
|
||||||
|
```toml
|
||||||
|
reqwest = { version = "0.12", features = ["json", "rustls-tls"], default-features = false }
|
||||||
|
rsa = { version = "0.9", features = ["sha2"] }
|
||||||
|
aes-gcm = "0.10"
|
||||||
|
hkdf = "0.12"
|
||||||
|
base64 = "0.22"
|
||||||
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
serde_json = "1"
|
||||||
|
chrono = { version = "0.4", features = ["serde"] }
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.2 关键数据结构
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub struct DeviceFingerprint {
|
||||||
|
pub composite_hash: String, // SHA-256(layer1|layer2|layer3|layer4)
|
||||||
|
pub stability_score: u8, // 0~100,影响匹配策略
|
||||||
|
pub layers: Vec<FingerprintLayer>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct LicenseStatus {
|
||||||
|
pub licensed: bool,
|
||||||
|
pub not_after: Option<i64>,
|
||||||
|
pub features: HashMap<String, bool>,
|
||||||
|
pub device_count: u32,
|
||||||
|
pub max_devices: u32,
|
||||||
|
pub heartbeat_due: Option<i64>,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.3 硬件指纹分层采集
|
||||||
|
|
||||||
|
| 层级 | 权重 | 来源 |
|
||||||
|
|------|------|------|
|
||||||
|
| Layer 1 硬件 | 40 | Linux: DMI product_uuid / Windows: SMBIOS UUID / macOS: IOPlatformUUID |
|
||||||
|
| Layer 2 OS | 30 | Linux: /etc/machine-id / Windows: MachineGuid / macOS: 同 L1 |
|
||||||
|
| Layer 3 存储 | 20 | 根文件系统 UUID (blkid/VolumeSerialNumber) |
|
||||||
|
| Layer 4 网络 | 10 | 物理网卡 MAC(跳过 docker/tap/lo/veth) |
|
||||||
|
|
||||||
|
**稳定度评分**:Layer1=有+Layer2=有 → 70(强指纹);仅 Layer2 有 → 40;仅 Layer4 → 10。
|
||||||
|
|
||||||
|
**兜底**:稳定度 < 20 时,服务器分配 UUID 并加密存储到 `~/.craftlabs/device_id`,管理员可手动吊销释放配额。
|
||||||
|
|
||||||
|
### 4.4 离线宽限期
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn check_license离线(&self) -> Result<LicenseStatus, LicenseError> {
|
||||||
|
// 1. 加载缓存许可证
|
||||||
|
// 2. 检查距离上次在线心跳的天数
|
||||||
|
// 3. 超过 offline_grace_days → OfflineGraceExceeded
|
||||||
|
// 4. 未超过 → 时间窗口校验(not_before/not_after)
|
||||||
|
// 5. 返回 LicenseStatus(device_count=0 标记离线)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.5 错误码体系
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub enum LicenseError {
|
||||||
|
// 通用
|
||||||
|
Success, ConfigMissing, Network, NotInitialized,
|
||||||
|
// 签名/加密
|
||||||
|
InvalidFormat, InvalidSignature, SignatureMismatch, CryptoError,
|
||||||
|
DecryptionFailed, CorruptedPayload, LicenseIdMismatch,
|
||||||
|
// 许可状态
|
||||||
|
NotYetValid, Expired, NoCachedLicense,
|
||||||
|
OfflineGraceExceeded { days_offline, max_days },
|
||||||
|
InvalidLicense, LicenseRevoked,
|
||||||
|
DeviceLimitReached, ConcurrentUserLimitReached, ActivationLimitReached,
|
||||||
|
// 兼容比特
|
||||||
|
BitAnswerStatus(u32),
|
||||||
|
UnknownStatus(u16),
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.6 本地缓存
|
||||||
|
|
||||||
|
```
|
||||||
|
~/.craftlabs/
|
||||||
|
├── device_id # 硬件指纹或服务器 UUID
|
||||||
|
├── license_cache.json # AES-256-GCM 加密的许可证副本
|
||||||
|
│ # 加密密钥 = SHA256(device_id + EMBEDDED_SALT)
|
||||||
|
└── heartbeat_state.json # { last_heartbeat, lease_until }
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.7 编译期公钥嵌入
|
||||||
|
|
||||||
|
`build.rs` 读取 `native/craft-core/embedded/pubkey.pem`,生成 `const EMBEDDED_PUBLIC_KEY: &str = "..."`,`crypto.rs` 在 `initialize` 时解析为 `RsaPublicKey`。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 平台后端变更
|
||||||
|
|
||||||
|
### 5.1 delivery-platform-api 新增模块
|
||||||
|
|
||||||
|
```
|
||||||
|
license/
|
||||||
|
├── LicenseController.java # REST /api/v1/licenses
|
||||||
|
├── LicenseService.java # 签发/吊销/查询
|
||||||
|
├── LicenseSigner.java # RSA+AES 签发
|
||||||
|
└── LicenseKeyManager.java # 密钥对管理
|
||||||
|
|
||||||
|
persistence/license/
|
||||||
|
├── PlatformLicense.java & Mapper
|
||||||
|
├── PlatformLicenseFeature.java & Mapper
|
||||||
|
├── PlatformLicenseActivation.java & Mapper
|
||||||
|
├── PlatformLicenseKey.java & Mapper
|
||||||
|
└── PlatformLicensePolicy.java & Mapper
|
||||||
|
```
|
||||||
|
|
||||||
|
新增角色:`LICENSE_OPS`,管理 `/api/v1/licenses/**` 的写权限。
|
||||||
|
|
||||||
|
### 5.2 license-webhook-ingress 新增模块
|
||||||
|
|
||||||
|
```
|
||||||
|
license/
|
||||||
|
├── LicenseController.java # /license/v1/*
|
||||||
|
├── LicenseActivateService.java # 激活 + 终端匹配
|
||||||
|
├── LicenseHeartbeatService.java # 心跳 + 吊销检测
|
||||||
|
├── LicenseCheckService.java # 在线校验
|
||||||
|
├── LicenseReleaseService.java # 设备释放
|
||||||
|
├── DeviceMatcher.java # 分层指纹匹配
|
||||||
|
└── NonceValidator.java # 防重放
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.3 事件回调
|
||||||
|
|
||||||
|
激活/心跳失败/到期/吊销等事件,沿现有 Webhook→API 异步投递链路通知 API 更新台账和审计。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 安全设计
|
||||||
|
|
||||||
|
### 6.1 许可证防篡改
|
||||||
|
|
||||||
|
| 措施 | 作用 |
|
||||||
|
|------|------|
|
||||||
|
| **载荷 AES-256-GCM 加密** | 阻止直接查看许可证内容(特性/期限/终端数),每个 license_id 独立密钥防批量破解 |
|
||||||
|
| **密文 RSA-SHA256 签名** | 验签不通过 = 篡改,先行快速拒绝 |
|
||||||
|
| **HKDF 密钥派生** | 盐编译期嵌入 + license_id,增加逆向提取难度 |
|
||||||
|
| **公钥编译期嵌入** | 不在运行时从文件或网络加载,防替换攻击 |
|
||||||
|
|
||||||
|
### 6.2 SDK 在线交互安全
|
||||||
|
|
||||||
|
| 措施 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| **TLS** | 全链路 HTTPS,证书校验 |
|
||||||
|
| **HMAC 签名** | 每个请求 X-Craft-Signature = HMAC-SHA256(nonce|ts|method|path|body, tenantKey) |
|
||||||
|
| **Nonce 去重** | Redis SETNX + 时间窗口 5 分钟,防重放 |
|
||||||
|
| **Authorization 头** | Bearer tenantKey 双向验证 |
|
||||||
|
|
||||||
|
### 6.3 运行时保护
|
||||||
|
|
||||||
|
Rust 侧复用现有 `security/` 模块:反调试检测、完整性校验、字符串混淆,适配自研路径。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 实施阶段
|
||||||
|
|
||||||
|
### Phase 1:离线核心
|
||||||
|
**目标**:管理员签发 → 文件交付 → SDK 本地验签解密
|
||||||
|
|
||||||
|
- Rust: crypto.rs、license.rs、cache.rs、device.rs、error.rs
|
||||||
|
- Java: SelfhostedConfigSection 扩展字段
|
||||||
|
- 平台: 数据库迁移、LicenseSigner、LicenseController(签发/查询/吊销)
|
||||||
|
- 验证: AES 往返测试、RSA 验签测试、过期拒绝测试
|
||||||
|
|
||||||
|
### Phase 2:在线激活
|
||||||
|
**目标**:SDK 网络激活获取许可证,终端配额限制
|
||||||
|
|
||||||
|
- Rust: activate.rs、protocol.rs、trait_provider.rs、reqwest 集成
|
||||||
|
- Webhook: LicenseController、ActivateService、DeviceMatcher、NonceValidator
|
||||||
|
- 平台: 终端释放端点
|
||||||
|
- 验证: Mock HTTP 测试、终端满 409 测试
|
||||||
|
|
||||||
|
### Phase 3:心跳 + 离线兜底
|
||||||
|
**目标**:心跳维持租约、离线宽限期降级、吊销远程生效
|
||||||
|
|
||||||
|
- Rust: heartbeat.rs、check_license 离线逻辑
|
||||||
|
- Webhook: HeartbeatService、CheckService、ReleaseService
|
||||||
|
- 验证: 心跳成功更新租约、断网 8 天 OfflineGraceExceeded、吊销后 410
|
||||||
|
|
||||||
|
### Phase 4:完善与生产加固
|
||||||
|
**目标**:双 Provider 切换、CI/CD、文档
|
||||||
|
|
||||||
|
- Rust: build.rs 公钥嵌入、security 模块适配
|
||||||
|
- Java: MultiProviderSmokeTest、SelfHostedProviderTest
|
||||||
|
- CI: ci-native.yml 适配、ci-platform.yml 新增
|
||||||
|
- 文档: 集成指南、操作手册、CHANGELOG
|
||||||
|
|
||||||
|
### 工作量估算
|
||||||
|
|
||||||
|
| Phase | Rust | Java SDK | Platform API | Webhook | 合计 |
|
||||||
|
|-------|------|----------|-------------|---------|------|
|
||||||
|
| P1 | M | S | M | — | M~L |
|
||||||
|
| P2 | M | — | S | M | M |
|
||||||
|
| P3 | S | — | — | M | M |
|
||||||
|
| P4 | S | S | — | — | S |
|
||||||
|
|
||||||
|
(S=小,M=中,L=大)
|
||||||
@@ -0,0 +1,442 @@
|
|||||||
|
# CraftLabs 前端设计体系 v1.0
|
||||||
|
|
||||||
|
> 基于 Figma「安徽地质博物馆 v2.0」设计 Token 提取,适配 delivery-platform-ui
|
||||||
|
> 最后更新:2026-05-19
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 目录
|
||||||
|
|
||||||
|
1. [设计 Token](#1-设计-token)
|
||||||
|
2. [布局体系](#2-布局体系)
|
||||||
|
3. [组件规范](#3-组件规范)
|
||||||
|
4. [页面模板](#4-页面模板)
|
||||||
|
5. [弹框体系](#5-弹框体系)
|
||||||
|
6. [实施指南](#6-实施指南)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 设计 Token
|
||||||
|
|
||||||
|
### 1.1 色彩
|
||||||
|
|
||||||
|
| Token | 值 | 用途 |
|
||||||
|
|-------|-----|------|
|
||||||
|
| `--color-brand` | `#2C3E6B` | 主色:按钮、链接、选中态、表头文字 |
|
||||||
|
| `--color-brand-hover` | `#3D5A99` | 主色悬停 |
|
||||||
|
| `--color-brand-light` | `#F2F5FC` | 主色淡化:表头背景、选中背景 |
|
||||||
|
| `--color-page-bg` | `#EAEFFA` | 页面底色 |
|
||||||
|
| `--color-card-bg` | `#FFFFFF` | 卡片/面板/弹窗背景 |
|
||||||
|
| `--color-text-primary` | `#303133` | 主要文字:标题、正文 |
|
||||||
|
| `--color-text-secondary` | `#606266` | 次要文字:标签、说明 |
|
||||||
|
| `--color-text-placeholder` | `#909399` | 占位/辅助文字 |
|
||||||
|
| `--color-text-disabled` | `#C0C4CC` | 禁用/图标 |
|
||||||
|
| `--color-border` | `#E8ECF1` | 通用边框:卡片、表格 |
|
||||||
|
| `--color-border-input` | `#E0E3E8` | 输入框边框 |
|
||||||
|
| `--color-success` | `#1A7A3A` | 成功文字 |
|
||||||
|
| `--color-success-bg` | `#E6F7EE` | 成功背景 |
|
||||||
|
| `--color-success-border` | `#A8E6C1` | 成功边框 |
|
||||||
|
| `--color-danger` | `#F56C6C` | 危险/错误 |
|
||||||
|
| `--color-danger-bg` | `#FEF0F0` | 危险背景 |
|
||||||
|
| `--color-warning` | `#E6A23C` | 警告 |
|
||||||
|
| `--color-warning-bg` | `#FDF6EC` | 警告背景 |
|
||||||
|
| `--color-badge` | `#D54941` | 通知红点 |
|
||||||
|
| `--color-search-bg` | `#F8F9FB` | 搜索框背景 |
|
||||||
|
|
||||||
|
#### 色板速查
|
||||||
|
|
||||||
|
```
|
||||||
|
品牌色 #2C3E6B ████████ ████████ ████████
|
||||||
|
页面底色 #EAEFFA ████████ ████████
|
||||||
|
卡片白 #FFFFFF ████████ ████████ ████████
|
||||||
|
表头蓝 #F2F5FC ████████
|
||||||
|
成功绿 #E6F7EE ████████
|
||||||
|
危险红 #FEF0F0 ████████
|
||||||
|
警告橙 #FDF6EC ████████
|
||||||
|
Badge红 #D54941 ████████
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.2 字体
|
||||||
|
|
||||||
|
| 层级 | 字号 | 字重 | 颜色 | 场景 |
|
||||||
|
|------|:--:|:--:|------|------|
|
||||||
|
| H1 页面标题 | 22px | 700 | `--color-text-primary` | 页面主标题 |
|
||||||
|
| H2 区块标题 | 20px | 700 | `--color-text-primary` | 卡片标题 |
|
||||||
|
| H3 子标题 | 16px | 600 | `--color-text-primary` | 弹框标题 |
|
||||||
|
| Body 正文 | 14px | 400 | `--color-text-primary` | 表格内容、菜单 |
|
||||||
|
| Body-Secondary | 13px | 400 | `--color-text-secondary` | 标签、说明 |
|
||||||
|
| Caption | 12px | 400 | `--color-text-placeholder` | 时间、统计小字 |
|
||||||
|
| Code/Mono | 12px | 400 | `--color-text-primary` | 许可证ID、SN编码 |
|
||||||
|
| Badge | 10-11px | 600 | `#FFFFFF` | 通知数字、标签 |
|
||||||
|
|
||||||
|
**字体族**:`-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif`
|
||||||
|
|
||||||
|
### 1.3 间距
|
||||||
|
|
||||||
|
| Token | 值 | 用途 |
|
||||||
|
|-------|:--:|------|
|
||||||
|
| `--space-xs` | 4px | 图标与文字间距 |
|
||||||
|
| `--space-sm` | 8px | 按钮组间距、筛选条件间距 |
|
||||||
|
| `--space-md` | 12px | 卡片间距、统计卡间距 |
|
||||||
|
| `--space-lg` | 16px | 表单项间距、内容区上下内边距 |
|
||||||
|
| `--space-xl` | 20px | 内容区左右内边距、弹框内边距 |
|
||||||
|
| `--space-xxl` | 24px | 页面区块间距 |
|
||||||
|
|
||||||
|
### 1.4 圆角
|
||||||
|
|
||||||
|
| Token | 值 | 场景 |
|
||||||
|
|-------|:--:|------|
|
||||||
|
| `--radius-sm` | 4px | 输入框、按钮、标签 |
|
||||||
|
| `--radius-md` | 6px | 卡片、搜索框、菜单项 |
|
||||||
|
| `--radius-lg` | 8px | 弹框 |
|
||||||
|
|
||||||
|
### 1.5 阴影
|
||||||
|
|
||||||
|
| Token | 值 | 场景 |
|
||||||
|
|-------|-----|------|
|
||||||
|
| `--shadow-card` | `0 1px 2px rgba(0,0,0,.03)` | 卡片默认 |
|
||||||
|
| `--shadow-card-hover` | `0 4px 12px rgba(0,0,0,.06)` | 卡片悬停 |
|
||||||
|
| `--shadow-btn` | `0 2px 6px rgba(44,62,107,.2)` | CTA 主按钮 |
|
||||||
|
| `--shadow-dialog` | `0 8px 40px rgba(0,0,0,.15)` | 弹框 |
|
||||||
|
| `--overlay` | `rgba(0,0,0,.45)` | 弹框遮罩 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 布局体系
|
||||||
|
|
||||||
|
### 2.1 整体框架
|
||||||
|
|
||||||
|
```
|
||||||
|
┌──────────────────────────────────────────────────────────┐
|
||||||
|
│ Header 60px │
|
||||||
|
│ Logo [导航1] [导航2] 🔍搜索 🔔³ ⚙️ 👤用户名 │
|
||||||
|
├────────┬─────────────────────────────────────────────────┤
|
||||||
|
│Sidebar │ Breadcrumb 46px │
|
||||||
|
│232px │ 授权运营 › 当前页面 │
|
||||||
|
│ ├─────────────────────────────────────────────────┤
|
||||||
|
│ 菜单 │ │ │
|
||||||
|
│ 分组 │ Tree Panel 280px │ Main Content │
|
||||||
|
│ │ (可选) │ (自适应) │
|
||||||
|
│ 11项 │ │ │
|
||||||
|
│ │ │ │
|
||||||
|
│v0.1.0 │ │ │
|
||||||
|
└────────┴────────────────────┴────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.2 尺寸规范
|
||||||
|
|
||||||
|
| 元素 | 尺寸 | 说明 |
|
||||||
|
|------|:--:|------|
|
||||||
|
| Header 高度 | 60px | 固定,含 Logo + 导航 + 搜索 + 通知 + 用户 |
|
||||||
|
| Sidebar 宽度 | 232px | 白色底色,菜单项高 42-50px |
|
||||||
|
| Sidebar 菜单项 | 42-88px | 主菜单 50px,子菜单 42px,分组标题 88px |
|
||||||
|
| Breadcrumb 高度 | 46px | 内容区顶部,含当前路径 |
|
||||||
|
| Tree Panel 宽度 | 280px | 许可证/客户页可选,含搜索框 |
|
||||||
|
| Content 内边距 | 20px(左右) / 16px(上下) | 统一页面内边距 |
|
||||||
|
| 卡片间距 | 12px | 统计卡 / 内容卡之间 |
|
||||||
|
|
||||||
|
### 2.3 页面类型
|
||||||
|
|
||||||
|
| 类型 | Tree Panel | 用途 | 示例页面 |
|
||||||
|
|------|:--:|------|---------|
|
||||||
|
| 标准列表页 | ❌ | 全宽卡片 + 筛选栏 + 表格 | 客户管理、合同管理、交付管理 |
|
||||||
|
| Tree + 列表页 | ✅(280px) | 左侧层级导航 + 右侧列表 | 许可证管理 |
|
||||||
|
| 工作台 | ❌ | 统计卡片 + 图表 + 待办 | 首页 Dashboard |
|
||||||
|
| 详情页 | ❌ | 详情卡片 + 操作按钮 | 合同详情、交付详情 |
|
||||||
|
| 只读列表 | ❌ | 简单表格 | 集成环境、产品线 |
|
||||||
|
|
||||||
|
### 2.4 路由命名
|
||||||
|
|
||||||
|
| 模块 | 路径 | 页面 |
|
||||||
|
|------|------|------|
|
||||||
|
| 工作台 | `/` | HomeView |
|
||||||
|
| 客户管理 | `/customers` | CustomersView |
|
||||||
|
| 合同管理 | `/contracts` | ContractsView |
|
||||||
|
| 交付管理 | `/deliveries` | DeliveriesView |
|
||||||
|
| 许可 SN | `/licenses/sn` | LicenseSnListView |
|
||||||
|
| 许可证管理 | `/licenses` | LicenseList |
|
||||||
|
| Callback | `/callbacks` | CallbackInboxView |
|
||||||
|
| 集成环境 | `/integration/environments` | — |
|
||||||
|
| 产品线 | `/integration/product-lines` | — |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 组件规范
|
||||||
|
|
||||||
|
### 3.1 按钮
|
||||||
|
|
||||||
|
```css
|
||||||
|
/* 主按钮 */
|
||||||
|
.btn-primary {
|
||||||
|
background: #2C3E6B; color: #fff; border: none;
|
||||||
|
border-radius: 4px; padding: 6px 14px;
|
||||||
|
font-size: 13px; font-weight: 500; cursor: pointer;
|
||||||
|
}
|
||||||
|
.btn-primary:hover { background: #3D5A99; }
|
||||||
|
|
||||||
|
/* CTA 按钮(带阴影) */
|
||||||
|
.btn-cta {
|
||||||
|
background: #2C3E6B; color: #fff; border: none;
|
||||||
|
border-radius: 4px; padding: 7px 16px;
|
||||||
|
box-shadow: 0 2px 6px rgba(44,62,107,.2);
|
||||||
|
font-size: 13px; font-weight: 500; cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 取消/次要按钮 */
|
||||||
|
.btn-cancel {
|
||||||
|
background: #fff; color: #606266;
|
||||||
|
border: 1px solid #E0E3E8; border-radius: 4px;
|
||||||
|
padding: 8px 20px; font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 危险按钮 */
|
||||||
|
.btn-danger {
|
||||||
|
background: #F56C6C; color: #fff; border: none;
|
||||||
|
border-radius: 4px; padding: 8px 20px; font-size: 14px;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
| 类型 | 背景 | 用途 |
|
||||||
|
|------|------|------|
|
||||||
|
| Primary | `#2C3E6B` | 查询、保存、签发 |
|
||||||
|
| CTA | `#2C3E6B` + shadow | 创建/新建操作 |
|
||||||
|
| Cancel | `#FFFFFF` + border | 取消、关闭 |
|
||||||
|
| Danger | `#F56C6C` | 删除、吊销 |
|
||||||
|
| Link | transparent + `#2C3E6B` | 详情、编辑 |
|
||||||
|
|
||||||
|
### 3.2 输入框
|
||||||
|
|
||||||
|
```css
|
||||||
|
input, .input {
|
||||||
|
border: 1px solid #E0E3E8; border-radius: 4px;
|
||||||
|
padding: 7px 12px; font-size: 13px; color: #303133;
|
||||||
|
}
|
||||||
|
input:focus { border-color: #2C3E6B; }
|
||||||
|
input::placeholder { color: #C0C4CC; }
|
||||||
|
|
||||||
|
/* 搜索框 */
|
||||||
|
.search-box {
|
||||||
|
border: 1px solid #E0E3E8; border-radius: 6px;
|
||||||
|
padding: 4px 10px; background: #F8F9FB;
|
||||||
|
display: flex; align-items: center; gap: 6px;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.3 表格
|
||||||
|
|
||||||
|
```css
|
||||||
|
table { width: 100%; border-collapse: collapse; font-size: 13px; }
|
||||||
|
thead th {
|
||||||
|
padding: 10px 12px; text-align: left; font-weight: 600;
|
||||||
|
font-size: 12px; color: #2C3E6B; background: #F2F5FC;
|
||||||
|
border-bottom: 1px solid #E8ECF1; white-space: nowrap;
|
||||||
|
}
|
||||||
|
tbody td {
|
||||||
|
padding: 9px 12px; border-bottom: 1px solid #F2F5FC;
|
||||||
|
}
|
||||||
|
tr:hover td { background: #F8F9FB; }
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.4 状态标签
|
||||||
|
|
||||||
|
```css
|
||||||
|
.tag { display: inline-block; padding: 2px 8px; border-radius: 3px; font-size: 11px; font-weight: 500; }
|
||||||
|
.tag-active { background: #E6F7EE; color: #1A7A3A; border: 1px solid #A8E6C1; }
|
||||||
|
.tag-revoked { background: #FEF0F0; color: #F56C6C; border: 1px solid #FBC4C4; }
|
||||||
|
.tag-pending { background: #FDF6EC; color: #E6A23C; border: 1px solid #F5DAB1; }
|
||||||
|
.tag-expired { background: #f4f4f5; color: #909399; border: 1px solid #e9e9eb; }
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.5 通知 Badge
|
||||||
|
|
||||||
|
```css
|
||||||
|
.badge {
|
||||||
|
position: absolute; top: -2px; right: -5px;
|
||||||
|
width: 16px; height: 16px; background: #D54941;
|
||||||
|
border-radius: 50%; font-size: 10px; color: #fff;
|
||||||
|
line-height: 16px; text-align: center; font-weight: 600;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.6 侧边菜单
|
||||||
|
|
||||||
|
```css
|
||||||
|
.sidebar { width: 232px; background: #fff; border-right: 1px solid #E8ECF1; }
|
||||||
|
.menu-item {
|
||||||
|
display: flex; align-items: center; gap: 10px;
|
||||||
|
padding: 9px 20px; font-size: 14px; color: #606266;
|
||||||
|
border-right: 3px solid transparent; cursor: pointer;
|
||||||
|
}
|
||||||
|
.menu-item:hover { background: #F2F5FC; color: #2C3E6B; }
|
||||||
|
.menu-item.active {
|
||||||
|
background: #F2F5FC; color: #2C3E6B; font-weight: 600;
|
||||||
|
border-right-color: #2C3E6B;
|
||||||
|
}
|
||||||
|
.menu-section-label {
|
||||||
|
padding: 6px 20px; font-size: 11px; color: #C0C4CC;
|
||||||
|
text-transform: uppercase; font-weight: 600;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.7 面包屑
|
||||||
|
|
||||||
|
```css
|
||||||
|
.breadcrumb {
|
||||||
|
height: 46px; display: flex; align-items: center;
|
||||||
|
padding: 0 20px; gap: 6px; font-size: 13px;
|
||||||
|
background: #fff; border-bottom: 1px solid #E8ECF1;
|
||||||
|
}
|
||||||
|
.breadcrumb .sep { color: #C0C4CC; }
|
||||||
|
.breadcrumb .current { color: #2C3E6B; font-weight: 600; }
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 页面模板
|
||||||
|
|
||||||
|
### 4.1 标准列表页
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────┐
|
||||||
|
│ 统计卡片 (4列可选) │
|
||||||
|
│ [总XX] [活跃] [已吊销] [已过期] │
|
||||||
|
├─────────────────────────────────────────┤
|
||||||
|
│ [🔍搜索框] [状态下拉] [查询] [+ 新建] │
|
||||||
|
├─────────────────────────────────────────┤
|
||||||
|
│ 表格 │
|
||||||
|
│ ID │ 名称 │ 关联 │ 状态 │ 时间 │ 操作 │
|
||||||
|
│ ... │
|
||||||
|
├─────────────────────────────────────────┤
|
||||||
|
│ 共XX条 ‹ 1 2 › │
|
||||||
|
└─────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
**对应文件**:`CustomersView.vue`、`ContractsView.vue`、`DeliveriesView.vue`
|
||||||
|
|
||||||
|
### 4.2 Tree + 列表页(许可证管理)
|
||||||
|
|
||||||
|
```
|
||||||
|
┌──────────┬──────────────────────────────┐
|
||||||
|
│ 🔍搜索 │ 统计卡片 │
|
||||||
|
│ │ [47] [38] [6] [3] │
|
||||||
|
│ ▸ 项目A ├──────────────────────────────┤
|
||||||
|
│ ▸ 项目B │ [🔍] [类型▼] [状态▼] [查询] │
|
||||||
|
│ ▸ 项目C │ [+ 签发许可证] │
|
||||||
|
│ ├──────────────────────────────┤
|
||||||
|
│ │ 表格 │
|
||||||
|
│ │ 许可证ID │ 租户 │ 产品 │ ... │
|
||||||
|
│ │ ... │
|
||||||
|
└──────────┴──────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
**对应文件**:`LicenseList.vue`
|
||||||
|
|
||||||
|
### 4.3 工作台
|
||||||
|
|
||||||
|
```
|
||||||
|
┌──────────────────────────────────────────┐
|
||||||
|
│ [总许可证] [活跃终端] [待处理] [本月签发] │
|
||||||
|
├──────────────────────┬───────────────────┤
|
||||||
|
│ 📊 许可证签发趋势 │ ⚠️ 待处理事项 │
|
||||||
|
│ (柱状图) │ · Callback 待处理 │
|
||||||
|
│ │ · 许可证将到期 │
|
||||||
|
│ │ · 终端额度满 │
|
||||||
|
└──────────────────────┴───────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
**对应文件**:`HomeView.vue`(待完善为仪表盘)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 弹框体系
|
||||||
|
|
||||||
|
### 5.1 基础规范
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────┐
|
||||||
|
│ 标题 ✕ 关闭 │ ← Header: padding 16px/20px
|
||||||
|
├─────────────────────────────────┤ ← 圆角: 8px
|
||||||
|
│ │ 阴影: 0 8px 40px rgba(0,0,0,.15)
|
||||||
|
│ 表单内容区 │ ← Body: padding 20px
|
||||||
|
│ │ 表单项间距: 16px
|
||||||
|
│ label: [______________] │ 标签色: #606266
|
||||||
|
│ │
|
||||||
|
├─────────────────────────────────┤
|
||||||
|
│ [取消] [确认/签发] │ ← Footer: gap 10px, 右对齐
|
||||||
|
└─────────────────────────────────┘
|
||||||
|
|
||||||
|
遮罩: rgba(0,0,0,.45)
|
||||||
|
动画: scale(.96→1) + opacity 200ms
|
||||||
|
关闭按钮: 28×28, hover #F2F5FC
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.2 类型规格
|
||||||
|
|
||||||
|
| 类型 | 宽度 | Header | 内容布局 | Footer |
|
||||||
|
|------|:--:|--------|---------|--------|
|
||||||
|
| 签发表单 | 560px | 标题+关闭 | 6+ 字段 + 特性复选框 | 取消+签发 |
|
||||||
|
| 新建/编辑 | 480px | 标题+关闭 | 2-4 字段 | 取消+保存 |
|
||||||
|
| 详情查看 | 480-520px | 标题+关闭 | KV 对(标签100px) | 关闭(+操作) |
|
||||||
|
| 确认删除 | 420px | — | 居中图标+说明 | 取消+删除(红) |
|
||||||
|
| 危险操作 | 420px | — | 红色危险提示区 | 取消+确认(红) |
|
||||||
|
|
||||||
|
### 5.3 危险操作区分
|
||||||
|
|
||||||
|
```css
|
||||||
|
/* 普通确认:居中图标 + 文字 */
|
||||||
|
/* 危险确认:额外红色背景提示区 */
|
||||||
|
.danger-zone {
|
||||||
|
background: #FEF0F0; border: 1px solid #FBC4C4;
|
||||||
|
border-radius: 4px; padding: 10px 12px;
|
||||||
|
color: #F56C6C; font-size: 12px;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 实施指南
|
||||||
|
|
||||||
|
### 6.1 Element Plus CSS 覆盖
|
||||||
|
|
||||||
|
```css
|
||||||
|
:root {
|
||||||
|
/* 核心 3 行 */
|
||||||
|
--el-color-primary: #2C3E6B;
|
||||||
|
--el-bg-color-page: #EAEFFA;
|
||||||
|
--el-border-radius-base: 6px;
|
||||||
|
|
||||||
|
/* 扩展 */
|
||||||
|
--el-color-primary-light-3: #3D5A99;
|
||||||
|
--el-color-primary-light-9: #D6DFF0;
|
||||||
|
--el-color-success: #1A7A3A;
|
||||||
|
--el-color-danger: #F56C6C;
|
||||||
|
--el-table-header-bg-color: #F2F5FC;
|
||||||
|
--el-table-header-text-color: #2C3E6B;
|
||||||
|
--el-dialog-border-radius: 8px;
|
||||||
|
--el-overlay-color-lighter: rgba(0,0,0,.45);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.2 文件对照
|
||||||
|
|
||||||
|
| 设计 Token | 实现文件 |
|
||||||
|
|-----------|---------|
|
||||||
|
| 全局变量 | `src/theme.css` |
|
||||||
|
| 整体布局 | `src/layout/MainLayout.vue` |
|
||||||
|
| 路由+鉴权 | `src/router/index.js` |
|
||||||
|
| 认证状态 | `src/stores/auth.js` |
|
||||||
|
| API 封装 | `src/api/platform.js` |
|
||||||
|
| 许可证页 | `src/views/LicenseList.vue` |
|
||||||
|
| 设计对比 | `src/views/LayoutCompareView.vue` |
|
||||||
|
| Demo | `public/design-demo.html` |
|
||||||
|
|
||||||
|
### 6.3 新增页面检查清单
|
||||||
|
|
||||||
|
- [ ] 使用 `<script setup>` + Composition API
|
||||||
|
- [ ] 所有 API 调用有 try/catch + `apiErrorMessage`
|
||||||
|
- [ ] 列表有 `v-loading` / 提交有 `:loading`
|
||||||
|
- [ ] 路由 meta 包含 `roles` 鉴权
|
||||||
|
- [ ] 表格表头使用 `#F2F5FC` 背景
|
||||||
|
- [ ] 状态标签使用 `.tag-active` / `.tag-revoked` 等
|
||||||
|
- [ ] CTA 按钮使用 `.btn-cta` 带阴影
|
||||||
|
- [ ] 弹框使用 `<el-dialog>` 自动继承 `8px` 圆角
|
||||||
@@ -0,0 +1,665 @@
|
|||||||
|
# CraftLabs 前端框架设计 v1.0
|
||||||
|
|
||||||
|
> 基于设计体系 `2026-05-19-design-system.md` 提炼的前端工程框架规范
|
||||||
|
> 目标:统一架构、约束风格、加速开发、降低交接成本
|
||||||
|
> 日期:2026-05-19
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 目录
|
||||||
|
|
||||||
|
1. [框架概述](#1-框架概述)
|
||||||
|
2. [技术栈](#2-技术栈)
|
||||||
|
3. [工程结构](#3-工程结构)
|
||||||
|
4. [核心模块](#4-核心模块)
|
||||||
|
5. [布局系统](#5-布局系统)
|
||||||
|
6. [组件体系](#6-组件体系)
|
||||||
|
7. [页面模板](#7-页面模板)
|
||||||
|
8. [数据流](#8-数据流)
|
||||||
|
9. [开发规范](#9-开发规范)
|
||||||
|
10. [构建与部署](#10-构建与部署)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 框架概述
|
||||||
|
|
||||||
|
### 1.1 定位
|
||||||
|
|
||||||
|
CraftLabs 前端框架是基于 Vue 3 + Element Plus 的企业级 B2B 授权运营平台前端方案。在 Element Plus 之上注入品牌设计 Token,提供开箱即用的布局、组件、页面模板和开发约束。
|
||||||
|
|
||||||
|
### 1.2 核心原则
|
||||||
|
|
||||||
|
| 原则 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| **约定优于配置** | 路由、鉴权、API 封装、错误处理均有默认行为,开发者只需关注业务 |
|
||||||
|
| **Token 驱动** | 所有视觉属性通过 CSS 变量控制,换肤只需改 `theme.css` |
|
||||||
|
| **组件隔离** | 每个 `.vue` 文件自包含模板+逻辑+样式,`<script setup>` 统一风格 |
|
||||||
|
| **渐进增强** | 在 Element Plus 基础上叠加品牌样式,不 fork 组件库 |
|
||||||
|
| **安全默认** | JWT 存储 localStorage、401 自动登出、路由级鉴权 |
|
||||||
|
|
||||||
|
### 1.3 层级架构
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────┐
|
||||||
|
│ Views (页面层) │
|
||||||
|
│ LicenseList CustomersView ContractsView │
|
||||||
|
├─────────────────────────────────────────────┤
|
||||||
|
│ Components (组件层) │
|
||||||
|
│ StatsCard SearchBar DataTable ... │
|
||||||
|
├─────────────────────────────────────────────┤
|
||||||
|
│ Layout System (布局系统) │
|
||||||
|
│ MainLayout (Header+Sidebar+Breadcrumb) │
|
||||||
|
├──────────────┬──────────────┬───────────────┤
|
||||||
|
│ Router │ Store │ API │
|
||||||
|
│ (鉴权/懒加载) │ (Pinia JWT) │ (axios封装) │
|
||||||
|
├──────────────┴──────────────┴───────────────┤
|
||||||
|
│ Element Plus + Vue 3 │
|
||||||
|
├─────────────────────────────────────────────┤
|
||||||
|
│ Theme System (Token层) │
|
||||||
|
│ theme.css · CSS Variables │
|
||||||
|
└─────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 技术栈
|
||||||
|
|
||||||
|
| 层 | 选型 | 版本 | 说明 |
|
||||||
|
|----|------|:--:|------|
|
||||||
|
| 框架 | Vue 3 | ^3.5 | Composition API + `<script setup>` |
|
||||||
|
| 构建 | Vite | ^6.0 | 极速 HMR + 开箱即用 |
|
||||||
|
| UI 库 | Element Plus | ^2.9 | 组件库本体,CSS 变量覆盖换肤 |
|
||||||
|
| 路由 | vue-router | ^4.5 | 懒加载 + meta 鉴权 |
|
||||||
|
| 状态 | Pinia | ^2.3 | 轻量状态管理 |
|
||||||
|
| HTTP | axios | ^1.7 | 拦截器 + JWT 注入 |
|
||||||
|
| 语言 | JavaScript (ESM) | — | 暂用 JS,必要时渐进引入 TS |
|
||||||
|
|
||||||
|
### 2.1 不引入的依赖
|
||||||
|
|
||||||
|
| 库 | 原因 |
|
||||||
|
|----|------|
|
||||||
|
| Tailwind CSS | Element Plus 已覆盖组件样式,避免双体系 |
|
||||||
|
| Vuex | Pinia 更轻量,官方推荐 |
|
||||||
|
| Lodash | 项目规模不需要工具函数库 |
|
||||||
|
| Moment.js | 日期格式化用原生 `Intl` 或字符串裁剪 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 工程结构
|
||||||
|
|
||||||
|
```
|
||||||
|
web/delivery-platform-ui/
|
||||||
|
├── public/
|
||||||
|
│ ├── design-demo.html # 设计 Demo(独立 HTML,无依赖)
|
||||||
|
│ └── design-system.html # 设计体系可视化
|
||||||
|
├── src/
|
||||||
|
│ ├── main.js # 入口:挂载 Vue + Pinia + Router + 401拦截器
|
||||||
|
│ ├── App.vue # 根组件:纯 <router-view>
|
||||||
|
│ ├── theme.css # ★ 全局主题 Token(CSS 变量)
|
||||||
|
│ │
|
||||||
|
│ ├── layout/
|
||||||
|
│ │ └── MainLayout.vue # ★ 主布局:Header + Sidebar + Breadcrumb
|
||||||
|
│ │
|
||||||
|
│ ├── router/
|
||||||
|
│ │ └── index.js # ★ 路由表 + 鉴权守卫
|
||||||
|
│ │
|
||||||
|
│ ├── stores/
|
||||||
|
│ │ └── auth.js # ★ 认证状态:JWT + login/logout/roles
|
||||||
|
│ │
|
||||||
|
│ ├── api/
|
||||||
|
│ │ └── platform.js # ★ API 封装:按模块导出 axios 请求函数
|
||||||
|
│ │
|
||||||
|
│ ├── utils/
|
||||||
|
│ │ ├── apiErrorMessage.js # 统一错误消息提取
|
||||||
|
│ │ └── redactPayload.js # Callback payload 脱敏
|
||||||
|
│ │
|
||||||
|
│ └── views/ # ★ 页面组件(按模块命名)
|
||||||
|
│ ├── HomeView.vue # 工作台/Dashboard
|
||||||
|
│ ├── LoginView.vue # 登录页
|
||||||
|
│ ├── ForbiddenView.vue # 403
|
||||||
|
│ ├── NotFoundView.vue # 404
|
||||||
|
│ ├── CustomersView.vue # 客户管理
|
||||||
|
│ ├── ProjectsView.vue # 项目管理
|
||||||
|
│ ├── ContractsView.vue # 合同列表
|
||||||
|
│ ├── ContractWizardView.vue # 合同创建向导
|
||||||
|
│ ├── ContractDetailView.vue # 合同详情
|
||||||
|
│ ├── DeliveriesView.vue # 交付列表
|
||||||
|
│ ├── DeliveryBatchWizardView.vue
|
||||||
|
│ ├── DeliveryBatchDetailView.vue
|
||||||
|
│ ├── LicenseSnListView.vue # 许可SN列表
|
||||||
|
│ ├── LicenseSnWizardView.vue
|
||||||
|
│ ├── LicenseSnDetailView.vue
|
||||||
|
│ ├── LicenseList.vue # ★ 许可证管理(自研SDK核心页)
|
||||||
|
│ ├── CallbackInboxView.vue # Callback 收件箱
|
||||||
|
│ ├── CallbackInboxDetailView.vue
|
||||||
|
│ ├── IntegrationEnvironmentsView.vue
|
||||||
|
│ ├── IntegrationProductLinesView.vue
|
||||||
|
│ └── LayoutCompareView.vue # 设计审核工具(内部)
|
||||||
|
│
|
||||||
|
├── package.json
|
||||||
|
├── vite.config.js
|
||||||
|
├── Dockerfile
|
||||||
|
└── README.md
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.1 文件命名规范
|
||||||
|
|
||||||
|
| 类型 | 规范 | 示例 |
|
||||||
|
|------|------|------|
|
||||||
|
| 页面视图 | `PascalCaseView.vue` | `CustomersView.vue` |
|
||||||
|
| 布局组件 | `PascalCase.vue` | `MainLayout.vue` |
|
||||||
|
| 路由/Store/API | `kebab-case.js` | `platform.js` |
|
||||||
|
| 工具函数 | `camelCase.js` | `apiErrorMessage.js` |
|
||||||
|
| 样式文件 | `kebab-case.css` | `theme.css` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 核心模块
|
||||||
|
|
||||||
|
### 4.1 主题系统 (`theme.css`)
|
||||||
|
|
||||||
|
所有视觉 Token 集中在一个 CSS 文件中,通过覆盖 Element Plus 变量实现换肤。
|
||||||
|
|
||||||
|
```css
|
||||||
|
/* 核心 3 行即可改变全站主色调 */
|
||||||
|
:root {
|
||||||
|
--el-color-primary: #2C3E6B;
|
||||||
|
--el-bg-color-page: #EAEFFA;
|
||||||
|
--el-border-radius-base: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 完整 Token 见 docs/superpowers/specs/2026-05-19-design-system.md */
|
||||||
|
```
|
||||||
|
|
||||||
|
**设计意图**:
|
||||||
|
- 不修改 Element Plus 源码,只覆盖 CSS 变量
|
||||||
|
- 更换品牌色只需改 `--el-color-primary` 一个值
|
||||||
|
- 新页面自动继承主题,无需额外引入
|
||||||
|
|
||||||
|
### 4.2 路由系统 (`router/index.js`)
|
||||||
|
|
||||||
|
**路由表结构**:
|
||||||
|
|
||||||
|
```js
|
||||||
|
const routes = [
|
||||||
|
// 公开路由(无鉴权)
|
||||||
|
{ path: "/login", component: LoginView },
|
||||||
|
|
||||||
|
// 受保护路由(需要登录)
|
||||||
|
{
|
||||||
|
path: "/",
|
||||||
|
component: MainLayout, // 统一布局壳
|
||||||
|
meta: { requiresAuth: true },
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: "licenses", // 子路由路径
|
||||||
|
name: "licenses", // 命名路由(用于编程跳转)
|
||||||
|
component: () => import("..."), // 懒加载
|
||||||
|
meta: { roles: ["SYS_ADMIN", "DEVELOPER"] } // 角色鉴权
|
||||||
|
},
|
||||||
|
// ... 更多子路由
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
// 兜底路由
|
||||||
|
{ path: "/403", component: ForbiddenView },
|
||||||
|
{ path: "/:pathMatch(.*)*", component: NotFoundView },
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
**鉴权守卫逻辑**:
|
||||||
|
|
||||||
|
```
|
||||||
|
用户访问页面
|
||||||
|
│
|
||||||
|
├─ meta.requiresAuth && 无 token → 重定向 /login?redirect=原路径
|
||||||
|
│
|
||||||
|
├─ meta.roles && 用户角色不匹配 → 重定向 /403
|
||||||
|
│
|
||||||
|
└─ 通过 → 正常渲染
|
||||||
|
```
|
||||||
|
|
||||||
|
**新增页面只需**:
|
||||||
|
1. 在 `children` 数组中加一条路由
|
||||||
|
2. 设置 `meta.roles` 控制可见角色
|
||||||
|
3. `component: () => import(...)` 懒加载
|
||||||
|
|
||||||
|
### 4.3 状态管理 (`stores/auth.js`)
|
||||||
|
|
||||||
|
```js
|
||||||
|
export const useAuthStore = defineStore("auth", {
|
||||||
|
state: () => ({
|
||||||
|
token: localStorage.getItem("craftlabs_platform_token") || "",
|
||||||
|
displayName: "",
|
||||||
|
roles: [],
|
||||||
|
}),
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
async login(username, password) {
|
||||||
|
const { data } = await axios.post("/api/v1/auth/login", { username, password })
|
||||||
|
this.token = data.token
|
||||||
|
this.roles = data.roles
|
||||||
|
localStorage.setItem("craftlabs_platform_token", this.token)
|
||||||
|
axios.defaults.headers.common.Authorization = `Bearer ${this.token}`
|
||||||
|
},
|
||||||
|
|
||||||
|
logout() {
|
||||||
|
this.token = ""
|
||||||
|
localStorage.removeItem("craftlabs_platform_token")
|
||||||
|
delete axios.defaults.headers.common.Authorization
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
**使用方式**:
|
||||||
|
|
||||||
|
```js
|
||||||
|
import { useAuthStore } from "../stores/auth"
|
||||||
|
const auth = useAuthStore()
|
||||||
|
|
||||||
|
// 模板中
|
||||||
|
auth.hasAnyRole(["SYS_ADMIN", "DEVELOPER"]) // 角色判断
|
||||||
|
auth.displayName // 用户名
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.4 API 层 (`api/platform.js`)
|
||||||
|
|
||||||
|
```js
|
||||||
|
// 每个后端端点导出为一个具名函数
|
||||||
|
export function listCustomers(params) {
|
||||||
|
return axios.get("/api/v1/customers", { params })
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createCustomer(body) {
|
||||||
|
return axios.post("/api/v1/customers", body)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateCustomer(id, body) {
|
||||||
|
return axios.put(`/api/v1/customers/${id}`, body)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deleteCustomer(id) {
|
||||||
|
return axios.delete(`/api/v1/customers/${id}`)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**约定**:
|
||||||
|
- 函数名 = HTTP 动词 + 资源名:`listXxx` / `createXxx` / `updateXxx` / `deleteXxx` / `getXxx`
|
||||||
|
- 状态变更用语义化函数名:`patchContractStatus()`、`addLine()`
|
||||||
|
- 所有函数返回 axios Promise,由调用方 `await` + try/catch
|
||||||
|
- 查询参数用 `{ params }` 传递(axios 自动序列化)
|
||||||
|
|
||||||
|
### 4.5 全局错误处理 (`main.js`)
|
||||||
|
|
||||||
|
```js
|
||||||
|
// 401 自动登出拦截器
|
||||||
|
axios.interceptors.response.use(
|
||||||
|
(r) => r,
|
||||||
|
(err) => {
|
||||||
|
if (err.response?.status === 401) {
|
||||||
|
const auth = useAuthStore()
|
||||||
|
auth.logout()
|
||||||
|
router.push({ name: "login", query: { redirect: router.currentRoute.value.fullPath } })
|
||||||
|
}
|
||||||
|
return Promise.reject(err)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 布局系统
|
||||||
|
|
||||||
|
### 5.1 MainLayout 结构
|
||||||
|
|
||||||
|
```
|
||||||
|
┌──────────────────────────────────────────────────────────┐
|
||||||
|
│ <header> 60px │
|
||||||
|
│ Logo │ 导航菜单 │ 全局搜索 │ 🔔 │ ⚙️ │ 👤用户名 │
|
||||||
|
├────────┬─────────────────────────────────────────────────┤
|
||||||
|
│<aside> │ <div class="breadcrumb"> 46px │
|
||||||
|
│ 232px │ 授权运营 › 当前页面 │
|
||||||
|
│ ├─────────────────────────────────────────────────┤
|
||||||
|
│ 菜单 │ │
|
||||||
|
│ 分组 │ <router-view /> │
|
||||||
|
│ │ (各页面通过 slot 注入到此区域) │
|
||||||
|
│ 11项 │ │
|
||||||
|
│ │ │
|
||||||
|
└────────┴─────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.2 布局尺寸契约
|
||||||
|
|
||||||
|
| 元素 | 尺寸 | CSS |
|
||||||
|
|------|:--:|------|
|
||||||
|
| Header | 60px | `height: 60px; flex-shrink: 0` |
|
||||||
|
| Sidebar | 232px | `width: 232px; flex-shrink: 0` |
|
||||||
|
| Breadcrumb | 46px | `height: 46px; flex-shrink: 0` |
|
||||||
|
| Content | 自适应 | `flex: 1; padding: 16px 20px` |
|
||||||
|
|
||||||
|
### 5.3 布局变体
|
||||||
|
|
||||||
|
| 变体 | 触发条件 | 说明 |
|
||||||
|
|------|---------|------|
|
||||||
|
| 标准布局 | 默认 | Header + Sidebar + Breadcrumb + 全宽 Content |
|
||||||
|
| Tree 布局 | `activeModule === 'licenses'` | 额外 280px Tree 面板在 Content 左侧 |
|
||||||
|
| 全屏布局 | 无 Header/Sidebar | 登录页、403/404 页 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 组件体系
|
||||||
|
|
||||||
|
### 6.1 组件分类
|
||||||
|
|
||||||
|
```
|
||||||
|
组件层级:
|
||||||
|
Element Plus 基础组件 (el-button, el-table, el-dialog, ...)
|
||||||
|
│
|
||||||
|
├── 直接使用(无需封装)
|
||||||
|
│ el-button el-input el-select el-table el-pagination
|
||||||
|
│ el-tag el-badge el-tree el-dialog el-card
|
||||||
|
│
|
||||||
|
├── 组合组件(页面内组合)
|
||||||
|
│ 搜索栏 + 表格 + 分页 → 列表页模板
|
||||||
|
│ 统计卡片行 → Dashboard 模板
|
||||||
|
│
|
||||||
|
└── 业务组件(跨页面复用,按需抽取)
|
||||||
|
StatsCard SearchFilterBar StatusTag ConfirmDialog
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.2 组件开发原则
|
||||||
|
|
||||||
|
- **优先直接用 Element Plus**:90% 场景不需要封装
|
||||||
|
- **抽取时机**:同一模式在 3+ 个页面出现时才抽取为独立组件
|
||||||
|
- **Props 优先于全局状态**:组件通过 props 接收数据,不直接读 store
|
||||||
|
- **Events 向上**:组件通过 `$emit` 通知父组件,不直接改父组件状态
|
||||||
|
|
||||||
|
### 6.3 CSS 约定
|
||||||
|
|
||||||
|
```css
|
||||||
|
/* ✅ 推荐:scoped + CSS 变量 */
|
||||||
|
<style scoped>
|
||||||
|
.my-page { background: var(--el-bg-color-page); }
|
||||||
|
.card { border-radius: var(--el-border-radius-base); }
|
||||||
|
.btn-primary { background: var(--el-color-primary); }
|
||||||
|
</style>
|
||||||
|
|
||||||
|
/* ❌ 避免:硬编码色值 */
|
||||||
|
<style scoped>
|
||||||
|
.card { background: #EAEFFA; } /* 应使用 CSS 变量 */
|
||||||
|
</style>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 页面模板
|
||||||
|
|
||||||
|
### 7.1 标准列表页
|
||||||
|
|
||||||
|
适用于:客户管理、合同管理、交付管理、许可SN
|
||||||
|
|
||||||
|
```
|
||||||
|
结构:
|
||||||
|
<统计卡片行> (可选)
|
||||||
|
<筛选栏>
|
||||||
|
[搜索框] [状态下拉] [查询按钮] [+ 新建按钮]
|
||||||
|
</筛选栏>
|
||||||
|
<数据表格>
|
||||||
|
<el-table> + <el-pagination>
|
||||||
|
</数据表格>
|
||||||
|
<新建/编辑 Dialog>
|
||||||
|
```
|
||||||
|
|
||||||
|
**样板代码(约 200 行)**:
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<script setup>
|
||||||
|
import { ref, onMounted } from "vue"
|
||||||
|
import { ElMessage, ElMessageBox } from "element-plus"
|
||||||
|
import { useAuthStore } from "../stores/auth"
|
||||||
|
import { listXxx, createXxx, updateXxx, deleteXxx } from "../api/platform"
|
||||||
|
import { apiErrorMessage } from "../utils/apiErrorMessage"
|
||||||
|
|
||||||
|
const auth = useAuthStore()
|
||||||
|
const loading = ref(false), saving = ref(false)
|
||||||
|
const rows = ref([]), total = ref(0), page = ref(1), pageSize = ref(10)
|
||||||
|
const keyword = ref(""), filterStatus = ref("")
|
||||||
|
const dialogVisible = ref(false), editingId = ref(null)
|
||||||
|
const formRef = ref(null)
|
||||||
|
const form = reactive({ name: "", ... })
|
||||||
|
|
||||||
|
onMounted(() => { auth.restoreAxiosAuth(); load() })
|
||||||
|
|
||||||
|
async function load() {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const { data } = await listXxx({ page: page.value-1, size: pageSize.value, keyword: keyword.value?.trim() })
|
||||||
|
rows.value = data.content ?? []
|
||||||
|
total.value = data.totalElements ?? 0
|
||||||
|
} catch(e) {
|
||||||
|
ElMessage.error(apiErrorMessage(e))
|
||||||
|
} finally { loading.value = false }
|
||||||
|
}
|
||||||
|
|
||||||
|
async function submit() {
|
||||||
|
await formRef.value.validate()
|
||||||
|
saving.value = true
|
||||||
|
try {
|
||||||
|
const payload = { name: form.name.trim(), ... }
|
||||||
|
editingId.value ? await updateXxx(editingId.value, payload) : await createXxx(payload)
|
||||||
|
dialogVisible.value = false
|
||||||
|
await load()
|
||||||
|
} catch(e) {
|
||||||
|
ElMessage.error(apiErrorMessage(e))
|
||||||
|
} finally { saving.value = false }
|
||||||
|
}
|
||||||
|
|
||||||
|
async function onDelete(row) {
|
||||||
|
await ElMessageBox.confirm(`确定删除「${row.name}」?`, "提示", { type: "warning" })
|
||||||
|
await deleteXxx(row.id)
|
||||||
|
await load()
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.2 Tree + 列表页
|
||||||
|
|
||||||
|
适用于:许可证管理
|
||||||
|
|
||||||
|
```
|
||||||
|
结构:
|
||||||
|
<div class="license-page"> ← display: flex
|
||||||
|
<Tree Panel 280px> ← 左侧固定宽度
|
||||||
|
<Main Panel> ← flex: 1
|
||||||
|
<统计卡片行>
|
||||||
|
<筛选栏 + 签发按钮>
|
||||||
|
<数据表格>
|
||||||
|
</Main Panel>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.3 工作台 Dashboard
|
||||||
|
|
||||||
|
适用于:首页
|
||||||
|
|
||||||
|
```
|
||||||
|
结构:
|
||||||
|
<统计卡片行 4列>
|
||||||
|
<趋势图 + 待办列表 2列>
|
||||||
|
<快捷入口>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.4 详情页
|
||||||
|
|
||||||
|
适用于:合同详情、交付详情、SN详情
|
||||||
|
|
||||||
|
```
|
||||||
|
结构:
|
||||||
|
<返回按钮 + 页面标题>
|
||||||
|
<详情卡片>
|
||||||
|
<el-descriptions> 或 自定义 KV 布局
|
||||||
|
</详情卡片>
|
||||||
|
<关联数据卡片> (可选:审计事件、Callback关联)
|
||||||
|
<操作按钮行>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. 数据流
|
||||||
|
|
||||||
|
### 8.1 请求生命周期
|
||||||
|
|
||||||
|
```
|
||||||
|
用户操作(点击查询/新建/保存)
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
loading.value = true ← 显示加载态
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
await apiFunction(payload) ← axios 自动注入 JWT Header
|
||||||
|
│
|
||||||
|
├─ 200: data → 更新响应式状态
|
||||||
|
│ ElMessage.success("操作成功")
|
||||||
|
│
|
||||||
|
├─ 4xx: err → ElMessage.error(apiErrorMessage(e))
|
||||||
|
│ 401 → 自动登出
|
||||||
|
│ 403 → 提示无权限
|
||||||
|
│
|
||||||
|
└─ 5xx / Network Error → ElMessage.error("服务器错误")
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
loading.value = false ← 恢复交互
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.2 状态分类
|
||||||
|
|
||||||
|
| 状态类型 | 存储位置 | 示例 |
|
||||||
|
|---------|---------|------|
|
||||||
|
| 页面级临时状态 | 组件内 `ref()` / `reactive()` | 表格数据、表单数据、loading |
|
||||||
|
| 跨页面持久状态 | Pinia `auth.js` | token、用户名、角色 |
|
||||||
|
| UI 状态 | 组件内 | dialogVisible、activeTab |
|
||||||
|
| URL 状态 | `route.query` / `route.params` | 分页页码、详情页 ID |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. 开发规范
|
||||||
|
|
||||||
|
### 9.1 新增页面检查清单
|
||||||
|
|
||||||
|
```
|
||||||
|
□ 文件命名:PascalCaseView.vue
|
||||||
|
□ 使用 <script setup> + Composition API
|
||||||
|
□ 所有 API 调用有 try/catch + apiErrorMessage
|
||||||
|
□ 列表有 v-loading / 提交按钮有 :loading
|
||||||
|
□ 路由 meta 包含 roles 鉴权
|
||||||
|
□ 表格表头使用 #F2F5FC 背景(继承 theme.css)
|
||||||
|
□ 状态标签使用 .tag-active / .tag-revoked 等语义类
|
||||||
|
□ CTA 按钮使用 .btn-cta 带阴影
|
||||||
|
□ 色值使用 CSS 变量,不硬编码
|
||||||
|
□ 弹框使用 <el-dialog>(自动继承 8px 圆角)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 9.2 禁止事项
|
||||||
|
|
||||||
|
| 禁止 | 原因 |
|
||||||
|
|------|------|
|
||||||
|
| 硬编码色值 (`#EAEFFA`) | 应使用 CSS 变量,便于换肤 |
|
||||||
|
| 直接操作 DOM (`document.querySelector`) | 使用 Vue 响应式 + ref |
|
||||||
|
| 在 `setup` 外定义响应式数据 | 非 `.vue` 文件需使用 `defineStore` |
|
||||||
|
| `v-if` + `v-for` 同时使用 | Vue 性能警告 |
|
||||||
|
| 忽略 try/catch | 未捕获异常导致白屏 |
|
||||||
|
| 提交 `console.log` | 使用 `ElMessage` 或移除 |
|
||||||
|
|
||||||
|
### 9.3 Git 提交规范
|
||||||
|
|
||||||
|
```
|
||||||
|
feat(web): 新增许可证管理页面
|
||||||
|
fix(web): 修复合同列表分页重置bug
|
||||||
|
refactor(web): 抽取 StatsCard 为独立组件
|
||||||
|
style(web): 统一表格表头样式
|
||||||
|
docs: 更新前端框架设计文档
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. 构建与部署
|
||||||
|
|
||||||
|
### 10.1 开发环境
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd web/delivery-platform-ui
|
||||||
|
npm install
|
||||||
|
npm run dev # → http://localhost:5173
|
||||||
|
# /api → proxy → http://127.0.0.1:8080
|
||||||
|
```
|
||||||
|
|
||||||
|
### 10.2 生产构建
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 指定后端地址
|
||||||
|
VITE_API_BASE=https://api.craftlabs.cn npm run build
|
||||||
|
|
||||||
|
# 产物:dist/
|
||||||
|
# 部署:Nginx 静态托管 + 反代 /api 到后端
|
||||||
|
```
|
||||||
|
|
||||||
|
### 10.3 CI/CD
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# .github/workflows/ci-platform.yml (前端部分)
|
||||||
|
- name: Build Frontend
|
||||||
|
run: |
|
||||||
|
cd web/delivery-platform-ui
|
||||||
|
npm ci
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
### 10.4 Nginx 配置模板
|
||||||
|
|
||||||
|
```nginx
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name platform.craftlabs.cn;
|
||||||
|
root /var/www/delivery-platform-ui/dist;
|
||||||
|
index index.html;
|
||||||
|
|
||||||
|
location /api/ {
|
||||||
|
proxy_pass http://127.0.0.1:8080;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
}
|
||||||
|
|
||||||
|
location / {
|
||||||
|
try_files $uri $uri/ /index.html; # SPA fallback
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 附录 A:文件职责速查
|
||||||
|
|
||||||
|
| 文件 | 职责 | 谁需要修改 |
|
||||||
|
|------|------|-----------|
|
||||||
|
| `theme.css` | 全局色彩/圆角/阴影 Token | 设计师/UED |
|
||||||
|
| `MainLayout.vue` | 页面壳:Header/Sidebar/Breadcrumb | 架构师 |
|
||||||
|
| `router/index.js` | 路由表 + 鉴权守卫 | 全栈/前端 |
|
||||||
|
| `stores/auth.js` | JWT 认证状态 | 全栈/前端 |
|
||||||
|
| `api/platform.js` | 后端 API 封装 | 前端(新增接口时) |
|
||||||
|
| `utils/*.js` | 通用工具函数 | 前端 |
|
||||||
|
| `views/*View.vue` | 业务页面 | 前端 |
|
||||||
|
|
||||||
|
## 附录 B:快速上手
|
||||||
|
|
||||||
|
**创建一个新页面(以「审计日志」为例)**:
|
||||||
|
|
||||||
|
1. 创建 `src/views/AuditLogView.vue`,使用标准列表页模板
|
||||||
|
2. 在 `api/platform.js` 添加 `listAuditEvents()` 函数
|
||||||
|
3. 在 `router/index.js` 添加路由,设置 `meta.roles`
|
||||||
|
4. 在 `MainLayout.vue` 的 `menuItems` 数组添加菜单项
|
||||||
|
5. `npm run dev` 验证
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*修订记录:2026-05-19 初版,基于设计体系 v1.0*
|
||||||
@@ -0,0 +1,221 @@
|
|||||||
|
# 客户端授权管理工具设计
|
||||||
|
|
||||||
|
> **日期**:2026-05-25
|
||||||
|
> **目标**:提供客户端授权管理工具,让客户终端用户自助完成设备授权、授权迁移、撤销授权。分为 CLI 和 GUI 两个形态。
|
||||||
|
> **实施顺序**:① 完善现有 SDK(JNI 桥接 + 集成测试)→ ② CLI 工具 → ③ GUI 桌面工具
|
||||||
|
> **技术选型**:CLI = Rust clap + craft-core;GUI = Tauri 2.x + Vue 3 + craft-core
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 现有 SDK 能力复用
|
||||||
|
|
||||||
|
```text
|
||||||
|
┌─────────────────────────────────────┐
|
||||||
|
│ Client Authorization Tool (Tauri) │
|
||||||
|
│ ┌───────────────────────────────┐ │
|
||||||
|
│ │ Vue 3 UI Layer │ │
|
||||||
|
│ │ (授权状态/激活/迁移/撤销) │ │
|
||||||
|
│ └───────────┬───────────────────┘ │
|
||||||
|
│ │ IPC (invoke) │
|
||||||
|
│ ┌───────────▼───────────────────┐ │
|
||||||
|
│ │ Rust Backend (Tauri cmd) │ │
|
||||||
|
│ │ - 调用 craft-core C ABI │ │
|
||||||
|
│ │ - HTTP 请求平台 API │ │
|
||||||
|
│ │ - 本地配置持久化 │ │
|
||||||
|
│ └───────────┬───────────────────┘ │
|
||||||
|
│ │ FFI │
|
||||||
|
│ ┌───────────▼───────────────────┐ │
|
||||||
|
│ │ craft-core (Rust cdylib) │ │
|
||||||
|
│ │ - activate/check/release │ │
|
||||||
|
│ │ - 设备指纹 / 加密 / 心跳 │ │
|
||||||
|
│ └───────────────────────────────┘ │
|
||||||
|
└─────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.1 可直接复用的 Rust 模块
|
||||||
|
|
||||||
|
| 模块 | 复用方式 | 现状 |
|
||||||
|
|------|---------|------|
|
||||||
|
| `craft_initialize` | Tauri 启动时调用 | ✅ 已实现 |
|
||||||
|
| `craft_activate` | 用户点击"激活"时调用 | ✅ 已实现 |
|
||||||
|
| `craft_check_license` | 首页状态展示 | ✅ 已实现 |
|
||||||
|
| `craft_get_license_info` | 授权详情展示 | ✅ 已实现 |
|
||||||
|
| `craft_has_feature` | 功能特性开关展示 | ✅ 已实现 |
|
||||||
|
| `craft_release` | 撤销授权时调用 | ✅ 已实现 |
|
||||||
|
| `craft_heartbeat` | 后台定期心跳 | ✅ 已实现 |
|
||||||
|
| `device.rs` | 设备指纹采集 | ✅ 已实现 |
|
||||||
|
|
||||||
|
### 1.2 需新增的能力
|
||||||
|
|
||||||
|
| 能力 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| **HTTP 客户端** | Tauri Rust 后端调平台 REST API(SN 查询/换机申请/状态同步) |
|
||||||
|
| **本地配置持久化** | 保存激活的 SN、授权信息到本地安全存储 |
|
||||||
|
| **自动启动/托盘** | 系统托盘常驻,后台心跳,状态变更通知 |
|
||||||
|
| **平台 API 客户端** | 封装若干平台 API(查询绑定、提交换机、提交激活结果) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 功能设计
|
||||||
|
|
||||||
|
### 2.1 首页 — 授权概览
|
||||||
|
|
||||||
|
| 区块 | 内容 |
|
||||||
|
|------|------|
|
||||||
|
| **授权状态卡片** | 大图标 + 状态(已授权/未授权/已过期)+ 授权类型(正式/试用) |
|
||||||
|
| **设备信息** | 设备名称、设备指纹(mid)、操作系统 |
|
||||||
|
| **授权摘要** | SN 编码、有效期、剩余天数、功能特性列表 |
|
||||||
|
| **操作区** | 激活授权、迁移授权、撤销授权三个主按钮 |
|
||||||
|
|
||||||
|
### 2.2 激活授权流程
|
||||||
|
|
||||||
|
```
|
||||||
|
1. 用户点击「激活授权」
|
||||||
|
2. 弹出对话框:输入 SN 编码(或从剪贴板粘贴)
|
||||||
|
3. 工具调用 craft_activate(SN) → 本地验证
|
||||||
|
4. 若为 selfhosted 模式 → HTTP 请求平台 API 完成远程验证
|
||||||
|
5. 成功 → 显示授权详情,开始心跳
|
||||||
|
6. 失败 → 显示错误原因(SN 无效/已吊销/网络超时等)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.3 授权迁移流程
|
||||||
|
|
||||||
|
```
|
||||||
|
1. 用户点击「迁移授权」
|
||||||
|
2. 工具显示当前绑定的设备信息 + SN
|
||||||
|
3. 用户确认「迁移到本设备」
|
||||||
|
4. 工具先调用 craft_release() 释放旧设备授权
|
||||||
|
5. HTTP 请求平台 API 记录换机申请
|
||||||
|
6. 重新调用 craft_activate() 在新设备激活
|
||||||
|
7. 完成迁移
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.4 撤销授权流程
|
||||||
|
|
||||||
|
```
|
||||||
|
1. 用户点击「撤销授权」
|
||||||
|
2. 二次确认对话框(含风险提示)
|
||||||
|
3. 工具调用 craft_release() 释放本地授权
|
||||||
|
4. HTTP 请求平台 API 更新 SN 状态为 REVOKED
|
||||||
|
5. 清除本地配置
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.5 授权详情页
|
||||||
|
|
||||||
|
| 展示项 | 来源 |
|
||||||
|
|--------|------|
|
||||||
|
| SN 编码 | craft_get_license_info |
|
||||||
|
| 授权状态 | craft_check_license |
|
||||||
|
| 有效期 | expiration_date |
|
||||||
|
| 已授权特性 | feature_names / feature_values |
|
||||||
|
| 设备指纹(mid) | device.rs |
|
||||||
|
| 首次激活时间 | 平台 API |
|
||||||
|
| 最近心跳时间 | 本地记录 |
|
||||||
|
| 绑定历史 | 平台 API |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 与平台 API 的交互
|
||||||
|
|
||||||
|
工具需要通过 HTTP 与交付平台后端通信:
|
||||||
|
|
||||||
|
| 平台 API | 方法 | 用途 |
|
||||||
|
|----------|------|------|
|
||||||
|
| `/api/v1/auth/client-login` | POST | 客户端登录(获取工具专用 token) |
|
||||||
|
| `/api/v1/licenses/verify` | POST | 验证 SN 有效性 |
|
||||||
|
| `/api/v1/licenses/activate` | POST | 提交激活结果 + 设备指纹 |
|
||||||
|
| `/api/v1/licenses/revoke` | POST | 提交撤销申请 |
|
||||||
|
| `/api/v1/devices/swap-request` | POST | 提交换机申请 |
|
||||||
|
| `/api/v1/devices/{mid}/bindings` | GET | 查询绑定历史 |
|
||||||
|
|
||||||
|
### API 安全
|
||||||
|
|
||||||
|
- 客户端工具使用独立 API Token(非管理后台 JWT)
|
||||||
|
- Token 限制:仅可操作本设备关联的 SN
|
||||||
|
- 所有请求附带设备指纹签名
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 目录结构
|
||||||
|
|
||||||
|
```
|
||||||
|
client-tool/
|
||||||
|
├── src-tauri/
|
||||||
|
│ ├── src/
|
||||||
|
│ │ ├── main.rs # Tauri 入口 + 命令注册
|
||||||
|
│ │ ├── commands.rs # Tauri IPC 命令(activate/check/release/migrate)
|
||||||
|
│ │ ├── platform_api.rs # 平台 REST API 客户端
|
||||||
|
│ │ ├── license.rs # 授权生命周期管理
|
||||||
|
│ │ └── config.rs # 本地持久化配置
|
||||||
|
│ ├── Cargo.toml # 依赖 craft-core 等
|
||||||
|
│ └── tauri.conf.json # Tauri 配置
|
||||||
|
├── src/
|
||||||
|
│ ├── App.vue
|
||||||
|
│ ├── views/
|
||||||
|
│ │ ├── DashboardView.vue # 首页概览
|
||||||
|
│ │ ├── ActivateView.vue # 激活向导
|
||||||
|
│ │ ├── DetailView.vue # 授权详情
|
||||||
|
│ │ └── SettingsView.vue # 设置
|
||||||
|
│ └── components/
|
||||||
|
│ ├── StatusCard.vue
|
||||||
|
│ └── FeatureList.vue
|
||||||
|
├── package.json
|
||||||
|
└── README.md
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. CLI 工具设计
|
||||||
|
|
||||||
|
### 5.1 命令结构
|
||||||
|
|
||||||
|
```text
|
||||||
|
craftlabs-auth-cli
|
||||||
|
├── craft status # 查看本地授权状态
|
||||||
|
├── craft activate <SN> # 使用 SN 激活本机
|
||||||
|
├── craft check # 检查授权是否有效
|
||||||
|
├── craft info # 显示授权详情 + 功能特性
|
||||||
|
├── craft release # 撤销本机授权
|
||||||
|
├── craft migrate <SN> # 迁移授权到本机
|
||||||
|
├── craft heartbeat # 手动触发心跳
|
||||||
|
├── craft device-id # 显示本机设备指纹
|
||||||
|
└── craft config # 查看/修改本地配置
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.2 技术实现
|
||||||
|
|
||||||
|
- Rust 二进制 crate,`Cargo.toml` 中依赖 `craft-core`(path dependency)
|
||||||
|
- 使用 `clap` crate 解析命令行参数
|
||||||
|
- 平台 API 调用使用 `reqwest`(已有依赖)
|
||||||
|
- 输出格式支持 text(默认)和 json(`--json` 参数)
|
||||||
|
- 跨平台编译:Linux x86_64 + aarch64, macOS x86_64 + arm64, Windows x86_64
|
||||||
|
|
||||||
|
### 5.3 CLI 与 craft-core 的调用关系
|
||||||
|
|
||||||
|
```
|
||||||
|
craft activate SN-12345
|
||||||
|
└→ clap 解析 args → "activate"
|
||||||
|
└→ craft_initialize(config) → 初始化上下文
|
||||||
|
└→ craft_activate(handle, "SN-12345")
|
||||||
|
├→ 成功 → 持久化 SN 到本地配置
|
||||||
|
└→ 失败 → 打印错误原因
|
||||||
|
```
|
||||||
|
|
||||||
|
## 6. 实施路线
|
||||||
|
|
||||||
|
| 阶段 | 内容 | 估计 | 交付物 |
|
||||||
|
|------|------|------|--------|
|
||||||
|
| **S1: 完善SDK** | JNI bridge 编译+集成测试+CI | 1周 | 可调用的 Java SDK |
|
||||||
|
| **S2: CLI MVP** | 基础 CLI(status/activate/check/release) | 1周 | `craftlabs-auth-cli` 二进制 |
|
||||||
|
| **S3: CLI 完整** | migrate/heartbeat/config + 平台 API 对接 | 1周 | 完整 CLI 功能 |
|
||||||
|
| **S4: GUI P0** | Tauri 壳 + Vue UI + 激活/状态查看 | 2周 | 桌面应用 |
|
||||||
|
| **S5: GUI P1** | 迁移/撤销 + 系统托盘 + 心跳 | 1周 | 完整桌面应用 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 修订记录
|
||||||
|
|
||||||
|
| 日期 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| 2026-05-25 | 初版:基于 PRD 评估和 Tauri 技术选型撰写 |
|
||||||
|
| 2026-05-25 | 补充:CLI 工具设计 + 实施顺序调整为 SDK→CLI→GUI |
|
||||||
@@ -0,0 +1,401 @@
|
|||||||
|
# Mid 阶段原型设计 — M7 设备管理 / M8 通知待办 / M9 报表对账
|
||||||
|
|
||||||
|
> **基于**:PRD `docs/chuangfei-platform-product-modules.md`(2026-05-25 更新版)§16 原型已知局限
|
||||||
|
> **设计目标**:补齐 I1~I9 未覆盖的三个模块的 UI 原型,完善完整产品流程(登录 → 客户 → 合同 → 交付 → SN → Callback → 设备 → 待办 → 报表)
|
||||||
|
> **设计验证**:通过 Visual Companion 线框图确认,2026-05-25
|
||||||
|
> **关联文档**:[BPM 与版本排期](../../chuangfei-platform-bpm-and-roadmap.md) · [并行迭代索引](../../engineering/PARALLEL_ITERATION_INDEX.md)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 背景与动机
|
||||||
|
|
||||||
|
### 1.1 当前状态
|
||||||
|
|
||||||
|
I1~I9 已交付 M1~M6 + M10-F01 + M11 基础 + 自研许可证管理(V6)。三个模块完全未开始:
|
||||||
|
|
||||||
|
| 模块 | PRD 优先级 | 计划迭代 | 功能点数 |
|
||||||
|
|------|-----------|---------|---------|
|
||||||
|
| M7 设备与终端治理 | P1 | I10~I12 | 6(F01~F06) |
|
||||||
|
| M8 通知与待办 | P1 | I10~I12 | 5(F01~F05) |
|
||||||
|
| M9 报表与对账 | P1 | I11 | 6(F01~F06) |
|
||||||
|
|
||||||
|
### 1.2 设计原则
|
||||||
|
|
||||||
|
- **遵循现有模式**:与 I1~I9 的 Vue 3 + Element Plus + Pinia 风格一致
|
||||||
|
- **渐进增强**:现有原型框架不变,新增菜单项和路由
|
||||||
|
- **数据溯源**:优先复用已有 API 和数据库表,新增表最小化
|
||||||
|
- **角色一致的访问控制**:按 PRD §13 权限矩阵控制可见性
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 导航结构更新
|
||||||
|
|
||||||
|
### 2.1 左侧菜单(新增项以 ★ 标记)
|
||||||
|
|
||||||
|
```
|
||||||
|
📊 首页
|
||||||
|
👥 客户管理
|
||||||
|
📋 项目
|
||||||
|
📋 合同管理
|
||||||
|
📦 交付管理
|
||||||
|
🔑 许可 SN
|
||||||
|
📨 Callback 收件箱
|
||||||
|
⚙️ 集成配置
|
||||||
|
🖥️ 设备管理 ★ M7 - SYS_ADMIN / DELIVERY / LICENSE_OPS
|
||||||
|
🔔 待办中心 / 通知设置 ★ M8 - 全员(按角色过滤)
|
||||||
|
📊 报表中心 ★ M9 - 管理层 / FINANCE_VIEW / COMPLIANCE
|
||||||
|
🔍 审计日志
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.2 新增路由表
|
||||||
|
|
||||||
|
| 路由 | 页面组件 | 模块 | 角色 |
|
||||||
|
|------|---------|------|------|
|
||||||
|
| `/devices` | `DeviceListView.vue` | M7 | SYS_ADMIN, DELIVERY, LICENSE_OPS |
|
||||||
|
| `/devices/:id` | `DeviceDetailView.vue` | M7 | 同上 |
|
||||||
|
| `/todos` | `TodoCenterView.vue` | M8 | 全员 |
|
||||||
|
| `/notifications/settings` | `NotificationSettingsView.vue` | M8 | SYS_ADMIN 或各角色自配置 |
|
||||||
|
| `/reports/contract-sn` | `ContractSnReportView.vue` | M9 | FINANCE_VIEW, COMPLIANCE, EXEC_VIEW |
|
||||||
|
| `/reports/callback-stats` | `CallbackStatsView.vue` | M9 | LICENSE_OPS, COMPLIANCE |
|
||||||
|
| `/reports/project-health` | `ProjectHealthView.vue` | M9 | EXEC_VIEW, SYS_ADMIN |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. M7 设备与终端治理
|
||||||
|
|
||||||
|
### 3.1 数据模型(新增表)
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- M7:设备主表
|
||||||
|
CREATE TABLE platform_device (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
mid VARCHAR(128) NOT NULL UNIQUE, -- 设备指纹 / 标识
|
||||||
|
alias VARCHAR(256), -- 别名(可读名称)
|
||||||
|
site VARCHAR(256), -- 场站 / 部署位置
|
||||||
|
customer_id BIGINT REFERENCES platform_customer(id),
|
||||||
|
project_id BIGINT REFERENCES platform_project(id),
|
||||||
|
status VARCHAR(32) NOT NULL DEFAULT 'ACTIVE', -- ACTIVE / INACTIVE / DECOMMISSIONED
|
||||||
|
first_seen_at TIMESTAMPTZ,
|
||||||
|
last_heartbeat_at TIMESTAMPTZ,
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||||
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||||
|
);
|
||||||
|
|
||||||
|
-- M7:设备↔SN 绑定历史(时间线)
|
||||||
|
CREATE TABLE platform_device_sn_binding (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
device_id BIGINT NOT NULL REFERENCES platform_device(id),
|
||||||
|
license_sn_id BIGINT NOT NULL REFERENCES platform_license_sn(id),
|
||||||
|
bind_type VARCHAR(32) NOT NULL DEFAULT 'ACTIVATE', -- ACTIVATE / SWAP / RELEASE
|
||||||
|
bind_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||||
|
remark TEXT,
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||||
|
);
|
||||||
|
|
||||||
|
-- M7:换机申请记录
|
||||||
|
CREATE TABLE platform_device_swap_request (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
old_device_id BIGINT NOT NULL REFERENCES platform_device(id),
|
||||||
|
new_mid VARCHAR(128) NOT NULL,
|
||||||
|
sn_id BIGINT NOT NULL REFERENCES platform_license_sn(id),
|
||||||
|
reason VARCHAR(512),
|
||||||
|
status VARCHAR(32) NOT NULL DEFAULT 'PENDING', -- PENDING / APPROVED / REJECTED
|
||||||
|
processed_by VARCHAR(256),
|
||||||
|
processed_at TIMESTAMPTZ,
|
||||||
|
remark TEXT,
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.2 页面规格
|
||||||
|
|
||||||
|
**P1. 设备列表页(/devices)**
|
||||||
|
|
||||||
|
| 组件 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| 筛选栏 | 客户下拉(可搜索)、场站文本、SN 关键词、「查询」「重置」 |
|
||||||
|
| 工具栏 | 「登记设备」按钮 → 弹出登记对话框 |
|
||||||
|
| 表格列 | mid、别名、场站、关联客户、关联 SN、状态(Tag)、最近心跳时间、操作(详情) |
|
||||||
|
| 状态枚举 | Online(在线)/ Offline(离线)/ Decommissioned(已退役) |
|
||||||
|
| 分页 | 同现有模式:10/20/50 |
|
||||||
|
|
||||||
|
**P2. 设备详情页(/devices/:id)**
|
||||||
|
|
||||||
|
| 区块 | 内容 |
|
||||||
|
|------|------|
|
||||||
|
| 头部 | ← 设备列表;mid 标识;状态 Tag |
|
||||||
|
| 信息区 | mid、别名、场站、客户/项目、首次发现时间、最近心跳时间 |
|
||||||
|
| 操作区 | 「编辑设备信息」按钮、「发起换机申请」按钮、「查看 SN 绑定历史」按钮 |
|
||||||
|
| SN 绑定时间线 | 列表展示:首次激活、换机、解绑等事件,按时间倒序 |
|
||||||
|
|
||||||
|
**P3. 换机申请对话框**
|
||||||
|
|
||||||
|
| 字段 | 类型 | 校验 |
|
||||||
|
|------|------|------|
|
||||||
|
| 原设备 | 只读 | 从设备详情传入 |
|
||||||
|
| 原 SN | 只读 | 该设备当前绑定的 SN |
|
||||||
|
| 新设备 mid | 文本输入 | 必填,maxlength=128 |
|
||||||
|
| 换机原因 | 下拉选择 | 硬件更换 / 场地迁移 / 性能升级 / 其他 |
|
||||||
|
| 备注 | 文本框 | 选填,maxlength=512 |
|
||||||
|
|
||||||
|
**P4. 登记设备对话框**
|
||||||
|
|
||||||
|
| 字段 | 类型 | 校验 |
|
||||||
|
|------|------|------|
|
||||||
|
| mid | 文本输入 | 必填,maxlength=128,唯一性校验 |
|
||||||
|
| 别名 | 文本输入 | 选填,maxlength=256 |
|
||||||
|
| 场站 | 文本输入 | 选填,maxlength=256 |
|
||||||
|
| 关联客户 | 下拉选择 | 选填,从已有客户列表选取 |
|
||||||
|
| 关联项目 | 下拉选择 | 选填,依赖所选客户过滤 |
|
||||||
|
|
||||||
|
### 3.3 API 端点
|
||||||
|
|
||||||
|
| 方法 | 路径 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| GET | `/api/v1/devices` | 设备分页列表(支持 customerId, site, snCode 筛选) |
|
||||||
|
| POST | `/api/v1/devices` | 登记设备 |
|
||||||
|
| GET | `/api/v1/devices/{id}` | 设备详情 |
|
||||||
|
| PUT | `/api/v1/devices/{id}` | 编辑设备信息 |
|
||||||
|
| GET | `/api/v1/devices/{id}/bindings` | SN 绑定时间线 |
|
||||||
|
| POST | `/api/v1/devices/{id}/swap-request` | 提交换机申请 |
|
||||||
|
| GET | `/api/v1/swap-requests` | 换机申请列表(Ops 审批用) |
|
||||||
|
| PATCH | `/api/v1/swap-requests/{id}/status` | 审批换机申请 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. M8 通知与待办
|
||||||
|
|
||||||
|
### 4.1 数据模型(新增表)
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- M8:待办事项
|
||||||
|
CREATE TABLE platform_todo_item (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
todo_type VARCHAR(64) NOT NULL, -- CALLBACK_PENDING / SN_PENDING / ACTIVATION_OVERDUE
|
||||||
|
title VARCHAR(512) NOT NULL,
|
||||||
|
source_id BIGINT, -- 关联业务 ID(如 callback_inbox_id)
|
||||||
|
source_type VARCHAR(64), -- 关联业务类型
|
||||||
|
priority VARCHAR(16) NOT NULL DEFAULT 'MEDIUM', -- HIGH / MEDIUM / LOW
|
||||||
|
status VARCHAR(32) NOT NULL DEFAULT 'PENDING', -- PENDING / PROCESSED / IGNORED
|
||||||
|
assigned_role VARCHAR(64), -- 目标角色
|
||||||
|
assigned_user_id VARCHAR(256), -- 认领人
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||||
|
processed_at TIMESTAMPTZ,
|
||||||
|
remark TEXT
|
||||||
|
);
|
||||||
|
|
||||||
|
-- M8:通知配置(每个用户或角色)
|
||||||
|
CREATE TABLE platform_notification_config (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
role_code VARCHAR(64), -- 角色级别默认配置
|
||||||
|
channel_email BOOLEAN DEFAULT FALSE,
|
||||||
|
channel_wecom BOOLEAN DEFAULT FALSE,
|
||||||
|
channel_in_app BOOLEAN DEFAULT TRUE,
|
||||||
|
event_type VARCHAR(64) NOT NULL, -- 订阅的事件类型
|
||||||
|
aggregation_rule VARCHAR(64), -- 聚合规则:NONE / 30MIN / DAILY_DIGEST
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||||
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.2 页面规格
|
||||||
|
|
||||||
|
**P1. 待办中心(/todos)**
|
||||||
|
|
||||||
|
| 组件 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| 统计卡片 | 3 个 KPI 卡片:待处理 Callback 数 / 待发放 SN 数 / 待核对激活数(带角色过滤) |
|
||||||
|
| 筛选栏 | 类型下拉(全部 / Callback / SN 发放 / 激活核对)、状态、「查询」 |
|
||||||
|
| 表格列 | 类型(Tag)、标题、来源、优先级(高/中/低)、创建时间、操作(认领/前往/忽略) |
|
||||||
|
| 批量操作 | 「批量标记已处理」「批量忽略」 |
|
||||||
|
|
||||||
|
**P2. 通知设置(/notifications/settings)**
|
||||||
|
|
||||||
|
| 区块 | 内容 |
|
||||||
|
|------|------|
|
||||||
|
| 通知通道 | 复选框组:站内待办 / 邮件 / 企业微信 / 短信。**MVP 实际仅实现「站内待办」**;邮件和企微为配置项占位,实际发送通道依赖独立基建,列 Mid 增强 |
|
||||||
|
| 事件订阅表 | 事件类型、通知方式、订阅角色、静默规则 |
|
||||||
|
| 操作 | "保存配置"按钮 |
|
||||||
|
|
||||||
|
**事件类型订阅清单**(与 Callback 事件类型对齐):
|
||||||
|
|
||||||
|
| 事件类型 | 默认通道 | 默认角色 | 聚合规则 |
|
||||||
|
|---------|---------|---------|---------|
|
||||||
|
| Callback 待处理 | 站内 + 邮件 | LICENSE_OPS | 30 分钟内合并 |
|
||||||
|
| SN 待发放 | 站内 | LICENSE_OPS | 每日汇总 |
|
||||||
|
| 激活超期 7 日 | 站内 + 邮件 | DELIVERY + LICENSE_OPS | 不重复 |
|
||||||
|
| 换机申请待审批 | 站内 | LICENSE_OPS | 不重复 |
|
||||||
|
| 合同到期提醒 | 邮件 | SALES | 每周汇总 |
|
||||||
|
|
||||||
|
### 4.3 API 端点
|
||||||
|
|
||||||
|
| 方法 | 路径 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| GET | `/api/v1/todos` | 待办列表(支持 type, status, role 筛选) |
|
||||||
|
| PATCH | `/api/v1/todos/{id}/status` | 更新待办状态(认领/完成/忽略) |
|
||||||
|
| POST | `/api/v1/todos/batch-status` | 批量更新待办状态 |
|
||||||
|
| GET | `/api/v1/notifications/config` | 获取通知配置 |
|
||||||
|
| PUT | `/api/v1/notifications/config` | 更新通知配置 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. M9 报表与对账
|
||||||
|
|
||||||
|
### 5.1 数据说明
|
||||||
|
|
||||||
|
M9 **不新增独立表**,全部基于已有业务表做聚合查询:
|
||||||
|
|
||||||
|
| 视图 | 数据源 | 聚合逻辑 |
|
||||||
|
|------|--------|---------|
|
||||||
|
| 合同 vs SN 对账 | `platform_contract` + `platform_contract_line` + `platform_license_sn` | 按合同行分组 COUNT SN |
|
||||||
|
| 已发 vs 已激活 | `platform_license_sn` | 按 status 分组统计 |
|
||||||
|
| Callback 统计 | `platform_callback_inbox` | 按 event_type / status / 时间聚合 |
|
||||||
|
| 项目健康度 | 多表联合 | 交付率 / SN 发放率 / 激活率 加权计算 |
|
||||||
|
|
||||||
|
### 5.2 页面规格
|
||||||
|
|
||||||
|
**P1. 履约对账(/reports/contract-sn)**
|
||||||
|
|
||||||
|
| 组件 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| 筛选栏 | 项目下拉、合同下拉、「查询」「导出 CSV」 |
|
||||||
|
| KPI 卡片 | 合同总行项数、已发 SN 数、未发缺口、已激活/已发 |
|
||||||
|
| 表格列 | 合同编号、客户、行项、应发、实发、已激活、缺口、状态(正常/缺额/超发) |
|
||||||
|
|
||||||
|
**P2. Callback 统计(/reports/callback-stats)**
|
||||||
|
|
||||||
|
| 区块 | 内容 |
|
||||||
|
|------|------|
|
||||||
|
| 时间段选择 | 近 24 小时 / 近 7 天 / 近 30 天 / 自定义 |
|
||||||
|
| 事件类型分布 | 柱状图或百分比条:各事件类型占比 |
|
||||||
|
| 处理成功率趋势 | 日/周/月成功率 + 总量 |
|
||||||
|
| Top 失败原因 | 排名列表(失败原因 + 占比 + 趋势) |
|
||||||
|
|
||||||
|
**P3. 项目健康度看板(/reports/project-health)**
|
||||||
|
|
||||||
|
| 组件 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| 表格列 | 项目名称、交付完成率、SN 发放率、激活率、健康度(🟢正常/🟡关注/🔴风险) |
|
||||||
|
| 健康度规则 | 绿:三项均 ≥80%;黄:任一项 <80%;红:任一项 <50% |
|
||||||
|
|
||||||
|
### 5.3 API 端点
|
||||||
|
|
||||||
|
| 方法 | 路径 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| GET | `/api/v1/reports/contract-sn` | 履约对账报表(支持 projectId, contractId 筛选) |
|
||||||
|
| GET | `/api/v1/reports/callback-stats` | Callback 统计(支持时间范围、eventType 筛选) |
|
||||||
|
| GET | `/api/v1/reports/project-health` | 项目健康度列表 |
|
||||||
|
| GET | `/api/v1/reports/export` | 导出 CSV — 后端生成文件流,前端触发下载(Content-Disposition: attachment) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 实现注意事项
|
||||||
|
|
||||||
|
### 6.1 与现有系统的集成
|
||||||
|
|
||||||
|
- **设备↔SN 绑定**:`platform_device_sn_binding.license_sn_id` → 引用已有 `platform_license_sn.id`
|
||||||
|
- **待办自动生成**:`platform_todo_item` 的 `source_id` + `source_type` 可指向 `platform_callback_inbox.id` 等已有业务表
|
||||||
|
- **报表聚合**:复用现有 MyBatis-Plus Mapper,新增报表专用的 `@Select` 查询方法
|
||||||
|
|
||||||
|
### 6.2 角色与权限
|
||||||
|
|
||||||
|
- 初始角色配置使用简化三角色(SYS_ADMIN / DEVELOPER / OPS)
|
||||||
|
- Mid 阶段实际交付时应按 PRD §13.2 的产品定义角色集落地:
|
||||||
|
- DELIVERY → M7 设备读写
|
||||||
|
- LICENSE_OPS → M7 设备读写、M8 待办、M9 Callback 统计
|
||||||
|
- SALES → M9 合同对账只读
|
||||||
|
- FINANCE_VIEW / COMPLIANCE → M9 报表只读
|
||||||
|
- EXEC_VIEW → M9 项目健康度只读
|
||||||
|
|
||||||
|
### 6.3 M7-F05 设备事件联动实现机制
|
||||||
|
|
||||||
|
Callback `device:*` 事件(`device:pre_activate` / `post_activate`)到达时:
|
||||||
|
|
||||||
|
1. Webhook Ingress 接收并写入 `platform_callback_inbox`
|
||||||
|
2. `CallbackInboxService` 解析事件中的 `mid` 字段
|
||||||
|
3. 若 `mid` 在 `platform_device` 中已存在 → 更新 `last_heartbeat_at`,并在 `platform_device_sn_binding` 添加记录
|
||||||
|
4. 若 `mid` 不存在 → 自动创建设备草稿记录(status=INACTIVE),并在待办中心生成一条「新设备待确认」待办
|
||||||
|
5. Ops 可在设备详情页手动补充别名/场站/客户关联
|
||||||
|
|
||||||
|
此机制**不依赖 M7 管理页面存在**即可在后台运行;M7 页面提供的是查看和手工干预入口。
|
||||||
|
|
||||||
|
### 6.4 与 PRD 状态对照
|
||||||
|
|
||||||
|
| 功能点 | 本设计覆盖 | 说明 |
|
||||||
|
|--------|-----------|------|
|
||||||
|
| M7-F01 设备登记 | ✅ | 登记对话框 + 列表 |
|
||||||
|
| M7-F02 绑定历史 | ✅ | 设备详情时间线 |
|
||||||
|
| M7-F03 换机申请 | ✅ | 换机申请对话框 + 审批 |
|
||||||
|
| M7-F04 设备检索 | ✅ | 列表多维度筛选 |
|
||||||
|
| M7-F05 Callback 联动 | ✅ | 待办自动生成 |
|
||||||
|
| M7-F06 策略展示 | ❌ 后续 | 依赖 BitAnswer 策略查询 |
|
||||||
|
| M8-F01 待办列表 | ✅ | 待办中心 |
|
||||||
|
| M8-F02 认领/完成 | ✅ | 状态 PATCH + 批量 |
|
||||||
|
| M8-F03 通知通道 | ✅ | 通知配置页 |
|
||||||
|
| M8-F04 通知模板 | ✅ | 事件→模板配置 |
|
||||||
|
| M8-F05 静默规则 | ✅ | 聚合规则配置 |
|
||||||
|
| M9-F01 合同 vs SN | ✅ | 履约对账页 |
|
||||||
|
| M9-F02 已发 vs 激活 | ✅ | 履约对账页含 |
|
||||||
|
| M9-F03 Callback 统计 | ✅ | Callback 统计页 |
|
||||||
|
| M9-F04 导出 CSV | ✅ | 导出按钮 |
|
||||||
|
| M9-F05 项目健康度 | ✅ | 健康度看板 |
|
||||||
|
| M9-F06 订阅报表 | ❌ 后续 | 定时邮件推送 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 页面关系图
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart TB
|
||||||
|
subgraph Existing["已有页面(I1-I9)"]
|
||||||
|
Login[登录页]
|
||||||
|
Home[首页]
|
||||||
|
Customers[客户管理]
|
||||||
|
Projects[项目]
|
||||||
|
Contracts[合同管理]
|
||||||
|
Deliveries[交付管理]
|
||||||
|
SNS[许可 SN]
|
||||||
|
Callbacks[Callback 收件箱]
|
||||||
|
Integration[集成配置]
|
||||||
|
Audit[审计日志]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph New["新增页面(Mid)"]
|
||||||
|
Devices[设备列表]
|
||||||
|
DeviceDetail[设备详情]
|
||||||
|
SwapRequest[换机申请]
|
||||||
|
Todos[待办中心]
|
||||||
|
NotifConfig[通知设置]
|
||||||
|
ReportCS[履约对账]
|
||||||
|
ReportCB[Callback 统计]
|
||||||
|
ReportPH[项目健康度]
|
||||||
|
end
|
||||||
|
|
||||||
|
Login --> Home
|
||||||
|
Home --> Customers --> Projects
|
||||||
|
Projects --> Contracts
|
||||||
|
Contracts --> Deliveries
|
||||||
|
Deliveries --> SNS
|
||||||
|
Callbacks --> SNS
|
||||||
|
Integration --> SNS
|
||||||
|
|
||||||
|
SNS --> Devices
|
||||||
|
Devices --> DeviceDetail
|
||||||
|
DeviceDetail --> SwapRequest
|
||||||
|
Callbacks --> Todos
|
||||||
|
SNS --> Todos
|
||||||
|
Devices --> Todos
|
||||||
|
Contracts --> ReportCS
|
||||||
|
SNS --> ReportCS
|
||||||
|
Callbacks --> ReportCB
|
||||||
|
ReportCS --> ReportPH
|
||||||
|
ReportCB --> ReportPH
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. 修订记录
|
||||||
|
|
||||||
|
| 日期 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| 2026-05-25 | 初版:基于 PRD 更新版和 Visual Companion 线框图确认结果撰写 |
|
||||||
@@ -0,0 +1,318 @@
|
|||||||
|
# 代码实现审计报告 — PRD vs 实际实现
|
||||||
|
|
||||||
|
**审计日期:** 2026-05-26
|
||||||
|
**审计范围:**
|
||||||
|
- PRD 文档: `chuangfei-platform-product-modules.md` (M1-M11), `FRONTEND_UI_SPECIFICATION.md`, `chuangfei-platform-bpm-and-roadmap.md`
|
||||||
|
- 后端: `services/delivery-platform-api/` (153 Java 文件) + `services/license-webhook-ingress/`
|
||||||
|
- 前端: `web/delivery-platform-ui/src/` (47 源文件)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 严重缺陷 (Critical)
|
||||||
|
|
||||||
|
### CR-01: 认证系统硬编码用户凭据
|
||||||
|
|
||||||
|
**位置:** `AuthController.java:54-89`
|
||||||
|
**PRD 对照:** M11-F14 要求「用户与账号生命周期:创建、启用/禁用、离职归档」
|
||||||
|
**实际实现:** 4 个用户硬编码在 Java switch 语句中:
|
||||||
|
```java
|
||||||
|
case "admin" → role=SYS_ADMIN
|
||||||
|
case "sales" → role=SALES
|
||||||
|
case "delivery" → role=DELIVERY
|
||||||
|
case "ops" → role=LICENSE_OPS
|
||||||
|
```
|
||||||
|
**缺陷:**
|
||||||
|
- 密码 = 小写用户名 (`pass.equals(user.toLowerCase())`) — admin/admin, sales/sales
|
||||||
|
- 无数据库用户表 — 无法 CRUD、禁用、归档
|
||||||
|
- 注入的 `PasswordEncoder` (BCrypt) 仅用于 `changePassword` 端点,登录完全不使用
|
||||||
|
- `changePassword` 验证旧密码时硬编码 `passwordEncoder.encode("admin")`,对非 admin 用户永远失败
|
||||||
|
|
||||||
|
**影响:** 无法管理用户、密码与用户名相同、安全基线不达标
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### CR-02: JWT Token 存储在 localStorage
|
||||||
|
|
||||||
|
**位置:** `stores/auth.js:4,8,29,37`
|
||||||
|
**PRD 对照:** §16.6 已知局限明确标注「前端 Token 存 localStorage(非 HttpOnly Cookie)」为已知安全缺陷,计划 Mid 迁移
|
||||||
|
**实际实现:** Token 通过 `localStorage.setItem(TOKEN_KEY)` 持久化
|
||||||
|
```javascript
|
||||||
|
// auth.js:29
|
||||||
|
localStorage.setItem(TOKEN_KEY, this.token);
|
||||||
|
axios.defaults.headers.common.Authorization = `Bearer ${this.token}`;
|
||||||
|
```
|
||||||
|
**缺陷:** XSS 攻击可窃取 localStorage 中的 JWT,获得完整 API 访问权限
|
||||||
|
**影响:** 安全 — XSS 窃取 → 权限丢失
|
||||||
|
**缓解:** 当前通过 CSP + 前端无富文本渲染降低风险,但未根本解决
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### CR-03: Error Message 泄露
|
||||||
|
|
||||||
|
**位置:**
|
||||||
|
- `LicenseController.java:25` — `ResponseEntity.internalServerError().body(Map.of("error", e.getMessage()))`
|
||||||
|
- `ContractController.java:136` — `ResponseEntity.status(500).body(Map.of("error", e.getMessage()))`
|
||||||
|
**PRD 对照:** 无明确的错误消息规范,但全局 `ApiExceptionHandler` 已返回泛化消息 "服务器内部错误"
|
||||||
|
**缺陷:** 这两个 Controller 使用 try-catch 捕获 `Exception` 并将异常消息原文 (`e.getMessage()`) 返回给客户端,绕过了全局异常处理器。可能泄露实现细节(表名、SQL、文件路径)。
|
||||||
|
**影响:** 信息安全 — 生产环境可能泄露堆栈或内部路径信息
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### CR-04: resetPassword 和 forceLogout 是空操作
|
||||||
|
|
||||||
|
**位置:** `AuthController.java:131-148`
|
||||||
|
**PRD 对照:** M11-F08「密码重置: 管理员重置密码或邮件/短信重置链接」、M11-F12「管理员强制下线」
|
||||||
|
**实际实现:** 两个端点均仅有参数校验,无实际逻辑
|
||||||
|
```java
|
||||||
|
// resetPassword — 校验参数后直接返回 200 OK,未更新任何密码
|
||||||
|
@PostMapping("/admin/reset-password")
|
||||||
|
public ResponseEntity<Void> resetPassword(@RequestBody Map<String, String> body) {
|
||||||
|
String username = body.get("username");
|
||||||
|
String newPassword = body.get("newPassword");
|
||||||
|
if (username == null || newPassword == null || newPassword.length() < 6) {
|
||||||
|
throw new ResponseStatusException(...);
|
||||||
|
}
|
||||||
|
return ResponseEntity.ok().build(); // 没有实际更新密码!
|
||||||
|
}
|
||||||
|
|
||||||
|
// forceLogout — 同上,无会话失效逻辑
|
||||||
|
@PostMapping("/admin/force-logout")
|
||||||
|
public ResponseEntity<Void> forceLogout(@RequestBody Map<String, String> body) {
|
||||||
|
String username = body.get("username");
|
||||||
|
if (username == null) throw new ResponseStatusException(...);
|
||||||
|
return ResponseEntity.ok().build(); // 没有实际使会话失效!
|
||||||
|
}
|
||||||
|
```
|
||||||
|
**影响:** 功能完全不可用 — 前端调用后显示成功,实际无效果
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 高危缺陷 (High)
|
||||||
|
|
||||||
|
### HI-01: 无用户管理数据库表
|
||||||
|
|
||||||
|
**位置:** `AuthController.java` (全部)
|
||||||
|
**PRD 对照:** M11-F14「用户与账号生命周期:创建、启用/禁用、离职归档」— P0
|
||||||
|
**实际:** 无 `platform_user` 表或类似实体。4 个用户硬编码。Flyway 迁移 V15 `seed_product_roles.sql` 仅涉及角色种子数据。
|
||||||
|
**影响:** M11-F14 完全未实现,无法添加/禁用/管理用户
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### HI-02: 权限模型硬编码
|
||||||
|
|
||||||
|
**位置:** `AuthController.java:91-114`
|
||||||
|
**PRD 对照:** §13.4 要求权限码命名规范(如 `customer:project:rw`、`contract:order:export`)
|
||||||
|
**实际:** 权限字符串在 Java switch 中硬编码,非数据库驱动、不可配置
|
||||||
|
```java
|
||||||
|
case "SALES":
|
||||||
|
permissions.add("customer:*");
|
||||||
|
permissions.add("project:*");
|
||||||
|
permissions.add("contract:*");
|
||||||
|
permissions.add("delivery:read");
|
||||||
|
break;
|
||||||
|
```
|
||||||
|
**影响:** 新增角色或调整权限需改代码重启;权限码 `v-permission` 指令在前端存在但后端无对应校验
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### HI-03: 会话管理后端无状态
|
||||||
|
|
||||||
|
**位置:** `SecurityConfig.java:55-56` — `SessionCreationPolicy.STATELESS`
|
||||||
|
**PRD 对照:** M11-F03「空闲超时自动登出」、M11-F11「并发会话策略」
|
||||||
|
**实际:** JWT 无状态设计意味着后端无法主动使会话失效(无会话存储)。空闲超时仅在前端实现(`idleTimer.js`),后端无法强制登出。`forceLogout` API 为空操作。
|
||||||
|
**影响:** 并发会话、强制下线、空闲超时功能均无法在后端层面实现
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### HI-04: LicenseController 异常处理绕过全局 Handler
|
||||||
|
|
||||||
|
**位置:** `LicenseController.java:19-27`
|
||||||
|
**PRD 对照:** 全局 `ApiExceptionHandler` 已提供统一错误格式 `{status, message}`
|
||||||
|
**实际:** `create` 方法手动 try-catch,返回非标准错误格式
|
||||||
|
```java
|
||||||
|
try {
|
||||||
|
return ResponseEntity.ok(licenseService.create(request));
|
||||||
|
} catch (Exception e) {
|
||||||
|
return ResponseEntity.internalServerError().body(Map.of("error", e.getMessage()));
|
||||||
|
}
|
||||||
|
```
|
||||||
|
返回格式为 `{"error": "..."}` 而非全局标准的 `{"status": 500, "message": "..."}`
|
||||||
|
**影响:** API 响应格式不一致,前端 `apiErrorMessage.js` 可能无法解析
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 中危缺陷 (Medium)
|
||||||
|
|
||||||
|
### ME-01: 合同附件上传无校验
|
||||||
|
|
||||||
|
**位置:** `ContractController.java:118-137`
|
||||||
|
**PRD 对照:** M2-F05「合同附件:上传扫描件/电子签输出(存储与权限受控)」
|
||||||
|
**实际:** 上传端点无文件大小限制、无文件类型白名单、无病毒扫描
|
||||||
|
```java
|
||||||
|
@PostMapping("/{id}/attachments")
|
||||||
|
public ResponseEntity<Map<String, Object>> uploadAttachment(@PathVariable Long id, @RequestParam("file") MultipartFile file) {
|
||||||
|
// 无 file.getSize() 校验
|
||||||
|
// 无 file.getContentType() 白名单
|
||||||
|
// 直接将文件写入本地磁盘
|
||||||
|
```
|
||||||
|
**影响:** 可能被用于上传恶意文件;磁盘可能被大文件填满
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### ME-02: 部分 Controller 返回格式不统一
|
||||||
|
|
||||||
|
**位置:** 多文件
|
||||||
|
**PRD 对照:** 无明确 API 响应规范
|
||||||
|
**实际:** 存在三种返回风格:
|
||||||
|
1. 全局 `ApiExceptionHandler` → `{status, message}` (标准)
|
||||||
|
2. `LicenseController` → `{error, ...}` (非标准)
|
||||||
|
3. `ContractController` → `{error, ...}` (非标准)
|
||||||
|
4. 部分端点直接返回实体对象(非 Map)
|
||||||
|
|
||||||
|
**影响:** 前端 `apiErrorMessage.js` 兼容多种格式但无法覆盖所有情况
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### ME-03: 系统参数仅存于 localStorage
|
||||||
|
|
||||||
|
**位置:** `SystemParamsView.vue:14-33`
|
||||||
|
**PRD 对照:** M11-F20「系统参数」— 期望持久化到后端数据库
|
||||||
|
**实际:**
|
||||||
|
```javascript
|
||||||
|
// 直接保存到浏览器 localStorage
|
||||||
|
localStorage.setItem('systemParams', JSON.stringify(params.value))
|
||||||
|
ElMessage.success('参数已保存(MVP: 存储于浏览器本地)')
|
||||||
|
```
|
||||||
|
**影响:** 参数仅对当前浏览器有效,不同用户/设备参数不一致;清除浏览器数据后丢失
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### ME-04: 后端 changePassword 验证逻辑错误
|
||||||
|
|
||||||
|
**位置:** `AuthController.java:151-168`
|
||||||
|
**PRD 对照:** M11-F07「已登录用户修改本人密码;校验旧密码强度与新密码策略」
|
||||||
|
**实际:**
|
||||||
|
```java
|
||||||
|
String currentPasswordHash = passwordEncoder.encode("admin"); // 始终比较 admin 的密码!
|
||||||
|
if (!passwordEncoder.matches(oldPassword, currentPasswordHash)) {
|
||||||
|
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "旧密码错误");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
`passwordEncoder.encode("admin")` 硬编码为 "admin",导致:
|
||||||
|
- admin 用户可以改密(旧密码 = admin 通过)
|
||||||
|
- 其他用户(sales/delivery/ops)永远无法通过旧密码验证
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### ME-05: SN 批量导入缺少事务回滚
|
||||||
|
|
||||||
|
**位置:** `LicenseSnService.java:134-155`
|
||||||
|
**PRD 对照:** M4-F01/F07 批量操作
|
||||||
|
**实际:** 逐条插入,失败项跳过继续,但方法未标注 `@Transactional`
|
||||||
|
```java
|
||||||
|
public Map<String, Object> batchImport(List<LicenseSnCreateRequest> requests) {
|
||||||
|
// for 循环逐条 insert,无事务保护
|
||||||
|
// 部分成功 = 部分写入无法回滚
|
||||||
|
```
|
||||||
|
**影响:** 批量导入 100 条中第 50 条失败时,前 49 条已写入无法撤销
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. PRD 与实现偏离 (Misalignment)
|
||||||
|
|
||||||
|
### MA-01: M11 角色模型偏离产品定义
|
||||||
|
|
||||||
|
| 产品定义角色 | 实际实现 | 状态 |
|
||||||
|
|------------|---------|------|
|
||||||
|
| SYS_ADMIN | ✅ | 产品定义包含 |
|
||||||
|
| SALES | ✅ I10 新增 | 产品定义包含 |
|
||||||
|
| DELIVERY | ✅ I10 新增 | 产品定义包含 |
|
||||||
|
| LICENSE_OPS | ✅ I10 新增 | 产品定义包含 |
|
||||||
|
| ORDER_SUPPORT | ○ | 产品定义但未实现 |
|
||||||
|
| FINANCE_VIEW | ○ | 产品定义但未实现 |
|
||||||
|
| COMPLIANCE | ○ | 产品定义但未实现 |
|
||||||
|
| EXEC_VIEW | ○ | 产品定义但未实现 |
|
||||||
|
| SECURITY_ADMIN | ○ | 产品定义但未实现 |
|
||||||
|
| DEVELOPER | ✅ (应废弃) | MVP 遗留非标角色 |
|
||||||
|
| OPS | ✅ (应废弃) | MVP 遗留非标角色 |
|
||||||
|
|
||||||
|
前端路由角色标记(`router/index.js`)仍广泛使用 `SYS_ADMIN` 和 `SALES`,但 `DEVELOPER` 已从路由角色列表中移除,而 `LICENSE_OPS` 和 `DELIVERY` 已加入。
|
||||||
|
|
||||||
|
### MA-02: M1-F07 客户冻结后端就绪前端缺 UI
|
||||||
|
|
||||||
|
**位置:** 后端 `CustomerController.java` 有 `PATCH /{id}/freeze` 和 `/unfreeze` 端点,前端 `CustomersView.vue` 无冻结操作入口
|
||||||
|
|
||||||
|
### MA-03: M11-F07 密码修改已实现但产品文档未标记
|
||||||
|
|
||||||
|
`ProfileView.vue` 已包含完整的改密弹窗,`AuthController` 有对应端点,但文档标注为 ○。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 代码质量问题 (Code Quality)
|
||||||
|
|
||||||
|
### CQ-01: SecurityConfig 重复 import (已修复)
|
||||||
|
|
||||||
|
**位置:** `SecurityConfig.java:5,20` — 两次 `import org.springframework.context.annotation.Bean`
|
||||||
|
**状态:** ✅ 本次审计已修复
|
||||||
|
|
||||||
|
### CQ-02: 个别 Controller 使用 `@PreAuthorize` 而非 JWT Filter 角色
|
||||||
|
|
||||||
|
**位置:** `LicenseController.java:20,30,40` — 使用 `@PreAuthorize("hasRole('LICENSE_OPS') or hasRole('ADMIN')")`
|
||||||
|
**问题:** 与 JWT Filter 的双重验证增加了 role 前缀处理的复杂性(JwtAuthFilter 添加 `ROLE_` 前缀,`@PreAuthorize` 期望 `ROLE_` 格式)
|
||||||
|
|
||||||
|
### CQ-03: 前端 API 层中个别函数含 query 参数拼接
|
||||||
|
|
||||||
|
**位置:** `platform.js:460`
|
||||||
|
```javascript
|
||||||
|
export function createSkuMapping(contractLineId, body) {
|
||||||
|
return axios.post(`/api/v1/integration/sku-mappings?contractLineId=${contractLineId}`, body);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
**影响:** 非紧急,但建议统一使用 `{ params: { contractLineId } }` 方式
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 未被 PRD 覆盖但代码已实现的模块(超前实现)
|
||||||
|
|
||||||
|
| 模块 | 功能 | 建议 |
|
||||||
|
|------|------|------|
|
||||||
|
| M7 设备管理 | 登记/列表/详情/绑定/换机申请 | 核对 PRD 需求后决定是否纳入正式范围 |
|
||||||
|
| M8 通知待办 | 待办中心 + 通知通道配置 UI | 需补充实际发送逻辑 |
|
||||||
|
| M9 报表对账 | 4 个报表页面均已上线 | 补充导出按钮和推送逻辑 |
|
||||||
|
| M6 ID/特征/SKU 映射 | 前后端均已实现 | 更新产品文档状态 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 汇总统计
|
||||||
|
|
||||||
|
| 严重级别 | 数量 | 编号 |
|
||||||
|
|---------|------|------|
|
||||||
|
| 🔴 Critical | 4 | CR-01~CR-04 |
|
||||||
|
| 🟠 High | 4 | HI-01~HI-04 |
|
||||||
|
| 🟡 Medium | 5 | ME-01~ME-05 |
|
||||||
|
| 🔵 Misalignment | 3 | MA-01~MA-03 |
|
||||||
|
| ⚪ Code Quality | 3 | CQ-01~CQ-03 |
|
||||||
|
| **合计** | **19** | |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. 修复建议优先级
|
||||||
|
|
||||||
|
### P0 — 立即修复(安全基线)
|
||||||
|
|
||||||
|
1. **CR-01** (硬编码用户): 创建 `platform_user` 表 + Flyway 迁移 + AuthController 改用数据库查询 + BCrypt 密码校验
|
||||||
|
2. **CR-04** (空操作端点): `resetPassword` 和 `forceLogout` 补充实际逻辑 — resetPassword 更新用户密码,forceLogout 增加黑名单机制
|
||||||
|
3. **CR-03** (错误泄露): `LicenseController` 和 `ContractController` 移除 try-catch,让全局 `ApiExceptionHandler` 接管
|
||||||
|
4. **ME-04** (改密逻辑错误): `changePassword` 从 SecurityContext 获取当前用户名,从数据库查询对应用户的密码哈希
|
||||||
|
|
||||||
|
### P1 — 短期修复
|
||||||
|
|
||||||
|
5. **HI-01** (用户管理): 在 P0 用户表基础上实现用户 CRUD API + 前端管理页面
|
||||||
|
6. **ME-01** (附件校验): ContractController upload 增加 `@Size` 注解和文件类型白名单
|
||||||
|
7. **ME-05** (事务缺失): `batchImport` 方法添加 `@Transactional`
|
||||||
|
|
||||||
|
### P2 — 中长期
|
||||||
|
|
||||||
|
8. **CR-02** (Token 存储): 迁移至 HttpOnly Cookie(需后端配合返回 Set-Cookie header)
|
||||||
|
9. **HI-02** (权限模型): 权限码持久化到数据库,实现可配置 RBAC
|
||||||
|
10. **HI-03** (会话管理): 引入 Token 黑名单/白名单机制或 Redis 会话存储
|
||||||
@@ -0,0 +1,469 @@
|
|||||||
|
# 原型实现复盘 — 缺漏功能 & 页面清单
|
||||||
|
|
||||||
|
**生成日期:** 2026-05-26
|
||||||
|
**参考来源:**
|
||||||
|
- `docs/chuangfei-platform-product-modules.md` (§2~§12 功能点表 + §16 原型说明)
|
||||||
|
- `docs/engineering/FRONTEND_UI_SPECIFICATION.md` (前端 UI 规格)
|
||||||
|
- `services/delivery-platform-api/` 全部 Controller 端点
|
||||||
|
- `web/delivery-platform-ui/src/router/index.js` + `src/views/` (38 视图)
|
||||||
|
- `docs/engineering/iterations/I9_IMPLEMENTATION_REVIEW.md`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 总览
|
||||||
|
|
||||||
|
| 模块 | 功能点总数 | ✅ 已实现 | ◐ 部分实现 | ○ 未实现 | — 依赖前置 |
|
||||||
|
|------|-----------|-----------|------------|----------|-----------|
|
||||||
|
| **M1** 客户与项目 | 9 | 4 | 1 | 4 | 0 |
|
||||||
|
| **M2** 合同与履约行 | 9 | 5 | 0 | 4 | 0 |
|
||||||
|
| **M3** 交付管理 | 8 | 5 | 1 | 2 | 0 |
|
||||||
|
| **M4** 授权与许可运营 | 11 | 3 | 2 | 5 | 1 |
|
||||||
|
| **M5** Callback 运营 | 10 | 7 | 1 | 2 | 0 |
|
||||||
|
| **M6** 授权集成与配置 | 9 | 3 | 0 | 6 | 0 |
|
||||||
|
| **M7** 设备与终端 | 6 | 0 | 4 | 2 | 0 |
|
||||||
|
| **M8** 通知与待办 | 5 | 0 | 2 | 3 | 0 |
|
||||||
|
| **M9** 报表与对账 | 6 | 0 | 4 | 2 | 0 |
|
||||||
|
| **M10** 审计与合规 | 4 | 1 | 1 | 2 | 0 |
|
||||||
|
| **M11** 身份与平台管理 | 21 | 6 | 3 | 12 | 0 |
|
||||||
|
| **合计** | **98** | **34 (35%)** | **19 (19%)** | **44 (45%)** | **1 (1%)** |
|
||||||
|
|
||||||
|
> **说明**: 实际代码实现程度高于产品模块文档中的状态标记。以下按模块逐个详细盘点。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## M1 — 客户与项目中心
|
||||||
|
|
||||||
|
**当前页面**: `/customers` `CustomersView.vue`, `/customers/:id` `CustomerDetailView.vue`, `/projects` `ProjectsView.vue`
|
||||||
|
|
||||||
|
### 功能点复盘
|
||||||
|
|
||||||
|
| ID | 功能点 | 实现状态 | 详情 |
|
||||||
|
|----|--------|---------|------|
|
||||||
|
| M1-F01 | 客户档案创建/编辑 | ◐ | 仅 name + credit_code,缺行业/地址/开票信息字段 |
|
||||||
|
| M1-F02 | 客户列表与检索 | ✅ | 关键词搜索 + 分页 |
|
||||||
|
| M1-F03 | 客户详情聚合视图 | ○ | **未实现** — 缺少关联项目数/在履约合同/在途 SN 统计摘要。后端 `GET /{id}/summary` 已存在 |
|
||||||
|
| M1-F04 | 项目创建/编辑 | ◐ | 仅 name + customer_id + phase,缺计划起止日期、项目经理 |
|
||||||
|
| M1-F05 | 项目列表与筛选 | ✅ | 按客户、阶段筛选 |
|
||||||
|
| M1-F06 | 项目干系人 | ◐ | 后端有 `/stakeholders` CRUD 端点,但前端 **无独立 UI 入口**(仅 API 可用) |
|
||||||
|
| M1-F07 | 客户/项目冻结与解冻 | ◐ | 后端 `PATCH /{id}/freeze` `/unfreeze` 已实现,但前端 **缺 UI 操作** |
|
||||||
|
| M1-F08 | 客户合并与去重 | ○ | 未开始 |
|
||||||
|
| M1-F09 | 外部 CRM 主数据同步 | ○ | 未开始 |
|
||||||
|
|
||||||
|
### 前端页面缺口
|
||||||
|
|
||||||
|
| 缺漏 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| 客户详情聚合视图 | 后端 `/customers/{id}/summary` 已就绪,缺前端展示页 |
|
||||||
|
| 项目干系人管理 UI | 后端 CRUD 就绪,前端口/弹窗未实现 |
|
||||||
|
| 冻结/解冻操作 UI | 后端端点就绪,前端缺按钮和确认弹窗 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## M2 — 合同与履约行
|
||||||
|
|
||||||
|
**当前页面**: `/contracts` `ContractsView.vue`, `/contracts/new` `ContractWizardView.vue`, `/contracts/:id` `ContractDetailView.vue`
|
||||||
|
|
||||||
|
### 功能点复盘
|
||||||
|
|
||||||
|
| ID | 功能点 | 实现状态 | 详情 |
|
||||||
|
|----|--------|---------|------|
|
||||||
|
| M2-F01 | 合同登记与编辑 | ✅ | 完整 CRUD + 客户/项目关联 |
|
||||||
|
| M2-F02 | 合同状态机 | ✅ | DRAFT→PENDING_EFFECTIVE→EFFECTIVE→CHANGING→TERMINATED |
|
||||||
|
| M2-F03 | 合同标的摘要 | ✅ | 行项汇总展示 |
|
||||||
|
| M2-F04 | 合同行项 | ✅ | 多行 CRUD,含数量/单位 |
|
||||||
|
| M2-F05 | 合同附件 | ◐ | 后端有 `POST /{id}/attachments` 端点,前端 **缺上传 UI** |
|
||||||
|
| M2-F06 | 合同与订单关联 | ○ | 未开始 |
|
||||||
|
| M2-F07 | 合同变更与版本 | ◐ | 后端有 `POST /{id}/changes` + `/complete` 端点,前端 **缺变更 UI** |
|
||||||
|
| M2-F08 | 合同行↔SKU 映射 | ○ | 依赖 M6 联动 |
|
||||||
|
| M2-F09 | 合同到期与续费提醒 | ○ | 依赖 M8 联动 |
|
||||||
|
|
||||||
|
### 前端页面缺口
|
||||||
|
|
||||||
|
| 缺漏 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| 附件上传弹窗 | 后端端点已存在,详情页缺附件区块 |
|
||||||
|
| 变更单发起/完成 UI | 后端 `changes` 端点已实现,合同详情缺变更操作入口 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## M3 — 交付管理
|
||||||
|
|
||||||
|
**当前页面**: `/deliveries` `DeliveriesView.vue`, `/deliveries/new` `DeliveryBatchWizardView.vue`, `/deliveries/:id` `DeliveryBatchDetailView.vue`
|
||||||
|
|
||||||
|
### 功能点复盘
|
||||||
|
|
||||||
|
| ID | 功能点 | 实现状态 | 详情 |
|
||||||
|
|----|--------|---------|------|
|
||||||
|
| M3-F01 | 交付批次创建 | ✅ | |
|
||||||
|
| M3-F02 | 交付清单 | ✅ | 行项管理 |
|
||||||
|
| M3-F03 | 交付与合同行关联 | ✅ | |
|
||||||
|
| M3-F04 | 交付状态 | ✅ | PENDING→DELIVERED→CANCELLED |
|
||||||
|
| M3-F05 | 交付完成确认 | ✅ | |
|
||||||
|
| M3-F06 | 现场环境信息 | ○ | 未实现 |
|
||||||
|
| M3-F07 | SN 发放门禁 | ○ | 后端 system params `deliveryGateEnabled` 已定义,但门禁逻辑未实际执行 |
|
||||||
|
| M3-F08 | 交付模板 | ○ | 未开始 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## M4 — 授权与许可运营
|
||||||
|
|
||||||
|
**当前页面**: `/licenses/sn` `LicenseSnListView.vue`, `/licenses/sn/new` `LicenseSnWizardView.vue`, `/licenses/sn/:id` `LicenseSnDetailView.vue`
|
||||||
|
|
||||||
|
### 功能点复盘
|
||||||
|
|
||||||
|
| ID | 功能点 | 实现状态 | 详情 |
|
||||||
|
|----|--------|---------|------|
|
||||||
|
| M4-F01 | SN 手工录入/导入 | ◐ | 手工录入✅,批量导入 **缺前端 UI**(后端 `POST /batch-import` 已存在) |
|
||||||
|
| M4-F02 | SN 与合同/项目/客户绑定 | ✅ | |
|
||||||
|
| M4-F03 | SN 生命周期状态 | ✅ | REGISTERED→ISSUED→ACTIVATED→SUSPENDED→REVOKED |
|
||||||
|
| M4-F04 | SN 详情页 | ✅ | 绑定/状态/备注 |
|
||||||
|
| M4-F05 | 激活结果回写 | ◐ | 支持手工状态更新,缺原因码分类 |
|
||||||
|
| M4-F06 | 比特控制台状态摘要 | ○ | 依赖比特对接,未实现 |
|
||||||
|
| M4-F07 | 批量 SN 操作 | ◐ | 后端 `POST /batch-import` 已存在,前端 **缺批量操作 UI** |
|
||||||
|
| M4-F08 | 授权需求单 | ○ | 未开始 |
|
||||||
|
| M4-F09 | 试用/正式/续期标签 | ○ | 未开始 |
|
||||||
|
| M4-F10 | SN 与设备关联视图 | — | 依赖 M7 |
|
||||||
|
| M4-F11 | 授权策略生效视图 | ○ | 依赖 M6 联动 |
|
||||||
|
|
||||||
|
### 前端页面缺口
|
||||||
|
|
||||||
|
| 缺漏 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| 批量导入 SN UI | 后端 `POST /license-sns/batch-import` 已就绪,前端缺导入页面/弹窗 |
|
||||||
|
| 批量 SN 操作 UI | 列表页缺批量选择 + 批量状态变更 |
|
||||||
|
| 原因码分类选择 | 详情页状态变更时缺原因码下拉 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## M5 — Callback 运营
|
||||||
|
|
||||||
|
**当前页面**: `/callbacks` `CallbackInboxView.vue`, `/callbacks/:id` `CallbackInboxDetailView.vue`
|
||||||
|
|
||||||
|
### 功能点复盘
|
||||||
|
|
||||||
|
| ID | 功能点 | 实现状态 | 详情 |
|
||||||
|
|----|--------|---------|------|
|
||||||
|
| M5-F01 | 事件收件箱列表 | ✅ | 多维度筛选 |
|
||||||
|
| M5-F02 | 事件详情 | ✅ | payload 脱敏预览 |
|
||||||
|
| M5-F03 | 处理状态 | ✅ | PENDING→PROCESSED/FAILED/IGNORED |
|
||||||
|
| M5-F04 | 关联解析失败兜底 | ✅ | 人工挂接 SN/项目/合同 |
|
||||||
|
| M5-F05 | 事件类型字典 | ✅ | |
|
||||||
|
| M5-F06 | 失败原因标注 | ○ | 未实现 |
|
||||||
|
| M5-F07 | 批量重处理/重试 | ◐ | 单条 DEAD 重放✅ (I8),**批量未做** |
|
||||||
|
| M5-F08 | 死信与积压监控视图 | ○ | 未实现 |
|
||||||
|
| M5-F09 | 事件驱动待办 | — | 依赖 M8 |
|
||||||
|
| M5-F10 | 模拟投递 | ◐ | `POST /simulate` 端点已存在,前端 **缺测试工具 UI** |
|
||||||
|
|
||||||
|
### 前端页面缺口
|
||||||
|
|
||||||
|
| 缺漏 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| 模拟投递测试工具 | 后端 `POST /callback-inbox/simulate` 就绪,前端缺页面/弹窗 |
|
||||||
|
| 批量重处理 UI | 列表页缺批量选择 + 批量重入队 |
|
||||||
|
| 死信积压监控 | 独立视图或仪表盘区块 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## M6 — 授权集成与配置
|
||||||
|
|
||||||
|
**当前页面**: `/integration/environments`, `/integration/product-lines`, `/integration/id-mappings`, `/integration/sku-mappings`, `/integration/feature-mappings`, `/integration/json-templates`
|
||||||
|
|
||||||
|
### 功能点复盘
|
||||||
|
|
||||||
|
| ID | 功能点 | 实现状态 | 详情 |
|
||||||
|
|----|--------|---------|------|
|
||||||
|
| M6-F01 | 产品线定义 | ✅ | 列表已实现 |
|
||||||
|
| M6-F02 | 环境维度 | ✅ | dev/prod seed 数据 |
|
||||||
|
| M6-F03 | 比特 ID 映射 | ◐ | 前端 `IntegrationIdMappingView` 已存在,后端 CRUD 就绪,但 **产品模块标记为○**,需确认映射字段对齐 |
|
||||||
|
| M6-F04 | 特征映射 | ◐ | 前端 `IntegrationFeatureMappingView` 已存在,后端就绪 |
|
||||||
|
| M6-F05 | JSON 模板管理 | ◐ | 前端 `IntegrationJsonTemplateView` 已存在,后端 CRUD 就绪,但 **Schema 校验未关联 UI** |
|
||||||
|
| M6-F06 | 配置发布记录 | ○ | 未实现 |
|
||||||
|
| M6-F07 | 控制台链接与说明 | ○ | 未实现 |
|
||||||
|
| M6-F08 | SDK 版本矩阵 | ○ | 未开始 |
|
||||||
|
| M6-F09 | 变更影响分析 | ○ | 未开始 |
|
||||||
|
|
||||||
|
> **说明**: M6 实际代码实现远超产品文档标记。ID 映射/特征映射/JSON 模板 的前后端均已实现但未标记。需核对字段完整性。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## M7 — 设备与终端治理
|
||||||
|
|
||||||
|
**当前页面**: `/devices` `DeviceListView.vue`, `/devices/:id` `DeviceDetailView.vue`
|
||||||
|
|
||||||
|
### 功能点复盘
|
||||||
|
|
||||||
|
| ID | 功能点 | 实现状态 | 详情 |
|
||||||
|
|----|--------|---------|------|
|
||||||
|
| M7-F01 | 设备登记 | ◐ | 前端 `DeviceListView` 已实现列表+登记弹窗,后端 CRUD 就绪,但字段覆盖需确认 |
|
||||||
|
| M7-F02 | 设备与 SN 绑定历史 | ◐ | `DeviceDetailView` 已实现,绑定时间线需核对完整性 |
|
||||||
|
| M7-F03 | 换机申请与处理 | ◐ | 后端 `POST /{id}/swap-request` 已存在,审批流未实现 |
|
||||||
|
| M7-F04 | 设备列表与检索 | ✅ | 已实现 |
|
||||||
|
| M7-F05 | 与 Callback 设备事件联动 | ○ | 未实现 |
|
||||||
|
| M7-F06 | 终端数/并发策略展示 | ○ | 未开始 |
|
||||||
|
|
||||||
|
> **说明**: M7 实际实现远超产品文档标记的"全 ○"。设备登记、列表、详情、绑定历史已上线。待确认字段和审批流完整性。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## M8 — 通知与待办
|
||||||
|
|
||||||
|
**当前页面**: `/todos` `TodoCenterView.vue`, `/notifications/settings` `NotificationSettingsView.vue`
|
||||||
|
|
||||||
|
### 功能点复盘
|
||||||
|
|
||||||
|
| ID | 功能点 | 实现状态 | 详情 |
|
||||||
|
|----|--------|---------|------|
|
||||||
|
| M8-F01 | 站内待办列表 | ◐ | `TodoCenterView` 已实现,支持类型筛选/优先级/状态,但 **自动化生成待办** 未接入 |
|
||||||
|
| M8-F02 | 待办认领与完成 | ◐ | 状态流转已实现,但标注/备注功能待补 |
|
||||||
|
| M8-F03 | 邮件/企微通道 | ◐ | `NotificationSettingsView` 已实现通道配置 UI + 事件订阅表,但 **实际发送逻辑** 未接入 |
|
||||||
|
| M8-F04 | 通知模板 | ○ | 未实现 |
|
||||||
|
| M8-F05 | 静默规则 | ○ | 未开始 |
|
||||||
|
|
||||||
|
> **说明**: M8 实际实现远超产品文档标记。待办中心+通知设置均已上线。核心缺口是自动化待办生成和通知发送通道的实际对接。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## M9 — 报表与对账
|
||||||
|
|
||||||
|
**当前页面**: `/reports/contract-sn` `ContractSnReportView.vue`, `/reports/callback-stats` `CallbackStatsView.vue`, `/reports/project-health` `ProjectHealthView.vue`, `/reports/subscriptions` `SubscriptionReportView.vue`
|
||||||
|
|
||||||
|
### 功能点复盘
|
||||||
|
|
||||||
|
| ID | 功能点 | 实现状态 | 详情 |
|
||||||
|
|----|--------|---------|------|
|
||||||
|
| M9-F01 | 合同标的 vs 已发 SN 视图 | ◐ | `ContractSnReportView` 已实现,需核对数据准确性和维度 |
|
||||||
|
| M9-F02 | 已发 vs 已激活视图 | ○ | 未专门实现(可合并到 F01) |
|
||||||
|
| M9-F03 | Callback 统计 | ◐ | `CallbackStatsView` 已实现 |
|
||||||
|
| M9-F04 | 导出 CSV/Excel | ◐ | 后端 `GET /reports/export` 已存在,前端 **缺导出按钮 UI** |
|
||||||
|
| M9-F05 | 项目健康度看板 | ◐ | `ProjectHealthView` 已实现,红黄绿规则可配置性待确认 |
|
||||||
|
| M9-F06 | 订阅报表 | ◐ | `SubscriptionReportView` 已实现,后端推送逻辑待确认 |
|
||||||
|
|
||||||
|
> **说明**: M9 的实际实现远超文档标记,4 个报表页面均已上线。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## M10 — 审计与合规
|
||||||
|
|
||||||
|
**当前页面**: `/audit` `AuditSearchView.vue`, `/audit/retention` `AuditRetentionView.vue`
|
||||||
|
|
||||||
|
### 功能点复盘
|
||||||
|
|
||||||
|
| ID | 功能点 | 实现状态 | 详情 |
|
||||||
|
|----|--------|---------|------|
|
||||||
|
| M10-F01 | 关键字段变更日志 | ✅ | |
|
||||||
|
| M10-F02 | 审计检索 | ◐ | `AuditSearchView` 已实现,筛选维度待确认是否齐全 |
|
||||||
|
| M10-F03 | 导出审计包 | ○ | 未实现 |
|
||||||
|
| M10-F04 | 留存策略配置 | ◐ | `AuditRetentionView` 已实现,配置生效待确认 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## M11 — 身份、访问与平台管理
|
||||||
|
|
||||||
|
**当前页面**: `/login` `LoginView.vue`, `/profile` `ProfileView.vue`, `/admin/params` `SystemParamsView.vue`
|
||||||
|
**其他**: `/403`, `/404`
|
||||||
|
|
||||||
|
### 12.1 账户登录、登出与会话
|
||||||
|
|
||||||
|
| ID | 功能点 | 实现状态 | 详情 |
|
||||||
|
|----|--------|---------|------|
|
||||||
|
| M11-F01 | 登录页 | ✅ | JWT Bearer |
|
||||||
|
| M11-F02 | 登出 | ✅ | |
|
||||||
|
| M11-F03 | 登录态保持与超时 | ○ | **未实现** — 空闲超时自动登出缺失。`sessionTimeoutMinutes` 系统参数已定义但前端路由守卫未接入 |
|
||||||
|
| M11-F04 | 未登录访问拦截 | ✅ | 路由 `requiresAuth` guard |
|
||||||
|
| M11-F05 | 登录失败锁定 | ○ | **未实现** — 连续失败锁定/验证码缺失 |
|
||||||
|
| M11-F06 | 登录/登出审计 | ✅ | |
|
||||||
|
| M11-F07 | 密码修改 | ◐ | 后端 `POST /auth/change-password` 已存在,前端 Profile 页 **缺改密 UI** |
|
||||||
|
| M11-F08 | 密码重置 | ◐ | 后端 `POST /auth/admin/reset-password` 已存在,前端 **缺管理员重置 UI** |
|
||||||
|
| M11-F09 | 企业 SSO / OIDC | ○ | 未开始 |
|
||||||
|
| M11-F10 | 双因素认证 MFA | ○ | 未开始 |
|
||||||
|
| M11-F11 | 并发会话策略 | ○ | 后端未实现 |
|
||||||
|
| M11-F12 | 管理员强制下线 | ◐ | 后端 `POST /auth/admin/force-logout` 已存在,前端 **缺管理 UI** |
|
||||||
|
| M11-F13 | 服务时间窗提示 | ○ | 未开始 |
|
||||||
|
|
||||||
|
### 12.2 用户、角色与权限配置
|
||||||
|
|
||||||
|
| ID | 功能点 | 实现状态 | 详情 |
|
||||||
|
|----|--------|---------|------|
|
||||||
|
| M11-F14 | 用户与账号生命周期 | ◐ | 种子用户已创建,缺完整的用户管理页面(CRUD+启用/禁用) |
|
||||||
|
| M11-F15 | 角色定义与分配 | ◐ | 三角色已落地,产品定义 10+ 角色待补齐 |
|
||||||
|
| M11-F16 | 功能权限 RBAC | ◐ | 路由级 RBAC ✅,按钮级权限码 `v-permission` 正在落地未全覆盖 |
|
||||||
|
| M11-F17 | 数据范围 | ○ | 未开始 |
|
||||||
|
| M11-F18 | 数据属主/团队 | ○ | 未开始 |
|
||||||
|
| M11-F19 | 业务字典 | ✅ | |
|
||||||
|
| M11-F20 | 系统参数 | ◐ | `SystemParamsView` 已实现 + 后端 `system_params` 表,参数种类待扩充 |
|
||||||
|
| M11-F21 | 管理员敏感操作留痕 | ○ | 未实现 |
|
||||||
|
|
||||||
|
### 前端页面缺口
|
||||||
|
|
||||||
|
| 缺漏 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| 用户管理页面 | 用户 CRUD + 启用/禁用/角色分配页面缺失 |
|
||||||
|
| 角色管理页面 | 角色定义 + 权限码分配页面缺失 |
|
||||||
|
| 改密 UI | Profile 页缺修改密码表单 |
|
||||||
|
| 管理员重置密码 UI | 后端端点已存在,缺对应管理页面/弹窗 |
|
||||||
|
| 强制下线管理 UI | 后端端点已存在,缺在线会话管理页面 |
|
||||||
|
| 登录态超时拦截 | 系统参数已定义但前端路由守卫未接入空闲检测 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 前端页面完整盘点
|
||||||
|
|
||||||
|
### 已实现页面(38 视图)
|
||||||
|
|
||||||
|
| 路由 | 视图 | 模块 | 状态 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| `/login` | `LoginView.vue` | M11 | ✅ |
|
||||||
|
| `/` | `HomeView.vue` | — | ✅ |
|
||||||
|
| `/customers` | `CustomersView.vue` | M1 | ✅ |
|
||||||
|
| `/customers/:id` | `CustomerDetailView.vue` | M1 | ✅ |
|
||||||
|
| `/projects` | `ProjectsView.vue` | M1 | ✅ |
|
||||||
|
| `/contracts` | `ContractsView.vue` | M2 | ✅ |
|
||||||
|
| `/contracts/new` | `ContractWizardView.vue` | M2 | ✅ |
|
||||||
|
| `/contracts/:id` | `ContractDetailView.vue` | M2 | ✅ |
|
||||||
|
| `/deliveries` | `DeliveriesView.vue` | M3 | ✅ |
|
||||||
|
| `/deliveries/new` | `DeliveryBatchWizardView.vue` | M3 | ✅ |
|
||||||
|
| `/deliveries/:id` | `DeliveryBatchDetailView.vue` | M3 | ✅ |
|
||||||
|
| `/licenses/sn` | `LicenseSnListView.vue` | M4 | ✅ |
|
||||||
|
| `/licenses/sn/new` | `LicenseSnWizardView.vue` | M4 | ✅ |
|
||||||
|
| `/licenses/sn/:id` | `LicenseSnDetailView.vue` | M4 | ✅ |
|
||||||
|
| `/licenses` | `LicenseList.vue` | V6 | ✅ |
|
||||||
|
| `/callbacks` | `CallbackInboxView.vue` | M5 | ✅ |
|
||||||
|
| `/callbacks/:id` | `CallbackInboxDetailView.vue` | M5 | ✅ |
|
||||||
|
| `/integration/environments` | `IntegrationEnvironmentsView.vue` | M6 | ✅ |
|
||||||
|
| `/integration/product-lines` | `IntegrationProductLinesView.vue` | M6 | ✅ |
|
||||||
|
| `/integration/id-mappings` | `IntegrationIdMappingView.vue` | M6 | ✅ |
|
||||||
|
| `/integration/sku-mappings` | `IntegrationSkuMappingView.vue` | M6 | ✅ |
|
||||||
|
| `/integration/feature-mappings` | `IntegrationFeatureMappingView.vue` | M6 | ✅ |
|
||||||
|
| `/integration/json-templates` | `IntegrationJsonTemplateView.vue` | M6 | ✅ |
|
||||||
|
| `/devices` | `DeviceListView.vue` | M7 | ✅ |
|
||||||
|
| `/devices/:id` | `DeviceDetailView.vue` | M7 | ✅ |
|
||||||
|
| `/todos` | `TodoCenterView.vue` | M8 | ✅ |
|
||||||
|
| `/notifications/settings` | `NotificationSettingsView.vue` | M8 | ✅ |
|
||||||
|
| `/reports/contract-sn` | `ContractSnReportView.vue` | M9 | ✅ |
|
||||||
|
| `/reports/callback-stats` | `CallbackStatsView.vue` | M9 | ✅ |
|
||||||
|
| `/reports/project-health` | `ProjectHealthView.vue` | M9 | ✅ |
|
||||||
|
| `/reports/subscriptions` | `SubscriptionReportView.vue` | M9 | ✅ |
|
||||||
|
| `/audit` | `AuditSearchView.vue` | M10 | ✅ |
|
||||||
|
| `/audit/retention` | `AuditRetentionView.vue` | M10 | ✅ |
|
||||||
|
| `/admin/params` | `SystemParamsView.vue` | M11 | ✅ |
|
||||||
|
| `/profile` | `ProfileView.vue` | M11 | ✅ |
|
||||||
|
| `/license-compare` | `LayoutCompareView.vue` | — | ✅ |
|
||||||
|
| `/403` | `ForbiddenView.vue` | M11 | ✅ |
|
||||||
|
| `/*` | `NotFoundView.vue` | M11 | ✅ |
|
||||||
|
|
||||||
|
### 结论:前端页面完整性
|
||||||
|
|
||||||
|
- **原型 UI 规格(§16.2)定义页面**: 全部 13 个页面已实现 ✅
|
||||||
|
- **超出原型范围的已实现页面(I10 及以上提前完成)**: 25 个额外页面(设备/待办/通知/报表/审计/集成配置等)
|
||||||
|
- **仍有缺口的功能区域**(见各模块复盘)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 后端 API 缺口汇总
|
||||||
|
|
||||||
|
以下端点已在产品模块文档中定义但尚未实现:
|
||||||
|
|
||||||
|
| 模块 | 缺失端点 | 说明 |
|
||||||
|
|------|---------|------|
|
||||||
|
| M1 | 客户详情聚合统计 | `/customers/{id}/summary` 已存在,确认数据完整性 |
|
||||||
|
| M1 | 客户合并 | 无端点 |
|
||||||
|
| M2 | 订单关联 | 无端点 |
|
||||||
|
| M4 | 批量 SN 导入 | `POST /batch-import` 已存在,前端缺 UI |
|
||||||
|
| M5 | 批量重处理 | 无端点 |
|
||||||
|
| M6 | 配置发布记录 | 无端点 |
|
||||||
|
| M6 | 版本矩阵 | 无端点 |
|
||||||
|
| M7 | 换机审批流程 | 无审批端点 |
|
||||||
|
| M8 | 通知通道实际发送 | 配置已就绪,发送接口未接入 |
|
||||||
|
| M9 | 报表导出 | `GET /reports/export` 已存在,前端导出按钮缺失 |
|
||||||
|
| M10 | 审计导出包 | 无端点 |
|
||||||
|
| M11 | 用户管理 CRUD | 无专用端点(当前硬编码种子用户) |
|
||||||
|
| M11 | 角色管理 CRUD | 无专用端点 |
|
||||||
|
| M11 | 在线会话管理 | `force-logout` 已存在,会话列表端点缺失 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 按优先级分类的缺失功能清单
|
||||||
|
|
||||||
|
### P0 级别(MVP 应含但未完成)
|
||||||
|
|
||||||
|
| 功能 | 模块 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| 客户详情聚合视图 (M1-F03) | M1 | 后端 `/summary` 已就绪,前端未开发 |
|
||||||
|
| 登录态空闲超时 (M11-F03) | M11 | 安全基线必备,`sessionTimeoutMinutes` 参数已定义但未接入 |
|
||||||
|
| 登录失败锁定 (M11-F05) | M11 | 安全基线必备 |
|
||||||
|
| 密码修改 (M11-F07) | M11 | 后端端点已存在,Profile 页缺 UI |
|
||||||
|
| 用户管理页面 (M11-F14) | M11 | 用户 CRUD + 启用/禁用页面缺失 |
|
||||||
|
|
||||||
|
### P1 级别(增强运营效率)
|
||||||
|
|
||||||
|
| 功能 | 模块 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| 项目干系人管理 UI (M1-F06) | M1 | 后端就绪前端口 |
|
||||||
|
| 客户/项目冻结 UI (M1-F07) | M1 | 后端就绪前端口 |
|
||||||
|
| 合同附件管理 UI (M2-F05) | M2 | 后端就绪前端口 |
|
||||||
|
| 合同变更 UI (M2-F07) | M2 | 后端 `changes` 端点就绪前端口 |
|
||||||
|
| 批量 SN 导入 (M4-F01/F07) | M4 | 后端就绪前端口 |
|
||||||
|
| Callback 模拟投递 UI (M5-F10) | M5 | 后端就绪前端口 |
|
||||||
|
| Callback 批量重处理 (M5-F07) | M5 | 后端缺批量端点 |
|
||||||
|
| 账号密码重置 UI (M11-F08) | M11 | 后端就绪前端口 |
|
||||||
|
| 管理员强制下线 UI (M11-F12) | M11 | 后端就绪前端口 |
|
||||||
|
| 角色权限管理页面 (M11-F15/F16) | M11 | 角色 CRUD + 权限码分配 |
|
||||||
|
| 报表导出按钮 (M9-F04) | M9 | 后端就绪前端口 |
|
||||||
|
| 通知通道实际发送 (M8-F03) | M8 | 配置就绪但未对接 |
|
||||||
|
|
||||||
|
### P2 级别(治理与规模化)
|
||||||
|
|
||||||
|
| 功能 | 模块 |
|
||||||
|
|------|------|
|
||||||
|
| 客户合并与去重 (M1-F08) | M1 |
|
||||||
|
| CRM 同步 (M1-F09) | M1 |
|
||||||
|
| 合同到期续费提醒 (M2-F09) | M2 |
|
||||||
|
| 现场环境信息 (M3-F06) | M3 |
|
||||||
|
| 交付模板 (M3-F08) | M3 |
|
||||||
|
| 配置发布记录 (M6-F06) | M6 |
|
||||||
|
| SDK 版本矩阵 (M6-F08) | M6 |
|
||||||
|
| 变更影响分析 (M6-F09) | M6 |
|
||||||
|
| 终端并发策略展示 (M7-F06) | M7 |
|
||||||
|
| 通知模板 (M8-F04) | M8 |
|
||||||
|
| 静默规则 (M8-F05) | M8 |
|
||||||
|
| 审计导出包 (M10-F03) | M10 |
|
||||||
|
| SSO/OIDC (M11-F09) | M11 |
|
||||||
|
| MFA (M11-F10) | M11 |
|
||||||
|
| 数据范围 (M11-F17) | M11 |
|
||||||
|
| 数据属主 (M11-F18) | M11 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 与产品模块文档的状态差异(代码领先文档)
|
||||||
|
|
||||||
|
以下功能点的实际实现状态优于 `chuangfei-platform-product-modules.md` 中的标记:
|
||||||
|
|
||||||
|
| 功能点 | 文档标记 | 实际代码状态 | 差异说明 |
|
||||||
|
|--------|---------|-------------|---------|
|
||||||
|
| **M1-F06** 项目干系人 | ○ | ◐ | 后端 CRUD 已实现,仅前端口 |
|
||||||
|
| **M1-F07** 冻结解冻 | ○ | ◐ | 后端端点已实现,仅前端口 |
|
||||||
|
| **M2-F05** 合同附件 | ○ | ◐ | 后端上传端点已实现 |
|
||||||
|
| **M2-F07** 合同变更 | ○ | ◐ | 后端 `changes` 端点已实现 |
|
||||||
|
| **M4-F01** 批量导入 | ○ | ◐ | 后端 `batch-import` 已实现 |
|
||||||
|
| **M6** 全模块 | 大段 ○ | ◐ | ID 映射/JSON 模板/特征映射/SKU 映射均已前后端实现 |
|
||||||
|
| **M7** 全模块 | 全 ○ | ◐ | 设备登记/列表/详情/绑定历史已上线 |
|
||||||
|
| **M8** 全模块 | 全 ○ | ◐ | 待办中心+通知设置已上线 |
|
||||||
|
| **M9** 全模块 | 全 ○ | ◐ | 4 个报表页面均上线 |
|
||||||
|
| **M10-F02** 审计检索 | ○ | ◐ | 已实现 |
|
||||||
|
| **M10-F04** 留存策略 | ○ | ◐ | 已实现 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 修订建议
|
||||||
|
|
||||||
|
### 文档层面
|
||||||
|
1. **更新 `chuangfei-platform-product-modules.md`** — 大量功能点(M6/M7/M8/M9)实际代码已实现但文档标记为 ○,需批量刷新状态列
|
||||||
|
2. **更新 `FRONTEND_UI_SPECIFICATION.md`** — 新增页面(集成 ID 映射/SKU 映射/特征映射/JSON 模板/审计留存/待办中心/通知设置等)未纳入 UI 规格文档
|
||||||
|
3. **补充角色与实际菜单对照** — 当前角色定义(SYS_ADMIN/SALES/LICENSE_OPS/DELIVERY)与产品文档三角色不符,新增角色需更新路由权限
|
||||||
|
|
||||||
|
### 工程层面
|
||||||
|
1. **优先级确认** — 按 Mid 版本规划,聚焦 P0 安全基线缺口(M11-F03 超时/F05 锁定/F07 改密/F14 用户管理)+ P1 就绪后端触点(M1/M2/M4/M5/M11 前端 UI 补全)
|
||||||
|
2. **文档与代码对齐** — 先产出更新后的产品模块文档,再进入 I10 实现
|
||||||
|
|
||||||
|
---
|
||||||
|
**附录**: 本清单基于源码走查 + 产品文档对比生成,未运行端到端测试验证。建议在启动 I10 实现前,由 QA 逐个功能点做冒烟验证。
|
||||||
@@ -0,0 +1,265 @@
|
|||||||
|
# 前端 UI 走查复盘报告
|
||||||
|
|
||||||
|
**日期:** 2026-05-27
|
||||||
|
**前端:** `web/delivery-platform-ui` — 38 views + MainLayout + Router
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 侧栏菜单布局与排序
|
||||||
|
|
||||||
|
### 当前排序
|
||||||
|
|
||||||
|
```
|
||||||
|
📊 首页
|
||||||
|
👥 客户管理
|
||||||
|
📋 合同管理
|
||||||
|
📦 交付管理
|
||||||
|
🔑 许可 SN
|
||||||
|
🛡️ 许可证管理 [NEW]
|
||||||
|
📨 Callback 收件箱
|
||||||
|
🌐 集成环境
|
||||||
|
📱 产品线
|
||||||
|
🖥️ 设备管理
|
||||||
|
🔔 待办中心
|
||||||
|
📊 报表中心
|
||||||
|
📧 报表订阅
|
||||||
|
👤 用户管理
|
||||||
|
```
|
||||||
|
|
||||||
|
### 评估
|
||||||
|
|
||||||
|
| 方面 | 评价 |
|
||||||
|
|------|------|
|
||||||
|
| **分组** | 无分组。所有菜单项单层平铺,缺少二级分类。当菜单项增多时不易查找 |
|
||||||
|
| **排序** | 基本合理:核心业务(客户→合同→交付→SN)在前,运营支持(Callback→设备→待办)在中,管理类(报表→用户)在后 |
|
||||||
|
| **建议优化** | 可增加分组标签「业务管理」「运营管理」「系统管理」来归类 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 首页(HomeView.vue)命名
|
||||||
|
|
||||||
|
### 当前状态
|
||||||
|
|
||||||
|
| 位置 | 显示文本 | 问题 |
|
||||||
|
|------|---------|------|
|
||||||
|
| 浏览器 title | 未设置 | `<title>` 标签缺失 |
|
||||||
|
| 登录页标题 | `客户商务与交付管理平台(I1)` | I1 标签过时,应该是已迭代到 I10+ |
|
||||||
|
| 顶栏 nav | `授权平台` | 品牌简称,可接受 |
|
||||||
|
| 首页内容 | `首页` | 正确 |
|
||||||
|
| 首页 alert | `交付平台(I7)` | 同样过时 |
|
||||||
|
|
||||||
|
### 问题
|
||||||
|
|
||||||
|
1. **无 `<title>` 标签** — 浏览器标签页显示 URL 路径而非平台名称
|
||||||
|
2. **I1 / I7 标签过时** — 登录页和首页仍显示迭代编号,应替换为稳定名称
|
||||||
|
3. **演示账号提示过时** — 登录页提示 `dev / dev(DEVELOPER)` 但 DEVELOPER 角色已废弃,应改为 `sales / sales`
|
||||||
|
4. **首页内容标题与侧栏** — 顶部显示 `授权平台`,登录页显示 `客户商务与交付管理平台`,二者不一致
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 逐页功能盘点
|
||||||
|
|
||||||
|
### M1 客户管理 (`/customers`)
|
||||||
|
|
||||||
|
| 功能 | 状态 | 备注 |
|
||||||
|
|------|------|------|
|
||||||
|
| 客户列表 | ✅ | 名称/信用代码分页 |
|
||||||
|
| 搜索 | ✅ | 关键词搜索 |
|
||||||
|
| 新建/编辑弹窗 | ✅ | 含行业/地址/开票信息 |
|
||||||
|
| 冻结按钮 | ✅ | 后端就绪,前端已联动 |
|
||||||
|
| 删除(软删) | ✅ | |
|
||||||
|
| **详情聚合视图** | ✅ | 关联项目/合同/SN 统计 |
|
||||||
|
| **合并/去重** | ○ | Full 版本范围 |
|
||||||
|
|
||||||
|
### M1 项目管理 (`/projects`)
|
||||||
|
|
||||||
|
| 功能 | 状态 | 备注 |
|
||||||
|
|------|------|------|
|
||||||
|
| 项目列表 | ✅ | |
|
||||||
|
| 按客户筛选 | ✅ | |
|
||||||
|
| 新建/编辑弹窗 | ✅ | |
|
||||||
|
| **干系人管理** | ✅ | CRUD 弹窗已实现 |
|
||||||
|
| 计划起止日期 | ✅ | 字段已存在 |
|
||||||
|
| 项目经理 | ✅ | 字段已存在 |
|
||||||
|
|
||||||
|
### M2 合同管理 (`/contracts`)
|
||||||
|
|
||||||
|
| 功能 | 状态 | 备注 |
|
||||||
|
|------|------|------|
|
||||||
|
| 合同列表 | ✅ | |
|
||||||
|
| 三步创建向导 | ✅ | |
|
||||||
|
| 状态机操作 | ✅ | DRAFT→PENDING→EFFECTIVE→CHANGING→TERMINATED |
|
||||||
|
| 行项管理 | ✅ | |
|
||||||
|
| **附件上传** | ✅ | el-upload + 文件列表 |
|
||||||
|
| **合同变更** | ✅ | changes 端点已联动 |
|
||||||
|
| SKU 映射 | ○ | 未在前端展示 |
|
||||||
|
|
||||||
|
### M3 交付管理 (`/deliveries`)
|
||||||
|
|
||||||
|
| 功能 | 状态 | 备注 |
|
||||||
|
|------|------|------|
|
||||||
|
| 交付列表 | ✅ | |
|
||||||
|
| 新建向导 | ✅ | |
|
||||||
|
| 状态变更 | ✅ | PENDING→DELIVERED→CANCELLED |
|
||||||
|
| 行项管理 | ✅ | |
|
||||||
|
| **现场环境信息** | ✅ | 部署地址/联系人/电话 |
|
||||||
|
| SN 发放门禁 | ○ | 后端参数已定义,逻辑未执行 |
|
||||||
|
|
||||||
|
### M4 许可 SN (`/licenses/sn`)
|
||||||
|
|
||||||
|
| 功能 | 状态 | 备注 |
|
||||||
|
|------|------|------|
|
||||||
|
| SN 列表 | ✅ | |
|
||||||
|
| 搜索 | ✅ | SN 编码/项目筛选 |
|
||||||
|
| **批量导入** | ✅ | CSV 批量导入弹窗 |
|
||||||
|
| **批量操作** | ✅ | 批量状态变更弹窗 |
|
||||||
|
| 详情页 | ✅ | 绑定/状态/备注 |
|
||||||
|
| 自研许可证管理 | ✅ | `/licenses` 额外页面 |
|
||||||
|
|
||||||
|
### M5 Callback (`/callbacks`)
|
||||||
|
|
||||||
|
| 功能 | 状态 | 备注 |
|
||||||
|
|------|------|------|
|
||||||
|
| 事件收件箱 | ✅ | |
|
||||||
|
| 多维度筛选 | ✅ | 状态/事件类型/SN/项目/时间 |
|
||||||
|
| **批量重试按钮** | ✅ | 前端已实现,后端 `/batch-replay` 已新增 |
|
||||||
|
| **模拟投递弹窗** | ✅ | 已实现 |
|
||||||
|
| 详情页 | ✅ | payload 脱敏预览 |
|
||||||
|
| **失败原因下拉** | ✅ | 选择 FAILED 时弹出原因选择,后端已联通 |
|
||||||
|
| 状态处置 | ✅ | PENDING→PROCESSED/FAILED/IGNORED |
|
||||||
|
| 人工挂接 | ✅ | SN/项目/合同 |
|
||||||
|
| 死信积压 | ◐ | **后端端点已新增, 前端统计卡片需补充** |
|
||||||
|
|
||||||
|
### M6 集成配置 (`/integration/*`)
|
||||||
|
|
||||||
|
| 功能 | 状态 | 备注 |
|
||||||
|
|------|------|------|
|
||||||
|
| 产品线 | ✅ | CRUD |
|
||||||
|
| 集成环境 | ✅ | CRUD |
|
||||||
|
| **ID 映射** | ✅ | 已实现 |
|
||||||
|
| **SKU 映射** | ✅ | 已实现 |
|
||||||
|
| **特征映射** | ✅ | 已实现 |
|
||||||
|
| **JSON 模板** | ✅ | CRUD |
|
||||||
|
|
||||||
|
### M7 设备管理 (`/devices`)
|
||||||
|
|
||||||
|
| 功能 | 状态 | 备注 |
|
||||||
|
|------|------|------|
|
||||||
|
| 设备列表 | ✅ | MID/别名/站点/状态/心跳 |
|
||||||
|
| 设备登记弹窗 | ✅ | |
|
||||||
|
| 设备详情 | ✅ | 绑定历史 |
|
||||||
|
| 换机申请 | ◐ | 后端端点就绪,前端 UI 待补 |
|
||||||
|
|
||||||
|
### M8 待办中心 (`/todos`)
|
||||||
|
|
||||||
|
| 功能 | 状态 | 备注 |
|
||||||
|
|------|------|------|
|
||||||
|
| 待办列表 | ✅ | 按类型/优先级/状态筛选 |
|
||||||
|
| 状态流转 | ✅ | |
|
||||||
|
| 通知配置 | ✅ | 通道/事件订阅 |
|
||||||
|
|
||||||
|
### M9 报表中心 (`/reports/*`)
|
||||||
|
|
||||||
|
| 功能 | 状态 | 备注 |
|
||||||
|
|------|------|------|
|
||||||
|
| 合同 SN 报表 | ✅ | 含**导出 CSV 按钮** |
|
||||||
|
| Callback 统计 | ✅ | |
|
||||||
|
| 项目健康度 | ✅ | |
|
||||||
|
| 报表订阅 | ✅ | |
|
||||||
|
|
||||||
|
### M10 审计 (`/audit`)
|
||||||
|
|
||||||
|
| 功能 | 状态 | 备注 |
|
||||||
|
|------|------|------|
|
||||||
|
| 审计检索 | ✅ | |
|
||||||
|
| 留存策略 | ✅ | |
|
||||||
|
| 导出审计包 | ○ | 未实现(Full 版本) |
|
||||||
|
|
||||||
|
### M11 系统管理
|
||||||
|
|
||||||
|
| 页面 | 功能 | 状态 | 备注 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| `/profile` | 个人设置/改密 | ✅ | |
|
||||||
|
| `/admin/params` | 系统参数 | ◐ | localStorage MVP |
|
||||||
|
| `/admin/users` | **用户管理** | ✅ | CRUD + 启用/禁用 |
|
||||||
|
| `/403` | 无权限 | ✅ | |
|
||||||
|
| `/*` | 404 | ✅ | |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 发现的问题清单
|
||||||
|
|
||||||
|
### 🔴 需要修复
|
||||||
|
|
||||||
|
| # | 问题 | 位置 | 建议 |
|
||||||
|
|---|------|------|------|
|
||||||
|
| 1 | 登录页演示提示仍显示 `dev/dev(DEVELOPER)`,DEVELOPER 已废弃 | `LoginView.vue:14` | 改为 `admin/admin123 / sales/sales / delivery/delivery / ops/ops` |
|
||||||
|
| 2 | 首页标题 `交付平台(I7)` 过时 | `HomeView.vue` | 改为稳定名称,去掉迭代编号 |
|
||||||
|
| 3 | 登录页标题 `(I1)` 过时 | `LoginView.vue:4` | 去掉 `(I1)` |
|
||||||
|
| 4 | 无 `<title>` 标签 | `index.html` | 添加 `<title>创飞·交付管理平台</title>` |
|
||||||
|
| 5 | Callback 积压统计卡片未展示 | `CallbackInboxView.vue` | 列表上方增加统计条(pending/failed/最久未处理) |
|
||||||
|
|
||||||
|
### 🟡 建议优化
|
||||||
|
|
||||||
|
| # | 问题 | 位置 | 建议 |
|
||||||
|
|---|------|------|------|
|
||||||
|
| 6 | 侧栏菜单无分组 | `MainLayout.vue` | 增加「业务管理」「运营管理」「系统管理」分组标签 |
|
||||||
|
| 7 | `授权平台` vs `客户商务与交付管理平台` 名称不一致 | 全局 | 统一品牌名称 |
|
||||||
|
| 8 | 通知 badge 显示 `3` 为硬编码 | `MainLayout.vue:20` | 应从后端获取未读数 |
|
||||||
|
| 9 | `许可证管理` 标记 `NEW` 但已上线多时 | `MainLayout.vue:132` | 可去掉 NEW 标记 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 页面功能完整度总表
|
||||||
|
|
||||||
|
| 页面 | 路由 | 完整度 | 备注 |
|
||||||
|
|------|------|--------|------|
|
||||||
|
| 登录 | `/login` | 95% | 演示提示需更新 |
|
||||||
|
| 首页 | `/` | 90% | 迭代编号过时 |
|
||||||
|
| 客户管理 | `/customers` | 95% | |
|
||||||
|
| 客户详情 | `/customers/:id` | 100% | 含聚合摘要 |
|
||||||
|
| 项目管理 | `/projects` | 100% | 含干系人 |
|
||||||
|
| 合同管理 | `/contracts` | 95% | |
|
||||||
|
| 合同向导 | `/contracts/new` | 100% | |
|
||||||
|
| 合同详情 | `/contracts/:id` | 100% | 含附件+变更 |
|
||||||
|
| 交付管理 | `/deliveries` | 95% | 含现场环境 |
|
||||||
|
| 交付向导 | `/deliveries/new` | 100% | |
|
||||||
|
| 交付详情 | `/deliveries/:id` | 100% | |
|
||||||
|
| 许可 SN | `/licenses/sn` | 100% | 含批量导入/操作 |
|
||||||
|
| SN 向导 | `/licenses/sn/new` | 100% | |
|
||||||
|
| SN 详情 | `/licenses/sn/:id` | 100% | |
|
||||||
|
| 许可证管理 | `/licenses` | 100% | 自研许可证 |
|
||||||
|
| Callback 收件箱 | `/callbacks` | 90% | 缺积压统计卡片 |
|
||||||
|
| Callback 详情 | `/callbacks/:id` | 100% | 含失败原因 |
|
||||||
|
| 集成环境 | `/integration/environments` | 100% | |
|
||||||
|
| 产品线 | `/integration/product-lines` | 100% | |
|
||||||
|
| ID 映射 | `/integration/id-mappings` | 100% | |
|
||||||
|
| SKU 映射 | `/integration/sku-mappings` | 100% | |
|
||||||
|
| 特征映射 | `/integration/feature-mappings` | 100% | |
|
||||||
|
| JSON 模板 | `/integration/json-templates` | 100% | |
|
||||||
|
| 设备管理 | `/devices` | 95% | 换机申请 UI 待补 |
|
||||||
|
| 设备详情 | `/devices/:id` | 100% | |
|
||||||
|
| 待办中心 | `/todos` | 100% | |
|
||||||
|
| 通知设置 | `/notifications/settings` | 100% | |
|
||||||
|
| 合同 SN 报表 | `/reports/contract-sn` | 100% | 含导出 |
|
||||||
|
| Callback 统计 | `/reports/callback-stats` | 100% | |
|
||||||
|
| 项目健康度 | `/reports/project-health` | 100% | |
|
||||||
|
| 报表订阅 | `/reports/subscriptions` | 100% | |
|
||||||
|
| 审计日志 | `/audit` | 100% | |
|
||||||
|
| 审计留存 | `/audit/retention` | 100% | |
|
||||||
|
| 系统参数 | `/admin/params` | 90% | localStorage 待迁移 |
|
||||||
|
| 用户管理 | `/admin/users` | 100% | |
|
||||||
|
| 个人设置 | `/profile` | 100% | |
|
||||||
|
| 403 | `/403` | 100% | |
|
||||||
|
| 404 | `/*` | 100% | |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 总结
|
||||||
|
|
||||||
|
- **总页面数**: 37 个页面/视图
|
||||||
|
- **100% 完成**: 30 页
|
||||||
|
- **90-95% 完成**: 6 页(需小修)
|
||||||
|
- **未实现**: 0 页
|
||||||
|
|
||||||
|
MVP/Mid 范围的前端 UI 已基本实现完毕。剩余工作集中在 Full 版本(积压监控卡片、换机审批流、审计导出包等)。
|
||||||
@@ -0,0 +1,217 @@
|
|||||||
|
# Full 版本 (V2.0) 实现缺失与不足复盘
|
||||||
|
|
||||||
|
**日期:** 2026-05-27
|
||||||
|
**来源:** `docs/chuangfei-platform-product-modules.md` §13.5, §14, §16.7
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Full 版本范围总览
|
||||||
|
|
||||||
|
Full 版本 = Mid + 所有 P2 功能点 + 以下专项能力:
|
||||||
|
|
||||||
|
| 分类 | 项目 | 涉及模块 | 当前状态 |
|
||||||
|
|------|------|---------|---------|
|
||||||
|
| **安全** | MFA 双因素认证 | M11-F10 | ○ 未开始 |
|
||||||
|
| **安全** | SECURITY_ADMIN 角色 | §13.2 | ○ 未开始 |
|
||||||
|
| **数据** | 事业部数据范围 (Data Scope) | M11-F17 | ○ 未开始 |
|
||||||
|
| **审计** | 审计导出包 | M10-F03 | ○ 未开始 |
|
||||||
|
| **集成** | CRM 同步 | M1-F09 | ○ 未开始 |
|
||||||
|
| **治理** | 细粒度互斥策略 | §13.5 | ○ 未开始 |
|
||||||
|
| **基础设施** | 消息队列 (MQ) | Webhook→API | ○ 未开始 |
|
||||||
|
| **基础设施** | 读模型分离 (CQRS) | 报表/查询 | ○ 未开始 |
|
||||||
|
| **P2 功能** | 13 个 P2 功能点(见 §2) | 多模块 | ◐ 部分上前端 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. P2 功能点逐项
|
||||||
|
|
||||||
|
| ID | 功能点 | 所属模块 | 当前状态 | Full 要求 |
|
||||||
|
|----|--------|---------|---------|----------|
|
||||||
|
| M1-F08 | 客户合并与去重 | M1 | ○ 未开始 | 疑似重复客户识别、合并流程与审计 |
|
||||||
|
| M1-F09 | CRM 主数据同步 | M1 | ○ 未开始 | 以外部 ID 关联、增量同步 |
|
||||||
|
| M2-F09 | 合同到期与续费提醒 | M2 | ○ 未开始 | 列表与订阅(与 M8 联动) |
|
||||||
|
| M3-F08 | 交付模板 | M3 | ○ 未开始 | 按产品线预置交付清单模板 |
|
||||||
|
| M4-F11 | 授权策略生效视图 | M4 | ○ 未开始 | 展示当前映射版本、环境(与 M6 联动) |
|
||||||
|
| M5-F10 | 模拟投递 | M5 | ◐ 后端就绪, 前端 UI 待补 | 联调验收工具 |
|
||||||
|
| M6-F08 | SDK/native 版本矩阵 | M6 | ○ 未开始 | 与现场客户端兼容范围说明 |
|
||||||
|
| M6-F09 | 变更影响分析 | M6 | ○ 未开始 | 映射变更影响哪些在服 SN/合同 |
|
||||||
|
| M7-F06 | 终端数/并发策略展示 | M7 | ○ 未开始 | 只读展示合同或比特策略摘要 |
|
||||||
|
| M8-F04 | 通知模板 | M8 | ○ 未开始 | 事件类型 → 模板变量 |
|
||||||
|
| M8-F05 | 静默规则 | M8 | ○ 未开始 | 重复事件聚合、防骚扰 |
|
||||||
|
| M9-F05 | 项目健康度看板 | M9 | ◐ 前端已上线 | 红黄绿规则可配置性待确认 |
|
||||||
|
| M9-F06 | 订阅报表 | M9 | ◐ 前端已上线 | 后端推送逻辑待确认 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Full 版本专项能力详述
|
||||||
|
|
||||||
|
### 3.1 M11-F10 双因素认证 MFA
|
||||||
|
|
||||||
|
**PRD 要求:**
|
||||||
|
```
|
||||||
|
TOTP/短信/企业令牌等一种;可配置为全员或高敏角色必选
|
||||||
|
```
|
||||||
|
|
||||||
|
**当前缺口:**
|
||||||
|
- 无 TOTP 生成/验证逻辑
|
||||||
|
- 无短信网关集成
|
||||||
|
- 无 MFA 绑定/解绑 UI
|
||||||
|
- 无角色级 MFA 强制策略
|
||||||
|
|
||||||
|
**实现思路:** TOTP (Time-based One-Time Password) 方案,使用 `java.security` 或 Google Authenticator 兼容库,前端显示二维码 + 验证码输入。
|
||||||
|
|
||||||
|
**预估工作量:** 中(3-5 天,含 Flyway 迁移 + 后端 + 前端 + 扫码绑定流程)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3.2 SECURITY_ADMIN 角色
|
||||||
|
|
||||||
|
**PRD 要求 (§13.2):**
|
||||||
|
```
|
||||||
|
锁定策略、强制下线、审计检索;与 SYS_ADMIN 分离(职责分离)
|
||||||
|
权限矩阵: M11 中 F05~F12、F21 及 M10 审计检索 RX
|
||||||
|
```
|
||||||
|
|
||||||
|
**当前缺口:**
|
||||||
|
- 角色代码未定义
|
||||||
|
- 路由/权限码未配置
|
||||||
|
- SECURITY_ADMIN 的专属 UI(会话管理页、锁定策略配置)
|
||||||
|
|
||||||
|
**预估工作量:** 小(1-2 天,角色定义 + 权限码 + 路由 + 会话管理页)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3.3 M11-F17 事业部数据范围 (Data Scope)
|
||||||
|
|
||||||
|
**PRD 要求:**
|
||||||
|
```
|
||||||
|
按事业部/区域/客户组限制列表可见行(与 M11-F18 二选一或组合)
|
||||||
|
```
|
||||||
|
|
||||||
|
**当前缺口:**
|
||||||
|
- 无数据范围模型(部门/区域/客户组表)
|
||||||
|
- 无 MyBatis-Plus 数据权限拦截器
|
||||||
|
- 前端无数据范围选择器
|
||||||
|
|
||||||
|
**实现思路:** MyBatis-Plus 的 `Interceptor` 或 `@SqlParser` 注解,在查询时自动追加数据范围条件。需要先定义组织/区域/客户组的基础数据模型。
|
||||||
|
|
||||||
|
**预估工作量:** 大(5-8 天,含数据模型 + 拦截器 + 配置 UI + 现有查询适配)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3.4 M10-F03 审计导出包
|
||||||
|
|
||||||
|
**PRD 要求:**
|
||||||
|
```
|
||||||
|
范围可选(项目/合同/时间窗),水印与权限
|
||||||
|
```
|
||||||
|
|
||||||
|
**当前缺口:**
|
||||||
|
- 后端 `GET /audit-events/export` 端点已存在(CSV 导出)
|
||||||
|
- 前端导出按钮未接入
|
||||||
|
- 无水印/权限控制
|
||||||
|
|
||||||
|
**预估工作量:** 小(0.5-1 天,前端按钮 + 参数传递)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3.5 M1-F09 CRM 主数据同步
|
||||||
|
|
||||||
|
**PRD 要求:**
|
||||||
|
```
|
||||||
|
以外部 ID 关联、增量同步状态展示
|
||||||
|
```
|
||||||
|
|
||||||
|
**当前缺口:**
|
||||||
|
- 完全未实现
|
||||||
|
- 无外部 ID 字段(已在 Customer entity 中有 `customerCode` 但非 CRM ID)
|
||||||
|
- 无同步状态跟踪
|
||||||
|
|
||||||
|
**预估工作量:** 中(3-5 天,含外部 ID 模型 + 同步端点 + UI 状态展示)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3.6 细粒度互斥策略 (§13.5)
|
||||||
|
|
||||||
|
**PRD 要求:**
|
||||||
|
```
|
||||||
|
角色互斥规则(如 SYS_ADMIN 与业务高敏导出)
|
||||||
|
```
|
||||||
|
|
||||||
|
**当前缺口:**
|
||||||
|
- 完全未实现
|
||||||
|
- 当前仅简单串联角色权限
|
||||||
|
|
||||||
|
**预估工作量:** 中(2-3 天,互斥规则定义 + 后端校验 + 前端提示)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3.7 消息队列 (MQ) 架构
|
||||||
|
|
||||||
|
**架构要求:**
|
||||||
|
```
|
||||||
|
当前: Webhook → 直写 PostgreSQL → API 轮询
|
||||||
|
Full: Webhook → MQ → API 消费(削峰、DLQ、可观测)
|
||||||
|
```
|
||||||
|
|
||||||
|
**当前状态:**
|
||||||
|
- 无 MQ 基础设施(RabbitMQ/RocketMQ/Kafka)
|
||||||
|
- Webhook 直写 inbox 表,API 轮询读取
|
||||||
|
- 已在 PRD 已知局限中标注(§16.6)
|
||||||
|
|
||||||
|
**预估工作量:** 大(5-10 天,含 MQ 选型 + 生产者/消费者 + 现有路径兼容 + DLQ)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3.8 读模型分离 (CQRS)
|
||||||
|
|
||||||
|
**架构要求:**
|
||||||
|
```
|
||||||
|
报表/查询从主库分离为独立读模型
|
||||||
|
```
|
||||||
|
|
||||||
|
**当前状态:**
|
||||||
|
- 全部查询直连主 PostgreSQL
|
||||||
|
- 无读模型/物化视图
|
||||||
|
|
||||||
|
**预估工作量:** 大(5-8 天,含读模型表 + 同步机制 + 现有查询迁移)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 总体评估
|
||||||
|
|
||||||
|
### 实现状态
|
||||||
|
|
||||||
|
| 分类 | 总项数 | ✅ 已完成 | ◐ 部分实现 | ○ 未开始 |
|
||||||
|
|------|--------|----------|-----------|---------|
|
||||||
|
| P2 功能点 | 13 | 0 | 3 (M5-F10, M9-F05, M9-F06) | 10 |
|
||||||
|
| Full 专项 | 8 | 0 | 0 | 8 |
|
||||||
|
| **合计** | **21** | **0 (0%)** | **3 (14%)** | **18 (86%)** |
|
||||||
|
|
||||||
|
### 工作量估算
|
||||||
|
|
||||||
|
| 项目 | 预估人天 | 依赖 |
|
||||||
|
|------|---------|------|
|
||||||
|
| M10-F03 审计导出按钮 | 0.5 | 无 |
|
||||||
|
| M5-F10 模拟投递 UI | 0.5 | 无 |
|
||||||
|
| SECURITY_ADMIN 角色 | 1 | 无 |
|
||||||
|
| M9-F05/F06 报表增强 | 1 | 无 |
|
||||||
|
| M2-F09 到期提醒 | 1 | M8 通知 |
|
||||||
|
| M6-F08/F09 版本/变更 | 2 | 无 |
|
||||||
|
| M11-F10 MFA | 4 | 无 |
|
||||||
|
| M1-F08 客户合并 | 3 | 无 |
|
||||||
|
| M1-F09 CRM 同步 | 4 | 无 |
|
||||||
|
| M11-F17 数据范围 | 6 | 组织模型 |
|
||||||
|
| MQ 消息队列 | 8 | 基础设施选型 |
|
||||||
|
| CQRS 读模型 | 7 | MQ 完成后 |
|
||||||
|
| **合计** | **~38 人天** | |
|
||||||
|
|
||||||
|
### 建议实施顺序
|
||||||
|
|
||||||
|
| 阶段 | 项目 | 人天 | 原因 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| **Phase 1** | M10-F03 导出, M5-F10 模拟UI, SECURITY_ADMIN | 2 | 快速 wins,无依赖 |
|
||||||
|
| **Phase 2** | M9 报表推送, M6-F08/F09 版本矩阵 | 3 | 增强已有功能 |
|
||||||
|
| **Phase 3** | M11-F10 MFA, M11-F17 数据范围 | 10 | 安全核心能力 |
|
||||||
|
| **Phase 4** | M1-F08/F09 客户合并+CRM | 7 | 集成能力 |
|
||||||
|
| **Phase 5** | MQ + CQRS | 15 | 基础设施重构,依赖最重 |
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
# ONLYOFFICE 集成状态复盘
|
||||||
|
|
||||||
|
**日期:** 2026-05-27
|
||||||
|
**决策文档:** `docs/superpowers/specs/2026-05-25-onlyoffice-integration-decision.md`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 当前状态:规划阶段,零代码实现
|
||||||
|
|
||||||
|
| 维度 | 状态 |
|
||||||
|
|------|------|
|
||||||
|
| 设计决策 | ✅ 已完成(2026-05-25) |
|
||||||
|
| 后端实现 | ○ 未开始 |
|
||||||
|
| 前端实现 | ○ 未开始 |
|
||||||
|
| 文档代理层 | ○ 未开始 |
|
||||||
|
| ONLYOFFICE 服务部署 | 存在 `craftsupport.cn:8088`(已知可用) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 决策回顾
|
||||||
|
|
||||||
|
| 决策项 | 结论 |
|
||||||
|
|--------|------|
|
||||||
|
| 是否集成 | ✅ 是,Mid 迭代完成后推进 |
|
||||||
|
| 集成范围 | **仅预览**,不做在线编辑 |
|
||||||
|
| 存储策略 | 附件本地存储,不接入 ONLYOFFICE 文档存储 |
|
||||||
|
| 优先级 | 非阻塞,排在 Mid (I10~I13) 之后 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 实施前置条件检查
|
||||||
|
|
||||||
|
### 已完成(可直接利用)
|
||||||
|
|
||||||
|
| 条件 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| 合同附件上传 | **M2-F05 已实现** — 后端 `POST /contracts/{id}/attachments` + 前端附件列表 |
|
||||||
|
| 附件文件存储 | 文件存储在本地 `uploads/contracts/{id}/` |
|
||||||
|
| ONLYOFFICE 服务 | `craftsupport.cn:8088` 已知可用 |
|
||||||
|
|
||||||
|
### 未开始
|
||||||
|
|
||||||
|
| 组件 | 计划方案 | 预估 |
|
||||||
|
|------|---------|------|
|
||||||
|
| `DocumentPreviewController` | 平台新增代理端点:接收文件 ID → 返回 ONLYOFFICE 所需的配置 JSON + JWT | 1 天 |
|
||||||
|
| 前端预览弹窗 | 附件列表行操作加「预览」按钮,点击弹窗内嵌 ONLYOFFICE iframe | 0.5 天 |
|
||||||
|
| JWT 密钥配置 | `ONLYOFFICE_JWT_SECRET` 环境变量 | 0.1 天 |
|
||||||
|
| 文件流式输出 | ONLYOFFICE 通过平台 URL 获取文件内容 | 0.5 天 |
|
||||||
|
|
||||||
|
**合计预估:** 2 天
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 阻碍因素
|
||||||
|
|
||||||
|
| 因素 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| **优先级排期** | 决策文档明确标注「Mid 迭代完成后推进」,当前 Tier 1+2 尚未全部完成 |
|
||||||
|
| **Mid 迭代未完成** | 当前工作仍在补齐 Tier 1 核心功能和 Tier 2 运营效率功能 |
|
||||||
|
| **ONLYOFFICE 服务可用性** | 需验证 `craftsupport.cn:8088` 当前是否可连接及 JWT 配置 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 实施路线
|
||||||
|
|
||||||
|
```
|
||||||
|
Phase 1: 后端 DocumentPreviewController
|
||||||
|
GET /api/v1/preview/{attachmentId}
|
||||||
|
→ 返回 ONLYOFFICE 配置 JSON ({fileType, key, title, url, permissions: {download:false,edit:false}})
|
||||||
|
|
||||||
|
Phase 2: 前端预览弹窗
|
||||||
|
ContractDetailView.vue 附件列表 → 每行加「预览」按钮
|
||||||
|
→ 弹窗 iframe src = ONLYOFFICE 文档服务 URL + config
|
||||||
|
|
||||||
|
Phase 3: 文件流式服务
|
||||||
|
GET /api/v1/preview/{attachmentId}/file
|
||||||
|
→ 流式输出附件文件内容
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 建议
|
||||||
|
|
||||||
|
1. **保持当前优先级** — Tier 1+2 业务功能完成后(预计 ~2 周)再启动 ONLYOFFICE
|
||||||
|
2. **验证 ONLYOFFICE 服务** — 启动前确认 `craftsupport.cn:8088` 可用并用 CORS 白名单允许平台域名
|
||||||
|
3. **最小实现** — 仅做 iframe 嵌入预览,不做编辑、不做保存回传,保持实现量最小
|
||||||
@@ -0,0 +1,211 @@
|
|||||||
|
# PRD 实现进度复盘
|
||||||
|
|
||||||
|
**日期:** 2026-05-27
|
||||||
|
**PRD:** `docs/chuangfei-platform-product-modules.md`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 整体进度
|
||||||
|
|
||||||
|
| 状态 | 数量 | 占比 |
|
||||||
|
|------|------|------|
|
||||||
|
| ✅ 完全实现 | 24 | 32% |
|
||||||
|
| ◐ 部分实现 | 23 | 32% |
|
||||||
|
| ○ 未实现 | 26 | 36% |
|
||||||
|
| **合计** | **73** | **100%** |
|
||||||
|
|
||||||
|
```
|
||||||
|
M1 客户与项目中心 ██░░░░░░░░ 20% ✅ 2 ◐ 4 ○ 3
|
||||||
|
M2 合同与履约行 ████░░░░░░ 40% ✅ 4 ◐ 2 ○ 3
|
||||||
|
M3 交付管理 ██████░░░░ 60% ✅ 5 ◐ 0 ○ 3
|
||||||
|
M4 授权与许可运营 ██░░░░░░░░ 20% ✅ 3 ◐ 3 ○ 4
|
||||||
|
M5 Callback 运营 █████░░░░░ 50% ✅ 5 ◐ 2 ○ 2
|
||||||
|
M6 授权集成与配置 ████░░░░░░ 40% ✅ 4 ◐ 1 ○ 4
|
||||||
|
M7 设备与终端 █░░░░░░░░░ 10% ✅ 1 ◐ 3 ○ 2
|
||||||
|
M8 通知与待办 ░░░░░░░░░░ 0% ✅ 0 ◐ 3 ○ 2
|
||||||
|
M9 报表与对账 ░░░░░░░░░░ 0% ✅ 0 ◐ 5 ○ 1
|
||||||
|
M10 审计与合规 ██░░░░░░░░ 20% ✅ 1 ◐ 2 ○ 1
|
||||||
|
M11 身份/访问/平台 ███░░░░░░░ 30% ✅ 7 ◐ 3 ○11
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 版本覆盖:MVP / Mid / Full
|
||||||
|
|
||||||
|
### MVP(I1~I9)— 标记为已完成 ✅
|
||||||
|
|
||||||
|
| 模块 | PRD 承诺 | 实际状态 |
|
||||||
|
|------|---------|---------|
|
||||||
|
| M1 客户项目 | P0 核心: 档案/列表/检索 | ✅ 完成, 缺详情聚合视图 |
|
||||||
|
| M2 合同 | 登记/状态机/行项 | ✅ 完成 |
|
||||||
|
| M3 交付 | 批次/清单/状态 | ✅ 完成 |
|
||||||
|
| M4 SN | 手工录入/绑定/状态 | ✅ 手工完成, 批量导入 UI 待补 |
|
||||||
|
| M5 Callback | 收件箱/详情/处置 | ✅ 完成 |
|
||||||
|
| M6 集成 | 产品线/环境只读 | ✅ 已超出(ID映射/JSON模板已实现) |
|
||||||
|
| M10 审计 | 关键字段变更日志 | ✅ 完成 |
|
||||||
|
| M11 身份 | JWT 登录/路由守卫/三角色/字典 | ◐ 路由级 RBAC ✅, 按钮级权限码未全覆盖 |
|
||||||
|
|
||||||
|
**MVP 已覆盖 P0 主链路:客户→项目→合同→交付→SN→Callback→审计** ✅
|
||||||
|
|
||||||
|
### Mid(I10~I13)— 进行中 🕐
|
||||||
|
|
||||||
|
| PRD 承诺 | 当前实现状态 |
|
||||||
|
|---------|-------------|
|
||||||
|
| M7 设备管理 | ◐ 设备登记/列表/详情已上线,换机审批/设备事件联动待补 |
|
||||||
|
| M8 通知待办 | ◐ 待办中心+通知配置已上线,实际发送逻辑未接入 |
|
||||||
|
| M9 报表对账 | ◐ 4 个报表页面已上线,导出按钮/推送逻辑待补 |
|
||||||
|
| 补齐 MVP 遗留 P0 | ◐ M1-F03 详情摘要后端已修复, M11-F03 空闲超时前端已实现 |
|
||||||
|
| M2/M4/M5/M6 P1 增强 | ○ 部分未开始 |
|
||||||
|
| M10-F02 审计检索 | ◐ AuditSearchView 已上线,筛选维度待确认 |
|
||||||
|
| M11 SSO/并发/强制下线 | ○ 未开始 |
|
||||||
|
| 角色模型对标产品定义集 | ◐ 4 角色已落地(ADMIN/SALES/DELIVERY/LICENSE_OPS), 仍有 6+ 角色未实现 |
|
||||||
|
|
||||||
|
### Full(V2.0)— 规划中 📋
|
||||||
|
|
||||||
|
全部未开始:MFA、SECURITY_ADMIN、数据范围、审计导出包、CRM 同步。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 按模块的缺失功能点清单
|
||||||
|
|
||||||
|
### M1 客户与项目中心 — ✅2 ◐4 ○3
|
||||||
|
|
||||||
|
| 功能点 | 优先级 | 当前状态 | 缺失内容 |
|
||||||
|
|--------|--------|---------|---------|
|
||||||
|
| F01 客户档案 | P0 | ◐ | 缺行业/地址/开票信息字段 |
|
||||||
|
| F03 详情聚合 | P0 | ◐ | 后端 `/summary` 已修复, 前端已展示 |
|
||||||
|
| F04 项目创建 | P0 | ◐ | 缺计划起止/项目经理字段 |
|
||||||
|
| F06 项目干系人 | P0 | ◐ | 后端 CRUD 就绪, 前端 UI 待补 |
|
||||||
|
| F07 冻结解冻 | P1 | ◐ | 后端就绪, 前端 UI 待补 |
|
||||||
|
| F08 客户合并 | P2 | ○ | 未开始 |
|
||||||
|
| F09 CRM 同步 | P2 | ○ | 未开始 |
|
||||||
|
|
||||||
|
### M2 合同与履约行 — ✅4 ◐2 ○3
|
||||||
|
|
||||||
|
| 功能点 | 优先级 | 当前状态 | 缺失内容 |
|
||||||
|
|--------|--------|---------|---------|
|
||||||
|
| F05 合同附件 | P1 | ◐ | 后端上传就绪, 前端 UI 有待(已校验) |
|
||||||
|
| F07 合同变更 | P1 | ◐ | 后端 changes 就绪, 前端 UI 待补 |
|
||||||
|
| F08 SKU 映射 | P1 | ○ | 未实现 |
|
||||||
|
| F09 到期提醒 | P2 | ○ | 未实现 |
|
||||||
|
|
||||||
|
### M3 交付管理 — ✅5 ◐0 ○3
|
||||||
|
|
||||||
|
| 功能点 | 优先级 | 当前状态 | 缺失内容 |
|
||||||
|
|--------|--------|---------|---------|
|
||||||
|
| F06 现场环境 | P1 | ○ | 未实现 |
|
||||||
|
| F07 SN 门禁 | P1 | ○ | `deliveryGateEnabled` 参数已定义但未执行 |
|
||||||
|
| F08 交付模板 | P2 | ○ | 未开始 |
|
||||||
|
|
||||||
|
### M4 授权与许可运营 — ✅3 ◐3 ○4
|
||||||
|
|
||||||
|
| 功能点 | 优先级 | 当前状态 | 缺失内容 |
|
||||||
|
|--------|--------|---------|---------|
|
||||||
|
| F01 批量导入 | P0 | ◐ | 后端 `/batch-import` 就绪, 前端缺 UI |
|
||||||
|
| F05 原因码分类 | P0 | ◐ | 手工状态更新缺原因码 |
|
||||||
|
| F07 批量操作 | P1 | ◐ | 后端就绪, 前端缺批量 UI |
|
||||||
|
| F06 比特状态 | P1 | ○ | 依赖比特对接 |
|
||||||
|
| F08 授权需求单 | P1 | ○ | 未开始 |
|
||||||
|
| F09 试用/正式标签 | P1 | ○ | 未开始 |
|
||||||
|
| F11 策略视图 | P2 | ○ | 未开始 |
|
||||||
|
|
||||||
|
### M5 Callback 运营 — ✅5 ◐2 ○2
|
||||||
|
|
||||||
|
| 功能点 | 优先级 | 当前状态 | 缺失内容 |
|
||||||
|
|--------|--------|---------|---------|
|
||||||
|
| F06 失败标注 | P1 | ○ | 未实现 |
|
||||||
|
| F07 批量重处理 | P1 | ◐ | 单条重放 ✅, 批量未做 |
|
||||||
|
| F08 死信监控 | P1 | ○ | 未实现 |
|
||||||
|
| F10 模拟投递 UI | P2 | ◐ | 后端 `/simulate` 就绪, 前端缺入口 |
|
||||||
|
|
||||||
|
### M6 授权集成与配置 — ✅4 ◐1 ○4
|
||||||
|
|
||||||
|
| 功能点 | 优先级 | 当前状态 | 缺失内容 |
|
||||||
|
|--------|--------|---------|---------|
|
||||||
|
| F05 JSON 模板 | P1 | ◐ | CRUD ✅, Schema 校验未关联 UI |
|
||||||
|
| F06 发布记录 | P1 | ○ | 未实现 |
|
||||||
|
| F07 控制台链接 | P1 | ○ | 未实现 |
|
||||||
|
| F08 版本矩阵 | P2 | ○ | 未开始 |
|
||||||
|
| F09 变更分析 | P2 | ○ | 未开始 |
|
||||||
|
|
||||||
|
### M7 设备与终端 — ✅1 ◐3 ○2
|
||||||
|
|
||||||
|
| 功能点 | 优先级 | 当前状态 | 缺失内容 |
|
||||||
|
|--------|--------|---------|---------|
|
||||||
|
| F01 设备登记 | P1 | ◐ | 登记/列表 ✅, 字段覆盖待确认 |
|
||||||
|
| F02 SN 绑定历史 | P1 | ◐ | 时间线已实现 |
|
||||||
|
| F03 换机审批 | P1 | ◐ | swap-request 端点就绪, 审批流未实现 |
|
||||||
|
| F05 Callback 联动 | P1 | ○ | 未实现 |
|
||||||
|
| F06 并发策略 | P2 | ○ | 未开始 |
|
||||||
|
|
||||||
|
### M8 通知与待办 — ✅0 ◐3 ○2
|
||||||
|
|
||||||
|
| 功能点 | 优先级 | 当前状态 | 缺失内容 |
|
||||||
|
|--------|--------|---------|---------|
|
||||||
|
| F01 待办列表 | P1 | ◐ | 待办中心 ✅, 自动化待办生成未接入 |
|
||||||
|
| F02 认领完成 | P1 | ◐ | 状态流转 ✅, 备注功能待补 |
|
||||||
|
| F03 邮件/企微 | P1 | ◐ | 配置 UI ✅, 实际发送未接入 |
|
||||||
|
| F04 通知模板 | P2 | ○ | 未实现 |
|
||||||
|
| F05 静默规则 | P2 | ○ | 未开始 |
|
||||||
|
|
||||||
|
### M9 报表与对账 — ✅0 ◐5 ○1
|
||||||
|
|
||||||
|
| 功能点 | 优先级 | 当前状态 | 缺失内容 |
|
||||||
|
|--------|--------|---------|---------|
|
||||||
|
| F01 合同 SN 视图 | P1 | ◐ | 已上线, 数据维度待确认 |
|
||||||
|
| F02 激活视图 | P1 | ○ | 未专门实现 |
|
||||||
|
| F03 Callback 统计 | P1 | ◐ | 已上线 |
|
||||||
|
| F04 导出按钮 | P1 | ◐ | 后端 `/export` 就绪, 前端缺按钮 |
|
||||||
|
| F05 项目健康度 | P2 | ◐ | 已上线, 规则可配置性待确认 |
|
||||||
|
| F06 订阅报表 | P2 | ◐ | 已上线, 推送逻辑待确认 |
|
||||||
|
|
||||||
|
### M10 审计与合规 — ✅1 ◐2 ○1
|
||||||
|
|
||||||
|
| 功能点 | 优先级 | 当前状态 | 缺失内容 |
|
||||||
|
|--------|--------|---------|---------|
|
||||||
|
| F02 审计检索 | P1 | ◐ | AuditSearchView ✅, 端点有 500 错误需调试 |
|
||||||
|
| F03 审计导出 | P2 | ○ | 未实现 |
|
||||||
|
| F04 留存策略 | P2 | ◐ | AuditRetentionView ✅ |
|
||||||
|
|
||||||
|
### M11 身份/访问/平台 — ✅7 ◐3 ○11
|
||||||
|
|
||||||
|
| 功能点 | 优先级 | 当前状态 | 缺失内容 |
|
||||||
|
|--------|--------|---------|---------|
|
||||||
|
| F03 空闲超时 | P0 | ◐ | 前端 idleTimer 已实现, 后端会话管理未完成 |
|
||||||
|
| F05 失败锁定 | P0 | ✅ | 后端已有 5 次/15 分钟锁定 |
|
||||||
|
| F07 密码修改 | P0 | ✅ | Profile 页弹窗+后端端点 |
|
||||||
|
| F08 密码重置 | P1 | ✅ | 后端端点已实现(非空操作) |
|
||||||
|
| F09 SSO/OIDC | P1 | ○ | 未开始 |
|
||||||
|
| F11 并发会话 | P1 | ○ | 未开始(JWT 无状态) |
|
||||||
|
| F12 强制下线 | P1 | ◐ | 端点已实现, 需前端 UI |
|
||||||
|
| F14 用户管理 | P0 | ◐ | 表已创建, 管理页面未实现 |
|
||||||
|
| F15 角色定义 | P0 | ◐ | 4 角色已落地, 产品定义 10+ 需补齐 |
|
||||||
|
| F16 权限码 | P0 | ◐ | 路由级 ✅, 按钮级 `v-permission` 未全覆盖 |
|
||||||
|
| F17 数据范围 | P2 | ○ | 未开始 |
|
||||||
|
| F20 系统参数 | P1 | ◐ | SystemParamsView ✅, localStorage MVP |
|
||||||
|
| F21 敏感操作留痕 | P1 | ○ | 未开始 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 已知 API 500 错误(需修复)
|
||||||
|
|
||||||
|
| 端点 | 影响模块 | 问题 |
|
||||||
|
|------|---------|------|
|
||||||
|
| `GET /api/v1/audit-events` | M10-F02 | 500 内部错误 |
|
||||||
|
| `GET /api/v1/integration/id-mappings` | M6-F03 | 500 内部错误 |
|
||||||
|
| `GET /api/v1/integration/feature-mappings` | M6-F04 | 500 内部错误 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 总结
|
||||||
|
|
||||||
|
| 版本 | PRD 范围 | 实现状态 |
|
||||||
|
|------|---------|---------|
|
||||||
|
| **MVP** (I1~I9) | M1-M6 P0 + M10-F01 + M11 基础 | ✅ 主链路已完成 |
|
||||||
|
| **Mid** (I10~I13) | M7-M9 + P0补齐 + P1增强 | 🕐 M7/M8/M9 前端超前上线, 后端逻辑待补 |
|
||||||
|
| **Full** (V2.0) | 安全/合规/规模化 | 📋 全部未开始 |
|
||||||
|
|
||||||
|
**当前最优先缺口:**
|
||||||
|
1. 修复 3 个 500 错误 (audit-events, id-mappings, feature-mappings)
|
||||||
|
2. M11-F14 用户管理页面 (表已就绪)
|
||||||
|
3. M8-F03 通知发送实际对接
|
||||||
@@ -0,0 +1,176 @@
|
|||||||
|
# UI 交互模式复盘 — 导航/弹框一致性 + 侧栏分组
|
||||||
|
|
||||||
|
**日期:** 2026-05-27
|
||||||
|
**范围:** 全部 38 个 Vue 视图
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 当前 CRUD 模式盘点
|
||||||
|
|
||||||
|
| 页面 | Create | Edit | Detail | 评估 |
|
||||||
|
|------|--------|------|--------|------|
|
||||||
|
| **客户管理** | 弹框 `el-dialog` | 弹框 | 独立页面 | ✅ 一致 |
|
||||||
|
| **项目管理** | 弹框 `el-dialog` | 弹框 | 无独立详情(干系人管理在弹框) | ✅ 一致 |
|
||||||
|
| **合同管理** | 向导页 `/contracts/new` | 详情页内编辑 | 独立详情页 | ✅ 合理(多步) |
|
||||||
|
| **交付管理** | 向导页 `/deliveries/new` | 详情页内编辑 | 独立详情页 | ✅ 合理(多步) |
|
||||||
|
| **许可 SN** | 向导页 `/licenses/sn/new` | 详情页内编辑 | 独立详情页 | ✅ 合理 |
|
||||||
|
| **Callback** | 无(来源Webhook) | 详情页内操作 | 独立详情页 | ✅ |
|
||||||
|
| **设备管理** | 弹框 | 详情页内编辑 | 独立详情页 | ✅ 一致 |
|
||||||
|
| **用户管理** | 弹框 | 弹框 | 无独立详情 | ✅ 一致 |
|
||||||
|
|
||||||
|
### 结论:当前模式基本合理
|
||||||
|
|
||||||
|
| 场景 | 推荐模式 | 示例 |
|
||||||
|
|------|---------|------|
|
||||||
|
| **简单表单 (≤5 字段)** | **弹框** `el-dialog` | 客户、项目、设备、用户 |
|
||||||
|
| **复杂表单/向导 (≥2 步)** | **独立页面** | 合同向导、交付向导、SN 向导 |
|
||||||
|
| **详情展示** | **独立页面** | 合同详情、交付详情、SN 详情、设备详情 |
|
||||||
|
| **关联数据管理** | **弹框** | 项目干系人、SN 批量导入、合同明细行 |
|
||||||
|
|
||||||
|
**不建议强行统一为弹框** — 合同向导有 3 步(选择客户项目→填写信息→明细行),用弹框会撑爆视口。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 发现的不一致问题
|
||||||
|
|
||||||
|
| # | 问题 | 当前行为 | 建议 |
|
||||||
|
|---|------|---------|------|
|
||||||
|
| 1 | **合同列表行操作** | 「详情」跳转详情页,无直接「编辑」入口 | 详情页支持编辑即可(已有) |
|
||||||
|
| 2 | **交付列表行操作** | 同上 | 同上 |
|
||||||
|
| 3 | **SN 列表行操作** | 同上 | 同上 |
|
||||||
|
| 4 | **客户列表行操作** | 「详情」「编辑」「冻结」「删除」都在列表页 | ✅ 正确 |
|
||||||
|
| 5 | **设备列表无编辑** | 只有「详情」→详情页编辑 | 详情页已支持编辑,可接受 |
|
||||||
|
| 6 | **项目无独立详情页** | 编辑、干系人都在列表弹框 | 对于简单实体,弹框够用 ✅ |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 侧栏分组实现方案
|
||||||
|
|
||||||
|
### 当前结构(扁平)
|
||||||
|
|
||||||
|
```
|
||||||
|
📊 首页
|
||||||
|
👥 客户管理
|
||||||
|
📋 合同管理
|
||||||
|
📦 交付管理
|
||||||
|
🔑 许可 SN
|
||||||
|
🛡️ 许可证管理
|
||||||
|
📨 Callback 收件箱
|
||||||
|
🌐 集成环境
|
||||||
|
📱 产品线
|
||||||
|
🖥️ 设备管理
|
||||||
|
🔔 待办中心
|
||||||
|
📊 报表中心
|
||||||
|
📧 报表订阅
|
||||||
|
👤 用户管理
|
||||||
|
```
|
||||||
|
|
||||||
|
### 目标结构(三级分组)
|
||||||
|
|
||||||
|
```
|
||||||
|
📊 首页
|
||||||
|
──── 业务管理 ────
|
||||||
|
👥 客户管理
|
||||||
|
📋 合同管理
|
||||||
|
📦 交付管理
|
||||||
|
🔑 许可 SN
|
||||||
|
🛡️ 许可证管理
|
||||||
|
──── 运营管理 ────
|
||||||
|
📨 Callback 收件箱
|
||||||
|
🌐 集成环境
|
||||||
|
📱 产品线
|
||||||
|
🖥️ 设备管理
|
||||||
|
🔔 待办中心
|
||||||
|
──── 分析管理 ────
|
||||||
|
📊 报表中心
|
||||||
|
📧 报表订阅
|
||||||
|
──── 系统管理 ────
|
||||||
|
⚙️ 系统参数
|
||||||
|
👤 用户管理
|
||||||
|
🔐 审计日志
|
||||||
|
```
|
||||||
|
|
||||||
|
### 实现方式
|
||||||
|
|
||||||
|
**方案:** 修改 `MainLayout.vue` 中的 `menuItems` 数组为分组结构,模板中循环渲染组。
|
||||||
|
|
||||||
|
#### Step 1: 改造 menuItems 数据结构
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const menuGroups = [
|
||||||
|
{
|
||||||
|
label: '业务管理',
|
||||||
|
items: [
|
||||||
|
{ path: "/customers", icon: "👥", label: "客户管理", roles: ["SYS_ADMIN","SALES"] },
|
||||||
|
{ path: "/contracts", icon: "📋", label: "合同管理", roles: ["SYS_ADMIN","SALES"] },
|
||||||
|
{ path: "/deliveries", icon: "📦", label: "交付管理", roles: ["SYS_ADMIN","SALES","DELIVERY"] },
|
||||||
|
{ path: "/licenses/sn", icon: "🔑", label: "许可 SN", roles: ["SYS_ADMIN","SALES"] },
|
||||||
|
{ path: "/licenses", icon: "🛡️", label: "许可证管理", roles: ["SYS_ADMIN","SALES"] },
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '运营管理',
|
||||||
|
items: [
|
||||||
|
{ path: "/callbacks", icon: "📨", label: "Callback 收件箱", roles: ["SYS_ADMIN","LICENSE_OPS"] },
|
||||||
|
{ path: "/integration/environments", icon: "🌐", label: "集成环境", roles: ["SYS_ADMIN","SALES","LICENSE_OPS"] },
|
||||||
|
{ path: "/integration/product-lines", icon: "📱", label: "产品线", roles: ["SYS_ADMIN","SALES","LICENSE_OPS"] },
|
||||||
|
{ path: "/devices", icon: "🖥️", label: "设备管理", roles: ["SYS_ADMIN","SALES","DELIVERY"] },
|
||||||
|
{ path: "/todos", icon: "🔔", label: "待办中心", roles: ["SYS_ADMIN","SALES","LICENSE_OPS"] },
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '分析管理',
|
||||||
|
items: [
|
||||||
|
{ path: "/reports/contract-sn", icon: "📊", label: "报表中心", roles: ["SYS_ADMIN"] },
|
||||||
|
{ path: "/reports/subscriptions", icon: "📧", label: "报表订阅", roles: ["SYS_ADMIN"] },
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '系统管理',
|
||||||
|
items: [
|
||||||
|
{ path: "/audit", icon: "🔐", label: "审计日志", roles: ["SYS_ADMIN"] },
|
||||||
|
{ path: "/admin/params", icon: "⚙️", label: "系统参数", roles: ["SYS_ADMIN"] },
|
||||||
|
{ path: "/admin/users", icon: "👤", label: "用户管理", roles: ["SYS_ADMIN"] },
|
||||||
|
]
|
||||||
|
},
|
||||||
|
];
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Step 2: 改造模板渲染
|
||||||
|
|
||||||
|
```html
|
||||||
|
<aside class="app-sidebar">
|
||||||
|
<div class="sidebar-section-label">📊 首页</div>
|
||||||
|
<div class="sidebar-item" :class="{ active: isActive(homeItem) }" @click="$router.push(homeItem.path)">
|
||||||
|
<span class="sidebar-item-icon">{{ homeItem.icon }}</span>
|
||||||
|
<span class="sidebar-item-text">{{ homeItem.label }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<template v-for="group in visibleGroups" :key="group.label">
|
||||||
|
<div class="sidebar-group-label">{{ group.label }}</div>
|
||||||
|
<div
|
||||||
|
v-for="item in group.items"
|
||||||
|
:key="item.path"
|
||||||
|
:class="['sidebar-item', { active: isActive(item) }]"
|
||||||
|
@click="$router.push(item.path)"
|
||||||
|
>
|
||||||
|
<span class="sidebar-item-icon">{{ item.icon }}</span>
|
||||||
|
<span class="sidebar-item-text">{{ item.label }}</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<div class="sidebar-footer">CraftLabs Platform v0.1.0</div>
|
||||||
|
</aside>
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Step 3: CSS 调整
|
||||||
|
|
||||||
|
```css
|
||||||
|
.sidebar-group-label {
|
||||||
|
font-size: 11px;
|
||||||
|
color: #999;
|
||||||
|
padding: 12px 16px 4px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
}
|
||||||
|
```
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
# 工程元数据(Engineering)
|
||||||
|
|
||||||
|
本目录存放 **工作区边界说明** 与 **规划中的独立工程占位**,不引入额外构建步骤。
|
||||||
|
|
||||||
|
|
||||||
|
| 文件/路径 | 说明 |
|
||||||
|
| ---------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ |
|
||||||
|
| `[workspace-manifest.json](workspace-manifest.json)` | 机器可读:当前组件路径、建议独立仓库列表 |
|
||||||
|
| `[planned/](planned/)` | 规划工程 README 占位(Webhook、平台 API、平台 UI) |
|
||||||
|
| **系统架构全景(C4、时序、全量组件表、§9 安全架构)** | `[../docs/engineering/SYSTEM_ARCHITECTURE.md](../docs/engineering/SYSTEM_ARCHITECTURE.md)` |
|
||||||
|
| **OpenAPI 契约(SSOT)** | `[../contracts/README.md](../contracts/README.md)` · `openapi/delivery-platform-api.json` |
|
||||||
|
| **平台部署 Runbook** | `[../services/RUNBOOK.md](../services/RUNBOOK.md)` |
|
||||||
|
| 详细架构走查 | `[../docs/engineering/WORKSPACE_ENGINEERING_LAYOUT.md](../docs/engineering/WORKSPACE_ENGINEERING_LAYOUT.md)` |
|
||||||
|
| 三轨并行迭代(后端 / 前端 / SDK) | `[../docs/engineering/PARALLEL_ITERATION_INDEX.md](../docs/engineering/PARALLEL_ITERATION_INDEX.md)` · `[../docs/engineering/tracks/](../docs/engineering/tracks/)` |
|
||||||
|
|
||||||
|
|
||||||
|
**原则**:**授权 SDK**(`java/`、`native/`)与 **商业交付平台**(Spring + Vue)**分仓或分流水线**,避免服务端依赖 JNI/native 与客户端库混用。
|
||||||
|
|
||||||
|
**部署形态**:平台/Webhook 后端采用 **多模块开发 + 单 Fat JAR**(仅 bootstrap 模块 `repackage`);SDK 侧为 **多枚 library jar + native**,**不打** Spring Boot 可执行包 —— 详见 [WORKSPACE_ENGINEERING_LAYOUT.md §5](../docs/engineering/WORKSPACE_ENGINEERING_LAYOUT.md)。
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
# 规划中的工程(占位)
|
||||||
|
|
||||||
|
以下目录 **仅含 README**,用于对齐 [工作区工程划分文档](../../docs/engineering/WORKSPACE_ENGINEERING_LAYOUT.md)。
|
||||||
|
**实现时**:建议在独立 Git 仓库中创建项目,并将本仓库 **`schemas/`** 以 submodule、复制或 Maven/Gradle 资源依赖方式对齐。
|
||||||
|
|
||||||
|
| 子目录 | 规划职责 |
|
||||||
|
|--------|----------|
|
||||||
|
| [`license-webhook-ingress`](license-webhook-ingress) | 比特规则 Callback 接入 |
|
||||||
|
| [`delivery-platform-api`](delivery-platform-api) | 客户商务与交付管理平台后端 |
|
||||||
|
| [`delivery-platform-ui`](delivery-platform-ui) | 管理端前端 |
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
# delivery-platform-api(规划)
|
||||||
|
|
||||||
|
## 职责
|
||||||
|
|
||||||
|
实现 [客户商务与交付管理平台](../../../docs/chuangfei-platform-product-modules.md) 中 **M1–M11** 领域 API(客户、项目、合同、交付、SN、Callback 处置、集成配置、设备、待办、报表、审计、身份与权限等)。
|
||||||
|
|
||||||
|
## 建议技术栈
|
||||||
|
|
||||||
|
- **Spring Boot 4.0.\***、Spring Security、**PostgreSQL 15**、**MyBatis-Plus**、Flyway/Liquibase。
|
||||||
|
- OpenAPI(springdoc);与 `license-webhook-ingress` 通过 **MQ/内部 API** 协作。
|
||||||
|
|
||||||
|
## 构建与部署:多模块开发 + 单 JAR 运维
|
||||||
|
|
||||||
|
与 **客户端 SDK** 严格区分(详见 [WORKSPACE_ENGINEERING_LAYOUT.md §5](../../../docs/engineering/WORKSPACE_ENGINEERING_LAYOUT.md)):
|
||||||
|
|
||||||
|
| 模式 | 做法 |
|
||||||
|
|------|------|
|
||||||
|
| **开发** | Maven **多模块**:`domain`、`application-service`、`adapters-web`、`adapters-persistence`、**`bootstrap`(唯一 main)** |
|
||||||
|
| **部署** | 仅 **`bootstrap`** 模块启用 `spring-boot-maven-plugin` **`repackage`**,产出 **一枚** 可执行 Fat JAR(`java -jar platform-bootstrap.jar`) |
|
||||||
|
| **构建命令示例** | `mvn -pl platform-bootstrap -am clean package` |
|
||||||
|
|
||||||
|
**禁止**:多个模块同时 `repackage`;在平台任意模块依赖 **`cn.craftlabs:craftlabs-auth-bitanswer`**(客户端 JNI SDK)。
|
||||||
|
|
||||||
|
## 与本工作区关系
|
||||||
|
|
||||||
|
- **可选**:将 [`schemas/craftlabs-auth-config.schema.json`](../../../schemas/craftlabs-auth-config.schema.json) 作为 **校验导出给现场的 JSON** 的资源(拷贝或构建时嵌入);**不**为引入 SDK 而把 `craftlabs-auth-parent` 作为父 POM。
|
||||||
|
- 若需共享纯配置 POJO:单独 **`craftlabs-auth-config-model`** 薄模块(无 native、无 `System.loadLibrary`),由 SDK 与平台 **分别** 以 **compileOnly/optional** 或复制方式对齐 —— 须经架构评审。
|
||||||
|
|
||||||
|
## 仓库建议名
|
||||||
|
|
||||||
|
`craftlabs-delivery-platform`
|
||||||
|
|
||||||
|
## Maven 坐标建议
|
||||||
|
|
||||||
|
- GroupId:如 `cn.craftlabs.platform`(与 `cn.craftlabs:craftlabs-auth-*` **区分**)
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
# delivery-platform-ui(规划)
|
||||||
|
|
||||||
|
## 职责
|
||||||
|
|
||||||
|
客户商务与交付管理平台 **SPA**:Vue 3 + Vite;对接 `delivery-platform-api` 的 OpenAPI;实现 [产品模块文档](../../../docs/chuangfei-platform-product-modules.md) 所列界面与权限(路由 meta + 后端 RBAC)。
|
||||||
|
|
||||||
|
## 与本工作区关系
|
||||||
|
|
||||||
|
- 无代码依赖;**文档与排期**见 [BPM 与 Roadmap](../../../docs/chuangfei-platform-bpm-and-roadmap.md)。
|
||||||
|
|
||||||
|
## 仓库建议名
|
||||||
|
|
||||||
|
`craftlabs-delivery-platform-ui`
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
# license-webhook-ingress(规划)
|
||||||
|
|
||||||
|
## 职责
|
||||||
|
|
||||||
|
- 接收比特授权云 **规则 Callback**(HTTPS POST + JSON)。
|
||||||
|
- 校验 `x-bitanswer-token`(或约定头);**快速返回 2xx**;正文异步处理。
|
||||||
|
- 将事件写入 **消息队列** 或 **inbox 表**(与平台一致:**PostgreSQL 15** + **MyBatis-Plus**),由 `delivery-platform-api` 消费(或同仓 Worker 模块)。
|
||||||
|
|
||||||
|
## 建议技术栈
|
||||||
|
|
||||||
|
- **Java 21+**(与团队 JDK LTS 一致)、**Spring Boot 4.0.\***。
|
||||||
|
- 无 UI;可选 Spring Cloud Gateway 前置,或裸 Tomcat/Jetty + Actuator。
|
||||||
|
|
||||||
|
## 与本工作区关系
|
||||||
|
|
||||||
|
- **不依赖** `craftlabs-auth-bitanswer` native。
|
||||||
|
- 事件 Schema 对齐 [比特规则文档](../../../docs/bitanswer-licensing-design-and-rules.md) §5 与 [Callback 评估](../../../docs/bitanswer-callbacks-endpoints-assessment.md)。
|
||||||
|
|
||||||
|
## 构建与部署
|
||||||
|
|
||||||
|
- 与平台 API 相同策略:**Maven 多模块** + **唯一 `bootstrap` 模块** `spring-boot-maven-plugin` **repackage** → **单枚** `webhook-bootstrap.jar` 部署。
|
||||||
|
- **不依赖** `craftlabs-auth-bitanswer`。
|
||||||
|
|
||||||
|
## 仓库建议名
|
||||||
|
|
||||||
|
`craftlabs-license-webhook`(可改名;与 `workspace-manifest.json` 中 `plannedRepositories` 保持一致即可)。
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
|
"$id": "https://craftlabs.cn/engineering/workspace-manifest.json",
|
||||||
|
"title": "craftlabs-authorization-sdk workspace manifest",
|
||||||
|
"workspace": "craftlabs-authorization-sdk",
|
||||||
|
"description": "客户端授权 SDK 与契约资产;商业交付平台与 Webhook 建议独立仓库,见 engineering/planned/",
|
||||||
|
"components": [
|
||||||
|
{
|
||||||
|
"id": "sdk-java",
|
||||||
|
"path": "java",
|
||||||
|
"type": "maven-multi-module",
|
||||||
|
"artifactId": "craftlabs-auth-parent",
|
||||||
|
"build": "mvn -f java/pom.xml verify"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "sdk-native",
|
||||||
|
"path": "native",
|
||||||
|
"type": "cmake",
|
||||||
|
"primaryTarget": "craftlabs_auth_bitanswer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "auth-config-schema",
|
||||||
|
"path": "schemas",
|
||||||
|
"type": "json-schema",
|
||||||
|
"mainFile": "schemas/craftlabs-auth-config.schema.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "examples",
|
||||||
|
"path": "examples",
|
||||||
|
"type": "samples"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "documentation",
|
||||||
|
"path": "docs",
|
||||||
|
"type": "markdown"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "engineering-meta",
|
||||||
|
"path": "engineering",
|
||||||
|
"type": "workspace-metadata"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"deploymentPrinciples": {
|
||||||
|
"clientSdk": "多枚 library JAR + native 动态库;不使用 spring-boot-maven-plugin repackage",
|
||||||
|
"platformBackend": "Maven 多模块开发;仅 bootstrap 模块 spring-boot-maven-plugin repackage 产出单枚可执行 Fat JAR;classpath 禁止 craftlabs-auth-bitanswer"
|
||||||
|
},
|
||||||
|
"plannedRepositories": [
|
||||||
|
{
|
||||||
|
"id": "license-webhook-ingress",
|
||||||
|
"suggestedName": "craftlabs-license-webhook",
|
||||||
|
"stack": "Spring Boot 4.0.x",
|
||||||
|
"role": "BitAnswer rules Callback HTTPS ingress, token verify, MQ enqueue",
|
||||||
|
"stubPath": "engineering/planned/license-webhook-ingress",
|
||||||
|
"deployArtifact": "单 Fat JAR(webhook-bootstrap 唯一 repackage)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "delivery-platform-api",
|
||||||
|
"suggestedName": "craftlabs-delivery-platform",
|
||||||
|
"stack": "Spring Boot 4.0.x",
|
||||||
|
"role": "Customer, contract, delivery, SN, M5–M11 domains",
|
||||||
|
"stubPath": "engineering/planned/delivery-platform-api",
|
||||||
|
"deployArtifact": "单 Fat JAR(platform-bootstrap 唯一 repackage)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "delivery-platform-ui",
|
||||||
|
"suggestedName": "craftlabs-delivery-platform-ui",
|
||||||
|
"stack": "Vue 3 + Vite",
|
||||||
|
"role": "Management console SPA",
|
||||||
|
"stubPath": "engineering/planned/delivery-platform-ui"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"schemaVersion": 1,
|
||||||
|
"provider": "bitanswer",
|
||||||
|
"scenario": "floating",
|
||||||
|
"bitanswer": {
|
||||||
|
"url": "https://cloud.bitanswer.example/e3",
|
||||||
|
"loginMode": "REMOTE"
|
||||||
|
},
|
||||||
|
"features": {
|
||||||
|
"face": { "bitanswerFeatureId": 301 }
|
||||||
|
},
|
||||||
|
"floating": {
|
||||||
|
"projectId": "migrant-flow-prj-2026-001",
|
||||||
|
"projectName": "某市流动人口人像核验",
|
||||||
|
"contractRef": "PO-2026-8848"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"schemaVersion": 1,
|
||||||
|
"provider": "bitanswer",
|
||||||
|
"scenario": "school",
|
||||||
|
"bitanswer": {
|
||||||
|
"url": "https://cloud.bitanswer.example/e3",
|
||||||
|
"loginMode": "AUTO",
|
||||||
|
"sn": ""
|
||||||
|
},
|
||||||
|
"features": {
|
||||||
|
"face": { "bitanswerFeatureId": 201 },
|
||||||
|
"expression": { "bitanswerFeatureId": 202 }
|
||||||
|
},
|
||||||
|
"school": {
|
||||||
|
"edgeDeviceId": "classroom-gate-01",
|
||||||
|
"tenantId": "school-district-demo"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"schemaVersion": 1,
|
||||||
|
"provider": "selfhosted",
|
||||||
|
"scenario": "school",
|
||||||
|
"selfhosted": {
|
||||||
|
"baseUrl": "https://license.internal.example/api/v1",
|
||||||
|
"tenantKey": "district-west",
|
||||||
|
"offlineGraceDays": 7,
|
||||||
|
"heartbeatIntervalHours": 24
|
||||||
|
},
|
||||||
|
"features": {
|
||||||
|
"face": { "selfhostedFeatureKey": "face" },
|
||||||
|
"expression": { "selfhostedFeatureKey": "expression" }
|
||||||
|
},
|
||||||
|
"school": {
|
||||||
|
"edgeDeviceId": "gate-02"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"schemaVersion": 1,
|
||||||
|
"provider": "bitanswer",
|
||||||
|
"scenario": "wharf",
|
||||||
|
"bitanswer": {
|
||||||
|
"url": "bit://license.example.com:8273",
|
||||||
|
"loginMode": "AUTO",
|
||||||
|
"rootPath": "/var/lib/craftlabs/bitanswer"
|
||||||
|
},
|
||||||
|
"features": {
|
||||||
|
"container_number_detect": { "bitanswerFeatureId": 101 },
|
||||||
|
"container_number_recognize": { "bitanswerFeatureId": 102 },
|
||||||
|
"iso_detect": { "bitanswerFeatureId": 103 },
|
||||||
|
"iso_recognize": { "bitanswerFeatureId": 104 }
|
||||||
|
},
|
||||||
|
"wharf": {
|
||||||
|
"topology": "group",
|
||||||
|
"groupServiceUrl": "bit://license.example.com:8273",
|
||||||
|
"notes": "边设备集中连集团服务;特征项与控制台产品一致后替换 id。"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +1,14 @@
|
|||||||
import cn.craftlabs.auth.AuthProvider;
|
import cn.craftlabs.auth.AuthProvider;
|
||||||
import cn.craftlabs.auth.AuthResult;
|
import cn.craftlabs.auth.AuthResult;
|
||||||
import cn.craftlabs.auth.bitanswer.BitAnswerProvider;
|
import cn.craftlabs.auth.bitanswer.BitAnswerProvider;
|
||||||
|
import cn.craftlabs.auth.config.AuthConfig;
|
||||||
|
import cn.craftlabs.auth.config.AuthConfigException;
|
||||||
|
import cn.craftlabs.auth.config.AuthConfigs;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 演示:通过 {@link BitAnswerProvider} 完成初始化与许可校验。
|
* 演示:校验 {@code config_json} 后初始化并完成许可校验。
|
||||||
|
*
|
||||||
|
* <p>编译时需将 {@code craftlabs-auth-core} 与 Jackson 置于 classpath(与 Maven 模块依赖一致)。
|
||||||
*
|
*
|
||||||
* <p>版权所有 © 广州创飞人工智能技术有限公司
|
* <p>版权所有 © 广州创飞人工智能技术有限公司
|
||||||
*
|
*
|
||||||
@@ -11,8 +16,30 @@ import cn.craftlabs.auth.bitanswer.BitAnswerProvider;
|
|||||||
*/
|
*/
|
||||||
public class ExampleApp {
|
public class ExampleApp {
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
|
String json =
|
||||||
|
"""
|
||||||
|
{
|
||||||
|
"schemaVersion": 1,
|
||||||
|
"provider": "bitanswer",
|
||||||
|
"scenario": "school",
|
||||||
|
"bitanswer": { "url": "https://example.invalid/bitanswer", "loginMode": "AUTO" },
|
||||||
|
"features": {
|
||||||
|
"face": { "bitanswerFeatureId": 201 },
|
||||||
|
"expression": { "bitanswerFeatureId": 202 }
|
||||||
|
},
|
||||||
|
"school": { "edgeDeviceId": "demo-edge-01", "tenantId": "demo-tenant" }
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
try {
|
||||||
|
AuthConfig cfg = AuthConfigs.parse(json);
|
||||||
|
System.out.println("scenario=" + cfg.scenario() + ", face id=" + cfg.bitanswerFeatureId("face"));
|
||||||
|
} catch (AuthConfigException e) {
|
||||||
|
System.err.println("config invalid: " + e.getMessage());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try (AuthProvider p = new BitAnswerProvider()) {
|
try (AuthProvider p = new BitAnswerProvider()) {
|
||||||
AuthResult r = p.initialize("{}");
|
AuthResult r = p.initialize(json);
|
||||||
System.out.println("init: " + r.isSuccess() + " " + r.getMessage());
|
System.out.println("init: " + r.isSuccess() + " " + r.getMessage());
|
||||||
r = p.checkLicense();
|
r = p.checkLicense();
|
||||||
System.out.println("check: " + r.isSuccess() + " " + r.getMessage());
|
System.out.println("check: " + r.isSuccess() + " " + r.getMessage());
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
# JAVA SDK
|
||||||
|
|
||||||
|
**Part of:** craftlabs-authorization-sdk
|
||||||
|
**Build:** Maven multi-module (JDK 17+)
|
||||||
|
|
||||||
|
## STRUCTURE
|
||||||
|
|
||||||
|
```
|
||||||
|
java/
|
||||||
|
├── craftlabs-auth-core/ # Core auth: config parsing, internal logic
|
||||||
|
├── craftlabs-auth-bitanswer/ # Bitanswer cloud licensing provider
|
||||||
|
├── craftlabs-auth-selfhosted/ # Self-hosted licensing provider
|
||||||
|
├── craftlabs-auth-tests/ # Integration tests
|
||||||
|
├── pom.xml # Parent aggregator POM
|
||||||
|
└── RELEASING.md # Release checklist & GPG signing guide
|
||||||
|
```
|
||||||
|
|
||||||
|
## WHERE TO LOOK
|
||||||
|
|
||||||
|
| Task | Location | Notes |
|
||||||
|
|------|----------|-------|
|
||||||
|
| Config model / schema | `craftlabs-auth-core/src/main/java/cn/craftlabs/auth/config/` | 9 files, config POJOs |
|
||||||
|
| Internal engine | `craftlabs-auth-core/src/main/java/cn/craftlabs/auth/internal/` | 3 files, core logic |
|
||||||
|
| Bitanswer provider | `craftlabs-auth-bitanswer/` | Single class, wraps bitanswer API |
|
||||||
|
| Selfhosted provider | `craftlabs-auth-selfhosted/` | Single class, local validation |
|
||||||
|
| Tests | `craftlabs-auth-tests/` | Config test cases |
|
||||||
|
| Release scripts | `scripts/sdk-release-checksums.sh` | SHA256 + GPG |
|
||||||
|
|
||||||
|
## CONVENTIONS
|
||||||
|
|
||||||
|
- **Package**: `cn.craftlabs.auth.*` (SDK) — distinct from `cn.craftlabs.platform.*` (backend)
|
||||||
|
- **No Spring Boot**: SDK modules are plain Java, no framework dependency
|
||||||
|
- **JNA bridge**: Java calls Rust native via JNA (not JNI)
|
||||||
|
- **Maven**: Parent POM at `java/pom.xml` aggregates sub-modules
|
||||||
|
|
||||||
|
## ANTI-PATTERNS
|
||||||
|
|
||||||
|
- **Do not** add Spring Boot / platform dependencies to SDK modules (keep plain Java)
|
||||||
|
- **Do not** put bitanswer/selfhosted specific logic into core module
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
# CraftLabs 授权 SDK — 发布与完整性
|
||||||
|
|
||||||
|
对齐架构文档 [SYSTEM_ARCHITECTURE §9.8](../docs/engineering/SYSTEM_ARCHITECTURE.md):官方渠道、**SHA-256 清单**、**GPG 签名(建议)**、`java` 与 `native` **同 Git tag**。
|
||||||
|
|
||||||
|
## 1. 发布前检查
|
||||||
|
|
||||||
|
- [ ] `mvn -f java/pom.xml verify` 通过(JDK 17+)。
|
||||||
|
- [ ] `native` 已在各目标平台完成构建,且与本次 **同一 tag** 一并交付。
|
||||||
|
- [ ] `CHANGELOG`(或发布说明)写明 **比特 SDK / 运行时** 兼容版本。
|
||||||
|
|
||||||
|
## 2. 生成 SHA256SUMS(必做)
|
||||||
|
|
||||||
|
在仓库根目录执行(会先 `mvn -DskipTests package`):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
chmod +x scripts/sdk-release-checksums.sh
|
||||||
|
./scripts/sdk-release-checksums.sh --output dist/sdk-release
|
||||||
|
```
|
||||||
|
|
||||||
|
已构建过时跳过 Maven:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./scripts/sdk-release-checksums.sh --no-mvn --output dist/sdk-release
|
||||||
|
```
|
||||||
|
|
||||||
|
把 **本机构建出的 Native** 一并写入清单(路径相对于仓库根,便于客户校验):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./scripts/sdk-release-checksums.sh --output dist/sdk-release --native-path "$(pwd)/native/build"
|
||||||
|
```
|
||||||
|
|
||||||
|
含完整测试的构建后再生成清单:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./scripts/sdk-release-checksums.sh --verify --output dist/sdk-release
|
||||||
|
```
|
||||||
|
|
||||||
|
输出:
|
||||||
|
|
||||||
|
- `dist/sdk-release/SHA256SUMS` — 每行:`哈希 相对路径`
|
||||||
|
- `dist/sdk-release/RELEASE-MANIFEST.txt` — 提交 SHA、UTC 时间
|
||||||
|
|
||||||
|
**客户校验**(在克隆/解压后的仓库根或同目录结构下):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sha256sum -c dist/sdk-release/SHA256SUMS
|
||||||
|
```
|
||||||
|
|
||||||
|
macOS:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
shasum -a 256 -c dist/sdk-release/SHA256SUMS
|
||||||
|
```
|
||||||
|
|
||||||
|
对 `SHA256SUMS` 本身做 **分离签名**(本机已配置 GPG):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
SIGN=1 ./scripts/sdk-release-checksums.sh --no-mvn --output dist/sdk-release
|
||||||
|
```
|
||||||
|
|
||||||
|
生成 `SHA256SUMS.asc`;客户使用公布的公钥:`gpg --verify SHA256SUMS.asc SHA256SUMS`。
|
||||||
|
|
||||||
|
## 3. Maven JAR 的 GPG 签名(强烈建议)
|
||||||
|
|
||||||
|
父 POM 已配置 `maven-gpg-plugin`,**默认跳过**(`gpg.skip=true`),不影响日常 `verify`。
|
||||||
|
|
||||||
|
发布前在已导入私钥的机器上:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
gpg --version # 确认可用
|
||||||
|
mvn -f java/pom.xml -Prelease-sign verify
|
||||||
|
```
|
||||||
|
|
||||||
|
各 **可发布模块**(`craftlabs-auth-core`、`craftlabs-auth-bitanswer`、`craftlabs-auth-selfhosted`)的 `target/*.jar` 旁会出现 **`.asc`**。`craftlabs-auth-tests` 模块固定 **不签名**。
|
||||||
|
|
||||||
|
若无私钥或未就绪,可只发 **SHA256SUMS**;待密钥就绪后再打开 `-Prelease-sign`。
|
||||||
|
|
||||||
|
### CI / 无人值守(可选)
|
||||||
|
|
||||||
|
在构建机配置 `MAVEN_GPG_PASSPHRASE` 等环境变量,或使用 `gpg-agent`;勿将私钥提交仓库。GitHub Actions 可用 `GPG_PRIVATE_KEY` secret + [crazy-max/ghaction-import-gpg](https://github.com/crazy-max/ghaction-import-gpg) 导入后再执行 `mvn -Prelease-sign verify`。
|
||||||
|
|
||||||
|
## 4. GitHub Release 建议资产
|
||||||
|
|
||||||
|
每个 **tag** 上传:
|
||||||
|
|
||||||
|
1. 三个 **release JAR**(及对应 **.asc**,若已签名)
|
||||||
|
2. 各平台 **Native** 压缩包
|
||||||
|
3. **`SHA256SUMS`** 与 **`SHA256SUMS.asc`**
|
||||||
|
4. 固定页面公布 **GPG 公钥指纹** 与下载说明
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
版权所有 © 广州创飞人工智能技术有限公司(以项目实际声明为准)。
|
||||||
+1
-1
@@ -18,7 +18,7 @@ import cn.craftlabs.auth.internal.NativeBridge;
|
|||||||
*/
|
*/
|
||||||
public final class BitAnswerProvider implements AuthProvider {
|
public final class BitAnswerProvider implements AuthProvider {
|
||||||
static {
|
static {
|
||||||
System.loadLibrary("craftlabs_auth_bitanswer");
|
System.loadLibrary("craftlabs_auth_core");
|
||||||
}
|
}
|
||||||
|
|
||||||
private long nativeHandle;
|
private long nativeHandle;
|
||||||
|
|||||||
@@ -13,4 +13,25 @@
|
|||||||
<artifactId>craftlabs-auth-core</artifactId>
|
<artifactId>craftlabs-auth-core</artifactId>
|
||||||
<name>CraftLabs Auth — core API</name>
|
<name>CraftLabs Auth — core API</name>
|
||||||
<packaging>jar</packaging>
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.fasterxml.jackson.core</groupId>
|
||||||
|
<artifactId>jackson-databind</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>net.java.dev.jna</groupId>
|
||||||
|
<artifactId>jna</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.junit.jupiter</groupId>
|
||||||
|
<artifactId>junit-jupiter</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.networknt</groupId>
|
||||||
|
<artifactId>json-schema-validator</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
</project>
|
</project>
|
||||||
|
|||||||
@@ -3,6 +3,9 @@ package cn.craftlabs.auth;
|
|||||||
/**
|
/**
|
||||||
* 授权能力的统一契约:初始化、激活、校验许可、查询特性与释放等生命周期方法。
|
* 授权能力的统一契约:初始化、激活、校验许可、查询特性与释放等生命周期方法。
|
||||||
*
|
*
|
||||||
|
* <p>{@link #initialize(String)} 的 JSON 建议使用 {@link cn.craftlabs.auth.config.AuthConfigs#parse(String)}
|
||||||
|
* 先校验后再传入,格式见仓库 {@code schemas/craftlabs-auth-config.schema.json} 与 {@code examples/config/}。
|
||||||
|
*
|
||||||
* <p>实现类负责加载对应 native 或远端适配器;调用方应在不再使用时调用 {@link #close()} 释放底层资源。
|
* <p>实现类负责加载对应 native 或远端适配器;调用方应在不再使用时调用 {@link #close()} 释放底层资源。
|
||||||
*
|
*
|
||||||
* <p>版权所有 © 广州创飞人工智能技术有限公司
|
* <p>版权所有 © 广州创飞人工智能技术有限公司
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
package cn.craftlabs.auth.config;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link cn.craftlabs.auth.AuthProvider#initialize(String)} 所用 JSON 的配置模型,与 {@code
|
||||||
|
* schemas/craftlabs-auth-config.schema.json} 对齐。
|
||||||
|
*
|
||||||
|
* <p>版权所有 © 广州创飞人工智能技术有限公司
|
||||||
|
*
|
||||||
|
* @author huangping@craftlabs.cn
|
||||||
|
*/
|
||||||
|
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||||
|
public record AuthConfig(
|
||||||
|
@JsonProperty("schemaVersion") int schemaVersion,
|
||||||
|
@JsonProperty("provider") String provider,
|
||||||
|
@JsonProperty("scenario") String scenario,
|
||||||
|
@JsonProperty("bitanswer") BitanswerConfigSection bitanswer,
|
||||||
|
@JsonProperty("selfhosted") SelfhostedConfigSection selfhosted,
|
||||||
|
@JsonProperty("features") Map<String, FeatureMapping> features,
|
||||||
|
@JsonProperty("wharf") WharfScenarioSection wharf,
|
||||||
|
@JsonProperty("school") SchoolScenarioSection school,
|
||||||
|
@JsonProperty("floating") FloatingScenarioSection floating) {
|
||||||
|
|
||||||
|
public AuthConfig {
|
||||||
|
features = features == null ? Map.of() : Collections.unmodifiableMap(new LinkedHashMap<>(features));
|
||||||
|
provider = Objects.requireNonNullElse(provider, "");
|
||||||
|
scenario = Objects.requireNonNullElse(scenario, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 逻辑特性键对应的比特特征 id;未配置或非数值映射时返回 {@code null}。 */
|
||||||
|
public Integer bitanswerFeatureId(String logicalFeatureKey) {
|
||||||
|
FeatureMapping m = features.get(logicalFeatureKey);
|
||||||
|
return m != null ? m.bitanswerFeatureId() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 逻辑特性键对应的比特特征名称;未配置时返回 {@code null}。 */
|
||||||
|
public String bitanswerFeatureName(String logicalFeatureKey) {
|
||||||
|
FeatureMapping m = features.get(logicalFeatureKey);
|
||||||
|
return m != null ? m.bitanswerFeatureName() : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
+19
@@ -0,0 +1,19 @@
|
|||||||
|
package cn.craftlabs.auth.config;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link AuthConfigs#parse(String)} 或校验失败时抛出。
|
||||||
|
*
|
||||||
|
* <p>版权所有 © 广州创飞人工智能技术有限公司
|
||||||
|
*
|
||||||
|
* @author huangping@craftlabs.cn
|
||||||
|
*/
|
||||||
|
public final class AuthConfigException extends Exception {
|
||||||
|
|
||||||
|
public AuthConfigException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AuthConfigException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,111 @@
|
|||||||
|
package cn.craftlabs.auth.config;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
|
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析并校验 {@link cn.craftlabs.auth.AuthProvider#initialize(String)} 的 JSON 配置。
|
||||||
|
*
|
||||||
|
* <p>版权所有 © 广州创飞人工智能技术有限公司
|
||||||
|
*
|
||||||
|
* @author huangping@craftlabs.cn
|
||||||
|
*/
|
||||||
|
public final class AuthConfigs {
|
||||||
|
|
||||||
|
/** 与 {@code schemas/craftlabs-auth-config.schema.json} 中 {@code schemaVersion} 一致。 */
|
||||||
|
public static final int SCHEMA_VERSION = 1;
|
||||||
|
|
||||||
|
private static final ObjectMapper MAPPER =
|
||||||
|
new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||||
|
|
||||||
|
private AuthConfigs() {}
|
||||||
|
|
||||||
|
/** 解析 JSON 并执行 {@link #validate(AuthConfig)}。 */
|
||||||
|
public static AuthConfig parse(String json) throws AuthConfigException {
|
||||||
|
if (json == null || json.isBlank()) {
|
||||||
|
throw new AuthConfigException("config JSON is null or blank");
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
AuthConfig cfg = MAPPER.readValue(json, AuthConfig.class);
|
||||||
|
validate(cfg);
|
||||||
|
return cfg;
|
||||||
|
} catch (JsonProcessingException e) {
|
||||||
|
String msg = e.getOriginalMessage() != null ? e.getOriginalMessage() : e.getMessage();
|
||||||
|
throw new AuthConfigException("Invalid config JSON: " + msg, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 将配置写回 JSON(便于日志脱敏后落盘或调试)。 */
|
||||||
|
public static String toJson(AuthConfig config) throws AuthConfigException {
|
||||||
|
try {
|
||||||
|
return MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(config);
|
||||||
|
} catch (JsonProcessingException e) {
|
||||||
|
throw new AuthConfigException("Failed to serialize config", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校验必填组合;通过后可安全交给 native / 供应商实现。
|
||||||
|
*
|
||||||
|
* @throws AuthConfigException 校验失败,消息为多条原因拼接
|
||||||
|
*/
|
||||||
|
public static void validate(AuthConfig c) throws AuthConfigException {
|
||||||
|
List<String> err = new ArrayList<>();
|
||||||
|
|
||||||
|
if (c.schemaVersion() != SCHEMA_VERSION) {
|
||||||
|
err.add("schemaVersion must be " + SCHEMA_VERSION);
|
||||||
|
}
|
||||||
|
|
||||||
|
String provider = norm(c.provider());
|
||||||
|
if (provider.isEmpty()) {
|
||||||
|
err.add("provider is required");
|
||||||
|
} else if (!provider.equals("bitanswer") && !provider.equals("selfhosted")) {
|
||||||
|
err.add("provider must be bitanswer or selfhosted");
|
||||||
|
}
|
||||||
|
|
||||||
|
String scenario = norm(c.scenario());
|
||||||
|
if (scenario.isEmpty()) {
|
||||||
|
err.add("scenario is required");
|
||||||
|
} else if (!scenario.equals("wharf") && !scenario.equals("school") && !scenario.equals("floating")) {
|
||||||
|
err.add("scenario must be wharf, school, or floating");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (provider.equals("bitanswer")) {
|
||||||
|
if (c.bitanswer() == null) {
|
||||||
|
err.add("bitanswer section is required when provider=bitanswer");
|
||||||
|
} else if (isBlank(c.bitanswer().url())) {
|
||||||
|
err.add("bitanswer.url is required and must be non-blank");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (provider.equals("selfhosted")) {
|
||||||
|
if (c.selfhosted() == null) {
|
||||||
|
err.add("selfhosted section is required when provider=selfhosted");
|
||||||
|
} else if (isBlank(c.selfhosted().baseUrl())) {
|
||||||
|
err.add("selfhosted.baseUrl is required and must be non-blank");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (scenario.equals("floating")) {
|
||||||
|
if (c.floating() == null || isBlank(c.floating().projectId())) {
|
||||||
|
err.add("floating.projectId is required when scenario=floating");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!err.isEmpty()) {
|
||||||
|
throw new AuthConfigException(String.join("; ", err));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String norm(String s) {
|
||||||
|
return s == null ? "" : s.trim().toLowerCase(Locale.ROOT);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isBlank(String s) {
|
||||||
|
return s == null || s.trim().isEmpty();
|
||||||
|
}
|
||||||
|
}
|
||||||
+17
@@ -0,0 +1,17 @@
|
|||||||
|
package cn.craftlabs.auth.config;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@code config_json} 中与比特安索客户端相关的字段子集。
|
||||||
|
*
|
||||||
|
* @author huangping@craftlabs.cn
|
||||||
|
*/
|
||||||
|
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||||
|
public record BitanswerConfigSection(
|
||||||
|
@JsonProperty("url") String url,
|
||||||
|
@JsonProperty("loginMode") String loginMode,
|
||||||
|
@JsonProperty("rootPath") String rootPath,
|
||||||
|
@JsonProperty("sn") String sn,
|
||||||
|
@JsonProperty("applicationData") String applicationData) {}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package cn.craftlabs.auth.config;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 逻辑特性键(如 {@code face})到比特特征项的映射;至少填 id 或 name 之一即可在实现层选用。
|
||||||
|
*
|
||||||
|
* @author huangping@craftlabs.cn
|
||||||
|
*/
|
||||||
|
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||||
|
public record FeatureMapping(
|
||||||
|
@JsonProperty("bitanswerFeatureId") Integer bitanswerFeatureId,
|
||||||
|
@JsonProperty("bitanswerFeatureName") String bitanswerFeatureName,
|
||||||
|
@JsonProperty("selfhostedFeatureKey") String selfhostedFeatureKey) {}
|
||||||
+11
@@ -0,0 +1,11 @@
|
|||||||
|
package cn.craftlabs.auth.config;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
|
||||||
|
/** 流动人口按项目授权场景补充字段。 */
|
||||||
|
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||||
|
public record FloatingScenarioSection(
|
||||||
|
@JsonProperty("projectId") String projectId,
|
||||||
|
@JsonProperty("projectName") String projectName,
|
||||||
|
@JsonProperty("contractRef") String contractRef) {}
|
||||||
+10
@@ -0,0 +1,10 @@
|
|||||||
|
package cn.craftlabs.auth.config;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
|
||||||
|
/** 学校按边设备运营场景补充字段。 */
|
||||||
|
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||||
|
public record SchoolScenarioSection(
|
||||||
|
@JsonProperty("edgeDeviceId") String edgeDeviceId,
|
||||||
|
@JsonProperty("tenantId") String tenantId) {}
|
||||||
+23
@@ -0,0 +1,23 @@
|
|||||||
|
package cn.craftlabs.auth.config;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@code provider=selfhosted} 时的 HTTP 后端参数。
|
||||||
|
*
|
||||||
|
* @author huangping@craftlabs.cn
|
||||||
|
*/
|
||||||
|
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||||
|
public record SelfhostedConfigSection(
|
||||||
|
@JsonProperty("baseUrl") String baseUrl,
|
||||||
|
@JsonProperty("tenantKey") String tenantKey,
|
||||||
|
@JsonProperty("offlineGraceDays") Integer offlineGraceDays,
|
||||||
|
@JsonProperty("heartbeatIntervalHours") Integer heartbeatIntervalHours,
|
||||||
|
@JsonProperty("publicKeyPem") String publicKeyPem) {
|
||||||
|
|
||||||
|
public SelfhostedConfigSection {
|
||||||
|
offlineGraceDays = offlineGraceDays != null ? offlineGraceDays : 7;
|
||||||
|
heartbeatIntervalHours = heartbeatIntervalHours != null ? heartbeatIntervalHours : 24;
|
||||||
|
}
|
||||||
|
}
|
||||||
+11
@@ -0,0 +1,11 @@
|
|||||||
|
package cn.craftlabs.auth.config;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
|
||||||
|
/** 码头 / 集中授权场景补充字段。 */
|
||||||
|
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||||
|
public record WharfScenarioSection(
|
||||||
|
@JsonProperty("topology") String topology,
|
||||||
|
@JsonProperty("groupServiceUrl") String groupServiceUrl,
|
||||||
|
@JsonProperty("notes") String notes) {}
|
||||||
+53
@@ -0,0 +1,53 @@
|
|||||||
|
package cn.craftlabs.auth.internal;
|
||||||
|
|
||||||
|
import com.sun.jna.Library;
|
||||||
|
import com.sun.jna.Native;
|
||||||
|
import com.sun.jna.Pointer;
|
||||||
|
import com.sun.jna.Structure;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public interface CraftCoreLibrary extends Library {
|
||||||
|
CraftCoreLibrary INSTANCE = Native.load("craftlabs_auth_core", CraftCoreLibrary.class);
|
||||||
|
|
||||||
|
class CraftResult extends Structure {
|
||||||
|
public int success;
|
||||||
|
public String message;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected List<String> getFieldOrder() {
|
||||||
|
return Arrays.asList("success", "message");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class LicenseInfoStruct extends Structure {
|
||||||
|
public int isLicensed;
|
||||||
|
public long expirationDate;
|
||||||
|
public Pointer featureNames;
|
||||||
|
public Pointer featureValues;
|
||||||
|
public int featureCount;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected List<String> getFieldOrder() {
|
||||||
|
return Arrays.asList("isLicensed", "expirationDate", "featureNames", "featureValues", "featureCount");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Pointer craft_initialize(String configJson);
|
||||||
|
|
||||||
|
void craft_destroy(Pointer handle);
|
||||||
|
|
||||||
|
CraftResult craft_activate(Pointer handle, String licenseKey);
|
||||||
|
|
||||||
|
CraftResult craft_check_license(Pointer handle);
|
||||||
|
|
||||||
|
LicenseInfoStruct craft_get_license_info(Pointer handle);
|
||||||
|
|
||||||
|
void craft_free_license_info(LicenseInfoStruct info);
|
||||||
|
|
||||||
|
int craft_has_feature(Pointer handle, String featureName);
|
||||||
|
|
||||||
|
CraftResult craft_release(Pointer handle);
|
||||||
|
|
||||||
|
CraftResult craft_heartbeat(Pointer handle);
|
||||||
|
}
|
||||||
+89
@@ -0,0 +1,89 @@
|
|||||||
|
package cn.craftlabs.auth.internal;
|
||||||
|
|
||||||
|
import cn.craftlabs.auth.AuthProvider;
|
||||||
|
import cn.craftlabs.auth.AuthResult;
|
||||||
|
import cn.craftlabs.auth.LicenseInfo;
|
||||||
|
import com.sun.jna.Native;
|
||||||
|
import com.sun.jna.Pointer;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class JnaAuthProvider implements AuthProvider {
|
||||||
|
private Pointer nativeHandle;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AuthResult initialize(String configJson) {
|
||||||
|
if (nativeHandle != null) {
|
||||||
|
CraftCoreLibrary.INSTANCE.craft_destroy(nativeHandle);
|
||||||
|
}
|
||||||
|
nativeHandle = CraftCoreLibrary.INSTANCE.craft_initialize(
|
||||||
|
configJson != null ? configJson : "{}");
|
||||||
|
return new AuthResult(nativeHandle != null, "Initialized");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AuthResult activate(String licenseKey) {
|
||||||
|
CraftCoreLibrary.CraftResult r = CraftCoreLibrary.INSTANCE.craft_activate(
|
||||||
|
nativeHandle, licenseKey);
|
||||||
|
return new AuthResult(r.success != 0, r.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AuthResult checkLicense() {
|
||||||
|
CraftCoreLibrary.CraftResult r = CraftCoreLibrary.INSTANCE.craft_check_license(nativeHandle);
|
||||||
|
return new AuthResult(r.success != 0, r.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LicenseInfo getLicenseInfo() {
|
||||||
|
CraftCoreLibrary.LicenseInfoStruct s = CraftCoreLibrary.INSTANCE.craft_get_license_info(nativeHandle);
|
||||||
|
if (s == null) {
|
||||||
|
return new LicenseInfo(false, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, Boolean> features = null;
|
||||||
|
if (s.featureCount > 0 && s.featureNames != null) {
|
||||||
|
features = new HashMap<>(s.featureCount);
|
||||||
|
for (int i = 0; i < s.featureCount; i++) {
|
||||||
|
Pointer namePtr = s.featureNames.getPointer((long) i * Native.POINTER_SIZE);
|
||||||
|
String name = namePtr != null ? namePtr.getString(0) : null;
|
||||||
|
if (name != null) {
|
||||||
|
int val = s.featureValues != null
|
||||||
|
? s.featureValues.getInt((long) i * Integer.BYTES)
|
||||||
|
: 0;
|
||||||
|
features.put(name, val != 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Date expirationDate = s.expirationDate > 0 ? new Date(s.expirationDate) : null;
|
||||||
|
CraftCoreLibrary.INSTANCE.craft_free_license_info(s);
|
||||||
|
return new LicenseInfo(s.isLicensed != 0, expirationDate, features);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasFeature(String featureName) {
|
||||||
|
return CraftCoreLibrary.INSTANCE.craft_has_feature(nativeHandle, featureName) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AuthResult release() {
|
||||||
|
CraftCoreLibrary.CraftResult r = CraftCoreLibrary.INSTANCE.craft_release(nativeHandle);
|
||||||
|
return new AuthResult(r.success != 0, r.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AuthResult heartbeat() {
|
||||||
|
CraftCoreLibrary.CraftResult r = CraftCoreLibrary.INSTANCE.craft_heartbeat(nativeHandle);
|
||||||
|
return new AuthResult(r.success != 0, r.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
if (nativeHandle != null) {
|
||||||
|
CraftCoreLibrary.INSTANCE.craft_destroy(nativeHandle);
|
||||||
|
nativeHandle = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
package cn.craftlabs.auth.config;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
class AuthConfigsTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void parseWharfExampleFromClasspath() throws Exception {
|
||||||
|
String json = readClasspath("examples/config/wharf.bitanswer.json");
|
||||||
|
AuthConfig c = AuthConfigs.parse(json);
|
||||||
|
assertEquals(1, c.schemaVersion());
|
||||||
|
assertEquals("bitanswer", c.provider());
|
||||||
|
assertEquals("wharf", c.scenario());
|
||||||
|
assertNotNull(c.bitanswer());
|
||||||
|
assertTrue(c.bitanswer().url().startsWith("bit://"));
|
||||||
|
assertEquals(101, c.bitanswerFeatureId("container_number_detect"));
|
||||||
|
assertEquals("group", c.wharf().topology());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void parseSchoolExample() throws Exception {
|
||||||
|
String json = readClasspath("examples/config/school.bitanswer.json");
|
||||||
|
AuthConfig c = AuthConfigs.parse(json);
|
||||||
|
assertEquals("school", c.scenario());
|
||||||
|
assertEquals(201, c.bitanswerFeatureId("face"));
|
||||||
|
assertEquals("classroom-gate-01", c.school().edgeDeviceId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void parseFloatingExample_requiresProjectId() throws Exception {
|
||||||
|
String json = readClasspath("examples/config/floating.bitanswer.json");
|
||||||
|
AuthConfig c = AuthConfigs.parse(json);
|
||||||
|
assertEquals("floating", c.scenario());
|
||||||
|
assertEquals("migrant-flow-prj-2026-001", c.floating().projectId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void validateFails_whenFloatingWithoutProject() {
|
||||||
|
String json =
|
||||||
|
"""
|
||||||
|
{"schemaVersion":1,"provider":"bitanswer","scenario":"floating",\
|
||||||
|
"bitanswer":{"url":"http://x"},"floating":{}}\
|
||||||
|
""";
|
||||||
|
AuthConfigException ex = assertThrows(AuthConfigException.class, () -> AuthConfigs.parse(json));
|
||||||
|
assertTrue(ex.getMessage().contains("projectId"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void validateFails_whenBitanswerWithoutUrl() {
|
||||||
|
String json =
|
||||||
|
"""
|
||||||
|
{"schemaVersion":1,"provider":"bitanswer","scenario":"school",\
|
||||||
|
"bitanswer":{"url":""}}\
|
||||||
|
""";
|
||||||
|
assertThrows(AuthConfigException.class, () -> AuthConfigs.parse(json));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void roundTrip_toJson() throws Exception {
|
||||||
|
String json = readClasspath("examples/config/school.bitanswer.json");
|
||||||
|
AuthConfig c = AuthConfigs.parse(json);
|
||||||
|
String out = AuthConfigs.toJson(c);
|
||||||
|
AuthConfig again = AuthConfigs.parse(out);
|
||||||
|
assertEquals(c.scenario(), again.scenario());
|
||||||
|
assertEquals(c.bitanswerFeatureId("expression"), again.bitanswerFeatureId("expression"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String readClasspath(String path) throws Exception {
|
||||||
|
try (InputStream in = AuthConfigsTest.class.getClassLoader().getResourceAsStream(path)) {
|
||||||
|
assertNotNull(in, "missing classpath resource: " + path);
|
||||||
|
return new String(in.readAllBytes(), StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+69
@@ -0,0 +1,69 @@
|
|||||||
|
package cn.craftlabs.auth.config;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.networknt.schema.JsonSchema;
|
||||||
|
import com.networknt.schema.JsonSchemaFactory;
|
||||||
|
import com.networknt.schema.SpecVersion;
|
||||||
|
import com.networknt.schema.ValidationMessage;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* BP-10 / I5:{@code examples/config/*.json} 与仓库根 {@code schemas/craftlabs-auth-config.schema.json} 一致。
|
||||||
|
*/
|
||||||
|
class ExamplesConfigSchemaValidationTest {
|
||||||
|
|
||||||
|
private static final ObjectMapper MAPPER = new ObjectMapper();
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void allExampleConfigsValidateAgainstSchema() throws Exception {
|
||||||
|
Path repoRoot = findRepoRoot();
|
||||||
|
Path schemaPath = repoRoot.resolve("schemas/craftlabs-auth-config.schema.json");
|
||||||
|
Path examplesDir = repoRoot.resolve("examples/config");
|
||||||
|
assertTrue(Files.isRegularFile(schemaPath), "schema missing: " + schemaPath);
|
||||||
|
assertTrue(Files.isDirectory(examplesDir), "examples dir missing: " + examplesDir);
|
||||||
|
|
||||||
|
JsonNode schemaNode = MAPPER.readTree(schemaPath.toFile());
|
||||||
|
JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V202012);
|
||||||
|
JsonSchema schema = factory.getSchema(schemaNode);
|
||||||
|
|
||||||
|
try (Stream<Path> stream = Files.list(examplesDir)) {
|
||||||
|
stream.filter(p -> p.toString().endsWith(".json")).forEach(jsonFile -> {
|
||||||
|
try {
|
||||||
|
JsonNode instance = MAPPER.readTree(jsonFile.toFile());
|
||||||
|
Set<ValidationMessage> errors = schema.validate(instance);
|
||||||
|
assertTrue(
|
||||||
|
errors.isEmpty(),
|
||||||
|
() -> jsonFile.getFileName() + ": " + errors);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new AssertionError(jsonFile.toString(), e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自 {@code user.dir} 向上查找含 {@code schemas/craftlabs-auth-config.schema.json} 的目录(兼容模块目录或仓库根执行)。
|
||||||
|
*/
|
||||||
|
static Path findRepoRoot() {
|
||||||
|
Path p = Path.of(System.getProperty("user.dir", ".")).toAbsolutePath().normalize();
|
||||||
|
for (int i = 0; i < 8 && p != null; i++) {
|
||||||
|
Path candidate = p.resolve("schemas/craftlabs-auth-config.schema.json");
|
||||||
|
if (Files.isRegularFile(candidate)) {
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
p = p.getParent();
|
||||||
|
}
|
||||||
|
throw new IllegalStateException(
|
||||||
|
"Could not find repo root (schemas/craftlabs-auth-config.schema.json) from user.dir="
|
||||||
|
+ System.getProperty("user.dir"));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"schemaVersion": 1,
|
||||||
|
"provider": "bitanswer",
|
||||||
|
"scenario": "floating",
|
||||||
|
"bitanswer": {
|
||||||
|
"url": "https://cloud.bitanswer.example/e3",
|
||||||
|
"loginMode": "REMOTE"
|
||||||
|
},
|
||||||
|
"features": {
|
||||||
|
"face": { "bitanswerFeatureId": 301 }
|
||||||
|
},
|
||||||
|
"floating": {
|
||||||
|
"projectId": "migrant-flow-prj-2026-001",
|
||||||
|
"projectName": "某市流动人口人像核验",
|
||||||
|
"contractRef": "PO-2026-8848"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"schemaVersion": 1,
|
||||||
|
"provider": "bitanswer",
|
||||||
|
"scenario": "school",
|
||||||
|
"bitanswer": {
|
||||||
|
"url": "https://cloud.bitanswer.example/e3",
|
||||||
|
"loginMode": "AUTO",
|
||||||
|
"sn": ""
|
||||||
|
},
|
||||||
|
"features": {
|
||||||
|
"face": { "bitanswerFeatureId": 201 },
|
||||||
|
"expression": { "bitanswerFeatureId": 202 }
|
||||||
|
},
|
||||||
|
"school": {
|
||||||
|
"edgeDeviceId": "classroom-gate-01",
|
||||||
|
"tenantId": "school-district-demo"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"schemaVersion": 1,
|
||||||
|
"provider": "bitanswer",
|
||||||
|
"scenario": "wharf",
|
||||||
|
"bitanswer": {
|
||||||
|
"url": "bit://license.example.com:8273",
|
||||||
|
"loginMode": "AUTO",
|
||||||
|
"rootPath": "/var/lib/craftlabs/bitanswer"
|
||||||
|
},
|
||||||
|
"features": {
|
||||||
|
"container_number_detect": { "bitanswerFeatureId": 101 },
|
||||||
|
"container_number_recognize": { "bitanswerFeatureId": 102 },
|
||||||
|
"iso_detect": { "bitanswerFeatureId": 103 },
|
||||||
|
"iso_recognize": { "bitanswerFeatureId": 104 }
|
||||||
|
},
|
||||||
|
"wharf": {
|
||||||
|
"topology": "group",
|
||||||
|
"groupServiceUrl": "bit://license.example.com:8273",
|
||||||
|
"notes": "边设备集中连集团服务;特征项与控制台产品一致后替换 id。"
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-1
@@ -17,7 +17,7 @@ import cn.craftlabs.auth.internal.NativeBridge;
|
|||||||
*/
|
*/
|
||||||
public final class SelfHostedAuthProvider implements AuthProvider {
|
public final class SelfHostedAuthProvider implements AuthProvider {
|
||||||
static {
|
static {
|
||||||
System.loadLibrary("craftlabs_auth_bitanswer");
|
System.loadLibrary("craftlabs_auth_core");
|
||||||
}
|
}
|
||||||
|
|
||||||
private long nativeHandle;
|
private long nativeHandle;
|
||||||
|
|||||||
@@ -28,6 +28,8 @@
|
|||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
|
<!-- 测试模块不发布;跳过 GPG 以免生成多余 .asc -->
|
||||||
|
<gpg.skip>true</gpg.skip>
|
||||||
<native.library.path>${project.basedir}/../../native/build</native.library.path>
|
<native.library.path>${project.basedir}/../../native/build</native.library.path>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,10 @@
|
|||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
<maven.compiler.release>17</maven.compiler.release>
|
<maven.compiler.release>17</maven.compiler.release>
|
||||||
<junit.version>5.10.2</junit.version>
|
<junit.version>5.10.2</junit.version>
|
||||||
|
<jackson.version>2.17.2</jackson.version>
|
||||||
|
<json-schema-validator.version>1.5.7</json-schema-validator.version>
|
||||||
|
<!-- 默认跳过 GPG;发布签名:mvn -Prelease-sign verify(见 RELEASING.md) -->
|
||||||
|
<gpg.skip>true</gpg.skip>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<dependencyManagement>
|
<dependencyManagement>
|
||||||
@@ -36,6 +40,22 @@
|
|||||||
<version>${junit.version}</version>
|
<version>${junit.version}</version>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.fasterxml.jackson.core</groupId>
|
||||||
|
<artifactId>jackson-databind</artifactId>
|
||||||
|
<version>${jackson.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.networknt</groupId>
|
||||||
|
<artifactId>json-schema-validator</artifactId>
|
||||||
|
<version>${json-schema-validator.version}</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>net.java.dev.jna</groupId>
|
||||||
|
<artifactId>jna</artifactId>
|
||||||
|
<version>5.14.0</version>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</dependencyManagement>
|
</dependencyManagement>
|
||||||
|
|
||||||
@@ -52,7 +72,39 @@
|
|||||||
<artifactId>maven-surefire-plugin</artifactId>
|
<artifactId>maven-surefire-plugin</artifactId>
|
||||||
<version>3.2.5</version>
|
<version>3.2.5</version>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-gpg-plugin</artifactId>
|
||||||
|
<version>3.2.7</version>
|
||||||
|
</plugin>
|
||||||
</plugins>
|
</plugins>
|
||||||
</pluginManagement>
|
</pluginManagement>
|
||||||
</build>
|
</build>
|
||||||
|
|
||||||
|
<!-- 对发布 JAR 做 GPG 分离签名(.asc);显式 -Prelease-sign 时启用 -->
|
||||||
|
<profiles>
|
||||||
|
<profile>
|
||||||
|
<id>release-sign</id>
|
||||||
|
<properties>
|
||||||
|
<gpg.skip>false</gpg.skip>
|
||||||
|
</properties>
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-gpg-plugin</artifactId>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>sign-release-artifacts</id>
|
||||||
|
<phase>verify</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>sign</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</profile>
|
||||||
|
</profiles>
|
||||||
</project>
|
</project>
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user