bindbox-game/internal/pkg/notify/lottery_notify.go
邹方成 45815bfb7d chore: 清理无用文件与优化代码结构
refactor(utils): 修复密码哈希比较逻辑错误
feat(user): 新增按状态筛选优惠券接口
docs: 添加虚拟发货与任务中心相关文档
fix(wechat): 修正Code2Session上下文传递问题
test: 补充订单折扣与积分转换测试用例
build: 更新配置文件与构建脚本
style: 清理多余的空行与注释
2025-12-18 17:35:55 +08:00

171 lines
5.5 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"
)
// 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个字符
if len(activityName) > 20 {
activityName = activityName[:17] + "..."
}
// 构建中奖结果描述phrase类型限制5个汉字以内
// 将中奖奖品写入到 resultPhrase
resultPhrase := strings.Join(rewardNames, ",")
if len(resultPhrase) > 5 {
resultPhrase = resultPhrase[:2] + "..."
}
// 温馨提示:固定短语
warmTips := "已发送到您的货柜"
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
}