Skill: zhihang9978/teamgram-rpc-development (version 2.0.0)

--- name: teamgram-rpc-development description: Complete guide for developing RPC services in Teamgram Server (v2.0.0). Use when creating new RPC methods, implementing business logic, or extending Teamgram functionality. Covers TL schema, DAO/Core/Server layers, error handling, performance optimization, security, testing, observability, and production best practices. version: 2.0.0 --- # Teamgram RPC Development Guide v2.0.0 Complete guide for developing RPC services in Teamgram Server. > 📚 **十次版本迭代优化** (v1.0.0 → v2.0.0) > > 本技能经过十次迭代完善，涵盖从基础开发到生产部署的完整知识体系。 ## 版本迭代历程 | 版本 | 主题 | 文档 | |------|------|------| | v1.0.0 | 基础开发 | 本指南 | | v1.1.0 | 错误处理与日志 | [查看](references/v1.1.0-error-handling.md) | | v1.2.0 | 性能优化与缓存 | [查看](references/v1.2.0-performance.md) | | v1.3.0 | 安全最佳实践 | [查看](references/v1.3.0-security.md) | | v1.4.0 | 测试策略 | [查看](references/v1.4.0-testing.md) | | v1.5.0 | 可观测性 | [查看](references/v1.5.0-observability.md) | | v1.6.0 | 数据库优化 | [查看](references/v1.6.0-database.md) | | v1.7.0 | 消息队列 | [查看](references/v1.7.0-queue.md) | | v1.8.0 | 熔断限流 | [查看](references/v1.8.0-circuit-breaker.md) | | v1.9.0 | 多租户 | [查看](references/v1.9.0-multi-tenant.md) | | v2.0.0 | 完整总结 | [查看](references/v2.0.0-final.md) | ## 快速导航 **新手指南**: 从 [Development Workflow](#development-workflow) 开始 **生产优化**: 查看 [v1.2.0性能优化](references/v1.2.0-performance.md) 和 [v1.3.0安全](references/v1.3.0-security.md) **问题排查**: 参考 [v1.1.0错误处理](references/v1.1.0-error-handling.md) 和 [v1.5.0可观测性](references/v1.5.0-observability.md) ## Overview Teamgram uses gRPC for inter-service communication and MTProto for client-server communication. This guide covers both. ## RPC Types ### 1. Internal RPC (Service-to-Service) ```protobuf // Internal gRPC between services service BizService { rpc MessagesSendMessage(MessagesSendMessageReq) returns (Updates); rpc UsersGetUsers(UsersGetUsersReq) returns (Vector<User>); } ``` ### 2. External RPC (Client-to-Server) ```tl // MTProto RPC for Telegram clients messages.sendMessage#520c3870 peer:InputPeer message:string = Updates; users.getUsers#d91a548 id:Vector<InputUser> = Vector<User>; ``` ## Development Workflow ### Step 1: Define TL Schema ```tl // File: specs/mtproto.tl // Request/Response types premium.getStatus#d0b5e0f2 user_id:long = PremiumStatus; premium.purchase#8f8c0e1c user_id:long plan_id:int payment_method:string = Bool; // Object types premiumStatus#9a4f3e2d user_id:long status:int expires_at:long = PremiumStatus; ``` **Naming Conventions**: - Method: `category.actionName` (camelCase) - Constructor ID: `#8hexdigits` (random or sequential) - Types: PascalCase ### Step 2: Generate Go Code ```bash # Generate from TL schema make generate # or go run cmd/mtprotoc/main.go -I specs/ -o app/ # Output files: # app/mtproto/premium_get_status.go # app/mtproto/premium_purchase.go # app/mtproto/premium_status.go ``` ### Step 3: Create Database Schema ```sql -- File: data/schema/premium.sql CREATE TABLE user_premium ( id BIGINT PRIMARY KEY AUTO_INCREMENT, user_id BIGINT NOT NULL, status TINYINT DEFAULT 0 COMMENT '0=none, 1=basic, 2=premium', plan_id INT DEFAULT 0, expires_at BIGINT DEFAULT 0, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, UNIQUE KEY uk_user_id (user_id), INDEX idx_expires (expires_at) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE premium_transactions ( id BIGINT PRIMARY KEY AUTO_INCREMENT, user_id BIGINT NOT NULL, plan_id INT NOT NULL, amount DECIMAL(10,2) NOT NULL, currency VARCHAR(3) DEFAULT 'USD', payment_method VARCHAR(50), status TINYINT DEFAULT 0 COMMENT '0=pending, 1=success, 2=failed', transaction_id VARCHAR(255), created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, INDEX idx_user_id (user_id), INDEX idx_created_at (created_at) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; ``` ### Step 4: Implement DAO Layer ```go // File: app/service/biz/internal/dao/premium_dao.go package dao import ( "context" "database/sql" "time" "github.com/teamgram/teamgram-server/app/service/biz/internal/model" "github.com/zeromicro/go-zero/core/stores/sqlx" ) type PremiumDAO struct { db sqlx.SqlConn } func NewPremiumDAO(db sqlx.SqlConn) *PremiumDAO { return &PremiumDAO{db: db} } // GetPremiumStatus 获取用户会员状态 func (d *PremiumDAO) GetPremiumStatus(ctx context.Context, userID int64) (*model.UserPremium, error) { var m model.UserPremium query := `SELECT * FROM user_premium WHERE user_id = ?` err := d.db.QueryRowCtx(ctx, &m, query, userID) if err == sqlx.ErrNotFound { // 返回默认状态（非会员） return &model.UserPremium{UserID: userID, Status: 0}, nil } return &m, err } // CreateOrUpdatePremium 创建或更新会员状态 func (d *PremiumDAO) CreateOrUpdatePremium(ctx context.Context, userID int64, status int32, planID int, expiresAt int64) error { query := ` INSERT INTO user_premium (user_id, status, plan_id, expires_at) VALUES (?, ?, ?, ?) ON DUPLICATE KEY UPDATE status = VALUES(status), plan_id = VALUES(plan_id), expires_at = VALUES(expires_at) ` _, err := d.db.ExecCtx(ctx, query, userID, status, planID, expiresAt) return err } // CreateTransaction 创建交易记录 func (d *PremiumDAO) CreateTransaction(ctx context.Context, txn *model.PremiumTransaction) (int64, error) { query := ` INSERT INTO premium_transactions (user_id, plan_id, amount, currency, payment_method, status, transaction_id) VALUES (?, ?, ?, ?, ?, ?, ?) ` result, err := d.db.ExecCtx(ctx, query, txn.UserID, txn.PlanID, txn.Amount, txn.Currency, txn.PaymentMethod, txn.Status, txn.TransactionID, ) if err != nil { return 0, err } return result.LastInsertId() } // UpdateTransactionStatus 更新交易状态 func (d *PremiumDAO) UpdateTransactionStatus(ctx context.Context, txnID int64, status int32) error { query := `UPDATE premium_transactions SET status = ? WHERE id = ?` _, err := d.db.ExecCtx(ctx, query, status, txnID) return err } ``` ### Step 5: Implement Core Logic ```go // File: app/service/biz/internal/core/premium_core.go package core import ( "context" "errors" "time" "github.com/teamgram/teamgram-server/app/service/biz/internal/dao" "github.com/teamgram/teamgram-server/app/service/biz/internal/model" "github.com/teamgram/teamgram-server/app/service/biz/internal/svc" ) var ( ErrInvalidPlan = errors.New("invalid premium plan") ErrPaymentFailed = errors.New("payment processing failed") ) type PremiumCore struct { svcCtx *svc.ServiceContext dao *dao.PremiumDAO } func NewPremiumCore(svcCtx *svc.ServiceContext) *PremiumCore { return &PremiumCore{ svcCtx: svcCtx, dao: dao.NewPremiumDAO(svcCtx.DB), } } // GetPremiumStatus 获取用户会员状态 func (c *PremiumCore) GetPremiumStatus(ctx context.Context, userID int64) (*model.PremiumStatus, error) { // 1. 查询数据库 premium, err := c.dao.GetPremiumStatus(ctx, userID) if err != nil { return nil, err } // 2. 检查是否过期 now := time.Now().Unix() if premium.Status > 0 && premium.ExpiresAt < now { // 已过期，更新状态 premium.Status = 0 _ = c.dao.CreateOrUpdatePremium(ctx, userID, 0, 0, 0) } return &model.PremiumStatus{ UserID: userID, Status: premium.Status, ExpiresAt: premium.ExpiresAt, }, nil } // PurchasePremium 购买会员 func (c *PremiumCore) PurchasePremium(ctx context.Context, userID int64, planID int, paymentMethod string) error { // 1. 验证套餐 plan := c.getPlanByID(planID) if plan == nil { return ErrInvalidPlan } // 2. 创建交易记录 txn := &model.PremiumTransaction{ UserID: userID, PlanID: planID, Amount: plan.Price, Currency: plan.Currency, PaymentMethod: paymentMethod, Status: 0, // pending } txnID, err := c.dao.CreateTransaction(ctx, txn) if err != nil { return err } // 3. 调用支付网关（异步处理） go c.processPayment(txnID, userID, plan) return nil } // processPayment 处理支付（异步） func (c *PremiumCore) processPayment(txnID int64, userID int64, plan *Plan) { ctx := context.Background() // 调用支付接口... // 成功后在回调中： // 1. 更新交易状态 _ = c.dao.UpdateTransactionStatus(ctx, txnID, 1) // success // 2. 更新用户会员状态 expiresAt := time.Now().AddDate(0, plan.DurationMonths, 0).Unix() _ = c.dao.CreateOrUpdatePremium(ctx, userID, 1, plan.ID, expiresAt) // 3. 发送通知 c.svcCtx.PushClient.SendToUser(ctx, userID, "Premium activated!") } func (c *PremiumCore) getPlanByID(planID int) *Plan { plans := map[int]*Plan{ 1: {ID: 1, Name: "Basic", Price: 4.99, DurationMonths: 1}, 2: {ID: 2, Name: "Premium", Price: 49.99, DurationMonths: 12}, } return plans[planID] } type Plan struct { ID int Name string Price float64 Currency string DurationMonths int } ``` ### Step 6: Implement RPC Server ```go // File: app/service/biz/internal/server/premium_server.go package server import ( "context" "github.com/teamgram/teamgram-server/app/mtproto" "github.com/teamgram/teamgram-server/app/service/biz/internal/core" "github.com/teamgram/teamgram-server/app/service/biz/internal/svc" ) func (s *Server) PremiumGetStatus(ctx context.Context, req *mtproto.TLPremiumGetStatus) (*mtproto.PremiumStatus, error) { // 1. 调用Core层 status, err := s.premiumCore.GetPremiumStatus(ctx, req.UserId) if err != nil { return nil, err } // 2. 转换为MTProto对象 return &mtproto.TLPremiumStatus{ UserId: status.UserID, Status: status.Status, ExpiresAt: status.ExpiresAt, }, nil } func (s *Server) PremiumPurchase(ctx context.Context, req *mtproto.TLPremiumPurchase) (*mtproto.Bool, error) { // 1. 调用Core层 err := s.premiumCore.PurchasePremium(ctx, req.UserId, int(req.PlanId), req.PaymentMethod) if err != nil { return mtproto.BoolFalse, err } return mtproto.BoolTrue, nil } ``` ### Step 7: Register RPC ```go // File: app/service/biz/internal/server/server.go func Register(grpcServer *grpc.Server, svcCtx *svc.ServiceContext) { s := &Server{ svcCtx: svcCtx, premiumCore: core.NewPremiumCore(svcCtx), // ... other cores } // Register all services mtproto.RegisterBizServiceServer(grpcServer, s) } ``` ### Step 8: Testing ```go // File: app/service/biz/internal/core/premium_core_test.go package core_test import ( "context" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/teamgram/teamgram-server/app/service/biz/internal/core" ) func TestGetPremiumStatus(t *testing.T) { // Setup svcCtx := setupTestContext() c := core.NewPremiumCore(svcCtx) // Test: Non-premium user status, err := c.GetPremiumStatus(context.Background(), 123456) // Assert assert.NoError(t, err) assert.Equal(t, int32(0), status.Status) assert.Equal(t, int64(123456), status.UserID) } func TestPurchasePremium(t *testing.T) { // Setup with mocked payment gateway svcCtx := setupTestContext() c := core.NewPremiumCore(svcCtx) // Execute err := c.PurchasePremium(context.Background(), 123456, 1, "stripe") // Assert assert.NoError(t, err) // Verify transaction created... } ``` ## Advanced Patterns ### 1. Rate Limiting ```go func (s *Server) PremiumPurchase(ctx context.Context, req *mtproto.TLPremiumPurchase) (*mtproto.Bool, error) { // Check rate limit key := fmt.Sprintf("rate_limit:purchase:%d", req.UserId) count, err := s.svcCtx.Redis.Incr(key) if err == nil && count > 10 { return nil, errors.New("rate limit exceeded") } s.svcCtx.Redis.Expire(key, 3600) // 1 hour // Process... } ``` ### 2. Caching ```go func (c *PremiumCore) GetPremiumStatus(ctx context.Context, userID int64) (*model.PremiumStatus, error) { // Try cache cacheKey := fmt.Sprintf("premium:%d", userID) cached, err := c.svcCtx.Redis.Get(cacheKey) if err == nil { var status model.PremiumStatus json.Unmarshal([]byte(cached), &status) return &status, nil } // Cache miss - query DB status, err := c.dao.GetPremiumStatus(ctx, userID) if err != nil { return nil, err } // Update cache data, _ := json.Marshal(status) c.svcCtx.Redis.Set(cacheKey, string(data), 300) // 5 min TTL return status, nil } ``` ### 3. Distributed Transactions ```go func (c *PremiumCore) PurchaseWithTransaction(ctx context.Context, userID int64, planID int) error { return c.svcCtx.DB.Transact(func(tx *sql.Tx) error { // Deduct balance if err := c.dao.DeductBalanceTx(tx, userID, amount); err != nil { return err } // Create premium record if err := c.dao.CreatePremiumTx(tx, userID, planID); err != nil { return err } // Record transaction return c.dao.RecordTransactionTx(tx, userID, amount) }) } ``` ## Common Mistakes ### 1. Not Handling Context Cancellation ```go // Wrong func (c *Core) LongOperation(ctx context.Context) { time.Sleep(10 * time.Second) // Blocks even if client disconnected } // Correct func (c *Core) LongOperation(ctx context.Context) error { select { case <-ctx.Done(): return ctx.Err() case <-time.After(10 * time.Second): return nil } } ``` ### 2. Ignoring Database Errors ```go // Wrong data, _ := c.dao.Get(ctx, id) // Silent failure! // Correct data, err := c.dao.Get(ctx, id) if err != nil { if err == sqlx.ErrNotFound { return nil, status.Error(codes.NotFound, "not found") } return nil, status.Error(codes.Internal, err.Error()) } ``` ### 3. Not Using Transactions ```go // Wrong - partial failure possible c.dao.DeductBalance(ctx, userID, amount) c.dao.AddPremium(ctx, userID, planID) // May fail after deduction! // Correct - atomic operation c.svcCtx.DB.Transact(func(tx *sql.Tx) error { c.dao.DeductBalanceTx(tx, userID, amount) c.dao.AddPremiumTx(tx, userID, planID) return nil }) ``` ## References - [TL Language Spec](https://core.telegram.org/mtproto/TL) - [gRPC Go Documentation](https://grpc.io/docs/languages/go/) - [Go Database/SQL Tutorial](https://golang.org/doc/tutorial/database-access) See [references/rpc-patterns.md](references/rpc-patterns.md) for common patterns.

