Files
AIclinicalresearch/docs/03-业务模块/RVW-稿件审查系统/04-开发计划/RVW V4.0 期刊SaaS版开发计划.md
HaHafeng 16179e16ca feat(rvw): deliver tenant portal v4 flow and config foundation
Implement RVW V4.0 tenant-aware backend/frontend flow with tenant routing, config APIs, and full portal UX updates. Sync system/RVW/deployment docs to capture verified upload-review-report workflow and next-step admin configuration work.

Made-with: Cursor
2026-03-14 22:29:40 +08:00

46 KiB
Raw Blame History

RVW V4.0 智能审稿 SaaS 开发计划

文档版本: v1.1
制定日期: 2026-03-14
最后更新: 2026-03-14架构审查报告修正 × 6
产品版本: RVW V4.0 (Journal AI Review SaaS)
当前基线: RVW V3.0.2(方法学分治并行 + 20检查点 + 展示收敛)
目标状态: 多租户期刊 SaaS 平台支持路径路由隔离、SSO统一账号、ADMIN配置中枢、Handlebars报告渲染、退修信 SSE 流式生成

⚠️ v1.1 架构前提变更(来自架构审查报告):
MVP 阶段放弃子域名方案(个人证书不支持泛域名),改用 URL 路径隔离review.xunzhengyixue.com/jtim。10 本期刊以内路径方案完全够用,超过 10 本后再迁移子域名架构。


一、升级背景与核心目标

1.1 为什么要升级

当前 V3.0.2 是面向"内部临床研究人员"的单租户审稿工具,已具备强大的 4 维审查能力(稿约规范性 / 方法学 / 数据验证 / 临床评估)。

痛点:

  1. 品牌混杂不同期刊编辑JTIM、CMJ等登录的是同一套通用界面无法体现期刊个性
  2. 标准无弹性:每个期刊有自己独特的稿约要求,但系统只有一套固定规则
  3. 缺乏完整工作流:没有"人工复核确认 → 勾选缺陷 → 导出退修信"的编辑侧工作闭环

1.2 核心目标PRD 定义的 4 个目标)

目标 说明
多租户隔离 每个期刊作为独立租户,拥有独立子域名(jtim.review.xunzhengyixue.com)、独立品牌登录页、数据硬隔离
完整期刊工作流 作者投递预审 → 责编AI审查 → 人工复核勾选 → 导出内部报告/退修信
ADMIN 配置中枢 不另建后台,直接扩展现有 ADMIN 端的 TenantDetailPage实现"千刊千面"配置
SSO 统一通行证 底层复用 platform_schema.users,同一账号可在主站与各期刊子域名间无缝漫游

1.3 MVP 策略

Managed SaaS代运营/配置)模式:复杂配置由内部运营团队通过 ADMIN 端完成,期刊客户只需"开箱即用"业务端。这意味着 Phase 1-2 是内部工具建设Phase 3 才是对外交付。


二、整体架构变更一览

V3.0.2 现状 (单租户)                V4.0 目标 (多租户 SaaS) — 路径隔离方案
────────────────────               ──────────────────────────────────────
单一登录页                         review.xunzhengyixue.com/jtim  (JTIM 专属)
   ↓                              review.xunzhengyixue.com/cmj   (CMJ 专属)
单一 ReviewTask 表                 review.xunzhengyixue.com/...   (更多期刊)
   ↓                                           ↓
固定 Skills 配置                   前端路由提取 :tenantId → x-tenant-id Header
   ↓                                           ↓
固定 Prompt 内容                   JWT Payload 注入 tenantId + 中间件双重校验
   ↓                                           ↓
固定 Word 导出格式                 tenant_rvw_configs 表 (per-tenant 配置)
                                               ↓
                                  SkillExecutor 按租户动态装配
                                   ├── 静态系统协议 (RVW_xxx_SYSTEM_BASE)
                                   └── 动态业务层 (tenant_rvw_configs)
                                               ↓
                                  Handlebars 渲染 expert_report_markdown
                                  ADMIN 端支持模板实时预览)
                                               ↓
                                  双轨导出: 内部审稿报告 + 作者退修信 (SSE 流式)

三、三阶段里程碑

Phase 1 (1周)        Phase 2 (1.5周)         Phase 3 (2周)
多租户数据基础         ADMIN配置中枢            期刊业务端重构
──────────────        ─────────────────        ──────────────────────
DB Schema 扩展  →     TenantDetailPage扩展 →   动态品牌登录页
JWT 注入 tenantId     RVW Config CRUD API      稿件管理池 Dashboard
子域名路由检测         Skills 引擎按租户装配    沉浸式 4-Tab 审稿详情
CORS 动态白名单        Handlebars 渲染引擎      人工复核 (HitL) 确认
DevOps SSL证书        Zod Schema 校验          退修信一键生成
                      Prompt 动静分离落地       双轨导出 (报告+退修信)

总工期预估4~4.5 周


四、Phase 1多租户数据隔离与账号体系预计 1 周)

4.1 数据库改造

