Files

48 KiB
Raw Permalink Blame History

NixMsg 产品需求

项 内容
版本 0.5
日期 2026-09-30
状态 已确认,可交给开发
读者 产品负责人、开发组、后续审核
配套 DEVELOPMENT.md 是实现规范。两者冲突时,以本文已确认的产品行为为准,并在 docs/DEVIATIONS.md 记录
修订 见第 11 节

1. 这是什么

NixMsg 是一套自建的消息中转服务。设备、程序、App 都作为「端」连到同一台服务器,端只用一个固定端口。端之间用端编号互发小消息,或在群里发同一条消息。

服务器负责:身份(开通、自助注册、登录)、在线状态、弱网重传、可选的离线暂存、定时和延迟发送、撤回、送达结果。服务器不保存聊天记录,也不提供业务聊天界面。业务界面由接入方自己做。我们提供管理后台和各语言 SDK。

一条消息的正文最大 256 KiB。更大的文件本期不传,以后用「先上传、再发链接」的方式做。

2. 要解决的问题

  • 端与端之间隔着 NAT,不能互相直连,需要一台中转。
  • 网络会抖、会断。已经交给服务器的消息要尽量送到,对方短暂掉线时不要立刻丢掉。
  • 对方长时间不在线时,发送方自己决定留不留、留多久。
  • 发送方可以反悔,也可以指定过一会儿再发。
  • 多个端要能收同一条消息。
  • 有的端不希望陌生人给它发消息。
  • 接入方要在自己的 App 里让用户注册、登录、改密码,但不能让陌生人随便注册。
  • 接入方语言不同,需要少写连接代码;没有现成 SDK 的设备要能按协议直接接。

3. 名词

名词 含义
端 一个通讯身份。有唯一编号、登录密码,既能发也能收。程序、设备、App 登录后都是端。端由管理员开通,或在管理员开启注册后自己注册
群 若干端的集合。发到群里的一条消息,内容只有一份,每个成员各有自己的送达结果
登录密码 端连接服务器时使用。防止别人冒充这个编号
会话令牌 用密码登录成功后服务器发给端的一串随机字符。之后断线重连都用它,不用密码,也和 IP 无关。每次用密码登录都会换新的,旧的立即作废
对话密码 端自己可选的一道门。别人要单聊它,或把它拉进群,得先知道这道密码。不设置则谁都能给它发
对话授权 服务器记住的「甲可以向乙单聊」。输对一次对话密码,或乙主动给甲发过单聊,都会形成授权。乙改对话密码后授权失效
注册安全码 管理员设置的一串字符。开启自助注册后,注册时必须带上它。更换后只影响之后的注册
提交 发送方把消息交给服务器,服务器写入存储并返回成功
发送时刻 服务器真正开始投递的时间。可以是立即、延迟若干秒,或指定的未来时间
投递 服务器把消息推给接收端
收下 接收端处理完并向服务器确认。这时才算送到
确认超时 消息推给在线端后,等它确认的最长时间,默认 5 分钟
离线保留 发送时刻对方不在线时,把消息留在服务器,等它上线再推。按条选择
保留时间 离线保留的最长期限,从发送时刻起算。超时还没收下就作废
抖动宽限 对方刚断线的一小段时间(默认 60 秒)。这段里即使本条没有选择保留,也先等它重连
撤回 发送方取消一条对方还没收下、且还没作废的消息
回执 服务器告诉发送方某个接收端的最终结果:已收下、已过期、已丢弃、已拒绝。撤回的结果从撤回请求的返回里得到,不再发回执
记录 不含正文的投递痕迹:谁发给谁、何时提交、何时发送、结果如何。给管理后台查询
圈子 这一台服务器上的全部端。谁都能看到别人在不在线

4. 谁在用

角色 做什么
程序或设备 主要使用者。自动收发,收到就处理
人 通过接入方自己的 App 或网页看消息、点发送;管理员开启注册时,也可以在接入方 App 里自己注册。本期不提供这套界面
管理员 使用自带的网页后台:开通端、开关注册和设置注册安全码、改密码、看在线、管群、查送没送到

典型场景:

  1. 后台给设备下发一条指令。设备当时不在线,指令保留 24 小时,上线后送到。设备回复时,若后台设了对话密码且曾经给设备发过单聊,设备不用再输密码。
  2. 人在网页里给一个端发消息,发出后 10 秒内可以撤回。
  3. 约定明天 8 点发到一个群。到点时按当时的群成员投递;选了保留的,不在线的成员上线后仍能收到同一条内容。
  4. 发送前先看对方在不在线,或列出当前在线的端。
  5. 接入方在自己的 App 里内置注册安全码,用户在 App 里注册、登录、改密码。管理员更换安全码后,旧版 App 不能再注册新用户,已注册的用户照常使用。

5. 本期做、本期不做

5.1 本期做

编号 能力 优先级
F01 开通和管理端 P0
F02 登录,同一编号只能一处在线 P0
F03 查看在线和端目录 P0
F04 上下线通知 P0
F05 单聊 P0
F06 群发 P0
F07 消息内容与 256 KiB 上限 P0
F08 至少送达一次、确认、去重、顺序 P0
F09 离线保留和保留时间 P0
F10 断线抖动宽限 P0
F11 定时发送 P0
F12 发送延迟(反悔窗口) P0
F13 撤回 P0
F14 回执 P0
F15 对话密码和授权 P0
F16 群管理 P0
F17 网页管理后台和管理 API 令牌 P0
F18 正文删除和投递记录 P0
F19 Go、Java/Android、JS/TS、Python SDK P0
F20 无 SDK 时按协议用 MQTT 接入 P0
F21 端只用一个固定端口,后台可用单独端口 P0
F22 单文件或 Docker 部署、备份、升级、监控指标 P0
F23 端自助注册(管理员开关、注册安全码) P0

