Files
im_server/docs/CODE-WALKTHROUGH.md
T
2026-08-11 15:59:57 +08:00

40 KiB
Raw Blame History

Tinode Chat Server 代码通读指南

本指南帮助你快速理解 Tinode 代码架构,掌握开发流程,能够独立完成新需求开发和项目部署。


目录

  1. 架构总览
  2. 启动流程详解
  3. 核心数据结构
  4. 消息处理流程
  5. 关键模块详解
  6. 开发实战指南
  7. 测试与调试
  8. 部署指南

架构总览

整体架构图

┌─────────────────────────────────────────────────────────────────────────────┐
│                              客户端层                                        │
│   ┌─────────────┐   ┌─────────────┐   ┌─────────────┐   ┌─────────────┐   │
│   │  WebSocket  │   │  Long Poll  │   │    gRPC     │   │    HTTP     │   │
│   └──────┬──────┘   └──────┬──────┘   └──────┬──────┘   └──────┬──────┘   │
└──────────┼─────────────────┼─────────────────┼─────────────────┼───────────┘
           │                 │                 │                 │
           ▼                 ▼                 ▼                 ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│                              协议处理层                                      │
│   ┌─────────────────────────────────────────────────────────────────────┐   │
│   │                     hdl_websock.go / hdl_longpoll.go                │   │
│   │                        hdl_grpc.go / http.go                        │   │
│   └─────────────────────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────────────────────┘
           │
           ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│                              会话层 (Session)                                │
│   ┌─────────────────────────────────────────────────────────────────────┐   │
│   │  Session 结构体 - 管理单个客户端连接                                │   │
│   │  - 认证状态、订阅列表、消息队列                                      │   │
│   └─────────────────────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────────────────────┘
           │
           ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│                              Hub (核心路由)                                  │
│   ┌─────────────────────────────────────────────────────────────────────┐   │
│   │  - 消息路由分发                                                      │   │
│   │  - Topic 生命周期管理                                                │   │
│   │  - 全局状态维护                                                      │   │
│   └─────────────────────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────────────────────┘
           │
           ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│                              Topic 层                                        │
│   ┌─────────┐   ┌─────────┐   ┌─────────┐   ┌─────────┐   ┌─────────┐     │
│   │   me    │   │   fnd   │   │   p2p   │   │   grp   │   │   sys   │     │
│   │ (用户)  │   │ (发现)  │   │ (单聊)  │   │ (群聊)  │   │ (系统)  │     │
│   └─────────┘   └─────────┘   └─────────┘   └─────────┘   └─────────┘     │
└─────────────────────────────────────────────────────────────────────────────┘
           │
           ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│                              存储层 (Store)                                  │
│   ┌─────────────────────────────────────────────────────────────────────┐   │
│   │  store.go - 存储抽象层                                              │   │
│   └─────────────────────────────────────────────────────────────────────┘   │
│   ┌─────────┐   ┌─────────┐   ┌─────────┐   ┌─────────┐                   │
│   │  MySQL  │   │PostgreSQL│   │ MongoDB │   │RethinkDB│                   │
│   └─────────┘   └─────────┘   └─────────┘   └─────────┘                   │
└─────────────────────────────────────────────────────────────────────────────┘

核心组件职责

组件 文件 职责
main.go 入口点 配置加载、组件初始化、服务启动
Hub hub.go 全局消息路由、Topic 生命周期管理
Session session.go 客户端连接管理、消息收发
Topic topic.go 聊天室/会话逻辑、消息处理
Store store/store.go 数据库操作抽象层
Plugins plugins.go 插件系统、事件钩子

启动流程详解

1. 入口函数 (main.go)

// main.go 启动流程
func main() {
    // 1. 解析命令行参数
    flag.Parse()

    // 2. 加载配置文件
    config := loadConfig(configFileName)

    // 3. 初始化日志系统
    logs.Init()

    // 4. 初始化数据库适配器
    store.Open(config.StoreConfig)

    // 5. 初始化认证模块
    auth.Init(config.AuthConfig)

    // 6. 初始化推送通知
    push.Init(config.PushConfig)

    // 7. 初始化媒体处理
    media.Init(config.MediaConfig)

    // 8. 创建 Hub(核心路由)
    globals.hub = newHub()

    // 9. 创建 SessionStore
    globals.sessionStore = newSessionStore()

    // 10. 初始化插件
    pluginsInit(config.PluginConfig)

    // 11. 启动 HTTP/WebSocket 服务
    listenAndServe(addr, mux, tlsConfig, stop)
}