path: references/v1.1.0-error-handling.md
content: # v1.1.0 - 错误处理与日志规范

## 错误处理最佳实践

### 错误码设计

```go
package code

// 错误码规则: 服务号(2位) + 模块号(2位) + 错误号(4位)
const (
    // 通用错误 00xxxx
    ErrSuccess          = 0
    ErrUnknown          = 100001
    ErrInvalidParam     = 100002
    ErrUnauthorized     = 100003
    ErrForbidden        = 100004
    ErrNotFound         = 100005
    ErrInternal         = 100006
    ErrTimeout          = 100007
    ErrRateLimit        = 100008
    
    // 业务错误 01xxxx (Biz服务)
    ErrUserNotFound     = 101001
    ErrChatNotFound     = 101002
    ErrMessageNotFound  = 101003
    ErrNotMember        = 101004
    ErrBanned           = 101005
    ErrFloodWait        = 101006
    
    // 支付错误 02xxxx (Premium)
    ErrPaymentFailed    = 102001
    ErrInsufficientFund = 102002
    ErrInvalidPlan      = 102003
    ErrAlreadyPremium   = 102004
)

type BizError struct {
    Code    int
    Message string
    Detail  string
}

func (e *BizError) Error() string {
    return fmt.Sprintf("[%d] %s: %s", e.Code, e.Message, e.Detail)
}

func NewError(code int, detail string) error {
    msg := GetErrorMsg(code)
    return &BizError{Code: code, Message: msg, Detail: detail}
}
```