5.2 本期不做

  • 大文件、分块、断点续传。后续另行做「上传文件后发送链接」。
  • 服务器保存历史供翻看。历史由各端自己存。
  • 同一编号同时在多台设备登录。要多台就开通多个端。
  • 多机集群。
  • 注册审核、邮箱或手机号验证。注册只靠注册安全码把关。
  • 忘记密码自助找回。忘记登录密码由管理员重置。
  • 端自己注销。需要时由管理员在后台删除。注意:接入方的 iOS App 如果提供注册,苹果审核要求 App 内能删除账号,届时需要补这项能力。
  • 已读(人有没有看)。只有「端有没有收下」。
  • 编辑已发消息。可以撤回后重发。
  • 端到端加密。传输可以用 TLS,服务器在投递完成前能看到正文。
  • 手机系统推送(APNs、FCM 等)。注意:手机 App 退到后台后,连接通常会被系统挂起,没有系统推送就收不到新消息提醒;人用的聊天场景要考虑这一点。
  • 管理员在后台代发或代撤回。
  • 多个管理员角色。本期一个管理员账号。
  • 嵌入式 C 语言 SDK。小设备按 F20 直接接 MQTT。

6. 功能需求

优先级 P0 为本期必须完成。每条都包含规则和验收。

F01 开通和管理端

端由管理员开通,或在管理员开启注册后由端自己注册(F23)。两种方式开出来的端没有区别。

  • 编号 1–64 字符,只能是小写字母、数字、_、.、-,全局唯一。留空则服务器生成,形如 e_ 加 8 位小写字母数字。不允许大写,是为了避免 Alice 和 alice 这类看起来相同的编号互相冒充。
  • 名称可选,最多 64 个 Unicode 字符。备注可选,最多 200 字符。
  • 登录密码 8–128 字符,不能以 nst_ 开头(这个前缀留给会话令牌)。留空则生成随机密码,只在创建成功时显示一次。
  • 对话密码可选,4–64 字符。空表示不设防。
  • 可设置默认发送延迟,单位秒,默认 0。
  • 支持批量开通:上传 CSV 文件(UTF-8),列为编号、名称、登录密码、对话密码、默认延迟、备注,一次最多 1000 行。先整体检查,有任何一行不合格就一行都不开通,并列出出错的行和原因。密码留空则生成,生成的密码只在结果里出现这一次,可以下载保存。
  • 可改名称、备注、默认延迟、启停、删除、重置登录密码、设置或清除对话密码、踢下线、解除登录锁定。重置登录密码会让它的会话令牌作废。踢下线只断开当前连接,令牌不变,SDK 会自动重连;要让它连不上,请停用。
  • 停用:立刻断开,不能再登录;发给它的新消息提交失败;尚未送到它的消息作废,原因 endpoint_disabled;它自己发出、还没推送出去的消息也作废,原因 sender_disabled。重新启用后可以正常登录,已作废的消息不恢复。
  • 删除:在停用的效果之外(原因记为 endpoint_deleted、sender_deleted),退出所有群;它当群主的群转给最早加入的成员,群里没有别人则解散;清掉与它相关的授权、它的待送回执和它发出的消息记录。编号删除后可以重新开通或注册,新端不继承任何旧数据。

验收:

  • 一次批量开通 1000 个端成功,生成的密码不写入日志。CSV 里有一行编号重复时,一个端都不开通,并指出是哪一行。
  • 停用后该端立刻离线,发给它的新消息被拒绝,发给它的定时未到点的消息作废;它停用前提交、还没到点的定时消息也不再发出。
  • 删除群主后,群主变为剩余成员里最早加入的那个。
  • 删除一个端后用同一编号重新开通,新端收不到旧端的回执。

F02 登录、会话令牌与单连接

  • 首次登录用端编号和登录密码。登录成功后服务器发给端一个会话令牌,之后断线重连都用这个令牌,不再用密码。令牌和 IP 无关:设备换网络、换 IP,或者服务器重启后,都直接用令牌重连。
  • 同一编号同时只有一个有效会话。每次用密码登录都换一个新令牌,旧令牌立即作废(D28):
    • 旧设备当时在线:当场被踢下线,原因是「已在别处登录」,停止自动重连并通知应用。
    • 旧设备当时不在线:之后用旧令牌重连会被拒绝,SDK 通知应用「会话已失效」并停止重连,需要重新用密码登录。
    • 用令牌重连不算新登录,令牌不变。
  • 应用应该保存令牌,而不是密码。令牌 30 天没用过自动失效(可配置,0 表示不失效),之后要重新用密码登录(D29)。应用退出登录时调用 SDK 的退出,服务器作废令牌。
  • 端可以自己改登录密码,必须提供旧密码。当前连接保持,并拿到新令牌。
  • 管理员重置登录密码、停用、删除:令牌作废,当前连接被踢下线,停止自动重连。管理员「踢下线」只断开连接,令牌不变,SDK 会自动重连。
  • 密码输错会临时锁定。锁定只针对用密码登录,用令牌重连不受影响,所以已登录的设备换 IP、断线重连都不会被锁(D18):
    • 同一编号在同一来源 IP 上 5 分钟内错 10 次,锁定这个组合 5 分钟。设备换了 IP,不会被别的 IP 上的失败牵连。
    • 同一编号不论来源 IP,1 小时内错 50 次,暂停这个编号的密码登录 1 小时,防止有人不断换 IP 猜密码。管理员可以在后台解除锁定。
  • 登录失败要分清两类:密码错误、会话已失效、编号不存在、已停用、已锁定,SDK 停止重连并通知应用;服务器暂时不可用(例如数据库故障、连接数已满),SDK 继续重连。

验收:

  • A 设备在线时,B 设备用密码登录同一编号:A 被踢下线且不再重连,B 可用。
  • A 设备离线期间,B 设备用密码登录;A 恢复网络后用旧令牌重连被拒,SDK 报「会话已失效」且不再重连。
  • 设备换了 IP(例如 Wi-Fi 切到 4G)后,用令牌自动重连成功,不需要密码。
  • 错误密码达到上限后,同一 IP 用正确密码在锁定期内也被拒绝,锁定期过后可以登录;换一个 IP 用正确密码不受影响。
  • 从多个 IP 轮流猜同一编号的密码,达到总数后这个编号暂停密码登录;已登录的设备断线后用令牌照常重连;管理员解除锁定后可以用密码登录。
  • 服务器数据库不可用时,SDK 不会进入「密码错误」状态,恢复后自动连上。

