Files
full/docs/ads.md
2026-09-22 18:53:53 +08:00

13 KiB
Raw Blame History

ads公共广告位内容分发代码审计报告

内容
审计对象 module/base/ads
服务域 基础与平台服务
审计日期 2026-09-22
代码规模 手写 Go 文件 12 个、385 行(含 test/rpc/rpc.go 23 行空壳);proto 2 个(ads.protoconst.protopb/ 生成文件 4 个
入口 cmd/maingRPC + HTTP Gateway 单进程)、聚合入口 pkgs/allpkgs/all/internal/service/ads.go
对外协议 gRPC + REST GatewayREST 路径 /ads.Fetch/ByPos
结论摘要 全模块只有 1 个只读接口 ByPos,落库查询用 pos_key + status=1 过滤,能正确屏蔽未启用广告;但 README 宣称的"Redis 缓存 10 分钟 / 广告位 30 分钟缓存"从未实现RedisService/MemorySerice 初始化后无任何引用),因此不存在缓存一致性问题;鉴权策略在"模块 yaml 声明匿名"与"聚合部署未放行"之间自相矛盾;conf.NotNil 未校验 Databases,缺失时启动直接 panic。

1. 服务定位与职责

按广告位 key 查询该广告位下已启用的广告内容(文本/图片/视频/音频/链接/附件),供客户端页面渲染。它只做"读取与分发",不提供广告位/广告的增删改,也不负责投放计费、定向或素材处理。

2. 代码结构与入口

路径 职责
cmd/main/main.go 独立进程入口:config.Newimpl.NewImplserver.New(nil)service.New(...).Start()Run()main() 分离供聚合入口复用
cmd/cli/main.go 命令行入口,仅 log.Println("广告服务命令行工具")8 行,占位)
internal/config/config.go 配置结构(Base/Databases/MicroService/Rpc/Gateway/APM/Etcd)与校验
internal/impl/impl.go 初始化 DB / Redis / Etcd / 内存缓存实例
internal/logic/fetch/by_pos.go ByPos 唯一业务逻辑:按 pos_key + status=1 查库并转 pb
internal/models/ads_item.go ads_item 表模型 + database.AppendMigrate 注册
internal/models/ads_pos.go ads_pos(广告位)表模型 + 迁移注册,全模块无读写
internal/server/fetch_server.go FetchServer.ByPos 转发到 logic/fetch
internal/server/new.go 注册 Fetch 到 gRPC独立运行时开 reflection
internal/routers 不存在(本模块走 gRPC + HTTP Gateway无 Gin 路由)
service/{expose,dependencies}.go 聚合宿主注入:Expose 注册 Gateway handlerapplyDependencies 覆盖 Redis/Etcd/DB/Cache
proto/{ads,const}.proto ads.proto 定义 Fetch.ByPosconst.proto 与本模块无关
etc/{ads_dev,ads_prod,ads_test}.yaml 三份内容一致Port 12216、Gateway 12102
test/rpc/rpc.go 整个 main 被注释23 行空壳)

3. 接口清单

方法 路径 功能 鉴权 实现位置
POST /ads.Fetch/ByPos 按广告位 key 返回已启用广告列表 无(模块 yaml 登记匿名,etc/ads_dev.yaml:15-16 internal/logic/fetch/by_pos.go
POST gRPC /ads.Fetch/ByPos 同上Gateway 反向代理同一 handler 同上 pb/ads.pb.gw.go:71,77internal/server/fetch_server.go:19
HEAD / 独立入口健康检查 【信息不足】由 git.apinb.com/bsm-sdk/core/service 提供,本模块未声明

声明但未实现/占位的接口:无。proto/ads.proto:6-9 只声明 Fetch.ByPos 一个 rpcpb/ads_grpc.pb.goFetchServer 接口也只有 ByPos,二者一致;不存在"proto 声明了但 server 未实现"的方法。

鉴权补充:etc/ads_dev.yaml:14MicroService.Enable: false,而 service.Start 只在 MsConf.Enable == true 时才注册服务并发布匿名清单(D:\work\bsm-sdk\core\service\service.go:66-90),因此独立部署时模块 yaml 的匿名清单不会生效,接口是否免鉴权完全取决于前置网关。

4. 数据模型与表

ads_iteminternal/models/ads_item.goGORM 自动迁移)

