fix: 修复P2P主题别名订阅的兼容性问题

1. 订阅重复检查新增Original与RcptTo匹配判断,避免别名订阅误判已订阅
2. 重构订阅get查询处理逻辑,提取为独立函数
3. 针对P2P别名订阅场景返回200响应而非304,帮助客户端完成别名到标准主题名的重命名,避免发布失败
4. 优化群摘要接口的权限校验和异常处理逻辑
This commit is contained in:
Austin
2026-09-15 18:53:49 +08:00
parent 55afd976c9
commit dbb0a06db0
3 changed files with 80 additions and 16 deletions
+25 -2
View File
@@ -5944,17 +5944,40 @@ func serveIMGroupSummary(wrt http.ResponseWriter, req *http.Request, topic strin
writeIMError(wrt, req, http.StatusMethodNotAllowed, 30002, imReasonMethodNotAllowed, "method not allowed", nil)
return
}
principal, selfSub, ok := requireIMGroupMember(wrt, req, topic, true)
principal, ok := authenticateIMHTTPBearer(wrt, req)
if !ok {
return
}
if types.GetTopicCat(topic) != types.TopicCatGrp {
writeIMError(wrt, req, http.StatusBadRequest, 30000, imReasonMalformedRequest, "malformed request", map[string]any{"field": "topic"})
return
}
// 已解散的群按文档约定对客户端表现为不存在(RESOURCE_NOT_FOUND),不暴露解散状态。
if settings, err := store.GroupBusiness.GetSettings(principal.TenantID, topic); err == nil &&
settings != nil && settings.GroupStatus == types.GroupStatusDissolved {
writeIMError(wrt, req, http.StatusNotFound, 30004, imReasonResourceNotFound, "group not found", nil)
return
}
selfSub, err := store.Subs.Get(principal.TenantID, topic, principal.Uid, false)
if err != nil {
writeIMError(wrt, req, http.StatusInternalServerError, 90000, imReasonInternal, "internal error", nil)
return
}
access := types.ModeNone
if selfSub != nil {
access = selfSub.ModeGiven & selfSub.ModeWant
}
if selfSub == nil || !access.IsJoiner() || !access.IsReader() {
writeIMError(wrt, req, http.StatusForbidden, 30005, imReasonPermissionDenied, "topic permission required", nil)
return
}
requests, _, _ := store.GroupBusiness.ListJoinRequests(principal.TenantID, topic, types.GroupJoinRequestStatusPending, "", 100)
announcement, _ := store.GroupBusiness.LatestAnnouncement(principal.TenantID, topic)
writeIMSuccess(wrt, req, map[string]any{
"topic": topic,
"pending_join_request_count": len(requests),
"announcement": groupAnnouncementPayload(announcement),
"permissions": groupPermissions(selfSub.ModeGiven & selfSub.ModeWant),
"permissions": groupPermissions(access),
})
}
+8 -1
View File
@@ -749,7 +749,14 @@ func (s *Session) subscribe(msg *ClientComMessage) {
s.inflightReqs.Add(1)
// Session can subscribe to topic on behalf of a single user at a time.
if sub := s.getSub(msg.RcptTo); sub != nil {
// Fast-path the duplicate attach only when the client used the canonical
// topic name itself. A p2p topic may also be addressed by an alias (the
// peer's user id, e.g. "usrXXX") while the session is already attached to
// the canonical topic ("p2pXXX"): forwarding it to the topic lets the
// client complete the alias->canonical rename with a 200 reply instead of
// a 304 that would leave the client-side alias topic object unattached
// (publishes on it then fail with "Cannot publish on inactive topic").
if sub := s.getSub(msg.RcptTo); sub != nil && msg.Original == msg.RcptTo {
s.queueOut(InfoAlreadySubscribed(msg.Id, msg.Original, msg.Timestamp))
s.inflightReqs.Done()
} else {
+47 -13
View File
@@ -353,7 +353,30 @@ func (t *Topic) registerSession(msg *ClientComMessage) {
// but there is a gap between topic creation/un-pausing and processing the
// first subscription request, before the topic is linked to session: a client
// may send several subscription requests in that gap.
msg.sess.queueOut(InfoAlreadySubscribed(msg.Id, msg.Original, msg.Timestamp))
//
// A p2p topic may be addressed by an alias (the peer's user id, e.g. "usrXXX")
// while the session is already attached to the same topic under its canonical
// name ("p2pXXX"). Replying 304 here leaves the client-side topic object created
// for the alias name unattached: the SDK treats any ctrl code >= 300 as failure
// and skips the alias->canonical rename, so publishes on it fail with
// "Cannot publish on inactive topic". Reply 200 with the canonical name and the
// effective access mode so the client can complete the rename instead.
asUid := types.ParseUserId(msg.AsUser)
if pud, ok := t.perUser[asUid]; ok && t.cat == types.TopicCatP2P && msg.Original != t.name {
msg.sess.queueOut(NoErrParams(msg.Id, t.name, types.TimeNow(), map[string]any{
"acs": &MsgAccessMode{
Want: pud.modeWant.String(),
Given: pud.modeGiven.String(),
Mode: (pud.modeGiven & pud.modeWant).String(),
},
}))
// The client-side topic object created for the alias name is fresh:
// serve the {get} query it sent along with the subscription, otherwise
// the reopened conversation would come up without desc/subs/history.
t.processSubGetQuery(msg, asUid, false, auth.Level(msg.AuthLvl))
} else {
msg.sess.queueOut(InfoAlreadySubscribed(msg.Id, msg.Original, msg.Timestamp))
}
} else {
// The topic is alive, so stop the kill timer, if it's ticking. We don't want the topic to die
// while processing the call.
@@ -879,60 +902,71 @@ func (t *Topic) handleSubscription(msg *ClientComMessage) error {
}
msgsub := msg.Sub
getWhat := 0
if msgsub.Get != nil {
getWhat = parseMsgClientMeta(msgsub.Get.What)
// Deliver the data requested by the {get} query attached to the subscription.
t.processSubGetQuery(msg, asUid, asChan, authLevel)
}
return nil
}
// processSubGetQuery responds to the {get} query attached to a subscription
// request (desc/sub/tags/cred/aux/data/del) by sending the answers as separate
// {meta}/{data} packets.
func (t *Topic) processSubGetQuery(msg *ClientComMessage, asUid types.Uid, asChan bool, authLevel auth.Level) {
msgsub := msg.Sub
if msgsub == nil || msgsub.Get == nil {
return
}
getWhat := parseMsgClientMeta(msgsub.Get.What)
if getWhat&constMsgMetaDesc != 0 {
// Send get.desc as a {meta} packet.
if err := t.replyGetDesc(msg.sess, asUid, asChan, msgsub.Get.Desc, msg); err != nil {
logs.Warn.Printf("topic[%s] handleSubscription Get.Desc failed: %v sid=%s", t.name, err, msg.sess.sid)
logs.Warn.Printf("topic[%s] Get.Desc failed: %v sid=%s", t.name, err, msg.sess.sid)
}
}
if getWhat&constMsgMetaSub != 0 {
// Send get.sub response as a separate {meta} packet
if err := t.replyGetSub(msg.sess, asUid, authLevel, asChan, msg); err != nil {
logs.Warn.Printf("topic[%s] handleSubscription Get.Sub failed: %v sid=%s", t.name, err, msg.sess.sid)
logs.Warn.Printf("topic[%s] Get.Sub failed: %v sid=%s", t.name, err, msg.sess.sid)
}
}
if getWhat&constMsgMetaTags != 0 {
// Send get.tags response as a separate {meta} packet
if err := t.replyGetTags(msg.sess, asUid, msg); err != nil {
logs.Warn.Printf("topic[%s] handleSubscription Get.Tags failed: %v sid=%s", t.name, err, msg.sess.sid)
logs.Warn.Printf("topic[%s] Get.Tags failed: %v sid=%s", t.name, err, msg.sess.sid)
}
}
if getWhat&constMsgMetaCred != 0 {
// Send get.tags response as a separate {meta} packet
// Send get.cred response as a separate {meta} packet
if err := t.replyGetCreds(msg.sess, asUid, msg); err != nil {
logs.Warn.Printf("topic[%s] handleSubscription Get.Cred failed: %v sid=%s", t.name, err, msg.sess.sid)
logs.Warn.Printf("topic[%s] Get.Cred failed: %v sid=%s", t.name, err, msg.sess.sid)
}
}
if getWhat&constMsgMetaAux != 0 {
// Send get.aux response as a separate {meta} packet
if err := t.replyGetAux(msg.sess, asUid, msg); err != nil {
logs.Warn.Printf("topic[%s] handleSubscription Get.Aux failed: %v sid=%s", t.name, err, msg.sess.sid)
logs.Warn.Printf("topic[%s] Get.Aux failed: %v sid=%s", t.name, err, msg.sess.sid)
}
}
if getWhat&constMsgMetaData != 0 {
// Send get.data response as {data} packets
if err := t.replyGetData(msg.sess, asUid, asChan, msgsub.Get.Data, msg); err != nil {
logs.Warn.Printf("topic[%s] handleSubscription Get.Data failed: %v sid=%s", t.name, err, msg.sess.sid)
logs.Warn.Printf("topic[%s] Get.Data failed: %v sid=%s", t.name, err, msg.sess.sid)
}
}
if getWhat&constMsgMetaDel != 0 {
// Send get.del response as a separate {meta} packet
if err := t.replyGetDel(msg.sess, asUid, msgsub.Get.Del, msg); err != nil {
logs.Warn.Printf("topic[%s] handleSubscription Get.Del failed: %v sid=%s", t.name, err, msg.sess.sid)
logs.Warn.Printf("topic[%s] Get.Del failed: %v sid=%s", t.name, err, msg.sess.sid)
}
}
return nil
}
// handleLeaveRequest processes a session leave request.