### 错误包装与追踪

```go
// 错误包装，添加上下文
func (c *Core) ProcessMessage(ctx context.Context, req *Request) (*Response, error) {
    // 数据库操作
    msg, err := c.dao.GetMessage(ctx, req.MessageID)
    if err != nil {
        if err == sql.ErrNoRows {
            return nil, NewError(ErrMessageNotFound, 
                fmt.Sprintf("message_id=%d", req.MessageID))
        }
        // 包装错误，添加上下文
        return nil, fmt.Errorf("failed to get message: %w", err)
    }
    
    // 权限检查
    if err := c.checkPermission(ctx, req.UserID, msg); err != nil {
        return nil, fmt.Errorf("permission check failed: %w", err)
    }
    
    return msg, nil
}
```

### gRPC错误转换

```go
// 业务错误转换为gRPC错误
func ToGRPCError(err error) error {
    if err == nil {
        return nil
    }
    
    var bizErr *BizError
    if errors.As(err, &bizErr) {
        switch bizErr.Code {
        case ErrInvalidParam:
            return status.Error(codes.InvalidArgument, bizErr.Error())
        case ErrUnauthorized:
            return status.Error(codes.Unauthenticated, bizErr.Error())
        case ErrForbidden:
            return status.Error(codes.PermissionDenied, bizErr.Error())
        case ErrNotFound:
            return status.Error(codes.NotFound, bizErr.Error())
        case ErrRateLimit:
            return status.Error(codes.ResourceExhausted, bizErr.Error())
        default:
            return status.Error(codes.Internal, bizErr.Error())
        }
    }
    
    return status.Error(codes.Unknown, err.Error())
}
```

## 结构化日志规范

### 日志级别使用规范

```go
// ERROR - 系统错误，需要立即处理
// 使用场景: 数据库连接失败、关键依赖不可用
log.Error().
    Err(err).
    Str("service", "biz").
    Str("method", "sendMessage").
    Int64("user_id", userID).
    Msg("database connection failed")

// WARN - 警告，需要关注但系统可用
// 使用场景: 降级处理、非关键依赖失败、接近阈值
log.Warn().
    Str("service", "biz").
    Str("method", "sendMessage").
    Int64("user_id", userID).
    Int("queue_depth", queueDepth).
    Msg("message queue depth approaching limit")

// INFO - 关键业务流程
// 使用场景: 请求处理完成、状态变更、重要决策点
log.Info().
    Str("service", "biz").
    Str("method", "sendMessage").
    Int64("user_id", userID).
    Int64("msg_id", msgID).
    Dur("latency", duration).
    Msg("message sent successfully")

// DEBUG - 调试信息
// 使用场景: 详细的执行路径、变量值、函数入口/出口
log.Debug().
    Str("service", "biz").
    Str("method", "sendMessage").
    Int64("user_id", userID).
    Str("request", req.String()).
    Msg("processing request")
```

