bindbox-game/internal/pkg/notify/lottery_notify.go
邹方成 a7a0f639e1 feat: 新增取消发货功能并优化任务中心
fix: 修复微信通知字段截断导致的编码错误
feat: 添加有效邀请相关字段和任务中心常量
refactor: 重构一番赏奖品格位逻辑
perf: 优化道具卡列表聚合显示
docs: 更新项目说明文档和API文档
test: 添加字符串截断工具测试
2025-12-23 22:26:07 +08:00

169 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"
)
// 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 开奖结果通知数据字段
// 根据微信订阅消息模板字段定义
// thing1: 活动名称, phrase3: 中奖结果, thing4: 温馨提示
type LotteryResultNotificationData struct {
Thing1 DataValue `json:"thing1"` // 活动名称
Phrase3 DataValue `json:"phrase3"` // 中奖结果
Thing4 DataValue `json:"thing4"` // 温馨提示
}
// 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 {
fmt.Printf("[开奖通知] 获取access_token失败: %v\n", err)
return err
}
// 活动名称限制长度thing类型不超过20个字符
activityName = pkgutils.TruncateRunes(activityName, 20)
// 构建中奖结果描述phrase类型限制5个汉字以内
// 由于奖品名称通常较长phrase3 放不下,改为固定文案 "恭喜中奖"
// 将奖品名称放入 Thing4 (温馨提示),限制 20 字符
resultPhrase := "恭喜中奖"
rewardsStr := strings.Join(rewardNames, ",")
warmTips := pkgutils.TruncateRunes(rewardsStr, 20)
req := &LotteryResultNotificationRequest{
Touser: openid,
TemplateID: cfg.LotteryResultTemplateID,
Page: "pages/mine/index", // 点击跳转到"我的"页面
MiniprogramState: "formal", // 正式版
Lang: "zh_CN",
Data: LotteryResultNotificationData{
Thing1: DataValue{Value: activityName}, // 活动名称
Phrase3: DataValue{Value: resultPhrase}, // 中奖结果
Thing4: DataValue{Value: warmTips}, // 温馨提示(中奖奖品)
},
}
fmt.Printf("[开奖通知] 尝试发送 openid=%s activity=%s rewards=%v\n", openid, activityName, 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 {
fmt.Printf("[开奖通知] 发送失败: %v\n", err)
return err
}
var result LotteryResultNotificationResponse
if err := json.Unmarshal(resp.Body(), &result); err != nil {
fmt.Printf("[开奖通知] 解析响应失败: %v\n", err)
return err
}
if result.Errcode != 0 {
// 常见错误码:
// 43101: 用户拒绝接受消息
// 47003: 模板参数不准确
fmt.Printf("[开奖通知] 发送失败 errcode=%d errmsg=%s\n", result.Errcode, result.Errmsg)
return fmt.Errorf("发送订阅消息失败: errcode=%d, errmsg=%s", result.Errcode, result.Errmsg)
}
fmt.Printf("[开奖通知] ✅ 发送成功 openid=%s\n", openid)
return nil
}