activity-hooks

SKILL.md

活动Hook/Hooker系统开发指南

帮助你使用活动框架的 Hook 系统扩展和修改框架核心行为,无需修改框架代码。


🤖 AI 执行指令

当用户调用此 skill 时,根据需求提供 Hook 系统的使用指导和代码示例。

使用场景判断

  • 扩展框架行为 → 推荐合适的 Hook 类型和注册方式
  • Hook vs Event 选择 → 分析场景帮助选择同步/异步方案
  • 实现特定 Hook → 提供具体 Hook 的代码模板
  • 理解 Hooker 接口 → 解释各组件 Hooker 的方法和作用

设计原理

Hook 是一个同步插件架构,允许活动通过实现 Hooker 接口或注册函数来扩展或修改组件的核心行为。


Hook vs Event

方面 Hook Event
触发方式 直接函数调用(同步) 异步消息队列
执行时序 同步,可拦截/修改结果 延迟,无法影响调用者
注册方式 GetHookers() 方法返回 配置级声明
典型用途 修改行为/过滤/拦截 通知/异步处理/统计

选择原则:需要修改调用者结果 → Hook;只需通知不影响流程 → Event。


Hooker 注册方式

唯一注册入口:GetHookers() 方法

所有活动的 Hooker 都在活动模型的 GetHookers() 方法中注册,返回 ActHookerList 结构体。

func (a *MyActivity) GetHookers() actmodel.ActHookerList {
    h := actmodel.ActHookerList{
        // 各组件的 Hooker 接口实现
        LotteryHooker:      &MyLotteryHooker{},
        TaskHooker:         &MyTaskHooker{},
        RankHooker:         &MyRankHooker{},
        ExtraGiftHooker:    &MyExtraGiftHooker{},
        CollectChipHooker:  &MyCollectChipHooker{},
        ExchangeHooker:     &MyExchangeHooker{},

        // 礼包钩子(map 形式,key 是 actId 或礼包 ID)
        GiftPkgHookers: map[int]gift_package.GiftPkgHookerI{
            conf.ActID: &MyGiftPkgHooker{},
        },
    }

    // 送礼钩子(函数形式,需要指定 giftId)
    h.AppendSendGiftHooker(weconfig.GetServerRegion(), conf.GiftIdGiftBox, HandleSendGiftBox)
    h.AppendSendGiftHooker(weconfig.GetServerRegion(), conf.BlindBoxGiftId, HandleSendBlindBoxGift)

    return h
}

ActHookerList 包含的 Hooker 类型

type ActHookerList struct {
    CollectChipHooker    collectChip.CollectChipHooker
    LotteryHooker        lottery.LotteryHooker
    TaskHooker           taskDef.TaskHooker
    RankHooker           rankstore.RankCheckoutHooker
    GiftPkgHookers       map[int]gift_package.GiftPkgHookerI // key: actId
    GiftPkgHookersV2     map[int]gift_package.GiftPkgHookerI // key: 礼包ID
    ExtraGiftHooker      extra_gift_hooker.ExtraGiftHooker
    ExchangeHooker       hooks.ExchangeHooker
    FamilyHooker         family.IFamilyHooker
    MiningHooker         mining.IMiningHooker
    TeamHooker           team.TeamHooker
    ActDiscoverHooker    act_discover.ActDiscoverHooker
    SignDiscontinue      signDiscontinue.SignDiscontinueHooker

    // 送礼相关(函数形式)
    SendGiftActivityFunc map[int]actsendgift.SendGiftActivityFunc // key: giftId
    GiftRandomCoinFunc   map[int]actsendgift.CommonGiftRandomCoinFunc
}

常用 Hooker 详解

1. LotteryHooker - 抽奖组件 Hook

抽奖组件提供了丰富的Hook点,用于扩展抽奖前后的行为。

接口定义(核心方法)