### 统一日志格式

```go
package logger

import (
    "github.com/rs/zerolog"
    "github.com/rs/zerolog/log"
)

// InitLogger 初始化结构化日志
func InitLogger(service string, env string) {
    // 设置时间格式
    zerolog.TimeFieldFormat = zerolog.TimeFormatUnixMs
    
    // 根据环境设置日志级别
    if env == "production" {
        zerolog.SetGlobalLevel(zerolog.InfoLevel)
    } else {
        zerolog.SetGlobalLevel(zerolog.DebugLevel)
    }
    
    // 添加全局字段
    log.Logger = log.With().
        Str("service", service).
        Str("env", env).
        Timestamp().
        Logger()
}

// ContextLogger 从context获取带trace_id的logger
func ContextLogger(ctx context.Context) zerolog.Logger {
    if traceID, ok := ctx.Value("trace_id").(string); ok {
        return log.With().Str("trace_id", traceID).Logger()
    }
    return log.Logger
}
```

### 分布式追踪日志

```go
// TraceMiddleware 添加追踪ID
func TraceMiddleware(next HandlerFunc) HandlerFunc {
    return func(ctx context.Context, req Request) (Response, error) {
        traceID := generateTraceID()
        ctx = context.WithValue(ctx, "trace_id", traceID)
        
        logger := logger.ContextLogger(ctx)
        logger.Info().
            Str("method", req.Method).
            Str("client_ip", req.ClientIP).
            Msg("request started")
        
        start := time.Now()
        resp, err := next(ctx, req)
        
        logger.Info().
            Dur("duration", time.Since(start)).
            Bool("success", err == nil).
            Msg("request completed")
        
        return resp, err
    }
}
```

## 错误重试策略

### 指数退避重试

```go
package retry

import (
    "context"
    "time"
)

type Config struct {
    MaxRetries  int
    InitialDelay time.Duration
    MaxDelay     time.Duration
    Multiplier   float64
}

func DefaultConfig() Config {
    return Config{
        MaxRetries:   3,
        InitialDelay: 100 * time.Millisecond,
        MaxDelay:     5 * time.Second,
        Multiplier:   2.0,
    }
}

// Do 执行带重试的函数
func Do(ctx context.Context, cfg Config, fn func() error) error {
    var err error
    delay := cfg.InitialDelay
    
    for i := 0; i <= cfg.MaxRetries; i++ {
        err = fn()
        if err == nil {
            return nil
        }
        
        // 不可重试的错误直接返回
        if !IsRetryable(err) {
            return err
        }
        
        // 最后一次不重试
        if i == cfg.MaxRetries {
            break
        }
        
        // 等待后重试
        select {
        case <-ctx.Done():
            return ctx.Err()
        case <-time.After(delay):
            delay = time.Duration(float64(delay) * cfg.Multiplier)
            if delay > cfg.MaxDelay {
                delay = cfg.MaxDelay
            }
        }
    }
    
    return fmt.Errorf("max retries exceeded: %w", err)
}

// IsRetryable 判断错误是否可重试
func IsRetryable(err error) bool {
    // 超时、连接错误、5xx错误可重试
    if errors.Is(err, context.DeadlineExceeded) {
        return true
    }
    
    var bizErr *code.BizError
    if errors.As(err, &bizErr) {
        switch bizErr.Code {
        case code.ErrTimeout, code.ErrInternal:
            return true
        }
    }
    
    return false
}
```

## 使用示例

```go
// 带重试的数据库操作
err := retry.Do(ctx, retry.DefaultConfig(), func() error {
    return c.dao.UpdateMessage(ctx, msg)
})

// 带日志的业务操作
func (c *Core) SendMessage(ctx context.Context, req *SendMessageReq) (*Message, error) {
    logger := logger.ContextLogger(ctx)
    
    logger.Debug().
        Int64("to_user", req.Peer.UserID).
        Str("message_preview", preview(req.Message)).
        Msg("sending message")
    
    // 检查配额
    allowed, err := c.checkQuota(ctx, req.FromUserID)
    if err != nil {
        logger.Error().
            Err(err).
            Msg("failed to check quota")
        return nil, err
    }
    
    if !allowed {
        logger.Warn().
            Int64("user_id", req.FromUserID).
            Msg("quota exceeded")
        return nil, code.NewError(code.ErrRateLimit, "daily quota exceeded")
    }
    
    // 发送消息
    msg, err := c.doSend(ctx, req)
    if err != nil {
        logger.Error().
            Err(err).
            Msg("failed to send message")
        return nil, err
    }
    
    logger.Info().
        Int64("msg_id", msg.ID).
        Int64("to_user", req.Peer.UserID).
        Msg("message sent")
    
    return msg, nil
}
```


path: references/v1.2.0-performance.md
content: # v1.2.0 - 性能优化与缓存策略

## 多级缓存架构

```
┌─────────────┐     ┌─────────────┐     ┌─────────────┐
│   L1 Cache  │ ──→ │   L2 Cache  │ ──→ │   Database  │
│  (Local)    │     │  (Redis)    │     │  (MySQL)    │
│   10ms      │     │   1-5ms     │     │   10-50ms   │
└─────────────┘     └─────────────┘     └─────────────┘
```

### 本地缓存 (L1)

```go
package cache

import (
    "time"
    "github.com/patrickmn/go-cache"
)

type LocalCache struct {
    c *cache.Cache
}

func NewLocalCache(defaultExpiration, cleanupInterval time.Duration) *LocalCache {
    return &LocalCache{
        c: cache.New(defaultExpiration, cleanupInterval),
    }
}

func (lc *LocalCache) Get(key string) (interface{}, bool) {
    return lc.c.Get(key)
}

func (lc *LocalCache) Set(key string, value interface{}, duration time.Duration) {
    lc.c.Set(key, value, duration)
}

func (lc *LocalCache) Delete(key string) {
    lc.c.Delete(key)
}

// 使用示例
var userCache = NewLocalCache(5*time.Minute, 10*time.Minute)

func (c *Core) GetUserWithLocalCache(ctx context.Context, userID int64) (*User, error) {
    key := fmt.Sprintf("user:%d", userID)
    
    // L1缓存
    if val, found := userCache.Get(key); found {
        return val.(*User), nil
    }
    
    // L2缓存 (Redis)
    user, err := c.getUserFromRedis(ctx, userID)
    if err == nil {
        userCache.Set(key, user, 5*time.Minute)
        return user, nil
    }
    
    // 数据库
    user, err = c.dao.GetUser(ctx, userID)
    if err != nil {
        return nil, err
    }
    
    // 回填缓存
    c.setUserToRedis(ctx, user)
    userCache.Set(key, user, 5*time.Minute)
    
    return user, nil
}
```

