first commit
This commit is contained in:
@@ -0,0 +1,111 @@
|
||||
---
|
||||
name: 修复 redismodel/redisdao 语义与健壮性问题(P1+P2)
|
||||
overview: 修复 redismodel/redisdao 内部的日期类型不一致、Quota 缓存语义不清,以及扣减/读取的健壮性边界问题,不修改 middleware。
|
||||
todos:
|
||||
- id: fix-quota-key-defs
|
||||
content: 统一日期类型并重命名 redismodel/quota.go 的剩余配额 key
|
||||
status: completed
|
||||
- id: fix-quota-dao
|
||||
content: 修改 redisdao/quota.go 函数签名、剩余配额语义与 Lua 扣减逻辑
|
||||
status: completed
|
||||
dependencies:
|
||||
- fix-quota-key-defs
|
||||
- id: fix-notification-decr
|
||||
content: 用 Lua 脚本修复 notification.go 未读数扣减防负
|
||||
status: completed
|
||||
- id: verify-build
|
||||
content: 运行 go build、go vet 与 lint 验证编译及诊断通过
|
||||
status: completed
|
||||
dependencies:
|
||||
- fix-quota-key-defs
|
||||
- fix-quota-dao
|
||||
- fix-notification-decr
|
||||
---
|
||||
|
||||
## 产品概述
|
||||
|
||||
针对 `redismodel` 与 `redisdao` 二次校验后发现的 P1(语义/一致性)与 P2(健壮性边界)问题进行修复,仅限这两层内部改动,不动 `middleware`。
|
||||
|
||||
## 核心修复内容
|
||||
|
||||
- **P1.1 日期类型统一**:`quota` 模块每日计数 key 的日期由 `%s`(string)统一为 `%d`(int64 YYYYMMDD),与 `horoscope` 及 `mysqlmodel` 的 `StatDate int64` 对齐。
|
||||
- **P1.2 Quota 缓存语义澄清**:将 `KeyQuotaTotal`/`QuotaTotalSet/Get/Decr` 重命名为 `KeyQuotaRemain`/`QuotaRemainSet/Get/Decr`,明确 Redis 存的是「剩余可用配额」而非「总配额」,消除与 `mysqlmodel.Quota`(Total + Used)的语义错位。
|
||||
- **P2.1 扣减原子化与防负**:`QuotaRemainDecr` 改用 Lua 脚本原子扣减,key 不存在时不产生负数脏 key,剩余不足时不扣减。
|
||||
- **P2.2 每日计数未命中返回 0**:`quotaDailyValue` 对 `redis.Nil` 返回 `0, nil`,与 `NotificationUnreadGet` 行为一致。
|
||||
- **P2.3 未读计数扣减防负**:`NotificationUnreadDecr` 改用 Lua 脚本保证结果不低于 0。
|
||||
|
||||
## 技术栈
|
||||
|
||||
- 语言:Go
|
||||
- Redis 客户端:`github.com/redis/go-redis/v9`
|
||||
- 仅修改 3 个文件,沿用现有 `redismodel`(key 常量)+ `redisdao`(操作函数)分层与 `redis.NewScript` Lua 脚本模式。
|
||||
|
||||
## 实现方案
|
||||
|
||||
### P1.1 日期类型统一为 int64
|
||||
|
||||
`internal/redismodel/quota.go` 三个每日 key 的 `%s` 改为 `%d`,注释标注 `date(YYYYMMDD)`;`internal/redisdao/quota.go` 中 6 个 `QuotaDaily*` 函数签名 `date string` 改为 `date int64`。`fmt.Sprintf` 的 `%d` 直接接收 int64,无其它改动。
|
||||
|
||||
### P1.2 配额语义重命名为「剩余配额」
|
||||
|
||||
- `redismodel/quota.go`:`KeyQuotaTotal` 重命名为 `KeyQuotaRemain`,key 由 `quota:total:%d:%d` 改为 `quota:remain:%d:%d`,注释改为「剩余可用配额」。
|
||||
- `redisdao/quota.go`:`QuotaTotalSet/Get/Decr` 重命名为 `QuotaRemainSet/Get/Decr`,`QuotaRemainSet` 形参 `total` 改为 `remain`,函数注释同步说明存的是「剩余可用配额」。
|
||||
|
||||
### P2.1 配额扣减原子化(Lua)
|
||||
|
||||
新增 `quotaRemainDecrScript`,`QuotaRemainDecr` 通过脚本执行,单次 RTT、Redis 端原子,避免并发超扣与脏 key:
|
||||
|
||||
```
|
||||
local v = redis.call('GET', KEYS[1])
|
||||
if not v then return -1 end
|
||||
v = tonumber(v)
|
||||
local d = tonumber(ARGV[1])
|
||||
if v < d then return -2 end
|
||||
return redis.call('DECRBY', KEYS[1], d)
|
||||
```
|
||||
|
||||
返回值契约:
|
||||
|
||||
- `-1`:配额未初始化(key 不存在)
|
||||
- `-2`:剩余配额不足
|
||||
- `>= 0`:扣减成功后的剩余值
|
||||
|
||||
### P2.2 每日计数未命中返回 0
|
||||
|
||||
`quotaDailyValue` 改为:
|
||||
|
||||
```
|
||||
v, err := global.RedisEngine.Get(ctx, key).Int64()
|
||||
if err == redis.Nil {
|
||||
return 0, nil
|
||||
}
|
||||
return v, err
|
||||
```
|
||||
|
||||
### P2.3 未读数扣减防负(Lua)
|
||||
|
||||
`NotificationUnreadDecr` 改用脚本,保证结果不低于 0:
|
||||
|
||||
```
|
||||
local v = redis.call('GET', KEYS[1])
|
||||
if v and tonumber(v) > 0 then return redis.call('DECRBY', KEYS[1], 1) end
|
||||
return 0
|
||||
```
|
||||
|
||||
## 目录结构
|
||||
|
||||
```
|
||||
internal/
|
||||
├── redismodel/
|
||||
│ └── quota.go # [MODIFY] 每日 key %s→%d;KeyQuotaTotal→KeyQuotaRemain(key quota:remain:%d:%d),注释澄清语义
|
||||
└── redisdao/
|
||||
├── quota.go # [MODIFY] 6 个 QuotaDaily* date→int64;QuotaTotal*→QuotaRemain* 重命名;QuotaRemainDecr 用 Lua;quotaDailyValue 未命中返回 0
|
||||
└── notification.go # [MODIFY] NotificationUnreadDecr 用 Lua 防负
|
||||
```
|
||||
|
||||
## 实施要点
|
||||
|
||||
- **影响范围可控**:已确认 `QuotaDaily*`/`QuotaTotal*` 及 `KeyQuotaDaily*`/`KeyQuotaTotal` 无业务层调用方,重命名与签名变更自包含、无连带改动。
|
||||
- **兼容性**:这些 key 均为新增未上线代码,key 字符串变更(`quota:total`→`quota:remain`、日期 `%s`→`%d`)不涉及存量数据迁移。
|
||||
- **性能**:Lua 脚本均为单次往返、Redis 端原子执行,与既有 `quotaDailyIncrScript`/`unlockScript` 风格一致,无额外开销。
|
||||
- **日志**:本次改动不引入日志;错误沿现有 `error` 返回值上抛,由上层统一处理。
|
||||
Reference in New Issue
Block a user