type LotteryHooker interface {
    // 判断是否可以进行抽奖
    CheckCanLottery(lotteryParams LotteryHookParams) error

    // 替换抽奖参数
    BeforeLottery(lotteryParams LotteryHookParams) LotteryReq

    // 扣碎片前替换数量
    BeforeCostChip(lotteryConf *weactivity.LotteryConf, actId, uid int, needChipMap map[int]int) map[int]int

    // 发奖前替换奖励(重要!用于拦截和替换奖励)
    BeforeSendRewards(lotteryParams LotteryHookParams, res []*actreward.RewardInfo) []*actreward.RewardInfo

    // 写入抽奖记录前修改记录内容
    BeforeWriteDb(lotteryParams LotteryHookParams, res []*actreward.RewardInfo) []*actreward.RewardInfo

    // 发全服弹幕之前替换奖励列表
    BeforeSendFullNotify(lotteryParams LotteryHookParams, res []*actreward.RewardInfo) []*actreward.RewardInfo

    // 抽完奖后替换奖励列表以控制展示逻辑
    BeforeReturnResp(lotteryParams LotteryHookParams, res []*actreward.RewardInfo) []*actreward.RewardInfo

    // 多抽时每抽前替换抽奖配置
    ReplacePoolForOnceLotteryBefore(lotteryParams LotteryHookParams) LotteryHookRsp

    // 多抽时每抽后替换抽奖配置
    AfterOnceLotteryEnd(lotteryParams LotteryHookParams, rewardInfo *actreward.RewardInfo, currResult []*actreward.RewardInfo)

    // 抽奖前替换抽奖配置
    CustomLotteryConfig(lotteryType string, req LotteryReq) *weactivity.LotteryConf

    // 替换查询接口返回内容
    AfterQueryLotteryInfo(lotteryParams LotteryHookParams, res *store.LotteryInfoResp)
}

BeforeSendRewards - 拦截和替换奖励

核心用途:在奖励发放前拦截,可以替换奖励或阻止发放,常用于实现限额控制、奖励转换等逻辑。

重要特性

  • 保底进度已经扣除,但奖励还未发放
  • 可以替换奖励为空奖励(实现限额控制)
  • 可以修改奖励内容(如转换成其他奖励)
  • 不影响保底进度:即使替换为空奖励,保底值也不会回退

典型场景1:实现每日限额控制

type MyLotteryHooker struct {
    lottery.DefaultLotteryHooker
}

func (h *MyLotteryHooker) BeforeSendRewards(lotteryParams lottery.LotteryHookParams, res []*actreward.RewardInfo) []*actreward.RewardInfo {
    // 检查今日已获得的特定碎片数量
    chipId := 2  // 假设要限制的碎片ID
    todayCount := getTodayChipCount(lotteryParams.Req.Uid, chipId)
    dailyLimit := 100  // 每日限额100个

    if todayCount >= dailyLimit {
        // 超过限额,替换为空奖励
        return []*actreward.RewardInfo{
            {
                RewardType: actreward.RewardTypeNone,
                RewardId:   0,
                RewardVal:  1,
            },
        }
    }

    // 未超限,允许发放原奖励
    return res
}

典型场景2:奖励转换

func (h *MyLotteryHooker) BeforeSendRewards(lotteryParams lottery.LotteryHookParams, res []*actreward.RewardInfo) []*actreward.RewardInfo {
    // 检查用户VIP等级
    vipLevel := getUserVipLevel(lotteryParams.Req.Uid)

    // 根据VIP等级转换奖励
    convertedRewards := make([]*actreward.RewardInfo, 0, len(res))
    for _, reward := range res {
        newReward := reward.Copy()
        if vipLevel >= 5 && reward.RewardType == actreward.RewardTypeCoin {
            // VIP5+ 金币奖励翻倍
            newReward.RewardVal *= 2
        }
        convertedRewards = append(convertedRewards, newReward)
    }

    return convertedRewards
}

典型场景3:全局限额控制

func (h *MyLotteryHooker) BeforeSendRewards(lotteryParams lottery.LotteryHookParams, res []*actreward.RewardInfo) []*actreward.RewardInfo {
    for _, reward := range res {
        if reward.RewardLevel == 3 {  // 稀有奖励
            // 检查全局已发放数量
            globalCount := getGlobalRewardCount(reward.RewardId)
            globalLimit := 1000  // 全服限额1000个

            if globalCount >= globalLimit {
                // 超过全局限额,替换为空奖励
                return []*actreward.RewardInfo{
                    {
                        RewardType: actreward.RewardTypeNone,
                        RewardId:   0,
                        RewardVal:  1,
                    },
                }
            }
        }
    }

    return res
}

注册方式

