fix: 命令 8 接受 last_msg_id 作为已读水位
客户端已读请求常只有 conversation_id 和 last_msg_id。任一消息 id 大于 0 即写入 last_read_msg_id 并清 Redis 未读,不再因此返回 40001。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
a65d59149c
commit
c0ed0d83de
@ -185,7 +185,7 @@ offset 23 Body bytes UTF-8 JSON
|
||||
| 5 | SEND_ACK | ← 仅 4 的成功应答 |
|
||||
| 6 | MESSAGE | ← 推送 |
|
||||
| 7 | PUSH_ACK | → 空;`SequenceID` = 推送帧的 SequenceID |
|
||||
| 8 | READ_MESSAGE | → `{"conversation_id","msg_id"}`;成功 `data` 同名字段,`msg_id` 为落库后的 `last_read_msg_id`(只增不减)。清该成员 Redis 未读,并把会话从 unread ZSET 挪到 read ZSET。水位前进时给该用户其他在线设备推 Command 8(不系统推送、不写 mailbox)。缺 `conversation_id` 或 `msg_id` 为 `40001` |
|
||||
| 8 | READ_MESSAGE | → `conversation_id`,以及 `msg_id` 或 `last_msg_id`(任一大于 0;两者都有时用 `msg_id`)。成功 `data` 为 `conversation_id` 与 `msg_id`,`msg_id` 为落库后的 `last_read_msg_id`(只增不减)。清该成员 Redis 未读,并把会话从 unread ZSET 挪到 read ZSET。水位前进时给该用户其他在线设备推 Command 8(不系统推送、不写 mailbox)。缺 `conversation_id`,或两个消息 id 都小于等于 0,为 `40001` |
|
||||
| 9 | RECALL_MESSAGE | → `{"msg_id"}` |
|
||||
| 10 | SYNC_INBOX | → 会话列表增量 |
|
||||
| 12 | SYNC_MESSAGES | → 某会话消息增量 |
|
||||
|
||||
@ -653,7 +653,13 @@ READ 更新 last_read_msg_id,清 Redis 未读,入队通知其他设
|
||||
{"conversation_id": 1001, "msg_id": 2002}
|
||||
```
|
||||
|
||||
`msg_id` 是成员已读到的消息 id。成功应答仍是命令 8,Body 为 V3 信封,`data.msg_id` 为写入后的 `last_read_msg_id`(只增不减;更小的 id 不回退)。缺 `conversation_id` 或 `msg_id`(≤0)返回 `40001`。消息不存在为 `40401`;消息不属于该会话为 `40001`;不是成员为 `40301`。
|
||||
也接受客户端现用的 `last_msg_id`(可同时带 `peer_id`,服务端不使用该字段):
|
||||
|
||||
```json
|
||||
{"conversation_id": 1001, "peer_id": 9, "last_msg_id": 2002}
|
||||
```
|
||||
|
||||
`msg_id` 与 `last_msg_id` 任一大于 0 即是成员已读到的消息 id;两者都大于 0 时用 `msg_id`。成功应答仍是命令 8,Body 为 V3 信封,`data.msg_id` 为写入后的 `last_read_msg_id`(只增不减;更小的 id 不回退)。缺 `conversation_id`,或两个消息 id 都小于等于 0,返回 `40001`。消息不存在为 `40401`;消息不属于该会话为 `40001`;不是成员为 `40301`。
|
||||
|
||||
水位前进时入队:`ToUserIDs` 仅为当前用户,`ExcludeDevice` 为当前设备,`NotPush` 与 `NotMailbox` 均为真。其他在线设备收到命令 8。
|
||||
|
||||
|
||||
@ -18,6 +18,15 @@ type msgIDBody struct {
|
||||
type readBody struct {
|
||||
ConversationID int64 `json:"conversation_id"`
|
||||
MsgID int64 `json:"msg_id"`
|
||||
LastMsgID int64 `json:"last_msg_id"`
|
||||
}
|
||||
|
||||
// readCursor accepts msg_id or last_msg_id. Either value above zero is the read cursor.
|
||||
func (b readBody) readCursor() int64 {
|
||||
if b.MsgID > 0 {
|
||||
return b.MsgID
|
||||
}
|
||||
return b.LastMsgID
|
||||
}
|
||||
|
||||
type syncInboxBody struct {
|
||||
@ -55,11 +64,11 @@ func (s *Server) handleReadMessage(ctx context.Context, conn *session.Conn, fram
|
||||
return nil, errs.ErrAvailable
|
||||
}
|
||||
var in readBody
|
||||
if err := json.Unmarshal(frame.Body, &in); err != nil || in.ConversationID <= 0 || in.MsgID <= 0 {
|
||||
if err := json.Unmarshal(frame.Body, &in); err != nil || in.ConversationID <= 0 || in.readCursor() <= 0 {
|
||||
return nil, errs.ErrInvalidArgument
|
||||
}
|
||||
out, err := s.messages.MarkRead(ctx, conn.UserID(), conn.DeviceID(), &msglogic.ReadInput{
|
||||
ConversationID: in.ConversationID, MsgID: in.MsgID,
|
||||
ConversationID: in.ConversationID, MsgID: in.readCursor(),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@ -37,6 +37,13 @@ func TestReadMessageRegistered(t *testing.T) {
|
||||
if missing != nil || !errors.Is(err, errs.ErrInvalidArgument) {
|
||||
t.Fatalf("missing fields frame=%v err=%v", missing, err)
|
||||
}
|
||||
onlyLast, err := srv.router.Dispatch(t.Context(), conn, protocol.Frame{
|
||||
Version: protocol.CurrentVersion, Command: protocol.CmdReadMessage,
|
||||
Body: []byte(`{"conversation_id":` + strconv.FormatInt(convID, 10) + `,"peer_id":1,"last_msg_id":0}`),
|
||||
})
|
||||
if onlyLast != nil || !errors.Is(err, errs.ErrInvalidArgument) {
|
||||
t.Fatalf("zero cursor frame=%v err=%v", onlyLast, err)
|
||||
}
|
||||
|
||||
resp, err := srv.router.Dispatch(t.Context(), conn, protocol.Frame{
|
||||
Version: protocol.CurrentVersion, Command: protocol.CmdReadMessage, SequenceID: 4,
|
||||
@ -53,6 +60,22 @@ func TestReadMessageRegistered(t *testing.T) {
|
||||
if err := json.Unmarshal(env.Data, &data); err != nil || data.MsgID != msgID || data.ConversationID != convID {
|
||||
t.Fatalf("data %+v %v", data, err)
|
||||
}
|
||||
|
||||
alias, err := srv.router.Dispatch(t.Context(), conn, protocol.Frame{
|
||||
Version: protocol.CurrentVersion, Command: protocol.CmdReadMessage, SequenceID: 5,
|
||||
Body: []byte(`{"conversation_id":` + strconv.FormatInt(convID, 10) + `,"peer_id":1,"last_msg_id":` + strconv.FormatInt(msgID, 10) + `}`),
|
||||
})
|
||||
if err != nil || alias == nil {
|
||||
t.Fatalf("last_msg_id %+v %v", alias, err)
|
||||
}
|
||||
aliasEnv, err := protocol.DecodeEnvelope(alias.Body)
|
||||
if err != nil || aliasEnv.Code != protocol.CodeOK {
|
||||
t.Fatalf("last_msg_id envelope %+v %v", aliasEnv, err)
|
||||
}
|
||||
var aliasData msglogic.ReadResult
|
||||
if err := json.Unmarshal(aliasEnv.Data, &aliasData); err != nil || aliasData.MsgID != msgID || aliasData.ConversationID != convID {
|
||||
t.Fatalf("last_msg_id data %+v %v", aliasData, err)
|
||||
}
|
||||
}
|
||||
|
||||
func readFixture(t *testing.T) (*msglogic.Service, int64, int64, int64) {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user