skills/mercury-api.wepieoa.com/activity-code-standards

activity-code-standards

SKILL.md

Vibe Coding Style Guide

go语言版本: go 1.24.0

1. 项目结构 (Project Structure)

1.1 标准目录布局

app/activity/{year}/{activity_name}/
├── internal/
│   ├── conf/           # 配置与常量
│   ├── store/          # Redis 数据层
│   ├── service/        # 业务逻辑层
│   ├── event/          # 事件处理
│   ├── cron/           # 定时任务
│   └── acttrack/       # 埋点上报(可选)
└── router/             # 路由注册

2. 命名规范 (Naming Conventions)

2.1 常量命名

// ✅ 使用大驼峰,按功能分组
const (
    ActID                   = 5811
    ChoseRewardTaskID       = 5
    ShareTaskNoRepeatTaskID = 13
    
    RankTypeGrowth  = "growth"
    DeferTaskGrowth = "growth_track"
)

2.2 配置结构体命名

// ✅ 主配置:SpecialConfig
type SpecialConfig struct {
    TargetVipLevel   int            `json:"target_vip_level"`
    PartyConfig      *PartyConfig   `json:"party_config"`
    Msg              Message        `json:"msg"`
}

// ✅ 子配置:按功能后缀 Config
type PartyConfig struct {
    RedPacketNpcID   int   `json:"red_packet_npc_id"`
    StayRoomDuration int   `json:"stay_room_duration"`
}

// ✅ 消息配置:Message
type Message struct {
    LoginMsg      weactivity.Message `json:"login_msg"`
    PartyStartMsg weactivity.Message `json:"party_start_msg"`
}

2.3 函数命名

// ✅ 事件处理:Handle + 事件名
func HandleLoginApp(data string) {}
func HandleSendGift(data string) {}
func HandleVipUpgrade(data string) {}

// ✅ 业务接口:动词 + 名词
func GetInfo(ctx context.Context, req *actutil.CommonReq) (*store.GetInfoRsp, error) {}
func ChoseRewardApi(ctx context.Context, req *store.ChoseRewardReq) (*store.ChoseRewardRsp, error) {}

// ✅ 工具函数:Can/Check/Get 开头
func CanUserJoinAct(config *conf.SpecialConfig, uid int) bool {}
func CheckCanRecvReward(actInfo *weactivity.Activity) bool {}
func GetUserFirstVipLevel(uid int) (int, error) {}

2.4 Redis Key 命名

// ✅ 变量名:大驼峰 + 语义化
var UserCanJoinAct = actredis.NewClient(...)
var PartyGrowthInfo = actredis.NewClient(...)

// ✅ KeyBase:活动简称:功能:参数占位符
KeyBase: "ar_super_bigr:party_growth_info:%d"
KeyBase: "ar_bigr_normal:user_send_gift_info:%d:%d"

// ✅ Field 常量:统一前缀 Field + 大驼峰
const (
    FieldPartyGrowthPercent = "party_growth_percent"
    FieldPartyGrowthEndTime = "party_growth_end_time"
)

// ✅ 提供 Getter 避免硬编码
func FieldCombo() string { return fieldCombo }
func FieldTotal() string { return fieldTotal }

3. 代码组织 (Code Organization)

3.1 函数顺序

// ✅ 推荐顺序(从公开到私有)
package service

// 1. 对外接口(按字母序)
func ChoseRewardApi(ctx context.Context, req *store.ChoseRewardReq) (*store.ChoseRewardRsp, error) {}
func GetInfo(ctx context.Context, req *actutil.CommonReq) (*store.GetInfoRsp, error) {}
func RecvTask(ctx context.Context, req *store.RecvTaskReq) (*store.RecvTaskRsp, error) {}

// 2. 内部辅助函数
func canUserJoinAct(config *conf.SpecialConfig, uid int) bool {}
func validateRewardRequest(req *store.ChoseRewardReq) error {}

4. 错误处理 (Error Handling)

4.1 参数校验

// ✅ 早返回 (Early Return)
if eventData.Uid <= 0 || eventData.Coin <= 0 {
    return
}

if req.VrCenterID <= 0 {
    return nil, acterror.NewCustomError(actmsg.ParamsErr)
}

4.2 依赖检查

// ✅ 逐层校验配置
actInfo := actinfo.GetActById(conf.ActID)
if actInfo == nil {
    return rsp, acterror.NewCustomError(actmsg.ActTimeErr)
}