F03 在线状态与目录

任何已登录的端都可以:

  • 查询若干编号是否在线。
  • 分页列出全部端。每项包含编号、名称、是否在线、最近上线时间、最近离线时间、是否设置了对话密码。不包含密码本身,也不包含「我是否已获授权」。

在线以服务器上的真实连接为准。心跳超时未收到数据即判离线。默认心跳 30 秒,超时按协议的 1.5 倍,约 45 秒。

验收:

  • 正常断开后 1 秒内,别人查到的状态为离线。
  • 拔掉网线后,在心跳超时窗口内变为离线。
  • 1000 个端同时在线时,拉全表在 1 秒内返回。

F04 上下线通知

端可以订阅某些编号的上下线,也可以订阅全部。变化时收到通知。通知尽力送达,不落库、不补发。连接断开后订阅清空,SDK 在重连后按应用上次的选择重新订阅;应用需要准确状态时,重连后再查一次在线状态。

验收:订阅 A 后,A 上下线各收到一次通知;没订阅的 B 上下线不收到。

F05 单聊

  • 端可以给另一个端发消息,也可以给自己发(用于定时提醒)。发给自己不需要对话密码。
  • 每条消息由发送方提供消息号,SDK 自动生成。1–64 字符,字母、数字、_、.、-,区分大小写。同一发送方不要重复使用消息号。
  • 服务器把消息落入存储后才返回提交成功。返回前宕机,客户端按未提交处理并重试。
  • 用同一消息号、同一请求内容重试,返回原来的结果,不产生第二条;即使对方在两次之间停用或改了对话密码,也以第一次的结果为准。同一消息号但请求内容不同(目标、正文、选项有任何不同),返回冲突。服务器至少在 24 小时内(防重窗口,可配置)识别重试;这条消息的记录还在时也一直识别。
  • 对方不存在、已停用、已删除:提交失败并给出原因。
  • 对方设了对话密码且发送方没有有效授权:提交失败,错误为需要对话密码。本条请求里带上正确密码则授权并提交成功。
  • 每个端默认每秒最多 50 个请求(可短时突发到 100,确认类请求不算),可配置。用一个端集中下发大量消息的接入方,可以调高或改用群发。
  • 每个端同时处于「等待发送」或「投递中」的消息最多 10000 条(群消息算一条),超出时提交失败;每个端排队等它收下的投递最多 10000 条,超出时新来的投递直接拒绝,原因 queue_full,发送方收到回执。两个上限都可配置,设为 0 表示不设上限(D25),用来防止一个端把服务器磁盘塞满。

验收:

  • 提交成功后立刻杀掉服务器进程,重启后这条消息仍会投递。
  • 同一消息号连续提交两次,接收方只处理一次。同一消息号换了正文再提交,返回冲突。
  • 未授权时不带密码被拒绝;带对密码后成功,之后不带密码也能再发。
  • 一个端未完成的消息达到上限后,新提交失败;接收端排队达到上限后,新投递被拒绝,发送方收到回执。

F06 群发

  • 提交时发送方必须是群成员。到发送时刻发送者已不在群里:消息作废,不再投递。
  • 内容只存一份。每个接收成员一条投递结果。
  • 接收者是发送时刻的成员,不含发送者本人。已停用的成员收不到,结果记为已拒绝。
  • 发送时刻之后才入群的端收不到。发送时刻之前已退群的端收不到。
  • 群消息不看成员的对话密码。

验收:

  • 群内 A、B、C,A 发送,B 和 C 收到同一消息号、同一内容,A 自己不收到。
  • A 发送时选了离线保留,B 当时离线,B 在保留时间内上线后收到的内容与 C 相同。
  • B 在发送时刻之后入群,收不到这条。

F07 内容与大小

  • 正文是 UTF-8 文本,或二进制(线上用 Base64)。可带内容类型。
  • 自定义键值附在消息上,序列化后不超过 4 KiB。
  • 正文解码后最大 256 KiB(262144 字节)。这个上限可以调小,本期不能调大。超过则发送端直接失败,服务器不接收。
  • 接收端可在登录握手时声明自己一次最多能收多少字节(按服务器推来的整条数据计算,不小于 1024)。某条投递超过该值:这条投递拒绝,原因 too_large,不重试,并回执发送方。
  • 不做分块,不做断点续传。

验收:

  • 256 KiB 文本可以送达。多 1 字节在 SDK 本地失败;绕过 SDK 直接提交过大的正文,服务器拒绝且不产生投递。
  • 声明只能收 1024 字节的端,收到一条 2048 字节消息的投递结果是拒绝,发送方收到对应回执,这个端的连接不受影响。

F08 投递、确认、重复和顺序

  • 到了发送时刻且对方已完成登录握手:立刻推送。
  • 接收方处理完成后确认。SDK 默认在收到回调正常返回后自动确认;也可以改成应用自己调用确认。
  • 处理回调抛错则不确认。选了离线保留的消息,在保留时间内服务器会在重连后或确认超时后再推;不保留的消息推送后确认超时仍未确认,按已丢弃结束(D19)。应用必须能接受同一条到达多次。
  • 保证的是至少送到一次。弱网上可能重复。SDK 在进程运行期间按「发送方编号 + 消息号」去重后再交给应用,默认记住最近 10000 条;已经确认过的消息再次到达时,SDK 直接再确认一次,不交给应用。进程重启后,重启前没确认的消息可能再到一次。
  • 同一接收端按发送时刻排序;发送时刻相同则按提交先后。SDK 对同一连接上的回调串行执行。重推的消息可能排在后面的消息之后。
  • MQTT 传输层的确认只表示服务器收到了请求。对方收下以应用层确认为准。