2. 配置加载流程

配置文件 tinode.conf 是 JSON 格式,主要配置项:

{
    "listen": ":6060",              // HTTP 监听地址
    "grpc_listen": ":16060",        // gRPC 监听地址
    "api_key_salt": "...",          // API 密钥盐值
    "max_message_size": 131072,     // 最大消息大小

    "store_config": {               // 数据库配置
        "use_adapter": "mysql",
        "adapters": {
            "mysql": { "dsn": "..." }
        }
    },

    "auth_config": {                // 认证配置
        "basic": { "add_to_tags": true }
    },

    "push_config": {                // 推送配置
        "fcm": { "credentials_file": "..." }
    }
}

3. 数据库初始化 (tinode-db)

# 初始化数据库并加载测试数据
./tinode-db -config=tinode.conf -data=data.json

# 主要步骤:
# 1. 连接数据库
# 2. 创建表/集合
# 3. 创建索引
# 4. 加载初始数据(用户、话题)

核心数据结构

1. Hub 结构体

Hub 是整个系统的核心路由器:

// server/hub.go
type Hub struct {
    // Topic 缓存 (name -> *Topic)
    topics *sync.Map

    // 客户端消息路由通道
    routeCli chan *ClientComMessage

    // 服务端消息路由通道
    routeSrv chan *ServerComMessage

    // 订阅请求通道
    join chan *ClientComMessage

    // Topic 注销通道
    unreg chan *topicUnreg

    // 用户状态变更通道
    userStatus chan *userStatusReq

    // 关闭通道
    shutdown chan chan<- bool
}

Hub 的核心方法:

// hub.go - 主事件循环
func (h *Hub) run() {
    for {
        select {
        case msg := <-h.routeCli:
            // 路由客户端消息到对应 Topic
            h.routeClientMessage(msg)

        case msg := <-h.routeSrv:
            // 路由服务端消息
            h.routeServerMessage(msg)

        case req := <-h.join:
            // 处理订阅请求,创建或加载 Topic
            h.processJoin(req)

        case req := <-h.unreg:
            // 注销 Topic
            h.processUnregister(req)

        case done := <-h.shutdown:
            // 关闭所有 Topic
            h.gracefulShutdown()
            done <- true
            return
        }
    }
}

2. Topic 结构体

Topic 是聊天的核心单元:

// server/topic.go
type Topic struct {
    // Topic 名称 (如 "usrABC", "grpXYZ")
    name string

    // Topic 类型
    cat types.TopicCat  // me, fnd, p2p, grp, sys, slf

    // 订阅者数据
    perUser map[types.Uid]perUserData

    // 附加的会话
    sessions map[*Session]perSessionData

    // 消息通道
    clientMsg chan *ClientComMessage  // 客户端消息
    serverMsg chan *ServerComMessage  // 服务端消息
    reg       chan *ClientComMessage  // 订阅请求
    unreg     chan *ClientComMessage  // 取消订阅
    meta      chan *ClientComMessage  // 元数据操作
    exit      chan *shutDown          // 关闭信号

    // 状态
    status int32  // 0=none, 1=new, 2=ready, 3=paused
}

Topic 类型分类:

类型 前缀 说明
me me 用户个人话题,管理联系人列表
fnd fnd 搜索话题,用于发现用户
p2p p2p 点对点聊天
grp grp 群组聊天
chn chn 频道(只读群组)
sys sys 系统话题
slf slf 自我话题(草稿/收藏)

3. Session 结构体

Session 代表一个客户端连接:

// server/session.go
type Session struct {
    // 协议类型
    proto SessionProto  // WEBSOCK, LPOLL, GRPC, PROXY

    // 会话 ID
    sid string

    // 连接句柄
    ws       *websocket.Conn           // WebSocket
    grpcnode pbx.Node_MessageLoopServer // gRPC

    // 用户信息
    uid     types.Uid   // 用户 ID
    authLvl auth.Level  // 认证级别

    // 消息队列
    send chan any       // 发送队列
    stop chan any       // 停止信号
    detach chan string  // 分离话题

    // 订阅的话题
    subs map[string]*Subscription

    // 状态标记
    terminating int32   // 是否正在终止
    background bool     // 是否后台会话
}

4. 消息结构体

客户端到服务端的消息定义:

// server/datamodel.go

// 握手消息
type MsgClientHi struct {
    Id        string `json:"id,omitempty"`
    UserAgent string `json:"ua,omitempty"`
    Version   string `json:"ver,omitempty"`
    DeviceID  string `json:"dev,omitempty"`
    Platform  string `json:"platf,omitempty"`
}

// 登录消息
type MsgClientLogin struct {
    Id     string `json:"id,omitempty"`
    Scheme string `json:"scheme,omitempty"`
    Secret []byte `json:"secret,omitempty"`
}

// 订阅消息
type MsgClientSub struct {
    Id      string       `json:"id,omitempty"`
    Topic   string       `json:"topic,omitempty"`
    Get     *MsgGetQuery `json:"get,omitempty"`
    Set     *MsgSetQuery `json:"set,omitempty"`
}

// 发布消息
type MsgClientPub struct {
    Id      string `json:"id,omitempty"`
    Topic   string `json:"topic,omitempty"`
    Content any    `json:"content,omitempty"`
}

消息处理流程

1. 完整消息流转图

┌──────────────────────────────────────────────────────────────────────────────┐
│  客户端发送: {"hi", "ver":"0.25", "ua":"Chrome/120"}                        │
└──────────────────────────────────────────────────────────────────────────────┘
                                    │
                                    ▼
┌──────────────────────────────────────────────────────────────────────────────┐
│  WebSocket Handler (hdl_websock.go)                                          │
│  serveWebSocket() -> sess.readLoop() -> sess.dispatchRaw(raw)               │
└──────────────────────────────────────────────────────────────────────────────┘
                                    │
                                    ▼
┌──────────────────────────────────────────────────────────────────────────────┐
│  Session 消息分发 (session.go)                                               │
│  dispatchRaw() -> 解析 JSON -> 识别消息类型 -> 调用对应 handler              │
└──────────────────────────────────────────────────────────────────────────────┘
                                    │
            ┌───────────────────────┼───────────────────────┐
            ▼                       ▼                       ▼
     ┌──────────┐            ┌──────────┐            ┌──────────┐
     │  hi      │            │  login   │            │  sub     │
     │  handler │            │  handler │            │  handler │
     └──────────┘            └──────────┘            └──────────┘
            │                       │                       │
            └───────────────────────┼───────────────────────┘
                                    ▼
┌──────────────────────────────────────────────────────────────────────────────┐
│  Hub 路由 (hub.go)                                                           │
│  routeCli <- ClientComMessage -> h.routeClientMessage()                      │
│  根据 RcptTo 找到对应 Topic -> 发送到 Topic.clientMsg 通道                   │
└──────────────────────────────────────────────────────────────────────────────┘
                                    │
                                    ▼
┌──────────────────────────────────────────────────────────────────────────────┐
│  Topic 处理 (topic.go)                                                       │
│  t.clientMsg <- msg -> t.processClientMessage()                              │
│  根据 msg.Original 类型调用对应处理函数                                       │
│  - hi -> t.handleHi()                                                        │
│  - login -> t.handleLogin()                                                  │
│  - sub -> t.handleSubscribe()                                                │
│  - pub -> t.handlePublish()                                                  │
└──────────────────────────────────────────────────────────────────────────────┘
                                    │
                                    ▼
┌──────────────────────────────────────────────────────────────────────────────┐
│  数据库操作 (store/store.go)                                                 │
│  store.MessageSave() / store.TopicGet() / store.UserCreate()                │
└──────────────────────────────────────────────────────────────────────────────┘
                                    │
                                    ▼
