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
46 KiB
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 维审查能力(稿约规范性 / 方法学 / 数据验证 / 临床评估)。
痛点:
- 品牌混杂:不同期刊编辑(JTIM、CMJ等)登录的是同一套通用界面,无法体现期刊个性
- 标准无弹性:每个期刊有自己独特的稿约要求,但系统只有一套固定规则
- 缺乏完整工作流:没有"人工复核确认 → 勾选缺陷 → 导出退修信"的编辑侧工作闭环
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 会因历史数据为空而报错中断。
必须严格按以下两步执行:
- 先在
platform_schema.tenants中初始化"默认临床主站租户"(如tenant-default-yanjiu)- 在 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.2:review_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.3:JWT 身份信息(⚠️ 架构修正:JWT 不含 tenantId)
⚠️ 【Sprint 审查修正 #1】JWT 中不应写入 tenantId
若 JWT 中写死tenantId: 'jtim',由于主站与期刊门户共用同一个localStorage,用户在新标签页打开review.../cmj时,系统会携带含jtim的 Token,造成跨租户漫游验证失败。正确的 SSO 统一通行证架构:
- JWT Payload 中仅包含
userId(个人身份,不含业务 tenantId)- 中间件(任务 1.4)完全通过
x-tenant-idHeader 获取租户上下文- 后端实时查询
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: jtim是tenants.code字段(业务缩写),而 Prisma 查询需要tenants.id。
查阅现有 Schema 确认:tenants表已有code String @unique字段,id为手动维护的字符串主键。中间件处理逻辑(必须严格遵循):
- 从 Header 中提取
tenantSlug = request.headers['x-tenant-id'](即code值,如'jtim')- 查询
tenant = await prisma.tenants.findUnique({ where: { code: tenantSlug } })
用 Redis 或内存 LRU 缓存此查询,TTL 5 分钟,避免每次请求都打 DB- 若查不到 → 返回 404(租户不存在)
- 将
tenant.id挂载到request.tenantId,将完整tenant对象挂载到request.tenant- 跨域校验:校验该
userId是否为tenant_members表中该租户的成员
若不是 → 拒绝 403 Forbidden(内部运营人员持ops:user-ops权限除外)
任务 1.5:Prisma 全局查询守护
在 RVW 模块所有 Prisma 查询中强制附带 where: { tenantId: request.tenantId },杜绝"裸查"。
4.3 前端路径路由(已调整为路径隔离方案)
⚠️ 【审查报告修正 P2】tenantId 提取方式变更(来自架构审查报告第 6 条):
路径方案下,后端无法从Host或Origin隐式推断租户,必须由前端显式携带x-tenant-idHeader。
任务 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 store(tenantSlug 字段)
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.6b:Zustand 租户 Store + 路由观察者 + RouteGuard 修正(✅ 已完成)
解决的三个问题(对应 Sprint 审查 #3 React 状态陷阱):
- SPA 内部跳转(
<Link>导航)不刷新页面,一次性初始化代码不重新执行- Axios
defaults.headers全局写死导致租户状态污染- 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(行为不变) - 登录页可通过
tenantquery param 加载 JTIM 品牌 UI,登录后可通过redirectparam 跳回原页面
任务 1.7:模块动态过滤
利用现有 moduleRegistry.ts:若当前 tenantId 为期刊租户,隐藏 AIA/DC/PKB 等非 RVW 模块入口。
4.4 DevOps 配置(路径方案简化版)
⚠️ 【审查报告修正 P1】前端打包 base 路径 + Nginx SPA 回退(来自架构审查报告第 3 条):
路径隔离方案下,若前端打包使用相对路径(./),访问/jtim时浏览器会错误请求/jtim/assets/...,导致白屏。
且用户在/jtim/dashboard直接刷新时,Nginx 找不到物理目录会报 404。
修正: Vitevite.config.ts中设置base: '/';Nginx 必须配置try_files $uri $uri/ /index.html;
任务 1.8:Nginx + Vite 配置(运维 + 前端)
| 项 | 内容 | 负责人 |
|---|---|---|
| DNS | 无需泛解析,沿用现有 review.xunzhengyixue.com A 记录即可 |
运维 |
| SSL 证书 | 沿用现有 *.xunzhengyixue.com 证书,无需额外申购 |
运维 |
| Nginx 配置 | 确保 SPA 路由回退:try_files $uri $uri/ /index.html; |
运维 |
| Vite 打包 | vite.config.ts 中 base: '/'(绝对根路径,不可用相对路径) |
前端 |
| CORS 白名单 | 后端 Fastify CORS 配置中增加 review.xunzhengyixue.com |
后端 |
4.5 Phase 1 验收标准
tenant_rvw_configs表成功创建并迁移- 历史
review_tasks的tenant_id已批量刷新为默认租户 ID,无数据丢失 - 访问
review.xunzhengyixue.com/jtim可正常加载前端(不白屏、不 404) - 在
/jtim/dashboard直接刷新,页面正常加载(Nginx SPA 回退生效) - 使用主站账号登录,JWT 中包含
tenantId: 'jtim',Axios 自动携带x-tenant-idHeader - 用 JTIM 账号请求 CMJ 数据,后端返回 403
- 前端侧边栏在期刊租户下隐藏非 RVW 模块
五、Phase 2:ADMIN 运营端配置中枢扩展(预计 1.5 周)
5.1 后端 API 新增
任务 2.1:RVW Config CRUD 接口
新文件:backend/src/modules/admin/rvw-config/
rvwConfigController.tsrvwConfigService.tsrvwConfigRoutes.ts
接口规范:
GET /api/admin/tenants/:id/rvw-config 获取租户审稿配置
PUT /api/admin/tenants/:id/rvw-config 更新(UPSERT)租户审稿配置
权限:复用现有 ops:user-ops 中间件,仅内部运营人员可访问。
5.2 前端 ADMIN 扩展
任务 2.2:TenantDetailPage 新增"智能审稿配置" 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_metricsJSON 假数据,点击"预览"按钮即可在右侧即时看到渲染效果,语法正确后再保存。
Panel C — 数据验证配置
- 下拉选择验证深度:
L1 算术验证/L2 统计验证/L3 双通道核查
Panel D — 临床评估配置
- 5 个 FINER 权重输入框(总和校验 100%)
- 文本区:专科特色补充要求
5.3 引擎改造(核心)
任务 2.3:SkillExecutor 按租户动态装配
修改 backend/src/modules/rvw/skills/core/executor.ts(或 reviewWorker.ts):
- 在执行每个 Skill 前,通过
tenantId查询TenantRvwConfig - 将租户配置注入到 Skill 的执行上下文中
任务 2.4:Hybrid 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.5:LLM 调用升级 — Structured Outputs 强约束
修改 backend/src/common/llm/LLMFactory.ts(或各 Skill 的 LLM 调用):
- 调用 DeepSeek-V3 / GPT-4o 时,传入
response_format: { type: "json_object" } - 彻底摒弃仅靠 Prompt 文字要求 JSON 的"软性约束"
任务 2.6:Handlebars 渲染引擎
新文件: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有值 → 显示期刊专属品牌 UI(Logo、品牌色、背景图)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/5:4-Tab 审查工作台(稿约规范 / 方法学 / 数据验证 / 临床评估)
任务 3.5:Human-in-the-Loop 问题确认
修改各 Report 组件(EditorialReport、MethodologyReport等):
每个问题卡片底部增加复核操作:
[✓ 采纳此问题] [✗ 误报/忽略]
- 状态持久化到后端(新增
review_task_confirmations字段或表) - 只有"已采纳"的问题才会进入退修信
任务 3.6:主报告渲染升级
修改 MethodologyReport.tsx 等:
- 优先展示
expert_report_markdown(Handlebars 渲染后的纯文本/Markdown) - 不再依赖组件内部"拼接 JSON 字段展示"的旧逻辑
6.4 用户-租户绑定 SOP(无需新开发,但必须列入上线 Checklist)
⚠️ 【审查报告修正 P1】业务闭环缺失的关键补丁(来自架构审查报告第 4 条):
系统已有platform_schema.tenant_members表和用户管理 API,但计划中未说明"谁把责编拉进特定期刊租户"。
此项无需新开发,但必须写入交付 SOP:
上线 Checklist(必须完成):
- 内部超管通过 ADMIN 端的【用户管理 → 租户成员管理】,将期刊责编账号添加至对应租户(如 JTIM)
- 赋予该用户 RVW 模块权限
- 验证责编登录
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 }
逻辑:
- 获取已确认的问题列表(按维度分组)
- 调用 LLM,以 stream 模式生成退修信,通过 SSE 推流到前端
- 前端生成完毕后,用户确认内容,点击"导出 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
七、关键技术风险与防范
风险 1:JSON 转义风暴(最高优先级)
风险:LLM 在 expert_report_markdown 字段中输出含换行符、引号的长文本时,JSON.parse 崩溃。
防范方案:
- API 层强约束:所有 LLM 调用启用
response_format: { type: "json_object" } - 引入 jsonrepair:安装
jsonrepair包,替换直接使用JSON.parse的地方 - Partial 容灾:继续保持
Promise.allSettled+partial_completed机制
风险 2:Handlebars 模板变量错位
风险:运营人员配置的模板引用了 LLM 没有输出的字段,渲染报错。
防范方案:
- Zod 校验先行:在 Handlebars 渲染前,用 Zod Schema 对 LLM 输出进行校验并填充默认值
- 防御性模板规范:模板中大量使用
{{#if field}}...{{else}}N/A{{/if}},并在 ADMIN 端提示
风险 3:方法学超时(已有缓解措施)
风险:20 检查点 + 长文本生成,超过 8 分钟超时。
防范方案(V3.0.2 已实施 + V4.0 增强):
- ✅ 分治并行(A/B/C 三段)已实施
- V4.0 增强:
expert_report_markdown字段让 LLM 汇总输出一份完整报告,减少结构化字段填充压力 - 快速模式开关(
RVW_METHODOLOGY_FAST_MODE)规划中
风险 4:多租户数据越权(安全红线)
风险:A 期刊编辑通过修改参数访问 B 期刊稿件。
防范方案:
- JWT Payload 中 tenantId 不可伪造(服务端签发)
- auth.middleware.ts 中双重校验(userId 有效性 + tenantId 归属)
- 所有 RVW Prisma 查询强制附带
where: { tenantId: request.tenantId } - 开发规范:禁止跳过 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 新增"智能审稿配置"Tab(4个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:
* - 暴露 Headers:
ETag, 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 # 退修信
- 报告/退修信下载链接:必须 使用带过期时间的签名 URL(Signed 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.ts的apiClient(已内置 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+)规划:
- 投稿作者侧门户:作者直接上传投稿、查看审稿意见(本次只做责编侧)
- 期刊主编仪表盘:全刊数据统计(本次只做责编工作台)
- 外审专家模块:分配外审、专家接收稿件(超出本次范围)
- 配置中心自助访问:期刊客户自行修改配置(MVP 阶段由内部运营代劳)
- 微信登录:认证网关方案规划中,MVP 阶段账密登录
- 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 tenants 表 config JSON 字段 + P2 - 品牌定制 待开发项 |
动态登录页的 Logo/主题色配置,与 ADMIN 品牌定制对齐,避免冲突 |
10.3 LLM 调用规范
| 项目 | 要求 |
|---|---|
| Structured Output | 所有新增 LLM 调用启用 response_format: { type: "json_object" } |
通过 LLMFactory |
禁止直接 new SDK 客户端 |
| maxTokens | 各 Section 分治调用使用 reduced budget(2048),防超时 |
十二、文档与设计参考
| 文档 | 路径 |
|---|---|
| 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 Slug(jtim)→ 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.ts,SSE 用 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 Limiting,Handlebars 渲染加沙箱防注入 | 安全规范 §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 调至 300s(SSE 退修信生成防切断) | 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