### **项目代号**: NaughtyMan
**文档版本**: v1.0
**编制日期**: 2025年10月21日
**技术架构师**: [待填写]
---
## 系统架构设计
### 总体架构
系统采用**分层微服务架构**,由接口层、业务逻辑层、数据访问层和外部服务层构成。架构设计遵循高内聚低耦合原则,确保各模块独立演进能力。
```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]
end
subgraph "数据访问层"
D1[消息队列
PriorityQueue]
D2[SQLite数据库
WAL模式]
D3[缓存层
sync.Map]
end
subgraph "外部服务层"
E1[Telegram Bot API]
E2[OpenAI API]
E3[Google Gemini]
E4[xAI Grok]
E5[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
C4 --> E3
C4 --> E4
E1 -.代理.-> E5
```
### 技术选型说明
| 技术组件 | 选型方案 | 选型理由 |
| --- | --- | --- |
| 开发语言 | Go 1.21+ | 原生并发支持、高性能、跨平台编译、内存安全 |
| Web框架 | Gin v1.9 | 轻量级、路由高效、中间件生态完善、社区活跃 |
| 数据库 | SQLite 3.40+ | 零配置部署、事务ACID保证、跨平台兼容、适合中小规模数据 |
| Bot SDK | tgbotapi v5 | 官方API完整封装、长连接稳定、支持代理配置 |
| 并发控制 | 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[Telegram API]
I -->|成功| J[消息日志]
I -->|失败| K{重试判断}
K -->|次数<3| L[指数退避重试]
K -->|次数≥3| M[死信队列]
L --> H
```
### 优先级队列实现
**数据结构设计**:
```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
}
```
---
### 定时提醒系统
### 状态机设计
```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 -->|私聊| C[直接触发]
B -->|"群聊@Bot"| C
B -->|其他| D[忽略]
C --> E[上下文提取器]
E --> F[消息历史查询
最近3条]
F --> G[提示词构建器]
G --> H[AI路由器]
H --> I1[OpenAI适配器]
H --> I2[Gemini适配器]
H --> I3[Grok适配器]
H --> I4[OpenRouter适配器]
I1 --> J[流式响应处理器]
I2 --> J
I3 --> J
I4 --> J
J --> K[消息分段器
500字符/5秒]
K --> L[editMessageText]
L --> M[速率限制器]
M --> N[Telegram API]
```
### AI适配器接口定义
```go
type AIProvider interface {
Name() string
Chat(ctx context.Context, messages []ChatMessage, stream bool) (<-chan string, error)
SupportStream() bool
SupportReasoning() bool
}
type ChatMessage struct {
Role string `json:"role"` // system/user/assistant
Content string `json:"content"`
Name string `json:"name,omitempty"`
Meta map[string]interface{} `json:"-"`
}
// OpenAI适配器实现
type OpenAIProvider struct {
apiKey string
model string
client *http.Client
}
func (p *OpenAIProvider) Chat(ctx context.Context, messages []ChatMessage, stream bool) (<-chan string, error) {
reqBody := map[string]interface{}{
"model": p.model,
"messages": messages,
"stream": stream,
}
if stream {
return p.streamChat(ctx, reqBody)
}
return p.blockingChat(ctx, reqBody)
}
func (p *OpenAIProvider) streamChat(ctx context.Context, reqBody map[string]interface{}) (<-chan string, error) {
ch := make(chan string, 10)
req, _ := http.NewRequestWithContext(ctx, "POST",
"",
toJSON(reqBody))
req.Header.Set("Authorization", "Bearer "+p.apiKey)
req.Header.Set("Content-Type", "application/json")
resp, err := p.client.Do(req)
if err != nil {
return nil, err
}
go func() {
defer close(ch)
defer resp.Body.Close()
scanner := bufio.NewScanner(resp.Body)
for scanner.Scan() {
line := scanner.Text()
if !strings.HasPrefix(line, "data: ") {
continue
}
data := strings.TrimPrefix(line, "data: ")
if data == "[DONE]" {
break
}
var chunk struct {
Choices []struct {
Delta struct {
Content string `json:"content"`
} `json:"delta"`
} `json:"choices"`
}
json.Unmarshal([]byte(data), &chunk)
if len(chunk.Choices) > 0 {
ch <- chunk.Choices[^0].Delta.Content
}
}
}()
return ch, nil
}
```
### 流式响应处理策略
**分段更新逻辑**:
```go
type StreamHandler struct {
bot *tgbotapi.BotAPI
rateLimiter *RateLimiter
updateBuffer strings.Builder
lastUpdate time.Time
messageID int
chatID int64
}
func (sh *StreamHandler) ProcessStream(ctx context.Context, stream <-chan string) error {
initialMsg := tgbotapi.NewMessage(sh.chatID, "🤔 思考中...")
sent, _ := sh.bot.Send(initialMsg)
sh.messageID = sent.MessageID
sh.lastUpdate = time.Now()
for {
select {
case chunk, ok := <-stream:
if !ok {
// 流结束,发送最终消息
sh.finalUpdate("✅ 回答完成")
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()
}
case <-ctx.Done():
return ctx.Err()
}
}
}
func (sh *StreamHandler) sendUpdate() {
content := sh.updateBuffer.String()
// 等待速率限制器允许
for !sh.rateLimiter.AllowSend(sh.chatID, true) {
time.Sleep(100 * time.Millisecond)
}
edit := tgbotapi.NewEditMessageText(sh.chatID, sh.messageID, content)
edit.ParseMode = "Markdown"
sh.bot.Send(edit)
sh.lastUpdate = time.Now()
}
```
---
## 数据库设计
### ER图
```mermaid
erDiagram
API_CREDENTIALS ||--o{ TOKEN_SESSIONS : generates
API_CREDENTIALS ||--o{ MESSAGE_LOG : sends
WHITELIST ||--o{ API_CREDENTIALS : authorizes
REMINDERS }o--|| WHITELIST : belongs_to
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
bigint chat_id FK
text content
datetime trigger_time
string repeat_rule
datetime next_trigger
int status
}
AI_CONVERSATIONS {
int id PK
bigint chat_id
bigint user_id
text user_message
text ai_response
string provider
string model
int tokens_used
datetime created_at
}
```
### 核心表索引策略
```sql
-- 消息日志表优化索引
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规范
### 认证接口
**1. 握手接口**
```
POST /api/v1/auth/handshake
Content-Type: application/json
{
"api_key": "octopus_ak_1234567890"
}
```
**响应**:
```json
{
"challenge": "uuid-v4-string",
"expire_at": "2025-10-21T17:01:00Z",
"algorithm": "HMAC-SHA256"
}
```
**2. Token获取接口**
```
POST /api/v1/auth/token
Content-Type: application/json
{
"api_key": "octopus_ak_1234567890",
"challenge": "uuid-v4-string",
"signature": "hex-encoded-hmac-sha256"
}
```
**响应**:
```json
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 21600,
"refresh_token": "refresh-token-string"
}
```
### 消息发送接口
```
POST /api/v1/message/send
Authorization: Bearer
Content-Type: application/json
{
"target_type": "user",
"target_id": "123456789",
"level": "warning",
"project": "octopus",
"content": {
"title": "CPU使用率告警",
"body": "服务器CPU使用率达85%",
"metadata": {
"server": "prod-web-01",
"cpu_usage": 85.3,
"threshold": 80,
"timestamp": "2025-10-21T16:58:00Z",
"dashboard_url": ""
}
}
}
```
**响应**:
```json
{
"message_id": "msg_abc123",
"status": "queued",
"estimated_send_time": "2025-10-21T17:00:00Z",
"priority": 2
}
```
### 消息状态查询接口
```
GET /api/v1/message/status?message_id=msg_abc123
Authorization: Bearer
```
**响应**:
```json
{
"message_id": "msg_abc123",
"status": "sent",
"telegram_message_id": 98765,
"sent_at": "2025-10-21T17:00:05Z",
"retry_count": 0
}
```
### Telegram Bot指令接口
| 指令 | 功能 | 权限要求 | 响应方式 |
| --- | --- | --- | --- |
| `/start` | 激活机器人 | 白名单 | 欢迎消息+功能菜单 |
| `/help` | 帮助文档 | 白名单 | 富文本说明 |
| `/notify` | 创建提醒 | 白名单 | InlineKeyboard交互 |
| `/notify_list` | 查看提醒列表 | 白名单 | 列表+删除按钮 |
| `/status` | 系统状态 | 管理员 | 队列长度/限流状态 |
| `/whitelist add ` | 添加白名单 | 管理员 | 确认消息 |
---
## 部署架构
### 系统部署拓扑
```mermaid
graph TB
subgraph "生产环境"
A[Nginx反向代理
:443 HTTPS]
B[NaughtyMan服务
:8080]
C[SQLite数据库
bot.db]
D[日志文件
/var/log/naughty_man]
end
subgraph "网络层"
E[SOCKS5代理
:1080]
end
subgraph "外部服务"
F[Telegram API
api.telegram.org]
G[OpenAI API]
end
A -->|TLS| B
B --> C
B --> D
B -->|代理| E
E --> F
B -->|直连| G
subgraph "监控系统"
H[Prometheus
:9090]
I[Grafana
:3000]
end
B -->|metrics| H
H --> I
```
### 配置文件结构
**config.yaml**:
```yaml
server:
host: "0.0.0.0"
port: 8080
mode: "release" # debug/release
telegram:
bot_token: "${TELEGRAM_BOT_TOKEN}"
proxy:
enabled: true
type: "socks5"
host: "127.0.0.1"
port: 1080
username: ""
password: ""
rate_limit:
global_rate: 30 # 消息/秒
chat_rate: 1 # 私聊消息/秒
group_rate: 20 # 群聊消息/分钟
edit_rate: 20 # 编辑消息/分钟
database:
path: "./data/bot.db"
wal_mode: true
max_open_conns: 25
max_idle_conns: 5
conn_max_lifetime: 3600 # 秒
ai:
default_provider: "openai"
providers:
openai:
api_key: "${OPENAI_API_KEY}"
model: "gpt-4-turbo"
base_url: ""
timeout: 30
gemini:
api_key: "${GEMINI_API_KEY}"
model: "gemini-1.5-pro"
grok:
api_key: "${GROK_API_KEY}"
model: "grok-2"
context_window: 3 # 历史消息条数
stream_enabled: true
security:
jwt_secret: "${JWT_SECRET}"
token_expire_hours: 6
challenge_expire_seconds: 60
admin_chat_ids: [^123456789]
logging:
level: "info" # debug/info/warn/error
format: "json"
output: "./logs/app.log"
max_size: 100 # MB
max_backups: 10
max_age: 30 # 天
compress: true
monitoring:
prometheus_enabled: true
metrics_path: "/metrics"
```
### 环境变量清单
```bash
# Telegram配置
export TELEGRAM_BOT_TOKEN="1234567890:ABCdefGHIjklMNOpqrsTUVwxyz"
# AI服务密钥
export OPENAI_API_KEY="sk-proj-..."
export GEMINI_API_KEY="AIza..."
export GROK_API_KEY="xai-..."
# 安全密钥
export JWT_SECRET="random-256-bit-secret"
# 数据库路径
export DB_PATH="./data/bot.db"
# 运行模式
export GIN_MODE="release"
```
---
## 非功能性需求实现
### 性能优化策略
### 数据库连接池配置
```go
func InitDB(config DBConfig) *sql.DB {
db, _ := sql.Open("sqlite3", config.Path+"?_journal=WAL&_busy_timeout=5000")
db.SetMaxOpenConns(config.MaxOpenConns)
db.SetMaxIdleConns(config.MaxIdleConns)
db.SetConnMaxLifetime(time.Duration(config.ConnMaxLifetime) * time.Second)
// 启用WAL模式提升并发性能
db.Exec("PRAGMA journal_mode=WAL")
db.Exec("PRAGMA synchronous=NORMAL")
db.Exec("PRAGMA cache_size=-64000") // 64MB缓存
return db
}
```
### 消息批量处理
```go
type BatchProcessor struct {
messages []Message
timer *time.Timer
mutex sync.Mutex
batchSize int
batchDelay time.Duration
}
func (bp *BatchProcessor) Add(msg Message) {
bp.mutex.Lock()
defer bp.mutex.Unlock()
bp.messages = append(bp.messages, msg)
// 达到批量大小或超时则触发发送
if len(bp.messages) >= bp.batchSize {
bp.flush()
} else if bp.timer == nil {
bp.timer = time.AfterFunc(bp.batchDelay, bp.flush)
}
}
func (bp *BatchProcessor) flush() {
bp.mutex.Lock()
msgs := bp.messages
bp.messages = nil
bp.timer = nil
bp.mutex.Unlock()
// 聚合同类消息
aggregated := aggregateMessages(msgs)
for _, msg := range aggregated {
sendToTelegram(msg)
}
}
```
### 可靠性保障
### 消息重试机制
```go
type RetryPolicy struct {
MaxRetries int
InitialDelay time.Duration
MaxDelay time.Duration
Multiplier float64
}
var defaultPolicy = RetryPolicy{
MaxRetries: 3,
InitialDelay: 1 * time.Second,
MaxDelay: 30 * time.Second,
Multiplier: 2.0,
}
func SendWithRetry(msg Message, policy RetryPolicy) error {
var lastErr error
for attempt := 0; attempt <= policy.MaxRetries; attempt++ {
err := sendToTelegram(msg)
if err == nil {
return nil
}
lastErr = err
// 判断是否可重试(非4xx错误)
if !isRetryable(err) {
return err
}
// 计算退避延迟
delay := policy.InitialDelay * time.Duration(math.Pow(policy.Multiplier, float64(attempt)))
if delay > policy.MaxDelay {
delay = policy.MaxDelay
}
log.Warn("消息发送失败,重试中",
"attempt", attempt+1,
"delay", delay,
"error", err)
time.Sleep(delay)
}
// 转入死信队列
deadLetterQueue <- msg
return fmt.Errorf("重试%d次后仍失败: %w", policy.MaxRetries, lastErr)
}
```
### 优雅关闭机制
```go
func (app *Application) Shutdown(ctx context.Context) error {
log.Info("开始优雅关闭...")
// 1. 停止接收新请求
app.httpServer.SetKeepAlivesEnabled(false)
// 2. 停止Telegram轮询
app.bot.StopReceivingUpdates()
// 3. 等待消息队列清空(最多30秒)
queueCtx, cancel := context.WithTimeout(ctx, 30*time.Second)
defer cancel()
for {
select {
case <-queueCtx.Done():
log.Warn("消息队列未完全处理完毕", "remaining", app.queue.Len())
goto CLEANUP
default:
if app.queue.Len() == 0 {
goto CLEANUP
}
time.Sleep(100 * time.Millisecond)
}
}
CLEANUP:
// 4. 关闭HTTP服务器
app.httpServer.Shutdown(ctx)
// 5. 关闭数据库连接
app.db.Close()
log.Info("优雅关闭完成")
return nil
}
```
### 监控与可观测性
### Prometheus指标定义
```go
var (
// 消息发送指标
messagesSent = prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "naughty_man_messages_sent_total",
Help: "消息发送总数",
},
[]string{"project", "level", "status"},
)
// 消息发送延迟
messageSendDuration = prometheus.NewHistogramVec(
prometheus.HistogramOpts{
Name: "naughty_man_message_send_duration_seconds",
Help: "消息发送耗时",
Buckets: prometheus.ExponentialBuckets(0.01, 2, 10),
},
[]string{"project", "level"},
)
// 队列长度
queueLength = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "naughty_man_queue_length",
Help: "消息队列长度",
},
[]string{"priority"},
)
// 速率限制器状态
rateLimiterTokens = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "naughty_man_rate_limiter_tokens",
Help: "速率限制器剩余令牌数",
},
[]string{"limiter_type"},
)
// AI调用指标
aiRequests = prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "naughty_man_ai_requests_total",
Help: "AI API调用总数",
},
[]string{"provider", "model", "status"},
)
)
```
### 结构化日志规范
```go
// 使用zap实现结构化日志
logger, _ := zap.NewProduction()
defer logger.Sync()
sugar := logger.Sugar()
// 业务日志示例
sugar.Infow("消息入队",
"message_id", msg.ID,
"chat_id", msg.ChatID,
"level", msg.Level,
"priority", msg.Priority,
"scheduled_at", msg.ScheduledAt,
)
sugar.Errorw("消息发送失败",
"message_id", msg.ID,
"error", err.Error(),
"retry_count", msg.RetryCount,
"will_retry", msg.RetryCount < 3,
)
```
---
## 测试策略
### 单元测试覆盖计划
| 模块 | 测试重点 | 目标覆盖率 |
| --- | --- | --- |
| 认证模块 | 签名验证、Token生成/解析、白名单校验 | >90% |
| 速率限制器 | 令牌桶算法、并发安全性 | >95% |
| 消息队列 | 优先级排序、入队出队操作 | >90% |
| 模板引擎 | 变量替换、错误处理 | >85% |
| AI适配器 | 流式解析、超时处理 | >80% |
### 集成测试场景
**场景1: 端到端消息发送流程**
```go
func TestE2EMessageFlow(t *testing.T) {
// 1. 创建测试环境
app := setupTestApp()
defer app.Cleanup()
// 2. 获取Token
token := authenticateTestClient(app)
// 3. 发送CRITICAL消息
msgID := sendTestMessage(app, token, Message{
TargetID: testChatID,
Level: "CRITICAL",
Content: testContent,
})
// 4. 验证消息入队
assert.Eventually(t, func() bool {
return app.Queue.Contains(msgID)
}, 5*time.Second, 100*time.Millisecond)
// 5. 模拟Telegram API响应
mockTelegramResponse(testChatID, msgID)
// 6. 验证消息状态更新
status := queryMessageStatus(app, msgID)
assert.Equal(t, "sent", status)
}
```
### 压力测试指标
**测试工具**: Apache JMeter / Vegeta
**测试用例**:
```bash
# 并发消息发送压测(1000 QPS持续1分钟)
echo "POST " | \\
vegeta attack -rate=1000 -duration=60s -header="Authorization: Bearer $TOKEN" \\
| vegeta report
# 期望指标:
# - 成功率 > 99%
# - P95延迟 < 200ms
# - P99延迟 < 500ms
# - 无内存泄漏
```
---
## 风险评估与对策
| 风险项 | 发生概率 | 影响程度 | 应对策略 | 责任人 |
| --- | --- | --- | --- | --- |
| Telegram API限流导致消息积压 | 高 | 高 | 1. 实现自适应速率限制
2. 优先级队列保证CRITICAL优先
3. 消息聚合减少调用 | 后端负责人 |
| SQLite写入冲突 | 中 | 中 | 1. 启用WAL模式
2. 写操作串行化
3. 必要时迁移PostgreSQL | 数据库架构师 |
| AI服务不稳定 | 中 | 低 | 1. 多厂商自动切换
2. 超时熔断机制
3. 降级为普通回复 | AI集成负责人 |
| 代理服务中断 | 低 | 高 | 1. 备用代理配置
2. 心跳检测自动切换
3. 监控告警 | 运维负责人 |
| 死信队列溢出 | 低 | 中 | 1. 定时任务清理老数据
2. 人工审核重试
3. 容量告警 | 值班工程师 |
---
## 项目实施计划
### 迭代排期
```mermaid
gantt
title NaughtyMan开发计划
dateFormat YYYY-MM-DD
section 阶段一:基础设施
数据库表结构设计 :done, db, 2025-10-22, 2d
速率限制器实现 :active, rate, 2025-10-24, 3d
Token认证系统 :auth, after rate, 3d
白名单中间件 :whitelist, after auth, 2d
代理配置与测试 :proxy, after whitelist, 2d
section 阶段二:核心功能
消息通知API开发 :api, after proxy, 4d
模板渲染引擎 :template, after api, 2d
优先级队列实现 :queue, after template, 3d
消息重试机制 :retry, after queue, 2d
定时提醒CRUD :reminder, after retry, 4d
section 阶段三:高级功能
InlineKeyboard交互 :inline, after reminder, 4d
AI多厂商适配 :ai, after inline, 5d
流式响应处理 :stream, after ai, 3d
性能优化与压测 :perf, after stream, 3d
section 阶段四:上线准备
集成测试 :integration, after perf, 3d
文档编写 :doc, after integration, 2d
监控告警配置 :monitor, after doc, 2d
灰度发布 :release, after monitor, 2d
```
### 人员分工
| 角色 | 职责 | 人数 |
| --- | --- | --- |
| 技术负责人 | 架构设计审核、技术选型决策、风险评估 | 1 |
| 后端工程师 | 核心业务逻辑开发、API接口实现 | 2 |
| AI集成工程师 | 多厂商适配层、流式响应处理 | 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) |
### 参考资料
- [Telegram Bot API官方文档](https://core.telegram.org/bots/api)
- [Go语言并发模式](https://go.dev/blog/pipelines)
- [SQLite WAL模式详解](https://www.sqlite.org/wal.html)
- [JWT最佳实践](https://tools.ietf.org/html/rfc8725)
- [OpenAPI规范3.0](https://swagger.io/specification/)
---
**文档编制完成** | 总计约1.2万字 | 包含8个Mermaid图表 | 覆盖12个核心设计模块