func (a *MyActivity) GetHookers() actmodel.ActHookerList {
    return actmodel.ActHookerList{
        LotteryHooker: &MyLotteryHooker{},
    }
}

2. TaskHooker - 任务组件 Hook

任务组件提供多个生命周期Hook点。

接口定义

type TaskHooker interface {
    // 增加任务条件值前
    BeforeIncrConditionValue(param *IncrConditionValueParams) error

    // 替换增加的分值
    ReplaceIncrValue(param *IncrConditionValueParams) (error, int)

    // 增加任务条件值后
    AfterIncrConditionValue(param *IncrConditionValueParams, memberValue, userValue int)

    // 完成阶段后
    AfterCompleteStage(param *IncrConditionValueParams, uid, stageId, recordTimes, realTimes int)

    // 发奖前替换奖励
    BeforeSendReward(actInfo *weactivity.Activity, task *weactivity.Task, taskInfo *store.TaskInfo,
                     stage *store.Stage, uid, receiveTimes int, rewards []*actreward.RewardInfo) ([]*actreward.RewardInfo, error)

    // 检测是否可以领取奖励
    CanReceiveReward(actInfo *weactivity.Activity, task *weactivity.Task, taskInfo *store.TaskInfo,
                     uid, stageId int) (bool, error)

    // 发奖后替换奖励
    AfterSendReward(actInfo *weactivity.Activity, task *weactivity.Task, taskInfo *store.TaskInfo,
                    uid, stageId int, rewards []*actreward.RewardInfo) ([]*actreward.RewardInfo, error)

    // 领取奖励后
    AfterRecvReward(actInfo *weactivity.Activity, task *weactivity.Task, taskInfo *store.TaskInfo, uid int) error

    // 查询任务信息后
    AfterQueryTaskInfo(actInfo *weactivity.Activity, task *weactivity.Task, uid int, t time.Time, taskInfo *store.TaskInfo)

    // 自定义结算消息
    CustomCheckoutMessage(actInfo *weactivity.Activity, task *weactivity.Task, stageId int, uid int,
                         t time.Time, msgData map[string]interface{}) (map[string]interface{}, bool)
}

使用示例

type MyTaskHooker struct {
    taskDef.DefaultTaskHooker
}

// 检测是否可以领取该奖励
func (h *MyTaskHooker) CanReceiveReward(actInfo *weactivity.Activity, task *weactivity.Task,
                                        taskInfo *task_store.TaskInfo, uid, stageId int) (bool, error) {
    // 获取用户设备id
    deviceId := user.GetActiveDeviceId(int32(uid))
    if deviceId == "" {
        return false, nil
    }

    // 获取该设备绑定的用户列表
    uids, err := store.GetDeviceUids(conf.ActID, deviceId)
    if err != nil {
        return false, nil
    }

    // 如果用户列表超限,则不可领取奖励
    if len(uids) >= 3 && !helper.InArrayInt(uid, uids) {
        return false, acterror.NewWithMsg(500, "设备超限")
    }

    return true, nil
}

// 领取奖励后,绑定设备与用户的关系
func (h *MyTaskHooker) AfterRecvReward(actInfo *weactivity.Activity, task *weactivity.Task,
                                       taskInfo *task_store.TaskInfo, uid int) error {
    deviceId := user.GetActiveDeviceId(int32(uid))
    if deviceId == "" {
        return nil
    }

    // 记录设备与用户的绑定关系
    _, err := store.SetDeviceUid(conf.ActID, deviceId, uid)
    return err
}

3. RankHooker - 排行榜组件 Hook

排行榜结算和维护的扩展逻辑。

接口定义(核心方法)

