bindbox-game/internal/pkg/notify/lottery_notify.go

170 lines
5.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package notify
import (
"context"
"encoding/json"
"fmt"
"net/http"
"strings"
"time"
"bindbox-game/internal/pkg/httpclient"
pkgutils "bindbox-game/internal/pkg/utils"
"go.uber.org/zap"
)
// WechatNotifyConfig 微信通知配置
type WechatNotifyConfig struct {
AppID string
AppSecret string
LotteryResultTemplateID string
}
// LotteryResultNotificationRequest 开奖结果通知请求结构
type LotteryResultNotificationRequest struct {
Touser string `json:"touser"`
TemplateID string `json:"template_id"`
Page string `json:"page"`
MiniprogramState string `json:"miniprogram_state"`
Lang string `json:"lang"`
Data LotteryResultNotificationData `json:"data"`
}
// LotteryResultNotificationData 开奖结果通知数据字段
// 使用 map 支持动态字段类型,根据模板灵活配置
type LotteryResultNotificationData map[string]DataValue
// DataValue 数据值包装
type DataValue struct {
Value string `json:"value"`
}
// LotteryResultNotificationResponse 发送结果响应
type LotteryResultNotificationResponse struct {
Errcode int `json:"errcode"`
Errmsg string `json:"errmsg"`
}
// AccessTokenResponse access_token 响应
type AccessTokenResponse struct {
AccessToken string `json:"access_token"`
ExpiresIn int `json:"expires_in"`
ErrCode int `json:"errcode,omitempty"`
ErrMsg string `json:"errmsg,omitempty"`
}
// getAccessToken 获取微信 access_token
func getAccessToken(ctx context.Context, appID, appSecret string) (string, error) {
url := "https://api.weixin.qq.com/cgi-bin/token"
client := httpclient.GetHttpClient()
resp, err := client.R().
SetQueryParams(map[string]string{
"grant_type": "client_credential",
"appid": appID,
"secret": appSecret,
}).
Get(url)
if err != nil {
return "", fmt.Errorf("获取access_token失败: %v", err)
}
if resp.StatusCode() != http.StatusOK {
return "", fmt.Errorf("HTTP请求失败状态码: %d", resp.StatusCode())
}
var tokenResp AccessTokenResponse
if err := json.Unmarshal(resp.Body(), &tokenResp); err != nil {
return "", fmt.Errorf("解析access_token响应失败: %v", err)
}
if tokenResp.ErrCode != 0 {
return "", fmt.Errorf("获取access_token失败: errcode=%d, errmsg=%s", tokenResp.ErrCode, tokenResp.ErrMsg)
}
if tokenResp.AccessToken == "" {
return "", fmt.Errorf("获取到的access_token为空")
}
return tokenResp.AccessToken, nil
}
// SendLotteryResultNotification 发送开奖结果订阅消息
// ctx: context
// cfg: 微信通知配置
// openid: 用户 openid
// activityName: 活动名称
// rewardNames: 中奖奖品列表
// orderNo: 订单编号
// drawTime: 开奖时间
func SendLotteryResultNotification(ctx context.Context, cfg *WechatNotifyConfig, openid string, activityName string, rewardNames []string, orderNo string, drawTime time.Time) error {
if cfg == nil || cfg.LotteryResultTemplateID == "" {
fmt.Printf("[开奖通知] 模板ID未配置跳过发送 openid=%s\n", openid)
return nil
}
if openid == "" {
fmt.Printf("[开奖通知] openid为空跳过发送\n")
return nil
}
// 获取 access_token
accessToken, err := getAccessToken(ctx, cfg.AppID, cfg.AppSecret)
if err != nil {
zap.L().Error("[开奖通知] 获取access_token失败", zap.Error(err), zap.String("openid", openid))
return err
}
// 活动名称限制长度thing类型不超过20个字符
activityName = pkgutils.TruncateRunes(activityName, 20)
// 活动结果:展示奖品列表
rewardsStr := strings.Join(rewardNames, ",")
if rewardsStr == "" {
rewardsStr = "无奖励"
}
// thing类型限制20字符
resultVal := pkgutils.TruncateRunes(rewardsStr, 20)
// 当前进度:固定为"已发货"
progress := "已发货"
// 使用模板字段thing6=活动名称, thing8=当前进度, thing9=活动结果
req := &LotteryResultNotificationRequest{
Touser: openid,
TemplateID: cfg.LotteryResultTemplateID,
Page: "pages/mine/index", // 点击跳转到"我的"页面
MiniprogramState: "formal", // 正式版
Lang: "zh_CN",
Data: LotteryResultNotificationData{
"thing6": {Value: activityName}, // 活动名称
"thing8": {Value: progress}, // 当前进度
"thing9": {Value: resultVal}, // 活动结果
},
}
zap.L().Info("[开奖通知] 尝试发送", zap.String("openid", openid), zap.String("activity", activityName), zap.Strings("rewards", rewardNames))
// 发送请求
url := fmt.Sprintf("https://api.weixin.qq.com/cgi-bin/message/subscribe/send?access_token=%s", accessToken)
client := httpclient.GetHttpClient()
resp, err := client.R().
SetHeader("Content-Type", "application/json").
SetBody(req).
Post(url)
if err != nil {
zap.L().Error("[开奖通知] 发送失败", zap.Error(err), zap.String("openid", openid))
return err
}
var result LotteryResultNotificationResponse
if err := json.Unmarshal(resp.Body(), &result); err != nil {
zap.L().Error("[开奖通知] 解析响应失败", zap.Error(err), zap.String("body", string(resp.Body())))
return err
}
if result.Errcode != 0 {
// 常见错误码:
// 43101: 用户拒绝接受消息
// 47003: 模板参数不准确
zap.L().Warn("[开奖通知] 发送失败", zap.Int("errcode", result.Errcode), zap.String("errmsg", result.Errmsg), zap.String("openid", openid))
return fmt.Errorf("发送订阅消息失败: errcode=%d, errmsg=%s", result.Errcode, result.Errmsg)
}
zap.L().Info("[开奖通知] ✅ 发送成功", zap.String("openid", openid))
return nil
}