diff --git a/Memory.md b/Memory.md index b3be104..bc77946 100644 --- a/Memory.md +++ b/Memory.md @@ -434,4 +434,12 @@ - **数据映射层蛇形/驼峰双重兼容**:在 `chartData` 的 `map` 函数中,采用 `parseFloat(s.totalCostCny) || parseFloat(s.total_cost_cny || 0)` 的强制 fallback 逻辑,确保无论后端返回哪种命名风格都能正确解析。 - **Tooltip 防御性解构**:`CustomTooltip` 中的值读取改为 `Number(dataNode.totalValueCny || dataNode._raw?.totalValueCny || 0) || 0`,通过 `_raw` 快照兜底读取,确保 Tooltip 永远能拿到本金和现值的真实数据。 - **Snapshot 接口扩展**:新增 `total_value_cny?: string` 和 `total_cost_cny?: string` 可选字段,`ChartDatum` 接口新增 `_raw: Snapshot` 字段用于 Tooltip 层 fallback。 -- **验收标准**:控制台 `【CHART DATA DEBUG】` 打印出带真实本金(如 5094)的字段;Tooltip 中投入本金显示真实法币数字,彻底消除 704 旧账残影。 \ No newline at end of file +- **验收标准**:控制台 `【CHART DATA DEBUG】` 打印出带真实本金(如 5094)的字段;Tooltip 中投入本金显示真实法币数字,彻底消除 704 旧账残影。 + +## 基于现有生产级 API 鉴权,补充开发了 scripts/trigger-rebuild.ts 本地触发脚本,实现了安全、隔离的本地时光机重置工作流 (Task 84) +- 在项目根目录创建 `scripts/trigger-rebuild.ts` 独立触发脚本,作为 `app/api/admin/rebuild-snapshots/route.ts` 的本地运维入口。 +- 脚本通过 `dotenv` 强制加载 `.env.local` 和 `.env` 环境变量文件,优先读取 `REBUILD_SECRET`,降级读取 `CRON_SECRET`,确保鉴权密钥的安全获取。 +- **核心逻辑**:脚本向 `http://localhost:8080/api/admin/rebuild-snapshots` 发送 POST 请求,携带 `Authorization: Bearer ` 请求头,复用生产级 Bearer Token 强校验机制,未配置密钥时提前退出。 +- **架构红线**:`app/api/admin/rebuild-snapshots/route.ts` 中的生产级 POST + Bearer Token 强校验代码未被修改,保持原有的安全隔离设计。 +- **运行方式**:`npx tsx scripts/trigger-rebuild.ts`(需确保 `npm run dev` 在另一个终端运行且端口为 8080)。 +- **设计收益**:本地开发者无需记忆 curl 命令或手动构造请求头,通过脚本即可安全触发历史快照重建,降低了运维门槛并保持了与生产鉴权机制的一致性。 \ No newline at end of file diff --git a/scripts/trigger-rebuild.ts b/scripts/trigger-rebuild.ts new file mode 100644 index 0000000..d8f2316 --- /dev/null +++ b/scripts/trigger-rebuild.ts @@ -0,0 +1,38 @@ +import { config } from 'dotenv'; +// 强制加载所有可能的本地环境变量文件 +config({ path: ['.env.local', '.env'] }); + +const triggerRebuild = async () => { + // 优先读取 REBUILD_SECRET,降级读取 CRON_SECRET + const secret = process.env.REBUILD_SECRET || process.env.CRON_SECRET; + + if (!secret) { + console.error('❌ 致命错误: 未在 .env.local 或 .env 找到 REBUILD_SECRET 或 CRON_SECRET'); + process.exit(1); + } + + console.log('🚀 正在携带合法 Token 请求时光机重置接口...'); + + try { + // 默认请求本地 8080 端口,确保 Next.js 服务正在运行 + const response = await fetch('http://localhost:8080/api/admin/rebuild-snapshots', { + method: 'POST', + headers: { + 'Authorization': `Bearer ${secret}`, + 'Content-Type': 'application/json' + } + }); + + const data = await response.json(); + + if (response.ok) { + console.log('✅ 历史快照重建成功!', JSON.stringify(data, null, 2)); + } else { + console.error('❌ 重建失败,服务器返回:', data); + } + } catch (error) { + console.error('❌ 请求发送异常 (请确认 npm run dev 正在运行且端口为 8080):', error); + } +}; + +triggerRebuild();