验收:

  • 弱网(延迟、丢包、随机断开)下,选择了离线保留且在保留期内的消息最终全部送达;SDK 进程不重启时,交给应用的回调不重复。
  • 服务器在已返回提交成功后强制重启,未收下的消息会继续投递。

F09 离线保留

  • 每条消息选择是否保留,默认不保留。
  • 保留时指定时长,默认 24 小时,最长 30 天(可配置)。从发送时刻起算,定时等待的时间不算进去。
  • 超时仍未收下:结果为已过期,正文删除。
  • 已经推给在线端、正在等确认的消息,这一轮等待(最长一个确认超时)里不会因为保留时间到了而作废;这一轮结束仍未确认且已过保留期限,按已过期结束,否则再推。

验收:

  • 保留 1 小时,对方 30 分钟后上线,能收到。
  • 对方 2 小时后才上线,收不到,发送方得到已过期。
  • 指定明天 8 点发送、保留 1 小时:在明天 8 点之前一直处于等待发送;8 点才开始算 1 小时。

F10 抖动宽限

  • 不保留,且发送时刻对方不在线:若对方在宽限内刚断线(默认 60 秒),先等到宽限结束;期间重连则照常推送,宽限结束仍不在则丢弃。
  • 从未上线或断线已超过宽限:不保留的消息立即丢弃。
  • 正在推送时连接断开:无论是否保留,至少再等一个宽限。保留消息的截止时间若早于「现在 + 宽限」,延长到「现在 + 宽限」。
  • 服务器重启时,所有未完成的投递按对方刚断线处理:至少再等一个宽限,端在宽限内重连就能收到(D23)。

验收:

  • 不保留。对方断网 30 秒内恢复,消息送到。
  • 不保留。对方断网超过 2 分钟,消息丢弃,发送方收到已丢弃。
  • 不保留。服务器重启后端在 30 秒内重连,重启前没收下的消息仍能送到。

F11 定时发送

  • 发送方指定未来的发送时刻,以服务器时钟为准。SDK 用握手返回的服务器时间校正本机偏差。
  • 提交成功后发送方可以下线,到点由服务器投递,然后再走 F09、F10。
  • 最远可定到 365 天后(可配置)。指定时间已过则立即发送。
  • 服务器停机期间到点的定时消息,启动后立即发送。
  • 定时与「延迟秒数」不要同时使用。同时使用则提交失败。指定了发送时刻时,不再叠加端的默认延迟。

验收:提交一条 2 分钟后发送的消息,发送方立刻断开;2 分钟后接收方在线则收到,不在线则按是否保留处理。

F12 发送延迟

  • 端可以固定默认延迟。每条消息也可以单独指定延迟秒数,指定 0 表示这条立即发送。都没指定则立即发送。
  • 延迟由服务器时钟计算:发送时刻 = 服务器当前时间 + 延迟。
  • 在发送时刻之前消息还在服务器上,撤回一定成功。这就是反悔窗口。

验收:默认延迟 10 秒。发送后 3 秒撤回,接收方永远收不到。超过 10 秒且对方在线,消息已经进入投递。

F13 撤回

  • 只有发送方能撤回。管理员不能代撤。
  • 对象是已提交、接收方尚未收下、且尚未结束(过期、丢弃、拒绝)的消息。
  • 还没到发送时刻:一定成功,之后不再发送。
  • 已在离线保留中、尚未推送:一定成功。
  • 已经推给在线端但对方还没确认:撤回和确认谁先被服务器记下来谁生效。撤回先到,则对方之后的确认会得知已撤回,SDK 发出撤回事件;应用如果已经处理了这条,由应用自己决定怎么撤销(例如隐藏)。确认先到则撤回失败(或群里该成员失败)。
  • 已经收下、已过期、已丢弃、已拒绝:不能撤回。
  • 群消息:还没收下的成员全部撤回;已经收下的成员保持已收下。至少撤回一个且没有成员已收下,结果为「全部撤回」;有撤回也有已收下,为「部分撤回」;一个都没撤回,为「失败」。已经过期、丢弃、拒绝的成员不影响结果。
  • 撤回的结果在撤回请求的返回里给出,不再为撤回单独发回执(D20)。
  • 撤回不收回已经形成的回复授权(F15 第 2 种授权,D22)。

验收:

  • 延迟窗口内撤回,接收方无回调。
  • 对方离线且在保留中,撤回后上线也收不到。
  • 群里一人已确认、一人未推送:结果为部分撤回,未推送的成员收不到,已确认的成员仍保留本地副本。

F14 回执

  • 每条消息默认要回执,可以按条关闭。
  • 每个接收端一条最终回执:已收下、已过期、已丢弃、已拒绝。
  • 发送方当时不在线:回执保留,默认最多 7 天,上线后补送。回执可以重复到达,SDK 按回执号去重。
  • 关闭回执的消息不占这份队列。管理后台仍能按 F18 查记录。

验收:接收方确认后发送方收到已收下。发送方离线期间对方确认,发送方重连后补收到回执。

F15 对话密码

  • 不设置:任何端都能向它单聊。
  • 设置后,向它单聊必须有有效授权。授权只有两种:
    1. 发送时或单独解锁时输对当前对话密码。
    2. 对方曾经成功提交过发给我的单聊。群消息不算。
  • 授权只看提交那一刻。提交成功之后对方才改密码,这条已提交的消息照常投递。改密码只让之后的新发送重新验证。
  • 对方改密码后,旧授权全部失效,包括「它先给我发过消息」带来的回复权。
  • 把它拉进群时,这一次请求里必须带上它当前的对话密码,已有单聊授权也不能省略。管理员在后台拉人例外,不需要密码。
  • 进群之后,群消息直接送达,不再逐条要密码。
  • 对话密码连续猜错会临时锁定这一对端。默认与登录锁定相同:5 分钟 10 次。另外按被猜的端计总数:1 小时内被猜错 50 次,暂停验证它的对话密码 1 小时,已有授权照常可用;它改对话密码时计数清零(D24)。这是为了防止有人注册很多账号轮流猜,4 位的对话密码经不起这样猜。
  • 不希望被大量回复的发送方(例如给很多端发通知的后台端),可以改用群发:群消息不产生回复授权。

