561 lines
17 KiB
Go
561 lines
17 KiB
Go
package models
|
||
|
||
import (
|
||
"crypto/md5"
|
||
"encoding/hex"
|
||
"encoding/json"
|
||
"fmt"
|
||
"github.com/go-admin-team/go-admin-core/logger"
|
||
"go-admin/common/database"
|
||
"go-admin/common/storage"
|
||
"io"
|
||
"net/http"
|
||
"net/url"
|
||
"strings"
|
||
"time"
|
||
)
|
||
|
||
const (
|
||
MusicProductID = 2
|
||
LoginType = "3"
|
||
MaxRetries = 3
|
||
ServiceId = "698039049108516515"
|
||
|
||
MiGuSubscriptionUrl = "http://hz.migu.cn/order/rest/201906/all/bjhy/and/package/query.do"
|
||
MiGuLoginUrl = "http://hz.migu.cn/order/rest/login/secret/url.do"
|
||
SignSecretKey = "1524b4ed9eef4cdca934056499a1dd14" // 签名密钥
|
||
LoginSecretKey = "913efe5c6f274c278988af817f9d1c7d" // 登陆密钥
|
||
)
|
||
|
||
type CallbackRequest struct {
|
||
ChannelCode string `json:"channel_code" binding:"required"` // 渠道编码,例如:00211NV
|
||
ExternalOrderNo string `json:"external_order_no"` // 外部平台流水号,例如:1909274381102424065
|
||
PhoneNumber string `json:"phone_number" binding:"required"` // 手机号,例如:13590011234
|
||
Province string `json:"province"` // 省份,例如:广东
|
||
ResCode int `json:"res_code"` // 交易结果,1-成功,0-失败
|
||
ResDesc string `json:"res_desc"` // 交易结果描述,例如:【OPEN】系统繁忙
|
||
}
|
||
|
||
type ResultFeedbackEvt struct {
|
||
MSISDN string `json:"MSISDN"` // 用户手机号码,长度11
|
||
RevDate string `json:"revdate"` // 平台接收时间戳,格式为:yyyymmddHHMMss
|
||
ResCode string `json:"rescode"` // 结果代码,长度6
|
||
ResDesc string `json:"resdesc"` // 结果描述,长度1-128
|
||
Msg string `json:"msg"` // 短信或订购信息,长度1-80
|
||
}
|
||
|
||
type MsgDetails struct {
|
||
OrderKeyword string // 订购关键字
|
||
ChannelCode string // 渠道编码
|
||
ChannelCustomNo string // 渠道自用编码
|
||
SongID string // 歌曲ID
|
||
PlatformType string // 平台类型
|
||
Price string // 价格
|
||
CustomSerialNo string // 自定义流水号(可选)
|
||
}
|
||
|
||
type MusicFeedbackResp struct {
|
||
ReturnCode string `json:"return_code"` // 返回代码
|
||
ReturnDesc string `json:"return_desc"` // 返回描述
|
||
}
|
||
|
||
func ParseMsg(msg string) (*MsgDetails, error) {
|
||
parts := strings.Split(msg, "@")
|
||
if len(parts) < 6 {
|
||
return nil, fmt.Errorf("invalid message format")
|
||
}
|
||
|
||
details := &MsgDetails{
|
||
OrderKeyword: parts[0],
|
||
ChannelCode: parts[1],
|
||
ChannelCustomNo: parts[2],
|
||
SongID: parts[3],
|
||
PlatformType: parts[4],
|
||
Price: parts[5],
|
||
}
|
||
|
||
if details.PlatformType == "sdk" && len(parts) == 7 {
|
||
details.CustomSerialNo = parts[6]
|
||
}
|
||
|
||
return details, nil
|
||
}
|
||
|
||
func ParseRevDate(revDate string) (time.Time, error) {
|
||
layout := "20060102150405"
|
||
// 使用本地时区解析
|
||
loc, _ := time.LoadLocation("Asia/Shanghai") // 你也可以用 time.Local
|
||
t, err := time.ParseInLocation(layout, revDate, loc)
|
||
if err != nil {
|
||
return time.Time{}, fmt.Errorf("invalid revdate format: %v", err)
|
||
}
|
||
return t, nil
|
||
}
|
||
|
||
// CheckMusicOrderState 定时任务,检查订单状态,是否有1小时退订
|
||
func CheckMusicOrderState() {
|
||
logger.Info("****** CheckMusicOrderState start ******")
|
||
fmt.Println("****** CheckMusicOrderState start ******")
|
||
if database.Db == nil {
|
||
logger.Error("Database connection is nil")
|
||
fmt.Println("Database connection is nil")
|
||
return
|
||
}
|
||
|
||
// 查询订单列表中未退订的用户,查询其是否退订;如果退订,则更新退订时间,判断是否为1小时内退订
|
||
var orderList []MgOrderLog
|
||
|
||
// 获取当前时间前2个小时
|
||
oneHourAgo := time.Now().Add(-2 * time.Hour)
|
||
fmt.Println("check oneHourAgo is:", oneHourAgo)
|
||
logger.Info("check oneHourAgo is:", oneHourAgo)
|
||
|
||
err := database.Db.Where("state = 1").
|
||
Where("created_at >= ?", oneHourAgo).
|
||
Where("product_id = ?", MusicProductID).
|
||
Order("created_at desc").
|
||
Find(&orderList).Error
|
||
|
||
if err != nil {
|
||
fmt.Println("query mg_order err:", err.Error())
|
||
logger.Error("query mg_order err:", err.Error())
|
||
|
||
return
|
||
}
|
||
|
||
fmt.Println("orderList size is:", len(orderList))
|
||
logger.Info("orderList size is:", len(orderList))
|
||
|
||
for _, order := range orderList {
|
||
var loginResp LoginResponse
|
||
var token string
|
||
|
||
// 获取手机号并检查Redis中是否已有token
|
||
phoneNumber := order.PhoneNumber
|
||
token, err = getTokenFromRedis(phoneNumber) // 假设有此函数查询Redis
|
||
|
||
// 如果token不存在,则调用Login接口获取token,添加重试机制
|
||
if err != nil || token == "" {
|
||
maxRetries := MaxRetries
|
||
for j := 0; j < maxRetries; j++ {
|
||
loginResp, err = Login(order.ChannelCode, LoginType, "", phoneNumber)
|
||
if err != nil || loginResp.ResCode != "000000" {
|
||
if err != nil {
|
||
fmt.Println("Login failed:", err.Error())
|
||
logger.Error("query mg_order err:", err.Error())
|
||
} else {
|
||
fmt.Println("Login failed, loginResp.ResCode:", loginResp.ResCode)
|
||
logger.Error("Login failed, loginResp.ResCode:", loginResp.ResCode)
|
||
}
|
||
|
||
if j < maxRetries-1 { // 如果不是最后一次尝试,继续重试
|
||
continue
|
||
}
|
||
break
|
||
}
|
||
|
||
token = loginResp.Token
|
||
// 存储token到Redis
|
||
err = storeTokenInRedis(phoneNumber, token) // 假设有此函数存储到Redis
|
||
if err != nil {
|
||
fmt.Println("Failed to store token in Redis:", err.Error())
|
||
logger.Error("Failed to store token in Redis:", err.Error())
|
||
continue
|
||
}
|
||
break
|
||
}
|
||
}
|
||
|
||
// 重试最大次数
|
||
maxRetries := MaxRetries
|
||
|
||
for j := 0; j < maxRetries; j++ {
|
||
// 尝试调用MonthlySubscriptionQuery接口获取订阅状态
|
||
resp, err := MonthlySubscriptionQuery(token, ServiceId, order.ChannelCode)
|
||
if err != nil || resp.ResCode != "000000" {
|
||
if err != nil {
|
||
fmt.Println("MonthlySubscriptionQuery failed:", err.Error())
|
||
logger.Error("MonthlySubscriptionQuery failed:", err.Error())
|
||
} else {
|
||
fmt.Println("MonthlySubscriptionQuery failed, loginResp.ResCode:", resp.ResCode)
|
||
logger.Error("MonthlySubscriptionQuery failed, loginResp.ResCode:", resp.ResCode)
|
||
}
|
||
if j < maxRetries-1 { // 如果不是最后一次尝试,继续重试
|
||
continue
|
||
}
|
||
break
|
||
}
|
||
|
||
if resp.Status == "0" { // 已订购
|
||
// 订购状态为已订购,不处理
|
||
break
|
||
} else if resp.Status == "1" { // 未订购
|
||
// 未订购,更新订单状态
|
||
var cancelFlag int
|
||
subscribeTime := order.CreatedAt
|
||
unsubTime := time.Now().Format("2006-01-02 15:04:05")
|
||
|
||
// 检查是否在1小时内取消
|
||
if IsWithinOneHourCancel(subscribeTime, unsubTime) {
|
||
cancelFlag = 1
|
||
}
|
||
|
||
// 更新订单状态
|
||
err = database.Db.Table("mg_order_log").Where("order_serial = ?", order.OrderSerial).Updates(map[string]interface{}{
|
||
"state": UnsubscribeOK,
|
||
"is_one_hour_cancel": cancelFlag,
|
||
"unsubscribe_time": unsubTime,
|
||
"updated_at": time.Now(),
|
||
}).Error
|
||
if err != nil {
|
||
fmt.Println("Failed to update order:", err.Error())
|
||
logger.Error("Failed to update order:", err.Error())
|
||
continue
|
||
}
|
||
break
|
||
}
|
||
}
|
||
}
|
||
|
||
fmt.Println("****** CheckMusicOrderState end ******")
|
||
logger.Info("****** CheckMusicOrderState end ******")
|
||
}
|
||
|
||
// CheckAIMusicOrderState 定时任务,检查订单状态,是否有1小时退订
|
||
func CheckAIMusicOrderState() {
|
||
logger.Info("****** CheckAIMusicOrderState start ******")
|
||
fmt.Println("****** CheckAIMusicOrderState start ******")
|
||
if database.Db == nil {
|
||
logger.Error("Database connection is nil")
|
||
fmt.Println("Database connection is nil")
|
||
return
|
||
}
|
||
|
||
// 查询订单列表中未退订的用户,查询其是否退订;如果退订,则更新退订时间,判断是否为1小时内退订
|
||
var orderList []MgOrder
|
||
|
||
// 获取当前时间前2个小时
|
||
oneHourAgo := time.Now().Add(-2 * time.Hour)
|
||
fmt.Println("check oneHourAgo is:", oneHourAgo)
|
||
logger.Info("check oneHourAgo is:", oneHourAgo)
|
||
|
||
err := database.Db.Where("state = 1").
|
||
Where("created_at >= ?", oneHourAgo).
|
||
Where("product_id = ?", MusicProductID).
|
||
Order("created_at desc").
|
||
Find(&orderList).Error
|
||
|
||
if err != nil {
|
||
fmt.Println("query mg_order err:", err.Error())
|
||
logger.Error("query mg_order err:", err.Error())
|
||
|
||
return
|
||
}
|
||
|
||
fmt.Println("orderList size is:", len(orderList))
|
||
logger.Info("orderList size is:", len(orderList))
|
||
|
||
for _, order := range orderList {
|
||
var loginResp LoginResponse
|
||
var token string
|
||
|
||
// 获取手机号并检查Redis中是否已有token
|
||
phoneNumber := order.PhoneNumber
|
||
token, err = getTokenFromRedis(phoneNumber) // 假设有此函数查询Redis
|
||
|
||
// 如果token不存在,则调用Login接口获取token,添加重试机制
|
||
if err != nil || token == "" {
|
||
maxRetries := MaxRetries
|
||
for j := 0; j < maxRetries; j++ {
|
||
loginResp, err = Login(order.ChannelCode, LoginType, "", phoneNumber)
|
||
if err != nil || loginResp.ResCode != "000000" {
|
||
if err != nil {
|
||
fmt.Println("Login failed:", err.Error())
|
||
logger.Error("query mg_order err:", err.Error())
|
||
} else {
|
||
fmt.Println("Login failed, loginResp.ResCode:", loginResp.ResCode)
|
||
logger.Error("Login failed, loginResp.ResCode:", loginResp.ResCode)
|
||
}
|
||
|
||
if j < maxRetries-1 { // 如果不是最后一次尝试,继续重试
|
||
continue
|
||
}
|
||
break
|
||
}
|
||
|
||
token = loginResp.Token
|
||
// 存储token到Redis
|
||
err = storeTokenInRedis(phoneNumber, token) // 假设有此函数存储到Redis
|
||
if err != nil {
|
||
fmt.Println("Failed to store token in Redis:", err.Error())
|
||
logger.Error("Failed to store token in Redis:", err.Error())
|
||
continue
|
||
}
|
||
break
|
||
}
|
||
}
|
||
|
||
// 重试最大次数
|
||
maxRetries := MaxRetries
|
||
|
||
for j := 0; j < maxRetries; j++ {
|
||
// 尝试调用MonthlySubscriptionQuery接口获取订阅状态
|
||
resp, err := MonthlySubscriptionQuery(token, ServiceId, order.ChannelCode)
|
||
if err != nil || resp.ResCode != "000000" {
|
||
if err != nil {
|
||
fmt.Println("MonthlySubscriptionQuery failed:", err.Error())
|
||
logger.Error("MonthlySubscriptionQuery failed:", err.Error())
|
||
} else {
|
||
fmt.Println("MonthlySubscriptionQuery failed, loginResp.ResCode:", resp.ResCode)
|
||
logger.Error("MonthlySubscriptionQuery failed, loginResp.ResCode:", resp.ResCode)
|
||
}
|
||
if j < maxRetries-1 { // 如果不是最后一次尝试,继续重试
|
||
continue
|
||
}
|
||
break
|
||
}
|
||
|
||
if resp.Status == "0" { // 已订购
|
||
// 订购状态为已订购,不处理
|
||
break
|
||
} else if resp.Status == "1" { // 未订购
|
||
// 未订购,更新订单状态
|
||
var cancelFlag int
|
||
subscribeTime := order.CreatedAt
|
||
unsubTime := time.Now().Format("2006-01-02 15:04:05")
|
||
|
||
// 检查是否在1小时内取消
|
||
if IsWithinOneHourCancel(subscribeTime, unsubTime) {
|
||
cancelFlag = 1
|
||
}
|
||
|
||
// 更新订单状态
|
||
err = database.Db.Table("mg_order").Where("order_serial = ?", order.OrderSerial).Updates(map[string]interface{}{
|
||
"state": UnsubscribeOK,
|
||
"is_one_hour_cancel": cancelFlag,
|
||
"unsubscribe_time": unsubTime,
|
||
"updated_at": time.Now(),
|
||
}).Error
|
||
if err != nil {
|
||
fmt.Println("Failed to update order:", err.Error())
|
||
logger.Error("Failed to update order:", err.Error())
|
||
continue
|
||
}
|
||
break
|
||
}
|
||
}
|
||
}
|
||
|
||
fmt.Println("****** CheckAIMusicOrderState end ******")
|
||
logger.Info("****** CheckAIMusicOrderState end ******")
|
||
}
|
||
|
||
type MonthlySubscriptionReq struct {
|
||
ChannelCode string `json:"channelCode"`
|
||
Timestamp string `json:"timestamp"`
|
||
Signature string `json:"signature"`
|
||
Token string `json:"token"`
|
||
ServiceId string `json:"serviceId"`
|
||
}
|
||
|
||
type MonthlySubscriptionResp struct {
|
||
ResCode string `json:"resCode"`
|
||
ResMsg string `json:"resMsg"`
|
||
Status string `json:"status"`
|
||
ValidTime string `json:"validTime"`
|
||
}
|
||
|
||
func generateSignature(channelCode, timestamp string) string {
|
||
data := channelCode + timestamp + SignSecretKey
|
||
hash := md5.Sum([]byte(data))
|
||
return hex.EncodeToString(hash[:])
|
||
}
|
||
|
||
// getTokenFromRedis 获取用户 token 从 Redis
|
||
func getTokenFromRedis(phoneNumber string) (string, error) {
|
||
//fmt.Println("enter getTokenFromRedis")
|
||
//logger.Info("enter getTokenFromRedis")
|
||
|
||
// 根据手机号从 Redis 获取 token
|
||
token, err := storage.CacheAdapter.Get(phoneNumber)
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
|
||
//fmt.Println("leave getTokenFromRedis")
|
||
//logger.Info("leave getTokenFromRedis")
|
||
|
||
return token, nil
|
||
}
|
||
|
||
// storeTokenInRedis 设置用户 token 到 Redis
|
||
func storeTokenInRedis(phoneNumber, token string) error {
|
||
//fmt.Println("enter storeTokenInRedis")
|
||
//logger.Info("enter storeTokenInRedis")
|
||
|
||
// 设置 token 有效期为 30 分钟
|
||
expiration := time.Minute * 30
|
||
err := storage.CacheAdapter.Set(phoneNumber, token, int(expiration.Seconds()))
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
//fmt.Println("leave storeTokenInRedis")
|
||
//logger.Info("leave storeTokenInRedis")
|
||
|
||
return nil
|
||
}
|
||
|
||
func MonthlySubscriptionQuery(token, serviceId, channelCode string) (MonthlySubscriptionResp, error) {
|
||
fmt.Println("MonthlySubscriptionQuery start")
|
||
logger.Info("MonthlySubscriptionQuery start")
|
||
|
||
var respData MonthlySubscriptionResp
|
||
|
||
timestamp := time.Now().Format("20060102150405")
|
||
signature := generateSignature(channelCode, timestamp)
|
||
|
||
reqBody := MonthlySubscriptionReq{
|
||
ChannelCode: channelCode,
|
||
Timestamp: timestamp,
|
||
Signature: signature,
|
||
Token: token,
|
||
ServiceId: serviceId,
|
||
}
|
||
|
||
jsonData, err := json.Marshal(reqBody)
|
||
if err != nil {
|
||
fmt.Println("Error marshalling request body:", err)
|
||
return respData, err
|
||
}
|
||
fmt.Println("MonthlySubscriptionQuery req:", string(jsonData))
|
||
logger.Info("MonthlySubscriptionQuery req:", string(jsonData))
|
||
|
||
client := &http.Client{}
|
||
req, err := http.NewRequest("GET", MiGuSubscriptionUrl+"?data="+url.QueryEscape(string(jsonData)), nil)
|
||
if err != nil {
|
||
fmt.Println("Error creating HTTP request:", err)
|
||
logger.Error("Error creating HTTP request:", err)
|
||
return respData, err
|
||
}
|
||
|
||
req.Header.Set("Content-Type", "application/json; charset=utf-8")
|
||
|
||
resp, err := client.Do(req)
|
||
if err != nil {
|
||
fmt.Println("Error making HTTP request:", err)
|
||
logger.Error("Error making HTTP request:", err)
|
||
return respData, err
|
||
}
|
||
defer resp.Body.Close()
|
||
|
||
body, err := io.ReadAll(resp.Body)
|
||
if err != nil {
|
||
fmt.Println("Error reading response body:", err)
|
||
logger.Error("Error reading response body:", err)
|
||
return respData, err
|
||
}
|
||
|
||
fmt.Println("MonthlySubscriptionQuery resp:", string(body))
|
||
logger.Info("MonthlySubscriptionQuery resp:", string(body))
|
||
|
||
err = json.Unmarshal(body, &respData)
|
||
if err != nil {
|
||
fmt.Println("Error unmarshalling response body:", err)
|
||
logger.Error("Error unmarshalling response body:", err)
|
||
return respData, err
|
||
}
|
||
|
||
fmt.Println("MonthlySubscriptionQuery end")
|
||
logger.Info("MonthlySubscriptionQuery end")
|
||
|
||
return respData, nil
|
||
}
|
||
|
||
type LoginRequest struct {
|
||
ChannelCode string `json:"channelCode"`
|
||
Timestamp string `json:"timestamp"`
|
||
Signature string `json:"signature"`
|
||
LoginType string `json:"loginType"`
|
||
CallBackUrl string `json:"callBackUrl,omitempty"`
|
||
Key string `json:"key,omitempty"`
|
||
Msisdn string `json:"msisdn,omitempty"`
|
||
}
|
||
|
||
type LoginResponse struct {
|
||
ResCode string `json:"resCode"`
|
||
ResMsg string `json:"resMsg"`
|
||
Token string `json:"token"`
|
||
}
|
||
|
||
func Login(channelCode, loginType, callBackUrl, msisdn string) (LoginResponse, error) {
|
||
fmt.Println("login start")
|
||
logger.Info("login start")
|
||
var respData LoginResponse
|
||
|
||
timestamp := time.Now().Format("20060102150405")
|
||
signature := generateSignature(channelCode, timestamp)
|
||
|
||
reqBody := LoginRequest{
|
||
ChannelCode: channelCode,
|
||
Timestamp: timestamp,
|
||
Signature: signature,
|
||
LoginType: loginType,
|
||
}
|
||
|
||
if loginType == "1" && callBackUrl != "" {
|
||
reqBody.CallBackUrl = callBackUrl
|
||
} else if loginType == "3" {
|
||
reqBody.Key = LoginSecretKey
|
||
reqBody.Msisdn = msisdn
|
||
}
|
||
|
||
jsonData, err := json.Marshal(reqBody)
|
||
if err != nil {
|
||
fmt.Println("Error marshalling request body:", err)
|
||
return respData, err
|
||
}
|
||
fmt.Println("login req:", string(jsonData))
|
||
logger.Info("login req:", string(jsonData))
|
||
|
||
client := &http.Client{}
|
||
req, err := http.NewRequest("GET", MiGuLoginUrl+"?data="+url.QueryEscape(string(jsonData)), nil)
|
||
if err != nil {
|
||
fmt.Println("Error creating HTTP request:", err)
|
||
logger.Error("Error creating HTTP request:", err)
|
||
return respData, err
|
||
}
|
||
|
||
req.Header.Set("Content-Type", "application/json; charset=utf-8")
|
||
|
||
resp, err := client.Do(req)
|
||
if err != nil {
|
||
fmt.Println("Error making HTTP request:", err)
|
||
logger.Error("Error making HTTP request:", err)
|
||
return respData, err
|
||
}
|
||
defer resp.Body.Close()
|
||
|
||
body, err := io.ReadAll(resp.Body)
|
||
if err != nil {
|
||
fmt.Println("Error reading response body:", err)
|
||
logger.Error("Error reading response body:", err)
|
||
return respData, err
|
||
}
|
||
|
||
fmt.Println("login resp:", string(body))
|
||
logger.Info("login resp:", string(body))
|
||
|
||
err = json.Unmarshal(body, &respData)
|
||
if err != nil {
|
||
fmt.Println("Error unmarshalling response body:", err)
|
||
logger.Error("Error unmarshalling response body:", err)
|
||
return respData, err
|
||
}
|
||
|
||
fmt.Println("login end")
|
||
logger.Info("login end")
|
||
|
||
return respData, nil
|
||
}
|