specialConfig := conf.GetSpecialConfig(actInfo)
if specialConfig == nil {
    return rsp, acterror.NewCustomError(actmsg.ConfigErr)
}

4.3 错误日志

// ✅ 错误时记录关键上下文(例如ChoseReward方法)
if err != nil {
    entry.WithError(err).Errorln("ar_super_bigr chose reward SendRewards err")
    return rsp, err
}

5. 并发控制 (Concurrency)

5.1 分布式锁

使用 actutil.TryLockUidWithRetry / TryLockUidOnce / TryLockWithRetryWithSuffix 等封装函数(来自 app/activity/common/actutil/redis_lock.go)。

禁止直接使用 actstore.SetExNx / actstore.SetNx 实现分布式锁。

// ✅ 细粒度锁 + 自动重试(有业务后缀维度时)
lock := actutil.TryLockWithRetryWithSuffix(
    conf.ActID,               // 活动 ID
    req.StageID,              // 业务维度 1(int)
    strconv.Itoa(req.Index),  // 业务维度 2(string 后缀)
    10,                       // 超时秒数
)
if lock.Failed() {
    return rsp, acterror.NewCustomError(actmsg.LockErr)
}
defer lock.Unlock()

// ✅ 仅需 uid 级别锁时
lock := actutil.TryLockUidWithRetry(conf.ActID, uid, 10)
if lock.Failed() {
    return rsp, acterror.NewCustomError(actmsg.LockErr)
}
defer lock.Unlock()
// ❌ 错误:直接用 actstore.SetExNx 实现锁
ok, _ := actstore.SetExNx(lockKey, 1, 10)
if !ok {
    return rsp, acterror.NewCustomError(actmsg.LockErr)
}
// 缺少 Unlock、缺少重试、缺少标准化 Key 格式

5.3 异步非阻塞

// ✅ 非关键路径使用 safego
safego.SafeGo(func() {
    store.GrowthEnjoyUserNumber.BuildKey(ctx, rid, growthId).SAdd(sendUid)
    store.GrowthEnjoyCount.BuildKey(ctx, rid, growthId).HIncrBy(store.FieldGrowthAddScore, needAddVal)
})

6. Redis 使用 (Redis Best Practices)

6.1 Key 定义标准

// ✅ 使用 actredis 统一封装
var UserSendGiftInfo = actredis.NewClient(db.ActiveRedis, actredis.Extra{
    KeyBase:      "ar_bigr_normal:user_send_gift_info:%d:%d", // 活动简称:功能:参数
    ActId:        conf.ActID,                                  // 自动添加前缀
    KeyWithActId: true,                                        // 是否包含 ActID
    Desc:         "用户送礼信息(uid, gift_id)",                // 中文描述
}).GetHashRedis()

6.2 原子操作

// ✅ 使用 IncrBy + 判断阈值
afterSeconds, err := store.StayRoomSeconds.BuildKey(ctx, rid).HIncrBy(uid, duration).Result()
beforeSeconds := afterSeconds - duration

if afterSeconds >= threshold && beforeSeconds < threshold {
    // 触发奖励
}

7. 日志规范 (Logging Standards)

7.1 日志初始化

// ✅ 使用 actlogger 带活动上下文
ctx := actlogger.GenCtx(conf.ActID, uid)
entry := actlogger.GetLogger(ctx).WithFields(logrus.Fields{
    "func":  "HandleSendGift",
    "event": event,
})

7.2 日志级别

// ✅ Info:关键流程节点
entry.Infoln("ar_super_bigr HandleLoginApp send party login msg success")

// ✅ Warning:预期内的异常(如库存不足)
entry.WithError(err).Warningln("ar super bigr chose reward ReceiveTaskReward err")

// ✅ Error:需要人工介入的错误
entry.WithError(err).Errorln("ar_super_bigr HandleSendGift HIncrBy total err")

// ✅ Debug:调试信息
entry.Debugln("ar_super_bigr HandleVoiceRoom rid is not in start party rid set")

7.3 日志前缀

// ✅ 统一格式:活动简称 + 函数名/模块名 + 动作
"ar_super_bigr HandlePartyVal target vr center start"
"ar_super_bigr GetPartyInfo GetRankList err"

8. 配置解析 (Configuration Parsing)

8.1 GetSpecialConfig 实现规范

GetSpecialConfig 内部必须使用 actinfo.ParseSpecialInfoV2 进行配置解析,禁止直接 json.Unmarshal