验收:

  • B 设了密码。A 不带密码发送失败;带对一次后,第二条不再带密码也成功。
  • B 改密码后,A 的下一条失败,直到重新输对新密码。
  • B 先给 A 发单聊,A 立刻可以回复且不用密码。B 改密码后,A 再回复失败。
  • A 已能给 B 单聊,把 B 拉进群时仍要带对话密码;密码错误则 B 不进群。
  • B 改密码时,A 此前已提交、尚在延迟中的消息到点仍会送达。
  • 多个账号轮流猜 B 的对话密码,达到总数上限后,正确密码也暂时无法解锁;已有授权的端照常发给 B。

F16 群

  • 已登录的端可以建群,建群者是群主。管理员也可以建群并指定群主,群主同时是成员。
  • 编号规则同端编号(小写字母、数字、_、.、-),留空则生成,形如 g_ 加 8 位。名称最多 64 字。
  • 群主可以改名、加人、踢人、转让群主、解散。成员可以自己退出。群主不能直接退出,要先转让;只剩群主一人时可以解散。
  • 加人时对设了对话密码的端适用 F15。已停用的端不能加入。一次请求里可以带多个成员,校验失败的成员不加入,其余加入,结果里列出失败原因。
  • 踢出或退出:尚未推送的投递作废,原因 left_group;已经推送、尚未确认的,向该端发作废通知。
  • 解散:全体未完成投递作废;发往该群、还没到发送时刻的消息也立即作废,原因 group_dissolved。群编号之后可以被重新使用,新群收不到旧群的任何消息。
  • 群主被删除:群主转给最早加入的其他成员;没有其他成员则解散。
  • 成员数默认上限 1000,可配置。

验收:

  • 非群主加人失败。群主把设密端拉入时密码错误,该端不在成员里,群本身创建成功。
  • 成员退出后收不到之后的消息,也收不到退出前已提交但还没推送的消息。
  • 解散群后立刻用同一编号建新群,旧群里还没到点的定时消息不会发给新群。

F17 管理后台

网页后台,界面中文。功能:

  • 登录、修改管理员自己的密码(至少 12 位)、退出。页面上的时间都按浏览器本地时区显示。
  • 概览:端数量(其中自助注册的数量)、在线数、群数量、待投递数、版本。
  • 端:查询(可按来源筛选:后台开通、自助注册)、开通、批量开通、编辑、启停、删除、重置登录密码、设置或清除对话密码、踢下线、解除登录锁定。列表显示是否在线、最近上下线时间、来源;支持多选后批量停用、删除,方便清理异常注册。
  • 注册:开启或关闭自助注册;查看、手填或生成注册安全码。
  • 群:查询、创建、改名、成员增减、转让、解散。
  • 投递记录:按发送方、接收方、群、状态、时间筛选。只显示记录,不显示正文。点开可看每个接收端的结果、原因、推送次数、时间。
  • API 令牌:管理员可以创建多个令牌(填名称),令牌只在创建时显示一次;可以停用、启用、删除,列表显示最近使用时间。接入方自己的后台带令牌调用同一套管理接口,用程序完成开通、停用、重置密码等操作。令牌不能改管理员密码,也不能管理令牌(D27)。
  • 每个改变状态的操作都记日志:谁做的(管理员或哪个令牌)、做了什么、对象是谁。
  • 运行参数本页只读,改参数通过配置文件。管理员密码、注册开关、注册安全码和 API 令牌在后台修改。

验收:

  • 用后台完成开通、改密、开启注册并设置安全码、建群、查看一条未完成投递。正文在页面、接口和浏览器网络响应里都不出现。
  • 创建一个 API 令牌,用它通过管理接口开通一个端并重置密码;用它改管理员密码被拒绝;停用令牌后立即不能再用。

F18 正文删除与记录

  • 一条消息的全部投递都进入最终状态,或在发送前被撤回、作废:立即删除正文。
  • 记录默认保留 7 天,可配置。设为 0 则完成后连记录一起删除,后台只能看到尚未完成的消息。
  • 防重标记另存「发送方 + 消息号 + 请求指纹」,不含正文。默认保留 24 小时,消息记录还在时一直保留。这是为了弱网重试不产生第二条,不是历史记录。
  • 日志里不写正文、登录密码、对话密码、注册安全码。
  • 备份文件里包含备份当时还没送完的正文,要按敏感数据保管。

验收:接收方确认后,数据库和后台都读不到正文。保留天数设为 0 时,完成后后台列表不再出现这条。24 小时内重试同一消息号仍不重复投递。

F19 SDK

第一批:Go、Java/Android、JavaScript/TypeScript(浏览器和 Node.js)、Python。

四种 SDK 行为一致:

  • 注册(管理员开启注册时可用,需要注册安全码,不需要先登录)。
  • 登录与会话:首次用密码登录,把拿到的会话令牌交给应用保存;之后重连用令牌;令牌失效时通知应用重新登录;退出登录时作废令牌。
  • 连接、自动重连、心跳。被踢下线、密码错误、会话已失效、编号停用或删除时停止重连;服务器暂时不可用时继续重连。
  • 发送(单聊、群、延迟、定时、是否保留、保留时长、是否要回执、附带对话密码)。
  • 撤回、查询本条状态、解锁对话密码。
  • 收消息、收回执、收撤回或作废、去重、自动或手动确认。
  • 在线查询、目录、上下线订阅。
  • 建群、改名、加人、踢人、退出、转让、解散、我的群列表。
  • 修改自己的名称、默认延迟、登录密码(需要旧密码)、对话密码。
  • 发送方暂时连不上服务器时,发送进入内存队列,重连后按原消息号再交;已经发出但没等到结果的也一样。进程退出则队列丢失。除发送以外的请求在离线时直接失败。
  • 本机时间与服务器偏差由 SDK 校正后再计算定时发送。
  • 最低支持:Node.js 20、近两年发布的主流浏览器、Python 3.10、Java 8、Android API 24(D30)。
  • 发布:npm 包 @nixevol/nixmsg、PyPI 包 nixmsg、Maven 包 asia.asio.nixmsg:nixmsg-sdk 发布到 Gitea 包仓库(git.asio.asia);Go SDK 直接从代码仓库获取,模块 git.asio.asia/nixevol/NixMsg/sdk/go。

