--- name: dao-tracing-refactor overview: 按 UserCreate 的链路追踪模式,为 mysqldao(13 文件) 与 redisdao(10 文件) 中所有 DAO 方法/函数统一补全 tracing span(Start/defer End/RecordError),并保持增删改查list 顺序。 todos: - id: mysqldao-batch-a content: 按样板改造 mysqldao 的 user.go 剩余方法、auditlog.go、closet.go、clothes.go、collect.go、goods.go,补充 Span 追踪与错误记录 status: completed - id: mysqldao-batch-b content: 改造 mysqldao 的 horoscope.go、like.go、notification.go、order.go、outfit.go、quota.go、vipflowlog.go,处理 RowsAffected 分支并加追踪 status: in_progress dependencies: - mysqldao-batch-a - id: redisdao-batch-a content: 改造 redisdao 的 auth.go、closet.go、collect.go、horoscope.go、like.go,处理管道 Exec 与 redis.Nil 豁免并加追踪 status: pending - id: redisdao-batch-b content: 改造 redisdao 的 lock.go、notification.go、outfit.go、quota.go、user.go,处理 Lua 脚本与 redis.Nil 豁免(user.go 补充 go-redis 导入) status: pending dependencies: - redisdao-batch-a - id: verify-build content: 执行 go build ./... 与 go vet ./... 验证编译通过、无新增告警且语义不变 status: pending dependencies: - mysqldao-batch-b - redisdao-batch-b --- ## 产品概述 为 `zt-closet-backend` 的数据访问层(DAO)统一接入 OpenTelemetry 链路追踪能力,使 MySQL 与 Redis 的每个数据操作方法都具备可观测的 Span 追踪和错误记录,便于分布式追踪与线上问题定位。 ## 核心功能 - 以 `UserCreate` 方法为改造样板,为 `internal/mysqldao` 下全部 DAO 方法(13 个文件)补充统一的 Span 追踪与错误记录逻辑。 - 为 `internal/redisdao` 下全部包级函数/方法(10 个文件)补充相同风格的 Span 追踪与错误记录逻辑。 - 统一改造模式:方法开头启动 Span(命名 `dao.<方法名>`)→ `defer span.End()` → 执行原有 DB/缓存调用 → 出错时先 `span.RecordError(err)` 再返回。 - 保持各方法原有业务逻辑、入参与返回值语义完全不变,仅新增追踪与错误记录。 - 对 Redis 查询"未命中"(`redis.Nil`)等属于正常业务分支的场景不记录为错误,避免污染追踪链路。 - 维持既有 "增-删-改-查-list" 的方法排列顺序。 ## 技术栈 - 语言:Go - ORM:GORM(`gorm.io/gorm`),已通过 `d.db.WithContext(ctx)` 传递上下文 - 缓存:go-redis v9(`github.com/redis/go-redis/v9`) - 追踪:OpenTelemetry,全局追踪器 `global.Tracer`(定义于 `global/otel.go` 的 `var Tracer trace.Tracer`) ## 实现方式 基于用户提供的 `UserCreate` 样板,对每个 DAO 方法套用统一的追踪装饰模式。这是纯增量式改造,不改变任何业务语义与函数签名,风险可控、易于批量执行。 ### 改造模板(mysqldao,结构体方法) ``` func (d *XxxDao) XxxCreate(ctx context.Context, ...) (...) { ctx, span := global.Tracer.Start(ctx, "dao.XxxCreate") defer span.End() if err := d.db.WithContext(ctx).Create(x).Error; err != nil { span.RecordError(err) return err } return nil } ``` - 单行 `return ...Error` 需改写为 `if err := ...; err != nil { span.RecordError(err); return err }` 形式。 - 带 `RowsAffected` 返回值的方法(如 `QuotaIncreaseUsed`、`OrderUpdateStatusByOrderNo`):先判断 `res.Error`,记录后再返回,保留 `RowsAffected > 0` 判断逻辑。 - 查询类方法(`First`/`Find`)出错统一 `span.RecordError(err)` 后返回。 - `ctx` 必须重新赋值(`ctx, span := ...`),确保新 Span 的上下文继续向下游 `WithContext(ctx)` 传递。 ### 改造模板(redisdao,多为包级函数) ``` func XxxSet(ctx context.Context, ...) error { ctx, span := global.Tracer.Start(ctx, "dao.XxxSet") defer span.End() if err := global.RedisEngine.Set(ctx, key, val, ttl).Err(); err != nil { span.RecordError(err) return err } return nil } ``` - redisdao 方法原多为 `return xxx.Err()` / `return xxx.Result()`,需先捕获 error/result 再记录并返回。 - 事务管道(`TxPipeline` + `Exec`,见 `auth.go`):对 `pipe.Exec(ctx)` 返回的 err 做记录后返回。 - Lua 脚本调用(`lock.go`、`quota.go`):对 `script.Run(...)` 返回结果的 error 做记录后返回。 - **`redis.Nil` 处理策略**:对 Get 类"未命中属正常"的方法(如 `UserInfoGet`、`AuthUserIDByAccess`、`QuotaRemainGet`),仅当 `err != nil && err != redis.Nil` 时才调用 `span.RecordError(err)`,其余错误正常记录;`redis.Nil` 仍原样返回给调用方,语义不变。`quotaDailyValue` 已内部吞掉 `redis.Nil`,无需特殊处理。 ### 实现要点 - **保持顺序**:延续既有 "增删改查list" 排列,不调整方法位置。 - **import**:mysqldao 各文件已含 `global`;redisdao 各文件已含 `global`,对需判断 `redis.Nil` 的文件确认已导入 `github.com/redis/go-redis/v9`(`user.go` 当前未导入,需新增)。 - **零行为变更**:不改动 SQL/Redis 命令、条件、返回值与错误传播路径,仅叠加追踪。 - **性能**:Span 创建为轻量操作,DAO 均为单次 I/O,无额外热点开销;不引入 N+1 或重复遍历。 - **安全**:仅记录 error 对象,不写入用户敏感数据(避免 PII 进入追踪属性)。 ## 目录结构 ``` zt-closet-backend/ ├── internal/ │ ├── mysqldao/ │ │ ├── user.go # [MODIFY] UserCreate 已完成;改造 UserUpdateByID/UserGetByID/UserGetByUUID/UserGetByOpenID │ │ ├── auditlog.go # [MODIFY] ClosetAuditLog* 与 OutfitAuditLog* 全部方法加追踪 │ │ ├── closet.go # [MODIFY] ClosetShare* 与 ClosetShareItem* 全部方法加追踪 │ │ ├── clothes.go # [MODIFY] Clothes* 全部方法加追踪 │ │ ├── collect.go # [MODIFY] 收藏相关 DAO 方法加追踪 │ │ ├── goods.go # [MODIFY] 商品相关 DAO 方法加追踪 │ │ ├── horoscope.go # [MODIFY] 星座相关 DAO 方法加追踪 │ │ ├── like.go # [MODIFY] 点赞相关 DAO 方法加追踪 │ │ ├── notification.go # [MODIFY] 通知相关 DAO 方法加追踪 │ │ ├── order.go # [MODIFY] Order* 与 RefundFlowLog* 方法加追踪(含 RowsAffected 分支) │ │ ├── outfit.go # [MODIFY] 穿搭相关 DAO 方法加追踪 │ │ ├── quota.go # [MODIFY] Quota*/UserDailyUsage*/QuotaFlowLog* 方法加追踪(含 RowsAffected 分支) │ │ └── vipflowlog.go # [MODIFY] VIP 流水相关 DAO 方法加追踪 │ └── redisdao/ │ ├── auth.go # [MODIFY] AuthTokenSet/Del(管道 Exec)、AuthUserIDBy*(redis.Nil 豁免) │ ├── closet.go # [MODIFY] 衣柜缓存相关函数加追踪 │ ├── collect.go # [MODIFY] 收藏缓存相关函数加追踪 │ ├── horoscope.go # [MODIFY] 星座缓存相关函数加追踪 │ ├── like.go # [MODIFY] LikeAdd/Remove/IsMember/Count 加追踪 │ ├── lock.go # [MODIFY] TryLock/Unlock(Lua 脚本)加追踪 │ ├── notification.go # [MODIFY] 通知缓存相关函数加追踪 │ ├── outfit.go # [MODIFY] 穿搭缓存相关函数加追踪 │ ├── quota.go # [MODIFY] Quota 缓存/Lua 脚本相关函数加追踪 │ └── user.go # [MODIFY] UserInfoSet/Del/Get(Get 需 redis.Nil 豁免,新增 go-redis 导入) ├── global/ │ └── otel.go # [无需修改] 提供 global.Tracer ```