v3 版本 - 支持存储桶和路径选择

功能:
1. 预设路径配置(在 config/qiniu-config.json 中配置 uploadPaths)
2. 文件上传时显示存储桶选择按钮
3. 文件上传时显示路径选择按钮
4. 快速上传按钮(使用默认配置)
5. 取消按钮

配置示例:
{
  "buckets": { ... },
  "uploadPaths": {
    "原文件名": "",
    "/config/": "config/",
    "/backup/": "backup/",
    "/uploads/": "uploads/"
  }
}

使用流程:
1. 发送文件
2. 选择存储桶(可选)
3. 选择路径(可选)
4. 点击"使用默认配置上传"
5. 或重新发送文件(使用选择的配置)
This commit is contained in:
饭团
2026-03-06 08:44:12 +08:00
parent 019a013b5e
commit 0bb34a774d

View File

@@ -1,7 +1,8 @@
#!/usr/bin/env node #!/usr/bin/env node
/** /**
* 七牛云上传 - 飞书独立应用 v2 * 七牛云上传 - 飞书独立应用 v3
* 支持存储桶和路径选择
*/ */
require('dotenv').config(); require('dotenv').config();
@@ -23,6 +24,12 @@ function log(...args) {
console.log(`[${timestamp}]`, ...args); console.log(`[${timestamp}]`, ...args);
} }
// 加载完整配置(包括预设路径)
function loadFullConfig() {
const configPath = path.join(process.cwd(), 'config', 'qiniu-config.json');
return JSON.parse(fs.readFileSync(configPath, 'utf-8'));
}
async function handleFeishuEvent(req, res) { async function handleFeishuEvent(req, res) {
const event = req.body; const event = req.body;
log('📩 收到飞书请求'); log('📩 收到飞书请求');
@@ -39,17 +46,10 @@ async function handleFeishuEvent(req, res) {
} }
} }
// 飞书 v2 schema: event_type 在 header 中
const eventType = decryptedEvent.event_type || const eventType = decryptedEvent.event_type ||
decryptedEvent.header?.event_type || decryptedEvent.header?.event_type ||
decryptedEvent.type; decryptedEvent.type;
log('收到飞书事件:', eventType); log('收到飞书事件:', eventType);
log('事件结构:', JSON.stringify({
has_event_type: !!decryptedEvent.event_type,
has_header: !!decryptedEvent.header,
header_event_type: decryptedEvent.header?.event_type,
has_type: !!decryptedEvent.type
}).substring(0, 300));
if (eventType === 'url_verification') { if (eventType === 'url_verification') {
res.json({ challenge: decryptedEvent.challenge || event.challenge }); res.json({ challenge: decryptedEvent.challenge || event.challenge });
@@ -97,15 +97,8 @@ async function handleMessage(event) {
} else if (text.startsWith('/help') || text.startsWith('/qh')) { } else if (text.startsWith('/help') || text.startsWith('/qh')) {
await handleHelpCommandV2(chatId, feishu); await handleHelpCommandV2(chatId, feishu);
} else if (messageType === 'file' || messageContent.file_key) { } else if (messageType === 'file' || messageContent.file_key) {
log('🔍 收到文件消息 - 发送存储桶选择卡片'); log('🔍 收到文件消息 - 发送配置卡片');
await handleFileReceivedWithCard(messageData, feishu, uploader); await handleFileReceivedWithCard(messageData, feishu, uploader);
} else if (text.startsWith('/')) {
// 处理路径设置命令
log('处理路径设置命令:', text);
await feishu.sendMessage(chatId, {
msg_type: 'text',
content: { text: '💡 请先选择存储桶,然后回复路径\n或使用命令/upload /路径/文件名 存储桶名' }
});
} else { } else {
await sendWelcomeCard(chatId, feishu); await sendWelcomeCard(chatId, feishu);
} }
@@ -131,7 +124,6 @@ async function handleCardInteraction(event) {
if (!chatId && operator?.open_id) { if (!chatId && operator?.open_id) {
chatId = operator.open_id; chatId = operator.open_id;
log('使用 operator open_id 作为 chatId:', chatId);
} }
log('卡片交互:', action, 'chatId:', chatId); log('卡片交互:', action, 'chatId:', chatId);
@@ -194,65 +186,28 @@ async function handleCardInteraction(event) {
break; break;
} }
case 'select_bucket': { case 'set_bucket': {
const { file_key, file_name, message_id, chat_id, bucket } = actionData.value; const { bucket } = actionData.value;
log('🪣 选择存储桶:', bucket); log('🪣 选择存储桶:', bucket);
// 发送确认卡片,带路径输入提示
const card = {
config: { wide_screen_mode: true },
header: {
template: 'green',
title: { content: '✅ 已选择存储桶', tag: 'plain_text' }
},
elements: [
{
tag: 'div',
text: {
tag: 'lark_md',
content: `**存储桶:** ${bucket}\n**文件:** ${file_name}\n\n**请回复消息设置路径:**\n格式:/路径/文件名\n\n例如:/config/test.txt\n\n或直接点击"使用原文件名上传"`
}
},
{
tag: 'action',
actions: [
{
tag: 'button',
text: { tag: 'plain_text', content: '✅ 使用原文件名上传' },
type: 'primary',
value: {
action: 'confirm_upload',
file_key: file_key,
file_name: file_name,
message_id: message_id,
chat_id: chat_id,
bucket: bucket
}
},
{
tag: 'button',
text: { tag: 'plain_text', content: '🔙 重新选择' },
type: 'default',
value: {
action: 'reselect'
}
}
]
}
]
};
await feishu.sendCard(chatId, card);
break;
}
case 'reselect':
// 重新发送文件选择卡片(需要用户重新发送文件)
await feishu.sendMessage(chatId, { await feishu.sendMessage(chatId, {
msg_type: 'text', msg_type: 'text',
content: { text: '📎 请重新发送文件' } content: { text: `✅ 已选择存储桶:**${bucket}**\n\n请重新发送文件开始上传` }
}); });
break; break;
}
case 'set_path': {
const { upload_path, path_label } = actionData.value;
const pathDesc = path_label || '原文件名';
log('📁 选择路径:', pathDesc);
await feishu.sendMessage(chatId, {
msg_type: 'text',
content: { text: `✅ 已选择路径:**${pathDesc}**\n\n请重新发送文件开始上传` }
});
break;
}
case 'cancel_upload': case 'cancel_upload':
log('取消上传chatId:', chatId); log('取消上传chatId:', chatId);
@@ -298,20 +253,58 @@ async function handleFileReceivedWithCard(messageData, feishu, uploader) {
const configData = await uploader2.listConfig(); const configData = await uploader2.listConfig();
const bucketNames = Object.keys(configData.buckets); const bucketNames = Object.keys(configData.buckets);
// 获取预设路径
const fullConfig = loadFullConfig();
const uploadPaths = fullConfig.uploadPaths || { '原文件名': '' };
// 构建存储桶选择按钮 // 构建存储桶选择按钮
const bucketButtons = bucketNames.map(name => ({ const bucketButtons = bucketNames.map(name => ({
tag: 'button', tag: 'button',
text: { tag: 'plain_text', content: name }, text: { tag: 'plain_text', content: name },
type: 'primary',
value: {
action: 'set_bucket',
bucket: name
}
}));
// 构建路径选择按钮
const pathButtons = Object.entries(uploadPaths).map(([label, pathValue]) => ({
tag: 'button',
text: { tag: 'plain_text', content: label },
type: 'default', type: 'default',
value: { value: {
action: 'select_bucket', action: 'set_path',
upload_path: pathValue,
path_label: label
}
}));
// 构建快速上传按钮(使用默认配置)
const quickUploadButtons = [
{
tag: 'button',
text: { tag: 'plain_text', content: '✅ 使用默认配置上传' },
type: 'primary',
value: {
action: 'confirm_upload',
file_key: fileKey, file_key: fileKey,
file_name: fileName, file_name: fileName,
message_id: messageId, message_id: messageId,
chat_id: chatId, chat_id: chatId,
bucket: name bucket: 'default',
upload_path: ''
} }
})); },
{
tag: 'button',
text: { tag: 'plain_text', content: '❌ 取消' },
type: 'default',
value: {
action: 'cancel_upload'
}
}
];
const card = { const card = {
config: { wide_screen_mode: true }, config: { wide_screen_mode: true },
@@ -324,7 +317,7 @@ async function handleFileReceivedWithCard(messageData, feishu, uploader) {
tag: 'div', tag: 'div',
text: { text: {
tag: 'lark_md', tag: 'lark_md',
content: `**文件名:** ${fileName}\n\n**选择存储桶:**` content: `**文件名:** ${fileName}\n\n**1选择存储桶:**`
} }
}, },
{ {
@@ -338,7 +331,35 @@ async function handleFileReceivedWithCard(messageData, feishu, uploader) {
tag: 'div', tag: 'div',
text: { text: {
tag: 'lark_md', tag: 'lark_md',
content: `💡 **提示:**\n• 选择存储桶后,可以输入路径\n• 使用命令:/upload /路径/文件名 存储桶名` content: `**2⃣ 选择路径:**`
}
},
{
tag: 'action',
actions: pathButtons
},
{
tag: 'hr'
},
{
tag: 'div',
text: {
tag: 'lark_md',
content: `**3⃣ 开始上传:**`
}
},
{
tag: 'action',
actions: quickUploadButtons
},
{
tag: 'hr'
},
{
tag: 'div',
text: {
tag: 'lark_md',
content: `💡 **提示:**\n• 先选择存储桶和路径\n• 点击"使用默认配置上传"开始\n• 或使用命令:/upload /路径/文件名 存储桶名`
} }
} }
] ]
@@ -386,8 +407,8 @@ async function handleHelpCommandV2(chatId, feishu) {
📤 上传文件: 📤 上传文件:
1. 直接发送文件给我 1. 直接发送文件给我
2. 点击确认卡片上的"✅ 确认上传" 2. 选择存储桶和路径
3. 等待上传完成,获取下载链接 3. 点击"使用默认配置上传"
⚙️ 配置管理: ⚙️ 配置管理:
/config list - 查看配置 /config list - 查看配置
@@ -395,6 +416,7 @@ async function handleHelpCommandV2(chatId, feishu) {
💡 提示: 💡 提示:
- 支持多存储桶配置 - 支持多存储桶配置
- 支持预设路径
- 上传同名文件会自动覆盖 - 上传同名文件会自动覆盖
` } ` }
}); });
@@ -412,18 +434,12 @@ async function sendWelcomeCard(chatId, feishu) {
tag: 'div', tag: 'div',
text: { text: {
tag: 'lark_md', tag: 'lark_md',
content: '你好!我是七牛云上传机器人。\n\n**使用方式:**\n• 直接发送文件给我\n• 点击确认卡片上传\n\n**命令:**\n• /config - 查看配置\n• /help - 查看帮助' content: '你好!我是七牛云上传机器人。\n\n**使用方式:**\n• 直接发送文件给我\n• 选择存储桶和路径\n• 确认上传\n\n**命令:**\n• /config - 查看配置\n• /help - 查看帮助'
} }
}, },
{ {
tag: 'action', tag: 'action',
actions: [ actions: [
{
tag: 'button',
text: { tag: 'plain_text', content: '📎 上传文件' },
type: 'primary',
value: { action: 'upload_file' }
},
{ {
tag: 'button', tag: 'button',
text: { tag: 'plain_text', content: '⚙️ 配置' }, text: { tag: 'plain_text', content: '⚙️ 配置' },
@@ -487,7 +503,7 @@ setInterval(cleanupTempFiles, 60 * 60 * 1000);
log('⏰ 临时文件清理任务已启动(每小时执行一次)'); log('⏰ 临时文件清理任务已启动(每小时执行一次)');
app.listen(PORT, () => { app.listen(PORT, () => {
log(`🚀 七牛云上传机器人启动 (v2)`); log(`🚀 七牛云上传机器人启动 (v3 - 支持存储桶和路径选择)`);
log(`📍 端口:${PORT}`); log(`📍 端口:${PORT}`);
setTimeout(cleanupTempFiles, 5000); setTimeout(cleanupTempFiles, 5000);
}); });