验收:每种语言都通过 DEVELOPMENT.md 里的同一份接入清单。清单覆盖注册、登录、收发、去重、重连、离线队列、撤回、定时、群、对话密码、改密、被踢、超限。

F20 无 SDK 接入

不提供 C SDK。设备用 MQTT 5 按 DEVELOPMENT.md 的帧格式接入,至少能:登录、收消息、确认、发送、声明自己能接收的最大字节数。能发 HTTP 请求的设备也可以调用注册接口;不能的由管理员开通。设备可以每次都用密码登录(每次都算新登录、会换令牌,只有这一台设备时没有影响),也可以保存会话令牌用来重连。

验收:用一个不依赖四套 SDK 的 MQTT 客户端走通登录、发送、接收、确认。

F21 端口

  • 端只用一个固定端口连服务器(默认 7443)。这个端口同时提供:MQTT over WebSocket(路径 /mqtt,子协议 mqtt)、MQTT over TCP(给跑不动 WebSocket 的设备)、注册接口。TLS 和明文在同一端口上自动识别。
  • 管理后台默认也在这个端口。可以配置成单独端口,例如只监听内网或本机地址,不对公网暴露;配置后,端接入端口不再提供后台。
  • 内置 MQTT 不单独占端口。本期不需要 Redis 或其他中间件。
  • 配置了证书则默认只接受 TLS。没有证书时允许明文,并在启动日志里明确警告。是否额外允许明文可配置。
  • 证书文件更新后自动生效,不用重启。程序本身不申请证书;生产环境由服务器上的 1Panel 申请和自动续签,并推送到本地目录给程序读取(见 DEVELOPMENT.md 第 11.3 节)。

验收:

  • 默认配置只开一个端口:浏览器能打开后台,SDK 能用 WebSocket 注册和收发,一个裸 MQTT TCP 客户端能登录。
  • 配置单独的后台端口后:后台只能从后台端口打开,端接入端口上访问后台返回 404,端的注册和收发不受影响。

F22 部署与运维

  • 一个 Go 程序,一个可执行文件,内置 MQTT 和网页。不另外部署数据库、消息队列或缓存。
  • 同时提供 Docker 镜像(amd64、arm64,发布在 git.asio.asia/nixevol/nixmsg)和 docker-compose 示例,装有 1Panel 的服务器可以直接用它的容器编排部署。
  • 数据在一个目录里的 SQLite 文件。
  • 首次使用先运行初始化命令生成管理员密码(只在终端显示一次,不写日志),再启动服务。
  • 提供备份命令。程序本身不做定时备份,用 1Panel 计划任务或 cron 定时调用(D32)。升级时自动迁移数据库;迁移前自动把库复制一份。
  • 源码、SDK 包和镜像都公开可读;许可证为专有,见仓库根目录的 LICENSE(D33)。
  • 提供健康检查接口,以及 Prometheus 格式的监控指标(在线数、待投递数、投递耗时等,不含正文和编号明细)。指标只在后台端口上开放;后台和端共用端口时,要带配置的令牌才能访问。

验收:

  • 空目录运行初始化命令后启动,可登录后台。二进制和 Docker 镜像(两个架构)都能这样启动。
  • 备份文件能在另一目录启动并看到原来的端。旧版本数据经过一次升级后端和未完成消息都在。
  • 替换证书文件后,不重启服务,新连接在 1 小时内用上新证书。
  • Prometheus 能抓到指标;共用端口时不带令牌抓不到。

F23 端自助注册

管理员可以允许端自己注册,接入方就能在自己的 App 里做注册、登录、改密码。

  • 默认关闭。管理员在后台开启,并设置注册安全码(8–64 字符,可以手填或让系统生成)。关闭时任何注册都失败。
  • 注册必须带当前的注册安全码,错误则失败。
  • 管理员随时可以更换安全码或关闭注册。已注册的端不受影响,照常登录收发;之后的注册必须用新安全码,旧码无法注册。
  • 注册时填写:编号(可留空,由服务器生成)、登录密码(可留空,由服务器生成并只返回一次)、名称(可选)、对话密码(可选)。规则同 F01。编号已被占用则失败。
  • 注册成功即可登录,不需要管理员审核。后台把这类端标记为「自助注册」,管理员可以像其他端一样停用、删除、重置密码。
  • 注册在端接入端口上完成,不需要先登录。同一来源 IP 连续输错安全码会临时锁定:默认 5 分钟 10 次,锁定 5 分钟。
  • 注册之后的改名称、改默认延迟、改登录密码(需要旧密码)、改对话密码,都由端登录后通过 SDK 完成(F19)。
  • 开放注册后,注册者和其他端一样能看到目录(全部端的编号、名称和在线状态),也能给没设对话密码的端发消息。圈子规则不变(D26)。需要防陌生人的端应设置对话密码。
  • 安全码内置在 App 里时,拿到安装包的人就能取出来。它挡的是没有 App 的人,不能证明注册者身份;泄露后换码即可,已注册的端不受影响。
  • 服务对公网开放注册时应配置证书,否则安全码和密码会以明文经过网络。

