feat: 新增群组成员数量统计功能并在接口中返回

实现了群组成员数量统计的接口,包含数据库层统计逻辑,在群组详情、设置等接口中返回实时成员数,同时修复了测试用例中参数空格问题。
This commit is contained in:
Austin
2026-10-13 17:58:05 +08:00
parent 43808c36d1
commit e8804883fc
7 changed files with 120 additions and 14 deletions
+42 -11
View File
@@ -4217,6 +4217,25 @@ func serveIMGroupList(wrt http.ResponseWriter, req *http.Request) {
items = append(items, item)
seen++
}
if len(items) > 0 {
listTopics := make([]string, 0, len(items))
for _, item := range items {
if topic, ok := item["topic"].(string); ok && topic != "" {
listTopics = append(listTopics, topic)
}
}
if counts, err := store.GroupBusiness.MembersCount(principal.TenantID, listTopics); err == nil {
for _, item := range items {
if topic, ok := item["topic"].(string); ok {
if count, ok := counts[topic]; ok {
item["member_count"] = count
}
}
}
} else {
logs.Warn.Printf("serveIMGroupList: failed to count members for %d topics: %v", len(listTopics), err)
}
}
writeIMSuccess(wrt, req, map[string]any{"items": items, "next_cursor": nextCursor, "has_more": nextCursor != ""})
}
@@ -4310,7 +4329,7 @@ func serveIMGroupCreate(wrt http.ResponseWriter, req *http.Request) {
writeIMRelationError(wrt, req, err)
return
}
writeIMSuccess(wrt, req, groupDetailPayload(created, selfSub))
writeIMSuccess(wrt, req, groupDetailPayload(created, selfSub, groupMemberCountFor(principal.TenantID, topicName)))
}
func serveIMGroupResource(wrt http.ResponseWriter, req *http.Request) {
@@ -4607,7 +4626,7 @@ func serveIMGroupDetail(wrt http.ResponseWriter, req *http.Request, topic string
writeIMError(wrt, req, http.StatusNotFound, 30004, imReasonMalformedRequest, "group not found", nil)
return
}
payload := groupDetailPayload(group, selfSub)
payload := groupDetailPayload(group, selfSub, groupMemberCountFor(principal.TenantID, topic))
if settings, err := store.GroupBusiness.GetSettings(principal.TenantID, topic); err == nil {
if settings != nil && settings.GroupStatus == types.GroupStatusDissolved {
writeIMError(wrt, req, http.StatusGone, 30004, imReasonGroupDissolved, "group dissolved", map[string]any{"topic": topic})
@@ -4703,7 +4722,7 @@ func serveIMGroupDetail(wrt http.ResponseWriter, req *http.Request, topic string
writeIMError(wrt, req, http.StatusInternalServerError, 90000, imReasonInternal, "internal error", nil)
return
}
payload := groupDetailPayload(updated, selfSub)
payload := groupDetailPayload(updated, selfSub, groupMemberCountFor(principal.TenantID, topic))
if settingsUpdated {
if settings, err := store.GroupBusiness.GetSettings(principal.TenantID, topic); err == nil {
payload = mergeGroupSettingsPayload(payload, settings)
@@ -4769,7 +4788,7 @@ func serveIMGroupSettings(wrt http.ResponseWriter, req *http.Request, topic stri
writeIMError(wrt, req, http.StatusInternalServerError, 90000, imReasonInternal, "internal error", nil)
return
}
payload := groupSettingsPayload(group, selfSub)
payload := groupSettingsPayload(group, selfSub, groupMemberCountFor(principal.TenantID, topic))
if settings, err := store.GroupBusiness.GetSettings(principal.TenantID, topic); err == nil {
payload = mergeGroupSettingsPayload(payload, settings)
payload["governance"] = groupGovernancePayloadFromSettings(settings, selfSub)
@@ -4946,7 +4965,7 @@ func serveIMGroupQRCodes(wrt http.ResponseWriter, req *http.Request, topic strin
writeIMError(wrt, req, http.StatusInternalServerError, 90000, imReasonInternal, "internal error", nil)
return
}
groupPayload := groupDetailPayload(group, selfSub)
groupPayload := groupDetailPayload(group, selfSub, groupMemberCountFor(principal.TenantID, topic))
writeIMSuccess(wrt, req, map[string]any{
"qr_token": token,
"qr_payload": "im://qr?token=" + url.QueryEscape(token) + "&purpose=join_group",
@@ -6088,7 +6107,7 @@ func serveIMQRCodesResolve(wrt http.ResponseWriter, req *http.Request) {
if selfSub != nil && (selfSub.ModeGiven & selfSub.ModeWant).IsJoiner() {
status = "joined"
}
groupPayload := groupDetailPayload(group, selfSub)
groupPayload := groupDetailPayload(group, selfSub, groupMemberCountFor(principal.TenantID, claims.Topic))
writeIMSuccess(wrt, req, map[string]any{
"purpose": claims.Purpose,
"expires_at": time.Unix(claims.Expires, 0).UTC().Format(time.RFC3339Nano),
@@ -7505,15 +7524,27 @@ func mergeGroupListSettingsPayload(payload map[string]any, settings *types.Group
return payload
}
func groupDetailPayload(group *types.Topic, selfSub *types.Subscription) map[string]any {
// groupMemberCountFor returns the live member count for a single group topic,
// mirroring the visibility rules of the group members list endpoint.
func groupMemberCountFor(tenantID types.TenantID, topic string) int64 {
if topic == "" {
return 0
}
counts, err := store.GroupBusiness.MembersCount(tenantID, []string{topic})
if err != nil {
logs.Warn.Printf("group[%s]: failed to count members: %v", topic, err)
return 0
}
return counts[topic]
}
func groupDetailPayload(group *types.Topic, selfSub *types.Subscription, memberCount int64) map[string]any {
public := map[string]any{}
topic := ""
memberCount := 0
ownerID := ""
if group != nil {
public = groupPublicMap(group.Public)
topic = group.Id
memberCount = group.SubCnt
if owner := types.ParseUid(group.Owner); !owner.IsZero() {
ownerID = owner.UserId()
}
@@ -7542,8 +7573,8 @@ func groupDetailPayload(group *types.Topic, selfSub *types.Subscription) map[str
}
}
func groupSettingsPayload(group *types.Topic, selfSub *types.Subscription) map[string]any {
data := groupDetailPayload(group, selfSub)
func groupSettingsPayload(group *types.Topic, selfSub *types.Subscription, memberCount int64) map[string]any {
data := groupDetailPayload(group, selfSub, memberCount)
private := map[string]any{}
if selfSub != nil {
private = groupPrivateMap(selfSub.Private)