⚠️ 【审查报告修正 P0】历史数据迁移两步走(来自架构审查报告第 1 条):
生产环境中已存在大量 V3.0 历史审稿任务。直接添加 tenantId 非空外键约束Prisma Migrate 会因历史数据为空而报错中断。
必须严格按以下两步执行:

  1. 先在 platform_schema.tenants 中初始化"默认临床主站租户"(如 tenant-default-yanjiu
  2. 在 Prisma migration SQL 中,先 ALTER TABLE ADD COLUMN tenant_id TEXT NULL,再 UPDATE review_tasks SET tenant_id = 'tenant-default-yanjiu' WHERE tenant_id IS NULL,最后 ALTER TABLE ALTER COLUMN tenant_id SET NOT NULL

任务 1.1:新增 tenant_rvw_configs 扩展表

-- Prisma Schema 新增
model TenantRvwConfig {
  id                          String   @id @default(uuid())
  tenantId                    String   @unique
  tenant                      Tenant   @relation(fields: [tenantId], references: [id])
  
  -- 稿约规范评估配置
  editorialRules              Json?    -- JSONB: 规则数组 + fatal 标记
  
  -- 方法学评估配置
  methodologyExpertPrompt     String?  -- Text: 专家的业务评判标准
  methodologyHandlebarsTemplate String? -- Text: 报告渲染模板
  
  -- 数据验证配置
  dataForensicsLevel          String   @default("L2") -- L1/L2/L3
  
  -- 临床评估配置
  finerWeights                Json?    -- JSONB: {feasibility, innovation, ethics, relevance, novelty}
  clinicalExpertPrompt        String?  -- Text: 临床评估补充要求
  
  createdAt                   DateTime @default(now())
  updatedAt                   DateTime @updatedAt
  
  @@schema("platform_schema")
}

执行方式: prisma migrate dev --name add_tenant_rvw_configs

任务 1.2review_tasks 表添加 tenantId 字段

当前 review_tasks 表未关联租户。需要:

ALTER TABLE rvw_schema.review_tasks ADD COLUMN tenant_id TEXT REFERENCES platform_schema.tenants(id);
CREATE INDEX idx_review_tasks_tenant_id ON rvw_schema.review_tasks(tenant_id);

执行方式:prisma migrate dev --name add_tenant_id_to_review_tasks


4.2 SSO 鉴权体系升级

任务 1.3JWT 身份信息(⚠️ 架构修正JWT 不含 tenantId

⚠️ 【Sprint 审查修正 #1】JWT 中不应写入 tenantId
若 JWT 中写死 tenantId: 'jtim',由于主站与期刊门户共用同一个 localStorage,用户在新标签页打开 review.../cmj 时,系统会携带含 jtim 的 Token造成跨租户漫游验证失败。

正确的 SSO 统一通行证架构:

  • JWT Payload 中仅包含 userId(个人身份,不含业务 tenantId
  • 中间件(任务 1.4)完全通过 x-tenant-id Header 获取租户上下文
  • 后端实时查询 tenant_members 表核验 userId 是否属于该租户
  • 优势:同一个 Token 可以在多期刊间无缝漫游,零冲突

修改 backend/src/modules/auth/auth.service.ts

  • JWT Payload 格式:{ userId, role, iat, exp }(移除 tenantId 字段)
  • 登录接口本身不感知 tenantId登录是个人行为与租户无关

任务 1.4:中间件双重校验防线

修改 backend/src/modules/auth/auth.middleware.ts

⚠️ 【审查报告修正 P1-2】URL Slug → tenantId 映射
前端传来的 x-tenant-id: jtimtenants.code 字段(业务缩写),而 Prisma 查询需要 tenants.id
查阅现有 Schema 确认:tenants 表已有 code String @unique 字段,id 为手动维护的字符串主键。

中间件处理逻辑(必须严格遵循):

  1. 从 Header 中提取 tenantSlug = request.headers['x-tenant-id'](即 code 值,如 'jtim'
  2. 查询 tenant = await prisma.tenants.findUnique({ where: { code: tenantSlug } })
    用 Redis 或内存 LRU 缓存此查询TTL 5 分钟,避免每次请求都打 DB
  3. 若查不到 → 返回 404租户不存在
  4. tenant.id 挂载到 request.tenantId,将完整 tenant 对象挂载到 request.tenant
  5. 跨域校验:校验该 userId 是否为 tenant_members 表中该租户的成员
    若不是 → 拒绝 403 Forbidden内部运营人员持 ops:user-ops 权限除外)

任务 1.5Prisma 全局查询守护

在 RVW 模块所有 Prisma 查询中强制附带 where: { tenantId: request.tenantId },杜绝"裸查"。


4.3 前端路径路由(已调整为路径隔离方案)

⚠️ 【审查报告修正 P2】tenantId 提取方式变更(来自架构审查报告第 6 条):
路径方案下,后端无法从 HostOrigin 隐式推断租户,必须由前端显式携带 x-tenant-id Header。

任务 1.6:前端 Tenant 识别逻辑

修改 frontend-v2/src/App.tsx(或路由初始化):

// MVP 路径隔离方案review.xunzhengyixue.com/:tenantSlug
const pathname = window.location.pathname;
const match = pathname.match(/^\/([a-zA-Z0-9-]+)/);
const tenantSlug = (match && match[1] !== 'api') ? match[1] : 'default';
// 存入 Zustand storetenantSlug 字段)
useTenantStore.setState({ tenantSlug });

⚠️ 【审查报告修正 P2-3】严禁使用 axiosInstance.defaults.headers 全局赋值
SPA 中 Axios 实例是全局单例,用 defaults.headers 写死后,用户从 /jtim 返回主站 / 或切换至 /cmj 时,旧 Header 不会自动清除,导致"在主站发出了携带 JTIM Header 的请求"状态污染。
必须使用请求拦截器动态读取:

// 在 axiosInstance 初始化时注册一次,此后永远动态读取当前租户
axiosInstance.interceptors.request.use(config => {
  const { tenantSlug } = useTenantStore.getState();
  if (tenantSlug && tenantSlug !== 'default') {
    config.headers['x-tenant-id'] = tenantSlug;
  }
  return config;
});

任务 1.6bZustand 租户 Store + 路由观察者 + RouteGuard 修正( 已完成)

解决的三个问题(对应 Sprint 审查 #3 React 状态陷阱):

  1. SPA 内部跳转(<Link> 导航)不刷新页面,一次性初始化代码不重新执行
  2. Axios defaults.headers 全局写死导致租户状态污染
  3. RouteGuard 将主平台业务路径(/rvw/ai-qa)误判为租户 slug

新增文件:

frontend-v2/src/framework/tenant/useTenantStore.ts — Zustand 全局租户 store

export const useTenantStore = create<TenantState>((set) => ({
  tenantSlug: null,
  setTenantSlug: (slug) => set({ tenantSlug: slug }),
}))

frontend-v2/src/framework/tenant/useTenantObserver.ts — 路由感知 hook使用 useLocation()

// RESERVED_PATHS = 静态保留词 + MODULES 中所有注册路径首段(自动维护)
export function useTenantObserver() {
  const location = useLocation()
  const setTenantSlug = useTenantStore(s => s.setTenantSlug)
  useEffect(() => {
    setTenantSlug(extractTenantSlug(location.pathname))
  }, [location.pathname, setTenantSlug])
}

修改文件 App.tsx

  • <BrowserRouter> 内挂载 <TenantBootstrap />(调用 useTenantObserver()
  • Axios 拦截器改为实时读取 store防止状态污染
apiClient.interceptors.request.use((config) => {
  const { tenantSlug } = useTenantStore.getState()
  if (tenantSlug) config.headers['x-tenant-id'] = tenantSlug
  return config
})

修改文件 RouteGuard.tsx

  • 复用 extractTenantSlug(自动排除所有注册模块路径,防止 /rvw 误判)
  • 未登录跳转对齐 App.tsx 现有路由 /t/:tenantCode/login
// /jtim/dashboard 未登录 → /t/jtim/login?redirect=%2Fjtim%2Fdashboard
// /rvw/tasks 未登录      → /login行为不变
function buildLoginRedirect(pathname: string): string {
  const tenantSlug = extractTenantSlug(pathname)
  if (!tenantSlug) return '/login'
  return `/t/${tenantSlug}/login?redirect=${encodeURIComponent(pathname)}`
}

任务 1.6b:路由守卫租户感知重定向( 已完成)

问题根因RouteGuard.tsx 原来写死 <Navigate to="/login" />,访问 /jtim/* 未登录时会跳到 /login,丢失 jtim 标识,登录页无法加载期刊品牌 UI。

已修改 frontend-v2/src/framework/router/RouteGuard.tsx

// 新增两个辅助函数:

/** 不属于期刊租户的保留路径前缀 */
const RESERVED_PATHS = new Set(['login', 'admin', 'org', 'api', ''])

/** 提取第一段路径为 tenantSlug保留字返回 null */
function extractTenantSlug(pathname: string): string | null {
  const firstSegment = pathname.split('/').filter(Boolean)[0] ?? ''
  return RESERVED_PATHS.has(firstSegment) ? null : firstSegment
}

/** 构造携带租户标识的登录跳转目标 */
function buildLoginRedirect(pathname: string): string {
  const tenantSlug = extractTenantSlug(pathname)
  if (!tenantSlug) return '/login'  // 主站保持原有行为
  const redirectParam = encodeURIComponent(pathname)
  return `/login?tenant=${tenantSlug}&redirect=${redirectParam}`
}

// 未登录跳转(原来):<Navigate to="/login" ... />
// 未登录跳转(现在):<Navigate to="/login?tenant=jtim&redirect=%2Fjtim%2Fdashboard" ... />

效果

  • 访问 /jtim/dashboard 未登录 → 跳转 /login?tenant=jtim&redirect=%2Fjtim%2Fdashboard
  • 访问 /login/admin 等主站路径 → 仍跳转 /login(行为不变)
  • 登录页可通过 tenant query param 加载 JTIM 品牌 UI登录后可通过 redirect param 跳回原页面

任务 1.7:模块动态过滤

利用现有 moduleRegistry.ts:若当前 tenantId 为期刊租户,隐藏 AIA/DC/PKB 等非 RVW 模块入口。


4.4 DevOps 配置(路径方案简化版)

⚠️ 【审查报告修正 P1】前端打包 base 路径 + Nginx SPA 回退(来自架构审查报告第 3 条):
路径隔离方案下,若前端打包使用相对路径(./),访问 /jtim 时浏览器会错误请求 /jtim/assets/...,导致白屏。
且用户在 /jtim/dashboard 直接刷新时Nginx 找不到物理目录会报 404。
修正: Vite vite.config.ts 中设置 base: '/'Nginx 必须配置 try_files $uri $uri/ /index.html;

任务 1.8Nginx + Vite 配置(运维 + 前端)

内容 负责人
DNS 无需泛解析,沿用现有 review.xunzhengyixue.com A 记录即可 运维
SSL 证书 沿用现有 *.xunzhengyixue.com 证书,无需额外申购 运维
Nginx 配置 确保 SPA 路由回退:try_files $uri $uri/ /index.html; 运维
Vite 打包 vite.config.tsbase: '/'(绝对根路径,不可用相对路径) 前端
CORS 白名单 后端 Fastify CORS 配置中增加 review.xunzhengyixue.com 后端

4.5 Phase 1 验收标准

  • tenant_rvw_configs 表成功创建并迁移
  • 历史 review_taskstenant_id 已批量刷新为默认租户 ID无数据丢失
  • 访问 review.xunzhengyixue.com/jtim 可正常加载前端(不白屏、不 404
  • /jtim/dashboard 直接刷新页面正常加载Nginx SPA 回退生效)
  • 使用主站账号登录JWT 中包含 tenantId: 'jtim'Axios 自动携带 x-tenant-id Header
  • 用 JTIM 账号请求 CMJ 数据,后端返回 403
  • 前端侧边栏在期刊租户下隐藏非 RVW 模块

五、Phase 2ADMIN 运营端配置中枢扩展(预计 1.5 周)

5.1 后端 API 新增

任务 2.1RVW Config CRUD 接口

新文件:backend/src/modules/admin/rvw-config/

  • rvwConfigController.ts
  • rvwConfigService.ts
  • rvwConfigRoutes.ts

接口规范:

GET    /api/admin/tenants/:id/rvw-config     获取租户审稿配置
PUT    /api/admin/tenants/:id/rvw-config     更新UPSERT租户审稿配置

权限:复用现有 ops:user-ops 中间件,仅内部运营人员可访问。


5.2 前端 ADMIN 扩展

任务 2.2TenantDetailPage 新增"智能审稿配置" Tab

修改 frontend-v2/src/modules/admin/pages/TenantDetailPage.tsx

Tab 内包含 4 个子配置面板:

Panel A — 稿约规范评估配置

  • 规则列表(可增删改):每条规则含 code / description / fatal(是否一票否决)
  • 示例:摘要字数 > 250 → fatal ✓

Panel B — 方法学评估配置

  • 文本区业务评判标准方法学专家语言可自由编辑不需懂JSON
  • 文本区Handlebars 报告展示模板(提供默认模板,可覆盖)
  • 变量说明:{{system_metrics.overall_score}}, {{#each system_metrics.checkpoints}}
  • ⚠️ 【审查报告修正 P1】必须增加"测试渲染"预览(来自架构审查报告第 5 条):
    若无预览功能,运营人员写错 Handlebars 语法后需上传真实 PDF 等待 5 分钟才能发现报错,试错成本极高。
    复用 ADMIN 现有的"测试渲染 (Test Render)"能力:提供一份 Mock 好的 system_metrics JSON 假数据,点击"预览"按钮即可在右侧即时看到渲染效果,语法正确后再保存。

Panel C — 数据验证配置

  • 下拉选择验证深度:L1 算术验证 / L2 统计验证 / L3 双通道核查

Panel D — 临床评估配置

  • 5 个 FINER 权重输入框(总和校验 100%
  • 文本区:专科特色补充要求

5.3 引擎改造(核心)

任务 2.3SkillExecutor 按租户动态装配

修改 backend/src/modules/rvw/skills/core/executor.ts(或 reviewWorker.ts

  • 在执行每个 Skill 前,通过 tenantId 查询 TenantRvwConfig
  • 将租户配置注入到 Skill 的执行上下文中

任务 2.4Hybrid Prompt 拼装机制

修改各 Skill 的 Prompt 获取逻辑:

最终发送给 LLM 的 Prompt = 
  [静态系统协议层]           ← promptService.get('RVW_METHODOLOGY_SYSTEM_BASE')
  + [换行分隔]
  + [动态业务层]             ← tenantRvwConfig.methodologyExpertPrompt
  
其中 静态系统协议层 强制包含:
  - 必须以纯 JSON 输出json_object 模式)
  - system_metrics 结构(含 20 项 checkpoints
  - expert_report_markdown 字段由LLM按专家要求自由撰写

任务 2.5LLM 调用升级 — Structured Outputs 强约束

修改 backend/src/common/llm/LLMFactory.ts(或各 Skill 的 LLM 调用):

  • 调用 DeepSeek-V3 / GPT-4o 时,传入 response_format: { type: "json_object" }
  • 彻底摒弃仅靠 Prompt 文字要求 JSON 的"软性约束"

任务 2.6Handlebars 渲染引擎

新文件:backend/src/modules/rvw/services/reportRenderer.ts

import Handlebars from 'handlebars';
import Zod from 'zod';

// Zod 校验 LLM 结构化输出
const methodologyOutputSchema = z.object({
  system_metrics: z.object({
    overall_score: z.number().min(0).max(100),
    conclusion: z.enum(['直接接收', '小修', '大修', '拒稿']),
    checkpoints: z.array(z.object({
      id: z.number(),
      item: z.string(),
      status: z.enum(['pass', 'minor_issue', 'major_issue', 'not_mentioned']),
      finding: z.string(),
      suggestion: z.string().optional()
    })).length(20)
  }),
  expert_report_markdown: z.string()
});

// Handlebars 渲染(使用租户模板或默认模板)
export function renderMethodologyReport(
  data: MethodologyLLMOutput,
  template?: string
): string {
  const safeData = methodologyOutputSchema.parse(data); // Zod 兜底
  const compiledTemplate = Handlebars.compile(template ?? DEFAULT_METHODOLOGY_TEMPLATE);
  return compiledTemplate(safeData);
}

任务 2.7:默认 Handlebars 模板(硬编码 + 可覆盖)

为 4 个 Skill 各提供一套默认 Handlebars 模板(内嵌在代码中),租户可在 ADMIN 配置页覆盖:

{{! 默认方法学报告模板 }}
# 方法学评估报告

**综合评分:** {{system_metrics.overall_score}} **审稿结论:** {{system_metrics.conclusion}}

## 详细审查意见

{{expert_report_markdown}}

## 20 项检查点覆盖情况

{{#each system_metrics.checkpoints}}
- [{{#if (eq status "pass")}}{{else if (eq status "minor_issue")}}🟡{{else if (eq status "major_issue")}}🔴{{else}}{{/if}}] **{{id}}. {{item}}**{{finding}}
  {{#if suggestion}}> 💡 建议:{{suggestion}}{{/if}}
{{/each}}

5.4 Phase 2 验收标准

  • 内部运营人员在 TenantDetailPage 的"智能审稿配置"Tab 中,能为 JTIM 保存独立的方法学 Prompt 和 Handlebars 模板
  • 运行 JTIM 租户的审稿任务,后端日志显示 Prompt 为"静态协议 + JTIM 业务层"拼接版本
  • LLM 返回 system_metrics + expert_report_markdown 双字段的 JSON不崩溃
  • Zod 校验可自动补全缺失的 checkpoints 字段(填入 not_mentioned 默认值)
  • Handlebars 渲染出最终报告文本,内容与 JTIM 模板格式一致

六、Phase 3面向外部的期刊业务端重构预计 2 周)

6.1 动态品牌登录页

任务 3.1:公开租户信息接口

新增接口(无需鉴权):

GET /api/v1/tenants/public-info/:tenantSlug
Response: { logoUrl, brandColor, brandName, backgroundImageUrl }

任务 3.2:动态品牌登录页渲染

前置条件:任务 1.6b 已完成,未登录跳转现在携带 ?tenant=jtim&redirect=/jtim/dashboard

修改现有 frontend-v2/src/pages/LoginPage.tsx(而非新建页面,避免重复):

// 在 LoginPage 初始化时读取 tenant query param
const [searchParams] = useSearchParams()
const tenantSlug = searchParams.get('tenant')         // e.g., 'jtim'
const redirectPath = searchParams.get('redirect')     // e.g., '/jtim/dashboard'

// 若 tenantSlug 非空,拉取公开配置,渲染期刊品牌
useEffect(() => {
  if (tenantSlug) {
    fetch(`/api/v1/tenants/public-info/${tenantSlug}`)
      .then(r => r.json())
      .then(config => setBrandConfig(config))
  }
}, [tenantSlug])

// 登录成功后
onLoginSuccess(() => {
  navigate(redirectPath ?? '/')
})

渲染逻辑

  • tenantSlug 有值 → 显示期刊专属品牌 UILogo、品牌色、背景图
  • tenantSlug 为空 → 显示主站默认 UI行为不变向后兼容

6.2 稿件管理池Dashboard 重构)

任务 3.3:稿件列表宽表升级

修改 frontend-v2/src/modules/rvw/pages/Dashboard.tsx

参考 AI审稿V1.html 原型设计:

  • 顶部统计卡片:待预审 / AI 审查中 / 需人工复核 / 今日已退修
  • 稿件宽表:稿件编号 / 标题 / 通讯作者 / 综合评分(环形) / 4 维状态图标 / 状态标签 / 操作按钮
  • 4 维状态图标: pass / ⚠️ warn / error / pending

6.3 沉浸式审稿详情页(核心)

任务 3.4:分屏布局重构

修改 frontend-v2/src/modules/rvw/components/TaskDetail.tsx

参考原型,重构为两栏:

  • 左侧 3/5稿件原文预览PDF/Word 渲染或纯文本),带浮动工具栏
  • 右侧 2/54-Tab 审查工作台(稿约规范 / 方法学 / 数据验证 / 临床评估)

任务 3.5Human-in-the-Loop 问题确认

修改各 Report 组件(EditorialReportMethodologyReport等):

每个问题卡片底部增加复核操作:

[✓ 采纳此问题] [✗ 误报/忽略]
  • 状态持久化到后端(新增 review_task_confirmations 字段或表)
  • 只有"已采纳"的问题才会进入退修信

任务 3.6:主报告渲染升级

修改 MethodologyReport.tsx 等:

  • 优先展示 expert_report_markdownHandlebars 渲染后的纯文本/Markdown
  • 不再依赖组件内部"拼接 JSON 字段展示"的旧逻辑

6.4 用户-租户绑定 SOP无需新开发但必须列入上线 Checklist

⚠️ 【审查报告修正 P1】业务闭环缺失的关键补丁(来自架构审查报告第 4 条):
系统已有 platform_schema.tenant_members 表和用户管理 API但计划中未说明"谁把责编拉进特定期刊租户"。
此项无需新开发,但必须写入交付 SOP
上线 Checklist必须完成

  1. 内部超管通过 ADMIN 端的【用户管理 → 租户成员管理】,将期刊责编账号添加至对应租户(如 JTIM
  2. 赋予该用户 RVW 模块权限
  3. 验证责编登录 review.xunzhengyixue.com/jtim 后能看到该期刊的稿件列表(不为空、不报 403

6.5 退修信生成(重点新功能)

任务 3.7退修信生成后端接口SSE 流式方案)

⚠️ 【审查报告修正 P0】退修信生成必须使用 SSE 流式,不可同步阻塞(来自架构审查报告第 2 条):
退修信涉及 LLM 生成长文本,耗时极易超过 SAE 网关 60 秒限制。
系统已有 SSE 基础设施ASL Deep Research V2.0 已验证),直接复用。

⚙️ 【Sprint 审查修正 #2】运维必须调整 ALB 空闲超时时间
阿里云 ALB 默认"连接空闲超时时间"为 15 秒。LLM 在生成退修信时若有思考停顿,超过 15 秒未推流ALB 会强制切断 TCP 连接,前端报 SSE 中断。
运维执行项:进入 ALB 控制台 → 监听443 端口)→ 高级配置 → 将"连接空闲超时时间"调大至 300 秒

接口设计(两步):

# 第一步SSE 流式生成(打字机效果)
GET  /api/v2/rvw/tasks/:id/revision-letter/stream
     Body via query: confirmedIssueIds=xxx,yyy
     Response: text/event-stream实时推送 letterContent 增量文本

# 第二步:生成完毕后触发 Word 导出(短耗时,同步可接受)
POST /api/v2/rvw/tasks/:id/revision-letter/export
     Body: { letterContent: string }
     Response: { wordFileUrl: string }

逻辑:

  1. 获取已确认的问题列表(按维度分组)
  2. 调用 LLM以 stream 模式生成退修信,通过 SSE 推流到前端
  3. 前端生成完毕后,用户确认内容,点击"导出 Word"触发第二步接口

任务 3.8前端退修信弹窗SSE 打字机效果)

⚠️ 【审查报告修正 P0-1】严禁使用原生 EventSource
浏览器原生 EventSource 规范层面不支持设置任何自定义 HTTP Header无法携带 Authorization: Bearer xxx,也无法携带 x-tenant-id)。直接使用会导致后端 401 / 403。
必须使用 @microsoft/fetch-event-source 或手写 Fetch ReadableStream

import { fetchEventSource } from '@microsoft/fetch-event-source';

// 安装npm install @microsoft/fetch-event-source
await fetchEventSource(`/api/v2/rvw/tasks/${taskId}/revision-letter/stream`, {
  method: 'GET',
  headers: {
    'Authorization': `Bearer ${token}`,
    'x-tenant-id': tenantSlug,
  },
  onmessage(event) {
    setLetterContent(prev => prev + event.data); // 打字机效果
  },
  onerror(err) { /* 错误处理 */ }
});

新建 RevisionLetterModal.tsx

  • 展示已采纳问题汇总(可再次勾选/取消)
  • "生成退修信"按钮
  • 打字机式实时渲染:使用 @microsoft/fetch-event-source,字符逐步出现
  • 生成完毕后显示"导出 Word"按钮(调用第二步同步接口)

6.5 双轨导出升级

任务 3.9:内部审稿报告导出

升级现有 Word 导出功能:

  • 使用 Handlebars 渲染的 expert_report_markdown 替代旧的 JSON 拼接展示
  • 保留 4 个维度的分章节结构

任务 3.10:作者退修信导出

退修信 Word 文档格式:

  • 标题:Revision Required - [稿件标题]
  • 信头:期刊名、日期
  • 正文:按维度分组的问题与建议(敬语体)
  • 结尾:编辑署名

6.6 Phase 3 验收标准

  • 访问 review.xunzhengyixue.com/jtim 显示 JTIM 品牌登录页Logo/品牌色正确)
  • 登录后进入 JTIM 专属工作台,左侧导航只显示 RVW 相关功能
  • 上传稿件后,在 Dashboard 宽表中可看到 4 维状态图标实时更新
  • 审稿完成后,点击"人工复核"进入分屏详情页
  • 在每个问题卡片上点击"采纳"/"忽略",刷新后状态保持
  • 方法学报告优先展示 Handlebars 渲染的 expert_report_markdown(格式与 ADMIN 配置模板一致)
  • 点击"生成退修信"退修信以打字机效果实时渲染SSE 流式),生成完毕后可导出 Word
  • 责编账号必须已通过 ADMIN 端绑定至 JTIM 租户,否则访问时正确提示 403

七、关键技术风险与防范

风险 1JSON 转义风暴(最高优先级)

风险LLM 在 expert_report_markdown 字段中输出含换行符、引号的长文本时JSON.parse 崩溃。

防范方案:

  1. API 层强约束:所有 LLM 调用启用 response_format: { type: "json_object" }
  2. 引入 jsonrepair:安装 jsonrepair 包,替换直接使用 JSON.parse 的地方
  3. Partial 容灾:继续保持 Promise.allSettled + partial_completed 机制

风险 2Handlebars 模板变量错位

风险:运营人员配置的模板引用了 LLM 没有输出的字段,渲染报错。

防范方案:

  1. Zod 校验先行:在 Handlebars 渲染前,用 Zod Schema 对 LLM 输出进行校验并填充默认值
  2. 防御性模板规范:模板中大量使用 {{#if field}}...{{else}}N/A{{/if}},并在 ADMIN 端提示

风险 3方法学超时已有缓解措施

风险20 检查点 + 长文本生成,超过 8 分钟超时。

防范方案V3.0.2 已实施 + V4.0 增强):

  1. 分治并行A/B/C 三段)已实施
  2. V4.0 增强expert_report_markdown 字段让 LLM 汇总输出一份完整报告,减少结构化字段填充压力
  3. 快速模式开关(RVW_METHODOLOGY_FAST_MODE)规划中

风险 4多租户数据越权安全红线

风险A 期刊编辑通过修改参数访问 B 期刊稿件。

防范方案:

  1. JWT Payload 中 tenantId 不可伪造(服务端签发)
  2. auth.middleware.ts 中双重校验userId 有效性 + tenantId 归属)
  3. 所有 RVW Prisma 查询强制附带 where: { tenantId: request.tenantId }
  4. 开发规范:禁止跳过 tenantId 校验的裸查

八、各阶段任务清单(按人员分工)

后端工程师

Phase 任务编号 任务描述 工期
Phase 1 1.1 新增 tenant_rvw_configs + Prisma 迁移 0.5d
Phase 1 1.2 review_tasks 添加 tenantId + Prisma 迁移 0.5d
Phase 1 1.3 JWT 注入 tenantId 1d
Phase 1 1.4 中间件双重校验 1d
Phase 1 1.5 Prisma 查询全局守护 1d
Phase 2 2.1 RVW Config CRUD API 1d
Phase 2 2.3 SkillExecutor 按租户动态装配 1d
Phase 2 2.4 Hybrid Prompt 拼装机制 1.5d
Phase 2 2.5 LLM Structured Outputs 强约束 0.5d
Phase 2 2.6 Handlebars 渲染引擎 + Zod 校验 1.5d
Phase 2 2.7 4 个 Skill 默认 Handlebars 模板 1d
Phase 3 3.1 公开租户信息接口 0.5d
Phase 3 3.7 退修信生成接口 1.5d
Phase 3 3.9 内部报告导出升级 1d
Phase 3 3.10 退修信 Word 导出 1d

前端工程师

Phase 任务编号 任务描述 工期
Phase 1 1.6 Tenant 识别逻辑 + 全局 Header 注入 1d
Phase 1 1.7 模块动态过滤 0.5d
Phase 2 2.2 TenantDetailPage 新增"智能审稿配置"Tab4个Panel 3d
Phase 3 3.2 动态品牌登录页 1.5d
Phase 3 3.3 Dashboard 稿件宽表升级 2d
Phase 3 3.4 分屏布局审稿详情页 2d
Phase 3 3.5 Human-in-the-Loop 问题确认 1.5d
Phase 3 3.6 主报告展示优先 expert_report_markdown 1d
Phase 3 3.8 退修信生成弹窗 1.5d

运维工程师

Phase 任务编号 任务描述 工期
Phase 1 1.8a 阿里云 DNS 泛解析 *.review 记录 0.5d
Phase 1 1.8b 申购 *.review.xunzhengyixue.com SSL 证书并挂载 1d
Phase 1 1.8c Nginx 配置子域名转发规则 0.5d
Phase 1 1.8d 后端 CORS 动态白名单配置 0.5d

九、开发规范对齐要求

9.1 数据库规范(必须遵守)

对应:docs/04-开发规范/09-数据库开发规范.md 三条铁律

  • 必须 使用 npx prisma migrate dev --name xxx 生成迁移文件,禁止 prisma db push
  • 每次 Prisma Schema 变更后,立即docs/05-部署文档/03-待部署变更清单.md 追加变更记录
  • 本次 V4.0 涉及的迁移文件清单(需逐一追加):
迁移名 内容 Schema
add_tenant_rvw_configs 新增 tenant_rvw_configs platform_schema
add_tenant_id_to_review_tasks 历史数据平滑迁移(两步走) rvw_schema

9.2 OSS 存储规范(必须遵守)

对应:docs/04-开发规范/11-OSS存储开发规范.md

  • 稿件文件上传/读取:必须 通过 common/storage/ 统一接口,禁止前端直连 OSS
  • ☁️ 【Sprint 审查修正 #4】OSS Bucket 必须配置 CORS 跨域规则
    前端通过签名 URL 直连 OSS 下载/预览 PDF 时,浏览器会发起 OPTIONS 预检请求。若 Bucket 未配置 CORS前端直接报 CORS Error 白屏。
    运维执行项:阿里云 OSS 控制台 → 对应 Bucket → 权限管理 → 跨域设置,新增规则:

    • 允许来源:https://review.xunzhengyixue.com
    • 允许方法:GET, PUT, POST, HEAD
    • 允许 Headers*
    • 暴露 HeadersETag, x-oss-request-id
  • 退修信 Word 导出:文件存储 Key 遵循规范路径格式:
rvw/{tenantCode}/{taskId}/manuscripts/{uuid}.docx     # 原稿
rvw/{tenantCode}/{taskId}/reports/{uuid}.docx          # 审稿报告
rvw/{tenantCode}/{taskId}/revision-letters/{uuid}.docx # 退修信
  • 报告/退修信下载链接:必须 使用带过期时间的签名 URLSigned URL禁止公开直链
  • 原始文件名(如 "张三投稿.docx"存数据库OSS Key 使用 UUID

9.3 认证授权规范(必须遵守)

对应:docs/04-开发规范/10-模块认证规范.md

后端路由层(所有新增 API 必须):

// RVW Config API运营管理员才能访问
fastify.put('/api/admin/tenants/:id/rvw-config', {
  preHandler: [authenticate, requirePermission('ops:user-ops')]
}, handler);

// RVW 业务 API普通用户 + 模块权限
fastify.get('/api/v2/rvw/tasks', {
  preHandler: [authenticate, requireModule('RVW')]
}, handler);

前端层

  • 普通业务 API使用 common/api/axios.tsapiClient(已内置 JWT
  • SSE退修信生成使用 @microsoft/fetch-event-source + 手动从 getAccessToken() 获取 Token

Controller 层

// 必须从 request 提取,禁止硬编码
const userId = getUserId(request);
const tenantId = request.tenantId; // 由中间件翻译后挂载

9.4 安全规范(必须遵守)

对应:docs/04-开发规范/12-安全开发规范.md

IDOR 防护P0 级强制):

// ✅ 所有 RVW 查询必须带 tenantId
const task = await prisma.reviewTasks.findFirst({
  where: { id: taskId, tenantId: request.tenantId }
});

// ❌ 禁止裸查
const task = await prisma.reviewTasks.findUnique({ where: { id: taskId } });

公开 API 限流(防爬):

  • GET /api/v1/tenants/public-info/:tenantSlug(无需鉴权的品牌配置接口)必须 加 Rate Limiting建议 30次/分钟/IP

Handlebars 模板注入防护

  • 运营人员配置的 Handlebars 模板在服务端渲染时,必须 使用 Handlebars.create() 独立沙箱实例
  • 禁用危险 helpers{{raw}} 等),防止模板注入攻击
  • 渲染时捕获所有异常,失败降级返回 expert_report_markdown 原始内容

9.5 与 ADMIN 模块融合要点

对应:docs/03-业务模块/ADMIN-运营管理端/00-模块当前状态与开发指南.md

V4.0 需求 ADMIN 现有能力 融合方式
Handlebars 模板编辑 + 预览 Phase 3.5.4 PromptEditorPage 已有 Test Render 在 RVW Config Tab 中复用该组件
方法学业务 Prompt 配置 Phase 3.5.1 PromptService + CodeMirror 编辑器 复用 PromptEditor 组件
用户-租户绑定 Phase 4.1 用户管理 + tenant_members 走已有流程,纳入上线 SOP
期刊品牌定制Logo/主题色) ADMIN P2 待开发:品牌定制配置 + 租户专属登录页 主动对齐V4.0 中的动态登录页实现即为该 P2 项落地,需同步更新 ADMIN 开发状态文档
运营数据统计 Phase 5.0 ActivityService 埋点 新增退修信生成、配置变更等埋点,使用现有 fire-and-forget 模式

十一、不在本次 V4.0 范围内(明确排除)

以下功能在本次 V4.0 中不开发待后续版本V4.1+)规划:

  1. 投稿作者侧门户:作者直接上传投稿、查看审稿意见(本次只做责编侧)
  2. 期刊主编仪表盘:全刊数据统计(本次只做责编工作台)
  3. 外审专家模块:分配外审、专家接收稿件(超出本次范围)
  4. 配置中心自助访问期刊客户自行修改配置MVP 阶段由内部运营代劳)
  5. 微信登录认证网关方案规划中MVP 阶段账密登录
  6. PDF 渲染左栏Phase 3 的左侧稿件预览可先用纯文本展示,真正的 PDF 渲染后续迭代

十、全局技术依赖与选型

10.1 新增依赖(确需安装)

用途 安装命令
@microsoft/fetch-event-source 支持自定义 Header 的 SSE 客户端 npm install @microsoft/fetch-event-source(前端)
jsonrepair LLM JSON 容错解析 npm install jsonrepair(后端)
zod LLM 输出 Schema 校验 npm install zod(后端,若未安装)

10.2 复用现有能力(禁止重复造轮子)

⚠️ 以下能力系统中已存在,直接调用,严禁重新实现

能力 现有位置 RVW V4.0 使用场景
SSE 流式响应 backend/src/common/streaming/ createStreamingService 退修信生成后端推流(替代自建 SSE
缓存服务 backend/src/common/cache/ 租户 slug → UUID 查询缓存TTL 5min
Prompt 管理 + Handlebars backend/src/common/prompt/ PromptService Handlebars 报告模板渲染PromptService 已支持,无需新建 reportRenderer.ts
LLM 网关 backend/src/common/llm/LLMFactory 所有 LLM 调用
认证中间件 backend/src/common/auth/auth.middleware.ts authenticate 所有新增 RVW API 路由
模块中间件 backend/src/common/auth/auth.middleware.ts requireModule RVW Config API 必须加 requireModule('RVW')
Axios 实例(含认证) frontend-v2/src/common/api/axios.ts 所有前端 API 调用(含自动 JWT 注入)
Token 获取 frontend-v2/src/framework/auth/api.ts getAccessToken() SSE 手动鉴权(@microsoft/fetch-event-source 中使用)
运营埋点 backend/src/common/logging/ActivityService 退修信生成、配置变更等新行为需埋点
存储服务 backend/src/common/storage/ 稿件上传、报告/退修信 Word 导出存储
Prompt 测试渲染 ADMIN PromptEditorPage 已有 Test Render 功能Phase 3.5.4 Handlebars 模板预览直接复用
租户详情页 frontend-v2/src/pages/admin/tenants/TenantDetailPage.tsx 新增"智能审稿配置"Tab在此文件扩展
用户-租户绑定 ADMIN 用户管理Phase 4.1 已完成) 新期刊用户绑定,走已有流程,无需开发
品牌定制能力 ADMIN tenantsconfig JSON 字段 + P2 - 品牌定制 待开发项 动态登录页的 Logo/主题色配置,与 ADMIN 品牌定制对齐,避免冲突

10.3 LLM 调用规范

项目 要求
Structured Output 所有新增 LLM 调用启用 response_format: { type: "json_object" }
通过 LLMFactory 禁止直接 new SDK 客户端
maxTokens 各 Section 分治调用使用 reduced budget2048防超时

十二、文档与设计参考

文档 路径
PRD docs/03-业务模块/RVW-稿件审查系统/00-系统设计/V3.0/AI智能审稿系统(期刊SaaS版)产品需求文档0314.md
UI 原型 docs/03-业务模块/RVW-稿件审查系统/00-系统设计/V3.0/AI审稿V1.html
技术风险指南 docs/03-业务模块/RVW-稿件审查系统/00-系统设计/V3.0/核心技术难点与防范指南.md
域名与多租户指南 docs/03-业务模块/RVW-稿件审查系统/00-系统设计/V3.0/AI智能审稿域名与多租户技术指南.md
架构白皮书 docs/03-业务模块/RVW-稿件审查系统/08-技术架构建议/智能审稿终极稳定架构白皮书.md
当前模块状态 docs/03-业务模块/RVW-稿件审查系统/00-模块当前状态与开发指南.md
输出解耦计划 docs/03-业务模块/RVW-稿件审查系统/04-开发计划/RVW V4.0 智能审稿输出解耦开发计划.md


十三、架构审查修正汇总v1.0 → v1.3

第一轮审查修正v1.0 → v1.1

# 等级 修正内容 影响章节
1 🔴 P0 历史 review_tasks 平滑迁移两步走脚本 第四章 4.1
2 🔴 P0 退修信生成改为 SSE 流式,拆分为两步接口 第六章 6.5
3 🟡 P1 Vite base: '/' + Nginx try_files 明确要求 第四章 4.4
4 🟡 P1 用户-租户绑定列入交付 SOP无需新开发 第六章 6.4
5 🟡 P1 Handlebars 编辑器增加"测试渲染"预览按钮 第五章 5.2
6 🟢 P2 前端携带 x-tenant-id Header + 后端从 Header 提取 第四章 4.3
- 架构前提 域名方案从子域名改为路径隔离 review.xunzhengyixue.com/:tenantId 全文

第二轮审查修正v1.1 → v1.2

# 等级 修正内容 影响章节
7 🔴 P0 SSE 接口严禁使用原生 EventSource,必须用 @microsoft/fetch-event-source 第六章 6.5
8 🟡 P1 URL Slugjtim)→ tenants.code → 中间件翻译为 tenants.id,加 Redis 缓存 第四章 4.2
9 🟢 P2 Axios 全局 Header 改为请求拦截器动态读取,防状态污染 第四章 4.3

第三轮规范对齐补丁v1.2 → v1.3,已完成实现)

# 类型 补丁内容 来源
A 🔄 复用 退修信 SSE 后端用 createStreamingService,不自建 通用能力层:common/streaming/
B 🔄 复用 租户 slug 缓存用 common/cache/ 服务,不自建 Redis 代码 通用能力层:common/cache/
C 🔄 复用 Handlebars 渲染直接扩展 PromptService,不新建 reportRenderer.ts 通用能力层:common/prompt/
D 🔄 复用 前端业务 API 用 common/api/axios.tsSSE 用 getAccessToken() 认证规范 §2
E 🔄 复用 Handlebars 预览复用 ADMIN PromptEditorPage Test Render ADMIN Phase 3.5.4
F 📏 规范 所有新增后端路由加 authenticate + requireModule('RVW') 认证规范 §3
G 📏 规范 所有新增 Prisma 查询必须带 tenantId,防 IDOR 安全规范 §1
H 📏 规范 OSS 路径遵循 rvw/{tenantCode}/{taskId}/xxx/ 格式,报告用签名 URL OSS 规范 §2-3
I 📏 规范 DB 每次迁移更新 docs/05-部署文档/03-待部署变更清单.md 数据库规范铁律
J 📏 规范 公开接口加 Rate LimitingHandlebars 渲染加沙箱防注入 安全规范 §1/§6
K 🤝 融合 动态登录页实现 = ADMIN P2 品牌定制配置 + 租户专属登录页 落地,对齐两端 ADMIN 待开发 P2

第四轮:运行时 Bug 修复v1.3 → v1.4 已完成实现)

# 文件 修复内容
L RouteGuard.tsx 租户感知重定向:未登录时从 URL 提取 tenantSlug,跳转 /t/jtim/login?redirect=/jtim/dashboard,保留期刊上下文;主站路径行为不变

第五轮Sprint 冲刺会架构细节修正v1.4 → v1.5 已完成实现)

# 类型 内容 影响
M 🏗️ 架构修正 JWT 不含 tenantId,后端实时通过 x-tenant-id Header + tenant_members 核验 Task 1.3
N 已实现 useTenantStore + useTenantObserver + TenantBootstrap 全链路,解决 SPA 状态陷阱 Task 1.6b
O 已实现 RouteGuard 动态排除 MODULES 路径,对齐 /t/:tenantCode/login 路由 Task 1.6b
P ⚙️ 运维 ALB 空闲超时从 15s 调至 300sSSE 退修信生成防切断) Task 3.7
Q ⚙️ 运维 OSS Bucket 新增 CORS 规则,允许 review.xunzhengyixue.com Task 9.2

文档版本: v1.5
制定日期: 2026-03-14
最后更新: 2026-03-14第五轮Sprint 冲刺会 4 条细节修正 + useTenantObserver 全链路已实现)
下一步行动: Phase 1 启动 — DB 迁移脚本(含历史数据平滑迁移)+ 运维执行 ALB 超时 + OSS CORS 配置 + @microsoft/fetch-event-source 安装 + /t/:tenantCode/login 路由渲染期刊品牌 UI