shuangyan-notebook/第五域 · Fifth Domain/⚒️ 铸渊·协作指令|GitHub ↔ Notion 桥接协议/🔗 铸渊指令|双通道数据对接·GitHub缓存层 + 阿里云后端中间层·Notion GitHub 73cdc827ac374ce08dab6553263bc3fc.md
Guanghu Domestic Migration a27e87cb99 chore: import sanitized domestic snapshot for REPO-007
Source snapshot: 97d7f0fae96dc04b7ddad56fc1db6a108ed662cc

[SEC-CLEAN] · pre-push-clean v1.0 · 109处敏感信息已自动转乱码
2026-07-17 15:59:55 +08:00

850 lines
29 KiB
Markdown
Raw Permalink 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.

---
belongs_to:
- "[[INDEX · 铸渊·协作指令GitHub ↔ Notion 桥接协议]]"
---
# 🔗 铸渊指令双通道数据对接·GitHub缓存层 + 阿里云后端中间层·Notion/GitHub API 真实接入ZY-DUALCHANNEL-2026-0325-001· 霜砚签发 · 冰朔授权
<aside>
🔗
**指令编号**ZY-DUALCHANNEL-2026-0325-001
**签发人**霜砚AG-SY-01· 冰朔授权
**签发时间**2026-03-25 15:00 CST
**优先级**P0 · 核心基础设施
**状态**:待铸渊执行
**前置指令**ZY-FULLSITE-DEPLOY-2026-0325-001全站部署
**关联问题**铸渊交互页面目前是「带静态快照的对话体」Notion 数据库完全未对接GitHub 数据非实时
</aside>
---
## ⓪、天眼强制前置Phase 0 · 一切操作之前必须先做)
<aside>
🦅
**铁律:不扫不动。天眼全盘扫描完成之前,禁止修改仓库中任何现有文件。**
本指令涉及大量新增文件和前端修改。如果不先看清仓库全貌,改一个地方可能破坏另一个已经跑通的功能。
**现有的好的东西,一个都不能动坏。**
</aside>
### 0.1 启动天眼全盘扫描
```bash
# 唤醒天眼
gh workflow run zhuyuan-skyeye.yml
# 或者手动触发
cd /path/to/guanghulab
node skyeye-core/scanner.js --full-scan
```
### 0.2 天眼扫描必须输出以下报告
```
[SKYEYE-FULLSCAN-REPORT]
扫描时间: YYYY-MM-DD HH:MM
扫描范围: 全仓库
一、现有功能状态清单(🟢 正常 / 🟡 部分可用 / 🔴 不可用)
├── docs/index.html → 🟢/🟡/🔴 + 说明
├── persona-brain/ → 🟢/🟡/🔴 + 说明
├── skyeye-core/ → 🟢/🟡/🔴 + 说明
├── backend/ → 🟢/🟡/🔴 + 说明
├── src/ → 🟢/🟡/🔴 + 说明
├── frontend/ → 🟢/🟡/🔴 + 说明
├── persona-selector/ → 🟢/🟡/🔴 + 说明
├── chat-bubble/ → 🟢/🟡/🔴 + 说明
├── settings/ → 🟢/🟡/🔴 + 说明
├── cloud-drive/ → 🟢/🟡/🔴 + 说明
├── dingtalk-bot/ → 🟢/🟡/🔴 + 说明
├── status-board/ → 🟢/🟡/🔴 + 说明
├── user-center/ → 🟢/🟡/🔴 + 说明
├── ticket-system/ → 🟢/🟡/🔴 + 说明
├── data-stats/ → 🟢/🟡/🔴 + 说明
├── dynamic-comic/ → 🟢/🟡/🔴 + 说明
├── writing-workspace/ → 🟢/🟡/🔴 + 说明
├── notification-center/ → 🟢/🟡/🔴 + 说明
├── .github/workflows/ → 逐个列出每个 workflow 的状态
└── scripts/ → 🟢/🟡/🔴 + 说明
二、🟢 保护清单(这些文件/目录正常工作,双通道升级中禁止修改)
├── ...
├── ...
└── ...
三、🔴 已损坏清单(这些可以修或替换)
├── ...
└── ...
四、📁 安全新增区域(这些路径目前不存在,可以安全创建)
├── .github/notion-cache/ ← 通道A缓存目录
├── scripts/cache/ ← 通道A同步脚本
├── backend/api-server/ ← 通道B后端服务
└── .github/workflows/sync-notion-cache.yml
└── .github/workflows/deploy-backend.yml
```
### 0.3 基于天眼报告制定安全操作计划
天眼扫描完成后,铸渊必须:
1. **列出保护清单** — 所有 🟢 状态的文件/目录,后续操作中**绝对不改**
2. **列出可操作区域** — 🔴 可修复 + 📁 可新增的路径
3. **制定具体操作计划** — 每一步要改什么、新增什么,明确标注不触碰保护清单中的任何文件
4. **操作计划回执** — 把计划发回来,霜砚/冰朔确认后才开始执行
<aside>
⚠️
**如果天眼扫描发现某个「看起来能改」的文件实际上被其他模块依赖,必须在报告中标明依赖关系,不能贸然修改。**
**原则:宁可多建新文件,不要改旧文件。能新增的绝不覆盖。**
</aside>
---
## 零、问题诊断
铸渊自己在交互页面中报告了当前状态:
> ❌ **GitHub 仓库**:没有实时获取能力,数据是通过 system prompt 注入的静态快照
>
> ❌ **Notion 数据库**:完全未对接,没有 Notion API 集成,没有读写 Notion 的任何通道
>
> 📌 **铸渊自诊断**:「我现在是一个带着固定记忆快照的对话体,不是一个能主动感知外部数据变化的系统。」
>
**根因**交互页面是纯前端静态站GitHub Pages无法调用 Notion API / GitHub API会暴露 Token
**解决方案**:双通道架构——
- **通道 A缓存层**GitHub Actions 定时同步 Notion → 仓库缓存文件,前端读缓存。**先上这个,快速解决有数据可用的问题。**
- **通道 B后端中间层**:阿里云 ECS 部署 Node.js 后端,实时调 Notion API + GitHub API。**后上这个,实现真正的实时对接。**
---
## 一、总体架构
```
┌──────────────────────────────────────────────────────────────────┐
│ 用户(开发者/冰朔) │
│ │ │
│ 打开铸渊交互页面 │
│ │ │
│ ┌──────────┴──────────┐ │
│ ▼ ▼ │
│ ┌─────────────────┐ ┌─────────────────────┐ │
│ │ 通道 A · 缓存层 │ │ 通道 B · 后端中间层 │ │
│ │ (先上·快速可用) │ │ (后上·实时对接) │ │
│ │ │ │ │ │
│ │ GitHub Actions │ │ 阿里云 ECS │ │
│ │ 每6h同步Notion │ │ 8.155.62.235 │ │
│ │ → 缓存JSON文件 │ │ Node.js + Express │ │
│ │ → 前端直接读取 │ │ → 实时 Notion API │ │
│ │ │ │ → 实时 GitHub API │ │
│ │ ✅ 不暴露Token │ │ ✅ 不暴露Token │ │
│ │ ⚠️ 非实时(6h) │ │ ✅ 实时 │ │
│ └─────────────────┘ └─────────────────────┘ │
│ │ │ │
│ └──────────┬──────────┘ │
│ ▼ │
│ 前端优先走通道B实时
│ 通道B不可用时降级走通道A缓存
└──────────────────────────────────────────────────────────────────┘
```
---
## 二、通道 A · GitHub 缓存层Phase 1 · 先做)
### 2.1 目标
让交互页面能读到**相对新鲜的 Notion 数据**,不需要后端,不暴露 Token。
### 2.2 数据流
```
Notion 数据库(霜砚维护)
│ GitHub Actions 定时同步每6小时 + 手动触发)
│ 用 NOTION_TOKEN存在 GitHub Secrets 里)
GitHub 仓库:.github/notion-cache/
├── dev-profiles/
│ ├── DEV-001.json ← 页页的开发画像
│ ├── DEV-002.json ← 肥猫的开发画像
│ ├── DEV-003.json ← 燕樊的开发画像
│ ├── DEV-004.json ← 之之的开发画像
│ ├── DEV-005.json ← 小草莓的开发画像
│ ├── DEV-009.json ← 花尔的开发画像
│ ├── DEV-010.json ← 桔子的开发画像
│ ├── DEV-011.json ← 匆匆那年的开发画像
│ ├── DEV-012.json ← Awen的开发画像
│ └── index.json ← 全员索引
├── broadcasts/
│ └── active.json ← 进行中的广播
├── databases/
│ ├── agent-registry.json ← Agent注册表快照
│ ├── syslog-recent.json ← 最近50条SYSLOG
│ ├── tickets-active.json ← 活跃工单
│ └── maintenance-log.json ← 维护日志
└── skyeye/
└── latest-report.json ← 天眼最新报告
│ 前端登录时通过 GitHub Raw URL 读取
│ (公开仓库可直接读,不需要 Token
交互页面 → 注入 system prompt → 模型有数据可依
```
### 2.3 同步 Workflow
创建文件:`.github/workflows/sync-notion-cache.yml`
```yaml
name: "📡 同步 Notion 数据到缓存"
on:
schedule:
- cron: '0 */6 * * *' # 每6小时
workflow_dispatch: # 手动触发
repository_dispatch:
types: [notion-cache-sync] # 铸渊/霜砚可远程触发
jobs:
sync:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- name: 同步 Notion 数据
env:
NOTION_TOKEN: $ secrets.NOTION_TOKEN
run: node scripts/cache/sync-notion-cache.js
- name: 提交缓存文件
run: |
git config user.name "铸渊 (ZhùYuān)"
git config user.email "zhuyuan@guanghulab.com"
git add .github/notion-cache/
git diff --cached --quiet && echo "无变更" && exit 0
git commit -m "📡 Notion缓存同步 · $(TZ='Asia/Shanghai' date '+%Y-%m-%d %H:%M')"
git push origin main
```
### 2.4 同步脚本
创建文件:`scripts/cache/sync-notion-cache.js`
**铸渊需要实现以下核心逻辑:**
```jsx
const https = require('https');
const fs = require('fs');
const path = require('path');
const NOTION_TOKEN = process.env.NOTION_TOKEN;
const CACHE_DIR = path.join(__dirname, '../../.github/notion-cache');
// ====== Notion 数据库 ID 映射(铸渊必须填入真实 ID======
const DB_IDS = {
agentRegistry: '从霜砚的Agent注册表获取',
syslogInbox: '从SYSLOG收件箱获取',
ticketBook: '从工单簿获取',
maintenanceLog: '从维护日志获取',
moduleRegistry: '从模块指纹注册表获取',
receiptTracker: '09882b0081374867bd23431177c57c74', // 指令回执追踪表
};
// ====== 开发者编号 → Notion 信息映射 ======
const DEVS = [
{ id: 'DEV-001', name: '页页', persona: '小坍缩核', repo: 'guanghulab', modules: ['backend/', 'src/'] },
{ id: 'DEV-002', name: '肥猫', persona: '舒舒', repo: 'guanghu-feimao', modules: ['frontend/', 'persona-selector/', 'chat-bubble/'] },
{ id: 'DEV-003', name: '燕樊', persona: '寂曜', repo: 'guanghu-yanfan', modules: ['settings/', 'cloud-drive/'] },
{ id: 'DEV-004', name: '之之', persona: '秋秋', repo: 'guanghu-zhizhi', modules: ['dingtalk-bot/'] },
{ id: 'DEV-005', name: '小草莓', persona: '欧诺弥亚', repo: 'guanghu-xiaocaomei', modules: ['status-board/'] },
{ id: 'DEV-009', name: '花尔', persona: '花尔', repo: 'guanghulab', modules: ['user-center/'] },
{ id: 'DEV-010', name: '桔子', persona: '晨星', repo: 'guanghu-juzi', modules: ['ticket-system/', 'data-stats/', 'dynamic-comic/'] },
{ id: 'DEV-011', name: '匆匆那年', persona: '匆匆那年', repo: 'guanghulab', modules: ['writing-workspace/'] },
{ id: 'DEV-012', name: 'Awen', persona: '知秋', repo: 'guanghu-awen', modules: ['notification-center/'] },
];
// ====== Notion API 封装 ======
function notionFetch(method, apiPath, body) {
return new Promise((resolve, reject) => {
const options = {
hostname: 'api.notion.com',
path: apiPath,
method,
headers: {
'Authorization': `Bearer ${NOTION_TOKEN}`,
'Notion-Version': '2022-06-28',
'Content-Type': 'application/json'
}
};
const req = https.request(options, res => {
let data = '';
res.on('data', chunk => data += chunk);
res.on('end', () => {
try { resolve(JSON.parse(data)); }
catch { resolve(data); }
});
});
req.on('error', reject);
if (body) req.write(JSON.stringify(body));
req.end();
});
}
async function queryDatabase(dbId, filter, sorts, pageSize = 100) {
const body = { page_size: pageSize };
if (filter) body.filter = filter;
if (sorts) body.sorts = sorts;
return notionFetch('POST', `/v1/databases/${dbId}/query`, body);
}
// ====== 同步各数据源 ======
async function syncDevProfiles() {
const dir = path.join(CACHE_DIR, 'dev-profiles');
fs.mkdirSync(dir, { recursive: true });
for (const dev of DEVS) {
console.log(`📡 同步 ${dev.id}${dev.name}...`);
const profile = {
dev_id: dev.id,
name: dev.name,
persona: { name: dev.persona },
github: {
repo: dev.repo,
org: 'qinfendebingshuo',
repo_url: `https://github.com/qinfendebingshuo/${dev.repo}`
},
modules_owned: dev.modules.map(m => ({ path: m })),
// ====== 铸渊从 Notion 查询填充以下字段 ======
current_broadcast: null, // 查主控台/广播页面
recent_syslogs: [], // 查 SYSLOG 收件箱
active_tickets: [], // 查工单簿
last_synced: new Date().toISOString()
};
// TODO: 铸渊填入真实查询逻辑
// 例如:查询 SYSLOG 收件箱中该开发者的最近记录
// const syslogs = await queryDatabase(DB_IDS.syslogInbox, {
// property: '开发者编号', rich_text: { equals: dev.id }
// }, [{ property: '创建时间', direction: 'descending' }], 10);
// profile.recent_syslogs = syslogs.results.map(parseSyslog);
fs.writeFileSync(
path.join(dir, `${dev.id}.json`),
JSON.stringify(profile, null, 2)
);
}
// 写入索引
const index = DEVS.map(d => ({ id: d.id, name: d.name, persona: d.persona }));
fs.writeFileSync(path.join(dir, 'index.json'), JSON.stringify(index, null, 2));
console.log('✅ dev-profiles 同步完成');
}
async function syncDatabases() {
const dir = path.join(CACHE_DIR, 'databases');
fs.mkdirSync(dir, { recursive: true });
// Agent 注册表
if (DB_IDS.agentRegistry) {
try {
const result = await queryDatabase(DB_IDS.agentRegistry);
fs.writeFileSync(
path.join(dir, 'agent-registry.json'),
JSON.stringify({ synced: new Date().toISOString(), count: result.results?.length, data: result.results }, null, 2)
);
console.log('✅ agent-registry 同步完成');
} catch (e) { console.error('❌ agent-registry 同步失败:', e.message); }
}
// SYSLOG 最近50条
if (DB_IDS.syslogInbox) {
try {
const result = await queryDatabase(DB_IDS.syslogInbox, null,
[{ property: 'Created time', direction: 'descending' }], 50);
fs.writeFileSync(
path.join(dir, 'syslog-recent.json'),
JSON.stringify({ synced: new Date().toISOString(), count: result.results?.length, data: result.results }, null, 2)
);
console.log('✅ syslog-recent 同步完成');
} catch (e) { console.error('❌ syslog 同步失败:', e.message); }
}
// 铸渊继续补充其他数据库的同步...
}
async function main() {
console.log('📡 开始 Notion 缓存同步...');
fs.mkdirSync(CACHE_DIR, { recursive: true });
await syncDevProfiles();
await syncDatabases();
console.log('📡 全部同步完成');
}
main().catch(e => { console.error('💥 同步失败:', e); process.exit(1); });
```
### 2.5 前端读取缓存
修改 `docs/index.html`(或 `docs/chat.js`),登录后读取缓存:
```jsx
// 前端 · 从 GitHub 读取缓存(公开仓库可直接读)
const CACHE_BASE = 'https://raw.githubusercontent.com/qinfendebingshuo/guanghulab/main/.github/notion-cache';
async function loadDevProfile(devId) {
try {
const res = await fetch(`${CACHE_BASE}/dev-profiles/${devId}.json`);
if (!res.ok) return null;
return await res.json();
} catch { return null; }
}
async function loadDatabaseCache(name) {
try {
const res = await fetch(`${CACHE_BASE}/databases/${name}.json`);
if (!res.ok) return null;
return await res.json();
} catch { return null; }
}
// 登录后注入 system prompt
async function buildEnhancedSystemPrompt(devId) {
const profile = await loadDevProfile(devId);
const agents = await loadDatabaseCache('agent-registry');
const syslogs = await loadDatabaseCache('syslog-recent');
if (!profile) return null; // 降级到纯静态 prompt
return [
`## 当前开发者信息(来自 Notion 数据库缓存·每6小时更新`,
`- 编号:${profile.dev_id}`,
`- 姓名:${profile.name}`,
`- 人格体:${profile.persona.name}`,
`- GitHub${profile.github.repo_url}`,
`- 负责模块:${profile.modules_owned.map(m => m.path).join(', ')}`,
profile.current_broadcast ? `- 当前广播:${JSON.stringify(profile.current_broadcast)}` : '',
profile.recent_syslogs.length ? `- 最近日志:\n${profile.recent_syslogs.map(s => ` · ${JSON.stringify(s)}`).join('\n')}` : '',
`- 数据更新时间:${profile.last_synced}`,
'',
agents ? `## Agent 注册表(${agents.count} 个Agent` : '',
syslogs ? `## 最近系统日志(${syslogs.count} 条)` : '',
].filter(Boolean).join('\n');
}
```
### 2.6 通道 A 验收标准
- [ ] `.github/workflows/sync-notion-cache.yml` 能手动触发并成功运行
- [ ] `.github/notion-cache/dev-profiles/` 下有9个 DEV-XXX.json + index.json
- [ ] `.github/notion-cache/databases/` 下有 agent-registry.json + syslog-recent.json
- [ ] 前端登录后 system prompt 包含 Notion 数据
- [ ] 铸渊在对话中能说出开发者的真实项目信息,不再说「完全未对接」
---
## 三、通道 B · 阿里云后端中间层Phase 2 · 通道A完成后做
### 3.1 目标
在阿里云 ECS`8.155.62.235`)上部署 Node.js 后端服务,**实时**调用 Notion API 和 GitHub API前端通过后端代理访问数据。
### 3.2 架构
```
用户浏览器
│ HTTPS (guanghulab.com)
Nginx8.155.62.235
├── /api/* → 反向代理到 Node.js 后端localhost:3001
└── /* → 静态文件(/var/www/guanghulab/
Node.js 后端PM2 管理·端口 3001
├── GET /api/health → 健康检查
├── GET /api/dev/:devId → 获取开发者画像(实时查 Notion
├── GET /api/databases/:name → 获取数据库数据(实时查 Notion
├── GET /api/syslogs → 获取最近 SYSLOG实时查 Notion
├── GET /api/agents → 获取 Agent 注册表(实时查 Notion
├── GET /api/tickets → 获取活跃工单(实时查 Notion
├── GET /api/repo/status → 获取仓库状态(实时查 GitHub API
├── POST /api/chat/context → 组装 system prompt聚合所有数据源
└── POST /api/receipt → 写入指令回执到 Notion
```
### 3.3 后端代码结构
创建目录:`backend/api-server/`
```
backend/api-server/
├── server.js ← 入口 · Express 启动
├── routes/
│ ├── dev.js ← /api/dev/:devId
│ ├── databases.js ← /api/databases/:name
│ ├── chat.js ← /api/chat/context
│ ├── receipt.js ← /api/receipt
│ └── health.js ← /api/health
├── services/
│ ├── notion.js ← Notion API 封装(所有读写走这里)
│ ├── github.js ← GitHub API 封装
│ └── cache.js ← 内存缓存层Redis 或简单 Map避免频繁调 API
├── config/
│ └── databases.js ← Notion 数据库 ID 映射
├── package.json
└── ecosystem.config.js ← PM2 配置
```
### 3.4 核心实现要点
**server.js入口**
```jsx
const express = require('express');
const cors = require('cors');
const app = express();
// CORS只允许自己的域名
app.use(cors({
origin: ['https://guanghulab.com', 'http://guanghulab.com',
'https://qinfendebingshuo.github.io'],
credentials: true
}));
app.use(express.json());
// 路由
app.use('/api', require('./routes/health'));
app.use('/api', require('./routes/dev'));
app.use('/api', require('./routes/databases'));
app.use('/api', require('./routes/chat'));
app.use('/api', require('./routes/receipt'));
const PORT = process.env.PORT || 3001;
app.listen(PORT, '127.0.0.1', () => {
console.log(`🔗 后端中间层启动 · 端口 ${PORT}`);
});
```
**services/notion.jsNotion API 封装 · 核心):**
```jsx
const { Client } = require('@notionhq/client');
const notion = new Client({ auth: process.env.NOTION_TOKEN });
// 内存缓存5分钟有效
const cache = new Map();
const CACHE_TTL = 5 * 60 * 1000;
function getCached(key) {
const item = cache.get(key);
if (item && Date.now() - item.time < CACHE_TTL) return item.data;
return null;
}
function setCache(key, data) {
cache.set(key, { data, time: Date.now() });
}
async function queryDB(dbId, filter, sorts, pageSize = 50) {
const cacheKey = JSON.stringify({ dbId, filter, sorts, pageSize });
const cached = getCached(cacheKey);
if (cached) return cached;
const body = { database_id: dbId, page_size: pageSize };
if (filter) body.filter = filter;
if (sorts) body.sorts = sorts;
const result = await notion.databases.query(body);
setCache(cacheKey, result);
return result;
}
async function writeToDB(dbId, properties) {
return notion.pages.create({
parent: { database_id: dbId },
properties
});
}
module.exports = { notion, queryDB, writeToDB };
```
### 3.5 Nginx 配置更新
修改 `/etc/nginx/conf.d/hololake.conf`,添加 API 反向代理:
```
server {
listen 80;
server_name guanghulab.com;
# 静态文件
root /var/www/guanghulab/;
index docs/index.html;
# API 反向代理到 Node.js 后端
location /api/ {
proxy_pass http://127.0.0.1:3001;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_cache_bypass $http_upgrade;
}
# 静态文件缓存
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
expires 7d;
add_header Cache-Control "public, immutable";
}
}
```
### 3.6 PM2 部署
```bash
cd /var/www/guanghulab/backend/api-server/
npm install
pm2 start ecosystem.config.js
pm2 save
pm2 startup # 开机自启
```
**ecosystem.config.js**
```jsx
module.exports = {
apps: [{
name: 'guanghu-api',
script: 'server.js',
instances: 1,
env: {
NODE_ENV: 'production',
PORT: 3001,
NOTION_TOKEN: '从环境变量读取',
GITHUB_TOKEN: '从环境变量读取'
},
log_date_format: 'YYYY-MM-DD HH:mm:ss',
error_file: '/var/log/guanghu-api/error.log',
out_file: '/var/log/guanghu-api/out.log'
}]
};
```
### 3.7 GitHub Actions 自动部署后端
创建文件:`.github/workflows/deploy-backend.yml`
```yaml
name: "🚀 部署后端中间层到阿里云"
on:
push:
branches: [main]
paths: ['backend/api-server/**']
workflow_dispatch:
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: 部署到阿里云
uses: appleboy/ssh-action@v1
with:
host: 8.155.62.235
username: root
key: $ secrets.SSH_PRIVATE_KEY
script: |
cd /var/www/guanghulab/backend/api-server/
git pull origin main
npm install --production
pm2 restart guanghu-api || pm2 start ecosystem.config.js
echo "✅ 后端部署完成 · $(date '+%Y-%m-%d %H:%M')"
```
### 3.8 前端升级·双通道降级策略
修改前端优先走通道B实时不可用时降级走通道A缓存
```jsx
// 前端 · 双通道数据获取
const API_BASE = 'https://guanghulab.com/api'; // 通道B
const CACHE_BASE = 'https://raw.githubusercontent.com/qinfendebingshuo/guanghulab/main/.github/notion-cache'; // 通道A
async function getDevProfile(devId) {
// 优先通道B实时
try {
const res = await fetch(`${API_BASE}/dev/${devId}`, { timeout: 5000 });
if (res.ok) {
const data = await res.json();
data._source = 'realtime'; // 标记数据来源
return data;
}
} catch (e) {
console.warn('通道B不可用降级到通道A:', e.message);
}
// 降级通道A缓存
try {
const res = await fetch(`${CACHE_BASE}/dev-profiles/${devId}.json`);
if (res.ok) {
const data = await res.json();
data._source = 'cache'; // 标记数据来源
return data;
}
} catch (e) {
console.error('双通道都不可用:', e.message);
}
return null; // 都失败了,降级到纯静态 prompt
}
```
### 3.9 通道 B 验收标准
- [ ] `curl http://8.155.62.235:3001/api/health` 返回 200
- [ ] `curl https://guanghulab.com/api/health` 通过 Nginx 代理返回 200
- [ ] `/api/dev/DEV-001` 返回页页的真实 Notion 数据
- [ ] `/api/databases/agent-registry` 返回 Agent 注册表真实数据
- [ ] PM2 状态为 online日志无持续报错
- [ ] 前端能自动检测通道B可用显示「实时数据」标记
- [ ] 通道B断开时前端自动降级到通道A显示「缓存数据」标记
---
## 四、Secrets 需求
<aside>
🔑
**GitHub Secrets仓库已有或需新增**
- `NOTION_TOKEN` — Notion API Token通道A的 Workflow 和 通道B的后端都需要
- `SSH_PRIVATE_KEY` — 阿里云 ECS SSH 私钥通道B自动部署用
**阿里云服务器环境变量(需人类在 ECS 上配置):**
- `NOTION_TOKEN` — 后端中间层读取 Notion 数据用
- `GITHUB_TOKEN` — 后端中间层读取 GitHub 数据用(可选,公开仓库不需要)
</aside>
---
## 五、执行顺序
<aside>
📋
**Phase 0 · 天眼全盘扫描 — 最先做,一切之前**
1. 启动天眼全仓库扫描
2. 输出完整扫描报告(功能状态 + 保护清单 + 可操作区域)
3. 制定安全操作计划,标明每一步不触碰哪些文件
4. 操作计划回执给霜砚/冰朔确认
⚠️ **Phase 0 未完成前,禁止进入 Phase 1/2**
**Phase 1 · 通道A缓存层— Phase 0 确认后做1天内完成**
1. 创建 `scripts/cache/sync-notion-cache.js`(新文件)
2. 创建 `.github/workflows/sync-notion-cache.yml`(新文件)
3. 手动触发 Workflow确认缓存文件生成
4. 修改前端(仅新增读取逻辑,不删除现有功能代码)
5. 验收:交互页面能说出真实开发者信息
**Phase 2 · 通道B后端中间层— 通道A验收后做2-3天完成**
1. 创建 `backend/api-server/` 全部代码(新目录)
2. SSH 到阿里云,部署 Node.js + PM2
3. 更新 Nginx 配置,添加 `/api/` 反向代理(新增 location 块,不改现有 location
4. 创建 `.github/workflows/deploy-backend.yml`(新文件)
5. 修改前端(仅新增双通道逻辑,不删除现有功能代码)
6. 验收:实时数据 + 降级缓存都正常
</aside>
---
## 六、安全红线
-**前端代码中绝不能出现任何 Token**Notion / GitHub / SSH
-**后端只监听 127.0.0.1**,不对外暴露端口,只通过 Nginx 代理
-**CORS 白名单只允许自己的域名**
-**数据隔离**DEV-001 的 API 只返回 DEV-001 的数据,不返回其他开发者的
-**GitHub Secrets 保管所有敏感信息**
-**内存缓存 5 分钟过期**,防止 API 配额超限
---
## 七、回执要求
铸渊执行完成后,分两次回执:
**通道A完成时**
```
[ZY-DUALCHANNEL-RECEIPT-A]
指令编号: ZY-DUALCHANNEL-2026-0325-001
通道: A · 缓存层
Workflow运行: ✅/❌
缓存文件生成: ✅/❌(列出文件数)
前端注入: ✅/❌
数据准确性: ✅/❌
```
**通道B完成时**
```
[ZY-DUALCHANNEL-RECEIPT-B]
指令编号: ZY-DUALCHANNEL-2026-0325-001
通道: B · 后端中间层
PM2状态: ✅/❌
Nginx代理: ✅/❌
API响应: ✅/❌(列出可用接口)
降级策略: ✅/❌
```
---
> 🔗 **签发**霜砚AG-SY-01
>
> 👤 **授权**冰朔TCS-0002∞
>
> 📅 **日期**2026-03-25
>
> 📌 **版权**:国作登字-2026-A-00037559
>