┌──────────────────────────────────────────────────────────────────────────────┐
│  响应消息生成                                                                 │
│  ServerComMessage{Ctrl/Data/Pres/Meta/Info}                                  │
│  -> 序列化为 JSON -> 发送到 Session.send 通道                                 │
└──────────────────────────────────────────────────────────────────────────────┘
                                    │
                                    ▼
┌──────────────────────────────────────────────────────────────────────────────┐
│  Session 发送 (session.go)                                                   │
│  sess.writeLoop() -> sess.send <- msg -> WebSocket.WriteMessage()           │
└──────────────────────────────────────────────────────────────────────────────┘

2. 消息类型与处理函数映射

消息类型 结构体 处理函数 说明
hi MsgClientHi handleHi() 握手,建立连接
login MsgClientLogin handleLogin() 用户登录认证
acc MsgClientAcc handleAccount() 创建/更新账户
sub MsgClientSub handleSubscribe() 订阅话题
leave MsgClientLeave handleLeave() 取消订阅
pub MsgClientPub handlePublish() 发布消息
get MsgClientGet handleGet() 获取元数据/消息
set MsgClientSet handleSet() 更新元数据
del MsgClientDel handleDelete() 删除消息/话题
note MsgClientNote handleNote() 发送通知

3. WebSocket 消息处理示例

// server/hdl_websock.go

// 处理 WebSocket 连接
func serveWebSocket(wrt http.ResponseWriter, req *http.Request) {
    // 1. 验证 API Key
    if isValid, _ := checkAPIKey(getAPIKey(req)); !isValid {
        wrt.WriteHeader(http.StatusForbidden)
        return
    }

    // 2. 升级 HTTP 连接为 WebSocket
    ws, err := upgrader.Upgrade(wrt, req, nil)

    // 3. 创建 Session
    sess, count := globals.sessionStore.NewSession(ws, "")

    // 4. 启动读写协程
    go sess.writeLoop()  // 发送消息
    go sess.readLoop()   // 接收消息
}

// 读循环
func (sess *Session) readLoop() {
    for {
        _, raw, err := sess.ws.ReadMessage()
        if err != nil {
            return
        }
        sess.dispatchRaw(raw)  // 分发消息
    }
}

// 写循环
func (sess *Session) writeLoop() {
    for {
        select {
        case msg := <-sess.send:
            sess.sendMessage(msg)
        case <-sess.stop:
            return
        }
    }
}

关键模块详解

1. 认证模块 (server/auth/)

认证模块采用插件式架构:

server/auth/
├── auth.go          # 认证接口定义
├── basic/           # 用户名密码认证
├── token/           # Token 认证
├── anon/            # 匿名认证
├── code/            # 验证码认证
└── rest/            # REST 外部认证

认证接口定义:

// server/auth/auth.go
type Authenticator interface {
    // 初始化
    Init(jsonconf json.RawMessage, opts ...any) error

    // 认证检查
    Authenticate(secret []byte) (types.Uid, Level, time.Time, error)

    // 生成秘密值
    GenSecret(uid types.Uid, authLvl Level, expires time.Time) ([]byte, error)
}

添加新认证方式步骤:

  1. 在 server/auth/ 下创建新目录,如 oauth/
  2. 实现 Authenticator 接口
  3. 在 main.go 中导入: _ "github.com/tinode/chat/server/auth/oauth"
  4. 在配置文件中添加相应配置

2. 数据库适配器 (server/db/)

数据库层采用适配器模式:

// server/db/adapter.go
type Adapter interface {
    // 连接管理
    Open(config json.RawMessage) error
    Close() error
    IsOpen() bool

    // 用户管理
    UserCreate(user *t.User) error
    UserGet(uid t.Uid) (*t.User, error)
    UserUpdate(uid t.Uid, update map[string]any) error
    UserDelete(uid t.Uid, hard bool) error

    // 话题管理
    TopicCreate(topic *t.Topic) error
    TopicGet(name string) (*t.Topic, error)

    // 消息管理
    MessageSave(msg *t.Message) error
    MessageGet(topic string, opts *MsgGetOpts) ([]t.Message, error)

    // 订阅管理
    SubscriptionGet(topic string, uid t.Uid) (*t.Subscription, error)
}