### Redis缓存模式

```go
package cache

import (
    "context"
    "encoding/json"
    "time"
    "github.com/redis/go-redis/v9"
)

type RedisCache struct {
    client *redis.Client
}

// CacheAside 旁路缓存模式
func (rc *RedisCache) CacheAside(ctx context.Context, key string, 
    expiration time.Duration, fn func() (interface{}, error)) (interface{}, error) {
    
    // 1. 读缓存
    val, err := rc.client.Get(ctx, key).Result()
    if err == nil {
        return val, nil
    }
    
    // 2. 缓存未命中，读数据库
    data, err := fn()
    if err != nil {
        return nil, err
    }
    
    // 3. 回填缓存
    bytes, _ := json.Marshal(data)
    rc.client.Set(ctx, key, bytes, expiration)
    
    return data, nil
}

// CacheAsideWithLock 防止缓存击穿
func (rc *RedisCache) CacheAsideWithLock(ctx context.Context, key string,
    expiration time.Duration, lockTimeout time.Duration,
    fn func() (interface{}, error)) (interface{}, error) {
    
    // 1. 读缓存
    val, err := rc.client.Get(ctx, key).Result()
    if err == nil {
        return val, nil
    }
    
    // 2. 尝试获取分布式锁
    lockKey := key + ":lock"
    locked, err := rc.client.SetNX(ctx, lockKey, "1", lockTimeout).Result()
    if err != nil || !locked {
        // 获取锁失败，等待后重试
        time.Sleep(100 * time.Millisecond)
        return rc.client.Get(ctx, key).Result()
    }
    
    // 3. 释放锁
    defer rc.client.Del(ctx, lockKey)
    
    // 4. 再次检查缓存（双重检查）
    val, err = rc.client.Get(ctx, key).Result()
    if err == nil {
        return val, nil
    }
    
    // 5. 读数据库
    data, err := fn()
    if err != nil {
        return nil, err
    }
    
    // 6. 回填缓存
    bytes, _ := json.Marshal(data)
    rc.client.Set(ctx, key, bytes, expiration)
    
    return data, nil
}

// WriteThrough 直写模式
func (rc *RedisCache) WriteThrough(ctx context.Context, key string,
    data interface{}, expiration time.Duration,
    writeFn func() error) error {
    
    // 1. 先写数据库
    if err := writeFn(); err != nil {
        return err
    }
    
    // 2. 再写缓存
    bytes, _ := json.Marshal(data)
    return rc.client.Set(ctx, key, bytes, expiration).Err()
}

// WriteBehind 异步写（高吞吐场景）
func (rc *RedisCache) WriteBehind(ctx context.Context, key string,
    data interface{}, expiration time.Duration,
    writeFn func() error) error {
    
    // 1. 先写缓存
    bytes, _ := json.Marshal(data)
    if err := rc.client.Set(ctx, key, bytes, expiration).Err(); err != nil {
        return err
    }
    
    // 2. 异步写数据库
    go func() {
        if err := writeFn(); err != nil {
            // 失败处理：重试或记录日志
        }
    }()
    
    return nil
}

// Invalidate 缓存失效
func (rc *RedisCache) Invalidate(ctx context.Context, keys ...string) error {
    return rc.client.Del(ctx, keys...).Err()
}

// InvalidatePattern 批量失效
func (rc *RedisCache) InvalidatePattern(ctx context.Context, pattern string) error {
    keys, err := rc.client.Keys(ctx, pattern).Result()
    if err != nil {
        return err
    }
    if len(keys) > 0 {
        return rc.client.Del(ctx, keys...).Err()
    }
    return nil
}
```

## 缓存一致性策略

### Cache-Aside + 延迟双删

```go
func (c *Core) UpdateUser(ctx context.Context, user *User) error {
    key := fmt.Sprintf("user:%d", user.ID)
    
    // 1. 删除缓存
    c.redis.Del(ctx, key)
    
    // 2. 更新数据库
    if err := c.dao.UpdateUser(ctx, user); err != nil {
        return err
    }
    
    // 3. 延迟再次删除（防止脏读）
    go func() {
        time.Sleep(500 * time.Millisecond)
        c.redis.Del(ctx, key)
    }()
    
    return nil
}
```

### 基于Binlog的缓存同步

```go
// Canal监听MySQL binlog
func (cs *CacheSync) HandleBinlog(event *canal.RowsEvent) {
    switch event.Table.Name {
    case "users":
        for _, row := range event.Rows {
            userID := row[0].(int64)
            
            // 删除缓存
            cs.redis.Del(ctx, fmt.Sprintf("user:%d", userID))
            
            // 删除本地缓存
            userCache.Delete(fmt.Sprintf("user:%d", userID))
        }
    }
}
```

## 数据库查询优化

### 分页查询优化

```go
// 游标分页（推荐）
func (d *DAO) GetMessagesCursor(ctx context.Context, chatID int64, cursor int64, limit int) ([]*Message, error) {
    query := `
        SELECT * FROM messages 
        WHERE chat_id = ? AND id < ? 
        ORDER BY id DESC 
        LIMIT ?
    `
    var msgs []*Message
    err := d.db.QueryRowsCtx(ctx, &msgs, query, chatID, cursor, limit)
    return msgs, err
}

// 避免OFFSET大数值的性能问题
// 差：OFFSET 100000
// 好：WHERE id < last_seen_id LIMIT 20
```

### 批量操作

```go
// 批量插入
func (d *DAO) BatchInsertMessages(ctx context.Context, msgs []*Message) error {
    query := `INSERT INTO messages (chat_id, sender_id, content, created_at) VALUES `
    
    var values []string
    var args []interface{}
    
    for _, msg := range msgs {
        values = append(values, "(?, ?, ?, ?)")
        args = append(args, msg.ChatID, msg.SenderID, msg.Content, msg.CreatedAt)
    }
    
    query += strings.Join(values, ", ")
    _, err := d.db.ExecCtx(ctx, query, args...)
    return err
}

// 批量查询（IN查询优化）
func (d *DAO) GetUsersByIDs(ctx context.Context, userIDs []int64) ([]*User, error) {
    // 分批处理，每批1000
    batchSize := 1000
    var result []*User
    
    for i := 0; i < len(userIDs); i += batchSize {
        end := i + batchSize
        if end > len(userIDs) {
            end = len(userIDs)
        }
        
        batch := userIDs[i:end]
        query, args, _ := sqlx.In(`SELECT * FROM users WHERE id IN (?)`, batch)
        query = d.db.Rebind(query)
        
        var users []*User
        err := d.db.SelectCtx(ctx, &users, query, args...)
        if err != nil {
            return nil, err
        }
        
        result = append(result, users...)
    }
    
    return result, nil
}
```

