feat: 飞书单点登录和通知功能

This commit is contained in:
2026-02-03 11:38:16 +08:00
parent c657fbe01a
commit 78ebc67e2a
18 changed files with 2136 additions and 105 deletions

View File

@@ -21,6 +21,14 @@ const config = {
baseUrl: process.env.JIRA_BASE_URL,
botId: process.env.JIRA_BOT_ID || '',
botName: process.env.JIRA_BOT_NAME
},
lark: {
appId: process.env.LARK_APP_ID || '',
appSecret: process.env.LARK_APP_SECRET || '',
webhookUrl: process.env.LARK_WEBHOOK_URL || '',
webhookSecret: process.env.LARK_WEBHOOK_SECRET || '',
redirectUri: process.env.LARK_REDIRECT_URI || '',
adminIds: (process.env.ADMIN_IDS || '').split(',').map(id => id.trim()).filter(id => id)
}
};

121
src/config/larkRules.js Normal file
View File

@@ -0,0 +1,121 @@
/**
* 飞书提醒规则管理
*/
const fs = require('fs');
const path = require('path');
const crypto = require('crypto');
const logger = require('../utils/logger');
const DATA_FILE = path.join(__dirname, '../../data/larkRules.json');
//确保数据文件存在
function ensureDataFile() {
const dir = path.dirname(DATA_FILE);
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
if (!fs.existsSync(DATA_FILE)) {
fs.writeFileSync(DATA_FILE, JSON.stringify({ rules: [] }, null, 2));
}
}
//读取所有规则
function getAllRules() {
ensureDataFile();
try {
const data = fs.readFileSync(DATA_FILE, 'utf-8');
return JSON.parse(data).rules || [];
} catch (e) {
logger.error('[LarkRules] Failed to read rules:', e.message);
return [];
}
}
//保存所有规则
function saveAllRules(rules) {
ensureDataFile();
fs.writeFileSync(DATA_FILE, JSON.stringify({ rules }, null, 2));
}
//获取单个规则
function getRule(id) {
return getAllRules().find(r => r.id === id);
}
//创建规则
function createRule(rule) {
const rules = getAllRules();
const newRule = {
id: crypto.randomUUID(),
createdAt: new Date().toISOString(),
enabled: true,
...rule
};
rules.push(newRule);
saveAllRules(rules);
logger.info(`[LarkRules] created rule: ${newRule.name} (${newRule.id})`);
return newRule;
}
//更新规则
function updateRule(id, updates) {
const rules = getAllRules();
const index = rules.findIndex(r => r.id === id);
if (index === -1) {
throw new Error('Rule not found');
}
rules[index] = { ...rules[index], ...updates, id, updatedAt: new Date().toISOString() };
saveAllRules(rules);
logger.info(`[LarkRules] updated rule: ${rules[index].name} (${id})`);
return rules[index];
}
//删除规则
function deleteRule(id) {
const rules = getAllRules();
const index = rules.findIndex(r => r.id === id);
if (index === -1) {
throw new Error('Rule not found');
}
const deleted = rules.splice(index, 1)[0];
saveAllRules(rules);
logger.info(`[LarkRules] deleted rule: ${deleted.name} (${id})`);
return deleted;
}
//根据事件类型获取匹配的规则
function getRulesByEvent(eventType) {
return getAllRules().filter(r => r.enabled && r.event === eventType);
}
//支持的事件类型
const EVENT_TYPES = [
{ value: 'issue.opened', label: '新建 Issue' },
{ value: 'issue.closed', label: '关闭 Issue' },
{ value: 'issue.reopened', label: '重开 Issue' },
{ value: 'issue.assigned', label: '指派 Issue' },
{ value: 'issue.unassigned', label: '取消指派 Issue' },
{ value: 'issue.edited', label: '编辑 Issue' },
{ value: 'issue.label_updated', label: 'Issue 标签变更' },
{ value: 'issue.milestoned', label: 'Issue 里程碑变更' },
{ value: 'issue.comment', label: 'Issue 评论' },
{ value: 'pr.opened', label: '新建合并请求' },
{ value: 'pr.closed', label: '关闭合并请求' },
{ value: 'pr.merged', label: '合并请求已合并' },
{ value: 'pr.reopened', label: '重开合并请求' },
{ value: 'release.created', label: '创建发布' },
{ value: 'release.published', label: '发布版本' },
{ value: 'release.deleted', label: '删除发布' },
{ value: 'release.updated', label: '更新发布' }
];
module.exports = {
getAllRules,
getRule,
createRule,
updateRule,
deleteRule,
getRulesByEvent,
EVENT_TYPES
};

View File

@@ -2,7 +2,7 @@ const fs = require('fs');
const path = require('path');
//读取配置文件路径
const configPath = path.join(__dirname, '../../mappings.json');
const configPath = path.join(__dirname, '../../data/mappings.json');
let mappingsConfig = null;