Files
backend/docs/02-技术架构设计.md
T
34047007@qq.com a6cd99a4ca
CI / backend (push) Canceled after 0s
CI / frontend (push) Canceled after 0s
feat: initial commit - oncology literature search platform
OncoLit: a multi-tenant oncology literature search, feed, and
collaboration platform. Built with FastAPI + Vue 3 + PostgreSQL.
Includes PubMed pipeline, drug approvals, AI summaries, and
systematic review tools.
2026-07-27 07:59:18 +08:00

8.8 KiB
Raw Blame History

技术架构设计

一、核心架构决策

1.1 配置驱动:一套代码 = 一个垂直科室

每个专科(肿瘤/心血管/神经...)是独立部署实例。一套代码通过配置文件切换专科,实例间完全隔离。

# config/specialties/oncology.yaml
specialty:
  name: "肿瘤科"
  slug: "oncology"
  
pubmed_filter:
  mesh_include_categories: ["C04"]
  mesh_include_subcategories: ["C04.557", "C04.588", "C04.697"]
  mesh_cross_include: ["E02.319", "E02.815", "D27.505"]
  
journals:
  tier_config:
    tier1:  # 🔴 四大综合顶刊
      issns: ["0028-4793", "0140-6736", "0098-7484", "0959-8138"]
    tier2:  # 🟠 肿瘤顶刊
      issns: ["0732-183X", "1474-5488", "2374-2437", "2159-8290"]

tag_engine:
  tag_tree_file: "oncology_tags.json"
  mesh_to_tag_mapping: "oncology_mesh_mapping.json"

ui:
  theme:
    primary_color: "#1a5276"
    logo_text: "肿瘤科文献中心"

1.2 技术栈

层级 选型 依据
后端框架 FastAPI (async) async SQLAlchemy + asyncpg 比同步快 4.1x
ORM SQLAlchemy 2.0 (async) 原生 async,避免事件循环阻塞
数据库 PostgreSQL 16 + pgvector pgvector 为未来 AI 向量搜索预留
缓存/队列 Redis + ARQ 任务队列 ARQ 比 Celery 轻量,原生 async
搜索 PostgreSQL tsvector(当前)/ Elasticsearch 8.x(未来) 当前阶段 PG 触发器维护 tsvector + GIN 索引;ES 配置为可选,ES_URL 为空时自动回退 PG
文件存储 MinIO (dev) / COS (prod) S3 兼容 MinIO + 腾讯云 COS 双存储支持
任务队列 Redis + ARQ 原生 async,比 Celery 轻量,定时任务 + 异步任务均由其管理
前端 Vue 3 + Naive UI + Pinia + Vite 80+ 组件,TypeScript 支持好
计费 Stripe + 本地最小化缓存 Stripe 为计费真相来源
可观测 structlog + Sentry 全链路日志+错误追踪

1.3 日期时间类型

全部使用 TIMESTAMPTZ(内部存 UTC,读取时自动转时区)。特定纯日期字段(pub_date、approval_date)用 DATE

  • 存储 8 bytes
  • 数据库自动处理时区转换
  • SQL 中日期运算自然
  • 禁止用整数时间戳代替日期类型

二、多租户架构

2.1 策略:共享表 + PostgreSQL RLS 双层防御

应用层:ContextVar + Repository 自动过滤
数据库层:PostgreSQL RLS 强制执行

双层保险,防止开发者遗漏 tenant_id 过滤或 SQL 注入绕过

2.2 请求生命周期

客户端请求
  → CORS / RateLimit / CSRF / Trace 中间件
  → FastAPI 路由
  → get_current_user (依赖注入,从 JWT 解析 user + tenant_id + is_superuser)
  → TenantContext (ContextVar 设置,非中间件—避免连接池竞争)
  → Service 层
  → Model 层 (SQLAlchemy 自动加 tenant_id 过滤)
  → PostgreSQL RLS (数据库层强制检查,仅生产启用)