## 连接池优化

```go
// MySQL连接池配置
db, err := sqlx.Open("mysql", dataSource)
if err != nil {
    log.Fatal(err)
}

// 连接池设置
db.SetMaxOpenConns(100)        // 最大连接数
db.SetMaxIdleConns(20)         // 空闲连接数
db.SetConnMaxLifetime(5 * time.Minute)  // 连接最大生命周期
db.SetConnMaxIdleTime(1 * time.Minute)  // 空闲连接最大时间

// Redis连接池配置
redisClient := redis.NewClient(&redis.Options{
    Addr:         "localhost:6379",
    PoolSize:     100,              // 连接池大小
    MinIdleConns: 10,               // 最小空闲连接
    MaxConnAge:   5 * time.Minute,  // 连接最大年龄
})
```

## 性能监控

```go
// 慢查询监控
func SlowQueryMiddleware(next HandlerFunc) HandlerFunc {
    return func(ctx context.Context, req Request) (Response, error) {
        start := time.Now()
        resp, err := next(ctx, req)
        duration := time.Since(start)
        
        if duration > 100*time.Millisecond {
            log.Warn().
                Str("method", req.Method).
                Dur("duration", duration).
                Msg("slow query detected")
        }
        
        return resp, err
    }
}

// 缓存命中率监控
type CacheStats struct {
    Hits   int64
    Misses int64
}

func (cs *CacheStats) HitRate() float64 {
    total := cs.Hits + cs.Misses
    if total == 0 {
        return 0
    }
    return float64(cs.Hits) / float64(total)
}
```


path: references/v1.3.0-security.md
content: # v1.3.0 - 安全最佳实践

## 输入验证

```go
package validator

import (
    "regexp"
    "unicode/utf8"
)

// ValidateUserID 验证用户ID
func ValidateUserID(userID int64) error {
    if userID <= 0 {
        return errors.New("invalid user_id: must be positive")
    }
    return nil
}

// ValidateMessage 验证消息内容
func ValidateMessage(content string) error {
    length := utf8.RuneCountInString(content)
    if length == 0 {
        return errors.New("message cannot be empty")
    }
    if length > 4096 {
        return errors.New("message too long: max 4096 characters")
    }
    
    // 检查危险字符
    dangerous := []string{"<script", "javascript:", "onerror", "onload"}
    lower := strings.ToLower(content)
    for _, d := range dangerous {
        if strings.Contains(lower, d) {
            return errors.New("message contains dangerous content")
        }
    }
    
    return nil
}

// ValidatePhone 验证手机号
func ValidatePhone(phone string) error {
    // E.164格式
    matched, _ := regexp.MatchString(`^\+[1-9]\d{1,14}$`, phone)
    if !matched {
        return errors.New("invalid phone number format")
    }
    return nil
}

// ValidateFileName 验证文件名
func ValidateFileName(name string) error {
    // 防止路径遍历
    if strings.Contains(name, "..") || strings.Contains(name, "/") || strings.Contains(name, "\\") {
        return errors.New("invalid filename: path traversal detected")
    }
    
    // 检查扩展名
    allowedExts := map[string]bool{
        ".jpg": true, ".jpeg": true, ".png": true, ".gif": true,
        ".mp4": true, ".mov": true, ".pdf": true, ".doc": true,
    }
    
    ext := strings.ToLower(filepath.Ext(name))
    if !allowedExts[ext] {
        return errors.New("file type not allowed")
    }
    
    return nil
}
```

## SQL注入防护

```go
package dao

// 使用参数化查询（永远不要用字符串拼接）

// ✅ 安全
func (d *DAO) GetUserSafe(userID int64) (*User, error) {
    query := "SELECT * FROM users WHERE id = ?"
    var user User
    err := d.db.QueryRow(query, userID).Scan(&user)
    return &user, err
}

// ❌ 危险 - SQL注入
func (d *DAO) GetUserUnsafe(userID string) (*User, error) {
    query := fmt.Sprintf("SELECT * FROM users WHERE id = %s", userID)
    // 攻击: userID = "1 OR 1=1"
    var user User
    err := d.db.QueryRow(query).Scan(&user)
    return &user, err
}

// IN查询安全处理
func (d *DAO) GetUsersByIDs(userIDs []int64) ([]*User, error) {
    query, args, err := sqlx.In("SELECT * FROM users WHERE id IN (?)", userIDs)
    if err != nil {
        return nil, err
    }
    query = d.db.Rebind(query)
    
    var users []*User
    err = d.db.Select(&users, query, args...)
    return users, err
}

// LIKE查询安全处理
func (d *DAO) SearchUsers(name string) ([]*User, error) {
    // 转义通配符
    safeName := strings.ReplaceAll(name, "%", "\\%")
    safeName = strings.ReplaceAll(safeName, "_", "\\_")
    
    query := "SELECT * FROM users WHERE name LIKE ? ESCAPE '\\'"
    pattern := "%" + safeName + "%"
    
    var users []*User
    err := d.db.Select(&users, query, pattern)
    return users, err
}
```

## XSS防护

```go
package security

import (
    "html"
    "strings"
)

// SanitizeHTML 转义HTML特殊字符
func SanitizeHTML(input string) string {
    return html.EscapeString(input)
}

// StripTags 移除HTML标签
func StripTags(input string) string {
    // 简单实现，生产环境使用bluemonday
    re := regexp.MustCompile(`<[^>]+>`)
    return re.ReplaceAllString(input, "")
}

// 消息内容处理
func ProcessMessageContent(content string) string {
    // 1. 转义HTML
    content = html.EscapeString(content)
    
    // 2. 保留允许的格式（Markdown-style）
    // **bold** → <b>bold</b>
    content = processBold(content)
    
    // 3. 链接处理
    content = processLinks(content)
    
    return content
}
```

## CSRF防护

