feat: 添加Telegram Bot通知功能
✨ 新功能: - 添加Telegram Bot通知支持 - 账目记录自动推送到Telegram - 支持多个Bot配置管理 - 支持群组和个人通知 📊 数据库: - 新增telegram_notification_configs表 - 存储Bot配置和通知类型 🔧 后端API: - GET /api/telegram/notifications - 获取所有配置 - POST /api/telegram/notifications - 创建配置 - PUT /api/telegram/notifications/:id - 更新配置 - DELETE /api/telegram/notifications/:id - 删除配置 - POST /api/telegram/test - 测试Bot配置 💬 通知功能: - 自动发送账目记录通知 - 包含交易类型、金额、分类、账户等信息 - 支持格式化显示(类型图标、状态标识) - 配置创建时自动测试有效性 📝 文档: - 添加完整的使用说明文档 - API接口说明和示例 - 常见问题解答
This commit is contained in:
27
apps/backend/api/telegram/notifications.get.ts
Normal file
27
apps/backend/api/telegram/notifications.get.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import db from '~/utils/sqlite';
|
||||
import { useResponseSuccess } from '~/utils/response';
|
||||
|
||||
export default defineEventHandler(() => {
|
||||
const configs = db
|
||||
.prepare<{ id: number; name: string; bot_token: string; chat_id: string; notification_types: string; is_enabled: number; created_at: string; updated_at: string }>(
|
||||
`
|
||||
SELECT id, name, bot_token, chat_id, notification_types, is_enabled, created_at, updated_at
|
||||
FROM telegram_notification_configs
|
||||
ORDER BY created_at DESC
|
||||
`,
|
||||
)
|
||||
.all();
|
||||
|
||||
const result = configs.map((row) => ({
|
||||
id: row.id,
|
||||
name: row.name,
|
||||
botToken: row.bot_token,
|
||||
chatId: row.chat_id,
|
||||
notificationTypes: JSON.parse(row.notification_types) as string[],
|
||||
isEnabled: row.is_enabled === 1,
|
||||
createdAt: row.created_at,
|
||||
updatedAt: row.updated_at,
|
||||
}));
|
||||
|
||||
return useResponseSuccess(result);
|
||||
});
|
||||
55
apps/backend/api/telegram/notifications.post.ts
Normal file
55
apps/backend/api/telegram/notifications.post.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import { readBody } from 'h3';
|
||||
import db from '~/utils/sqlite';
|
||||
import { useResponseError, useResponseSuccess } from '~/utils/response';
|
||||
import { testTelegramConfig } from '~/utils/telegram-bot';
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const body = await readBody(event);
|
||||
|
||||
if (!body?.name || !body?.botToken || !body?.chatId) {
|
||||
return useResponseError('缺少必填字段', -1);
|
||||
}
|
||||
|
||||
const notificationTypes = Array.isArray(body.notificationTypes)
|
||||
? body.notificationTypes
|
||||
: ['transaction'];
|
||||
|
||||
// 测试配置是否有效
|
||||
const testResult = await testTelegramConfig(body.botToken, body.chatId);
|
||||
if (!testResult.success) {
|
||||
return useResponseError(
|
||||
`Telegram配置测试失败: ${testResult.error}`,
|
||||
-1,
|
||||
);
|
||||
}
|
||||
|
||||
const now = new Date().toISOString();
|
||||
|
||||
const result = db
|
||||
.prepare<unknown, [string, string, string, string, number, string, string]>(
|
||||
`
|
||||
INSERT INTO telegram_notification_configs (name, bot_token, chat_id, notification_types, is_enabled, created_at, updated_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
`,
|
||||
)
|
||||
.run(
|
||||
body.name,
|
||||
body.botToken,
|
||||
body.chatId,
|
||||
JSON.stringify(notificationTypes),
|
||||
body.isEnabled !== false ? 1 : 0,
|
||||
now,
|
||||
now,
|
||||
);
|
||||
|
||||
return useResponseSuccess({
|
||||
id: result.lastInsertRowid,
|
||||
name: body.name,
|
||||
botToken: body.botToken,
|
||||
chatId: body.chatId,
|
||||
notificationTypes,
|
||||
isEnabled: body.isEnabled !== false,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
});
|
||||
});
|
||||
19
apps/backend/api/telegram/notifications/[id].delete.ts
Normal file
19
apps/backend/api/telegram/notifications/[id].delete.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import db from '~/utils/sqlite';
|
||||
import { useResponseError, useResponseSuccess } from '~/utils/response';
|
||||
|
||||
export default defineEventHandler((event) => {
|
||||
const id = event.context.params?.id;
|
||||
if (!id) {
|
||||
return useResponseError('缺少ID参数', -1);
|
||||
}
|
||||
|
||||
const result = db
|
||||
.prepare('DELETE FROM telegram_notification_configs WHERE id = ?')
|
||||
.run(id);
|
||||
|
||||
if (result.changes === 0) {
|
||||
return useResponseError('配置不存在或删除失败', -1);
|
||||
}
|
||||
|
||||
return useResponseSuccess({ id });
|
||||
});
|
||||
96
apps/backend/api/telegram/notifications/[id].put.ts
Normal file
96
apps/backend/api/telegram/notifications/[id].put.ts
Normal file
@@ -0,0 +1,96 @@
|
||||
import { readBody } from 'h3';
|
||||
import db from '~/utils/sqlite';
|
||||
import { useResponseError, useResponseSuccess } from '~/utils/response';
|
||||
import { testTelegramConfig } from '~/utils/telegram-bot';
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const id = event.context.params?.id;
|
||||
if (!id) {
|
||||
return useResponseError('缺少ID参数', -1);
|
||||
}
|
||||
|
||||
const body = await readBody(event);
|
||||
|
||||
// 如果更新了botToken或chatId,需要测试配置
|
||||
if (body.botToken || body.chatId) {
|
||||
const existing = db
|
||||
.prepare<{ bot_token: string; chat_id: string }>('SELECT bot_token, chat_id FROM telegram_notification_configs WHERE id = ?')
|
||||
.get(id);
|
||||
|
||||
if (!existing) {
|
||||
return useResponseError('配置不存在', -1);
|
||||
}
|
||||
|
||||
const tokenToTest = body.botToken || existing.bot_token;
|
||||
const chatIdToTest = body.chatId || existing.chat_id;
|
||||
|
||||
const testResult = await testTelegramConfig(tokenToTest, chatIdToTest);
|
||||
if (!testResult.success) {
|
||||
return useResponseError(
|
||||
`Telegram配置测试失败: ${testResult.error}`,
|
||||
-1,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const updates: string[] = [];
|
||||
const values: (string | number)[] = [];
|
||||
|
||||
if (body.name !== undefined) {
|
||||
updates.push('name = ?');
|
||||
values.push(body.name);
|
||||
}
|
||||
|
||||
if (body.botToken !== undefined) {
|
||||
updates.push('bot_token = ?');
|
||||
values.push(body.botToken);
|
||||
}
|
||||
|
||||
if (body.chatId !== undefined) {
|
||||
updates.push('chat_id = ?');
|
||||
values.push(body.chatId);
|
||||
}
|
||||
|
||||
if (body.notificationTypes !== undefined) {
|
||||
updates.push('notification_types = ?');
|
||||
values.push(JSON.stringify(body.notificationTypes));
|
||||
}
|
||||
|
||||
if (body.isEnabled !== undefined) {
|
||||
updates.push('is_enabled = ?');
|
||||
values.push(body.isEnabled ? 1 : 0);
|
||||
}
|
||||
|
||||
if (updates.length === 0) {
|
||||
return useResponseError('没有可更新的字段', -1);
|
||||
}
|
||||
|
||||
updates.push('updated_at = ?');
|
||||
values.push(new Date().toISOString());
|
||||
values.push(id);
|
||||
|
||||
db.prepare(`UPDATE telegram_notification_configs SET ${updates.join(', ')} WHERE id = ?`).run(
|
||||
...values,
|
||||
);
|
||||
|
||||
const updated = db
|
||||
.prepare<{ id: number; name: string; bot_token: string; chat_id: string; notification_types: string; is_enabled: number; created_at: string; updated_at: string }>(
|
||||
'SELECT * FROM telegram_notification_configs WHERE id = ?',
|
||||
)
|
||||
.get(id);
|
||||
|
||||
if (!updated) {
|
||||
return useResponseError('更新失败', -1);
|
||||
}
|
||||
|
||||
return useResponseSuccess({
|
||||
id: updated.id,
|
||||
name: updated.name,
|
||||
botToken: updated.bot_token,
|
||||
chatId: updated.chat_id,
|
||||
notificationTypes: JSON.parse(updated.notification_types) as string[],
|
||||
isEnabled: updated.is_enabled === 1,
|
||||
createdAt: updated.created_at,
|
||||
updatedAt: updated.updated_at,
|
||||
});
|
||||
});
|
||||
19
apps/backend/api/telegram/test.post.ts
Normal file
19
apps/backend/api/telegram/test.post.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { readBody } from 'h3';
|
||||
import { useResponseError, useResponseSuccess } from '~/utils/response';
|
||||
import { testTelegramConfig } from '~/utils/telegram-bot';
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const body = await readBody(event);
|
||||
|
||||
if (!body?.botToken || !body?.chatId) {
|
||||
return useResponseError('缺少Bot Token或Chat ID', -1);
|
||||
}
|
||||
|
||||
const result = await testTelegramConfig(body.botToken, body.chatId);
|
||||
|
||||
if (result.success) {
|
||||
return useResponseSuccess({ message: '测试消息发送成功' });
|
||||
} else {
|
||||
return useResponseError(result.error || '测试失败', -1);
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user