فهرست منبع

docs: 添加项目文档和用户组 API

包含功能测试文档、功能完善清单、样本中心API文档、
统一认证接入文档,以及用户组管理前端 API。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
mengboxin137-blip 6 روز پیش
والد
کامیت
89ddd73dc0

+ 313 - 0
docs/功能测试文档.md

@@ -0,0 +1,313 @@
+# 智能体平台新增功能测试文档
+
+> 测试日期:2026-05-19
+>
+> 环境:Docker 本地环境(http://127.0.0.1:80)
+
+---
+
+## 前置条件
+
+1. 已执行 `docker-compose up -d` 启动所有服务
+2. 已登录管理员账号,获取 Token
+3. 知道自己的 `workspace_id` 和 `application_id`
+
+---
+
+## 一、工具使用统计(任务 8)
+
+### 测试入口
+
+工具列表页 → 鼠标悬停工具卡片 → 点击统计图标(🔍)
+
+### 测试步骤
+
+| 步骤 | 操作 | 预期结果 |
+|:----:|------|----------|
+| 1 | 进入 `/tool` 页面,鼠标悬停任意工具卡片 | 出现统计图标按钮 |
+| 2 | 点击统计图标 | 弹出统计抽屉,显示标题 "{工具名} - Statistics" |
+| 3 | 查看统计数据 | 显示:总调用次数、成功率、成功/失败次数、平均/最小/最大耗时 |
+| 4 | 切换时间范围(7天/30天/90天) | 数据随之刷新 |
+| 5 | 关闭抽屉 | 抽屉正常关闭 |
+
+### 验证要点
+
+- [ ] 统计图标在非批量模式下才显示
+- [ ] 未调用过的工具显示全 0 数据
+- [ ] 时间范围切换后数据正确刷新
+- [ ] 抽屉关闭后再打开数据正常
+
+---
+
+## 二、知识库用量统计(任务 9)
+
+### 测试入口
+
+知识库设置页 → 信息区顶部统计卡片
+
+### 测试步骤
+
+| 步骤 | 操作 | 预期结果 |
+|:----:|------|----------|
+| 1 | 进入任意知识库 → 点击"设置" tab | 页面顶部显示三个统计卡片 |
+| 2 | 查看"文档数"卡片 | 显示该知识库的文档总数 |
+| 3 | 查看"段落数"卡片 | 显示该知识库的段落总数 |
+| 4 | 查看"命中数"卡片 | 显示该知识库的总命中次数 |
+| 5 | 上传新文档后刷新页面 | 文档数、段落数相应增加 |
+
+### 验证要点
+
+- [ ] 统计卡片在 Info 标题下方、BaseForm 上方
+- [ ] 空知识库显示全 0
+- [ ] 加载中有 loading 动画
+- [ ] 不同知识库数据独立
+
+---
+
+## 三、应用 API 限流(任务 10)
+
+### 测试入口
+
+应用概览页 → 点击 "API Rate Limit" 按钮
+
+### 测试步骤
+
+| 步骤 | 操作 | 预期结果 |
+|:----:|------|----------|
+| 1 | 进入应用概览页 → 点击 "API Rate Limit" | 弹出限流配置对话框 |
+| 2 | 查看默认配置 | 默认关闭状态,开关为 off |
+| 3 | 开启限流开关 | 展开配置项:类型、最大请求数、突发大小、窗口时间 |
+| 4 | 选择限流类型 QPM | 下拉框正确切换 |
+| 5 | 设置最大请求数为 60 | 输入框数值更新 |
+| 6 | 设置突发大小为 10 | 输入框数值更新 |
+| 7 | 设置窗口时间为 60 秒 | 输入框数值更新 |
+| 8 | 点击"保存" | 对话框关闭,设置成功提示 |
+| 9 | 重新打开对话框 | 之前保存的配置正确回显 |
+| 10 | 点击"Reset Counters" | 弹出确认框,确认后提示重置成功 |
+
+### 验证要点
+
+- [ ] 关闭状态下配置项隐藏
+- [ ] 开启状态下所有配置项可编辑
+- [ ] 限流类型支持 QPS/QPM/QPH/QPD 四种
+- [ ] 保存后重新打开数据一致
+- [ ] Reset Counters 有二次确认
+
+### API 验证(可选)
+
+```bash
+# 获取限流配置
+curl -H "Authorization: Bearer YOUR_TOKEN" \
+  http://127.0.0.1:8080/admin/api/workspace/default/application/{APP_ID}/rate_limit
+
+# 更新限流配置
+curl -X PUT -H "Authorization: Bearer YOUR_TOKEN" \
+  -H "Content-Type: application/json" \
+  -d '{"is_enabled":true,"rate_type":"QPM","max_requests":60,"burst_size":10,"window_seconds":60}' \
+  http://127.0.0.1:8080/admin/api/workspace/default/application/{APP_ID}/rate_limit/update
+
+# 重置计数
+curl -X POST -H "Authorization: Bearer YOUR_TOKEN" \
+  http://127.0.0.1:8080/admin/api/workspace/default/application/{APP_ID}/rate_limit/reset
+```
+
+---
+
+## 四、插件管理(任务 4/5/6)
+
+### 4.1 插件注册与列表
+
+| 步骤 | 操作 | 预期结果 |
+|:----:|------|----------|
+| 1 | 进入 `/plugin` 页面 | 显示插件列表(空列表或已有插件) |
+| 2 | 点击"创建"按钮 | 弹出创建插件对话框 |
+| 3 | 填写名称、编码、类型、描述 | 表单字段正确 |
+| 4 | 提交创建 | 对话框关闭,列表刷新,新插件出现 |
+
+### 4.2 插件生命周期
+
+| 步骤 | 操作 | 预期结果 |
+|:----:|------|----------|
+| 1 | 鼠标悬停插件卡片 | 出现启用/禁用开关和更多操作菜单 |
+| 2 | 切换启用/禁用开关 | 状态切换成功,卡片状态标签更新 |
+| 3 | 点击更多菜单 → 卸载 | 弹出确认弹窗 |
+| 4 | 确认卸载 | 插件状态变为未安装 |
+| 5 | 再次安装 | 插件状态恢复 |
+
+### 4.3 插件测试
+
+| 步骤 | 操作 | 预期结果 |
+|:----:|------|----------|
+| 1 | 进入插件详情 → 打开测试抽屉 | 显示 JSON 输入框和超时配置 |
+| 2 | 输入测试数据,点击"执行测试" | 测试执行,显示结果 |
+| 3 | 查看测试结果 | 显示状态(成功/失败)、耗时、错误码 |
+| 4 | 切换到"测试历史" tab | 显示历史测试记录列表 |
+
+### 4.4 版本管理
+
+| 步骤 | 操作 | 预期结果 |
+|:----:|------|----------|
+| 1 | 进入插件详情 → 打开版本历史 | 显示版本时间线 |
+| 2 | 查看版本列表 | 显示版本号、状态、创建时间 |
+| 3 | 点击回滚按钮 | 弹出确认,确认后版本回滚 |
+
+---
+
+## 五、模型批量操作(任务 7)
+
+### 测试入口
+
+模型列表页 → 勾选多个模型
+
+### 测试步骤
+
+| 步骤 | 操作 | 预期结果 |
+|:----:|------|----------|
+| 1 | 进入模型列表页 | 显示模型列表 |
+| 2 | 勾选 2-3 个模型 | 出现批量操作栏 |
+| 3 | 点击"批量禁用" | 选中的模型状态变为禁用 |
+| 4 | 勾选模型 → 点击"批量删除" | 弹出确认弹窗 |
+| 5 | 确认删除 | 选中的模型被删除,列表刷新 |
+
+### 验证要点
+
+- [ ] 未勾选时批量操作按钮禁用
+- [ ] 勾选数量正确显示
+- [ ] 批量操作有确认提示
+- [ ] 操作后列表正确刷新
+
+---
+
+## 六、SSO 统一认证(任务 8.5)
+
+### 测试入口
+
+登录页 → SSO 登录按钮(需配置真实 SSO 服务端)
+
+### 测试步骤
+
+| 步骤 | 操作 | 预期结果 |
+|:----:|------|----------|
+| 1 | 访问登录页 | 页面正常渲染 |
+| 2 | 点击 SSO 登录(如已配置) | 跳转到 SSO 授权页 |
+| 3 | SSO 授权完成 | 回调到平台,自动登录 |
+
+### API 验证
+
+```bash
+# 获取 SSO 授权 URL
+curl -s http://127.0.0.1:8080/admin/api/sso/authorize | python -m json.tool
+```
+
+---
+
+## 七、用户组批量授权(任务 11)
+
+### 测试入口
+
+系统管理 → 用户组管理(`/system/chat/group`)
+
+### 测试步骤
+
+| 步骤 | 操作 | 预期结果 |
+|:----:|------|----------|
+| 1 | 进入用户组管理页 | 显示用户组列表 |
+| 2 | 创建新用户组 | 输入名称,提交成功 |
+| 3 | 进入用户组详情 | 显示成员列表和授权列表 |
+| 4 | 批量添加成员 | 选择用户,添加成功 |
+| 5 | 批量移除成员 | 选择成员,移除成功 |
+| 6 | 批量授权资源 | 选择资源类型和 ID,授权成功 |
+| 7 | 批量撤销授权 | 选择授权记录,撤销成功 |
+
+### API 验证
+
+```bash
+# 获取用户组列表
+curl -H "Authorization: Bearer YOUR_TOKEN" \
+  http://127.0.0.1:8080/admin/api/workspace/default/user_group
+
+# 创建用户组
+curl -X POST -H "Authorization: Bearer YOUR_TOKEN" \
+  -H "Content-Type: application/json" \
+  -d '{"name":"测试用户组"}' \
+  http://127.0.0.1:8080/admin/api/workspace/default/user_group/create
+```
+
+---
+
+## 八、智能体记忆(任务 2)
+
+### 测试入口
+
+智能体设置 → 记忆配置
+
+### 测试步骤
+
+| 步骤 | 操作 | 预期结果 |
+|:----:|------|----------|
+| 1 | 创建或编辑智能体 | 进入设置页面 |
+| 2 | 开启记忆功能 | 记忆配置区域展开 |
+| 3 | 添加记忆条目 | 输入内容、选择类型,保存成功 |
+| 4 | 对话测试 | 多轮对话后,智能体能引用历史记忆 |
+| 5 | 禁用某条记忆 | 记忆状态切换,对话中不再引用 |
+
+---
+
+## 九、专业知识库(任务 1)
+
+### 测试入口
+
+知识库列表 → 创建专业知识库
+
+### 测试步骤
+
+| 步骤 | 操作 | 预期结果 |
+|:----:|------|----------|
+| 1 | 点击创建知识库 | 选择知识库类型 |
+| 2 | 选择"专业知识库" | 显示样本中心配置表单 |
+| 3 | 填写样本中心连接信息 | 输入 base_url、app_id、app_secret |
+| 4 | 获取知识库列表 | 从样本中心拉取知识库列表 |
+| 5 | 选择知识库导入 | 提交导入任务 |
+| 6 | 查看导入进度 | 显示任务状态和进度 |
+| 7 | 导入完成后检索 | 知识库可正常检索 |
+
+---
+
+## 十、智能体发布权限(任务 3)
+
+### 测试入口
+
+应用概览页 → 访问限制按钮
+
+### 测试步骤
+
+| 步骤 | 操作 | 预期结果 |
+|:----:|------|----------|
+| 1 | 进入应用概览页 → 点击"访问限制" | 弹出权限配置对话框 |
+| 2 | 开启白名单 | 输入 IP 列表,保存 |
+| 3 | 使用白名单内 IP 访问 | 正常响应 |
+| 4 | 使用白名单外 IP 访问 | 被拒绝 |
+| 5 | 配置嵌入参数 | 复制嵌入代码 |
+| 6 | 在网页中嵌入测试 | 智能体正常加载 |
+
+---
+
+## 问题记录
+
+| 序号 | 模块 | 问题描述 | 严重程度 | 状态 |
+|:----:|------|----------|:--------:|:----:|
+| 1 | | | | |
+| 2 | | | | |
+| 3 | | | | |
+
+---
+
+## 测试环境信息
+
+| 项目 | 值 |
+|------|-----|
+| 平台版本 | ZhAgentOS 2.0 |
+| 浏览器 | Chrome 最新版 |
+| 操作系统 | Windows 11 |
+| Docker 版本 | |
+| 数据库 | PostgreSQL 16 + pgvector |

+ 200 - 0
docs/智能体平台功能完善清单.md

@@ -0,0 +1,200 @@
+# 智能体平台功能完善清单
+
+> 基于《智能体平台功能清单》需求文档,对比项目当前实现状态制定
+>
+> 最后更新:2026-05-19
+
+---
+
+## 一、需求 vs 现状对照
+
+| 序号 | 模块 | 功能点 | 状态 | 优先级 | 备注 |
+|:----:|------|--------|:----:|:------:|------|
+| 1 | 基础配置 | 模型接入(公域/私域) | ✅ | - | 24家Provider + OpenAI Compatible |
+| 2 | 基础配置 | 工具配置(三方工具/API Key) | ✅ | - | 6种工具类型 + MCP集成 |
+| 3 | 基础配置 | (需求文档空项) | - | - | - |
+| 4 | 系统管理 | 用户管理 | ✅ | - | 完整CRUD + 多种认证方式 |
+| 5 | 系统管理 | 角色管理 | ✅ | - | 3种角色 + 细粒度权限 |
+| 6 | 系统管理 | 资源授权(智能体/工具/模型) | ✅ | - | 工作空间级资源授权 |
+| 7 | 系统管理 | (需求文档空项) | - | - | - |
+| 8 | 知识库管理 | 通用知识库 | ✅ | - | 文档/表格/QA/向量检索 |
+| 9 | 知识库管理 | 工作流知识库 | ✅ | - | 工作流编排 + 数据入库 |
+| 10 | 知识库管理 | 知识库检索 | ✅ | - | 语义检索 + 相似度阈值 |
+| 11 | 知识库管理 | 专业知识库 | ✅ | P0 | 样本中心API对接 + 前端UI已完成 |
+| 12 | 智能体管理 | 简单智能体 | ✅ | - | SIMPLE模式对话 |
+| 13 | 智能体管理 | 高级智能体 | ✅ | P0 | 工作流 + 智能体记忆已完成 |
+| 14 | 智能体管理 | 智能体管理 | ✅ | - | 全生命周期 + 版本管理 |
+| 15 | 智能体管理 | 工作流管理 | ✅ | - | 可视化画布 + 30+节点 |
+| 16 | 智能体管理 | 智能体发布 | ✅ | P0 | 白名单 + 嵌入 + 访问控制已完成 |
+| 17 | 智能体管理 | Skills技能 | ✅ | - | SKILL工具类型已支持 |
+| 18 | 插件管理 | 插件注册封装 | ✅ | P1 | 独立插件模块已实现 |
+| 19 | 插件管理 | 插件日常管理 | ✅ | P1 | 启用/禁用/卸载/重装已实现 |
+| 20 | 插件管理 | 插件测试验证与版本控制 | ✅ | P1 | 标准错误码 + 异常分类已实现 |
+
+**统计**:✅ 已完成 17 项 | ⬜ 空项 2 项 | 🔧 新增增强 3 项(知识库统计、应用限流、用户组授权)
+
+---
+
+## 二、优先级定义
+
+- **P0(核心)**:必须完成,直接影响核心业务流程
+- **P1(重要)**:应该完成,提升平台完整性和可维护性
+- **P2(增强)**:可以完成,锦上添花的功能
+
+---
+
+## 三、详细任务清单
+
+### P0 - 核心功能
+
+#### 任务 1:专业知识库打通样本中心
+- [x] 后端:新增专业知识库类型枚举(`apps/knowledge/models/`)— `SAMPLE_CENTER = 5`
+- [x] 后端:新增样本中心 API 客户端(`apps/knowledge/services/sample_center_client.py`)
+- [x] 后端:新增样本中心对接视图(`apps/knowledge/views/sample_center.py`)
+- [x] 后端:新增样本中心路由(`apps/knowledge/urls.py`)
+- [x] 前端:新增样本中心 API 调用(`ui/src/api/knowledge/sample-center.ts`)
+- [x] 前端:新增样本中心知识库创建对话框(`ui/src/views/knowledge/create-component/CreateSampleCenterKnowledgeDialog.vue`)
+- [x] 验证:枚举导入 ✅ | 模块导入 ✅ | 路由注册 ✅ | API 端点响应 ✅
+
+#### 任务 2:智能体记忆功能
+- [x] 后端:新增 ApplicationMemory 模型(`apps/application/models/`)
+- [x] 后端:新增记忆 CRUD API(`apps/application/views/`)
+- [x] 后端:新增记忆服务(`apps/application/services/`)
+- [x] 后端:新增数据库迁移并应用
+- [x] 前端:新增记忆 API(`ui/src/api/application/memory.ts`)
+- [x] 前端:新增记忆配置组件(`ui/src/views/application/component/MemorySettingDialog.vue`)
+- [x] 验证:模块导入 ✅ | 路由注册 ✅ | 数据库迁移 ✅ | API 端点响应 ✅
+
+#### 任务 3:智能体发布权限控制增强
+- [x] 后端:发布权限配置字段已存在于 ApplicationAccessToken 模型(white_active, white_list, authentication)
+- [x] 后端:发布 API 已支持白名单(通过 AccessToken 管理)
+- [x] 前端:发布设置 UI 已实现(LimitDialog.vue + XPackLimitDrawer.vue)
+- [x] 验证:白名单功能已实现 ✅ | 嵌入功能已实现(EmbedDialog.vue)✅ | 访问控制已实现 ✅
+
+---
+
+### P1 - 重要功能
+
+#### 任务 4:插件管理模块 - 注册封装
+- [x] 后端:新建 Plugin 模型(`apps/plugin/models/plugin.py`)
+- [x] 后端:新建 PluginVersion 模型(`apps/plugin/models/plugin_version.py`)
+- [x] 后端:实现插件注册服务(`apps/plugin/services/plugin_registry.py`)
+- [x] 后端:实现插件 CRUD API(`apps/plugin/views/plugin_view.py`)
+- [x] 后端:新增数据库迁移并应用
+- [x] 前端:新增插件 API(`ui/src/api/plugin/plugin.ts`)
+- [x] 前端:新建插件列表页(`ui/src/views/plugin/index.vue`)
+- [x] 前端:新建插件卡片组件(`ui/src/views/plugin/component/PluginCard.vue`)
+- [x] 前端:新建创建插件对话框(`ui/src/views/plugin/component/CreatePluginDialog.vue`)
+- [x] 前端:新增插件路由(`ui/src/router/modules/plugin.ts`)
+- [x] 验证:模块导入 ✅ | 路由注册 ✅ | 数据库迁移 ✅ | API 端点响应 ✅
+
+#### 任务 5:插件管理模块 - 生命周期管理
+- [x] 后端:实现插件启用/禁用 API(`apps/plugin/views/plugin_view.py`)
+- [x] 后端:实现插件卸载/重装 API(`apps/plugin/views/plugin_view.py`)
+- [x] 前端:实现插件状态切换 UI(`ui/src/views/plugin/component/PluginCard.vue`)
+- [x] 前端:实现插件卸载确认弹窗(`ui/src/views/plugin/index.vue`)
+- [x] 验证:所有生命周期 API 端点返回 401(需认证),路由正确
+
+#### 任务 6:插件测试验证与版本控制
+- [x] 后端:新增 PluginTest 模型(`apps/plugin/models/plugin_test.py`)
+- [x] 后端:实现插件测试服务(`apps/plugin/services/plugin_validator.py`)
+- [x] 后端:定义标准化错误码体系(`apps/plugin/services/error_codes.py`)
+- [x] 后端:实现适配异常分类(`apps/plugin/services/exception_handler.py`)
+- [x] 后端:实现版本管理 API(`apps/plugin/views/plugin_test_view.py`)
+- [x] 前端:新建插件测试抽屉(`ui/src/views/plugin/component/PluginTestDrawer.vue`)
+- [x] 前端:新建版本历史组件(`ui/src/views/plugin/component/VersionHistory.vue`)
+- [x] 验证:所有测试和版本管理 API 端点返回 401(需认证),路由正确
+
+#### 任务 7:模型批量操作 API
+- [x] 后端:实现批量删除端点(`apps/models_provider/views/model.py`)
+- [x] 后端:实现批量启用/禁用端点(`apps/models_provider/views/model.py`)
+- [x] 前端:模型列表页添加批量操作按钮
+- [x] 验证:批量删除模型 → 批量禁用模型
+
+#### 任务 8:工具使用统计分析
+- [x] 后端:新增统计 API(`apps/tools/views/tool.py`)
+- [x] 后端:实现调用次数、成功率、耗时统计
+- [x] 前端:新增统计面板(`ui/src/views/tool/`)
+- [x] 验证:调用工具 → 查看统计面板
+
+---
+
+### P2 - 增强功能
+
+#### 任务 8.5:SSO 统一认证接入
+- [x] 后端:新建 SSO 客户端(`apps/sso/services/sso_client.py`)
+- [x] 后端:新建 SSO 视图(`apps/sso/views/sso_view.py`)
+- [x] 后端:新增 SSO 路由(`apps/sso/urls.py`)
+- [x] 前端:新增 SSO API(`ui/src/api/sso.ts`)
+- [x] 前端:新增 SSO 回调页面(`ui/src/views/sso/SSOCallback.vue`)
+- [x] 前端:新增 SSO 回调路由(`/auth/callback`)
+- [x] 验证:模块导入 ✅ | 路由注册 ✅ | API 端点响应 ✅ | 待配置真实 SSO 服务端联调
+
+#### 任务 9:知识库用量统计 API
+- [x] 后端:新增知识库统计端点(`apps/knowledge/views/knowledge.py`)
+- [x] 前端:知识库 API 添加统计接口(`ui/src/api/knowledge/knowledge.ts`)
+- [x] 验证:API 端点响应 401(未认证),路由注册正确
+
+#### 任务 10:应用 API 调用限流
+- [x] 后端:新增限流配置模型(`apps/application/models/rate_limit.py`)
+- [x] 后端:实现限流中间件(`apps/application/middleware/rate_limit.py`)
+- [x] 前端:新增限流配置 API(`ui/src/api/application/rate_limit.ts`)
+- [x] 验证:所有限流 API 端点返回 401(需认证),路由正确
+
+#### 任务 11:用户组批量授权增强
+- [x] 后端:基于已有 system_manage.UserGroup 模型扩展批量授权功能
+- [x] 后端:实现批量授权 API(`apps/users/views/user_group_view.py`)
+- [x] 前端:新增用户组 API(`ui/src/api/users/user_group.ts`)
+- [x] 验证:所有用户组 API 端点返回 401(需认证),路由正确
+
+---
+
+## 四、验收标准
+
+### 功能验收
+- [ ] 专业知识库能从样本中心导入数据并检索
+- [ ] 智能体记忆能持久化存储和读取
+- [ ] 智能体发布支持白名单和嵌入控制
+- [ ] 插件能注册、启用、禁用、卸载、重装
+- [ ] 插件测试能覆盖异常场景并返回标准错误码
+- [ ] 模型能批量删除和批量禁用
+- [ ] 工具使用统计能展示调用次数和成功率
+
+### 技术验收
+- [x] `python manage.py migrate` 成功
+- [x] `npm run build:admin` 构建成功
+- [x] `docker-compose up --build` 所有服务正常
+- [x] 所有新 API 端点返回正确响应(401 需认证)
+- [ ] 所有新页面 UI 渲染正常(待浏览器测试)
+
+---
+
+## 五、开发顺序建议
+
+```
+Phase 1(1-2天):P0 任务 1-3(专业知识库、智能体记忆、发布权限)
+    ↓
+Phase 2(3-5天):P1 任务 4-6(插件管理模块核心)
+    ↓
+Phase 3(1-2天):P1 任务 7-8(模型批量操作、工具统计)
+    ↓
+Phase 4(按需):P2 任务 9-11(增强功能)
+```
+
+---
+
+## 六、技术栈参考
+
+| 层次 | 技术 |
+|------|------|
+| 前端 | Vue 3 + TypeScript + Element Plus + Vite |
+| 后端 | Django 5.2 + DRF + drf-spectacular |
+| 数据库 | PostgreSQL 16 + pgvector |
+| 缓存/消息 | Redis 7 |
+| 任务队列 | Celery 5.5 + django-celery-beat |
+| AI 框架 | LangChain + LangGraph |
+| 部署 | Docker Compose + Nginx |
+
+---
+
+*本文档由 Claude 自动生成,基于需求文档分析和项目现状评估*

+ 590 - 0
docs/样本中心提供API接口文档_外部.md

@@ -0,0 +1,590 @@
+# 样本中心对外API接口文档
+
+## 1. 概述
+
+### 1.1 文档目的
+本文档定义样本中心对外提供的API接口规范,供外部系统(如采集系统)接入使用。
+
+### 1.2 基础信息
+| 项目 | 说明 |
+|------|------|
+| Base URL | `https://{host}/api/v1` |
+| 数据格式 | JSON |
+| 字符编码 | UTF-8 |
+| 认证方式 | Bearer Token |
+
+---
+
+## 2. 认证与鉴权
+
+### 2.1 Token 设计方案
+
+采用 **API Key + Secret 签名机制**,外部系统通过以下方式获取访问 Token:
+
+#### 2.1.1 凭证分配
+每个接入系统在样本中心注册后,分配一对凭证:
+- `app_id`:应用唯一标识
+- `app_secret`:应用密钥(仅初始化时展示一次,需妥善保存)
+
+#### 2.1.2 获取访问令牌
+
+**POST /api/v1/auth/token**
+
+| 项目 | 说明 |
+|------|------|
+| Content-Type | application/json |
+
+**请求参数:**
+
+| 参数名 | 类型 | 必录 | 说明 |
+|--------|------|------|------|
+| app_id | string | 是 | 应用标识 |
+| app_secret | string | 是 | 应用密钥 |
+
+**请求示例:**
+```json
+{
+  "app_id": "collect_system_001",
+  "app_secret": "sk_xxxxxxxxxxxxxxxx"
+}
+```
+
+**响应参数:**
+
+| 参数名 | 类型 | 说明 |
+|--------|------|------|
+| access_token | string | 访问令牌 |
+| expires_in | integer | 过期时间(秒),默认 7200 |
+| token_type | string | 令牌类型,固定 "Bearer" |
+
+**响应示例:**
+```json
+{
+  "code": "000000",
+  "message": "success",
+  "data": {
+    "access_token": "eyJhbGciOiJIUzI1NiIs...",
+    "expires_in": 7200,
+    "token_type": "Bearer"
+  }
+}
+```
+
+#### 2.1.3 Token 使用
+所有业务接口需在 HTTP Header 中携带 Token:
+```
+Authorization: Bearer {access_token}
+X-App-Id: {app_id}
+```
+
+#### 2.1.4 Token 管理规则
+- Token 有效期 2 小时,过期后需重新获取
+- 支持多 Token 并存,新 Token 获取不影响旧 Token 使用
+- 单 app_id 同时最多持有 3 个有效 Token
+- Token 被吊销后即时失效
+
+---
+
+## 3. 统一响应格式
+
+所有接口返回统一的 JSON 结构:
+
+```json
+{
+  "code": "000000",
+  "message": "success",
+  "data": { ... },
+  "request_id": "req_xxxxxxxxxxxxxxxx"
+}
+```
+
+| 参数名 | 类型 | 说明 |
+|--------|------|------|
+| code | string | 状态码,"000000" 表示成功,非 "000000" 表示失败 |
+| message | string | 提示信息 |
+| data | object/array/null | 业务数据 |
+| request_id | string | 请求追踪 ID |
+
+### 3.1 错误码定义
+
+| 错误码 | 说明 |
+|--------|------|
+| 000000 | 成功 |
+| 000400 | 请求参数错误 |
+| 000401 | 认证失败(Token 无效/过期) |
+| 000403 | 权限不足 |
+| 000404 | 资源不存在 |
+| 000429 | 请求频率超限 |
+| 000500 | 服务端内部错误 |
+| 001001 | 知识库不存在 |
+| 001002 | 知识库未启用 |
+| 002001 | 批量入库参数校验失败 |
+| 002002 | 批量入库任务不存在 |
+| 002003 | 批量入库任务数据格式错误 |
+
+---
+
+## 4. 接口详情
+
+### 4.1 知识库列表查询
+
+**GET /api/v1/knowledge-bases**
+
+查询当前应用有权限访问的已启用知识库列表。
+
+**请求参数(Query):**
+
+| 参数名 | 类型 | 必录 | 说明 |
+|--------|------|------|------|
+| page | integer | 否 | 页码,默认 1 |
+| page_size | integer | 否 | 每页条数,默认 20,最大 100 |
+
+**响应参数:**
+
+```json
+{
+  "code": "000000",
+  "message": "success",
+  "data": {
+    "total": 10,
+    "page": 1,
+    "page_size": 20,
+    "items": [
+      {
+        "id": "5138215886300893584",
+        "name": "建设工程知识库",
+        "parent_table": "kb_parent_table_001",
+        "child_table": "kb_child_table_001",
+        "document_count": 1520,
+        "status": 1,
+        "created_at": "2026-05-10T10:30:00Z",
+        "created_by": "admin",
+        "metadata_schema": [
+          {
+            "field_name_cn": "文档编号",
+            "field_name_en": "doc_number",
+            "field_type": "string",
+            "description": "文档的唯一编号"
+          },
+          {
+            "field_name_cn": "发布日期",
+            "field_name_en": "publish_date",
+            "field_type": "date",
+            "description": "文档发布日期"
+          }
+        ]
+      }
+    ]
+  },
+  "request_id": "req_xxxxxxxxxxxxxxxx"
+}
+```
+
+**列表项字段说明:**
+
+| 参数名 | 类型 | 说明 |
+|--------|------|------|
+| id | string | 知识库ID |
+| name | string | 知识库名称 |
+| parent_table | string | 父集合表名 |
+| child_table | string | 子集合表名 |
+| document_count | integer | 文档数量 |
+| status | integer | 状态:1-启用,0-禁用 |
+| created_at | string | 创建时间,ISO 8601 格式 |
+| created_by | string | 创建人 |
+| metadata_schema | array | 元数据字典定义列表 |
+
+**metadata_schema 字段说明:**
+
+| 参数名 | 类型 | 说明 |
+|--------|------|------|
+| field_name_cn | string | 字段中文名称 |
+| field_name_en | string | 字段英文名称 |
+| field_type | string | 字段类型:string/integer/float/date/boolean/array |
+| description | string | 字段描述 |
+
+---
+
+### 4.2 知识库详情查询
+
+**GET /api/v1/knowledge-bases/{id}**
+
+查询指定知识库的详细信息,包括元数据字典定义。
+
+**路径参数:**
+
+| 参数名 | 类型 | 必录 | 说明 |
+|--------|------|------|------|
+| id | string | 是 | 知识库ID |
+
+**响应参数:**
+
+```json
+{
+  "code": "000000",
+  "message": "success",
+  "data": {
+    "id": "5138215886300893584",
+    "name": "建设工程知识库",
+    "description": "建设工程相关法规文档",
+    "parent_table": "kb_parent_table_001",
+    "child_table": "kb_child_table_001",
+    "document_count": 1520,
+    "status": 1,
+    "created_at": "2026-05-10T10:30:00Z",
+    "created_by": "admin",
+    "updated_at": "2026-05-15T14:20:00Z",
+    "metadata_schema": [
+      {
+        "field_name_cn": "文档编号",
+        "field_name_en": "doc_number",
+        "field_type": "string",
+        "description": "文档的唯一编号"
+      },
+      {
+        "field_name_cn": "发布日期",
+        "field_name_en": "publish_date",
+        "field_type": "date",
+        "description": "文档发布日期"
+      },
+      {
+        "field_name_cn": "文档来源",
+        "field_name_en": "source",
+        "field_type": "string",
+        "description": "文档来源机构"
+      }
+    ]
+  },
+  "request_id": "req_xxxxxxxxxxxxxxxx"
+}
+```
+
+---
+
+### 4.3 知识库批量入库
+
+**POST /api/v1/knowledge-bases/{kb_id}/batch-import**
+
+提交批量入库任务,系统将片段向量化后存入向量数据库。
+
+**路径参数:**
+
+| 参数名 | 类型 | 必录 | 说明 |
+|--------|------|------|------|
+| kb_id | string | 是 | 知识库ID |
+
+**请求体:**
+
+```json
+{
+  "task_no": "IMP202605170001",
+  "callback_url": "https://collect-system.example.com/api/callback/import-result",
+  "parents": [
+    {
+      "index": 0,
+      "parent_id": "5138215886300893584",
+      "hierarchy": "建设工程质量管理条例",
+      "text": "第一条 为了加强对建设工程质量的管理...",
+      "metadata": {
+        "doc_number": "国务院令第279号",
+        "publish_date": "2000-01-30"
+      },
+      "doc_id": "doc_001",
+      "tag_list": ["法规", "质量管理"],
+      "permission": {
+        "visible_roles": ["role_admin", "role_engineer"],
+        "visible_users": ["user_001"]
+      }
+    }
+  ],
+  "children": [
+    {
+      "index": 0,
+      "parent_id": "5138215886300893584",
+      "hierarchy": "第一章 总则",
+      "text": "第一条 为了加强对建设工程质量的管理,保证建设工程质量...",
+      "metadata": {
+        "doc_number": "国务院令第279号",
+        "section": "第一章"
+      },
+      "doc_id": "doc_001",
+      "tag_list": ["法规", "总则"],
+      "permission": {
+        "visible_roles": ["role_admin"]
+      }
+    }
+  ]
+}
+```
+
+**请求参数说明:**
+
+| 参数名 | 类型 | 必录 | 说明 |
+|--------|------|------|------|
+| task_no | string | 是 | 入库任务号,由调用方生成,用于记录当前入库任务状态及后续查询进度 |
+| callback_url | string | 否 | 回调地址,任务完成后样本中心将处理结果推送至该地址,不传则不回调 |
+| parents | array | 是 | 父段信息列表 |
+| children | array | 否 | 子段信息列表 |
+
+**parents/children 项字段说明:**
+
+| 参数名 | 类型 | 必录 | 说明 |
+|--------|------|------|------|
+| index | integer | 是 | 分片索引号 |
+| parent_id | string | 是 | 父段ID,子段通过此字段关联父段 |
+| hierarchy | string | 否 | 章节信息 |
+| text | string | 是 | 段文本信息 |
+| metadata | object | 否 | 段元数据信息,键值对结构 |
+| doc_id | string | 否 | 文档ID |
+| tag_list | array | 否 | 标签列表 |
+| permission | object | 否 | 权限配置 |
+
+**permission 字段说明:**
+
+| 参数名 | 类型 | 说明 |
+|--------|------|------|
+| visible_roles | array | 可见角色 ID 列表 |
+| visible_users | array | 可见用户 ID 列表 |
+
+**响应参数:**
+
+```json
+{
+  "code": "000000",
+  "message": "success",
+  "data": {
+    "task_id": "task_20260517xxxxxxxx",
+    "status": "pending"
+  },
+  "request_id": "req_xxxxxxxxxxxxxxxx"
+}
+```
+
+| 参数名 | 类型 | 说明 |
+|--------|------|------|
+| task_id | string | 任务ID |
+| status | string | 任务状态:pending-待处理 |
+
+---
+
+### 4.4 批量入库任务查询
+
+**GET /api/v1/knowledge-bases/batch-import/{task_id}**
+
+查询批量入库任务的处理状态和结果。
+
+**路径参数:**
+
+| 参数名 | 类型 | 必录 | 说明 |
+|--------|------|------|------|
+| task_id | string | 是 | 入库任务ID |
+
+**响应参数:**
+
+**进行中:**
+```json
+{
+  "code": "000000",
+  "message": "success",
+  "data": {
+    "task_id": "task_20260517xxxxxxxx",
+    "task_no": "IMP202605170001",
+    "status": "processing",
+    "progress": {
+      "total": 100,
+      "processed": 45,
+      "succeeded": 43,
+      "failed": 2
+    },
+    "created_at": "2026-05-17T10:00:00Z",
+    "updated_at": "2026-05-17T10:01:30Z"
+  },
+  "request_id": "req_xxxxxxxxxxxxxxxx"
+}
+```
+
+**已完成:**
+```json
+{
+  "code": "000000",
+  "message": "success",
+  "data": {
+    "task_id": "task_20260517xxxxxxxx",
+    "task_no": "IMP202605170001",
+    "status": "completed",
+    "progress": {
+      "total": 100,
+      "processed": 100,
+      "succeeded": 98,
+      "failed": 2
+    },
+    "created_at": "2026-05-17T10:00:00Z",
+    "completed_at": "2026-05-17T10:05:00Z",
+    "failures": [
+      {
+        "index": 12,
+        "parent_id": "5138215886300893584",
+        "error": "文本内容为空,跳过入库"
+      },
+      {
+        "index": 56,
+        "parent_id": "5138215886300893584",
+        "error": "向量化模型调用超时"
+      }
+    ]
+  },
+  "request_id": "req_xxxxxxxxxxxxxxxx"
+}
+```
+
+**失败:**
+```json
+{
+  "code": "000000",
+  "message": "success",
+  "data": {
+    "task_id": "task_20260517xxxxxxxx",
+    "task_no": "IMP202605170001",
+    "status": "failed",
+    "error": "向量数据库连接异常",
+    "created_at": "2026-05-17T10:00:00Z",
+    "completed_at": "2026-05-17T10:00:05Z"
+  },
+  "request_id": "req_xxxxxxxxxxxxxxxx"
+}
+```
+
+**字段说明:**
+
+| 参数名 | 类型 | 说明 |
+|--------|------|------|
+| task_id | string | 任务ID,系统生成 |
+| task_no | string | 入库任务号,调用方传入 |
+| status | string | 任务状态:pending-待处理,processing-处理中,completed-已完成,failed-失败 |
+| progress | object | 进度信息 |
+| progress.total | integer | 总条数 |
+| progress.processed | integer | 已处理条数 |
+| progress.succeeded | integer | 成功条数 |
+| progress.failed | integer | 失败条数 |
+| failures | array | 失败明细列表 |
+| error | string | 整体失败原因(仅 status=failed 时返回) |
+| created_at | string | 任务创建时间 |
+| updated_at | string | 状态更新时间 |
+| completed_at | string | 任务完成时间 |
+
+---
+
+## 5. 任务状态流转
+
+```
+pending → processing → completed
+                      → failed
+```
+
+| 状态 | 说明 |
+|------|------|
+| pending | 任务已接收,排队等待处理 |
+| processing | 任务正在处理中 |
+| completed | 任务处理完成(可能部分失败) |
+| failed | 任务整体失败,无数据入库 |
+
+---
+
+## 6. 轮询与回调机制
+
+### 6.1 轮询方式
+外部系统提交任务后,通过 **GET /api/v1/knowledge-bases/batch-import/{task_id}** 轮询任务状态。
+
+**建议轮询策略:**
+- 初始间隔 2 秒
+- 每次轮询后间隔 × 1.5,上限 30 秒
+- 任务终态(completed/failed)后停止轮询
+
+### 6.2 回调方式(可选)
+
+外部系统在提交批量入库任务时,若无法主动轮询,可在请求体中传入 `callback_url`。任务完成后样本中心主动向该地址推送处理结果。
+
+**回调请求:** POST {callback_url}
+
+```json
+{
+  "task_id": "task_20260517xxxxxxxx",
+  "task_no": "IMP202605170001",
+  "status": "completed",
+  "kb_id": "5138215886300893584",
+  "progress": {
+    "total": 100,
+    "processed": 100,
+    "succeeded": 98,
+    "failed": 2
+  },
+  "completed_at": "2026-05-17T10:05:00Z"
+}
+```
+
+回调失败时最多重试 3 次,间隔 10s / 30s / 60s。
+
+---
+
+
+## 7. 接口总览
+
+| 序号 | 方法 | 路径 | 说明 |
+|------|------|------|------|
+| 1 | POST | /api/v1/auth/token | 获取访问令牌 |
+| 2 | GET | /api/v1/knowledge-bases | 知识库列表查询 |
+| 3 | GET | /api/v1/knowledge-bases/{id} | 知识库详情查询 |
+| 4 | POST | /api/v1/knowledge-bases/{kb_id}/batch-import | 批量入库提交 |
+| 5 | GET | /api/v1/knowledge-bases/batch-import/{task_id} | 入库任务查询 |
+
+---
+
+## 9. 异步任务表设计
+
+### 9.1 表结构定义
+
+**表名:`t_samp_task_management`**
+
+样本中心异步任务表,用于记录所有异步任务的生命周期。
+
+
+### 9.2 任务状态枚举
+
+| 状态值 | 说明 |
+|--------|------|
+| pending | 任务已接收,排队等待处理 |
+| processing | 任务正在处理中 |
+| completed | 任务处理完成(可能部分失败) |
+| failed | 任务整体失败,无数据入库 |
+
+### 9.3 回调状态枚举
+
+| 状态值 | 说明 |
+|--------|------|
+| pending | 待处理 |
+| processing | 处理中 |
+| success | 回调成功 |
+| failed | 回调失败 |
+
+### 9.4 任务处理逻辑
+
+1. **任务接收**:批量入库任务接收请求后,先将任务保存到任务表,状态设为 `pending`
+2. **异步处理**:发起异步任务进行知识片段入库处理,同时更新任务表状态为 `processing`
+3. **任务完成**:处理完成后更新任务表状态为 `completed`,记录完成时间
+4. **任务失败**:处理失败时更新任务表状态为 `failed`,同时写入错误信息到 `error_message`
+5. **回调通知**:任务处理完成后,若 `callback_url` 不为空,则发起回调请求,并根据回调结果更新 `callback_status`
+6. **状态查询**:入库任务查询接口直接查询任务表的 `status` 和 `callback_status`
+
+### 9.5 任务类型扩展
+
+任务表通过 `task_type` 字段支持多种任务类型,当前支持:
+
+| task_type | 说明 |
+|-----------|------|
+| bi | 批量入库任务 |
+
+后续可按需扩展其他任务类型(如:批量删除、数据同步等)。
+
+
+
+

+ 681 - 0
docs/统一认证平台接入流程及API接口文档(1)(1).md

@@ -0,0 +1,681 @@
+# 统一认证平台(LQAI-middle-platform)接入流程及 API 接口文档
+
+> **参考样本**: 样本中心(LQAdminPlatform)已实现的统一认证接入方案
+> **统一认证平台地址**: `http://192.168.92.61:8200`
+> **统一认证平台前端**: `http://192.168.92.61:9200`
+
+---
+
+## 一、系统架构概览
+
+```
+┌─────────────────────────────────────────────────────────────┐
+│                统一认证平台 (LQAI-middle-platform)            │
+│                    端口: 8200 (后端) / 9200 (前端)            │
+│                                                             │
+│  功能:                                                       │
+│  • 用户注册 / 登录 / 密码管理                                  │
+│  • 应用管理 (子应用注册、client_id/secret 生成)                │
+│  • OAuth2 授权码流程                                          │
+│  • RBAC 角色权限管理                                          │
+│  • JWT Token 签发与验证                                       │
+│  • 子应用工作台(点击子应用图标触发 SSO 跳转)                  │
+│                                                             │
+│  数据存储: MySQL (lq_ai_middle_platform) + Redis             │
+└──────────────────────┬──────────────────────────────────────┘
+                       │ OAuth2 / HTTP
+                       │
+        ┌──────────────┼──────────────┐
+        │              │              │
+   ┌────▼────┐  ┌─────▼─────┐  ┌────▼────┐
+   │样本中心  │  │Agent平台   │  │ 标注平台 │  ... 其他子系统
+   │(8000)   │  │ (8002)    │  │ (9003)  │
+   └─────────┘  └───────────┘  └─────────┘
+```
+
+### 角色定位
+
+| 角色 | 说明 |
+|------|------|
+| **统一认证平台** (LQAI-middle-platform) | OAuth2 授权服务器(Authorization Server),同时也是一个 SSO 中心 |
+| **子系统** (如样本中心 LQAdminPlatform) | OAuth2 客户端(Client),同时自身也是一个小型 OAuth2 服务器,可以为下游系统提供授权 |
+| **子系统前端** | 负责 SSO 回调处理和 Token 交换 |
+
+---
+
+## 二、SSO 单点登录接入流程
+
+### 2.1 整体流程图
+
+```
+用户在统一认证平台工作台点击子系统图标
+            │
+            ▼
+① 统一认证平台后端生成 OAuth2 授权码 code
+   POST /auth/sso-redirect  →  返回重定向URL: {frontend_url}/auth/callback?code=xxx
+            │
+            ▼
+② 302 重定向到子系统前端 /auth/callback?code=xxx
+            │
+            ▼
+③ 子系统前端 POST /api/oauth/exchange-code { "code": "xxx" }
+            │
+            ▼
+④ 子系统后端与统一认证平台进行后端到后端通信:
+   4a. POST {SSO_BASE_URL}/oauth/token  →  换取 SSO access_token
+   4b. GET  {SSO_BASE_URL}/oauth/userinfo →  获取用户信息+角色
+            │
+            ▼
+⑤ 子系统后端同步用户到本地数据库(查找或创建用户、同步角色)
+            │
+            ▼
+⑥ 子系统后端签发本地 JWT,返回 { token, refresh_token, user }
+            │
+            ▼
+⑦ 子系统前端保存 Token,跳转首页
+```
+
+### 2.2 详细步骤说明
+
+#### Step 1: 统一认证平台生成授权码
+
+当用户在统一认证平台前端工作台的"应用列表"中点击某个子系统图标时:
+
+- 统一认证平台后端调用 `POST /auth/sso-redirect` 接口
+- 后端为当前登录用户生成一个 OAuth2 授权码(authorization code),存入 Redis,有效期 10 分钟
+- 返回子系统的回调 URL,格式为 `{REDIRECT_URI}?code={auth_code}`
+
+#### Step 2: 重定向到子系统前端
+
+- 统一认证平台前端通过 302 重定向将用户引导至子系统前端的回调页面
+- URL 格式: `http://localhost:3000/auth/callback?code=xxxxxx`
+
+#### Step 3: 子系统前端调用换码接口
+
+子系统前端从 URL 参数中提取 `code`,调用子系统后端的换码接口:
+
+```typescript
+// 示例代码
+const code = new URLSearchParams(window.location.search).get('code');
+const response = await fetch('/api/oauth/exchange-code', {
+  method: 'POST',
+  headers: { 'Content-Type': 'application/json' },
+  body: JSON.stringify({ code })
+});
+const result = await response.json();
+// result.data.token       → 本地 JWT access_token
+// result.data.refresh_token → 刷新令牌
+// result.data.user         → 用户信息(含角色)
+```
+
+#### Step 4-5: 子系统后端与统一认证平台交互 + 同步用户
+
+子系统后端收到 `code` 后执行 `_sso_exchange_code` 核心流程:
+
+1. **用 code 换 SSO access_token** — 调用统一认证平台的 `/oauth/token` 端点
+2. **获取用户信息** — 调用统一认证平台的 `/oauth/userinfo` 端点
+3. **同步用户** — 在本地数据库查找或创建用户,同步角色信息
+
+#### Step 6-7: 签发本地 JWT 并返回
+
+- 子系统后端用自己的 `SECRET_KEY` 签发本地 JWT
+- 返回给前端 `{ token, refresh_token, user }`
+- 前端保存 Token 到 localStorage/sessionStorage,跳转首页
+
+---
+
+## 三、子系统接入前准备
+
+### 3.1 在统一认证平台注册子应用
+
+通过统一认证平台的系统管理模块(或数据库直接插入)在 `t_sys_app` 表中注册子系统:
+
+| 字段 | 说明 | 示例值 |
+|------|------|--------|
+| `name` | 子系统名称 | "样本中心" |
+| `app_key` | 客户端 ID(client_id),32 位随机字符串 | `WviiGL8KQE20tQhmhQPQhhJ5QpFK51F6` |
+| `app_secret` | 客户端密钥(client_secret),64 位随机字符串 | `9WXP88hEHJiHRSiUdmx7ip5oQPzY0bnJNsEswQoO4sk6juCplyJTcnAiZsv7e3lJ` |
+| `redirect_uris` | 允许的重定向 URI(JSON 数组) | `["http://localhost:3000/auth/callback"]` |
+| `scope` | 授权范围(JSON 数组) | `["profile", "email"]` |
+| `is_active` | 是否启用 | `1` |
+| `is_trusted` | 是否信任(跳过授权确认页) | `1` |
+| `home_url` | 子系统首页 URL | `http://localhost:3000` |
+| `icon_url` | 子系统图标 URL | 可选 |
+
+### 3.2 子系统侧配置
+
+在子系统的配置文件中添加 SSO 配置段:
+
+```ini
+[sso]
+SSO_BASE_URL=http://192.168.92.61:8200
+CLIENT_ID=WviiGL8KQE20tQhmhQPQhhJ5QpFK51F6
+CLIENT_SECRET=9WXP88hEHJiHRSiUdmx7ip5oQPzY0bnJNsEswQoO4sk6juCplyJTcnAiZsv7e3lJ
+REDIRECT_URI=http://localhost:3000/auth/callback
+FRONTEND_URL=http://localhost:3000
+SCOPE=email
+SSO_LOGOUT_REDIRECT_URL=http://192.168.92.61:9200/login
+```
+
+> **注意**: `CLIENT_ID` 和 `CLIENT_SECRET` 必须与统一认证平台 `t_sys_app` 表中注册的 `app_key` 和 `app_secret` 一致。
+
+### 3.3 角色映射
+
+子系统需要确保本地 `t_sys_role` 表中存在与统一认证平台对应的角色。样本中心的角色包括:
+
+| 统一认证平台角色 code | 说明 |
+|----------------------|------|
+| `super_admin` | 超级管理员 |
+| `sam_sys_admin` | 样本中心系统管理员 |
+| `sam_data_operator` | 样本中心数据操作员 |
+
+统一认证平台 `/oauth/userinfo` 返回的 `roles` 字段格式为对象数组:
+
+```json
+{
+  "roles": [
+    { "name": "超级管理员", "code": "super_admin" },
+    { "name": "样本中心系统管理员", "code": "sam_sys_admin" }
+  ]
+}
+```
+
+子系统从每个对象中提取 `code` 字段进行角色匹配和本地同步。
+
+---
+
+## 四、统一认证平台核心 API 接口
+
+以下接口由 **统一认证平台(LQAI-middle-platform,端口 8200)** 提供,子系统需要调用。
+
+### 4.1 获取 SSO 授权 URL
+
+> 供子系统前端构建"跳转到统一认证平台登录"的链接
+
+- **接口**: `GET /oauth/authorize`
+- **认证**: 不需要
+
+| 参数 | 类型 | 必填 | 说明 |
+|------|------|------|------|
+| `response_type` | string | 是 | 固定值 `code` |
+| `client_id` | string | 是 | 子应用的 app_key |
+| `redirect_uri` | string | 是 | 回调地址(必须在 t_sys_app 中注册过) |
+| `scope` | string | 否 | 授权范围,空格分隔,默认 `profile` |
+| `state` | string | 否 | 防 CSRF 状态参数 |
+
+**响应**: 302 重定向(如果用户已登录则重定向到 `redirect_uri?code=xxx`,未登录则重定向到登录页面)
+
+### 4.2 获取授权码 URL(SSO 免登)
+
+> 统一认证平台内部使用,当用户点击子系统图标时调用
+
+- **接口**: `POST /auth/sso-redirect`
+- **认证**: 需要用户已登录统一认证平台
+- **请求体**:
+
+```json
+{
+  "client_id": "WviiGL8KQE20tQhmhQPQhhJ5QpFK51F6",
+  "redirect_uri": "http://localhost:3000/auth/callback"
+}
+```
+
+- **响应**:
+
+```json
+{
+  "code": "000000",
+  "message": "success",
+  "data": {
+    "redirect_url": "http://localhost:3000/auth/callback?code=xxxxxx"
+  }
+}
+```
+
+### 4.3 令牌交换端点
+
+> **子系统后端调用此接口,用授权码换取 SSO access_token**
+
+- **接口**: `POST /oauth/token`
+- **Content-Type**: `application/x-www-form-urlencoded`
+- **认证**: 不需要
+
+| 参数 | 类型 | 必填 | 说明 |
+|------|------|------|------|
+| `grant_type` | string | 是 | 固定值 `authorization_code` |
+| `code` | string | 是 | 授权码 |
+| `redirect_uri` | string | 是 | 与 Step 1 中使用的 redirect_uri 一致 |
+| `client_id` | string | 是 | 子应用的 app_key |
+| `client_secret` | string | 是 | 子应用的 app_secret |
+
+**成功响应** (200):
+
+```json
+{
+  "access_token": "eyJhbGciOiJIUzI1NiIs...",
+  "token_type": "Bearer",
+  "expires_in": 7200,
+  "refresh_token": "xxxxxxxxxxxxxxxxxxxx",
+  "scope": "profile email"
+}
+```
+
+**失败响应**:
+
+```json
+{
+  "error": "invalid_grant",
+  "error_description": "授权码无效"
+}
+```
+
+| error 值 | 说明 |
+|----------|------|
+| `invalid_request` | 缺少必填参数 |
+| `unsupported_grant_type` | grant_type 不支持 |
+| `invalid_client` | client_id 或 client_secret 错误 |
+| `invalid_grant` | 授权码无效/已使用/过期,或 redirect_uri 不匹配 |
+
+### 4.4 获取用户信息
+
+> **子系统后端调用此接口,获取当前登录用户的详细信息**
+
+- **接口**: `GET /oauth/userinfo`
+- **认证**: Bearer Token(使用 4.3 返回的 access_token)
+
+**请求头**:
+
+```
+Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
+```
+
+**成功响应** (200):
+
+```json
+{
+  "sub": "user_xxx",
+  "username": "admin",
+  "email": "admin@example.com",
+  "avatar_url": "https://...",
+  "real_name": "管理员",
+  "company": "四川路桥",
+  "department": "技术部",
+  "position": "工程师",
+  "roles": [
+    { "name": "超级管理员", "code": "super_admin" },
+    { "name": "样本中心系统管理员", "code": "sam_sys_admin" }
+  ]
+}
+```
+
+> 返回的字段根据 token 的 scope 过滤:`profile` scope 返回基本信息,`email` scope 返回邮箱,`phone` scope 返回手机号。
+
+**失败响应**:
+
+```json
+{
+  "error": "invalid_token",
+  "error_description": "User not found or inactive"
+}
+```
+
+---
+
+## 五、子系统侧需要实现的 API 接口
+
+以下是子系统(如样本中心)自身需要实现的接口,用于完成 SSO 接入。
+
+### 5.1 授权码交换接口(核心免登接口)
+
+- **接口**: `POST /api/oauth/exchange-code`
+- **Content-Type**: `application/json`
+- **认证**: 不需要
+- **用途**: 前端收到 SSO 回调的 code 后,调用此接口换取本地 JWT
+
+**请求体**:
+
+```json
+{
+  "code": "xxxxxx"
+}
+```
+
+**成功响应**:
+
+```json
+{
+  "code": "000000",
+  "message": "登录成功",
+  "data": {
+    "token": "eyJhbGciOiJIUzI1NiIs...",
+    "refresh_token": "xxxxxxxxxxxxxxxxxxxx",
+    "user": {
+      "id": "user_xxx",
+      "username": "admin",
+      "email": "admin@example.com",
+      "phone": "138xxxx",
+      "is_superuser": true,
+      "is_active": true,
+      "roles": ["super_admin", "sam_sys_admin"]
+    }
+  }
+}
+```
+
+**失败响应**:
+
+```json
+{
+  "code": "100001",
+  "message": "缺少授权码",
+  "data": null
+}
+```
+
+```json
+{
+  "code": "500001",
+  "message": "登录失败: 获取令牌失败",
+  "data": null
+}
+```
+
+### 5.2 获取 SSO 授权 URL
+
+- **接口**: `GET /auth/sso/authorize`
+- **认证**: 不需要
+
+| 参数 | 类型 | 必填 | 说明 |
+|------|------|------|------|
+| `redirect` | bool | 否 | 为 true 时直接 302 重定向到 SSO 授权页 |
+
+**响应** (redirect=false):
+
+```json
+{
+  "code": "000000",
+  "message": "获取授权URL成功",
+  "data": {
+    "authorize_url": "http://192.168.92.61:8200/oauth/authorize?client_id=xxx&redirect_uri=xxx&response_type=code&scope=email"
+  }
+}
+```
+
+### 5.3 SSO 回调端点(旧流程,后端 302 重定向方式)
+
+- **接口**: `GET /auth/callback`
+- **认证**: 不需要
+
+| 参数 | 类型 | 必填 | 说明 |
+|------|------|------|------|
+| `code` | string | 是 | SSO 授权码 |
+| `error` | string | 否 | SSO 返回的错误码 |
+| `error_description` | string | 否 | 错误描述 |
+| `state` | string | 否 | 状态参数 |
+
+**响应**: 302 重定向到 `前端URL/oauth/callback?token=xxx&refresh_token=xxx`
+
+> **注意**: 这是旧流程,建议使用 `POST /api/oauth/exchange-code` 的新流程(前端直接调用 API 换码,不依赖后端 302 重定向)。
+
+---
+
+## 六、子系统侧的其他认证相关 API
+
+除了 SSO 接入外,子系统还需要提供标准认证接口供前端使用。
+
+### 6.1 本地密码登录
+
+- **接口**: `POST /api/v1/auth/login`
+- **Content-Type**: `application/json`
+
+**请求体**:
+
+```json
+{
+  "username": "admin",
+  "password": "Admin123456",
+  "remember_me": false
+}
+```
+
+**响应**:
+
+```json
+{
+  "code": "000000",
+  "message": "登录成功",
+  "data": {
+    "access_token": "eyJ...",
+    "refresh_token": "xxx...",
+    "token_type": "Bearer",
+    "expires_in": 1200
+  }
+}
+```
+
+### 6.2 刷新 Token
+
+- **接口**: `POST /api/v1/auth/refresh`
+- **请求体**: `{ "refresh_token": "xxx" }`
+
+### 6.3 登出
+
+- **接口**: `POST /api/v1/auth/logout`
+- **请求体**: `{ "token": "xxx", "refresh_token": "xxx" }`
+- **响应** (含 SSO 登出重定向 URL):
+
+```json
+{
+  "code": "000000",
+  "message": "登出成功",
+  "data": {
+    "sso_logout_url": "http://192.168.92.61:9200/login"
+  }
+}
+```
+
+### 6.4 获取当前用户信息
+
+- **接口**: `GET /api/v1/auth/userinfo` 或 `GET /api/v1/auth/me`
+- **认证**: Bearer Token
+- **响应**:
+
+```json
+{
+  "code": "000000",
+  "data": {
+    "id": "user_xxx",
+    "username": "admin",
+    "email": "admin@example.com",
+    "phone": "138xxxx",
+    "roles": ["super_admin"],
+    "permissions": []
+  }
+}
+```
+
+### 6.5 获取验证码
+
+- **接口**: `GET /api/v1/auth/captcha`
+- **认证**: 不需要
+
+---
+
+## 七、统一认证平台的 OAuth2 服务端 API
+
+统一认证平台自身也是一个 OAuth2 授权服务器,可以为下游子系统提供 OAuth2 服务。
+
+### 7.1 应用管理(系统管理员操作)
+
+通过统一认证平台的系统管理模块管理子应用,相关接口前缀 `/api/v1`:
+
+| 方法 | 路径 | 说明 |
+|------|------|------|
+| `GET` | `/api/v1/system/app/list` | 获取应用列表 |
+| `POST` | `/api/v1/system/app/create` | 创建应用(自动生成 app_key/app_secret) |
+| `PUT` | `/api/v1/system/app/update/{id}` | 更新应用信息 |
+| `DELETE` | `/api/v1/system/app/delete/{id}` | 删除应用 |
+| `POST` | `/api/v1/system/app/reset_secret/{id}` | 重置应用密钥 |
+| `GET` | `/api/v1/system/app/accessible` | 获取当前用户可访问的应用列表 |
+
+### 7.2 角色管理
+
+| 方法 | 路径 | 说明 |
+|------|------|------|
+| `GET` | `/api/v1/system/role/list` | 获取角色列表 |
+| `POST` | `/api/v1/system/role/create` | 创建角色 |
+| `PUT` | `/api/v1/system/role/update/{id}` | 更新角色 |
+| `DELETE` | `/api/v1/system/role/delete/{id}` | 删除角色 |
+| `GET` | `/api/external/v1/system/role/user_list/{role_code}` | 按角色 code 查询用户列表 |
+
+---
+
+## 八、Token 机制说明
+
+### 8.1 统一认证平台侧 Token
+
+| Token 类型 | 有效期 | 存储方式 | 说明 |
+|-----------|--------|---------|------|
+| Access Token | 30 分钟(默认)| Redis (`auth:access:{user_id}:{token}`) | 访问令牌,含用户身份和角色 |
+| Refresh Token | 30 天 | Redis (`auth:refresh:{user_id}:{token}`) | 刷新令牌,用于换取新 Access Token |
+| OAuth Access Token | 120 分钟 | Redis (`auth:oauth_access:{client_id}:{token}`) | OAuth2 流程中颁发给子应用的令牌 |
+| OAuth Authorization Code | 10 分钟 | Redis (`auth:oauth_code:{code}`) | 一次性授权码,使用后即失效 |
+
+### 8.2 子系统侧 Token
+
+| Token 类型 | 有效期 | 存储方式 | 说明 |
+|-----------|--------|---------|------|
+| Access Token | 20 分钟 | MySQL + Redis | 子系统本地登录令牌 |
+| Refresh Token | 24 小时 | MySQL + Redis | 刷新令牌 |
+
+### 8.3 滑动过期机制
+
+子系统实现了 JWT Token 的滑动过期机制:
+
+- 当 Token 使用时间超过其总生命周期的 **50%** 时,中间件会自动生成新 Token
+- 新 Token 通过响应头 `X-New-Token` 返回给前端
+- 前端检测到 `X-New-Token` 后替换本地旧 Token
+- 无需前端主动刷新即可保持登录状态
+
+---
+
+## 九、前端接入参考代码
+
+### 9.1 SSO 免登流程(推荐方式)
+
+```typescript
+// 1. 统一认证平台重定向到子系统前端,URL 携带 code 参数
+// 例如: http://localhost:3000/auth/callback?code=xxxxxx
+
+// 2. 在回调页面中提取 code 并调用换码接口
+const code = new URLSearchParams(window.location.search).get('code');
+
+if (code) {
+  const response = await fetch('/api/oauth/exchange-code', {
+    method: 'POST',
+    headers: { 'Content-Type': 'application/json' },
+    body: JSON.stringify({ code })
+  });
+  const result = await response.json();
+
+  if (result.code === '000000') {
+    // 3. 保存 Token
+    localStorage.setItem('token', result.data.token);
+    localStorage.setItem('refresh_token', result.data.refresh_token);
+    localStorage.setItem('user', JSON.stringify(result.data.user));
+
+    // 4. 跳转到首页
+    router.push('/home');
+  } else {
+    // 登录失败,跳回登录页
+    router.push(`/login?error=${result.message}`);
+  }
+}
+```
+
+### 9.2 前端请求拦截器(携带 Token)
+
+```typescript
+// Axios 请求拦截器示例
+axios.interceptors.request.use(config => {
+  const token = localStorage.getItem('token');
+  if (token) {
+    config.headers.Authorization = `Bearer ${token}`;
+  }
+  return config;
+});
+
+// 响应拦截器处理 Token 刷新
+axios.interceptors.response.use(
+  response => {
+    const newToken = response.headers['x-new-token'];
+    if (newToken) {
+      localStorage.setItem('token', newToken);
+    }
+    return response;
+  },
+  error => {
+    if (error.response?.status === 401) {
+      // Token 过期,清除并跳转登录
+      localStorage.removeItem('token');
+      localStorage.removeItem('refresh_token');
+      router.push('/login');
+    }
+    return Promise.reject(error);
+  }
+);
+```
+
+---
+
+## 十、接入 Checklist
+
+- [ ] 在统一认证平台注册子应用,获取 `client_id` 和 `client_secret`
+- [ ] 在子应用配置文件中添加 `[sso]` 配置段
+- [ ] 子应用后端实现 `POST /api/oauth/exchange-code` 换码接口
+- [ ] 子应用前端实现回调页面 `/auth/callback`,从 URL 提取 code 并调换码接口
+- [ ] 子应用本地数据库初始化角色表(`t_sys_role`),确保角色 code 与统一认证平台一致
+- [ ] 子应用配置 CORS 允许前端域名访问
+- [ ] 配置 Token 滑动过期中间件
+- [ ] 配置登出时返回 SSO 登出重定向 URL
+- [ ] 测试完整 SSO 流程:统一认证平台登录 → 点击子系统 → 自动免登进入子系统
+
+---
+
+## 十一、错误码说明
+
+### 统一响应格式
+
+所有接口使用统一的响应格式:
+
+```json
+{
+  "code": "000000",
+  "message": "success",
+  "data": { ... },
+  "timestamp": "2026-05-10T12:00:00Z"
+}
+```
+
+### 业务错误码
+
+| 错误码 | 说明 |
+|--------|------|
+| `000000` | 成功 |
+| `100001` | 缺少授权码 |
+| `400001` | SSO 授权码无效 |
+| `400002` | SSO 用户信息格式异常 |
+| `500001` | 服务器内部错误 |
+
+### OAuth2 标准错误
+
+| error | 说明 |
+|-------|------|
+| `invalid_request` | 请求参数不完整或格式错误 |
+| `invalid_client` | client_id 或 client_secret 错误 |
+| `invalid_grant` | 授权码无效、已使用、过期或 redirect_uri 不匹配 |
+| `invalid_scope` | 请求的 scope 超出应用注册范围 |
+| `unsupported_response_type` | response_type 不支持(仅支持 code) |
+| `unsupported_grant_type` | grant_type 不支持(仅支持 authorization_code) |
+| `access_denied` | 用户拒绝授权 |
+| `server_error` | 服务器内部错误 |

+ 115 - 0
ui/src/api/users/user_group.ts

@@ -0,0 +1,115 @@
+import { get, post, put, del } from '@/request/index'
+import { type Ref } from 'vue'
+import type { Result } from '@/request/Result'
+
+export interface UserGroup {
+  id: string
+  name: string
+  desc: string
+  is_active: boolean
+  member_count?: number
+  auth_count?: number
+  create_time: string
+}
+
+export interface UserGroupMember {
+  id: string
+  username: string
+  nick_name: string
+}
+
+export interface UserGroupAuthorization {
+  id: string
+  resource_type: 'APPLICATION' | 'MODEL' | 'TOOL' | 'KNOWLEDGE'
+  resource_id: string
+  permission: 'READ' | 'WRITE' | 'ADMIN'
+}
+
+// 获取用户组列表
+export function getUserGroupList(
+  workspaceId: string,
+  loading?: Ref<boolean>,
+): Promise<Result<UserGroup[]>> {
+  return get(`workspace/${workspaceId}/user_group`, undefined, loading)
+}
+
+// 创建用户组
+export function createUserGroup(
+  workspaceId: string,
+  data: { name: string; desc?: string },
+  loading?: Ref<boolean>,
+): Promise<Result<UserGroup>> {
+  return post(`workspace/${workspaceId}/user_group/create`, data, loading)
+}
+
+// 获取用户组详情
+export function getUserGroupDetail(
+  workspaceId: string,
+  groupId: string,
+  loading?: Ref<boolean>,
+): Promise<Result<UserGroup & { members: UserGroupMember[]; authorizations: UserGroupAuthorization[] }>> {
+  return get(`workspace/${workspaceId}/user_group/${groupId}`, undefined, loading)
+}
+
+// 更新用户组
+export function updateUserGroup(
+  workspaceId: string,
+  groupId: string,
+  data: { name?: string; desc?: string; is_active?: boolean },
+  loading?: Ref<boolean>,
+): Promise<Result<UserGroup>> {
+  return put(`workspace/${workspaceId}/user_group/${groupId}`, data, loading)
+}
+
+// 删除用户组
+export function deleteUserGroup(
+  workspaceId: string,
+  groupId: string,
+  loading?: Ref<boolean>,
+): Promise<Result<boolean>> {
+  return del(`workspace/${workspaceId}/user_group/${groupId}`, undefined, loading)
+}
+
+// 批量添加成员
+export function batchAddMembers(
+  workspaceId: string,
+  groupId: string,
+  userIds: string[],
+  loading?: Ref<boolean>,
+): Promise<Result<{ added_count: number }>> {
+  return post(`workspace/${workspaceId}/user_group/${groupId}/batch_add_members`, { user_ids: userIds }, loading)
+}
+
+// 批量移除成员
+export function batchRemoveMembers(
+  workspaceId: string,
+  groupId: string,
+  userIds: string[],
+  loading?: Ref<boolean>,
+): Promise<Result<{ removed_count: number }>> {
+  return post(`workspace/${workspaceId}/user_group/${groupId}/batch_remove_members`, { user_ids: userIds }, loading)
+}
+
+// 批量授权
+export function batchAuthorize(
+  workspaceId: string,
+  groupId: string,
+  authorizations: Array<{
+    resource_type: string
+    resource_id: string
+    permission?: string
+  }>,
+  loading?: Ref<boolean>,
+): Promise<Result<{ added_count: number }>> {
+  return post(`workspace/${workspaceId}/user_group/${groupId}/batch_authorize`, { authorizations }, loading)
+}
+
+// 批量撤销授权
+export function batchRevokeAuthorization(
+  workspaceId: string,
+  groupId: string,
+  authIds: string[],
+  loading?: Ref<boolean>,
+): Promise<Result<{ removed_count: number }>> {
+  return post(`workspace/${workspaceId}/user_group/${groupId}/batch_revoke`, { auth_ids: authIds }, loading)
+}