```go
package middleware

// CSRF中间件
func CSRFMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        // 验证Origin头
        origin := r.Header.Get("Origin")
        if origin != "" && !isAllowedOrigin(origin) {
            http.Error(w, "Invalid origin", http.StatusForbidden)
            return
        }
        
        // 验证Referer
        referer := r.Header.Get("Referer")
        if referer != "" && !strings.HasPrefix(referer, allowedDomain) {
            http.Error(w, "Invalid referer", http.StatusForbidden)
            return
        }
        
        // 非安全方法需要验证CSRF Token
        if r.Method != "GET" && r.Method != "HEAD" {
            token := r.Header.Get("X-CSRF-Token")
            if !validateCSRFToken(r, token) {
                http.Error(w, "Invalid CSRF token", http.StatusForbidden)
                return
            }
        }
        
        next.ServeHTTP(w, r)
    })
}
```

## 敏感数据保护

```go
package security

import (
    "crypto/aes"
    "crypto/cipher"
    "crypto/rand"
    "encoding/base64"
    "io"
)

// 敏感字段加密（数据库中存储加密数据）
type SensitiveData struct {
    key []byte
}

func NewSensitiveData(key []byte) *SensitiveData {
    return &SensitiveData{key: key}
}

// Encrypt 加密敏感数据
func (s *SensitiveData) Encrypt(plaintext string) (string, error) {
    block, err := aes.NewCipher(s.key)
    if err != nil {
        return "", err
    }
    
    gcm, err := cipher.NewGCM(block)
    if err != nil {
        return "", err
    }
    
    nonce := make([]byte, gcm.NonceSize())
    if _, err = io.ReadFull(rand.Reader, nonce); err != nil {
        return "", err
    }
    
    ciphertext := gcm.Seal(nonce, nonce, []byte(plaintext), nil)
    return base64.StdEncoding.EncodeToString(ciphertext), nil
}

// Decrypt 解密敏感数据
func (s *SensitiveData) Decrypt(ciphertext string) (string, error) {
    data, err := base64.StdEncoding.DecodeString(ciphertext)
    if err != nil {
        return "", err
    }
    
    block, err := aes.NewCipher(s.key)
    if err != nil {
        return "", err
    }
    
    gcm, err := cipher.NewGCM(block)
    if err != nil {
        return "", err
    }
    
    nonceSize := gcm.NonceSize()
    if len(data) < nonceSize {
        return "", errors.New("ciphertext too short")
    }
    
    nonce, ciphertext := data[:nonceSize], data[nonceSize:]
    plaintext, err := gcm.Open(nil, nonce, ciphertext, nil)
    if err != nil {
        return "", err
    }
    
    return string(plaintext), nil
}

// 使用示例
func (d *DAO) SaveUserPhone(userID int64, phone string) error {
    // 加密手机号后存储
    encrypted, err := sensitiveData.Encrypt(phone)
    if err != nil {
        return err
    }
    
    query := "UPDATE users SET phone_encrypted = ? WHERE id = ?"
    _, err = d.db.Exec(query, encrypted, userID)
    return err
}
```

## 密钥管理

```go
package security

import (
    "os"
    "github.com/hashicorp/vault/api"
)

// Vault密钥管理
type VaultManager struct {
    client *api.Client
}

func NewVaultManager(addr, token string) (*VaultManager, error) {
    config := &api.Config{
        Address: addr,
    }
    
    client, err := api.NewClient(config)
    if err != nil {
        return nil, err
    }
    
    client.SetToken(token)
    
    return &VaultManager{client: client}, nil
}

func (v *VaultManager) GetSecret(path string) (map[string]interface{}, error) {
    secret, err := v.client.Logical().Read(path)
    if err != nil {
        return nil, err
    }
    
    if secret == nil {
        return nil, errors.New("secret not found")
    }
    
    return secret.Data, nil
}

// 本地开发环境密钥加载
func LoadSecret(keyName string) string {
    // 1. 尝试从环境变量读取
    if val := os.Getenv(keyName); val != "" {
        return val
    }
    
    // 2. 尝试从文件读取（Docker Secrets等）
    if data, err := os.ReadFile("/run/secrets/" + keyName); err == nil {
        return string(data)
    }
    
    // 3. 开发环境使用默认值（生产环境不允许）
    if os.Getenv("ENV") == "development" {
        return getDevSecret(keyName)
    }
    
    log.Fatal("Secret not found: ", keyName)
    return ""
}
```

## 安全Headers

```go
package middleware

func SecurityHeaders(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        // 内容安全策略
        w.Header().Set("Content-Security-Policy", 
            "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'")
        
        // 禁用MIME类型嗅探
        w.Header().Set("X-Content-Type-Options", "nosniff")
        
        // XSS保护
        w.Header().Set("X-XSS-Protection", "1; mode=block")
        
        // 点击劫持保护
        w.Header().Set("X-Frame-Options", "DENY")
        
        // HSTS（HTTPS强制）
        w.Header().Set("Strict-Transport-Security", 
            "max-age=31536000; includeSubDomains; preload")
        
        //  Referrer策略
        w.Header().Set("Referrer-Policy", "strict-origin-when-cross-origin")
        
        // 权限策略
        w.Header().Set("Permissions-Policy", 
            "geolocation=(), microphone=(), camera=()")
        
        next.ServeHTTP(w, r)
    })
}
```

## 审计日志

```go
package audit

import (
    "context"
    "encoding/json"
    "time"
)

type AuditEvent struct {
    Timestamp   time.Time              `json:"timestamp"`
    UserID      int64                  `json:"user_id"`
    Action      string                 `json:"action"`
    Resource    string                 `json:"resource"`
    ResourceID  int64                  `json:"resource_id"`
    Status      string                 `json:"status"`
    IP          string                 `json:"ip"`
    UserAgent   string                 `json:"user_agent"`
    Details     map[string]interface{} `json:"details"`
}

type Auditor struct {
    producer KafkaProducer
}

func (a *Auditor) Log(ctx context.Context, event *AuditEvent) {
    event.Timestamp = time.Now()
    
    // 异步发送审计日志
    go func() {
        data, _ := json.Marshal(event)
        a.producer.Publish("audit.events", data)
    }()
}

// 使用示例
func (s *Server) DeleteMessage(ctx context.Context, req *DeleteMessageReq) (*Bool, error) {
    // 执行删除
    err := s.core.DeleteMessage(ctx, req.ChatID, req.MessageID)
    
    // 记录审计日志
    s.auditor.Log(ctx, &audit.AuditEvent{
        UserID:     getUserID(ctx),
        Action:     "DELETE",
        Resource:   "message",
        ResourceID: req.MessageID,
        Status:     getStatus(err),
        IP:         getClientIP(ctx),
        UserAgent:  getUserAgent(ctx),
        Details: map[string]interface{}{
            "chat_id": req.ChatID,
            "reason":  req.Reason,
        },
    })
    
    return BoolTrue, err
}
```


path: references/v1.4.0-testing.md
content: # v1.4.0 - 测试策略

## 单元测试

