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

995 lines
46 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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` 扩展表**
```sql
-- 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` 表未关联租户。需要:
```sql
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: jtim` 是 `tenants.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 条):
> 路径方案下,后端无法从 `Host` 或 `Origin` 隐式推断租户,必须由前端显式携带 `x-tenant-id` Header。
**任务 1.6:前端 Tenant 识别逻辑**
修改 `frontend-v2/src/App.tsx`(或路由初始化):
```typescript
// 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 的请求"状态污染。
> **必须使用请求拦截器动态读取:**
```typescript
// 在 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
```typescript
export const useTenantStore = create<TenantState>((set) => ({
tenantSlug: null,
setTenantSlug: (slug) => set({ tenantSlug: slug }),
}))
```
`frontend-v2/src/framework/tenant/useTenantObserver.ts` — 路由感知 hook使用 `useLocation()`
```typescript
// 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防止状态污染
```typescript
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`
```typescript
// /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`
```typescript
// 新增两个辅助函数:
/** 不属于期刊租户的保留路径前缀 */
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.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-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`
```typescript
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 配置页覆盖:
```handlebars
{{! 默认方法学报告模板 }}
# 方法学评估报告
**综合评分:** {{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`(而非新建页面,避免重复):
```typescript
// 在 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/5**4-Tab 审查工作台(稿约规范 / 方法学 / 数据验证 / 临床评估)
**任务 3.5Human-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必须完成**
> 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**
```typescript
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`*`
> - 暴露 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 # 退修信
```
- 报告/退修信下载链接:**必须** 使用带过期时间的签名 URLSigned URL禁止公开直链
- 原始文件名(如 "张三投稿.docx"存数据库OSS Key 使用 UUID
### 9.3 认证授权规范(必须遵守)
> 对应:`docs/04-开发规范/10-模块认证规范.md`
**后端路由层**(所有新增 API 必须):
```typescript
// 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 层**
```typescript
// 必须从 request 提取,禁止硬编码
const userId = getUserId(request);
const tenantId = request.tenantId; // 由中间件翻译后挂载
```
### 9.4 安全规范(必须遵守)
> 对应:`docs/04-开发规范/12-安全开发规范.md`
**IDOR 防护**P0 级强制):
```typescript
// ✅ 所有 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 `tenants``config 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 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 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