449 lines
19 KiB
Markdown
449 lines
19 KiB
Markdown
---
|
||
title: "feat: Enterprise Client-Server Architecture Evolution"
|
||
type: feat
|
||
status: completed
|
||
created: 2026-06-19
|
||
completed: 2026-06-19
|
||
origin: docs/plans/2026-06-19-002-enterprise-client-server-architecture-plan.md
|
||
deepened: false
|
||
---
|
||
|
||
# feat: Enterprise Client-Server Architecture Evolution
|
||
|
||
## Summary
|
||
|
||
将 Fischer AgentKit 从纯本地运行架构演进为企业级客户端+服务端架构。客户端(Tauri 桌面端)作为 AI 工作台本地执行 Agent/终端/文件操作,服务端作为企业平台提供 LLM 网关(统一 Key 管理)、用户权限、审计日志、知识库共享能力。采用渐进式改造,每个阶段设立检查点验证核心假设。
|
||
|
||
## Problem Frame
|
||
|
||
当前 AgentKit 是纯本地架构:Tauri 启动本地 Python sidecar,LLM API Key 存在本地 agentkit.yaml,无用户系统、无权限管理、无服务端部署。企业场景需要:统一管控 LLM Key 和成本、团队知识共享、权限与审计、多用户支持。
|
||
|
||
## Scope Boundaries
|
||
|
||
### In Scope
|
||
|
||
- 服务端 LLM 网关(统一 Key + 用量统计 + 流式透传)
|
||
- 用户认证(JWT + API Key 双轨)
|
||
- 三级 RBAC + 权限位
|
||
- 双模式终端(本地 + 服务端)+ 三层白名单
|
||
- 终端审计日志
|
||
- 配置同步(轮询版)
|
||
- 前端连接模式切换
|
||
|
||
### Deferred to Follow-Up Work
|
||
|
||
- Web 管理台(V1 用 CLI 管理)
|
||
- 技能市场 / 工作流模板中心(YAML + Git 够用)
|
||
- 生产系统集成(暴露 Webhook 即可)
|
||
- SSO/OIDC(V1 本地账号,V2 预留接口)
|
||
- 代码库语义索引
|
||
- 多租户隔离(表预留 tenant_id,V2 启用)
|
||
- 多设备会话同步
|
||
|
||
## Key Technical Decisions
|
||
|
||
### KTD1: 终端保留在本地 Python sidecar(非 Tauri Rust)
|
||
|
||
**决策**:终端 PTY 逻辑保留在 Python sidecar 中,不迁移到 Tauri Rust 层。
|
||
|
||
**理由**:当前 `terminal.py` + `PTYSession` 已完整实现 PTY 管理、安全检测、确认流程。用 Rust 重写风险高、收益低。本地 sidecar 模式下终端逻辑不变,服务端终端新增独立端点。
|
||
|
||
### KTD2: LLM 代理模式(非客户端直连)
|
||
|
||
**决策**:客户端通过服务端 LLM 网关间接调用 LLM,不在本地存储 API Key。
|
||
|
||
**理由**:核心价值是成本管控 + 用量审计(非安全边界)。服务端必须支持内网部署以避免跨地域延迟。客户端保留直连降级能力(服务端不可用时用户自填 Key)。
|
||
|
||
### KTD3: 配置同步用轮询(非 WebSocket 推送)
|
||
|
||
**决策**:启动时全量拉取 + 每 5 分钟轮询版本号 + 手动刷新。
|
||
|
||
**理由**:配置变更频率是周/月级,WebSocket 推送是杀鸡用牛刀。轮询实现量是 WebSocket 的 1/10,覆盖 99% 场景。
|
||
|
||
### KTD4: 三级 RBAC + 权限位(非四级角色)
|
||
|
||
**决策**:member / operator / admin 三级角色 + 独立权限位。
|
||
|
||
**理由**:四级中的 viewer 是想象出来的角色。企业真实需求是权限点(能否用终端、能否管理 KB),不是角色等级。权限位比固定等级更灵活。
|
||
|
||
### KTD5: 双模式终端 + 三层白名单
|
||
|
||
**决策**:支持本地终端(客户端 sidecar PTY)和服务端终端(服务端 PTY),均配三层白名单(内置/全局/用户)+ 黑名单。
|
||
|
||
**理由**:本地终端满足开发者本地操作需求,服务端终端满足 DevOps 管理服务器需求。白名单机制平衡安全与效率。
|
||
|
||
## High-Level Technical Design
|
||
|
||
```
|
||
Phase 1: LLM Gateway + Auth Phase 2: RBAC + Audit + Terminal
|
||
┌──────────────┐ ┌──────────────┐
|
||
│ Client │ │ Client │
|
||
│ RemoteLLM │──JWT──► │ Terminal │──JWT──►
|
||
│ Provider │ │ (Local) │
|
||
└──────┬───────┘ └──────┬───────┘
|
||
│ SSE │ WS
|
||
▼ ▼
|
||
┌──────────────┐ ┌──────────────┐
|
||
│ Server │ │ Server │
|
||
│ LLM Gateway │ │ Auth+RBAC │
|
||
│ + JWT Auth │ │ + Audit │
|
||
│ + Usage │ │ + Whitelist │
|
||
└──────────────┘ └──────────────┘
|
||
|
||
Phase 3: Server Terminal + KB
|
||
┌──────────────┐
|
||
│ Client │
|
||
│ Terminal │──JWT──►
|
||
│ (Server) │
|
||
└──────┬───────┘
|
||
│ WSS
|
||
▼
|
||
┌──────────────┐
|
||
│ Server │
|
||
│ Server PTY │
|
||
│ + Approval │
|
||
│ + KB Search │
|
||
└──────────────┘
|
||
```
|
||
|
||
---
|
||
|
||
## Implementation Units
|
||
|
||
### U1. 服务端 LLM 网关 API
|
||
|
||
**Goal**: 在服务端新增 LLM 代理端点,接收客户端请求并转发到 LLM Provider,记录用量。
|
||
|
||
**Dependencies**: 无(首个单元)
|
||
|
||
**Files**:
|
||
- `src/agentkit/server/routes/llm_gateway.py`(新建)
|
||
- `src/agentkit/server/app.py`(注册路由)
|
||
- `tests/unit/test_llm_gateway.py`(新建)
|
||
|
||
**Approach**:
|
||
- 新增 `POST /api/v1/llm/chat`(非流式)和 `POST /api/v1/llm/chat/stream`(SSE 流式)
|
||
- 复用现有 `LLMGateway.chat()` 和 `chat_stream()`
|
||
- 流式端点将 `StreamChunk` 序列化为 SSE 格式(`data: {json}\n\n`)
|
||
- 记录每次调用的 user_id、model、tokens、cost 到 `llm_usage_records` 表
|
||
- JWT 认证(U2 完成前临时用 API Key)
|
||
|
||
**Test scenarios**:
|
||
- 非流式 chat 请求返回正确的 `LLMResponse` JSON
|
||
- 流式 chat 请求返回 SSE 格式,每个 chunk 是 `data: {json}\n\n`
|
||
- 无效 model 返回 404
|
||
- LLM Provider 失败时返回 502
|
||
- 用量记录正确写入数据库(model, tokens, cost)
|
||
- 无认证时返回 401
|
||
|
||
**Checkpoint CP1**: 服务端 LLM 网关可独立运行,通过 curl 验证流式和非流式调用。
|
||
|
||
---
|
||
|
||
### U2. JWT 认证模块
|
||
|
||
**Goal**: 实现用户注册/登录/JWT 签发验证,替换当前纯 API Key 认证。
|
||
|
||
**Dependencies**: U1
|
||
|
||
**Files**:
|
||
- `src/agentkit/server/auth/__init__.py`(新建)
|
||
- `src/agentkit/server/auth/models.py`(新建,UserModel + UserApiKeyModel)
|
||
- `src/agentkit/server/auth/jwt_utils.py`(新建,JWT 签发/验证)
|
||
- `src/agentkit/server/auth/password.py`(新建,bcrypt 哈希)
|
||
- `src/agentkit/server/auth/middleware.py`(新建,AuthMiddleware)
|
||
- `src/agentkit/server/auth/dependencies.py`(新建,require_permission 依赖)
|
||
- `src/agentkit/server/routes/auth.py`(新建,login/refresh/logout/me)
|
||
- `src/agentkit/server/app.py`(注册路由 + 中间件替换)
|
||
- `tests/unit/test_auth.py`(新建)
|
||
|
||
**Approach**:
|
||
- `UserModel`: id, username, email, password_hash, role, is_active, is_terminal_authorized, is_server_terminal_authorized
|
||
- `UserApiKeyModel`: user_id, key_hash(SHA256), key_prefix, name, expires_at, is_revoked
|
||
- JWT: access_token(15min) + refresh_token(7d),HS256 签名
|
||
- AuthMiddleware: JWT 优先 → API Key → 兼容 clients.yaml → dev mode 放行
|
||
- `require_permission(*perms)`: FastAPI 依赖注入,检查 `request.state.current_user` 的角色权限
|
||
- 初始 admin 引导: 首次启动时从 `AGENTKIT_ADMIN_USERNAME/PASSWORD` 创建 admin 用户
|
||
- 数据库: V1 用 SQLite(复用 aiosqlite),后续切换 PostgreSQL
|
||
|
||
**Test scenarios**:
|
||
- 注册/登录返回 access_token + refresh_token
|
||
- refresh_token 可刷新 access_token
|
||
- 错误密码返回 401
|
||
- JWT 过期后返回 401,refresh 后可用
|
||
- API Key 认证识别用户身份
|
||
- 无认证 dev mode 放行
|
||
- 无认证生产 mode 返回 401
|
||
- `require_permission` 正确拒绝/放行
|
||
- 初始 admin 创建成功
|
||
|
||
**Checkpoint CP2**: 可通过登录 API 获取 JWT,使用 JWT 访问 LLM 网关。
|
||
|
||
---
|
||
|
||
### U3. RemoteLLMProvider(客户端 LLM 代理)
|
||
|
||
**Goal**: 在客户端新增 `RemoteLLMProvider`,通过服务端 LLM 网关调用 LLM。
|
||
|
||
**Dependencies**: U1, U2
|
||
|
||
**Files**:
|
||
- `src/agentkit/llm/remote_provider.py`(新建)
|
||
- `src/agentkit/llm/gateway.py`(增加 RemoteLLMProvider 注册支持)
|
||
- `tests/unit/test_remote_provider.py`(新建)
|
||
|
||
**Approach**:
|
||
- `RemoteLLMProvider(LLMProvider)`: 实现 `chat()` 和 `chat_stream()`
|
||
- `chat()`: POST `/api/v1/llm/chat`,带 `Authorization: Bearer {jwt}`
|
||
- `chat_stream()`: POST `/api/v1/llm/chat/stream`,解析 SSE 流为 `StreamChunk`
|
||
- 构造函数接收 `server_url` + `auth_token_provider`(callable,返回当前 JWT)
|
||
- 错误处理: 401 触发 token 刷新重试,502 抛 `LLMProviderError`
|
||
- 客户端 `LLMGateway` 配置为使用 `RemoteLLMProvider` 时,不加载本地 Provider 配置
|
||
|
||
**Test scenarios**:
|
||
- `chat()` 正确发送请求并解析 `LLMResponse`
|
||
- `chat_stream()` 正确解析 SSE 流为 `StreamChunk` 序列
|
||
- 401 响应触发 token 刷新重试
|
||
- 502 响应抛出 `LLMProviderError`
|
||
- 网络超时抛出异常
|
||
- `is_final=True` 的 chunk 正确标记
|
||
|
||
**Checkpoint CP3**: 客户端 Agent 通过 RemoteLLMProvider → 服务端网关 → LLM Provider 完成对话。
|
||
|
||
---
|
||
|
||
### U4. 前端认证 + 连接模式切换
|
||
|
||
**Goal**: 前端实现登录页、JWT 管理、API Client 附加 JWT、连接模式切换。
|
||
|
||
**Dependencies**: U2, U3
|
||
|
||
**Files**:
|
||
- `src/agentkit/server/frontend/src/stores/auth.ts`(新建)
|
||
- `src/agentkit/server/frontend/src/api/base.ts`(改造,附加 JWT)
|
||
- `src/agentkit/server/frontend/src/views/LoginView.vue`(新建)
|
||
- `src/agentkit/server/frontend/src/router/index.ts`(路由守卫)
|
||
- `src/agentkit/server/frontend/src/api/auth.ts`(新建)
|
||
- `src/agentkit/server/frontend/src/App.vue`(启动时初始化认证)
|
||
|
||
**Approach**:
|
||
- `auth` store: user, accessToken, refreshToken, permissions, hasPermission(), canAccessTerminal()
|
||
- `BaseApiClient.request()`: 自动附加 `Authorization: Bearer {token}`,401 时自动刷新
|
||
- `BaseApiClient.createWebSocket()`: 附加 `?token={jwt}` 查询参数
|
||
- 登录页: 用户名 + 密码 → 调用 `/api/v1/auth/login` → 存储 JWT → 跳转主页
|
||
- 路由守卫: 未登录跳转 `/login`,权限检查
|
||
- `initApiBaseURL()`: Tauri 模式检查 localStorage 中的远程服务器配置
|
||
|
||
**Test scenarios**:
|
||
- 登录成功后 JWT 存入 localStorage,跳转主页
|
||
- 登录失败显示错误提示
|
||
- API 请求自动附加 JWT
|
||
- 401 响应自动触发 token 刷新
|
||
- token 刷新失败跳转登录页
|
||
- WebSocket 连接附加 token 参数
|
||
- 路由守卫正确拦截未登录用户
|
||
|
||
**Checkpoint CP4**: 前端登录 → JWT 认证 → 通过服务端 LLM 网关对话,全链路打通。
|
||
|
||
---
|
||
|
||
### U5. 权限模型 + RBAC 中间件
|
||
|
||
**Goal**: 实现三级 RBAC + 权限位,集成到所有路由。
|
||
|
||
**Dependencies**: U2
|
||
|
||
**Files**:
|
||
- `src/agentkit/server/auth/permissions.py`(新建,Permission enum + ROLE_PERMISSIONS)
|
||
- `src/agentkit/server/auth/dependencies.py`(完善 require_permission)
|
||
- `src/agentkit/server/routes/portal.py`(增加权限检查)
|
||
- `src/agentkit/server/routes/terminal.py`(增加权限检查)
|
||
- `src/agentkit/server/routes/settings.py`(增加权限检查)
|
||
- `src/agentkit/server/routes/kb_management.py`(增加权限检查)
|
||
- `tests/unit/test_permissions.py`(新建)
|
||
|
||
**Approach**:
|
||
- `Permission` enum: CHAT, KB_QUERY, KB_WRITE, WORKFLOW_EXECUTE, TERMINAL_LOCAL_USE, TERMINAL_SERVER_USE, TERMINAL_WHITELIST_MANAGE, USER_MANAGE, SYSTEM_CONFIG
|
||
- `ROLE_PERMISSIONS`: member / operator / admin 权限映射
|
||
- `require_permission(*perms)`: 检查 `request.state.current_user` 的角色权限
|
||
- 现有路由增加 `Depends(require_permission(Permission.XXX))`
|
||
- dev mode(无用户): 终端等高危功能仍需认证,其他放行
|
||
|
||
**Test scenarios**:
|
||
- member 角色可对话但不可访问终端
|
||
- operator 角色可使用本地终端但不可管理用户
|
||
- admin 角色可访问所有功能
|
||
- 未认证用户在 dev mode 可对话但不可用终端
|
||
- 权限不足返回 403
|
||
|
||
**Checkpoint CP5**: 不同角色用户访问不同功能,权限检查生效。
|
||
|
||
---
|
||
|
||
### U6. 本地终端白名单 + 审计上报
|
||
|
||
**Goal**: 本地终端增加三层白名单机制 + 危险命令审计上报。
|
||
|
||
**Dependencies**: U5
|
||
|
||
**Files**:
|
||
- `src/agentkit/server/routes/terminal.py`(改造白名单逻辑)
|
||
- `src/agentkit/server/auth/models.py`(增加白名单表模型)
|
||
- `src/agentkit/server/routes/terminal_whitelist.py`(新建,白名单管理 API)
|
||
- `src/agentkit/server/app.py`(注册白名单路由)
|
||
- `tests/unit/test_terminal_whitelist.py`(新建)
|
||
|
||
**Approach**:
|
||
- 白名单匹配优先级: 黑名单 → 内置安全白名单 → 用户白名单 → 会话白名单 → 危险检测
|
||
- 用户白名单存储在服务端 DB(`terminal_whitelist_user` 表),客户端缓存
|
||
- 黑名单存储在服务端 DB(`terminal_blocklist` 表),admin 管理
|
||
- 危险命令确认后可选加入会话白名单(不持久化到用户白名单)
|
||
- 危险命令执行后上报审计日志到服务端(`terminal_audit_logs` 表)
|
||
- 白名单管理 API: GET/POST/DELETE `/api/v1/terminal/whitelist/user`
|
||
|
||
**Test scenarios**:
|
||
- 内置安全白名单命令直接执行
|
||
- 用户白名单命令直接执行
|
||
- 会话白名单命令直接执行
|
||
- 黑名单命令被拒绝
|
||
- 危险命令弹出确认,确认后执行
|
||
- 危险命令拒绝后不执行
|
||
- 危险命令审计日志正确上报
|
||
- 白名单 CRUD API 正常工作
|
||
|
||
**Checkpoint CP6**: 本地终端白名单生效,危险命令需确认且审计上报。
|
||
|
||
---
|
||
|
||
### U7. 配置同步引擎
|
||
|
||
**Goal**: 客户端从服务端同步技能/Agent/工作流配置。
|
||
|
||
**Dependencies**: U2
|
||
|
||
**Files**:
|
||
- `src/agentkit/server/routes/config_sync.py`(新建,配置同步 API)
|
||
- `src/agentkit/server/app.py`(注册路由)
|
||
- `src/agentkit/client/sync.py`(新建,客户端同步引擎)
|
||
- `tests/unit/test_config_sync.py`(新建)
|
||
|
||
**Approach**:
|
||
- 服务端 API: `GET /api/v1/config/version`(返回版本号)+ `GET /api/v1/config/skills|agents|workflows`(返回配置列表)
|
||
- 客户端 `ConfigSync`: 启动时全量拉取 → 每 5 分钟轮询版本号 → 版本变化时全量拉取
|
||
- 配置缓存在本地 SQLite(`config_cache` 表)
|
||
- 离线时使用本地缓存
|
||
|
||
**Test scenarios**:
|
||
- 启动时全量拉取配置
|
||
- 版本号未变化时不拉取
|
||
- 版本号变化时全量拉取
|
||
- 离线时使用本地缓存
|
||
- 配置正确更新到本地
|
||
|
||
**Checkpoint CP7**: 客户端启动后自动同步服务端配置,离线时使用缓存。
|
||
|
||
---
|
||
|
||
### U8. 服务端终端 + 审批机制
|
||
|
||
**Goal**: 新增服务端终端模式,非白名单危险命令需 admin 审批。
|
||
|
||
**Dependencies**: U5, U6
|
||
|
||
**Files**:
|
||
- `src/agentkit/server/routes/terminal_server.py`(新建,服务端终端 WebSocket)
|
||
- `src/agentkit/server/auth/models.py`(增加审批表模型)
|
||
- `src/agentkit/server/routes/terminal_whitelist.py`(增加全局白名单 + 审批 API)
|
||
- `src/agentkit/server/app.py`(注册路由)
|
||
- `tests/unit/test_terminal_server.py`(新建)
|
||
|
||
**Approach**:
|
||
- `WS /api/v1/terminal/server/ws`: 服务端 PTY WebSocket
|
||
- 权限检查: `TERMINAL_SERVER_USE` + `is_server_terminal_authorized`
|
||
- 命令执行流程: 黑名单 → 内置白名单 → 全局白名单 → 会话白名单 → 非白名单需 admin 审批
|
||
- 审批机制: 创建 `terminal_approvals` 记录 → WebSocket 推送 admin → admin 批准/拒绝 → 执行/取消
|
||
- 审批超时: 5 分钟自动拒绝
|
||
- 全量审计: 所有命令(含执行/拒绝/审批)记录到 `terminal_audit_logs`
|
||
- 会话隔离: 每个用户独立 PTY 会话,工作目录隔离
|
||
- 资源限制: 最大并发会话数、会话超时
|
||
|
||
**Test scenarios**:
|
||
- 有权限用户可连接服务端终端
|
||
- 无权限用户被拒绝
|
||
- 全局白名单命令直接执行
|
||
- 非白名单危险命令创建审批请求
|
||
- admin 批准后命令执行
|
||
- admin 拒绝后命令取消
|
||
- 审批超时自动拒绝
|
||
- 所有命令审计日志记录
|
||
- 会话隔离正确
|
||
|
||
**Checkpoint CP8**: 服务端终端可用,非白名单命令需 admin 审批,全量审计。
|
||
|
||
---
|
||
|
||
### U9. 前端终端双模式 UI + 白名单管理
|
||
|
||
**Goal**: 前端终端支持本地/服务端模式切换,白名单管理页面。
|
||
|
||
**Dependencies**: U6, U8
|
||
|
||
**Files**:
|
||
- `src/agentkit/server/frontend/src/components/terminal/TerminalPanel.vue`(改造,模式切换)
|
||
- `src/agentkit/server/frontend/src/components/terminal/WhitelistManager.vue`(新建)
|
||
- `src/agentkit/server/frontend/src/api/terminal.ts`(改造,支持双模式)
|
||
- `src/agentkit/server/frontend/src/stores/auth.ts`(增加 canUseServerTerminal)
|
||
|
||
**Approach**:
|
||
- 终端面板顶部: `[本地终端] [服务端终端]` Tab 切换
|
||
- 本地终端: 连接本地 sidecar WebSocket
|
||
- 服务端终端: 连接服务端 WebSocket(带 JWT)
|
||
- 非白名单命令: 本地终端用户确认 / 服务端终端显示"等待审批"
|
||
- 白名单管理页: Tab(我的白名单 / 全局白名单 / 黑名单)
|
||
|
||
**Test scenarios**:
|
||
- 本地终端模式正常执行命令
|
||
- 服务端终端模式正常执行命令
|
||
- 模式切换正确切换 WebSocket 连接
|
||
- 无权限用户看不到服务端终端 Tab
|
||
- 白名单管理 CRUD 正常工作
|
||
- 危险命令确认对话框正确显示
|
||
|
||
**Checkpoint CP9**: 前端双模式终端可用,白名单管理页面正常。
|
||
|
||
---
|
||
|
||
## Checkpoint Summary
|
||
|
||
| 检查点 | 验证内容 | 通过标准 |
|
||
|--------|---------|---------|
|
||
| CP1 | 服务端 LLM 网关 | curl 验证流式和非流式调用 |
|
||
| CP2 | JWT 认证 | 登录获取 JWT,使用 JWT 访问 API |
|
||
| CP3 | 客户端 LLM 代理 | 客户端 Agent 通过服务端网关完成对话 |
|
||
| CP4 | 全链路打通 | 前端登录 → JWT → 服务端 LLM → 对话 |
|
||
| CP5 | 权限模型 | 不同角色访问不同功能,权限检查生效 |
|
||
| CP6 | 本地终端白名单 | 危险命令需确认且审计上报 |
|
||
| CP7 | 配置同步 | 客户端自动同步服务端配置 |
|
||
| CP8 | 服务端终端 | 非白名单命令需 admin 审批 |
|
||
| CP9 | 前端双模式终端 | 双模式切换 + 白名单管理 |
|
||
|
||
## Risks & Dependencies
|
||
|
||
| 风险 | 影响 | 缓解 |
|
||
|------|------|------|
|
||
| 流式 fallback 跨网络语义变化 | 客户端可能看到内容跳变 | 服务端首 chunk 前完成 fallback,首 chunk 后不 fallback |
|
||
| `_verify_api_key` 全局替换遗漏 | 部分路由无权限检查 | 代码审查 + 测试覆盖 |
|
||
| clients.yaml 兼容 | 两套认证并存增加复杂度 | V1 保留兼容,V2 废弃 |
|
||
| Tauri sidecar 远程地址注入 | 前端 API 调用目标变化 | base.ts 支持双目标 |
|
||
| 专家团 LLM 调用放大延迟 | 多 expert 串行 × RTT | 服务端网关连接复用 |
|
||
|
||
## Verification Strategy
|
||
|
||
每个检查点(CP)必须通过以下验证才能进入下一阶段:
|
||
1. **单元测试**: 新增代码的单元测试全部通过
|
||
2. **集成测试**: 关键链路的集成测试通过
|
||
3. **手动验证**: 按检查点描述的手动验证步骤通过
|
||
4. **回归测试**: `pytest tests/unit/ -x -q` 不新增失败(已知 `test_rewoo_agent_yaml_loads` 除外)
|
||
5. **前端类型检查**: `npx vue-tsc --noEmit` 通过
|
||
6. **代码规范**: `ruff check` 改动文件无新增错误
|