fix: address code review — module-level JWT check, safer JSON extraction, Node.js in workflow
Co-authored-by: qinfendebingshuo <207279273+qinfendebingshuo@users.noreply.github.com> Agent-Logs-Url: https://github.com/qinfendebingshuo/guanghulab/sessions/9f983442-2152-4e4a-82b7-c13fe55f0b9a
This commit is contained in:
parent
d7ac8557ee
commit
a04003e944
|
|
@ -52,7 +52,7 @@ jobs:
|
|||
echo "🦅 天眼强制前置:检查健康状态..."
|
||||
# 读取最新天眼报告(从 notion-cache 或 skyeye-core)
|
||||
if [ -f ".github/notion-cache/skyeye/latest-report.json" ]; then
|
||||
STATUS=$(cat .github/notion-cache/skyeye/latest-report.json | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('overall_status','unknown'))" 2>/dev/null || echo "unknown")
|
||||
STATUS=$(node -e "try{const d=require('./.github/notion-cache/skyeye/latest-report.json');console.log(d.overall_status||'unknown')}catch(_){console.log('unknown')}" 2>/dev/null || echo "unknown")
|
||||
echo "天眼缓存状态:$STATUS"
|
||||
if [ "$STATUS" = "critical" ] || [ "$STATUS" = "error" ]; then
|
||||
echo "❌ 天眼报告严重问题,暂停执行"
|
||||
|
|
|
|||
|
|
@ -15,9 +15,13 @@
|
|||
|
||||
const https = require('https');
|
||||
|
||||
// 模块级检查:jsonwebtoken 是否可用
|
||||
let jwtLib = null;
|
||||
try { jwtLib = require('jsonwebtoken'); } catch (_) { /* fallback to crypto */ }
|
||||
|
||||
/**
|
||||
* 用 App Private Key 生成 JWT(不依赖 jsonwebtoken 库的 fallback 实现)
|
||||
* 在 CI 环境中可能没有安装 jsonwebtoken,因此提供纯 Node.js 实现
|
||||
* 用 App Private Key 生成 JWT
|
||||
* 优先使用 jsonwebtoken 库,不可用时降级到 Node.js crypto 实现
|
||||
*/
|
||||
function generateJWT() {
|
||||
const privateKey = process.env.GHAPP_PRIVATE_KEY;
|
||||
|
|
@ -27,42 +31,40 @@ function generateJWT() {
|
|||
throw new Error('GHAPP_APP_ID 和 GHAPP_PRIVATE_KEY 环境变量必须设置');
|
||||
}
|
||||
|
||||
// 尝试使用 jsonwebtoken 库
|
||||
try {
|
||||
const jwt = require('jsonwebtoken');
|
||||
if (jwtLib) {
|
||||
const payload = {
|
||||
iat: Math.floor(Date.now() / 1000) - 60,
|
||||
exp: Math.floor(Date.now() / 1000) + (10 * 60),
|
||||
iss: appId
|
||||
};
|
||||
return jwt.sign(payload, privateKey, { algorithm: 'RS256' });
|
||||
} catch (e) {
|
||||
// jsonwebtoken 不可用时,使用 Node.js crypto 实现
|
||||
const crypto = require('crypto');
|
||||
|
||||
function base64url(buf) {
|
||||
return buf.toString('base64')
|
||||
.replace(/=/g, '')
|
||||
.replace(/\+/g, '-')
|
||||
.replace(/\//g, '_');
|
||||
}
|
||||
|
||||
const header = base64url(Buffer.from(JSON.stringify({ alg: 'RS256', typ: 'JWT' })));
|
||||
const now = Math.floor(Date.now() / 1000);
|
||||
const payload = base64url(Buffer.from(JSON.stringify({
|
||||
iat: now - 60,
|
||||
exp: now + 600,
|
||||
iss: appId
|
||||
})));
|
||||
|
||||
const sigInput = header + '.' + payload;
|
||||
const sign = crypto.createSign('RSA-SHA256');
|
||||
sign.update(sigInput);
|
||||
sign.end();
|
||||
const signature = base64url(sign.sign(privateKey));
|
||||
|
||||
return sigInput + '.' + signature;
|
||||
return jwtLib.sign(payload, privateKey, { algorithm: 'RS256' });
|
||||
}
|
||||
|
||||
// jsonwebtoken 不可用时,使用 Node.js crypto 实现
|
||||
const crypto = require('crypto');
|
||||
|
||||
function base64url(buf) {
|
||||
return buf.toString('base64')
|
||||
.replace(/=/g, '')
|
||||
.replace(/\+/g, '-')
|
||||
.replace(/\//g, '_');
|
||||
}
|
||||
|
||||
const header = base64url(Buffer.from(JSON.stringify({ alg: 'RS256', typ: 'JWT' })));
|
||||
const now = Math.floor(Date.now() / 1000);
|
||||
const payload = base64url(Buffer.from(JSON.stringify({
|
||||
iat: now - 60,
|
||||
exp: now + 600,
|
||||
iss: appId
|
||||
})));
|
||||
|
||||
const sigInput = header + '.' + payload;
|
||||
const sign = crypto.createSign('RSA-SHA256');
|
||||
sign.update(sigInput);
|
||||
sign.end();
|
||||
const signature = base64url(sign.sign(privateKey));
|
||||
|
||||
return sigInput + '.' + signature;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -215,13 +215,17 @@ async function main() {
|
|||
try {
|
||||
plan = JSON.parse(aiResponse);
|
||||
} catch (_) {
|
||||
// 尝试从回复中提取 JSON
|
||||
const jsonMatch = aiResponse.match(/\{[\s\S]*\}/);
|
||||
// 尝试从回复中提取 JSON(非贪婪匹配,找第一个完整 JSON 对象)
|
||||
const jsonMatch = aiResponse.match(/\{[\s\S]*?"actions"[\s\S]*?\}(?=\s*$|\s*\n)/);
|
||||
if (jsonMatch) {
|
||||
try {
|
||||
plan = JSON.parse(jsonMatch[0]);
|
||||
} catch (_2) {
|
||||
plan = null;
|
||||
// 降级:找所有 { } 对中最长的合法 JSON
|
||||
const allMatches = aiResponse.match(/\{[^{}]*(?:\{[^{}]*\}[^{}]*)*\}/g) || [];
|
||||
for (const m of allMatches) {
|
||||
try { plan = JSON.parse(m); if (plan.actions) break; } catch (_3) { plan = null; }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue