Files
im_server/docs/TEST-RUN-2026-07-31-MYSQL-CN.md
T
2026-08-11 15:59:57 +08:00

48 KiB
Raw Blame History

后端测试执行报告(MySQL)

执行时间:2026-07-31 10:50:28 CST
项目路径:/Users/superysh/im/chat-0.25.3
Go 版本:go1.26.0 darwin/arm64
MySQL:8.0.42
数据库连接:127.0.0.1:3306,用户 root/root

结论

本次后端测试通过。按 MySQL build tag 编译并执行后,核心后端包、公共组件、MySQL 数据库适配器集成测试均通过;server 与 tinode-db 的 MySQL 目标构建也通过。

需要注意:不能直接把 go test ./... 作为本项目的标准全量测试命令,因为数据库适配器使用 Go build tags。未指定 tag 时,MySQL/PostgreSQL/MongoDB/RethinkDB 测试包会引用不到真实 adapter 的 GetTestAdapter,导致编译失败。

测试环境检查

已验证 MySQL 服务可连接,并确认存在业务库 tinode 与测试库 tinode_test。

mysql -uroot -proot -h127.0.0.1 -P3306 -e "SELECT VERSION() AS mysql_version; SHOW DATABASES LIKE 'tinode'; SHOW DATABASES LIKE 'tinode_test';"

结果摘要:

项 结果
MySQL 版本 8.0.42
tinode 存在
tinode_test 存在

说明:仓库自带 MySQL 测试配置 server/db/mysql/tests/test.conf 使用 tinode_test,并设置 reset_db_data=true。这组测试会重建测试库数据。为避免影响你已经建好的业务库 tinode,本次 MySQL adapter 测试使用 tinode_test。

执行命令与结果

1. 后端非数据库实例依赖测试

go test -count=1 $(go list ./server/... | grep -v '/server/db/.*/tests$')

结果:通过。

覆盖包摘要:

包 结果
github.com/tinode/chat/server ok
github.com/tinode/chat/server/db/common ok
github.com/tinode/chat/server/drafty ok
github.com/tinode/chat/server/media ok
github.com/tinode/chat/server/ringhash ok
github.com/tinode/chat/server/store/types ok

2. MySQL adapter 集成测试

go test -count=1 -tags mysql ./server/db/mysql/tests

结果:通过。

ok github.com/tinode/chat/server/db/mysql/tests 0.687s

覆盖的主要持久化能力:

能力 典型测试
建库与 schema 初始化 TestCreateDb
用户创建、查询、更新、删除 TestUserCreate、TestUserGet、TestUserUpdate、TestUserDelete
登录认证记录 TestAuthAddRecord、TestAuthGetRecord、TestAuthUpdRecord
凭证验证 TestCredUpsert、TestCredGetActive、TestCredConfirm、TestCredDel
Topic 创建与更新 TestTopicCreate、TestTopicCreateP2P、TestTopicUpdate、TestTopicDelete
订阅关系 TestTopicShare、TestSubscriptionGet、TestSubsUpdate、TestSubsDelete
消息存储、查询、删除 TestMessageSave、TestMessageGetAll、TestMessageDeleteList、TestMessageGetDeleted
文件与附件 TestFileStartUpload、TestFileFinishUpload、TestMessageAttachments
设备与推送记录 TestDeviceUpsert、TestDeviceGetAll、TestDeviceDelete
未读数 TestUserUnreadCount
持久缓存 TestPCacheUpsert、TestPCacheGet、TestPCacheDelete、TestPCacheExpire

3. MySQL tag 下的 Go 包测试

go test -count=1 -tags mysql $(go list ./... | grep -v '/server/db/mongodb/tests$' | grep -v '/server/db/postgres/tests$' | grep -v '/server/db/rethinkdb/tests$')

结果:通过。

结果摘要:

范围 结果
根模块 Go 包 通过
server 核心测试 通过
server/db/common 通过
server/db/mysql/tests 通过
drafty、media、ringhash、store/types 通过
tinode-db 无测试文件,编译检查通过

本次实际执行的测试函数约 185 个,不含 PostgreSQL/MongoDB/RethinkDB adapter 测试。

4. MySQL 目标构建

go build -tags mysql ./server ./tinode-db

结果:通过。

直接全量测试的已知失败

执行以下命令:

go test -count=1 ./...

结果:失败,原因是未指定数据库 build tag,导致各数据库测试包无法引用真实 adapter。

失败摘要:

server/db/mysql/tests/mysql_test.go:1319:16: undefined: backend.GetTestAdapter
server/db/postgres/tests/postgres_test.go:1385:16: undefined: backend.GetTestAdapter
server/db/rethinkdb/tests/rethink_test.go:1552:16: undefined: backend.GetTestAdapter
server/db/mongodb/tests/mongo_test.go:1256:16: undefined: backend.GetTestAdapter

这是项目构建方式导致的预期问题,不代表 MySQL 后端测试失败。标准命令应按目标数据库指定 build tag。

建议后续标准测试命令

当前以 MySQL 为后端开发目标时,建议使用:

go test -count=1 -tags mysql $(go list ./... | grep -v '/server/db/mongodb/tests$' | grep -v '/server/db/postgres/tests$' | grep -v '/server/db/rethinkdb/tests$')

如果只改核心消息、会话、Topic 逻辑,可先跑:

go test -count=1 ./server ./server/db/common ./server/drafty ./server/media ./server/ringhash ./server/store/types

如果改 MySQL 持久化层,必须补跑:

go test -count=1 -tags mysql ./server/db/mysql/tests

如果改启动、配置或数据库初始化工具,补跑:

go build -tags mysql ./server ./tinode-db

开发后回归记录

执行时间:2026-07-31 11:46:05 CST

本轮新增 x-im-* 协议扩展实现与测试后,已执行以下回归:

命令 结果
go test -count=1 ./server 通过
go test -count=1 ./server/drafty ./server/store/types ./server/db/common 通过
go test -count=1 -tags mysql $(go list ./... | grep -v '/server/db/mongodb/tests$' | grep -v '/server/db/postgres/tests$' | grep -v '/server/db/rethinkdb/tests$') 通过
go build -tags mysql ./server ./tinode-db 通过
校验 docs/IM-WS-API-CN.md 内全部 json 代码块 94/94 通过

新增覆盖点:

覆盖点 说明
x-im-type 白名单 拒绝未知或非字符串业务消息类型
通话记录类型 支持 head.x-im-type="call" 并保持原样广播
聊天记录筛选 覆盖按 x-im-filter.types 与 x-im-search.keyword 过滤
群全员禁言 覆盖普通成员拒绝、管理员允许

开发后回归记录(二)

执行时间:2026-07-31 11:57:58 CST

本轮新增好友/入群申请资料校验、群业务配置校验、创建群初始 aux 保存后,已执行以下回归:

命令 结果
go test -count=1 ./server 通过
go test -count=1 ./server/drafty ./server/store/types ./server/db/common 通过
go test -count=1 -tags mysql $(go list ./... | grep -v '/server/db/mongodb/tests$' | grep -v '/server/db/postgres/tests$' | grep -v '/server/db/rethinkdb/tests$') 通过
go build -tags mysql ./server ./tinode-db 通过
校验 docs/IM-WS-API-CN.md 内全部 json 代码块 94/94 通过

新增覆盖点:

覆盖点 说明
好友申请备注 private.x-im-applyText 支持保存,非法类型或超长返回 400
私有业务开关 x-im-blocked、x-im-muted、x-im-pinned 必须为布尔值
群业务配置 aux.x-im-group 的核心布尔字段、禁言成员数组、公告文本类型受校验
创建群初始配置 {sub topic="new" set.aux.x-im-group} 会写入 topics.aux
群配置更新 管理员 {set aux} 可更新 x-im-group,非法配置返回 400

开发后回归记录(三)

执行时间:2026-07-31 12:02:05 CST

本轮新增群级业务策略后,已执行以下回归:

命令 结果
go test -count=1 ./server ./server/drafty ./server/store/types ./server/db/common 通过
go test -count=1 -tags mysql $(go list ./... | grep -v '/server/db/mongodb/tests$' | grep -v '/server/db/postgres/tests$' | grep -v '/server/db/rethinkdb/tests$') 通过
go build -tags mysql ./server ./tinode-db 通过
校验 docs/IM-WS-API-CN.md 内全部 json 代码块 95/95 通过

新增覆盖点:

覆盖点 说明
单成员禁言 aux.x-im-group.mutedUsers 中的普通成员发消息返回 403
成员文件上传限制 allowMemberFileUpload=false 时普通成员发送附件类消息返回 403
仅管理员邀请 onlyAdminInvite=true 时非管理员邀请成员返回 403
管理员豁免 群全员禁言下管理员仍可发消息,沿用前序测试覆盖

开发后回归记录(四)

执行时间:2026-07-31 12:10:16 CST

本轮启动 Phase 2,新增 MySQL 消息业务索引 im_message_index,并把 MySQL adapter 版本升级到 117。测试库检查结果:

项 结果
tinode_test.kvmeta.version 117
tinode_test.im_message_index 存在

已执行以下回归:

命令 结果
go test -count=1 ./server ./server/drafty ./server/store/types ./server/db/common 通过
go test -count=1 -tags mysql ./server/db/mysql/tests 通过
go test -count=1 -tags mysql $(go list ./... | grep -v '/server/db/mongodb/tests$' | grep -v '/server/db/postgres/tests$' | grep -v '/server/db/rethinkdb/tests$') 通过
go build -tags mysql ./server ./tinode-db 通过

新增覆盖点:

覆盖点 说明
数据库迁移 MySQL adapter 116 -> 117,CreateDb 与 UpgradeDb 均支持 im_message_index
历史消息回填 旧库升级时扫描 messages 与 filemsglinks 回填索引
索引写入 MessageIndexSave 保存消息类型、检索文本、附件数量
索引查询 MessageIndexSearch 支持 topic、seq 范围、类型、关键词、limit
软删除过滤 索引查询继续按 Tinode dellog 排除当前用户已删除消息
服务层接入 {get data} 携带 x-im-search 或 x-im-filter 时优先使用索引,未实现索引的 adapter 回退内存过滤

注意:业务库 tinode 如仍是旧版本,需要使用 tinode-db --upgrade 升级到 117 后,当前 MySQL server 构建才能通过数据库版本检查。

业务库升级记录

执行时间:2026-07-31 12:28:43 CST

已按 Phase 2 迁移要求升级业务库 127.0.0.1:3306/tinode。

升级前检查:

项 结果
tinode.kvmeta.version 116
tinode.im_message_index 不存在

升级前已备份:

mysqldump -uroot -proot -h127.0.0.1 -P3306 --single-transaction --routines --triggers tinode > /Users/superysh/im/chat-0.25.3/db_backups/tinode-before-117-20260731-122808.sql

执行升级:

go run -tags mysql ./tinode-db --upgrade --config=server/tinode.conf

升级日志摘要:

Database adapter: 'mysql'; version: 117
Wrong DB version: expected 117, got 116. Upgrading the database.
Database successfully upgraded.
All done.

升级后验证:

项 结果
tinode.kvmeta.version 117
tinode.im_message_index 存在
tinode.messages 100 条
tinode.im_message_index 100 条
go run -tags mysql ./tinode-db --config=server/tinode.conf Database exists, version is correct.
go build -tags mysql ./server ./tinode-db 通过
go test -count=1 -tags mysql ./server/db/mysql/tests 通过

开发后回归记录(五)

执行时间:2026-07-31 12:37:59 CST

本轮继续 Phase 2,新增 MySQL 积分与每日事件持久化能力,并把 MySQL adapter 版本升级到 118。

代码变更摘要:

覆盖点 说明
数据库迁移 MySQL adapter 117 -> 118,CreateDb 与 UpgradeDb 均支持 im_user_points、im_user_daily_events
积分查询 IMUserPointsGet 支持读取用户积分余额
积分增加 IMUserPointsAdd 支持首次创建与余额累加
积分扣减 IMUserPointsConsume 使用条件更新保证余额不足时不扣减
每日事件幂等 IMDailyEventCreate 依赖唯一索引保证同一用户、日期、事件类型只记录一次
每日事件读取 IMDailyEventGet 支持读取事件 payload,供签到、每日任务等业务复用

已执行以下回归:

命令 结果
go test -count=1 ./server ./server/drafty ./server/store/types ./server/db/common 通过
go test -count=1 -tags mysql ./server/db/mysql/tests 通过
go test -count=1 -tags mysql $(go list ./... | grep -v '/server/db/mongodb/tests$' | grep -v '/server/db/postgres/tests$' | grep -v '/server/db/rethinkdb/tests$') 通过
go build -tags mysql ./server ./tinode-db 通过

新增测试用例:

用例 目的
TestIMUserPointsAddAndConsume 验证积分首次创建、增加、扣减后的余额
TestIMUserPointsInsufficient 验证余额不足时返回 types.ErrPolicy
TestIMDailyEventIdempotent 验证每日事件重复写入只成功一次,并可读取 payload

业务库升级记录(二)

执行时间:2026-07-31 12:37:59 CST

已按 Phase 2 二期迁移要求升级业务库 127.0.0.1:3306/tinode。

升级前检查:

项 结果
tinode.kvmeta.version 117
tinode.im_message_index 存在
tinode.im_user_points 不存在
tinode.im_user_daily_events 不存在

升级前已备份:

mysqldump -h127.0.0.1 -P3306 -uroot -proot tinode > /Users/superysh/im/chat-0.25.3/db_backups/tinode-before-118-20260731-123747.sql

执行升级:

go run -tags mysql ./tinode-db --upgrade --config=server/tinode.conf

升级日志摘要:

Database adapter: 'mysql'; version: 118
Wrong DB version: expected 118, got 117. Upgrading the database.
Database successfully upgraded.
All done.

升级后验证:

项 结果
tinode.kvmeta.version 118
tinode.im_message_index 存在
tinode.im_user_points 存在
tinode.im_user_daily_events 存在
tinode.messages 100 条
tinode.im_message_index 100 条

开发后回归记录(六)

执行时间:2026-07-31 12:50:00 CST

本轮将 Phase 2 签到能力正式接入 WebSocket {pub} 流程。客户端向 slf 发送 head.x-im-type="checkin" 时,服务端会先执行每日事件幂等与积分发放,再决定是否保存签到消息。

代码变更摘要:

覆盖点 说明
WS 签到接入 Topic.handlePubBroadcast 在消息落库前处理 checkin
幂等控制 同一用户、日期、事件类型重复签到返回 304 no action
消息去重 重复签到不保存第二条 slf 消息
固定奖励 服务端固定每日签到奖励为 5,拒绝客户端伪造其它积分值
原子发放 IMDailyEventCreateWithPoints 在同一事务内创建每日事件并增加积分

已执行以下回归:

命令 结果
go test -count=1 ./server ./server/drafty ./server/store/types ./server/db/common 通过
go test -count=1 -tags mysql ./server/db/mysql/tests 通过
go test -count=1 -tags mysql $(go list ./... | grep -v '/server/db/mongodb/tests$' | grep -v '/server/db/postgres/tests$' | grep -v '/server/db/rethinkdb/tests$') 通过
go build -tags mysql ./server ./tinode-db 通过

新增测试用例:

用例 目的
TestIMParseCheckinPayload 验证签到日期与默认积分解析
TestIMParseCheckinPayloadRejectsClientControlledReward 验证客户端不能伪造非默认签到奖励
TestIMDailyEventCreateWithPoints 验证每日事件与积分发放的事务一致性和重复不加分

开发后回归记录(七)

执行时间:2026-07-31 13:05:00 CST

本轮将 Phase 2 话题发布积分扣减接入 {sub topic="nch"} 频道创建流程。客户端在 public.x-im-topic.pointsCost 中声明扣费金额时,MySQL 后端会在同一事务中创建话题、创建作者订阅并扣减积分。

代码变更摘要:

覆盖点 说明
话题扣费解析 从 public.x-im-topic.pointsCost 读取非负整数扣费金额
普通群聊隔离 仅 nch 频道创建触发扣费,new 群聊创建不受影响
原子创建 IMTopicCreateWithPoints 在一个事务内完成 topic 创建、owner subscription 创建和积分扣减
余额不足保护 余额不足返回 types.ErrPolicy,事务回滚,话题不会创建
可选 adapter 不修改 Tinode 全局 adapter 接口,非 MySQL 后端没有实现时返回 unsupported

已执行以下回归:

命令 结果
go test -count=1 ./server ./server/drafty ./server/store/types ./server/db/common 通过
go test -count=1 -tags mysql ./server/db/mysql/tests 通过
go test -count=1 -tags mysql $(go list ./... | grep -v '/server/db/mongodb/tests$' | grep -v '/server/db/postgres/tests$' | grep -v '/server/db/rethinkdb/tests$') 通过
go build -tags mysql ./server ./tinode-db 通过

新增测试用例:

用例 目的
TestIMTopicPointsCost 验证话题发布扣费字段解析
TestIMTopicPointsCostRejectsMalformedCost 验证非法扣费金额返回 malformed
TestIMTopicCreateWithPoints 验证话题创建成功后积分被扣减,并创建作者订阅
TestIMTopicCreateWithPointsInsufficient 验证余额不足时返回 policy,且不会创建 topic

开发后回归记录(八)

执行时间:2026-07-31 13:58:00 CST

本轮接入产品通话 payload 校验,同时保持 Tinode 原生 WebRTC 信令兼容。

代码变更摘要:

覆盖点 说明
通话邀请校验 head.x-im-type="call" 且 head.webrtc="started" 时,content.callId 必填
控制事件校验 ringing、accept、hang-up 的 payload.callId 必填,非法 payload 静默丢弃
原生信令兼容 offer、answer、ice-candidate 不强制产品字段,继续透传 SDP/ICE payload
通话记录兼容 普通 x-im-type="call" 持久消息不强制 callId,避免影响历史通话记录展示

已执行以下回归:

命令 结果
go test -count=1 ./server 通过
go test -count=1 ./server/drafty ./server/store/types ./server/db/common 通过
go test -count=1 -tags mysql ./server/db/mysql/tests 通过
go test -count=1 -tags mysql $(go list ./... | grep -v '/server/db/mongodb/tests$' | grep -v '/server/db/postgres/tests$' | grep -v '/server/db/rethinkdb/tests$') 通过
go build -tags mysql ./server ./tinode-db 通过

新增测试用例:

���例 目的
TestIMValidateCallContent 验证产品通话内容中 callId 合法
TestIMValidateCallContentMalformed 验证产品通话内容缺少 callId 被拒绝
TestHandleBroadcastProductCallRejectsMissingCallID 验证产品通话邀请缺少 callId 返回 400 malformed,且不进入通话状态

开发后回归记录(九)

执行时间:2026-07-31 14:17:42 CST

本轮新增 WebSocket mock 联调程序 tools/wsmock,用于按前后端对接文档直接模拟前端通过 /v0/channels 与后端通信。程序不新增第三方依赖,复用项目已有 github.com/gorilla/websocket。

代码变更摘要:

覆盖点 说明
WS 握手 通过 X-Tinode-APIKey 连接 /v0/channels 并发送 {hi}
注册登录 支持 basic 注册;本地配置要求 email credential 时,自动走 acc -> 300 validate credentials -> login cred.resp
自存消息 订阅 slf 并发布 head.x-im-type="text" 消息,验证 202 accepted
签到幂等 首次签到验证 202 accepted,重复同日签到验证 304 no action
频道积分 多日签到累计积分后创建 nch 频道并扣减积分,再用超高 pointsCost 验证 422 policy violation
通话校验 产品通话邀请缺少 content.callId 时验证返回 400 malformed

已执行以下联调:

命令 结果
go build ./tools/wsmock 通过
go run -tags mysql ./server -config=tinode.conf -static_data=- 服务启动成功,监听 :6060
go run ./keygen -salt 'T713/rYYgW7g4m3vG6zGRh7+FM1t0T8j13koXScOAj4=' 生成本地联调用 API key
go run ./tools/wsmock -apikey '<generated>' -scenario all -timeout 12s 通过

联调观察:

协议点 实际返回
{hi} 成功 201 created
{pub} 成功 202 accepted,随后下发 {data}
重复签到 304 no action,params.x-im-checkin.already=true
频道余额不足 422 policy violation
产品通话缺少 callId 400 malformed
本地 SMTP tinode.conf 启用 dummy email response,但 SMTP 服务未启动;联调可通过,服务端会异步记录 SMTP connection refused

开发后回归记录(十)

执行时间:2026-07-31 14:27:57 CST

本轮增强 tools/wsmock,在原有 Phase 2 smoke/checkin/topic/call 场景基础上,新增双端 P2P、聊天记录搜索/分类筛选、举报到 sys 的端到端联调。

代码变更摘要:

覆盖点 说明
双端客户端 自动创建 Alice 和 Bob 两个 WebSocket 连接,各自完成 {hi}、注册、email credential 验证、登录和订阅 me
P2P 建链 Alice 订阅 Bob 的 usr...,Bob 订阅 Alice 的 usr...,验证 P2P topic 可创建和进入
P2P 消息 Alice 向 Bob 发送 Drafty 文本消息,Bob 收到 {data}
已收已读 Bob 发送 recv/read {note},Alice 收到 read {info}
文件分类 Alice 发送 head.x-im-type="file" 文件卡片,Bob 收到 {data}
索引搜索 Alice 使用 {get what="data" x-im-search} 搜索关键词,返回 208 delivered 且 params.count=1
类型筛选 Alice 使用 {get what="data" x-im-filter.types=["file"]} 查询文件消息,返回 208 delivered 且 params.count=1
举报 Alice 向 sys 发布 head.x-im-type="report",验证 202 accepted

已执行以下联调:

命令 结果
go build ./tools/wsmock 通过
go run -tags mysql ./server -config=tinode.conf -static_data=- 服务启动成功,监听 :6060
go run ./tools/wsmock -apikey '<generated>' -scenario all -timeout 12s 通过

联调观察:

协议点 实际返回
P2P 建链 {sub topic="usrPeer"} 返回 200 ok,双方订阅列表包含两个用户
P2P 文本发布 发布方 {ctrl code=202},双方收到对应 {data}
已读回执 接收方 {note what="read"} 后,发送方收到 {info what="read"}
搜索命中 {get x-im-search} 返回 208 delivered,params.count=1
文件筛选命中 {get x-im-filter} 返回 208 delivered,params.count=1
举报到 sys 普通用户不订阅 sys 也可 {pub},返回 202 accepted

开发后回归记录(十一)

执行时间:2026-07-31 14:31:44 CST

本轮继续增强 tools/wsmock,新增群聊多角色与业务权限策略联调,并纳入 all 全量场景。

代码变更摘要:

覆盖点 说明
建群 Alice 使用 {sub topic="new"} 创建群,初始化 aux.x-im-group
邀请成员 Alice 使用 {set sub.user mode="JRWP"} 邀请 Bob
成员入群 Bob 订阅 grp... 并读取 desc sub data aux
成员发言 Bob 发布 Drafty 文本,Alice 收到 {data}
全员禁言 Alice 设置 aux.x-im-group.muteAll=true 后,Bob 发言返回 403 permission denied
群主豁免 全员禁言下 Alice 作为群主发言仍返回 202 accepted
文件权限 Alice 设置 allowMemberFileUpload=false 后,Bob 发送 x-im-type="file" 返回 403 permission denied
管理员/群主文件豁免 文件上传受限时 Alice 发送文件仍返回 202 accepted

已执行以下联调:

命令 结果
go build ./tools/wsmock 通过
go run -tags mysql ./server -config=tinode.conf -static_data=- 服务启动成功,监听 :6060
go run ./tools/wsmock -apikey '<generated>' -scenario group -timeout 12s 通过
go run ./tools/wsmock -apikey '<generated>' -scenario all -timeout 12s 通过

联调观察:

协议点 实际返回
群创建 {sub topic="new"} 返回 200 ok,ctrl.topic=grp...
邀请成员 {set sub.user} 返回 200 ok,params.user=usr...
成员正常发言 {pub} 返回 202 accepted,群主收到 {data}
全员禁言拦截 普通成员 {pub text} 返回 403 permission denied
群主禁言豁免 群主 {pub text} 返回 202 accepted
文件上传限制 普通成员 {pub file} 返回 403 permission denied,群主 {pub file} 返回 202 accepted

开发后回归记录(十二)

执行时间:2026-07-31 14:48:06 CST

本轮继续增强 tools/wsmock,新增完整 WebRTC 通话信令专项联调 call-flow,并修正 mock 客户端等待逻辑:当服务端异步下发的 {meta}、{pres}、{data}、{info} 与当前等待条件不匹配时,先缓存后续步骤可消费,避免真实 WS 多帧交错导致误判超时。

代码变更摘要:

覆盖点 说明
通话邀请 Alice 向 Bob 的 P2P topic 发布 head.webrtc="started"、head.x-im-type="call"、content.callId
来电展示 Bob 收到 webrtc=started 的 {data}
振铃 Bob 发送 {note what="call" event="ringing"},Alice 收到 {info event="ringing"}
接听 Bob 发送 {note event="accept"},Alice 收到 {info event="accept"},双方收到 webrtc=accepted 替换消息
SDP 交换 Alice 发送 offer,Bob 收到;Bob 发送 answer,Alice 收到
ICE 交换 Alice/Bob 双向发送 ice-candidate,对端均收到对应 {info}
挂断 Alice 发送 hang-up,双方收到 {info event="hang-up"} 和 webrtc=finished 替换消息

已执行以下联调:

命令 结果
go build ./tools/wsmock 通过
go run -tags mysql ./server -config=/tmp/tinode-webrtc.conf -static_data=- 服务启动成功,监听 :6060,日志显示 Video calls enabled with 2 ICE servers
go run ./tools/wsmock -apikey '<generated>' -scenario call-flow -timeout 12s 通过
go run ./tools/wsmock -apikey '<generated>' -scenario all -timeout 12s 通过

联调观察:

协议点 实际返回
{hi} WebRTC 参数 params.callTimeout=30,并返回配置的 iceServers
通话邀请 {pub} 返回 202 accepted,接收方收到 webrtc=started
接听行为 发起方收到 {info event="accept"};接听方不额外收到 accept info,而是直接收到 webrtc=accepted 替换消息
通话媒体协商 offer、answer、双向 ice-candidate 均通过 {note} 发送,并由服务端以 {info} 转发给对端
挂断结束 双方均收到 event="hang-up" 与 webrtc=finished

开发后回归记录(十三)

执行时间:2026-07-31 15:02:40 CST

本轮新增更完整的群管理 WebSocket 专项联调 group-admin,覆盖 Alice 群主、Bob 管理员候选、Carol 普通成员、Dave 被邀请/被踢成员四个角色,并继续复用真实 /v0/channels 协议。

代码变更摘要:

覆盖点 说明
群配置读取 Alice 设置 aux.x-im-group.joinApproval/onlyAdminInvite/announcement 后,通过 {get what="aux"} 读取并断言字段
仅管理员邀请 Bob 先具备 S 分享权限但不具备 A 管理权限,在 onlyAdminInvite=true 下邀请 Dave 返回 403 permission denied
管理员授权 Alice 使用绝对权限 JRWPASD 授予 Bob 管理权限,Bob 再用自己的 {set.sub} 将 want 调整为 JRWPASD,最终有效权限变为管理员
管理员邀请 Bob 成为管理员后邀请 Dave 返回 200 ok,Dave 可订阅入群
单成员禁言 Alice 将 Carol 写入 aux.x-im-group.mutedUsers 后,Carol 发言返回 403;清空后 Carol 发言返回 202
全员禁言管理员豁免 Bob 设置 muteAll=true 后,Dave 发言返回 403,Bob 作为管理员发言返回 202
踢人 Bob 使用 {del what="sub" user=dave} 踢出 Dave 返回 200;Dave 当前会话收到 205 evicted,后续发言返回 409 must attach first

已执行以下联调:

命令 结果
gofmt -w tools/wsmock/main.go && go build ./tools/wsmock 通过
go test -count=1 ./tools/wsmock 通过,无测试文件
go run -tags mysql ./server -config=tinode.conf -static_data=- 服务启动成功,监听 :6060,日志显示 Video calls disabled
go run ./tools/wsmock -apikey '<generated>' -scenario group-admin -timeout 12s 通过
go run ./tools/wsmock -apikey '<generated>' -scenario all -timeout 12s 通过

联调观察:

协议点 实际返回
{set.sub.mode} 权限写法 当前后端使用绝对权限字符串,不接受 +A、-W 这类增量字符串;错误写法返回 400 malformed
有效权限计算 用户有效权限为 want & given,管理员授权需要服务端授予 given,目标用户也要把自己的 want 提升到包含 A
踢人后状态 被踢用户会收到无 id 的 {ctrl code=205 text="evicted" params.unsub=true},同一 WS 会话再发群消息返回 409 must attach first

开发后回归记录(十四)

执行时间:2026-07-31 15:12:24 CST

本轮补齐真实文件上传链路,不再只用“文件卡片消息”模拟附件。新增 tools/wsmock -scenario upload,通过 HTTP /v0/file/u/ 上传小文件,再通过 WebSocket 发布 Drafty 文件消息,并显式写入 extra.attachments,验证服务端保存消息时把上传文件绑定到消息,避免后续 GC 清理。

代码变更摘要:

覆盖点 说明
上传入口 从 ws://127.0.0.1:6060/v0/channels 自动推导 http://127.0.0.1:6060/v0/file/u/
上传鉴权 HTTP 请求带 X-Tinode-APIKey 与 Authorization: token <login-token>
Multipart 上传 使用字段 file 上传真实二进制内容,服务端返回 {ctrl code=200 params.url}
附件消息 WebSocket {pub} 使用 mime=text/x-drafty、x-im-type=file、Drafty EX entity,并写 extra.attachments=[url]
接收验证 Bob 收到 {data},其 Drafty content 中的 ref 与上传返回 URL 一致
查询验证 Alice 使用 x-im-filter.types=["file"] 查询到该文件消息
防 GC 引用 MySQL fileuploads 记录成功完成,filemsglinks 已绑定到对应 messages.id

已执行以下联调:

命令 结果
gofmt -w tools/wsmock/main.go && go build ./tools/wsmock 通过
go test -count=1 ./tools/wsmock 通过,无测试文件
go run -tags mysql ./server -config=tinode.conf -static_data=- 服务启动成功,监听 :6060,日志显示 Large media handling enabled fs
go run ./tools/wsmock -apikey '<generated>' -scenario upload -timeout 12s 通过
mysql -uroot -proot -h127.0.0.1 -P3306 tinode -e '<fileuploads/filemsglinks join query>' 查到上传文件与消息引用
curl -H 'X-Tinode-APIKey: ...' 'http://127.0.0.1:6060/v0/file/s/KX2Un74V9eI.conf?...' 200 OK,内容与上传文本一致

联调观察:

协议点 实际返回
/v0/file/u/ 200 ok,ctrl.params.url="/v0/file/s/KX2Un74V9eI.conf",ctrl.params.expires 返回未绑定前临时保留时间
文件落库 fileuploads.id=1557212408072638464,status=1,mimetype=text/plain; charset=utf-8,size=50,location=uploads/ff6zjh56cx26e
消息绑定 filemsglinks.fileid=1557212408072638464 关联 messages.id=170,消息 topic 为 P2P topic,seqid=1
文件下载 /v0/file/s/KX2Un74V9eI.conf 返回 200 OK,Content-Length=50,内容与上传文本一致
场景归属 upload 会写真实文件和业务库记录,暂不纳入默认 all,避免普通回归持续堆积附件

开发后回归记录(十五)

执行时间:2026-07-31 15:19:10 CST

本轮补齐好友/联系人业务状态专项联调。新增 tools/wsmock -scenario contacts,通过真实 WebSocket 验证 P2P 建链时的好友申请备注、联系人私有状态读写、拉黑 ACL 阻断、取消拉黑恢复发言,以及非法 private 字段校验。

代码变更摘要:

覆盖点 说明
申请备注 Alice 使用 {sub topic=bobUserId set.desc.private.x-im-applyText} 建立 P2P 关系
详情读回 Alice 使用 {get topic=bobUserId what="desc"} 读取 desc.private 并断言申请备注
通讯录读回 Alice 使用 {get topic="me" what="sub"} 读取联系人列表,断言目标联系人 sub.private
私有状态 写入并读回 comment、x-im-remarkUpdatedAt、x-im-muted、x-im-pinned、x-im-blocked
拉黑联动 Alice 设置 x-im-blocked=true 并把 Bob 的 P2P given 调整为 JRPA,Bob 再发言返回 403 permission denied
取消拉黑 Alice 设置 x-im-blocked=false 并把 Bob 的 P2P given 调整为 JRWPA,Bob 发言返回 202 accepted 且 Alice 收到 {data}
参数校验 x-im-blocked 非布尔值、x-im-applyText 超长均返回 400 malformed

已执行以下联调:

命令 结果
gofmt -w tools/wsmock/main.go 通过
go test -count=1 ./tools/wsmock 通过,无测试文件
go run -tags mysql ./server -config=tinode.conf -static_data=- 服务启动成功,监听 :6060,MySQL adapter schema 118
go run ./tools/wsmock -apikey '<generated>' -scenario contacts -timeout 12s 通过

联调观察:

协议点 实际返回
好友申请备注 private.x-im-applyText="好友申请备注联调 151910" 在 topic desc 和 me.sub 中均可读回
私有状态 comment、x-im-muted、x-im-pinned、x-im-blocked、x-im-remarkUpdatedAt 在 desc.private 与联系人列表中保持一致
拉黑 ACL {set.desc.private + set.sub.mode="JRPA"} 返回 200;服务端向 Bob 推送 pres what="acs" dacs.given="+P-W"
被拉黑发言 Bob 发布 P2P 文本返回 403 permission denied
取消拉黑 ACL {set.desc.private + set.sub.mode="JRWPA"} 返回 200;服务端向 Bob 推送 pres what="acs" dacs.given="+W"
恢复发言 Bob 发布 P2P 文本返回 202 accepted,Alice 收到对应 {data}
非法 private x-im-blocked="true" 与 257 字符 x-im-applyText 均返回 400 malformed
场景归属 contacts 会写真实联系人私有状态并调整 P2P ACL,暂不纳入默认 all,便于专项定位

开发后回归记录(十六)

执行时间:2026-07-31 15:37:26 CST

本轮补齐“页面型能力”中可复用 Tinode 原生协议低改动落地的专项联调。新增 tools/wsmock -scenario pages 与 tools/wsmock -scenario group-owner,覆盖收藏/文件助手/个人设置/黑名单视图/删除好友/群二维码/群主转让。

代码变更摘要:

覆盖点 说明
个人设置 Alice 写入 me.desc.private.x-im-settings,再通过 {get topic="me" what="desc"} 读回
收藏列表 Alice 向 slf 发布 head.x-im-type="favorite",再用 x-im-search.types=["favorite"] 搜索收藏
文件助手 Alice 向 slf 发布文件卡片消息,再用 x-im-filter.types=["file"] 读取文件助手列表
黑名单视图 Alice 拉黑 Bob 后,通过 {get topic="me" what="sub"} 读取联系人列表并过滤 sub.private.x-im-blocked=true
删除好友 Alice 对 P2P topic 执行 {leave unsub=true},再次读取 me.sub 时目标联系人已消失
群二维码 群主写入群 public.x-im-qrcode,再通过 {get what="desc"} 读回
群主转让 Alice 先给 Bob 的 given 授予 JRWPASDO,Bob 再设置自己的 want=JRWPASDO 接受转让
群主唯一性 转让后 Bob 的 acs.mode 包含 O,Alice 的 acs.mode 不再包含 O

已执行以下联调:

命令 结果
gofmt -w tools/wsmock/main.go 通过
go test -count=1 ./tools/wsmock 通过,无测试文件
go build ./tools/wsmock 通过
go run -tags mysql ./server -config=tinode.conf -static_data=- 服务启动成功,监听 :6060,MySQL adapter schema 118
go run ./tools/wsmock -apikey '<generated>' -scenario pages -timeout 12s 通过
go run ./tools/wsmock -apikey '<generated>' -scenario group-owner -timeout 12s 通过

联调观察:

协议点 实际返回
x-im-settings allowSearchByCard/allowSearchByQr/allowSearchByGroup/allowSearchByPhone/joinGroupNeedsApproval/teenMode 均可通过 me.private 保存并读回
收藏消息 slf 收藏消息返回 202 accepted,搜索返回 208 delivered 且 params.count=1
文件助手 slf 文件卡片返回 202 accepted,文件类型筛选返回 208 delivered 且 params.count=1
黑名单列表 拉黑后 me.sub 中对应联系人 private.x-im-blocked=true
删除好友 {leave topic=usrPeer unsub=true} 返回 200 ok,后续 me.sub 不再包含该联系人
群二维码 public.x-im-qrcode="im://group/<groupTopic>" 可从群 desc.public 读回
群主转让 Bob 接受后服务端向 Bob 推送 given="+O",向 Alice 推送 want="-O", given="-O"
新群主操作 Bob 成为群主后可继续更新 aux.x-im-group.announcement,返回 200 ok
场景归属 pages 会写真实用户 private、slf 消息和 P2P 订阅;group-owner 会改变群 owner,均暂不纳入默认 all

后续仍需前端或运营侧承接的页面:

页面/能力 当前状态
新朋友审批队列 后续记录(十九)已补齐 private.x-im-applyStatus 状态字段和 WS 联调
进群审批队列 后续记录(十九)已补齐 want/given 待审批订阅、x-im-joinStatus 自动流转和 WS 联调
系统消息列表 当前已覆盖普通用户向 sys 举报;平台主动系统通知需要系统账号、运营后台或服务端投递入口
版本更新/权限弹窗 多数为客户端本地或应用商店/配置中心能力,后端只需在后续配置接口中承载版本策略

开发后回归记录(十七)

执行时间:2026-07-31 15:40 CST

本轮按本地 MySQL 环境重新整理并验证测试命令。普通 go test ./server/... 会同时编译 PostgreSQL、MongoDB、RethinkDB 的 adapter 测试包;这些测试包没有对应 build tag 时会使用 blank adapter,因此 backend.GetTestAdapter 未定义。MySQL CI 应显式使用 -tags mysql,并排除其它数据库 adapter 的测试包。

测试环境:

项 值
MySQL 地址 127.0.0.1:3306
业务库 tinode,未用于 adapter reset 测试
测试库 tinode_test
测试配置 server/db/mysql/tests/test.conf,reset_db_data=true
数据库存在性 tinode 与 tinode_test 均存在

已执行以下测试:

命令 结果
mysql -uroot -proot -h127.0.0.1 -P3306 -e 'SHOW DATABASES LIKE "tinode%";' 查到 tinode、tinode_test
go test -count=1 -tags mysql ./server/db/mysql/tests 通过,ok github.com/tinode/chat/server/db/mysql/tests 0.617s
go test -count=1 -tags mysql $(go list ./... | grep -v '/server/db/mongodb/tests$' | grep -v '/server/db/postgres/tests$' | grep -v '/server/db/rethinkdb/tests$') 通过,包含核心 server、MySQL adapter tests、tools/wsmock 等包

后续 CI 建议:

go test -count=1 -tags mysql ./server/db/mysql/tests
go test -count=1 -tags mysql $(go list ./... | grep -v '/server/db/mongodb/tests$' | grep -v '/server/db/postgres/tests$' | grep -v '/server/db/rethinkdb/tests$')

风险与注意事项

  1. server/db/mysql/tests/test.conf 当前连接的是 tinode_test,并且会重置测试数据。不要把该配置直接改成生产/业务库 tinode 后执行测试。
  2. PostgreSQL、MongoDB、RethinkDB 测试本次未执行;如后续需要支持这些后端,应分别准备对应数据库实例并使用对应 build tag 单独测试。
  3. WebSocket 端到端联调已覆盖核心 Phase 2 场景、基础 P2P/搜索/举报、好友/联系人私有状态、拉黑 ACL、收藏、文件助手、删除好友、黑名单视图、群二维码、群主转让、真实文件上传与附件 GC 绑定、群业务权限、完整群管理流程和完整 WebRTC 信令;真实浏览器音视频采集、设备权限、NAT 穿透质量仍需在前端或真机环境补测。
  4. 当前目录不是 git 仓库,无法通过 git status 生成变更基线。

开发后回归记录(十八)

执行时间:2026-07-31 16:00 CST

本轮实现并验证 Phase 3 MVP:反诈风控、内容审核标记、话题推荐排序、MySQL 全文搜索优化。设计原则仍保持小改动:不新增 HTTP 业务接口,不修改 Tinode 原生协议含义,前后端继续通过 WebSocket x-im-* 扩展字段对接。

代码与数据库变更:

项 说明
反诈风控 {pub} 入库和广播前调用 imReviewPubMessage,命中转账、验证码、外链、违禁词等规则时写入 head.x-im-anti-fraud=true、head.x-im-risk-level、head.x-im-moderation
内容审核 当前为内置规则标记 MVP,只标记 flagged 与 reasons,不阻断消息,避免误伤主聊天链路
推荐排序 fnd 的 {get what="sub"} 支持 x-im-recommend,MySQL adapter 通过 FindRecommendedTopics 按关键词、类型标签、业务标签、订阅数和活跃时间排序
全文搜索优化 im_message_index.search_text 增加 FULLTEXT INDEX im_message_index_search_text(search_text);查询使用 MATCH ... AGAINST + LIKE fallback
数据库版本 MySQL adapter/schema 从 118 升级到 119
顺手修复 MySQL UserDelete 硬删除拥有 topic 的用户时,tx.Exec 参数切片已改为 args... 展开

数据库执行与确认:

命令 结果
go run -tags mysql ./tinode-db -config=server/tinode.conf -upgrade 业务库从 118 升级到 119,输出 Database successfully upgraded.
SELECT value FROM kvmeta WHERE key='version' 返回 119
SHOW INDEX FROM im_message_index WHERE Key_name='im_message_index_search_text' 返回 Index_type=FULLTEXT

已执行以下测试:

命令 结果
gofmt -w server/datamodel.go server/imext.go server/topic.go server/store/types/types.go server/db/mysql/adapter.go server/db/mysql/tests/mysql_test.go server/imext_test.go tools/wsmock/main.go 通过
go test -count=1 ./server 通过,ok github.com/tinode/chat/server 0.032s
go test -count=1 ./tools/wsmock 通过,无测试文件
go test -count=1 -tags mysql ./server/db/mysql/tests 通过,ok github.com/tinode/chat/server/db/mysql/tests 0.688s
go test -count=1 -tags mysql $(go list ./... | grep -v '/server/db/mongodb/tests$' | grep -v '/server/db/postgres/tests$' | grep -v '/server/db/rethinkdb/tests$') 通过,包含核心 server、MySQL adapter tests、tools/wsmock 等包
go run -tags mysql . -config=tinode.conf -static_data=- 服务启动成功,监听 :6060,MySQL adapter schema 119
go run ./tools/wsmock -apikey '<generated>' -scenario phase3 -timeout 12s 通过,输出 wsmock scenario "phase3" passed

WebSocket 联调观察:

协议点 实际返回
风险消息 Alice 发送 请点击链接并输入验证码后转账 后,Bob 收到 {data},head.x-im-anti-fraud=true
风险等级 返回 head.x-im-risk-level="high"
审核标记 返回 head.x-im-moderation.status="flagged",reasons=["payment-scam","credential-risk","external-link"]
推荐查询 Alice 通过 fnd 发送 {get what="sub" x-im-recommend={keyword:"发布会",types:["news"],labels:["美食"]}}
推荐返回 返回 meta.sub,频道话题按 Tinode 规范显示为 chn...,并带 public.x-im-topic 与标签列表

后续可演进项:

能力 当前状态 后续建议
风控 内置关键词规则标记 接入独立风控服务、用户画像、设备/IP 规则
内容审核 规则命中后 flagged 建后台审核队列、人工复核和处置闭环
推荐 MySQL 轻量排序 引入异步行为统计、热度衰减、个性化召回
全文搜索 MySQL FULLTEXT + LIKE fallback 如果搜索量上升,再接 OpenSearch/Elastic/Manticore 等独立搜索服务

开发后回归记录(十九)

执行时间:2026-07-31 16:31 CST

本轮补齐好友审批状态与进群审批队列,继续保持 WebSocket + Tinode ACL 的实现方式,不新增数据库表、不新增 HTTP 业务接口。

代码变更:

项 说明
好友审批状态 P2P 订阅 private 增加 x-im-applyStatus,支持 pending/accepted/rejected/expired 枚举校验
进群申请备注 群订阅 private 增加 x-im-joinApplyText 与 x-im-joinStatus,备注最长 256 字符
待审批订阅 群 joinApproval=true 且默认 defacs.auth=N 时,申请人 {sub set.sub.mode="JRWPS"} 会保存 want=JRWPS/given=N,但不会 attach 到群
管理员队列 管理员 {get topic=grp what=sub} 可看到待审批用户的 acs 与 private.x-im-join*
审批流转 管理员 {set sub user=... mode="JRWPS"} 自动把申请人 x-im-joinStatus 更新为 accepted;mode="N" 自动更新为 rejected
审批留痕 新增 x-im-type="group-join-approve/group-join-reject" 消息类型,管理员可按需发布审批结果消息
Mock 联调 新增 tools/wsmock -scenario approvals,覆盖好友状态、非法状态校验、进群 pending/accepted/rejected

已执行以下测试:

命令 结果
gofmt -w server/imext.go server/topic.go server/imext_test.go server/topic_test.go tools/wsmock/main.go 通过
go test -count=1 ./server 通过,ok github.com/tinode/chat/server 0.027s
go test -count=1 ./tools/wsmock 通过,无测试文件
go test -count=1 -tags mysql ./server/db/mysql/tests 通过,ok github.com/tinode/chat/server/db/mysql/tests 0.936s
go test -count=1 -tags mysql $(go list ./... | grep -v '/server/db/mongodb/tests$' | grep -v '/server/db/postgres/tests$' | grep -v '/server/db/rethinkdb/tests$') 通过,包含核心 server、MySQL adapter tests、tools/wsmock 等包
go run -tags mysql . -config=tinode.conf -static_data=- 服务启动成功,监听 :6060,MySQL adapter schema 119
go run ./tools/wsmock -apikey '<generated>' -scenario approvals -timeout 12s 通过,输出 wsmock scenario "approvals" passed

WebSocket 联调观察:

协议点 实际返回
好友申请 Alice 创建 P2P 时写入 private.x-im-applyText 与 x-im-applyStatus=pending,me.sub.private 可读回
好友同意 Bob 订阅 P2P 并写入 x-im-applyStatus=accepted;Alice 更新后从联系人列表读回 accepted
好友拒绝 Alice 写入 x-im-applyStatus=rejected 后从 me.sub.private 读回
非法状态 x-im-applyStatus="done" 返回 400 malformed
进群申请 Carol 对审批群 {sub set.sub.mode="JRWPS"} 返回 200 ok,acs.want=JRWPS/given=N/mode=N
待审批发言 Carol 未 attach 到群,发言返回 409 must attach first
管理员队列 Alice {get what=sub} 可看到 Carol 的 private.x-im-joinApplyText 与 x-im-joinStatus=pending
通过入群 Alice 授权 Carol mode=JRWPS 后,Carol 订阅群并读回 x-im-joinStatus=accepted,随后可发言
拒绝入群 Dave 申请后 Alice 设置 mode=N,管理员列表读回 x-im-joinStatus=rejected

仍需后续承接:

能力 说明
平台主动系统通知 仍需要系统账号、运营后台或服务端投递入口
审批后台页面 后端状态已可查可改,管理页面需要前端按 me.sub 和 grp.sub 渲染
真机推送 当前 WS 在线链路已通,离线 push 文案与跳转需要移动端补测