From 4b8d8908bc5d844f340cc28b4f55236c490c0095 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 8 Mar 2026 14:52:26 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=90=8E=E7=AB=AF=20Notion=20=E5=BC=80?= =?UTF-8?q?=E5=8F=91=E8=80=85=E5=90=8C=E6=AD=A5=20API=20+=20=E5=89=8D?= =?UTF-8?q?=E7=AB=AF=E8=87=AA=E5=8A=A8=E5=90=8C=E6=AD=A5=E9=9B=86=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: qinfendebingshuo <207279273+qinfendebingshuo@users.noreply.github.com> --- backend/routes/developers.js | 173 +++++++++++++++++++++++++++++++++++ backend/server.js | 4 +- docs/index.html | 46 ++++++++++ 3 files changed, 222 insertions(+), 1 deletion(-) create mode 100644 backend/routes/developers.js diff --git a/backend/routes/developers.js b/backend/routes/developers.js new file mode 100644 index 00000000..68764ced --- /dev/null +++ b/backend/routes/developers.js @@ -0,0 +1,173 @@ +const express = require('express'); +const axios = require('axios'); +const router = express.Router(); + +const NOTION_TOKEN = process.env.NOTION_TOKEN; +const DEVELOPERS_DB_ID = process.env.DEVELOPERS_DB_ID || ''; +const NOTION_VERSION = '2022-06-28'; +const BASE_URL = 'https://api.notion.com/v1'; + +const headers = () => ({ + 'Authorization': 'Bearer ' + NOTION_TOKEN, + 'Content-Type': 'application/json', + 'Notion-Version': NOTION_VERSION +}); + +// Validate DEV-XXX format +const DEV_ID_RE = /^DEV-\d{3,}$/; + +// Notion database property names (match your Notion schema) +const PROP = { + DEV_ID: 'DEV编号', + DEV_ID_ALT: 'DEV ID', + TITLE: '标题', + NAME: '姓名', + NAME_ALT: '名称', + ROLE: '角色', + JOB_TITLE: '职位', + JOB_TITLE_ALT: '岗位', + EMAIL: '邮箱', + GITHUB: 'GitHub', + STATUS: '状态', +}; + +// Build Notion filter query for developer lookup by DEV ID +function devIdFilter(devId) { + return { + or: [ + { property: PROP.DEV_ID, title: { equals: devId } }, + { property: PROP.DEV_ID_ALT, title: { equals: devId } }, + { property: PROP.TITLE, title: { equals: devId } } + ] + }; +} + +// Health check +router.get('/test', (req, res) => { + res.json({ + status: 'ok', + message: '开发者路由正常', + notion_configured: !!NOTION_TOKEN, + db_configured: !!DEVELOPERS_DB_ID + }); +}); + +// Extract developer info from a Notion page result +function extractDevInfo(page) { + const props = page.properties || {}; + const getText = (prop) => { + if (!prop) return ''; + if (prop.title) return prop.title.map(t => t.plain_text).join(''); + if (prop.rich_text) return prop.rich_text.map(t => t.plain_text).join(''); + if (prop.select) return prop.select.name; + if (prop.email) return prop.email || ''; + return ''; + }; + + return { + id: page.id, + devId: getText(props[PROP.DEV_ID] || props[PROP.DEV_ID_ALT] || props[PROP.TITLE]), + name: getText(props[PROP.NAME] || props[PROP.NAME_ALT]), + role: getText(props[PROP.ROLE]), + title: getText(props[PROP.JOB_TITLE] || props[PROP.JOB_TITLE_ALT]), + email: getText(props[PROP.EMAIL]), + github: getText(props[PROP.GITHUB]), + status: getText(props[PROP.STATUS]), + notionUrl: page.url || '' + }; +} + +// GET /api/v1/developers/:devId — Look up developer by DEV-XXX ID from Notion +router.get('/:devId', async (req, res) => { + const devId = (req.params.devId || '').trim().toUpperCase(); + + if (!DEV_ID_RE.test(devId)) { + return res.status(400).json({ error: '开发者编号格式不正确,请使用 DEV-XXX 格式' }); + } + + if (!NOTION_TOKEN) { + return res.status(503).json({ error: 'Notion API 未配置', devId: devId, fallback: true }); + } + + if (!DEVELOPERS_DB_ID) { + return res.status(503).json({ error: '开发者数据库未配置', devId: devId, fallback: true }); + } + + try { + const response = await axios.post( + BASE_URL + '/databases/' + DEVELOPERS_DB_ID + '/query', + { filter: devIdFilter(devId), page_size: 1 }, + { headers: headers() } + ); + + const results = response.data.results || []; + if (results.length > 0) { + const dev = extractDevInfo(results[0]); + return res.json({ found: true, developer: dev }); + } + + return res.json({ found: false, devId: devId, message: '未在 Notion 中找到该开发者,将使用默认配置' }); + } catch (err) { + const errStatus = err.response ? err.response.status : 500; + const detail = err.response ? err.response.data : null; + return res.status(errStatus >= 400 && errStatus < 600 ? errStatus : 500).json({ + error: '查询 Notion 失败: ' + err.message, + devId: devId, + fallback: true, + detail: detail + }); + } +}); + +// POST /api/v1/developers — Register a new developer in Notion +router.post('/', async (req, res) => { + const { devId, name } = req.body || {}; + + if (!devId || !DEV_ID_RE.test(devId)) { + return res.status(400).json({ error: '开发者编号格式不正确' }); + } + + if (!NOTION_TOKEN || !DEVELOPERS_DB_ID) { + return res.status(503).json({ error: 'Notion 未配置', devId: devId, fallback: true }); + } + + try { + // Check if already exists + const checkResp = await axios.post( + BASE_URL + '/databases/' + DEVELOPERS_DB_ID + '/query', + { filter: devIdFilter(devId), page_size: 1 }, + { headers: headers() } + ); + + if (checkResp.data.results && checkResp.data.results.length > 0) { + return res.json({ created: false, message: '开发者已存在', developer: extractDevInfo(checkResp.data.results[0]) }); + } + + // Create new developer page + const newPage = await axios.post( + BASE_URL + '/pages', + { + parent: { database_id: DEVELOPERS_DB_ID }, + properties: { + [PROP.TITLE]: { title: [{ text: { content: devId } }] }, + [PROP.NAME]: { rich_text: [{ text: { content: name || devId } }] }, + [PROP.ROLE]: { select: { name: '开发者' } }, + [PROP.STATUS]: { select: { name: '活跃' } } + } + }, + { headers: headers() } + ); + + return res.json({ created: true, message: '开发者已自动注册', developer: extractDevInfo(newPage.data) }); + } catch (err) { + const detail = err.response ? err.response.data : null; + return res.status(500).json({ + error: '注册失败: ' + err.message, + devId: devId, + fallback: true, + detail: detail + }); + } +}); + +module.exports = router; diff --git a/backend/server.js b/backend/server.js index 4ba0248e..251eb6af 100644 --- a/backend/server.js +++ b/backend/server.js @@ -11,12 +11,14 @@ const notionRoutes = require('./routes/notion'); const feishuRoutes = require('./routes/feishu'); const routerRoutes = require('./routes/router'); const coldstartRoutes = require('./routes/coldstart'); +const developersRoutes = require('./routes/developers'); const hliRoutes = require('../src/routes/hli'); app.use('/notion', notionRoutes); app.use('/feishu', feishuRoutes); app.use('/router', routerRoutes); app.use('/api/coldstart', coldstartRoutes); +app.use('/api/v1/developers', developersRoutes); app.use('/hli', hliRoutes); app.get('/', (req, res) => { @@ -24,7 +26,7 @@ app.get('/', (req, res) => { status: 'ok', message: 'HoloLake 后端服务运行中', version: '0.2.0', - routes: ['/notion/test', '/feishu/test', '/router/test', '/router/chat', '/api/coldstart', '/hli/test'] + routes: ['/notion/test', '/feishu/test', '/router/test', '/router/chat', '/api/coldstart', '/api/v1/developers/test', '/hli/test'] }); }); diff --git a/docs/index.html b/docs/index.html index 898f6ade..a3c36efe 100644 --- a/docs/index.html +++ b/docs/index.html @@ -1464,9 +1464,55 @@ function doTeamLogin(){ A.mdl = TEAM_API.model; lss('zy_teammode','1'); setIdentity(un, ''); + + // For custom DEV-XXX IDs, try to sync from Notion (non-blocking) + if(sel==='__custom__' && DEV_ID_RE.test(un)){ + syncDevFromNotion(un); + } + showApp(); } +// Sync developer info from Notion (non-blocking, best-effort) +function syncDevFromNotion(devId){ + var apiBase = (location.hostname==='localhost'||location.hostname==='127.0.0.1') + ? 'http://localhost:3000' : ''; + var url = apiBase + '/api/v1/developers/' + encodeURIComponent(devId); + + fetch(url).then(function(r){ return r.json(); }).then(function(data){ + if(data.found && data.developer){ + var dev = data.developer; + // Update identity with Notion data if available + if(dev.name && dev.name !== devId){ + A.userMeta = { + role: 'dev', + title: dev.title || dev.role || '开发者', + emoji: '💻', + badgeCls: 'rb-dev', + devId: devId + }; + A.userName = dev.name; + lss('zy_uname', A.userName); + applyIdentityUI(); + console.log('[铸渊] Notion 开发者同步成功:', devId, '→', dev.name); + } + } else { + // Developer not found in Notion — try to register (best effort) + fetch(apiBase + '/api/v1/developers', { + method: 'POST', + headers: {'Content-Type':'application/json'}, + body: JSON.stringify({devId: devId, name: devId}) + }).then(function(r){ return r.json(); }).then(function(reg){ + console.log('[铸渊] 开发者自动注册:', reg.created ? '成功' : '已存在', devId); + }).catch(function(e){ + console.log('[铸渊] 开发者注册跳过(后端未就绪):', e.message); + }); + } + }).catch(function(e){ + console.log('[铸渊] Notion 同步跳过(后端未就绪):', e.message); + }); +} + // ═══════════════════════════════════════════════════════ // SETTINGS PANEL // ═══════════════════════════════════════════════════════