
一、简要概述功能实现客户端发起的「订阅离线通知」请求。玩家离线后系统可按订阅类型推送微信/抖音小程序订阅消息例如体力恢复、现金相关、上线提醒。调用流程二、client向game服务进行离线消息推送订阅逻辑1、接收客户端订阅请求并向data持久化服务发信息进行存储并接受data服务传来的响应请求(1) 客户端发送订阅请求给game服务端(2) game验证之后向data传该用户订阅消息类型的信息进行持久化(3) data成功持久化之后发送一个结果请求game接收到请求后再将玩家订阅的状态写入到内存里面Tipgame使用位图进行存储存储类型为uint642、single服务进行消息通知的逻辑(1) game服务在离线的时候将PlayerId、用户名、订阅服务种类并根据计算消息推送的具体时间戳依次遍历发送请求传给single服务(2) single接收到game传过来的玩家离线订阅请求后将请求放入内存列表,根据推送时间进行排序(3) 定时器每秒遍历将到达时间戳的消息发送给玩家三、微信、抖音消息推送方法微信侧入口微信公众平台小游戏后台注册/登录主体创建或进入你们的微信小游戏开发设置里拿到AppIdAppSecret→ 对应single配置里的Wechat.AppId / AppSecret / Url换 token开通订阅消息后台选模板体力恢复、现金、上线提醒等拿到template_id→ 写进example/template/wechat_*.json的template_id客户端玩家勾选订阅 → 拿到该用户openid你们存成accountName并消耗一次订阅额度服务端access_tokentouser(openid)template_id→ 调subscribe/send抖音侧入口抖音开放平台小游戏创建/进入抖音小游戏应用拿到AppIdAppSecret→ 对应TikTok.AppId / AppSecret / Url开通订阅通知在后台申请模板拿到tpl_id客户端拉起订阅授权拿到用户open_id服务端access_tokenopen_idtpl_id→ 调subscribe_notification/.../notifyOpenID和玩家账号对应package internal type ITemplate interface { SendNotify(mgr IOfflineNotifyMgr, nowUnix int64) (err error) } type WechatTemplate struct { Touser string json:touser Template_id string json:template_id Miniprogram_state string json:miniprogram_state Lang string json:lang Data map[string]interface{} json:data } type WechatNotifyTokenRsp struct { ErrCode int json:errcode ErrMsg string json:errmsg } func (tpl *WechatTemplate) SendNotify(server *SingleServer, nowUnix int64, notifyData *pb.OfflineNotifyData) (err error) { if server nil { return errors.New(server nil) } if notifyData nil { return errors.New(notifyData nil) } tpl.Touser notifyData.AccountName var token string var expiretime int64 token, expiretime, err server.GetAccessToken(pb.OfflineNotifyPlatType_OfflineNotifyPlatType_WECHAT, nowUnix) if err ! nil { log.Error(get AccessToken fail, log.Int64(nowtime, nowUnix), log.Int64(id, int64(notifyData.UserId)), log.String(openid, notifyData.AccountName), log.Uint32(notifyType, notifyData.NotifyType), log.Int64(expiretime, expiretime), log.Err(err), ) return err } if token { return errors.New(token nil) } url : https://api.weixin.qq.com/cgi-bin/message/subscribe/send? access_token token var rsp *WechatNotifyTokenRsp rsp, err util.SendHttpReq[WechatTemplate, WechatNotifyTokenRsp](url, tpl) if err ! nil { log.Error(send http req fail, log.Err(err), log.String(openid, tpl.Touser)) return err } if rsp.ErrCode ! 0 { log.Error(send notify fail, log.Int(errorCode, rsp.ErrCode), log.String(errmsg, rsp.ErrMsg), log.Int64(nowtime, nowUnix), log.String(token, token), log.Int64(id, int64(notifyData.UserId)), log.String(openid, notifyData.AccountName), log.Uint32(notifyType, notifyData.NotifyType), ) return errors.New(send notify fail) } return nil } type TiktokTemplate struct { OpenId string json:open_id AccessToken string json:access_token AppID string json:app_id TplId string json:tpl_id Data map[string]interface{} json:data } type TiktokNotifyTokenRsp struct { ErrNo int64 json:err_no ErrTips string json:err_tips } https://developer.open-douyin.com/docs/resource/zh-CN/mini-game/develop/server/notify func (tpl *TiktokTemplate) SendNotify(server *SingleServer, nowUnix int64, notifyData *pb.OfflineNotifyData) (err error) { if server nil { return errors.New(server nil) } if notifyData nil { return errors.New(notifyData nil) } var expiretime int64 tpl.OpenId notifyData.AccountName tpl.AccessToken, expiretime, err server.GetAccessToken(pb.OfflineNotifyPlatType_OfflineNotifyPlatType_TIKTOK, nowUnix) if err ! nil { log.Error(get AccessToken fail, log.Int64(nowtime, nowUnix), log.Int64(id, int64(notifyData.UserId)), log.String(openid, notifyData.AccountName), log.Uint32(notifyType, notifyData.NotifyType), log.Int64(expiretime, expiretime), log.Err(err), ) return err } if tpl.AccessToken { return errors.New(token nil) } url : https://minigame.zijieapi.com/mgplatform/api/apps/subscribe_notification/developer/v1/notify var rsp *TiktokNotifyTokenRsp rsp, err util.SendHttpReq[TiktokTemplate, TiktokNotifyTokenRsp](url, tpl) if err ! nil { log.Error(send http req fail, log.Err(err), log.String(openid, tpl.OpenId)) return err } if rsp.ErrNo ! 0 { log.Error(send notify fail, log.Int64(errorCode, rsp.ErrNo), log.String(errTips, rsp.ErrTips), log.Int64(nowtime, nowUnix), log.String(token, tpl.AccessToken), log.Int64(id, int64(notifyData.UserId)), log.String(openid, notifyData.AccountName), log.Uint32(notifyType, notifyData.NotifyType), ) return errors.New(send notify fail) } return nil }