# Telegram Bot 智能通知与交互系统 - 详细设计说明书 **项目代号**: NaughtyMan **文档版本**: v2.0 **编制日期**: 2025年10月24日 **更新说明**: 根据PRD v2.1需求更新 *** ## 系统架构设计 ### 总体架构 系统采用**分层微服务架构**,由接口层、业务逻辑层、数据访问层和外部服务层构成。架构设计遵循高内聚低耦合原则,确保各模块独立演进能力。 ```mermaid graph TB subgraph "客户端层" A1[ProjectOctopus] A2[ProjectTonyStack] A3[Telegram用户端] end subgraph "接入层" B1[RESTful API网关
Gin框架] B2[Telegram Webhook
Long Polling] end subgraph "业务逻辑层" C1[认证服务
AuthService] C2[消息调度服务
MessageDispatcher] C3[提醒管理服务
ReminderService] C4[AI交互服务
AIService] C5[速率控制服务
RateLimiter] C6[用户管理服务
UserManagementService] C7[指令管理服务
CommandService] end subgraph "数据访问层" D1[消息队列
PriorityQueue] D2[SQLite数据库
WAL模式] D3[缓存层
sync.Map] end subgraph "外部服务层" E1[Telegram Bot API] E2[OpenRouter API] E3[SOCKS5/HTTP代理] end A1 --> B1 A2 --> B1 A3 --> B2 B1 --> C1 B1 --> C2 B2 --> C3 B2 --> C4 C2 --> C5 C3 --> D2 C4 --> D3 C2 --> D1 C5 --> E1 C4 --> E2 E1 -.代理.-> E3 C6 --> D2 C7 --> D2 B2 --> C6 B2 --> C7 ``` ### 技术选型说明 | 技术组件 | 选型方案 | 选型理由 | | :-- | :-- | :-- | | 开发语言 | Go 1.21+ | 原生并发支持、高性能、跨平台编译、内存安全 | | Web框架 | Gin v1.9 | 轻量级、路由高效、中间件生态完善、社区活跃 | | 数据库 | SQLite 3.40+ | 零配置部署、事务ACID保证、跨平台兼容、适合中小规模数据 | | Bot SDK | tgbotapi v5 | 官方API完整封装、长连接稳定、支持代理配置 | | AI SDK | OpenAI Go SDK | 统一接口、支持OpenRouter等多提供商、流式响应完善 | | 并发控制 | sync.Map + Channel | 原生并发安全结构、无锁化设计、性能优越 | | 日志框架 | zap | 结构化日志、高性能、灵活的日志等级控制 | *** ## 核心模块详细设计 ### 认证与安全模块 #### 双阶段Token认证流程 ```mermaid sequenceDiagram participant C as 客户端 participant G as API网关 participant A as 认证服务 participant D as 数据库 C->>G: POST /auth/handshake
{api_key} G->>A: 验证API Key A->>D: 查询白名单 D-->>A: 返回权限范围 A->>A: 生成Challenge码
(UUID + 时间戳) A->>D: 存储Challenge
(60s有效期) A-->>C: {challenge, expire_at} Note over C: 计算签名
HMAC-SHA256(challenge+api_secret) C->>G: POST /auth/token
{api_key, challenge, signature} G->>A: 验证签名 A->>D: 查询Challenge D-->>A: Challenge数据 A->>A: 对比签名
检查时效 A->>A: 生成JWT Token
(6h有效期) A->>D: 记录Token映射 A-->>C: {access_token, refresh_token} ``` #### Token数据结构设计 **JWT Payload规范**: ```go type TokenClaims struct { ApiKey string `json:"api_key"` Whitelist []int64 `json:"whitelist"` // 加密的授权目标ID列表 Permissions []string `json:"permissions"` // send_message, query_status IssuedAt int64 `json:"iat"` ExpiresAt int64 `json:"exp"` RefreshAfter int64 `json:"rfa"` // 允许刷新的时间阈值 } ``` **数据库表结构**: ```sql CREATE TABLE api_credentials ( id INTEGER PRIMARY KEY AUTOINCREMENT, api_key TEXT UNIQUE NOT NULL, api_secret TEXT NOT NULL, -- BCRYPT加密存储 project TEXT NOT NULL, -- octopus/tonystack whitelist_ids TEXT, -- JSON数组存储 status INTEGER DEFAULT 1, -- 1:启用 0:禁用 created_at DATETIME DEFAULT CURRENT_TIMESTAMP, last_used DATETIME, INDEX idx_api_key (api_key) ); CREATE TABLE token_sessions ( id INTEGER PRIMARY KEY AUTOINCREMENT, api_key TEXT NOT NULL, challenge TEXT, challenge_expire DATETIME, access_token TEXT UNIQUE, refresh_token TEXT UNIQUE, token_expire DATETIME, client_ip TEXT, user_agent TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, INDEX idx_access_token (access_token), INDEX idx_challenge (challenge, challenge_expire) ); ``` #### 白名单验证机制 **中间件实现**: ```go func AuthMiddleware() gin.HandlerFunc { return func(c *gin.Context) { token := c.GetHeader("Authorization") // 1. 解析JWT Token claims, err := jwt.Parse(token, secretKey) if err != nil || time.Now().Unix() > claims.ExpiresAt { c.JSON(401, gin.H{"error": "无效或过期的Token"}) c.Abort() return } // 2. 验证目标ID在白名单内 var req MessageRequest c.BindJSON(&req) if !contains(claims.Whitelist, req.TargetID) { c.JSON(403, gin.H{"error": "目标未授权"}) c.Abort() return } // 3. 注入上下文 c.Set("api_key", claims.ApiKey) c.Set("project", claims.Project) c.Next() } } ``` *** ### 消息通知系统 #### 消息调度核心架构 ```mermaid graph LR A[API请求] --> B{消息等级判断} B -->|INFO| C[时段检查器
08:00-23:00] B -->|WARNING| D[即时队列] B -->|CRITICAL| D C -->|时段内| E[批量聚合器
5分钟窗口] C -->|时段外| F[延迟队列
次日8:00] E --> G[优先级队列] D --> G F --> G G --> H[消息发送器选择] H --> I[速率限制器] I --> J[Telegram API] J -->|成功| K[消息日志] J -->|失败| L{重试判断} L -->|次数<3| M[指数退避重试] L -->|次数≥3| N[死信队列] M --> I ``` #### 消息发送器包装层设计 **[新增]** 为支持不同类型的消息发送需求,系统实现了发送器包装层: **基础发送器接口**: ```go type MessageSender interface { Send(chatID int64, content string, options ...SendOption) error ValidateTarget(chatID int64) error } type SendOption func(*SendConfig) type SendConfig struct { ParseMode string DisablePreview bool ReplyToMessageID int } ``` **普通消息发送器实现**: ```go type PlainMessageSender struct { bot *tgbotapi.BotAPI rateLimiter *RateLimiter whitelist *WhitelistChecker logger *zap.Logger } func (s *PlainMessageSender) Send(chatID int64, text string, options ...SendOption) error { // 1. 白名单验证 if err := s.whitelist.Check(chatID); err != nil { return fmt.Errorf("whitelist check failed: %w", err) } // 2. 应用配置选项 config := &SendConfig{} for _, opt := range options { opt(config) } // 3. 等待速率限制器许可 if !s.rateLimiter.AllowSend(chatID, false) { return ErrRateLimitExceeded } // 4. 发送消息 msg := tgbotapi.NewMessage(chatID, text) if config.ParseMode != "" { msg.ParseMode = config.ParseMode } msg.DisableWebPagePreview = config.DisablePreview _, err := s.bot.Send(msg) return err } ``` **AI消息发送器实现**: ```go type AIMessageSender struct { baseSender *PlainMessageSender logger *zap.Logger } func (s *AIMessageSender) SendMarkdown(chatID int64, markdown string) error { // 转义Markdown特殊字符为MarkdownV2格式 escaped := escapeMarkdownV2(markdown) // 使用基础发送器发送,指定ParseMode return s.baseSender.Send(chatID, escaped, func(cfg *SendConfig) { cfg.ParseMode = "MarkdownV2" cfg.DisablePreview = true }) } func (s *AIMessageSender) SendStreamUpdate(chatID int64, messageID int, content string) error { // 检查速率限制(编辑消息) if !s.baseSender.rateLimiter.AllowSend(chatID, true) { return ErrRateLimitExceeded } edit := tgbotapi.NewEditMessageText(chatID, messageID, content) edit.ParseMode = "MarkdownV2" _, err := s.baseSender.bot.Send(edit) return err } // Markdown V2格式转义 func escapeMarkdownV2(text string) string { specialChars := []string{"_", "*", "[", "]", "(", ")", "~", "`", ">", "#", "+", "-", "=", "|", "{", "}", ".", "!"} escaped := text for _, char := range specialChars { escaped = strings.ReplaceAll(escaped, char, "\\"+char) } return escaped } ``` **发送器工厂**: ```go type SenderFactory struct { bot *tgbotapi.BotAPI rateLimiter *RateLimiter whitelist *WhitelistChecker } func (f *SenderFactory) NewPlainSender() *PlainMessageSender { return &PlainMessageSender{ bot: f.bot, rateLimiter: f.rateLimiter, whitelist: f.whitelist, logger: zap.L(), } } func (f *SenderFactory) NewAISender() *AIMessageSender { return &AIMessageSender{ baseSender: f.NewPlainSender(), logger: zap.L(), } } ``` #### 优先级队列实现 **数据结构设计**: ```go type Message struct { ID string ChatID int64 Level string // INFO/WARNING/CRITICAL Content string Metadata map[string]interface{} Priority int // CRITICAL:3, WARNING:2, INFO:1 RetryCount int ScheduledAt time.Time CreatedAt time.Time } type PriorityQueue struct { heap *priorityHeap // 优先级小顶堆 mutex sync.RWMutex notEmpty chan struct{} // 通知消费者 deadLetter chan Message // 死信队列通道 } // 堆排序规则 func (pq *priorityHeap) Less(i, j int) bool { // 1. 优先级高的优先 if pq[i].Priority != pq[j].Priority { return pq[i].Priority > pq[j].Priority } // 2. 相同优先级按时间戳排序 return pq[i].ScheduledAt.Before(pq[j].ScheduledAt) } ``` #### 消息模板渲染引擎 **模板定义规范**: ```go type MessageTemplate struct { Project string Level string Template string } var templates = map[string]MessageTemplate{ "octopus_critical": { Project: "octopus", Level: "CRITICAL", Template: `🚨 【严重告警】{{.Title}} 📍 服务器: {{.Metadata.server}} 📊 详情: {{.Body}} ⏰ 时间: {{formatTime .Metadata.timestamp}} 🔗 查看详情: {{.Metadata.dashboard_url}} #服务器监控 #CRITICAL`, }, "tonystack_warning": { Project: "tonystack", Level: "WARNING", Template: `⚠️ 【风险提示】{{.Title}} 💰 标的: {{.Metadata.symbol}} 📈 触发值: {{.Body}} 📉 当前价格: {{.Metadata.current_price}} ⏰ 时间: {{formatTime .Metadata.timestamp}} #金融监控 #WARNING`, }, } // 渲染函数 func RenderTemplate(msg Message) (string, error) { key := fmt.Sprintf("%s_%s", msg.Project, strings.ToLower(msg.Level)) tmpl, exists := templates[key] if !exists { return "", errors.New("模板不存在") } t := template.New("message").Funcs(template.FuncMap{ "formatTime": func(ts string) string { t, _ := time.Parse(time.RFC3339, ts) return t.Format("2006-01-02 15:04:05") }, }) t, _ = t.Parse(tmpl.Template) var buf bytes.Buffer t.Execute(&buf, msg) return buf.String(), nil } ``` #### 速率限制器设计 **令牌桶算法实现**: ```go type TokenBucket struct { capacity int // 桶容量 tokens int // 当前令牌数 refillRate time.Duration // 补充速率 lastRefill time.Time mutex sync.Mutex } func (tb *TokenBucket) Take() bool { tb.mutex.Lock() defer tb.mutex.Unlock() // 计算应补充的令牌数 now := time.Now() elapsed := now.Sub(tb.lastRefill) refillCount := int(elapsed / tb.refillRate) if refillCount > 0 { tb.tokens = min(tb.capacity, tb.tokens + refillCount) tb.lastRefill = now } // 尝试获取令牌 if tb.tokens > 0 { tb.tokens-- return true } return false } type RateLimiter struct { globalBucket *TokenBucket // 全局30/s chatBuckets sync.Map // map[int64]*TokenBucket editBucket *TokenBucket // 编辑消息限流 } func NewRateLimiter() *RateLimiter { return &RateLimiter{ globalBucket: &TokenBucket{ capacity: 30, tokens: 30, refillRate: time.Second / 30, lastRefill: time.Now(), }, editBucket: &TokenBucket{ capacity: 20, tokens: 20, refillRate: time.Minute / 20, lastRefill: time.Now(), }, } } func (rl *RateLimiter) AllowSend(chatID int64, isEdit bool) bool { // 1. 检查全局限流 if !rl.globalBucket.Take() { return false } // 2. 编辑消息检查专用桶 if isEdit && !rl.editBucket.Take() { rl.globalBucket.tokens++ // 归还全局令牌 return false } // 3. 检查单聊天限流 bucket := rl.getChatBucket(chatID) if !bucket.Take() { rl.globalBucket.tokens++ return false } return true } ``` *** ### Telegram管理功能模块 **[新增]** #### 用户群组管理服务 **服务架构**: ```mermaid graph TB A[Telegram Update] --> B[Update Handler] B --> C{消息类型} C -->|新用户消息| D[用户信息提取器] C -->|群组消息| E[群组信息提取器] D --> F[UserProfileService] E --> G[GroupProfileService] F --> H[SQLite存储] G --> H I[管理API] --> F I --> G ``` **用户档案服务实现**: ```go type UserProfileService struct { db *sql.DB cache *sync.Map // 缓存用户信息 logger *zap.Logger } type UserProfile struct { ID int64 UserID int64 Username string FirstName string LastName string PhoneNumber string LanguageCode string IsBot bool LastInteraction time.Time } func (s *UserProfileService) UpsertFromUpdate(update tgbotapi.Update) error { user := update.Message.From if user == nil { return nil } profile := UserProfile{ UserID: user.ID, Username: user.UserName, FirstName: user.FirstName, LastName: user.LastName, LanguageCode: user.LanguageCode, IsBot: user.IsBot, LastInteraction: time.Now(), } // Upsert到数据库 _, err := s.db.Exec(` INSERT INTO user_profiles (user_id, username, first_name, last_name, language_code, is_bot, last_interaction, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP) ON CONFLICT(user_id) DO UPDATE SET username = excluded.username, first_name = excluded.first_name, last_name = excluded.last_name, language_code = excluded.language_code, last_interaction = excluded.last_interaction, updated_at = CURRENT_TIMESTAMP `, profile.UserID, profile.Username, profile.FirstName, profile.LastName, profile.LanguageCode, profile.IsBot, profile.LastInteraction) if err == nil { s.cache.Store(profile.UserID, profile) } return err } func (s *UserProfileService) GetByUserID(userID int64) (*UserProfile, error) { // 先查缓存 if cached, ok := s.cache.Load(userID); ok { profile := cached.(UserProfile) return &profile, nil } // 查数据库 var profile UserProfile err := s.db.QueryRow(` SELECT id, user_id, username, first_name, last_name, phone_number, language_code, is_bot, last_interaction FROM user_profiles WHERE user_id = ? `, userID).Scan(&profile.ID, &profile.UserID, &profile.Username, &profile.FirstName, &profile.LastName, &profile.PhoneNumber, &profile.LanguageCode, &profile.IsBot, &profile.LastInteraction) if err == nil { s.cache.Store(userID, profile) } return &profile, err } func (s *UserProfileService) ListAllUsers() ([]UserProfile, error) { rows, err := s.db.Query(` SELECT id, user_id, username, first_name, last_name, phone_number, language_code, is_bot, last_interaction FROM user_profiles ORDER BY last_interaction DESC `) if err != nil { return nil, err } defer rows.Close() var profiles []UserProfile for rows.Next() { var p UserProfile rows.Scan(&p.ID, &p.UserID, &p.Username, &p.FirstName, &p.LastName, &p.PhoneNumber, &p.LanguageCode, &p.IsBot, &p.LastInteraction) profiles = append(profiles, p) } return profiles, nil } ``` **群组档案服务实现**: ```go type GroupProfileService struct { db *sql.DB cache *sync.Map logger *zap.Logger } type GroupProfile struct { ID int64 GroupID int64 Title string Type string // group, supergroup, channel MemberCount int LastInteraction time.Time } func (s *GroupProfileService) UpsertFromUpdate(update tgbotapi.Update) error { chat := update.Message.Chat if chat.Type == "private" { return nil // 私聊不处理 } profile := GroupProfile{ GroupID: chat.ID, Title: chat.Title, Type: chat.Type, MemberCount: chat.MembersCount, LastInteraction: time.Now(), } _, err := s.db.Exec(` INSERT INTO group_profiles (group_id, title, type, member_count, last_interaction, updated_at) VALUES (?, ?, ?, ?, ?, CURRENT_TIMESTAMP) ON CONFLICT(group_id) DO UPDATE SET title = excluded.title, type = excluded.type, member_count = excluded.member_count, last_interaction = excluded.last_interaction, updated_at = CURRENT_TIMESTAMP `, profile.GroupID, profile.Title, profile.Type, profile.MemberCount, profile.LastInteraction) if err == nil { s.cache.Store(profile.GroupID, profile) } return err } ``` #### 机器人功能管理服务 **动态指令注册系统**: ```go type BotCommand struct { ID int Command string // 指令名称(如 "notify") Description string // 指令描述 HandlerName string // 处理器标识 IsEnabled bool // 是否启用 Scope string // user/group/all } type CommandService struct { db *sql.DB bot *tgbotapi.BotAPI handlers map[string]CommandHandler // 处理器注册表 registeredCmd sync.Map // 已注册指令缓存 logger *zap.Logger } type CommandHandler interface { Handle(update tgbotapi.Update) error Description() string } // 初始化预定义指令 func (s *CommandService) RegisterBuiltinCommands() error { builtinCommands := []BotCommand{ {Command: "start", Description: "开始使用机器人", HandlerName: "StartHandler", IsEnabled: true, Scope: "all"}, {Command: "help", Description: "查看帮助信息", HandlerName: "HelpHandler", IsEnabled: true, Scope: "all"}, {Command: "notify", Description: "创建定时提醒", HandlerName: "NotifyHandler", IsEnabled: true, Scope: "user"}, {Command: "notify_list", Description: "查看提醒列表", HandlerName: "NotifyListHandler", IsEnabled: true, Scope: "user"}, } for _, cmd := range builtinCommands { _, err := s.db.Exec(` INSERT INTO bot_commands (command, description, handler_name, is_enabled, scope) VALUES (?, ?, ?, ?, ?) ON CONFLICT(command) DO NOTHING `, cmd.Command, cmd.Description, cmd.HandlerName, cmd.IsEnabled, cmd.Scope) if err != nil { return err } } return s.SyncToTelegram() } // 同步指令到Telegram func (s *CommandService) SyncToTelegram() error { rows, err := s.db.Query(` SELECT command, description FROM bot_commands WHERE is_enabled = 1 `) if err != nil { return err } defer rows.Close() var commands []tgbotapi.BotCommand for rows.Next() { var cmd, desc string rows.Scan(&cmd, &desc) commands = append(commands, tgbotapi.BotCommand{ Command: cmd, Description: desc, }) } // 设置Bot命令列表 cfg := tgbotapi.NewSetMyCommands(commands...) _, err = s.bot.Request(cfg) return err } // 注册指令处理器 func (s *CommandService) RegisterHandler(handlerName string, handler CommandHandler) { s.handlers[handlerName] = handler } // 分发指令 func (s *CommandService) DispatchCommand(update tgbotapi.Update) error { if !update.Message.IsCommand() { return nil } command := update.Message.Command() // 查询指令配置 var handlerName string var isEnabled bool err := s.db.QueryRow(` SELECT handler_name, is_enabled FROM bot_commands WHERE command = ? `, command).Scan(&handlerName, &isEnabled) if err != nil || !isEnabled { return fmt.Errorf("command not found or disabled: %s", command) } // 执行处理器 handler, exists := s.handlers[handlerName] if !exists { return fmt.Errorf("handler not registered: %s", handlerName) } return handler.Handle(update) } // API: 添加新指令 func (s *CommandService) AddCommand(cmd BotCommand) error { _, err := s.db.Exec(` INSERT INTO bot_commands (command, description, handler_name, is_enabled, scope) VALUES (?, ?, ?, ?, ?) `, cmd.Command, cmd.Description, cmd.HandlerName, cmd.IsEnabled, cmd.Scope) if err == nil { s.SyncToTelegram() } return err } // API: 更新指令 func (s *CommandService) UpdateCommand(command string, updates map[string]interface{}) error { // 动态构建UPDATE语句 setParts := []string{} args := []interface{}{} for key, value := range updates { setParts = append(setParts, fmt.Sprintf("%s = ?", key)) args = append(args, value) } args = append(args, command) _, err := s.db.Exec(fmt.Sprintf(` UPDATE bot_commands SET %s, updated_at = CURRENT_TIMESTAMP WHERE command = ? `, strings.Join(setParts, ", ")), args...) if err == nil { s.SyncToTelegram() } return err } // API: 删除指令 func (s *CommandService) DeleteCommand(command string) error { _, err := s.db.Exec(`DELETE FROM bot_commands WHERE command = ?`, command) if err == nil { s.SyncToTelegram() } return err } // API: 列出所有指令 func (s *CommandService) ListCommands() ([]BotCommand, error) { rows, err := s.db.Query(` SELECT id, command, description, handler_name, is_enabled, scope FROM bot_commands ORDER BY command `) if err != nil { return nil, err } defer rows.Close() var commands []BotCommand for rows.Next() { var cmd BotCommand rows.Scan(&cmd.ID, &cmd.Command, &cmd.Description, &cmd.HandlerName, &cmd.IsEnabled, &cmd.Scope) commands = append(commands, cmd) } return commands, nil } ``` *** ### 定时提醒系统 #### 状态机设计 ```mermaid stateDiagram-v2 [*] --> 等待输入: /notify命令 等待输入 --> 选择日期: 点击日期按钮 选择日期 --> 选择时间: 确认日期 选择时间 --> 配置重复: 确认时间 配置重复 --> 输入内容: 选择重复规则 输入内容 --> 确认创建: 输入提醒文本 确认创建 --> 已激活: 提交成功 已激活 --> 已触发: 到达触发时间 已触发 --> 已激活: 有重复规则 已触发 --> 已完成: 无重复规则 已激活 --> 已删除: 用户删除 已完成 --> [*] 已删除 --> [*] ``` #### 日历选择器UI实现 **InlineKeyboard布局**: ```go func GenerateCalendar(year int, month time.Month) [][]tgbotapi.InlineKeyboardButton { firstDay := time.Date(year, month, 1, 0, 0, 0, 0, time.Local) lastDay := firstDay.AddDate(0, 1, -1) keyboard := [][]tgbotapi.InlineKeyboardButton{ // 月份导航行 { tgbotapi.NewInlineKeyboardButtonData("◀", fmt.Sprintf("cal_prev_%d_%d", year, month)), tgbotapi.NewInlineKeyboardButtonData(fmt.Sprintf("%d年%d月", year, month), "cal_ignore"), tgbotapi.NewInlineKeyboardButtonData("▶", fmt.Sprintf("cal_next_%d_%d", year, month)), }, // 星期标题行 { tgbotapi.NewInlineKeyboardButtonData("日", "cal_ignore"), tgbotapi.NewInlineKeyboardButtonData("一", "cal_ignore"), tgbotapi.NewInlineKeyboardButtonData("二", "cal_ignore"), tgbotapi.NewInlineKeyboardButtonData("三", "cal_ignore"), tgbotapi.NewInlineKeyboardButtonData("四", "cal_ignore"), tgbotapi.NewInlineKeyboardButtonData("五", "cal_ignore"), tgbotapi.NewInlineKeyboardButtonData("六", "cal_ignore"), }, } // 日期网格生成 currentWeek := []tgbotapi.InlineKeyboardButton{} for weekday := 0; weekday < int(firstDay.Weekday()); weekday++ { currentWeek = append(currentWeek, tgbotapi.NewInlineKeyboardButtonData(" ", "cal_ignore")) } for day := 1; day <= lastDay.Day(); day++ { date := time.Date(year, month, day, 0, 0, 0, 0, time.Local) callback := fmt.Sprintf("cal_select_%s", date.Format("2006-01-02")) // 过期日期禁用 label := fmt.Sprintf("%d", day) if date.Before(time.Now().Truncate(24 * time.Hour)) { callback = "cal_ignore" label = "×" } currentWeek = append(currentWeek, tgbotapi.NewInlineKeyboardButtonData(label, callback)) // 每行7天 if len(currentWeek) == 7 { keyboard = append(keyboard, currentWeek) currentWeek = []tgbotapi.InlineKeyboardButton{} } } if len(currentWeek) > 0 { keyboard = append(keyboard, currentWeek) } return keyboard } ``` #### 提醒调度器实现 **定时扫描机制**: ```go type ReminderScheduler struct { db *sql.DB bot *tgbotapi.BotAPI ticker *time.Ticker stopChan chan struct{} } func (rs *ReminderScheduler) Start() { rs.ticker = time.NewTicker(30 * time.Second) go func() { for { select { case <-rs.ticker.C: rs.processPendingReminders() case <-rs.stopChan: return } } }() } func (rs *ReminderScheduler) processPendingReminders() { // 查询需触发的提醒(未来5分钟内) rows, _ := rs.db.Query(` SELECT id, chat_id, user_id, content, repeat_rule, next_trigger FROM reminders WHERE status = 1 AND next_trigger <= datetime('now', '+5 minutes') ORDER BY next_trigger `) defer rows.Close() for rows.Next() { var r Reminder rows.Scan(&r.ID, &r.ChatID, &r.UserID, &r.Content, &r.RepeatRule, &r.NextTrigger) // 等待到精确触发时间 wait := r.NextTrigger.Sub(time.Now()) if wait > 0 { time.Sleep(wait) } // 发送提醒消息 msg := tgbotapi.NewMessage(r.ChatID, fmt.Sprintf("⏰ 提醒\\n\\n%s", r.Content)) rs.bot.Send(msg) // 更新下次触发时间 if r.RepeatRule != "" { nextTrigger := calculateNextTrigger(r.NextTrigger, r.RepeatRule) rs.db.Exec(`UPDATE reminders SET next_trigger = ? WHERE id = ?`, nextTrigger, r.ID) } else { rs.db.Exec(`UPDATE reminders SET status = 0 WHERE id = ?`, r.ID) } } } func calculateNextTrigger(current time.Time, rule string) time.Time { switch rule { case "daily": return current.AddDate(0, 0, 1) case "weekly": return current.AddDate(0, 0, 7) case "biweekly": return current.AddDate(0, 0, 14) case "monthly": return current.AddDate(0, 1, 0) default: // 解析RRULE格式 return parseRRule(current, rule) } } ``` *** ### AI智能体系统 #### 触发机制 **[更新]** **双模式触发判断**: ```mermaid graph TB A[接收Telegram消息] --> B{聊天类型判断} B -->|私聊private| C[直接触发AI] B -->|群聊group/supergroup| D{检查@机器人} D -->|是| C D -->|否| E[忽略消息] C --> F[提取上下文] F --> G[调用AI API] G --> H[流式响应处理] H --> I[发送回复] ``` **触发逻辑实现**: ```go type AITriggerService struct { bot *tgbotapi.BotAPI botUsername string aiService *AIService } func (s *AITriggerService) ShouldRespond(update tgbotapi.Update) bool { msg := update.Message if msg == nil { return false } // 私聊模式: 所有消息都响应 if msg.Chat.IsPrivate() { return true } // 群聊模式: 检查是否@了机器人 if msg.Chat.IsGroup() || msg.Chat.IsSuperGroup() { // 方式1: 检查Entities中的mention for _, entity := range msg.Entities { if entity.Type == "mention" { mention := msg.Text[entity.Offset:entity.Offset+entity.Length] if mention == "@"+s.botUsername { return true } } } // 方式2: 检查命令是否针对本Bot if msg.IsCommand() { parts := strings.Split(msg.Command(), "@") if len(parts) == 2 && parts[1] == s.botUsername { return true } if len(parts) == 1 { return true // 无@的命令也响应 } } } return false } func (s *AITriggerService) HandleMessage(update tgbotapi.Update) error { if !s.ShouldRespond(update) { return nil } // 移除@机器人的部分 cleanText := strings.ReplaceAll(update.Message.Text, "@"+s.botUsername, "") // 构建上下文并调用AI return s.aiService.ProcessMessage(update.Message.Chat.ID, update.Message.From.ID, cleanText) } ``` #### AI集成实现 **[更新]** **OpenRouter优先方案**: ```mermaid graph LR A[AI请求] --> B[AIService] B --> C{提供商选择} C -->|首选| D[OpenRouterProvider] C -->|备选| E[其他Provider] D --> F[OpenAI Go SDK] F --> G[OpenRouter API] G --> H[Claude/Gemini/GPT] H --> I[流式响应] I --> J[AIMessageSender] J --> K[Telegram Bot API] ``` **OpenRouter适配器实现**: ```go import ( "github.com/sashabaranov/go-openai" ) type OpenRouterProvider struct { client *openai.Client model string logger *zap.Logger } func NewOpenRouterProvider(apiKey string, model string) *OpenRouterProvider { config := openai.DefaultConfig(apiKey) config.BaseURL = "https://openrouter.ai/api/v1" // 可选: 添加自定义Headers config.HTTPClient = &http.Client{ Transport: &customTransport{ base: http.DefaultTransport, headers: map[string]string{ "HTTP-Referer": "https://github.com/your-repo", // 可选 "X-Title": "NaughtyMan Bot", // 可选 }, }, } return &OpenRouterProvider{ client: openai.NewClientWithConfig(config), model: model, logger: zap.L(), } } func (p *OpenRouterProvider) Chat(ctx context.Context, messages []ChatMessage, stream bool) (<-chan string, error) { // 转换消息格式 openaiMessages := make([]openai.ChatCompletionMessage, len(messages)) for i, msg := range messages { openaiMessages[i] = openai.ChatCompletionMessage{ Role: msg.Role, Content: msg.Content, Name: msg.Name, } } if stream { return p.streamChat(ctx, openaiMessages) } return p.blockingChat(ctx, openaiMessages) } func (p *OpenRouterProvider) streamChat(ctx context.Context, messages []openai.ChatCompletionMessage) (<-chan string, error) { ch := make(chan string, 10) req := openai.ChatCompletionRequest{ Model: p.model, Messages: messages, Stream: true, } stream, err := p.client.CreateChatCompletionStream(ctx, req) if err != nil { return nil, err } go func() { defer close(ch) defer stream.Close() for { response, err := stream.Recv() if errors.Is(err, io.EOF) { break } if err != nil { p.logger.Error("Stream error", zap.Error(err)) break } if len(response.Choices) > 0 { content := response.Choices[^0].Delta.Content if content != "" { ch <- content } } } }() return ch, nil } func (p *OpenRouterProvider) blockingChat(ctx context.Context, messages []openai.ChatCompletionMessage) (<-chan string, error) { ch := make(chan string, 1) req := openai.ChatCompletionRequest{ Model: p.model, Messages: messages, Stream: false, } resp, err := p.client.CreateChatCompletion(ctx, req) if err != nil { return nil, err } go func() { defer close(ch) if len(resp.Choices) > 0 { ch <- resp.Choices[^0].Message.Content } }() return ch, nil } func (p *OpenRouterProvider) Name() string { return "OpenRouter" } func (p *OpenRouterProvider) SupportStream() bool { return true } func (p *OpenRouterProvider) SupportReasoning() bool { // 取决于使用的模型 return strings.Contains(p.model, "claude-3.5") || strings.Contains(p.model, "gpt-4") } ``` **推荐模型配置**: ```go var RecommendedModels = map[string]string{ "reasoning": "anthropic/claude-3.5-sonnet", // 思考链最佳 "long_context": "google/gemini-pro-1.5", // 长上下文 "balanced": "openai/gpt-4-turbo", // 平衡性能 "fast": "anthropic/claude-3-haiku", // 快速响应 } ``` #### 上下文管理 **消息引用策略**: ```go type ContextManager struct { db *sql.DB cache *sync.Map // chatID -> []Message } func (cm *ContextManager) BuildContext(chatID int64, currentMessage string, userID int64) ([]ChatMessage, error) { // 1. 查询最近3条历史消息 rows, err := cm.db.Query(` SELECT user_id, user_message, ai_response, created_at FROM ai_conversations WHERE chat_id = ? ORDER BY created_at DESC LIMIT 3 `, chatID) if err != nil { return nil, err } defer rows.Close() var history []struct { UserID int64 UserMsg string AIResponse string CreatedAt time.Time } for rows.Next() { var h struct { UserID int64 UserMsg string AIResponse string CreatedAt time.Time } rows.Scan(&h.UserID, &h.UserMsg, &h.AIResponse, &h.CreatedAt) history = append(history, h) } // 2. 构建消息列表(倒序排列以保持时间顺序) messages := []ChatMessage{ { Role: "system", Content: "你是一个友好的Telegram群助手,请用简洁专业的语言回答问题。", }, } // 历史消息按时间正序添加 for i := len(history) - 1; i >= 0; i-- { messages = append(messages, ChatMessage{ Role: "user", Content: history[i].UserMsg, Name: fmt.Sprintf("user%d", history[i].UserID), }, ChatMessage{ Role: "assistant", Content: history[i].AIResponse, }, ) } // 3. 添加当前消息 messages = append(messages, ChatMessage{ Role: "user", Content: currentMessage, Name: fmt.Sprintf("user%d", userID), }) return messages, nil } ``` #### 流式响应处理 **分段更新策略**: ```go type StreamHandler struct { aiSender *AIMessageSender updateBuffer strings.Builder lastUpdate time.Time messageID int chatID int64 startTime time.Time } func (sh *StreamHandler) ProcessStream(ctx context.Context, stream <-chan string) error { // 1. 发送初始消息 initialMsg := tgbotapi.NewMessage(sh.chatID, "🤔 正在思考...") sent, err := sh.aiSender.baseSender.bot.Send(initialMsg) if err != nil { return err } sh.messageID = sent.MessageID sh.lastUpdate = time.Now() sh.startTime = time.Now() // 2. 处理流式分片 for { select { case chunk, ok := <-stream: if !ok { // 流结束,发送最终消息 duration := time.Since(sh.startTime) finalContent := sh.updateBuffer.String() + fmt.Sprintf("\n\n✅ 回答完成 | 用时%.1fs", duration.Seconds()) sh.sendUpdate(finalContent) return nil } sh.updateBuffer.WriteString(chunk) // 触发更新条件: 500字符 OR 5秒间隔 shouldUpdate := sh.updateBuffer.Len() >= 500 || time.Since(sh.lastUpdate) >= 5*time.Second if shouldUpdate { sh.sendUpdate(sh.updateBuffer.String()) } case <-ctx.Done(): return ctx.Err() } } } func (sh *StreamHandler) sendUpdate(content string) { // 使用AI消息发送器的流式更新方法 err := sh.aiSender.SendStreamUpdate(sh.chatID, sh.messageID, content) if err != nil { sh.aiSender.logger.Warn("Failed to update message", zap.Error(err)) } else { sh.lastUpdate = time.Now() } } ``` #### AI交互日志记录 **[新增]** **日志记录服务**: ```go type AIInteractionLogger struct { db *sql.DB userService *UserProfileService groupService *GroupProfileService logger *zap.Logger } type AIInteractionLog struct { Timestamp time.Time UserID int64 Username string PhoneNumber string GroupID int64 GroupName string MessageText string AIResponse string Duration time.Duration Provider string Model string TokensUsed int } func (l *AIInteractionLogger) LogInteraction(log AIInteractionLog) error { // 1. 获取用户详细信息 userProfile, err := l.userService.GetByUserID(log.UserID) if err == nil { log.Username = userProfile.Username log.PhoneNumber = userProfile.PhoneNumber } // 2. 获取群组信息(如果是群聊) if log.GroupID != 0 { groupProfile, err := l.groupService.GetByGroupID(log.GroupID) if err == nil { log.GroupName = groupProfile.Title } } // 3. 写入数据库 _, err = l.db.Exec(` INSERT INTO ai_interaction_log ( user_id, username, phone_number, group_id, group_name, message_text, ai_response, duration_ms, provider, model, tokens_used, created_at ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) `, log.UserID, log.Username, log.PhoneNumber, log.GroupID, log.GroupName, log.MessageText, log.AIResponse, log.Duration.Milliseconds(), log.Provider, log.Model, log.TokensUsed, log.Timestamp) // 4. 输出结构化日志 l.logger.Info("AI Interaction", zap.Int64("user_id", log.UserID), zap.String("username", log.Username), zap.String("phone", log.PhoneNumber), zap.Int64("group_id", log.GroupID), zap.String("group_name", log.GroupName), zap.String("message", log.MessageText), zap.Duration("duration", log.Duration), zap.String("provider", log.Provider), zap.String("model", log.Model), ) return err } // 集成到AI服务中 func (s *AIService) ProcessMessage(chatID int64, userID int64, message string) error { startTime := time.Now() // 构建上下文 context, _ := s.contextManager.BuildContext(chatID, message, userID) // 调用AI stream, err := s.provider.Chat(s.ctx, context, true) if err != nil { return err } // 处理流式响应 handler := &StreamHandler{ aiSender: s.aiSender, chatID: chatID, } var responseBuilder strings.Builder for chunk := range stream { responseBuilder.WriteString(chunk) handler.updateBuffer.WriteString(chunk) // ... 更新逻辑 } duration := time.Since(startTime) response := responseBuilder.String() // 记录交互日志 s.interactionLogger.LogInteraction(AIInteractionLog{ Timestamp: startTime, UserID: userID, GroupID: chatID, MessageText: message, AIResponse: response, Duration: duration, Provider: s.provider.Name(), Model: s.config.Model, }) return nil } ``` *** ## 数据库设计 ### ER图 **[更新]** ```mermaid erDiagram API_CREDENTIALS ||--o{ TOKEN_SESSIONS : generates API_CREDENTIALS ||--o{ MESSAGE_LOG : sends WHITELIST ||--o{ API_CREDENTIALS : authorizes REMINDERS }o--|| USER_PROFILES : belongs_to USER_PROFILES ||--o{ AI_CONVERSATIONS : participates GROUP_PROFILES ||--o{ AI_CONVERSATIONS : contains BOT_COMMANDS }o--|| USER_PROFILES : registered_by API_CREDENTIALS { int id PK string api_key UK string api_secret string project json whitelist_ids int status datetime created_at } TOKEN_SESSIONS { int id PK string api_key FK string challenge datetime challenge_expire string access_token UK string refresh_token UK datetime token_expire string client_ip } WHITELIST { int id PK string type bigint entity_id UK string alias int status datetime added_at } MESSAGE_LOG { int id PK string message_id bigint chat_id string level string project FK text content string status int retry_count datetime sent_at } REMINDERS { int id PK bigint user_id FK bigint chat_id text content datetime trigger_time string repeat_rule datetime next_trigger int status } USER_PROFILES { int id PK bigint user_id UK string username string first_name string last_name string phone_number string language_code int is_bot datetime last_interaction } GROUP_PROFILES { int id PK bigint group_id UK string title string type int member_count datetime last_interaction } BOT_COMMANDS { int id PK string command UK string description string handler_name int is_enabled string scope } AI_CONVERSATIONS { int id PK bigint chat_id bigint user_id FK text user_message text ai_response string provider string model int tokens_used datetime created_at } AI_INTERACTION_LOG { int id PK bigint user_id FK string username string phone_number bigint group_id string group_name text message_text text ai_response int duration_ms string provider string model int tokens_used datetime created_at } ``` ### 核心表DDL **[更新]** ```sql -- 用户档案表 CREATE TABLE user_profiles ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id BIGINT UNIQUE NOT NULL, username TEXT, first_name TEXT, last_name TEXT, phone_number TEXT, language_code TEXT, is_bot INTEGER DEFAULT 0, last_interaction DATETIME, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, INDEX idx_user_id (user_id), INDEX idx_last_interaction (last_interaction) ); -- 群组档案表 CREATE TABLE group_profiles ( id INTEGER PRIMARY KEY AUTOINCREMENT, group_id BIGINT UNIQUE NOT NULL, title TEXT, type TEXT, -- group, supergroup, channel member_count INTEGER, last_interaction DATETIME, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, INDEX idx_group_id (group_id), INDEX idx_last_interaction (last_interaction) ); -- 机器人指令表 CREATE TABLE bot_commands ( id INTEGER PRIMARY KEY AUTOINCREMENT, command TEXT UNIQUE NOT NULL, description TEXT, handler_name TEXT NOT NULL, is_enabled INTEGER DEFAULT 1, scope TEXT DEFAULT 'all', -- user/group/all created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, INDEX idx_command (command), INDEX idx_enabled (is_enabled) ); -- AI交互日志表 CREATE TABLE ai_interaction_log ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id BIGINT NOT NULL, username TEXT, phone_number TEXT, group_id BIGINT, group_name TEXT, message_text TEXT, ai_response TEXT, duration_ms INTEGER, provider TEXT, model TEXT, tokens_used INTEGER, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, INDEX idx_user_id (user_id), INDEX idx_group_id (group_id), INDEX idx_created_at (created_at) ); -- 消息日志表优化索引 CREATE INDEX idx_message_log_composite ON message_log(status, created_at); CREATE INDEX idx_message_log_chat ON message_log(chat_id, sent_at DESC); -- 提醒表优化索引 CREATE INDEX idx_reminders_trigger ON reminders(next_trigger, status) WHERE status = 1; CREATE INDEX idx_reminders_user ON reminders(user_id, status); -- Token会话表优化索引 CREATE INDEX idx_token_expire ON token_sessions(token_expire) WHERE token_expire > datetime('now'); -- AI对话表优化索引 CREATE INDEX idx_ai_conversations_chat ON ai_conversations(chat_id, created_at DESC); ``` *** ## 接口设计 ### RESTful API规范 #### 管理API接口 **[新增]** **1. 用户管理接口** ``` GET /api/v1/admin/users Authorization: Bearer {admin_token} 响应: { "users": [ { "user_id": 123456789, "username": "john_doe", "first_name": "John", "last_name": "Doe", "phone_number": "+1234567890", "language_code": "zh-CN", "last_interaction": "2025-10-24T15:30:00Z" } ], "total": 42 } ``` ``` GET /api/v1/admin/user/:id Authorization: Bearer {admin_token} 响应: { "user_id": 123456789, "username": "john_doe", "first_name": "John", "last_name": "Doe", "phone_number": "+1234567890", "language_code": "zh-CN", "is_bot": false, "last_interaction": "2025-10-24T15:30:00Z", "created_at": "2025-10-01T08:00:00Z", "interaction_count": 156 } ``` **2. 群组管理接口** ``` GET /api/v1/admin/groups Authorization: Bearer {admin_token} 响应: { "groups": [ { "group_id": -987654321, "title": "开发团队讨论组", "type": "supergroup", "member_count": 25, "last_interaction": "2025-10-24T16:00:00Z" } ], "total": 8 } ``` **3. 指令管理接口** ``` GET /api/v1/admin/commands Authorization: Bearer {admin_token} 响应: { "commands": [ { "id": 1, "command": "notify", "description": "创建定时提醒", "handler_name": "NotifyHandler", "is_enabled": true, "scope": "user" } ] } ``` ``` POST /api/v1/admin/commands Authorization: Bearer {admin_token} Content-Type: application/json { "command": "custom_cmd", "description": "自定义指令", "handler_name": "CustomHandler", "is_enabled": true, "scope": "all" } 响应: { "id": 10, "message": "指令创建成功" } ``` ``` PUT /api/v1/admin/commands/:cmd Authorization: Bearer {admin_token} Content-Type: application/json { "description": "更新后的描述", "is_enabled": false } 响应: { "message": "指令更新成功" } ``` ``` DELETE /api/v1/admin/commands/:cmd Authorization: Bearer {admin_token} 响应: { "message": "指令删除成功" } ``` ``` POST /api/v1/admin/commands/sync Authorization: Bearer {admin_token} 响应: { "message": "指令已同步到Telegram", "synced_count": 8 } ``` #### 消息发送接口 **[更新]** **1. 模板消息发送** ``` POST /api/v1/message/send Authorization: Bearer {access_token} Content-Type: application/json { "target_type": "user|group", "target_id": "123456789", "level": "info|warning|critical", "project": "octopus|tonystack", "content": { "title": "服务器CPU告警", "body": "服务器CPU使用率达85%", "metadata": { "server": "prod-web-01", "timestamp": "2025-10-21T10:23:00Z" } } } 响应: { "message_id": "msg_uuid_12345", "status": "queued", "scheduled_at": "2025-10-21T10:23:00Z" } ``` **2. 普通消息发送** **[新增]** ``` POST /api/v1/message/send-plain Authorization: Bearer {access_token} Content-Type: application/json { "target_type": "user|group", "target_id": "123456789", "text": "这是一条普通文本消息", "options": { "disable_preview": true } } 响应: { "message_id": "123", "status": "sent", "sent_at": "2025-10-24T15:45:00Z" } ``` **3. Markdown消息发送** **[新增]** ``` POST /api/v1/message/send-markdown Authorization: Bearer {access_token} Content-Type: application/json { "target_type": "user|group", "target_id": "123456789", "markdown": "**粗体** *斜体* `代码`\n\n- 列表项1\n- 列表项2" } 响应: { "message_id": "124", "status": "sent", "sent_at": "2025-10-24T15:46:00Z" } ``` *** ## 项目实施 ### 实施计划 **[更新]** #### 阶段一:基础设施 (2周) - 速率限制器实现与测试 - 数据库表结构设计(包含管理表) - 白名单+Token认证系统 - 代理支持与连接测试 #### 阶段二:核心功能 (3周) - 消息通知API开发 - 分级模板渲染引擎 - 消息发送器包装层实现 - 消息队列与重试机制 - 定时提醒CRUD功能 #### 阶段三:管理功能 (2周) **[新增]** - 用户档案采集与存储 - 群组信息管理服务 - 动态指令注册系统 - 管理API接口开发 - 预定义功能自动注册 #### 阶段四:AI功能 (3周) - OpenRouter API集成(使用OpenAI SDK) - 私聊/群聊双模式触发 - 上下文管理器实现 - 流式响应处理 - AI交互日志记录 - InlineKeyboard交互流程 #### 阶段五:上线准备 (1周) - 集成测试与修复 - 性能压测与优化 - 文档编写 - 监控告警配置 - 灰度发布 ### 团队组织 | 角色 | 职责 | 人数 | | :-- | :-- | :-- | | 技术负责人 | 架构设计审核、技术选型决策、风险评估 | 1 | | 后端工程师 | 核心业务逻辑开发、API接口实现 | 2 | | AI集成工程师 | OpenRouter适配、流式响应处理 | 1 | | 测试工程师 | 单元测试、集成测试、压力测试 | 1 | | 运维工程师 | 部署脚本、监控配置、应急响应 | 1 | ### 交付物清单 - [x] 源代码仓库(Git) - [x] 数据库迁移脚本 - [x] API接口文档(OpenAPI 3.0) - [x] 部署操作手册 - [x] 监控告警配置文件 - [x] 性能测试报告 - [x] 用户使用指南 *** ## 附录 ### 术语表 | 术语 | 英文 | 说明 | | :-- | :-- | :-- | | 令牌桶 | Token Bucket | 速率限制算法,以固定速率补充令牌实现流控 | | 死信队列 | Dead Letter Queue | 存储多次失败消息的特殊队列 | | 指数退避 | Exponential Backoff | 重试间隔呈指数增长的策略 | | WAL模式 | Write-Ahead Logging | SQLite日志模式,提升并发写入性能 | | RRULE | Recurrence Rule | iCalendar重复规则标准(RFC 5545) | ### 参考资料 - [Telegram Bot API官方文档](https://core.telegram.org/bots/api) - [OpenRouter API文档](https://openrouter.ai/docs) - [OpenAI Go SDK](https://github.com/sashabaranov/go-openai) - [Go语言并发模式](https://go.dev/blog/pipelines) - [SQLite WAL模式详解](https://www.sqlite.org/wal.html) - [JWT最佳实践](https://tools.ietf.org/html/rfc8725) *** ### 文档变更记录 **v2.0 (2025-10-24更新)** **主要变更**: 1. 新增消息发送器包装层(PlainMessageSender和AIMessageSender) 2. 新增Telegram管理功能模块(用户群组管理、机器人功能管理) 3. 更新AI触发机制为私聊全响应+群聊@模式 4. 更新AI实现方案为OpenRouter优先(使用OpenAI Go SDK) 5. 新增AI交互日志记录系统 6. 更新数据库ER图和表结构 7. 新增管理API接口文档 8. 调整实施计划,新增管理功能阶段 9. 术语规范: "机器人交互功能" -> "机器人预定义功能" **文档编制完成** | 总计约1.8万字 | 包含11个Mermaid图表 | 覆盖15个核心设计模块