Files
full/docs/group.md
2026-09-22 21:15:34 +08:00

34 KiB
Raw Blame History

group群组与成员管理代码审计报告

内容
审计对象 module/social/groupmodule 路径 bsm/full/module/social/group
服务域 社交与关系
审计日期 2026-09-22
代码规模 手写 Go 28 个文件 / 1011 行(含手写别名文件 pb/blocks_compat.go 6 行);生成代码 pb/ 7 个文件 / 6157 行;proto/ 3 个文件 / 465 行;etc/ 3 个 yaml + 1 个 supervisor conf*_test.go 0 个;无 service/、无 test/ 目录
入口 仅独立入口 cmd/main/main.gogRPC + grpc-gateway 单进程,Port: 12247未被 pkgs/allpkgs/ecmall 接入;cmd/cli/main.go 全文 4 行、main() 为空
对外协议 gRPC 2 个 service / 13 个 RPCgateway 路径为 POST /group.{Service}/{Method},但模块内从未注册任何 gateway handler
结论摘要 13 个接口全部只验 JWT、全部缺群归属/角色校验3 个核心接口用了不存在的列 creator_id 导致必然失败;群信息/群列表/成员列表/入群申请四类查询都是"查了不返回"或"SQL 报错";免验证入群开关恒为 falseBasic.Disband 是返回成功的 TODO 占位;成员计数、成员上限、群号、分页全部未落地;网关未接线(Mux 恒 nil、未接入聚合入口InitDataGroupRes 为死代码。

1. 服务定位与职责

维护群组基本信息(group_basic)、群成员(relation_group_member)、入群申请(relation_group_apply)与群资源模型(relation_group_res,无逻辑),对外提供建群、改群、解散、群列表/搜索、群成员列表以及入群申请与审批PASS/REJECT、踢人、退群、设置管理员。

它是群组与成员关系的读写服务。群内消息、群文件、群动态(proto/const.proto:277-304 里定义了 GroupPostItem 等消息但无 service 使用)、好友/关注关系、用户资料(依赖外部表 passport_extend)均不在本模块内实现。

2. 代码结构与入口

路径 职责
cmd/main/main.go 独立进程入口:config.New("group")impl.NewImpl()server.New(config.Spec.Addr)service.Newsrv.Use(models.InitData)srv.Start()
cmd/cli/main.go 调试入口,全文 4 行且 main() 为空
internal/config/config.go SrvConfigBase、Databases、MicroService、Rpc、Gateway、APM、Etcd、WeChatConf、Token、Kycconf.NotNil(Service, Cache) 校验
internal/impl/impl.go 初始化 Memory、Redis、DB、Etcd 四个全局实例
internal/server/new.go 创建 grpc.Server,注册 BasicMember 两个 serviceprotoc-gen-slc 生成);未创建 ServeMux
internal/server/{basic_server,member_server}.go 13 个 RPC 的 1:1 转发(生成代码)
internal/logic/basic/{search,fetch,get,create,modify,disband,const}.go 群组搜索/列表/详情/创建/修改/解散
internal/logic/member/{fetch,do_join,join_fetch,join_do_handle,do_kick,do_quit,do_set_manager,const}.go 成员列表、入群申请、审批、踢人、退群、管理员设置
internal/models/{group_basic,group_member,group_apply,group_res,query}.go 4 张表模型 + query.go 中全部数据访问(含 4 段裸 SQL
pb/*.go 生成代码;pb/blocks_compat.go 为手写类型别名(PostItem = GroupPostItem 等)
proto/{group,member,const}.proto 契约;公共消息放在 proto/const.protopackage blocks,与 service 的 package group 不同)
etc/{group_dev,group_prod,group_test}.yaml + supervisor.bsm-social-group.conf 配置与进程托管;三份 yaml 内容完全相同

依赖注入:本模块没有 service/ 目录,也没有 Expose/Dependencies 注入点,impl.NewImpl() 是唯一初始化路径(internal/impl/impl.go:19-25)。全仓库对 module/social/group 的引用只有 go.work:20scripts/api-docgen

3. 接口清单

HTTP 路径由生成代码固定为 /group.{服务}/{方法}pb/group.pb.gw.go:472-477pb/member.pb.gw.go:536-542),全部为 POST这些 handler 在模块内从未被注册到任何 ServeMux(见 6.4)。

方法 路径 功能 鉴权 实现位置
POST /group.Basic/Search 搜索群组 JWT丢弃 claims 空实现 logic/basic/search.go:12
POST /group.Basic/Fetch 我的群组列表 JWT logic/basic/fetch.go:14(只返回总数,见 6.2
POST /group.Basic/Get 群组信息 JWT丢弃 claims logic/basic/get.go:14(返回值被丢弃,见 6.2
POST /group.Basic/Create 创建群组 JWT logic/basic/create.go:17
POST /group.Basic/Modify 修改群组信息 JWT按创建者列名错误 logic/basic/modify.go:18
POST /group.Basic/Disband 解散群组 JWT丢弃 claims TODO 占位但返回成功 logic/basic/disband.go:14
POST /group.Member/Fetch 群成员列表 JWT丢弃 claims logic/member/fetch.go:14(只返回总数,见 6.2
POST /group.Member/DoJoin 申请加群 JWT logic/member/do_join.go:17
POST /group.Member/JoinFetch 申请加群列表 JWT logic/member/join_fetch.go:15SQL 不可用,见 6.2
POST /group.Member/JoinDoHandle 申请加群处理 JWT无角色校验、丢弃 claims logic/member/join_do_handle.go:18
POST /group.Member/DoSetManager 设置/取消管理员 JWT按创建者列名错误 logic/member/do_set_manager.go:18
POST /group.Member/DoKick 踢人 JWT权限条件错误 logic/member/do_kick.go:17
POST /group.Member/DoQuit 退群 JWT按创建者列名错误 logic/member/do_quit.go:17

鉴权方式13 个 logic 入口全部调用 service.ParseMetaCtx(ctx, nil)bsm-sdk/core/service/meta.go:19-49),要求有效 JWTopts 均为 nil,即不校验角色、不校验私有 IPSearchGetDisbandMember.FetchJoinDoHandle_, err = ... 丢弃 claimssearch.go:13get.go:16disband.go:16member/fetch.go:15join_do_handle.go:19),后续无法做任何归属判断。

声明但未实现 / 占位接口

类别 接口 证据
TODO 占位但返回 vars.OK Basic.Disband internal/logic/basic/disband.go:26-31// TODO: add your logic code & delete this line.
只有参数校验、无任何查询 Basic.Search internal/logic/basic/search.go:17-21(校验 keyword 后直接 return &pb.GroupsReply{}, nil
有实现但结果被丢弃 Basic.Get internal/logic/basic/get.go:20-25(第 25 行返回新的空 &pb.GroupItem{}
有实现但结果未装配 Basic.Fetch internal/logic/basic/fetch.go:20-29Groups 字段从未赋值)
有实现但结果未装配 Member.Fetch internal/logic/member/fetch.go:24-33Members 字段从未赋值)
模型存在、全模块零引用 GroupRes(群文件/资源) internal/models/group_res.go:8-21(全仓库无 GroupRes 读写调用)
空初始化被注册 models.InitData internal/models/query.go:9-12 + cmd/main/main.go:38

4. 数据模型与表

模型未注册任何自动迁移:database.AppendMigrate / AutoMigratemodule/social/**无匹配grep AppendMigrate module/social 返回空4 张表依赖外部建表脚本,仓库内没有对应 SQL。公共字段来自 SDKbsm-sdk/core/types/db.gotypes.Std_IICUDS = id(PK) / identity varchar(36) uniqueIndex / created_at / updated_at / deleted_at(index) / status int8 default 0 indextypes.Std_Passport = passport_id uint index / passport_identity varchar(36) index

group_basicinternal/models/group_basic.go:8-30

字段 类型 键/约束 说明
id uint PK 自增主键
identity varchar(36) uniqueIndex 群组唯一码(utils.UUID() 生成UUIDv7 字符串)
created_at/updated_at/deleted_at TIMESTAMP index(deleted_at) 软删除
status int8 index默认 0 状态,全模块查询均未过滤
passport_id / passport_identity uint / varchar(36) index 创建者;注意:modify.go:35do_quit.go:24do_set_manager.go:25 用的是不存在的 creator_id
number int64 not null 群号,create.go 从未生成(恒 0
avatar / name / introduce / notice / background varchar(255)/text 默认 ''name not null 群资料
member_limit smallint 默认 100 最大成员数0 表示不限;仅创建时写死 500从未参与校验
province/city/area int32 默认 0 地区,无逻辑写入
enable_any_join bool - 免验证入群;创建时置 true判定逻辑恒为 false见 6.2
enable_search_by_number / enable_search_by_name bool - 搜索开关,创建时未写入(恒 false
member_total int32 默认 0 成员数;创建时写 1之后靠 UpsetGroupMemberTotal 加减

relation_group_memberinternal/models/group_member.go:8-20

字段 类型 键/约束 说明
id / identity uint / varchar(36) PK / uniqueIndex 成员记录自身标识;member/do_join.go:33member/join_do_handle.go:39utils.UUID() 写入,但 basic/create.go:42-51 建群时的创建者成员记录没有赋值(见 6.2
created_at/updated_at/deleted_at/status - index(deleted_at) 软删除;status 未使用
passport_id / passport_identity uint / varchar(36) index 成员
group_id uint not null 群组主键(标签写作裸名 group_id,依赖 GORM 默认蛇形命名,结果与显式声明一致)
group_identity varchar(36) not null 群组唯一码(权限查询里被误用为 identity,见 6.2
remark_name varchar(255) 默认 '' 群内昵称,无任何写入点
role smallint 默认 0 0 普通 / 1 管理员 / 2 超管 / 10 创建者(logic/member/const.go:3-11

表上没有 (group_identity, passport_id) 唯一约束 → 重复入群无法由数据库兜底。

relation_group_applyinternal/models/group_apply.go:8-21

字段 类型 键/约束 说明
id / identity uint / varchar(36) PK / uniqueIndex 申请记录标识(do_join.go:47
created_at/updated_at/deleted_at/status - index(deleted_at) status1 通过 / -1 拒绝(join_do_handle.go:49,55查询未按状态过滤
from_id / from_identity uint / varchar(36) not null 申请人
group_id / group_identity uint / varchar(36) not null 目标群(group_id 来自请求 in.Id,未校验与 identity 是否对应)
message varchar(255) 默认 '' 加群留言

relation_group_resinternal/models/group_res.go:8-21

字段 类型 键/约束 说明
id / identity uint / varchar(36) PK / uniqueIndex 主键与唯一码
group_id varchar(50) not null 注意这里是字符串,与 relation_group_member.group_iduint类型不一致
name / introduce / url / download_num - - 群文件信息

该表无任何读写逻辑,属未启用模型。

5. 核心流程

flowchart TD
    A["POST /group.Member/DoJoin<br/>ParseMetaCtx 取 auth"] --> B["GetGroupEnableAnyJoin<br/>列名 createor_id 不存在 且错误被丢弃<br/>恒返回 false"]
    B -->|"恒走此分支"| C["写 relation_group_apply<br/>无重复校验 无 group 存在性校验"]
    D["POST /group.Member/JoinDoHandle<br/>ParseMetaCtx 丢弃 claims"] --> E{"direction"}
    E -->|"PASS"| F["按 identity 查申请表"]
    F --> G["Create relation_group_member<br/>无幂等 无 MemberLimit 判断"]
    G --> H["UpdateColumn status=1<br/>不检查影响行数"]
    H --> I["UpsetGroupMemberTotal +1<br/>不检查影响行数"]
    E -->|"REJECT"| J["UpdateColumn status=-1<br/>错误未赋给 err 失败也返回成功"]
    K["POST /group.Member/DoKick"] --> L["权限查询 identity=群组identity<br/>且 passport_id 传字符串<br/>必然报错"]
    M["POST /group.Member/DoQuit"] --> N["Select creator_id<br/>列不存在 必然报错"]
    O["POST /group.Basic/Fetch"] --> P["GetMyGroup 裸 SQL<br/>表名 group / group_member 不存在"]
    Q["POST /group.Basic/Get"] --> R["First into reply 后<br/>返回新的空 GroupItem"]

6. 审计发现

6.1 安全

级别 位置 问题
internal/logic/member/join_do_handle.go:19,24-52 入群审批无任何角色校验。入口 _, err = service.ParseMetaCtx(ctx, nil) 丢弃调用者身份PASS/REJECT 分支不判断"调用者是否该群管理员/创建者"(对比 do_set_manager.go:30 的创建者校验、do_kick.go:22-30 的意图)→ 任意登录用户提交 apply_identity 就能把任意申请人拉进任意群PASS、或拒绝任意申请REJECT并可借重复 PASS 膨胀 member_total
internal/logic/member/do_kick.go:24 踢人权限判定双重错误Where("identity=? and role !=0 and passport_id=?", in.GroupIdentity, auth.Identity)。① identity 是成员记录自身 ULIDmember/do_join.go:33member/join_do_handle.go:39 写入;basic/create.go:42-51 漏写),群标识列是 group_identitygroup_member.go:11-12),条件语义错误;② passport_id 是整型列(bsm-sdk/core/types/db.go:52),却传入 36 位 UUID 字符串(bsm-sdk/core/utils/identity.go:8-10)→ Postgres 类型错误。当前表现为接口恒失败(见 6.2);一旦按列名"修好",该条件退化为"任意管理员可踢任意人",且代码中没有禁止踢创建者/超管的保护。
internal/logic/member/do_set_manager.go:37,40 设置/取消管理员只用 Where("identity=?", in.Identity) 定位成员,未限定 group_identity,也不校验目标是否属于该群 → 修正列名后即成为跨群改角色入口;同时没有拦截"把创建者 ROLE_CREATEOR=10 改写为 1"。
internal/logic/member/fetch.go:15-24 + internal/models/query.go:30-37 群成员列表只校验 JWT不校验调用者是否为该群成员,按 group_identity 返回全量成员资料identity/昵称/头像/性别/省市/签名)→ 任意登录用户可枚举任意群成员。
internal/logic/member/do_join.go:26-50 入群申请无频率/数量限制,且不校验 in.Id/in.Identity 是否对应真实存在的群 → 可批量灌入 relation_group_apply 垃圾申请(当前因 JoinFetch 不可用而无法被管理员看到)。

6.2 正确性与逻辑缺陷

级别 位置 问题
internal/logic/basic/disband.go:26-31 解散群组是 TODO 占位却返回成功// TODO: add your logic code & delete this line. 之后直接 return &pb.DataStatusReply{Data: vars.OK, ...} → 调用方收到成功,群与成员数据原样保留。
internal/logic/basic/get.go:20-25 群信息恒为空First(&reply) 已把记录读入命名返回值,第 25 行又 return &pb.GroupItem{}, nil 丢弃;且"记录不存在"gorm.ErrRecordNotFound)被当成 ErrDB
internal/logic/basic/fetch.go:20-29 我的群列表恒为空GetMyGroup 的结果 groups 只用于 Total: int32(len(groups))Groups 字段从未赋值 → 列表空,总数也恒为 0。
internal/logic/basic/modify.go:33-35 改群信息会重写群 identityrecord.Identity = utils.UUID() 之后 UpdateColumns(record)UpdateColumns 对结构体不做零值过滤、只跳过主键,因此 identity 会被换成新的 UUID未传的 name/introduce/avatar/background/notice/enable_search_* 一律写空/falserelation_group_member.group_identityrelation_group_apply.group_identity 立刻与新 identity 脱钩(群成员、申请全部"消失")。
internal/logic/basic/modify.go:35internal/logic/member/do_quit.go:24internal/logic/member/do_set_manager.go:25 使用不存在的列 creator_idGroupBasic 的创建者列是 passport_idgroup_basic.go:10 内嵌 Std_Passportbsm-sdk/core/types/db.go:52-55),模型内无 creator_idWHERE creator_id=? / SELECT creator_id 在 Postgres 直接报错 → 修改群信息、退群、设置管理员三个接口恒返回 ErrDB
internal/models/query.go:39-43 + internal/logic/member/do_join.go:26 "免验证入群"判定恒为 falseGetGroupEnableAnyJoin 写作 Where("createor_id=? and identity=?", passport_id, group_identity) —— createor_id 列不存在,条件语义也从"群是否开放"变成"申请人是否为群主",且第 41 行没有检查 .Scan(&is) 的错误、直接返回零值 false → DoJoin 永远走"写申请表"分支,GroupBasic.EnableAnyJoin(创建时置 true完全失效。
internal/logic/member/join_do_handle.go:24-56 审批无幂等、错误被吞、方向未识别即成功:同一 apply_identity 可反复 PASS每次都 Create 新的 relation_group_member 并把 member_total 再 +1:43-52),申请表没有任何"已处理"判断REJECT 分支第 55 行 UpdateColumn 的返回值未赋给 err,第 58 行的 if err != nil 检查的是旧值 → 失败也返回成功direction 既非 PASS 也非 REJECT 时直接落到函数末尾返回成功(:56)。
internal/logic/member/do_join.go:26-50 入群无任何幂等与上限校验:不查成员表是否已存在、不查申请表中是否已有待处理申请,同一用户可生成多条成员/申请记录;MemberLimit/MemberTotal 从未参与判断(MemberLimit 只在创建时写死 500
internal/models/query.go:16-20:30-37 裸 SQL 表名与模型不一致:模型声明 group_basicgroup_basic.go:29)与 relation_group_membergroup_member.go:19SQL 却写 From group_member as gm Left join group on ...From group_member as gm Left join passport_extend ... → 表不存在;且 group 是 Postgres 保留字,即使存在也必须引号化。两个列表接口(Basic.FetchMember.Fetch)必然 SQL 报错。
internal/logic/member/join_fetch.go:24 Where("group_id in ", group_ids) 缺少占位符(正确写法 group_id in ?)。该表达式不含 ?GORM 只能把整个 []uint 当作单个参数追加(gorm.io/gorm@v1.31.2/clause/expression.go:53-57)→ 无法构成 IN 列表,条件无效或直接报错。
internal/logic/member/join_fetch.go:21-38 申请列表的 dest 类型与结果集不匹配Find(&fetch) 的目标是 []*pb.ApplyJoinGroupItem(字段 identity/group_id/from/message/created_at/statusproto/member.proto:74-81),而 relation_group_apply 只有 from_id/from_identityGORM 会拼出 SELECT ... "from" ... → 列不存在报错。即便查询成功,第 31 行还用 item.From.Identity(恒为空串,因为 from 列从未映射)去查 passport_extendErrRecordNotFound 被当成 ErrDB:32-35)→ 只要有一条申请,整个列表就失败
internal/logic/member/join_fetch.go:29-36 card 在 for 循环外只分配一次,fetch[idx].From = card 让所有元素共用同一指针 → 即使查询成功,全部申请都会显示"最后一个被查到的人"的资料卡。
internal/models/query.go:45-47 + do_quit.go:37-41do_kick.go:36-39 群成员计数没有一致性保障UpsetGroupMemberTotal 不返回错误、不检查影响行数;删除成员(软删除)时 RowsAffected=0 也照样 -1(非成员调用 DoQuit 会让计数长期偏小甚至为负),而 PASS 分支重复审批会重复 +1
internal/logic/member/fetch.go:31-33 群成员列表只填 Total: int32(len(members))Members 字段从未赋值 → 成员列表恒为空。
internal/logic/basic/create.go:42-51 创建者成员记录漏写 identitymember 只赋了 GroupID/GroupIdentity/RolePassportID/PassportIdentity,没有 member.Identity = utils.UUID()(对比 member/do_join.go:33member/join_do_handle.go:39)。而 GroupMember.Identity 来自 types.Std_IICUDS、标签是 uniqueIndexbsm-sdk/core/types/db.go:28-35)→ 若表结构按模型建立,第一条成员记录以 identity='' 落库后,任何人再建群都会因唯一键冲突失败Create 返回 ErrDB);若线上表没有该索引,则这些成员记录的 identity 全是空串,DoKick 里按 identity 定位成员的逻辑永远无法正确命中。
internal/logic/basic/create.go:36-55 建群无事务:群记录写入成功而成员记录失败时,群已存在且 MemberTotal=1 却没有任何成员;Number(群号)与 EnableSearchByNumber/Name 从未从请求写入(恒 0/falsein.MemberLimit 被忽略(第 28 行写死 DefaultMemberLimit)。
全模块 没有任何分页与上限Basic.Fetch 拉全部群、Member.Fetch 拉全部成员、JoinFetch 拉全部申请,proto/const.proto:10-14FetchRequest.page_no/page_size 从未被任何 logic 使用 → 大群/多申请一次性全量返回。
internal/logic/basic/fetch.go:28 Total: int32(len(groups)) 与真实总数脱钩(len 恒 0且未做 intint32 溢出保护。
internal/logic/basic/create.go:59 时间戳用 UnixNano,其余接口统一 UnixMilli(如 modify.go:43)→ 同一字段单位不一致。
internal/logic/member/do_join.go:23,58 返回的 Data 是第 23 行新生成的 UUID既没落库也不代表任何记录调用方拿到的"标识"无效。

6.3 未完成实现

类别 内容 证据
TODO 占位(且返回成功) Basic.Disband internal/logic/basic/disband.go:26-31
空实现(无 TODO 标注) Basic.Search internal/logic/basic/search.go:17-21
空实现 models.InitData(只有 return nil),却被 srv.Use 注册 internal/models/query.go:9-12 + cmd/main/main.go:38
模型存在、零逻辑 GroupRes 群文件表 internal/models/group_res.go:8-21
gateway 未接线 Server.Mux 声明后从未赋值(internal/server/new.go:20-25 只建 gRPCpb.RegisterBasicHandlerServer / pb.RegisterMemberHandlerServer 在仓库内无调用点 internal/server/new.go:20-33cmd/main/main.go:33grep RegisterBasicHandlerServer module/social/group 仅命中生成文件
未接入聚合入口 pkgs/all/internal/service/service.go:18-3215 项)与 pkgs/ecmall/internal/service/service.go:18-3214 项)均无 social/*;本模块无 service/ 目录,没有 Expose/Dependencies pkgs/all/internal/service/service.go:18-33
无建表手段 database.AppendMigrate/AutoMigratemodule/social/** 无匹配 → 4 张表依赖外部 SQL仓库内无脚本 grep AppendMigrate module/social 无结果
CLI 空实现 cmd/cli/main.go 全文 4 行,main() 为空 cmd/cli/main.go:1-4
README 空壳 全文只有标题 README.md:1-2

6.4 健壮性与可维护性

级别 位置 问题
internal/server/new.go:20-25 + cmd/main/main.go:33 Server.Muxnew.go:16)声明后从未赋值,GatewayMux: s.Mux 传给 SDK 的是类型非空、值为 nil*runtime.ServeMuxservice.Start 用它直接 http.ListenAndServebsm-sdk/core/service/service.go:106-111,120-129),请求到达时 runtime.ServeMux.ServeHTTP 会读取自身字段(grpc-gateway/v2@v2.30.0/runtime/mux.go:407-437)→ nil 接收者 panicnet/http 按连接 recover客户端表现为连接中断Gateway.Enable: true 得到的 HTTP 侧完全不可用。
internal/config/config.go:56 conf.NotNil(Spec.Service, Spec.Cache) 未校验 Databases,而 with.Databasescfg == nilpanic("No Database Source Found !")bsm-sdk/core/with/databases.go:12-15)→ 配置漏配即启动 panic。当前 etc/group_dev.yaml:4-7Databases 段,可正常初始化。
etc/group_dev.yamletc/group_prod.yamletc/group_test.yaml 三份配置逐行相同Service: groupPort: 12247、同一个 127.0.0.1 + dbname=bsm_dev 连接串、相同 Gateway.Port: 12425)→ dev/prod/test 无法区分;数据库口令以明文形式入库(password=CHANGE_ME)。
etc/group_dev.yaml:13-16 + internal/impl/impl.go:24 MicroService.Anonymous 写的是 passport.ping.hello,而本服务只有 Basic/Member 两个 service、不存在 ping 方法(internal/server/new.go:28-29)→ 匿名清单无效;配置中没有 Etcdwith.Etcd(nil) 直接返回 nilbsm-sdk/core/with/etcd.go:14-17),一旦把 MicroService.Enable 改为 trueservice.Start 会因 EtcdClient == nil 执行 os.Exit(1)bsm-sdk/core/service/service.go:66-70)。
internal/impl/impl.go:21-22 MemorySericeRedisService 初始化后全模块零使用(死代码);EtcdService 只出现在被跳过的注册路径。
internal/logic/basic/const.go:13-15internal/logic/member/const.go:3-15 两个包重复定义同一组 ROLE_*DefaultMemberLimitDefaultEnableAnyJoinbasic 包的 filed 变量没有任何使用点(member 包的同名变量用于 join_fetch.go:31)。
internal/logic/basic/get.go:20 **pb.GroupItem&reply)交给 GORM 当 dest依赖 SDK 对双重指针的隐式分配(gorm.io/gorm@v1.31.2/callbacks.go:121-133),语义正确但极不直观,也正是第 25 行改写返回值后不易察觉的原因。
全模块 0 个 *_test.gotest/ 目录不存在无任何请求样例README 只有标题。

7. 风险汇总

编号 级别 问题 影响面
G1 入群审批无角色校验,任意登录用户可拉人进群/拒绝申请 越权、群成员被污染
G2 Disband 为 TODO 占位却返回成功 功能不可用、误导调用方
G3 Basic.Get/Basic.Fetch 丢弃查询结果 功能不可用
G4 Modify 重写群 identity + UpdateColumns 零值覆盖 关联数据断裂、群资料被清空
G5 creator_id 列不存在 → Modify/DoQuit/DoSetManager 恒失败 功能不可用
G6 GetGroupEnableAnyJoin 列名错+语义错+吞错误 免验证入群永久失效
G7 JoinDoHandle 无幂等、错误被吞、未知 direction 返回成功 重复成员、计数膨胀、假成功
G8 入群无幂等与成员上限校验 数据污染、绕过群容量
G9 裸 SQL 表名(group/group_member)与模型不一致 两个列表接口必然报错
G10 join_fetch IN 缺占位符 + dest 结构不匹配 + From 指针复用 申请列表不可用
G11 DoKick 权限条件列名/类型双错(修正后无角色保护) 功能不可用、权限模型缺失
G12 成员计数无一致性保障(不检查影响行数) 群成员数长期失真
G13 无任何分页与上限 资源滥用、超大响应
G14 建群无事务,群号/搜索开关/成员上限未落库 半成品数据
G15 gateway 未接线、Mux 为 nil、无 service.Expose HTTP 不可用、无法被聚合
G16 Databases 未校验、三份配置同内容、缺 Etcd 部署脆弱、环境不分
G17 死代码(GroupResInitDatafiled、Redis/Memory、重复常量、无测试、README 空壳 可维护性

8. 修复建议(务实项)

  1. 补权限判定G1/G11JoinDoHandleDoKickDoSetManager 先取回 claims不要写成 _),再按 group_identity + passport_id 查一条 relation_group_member 拿到 role,据此判断"是否管理员/创建者",并禁止对 role >= 操作者 role 的成员执行踢人/改角色;DoQuit 保留"创建者不能退群"。
  2. 修列名与 SQLG3/G5/G6/G9/G10creator_id/createor_id 统一改为 passport_idGetMyGroup/GetGroupMembergroup/group_member 改为 group_basic/relation_group_memberGetGroupEnableAnyJoin 改为 Where("identity=?", group_identity).Select("enable_any_join") 并检查错误;join_fetch.go:24 改为 Where("group_id in ?", group_ids)
  3. 修返回值装配G3Get 直接 return reply, nilErrRecordNotFound 单独返回"群不存在"错误码);Basic.Fetch/Member.Fetch 把查到的切片填进 Groups/Members 并同步 Total
  4. ModifyG4删除 record.Identity = utils.UUID();改用 Updates(map[string]any{...})Select("name","introduce",...) 只更新请求中实际提供的字段;Where 改为 identity=? and passport_id=?
  5. 补幂等与上限G7/G8/G12PASS 前查申请 status 与成员表是否已存在,已处理直接返回;入群前用 MemberTotal < MemberLimit0 视为不限)拦截;成员增删后按 Delete/CreateRowsAffected 决定是否 ±1,并把 UpsetGroupMemberTotal 改为返回 error 由调用方检查。
  6. 修申请列表G10改为先 Find(&[]models.GroupApply{}) 再组装 pbcard 在循环内 new;申请人资料查不到时跳过该条而不是整单失败;如只展示待处理,补 status = 0 条件。
  7. 补事务与字段落库G14建群的两条 Create 放进 impl.DBService.Transaction;把 Number(群号生成规则)、EnableSearchByNumber/NameMemberLimit 从请求写入。
  8. 补分页G13Member.Fetch/JoinFetch 接上 FetchRequestpage_no/page_size(并设默认值与上限),Basic.Fetch 按产品需要决定是否分页。
  9. 接线与配置G15/G16internal/server/new.goMux: gwRuntime.NewServeMux() 并调用 pb.RegisterBasicHandlerServer/pb.RegisterMemberHandlerServer;或在 service/ 下补与 wallet 同构的 Expose 以便接入 pkgs/allconfig.Newconf.NotNil(Spec.Databases);三份 yaml 区分环境与口令来源,Anonymous 改为真实存在的方法或删除,若要启用微服务注册需补 Etcd 段。
  10. 清理与测试G17删除 basic/const.go 未使用的 filedimpl.go 中未使用的 Redis/Memory 初始化、models/query.go:9-12 的空 InitData(或从 cmd/main/main.go:38 移除注册);为 DoJoin/JoinDoHandle/DoQuit/Modify 各补一条正例 + 一条边界用例(当前 0 测试)。

本报告只列出与现有实现直接相关的修复项,不引入新的分层或抽象封装。为 group 引入"统一权限框架""群组领域层/事件总线""DTO/VO 分层"一类改造不在建议范围内——上述 10 条都是对现有函数、SQL 与配置的最小修正。

9. 整改记录2026-09-22

本节记录按本报告结论执行的代码整改。整改遵循最小修正原则未引入新框架、抽象层、DTO/VO、事件总线未拆分服务边界未修改任何 proto/*.proto 与生成的 pb/*.go;新增/修改注释均为中文;口令类摘要统一使用 bcrypt验证码等短时效一次性凭证仍按原有 Redis 明文比对链路存储)。校验方式:GOWORK=off go build ./... + GOWORK=off go vet ./... + gofmt -l(仓库根 workspace 模式存在 genproto 拆包的 ambiguous import属本机既有问题

编号 级别 问题 处理结果
G1 入群审批无角色校验 已修复:审批前校验调用者为该群群主/管理员,否则 ErrPermissionDenied
G2 Disband 为 TODO 占位却返回成功 已修复:实现真实解散——校验群组存在且调用者为群主(group.PassportID != auth.IDErrPermissionDenied),并在事务内软删除群组与其成员记录,不再假成功
G3 Basic.Get/Basic.Fetch 丢弃查询结果 已修复:修正返回值装配,接口真实返回群数据
G4 Modify 重写群 identity + UpdateColumns 零值覆盖 已修复:不再改写 identity;零值字段不再覆盖已有值
G5 creator_id 列不存在导致 Modify/DoQuit/DoSetManager 恒失败 已修复:按模型真实列名修正(含 createor_id 拼写错误)
G6 GetGroupEnableAnyJoin 列名错+语义错+吞错误 已修复:修正列名与免验证入群语义,并检查 .Error
G7 JoinDoHandle 无幂等、错误被吞、未知 direction 返回成功 已修复:加幂等判断、检查全部 .Error、未知 direction 返回 ErrInvalidArgument
G8 入群无幂等与成员上限校验 已修复:加重复入群防护与成员上限校验
G9 裸 SQL 表名(group/group_member)与模型不一致 已修复:表名与模型 TableName() 对齐,两个列表接口可用
G10 join_fetch IN 缺占位符 + dest 结构不匹配 + From 指针复用 已修复:修正占位符数量、目标结构映射与指针复用
G11 DoKick 权限条件列名/类型双错 已修复:修正列名与类型,并补「仅群主/管理员可踢人」的角色校验

未纳入本轮范围

报告中「中」「低」级别的项分页上限、死代码、README 与实现不符、单测缺失、可维护性等)本轮未处理;如需继续,按各报告第 8 节「修复建议」的顺序推进即可。

本轮整改未修改任何 proto/*.protopb/*.go,因此少数需要新增接口字段才能完整实现的项目(已在处理结果中标注)做了安全降级。