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

1165 lines
40 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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)
```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 格式,主要配置项:
```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)
```bash
# 初始化数据库并加载测试数据
./tinode-db -config=tinode.conf -data=data.json
# 主要步骤:
# 1. 连接数据库
# 2. 创建表/集合
# 3. 创建索引
# 4. 加载初始数据(用户、话题)
```
---
## 核心数据结构
### 1. Hub 结构体
Hub 是整个系统的核心路由器:
```go
// 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 的核心方法**:
```go
// 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 是聊天的核心单元:
```go
// 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 代表一个客户端连接:
```go
// 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. 消息结构体
客户端到服务端的消息定义:
```go
// 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 消息处理示例
```go
// 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 外部认证
```
**认证接口定义**:
```go
// 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/)
数据库层采用适配器模式:
```go
// 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/)
```go
// server/push/push.go
type PushHandler interface {
// 初始化
Init(jsonconf json.RawMessage) error
// 发送推送
Push(userId types.Uid, data *PushData) error
}
```
**FCM 推送配置**:
```json
// tinode.conf
"push_config": {
"fcm": {
"credentials_file": "/path/to/credentials.json"
}
}
```
### 4. 文件存储 (server/media/)
支持本地文件系统和 S3:
```go
// 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)
插件可以拦截和处理所有消息:
```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**: 定义消息结构体
```go
// server/datamodel.go
type MsgClientTyping struct {
Id string `json:"id,omitempty"`
Topic string `json:"topic,omitempty"`
IsTyping bool `json:"typing"`
}
```
**步骤 2**: 添加解析逻辑
```go
// 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**: 实现处理函数
```go
// 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
```go
// 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**: 注册路由
```go
// server/http.go - initHTTP()
func initHTTP() *http.ServeMux {
mux := http.NewServeMux()
// ... 其他路由
mux.HandleFunc("/v1/custom", serveCustomAPI) // 新增
return mux
}
```
### 场景 3: 添加新的数据库字段
**步骤 1**: 修改数据模型
```go
// server/store/types/types.go
type User struct {
Id types.Uid
CreatedAt time.Time
// 新增字段
Nickname string `json:"nickname"`
}
```
**步骤 2**: 修改数据库适配器
```go
// 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
```sql
-- server/db/mysql/schema.sql
ALTER TABLE users ADD COLUMN nickname VARCHAR(64);
```
### 场景 4: 开发插件
创建一个消息过滤插件:
```go
// 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
}
```
**注册插件**:
```go
// main.go
func main() {
plugin := &FilterPlugin{
bannedWords: []string{"badword1", "badword2"},
}
globals.plugins = append(globals.plugins, plugin)
}
```
---
## 测试与调试
### 1. 单元测试
```bash
# 运行所有测试
go test ./server/...
# 运行特定测试
go test -run TestTopicPublish ./server/
# 查看覆盖率
go test -cover ./server/...
```
### 2. 集成测试
使用 tn-cli 进行集成测试:
```bash
# 登录
python tn-cli.py --login-basic=alice:alice123
# 执行测试脚本
python tn-cli.py < test-script.txt
```
### 3. 调试技巧
**开启详细日志**:
```go
// 在 tinode.conf 中
{
"logging": {
"level": "debug"
}
}
```
**使用 pprof 性能分析**:
```bash
# 启动服务器时开启 pprof
./server -expvar=/debug/vars
# 访问性能分析
go tool pprof http://localhost:6060/debug/pprof/profile
```
**调试 WebSocket**:
```javascript
// 浏览器控制台
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. 数据库调试
```bash
# 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. 本地开发环境
```bash
# 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: 直接部署
```bash
# 构建
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 部署
```bash
# 使用官方镜像
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. 集群部署
```yaml
# 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. 监控配置
```yaml
# Prometheus 监控
"expvar": "/debug/vars"
# Grafana 仪表盘
# 导入: https://grafana.com/grafana/dashboards/...
```
### 5. 生产环境配置建议
```json
{
// 安全配置
"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. 连接问题
```bash
# 检查端口是否监听
netstat -tlnp | grep 6060
# 检查防火墙
iptables -L -n | grep 6060
# 测试连接
curl http://localhost:6060/v0/hi
```
### 2. 数据库问题
```bash
# 检查数据库连接
mysql -u root -p -e "SHOW PROCESSLIST;"
# 检查表结构
mysql -u root -p tinode -e "SHOW TABLES;"
# 查看错误日志
tail -f /var/log/mysql/error.log
```
### 3. 性能问题
```bash
# 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 .
```
---
## 扩展阅读
- [API 文档](docs/API.md) - 完整的 API 参考
- [Drafty 格式](docs/drafty.md) - 富文本消息格式
- [通话建立流程](docs/call-establishment.md) - 音视频通话信令
- [Docker 部署](docker/README.md) - 容器化部署详解
---
## 开发流程总结
```
┌─────────────────────────────────────────────────────────────────┐
│ 开发新功能流程 │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 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 │
└─────────────────────────────────────────────────────────────────┘
```
---
祝开发顺利!