feat: channels/audit UI unify, apply flow hardening, bump v1.1.12
This commit is contained in:
@@ -8,6 +8,7 @@ import (
|
||||
"time"
|
||||
|
||||
"xiaji-go/internal/service"
|
||||
"xiaji-go/models"
|
||||
|
||||
"github.com/tencent-connect/botgo"
|
||||
"github.com/tencent-connect/botgo/dto"
|
||||
@@ -15,6 +16,7 @@ import (
|
||||
"github.com/tencent-connect/botgo/event"
|
||||
"github.com/tencent-connect/botgo/openapi"
|
||||
"github.com/tencent-connect/botgo/token"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// DefaultUserID 统一用户ID,使所有平台共享同一份账本
|
||||
@@ -24,10 +26,12 @@ type QQBot struct {
|
||||
api openapi.OpenAPI
|
||||
finance *service.FinanceService
|
||||
credentials *token.QQBotCredentials
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewQQBot(appID string, secret string, finance *service.FinanceService) *QQBot {
|
||||
func NewQQBot(db *gorm.DB, appID string, secret string, finance *service.FinanceService) *QQBot {
|
||||
return &QQBot{
|
||||
db: db,
|
||||
finance: finance,
|
||||
credentials: &token.QQBotCredentials{
|
||||
AppID: appID,
|
||||
@@ -37,42 +41,35 @@ func NewQQBot(appID string, secret string, finance *service.FinanceService) *QQB
|
||||
}
|
||||
|
||||
func (b *QQBot) Start(ctx context.Context) {
|
||||
// 创建 token source 并启动自动刷新
|
||||
tokenSource := token.NewQQBotTokenSource(b.credentials)
|
||||
if err := token.StartRefreshAccessToken(ctx, tokenSource); err != nil {
|
||||
log.Printf("❌ QQ Bot Token 刷新失败: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// 初始化 OpenAPI
|
||||
b.api = botgo.NewOpenAPI(b.credentials.AppID, tokenSource).WithTimeout(5 * time.Second)
|
||||
|
||||
// 注册事件处理器
|
||||
_ = event.RegisterHandlers(
|
||||
b.groupATMessageHandler(),
|
||||
b.c2cMessageHandler(),
|
||||
b.channelATMessageHandler(),
|
||||
)
|
||||
|
||||
// 获取 WebSocket 接入信息
|
||||
wsInfo, err := b.api.WS(ctx, nil, "")
|
||||
if err != nil {
|
||||
log.Printf("❌ QQ Bot 获取 WS 信息失败: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// 设置 intents: 群聊和C2C (1<<25) + 公域消息 (1<<30)
|
||||
intent := dto.Intent(1<<25 | 1<<30)
|
||||
|
||||
log.Printf("🚀 QQ Bot 已启动 (WebSocket, shards=%d)", wsInfo.Shards)
|
||||
|
||||
// 启动 session manager (阻塞)
|
||||
if err := botgo.NewSessionManager().Start(wsInfo, tokenSource, &intent); err != nil {
|
||||
log.Printf("❌ QQ Bot WebSocket 断开: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// isCommand 判断是否匹配命令关键词
|
||||
func isCommand(text string, keywords ...string) bool {
|
||||
for _, kw := range keywords {
|
||||
if text == kw {
|
||||
@@ -82,7 +79,18 @@ func isCommand(text string, keywords ...string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// processAndReply 通用记账处理
|
||||
func (b *QQBot) isDuplicate(eventID string) bool {
|
||||
if b.db == nil || strings.TrimSpace(eventID) == "" {
|
||||
return false
|
||||
}
|
||||
var existed models.MessageDedup
|
||||
if err := b.db.Where("platform = ? AND event_id = ?", "qqbot_official", eventID).First(&existed).Error; err == nil {
|
||||
return true
|
||||
}
|
||||
_ = b.db.Create(&models.MessageDedup{Platform: "qqbot_official", EventID: eventID, ProcessedAt: time.Now()}).Error
|
||||
return false
|
||||
}
|
||||
|
||||
func (b *QQBot) processAndReply(userID string, content string) string {
|
||||
text := strings.TrimSpace(message.ETLInput(content))
|
||||
if text == "" {
|
||||
@@ -91,7 +99,6 @@ func (b *QQBot) processAndReply(userID string, content string) string {
|
||||
|
||||
today := time.Now().Format("2006-01-02")
|
||||
|
||||
// 命令处理
|
||||
switch {
|
||||
case isCommand(text, "帮助", "help", "/help", "/start", "菜单", "功能"):
|
||||
return "🦞 虾记记账\n\n" +
|
||||
@@ -173,17 +180,18 @@ func (b *QQBot) processAndReply(userID string, content string) string {
|
||||
return fmt.Sprintf("✅ 已记入【%s】:%.2f元\n📝 备注:%s", category, amountYuan, text)
|
||||
}
|
||||
|
||||
// channelATMessageHandler 频道@机器人消息
|
||||
func (b *QQBot) channelATMessageHandler() event.ATMessageEventHandler {
|
||||
return func(ev *dto.WSPayload, data *dto.WSATMessageData) error {
|
||||
eventID := "qq:channel:" + strings.TrimSpace(data.ID)
|
||||
if b.isDuplicate(eventID) {
|
||||
return nil
|
||||
}
|
||||
log.Printf("📩 inbound platform=qqbot_official event=%s chat=%s user=%s text=%q", eventID, data.ChannelID, data.Author.ID, strings.TrimSpace(message.ETLInput(data.Content)))
|
||||
reply := b.processAndReply(data.Author.ID, data.Content)
|
||||
if reply == "" {
|
||||
return nil
|
||||
}
|
||||
_, err := b.api.PostMessage(context.Background(), data.ChannelID, &dto.MessageToCreate{
|
||||
MsgID: data.ID,
|
||||
Content: reply,
|
||||
})
|
||||
_, err := b.api.PostMessage(context.Background(), data.ChannelID, &dto.MessageToCreate{MsgID: data.ID, Content: reply})
|
||||
if err != nil {
|
||||
log.Printf("QQ频道消息发送失败: %v", err)
|
||||
}
|
||||
@@ -191,17 +199,18 @@ func (b *QQBot) channelATMessageHandler() event.ATMessageEventHandler {
|
||||
}
|
||||
}
|
||||
|
||||
// groupATMessageHandler 群@机器人消息
|
||||
func (b *QQBot) groupATMessageHandler() event.GroupATMessageEventHandler {
|
||||
return func(ev *dto.WSPayload, data *dto.WSGroupATMessageData) error {
|
||||
eventID := "qq:group:" + strings.TrimSpace(data.ID)
|
||||
if b.isDuplicate(eventID) {
|
||||
return nil
|
||||
}
|
||||
log.Printf("📩 inbound platform=qqbot_official event=%s chat=%s user=%s text=%q", eventID, data.GroupID, data.Author.ID, strings.TrimSpace(message.ETLInput(data.Content)))
|
||||
reply := b.processAndReply(data.Author.ID, data.Content)
|
||||
if reply == "" {
|
||||
return nil
|
||||
}
|
||||
_, err := b.api.PostGroupMessage(context.Background(), data.GroupID, dto.MessageToCreate{
|
||||
MsgID: data.ID,
|
||||
Content: reply,
|
||||
})
|
||||
_, err := b.api.PostGroupMessage(context.Background(), data.GroupID, dto.MessageToCreate{MsgID: data.ID, Content: reply})
|
||||
if err != nil {
|
||||
log.Printf("QQ群消息发送失败: %v", err)
|
||||
}
|
||||
@@ -209,17 +218,18 @@ func (b *QQBot) groupATMessageHandler() event.GroupATMessageEventHandler {
|
||||
}
|
||||
}
|
||||
|
||||
// c2cMessageHandler C2C 私聊消息
|
||||
func (b *QQBot) c2cMessageHandler() event.C2CMessageEventHandler {
|
||||
return func(ev *dto.WSPayload, data *dto.WSC2CMessageData) error {
|
||||
eventID := "qq:c2c:" + strings.TrimSpace(data.ID)
|
||||
if b.isDuplicate(eventID) {
|
||||
return nil
|
||||
}
|
||||
log.Printf("📩 inbound platform=qqbot_official event=%s chat=%s user=%s text=%q", eventID, data.Author.ID, data.Author.ID, strings.TrimSpace(message.ETLInput(data.Content)))
|
||||
reply := b.processAndReply(data.Author.ID, data.Content)
|
||||
if reply == "" {
|
||||
return nil
|
||||
}
|
||||
_, err := b.api.PostC2CMessage(context.Background(), data.Author.ID, dto.MessageToCreate{
|
||||
MsgID: data.ID,
|
||||
Content: reply,
|
||||
})
|
||||
_, err := b.api.PostC2CMessage(context.Background(), data.Author.ID, dto.MessageToCreate{MsgID: data.ID, Content: reply})
|
||||
if err != nil {
|
||||
log.Printf("QQ私聊消息发送失败: %v", err)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user