type RankCheckoutHooker interface {
    // 结算奖励后(按周期类型)
    AfterDailyCheckoutReward(actInfo *weactivity.Activity, rank int, c *weactivity.Rank, memberList []int, score int, t time.Time)
    AfterTotalCheckoutReward(actInfo *weactivity.Activity, rank int, c *weactivity.Rank, memberList []int, t time.Time, score int)
    AfterWeeklyCheckoutReward(actInfo *weactivity.Activity, rank int, c *weactivity.Rank, memberList []int)

    // 结算后总回调
    AfterCheckout(actInfo *weactivity.Activity, c *weactivity.Rank, memberList []string, scoreList []string, t time.Time)

    // 自定义结算消息
    CustomCheckoutMessage(actInfo *weactivity.Activity, rankConfig *weactivity.Rank, config weactivity.CheckoutConfig,
                         member, rank, score int, rewards []*actreward.RewardInfo, data map[string]interface{}, t time.Time) (map[string]interface{}, bool)

    // 增加排名分数前
    BeforeIncrRankValue(actInfo *weactivity.Activity, rankConfig *weactivity.Rank, periodType int,
                       member string, value int, t time.Time) (BeforeIncrValueRsp, error)

    // 增加排名分数后
    AfterIncrRankValue(actInfo *weactivity.Activity, rankConfig *weactivity.Rank, periodType int,
                      member string, value int, t time.Time, score int) error

    // 填充榜单信息
    FillRankInfo(ctx context.Context, uid int, actInfo *weactivity.Activity, rankConfig *weactivity.Rank,
                t time.Time, item *RankInfoItem)
    FillRankList(ctx context.Context, uid int, actInfo *weactivity.Activity, rankConfig *weactivity.Rank,
                t time.Time, itemList []*RankInfoItem)

    // 替换 Redis 键
    BeforeGetRedisKey(actInfo *weactivity.Activity, rankConf *weactivity.Rank, t time.Time) string

    // 是否可以结算榜单
    CanCheckoutRank(actInfo *weactivity.Activity, rankConf *weactivity.Rank, members []string, t time.Time) bool
}

4. ExtraGiftHooker - 小礼物爆大礼物 Hook

扩展小礼物爆大礼物玩法的行为。

接口定义(核心方法)

type ExtraGiftHooker interface {
    // 处理随机列表特殊逻辑
    HandleRandomListSpecial(entry *logrus.Entry, actInfo *weactivity.Activity, config *conf.ExtraGift,
                          param *gift.SendGiftParam, ret *gift.GiftReturnPrize) bool

    // 替换礼物前
    BeforeReplaceGift(params *gift.SendGiftParam, actInfo *weactivity.Activity,
                     extraGiftInfo *weactivity.ExtraGiftInfo, isGuaranteeTrigger bool) *weactivity.RewardConfig

    // 替换礼物保底
    ReplaceGiftGuarantee(params *gift.SendGiftParam, actInfo *weactivity.Activity,
                        extraGiftInfo *weactivity.ExtraGiftInfo, replaceType int) *weactivity.ExtraGiftInfo

    // 替换权重映射前
    BeforeReplaceWeightMap(params *gift.SendGiftParam, actInfo *weactivity.Activity,
                          extraConfig weactivity.ExtraGift, originWeightMap map[int]int) map[int]int

    // 爆出大礼物后
    AfterExplodeBigGift(params *gift.SendGiftParam, actInfo *weactivity.Activity,
                       extraGiftInfo *weactivity.ExtraGiftInfo, giftId int, ret *gift.GiftReturnPrize) (int, bool)

    // 处理完成后
    AfterProcess(params *gift.SendGiftParam, actInfo *weactivity.Activity, ret *gift.GiftReturnPrize)

    // 替换消息前
    BeforeReplaceMessage(actInfo *weactivity.Activity, message weactivity.Message) weactivity.Message
}

使用示例

type MyExtraGiftHooker struct {
    extra_gift_hooker.DefaultExtraGiftHooker
}

func (d MyExtraGiftHooker) AfterProcess(params *gift.SendGiftParam, actInfo *weactivity.Activity, ret *gift.GiftReturnPrize) {
    saleConf := conf.GetSaleConfig(actInfo)
    if saleConf == nil {
        return
    }

    giftCountMap := ret.ExtraGiftReceive
    recvBigGift := false
    var bigGiftNames []string

    for giftId, val := range giftCountMap {
        if giftId == 0 {
            continue
        }
        if val >= 1 {
            recvBigGift = true
            bigGiftNames = append(bigGiftNames, gift.GetGiftName(giftId))
        }
    }

    if recvBigGift {
        giftDesc, _ := actutil.FormatString(saleConf.MagTmplMap["recv_big_tmpl"], map[string]interface{}{
            "NickName":     user.GetUserNickname(params.RecvUid),
            "GiftName":     gift.GetGiftName(params.GiftId),
            "RecvGiftName": strings.Join(bigGiftNames, ","),
        })
        ret.Desc += giftDesc
    }
}