添加新数据库支持:

  1. 在 server/db/ 下创建新目录,如 cockroach/
  2. 实现 Adapter 接口
  3. 在 main.go 中导入: _ "github.com/tinode/chat/server/db/cockroach"
  4. 构建时指定 tag: go build -tags cockroach

3. 推送通知 (server/push/)

// server/push/push.go
type PushHandler interface {
    // 初始化
    Init(jsonconf json.RawMessage) error

    // 发送推送
    Push(userId types.Uid, data *PushData) error
}

FCM 推送配置:

// tinode.conf
"push_config": {
    "fcm": {
        "credentials_file": "/path/to/credentials.json"
    }
}

4. 文件存储 (server/media/)

支持本地文件系统和 S3:

// server/media/media.go
type Handler interface {
    // 上传
    Upload(uri string, reader io.Reader) (string, error)

    // 下载
    Download(uri string) (io.ReadCloser, error)

    // 删除
    Delete(uri string) error
}

5. 插件系统 (server/plugins.go)

插件可以拦截和处理所有消息:

// 插件事件类型
const (
    plgHi    = 1 << iota  // 握手
    plgAcc                  // 账户操作
    plgLogin                // 登录
    plgSub                  // 订阅
    plgPub                  // 发布
    plgGet                  // 获取
    plgSet                  // 设置
    plgDel                  // 删除
)

// 插件接口
type Plugin interface {
    // 处理客户端消息
    HandleClientMessage(msg *ClientComMessage) (*ClientComMessage, error)

    // 处理服务端消息
    HandleServerMessage(msg *ServerComMessage) (*ServerComMessage, error)
}

开发实战指南

场景 1: 添加新的消息类型

步骤 1: 定义消息结构体

// server/datamodel.go
type MsgClientTyping struct {
    Id      string `json:"id,omitempty"`
    Topic   string `json:"topic,omitempty"`
    IsTyping bool  `json:"typing"`
}

步骤 2: 添加解析逻辑

// server/session.go - dispatchRaw()
func (sess *Session) dispatchRaw(raw []byte) {
    var msg struct {
        Hi    *MsgClientHi    `json:"hi"`
        // ... 其他类型
        Typing *MsgClientTyping `json:"typing"`  // 新增
    }
    json.Unmarshal(raw, &msg)

    if msg.Typing != nil {
        sess.handleTyping(msg.Typing)
    }
}

步骤 3: 实现处理函数

// server/topic.go
func (t *Topic) handleTyping(msg *MsgClientTyping) {
    // 广报给其他订阅者
    for sess := range t.sessions {
        if sess.uid != msg.sess.uid {
            sess.queueOut(&ServerComMessage{
                Info: &MsgServerInfo{
                    Topic:   t.name,
                    From:    msg.sess.uid.String(),
                    Content: map[string]any{"typing": msg.IsTyping},
                },
            })
        }
    }
}

场景 2: 添加新的 API 端点

步骤 1: 定义 HTTP Handler

// server/http.go
func serveCustomAPI(wrt http.ResponseWriter, req *http.Request) {
    // 验证请求
    if req.Method != http.MethodPost {
        wrt.WriteHeader(http.StatusMethodNotAllowed)
        return
    }

    // 处理请求
    var data struct {
        UserID string `json:"user_id"`
    }
    json.NewDecoder(req.Body).Decode(&data)

    // 返回响应
    wrt.Header().Set("Content-Type", "application/json")
    json.NewEncoder(wrt).Encode(map[string]any{
        "status": "ok",
    })
}

步骤 2: 注册路由

// server/http.go - initHTTP()
func initHTTP() *http.ServeMux {
    mux := http.NewServeMux()
    // ... 其他路由
    mux.HandleFunc("/v1/custom", serveCustomAPI)  // 新增
    return mux
}

场景 3: 添加新的数据库字段

步骤 1: 修改数据模型

