feat(govai): 0617 优化首批 — 安全/私有化/深度研究/服务层/可观测性

借鉴 odysseus 的能力设计,全程净室实现、零 AGPL 代码、不引入 AGPL 依赖。

T1 提示注入防护: pkg/promptguard 包裹外部/知识库内容为不可信数据,buildMessages 移出 system 指令区。 T2 安全 CI: .github/workflows(ci+security: govulncheck/gitleaks/actionlint/hadolint/trivy)+dependabot+.hadolint.yaml;go.mod 加 toolchain go1.25.11 修复 20 个 stdlib CVE。 T3 管理员 2FA: 迁移 000016 + RFC6238 TOTP/备份码(pkg/auth, 零依赖) + 登录流程集成(后端)。 T4 本地模型: LLM/embedding 支持本地 vLLM/Ollama(OpenAI 兼容, 鉴权头条件发送, NoAuth) + docs/local-deploy.md。 T6 深度研究: 迁移 000017 + Python research-worker(净室多步流水线, 检索避开 SearXNG) + Go research 服务/handler/路由。 T7 service 层: 新增 internal/service/{research,twofa}, 2FA 业务逻辑从胖 handler 下沉, 接口注入可单测。 T10 缓存/可观测性: internal/cache(Redis+内存, 优雅降级) 接入 store 热点列表; Prometheus 指标+/metrics; docs/openapi.yaml。 验证: go build/vet/test ./... 全绿(8 包); research-worker 12 单测过; 真实 PG 应用迁移并烟测。
This commit is contained in:
freedakgmail
2026-06-17 17:52:47 +08:00
parent 97feb42afb
commit c949204662
55 changed files with 4341 additions and 25 deletions
+447
View File
@@ -0,0 +1,447 @@
openapi: 3.0.3
info:
title: 政智通 GovAI Portal API
description: >
政务 AI 应用平台后端 API。所有业务响应使用统一信封 `{code,message,data}`
`code=0` 表示成功。认证使用 JWTBearer 头或 access_token Cookie)。
本规范覆盖主要端点,随实现演进补充。
version: "1.0.0"
servers:
- url: http://localhost:8080/api/v1
description: 本地开发
tags:
- name: auth
description: 认证与两步验证
- name: store
description: 应用商店(公开只读)
- name: apps
description: 应用使用(对话/补全)
- name: research
description: 深度研究(综合研判)
- name: ppt
description: PPT 生成
- name: knowledge
description: 知识库
- name: ops
description: 运维(健康检查 / 指标)
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
cookieAuth:
type: apiKey
in: cookie
name: access_token
schemas:
ApiResponse:
type: object
properties:
code:
type: integer
description: 0 为成功,其它为业务错误码
example: 0
message:
type: string
example: success
data:
nullable: true
description: 业务数据,结构随接口而定
LoginRequest:
type: object
required: [email, password]
properties:
email: { type: string, format: email }
password: { type: string, format: password }
org_id: { type: string, description: 机构 ID(可选) }
totp_code: { type: string, description: 已开启 2FA 时提供 }
backup_code: { type: string, description: 备份码(TOTP 不可用时) }
ResearchCreateRequest:
type: object
required: [topic]
properties:
topic: { type: string, description: 研究题目/问题 }
app_id: { type: string }
config:
type: object
properties:
max_steps: { type: integer, default: 4 }
max_sources: { type: integer, default: 6 }
language: { type: string, default: zh }
ResearchTask:
type: object
properties:
task_id: { type: string }
topic: { type: string }
status:
type: string
enum: [pending, planning, searching, reading, synthesizing, completed, failed, canceled]
progress: { type: integer, minimum: 0, maximum: 100 }
report: { type: string, nullable: true, description: 完成后的 Markdown 报告 }
sources:
type: array
items:
type: object
properties:
title: { type: string }
url: { type: string }
snippet: { type: string }
tokens_used: { type: integer }
created_at: { type: string, format: date-time }
security:
- bearerAuth: []
- cookieAuth: []
paths:
/auth/register:
post:
tags: [auth]
summary: 注册
security: []
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [name, email, password]
properties:
name: { type: string }
email: { type: string, format: email }
password: { type: string, minLength: 6 }
responses:
"201": { description: 注册成功, content: { application/json: { schema: { $ref: "#/components/schemas/ApiResponse" } } } }
"409": { description: 邮箱已注册 }
/auth/login:
post:
tags: [auth]
summary: 登录(支持 2FA
description: 已开启 2FA 的账号在密码正确但未带验证码时返回 `code=40110`,前端据此引导输入验证码后重试。
security: []
requestBody:
required: true
content:
application/json:
schema: { $ref: "#/components/schemas/LoginRequest" }
responses:
"200": { description: 登录成功, content: { application/json: { schema: { $ref: "#/components/schemas/ApiResponse" } } } }
"401": { description: 凭据错误 / 需要或验证码错误(40110 需要 2FA、40111 验证码错误) }
/auth/refresh:
post:
tags: [auth]
summary: 刷新 access token
security: []
responses:
"200": { description: 成功 }
/auth/logout:
post:
tags: [auth]
summary: 登出
responses:
"200": { description: 成功 }
/auth/me:
get:
tags: [auth]
summary: 当前用户信息
responses:
"200": { description: 成功, content: { application/json: { schema: { $ref: "#/components/schemas/ApiResponse" } } } }
/auth/2fa/status:
get:
tags: [auth]
summary: 查询 2FA 状态
responses:
"200":
description: 成功
content:
application/json:
schema:
allOf:
- $ref: "#/components/schemas/ApiResponse"
- type: object
properties:
data:
type: object
properties:
enabled: { type: boolean }
backup_codes_remaining: { type: integer }
/auth/2fa/enroll:
post:
tags: [auth]
summary: 开始 2FA 设置(返回密钥/二维码 URI/一次性备份码)
responses:
"200":
description: 成功
content:
application/json:
schema:
allOf:
- $ref: "#/components/schemas/ApiResponse"
- type: object
properties:
data:
type: object
properties:
secret: { type: string }
otpauth_uri: { type: string }
backup_codes: { type: array, items: { type: string } }
"409": { description: 已启用(40902 }
/auth/2fa/verify:
post:
tags: [auth]
summary: 校验验证码并启用 2FA
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [code]
properties:
code: { type: string }
responses:
"200": { description: 已启用 }
"401": { description: 验证码错误(40111 }
/auth/2fa/disable:
post:
tags: [auth]
summary: 关闭 2FA(需 TOTP 或备份码)
requestBody:
content:
application/json:
schema:
type: object
properties:
code: { type: string }
backup_code: { type: string }
responses:
"200": { description: 已关闭 }
"401": { description: 验证码或备份码错误(40111 }
/organizations:
get:
tags: [auth]
summary: 机构列表(公开)
security: []
responses:
"200": { description: 成功 }
/store/categories:
get:
tags: [store]
summary: 应用分类(缓存 60s
security: []
parameters:
- { name: org_id, in: query, schema: { type: string } }
responses:
"200": { description: 成功 }
/store/apps:
get:
tags: [store]
summary: 应用列表(分页/搜索/排序)
security: []
parameters:
- { name: page, in: query, schema: { type: integer, default: 1 } }
- { name: page_size, in: query, schema: { type: integer, default: 20, maximum: 50 } }
- { name: q, in: query, schema: { type: string } }
- { name: category, in: query, schema: { type: string } }
- { name: sort, in: query, schema: { type: string, enum: [popular, rating, latest] } }
- { name: org_id, in: query, schema: { type: string } }
responses:
"200": { description: 成功 }
/store/apps/{slug}:
get:
tags: [store]
summary: 应用详情
security: []
parameters:
- { name: slug, in: path, required: true, schema: { type: string } }
responses:
"200": { description: 成功 }
"404": { description: 不存在 }
/store/featured:
get:
tags: [store]
summary: 精选应用(缓存 60s
security: []
parameters:
- { name: org_id, in: query, schema: { type: string } }
responses:
"200": { description: 成功 }
/store/rankings:
get:
tags: [store]
summary: 应用排行榜(缓存 60s
security: []
parameters:
- { name: org_id, in: query, schema: { type: string } }
responses:
"200": { description: 成功 }
/apps/{id}/chat:
post:
tags: [apps]
summary: 对话(SSE 流式)
parameters:
- { name: id, in: path, required: true, schema: { type: string } }
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [message]
properties:
message: { type: string }
conversation_id: { type: string }
responses:
"200": { description: SSE 事件流(text/event-stream }
/apps/{id}/completion:
post:
tags: [apps]
summary: 补全(SSE 流式)
parameters:
- { name: id, in: path, required: true, schema: { type: string } }
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [message]
properties:
message: { type: string }
responses:
"200": { description: SSE 事件流 }
/research/tasks:
post:
tags: [research]
summary: 创建深度研究任务
requestBody:
required: true
content:
application/json:
schema: { $ref: "#/components/schemas/ResearchCreateRequest" }
responses:
"201": { description: 已创建(返回 task_id, status=pending }
"400": { description: 题目为空 }
get:
tags: [research]
summary: 我的研究任务列表
responses:
"200": { description: 成功 }
/research/tasks/{taskId}:
get:
tags: [research]
summary: 查询研究任务状态/结果
parameters:
- { name: taskId, in: path, required: true, schema: { type: string } }
responses:
"200":
description: 成功
content:
application/json:
schema:
allOf:
- $ref: "#/components/schemas/ApiResponse"
- type: object
properties:
data: { $ref: "#/components/schemas/ResearchTask" }
"404": { description: 不存在 }
/research/tasks/{taskId}/cancel:
post:
tags: [research]
summary: 取消进行中的研究任务
parameters:
- { name: taskId, in: path, required: true, schema: { type: string } }
responses:
"200": { description: 已取消 }
"404": { description: 不存在或无法取消 }
/ppt/tasks:
post:
tags: [ppt]
summary: 创建 PPT 生成任务
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [title, source_content]
properties:
title: { type: string }
source_type: { type: string, enum: [text, url] }
source_content: { type: string }
config: { type: object }
responses:
"201": { description: 已创建 }
/ppt/tasks/{taskId}:
get:
tags: [ppt]
summary: 查询 PPT 任务状态
parameters:
- { name: taskId, in: path, required: true, schema: { type: string } }
responses:
"200": { description: 成功 }
"404": { description: 不存在 }
/knowledge:
get:
tags: [knowledge]
summary: 知识库列表
responses:
"200": { description: 成功 }
post:
tags: [knowledge]
summary: 创建知识库
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [name]
properties:
name: { type: string }
description: { type: string }
responses:
"201": { description: 已创建 }
/health:
get:
tags: [ops]
summary: 健康检查
security: []
responses:
"200": { description: ok }
/metrics:
get:
tags: [ops]
summary: Prometheus 指标(文本格式,供监控抓取)
description: 注意此端点位于 `/metrics`(非 `/api/v1` 前缀下)。
security: []
responses:
"200": { description: Prometheus 文本格式指标 }