关键设计决策 (Architecture Decision Records)¶
概述
本文档以 ADR(Architecture Decision Record)格式,记录项目中所有关键架构决策。 每条决策包含 上下文(Context)、决策(Decision)、后果(Consequence) 及相关文件引用。
决策总览¶
| ADR | 决策名称 | 影响范围 | 关键文件 |
|---|---|---|---|
| ADR-1 | 双面板分离 (Two-Plane Separation) | 整体架构 | semantic-plane/, data-plane/ |
| ADR-2 | R/V/G 三引擎架构 (Triple-Engine) | 数据存储 | backend/app/agents/semantic_plane/ |
| ADR-3 | Supervisor 轻量化 | Agent 编排 | backend/app/agents/supervisor.py |
| ADR-4 | 显式路由决策 (Explicit Routing) | Agent 编排 | backend/app/agents/sub_agents/router.py |
| ADR-5 | SQL 护栏多层验证 | 安全 | backend/app/agents/sub_agents/guardrails.py |
| ADR-6 | Chat-First API 迁移 | API 层 | backend/app/api/chats.py |
| ADR-7 | 多模型注册与降级链 | 模型层 | backend/app/models/registry.py |
| ADR-8 | 语义版本化元数据 | 治理流程 | scripts/check_version_bump.py |
| ADR-9 | 双数据库连接池 | 运行时 | backend/app/main.py, backend/app/db/engine.py |
| ADR-10 | SQL-Only 缓存策略 | 性能 | backend/app/memory/sql_cache.py |
| ADR-11 | 治理叠加层 (Governance Overlays) | 多租户 | backend/app/skills/governance.py |
| ADR-12 | 三层治理模型 | 元数据治理 | semantic-plane/ |
| ADR-13 | 双语必备 (Bilingual Requirement) | 元数据规范 | semantic-plane/ |
| ADR-14 | PII 分级暴露 | 安全与隐私 | semantic-plane/, backend/app/agents/sub_agents/ |
| ADR-15 | LLM 驱动查询分解 (LLM Query Decomposition) | 检索管道 | backend/app/skills/query_decomposer.py |
| ADR-16 | 术语绑定强路由 (Term Binding Strong Routing) | 全管道 | backend/app/skills/rag.py, backend/app/skills/reranker.py |
| ADR-17 | Better Auth 统一认证 (Unified Auth via Better Auth) | 认证与授权 | web/lib/auth.ts, backend/app/security/auth.py |
ADR-1: 双面板分离 (Two-Plane Separation)¶
状态:已采纳
上下文 (Context)¶
元数据治理需要版本控制、代码审查和可追溯的变更历史;而运行时查询则需要低延迟、高吞吐的数据库访问。这两种诉求在迭代节奏和扩缩容策略上存在根本差异。
决策 (Decision)¶
将系统拆分为两个独立面板:
- Semantic Plane:治理资产以 YAML 文件形式存储在 Git 仓库中,通过 PR 审查流程管理变更
- Data Plane:运行时执行层使用 PostgreSQL(本地)/ Redshift(生产)提供高性能查询
后果 (Consequence)¶
| 正面 | 负面 |
|---|---|
| Git 原生的版本控制与审查流程 | 需要额外的同步管道 |
| 治理资产可独立于服务部署 | 存在短暂一致性窗口 |
| 开发者可用熟悉的 IDE 编辑元数据 | 需要 CI 校验保证 YAML 合法性 |
相关文件¶
semantic-plane/ # 治理资产(YAML + Git)
data-plane/ # 运行时执行层
backend/app/agents/semantic_plane/ # 同步管道:S3 → R+V+G pipeline
ADR-2: R/V/G 三引擎架构 (Triple-Engine Architecture)¶
状态:已采纳
上下文 (Context)¶
NL2SQL 场景下存在三种截然不同的检索模式:
- 精确匹配:通过 ID/名称查找具体表或指标定义
- 语义召回:通过自然语言相似度检索相关元数据
- 关系推理:沿实体关系图谱发现关联和路径
单一存储引擎无法同时优化这三种模式。
决策 (Decision)¶
采用 Relational + Vector + Graph 三引擎并存架构:
| 引擎 | 技术选型 | 用途 |
|---|---|---|
| Relational | Aurora PostgreSQL | 结构化 CRUD、精确查询、JOIN 规则 |
| Vector | pgvector 扩展 | Embedding 存储、语义相似度检索 |
| Graph | Apache AGE 扩展 | 实体关系图谱、路径推理、血缘追溯 |
统一数据库实例
三个引擎共享同一 PostgreSQL 实例(通过扩展实现),降低运维复杂度。
后果 (Consequence)¶
| 正面 | 负面 |
|---|---|
| 每种检索模式获得最佳性能 | 每条资产存储 3 次(结构化、向量化、图化) |
| 单一 PG 实例减少运维开销 | Backend pipeline 需维护三份写入一致性 |
| 可按需组合多种检索方式 | 调试时需理解三种存储的数据模型 |
相关文件¶
backend/app/agents/semantic_plane/ # 关系 upsert + AGE 图谱同步 + Embedding 生成
data-plane/sql/001_create_schema.sql # DDL(含 pgvector + AGE 初始化)
ADR-3: Supervisor 轻量化 (Lightweight Supervisor)¶
状态:已采纳
上下文 (Context)¶
早期设计中 Supervisor 承载了意图解析、路由、检索、生成等全部逻辑,导致单文件超过 1000 行,测试困难,修改任一功能需回归全链路。
决策 (Decision)¶
Supervisor 仅执行两项职责:
- Embedding 生成:对用户输入进行向量化
- Governance Overlay 解析:注入租户/区域/合规约束
所有业务逻辑委派给独立的 sub-agent。
后果 (Consequence)¶
| 正面 | 负面 |
|---|---|
| 各 sub-agent 可独立单元测试 | 调用链更长,需要良好的 tracing |
| 新路由/新 agent 可独立开发 | 状态传递依赖 LangGraph State |
| Supervisor 职责清晰,代码简洁 | 初学者需理解整体编排拓扑 |
相关文件¶
backend/app/agents/supervisor.py # Supervisor 入口
backend/app/agents/sub_agents/query_understanding.py # 意图结构化
backend/app/agents/sub_agents/router.py # 路由决策
ADR-4: 显式路由决策 (Explicit Routing)¶
状态:已采纳
上下文 (Context)¶
"学习型路由"(如 classifier 模型)虽然灵活,但在出错时难以定位原因,且训练数据不足时表现不稳定。对于关键业务系统,可解释性和确定性优先于灵活性。
决策 (Decision)¶
Router sub-agent 基于结构化意图分析,显式选择以下 7 条命名路由之一:
| 路由名称 | 用途 |
|---|---|
kpi_lookup |
指标快速查询 |
nl2sql_query |
标准 NL2SQL 生成 |
deep_analysis_workflow |
深度分析流程 |
analytical_workflow |
多步分析工作流 |
graph_reasoning |
图谱推理 |
business_knowledge_qa |
业务知识问答 |
clarification_required |
需要澄清 |
后果 (Consequence)¶
| 正面 | 负面 |
|---|---|
| 路由决策完全可解释、可审计 | 新增路由需手动维护映射表 |
| 调试时可直接定位失败节点 | 边界场景需人工定义归属 |
| 可针对单一路由做性能优化 | 不如学习型路由自适应 |
相关文件¶
ADR-5: SQL 护栏多层验证 (SQL Guardrails)¶
安全关键决策
上下文 (Context)¶
LLM 生成的 SQL 可能包含:
- 语法错误导致执行失败
DELETE/UPDATE/DROP等破坏性操作- 全表扫描或笛卡尔积导致资源耗尽
- 术语绑定违规 — 将绑定列引用到错误的物理表(如"价格"绑定 fact_transaction.price,却生成 dim_article.price)
需要在执行前进行多层防护。
决策 (Decision)¶
实施五层验证流水线:
| 层级 | 验证内容 | 工具/方式 |
|---|---|---|
| Layer 1 语法解析 | SQL 语法正确性 | sqlparse AST 解析 |
| Layer 2a 安全扫描 | DML 操作检测、注入检测 | 禁止关键词匹配 + 模式识别 |
| Layer 2c 列引用校验 | 引用不存在的列 | AST 列提取 + 白名单对比 |
| Layer 2d 术语绑定校验 | 绑定列被引用到错误表 | 正则 + term_bindings 元数据 |
| Layer 3 成本估算 | 预估行数/开销超阈值拦截 | EXPLAIN 执行计划分析 |
关键配置
后果 (Consequence)¶
| 正面 | 负面 |
|---|---|
| 有效阻止数据变更和资源滥用 | 复杂合法查询偶尔被误拦截 |
| 自动注入 LIMIT 兜底 | EXPLAIN 增加约 10-50ms 延迟 |
| 术语绑定语义保障 — 防止 LLM 将度量落到错误的维度表 | Layer 2d 仅对有 mapped_asset_id 的术语生效 |
| 五层独立,可按需启停 | 需维护禁止词列表更新 |
相关文件¶
backend/app/agents/sub_agents/guardrails.py # 五层验证主逻辑
backend/app/skills/sql_column_validator.py # Layer 2c AST 列校验
backend/app/security/guard.py # 安全扫描模块
ADR-6: Chat-First API 迁移 (Chat-First API Migration)¶
状态:已采纳
上下文 (Context)¶
早期 API 采用单次请求/响应模式(/query、/stream),无法支撑:
- 多轮对话上下文保持
- 对话历史回放
- 中间结果的增量推送
决策 (Decision)¶
- 主 API:
/api/v1/chats会话式端点,支持完整对话生命周期 - 流式传输:SSE(Server-Sent Events)通过 POST 请求(非 GET EventSource)
- 向后兼容:保留
/query+/stream作为 legacy 接口
后果 (Consequence)¶
| 正面 | 负面 |
|---|---|
| 原生多轮对话,上下文自动维护 | 前端需实现 SSE over POST |
| 对话历史持久化,可回放 | 会话管理增加服务端状态 |
| 统一入口简化客户端集成 | Legacy 接口仍需维护一段时间 |
相关文件¶
backend/app/api/chats.py # Chat-First 主端点
backend/app/api/router.py # API 路由注册(chats 为 primary)
backend/app/session/ # 会话管理模块
ADR-7: 多模型注册与降级链 (Multi-Model Registry with Fallback)¶
状态:已采纳
上下文 (Context)¶
依赖单一 LLM 提供商存在风险:
- 单点故障导致全系统不可用
- 不同任务对模型能力要求不同(推理 vs 编码 vs 速度)
- 成本优化需灵活切换
决策 (Decision)¶
采用 任务驱动的模型路由(非提供商驱动),配置可降级链:
| 任务类型 | 首选模型 | 降级模型 |
|---|---|---|
| SQL 生成 | qwen3-coder-plus | deepseek-v3.2 |
| 意图理解 | claude-4.5-sonnet | qwen3-max |
| 通用推理 | deepseek-v3.2 | qwen3-max |
| 代码审核 | claude-4.5-sonnet | qwen3-coder-plus |
运行时覆盖
管理员可通过数据库配置动态覆盖模型路由,无需重启服务。
后果 (Consequence)¶
| 正面 | 负面 |
|---|---|
| 单模型宕机时自动降级 | 需维护多提供商 API Key |
| 按任务优化成本和质量 | 不同模型输出格式需统一适配 |
| 可灵活接入新模型 | 降级链配置需持续调优 |
相关文件¶
ADR-8: 语义版本化元数据 (Semantic Versioning for Metadata)¶
状态:已采纳
上下文 (Context)¶
元数据变更会触发下游效应:
- MAJOR 变更(SQL 逻辑修改)需要重新向量化
- MINOR 变更(描述更新)可增量更新 embedding
- PATCH 变更(typo 修复)无需触发任何管道
需要一种机制区分变更影响级别。
决策 (Decision)¶
所有元数据资产遵循 SemVer 规范,CI 强制检查版本递增:
| 变更类型 | 版本变化 | 触发动作 |
|---|---|---|
格式修正、typo、reviewed_at 更新 |
PATCH | 无 |
描述更新、新增同义词、exceptions |
MINOR | 增量 re-embedding |
SQL 逻辑、rule_expression、bound_assets |
MAJOR | 全量 re-vectorize + 审批 |
后果 (Consequence)¶
| 正面 | 负面 |
|---|---|
| 精确控制下游管道触发时机 | 贡献者需理解版本规则 |
| MAJOR 变更可触发额外审批流程 | 偶尔出现版本争议 |
| 变更历史清晰可追溯 | CI 检查增加 PR 合入时间 |
相关文件¶
ADR-9: 双数据库连接池 (Dual Database Pool)¶
状态:已采纳
上下文 (Context)¶
后端存在两类数据库访问模式:
- CRUD 操作:会话管理、审计日志等,适合 ORM
- AI 管道:高吞吐 embedding 检索、批量向量查询,ORM 开销不可接受
决策 (Decision)¶
维护两个独立连接池:
| 连接池 | 技术选型 | 用途 |
|---|---|---|
| ORM Pool | SQLAlchemy async pool | CRUD、会话、审计 |
| Raw Pool | psycopg async pool | Embedding 检索、向量查询、图谱遍历 |
后果 (Consequence)¶
| 正面 | 负面 |
|---|---|
| AI 管道无 ORM 序列化开销 | 两套连接池需分别监控 |
| 连接池参数可独立调优 | 代码中需明确选择使用哪个池 |
| ORM 层仍享有 Schema 验证便利 | 数据库总连接数需合理规划 |
相关文件¶
ADR-10: SQL-Only 缓存策略 (SQL-Only Cache)¶
状态:已采纳
上下文 (Context)¶
缓存策略需要在性能和数据新鲜度间权衡:
- 缓存执行结果:快但可能返回过时数据
- 缓存生成的 SQL:仍需执行但保证数据实时性
- 完全不缓存:每次重新生成 SQL,延迟高
决策 (Decision)¶
仅缓存生成的 SQL 语句,使用 pgvector 语义相似度匹配:
缓存匹配流程
关键配置:
| 参数 | 值 | 说明 |
|---|---|---|
TTD_SQL_CACHE_SIMILARITY |
0.92 | 语义相似度阈值 |
TTD_SQL_CACHE_TTL_MINUTES |
15 | 缓存过期时间 |
后果 (Consequence)¶
| 正面 | 负面 |
|---|---|
| 数据始终是最新的 | 仍需执行 SQL,无法跳过查询开销 |
| 避免 LLM 重复生成,降低延迟和成本 | 语义相似度阈值需精细调优 |
| 缓存失效简单(TTL 即可) | 同义问法可能未命中缓存 |
相关文件¶
ADR-11: 治理叠加层 (Governance Overlays)¶
状态:已采纳
上下文 (Context)¶
多租户场景下,不同租户/区域/合规要求需要不同的数据访问约束,但不希望通过代码分支实现差异化。
决策 (Decision)¶
在 Supervisor 入口处注入 Governance Overlays,作为约束条件贯穿后续所有 sub-agent:
Overlay 类型包括:
- Tenant Overlay:数据范围隔离
- Region Overlay:地域合规(如 GDPR、数据出境)
- Compliance Overlay:行业合规(如金融监管)
后果 (Consequence)¶
| 正面 | 负面 |
|---|---|
| 多租户无需代码分支 | Overlay 规则需持续维护 |
| 新约束可热加载 | Sub-agent 需正确解读 overlay |
| 统一入口便于审计 | 复杂 overlay 组合需充分测试 |
相关文件¶
backend/app/skills/governance.py # Overlay 解析与注入
backend/app/agents/supervisor.py # Supervisor 中 overlay 初始化
ADR-12: 三层治理模型 (Three-Layer Governance Model)¶
状态:已采纳
上下文 (Context)¶
不同类型的元数据有不同的变更频率、负责人和审批流程:
- 表/列定义相对稳定,由 DBA 维护
- 术语网络持续演进,由 Data Steward 维护
- 业务规则频繁变化,由业务专家维护
决策 (Decision)¶
将治理模型分为三层:
| 层级 | 范围 | 负责人 | 变更频率 |
|---|---|---|---|
| Layer 1 规范元数据 | 表、列、指标、JOIN 规则、Few-shot | DBA / Data Engineer | 低(认证后稳定) |
| Layer 2 术语治理 | 术语生命周期、消歧、关系网络 | Data Steward | 中(持续演进) |
| Layer 3 业务知识 | 业务规则、上下文假设、约束 | Domain SME | 高(随业务变化) |
后果 (Consequence)¶
| 正面 | 负面 |
|---|---|
| 权责清晰,各层独立演进 | 跨层引用需严格校验 |
| Layer 1 稳定性保障查询质量 | 三层协调需要治理流程支撑 |
| Layer 3 可快速响应业务变化 | 新贡献者需理解层级划分 |
相关文件¶
semantic-plane/domains/ # Layer 1: 表/指标
semantic-plane/terms/ # Layer 1+2: 术语
semantic-plane/term_relationships/ # Layer 2: 术语关系
semantic-plane/business_rules/ # Layer 3: 业务规则
semantic-plane/business_contexts/ # Layer 3: 业务上下文
scripts/validate_cross_layer_refs.py # 跨层引用校验
ADR-13: 双语必备 (Bilingual Requirement)¶
状态:已采纳
上下文 (Context)¶
- 终端用户主要使用中文提问
- 系统内部(SQL、代码、API)使用英文
- LLM 需要同时理解中英文语义
决策 (Decision)¶
所有元数据字段必须提供 _zh 和 _en 双语对:
# 示例
business_name_zh: "订单总金额"
business_name_en: "Total Order Amount"
description_zh: "包含已完成订单的总交易金额(含税)"
description_en: "Total transaction amount of completed orders (tax inclusive)"
规范要求
- 描述必须阐释业务语义,不可仅描述物理存储
_zh和_en字段必须成对出现,缺一不可- CI 校验会检查双语字段完整性
后果 (Consequence)¶
| 正面 | 负面 |
|---|---|
| LLM 可根据用户语言偏好选择引用 | 元数据编写工作量翻倍 |
| 中文用户体验自然流畅 | 翻译质量需人工把控 |
| Embedding 可分别为两种语言生成 | 双语一致性维护成本 |
相关文件¶
ADR-14: PII 分级暴露 (PII Tiered Exposure)¶
安全关键决策
上下文 (Context)¶
NL2SQL 系统中,LLM 需要了解数据库 Schema 才能生成正确 SQL,但部分列包含 PII(个人可识别信息),如客户姓名、手机号、身份证号等。需要在 SQL 生成质量和隐私保护之间取得平衡。
决策 (Decision)¶
为每个列定义 llm_exposure_policy,实施三级暴露策略:
| 策略 | LLM 可见性 | 适用场景 | 示例 |
|---|---|---|---|
visible |
完全可见 | 非敏感业务字段 | order_id, amount |
masked |
仅可见脱敏样本 | 需要理解格式但不需真实值 | phone → 138****1234 |
hidden |
完全不可见 | 高敏感 PII | id_card_number |
# 元数据定义示例
columns:
- column_id: col_customer_name
sensitivity_level: pii
llm_exposure_policy: hidden
- column_id: col_order_amount
sensitivity_level: internal
llm_exposure_policy: visible
强制执行
hidden 策略的列不参与 embedding 生成,从源头杜绝 PII 泄露到 LLM 上下文。
后果 (Consequence)¶
| 正面 | 负面 |
|---|---|
| PII 从源头阻断,不进入 LLM | hidden 列上的查询可能失败 |
| 策略声明式,易于审计 | 需要为每列正确标注策略 |
masked 兼顾格式理解和隐私 |
脱敏逻辑需按类型定制 |
相关文件¶
semantic-plane/domains/ # 列定义中的 llm_exposure_policy
backend/app/agents/sub_agents/data_retrieval.py # 运行时策略执行
ADR-15: LLM 驱动查询分解 (LLM Query Decomposition)¶
上下文 (Context)¶
Engine 1(术语精确匹配)需要将用户自然语言问题拆分为搜索关键词,逐词对 business_term 表做 ILIKE 匹配。
最初实现使用正则表达式(按中文虚词切分 + 英文空格分词),但这种方法:
- 不通用 — 虚词列表硬编码,只覆盖了部分中文语法,对其他语言无效
- 不灵活 — 口语化、缩写、行话无法正确切分
- 反模式 — 在 data agent 中使用正则做 NLU 是 step backward;任何 case-by-case 的 regex 修补都会越来越脆弱
决策 (Decision)¶
- 创建独立 skill
query_decomposer.py,封装两种关键词提取方式: from_query_understanding(qu)— 复用 QU 节点已经用 LLM 解析出的entities.terms、metric_candidates等,零额外延迟-
QueryDecomposer(registry).extract_search_terms(query)— 独立 LLM 调用(仅当 QU 未运行时使用) -
data_retrieval_node优先从state["query_understanding"]提取搜索关键词传入 Engine 1 -
exact_term_match()接受search_terms: list[str] | None参数,不再包含任何分词逻辑 -
完全移除
_tokenize_for_term_match()和相关正则常量
后果 (Consequence)¶
| 正面 | 负面 |
|---|---|
| 语言无关、领域无关 — LLM 天然理解任何语言和领域术语 | 如果 QU 节点跳过(极端路径),需 fallback 到独立 LLM 调用 |
| 零额外延迟 — 正常流程复用已有的 QU LLM 输出 | QueryDecomposer 独立调用增加 ~200ms(仅 fallback) |
| 不需要维护停用词/虚词列表 | LLM 极低概率返回不合理关键词(有 fallback_split 兜底) |
| skill 可独立被其他节点复用(如 corrective_retrieval) | — |
相关文件¶
backend/app/skills/query_decomposer.py # 独立 skill
backend/app/agents/sub_agents/data_retrieval.py # 消费 QU 输出
backend/app/skills/rag.py # exact_term_match 接受 search_terms
ADR-16: 术语绑定强路由 (Term Binding Strong Routing)¶
上下文 (Context)¶
Semantic Plane 中的 business_term 通过 mapped_asset_id 字段绑定到物理列或指标。
例如 term_hm_price → col_hm_txn_price(属于 fact_transaction)。
此前这些绑定信息虽然存在于元数据,但 runtime 并未将其用作强约束——
LLM 仍可能将"价格"错误地映射到 dim_article.price(该表不含此列,或此列含义不同)。
根因是信号在管道中逐步衰减: - Engine 1 命中术语后,绑定信息仅用于图遍历的入口点 - Reranker 不感知绑定关系 - Prompt 只给白名单,未明确说 "价格必须从 fact_transaction 取" - Guardrails 只校验列是否存在,不校验列是否在正确的表 - 自愈只按错误文本搜索,不按治理知识修复
决策 (Decision)¶
将术语绑定作为强路由信号贯穿整个管道:
- 检索阶段 — 构建
_term_bindings视图注入retrieval_results - Reranker — Step 0 术语绑定加分/降权(绑定列 → 0.95,父表 → +0.25,非绑定表 → -0.10)
- Prompt Assembler — 新增
## ⚠ 术语绑定约束段落,生成必须使用 table.column强指令 - Guardrails Layer 2d — 拦截
wrong_table.column引用 - Corrective Retrieval — 检测到绑定违规后,优先按绑定关系拉取正确表和列
- Engine R+ 无条件合并 — 关键词列命中不再受 vector 命中数量门控
后果 (Consequence)¶
| 正面 | 负面 |
|---|---|
| 从根源消除"度量列落到错误表"的幻觉类错误 | 需要术语正确绑定(脏元数据会导致误拦截) |
| 五层强化互为兜底,单层失效不影响整体 | 管道节点间共享 _term_bindings 增加隐式耦合 |
| 自愈路径精准 — 直接告知正确 table.column | Layer 2d 正则匹配对复杂别名场景有边界 |
| 不需要额外 LLM 调用 — 纯规则引擎 | — |
相关文件¶
backend/app/skills/rag.py # _build_term_bindings + R+ 无条件合并
backend/app/skills/reranker.py # _boost_term_bound_assets
backend/app/prompt/assembler.py # _build_term_binding_context
backend/app/agents/sub_agents/guardrails.py # _validate_term_bindings (Layer 2d)
backend/app/skills/corrective_retrieval.py # _correct_via_term_bindings
ADR-17: Better Auth 统一认证 (Unified Auth via Better Auth)¶
状态:已采纳
上下文 (Context)¶
原系统使用共享 HS256 JWT Secret 进行认证:前端签发 Token、后端验证。这种方式存在以下问题:
- 无用户管理:没有注册/登录流程,用户身份仅来自手动生成的 Token
- 无会话管理:Token 一旦签发无法撤销,无 refresh 机制
- 安全性低:HS256 共享密钥意味着前后端都持有签名能力,违反最小权限原则
- Admin 无保护:后台管理 API 缺乏角色校验,依赖
TTD_DEBUG旁路
决策 (Decision)¶
引入 Better Auth 作为统一身份与会话权威,托管在 Next.js 内:
- Better Auth 作为唯一 IdP — 在 Next.js 内运行,管理注册、登录、会话、JWT 签发
- RS256 + JWKS 验签 — 放弃 HS256 共享密钥,改用公私钥对。FastAPI 通过 JWKS 端点获取公钥本地验签
- Better Auth user.id 作为全系统主体标识 — 直接映射到现有 Chat/Feedback 表的
user_id字段 - 全局角色模型 — 仅 admin/user 两级,通过 Better Auth Admin plugin 管理角色
- 数据共用 PostgreSQL — Better Auth 的 5 张表 (user, session, account, verification, jwks) 存于同一 PG 实例
- JWT 仅作 API 访问令牌 — 浏览器主会话由 Better Auth cookie 管理,减少长期 Token 风险
后果 (Consequence)¶
| 正面 | 负面 |
|---|---|
| 完整的注册/登录/OAuth/会话管理 | 新增 5 张 auth 表(已用迁移隔离) |
| RS256 非对称签名,后端仅持有公钥 | Better Auth 与 Next.js 强耦合 |
| Admin 路由有强制角色校验 | 现有裸 Token 使用者需迁移 |
| JWKS 公钥缓存,无逐请求 introspection 开销 | 需部署时确保 JWKS 端点可达 |
| 可渐进添加 OAuth provider (GitHub) | 迁移期需维护 HS256 回退逻辑 |
相关文件¶
web/lib/auth.ts # Better Auth 服务端配置
web/lib/auth-client.ts # 客户端 auth SDK
web/app/api/auth/[...all]/route.ts # Auth API 路由处理器
web/app/(auth)/ # 登录/注册页面
web/lib/api/client-config.ts # Token 注入到 FastAPI 请求
backend/app/security/auth.py # JWKS 验签 + UserContext + require_admin
backend/app/config.py # better_auth_url, better_auth_issuer, jwks_cache_ttl
backend/app/api/admin/router.py # Admin 路由保护 (Depends(require_admin))