// server/store/types/types.go
type User struct {
    Id        types.Uid
    CreatedAt time.Time
    // 新增字段
    Nickname  string `json:"nickname"`
}

步骤 2: 修改数据库适配器

// server/db/mysql/adapter.go
func (a *adapter) UserCreate(user *t.User) error {
    _, err := a.db.Exec(`
        INSERT INTO users (id, createdat, nickname)
        VALUES (?, ?, ?)
    `, user.Id.String(), user.CreatedAt, user.Nickname)
    return err
}

步骤 3: 更新数据库 Schema

-- server/db/mysql/schema.sql
ALTER TABLE users ADD COLUMN nickname VARCHAR(64);

场景 4: 开发插件

创建一个消息过滤插件:

// myplugin/filter.go
package main

import (
    "github.com/tinode/chat/pbx"
)

type FilterPlugin struct {
    bannedWords []string
}

func (p *FilterPlugin) HandleClientMessage(msg *pb.ClientMsg) (*pb.ClientMsg, error) {
    // 检查消息内容
    if msg.Pub != nil {
        content := string(msg.Pub.Content)
        for _, word := range p.bannedWords {
            if strings.Contains(content, word) {
                // 过滤敏感词
                content = strings.ReplaceAll(content, word, "***")
                msg.Pub.Content = []byte(content)
            }
        }
    }
    return msg, nil
}

func (p *FilterPlugin) HandleServerMessage(msg *pb.ServerMsg) (*pb.ServerMsg, error) {
    return msg, nil
}

注册插件:

// main.go
func main() {
    plugin := &FilterPlugin{
        bannedWords: []string{"badword1", "badword2"},
    }
    globals.plugins = append(globals.plugins, plugin)
}

测试与调试

1. 单元测试

# 运行所有测试
go test ./server/...

# 运行特定测试
go test -run TestTopicPublish ./server/

# 查看覆盖率
go test -cover ./server/...

2. 集成测试

使用 tn-cli 进行集成测试:

# 登录
python tn-cli.py --login-basic=alice:alice123

# 执行测试脚本
python tn-cli.py < test-script.txt

3. 调试技巧

开启详细日志:

// 在 tinode.conf 中
{
    "logging": {
        "level": "debug"
    }
}

使用 pprof 性能分析:

# 启动服务器时开启 pprof
./server -expvar=/debug/vars

# 访问性能分析
go tool pprof http://localhost:6060/debug/pprof/profile

调试 WebSocket:

// 浏览器控制台
const ws = new WebSocket('ws://localhost:6060/v0/channels');

ws.onopen = () => {
    ws.send(JSON.stringify({hi: {ver: "0.25", ua: "test"}}));
};

ws.onmessage = (e) => {
    console.log('收到:', JSON.parse(e.data));
};

4. 数据库调试

# MySQL
mysql -u root -p tinode -e "SELECT * FROM users LIMIT 10;"

# MongoDB
mongosh tinode --eval "db.users.find().limit(10)"

# PostgreSQL
psql -U postgres -d tinode -c "SELECT * FROM users LIMIT 10;"

部署指南

1. 本地开发环境

# 1. 克隆代码
git clone https://github.com/tinode/chat.git
cd chat

# 2. 安装依赖
go mod download

# 3. 启动数据库(以 MySQL 为例)
docker run -d --name mysql \
    -e MYSQL_ROOT_PASSWORD=root \
    -e MYSQL_DATABASE=tinode \
    -p 3306:3306 \
    mysql:8.0

# 4. 初始化数据库
go run ./tinode-db -tags mysql -data=./tinode-db/data.json

# 5. 启动服务器
go run ./server -tags mysql

2. 生产环境部署

方式 1: 直接部署

# 构建
go build -tags mysql -ldflags "-X main.buildstamp=`git describe --tags`" ./server

# 创建配置目录
mkdir -p /etc/tinode
cp server/tinode.conf /etc/tinode/

# 创建 systemd 服务
cat > /etc/systemd/system/tinode.service << 'EOF'
[Unit]
Description=Tinode Chat Server
After=network.target mysql.service

[Service]
Type=simple
User=tinode
ExecStart=/usr/local/bin/server -config=/etc/tinode/tinode.conf
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target
EOF

