adult and update go.mod

This commit is contained in:
2026-09-22 18:53:53 +08:00
parent db931a7c40
commit 9f86366638
74 changed files with 5796 additions and 1018 deletions

292
docs/feed.md Normal file
View File

@@ -0,0 +1,292 @@
# feed社交动态与内容互动代码审计报告
| 项 | 内容 |
| --- | --- |
| 审计对象 | `module/social/feed`module 路径 `bsm/full/module/social/feed` |
| 服务域 | 社交与内容 |
| 审计日期 | 2026-09-22 |
| 代码规模 | 手写 Go 32 个文件 / 1160 行;`pb/` 14 个文件 / 6527 行(含手写兼容文件 `pb/blocks_compat.go``proto/` 5 个文件 / 433 行;`etc/` 6 个 yaml + 1 个 supervisor conf`*_test.go` 0 个 |
| 入口 | 仅独立入口 `cmd/main/main.go`gRPC + gateway 单进程,端口 12212`service/` 为**空目录**,未被 `pkgs/all``pkgs/ecmall` 引用;`cmd/cli/main.go` 只打印配置 |
| 对外协议 | gRPC4 个 service / 17 个 RPCgateway 路由定义为 `POST /feed.{Service}/{Method}`,但模块内**从未注册任何 gateway handler** |
| 结论摘要 | 代码量小且 `go vet` 通过,但核心读写链路大面积失效:分页与过滤条件因 GORM 链式未回写而全部失效、列表恒不返回标签/附件、标签列表恒为空、删除评论为空实现;删除/修改动态与互动计数接口均无归属校验9 个 RPC 为 TODO 占位;`etc/feed_*.yaml` 字段名与 SDK 配置结构不匹配,服务启动即退出。 |
## 1. 服务定位与职责
承载用户发布动态(`feed_post`)、动态附件与标签关联、评论(`feed_comment`)、标签字典(`feed_tags`)与四类时间线(推荐/好友/关注/热门)的社交内容服务。
它是社交内容流的**读写与互动计数**服务。时间线排序与分发、好友/关注关系、推荐算法均不在本模块内实现(四类 Timeline 方法全部为占位);模块也不负责用户资料、关系链、消息推送或媒体存储。
## 2. 代码结构与入口
| 路径 | 职责 |
| --- | --- |
| `cmd/main/main.go` | 独立进程入口:`config.New("Feed")``impl.NewImpl()``server.New(addr)``service.New``srv.Use(models.InitData)``srv.Start()` |
| `cmd/cli/main.go` | 仅 `config.New("feed")` + `fmt.Println(config.Spec.Databases)` 的调试入口 |
| `internal/config/config.go` | `SrvConfig`Base、Databases、MicroService、Rpc、Gateway、APM、Etcd、WeChatConf、Token、Kyc与校验 |
| `internal/impl/impl.go` | 初始化 Memory、Redis、DB、Etcd 四个全局实例 |
| `internal/server/new.go` | 创建 grpc.Server、注册 4 个 service`protoc-gen-slc` 生成) |
| `internal/server/{post,tag,timeline,setting}_server.go` | 17 个 RPC 的 1:1 转发(生成代码) |
| `internal/logic/post/{fetch,create,change,remove,comment_list,add_comment,delete_comment,action}.go` | 动态 CRUD、评论、互动计数 |
| `internal/logic/tag/{list,create,post_list}.go` | 标签列表/创建/话题下动态 |
| `internal/logic/timeline/{recommend,friend,follow,hot}.go` | 四类时间线(全部占位) |
| `internal/logic/setting/{info,rights}.go` | 设置(全部占位) |
| `internal/models/{feed_post,feed_comment,feed_tags,feed_relate_tags,feed_relate_attachs,query}.go` | 5 张表的模型 + 迁移注册 + `query.go` 中全部数据访问函数 |
| `service/` | **空目录**(无 `expose.go`,因此无法被聚合宿主接入) |
| `pb/blocks_compat.go` | 手写类型别名文件(`PostListReply = FeedPostListReply` 等),非生成代码 |
| `proto/*.proto` | 4 个 service 契约;公共消息放在 `proto/const.proto``package blocks`,与 service `package feed` 不同) |
| `etc/{feed_dev,feed_prod,feed_test,passport_dev,passport_prod,passport_test}.yaml` | 配置;`passport_*.yaml` 的内容才是与本模块结构匹配的那一份(见 6.4 |
依赖注入:模块内无 `service.Expose``impl.NewImpl()` 是唯一初始化路径;`pkgs/all``pkgs/ecmall``go.mod` 均未引用本模块,全仓库对 `module/social/feed` 的引用只有 `scripts/api-docgen`(仅 import `pb`)与根 `README.md`
## 3. 接口清单
HTTP 路径由生成代码固定为 `/feed.{服务}/{方法}``pb/post.pb.gw.go:600-607``pb/tag.pb.gw.go:280-282``pb/timeline.pb.gw.go:344-347``pb/setting.pb.gw.go:216-217`)。注意:这些 handler 在模块内**从未被注册到任何 `ServeMux`**(见 6.4)。
| 方法 | 路径 | 功能 | 鉴权 | 实现位置 |
| --- | --- | --- | --- | --- |
| POST | `/feed.Post/Fetch` | 推文列表(支持 `params.key` 按标签过滤) | JWT | `logic/post/fetch.go:14` |
| POST | `/feed.Post/Create` | 新建推文(含附件、标签) | JWT | `logic/post/create.go:17` |
| POST | `/feed.Post/Change` | 修改推文 | JWT无归属校验 | `logic/post/change.go:17` |
| POST | `/feed.Post/Remove` | 删除推文 | JWT无归属校验 | `logic/post/remove.go:16` |
| POST | `/feed.Post/CommentList` | 评论列表 | JWT | **占位** `logic/post/comment_list.go:11` |
| POST | `/feed.Post/AddComment` | 新建评论 | JWT | `logic/post/add_comment.go:18` |
| POST | `/feed.Post/DeleteComment` | 删除评论 | JWT无归属校验 | `logic/post/delete_comment.go:16` + **空实现** `models/query.go:122-125` |
| POST | `/feed.Post/Action` | 点赞/点踩计数 | JWT不校验身份来源 | `logic/post/action.go:17` |
| POST | `/feed.Tag/List` | 标签列表 | JWT | `logic/tag/list.go:13`(返回值被丢弃,恒为空) |
| POST | `/feed.Tag/Create` | 新建标签 | JWT | `logic/tag/create.go:15` |
| POST | `/feed.Tag/PostList` | 话题相关动态 | JWT | **占位** `logic/tag/post_list.go:11` |
| POST | `/feed.Timeline/Recommend` | 推荐时间线 | JWT | **占位** `logic/timeline/recommend.go:11` |
| POST | `/feed.Timeline/Friend` | 好友动态 | JWT | **占位** `logic/timeline/friend.go:11` |
| POST | `/feed.Timeline/Follow` | 关注动态 | JWT | **占位** `logic/timeline/follow.go:11` |
| POST | `/feed.Timeline/Hot` | 热门动态 | JWT | **占位** `logic/timeline/hot.go:11` |
| POST | `/feed.Setting/Info` | 修改个人信息 | JWT | **占位** `logic/setting/info.go:11` |
| POST | `/feed.Setting/Rights` | 修改权限信息 | JWT | **占位** `logic/setting/rights.go:11` |
> 鉴权方式:全部 17 个 logic 入口都调用 `service.ParseMetaCtx(ctx, nil)``git.apinb.com/bsm-sdk/core/service/meta.go:19-49`),要求有效 JWT所有 `opts` 均为 `nil`,即**不校验角色、不校验私有 IP**。多个入口用 `_, err = ...` 丢弃 claims如 `logic/post/fetch.go:15`),因此拿不到调用者身份。
### 声明但未实现 / 空转的接口
| 类别 | 接口 | 证据 |
| --- | --- | --- |
| TODO 占位4 个时间线) | `Timeline.Recommend` / `Friend` / `Follow` / `Hot` | `internal/logic/timeline/{recommend,friend,follow,hot}.go:26``// TODO: add your logic code` |
| TODO 占位 | `Tag.PostList` | `internal/logic/tag/post_list.go:26` |
| TODO 占位 | `Post.CommentList` | `internal/logic/post/comment_list.go:26` |
| TODO 占位2 个) | `Setting.Info` / `Setting.Rights` | `internal/logic/setting/{info,rights}.go:18-20` |
| 有路由有转发但底层空实现 | `Post.DeleteComment` | `internal/models/query.go:122-125` 函数体只有 `return`,永远返回 nil |
| 有实现但返回值被丢弃 | `Tag.List` | `internal/logic/tag/list.go:32` 返回新建的空 `TagListReply`,忽略了上面组装好的 `res` |
> 9 个 TODO 占位函数统一写成 `func(...) (reply *XxxReply, err error) { ...; return }`,即返回 `(nil, nil)`gRPC 侧回空消息gateway 侧对 nil 响应序列化会失败(表现为 500/空响应)。
## 4. 数据模型与表
模型统一在 `init()` 中通过 `database.AppendMigrate` 注册(`internal/models/feed_post.go:20` 等为 `TableName` 方法;迁移注册在各文件内)。公共字段来自 SDK`types.Std_IICUDS` = `id`(PK) / `identity varchar(36) uniqueIndex` / `created_at` / `updated_at` / `deleted_at`(index) / `status int8 index``types.Std_IDIdentity` = `id`(PK) / `identity`(uniqueIndex)`types.Std_Passport` = `passport_id`(index) / `passport_identity varchar(36)`(index)。
### 表 `feed_post``internal/models/feed_post.go:8-22`
| 字段 | 类型 | 键/约束 | 说明 |
| --- | --- | --- | --- |
| `id` | uint | PK | 自增主键 |
| `identity` | varchar(36) | uniqueIndex | 动态唯一码 |
| `created_at`/`updated_at`/`deleted_at` | timestamp | index(deleted_at) | 软删除 |
| `status` | int8 | index, 默认 0 | 状态(-1 禁止 / 1 正常),**查询中未使用** |
| `passport_id` | uint | index | 发布者 ID |
| `passport_identity` | varchar(36) | index | 发布者唯一标识 |
| `content` | text | 默认 `''` | 正文 |
| `is_open` | bool | 默认 true | true 公开 / false 不公开,**查询中未过滤** |
| `cnt_like` | int64 | 默认 0 | 点赞数 |
| `cnt_unlike` | int64 | 默认 0 | 点踩数 |
| `cnt_comment` | int64 | 默认 0 | 评论数 |
| `Attachs` | `[]FeedRelateAttach` | `gorm:"-"` | **非持久化**,不会自动迁移,也永远不会被 GORM 加载 |
| `Tags` | `[]FeedRelateTags` | `gorm:"-"` | 同上 |
### 表 `feed_comment``internal/models/feed_comment.go:6-20`
| 字段 | 类型 | 键/约束 | 说明 |
| --- | --- | --- | --- |
| `id` / `identity` | uint / varchar(36) | PK / uniqueIndex | 主键与唯一码 |
| `created_at`/`updated_at`/`deleted_at` | timestamp | index(deleted_at) | 软删除 |
| `status` | int8 | index | 状态,未使用 |
| `passport_id` / `passport_identity` | uint / varchar(36) | index | 评论作者;**新增评论时从不写入**(见 6.2 |
| `post_identity` | varchar(36) | index | 所属动态唯一码 |
| `parent_identity` | varchar(36) | index | 上级评论唯一码 |
| `content` | text | 默认 `''` | 评论正文 |
| `SubComment` | `[]FeedComment` | `foreignkey:parent_identity` | 子评论关联,无任何查询使用 |
| `cnt_like`/`cnt_unlike`/`cnt_comment` | int64 | 默认 0 | 互动计数 |
### 表 `feed_tags``internal/models/feed_tags.go:8-16`
| 字段 | 类型 | 键/约束 | 说明 |
| --- | --- | --- | --- |
| `id` | uint | PK | 自增主键 |
| `created_at`/`updated_at`/`deleted_at` | timestamp | index(deleted_at) | 软删除 |
| `key` | varchar(36) | not null**无唯一索引** | 标签键;由 `tag/create.go:26` 写入随机 UUID |
| `content` | varchar(255) | not null | 标签名称 |
> 该表**没有 `identity` 列**,但 `models/query.go:81` 与 `:89` 使用 `Where("identity = ?", ...)`(见 6.2 与 6.3)。
### 表 `feed_relate_tags``internal/models/feed_relate_tags.go:6-15`
| 字段 | 类型 | 键/约束 | 说明 |
| --- | --- | --- | --- |
| `id` / `identity` | uint / varchar(36) | PK / uniqueIndex | 主键与唯一码 |
| `post_identity` | varchar(36) | index | 动态唯一码 |
| `key` | varchar(36) | index | 标签键 |
| `Content` | string | `gorm:"-"` | **非持久化**`create.go:46` 写入的标签名会被静默丢弃 |
### 表 `feed_relate_attach``internal/models/feed_relate_attachs.go:6-16`,表名单数)
| 字段 | 类型 | 键/约束 | 说明 |
| --- | --- | --- | --- |
| `id` | uint | PK内嵌 `Std_IDIdentity` | 自增主键 |
| `identity` | varchar(36) | uniqueIndex | 唯一码(内嵌字段与显式字段同名,见 6.4 |
| `post_identity` | varchar(36) | index | 动态唯一码 |
| `attach_type` | varchar(36) | index | 附件类型 |
| `url` | varchar(255) | index | 附件地址 |
## 5. 核心流程
### 5.1 发布动态 → 落库 → 列表读取
```mermaid
flowchart TD
A["POST /feed.Post/Create<br/>service.ParseMetaCtx JWT"] --> B["校验 content 非空"]
B --> C["models.CreatePost 事务"]
C --> D["tx.Create feed_post"]
D --> E["tx.Create feed_relate_attach"]
E --> F["tx.Create feed_relate_tags<br/>Content 字段 gorm 忽略 标签名丢失"]
F --> G["返回 identity"]
H["POST /feed.Post/Fetch<br/>service.ParseMetaCtx 丢弃 claims"] --> I["page_no 小于等于 0 置 1<br/>page_size 小于等于 0 置 10"]
I --> J["models.PostList<br/>Order created_at desc"]
J --> K["tx.Limit(page).Offset(...) 未回写<br/>分页整体失效"]
K --> L["Find 到 FeedPost 切片<br/>Tags 与 Attachs 为 gorm 忽略字段"]
L --> M["AssembleList 中两个循环永不执行<br/>标签与附件恒为空"]
M --> N["fmt.Print 把整页数据打到 stdout"]
N --> O["无 is_open 无 status 过滤<br/>含私密与禁用用户的动态"]
```
### 5.2 评论与互动计数
```mermaid
flowchart TD
A["POST /feed.Post/AddComment"] --> B["models.AddComment 事务"]
B --> C["Create feed_comment<br/>passport_identity 未写入 作者为空"]
C --> D["Table feed_post 后 UpdateColumn<br/>cnt_comment 加 1"]
D --> E{"ParentIdentity 非空"}
E -->|是| F["未指定 Model 的 UpdateColumn<br/>目标表只能靠 GORM 内部状态推断"]
G["POST /feed.Post/Action"] --> H["models.LikeAction"]
H --> I{"action_type"}
I -->|post| J["Model FeedPost"]
I -->|comment| K["Model FeedComment"]
I -->|其他| L["未设置 Model 目标表不确定"]
J --> M{"action_op"}
K --> M
M -->|ilike| N["column 为 cnt_like 计数加 1"]
M -->|unlike| O["条件误写成 action_type 等于 unlike<br/>column 保持空串"]
O --> P["UpdateColumn 空列名 SQL 错误"]
Q["POST /feed.Post/DeleteComment"] --> R["models.DeleteComment 空函数<br/>永远返回 nil"]
R --> S["接口返回成功但无任何删除"]
```
## 6. 审计发现
### 6.1 安全
| 级别 | 位置 | 问题 |
| --- | --- | --- |
| **高** | `internal/logic/post/remove.go:17-29` + `internal/models/query.go:34-37` | **越权删除动态**`Remove` 拿到 JWT 后直接用 `in.Identity``DeletePost`,模型层是 `Where("identity = ?", identity).Delete(&FeedPost{})`**没有任何 `passport_identity` 归属条件** → 任意登录用户可删除他人动态(软删除)。 |
| **高** | `internal/logic/post/change.go:17-40` + `internal/models/query.go:30-33` | **越权修改动态**`Change` 同样只校验 `in.Identity != ""``ChargePost``Where("identity = ?", data.Identity).Updates(&data)` → 可篡改任意用户的动态正文与公开状态。 |
| **高** | `internal/logic/post/fetch.go:15-36` + `internal/models/query.go:60-71` | **私密动态泄露**`Fetch` 丢弃 claims`PostList` 的查询**没有 `is_open` 条件、没有 `status` 条件、没有作者条件** → `is_open=false` 的"不公开"动态、`status=-1` 的被禁用用户动态都会被返回(`internal/models/feed_post.go:12` 明确 `false-不公开`)。 |
| **高** | `internal/logic/post/action.go:17-28` + `internal/models/query.go:40-57` | **互动计数可无限刷量**`Action` 只校验三个字符串非空,不校验调用者身份、不落库点赞人、无去重 → 任意登录用户对任意 `identity` 循环调用即可把 `cnt_like` 刷到任意值;且 `action_type` 可传任意值绕过模型绑定。 |
| 中 | `internal/logic/post/delete_comment.go:16-28` | 删除评论接口只校验 `in.Identity` 非空,**不校验评论归属**(底层当前是空实现,但设计上即越权入口)。 |
| 中 | `internal/logic/post/add_comment.go:23-34` + `internal/models/query.go:103-120` | 评论不记录作者:`data` 只设了 `Identity`/`PostIdentity`/`Content`/`ParentIdentity``Std_Passport` 全空,且模型函数签名里的 `authorIdentity` 形参**在函数体内从未被使用**`query.go:103-120`)→ 无法做归属校验、无法展示/追溯评论人。同时不校验 `PostIdentity` 是否存在或可见,可对任意/不存在的动态灌评论。 |
| 中 | `internal/logic/tag/create.go:20-36` | 任何登录用户可批量创建标签,无权限校验、无去重、无长度限制;`Key``utils.UUID()``create.go:26`)与标签内容无关联,标签字典的键不可读。 |
| 低 | `internal/logic/post/create.go:22-24` | 只校验 `content != ""`,无长度上限、无发布频率限制、无敏感内容校验;附件 `url``attach_type` 由请求方直接写入(`create.go:32-41`)。 |
### 6.2 正确性与逻辑缺陷
| 级别 | 位置 | 问题 |
| --- | --- | --- |
| **高** | `internal/models/query.go:67` | **分页完全失效,且页码被当成条数**`tx.Limit(int(page)).Offset(int((page - 1) * size))` 中第一个参数应为 `size` 却传了 `page`;更关键的是 `tx.Limit(...).Offset(...)` 的返回值**未回写 `tx`**,后续 `tx.Find` 用的是没有 Limit/Offset 的语句 → 实际返回**全表**(并受第 68 行 Count 影响)。`logic/post/fetch.go:26` 传入 `(in.PageNo, in.PageSize)` 的顺序本身是对的,缺陷在模型层。 |
| **高** | `internal/models/query.go:122-125` | **`DeleteComment` 是空实现**,函数体只有 `return`,永远返回 nil → `Post.DeleteComment` 永远返回"成功",但没有任何删除动作;`logic/post/delete_comment.go:24``err` 判断因此永远不会触发。 |
| **高** | `internal/models/query.go:60-71` + `internal/logic/post/fetch.go:51-63` | **列表恒不返回标签与附件**`PostList``tx.Find(&list)`,没有 `Preload("Tags")`/`Preload("Attachs")`;而 `FeedPost.Tags`/`Attachs` 的标签是 `gorm:"-"``internal/models/feed_post.go:16-17`GORM 永远不会加载它们 → `AssembleList` 里的两个 `for` 循环永不执行,`post.Tags`/`post.Attachs` 恒为空切片。 |
| **高** | `internal/models/query.go:40-57` | **点踩功能不可用**。第 50 行把判断写成了 `} else if action_type == "unlike" {`(应为 `action_op == "unlike"`)→ 点踩时 `column` 保持空串,第 53 行执行 `UpdateColumn("", gorm.Expr(" + ?", 1))`,列名非法导致 SQL 错误、接口报错。另:`action_type``"post"`/`"comment"` 时第 43-47 行不会调用 `.Model(...)``tx` 仍是裸 `impl.DBService`,目标表不确定。 |
| **高** | `internal/logic/post/change.go:25-40` + `internal/models/query.go:30-33` | **修改动态无法改为不公开,附件被静默丢弃**`ChargePost` 用结构体 `Updates(&data)`GORM 结构体更新会忽略零值 → `is_open=false` 永远写不进去;`change.go:31-39` 组装的 `postData.Attachs` 只放在 `gorm:"-"` 字段上,`ChargePost` 完全不处理 → 附件修改无效(且是"改了但没报错")。 |
| **高** | `internal/models/query.go:81,89` | `ModifyTags`/`DeleteTags` 使用 `Where("identity = ?", ...)`,而 `FeedTags` **没有 `identity` 列**`internal/models/feed_tags.go:8-12` 只有 `gorm.Model` + `key` + `content`)→ SQL 会因列不存在而报错。两函数当前无调用点(死代码),一旦接入即报错。 |
| 中 | `internal/logic/tag/list.go:20-32` | **标签列表恒为空**。第 25-31 行已经把 `tags` 组装进 `res`,第 32 行却 `return &pb.TagListReply{}, nil` 新建了一个空对象返回 → `tags``total` 全部丢失。 |
| 中 | `internal/models/query.go:68,99` | `err = tx.Find(&list).Count(&cnt).Error``Find``Count` 共用同一链,`Count` 会重新构建并执行一次统计查询,`Find` 的错误可能被 `Count` 的结果覆盖,`RowsAffected`/总数语义不清;`TagsList``query.go:96-101`)同样写法,且具名返回值 `list` 未被赋值。 |
| 中 | `internal/models/query.go:63-64` | 标签过滤构造不可靠:`tx.Joins("join feed_relate_tags on ...", impl.DBService.Where(&FeedRelateTags{Key: tag}))` 把一个 `*gorm.DB` 当作 `Joins` 的参数值传入(应为 `?` 参数);紧接着第 64 行又用未限定表名的 `Where("key = ?", tag)` 二次过滤 → 生成的 SQL 与预期语义不一致(该分支同时因分页失效而难以验证)。 |
| 中 | `internal/models/query.go:103-120` | `AddComment` 事务内链式顺序颠倒:第 106 行 `tx.Create(comment).Table("feed_post").Where(...).UpdateColumn(...)` 先插入评论、再切换目标表去更新帖子计数,与注释"添加评论,更新文章评论量"的书写顺序相反;第 112 行对"被评论的评论"更新时**未指定 `Model`/`Table`**,目标表只能由 GORM 内部语句状态决定(可能落到 `feed_comment` 自身的 `cnt_comment`),无法从代码确证。 |
| 中 | `internal/logic/post/fetch.go:19-24``internal/logic/timeline/*.go:19-24``internal/logic/tag/post_list.go:19-24``internal/logic/post/comment_list.go:19-24` | `page_size` 只做了下限兜底、**没有上限**`if in.GetPageSize() <= 0 { in.PageSize = 10 }`fetch`if in.GetPageSize() < 10 { in.PageSize = 50 }`(其余)两种语义还不一致(后者把传 5 放大成 50传 20 却保持 20配合分页失效可一次要求全表。 |
| 中 | `internal/logic/post/add_comment.go:23-30` | 允许 `in.PostIdentity` 为空串(无任何校验)→ 可写入 `post_identity` 为空的孤儿评论;同时 `in.Identity` 被当作 `ParentIdentity`,但未校验该父评论是否属于同一 `post_identity`。 |
| 中 | `internal/logic/post/fetch.go:26` | 标签过滤参数取自 `in.Params["key"]`,键名是硬编码的魔法字符串,与 `TagItem.Key` 语义耦合;传其他键(如 `tag`)会被静默忽略。 |
| 低 | `internal/models/query.go:11` | `InitData()` 直接 `return nil`,而 `cmd/main/main.go:38` 仍以 `srv.Use(models.InitData)` 注册(空初始化)。 |
| 低 | `internal/models/feed_relate_attachs.go:6-12` | `FeedRelateAttach` 内嵌 `types.Std_IDIdentity`(已含 `identity varchar(36) uniqueIndex`)后又显式声明 `Identity string`**同名列遮蔽**;实际生效的是显式字段,但列定义重复声明,依赖 GORM 的字段冲突消解规则。表名 `feed_relate_attach` 为单数,与 `feed_relate_tags``feed_post` 命名风格不一致。 |
### 6.3 未完成实现
| 类别 | 内容 | 证据 |
| --- | --- | --- |
| TODO 占位 RPC9 个) | `Timeline.Recommend`/`Friend`/`Follow`/`Hot``Tag.PostList``Post.CommentList``Setting.Info``Setting.Rights` | `internal/logic/timeline/{recommend,friend,follow,hot}.go:26``internal/logic/tag/post_list.go:26``internal/logic/post/comment_list.go:26``internal/logic/setting/{info,rights}.go:18-20` |
| 空实现函数 | `models.DeleteComment`(只有 `return` | `internal/models/query.go:122-125` |
| 空实现函数 | `models.InitData`(只有 `return nil` | `internal/models/query.go:10-12` |
| 无调用点的死函数 | `models.ModifyTags``models.DeleteTags`(且 SQL 列名不存在,见 6.2 | `internal/models/query.go:80-94` |
| 未接入聚合 | `service/` 为空目录,无 `Expose`/`Dependencies``pkgs/all``pkgs/ecmall``go.mod` 均未引用本模块 | 全仓库对 `module/social/feed` 的引用只有 `scripts/api-docgen/go.mod:18,47` 与根 `README.md:218` |
| gateway 未接线 | 模块内无任何 `RegisterPostHandlerServer` 等调用,`Server.Mux` 也从未被赋值 | `internal/server/new.go:16-36``grep RegisterPostHandlerServer module/social/feed` 无匹配) |
| 配置文件名错位 | `etc/passport_dev.yaml`/`passport_prod.yaml`/`passport_test.yaml` 的内容(`Service: feed`、含 `Databases`/`MicroService`/`WeChatConf`/`Kyc`/`Token`)才是与本模块匹配的配置,但 `conf.New``feed_<mode>.yaml` 取名SDK `conf/new.go:35-36`)→ 这三个文件**永远不会被加载** | `etc/passport_prod.yaml:1-2``internal/config/config.go:15-26`、SDK `git.apinb.com/bsm-sdk/core/conf/new.go:35-42` |
| README 为空壳 | `module/social/feed/README.md` 全文只有标题 `# content` | `README.md:1` |
### 6.4 健壮性与可维护性
| 级别 | 位置 | 问题 |
| --- | --- | --- |
| **高** | `etc/feed_dev.yaml:1-4``etc/feed_prod.yaml:1-4``etc/feed_test.yaml:1-4` | 三个 feed 配置都用 `Name`/`ListenOn`/`Dsn`,而 `SrvConfig` 只识别 `Service`/`Port`/`Databases``internal/config/config.go:17-18`。SDK 在 YAML 解析前先检查文件是否含子串 `"Service:"``git.apinb.com/bsm-sdk/core/conf/new.go:58-60`),这三个文件只含 `ServiceName:` → 直接 `log.Fatalln("ERROR: Service Not Nil")` 退出;即使能加载,`Spec.Service` 仍为空,`conf.NotNil``config.go:56`)同样致命。 |
| **高** | `internal/impl/impl.go:24` + `etc/feed_*.yaml` | 三个 feed 配置均无 `Databases` 段(用的是 `Dsn``with.Databases(nil, nil)` 会直接 `panic("No Database Source Found !")``git.apinb.com/bsm-sdk/core/with/databases.go:13-15`)。 |
| **高** | `etc/feed_dev.yaml:16-19``feed_prod.yaml:15-18``feed_test.yaml:16-19` | 匿名清单写成顶层 `Anonymous: {Key, Urls}`,而 SDK 的匿名清单在 `conf.MicroServiceConf.Anonymous``conf/types.go:25`)→ `Anonymous` 整块被忽略;三个配置也都没有 `MicroService` 段,`MsConf` 为 nil → `service.Start` 跳过 etcd 注册SDK `service/service.go:66`)。`passport_prod.yaml:15` 的匿名项还写成 `feed.ping.hello`(模块内不存在此方法)。 |
| 中 | `internal/server/new.go:16-36``cmd/main/main.go:33` | 既没有 `gwRuntime.NewServeMux()`,也没有 `pb.RegisterXxxHandlerServer` 调用,`Server.Mux` 恒为 nil → `cmd/main/main.go:33` 把 nil 传给 `GatewayMux``service.Start``http.ListenAndServe(addr, nil)``DefaultServeMux`,所有 `/feed.*` 路径 404。模块自身也无法像 wallet 那样通过 `service.Expose` 被聚合宿主接线。 |
| 中 | `etc/feed_prod.yaml:4``etc/feed_dev.yaml:4` | 数据库连接串明文提交入库,含生产库账号与口令(`postgres://prod:MakeW2023~PROD@192.168.0.224:5432/scf`)、开发库地址(`47.108.57.74:5432/milu`)。 |
| 中 | `etc/supervisor.bsm-apps-passport.conf:1` | 文件名仍是 passport内容 program 名却是 `bsm-apps-feed`;与 `etc/passport_*.yaml` 一并说明该目录是从 passport 服务复制而来,未清理。 |
| 中 | `etc/passport_prod.yaml:31-35` | 同一份配置里同时存在 `Kyc:``KycConf:` 两个段,而 `config.SrvConfig` 只映射 `Kyc``internal/config/config.go:25`)→ `KycConf` 整块为死配置。 |
| 中 | `internal/impl/impl.go:21-24` | `MemorySerice``RedisService` 被初始化,但全模块无任何使用点(死代码);`EtcdService` 仅在微服务注册路径使用,而 `MsConf` 恰好为 nil。 |
| 低 | `internal/models/query.go:69` | 残留调试语句 `fmt.Print("posts", list)`,会把整页动态实体(含 content打印到进程标准输出。 |
| 低 | `cmd/cli/main.go:9-12` | CLI 入口只做 `config.New("feed")``fmt.Println(config.Spec.Databases)`,调试残留;且服务键传小写 `"feed"`,与主入口的 `ServiceKey = "Feed"` 不一致(`conf.New` 拼文件名时会 `ToLower`,但 `vars.ServiceKey` 保留原样,影响日志/注册键大小写)。 |
| 低 | 全模块 | 0 个 `*_test.go``test/` 下只有一个**空的** `lint/` 目录,无任何请求样例。 |
| 低 | `internal/logic/post/fetch.go:39-67` | `AssembleList` 是包级导出函数但只在本文件使用,且内部对 `res` 使用具名返回追加(`res = append(res, post)`),在 data 为空时返回 nil 切片而非空切片,前端需要额外判空。 |
## 7. 风险汇总
| 编号 | 级别 | 问题 | 影响面 |
| --- | --- | --- | --- |
| F1 | 高 | 删除动态无归属校验,可删他人动态 | 数据完整性、越权 |
| F2 | 高 | 修改动态无归属校验,可篡改他人动态 | 数据完整性、越权 |
| F3 | 高 | 列表无 `is_open`/`status` 过滤,私密动态泄露 | 隐私泄露 |
| F4 | 高 | 互动计数无去重、无身份绑定,可无限刷量 | 数据可信性 |
| F5 | 高 | 分页 `Limit(page)` + 未回写 → 分页失效并全表返回 | 功能不可用 + 性能风险 |
| F6 | 高 | `DeleteComment` 空实现,删除接口永远"成功" | 功能不可用 |
| F7 | 高 | 列表恒不返回标签与附件(`gorm:"-"` + 无 `Preload` | 功能不可用 |
| F8 | 高 | 点踩计数条件写错导致 SQL 列名为空 | 功能不可用 |
| F9 | 高 | 修改动态无法改为不公开,附件修改被静默丢弃 | 功能不正确 |
| F10 | 高 | `feed_*.yaml` 字段名不匹配 → 启动即退出;无 `Databases` → panic | 部署可用性 |
| F11 | 高 | gateway handler 从未注册、`Mux` 为 nil、无 `service.Expose` | 接口不可用、无法聚合 |
| F12 | 高 | `feed_tags``identity` 列却被 `Where("identity")` 使用 | 接入即报错 |
| F13 | 中 | 评论不记录作者、不校验所属动态 | 越权、数据不可追溯 |
| F14 | 中 | 标签列表返回值被丢弃,恒为空 | 功能不可用 |
| F15 | 中 | `AddComment` 事务内 `Table` 用法与未指定 Model 的更新 | 结果不确定、可能整事务回滚 |
| F16 | 中 | 9 个 RPC 为 TODO 占位,返回 `(nil, nil)` | 功能缺失、网关异常 |
| F17 | 中 | 生产/开发数据库连接串明文入库 | 凭据泄露 |
| F18 | 中 | `page_size` 无上限、上下限语义不一致 | 资源滥用 |
| F19 | 低 | 死代码、调试 `fmt.Print`、无测试、README 空壳 | 可维护性 |
## 8. 修复建议(务实项)
1. **补归属校验**F1/F2/F13`Remove`/`Change` 在 logic 层先按 `identity + passport_identity` 查一次动态,查不到即返回"无权操作";或把模型函数改为 `DeletePost(identity, passportIdentity)``ChargePost(data, passportIdentity)` 并加上 `Where("passport_identity = ?", ...)`。评论同理,把 `AddComment``authorIdentity` 真正写进 `FeedComment.Std_Passport`,删除评论按 `identity + passport_identity` 定位。
2. **修可见性过滤**F3`PostList` 增加 `is_open = true` 与作者本人可见的 `OR` 条件,并按需过滤 `status <> -1``Fetch` 不要丢弃 claims用调用者身份决定是否可见私密动态。
3. **修分页**F5/F18`query.go:67``Limit(int(page))` 改为 `Limit(int(size))`,并把 `tx = tx.Limit(...).Offset(...)` 回写;给 `page_size` 设上限(如 50并把 `fetch.go``comment_list.go`、四个 timeline、`tag/post_list.go` 的兜底逻辑统一为 `<= 0 则取默认值`
4. **修删除与计数**F6/F8`DeleteComment` 补实现(软删除 + 同事务把对应 `feed_post.cnt_comment` 减 1并做下限保护`LikeAction` 的第 50 行改为 `action_op == "unlike"``action_type` 用白名单(`post`/`comment`)校验并显式设置 `Model``column` 为空时直接返回参数错误。
5. **修标签与附件读写**F7/F9/F12/F14`PostList``Preload("Tags")`/`Preload("Attachs")` 之前,需先把 `FeedPost.Tags`/`Attachs``gorm:"-"` 去掉或改写为显式二次查询(按 `post_identity IN (...)` 批量取关联表再回填);`FeedRelateTags.Content` 去掉 `gorm:"-"` 才能真正存标签名;`Change``Select("content","is_open").Updates(...)` 或 map 更新以写入 `is_open=false`,并显式处理附件的增删;`FeedTags``Where("identity = ?")` 改为按主键或 `key` 定位;`tag/list.go:32` 改为 `return res, nil`
6. **配置**F10`etc/feed_dev.yaml`/`feed_prod.yaml`/`feed_test.yaml``SrvConfig` 重写(`Service`/`Port`/`Databases`/`Cache`/`MicroService.Anonymous`/`Gateway`),或直接把已正确的 `etc/passport_*.yaml` 重命名为 `feed_*.yaml` 并删除冗余文件;`config.New` 里补 `conf.NotNil(Spec.Databases)`;把 `Dsn` 中的口令改为环境变量注入并轮换已泄露的生产口令。
7. **接线与启动**F11`internal/server/new.go` 中给 `Mux``gwRuntime.NewServeMux()` 并注册 4 组 handler或在 `service/` 下补一份与 wallet 同构的 `expose.go`/`dependencies.go` 以便接入 `pkgs/all`;否则 `Gateway.Enable=true` 只会得到一个 404 网关。
8. **补初始化与占位反馈**F16`models.InitData` 要么实现必要的基础数据,要么在 `cmd/main/main.go:38` 去掉 `srv.Use(models.InitData)`9 个 TODO RPC 在实现前应返回明确的 `Unimplemented` 错误,而不是 `(nil, nil)`
9. **清理**F19删除 `internal/models/query.go:69``fmt.Print``internal/models/query.go:80-94``ModifyTags`/`DeleteTags`(无调用点且 SQL 错误)、`internal/impl/impl.go` 中未使用的 Redis/Memory 初始化、`etc/passport_prod.yaml:28-33``KycConf` 死配置;`README.md` 至少补一段服务说明。
10. **测试**:为 `PostList`(分页与可见性过滤)、`LikeAction`(点赞/点踩各一次)、`DeleteComment``AddComment`(作者落库与计数一致)各写一条正例 + 一条边界用例。
> 本报告只列出与现有实现直接相关的修复项。为 feed 引入"统一内容框架""时间线抽象层""互动事件总线""DTO/VO 分层"一类改造不在建议范围内——上述 10 条都是对现有函数与配置的最小修正。