52 KiB
52 KiB
Telegram Bot 智能通知与交互系统 - 详细设计说明书
项目代号: NaughtyMan 文档版本: v2.0 编制日期: 2025年10月24日 更新说明: 根据PRD v2.1需求更新
系统架构设计
总体架构
系统采用分层微服务架构,由接口层、业务逻辑层、数据访问层和外部服务层构成。架构设计遵循高内聚低耦合原则,确保各模块独立演进能力。
graph TB
subgraph "客户端层"
A1[ProjectOctopus]
A2[ProjectTonyStack]
A3[Telegram用户端]
end
subgraph "接入层"
B1[RESTful API网关<br/>Gin框架]
B2[Telegram Webhook<br/>Long Polling]
end
subgraph "业务逻辑层"
C1[认证服务<br/>AuthService]
C2[消息调度服务<br/>MessageDispatcher]
C3[提醒管理服务<br/>ReminderService]
C4[AI交互服务<br/>AIService]
C5[速率控制服务<br/>RateLimiter]
C6[用户管理服务<br/>UserManagementService]
C7[指令管理服务<br/>CommandService]
end
subgraph "数据访问层"
D1[消息队列<br/>PriorityQueue]
D2[SQLite数据库<br/>WAL模式]
D3[缓存层<br/>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认证流程
sequenceDiagram
participant C as 客户端
participant G as API网关
participant A as 认证服务
participant D as 数据库
C->>G: POST /auth/handshake<br/>{api_key}
G->>A: 验证API Key
A->>D: 查询白名单
D-->>A: 返回权限范围
A->>A: 生成Challenge码<br/>(UUID + 时间戳)
A->>D: 存储Challenge<br/>(60s有效期)
A-->>C: {challenge, expire_at}
Note over C: 计算签名<br/>HMAC-SHA256(challenge+api_secret)
C->>G: POST /auth/token<br/>{api_key, challenge, signature}
G->>A: 验证签名
A->>D: 查询Challenge
D-->>A: Challenge数据
A->>A: 对比签名<br/>检查时效
A->>A: 生成JWT Token<br/>(6h有效期)
A->>D: 记录Token映射
A-->>C: {access_token, refresh_token}
Token数据结构设计
JWT Payload规范:
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"` // 允许刷新的时间阈值
}
数据库表结构:
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)
);
白名单验证机制
中间件实现:
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()
}
}
消息通知系统
消息调度核心架构
graph LR
A[API请求] --> B{消息等级判断}
B -->|INFO| C[时段检查器<br/>08:00-23:00]
B -->|WARNING| D[即时队列]
B -->|CRITICAL| D
C -->|时段内| E[批量聚合器<br/>5分钟窗口]
C -->|时段外| F[延迟队列<br/>次日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
消息发送器包装层设计 [新增]
为支持不同类型的消息发送需求,系统实现了发送器包装层:
基础发送器接口:
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
}
普通消息发送器实现:
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消息发送器实现:
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
}
发送器工厂:
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(),
}
}
优先级队列实现
数据结构设计:
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)
}
消息模板渲染引擎
模板定义规范:
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
}
速率限制器设计
令牌桶算法实现:
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管理功能模块 [新增]
用户群组管理服务
服务架构:
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
用户档案服务实现:
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
}
群组档案服务实现:
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
}
机器人功能管理服务
动态指令注册系统:
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
}
定时提醒系统
状态机设计
stateDiagram-v2
[*] --> 等待输入: /notify命令
等待输入 --> 选择日期: 点击日期按钮
选择日期 --> 选择时间: 确认日期
选择时间 --> 配置重复: 确认时间
配置重复 --> 输入内容: 选择重复规则
输入内容 --> 确认创建: 输入提醒文本
确认创建 --> 已激活: 提交成功
已激活 --> 已触发: 到达触发时间
已触发 --> 已激活: 有重复规则
已触发 --> 已完成: 无重复规则
已激活 --> 已删除: 用户删除
已完成 --> [*]
已删除 --> [*]
日历选择器UI实现
InlineKeyboard布局:
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
}
提醒调度器实现
定时扫描机制:
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智能体系统
触发机制 [更新]
双模式触发判断:
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[发送回复]
触发逻辑实现:
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优先方案:
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适配器实现:
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")
}
推荐模型配置:
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", // 快速响应
}
上下文管理
消息引用策略:
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
}
流式响应处理
分段更新策略:
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交互日志记录 [新增]
日志记录服务:
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图 [更新]
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 [更新]
-- 用户档案表
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 |
交付物清单
- 源代码仓库(Git)
- 数据库迁移脚本
- API接口文档(OpenAPI 3.0)
- 部署操作手册
- 监控告警配置文件
- 性能测试报告
- 用户使用指南
附录
术语表
| 术语 | 英文 | 说明 |
|---|---|---|
| 令牌桶 | Token Bucket | 速率限制算法,以固定速率补充令牌实现流控 |
| 死信队列 | Dead Letter Queue | 存储多次失败消息的特殊队列 |
| 指数退避 | Exponential Backoff | 重试间隔呈指数增长的策略 |
| WAL模式 | Write-Ahead Logging | SQLite日志模式,提升并发写入性能 |
| RRULE | Recurrence Rule | iCalendar重复规则标准(RFC 5545) |
参考资料
文档变更记录
v2.0 (2025-10-24更新)
主要变更:
- 新增消息发送器包装层(PlainMessageSender和AIMessageSender)
- 新增Telegram管理功能模块(用户群组管理、机器人功能管理)
- 更新AI触发机制为私聊全响应+群聊@模式
- 更新AI实现方案为OpenRouter优先(使用OpenAI Go SDK)
- 新增AI交互日志记录系统
- 更新数据库ER图和表结构
- 新增管理API接口文档
- 调整实施计划,新增管理功能阶段
- 术语规范: "机器人交互功能" -> "机器人预定义功能"
文档编制完成 | 总计约1.8万字 | 包含11个Mermaid图表 | 覆盖15个核心设计模块