get_current_user 而不是中间件设置 tenant_ctx 的原因:中间件在连接池层面可能跨请求污染 ContextVar,依赖注入方式确保每个请求独立。JWT access token 携带 tidtenant_id)和 is_superuser。Admin 路由(/admin/*)通过路由器级依赖 require_superuser 强制验证。Demo 用户访问 admin 返回 403。


三、RBAC 权限模型

3.1 角色层级

owner (4)
 └─ admin (3)
      └─ editor (2)
           └─ viewer (1)

3.2 核心权限矩阵

权限 owner admin editor viewer
查看文献
创建/导入文献
编辑文献
删除文献 仅自己
管理标签
导出引用
邀请/移除成员
管理团队
配置审批流
SSO配置
计费管理

3.3 代码实现

# app/core/permissions.py
def require_role(minimum_role: TenantRole):
    """FastAPI 依赖注入:强制检查当前租户内最低角色"""
    async def dependency(current_user=Depends(get_current_user), db=Depends(get_db)):
        user_tenant = await db.execute(
            select(UserTenant).where(
                UserTenant.user_id == current_user.id,
                UserTenant.tenant_id == tenant_ctx.get(),
            )
        )
        if ROLE_HIERARCHY[user_tenant.role] < ROLE_HIERARCHY[minimum_role]:
            raise HTTPException(403)
        return user_tenant
    return dependency

四、JWT 认证设计

Access Token15分钟):
{
    "sub": "<user_id>",
    "tid": "<tenant_id>",
    "role": "editor",
    "exp": 1680000000
}

Refresh Token30天,Redis存储):
- Key: refresh_token:<jti>
- Value: {user_id, tenant_id, issued_at}
- 支持轮换(rotation),防重放

五、Stripe 计费集成

5.1 原则

  • Stripe 是计费真相来源,本地只存 customer_id + subscription_id
  • Webhook 立即返回 200 → 队列异步处理 → event.id 做幂等
  • 监听最小事件集:checkout.session.completedcustomer.subscription.*invoice.*

5.2 方案功能矩阵

功能 Free Pro Team Enterprise
存储 500MB 10GB 无限 无限
文献 1000篇 10000篇 无限 无限
API日配额 100 1000 5000 无限
团队协作
SSO
审批流
品牌定制
最大成员 1 1 50 无限
价格 $0 $5/月 $10/人/月 定制

六、前端架构

6.1 技术栈

  • Vue 3 (Composition API + <script setup>) + TypeScript
  • Vite5 + Naive UI + Pinia + Vue Router
  • Axios 封装(JWT 自动刷新拦截器 + 租户头注入)

6.2 Pinia 状态管理

  • auth store:认证状态、用户信息、权限
  • tenant store:当前租户上下文
  • literature store:文献列表、筛选条件
  • ui store:侧边栏、主题、多标签

6.3 组件分层

业务组件 (LiteratureCard, SearchPanel...)
  └─ 基础组件 (BasicTable, BasicForm, BasicModal...)
       └─ Naive UI (n-button, n-table, n-card...)

七、基础设施

7.1 Docker Compose 核心服务

服务 镜像 端口
PostgreSQL 16 pgvector/pgvector:pg16 5432
Redis redis:7-alpine 6379
Elasticsearch 8(可选) elasticsearch:8.11.0 9200
MinIO minio/minio 9000, 9001
Backend FastAPI via Uvicorn (4 workers) 8000
Worker ARQ
Frontend Vite dev server (dev) / Nginx (prod) 5173 / 80

ES 为可选服务: dev 和 prod 的 docker-compose 均有 ES 配置,但 ES_URL 为空时系统自动回退 PostgreSQL tsvector 搜索。当前生产环境未启动 ES。

7.2 Redis 缓存策略

缓存 Key TTL 用途
user:{id}:profile 300s 用户信息
tenant:{id}:settings 600s 租户设置
tenant:{id}:plan 300s 方案功能
ratelimit:{tid}:{route}:{date} 1天 API限流
refresh_token:{jti} 30天 JWT刷新
homepage:feed 1800s 首页文献列表预缓存
hot_articles 1800s 热搜缓存(TOP30 高被引)

八、搜索引擎架构

8.1 当前阶段:PostgreSQL tsvector(生产可用)

文献搜索使用 PostgreSQL 内置的 tsvector + GIN 索引:

  • global_literature.search_tsvTSVECTOR 类型),由 trg_global_literature_tsv 触发器自动维护,包含 title + abstract + authors.family + authors.affiliation
  • GIN 索引 ix_gl_search_tsv(重建迁移:0314f4d28728),约源数据 30-50%
  • 所有搜索(全文搜索 + 高级搜索字段选择)走 @@ plainto_tsquery('english', term)ILIKE 仅做 NULL tsvector 记录兜底
  • 中文搜索自动检测 → 匹配 GlobalTag.name_zh → 注入 tag_ids 走标签递归检索(search_engine.py 中文检测逻辑)
  • 600 万记录预期 < 100ms

8.2 未来阶段:Elasticsearch(可选,按需启用)

配置文件中有 ES 连接,但 ES_URL 为空时自动回退 PG

es_enabled ──True──→ ES search_service (search_service.py)
     ↓ False
   PG AdvancedSearchEngine (fallback)

设计要点:

  • search_service.py 参数与 AdvancedSearchEngine.search() 对齐,输出格式一致
  • 管道写入 DB 后异步同步到 ES,索引失败只 log 不阻塞
  • docker-compose 已有 ES 8.11.0 容器配置,但默认不开启(端口关闭 + healthcheck start_period 30s

切换时机:

  • 搜索延迟 > 200ms(当前 1662 行 < 5ms3M 行预期 < 100ms
  • 需要模糊搜索、同义词扩展、加权排序等高级功能
  • 运维团队有余力管理 ES 集群(至少 2GB 内存额外开销)