# 启动服务
systemctl daemon-reload
systemctl enable tinode
systemctl start tinode

方式 2: Docker 部署

# 使用官方镜像
docker run -d --name tinode \
    -p 6060:18080 \
    -e MYSQL_DSN="root@tcp(mysql:3306)/tinode?parseTime=true" \
    tinode/tinode-mysql:latest

# Docker Compose
cat > docker-compose.yml << 'EOF'
version: '3'
services:
  mysql:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_DATABASE: tinode
    volumes:
      - mysql_data:/var/lib/mysql

  tinode:
    image: tinode/tinode-mysql:latest
    ports:
      - "6060:18080"
    depends_on:
      - mysql
    environment:
      STORE_CONFIG_ADAPTERS_MYSQL_DSN: "root@tcp(mysql:3306)/tinode?parseTime=true"

volumes:
  mysql_data:
EOF

docker-compose up -d

3. 集群部署

# tinode.conf 集群配置
{
    "cluster": {
        "enabled": true,
        "self": "node1",
        "nodes": [
            {"id": "node1", "addr": "node1.example.com:12000"},
            {"id": "node2", "addr": "node2.example.com:12000"},
            {"id": "node3", "addr": "node3.example.com:12000"}
        ]
    }
}

4. 监控配置

# Prometheus 监控
"expvar": "/debug/vars"

# Grafana 仪表盘
# 导入: https://grafana.com/grafana/dashboards/...

5. 生产环境配置建议

{
    // 安全配置
    "tls": {
        "enabled": true,
        "strict_max_age": 604800
    },

    // 性能配置
    "max_message_size": 262144,
    "max_subscriber_count": 256,

    // 日志配置
    "logging": {
        "level": "info"
    },

    // 数据库连接池
    "store_config": {
        "adapters": {
            "mysql": {
                "dsn": "user:pass@tcp(db-host:3306)/tinode?parseTime=true&max_open_conns=100&max_idle_conns=10"
            }
        }
    }
}

常见问题排查

1. 连接问题

# 检查端口是否监听
netstat -tlnp | grep 6060

# 检查防火墙
iptables -L -n | grep 6060

# 测试连接
curl http://localhost:6060/v0/hi

2. 数据库问题

# 检查数据库连接
mysql -u root -p -e "SHOW PROCESSLIST;"

# 检查表结构
mysql -u root -p tinode -e "SHOW TABLES;"

# 查看错误日志
tail -f /var/log/mysql/error.log

3. 性能问题

# CPU 分析
go tool pprof http://localhost:6060/debug/pprof/profile?seconds=30

# 内存分析
go tool pprof http://localhost:6060/debug/pprof/heap

# 查看统计
curl http://localhost:6060/debug/vars | jq .

扩展阅读


开发流程总结

┌─────────────────────────────────────────────────────────────────┐
│                        开发新功能流程                            │
└─────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────────┐
│  1. 理解需求                                                      │
│     - 阅读需求文档                                                 │
│     - 确认技术方案                                                 │
└─────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────────┐
│  2. 设计数据结构                                                  │
│     - 修改 datamodel.go 添加消息类型                              │
│     - 修改 store/types/types.go 添加数据模型                      │
│     - 更新数据库 schema                                            │
└─────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────────┐
│  3. 实现业务逻辑                                                  │
│     - 在 topic.go 添加处理函数                                     │
│     - 在 store.go 添加数据库操作                                   │
│     - 实现必要的适配器方法                                          │
└─────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────────┐
│  4. 编写测试                                                      │
│     - 单元测试 *_test.go                                           │
│     - 集成测试(使用 tn-cli)                                      │
└─────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────────┐
│  5. 测试验证                                                      │
│     - go test ./...                                               │
│     - 本地运行验证                                                 │
│     - 性能测试                                                    │
└─────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────────┐
│  6. 代码审查 & 提交                                               │
│     - 代码审查                                                    │
│     - 更新文档                                                    │
│     - Git commit                                                  │
└─────────────────────────────────────────────────────────────────┘

祝开发顺利!