5. GiftPkgHooker - 礼包组件 Hook

礼包购买流程的扩展逻辑。

接口定义

type GiftPkgHookerI interface {
    // 是否需要过滤(不展示)该礼包
    NeedFilter(pkgId, uid, platform int) bool

    // 获取礼包截止时间
    GetDeadline(pkgId, uid int) int64

    // 是否可以创建订单
    CanCreateOrder(pkgId, uid, platform int) error

    // 获取最大购买次数
    GetMaxPurchaseTimes(pkgId, uid int) int

    // 替换奖励
    ReplaceRewards(pkgId, uid int, isFirstCharge bool, rewards []*actreward.RewardInfo) []*actreward.RewardInfo

    // 生成额外奖励
    GenerateExtraRewards(pkgId, uid, orderId, goodsId int) []*actreward.RewardInfo

    // 替换礼包配置
    ReplaceGiftPackConf(pkgId, uid, platform int, item *PackageItem) *PackageItem

    // 获取礼包开始时间
    GetPkgStartTime(pkgId, uid, pkgStartTime int) int

    // 发奖后
    AfterSendRewards(pkgId, uid, orderId int, rewards []*actreward.RewardInfo) []*actreward.RewardInfo

    // 发奖前
    BeforeSendRewards(pkgId, uid, orderId int) error
}

使用示例

type MyGiftPkgHooker struct {
    gift_package.DefaultGiftPkgHooker
}

func (g *MyGiftPkgHooker) NeedFilter(pkgId, uid int, platform int) bool {
    if !helper.InArrayInt(pkgId, conf.PkgList) {
        return false
    }

    actInfo := actinfo.GetValidActByUid(conf.ActID, uid)
    if actInfo == nil {
        return true
    }

    rsp, err := service.GetChainPkgInfo(context.Background(), &actutil.CommonReq{
        ActId: conf.ActID,
        Uid:   uid,
    })
    if err != nil {
        return false
    }

    // 若上一个任务全部领完,且当前礼包还没购买,则不过滤
    if rsp.CurrPkgID != pkgId {
        return true
    }

    return false
}

func (g *MyGiftPkgHooker) CanCreateOrder(pkgId, uid int, platform int) error {
    if g.NeedFilter(pkgId, uid, platform) {
        return errors.New("can not create order")
    }
    return nil
}

6. 送礼相关 Hook

送礼相关的 Hook 不是接口方法,而是普通函数,通过 AppendSendGiftHooker 注册。

函数签名

// 通用送礼钩子签名
func HandleSendXXX(param *gift.SendGiftParam, ret *gift.GiftReturnPrize) *gift.SendGiftActivityResp

注册方式

func (a *MyActivity) GetHookers() actmodel.ActHookerList {
    h := actmodel.ActHookerList{}

    // 注册送礼钩子:region, giftId, handler
    h.AppendSendGiftHooker(weconfig.GetServerRegion(), conf.GiftIdGiftBox, HandleSendGiftBox)
    h.AppendSendGiftHooker(weconfig.GetServerRegion(), conf.BlindBoxGiftId, HandleSendBlindBoxGift)
    h.AppendSendGiftHooker(weconfig.GetServerRegion(), conf.LittleGiftId, HandleSendLittleGift)

    return h
}

使用示例 - 礼盒钩子

func HandleSendGiftBox(param *gift.SendGiftParam, ret *gift.GiftReturnPrize) *gift.SendGiftActivityResp {
    entry := actlogger.GetLogger(context.Background()).WithFields(map[string]interface{}{
        "act_id":   conf.ActId,
        "uid":      param.SendUid,
        "gift_id":  param.GiftId,
        "rid":      param.Rid,
    })

    resp := &gift.SendGiftActivityResp{
        AnimationIndex: -1,
        Desc:           "",
    }

    // 检查是否是目标礼盒
    if param.GiftId != conf.GiftIdGiftBox {
        return resp
    }

    entry.Infoln("HandleSendGiftBox start")

    actInfo := actinfo.GetValidActByUid(conf.ActId, param.SendUid)
    if actInfo == nil {
        return resp
    }

    saleConfig := conf.GetSaleConfig(actInfo)
    if saleConfig == nil {
        return resp
    }

    // 根据爆出的奖励进行处理
    for _, reward := range ret.ActualRewards {
        if reward.RewardValue == int32(saleConfig.GiftBoxConfig.OriginAvatarRewardId) {
            _, err := store.IncrOriginAvatarObtainTag(conf.ActId, param.RecvUid, 1)
            if err != nil {
                entry.WithError(err).Errorln("HandleSendGiftBox incr originAvatar err")
                return resp
            }
        }
    }

    return resp
}

