IM 聊天:消息入库后回显给发送方的好处

// 转发消息给接收方(对方,客服)
s.forwardMessage(msg)
// 回显消息给发送者(就是当前这个发消息的用户自己)
if err := conn.WriteJSON(msg); err != nil {
    utils.Logger.Error("im ws echo message failed", zap.Error(err), zap.String("uid", uid))
}

采用消息回显方案,前端使用本地预渲染提升体验:点击发送立刻展示消息气泡并显示发送加载圈;收到这条回显包就代表消息已经在服务端入库成功,前端可以使用后端返回的真实数据更新本地临时消息,清除加载转圈,标记发送成功;如果长时间收不到回显包,则判定发送失败,展示失败重试标识。 两条推送相互独立:forwardMessage 负责推送给对话的另一方;conn.WriteJSON 是给发送方自身的回执回包,不要混淆。

export interface WsRawResponse {
  msgId: string;
  msgSeq: number;
  convId: string;
  senderId: string;
  receiverId: string;
  content: string;
  sendTime: number;
  msgType: number; // 1=文本消息
}
  // 发送普通消息,适配后端WsMessage协议
  private sendNormalMessage() {
    if (!this.message.trim() || !this.currentConvId) {
      return;
    }
    // 组装后端WebSocket协议结构体
    const sendRaw: WsRawResponse = {
      msgId: "",
      msgSeq: 0,
      convId: this.currentConvId!,
      senderId: this.clientId.toString(),
      receiverId: "44", // 临时硬编码客服ID,后续从会话列表接口拿到targetId替换
      content: this.message.trim(),
      sendTime: Date.now(),
      msgType: 1, //1=文本消息
    };
    wsClient.sendMessage(sendRaw);

    // 本地临时渲染(可选,也可以等ws回包再渲染,避免双消息)
    const selfMsg = new MessageModel(
      "",
      this.clientId,
      this.message.trim(),
      Date.now(),
      true,
      0
    );
    //this.msgList = [...this.msgList,selfMsg]
    Logger.debug(TAG, `===== msgList 发送后 =====`, JSON.stringify(this.msgList));
    //this.msgList.push(selfMsg);
    this.message = '';
    this.scroller.scrollEdge(Edge.Bottom);
  }

前端发送消息时,只能填充临时数据:msgId为空、msgSeq置 0。消息入库之后,后端会生成真实的msgId、会话维度自增msgSeq以及服务端时间戳sendTime,并通过回显包把这一套权威数据推送回前端。

// 服务端强制生成、覆盖关键字段,防止前端篡改
msg.MsgId = fmt.Sprintf("msg_%d_%s", time.Now().UnixMilli(), uuid.NewString()[:8])
msg.SenderId = uid
msg.SendTime = time.Now().UnixMilli()
if msg.MsgType == 0 {
  msg.MsgType = 1
}
// 入库
s.saveMessageToDB(&msg)

该设计的业务价值:前端可以将本地预渲染的临时消息,替换成后端下发的真实msgId。后续消息撤回、已读回执、消息重发、离线消息同步等能力,都必须依赖后端生成的msgId;时间采用服务端时间,规避手机本地时间可随意修改带来的数据错乱;msgSeq用于消息断点续拉、多端消息同步以及弱网环境下的消息顺序校正。如果后端不做消息回显,前端本地预渲染出来的消息会一直没有真实msgId,撤回、标记已读等功能就无法实现。