218 lines
23 KiB
Markdown
218 lines
23 KiB
Markdown
|
|
# cms(站点内容管理)代码审计报告
|
|||
|
|
|
|||
|
|
| 项 | 内容 |
|
|||
|
|
| --- | --- |
|
|||
|
|
| 审计对象 | `module/base/cms` |
|
|||
|
|
| 服务域 | 基础与平台服务(内容管理:文章 / 页面 / 分类 / 标签 / 评论 / 赞踩) |
|
|||
|
|
| 审计日期 | 2026-09-22 |
|
|||
|
|
| 代码规模 | 手写 Go 文件约 62 个(logic 41、models 10、server 6、service 2、config 1、impl 1、utils 1、cmd 1);pb 生成代码 17 个(post/pages/tags/site/category 三件套 + const + blocks_compat);proto 6 个;`.http` 测试 36 个;无 `cmd/main`、无 yaml 配置、无 `*_test.go` 单测 |
|
|||
|
|
| 入口 | 仅聚合入口 `pkgs/all`、`pkgs/ecmall`(经 `service.Expose` 注册 gRPC + HTTP Gateway);`cmd/cli/main.go` 仅为 `Hello World` 占位,无独立可运行进程 |
|
|||
|
|
| 对外协议 | gRPC(`cms.Post`/`cms.Pages`/`cms.Category`/`cms.Tags`/`cms.Site`)+ grpc-gateway HTTP(`/cms.<Svc>/<Method>`);站点 `Site` 系列为**声明但未实现**的占位 |
|
|||
|
|
| 结论摘要 | 文章/页面查询、评论、计数增改等核心逻辑已实现,但存在**评论发布 panic(未命中 mall_staff 即崩溃)**、**赞/踩无去重且可刷成负数**、**删除父评论时文章评论计数不一致**、**Search 无分页上限(可拖全表)**、**文章/评论增删改不校验归属(越权)**、**Site 全系列占位空实现**等真实缺陷。 |
|
|||
|
|
|
|||
|
|
## 1. 服务定位与职责
|
|||
|
|
|
|||
|
|
站点内容管理(CMS)微服务,提供文章、单页、分类、标签的 CRUD 与查询,以及评论、点赞/踩等互动能力。数据模型以 `cms_post`/`cms_pages`/`cms_category`/`cms_tags`/`cms_comment` 为核心,通过 `cms_relate_category`/`cms_relate_tags`/`cms_accessory` 维护关联。它是内容存储与查询服务,不负责全文检索、富媒体转码或内容审核(这些不在本模块代码内)。`Site`(站点配置)在 proto 与 server 中均已声明并注册,但逻辑层全部为占位空实现。
|
|||
|
|
|
|||
|
|
## 2. 代码结构与入口
|
|||
|
|
|
|||
|
|
| 路径 | 职责 |
|
|||
|
|
| --- | --- |
|
|||
|
|
| `cmd/cli/main.go` | 占位入口,仅打印 `Hello World`,不启动任何服务 |
|
|||
|
|
| `service/expose.go` | 聚合宿主注入依赖 + 注册 5 个 gRPC 服务与 5 个 Gateway Handler |
|
|||
|
|
| `service/dependencies.go` | `Dependencies`(Redis/Etcd/DB/Cache)覆盖 `internal/impl` 全局对象 |
|
|||
|
|
| `internal/config/config.go` | 配置结构(`Base`/`Databases`/`MicroService`/`Rpc`/`Gateway`/`Apm`/`Etcd`)与校验 |
|
|||
|
|
| `internal/impl/impl.go` | 初始化 Redis、DB、Etcd、内存缓存为全局对象 |
|
|||
|
|
| `internal/server/new.go` | gRPC Server 注册入口(`Register*Server`) |
|
|||
|
|
| `internal/server/{post,pages,category,tags,site}_server.go` | gRPC 服务实现,转发到对应 logic 包 |
|
|||
|
|
| `internal/logic/post/*` | 文章 CRUD、查询、搜索、评论、赞/踩(20 文件) |
|
|||
|
|
| `internal/logic/category/*` `internal/logic/tags/*` `internal/logic/pages/*` `internal/logic/site/*` | 分类、标签、页面、站点逻辑 |
|
|||
|
|
| `internal/models/*.go` | 9 张表模型 + `query.go`(增删改查与计数) |
|
|||
|
|
| `internal/utils/string.go` | `FormatKey`(小写、去空格) |
|
|||
|
|
| `proto/*.proto` `pb/*.go` | 接口契约与生成代码(仅看签名) |
|
|||
|
|
| `test/**/*.http` | HTTP 调用示例(非自动化测试) |
|
|||
|
|
|
|||
|
|
依赖注入:`service.Dependencies` 由聚合入口 `pkgs/all` 传入共享连接(Redis/Etcd/DB/Memory),非 nil 时覆盖 `internal/impl` 全局对象;`config.NotNil` 仅校验 `Service`/`Cache`。
|
|||
|
|
|
|||
|
|
## 3. 接口清单
|
|||
|
|
|
|||
|
|
> 鉴权列:除 `CommentList` 外,所有 handler 均调用 `service.ParseMetaCtx(ctx, nil)` 解析登录态(无 token 即报错),即**已认证、未授权**(不校验资源归属)。`Site` 系列虽调用 `ParseMetaCtx`,但后续无实际操作。
|
|||
|
|
|
|||
|
|
| 服务 | 方法 | proto / 路径 | 功能 | 鉴权 | 实现位置 | 状态 |
|
|||
|
|
| --- | --- | --- | --- | --- | --- | --- |
|
|||
|
|
| Post | Fetch | `/cms.Post/Fetch` | 文章分页列表(按分类/关键词/类型) | 有 | `logic/post/fetch.go` | 已实现 |
|
|||
|
|
| Post | GetByIdentity | `/cms.Post/GetByIdentity` | 按 identity 获取详情(含点击量+1) | 有 | `logic/post/get_by_identity.go` | 已实现 |
|
|||
|
|
| Post | GetByKey | `/cms.Post/GetByKey` | 按 key(hash) 获取详情 | 有 | `logic/post/get_by_key.go` | 已实现 |
|
|||
|
|
| Post | Search | `/cms.Post/Search` | 标题/正文关键词搜索 | 有 | `logic/post/search.go` | 已实现(分页缺陷) |
|
|||
|
|
| Post | Create | `/cms.Post/Create` | 创建文章+关联 | 有 | `logic/post/create.go` | 已实现 |
|
|||
|
|
| Post | Modify | `/cms.Post/Modify` | 修改文章 | 有 | `logic/post/modify.go` | 已实现(无归属校验) |
|
|||
|
|
| Post | Delete | `/cms.Post/Delete` | 删除文章+关联 | 有 | `logic/post/delete.go` | 已实现(无归属校验) |
|
|||
|
|
| Post | IncrPostLike / DescPostLike | `/cms.Post/IncrPostLike` 等 | 文章点赞/取消 | 有 | `logic/post/incr_post_like.go` 等 4 文件 | 已实现(无去重) |
|
|||
|
|
| Post | CommentList | `/cms.Post/CommentList` | 评论分页(树形) | **无(已注释)** | `logic/post/comment_list.go:24-27` | 已实现 |
|
|||
|
|
| Post | AddComment | `/cms.Post/AddComment` | 发布评论(更新计数) | 有 | `logic/post/add_comment.go` | 已实现(panic 风险) |
|
|||
|
|
| Post | ModifyComment | `/cms.Post/ModifyComment` | 修改评论内容 | 有 | `logic/post/modify_comment.go` | 已实现(无归属校验) |
|
|||
|
|
| Post | DeleteComment | `/cms.Post/DeleteComment` | 删除评论(含子级) | 有 | `logic/post/delete_comment.go` | 已实现(无归属校验) |
|
|||
|
|
| Post | IncrCommentLike/DescCommentLike/IncrCommentUnlike/DescCommentUnlike | `/cms.Post/...` | 评论赞/踩 | 有 | `logic/post/incr_comment_like.go` 等 4 文件 | 已实现(无去重) |
|
|||
|
|
| Category | Fetch/Create/Modify/Delete | `/cms.Category/...` | 分类 CRUD + 树形列表 | 有 | `logic/category/*` | 已实现 |
|
|||
|
|
| Pages | Fetch/GetByIdentity/GetByKey/Create/Modify/Delete | `/cms.Pages/...` | 页面 CRUD + 查询 | 有 | `logic/pages/*` | 已实现(GetByIdentity 点击量不增) |
|
|||
|
|
| Tags | Fetch/Create/Modify/Delete | `/cms.Tags/...` | 标签 CRUD + 列表 | 有 | `logic/tags/*` | 已实现 |
|
|||
|
|
| **Site** | **Fetch** | `/cms.Site/Fetch` | 站点列表 | 有 | `logic/site/fetch.go` | **占位:返回空** `site/fetch.go:18,20` |
|
|||
|
|
| **Site** | **Get** | `/cms.Site/Get` | 站点详情 | 有 | `logic/site/get.go` | **占位:返回 nil** `site/get.go:24` |
|
|||
|
|
| **Site** | **Create** | `/cms.Site/Create` | 新增站点 | 有 | `logic/site/create.go` | **占位:成功但不落库** `site/create.go:20-22` |
|
|||
|
|
| **Site** | **Modify** | `/cms.Site/Modify` | 修改站点 | 有 | `logic/site/modify.go` | **占位:成功但不落库** `site/modify.go:21-23` |
|
|||
|
|
| **Site** | **Delete** | `/cms.Site/Delete` | 删除站点 | 有 | `logic/site/delete.go` | **占位:成功但不删** `site/delete.go:27` |
|
|||
|
|
|
|||
|
|
> 对照 proto(`post.proto`/`pages.proto`/`category.proto`/`tags.proto`/`site.proto`)与 `internal/server/*_server.go` + `service/expose.go`,5 个服务的所有 rpc 均已在 gRPC 与 Gateway 注册;不存在"声明但未注册"的方法。但 **`Site` 全部 5 个方法为"已注册、无逻辑"的占位实现**(代码内含 `// TODO: add your logic code & delete this line.`),且 `cms_site` 表已通过 `init()` 纳入自动迁移,却无任何写入路径——属于"声明但未实现"。
|
|||
|
|
|
|||
|
|
## 4. 数据模型与表
|
|||
|
|
|
|||
|
|
> 模型 gorm tag 存在反直觉之处:`cms_post.types` 为 `int32` 但 `gorm:"type:varchar(255)"`(`cms_post.go:26`);关联表 `CmsRelateCategory.Category`、`CmsRelateTags.Tags` 的 `foreignKey` 写反(`cms_relate_category.go:12`、`cms_relate_tags.go:13`),导致 `Preload("Categories.Category")`/`Preload("Tags.Tags")` 关联数据取不到(见 6.2)。
|
|||
|
|
|
|||
|
|
### 表 `cms_post`(经 GORM 自动迁移创建)
|
|||
|
|
|
|||
|
|
| 字段 | 类型 | 键/约束 | 说明 |
|
|||
|
|
| --- | --- | --- | --- |
|
|||
|
|
| `id` | uint | PK | 自增主键 |
|
|||
|
|
| `identity` | varchar(36) | index | 业务唯一标识(UUID) |
|
|||
|
|
| `owner_id`/`owner_identity` | - | - | 所有者(来自 `Std_Owner`) |
|
|||
|
|
| `site_identity` | varchar(36) | index | 站点标识 |
|
|||
|
|
| `types` | int32 | `gorm:"type:varchar(255)"` | 文章类型(tag 与字段类型矛盾) |
|
|||
|
|
| `title` | varchar(255) | - | 标题 |
|
|||
|
|
| `hash` | varchar(255) | uniqueIndex | URL 唯一键 |
|
|||
|
|
| `content`/`description`/`cover_path`/`target_url`/`source_url` | text/varchar | - | 内容/描述/封面/外链 |
|
|||
|
|
| `author`/`author_identity` | varchar | - | 作者 |
|
|||
|
|
| `hits`/`like_hits`/`unlike_hits`/`comment_hits` | int64 | default 0 | 统计计数 |
|
|||
|
|
| `has_accessory` | bool | - | 是否有附件 |
|
|||
|
|
| `categories`/`tags`/`accessories` | 关联 | - | 关联 `CmsRelateCategory`/`CmsRelateTags`/`CmsAccessory` |
|
|||
|
|
|
|||
|
|
### 表 `cms_comment`
|
|||
|
|
|
|||
|
|
| 字段 | 类型 | 键/约束 | 说明 |
|
|||
|
|
| --- | --- | --- | --- |
|
|||
|
|
| `id` | uint | PK(`gorm.Model`) | - |
|
|||
|
|
| `identity` | - | (来自 `Std_Identity`) | 业务标识 |
|
|||
|
|
| `post_identity`/`owner_identity`/`owner_name`/`role` | varchar | index | 归属/作者 |
|
|||
|
|
| `parent_id` | uint | index | 父评论 id(0 为顶级) |
|
|||
|
|
| `reply_identity` | varchar(36) | index | 被回复评论 |
|
|||
|
|
| `Cms` | text | - | 评论内容 |
|
|||
|
|
| `hits`/`like_hits`/`unlike_hits`/`comment_hits` | int64 | default 0 | 计数 |
|
|||
|
|
|
|||
|
|
### 表 `cms_category` / `cms_tags` / `cms_pages` / `cms_site`
|
|||
|
|
|
|||
|
|
| 表 | 关键字段 | 说明 |
|
|||
|
|
| --- | --- | --- |
|
|||
|
|
| `cms_category` | `category_key` uniqueIndex、`title` not null、`parent_id` index、`Children` 自关联 | 层级分类 |
|
|||
|
|
| `cms_tags` | `title` not null、`cover_path`、`intro` | 标签(无 uniqueKey 约束) |
|
|||
|
|
| `cms_pages` | `key` uniqueIndex、`title`/`content` 默认空串、`hits` 计数、`Tags`/`Accessories` 关联 | 单页 |
|
|||
|
|
| `cms_site` | `title`/`description`/`icon_path`/`keywords`/`domain`/`seo`/`theme`/`configs` 均 `not null` | 站点配置(**无任何写入路径**) |
|
|||
|
|
|
|||
|
|
### 关联表 `cms_relate_category` / `cms_relate_tags` / `cms_accessory`
|
|||
|
|
|
|||
|
|
| 表 | 关键字段 | 说明 |
|
|||
|
|
| --- | --- | --- |
|
|||
|
|
| `cms_relate_category` | `identity` uniqueIndex、`post_identity` index、`category_identity` index、`Category` 关联 | 文章-分类(foreignKey 写反) |
|
|||
|
|
| `cms_relate_tags` | `post_identity`/`pages_identity`/`tags_identity` index、`Tags` 关联 | 文章/页面-标签(foreignKey 写反) |
|
|||
|
|
| `cms_accessory` | `post_id`/`post_identity`/`pages_id`/`pages_identity`、`file_path` not null | 附件 |
|
|||
|
|
|
|||
|
|
## 5. 核心流程
|
|||
|
|
|
|||
|
|
```mermaid
|
|||
|
|
flowchart TD
|
|||
|
|
A["POST /cms.Post/IncrPostLike"] --> B["ParseMetaCtx 校验登录态"]
|
|||
|
|
B --> C["校验 op_identity / post_identity 非空"]
|
|||
|
|
C --> D["IncrOrDescPostField: UPDATE cms_post SET like_hits = like_hits + 1 WHERE identity=?"]
|
|||
|
|
D --> E["返回 OK"]
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
```mermaid
|
|||
|
|
flowchart TD
|
|||
|
|
A["POST /cms.Post/AddComment"] --> B["ParseMetaCtx 取 auth"]
|
|||
|
|
B --> C["impl.DBService.Table('mall_staff').Take(&authName, 'identity=?', auth.Identity)"]
|
|||
|
|
C --> D{"命中 mall_staff 行?"}
|
|||
|
|
D -- "否 / name 为 NULL" --> P["authName['name'].(string) 触发 panic → 500"]
|
|||
|
|
D -- "是" --> E["事务: 写 cms_comment + post.comment_hits +1 + 父评论 comment_hits +1"]
|
|||
|
|
E --> F["返回 comment identity"]
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 6. 审计发现
|
|||
|
|
|
|||
|
|
### 6.1 安全
|
|||
|
|
|
|||
|
|
| 级别 | 位置 | 问题 |
|
|||
|
|
| --- | --- | --- |
|
|||
|
|
| **高** | `internal/logic/post/delete.go:29`、`modify.go:15-22`、`delete_comment.go:24`、`modify_comment.go:33` | **越权删除/修改**:文章 `Delete`/`Modify`、评论 `DeleteComment`/`ModifyComment` 只校验"是否登录",不校验调用者是否为资源 owner/作者。`DeletePost(in.Identity, "")` 第二参 `authorIdentity` 恒为空且函数体未使用(`query.go:207`),`ModifyComment` 直接 `Where("identity=?", in.Identity).Updates(data)`。任意登录用户可删改他人文章/评论。 |
|
|||
|
|
| **高** | `internal/logic/post/search.go:31-34` | **无分页上限 / 可拖全表**:`if pageSize < 50 { pageSize = 50 }` 把小于 50 的请求强制放大到 50,且**完全没有 `page_size > 50` 的上限**。客户端传 `page_size=1000000` 即可一次拉走整张 `cms_post`(含 `content` 大字段),造成全表返回与读取放大(DoS)。proto 注释明确"最多 50",实现与之相反。 |
|
|||
|
|
| 中 | `internal/logic/post/comment_list.go:24-27` | `CommentList` 的 `ParseMetaCtx` 被整段注释,评论列表接口**对匿名开放**,可遍历任意文章全部评论与作者信息。 |
|
|||
|
|
| 中 | `internal/logic/post/incr_post_like.go:38`、`incr_comment_like.go:29` 等 4+4 处 | **赞/踩无去重、可刷量**:`op_identity` 仅做"非空"校验,从不记录"谁点过",同一用户可重复 `IncrPostLike` 无限累加;`Desc*` 路径可把计数刷成负数(无 floor 0 约束)。模型与计数仅用 `int64` 无 `CHECK`。 |
|
|||
|
|
| 低 | `internal/logic/tags/create.go:28-30` | 标签重复创建走 `First` 命中即返回 `errcode.ErrDB`(不是"已存在"语义),且 dedup 依赖"先查后插"非原子,存在 TOCTOU 竞态。 |
|
|||
|
|
|
|||
|
|
### 6.2 正确性与逻辑缺陷
|
|||
|
|
|
|||
|
|
| 级别 | 位置 | 问题 |
|
|||
|
|
| --- | --- | --- |
|
|||
|
|
| **高** | `internal/logic/post/add_comment.go:35-40` | **取 mall_staff 名字 panic**:`Take(&authName, "identity=?", auth.Identity)` 若当前用户不在 `mall_staff` 表(如普通会员/客户),`Take` 返回 `ErrRecordNotFound`,紧随其后的 `authName["name"].(string)` 对缺失 key 取到 `nil` 触发 **panic**(服务端 500,且 gRPC 默认无 recover 会中断该请求链路)。即便命中,`name` 列为 NULL 同样 panic。 |
|
|||
|
|
| **高** | `internal/models/query.go:303-334`(`DeleteComment`)+ `277-297`(`AddComment`) | **评论计数不一致**:`AddComment` 中**每条**评论(含子评论)都会让 `post.comment_hits +1`;而 `DeleteComment` 删除父评论时只做 `post.comment_hits -1`,再级联删子评论(`parent_id = comment.ID`)。当父评论下有 N 个子评论被一并删除时,文章评论数只减 1,实际应减 N+1 → 计数长期偏离真实值。 |
|
|||
|
|
| **高** | `internal/logic/pages/get_by_identity.go:21,76` | **页面点击量不增**:`GetPages("Cms_pages.identity", in.Identity)` 查询用 `identity`,但计数更新写成 `Where("key = ?", val)`(`val` 是 identity 值而非 `key`),匹配不到行,`hits` 永远不 +1。仅 `GetByKey` 路径(`GetPages("key", in.Key)`)计数正确。 |
|
|||
|
|
| 中 | `internal/models/cms_relate_category.go:12`、`cms_relate_tags.go:13` | **Preload 外键写反**:`Category CmsCategory gorm:"foreignKey:Identity;references:CategoryIdentity"` 实际生成 `cms_category.category_identity = cms_relate_category.identity`(应为 `cms_category.identity = cms_relate_category.category_identity`);`Tags` 同理。导致 `Preload("Categories.Category")`/`Preload("Tags.Tags")`(`query.go:136,242`、`post/fetch.go:52-72`、`get_by_identity.go:68-79`、`pages/fetch.go:52-62`)取到的分类/标签对象为空或错位,列表与详情的分类/标签数据失真。 |
|
|||
|
|
| 中 | `internal/models/query.go:162-169` | **修改文章时附件路径被覆盖**:`ModifyPost` 的 `accessory` 参数实际接收的是 `AccessoryIdentityArray`(`post/modify.go:43`),却把每个字符串直接赋给 `CmsAccessory.FilePath`(`FilePath: v`)。即修改文章后,附件记录的文件路径会被写成"附件 identity 字符串",原 `file_path` 丢失。 |
|
|||
|
|
| 中 | `internal/logic/pages/modify.go:33` vs `create.go:46` | **页面 key 不一致**:`Create` 用 `utils.FormatKey(in.Key)` 格式化,`Modify` 直接 `Key: in.Key` 不做格式化,二者对同名 key 可能写入不同值,破坏 uniqueIndex 一致性。 |
|
|||
|
|
| 中 | `internal/logic/tags/create.go:19,28-33` | **死代码导致语义错误**:`var cnt int64 = 0` 声明后从未赋值,下方 `if cnt > 0 { return ... ErrAlreadyExists }` 永远为假;重复标签判定实际依赖 `First` 的 `err == nil`,但返回的是 `errcode.ErrDB` 而非"已存在"。 |
|
|||
|
|
| 低 | `internal/models/cms_post.go:26` | `Types int32` 但 gorm `type:varchar(255)`;`PostList` 用 `cms_post.types = ?`(int)比较 varchar 列(`query.go:130`),类型语义错位,依赖数据库隐式转换。 |
|
|||
|
|
| 低 | `internal/logic/post/modify.go:38`、`post/delete.go:30` | 错误透传不一致:`ModifyPost`/`DeletePost` 失败统一返回 `errcode.ErrDB`,但 `modify_comment.go:34` 把原始 `err` 直接返回,错误体格式不统一。 |
|
|||
|
|
|
|||
|
|
### 6.3 未完成实现
|
|||
|
|
|
|||
|
|
| 级别 | 位置 | 问题 |
|
|||
|
|
| --- | --- | --- |
|
|||
|
|
| **高** | `internal/logic/site/{create,modify,delete,fetch,get}.go` | **Site 全系列占位**:`Fetch`/`Get` 仅 `TODO` 后返回空(`site/fetch.go:18,20`、`site/get.go:24`);`Create`/`Modify`/`Delete` 解析登录态后直接 `return StatusReply{OK}` 不落库/不删除(`site/create.go:20-22`、`site/modify.go:21-23`、`site/delete.go:27`)。proto 与 server 已对外暴露 `cms.Site`,调用方会拿到"成功但无数据/数据不变"的假象。`cms_site` 表已迁移却无任何写入路径。 |
|
|||
|
|
| 中 | `internal/models/query.go:346` | 评论点赞明确留 `// TODO: 记录点赞对象避免重复点赞`,去重能力长期缺失(与 6.1 赞踩刷量同一根因)。 |
|
|||
|
|
| 低 | `internal/logic/post/delete.go:29`、各 `Modify`/`Delete` 的 `// Todo:` 注释 | 归属校验、作者校验多处以 `// Todo:` 留空,未落地。 |
|
|||
|
|
|
|||
|
|
### 6.4 健壮性与可维护性
|
|||
|
|
|
|||
|
|
| 级别 | 位置 | 问题 |
|
|||
|
|
| --- | --- | --- |
|
|||
|
|
| 中 | `internal/models/query.go:119`、`category/fetch.go:20`、`pages/get_by_identity.go:71`、`post/comment_list.go:44` | 多处 `.Debug()` 打开 SQL 全量日志,生产环境会刷屏并可能泄露数据内容。 |
|
|||
|
|
| 低 | `internal/logic/post/comment_list.go:30`、`category/fetch.go:21`、`tags/fetch.go:21` | 调试用 `fmt.Println("err = ", ...)`/`fmt.Println("in = ", in)` 残留,泄漏请求参数到标准输出。 |
|
|||
|
|
| 低 | `internal/logic/post/ext.go`、`pages/ext.go` | `fmtKey` 函数定义但从未被调用(create/modify 用的是 `utils.FormatKey`),属死代码。 |
|
|||
|
|
| 低 | `internal/logic/tags/delete.go:24` | 删除标签时不清理 `cms_relate_tags`,遗留孤儿关联(文章/页面仍指向已删标签)。 |
|
|||
|
|
| 低 | `internal/logic/category/delete.go:23` → `query.go:370-389` | `DeleteCategory` 仅禁止"有子分类"的删除,未检查分类是否仍被 `cms_relate_category` 引用,删除被引用的分类会留下孤儿关联。 |
|
|||
|
|
| 低 | 全模块 | **无任何 `*_test.go` 单元测试**;36 个 `.http` 仅为手工调用示例。上述 panic、计数不一致、分页越界等问题若有单测可早期暴露。 |
|
|||
|
|
| 低 | `internal/config/config.go:49` | `conf.NotNil(Spec.Service, Spec.Cache)` 未校验 `Databases`;但 `impl.go:43` 的 `with.Databases` 多数实现在配置缺失时返回 nil,首个查询即空指针(`pkgs/all` 已注入共享 DB 故当前不触发,独立运行时风险高)。 |
|
|||
|
|
|
|||
|
|
## 7. 风险汇总
|
|||
|
|
|
|||
|
|
| 编号 | 级别 | 问题 | 影响面 |
|
|||
|
|
| --- | --- | --- | --- |
|
|||
|
|
| C1 | 高 | 文章/评论 增/删/改 不校验归属(越权) | 数据完整性、内容安全 |
|
|||
|
|
| C2 | 高 | `AddComment` 未命中 `mall_staff` 时 `authName["name"].(string)` panic | 接口可用性(500/链路中断) |
|
|||
|
|
| C3 | 高 | `Search` 无 `page_size` 上限(可拖全表) | 性能/DoS、数据泄露 |
|
|||
|
|
| C4 | 高 | 删除父评论时 `post.comment_hits` 计数不一致 | 统计数据失真 |
|
|||
|
|
| C5 | 高 | `Site` 全系列占位空实现(声明未实现) | 功能缺失、调用方误判成功 |
|
|||
|
|
| C6 | 高 | 赞/踩无去重且可刷成负数 | 互动数据可信性、刷量 |
|
|||
|
|
| C7 | 中 | `GetByIdentity`(页面) 点击量不增 | 统计失真 |
|
|||
|
|
| C8 | 中 | `CommentList` 鉴权被注释(匿名可读全部评论) | 信息泄露 |
|
|||
|
|
| C9 | 中 | 关联表 Preload 外键写反,分类/标签数据取不到 | 列表/详情数据错误 |
|
|||
|
|
| C10 | 中 | `ModifyPost` 附件路径被写成 identity 字符串 | 附件数据损坏 |
|
|||
|
|
| C11 | 中 | 页面 `Modify` 的 key 未格式化,与 `Create` 不一致 | 唯一键语义混乱 |
|
|||
|
|
| C12 | 中 | `Tags.Create` 死代码 `cnt` + dedup 返回 ErrDB | 重复标签处理语义错误 |
|
|||
|
|
| C13 | 低 | 多处 `.Debug()` + `fmt.Println` 残留 | 日志泄露、性能 |
|
|||
|
|
| C14 | 低 | 标签/分类删除遗留孤儿关联 | 数据一致性 |
|
|||
|
|
| C15 | 低 | 无单测、死代码 `fmtKey` | 可维护性 |
|
|||
|
|
| C16 | 低 | `config.NotNil` 未校验 `Databases` | 独立运行崩溃风险 |
|
|||
|
|
|
|||
|
|
## 8. 修复建议(务实项)
|
|||
|
|
|
|||
|
|
1. **越权(C1)**:在 `post.Delete`/`post.Modify`/`post.DeleteComment`/`post.ModifyComment` 中,先按 identity 取资源,校验 `owner_identity`/`author_identity` 与 `ParseMetaCtx` 返回的登录身份一致(或调用方具备管理角色)再执行;`DeletePost` 应接收并比较 `authorIdentity`。不要新增抽象层,仅在现有函数开头加一段归属判定。
|
|||
|
|
2. **评论 panic(C2)**:`add_comment.go:35-40` 不要用 `map[string]any` + 类型断言取 `name`。改为先用 `Select("name")` 查 `mall_staff`,对 `ErrRecordNotFound`/NULL 给出兜底(如空串或调用方已下发的昵称),删除 `.(string)` 断言。
|
|||
|
|
3. **分页上限(C3)**:`search.go:31` 改为"若 `page_size<=0` 默认 10,若 `page_size>50` 截断为 50",与 `Fetch` 的既有逻辑(`post/fetch.go:18-23`)对齐;同时给 `pages.Fetch`/`tags.Fetch` 等补上分页(当前 tags 列表无分页)。
|
|||
|
|
4. **计数一致性(C4)**:`DeleteComment` 级联删除子评论时,按"被删评论及子评论总条数"一次性 `post.comment_hits - N`;或在删除前 `Count` 子评论数量后再减,避免只减 1。
|
|||
|
|
5. **页面点击量(C7)**:`get_pages.go:76` 的 `Where("key = ?", val)` 改为 `Where("identity = ?", val)`(与查询 key 一致);或统一用 `in.Identity` 作为更新条件,与 `GetPost`(`query.go:248`)保持一致写法。
|
|||
|
|
6. **Preload 外键(C9)**:把 `CmsRelateCategory.Category`、`CmsRelateTags.Tags` 的 gorm tag 改为 `foreignKey:CategoryIdentity;references:Identity` / `foreignKey:TagsIdentity;references:Identity`,使 `Preload` 正确取到分类/标签对象。
|
|||
|
|
7. **赞/踩去重(C6,对应 TODO `query.go:346`)**:在 `IncrOrDescPostField`/`IncrOrDescCommentField` 前,先查"该 op_identity 是否已对目标点过赞"的记录(新增轻量关系表或唯一索引),已点过则忽略/抵消;并对 `desc` 路径加 `WHERE col > 0` 约束防止负数。
|
|||
|
|
8. **Site 占位(C5)**:要么补齐 `site/*` 五个方法的真实读写逻辑(建/改/删走 `models.CmsSite`、列表/详情查询),要么在 proto 与 server 中移除该服务以免对外暴露假成功;当前不应让其返回"OK 但无副作用"。
|
|||
|
|
9. **清理(C13)**:删除 `comment_list.go:30`、`category/fetch.go:21`、`tags/fetch.go:21` 的 `fmt.Println`,以及 `query.go`/`get_pages.go`/`comment_list.go`/`category/fetch.go` 的 `.Debug()`;删除未使用的 `fmtKey`(`post/ext.go`、`pages/ext.go`)。
|
|||
|
|
10. **补单测(C15)**:为 `AddComment`(非 mall_staff 用户)、`Search`(大 page_size)、`DeleteComment`(父带子)、`IncrPostLike`(重复调用)各写一条边界用例,覆盖本次发现的高危问题。
|
|||
|
|
|
|||
|
|
> 本报告只列出与现有实现直接相关的修复项,不引入统一框架、DTO/VO 层、DDD/CQRS 等额外抽象。所有结论均附 `文件:行号` 证据;未发现证据处已标注【信息不足】或明确为推断。
|