使用示例 - 盲盒钩子

func HandleSendBlindBoxGift(param *gift.SendGiftParam, ret *gift.GiftReturnPrize) *gift.SendGiftActivityResp {
    entry := actlogger.GetLogger(context.Background()).WithFields(map[string]interface{}{
        "act_id":   conf.ActId,
        "uid":      param.SendUid,
        "gift_id":  param.GiftId,
    })

    resp := &gift.SendGiftActivityResp{
        AnimationIndex: -1,
        Desc:           "",
    }

    actInfo := actinfo.GetValidActByUid(conf.ActId, param.SendUid)
    if actInfo == nil {
        return resp
    }

    saleConfig := conf.GetSaleConfig(actInfo)
    if saleConfig == nil {
        return resp
    }

    entry.Infoln("HandleSendBlindBoxGift start")

    // 获取盲盒配置
    blindBoxConfig := gift.GetBlindBoxConfig(int32(param.GiftId), weconfig.GetServerLanguage())
    if blindBoxConfig.DiffItems.RoundDouble.BaseNumber == 0 {
        return resp
    }

    baseNumber := blindBoxConfig.DiffItems.RoundDouble.BaseNumber

    // 获取送礼次数,计算距离下次概率提升的次数
    giftCount, err := collect_gift.GetGiftCount(param.SendUid, conf.ActId, param.GiftId, collect_gift.CollectTypeSend)
    needCount := baseNumber - giftCount%baseNumber

    comboDesc, err := actutil.FormatString(saleConfig.MagTmplMap["blind_combo_desc_tmpl"], map[string]interface{}{
        "NeedCount": needCount,
    })
    if err != nil {
        entry.WithError(err).Errorln("HandleSendBlindBoxGift FormatString err")
        return resp
    }

    resp.ComboDesc = comboDesc
    return resp
}

7. 特殊礼物 Hook(GiftBoxHook / BlindBoxHook)

这些 Hook 是针对特定礼物ID的扩展,不通过 GetHookers() 注册,而是直接调用注册函数。

GiftBoxHook - 礼盒钩子

type GiftBoxHook interface {
    // 发奖前进行奖励替换
    BeforeSendReward(param GiftBoxHookParam) *SpecialGiftActivity
}

// 注册方式(通常在 init() 函数中)
func init() {
    gift.RegisterGiftBoxHook(conf.GiftIdGiftBox, &MyGiftBoxHook{})
}

BlindBoxHook - 盲盒钩子

type BlindBoxHook interface {
    // 每次抽取前更新权重
    UpdateWeightOnce(params BlindBoxHookParams) (map[int]int, int)

    // 获取随机ID后
    AfterGetRandId(params BlindBoxHookParams) (bool, int)

    // 替换盲盒实际收到的内容
    ReplaceBlindBoxActualReceive(params BlindBoxHookParams, isGuaranteeExplode []int,
                                 repStageList []int) ReplaceBlindBoxReturn

    // 替换盲盒等级
    ReplaceBlindBoxLv(params BlindBoxHookParams) (bool, int)
}

// 注册方式(通常在 init() 函数中)
func init() {
    gift.RegisterBlindBoxHook(conf.BlindBoxGiftId, &MyBlindBoxHook{})
}

DefaultHooker 默认实现

所有 Hooker 接口都提供了 Default 实现,方法体为空或返回默认值。活动通过嵌入 Default 结构体,只需实现需要扩展的方法。

type MyLotteryHooker struct {
    lottery.DefaultLotteryHooker  // 嵌入默认实现
}

// 只需实现需要的方法
func (h *MyLotteryHooker) BeforeSendRewards(lotteryParams lottery.LotteryHookParams, res []*actreward.RewardInfo) []*actreward.RewardInfo {
    // 自定义逻辑
    return res
}