```go
func TestSendMessage(t *testing.T) {
    // 使用 testify/mock
    mockDAO := new(MockMessageDAO)
    core := NewMessageCore(mockDAO)
    
    mockDAO.On("Insert", mock.Anything, mock.Anything).Return(int64(1), nil)
    
    msg, err := core.SendMessage(ctx, req)
    
    assert.NoError(t, err)
    assert.Equal(t, int64(1), msg.ID)
    mockDAO.AssertExpectations(t)
}
```

## 集成测试

```go
func TestMessageFlow(t *testing.T) {
    // 使用 testcontainers 启动真实依赖
    mysql := testcontainers.NewMySQLContainer()
    redis := testcontainers.NewRedisContainer()
    
    // 执行完整流程
    msg := sendMessage(t, user1, user2, "hello")
    assertMessageDelivered(t, user2, msg.ID)
}
```

## 负载测试

```go
func BenchmarkSendMessage(b *testing.B) {
    for i := 0; i < b.N; i++ {
        core.SendMessage(ctx, req)
    }
}
```


path: references/v1.5.0-observability.md
content: # v1.5.0 - 可观测性

## 指标收集

```go
// Prometheus指标
var (
    requestDuration = prometheus.NewHistogramVec(
        prometheus.HistogramOpts{
            Name: "rpc_request_duration_seconds",
            Help: "RPC request duration",
        },
        []string{"method", "status"},
    )
)

// 链路追踪
func TraceMiddleware(next Handler) Handler {
    return func(ctx context.Context, req Request) {
        span, ctx := opentracing.StartSpanFromContext(ctx, req.Method)
        defer span.Finish()
        
        next(ctx, req)
    }
}
```

## 健康检查

```go
func HealthCheck(w http.ResponseWriter, r *http.Request) {
    checks := map[string]bool{
        "mysql": checkMySQL(),
        "redis": checkRedis(),
    }
    
    for name, ok := range checks {
        if !ok {
            http.Error(w, name+" unhealthy", 503)
            return
        }
    }
    
    w.Write([]byte("OK"))
}
```


path: references/v1.6.0-database.md
content: # v1.6.0 - 数据库优化

## 分库分表

```go
// 按user_id分表
func GetTableName(userID int64) string {
    return fmt.Sprintf("messages_%d", userID%16)
}

// 读写分离
func (d *DAO) Query(ctx context.Context, query string, args ...interface{}) {
    return d.readDB.QueryContext(ctx, query, args...)
}

func (d *DAO) Exec(ctx context.Context, query string, args ...interface{}) {
    return d.writeDB.ExecContext(ctx, query, args...)
}
```

## 索引优化

```sql
-- 复合索引
CREATE INDEX idx_chat_created ON messages(chat_id, created_at);

-- 覆盖索引
CREATE INDEX idx_user_status ON users(status) INCLUDE (id, username);
```


path: references/v1.7.0-queue.md
content: # v1.7.0 - 消息队列

## Kafka集成

```go
// 生产者
producer, _ := kafka.NewProducer(&kafka.ConfigMap{
    "bootstrap.servers": "localhost:9092",
})

producer.Produce(&kafka.Message{
    TopicPartition: kafka.TopicPartition{Topic: "messages"},
    Value:          msgBytes,
}, nil)

// 消费者
consumer, _ := kafka.NewConsumer(&kafka.ConfigMap{
    "bootstrap.servers": "localhost:9092",
    "group.id":          "message-processor",
})

consumer.Subscribe("messages", nil)
```

## 事件驱动架构

```go
// 事件处理器
type EventHandler struct {
    handlers map[string]func(event Event)
}

func (h *EventHandler) Handle(event Event) {
    if handler, ok := h.handlers[event.Type]; ok {
        handler(event)
    }
}
```


path: references/v1.8.0-circuit-breaker.md
content: # v1.8.0 - 熔断限流

## 熔断器

```go
import "github.com/sony/gobreaker"

var cb = gobreaker.NewCircuitBreaker(gobreaker.Settings{
    Name:        "mysql",
    MaxRequests: 100,
    Interval:    10 * time.Second,
    Timeout:     30 * time.Second,
    ReadyToTrip: func(counts gobreaker.Counts) bool {
        return counts.ConsecutiveFailures > 5
    },
})

result, err := cb.Execute(func() (interface{}, error) {
    return db.Query(query)
})
```

## 限流

```go
import "golang.org/x/time/rate"

limiter := rate.NewLimiter(rate.Limit(100), 200) // 100/s, burst 200

if !limiter.Allow() {
    return nil, errors.New("rate limit exceeded")
}
```


path: references/v1.9.0-multi-tenant.md
content: # v1.9.0 - 多租户

## 租户隔离

```go
type TenantContext struct {
    TenantID string
    DB       *sql.DB
}

func (c *TenantContext) Query(query string, args ...interface{}) {
    // 自动添加租户过滤
    tenantQuery := fmt.Sprintf("%s WHERE tenant_id = ?", query)
    return c.DB.Query(tenantQuery, append(args, c.TenantID)...)
}
```

## 数据隔离策略

```sql
-- 行级隔离
ALTER TABLE messages ADD COLUMN tenant_id VARCHAR(64);
CREATE INDEX idx_tenant ON messages(tenant_id);
```


path: references/v2.0.0-final.md
content: # v2.0.0 - 完整总结

## 十次迭代回顾

| 版本 | 主题 | 关键改进 |
|------|------|----------|
| v1.1.0 | 错误处理 | 统一错误码、结构化日志、重试策略 |
| v1.2.0 | 性能优化 | 多级缓存、连接池、查询优化 |
| v1.3.0 | 安全 | 输入验证、SQL注入防护、XSS防护 |
| v1.4.0 | 测试 | 单元测试、集成测试、负载测试 |
| v1.5.0 | 可观测性 | 指标、链路追踪、健康检查 |
| v1.6.0 | 数据库 | 分库分表、读写分离、索引优化 |
| v1.7.0 | 消息队列 | Kafka集成、事件驱动架构 |
| v1.8.0 | 熔断限流 | 熔断器、限流算法 |
| v1.9.0 | 多租户 | 租户隔离、数据隔离策略 |
| v2.0.0 | 总结 | 完整知识体系 |

## 生产检查清单

- [ ] 错误处理完善
- [ ] 缓存策略配置
- [ ] 安全扫描通过
- [ ] 测试覆盖>80%
- [ ] 监控告警配置
- [ ] 数据库优化
- [ ] 熔断限流启用
- [ ] 性能压测通过


Security scan verdicts — ClawScan: suspicious | static analysis: clean | VirusTotal: clean | SkillSpector: clean (LOW)