验收:

  • 注册关闭时,带正确安全码也注册失败。
  • 开启后,错误安全码失败,正确安全码成功,注册出的端能立即登录。
  • 管理员更换安全码后,旧码注册失败、新码成功;更换前已注册的端照常登录收发。
  • 同一 IP 连续输错安全码达到上限后,锁定期内带正确安全码也被拒绝。
  • 编号已存在时注册失败,原有端不受影响。

7. 消息怎么走

stateDiagram-v2
    [*] --> 等待发送: 提交成功
    等待发送 --> 已撤回: 发送时刻前撤回
    等待发送 --> 已拒绝: 到点前或到点时发现对方已停用或删除、群已解散、发送者已退群或被停用
    等待发送 --> 投递中: 到发送时刻
    投递中 --> 已收下: 接收端确认
    投递中 --> 已撤回: 确认前撤回成功
    投递中 --> 已过期: 保留期限到,或确认超时时已过保留期限
    投递中 --> 已丢弃: 不保留且宽限结束仍离线,或推送后确认超时
    投递中 --> 已拒绝: 退群、停用、删除、超过对方接收上限等
    已收下 --> [*]
    已撤回 --> [*]
    已过期 --> [*]
    已丢弃 --> [*]
    已拒绝 --> [*]

群消息是同一张图的多份投递。全部投递都结束后删除正文。

结果 何时 发送方能否撤回
等待发送 没到发送时刻 能,一定成功
投递中,还没推送 离线保留、宽限中,或在排队等推送 能,一定成功
投递中,已推送 在线但还没确认 能发起,和确认抢先
已收下 端已确认 不能
已过期 / 已丢弃 / 已拒绝 已经结束 不能

8. 非功能需求

项 要求
规模 单机 1000 个端同时在线,端总数按 1 万设计。内部连接上限按 2000 留余量
容量 记录量约等于每天消息数乘以保留天数。按每天 100 万条、保留 7 天设计,后台带筛选的查询在 1 秒内返回
吞吐 验收环境能持续每秒 200 条单聊提交;一条 1000 人群消息能在 5 秒内完成推送排队
延迟 双方在线、消息小于 1 KiB 时,从提交成功到对方回调,机房内 P95 小于 300 毫秒
可靠 已返回提交成功的消息,进程崩溃后仍在。断电最多丢失最后一次尚未落盘的写入;默认使用最严格的 SQLite 同步
弱网 20% 丢包、2 秒延迟、随机断开下,保留期内消息最终全部送达,SDK 进程不重启时应用层无重复
安全 密码单向哈希,会话令牌和管理 API 令牌只存哈希;登录、对话密码、注册安全码、管理员登录和令牌都防暴力尝试;后台 Cookie 不可被脚本读取;日志无正文、无密码、无注册安全码、无令牌;端只能订阅自己的下行
时间 服务器时钟为准。定时消息在时钟被人为大改时按新时钟触发,这一点写入运维说明
兼容 MQTT 5 为主。MQTT 3.1.1 仅保证能连接和收发 QoS 1,帧格式仍是同一套 JSON;3.1.1 设备被顶号时看不到原因

9. 写进本文的默认

下面是写文档时定下的默认规则,已全部由负责人确认。以后要改,同步改本文和 DEVELOPMENT.md 对应小节,并在第 11 节记录。D14 起是 0.2 新增,D26 起是 0.3 新增,D28 起是 0.4 新增,D30 起是 0.5 新增。

编号 默认 状态
D1 改对话密码不影响已经提交成功的消息 已确认 2026-09-30
D2 拉人进群必须在当次请求里带对话密码;已有单聊授权不能代替。管理员后台例外 已确认 2026-09-30
D3 回执默认开启,发送方离线时回执最多留 7 天 已确认 2026-09-30
D4 不保留的消息,对方断线未超过 60 秒时先等重连 已确认 2026-09-30
D5 投递记录默认留 7 天且不含正文;防重标记另留 24 小时 已确认 2026-09-30
D6 离线保留默认 24 小时,最长 30 天;端默认延迟 0;定时最远 365 天 已确认 2026-09-30
D7 允许给自己发消息 已确认 2026-09-30
D8 接收端可声明最大接收字节,超限的投递直接拒绝 已确认 2026-09-30
D9 停用或删除端时,尚未送到的消息作废 已确认 2026-09-30
D10 群主被删除时,群主转给最早加入的成员 已确认 2026-09-30
D11 正文用 UTF-8 或 Base64,自定义字段最多 4 KiB 已确认 2026-09-30
D12 一个管理员账号;后台不能代发、代撤回 已确认 2026-09-30
D13 被踢下线的 SDK 停止自动重连 已确认 2026-09-30
D14 自助注册默认关闭。注册安全码 8–64 字符,明文存库、可在后台查看(要分发给接入方;负责人确认不考虑数据库泄露的风险),不写日志 已确认 2026-09-30
D15 注册时编号可以自选也可以留空生成;登录密码留空则服务器生成并只返回一次;注册不需要审核 已确认 2026-09-30
D16 不做忘记密码自助找回和端自己注销,都由管理员处理 已确认 2026-09-30
D17 端编号和群编号只允许小写字母、数字、_、.、-;消息号区分大小写 已确认 2026-09-30
D18 登录密码失败按「编号 + 来源 IP」锁定(5 分钟 10 次),另按编号计总数暂停密码登录(1 小时 50 次);会话令牌重连不受锁定影响;注册安全码按来源 IP 锁定 已确认 2026-09-30
D19 不保留的消息推给在线端后,确认超时(5 分钟)仍未确认就按已丢弃结束,不再重推;保留的消息在保留期内继续重推 已确认 2026-09-30
D20 撤回不单独发回执,结果看撤回请求的返回 已确认 2026-09-30
D21 停用或删除端时,它发出、还没推送的消息一并作废 已确认 2026-09-30
D22 撤回不收回已经形成的回复授权 已确认 2026-09-30
D23 服务器重启时,所有未完成投递至少再等一个宽限,包括保留期在停机期间已过的 已确认 2026-09-30
D24 对话密码另按被猜的端限总数:1 小时错 50 次后暂停新的验证 1 小时,已有授权不受影响 已确认 2026-09-30
D25 每个端未完成的发出消息最多 10000 条,排队待收的投递最多 10000 条;都可配置,设为 0 表示不设上限 已确认 2026-09-30
D26 开放自助注册后,目录仍对所有端可见,谁都能列出全部端 已确认 2026-09-30
D27 管理 API 令牌不过期,停用或删除后立即失效;权限等同管理员,但不能改管理员密码、不能管理令牌;令牌只在创建时显示一次 已确认 2026-09-30
D28 每次用密码登录都换新的会话令牌,旧令牌立即作废,旧设备自动退出 已确认 2026-09-30(负责人提出)
D29 用令牌重连不换令牌;令牌 30 天没用自动失效(可配置,0 表示不失效);登录密码不能以 nst_ 开头;端可以主动退出登录、作废令牌 已确认 2026-09-30
D30 SDK 最低支持 Node.js 20、近两年发布的主流浏览器、Python 3.10、Java 8、Android API 24;Go SDK 和服务端的 Go 版本要求相同 已确认 2026-09-30
D31 管理员密码至少 12 位;后台所有时间按浏览器本地时区显示 已确认 2026-09-30
D32 程序本身不做定时备份,用 1Panel 计划任务或 cron 定时调用备份命令 已确认 2026-09-30
D33 源码仓库、SDK 包、Docker 镜像都公开可读;许可证为专有,公开不代表授权使用 已确认 2026-09-30