字段 类型 键/约束 说明
id uint PK 自增主键(来自 gorm.Model
created_at/updated_at/deleted_at timestamp - gorm.Model 提供,带软删
title varchar(255) not null 广告名称
pos_key varchar(255) not null 广告位 key无索引声明
content varchar(255) default '' 广告内容
type int default 0 广告类型,ContentType 常量从 1 起1 文本…6 附件)
to_url varchar(255) default '' 跳转链接
status int64 default 0, index 来自内嵌 types.Std_StatusD:\work\bsm-sdk\core\types\db.go:74-76

ads_posinternal/models/ads_pos.go

字段 类型 说明
id uint PK来自 types.Std_IdCreated
created_at timestamp 创建时间
key varchar(255) 广告位标识
name varchar(255) 广告位名称

该表模型在本模块内仅注册迁移(ads_pos.go:21-23全模块无任何读写ByPos 也不校验 pos_key 是否存在于 ads_pos

5. 核心流程

flowchart TD
    A["POST /ads.Fetch/ByPos"] --> B["校验 in.Key 非空,否则 ErrInvalidArgument"]
    B --> C["SELECT * FROM ads_item WHERE pos_key = ? AND status = 1"]
    C --> D["逐条映射为 pb.AdsItem含 CreatedAt 格式化)"]
    D --> E["返回 ByPosReply{data: [...]}"]
    C --> F["查询失败返回 errcode.ErrDB"]

6. 审计发现

6.1 安全

级别 位置 问题
etc/ads_dev.yaml:15-16etc/ads_prod.yaml:15-16etc/ads_test.yaml:15-16 - ads.Fetch.ByPos 被列入 MicroService.Anonymous,属无鉴权读取的设计声明;同时 MicroService.Enable: falseads_dev.yaml:14)使该清单在独立部署时根本不会发布到 etcd等于"声明了但不会生效",容易让部署方误判防护已就位。
pkgs/all/etc/default_dev.yaml:24-43 聚合入口的 Authorization.Anonymous 白名单里没有 /ads.Fetch/ByPos(只有 passport/mall/market 登录、`/rest/fts/ping
internal/logic/fetch/by_pos.go:21 广告查询只以 pos_key 为唯一凭据,无 app / 店铺 / 租户维度的归属过滤,ads_item 也没有归属字段。任何能调用该接口的客户端,只要知道 pos_key,就取到完全相同的一批广告;无法支持"按端/按租户下发不同广告"。

6.2 正确性与逻辑缺陷

级别 位置 问题
internal/logic/fetch/by_pos.go:21 Where("pos_key = ? AND status = ?", in.Key, 1)已正确过滤启用状态,不存在"可查未启用广告"的问题(按任务要求核实,特此说明)。
internal/logic/fetch/by_pos.go:21 FindLIMIT,单个 pos_key 下条目数不受约束,接口全量返回;大广告位会一次性吐出全部记录。
internal/models/ads_item.go:32:18-25 typedefault:0,但 ContentType 常量从 1 开始(CONTENT_TYPE_TEXT iota + 1)。0 无对应类型,历史数据或未显式赋值的记录会返回前端无法识别的 type
internal/models/ads_item.go:31 contentvarchar(255),而图片/视频/链接类广告的 content 实际承载 URL255 上限偏紧超长时不同数据库行为不同PostgreSQL 报错、MySQL 截断),仓库内 yaml 为 postgres【具体行为取决于驱动信息不足】。
internal/logic/fetch/by_pos.go:13,21 入参 ctx 未被使用,请求上下文(超时/取消)不会传导到 DB 查询,无法对慢查询做超时控制。

6.3 未完成实现

  • 缓存完全未落地README 属不实描述README.md:13-14"Redis缓存""智能缓存10分钟缓存策略")、README.md:110"Redis缓存加速缓存时间10分钟")、README.md:319-322"广告数据 10 分钟 / 广告位信息 30 分钟"缓存策略表)均宣称有缓存;但 internal/logic/fetch/by_pos.go:21 直接查库,internal/impl/impl.go:13,16,22,24 初始化的 RedisServiceMemorySerice 在本模块没有任何引用(全仓检索仅命中 internal/impl/impl.goservice/dependencies.go:21,30 的赋值点)。结论:不存在缓存,也就不存在缓存一致性问题README.md:315-339 的"性能优化/监控指标"章节同样与实现不符。
  • proto/const.proto:1-326 定义了 OrderSummaryItemFeedPostItemGroupPostItemRelationItemMarketLoginReply 等大量与广告无关的共享消息,本模块未引用任何一条 → 死定义,且被 pb/const.pb.go 一并编译进本模块。
  • internal/models/ads_pos.go:15-28ads_pos 模型仅注册迁移,无任何查询/写入 → 预留实现(表由其它模块维护)。
  • cmd/cli/main.go:1-8 仅打印一行;test/rpc/rpc.go:3-23 整个函数体被注释,且引用的是不存在的 pb.NewMethodClient/pb.Crc → 占位/死代码。
  • etc/ads_dev.yaml:26-29Rpc 段被注释,internal/config/config.go:20SrvConfig.Rpc 字段全模块无使用点 → 死配置。

6.4 健壮性与可维护性

级别 位置 问题
internal/config/config.go:37 conf.NotNil(Spec.Service, Spec.Cache) 未校验 Databases。而 with.DatabasesD:\work\bsm-sdk\core\with\databases.go:13-15)在 cfg == nil || len(cfg.Source) == 0 时直接 panic("No Database Source Found !"),因此缺 Databases 段时 impl.NewImpl()internal/impl/impl.go:26)在启动阶段就 panic,且报错文本不含配置项名,排查成本高。
internal/impl/impl.go:16 变量名拼写错误 MemorySerice(应为 MemoryService)。同一 workspace 的 pkgs/all/internal/impl 用的是正确拼写 MemoryService(见 pkgs/all/internal/service/ads.go:15),跨模块阅读容易误判为两个不同变量。
internal/impl/impl.go:13,16,22,24 RedisServiceMemorySerice 初始化后在本模块无使用点(EtcdService 仅透传给 service.Options.EtcdClientcmd/main/main.go:34)→ 死代码。
README.md:88-96 README 声明的 swagger/scripts/DockerfileMakefile 在仓库中均不存在(与本模块实际文件清单不符),属虚构文档。
全模块 无任何 *_test.gotest/rpc/rpc.go 为注释空壳,无法覆盖 ByPos 的过滤语义。
internal/logic/fetch/by_pos.go:15-17 只校验 in.Key == "",无长度/字符白名单与上限约束。

7. 风险汇总

编号 级别 问题 影响面
A1 鉴权策略两处矛盾:模块 yaml 声明 ByPos 匿名,聚合部署 Anonymous 未放行 上线 401 / 误判防护已生效
A2 广告读取仅凭 pos_key,无租户/端隔离 内容越权读取、无法按租户差异化投放
A3 Databases 未校验,缺失即启动 panic 且报错不明确 部署可用性、排障成本
A4 README 宣称的缓存/性能/监控能力全部未实现 文档误导、容量评估失真
A5 ByPos 无返回条数上限 大广告位全量返回、响应体膨胀
A6 死代码与死配置(const.protoads_poscmd/clitest/rpcRpc 段)+ 无自动化测试 可维护性

8. 修复建议(务实项)

  1. 鉴权策略对齐(二选一,必须选):若 ByPos 应当匿名,则把 /ads.Fetch/ByPos 加入 pkgs/all/etc/default_dev.yaml:24-43Authorization.Anonymous(并同步生产配置);若应当鉴权,则从 etc/ads_{dev,prod,test}.yaml:15-16 的匿名清单中删除该条。注意 MicroService.Enable: false 时模块匿名清单不会生效,不要把它当作生效依据。
  2. 配置校验internal/config/config.go:37conf.NotNil 补上 Databases;或在 impl.NewImpl 前显式判空并打印缺失的配置项名。
  3. 返回值上限internal/logic/fetch/by_pos.go:21Find 增加 .Limit(...)(如 100避免单广告位无界返回。
  4. 类型默认值internal/models/ads_item.go:32type 默认值改为 1(文本),或在 ByPos 中跳过 type == 0 的记录,避免下发未定义类型。
  5. 上下文传导by_pos.go 改用 impl.DBService.WithContext(ctx) 发起查询,使上游超时/取消能生效。
  6. 文档与死代码清理:删除/改写 README.md:13-14,110,319-339 中"Redis 缓存 10 分钟""广告位 30 分钟缓存""swagger/scripts/Dockerfile/Makefile"等不实内容;删除 proto/const.proto 与本模块无关的消息、test/rpc/rpc.goetc/ads_dev.yaml:26-29Rpc 注释段;变量名 MemorySericeMemoryService,并删除未使用的 RedisService/MemorySerice 初始化(或按其文档用途落地)。
  7. 补测试:为 ByPos 增加 2~3 条最小用例——命中启用广告、同 pos_key 下含 status != 1 的记录(须被过滤)、key 为空。

本报告只列出与现有实现直接相关的修复项,不引入新的分层、抽象封装或 DTO/VO 改造。