// ✅ GetSpecialConfig 标准实现
func GetSpecialConfig(actInfo *weactivity.Activity) *SpecialConfig {
    if actInfo == nil {
        return nil
    }
    specialConfig := &SpecialConfig{}
    if err := actinfo.ParseSpecialInfoV2(actInfo, specialConfig); err != nil {
        return nil
    }
    return specialConfig
}
// ❌ 错误:直接 json.Unmarshal
func GetSpecialConfig(actInfo *weactivity.Activity) *SpecialConfig {
    specialConfig := &SpecialConfig{}
    if err := json.Unmarshal(actInfo.SpecialInfo, specialConfig); err != nil {
        return nil
    }
    return specialConfig
}

8.2 业务逻辑前校验

// ✅ 业务逻辑前先校验配置完整性
actInfo := actinfo.GetActById(conf.ActID)
specialConfig := conf.GetSpecialConfig(actInfo)
if specialConfig == nil {
    return
}

8.3 SpecialConfig 设计原则

✅ 奖励和概率放入 SpecialConfig,不写成常量

奖励 ID、概率权重、数值倍率、时长等可配置的业务数值,必须通过 SpecialConfig 字段下发,禁止硬编码为 Go 常量。

奖励配置:用 []*weactivity.Reward 或自定义结构体内嵌 []*weactivity.Reward 表达。

// ✅ 正确:奖励放 SpecialConfig
type SpecialConfig struct {
    // 扭蛋机配置(含奖励ID和权重,通过后台配置)
    GachaMachines []GachaMachineConfig `json:"gacha_machines"`
    // 日榜 buff 倍率(key: "1"/"2"/"3" → 倍率)
    DailyRoomBuffRates map[string]float64 `json:"daily_room_buff_rates"`
    // 语音房 S 级加成倍率
    VoiceRoomSBuffRate float64 `json:"voice_room_s_buff_rate"`
}

type GachaMachineConfig struct {
    MachineType    string               `json:"machine_type"`
    GuaranteeCount int                  `json:"guarantee_count"`
    Rewards        []*weactivity.Reward `json:"rewards"` // ✅ 用 weactivity.Reward 承载奖励
}
// ❌ 错误:奖励ID和数值倍率写成常量
const (
    DailyRoomBuffTop1      = 2.0
    DefaultVoiceRoomSBuff  = 2.0
    GachaIntimacyGift1     = 1722191
)
var DefaultGachaMachines = []GachaMachineConfig{...} // ❌ 不应有写死奖励ID的默认变量

发放奖励时配合 actreward.ParseRewards 转换(见 12.3):

rewardInfos := actreward.ParseRewards(uid, machineConf.Rewards)
actreward.SendRewards(uid, rewardInfos, &actreward.ExtraInfo{ActId: conf.ActID})

8.4 Hook 注册规范

钩子必须通过 GetHookers() 返回 actmodel.ActHookerList 的方式注册,禁止在 init() 中调用全局 RegisterXxxHooker

在实现前,必须阅读 app/activity/common/actmodel/actmodel.go 中的 ActHookerList 结构体,找到对应 Hook 类型的字段名,再在 GetHookers() 中填充。

// ✅ 正确:通过 GetHookers() 注册
func (a *Model) GetHookers() actmodel.ActHookerList {
    return actmodel.ActHookerList{
        LotteryHooker: &hook.SakuraLotteryHooker{},
        TaskHooker:    &hook.SakuraTaskHooker{},
        RankHooker:    &hook.SakuraRankHooker{},
        BlindBoxHooks: map[int]gift.BlindBoxHook{
            conf.GiftIDBlindBox: &hook.SakuraBlindBoxHook{},
        },
        GiftBoxHooks: map[int]gift.GiftBoxHook{
            conf.GiftIDGiftBox: &hook.SakuraGiftBoxHook{},
        },
    }
}
// ❌ 错误:在 init() 或 hook 文件内全局注册
func init() {
    lottery.RegisterLotteryHooker(conf.ActID, &SakuraLotteryHooker{})
    gift.RegisterBlindBoxHook(conf.GiftIDBlindBox, &SakuraBlindBoxHook{})
}