10. 验收总表

开发完成时逐条给出结果:通过、失败或未测。未测要写原因。

编号 一句话
F01 批量开通整批校验、停用、删除群主转让、删除后同编号重开不串数据
F02 新设备登录后旧设备自动退出(在线被踢、离线的令牌失效)、换 IP 用令牌重连、两种密码锁定、重置密码后被踢、服务器故障不误报密码错误
F03 断开后状态及时变离线,全表可列出
F04 只通知订阅了的端
F05 崩溃不丢已提交消息,消息号去重和冲突,密码门生效,配额生效
F06 群成员收到同一份,入群前不补,发送者不收到自己的
F07 256 KiB 通过,超出拒绝,接收上限生效
F08 弱网最终送达且应用层不重复,重启后续传
F09 保留时间从发送时刻起算,超时过期
F10 短断线送到,长断线丢弃,服务器重启后宽限内重连送到
F11 发送方离线后到点仍发送
F12 延迟窗口内撤回对方收不到
F13 未推送必撤成功;群部分确认得到部分撤回
F14 回执能补送给当时离线的发送方
F15 输一次记住、改密失效、回复免密、进群仍要密码、防多账号轮流猜
F16 群主权限、退出后不再收到、解散后同编号新群不收旧消息
F17 后台管端、管注册、管群、查记录,响应里没有正文;API 令牌可用且不能越权
F18 送达后正文消失;记录天数 0 时连记录消失;防重仍在
F19 四种 SDK 通过同一清单
F20 裸 MQTT 能登录、收、确认、发
F21 默认一个端口提供后台、WebSocket、TCP、注册;后台可分到单独端口
F22 初始化后单文件或 Docker 启动、备份恢复、升级迁移、证书自动重载、指标可抓取
F23 注册开关、安全码校验、换码不影响已注册、输错锁定

11. 修订记录

版本 日期 说明
0.1 2026-09-30 初稿
0.2 2026-09-30 端口改为「端只用一个固定端口,后台可用单独端口」(F21)。新增端自助注册(F23),从「本期不做」中移除。按审查补充和修正:编号只允许小写(F01、F16);批量开通整批校验(F01);停用或删除端时作废它发出的消息,删除后编号可复用(F01);登录锁定按编号加 IP,并区分服务器故障(F02);消息号按请求内容判冲突,防重标记与记录并存(F05、F18);请求频率上限写入需求(F05);接收上限按整条数据计算(F07);不保留消息的确认超时(F08、F09);服务器重启后的宽限和补发(F10、F11);撤回结果的判定和不发回执(F13、F14);解散群时作废定时消息(F16);后台批量停用、删除和注册设置(F17);首次启动先初始化(F22);单位统一为 KiB;重试先认防重再校验(F05);每个端未完成消息和排队投递的上限(F05);对话密码另按被猜的端限总数(F15);后台端列表显示在线状态(F17);注册安全码和明文传输的提醒(F23);手机 App 在后台收不到提醒的说明(5.2);新增默认 D14–D25
0.3 2026-09-30 负责人确认第 9 节 D1–D26(D27 待确认):开放注册后目录仍对所有端可见(D26),配额可设为 0 表示不设上限(D25)。新增管理 API 令牌和管理操作日志(F17)。F22 增加 Docker 镜像和 Prometheus 监控指标。F21 写明证书文件自动重载、生产环境用 1Panel 申请和续签。技术栈写入 DEVELOPMENT.md 第 2 节:Go + 标准库 net/http、SQLite + 手写 SQL、不用 Redis、Vue 3 + TypeScript + Naive UI 打包嵌入程序
0.4 2026-09-30 登录改为会话令牌(F02):首次用密码登录后发令牌,重连用令牌、和 IP 无关;每次用密码登录都换新令牌,旧设备自动退出(D28);令牌闲置 30 天失效,可以主动退出登录(D29)。密码锁定改为按编号加 IP、按编号总数两种,只拦密码登录,不拦令牌重连(D18);后台可以解除锁定(F01、F17);登录密码不能以 nst_ 开头。新增 docs/TASKS.md 开发任务拆分
0.5 2026-09-30 负责人确认 D18、D27、D29,第 9 节全部确认,状态改为可交给开发。新增 D30–D33:SDK 最低支持版本、管理员密码至少 12 位和后台时间显示、定时备份方式、源码和发布物公开可读但许可证为专有。F19 写明 SDK 包名和发布到 Gitea 包仓库;F22 写明镜像地址、备份方式和许可证