常见注意事项

  1. Hook 是同步执行的:不要在 Hook 中执行耗时操作(如网络请求),否则会阻塞主流程
  2. 注意作用域:Hook 只影响当前活动,不影响其他活动
  3. 错误处理:Hook 中的 panic 会影响主流程,务必做好错误处理
  4. 不要直接修改参数:需要修改时先 Copy,避免影响其他 Hook
  5. 注册时机:所有 Hook 在 GetHookers() 中一次性注册,不支持动态注册

典型使用场景

场景 1:抽奖限额控制

需求:活动抽奖每日限额100个稀有碎片

实现

func (h *MyLotteryHooker) BeforeSendRewards(lotteryParams lottery.LotteryHookParams, res []*actreward.RewardInfo) []*actreward.RewardInfo {
    for _, reward := range res {
        if reward.RewardId == conf.RareChipId {
            todayCount := getTodayChipCount(lotteryParams.Req.Uid, conf.RareChipId)
            if todayCount >= 100 {
                return []*actreward.RewardInfo{
                    {RewardType: actreward.RewardTypeNone, RewardId: 0, RewardVal: 1},
                }
            }
        }
    }
    return res
}

场景 2:任务设备限制

需求:连锁礼包任务同一设备最多3个用户领取

实现

func (h *MyTaskHooker) CanReceiveReward(actInfo *weactivity.Activity, task *weactivity.Task,
                                        taskInfo *task_store.TaskInfo, uid, stageId int) (bool, error) {
    deviceId := user.GetActiveDeviceId(int32(uid))
    uids, _ := store.GetDeviceUids(conf.ActID, deviceId)

    if len(uids) >= 3 && !helper.InArrayInt(uid, uids) {
        return false, acterror.NewWithMsg(500, "设备超限")
    }
    return true, nil
}

场景 3:礼包过滤

需求:连锁礼包按顺序解锁,上一个未购买不展示下一个

实现

func (g *MyGiftPkgHooker) NeedFilter(pkgId, uid int, platform int) bool {
    rsp, _ := service.GetChainPkgInfo(context.Background(), &actutil.CommonReq{
        ActId: conf.ActID,
        Uid:   uid,
    })

    // 当前可购买礼包不是该礼包,则过滤
    if rsp.CurrPkgID != pkgId {
        return true
    }
    return false
}

场景 4:榜单结算后处理

需求:榜单结算后发送额外奖励

实现

func (h *MyRankHooker) AfterCheckoutReward(actInfo *weactivity.Activity, rank int, rankConfig *weactivity.Rank,
                                           member string, memberList []int, score int, t time.Time) {
    if rank <= 10 {
        // 前10名额外发放特殊称号
        extraRewards := []*actreward.RewardInfo{
            {
                RewardType: actreward.RewardTypeTitle,
                RewardId:   conf.TopRankerTitleId,
                RewardVal:  30 * 24 * 3600, // 30天
            },
        }

        for _, uid := range memberList {
            actreward.SendRewards(uid, extraRewards, &actreward.ExtraInfo{
                ActId:          conf.ActID,
                PropChangeType: def.PropChangeTypeActivityRank,
            })
        }
    }
}

场景 5:送礼动效控制

需求:送礼盒时根据爆出的奖励展示不同动效

实现

func HandleSendGiftBox(param *gift.SendGiftParam, ret *gift.GiftReturnPrize) *gift.SendGiftActivityResp {
    resp := &gift.SendGiftActivityResp{
        AnimationIndex: -1,
    }

    // 根据爆出奖励设置动效索引
    for _, reward := range ret.ActualRewards {
        if reward.RewardLevel == 3 {
            resp.AnimationIndex = 2  // 稀有奖励动效
            break
        } else if reward.RewardLevel == 2 {
            resp.AnimationIndex = 1  // 普通奖励动效
        }
    }

    return resp
}

Hook 开发最佳实践

  1. 最小化实现:只实现需要的 Hook 方法,其他保持默认
  2. 日志记录:Hook 入口处打印日志,方便追踪和调试
  3. 性能优先:Hook 是同步调用,避免耗时操作
  4. 错误容错:Hook 失败不应影响主流程,做好错误处理
  5. 数据隔离:不同活动的 Hook 不要相互影响
  6. 测试覆盖:Hook 逻辑务必充分测试,特别是边界情况
Installs
1
First Seen
Apr 16, 2026