确认字段名的步骤

  1. 打开 app/activity/common/actmodel/actmodel.go
  2. ActHookerList struct 中找到对应类型的字段(如 LotteryHooker lottery.LotteryHookerBlindBoxHooks map[int]gift.BlindBoxHook
  3. GetHookers() 中按字段名填入实例

9. 事件处理 (Event Handling)

9.1 事件函数结构

func HandleEventName(data string) {
    // 1. 反序列化
    eventData := &actevent.EventType{}
    if err := json.Unmarshal([]byte(data), eventData); err != nil {
        logrus.WithError(err).Errorln("activity_name HandleEventName Unmarshal err")
        return
    }
    
    // 2. 参数校验
    if eventData.Uid <= 0 {
        return
    }
    
    // 3. 活动校验
    actInfo := actinfo.GetValidActByUid(conf.ActID, eventData.Uid)
    if actInfo == nil {
        return
    }
    
    // 4. 配置获取
    specialInfo := conf.GetSpecialConfig(actInfo)
    if specialInfo == nil {
        return
    }
    
    // 5. 业务逻辑
    ctx := actlogger.GenCtx(conf.ActID, eventData.Uid)
    // ...
}

9.2 事件过滤

// ✅ 多条件过滤前置
if eventData.ActId != conf.ActID {
    return
}

if eventData.ChipId != conf.GrowthChipID {
    return
}

if !helper.InArrayInt(event.GiftId, specialInfo.TargetGiftIDList) {
    return
}

10. 接口设计 (API Design)

10.1 请求响应结构

// ✅ Request:统一后缀 Req
type ChoseRewardReq struct {
    ActId   int `json:"act_id"`
    Uid     int `json:"uid"`
    StageID int `json:"stage_id"`
    Index   int `json:"index"`
}

// ✅ Response:统一后缀 Rsp(非 Resp)
type ChoseRewardRsp struct {
    RewardInfo *actreward.RewardInfo `json:"reward_info"`
}

10.2 接口函数签名

// ✅ 标准签名:(ctx, req) (rsp, error)
func GetInfo(c context.Context, req *actutil.CommonReq) (*store.GetInfoRsp, error) {
    rsp := &store.GetInfoRsp{
        GiftInfoList:  make([]*store.GiftInfo, 0),
        LimitInfoList: make([]*store.DailyLimitInfo, 0),
    }
    
    // 业务逻辑
    
    return rsp, nil
}

11. 测试与调试 (Testing & Debugging)

11.2 空跑模式

// ✅ 定时任务支持预演(apply = false)
func SendActEndMsg(apply bool) int {
    // ...
    if !apply {
        count++
        continue
    }
    // 实际发送消息
}

12. 业务特定模式 (Business Patterns)

12.1 自选奖励模式

// ✅ 任务完成 → 用户选择 → 扣减限量 → 发放奖励
// 1. 检查任务状态
taskInfo, err := taskSrv.GetTaskInfo(actInfo, task, uid, t)
if taskInfo.State != taskDef.TaskStatusCompleted {
    return rsp, acterror.NewCustomError(actmsg.FrequentOperation)
}

// 2. 分布式锁保护限量
lock := actutil.TryLockWithRetryWithSuffix(conf.ActID, stageID, index, 10)
defer lock.Unlock()

// 3. 检查库存
currNum, _ := store.DailyLimit.BuildKey(ctx, stageID, date).HGet(index).Int()
if currNum >= dailyLimit {
    return rsp, acterror.NewCustomError(actmsg.FrequentOperation)
}

// 4. 发放奖励(通过 weactivity.Reward → ParseRewards 转换后发放)
rewardInfos := actreward.ParseRewards(uid, configRewards)
actreward.SendRewards(uid, rewardInfos, &actreward.ExtraInfo{ActId: conf.ActID})

12.2 概率计算模式

涉及权重/概率计算时,必须优先使用 helper.GetRandomNumber,禁止自行实现随机权重逻辑。

// ✅ 正确:使用 helper.GetRandomNumber 进行权重随机
weightMap := map[int]int{
    1: 50,  // id=1,权重50
    2: 30,  // id=2,权重30
    3: 20,  // id=3,权重20
}
resultId := helper.GetRandomNumber(weightMap)
// ❌ 错误:自行实现随机权重逻辑
totalWeight := 0
for _, w := range weights {
    totalWeight += w
}
randNum := rand.Intn(totalWeight)
// ... 手动遍历累加判断

12.3 奖励发放规范

发放奖励时,禁止直接手动拼接 actreward.RewardInfo,应通过配置中的 weactivity.Reward 或自行构造 weactivity.Reward,再通过 actreward.ParseRewards 转换后发放。

// ✅ 正确:通过配置中的 weactivity.Reward 转换发放
// 方式1:直接使用配置中的 []*weactivity.Reward
rewardInfos := actreward.ParseRewards(uid, specialConfig.Rewards)
err := actreward.SendRewards(uid, rewardInfos, &actreward.ExtraInfo{ActId: conf.ActID})

// 方式2:手动构造 weactivity.Reward 后转换
rewards := []*weactivity.Reward{
    {
        RewardType:     "prop",
        RewardId:       10001,
        FemaleRewardId: 10002, // 选填,如果男女奖励不一样
        RewardVal:      1,
        ActId:          conf.ActID,
    },
}
rewardInfos := actreward.ParseRewards(uid, rewards)
err := actreward.SendRewards(uid, rewardInfos, &actreward.ExtraInfo{ActId: conf.ActID})
// ❌ 错误:直接拼接 actreward.RewardInfo 发放
rewardInfos := []*actreward.RewardInfo{
    {
        RewardType: "prop",
        RewardId:   10001,
        RewardVal:  1,
    },
}
actreward.SendRewards(uid, rewardInfos, &actreward.ExtraInfo{ActId: conf.ActID})

12.4 全服弹幕规范

全服弹幕分为两种使用场景:

场景一:获得奖励时发送全服弹幕

当用户通过抽奖等方式获得高等级奖励时,使用 actnotify.SendRewardFullNotify,根据 RewardLevel 自动筛选(默认 RewardLevel == 3 才发送)。

// ✅ 获得奖励后发送全服弹幕(抽奖组件内已内置,手写时参考此模式)
func SendLotteryFullNotify(actInfo *weactivity.Activity, uid int32, gifts []*actreward.RewardInfo) {
    if actInfo == nil {
        return
    }
    notifyRewards := gifts
    if helper.InActWhiteUids(uid) {
        notifyRewards = actreward.MergeRewards(int(uid), gifts)
    }
    text1 := actmsg.GetMsgByKey(actmsg.FullNotifyText1)
    text2 := actmsg.GetMsgByKey(actmsg.FullNotifyText2, actInfo.Name)
    text3 := actmsg.GetMsgByKey(actmsg.FullNotifyText3)
    bgUrl := actnotify.GetBgUrl()
    btnUrl := actnotify.GetBtnUrl()
    deepLink := helper.GetAppRedirectUrl(actInfo.ActUrl, "")
    for _, gift := range notifyRewards {
        if gift.RewardLevel != 3 {
            continue
        }
        actnotify.SendFullNotifyV2(gift.RewardId, int(uid), gift.Broadcast, text1, text2, text3, deepLink, bgUrl, btnUrl)
    }
}

场景二:自定义文案的全服弹幕

当需要发送自定义内容(非奖励触发)的全服弹幕时,通过配置 weactivity.FullNotify 结构体,使用 actnotify.TransformToFullNotify 转换后调用 .Do(uid, params) 发送。

// ✅ 配置结构体中定义 FullNotify(在 special_info 中配置)
type HuntConfig struct {
    CoinNotify *weactivity.FullNotify `json:"coin_notify"`
}

// ✅ 使用 TransformToFullNotify 转换 + Do 发送
notify := actnotify.TransformToFullNotify(config.HuntConfig.CoinNotify)
notify.Do(uid, map[string]string{
    "Uid":  strconv.Itoa(uid),
    "Coin": strconv.Itoa(coinAmount),
})

12.5 结构体深拷贝

一般结构体的深拷贝必须使用 helper.GobDeepCopy,禁止手动逐字段复制或使用 json.Marshal/Unmarshal 做深拷贝。

// ✅ 正确:使用 helper.GobDeepCopy
dst := new(TargetStruct)
_ = helper.GobDeepCopy(dst, src)

// ✅ 实际使用示例(FullNotify 深拷贝)
func TransformToFullNotify(notify *weactivity.FullNotify) *FullNotify {
    dst := new(FullNotify)
    _ = helper.GobDeepCopy(dst, notify)
    return dst
}
// ❌ 错误:使用 json 做深拷贝(性能差且类型信息丢失)
data, _ := json.Marshal(src)
dst := new(TargetStruct)
json.Unmarshal(data, dst)

13. 注释规范 (Comments)

// ✅ 包注释:描述模块职责
// Package event 处理活动相关的异步事件,包括登录、送礼、成长值变更等

// ✅ 常量分组注释
const (
    // 活动基础配置
    ActID            = 5811
    GrowthChipID     = 7
    
    // 排行榜类型
    RankTypeGrowth   = "growth"
    RankTypeRoom     = "room"
)

// ✅ 复杂逻辑内联注释
// 累计次数:按本次礼物数量累加
if _, err := store.UserSendGiftInfo.BuildKey(ctx, uid, giftId).HIncrBy(store.FieldTotal(), number).Result(); err != nil {
    // ...
}

// 最高连击:若本次连击大于历史最高则覆盖
if newCombo > oldCombo {
    // ...
}

14. 反模式 (Anti-Patterns)

❌ 避免硬编码

// ❌ 错误
store.UserSendGiftInfo.BuildKey(ctx, uid, giftId).HGet("combo_times")

// ✅ 正确
store.UserSendGiftInfo.BuildKey(ctx, uid, giftId).HGet(store.FieldCombo())

❌ 避免深层嵌套

// ❌ 错误(4 层嵌套)
if actInfo != nil {
    if specialInfo != nil {
        if taskInfo != nil {
            if taskInfo.State == completed {
                // 业务逻辑
            }
        }
    }
}

// ✅ 正确(早返回)
if actInfo == nil {
    return
}
if specialInfo == nil {
    return
}
if taskInfo == nil || taskInfo.State != completed {
    return
}
// 业务逻辑

❌ 避免裸返回值

// ❌ 错误
func GetUserLevel(uid int) int {
    // ...
    return 0 // 无法区分是真实等级 0 还是错误
}

// ✅ 正确
func GetUserLevel(uid int) (int, error) {
    // ...
    return level, nil
}

❌ 避免魔法数字

// ❌ 错误
func GetRankLimit() int {
    // ...
    return 500 
}

// ✅ 正确
func GetRankLimit() int {
    // ...
    return conf.DefaultRankLimit
}

15. Checklist(代码自查清单)

开发完成后,对照以下清单自查:

  • 命名规范:常量、函数、变量命名符合规范
  • 错误处理:所有 error 均有处理或日志
  • 并发安全:共享资源使用分布式锁或原子操作
  • 日志完整:关键节点有 Info 日志,错误有 Error 日志
  • 配置校验:业务逻辑前校验配置/活动有效性
  • 配置解析:GetSpecialConfig 内部使用 actinfo.ParseSpecialInfoV2
  • Redis Key:使用 actredis 统一管理,无硬编码
  • 资源释放:defer 释放锁/连接
  • 概率计算:使用 helper.GetRandomNumber,禁止自行实现权重随机
  • 奖励发放:通过 weactivity.Reward + actreward.ParseRewards 转换,禁止直接拼接 RewardInfo
  • 全服弹幕:奖励弹幕用 SendRewardFullNotify/SendFullNotifyV2,自定义文案用 TransformToFullNotify + .Do()
  • 深拷贝:结构体深拷贝使用 helper.GobDeepCopy
  • SpecialConfig 设计:奖励ID、概率权重、倍率、时长等可配置数值放入 SpecialConfig(用 []*weactivity.Reward 等),禁止硬编码为 Go 常量
  • 分布式锁:使用 actutil.TryLockUidWithRetry 等封装函数,禁止直接使用 actstore.SetExNx
  • Hook 注册:通过 GetHookers() 返回 actmodel.ActHookerList 注册,禁止在 init() 中调用全局 RegisterXxxHooker

16. 错题本(Mistake Notebook)

本 skill 附带一个持续更新的错题本,收录开发中真实发生的规范违规案例。

目录位置: mistake-notebook/entries/

文件 内容
naming.md 命名规范错误(Field 硬编码、Resp/Rsp 混用、常量未分组等)
error-handling.md 错误处理错误(忽略 error、缺少活动校验、日志无上下文等)
concurrency.md 并发与锁错误(缺锁、未检查 lock.Failed、裸 goroutine 等)
redis.md Redis 使用错误(Key 硬编码、读写竞态、参数数量不匹配等)
structure.md 结构设计错误(魔法数字、深层嵌套、空函数体、过度封装等)

AI 执行指令

加载本 skill 时,同时读取 mistake-notebook/entries/ 下所有文件,在代码生成或 CR 时主动检查是否命中已知错误模式。

发现新的规范违规案例时,按 mistake-notebook/README.md 中的模板追加到对应分类文件,形成持续积累的错题库。

Installs
9
First Seen
Apr 13, 2026