migu_admin_server/app/admin/models/migu.go

1784 lines
67 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 models
import (
"bytes"
"encoding/json"
"fmt"
"github.com/go-admin-team/go-admin-core/logger"
"github.com/xuri/excelize/v2"
"go-admin/common/database"
"go-admin/tools"
"gorm.io/gorm"
"io"
"log"
"math/rand"
"net/http"
"net/url"
"strconv"
"sync"
"time"
)
const (
MiGUSendCaptchaUrl = "https://mg.zeqinkeji.cn/coupon-provider/captcha/request"
MiGUSubmitCaptchaUrl = "https://mg.zeqinkeji.cn/coupon-provider/captcha/submit"
MiGUCheckOrderUrl = "https://mg.zeqinkeji.cn/coupon-provider/api/orders/exchange-type/check"
MiGUQueryRightsInfoUrl = "https://betagame.migufun.com/member/shareRights/v1.1.0.7/queryRightsInfo"
ProductID = 1
ChannelCode = "40458652536"
SM4KEy = "ve3N1I75AJ0Oy6nA"
SubscribeOK = 1 // 订阅成功
UnsubscribeOK = 2 // 退订
SKUCODE = "miguyouxizuanshihuiyuan-yy"
ExportFile = "/www/server/images/export/"
MiGuExportUrl = "https://migu.admin.deovo.com/load/export/"
//MiGuExportUrl = "/Users/max/Documents/" // 本地环境
)
// 以下是数据库表结构
// MgProduct 产品管理表对应的结构体
type MgProduct struct {
Model
Name string `gorm:"size:255;not null" json:"name"` // 产品名称
UniqueCode string `gorm:"size:255;not null" json:"unique_code"` // 产品唯一标识编码
SkuName string `gorm:"size:255" json:"sku_name"` // SKU名称
BillingPointID int64 `json:"billing_point_id"` // 计费点ID
ChannelCode string `gorm:"size:255" json:"channel_code"` // 渠道编码
ProductApiID string `gorm:"size:255" json:"product_api_id"` // 产品ID接口用
ChannelApiID string `gorm:"size:255" json:"channel_api_id"` // 渠道ID接口用
OfficialPage string `gorm:"size:255" json:"official_page"` // 官方落地页
}
// MgOrder 订单管理表对应的结构体
type MgOrder struct {
Model
ProductID int64 `json:"product_id"` // 产品ID
ChannelCode string `gorm:"size:255" json:"channel_code"` // 渠道编码
OrderSerial string `gorm:"size:255;not null" json:"order_serial"` // 订单流水号
SubscribeTime *time.Time `json:"subscribe_time"` // 订阅时间
PhoneNumber string `gorm:"size:20;not null" json:"phone_number"` // 手机号
SM4PhoneNumber string `gorm:"size:255" json:"sm4_phone_number"` // SM4加密手机号
ExternalOrderID string `gorm:"size:255" json:"external_order_id"` // 外部平台订单号(如咪咕等)
ChannelTradeNo string `gorm:"size:255" json:"channel_trade_no"` // 渠道订单号
State int `gorm:"size:255" json:"state"` // 用户订阅状态 1-订阅成功 2-已取消订阅
UnsubscribeTime *time.Time `json:"unsubscribe_time"` // 取消订阅时间
IsOneHourCancel int `json:"is_one_hour_cancel"` // 是否1小时内退订 1-是 其他-否
}
type MgOrderCopy struct {
Model
ProductID int64 `json:"product_id"` // 产品ID
ChannelCode string `gorm:"size:255" json:"channel_code"` // 渠道编码
OrderSerial string `gorm:"size:255;not null" json:"order_serial"` // 订单流水号
SubscribeTime *time.Time `json:"subscribe_time"` // 订阅时间
PhoneNumber string `gorm:"size:20;not null" json:"phone_number"` // 手机号
SM4PhoneNumber string `gorm:"size:255" json:"sm4_phone_number"` // SM4加密手机号
ExternalOrderID string `gorm:"size:255" json:"external_order_id"` // 外部平台订单号(如咪咕等)
ChannelTradeNo string `gorm:"size:255" json:"channel_trade_no"` // 渠道订单号
State int `gorm:"size:255" json:"state"` // 用户订阅状态 1-订阅成功 2-已取消订阅
UnsubscribeTime *time.Time `json:"unsubscribe_time"` // 取消订阅时间
IsOneHourCancel int `json:"is_one_hour_cancel"` // 是否1小时内退订 1-是 其他-否
}
// MgChannel 渠道列表
type MgChannel struct {
Model
ProductID int `json:"product_id"` // 产品ID
MainChannelCode string `json:"main_channel_code"` // 主渠道编码
SubChannelCode string `json:"sub_channel_code"` // 子渠道编码
SubscribeURL string `json:"subscribe_url"` // 订购成功通知接口url
UnsubscribeURL string `json:"unsubscribe_url"` // 退订通知接口url
Status int `json:"status"` // 状态 (0: 停用, 1: 启用)
Remarks string `json:"remarks"` // 备注
}
// HourSummaryListResp 历史汇总(按小时)-出参
type HourSummaryListResp struct {
List []MgHourSummary `json:"list"` // 列表数据
Count int `json:"count"` // 数据总数
PageSize int `json:"page_size"` // 每页条数
PageNum int `json:"page_num"` // 当前页数
SummaryData *TotalHourSummary `json:"summary_data"` // 汇总数据,单条数据时返回
}
// TotalHourSummary 历史汇总查询表汇总数据对应的结构体(按小时)
type TotalHourSummary struct {
SubmissionCount int `json:"submission_count"` // 提交数
NewUserCount int `json:"new_user_count"` // 新用户数
SubmissionSuccessRate string `json:"submission_success_rate"` // 提交成功率
NewUserUnsubWithinHour int `json:"new_user_unsub_within_hour"` // 当日新用户退订数1小时以内
NewUserUnsubWithinHourRate string `json:"new_user_unsub_within_hour_rate"` // 当日新用户退订率1小时以内
NewUserUnsubOnDay int `json:"new_user_unsub_on_day"` // 当日新用户退订数
NewUserUnsubOnDayRate string `json:"new_user_unsub_on_day_rate"` // 当日新用户退订率
TotalNewUserUnsub int `json:"total_new_user_unsub"` // 累计新用户退订数
TotalNewUserUnsubRate string `json:"total_new_user_unsub_rate"` // 累计新用户退订率
}
// MgHourSummary 历史汇总查询表对应的结构体(按小时)
type MgHourSummary struct {
Hour string `json:"hour"` // 日期
ProductID int64 `json:"product_id"` // 产品ID
ChannelCode string `gorm:"size:255" json:"channel_code"` // 渠道编码
SubmissionCount int `json:"submission_count"` // 提交数
NewUserCount int `json:"new_user_count"` // 新用户数
SubmissionSuccessRate string `json:"submission_success_rate"` // 提交成功率
NewUserUnsubWithinHour int `json:"new_user_unsub_within_hour"` // 当日新用户退订数1小时以内
NewUserUnsubWithinHourRate string `json:"new_user_unsub_within_hour_rate"` // 当日新用户退订率1小时以内
NewUserUnsubOnDay int `json:"new_user_unsub_on_day"` // 当日新用户退订数
NewUserUnsubOnDayRate string `json:"new_user_unsub_on_day_rate"` // 当日新用户退订率
TotalNewUserUnsub int `json:"total_new_user_unsub"` // 累计新用户退订数
TotalNewUserUnsubRate string `json:"total_new_user_unsub_rate"` // 累计新用户退订率
}
// MgHistoricalSummary 历史汇总查询表对应的结构体
type MgHistoricalSummary struct {
Date string `json:"date"` // 日期
ProductID int64 `json:"product_id"` // 产品ID
ChannelCode string `gorm:"size:255" json:"channel_code"` // 渠道编码
SubmissionCount int `json:"submission_count"` // 提交数
NewUserCount int `json:"new_user_count"` // 新用户数
SubmissionSuccessRate string `json:"submission_success_rate"` // 提交成功率
NewUserUnsubWithinHour int `json:"new_user_unsub_within_hour"` // 当日新用户退订数1小时以内
NewUserUnsubWithinHourRate string `json:"new_user_unsub_within_hour_rate"` // 当日新用户退订率1小时以内
NewUserUnsubOnDay int `json:"new_user_unsub_on_day"` // 当日新用户退订数
NewUserUnsubOnDayRate string `json:"new_user_unsub_on_day_rate"` // 当日新用户退订率
TotalNewUserUnsub int `json:"total_new_user_unsub"` // 累计新用户退订数
TotalNewUserUnsubRate string `json:"total_new_user_unsub_rate"` // 累计新用户退订率
//Province string `gorm:"size:255" json:"province"` // 省份
}
// MgRealtimeSummary 当日实时汇总表对应的结构体
type MgRealtimeSummary struct {
ProductID int64 `json:"product_id"` // 产品ID
ChannelCode string `json:"channel_code"` // 渠道编码
SubmissionCount int `json:"submission_count"` // 提交数
NewUserCount int `json:"new_user_count"` // 新用户数
SubmissionSuccessRate string `json:"submission_success_rate"` // 提交成功率
NewUserUnsubWithinHour int `json:"new_user_unsub_within_hour"` // 当日新用户退订数1小时以内
NewUserUnsubWithinHourRate string `json:"new_user_unsub_within_hour_rate"` // 当日新用户退订率1小时以内
NewUserUnsubOnDay int `json:"new_user_unsub_on_day"` // 当日新用户退订数
NewUserUnsubOnDayRate string `json:"new_user_unsub_on_day_rate"` // 当日新用户退订率
//Province string `json:"province"` // 省份
}
// MgTransactionLog 交易流水记录表对应的结构体
type MgTransactionLog struct {
Model
ProductID int64 `json:"product_id"` // 产品ID
ChannelCode string `gorm:"size:255" json:"channel_code"` // 渠道编码
Province string `gorm:"size:255" json:"province"` // 省份
PhoneNumber string `gorm:"size:20" json:"phone_number"` // 手机号
OutTradeNo string `gorm:"size:255" json:"out_trade_no"` // 平台订单号
LinkId string `gorm:"size:255" json:"link_id"` // linkId咪咕订单号
ChannelTradeNo string `gorm:"size:255" json:"channel_trade_no"` // 渠道订单号
Result string `gorm:"size:255" json:"result"` // 交易结果
Reason string `gorm:"size:255" json:"reason"` // 交易失败原因
VerificationCode string `gorm:"size:255" json:"verification_code"` // 验证码
OrderTime *time.Time `gorm:"type:datetime" json:"order_time"` // 订单时间
}
// MgUserRetention 用户留存记录表对应的结构体
type MgUserRetention struct {
RetentionMonth string `gorm:"size:7" json:"retention_month"` // 留存月份格式YYYY-MM
ChannelCode string `gorm:"size:255" json:"channel_code"` // 渠道编码
ProductID int64 `json:"product_id"` // 产品ID
NewUserCount int `json:"new_user_count"` // 新增用户数
RetainedUserCount int `json:"retained_user_count"` // 留存用户数(实时)
RetentionRate string `json:"retention_rate"` // 留存率(实时,以百分比形式存储)
LastTwoMonthDate string `json:"last_two_month_date"` // 最近2个月留存日期格式YYYY-MM-DD
LastTwoMonthRetentionCount int `json:"last_two_month_retention_count"` // 最近2个月留存用户数如12/1, 11/1
LastTwoMonthRetentionRate string `json:"last_two_month_retention_rate"` // 最近2个月留存率如12/1, 11/1
LastMonthDate string `json:"last_month_date"` // 最近1个月留存日期格式YYYY-MM-DD
LastMonthRetentionCount int `json:"last_month_retention_count"` // 最近1个月留存用户数如12/1
LastMonthRetentionRate string `json:"last_month_retention_rate"` // 最近1个月留存率如12/1
}
// MgUserDayRetention 用户留存记录表(按天)对应的结构体
type MgUserDayRetention struct {
Date string `json:"date"` // 留存日期格式YYYY-MM-DD
RetainedUserCount int `json:"retained_user_count"` // 留存用户数
RetentionRate string `json:"retention_rate"` // 留存率
UserUnsubOnDay int `json:"user_unsub_on_day"` // 当日退订数
}
// 以下是接口出入参结构体
type Action struct {
OrderChannel string `json:"orderChannel"` // 订购渠道
OrderApp string `json:"orderApp"` // 订购包名
}
type SendCaptchaReq struct {
Phone string `json:"phone" binding:"required"` // 手机号码
Channel string `json:"channel" binding:"required"` // 渠道号
SkuCode string `json:"skuCode" binding:"required"` // 产品编号
UserAction Action `json:"userAction"` // 用户操作记录
OutTradeNo string `json:"outTradeNo"` // 自定义订单号
}
type SendCaptchaReqEx struct {
Phone string `json:"phone" binding:"required"` // 手机号码
Channel string `json:"channel" binding:"required"` // 渠道号
SkuCode string `json:"skuCode" binding:"required"` // 产品编号
OutTradeNo string `json:"outTradeNo"` // 自定义订单号
}
type MiGuSendCaptchaReq struct {
Phone string `json:"phone" binding:"required"` // 手机号码
Channel string `json:"channel" binding:"required"` // 渠道号
SkuCode string `json:"skuCode" binding:"required"` // 产品编号
UserAction Action `json:"userAction"` // 用户操作记录
OutTradeNo string `json:"outTradeNo"` // 自定义订单号
}
type SendCaptchaResp struct {
LinkId string `json:"linkId"`
}
type SubmitOrderReq struct {
LinkId string `json:"linkId" binding:"required"` // 验证码接口返回的linkId
SmsCode string `json:"smsCode" binding:"required"` // 验证码
Phone string `json:"phone" binding:"required"` // 手机号码
Channel string `json:"channel" binding:"required"` // 渠道号
SkuCode string `json:"skuCode" binding:"required"` // 产品编号
UserAction Action `json:"userAction"` // 用户操作记录
OutTradeNo string `json:"outTradeNo"` // 自定义订单号
}
type SubmitOrderReqEx struct {
LinkId string `json:"linkId" binding:"required"` // 验证码接口返回的linkId
SmsCode string `json:"smsCode" binding:"required"` // 验证码
Phone string `json:"phone" binding:"required"` // 手机号码
Channel string `json:"channel" binding:"required"` // 渠道号
SkuCode string `json:"skuCode" binding:"required"` // 产品编号
OutTradeNo string `json:"outTradeNo"` // 自定义订单号
}
type MiGuSubmitOrderReq struct {
LinkId string `json:"linkId" binding:"required"` // 验证码接口返回的linkId
SmsCode string `json:"smsCode" binding:"required"` // 验证码
Phone string `json:"phone" binding:"required"` // 手机号码
Channel string `json:"channel" binding:"required"` // 渠道号
SkuCode string `json:"skuCode" binding:"required"` // 产品编号
UserAction Action `json:"userAction"` // 用户操作记录
OutTradeNo string `json:"outTradeNo"` // 自定义订单号
}
type SubmitOrderResp struct {
LinkId string `json:"linkId"`
}
type MiGuRsp struct {
RequestId string `json:"requestId"`
Code string `json:"code"`
Msg string `json:"message"`
Data SubmitOrderResp `json:"data"`
}
type MiGuCheckRsp struct {
RequestId string `json:"requestId"`
Code int `json:"code"`
Msg string `json:"message"`
Data SubmitOrderResp `json:"data"`
}
type CheckOrderReq struct {
OutTradeNo string `json:"outTradeNo" binding:"required"` // 自定义订单号
}
type CheckOrderReqEx struct {
LinkId string `json:"linkId"`
}
type CheckOrderResp struct {
RequestId string `json:"requestId"`
Code string `json:"code"` //-1已退订0:未退订404:订单记录不存在
Msg string `json:"message"`
}
// QueryRightsInfoReq 查询用户会员权益订购信息接口-入参
type QueryRightsInfoReq struct {
AppChannelList []string `json:"appChannelList" binding:"required"` // 渠道号
Mobile string `json:"mobile" binding:"required"` // 手机号sm4加密
PackageId string `json:"packageId"` // 计费点id
}
type QueryRightsInfoReqEx struct {
Channel string `json:"channel" binding:"required"` // 渠道号
SkuCode string `json:"skuCode" binding:"required"` // 产品编号
Phone string `json:"phone" binding:"required"` // 手机号码
}
// QueryRightsInfoResp 查询用户会员权益订购信息接口-出参
type QueryRightsInfoResp struct {
ReturnCode string `json:"returnCode"`
Message string `json:"message"`
ResultData []Package `json:"resultData"`
ServerTime int64 `json:"serverTime"`
}
type Package struct {
AppChannel string `json:"appChannel"`
PackageID string `json:"packageId"`
PackageName string `json:"packageName"`
SubTime string `json:"subTime"`
ExpireTime string `json:"expireTime"`
MonthlyContinuous int `json:"monthlyContinuous"`
IsUnsub int `json:"isUnsub"`
UnsubTime string `json:"unsubTime"`
}
// TransactionListReq 查询交易流水记录-入参
type TransactionListReq struct {
Phone string `json:"phone"` // 手机号码
Channel string `json:"channel"` // 渠道号
SkuCode int `json:"skuCode"` // 产品编号
Result string `json:"result"` // 交易结果
StartTime string `json:"start_time"` // 开始时间
EndTime string `json:"end_time"` // 结束时间
PageNum int `json:"page_num"` // 页码
PageSize int `json:"page_size"` // 每页条数
}
// TransactionListResp 查询交易流水记录-出参
type TransactionListResp struct {
List []MgTransactionLog `json:"list"`
Count int `json:"count"`
PageSize int `json:"page_size"`
PageNum int `json:"page_num"`
}
// ProductListReq 查询权益产品列表-入参
type ProductListReq struct {
PageNum int `json:"page_num"` // 页码
PageSize int `json:"page_size"` // 每页条数
}
// ProductListResp 查询权益产品列表-出参
type ProductListResp struct {
List []MgProduct `json:"list"`
Count int `json:"count"`
PageSize int `json:"page_size"`
PageNum int `json:"page_num"`
}
// ChannelListReq 查询渠道列表-入参
type ChannelListReq struct {
PageNum int `json:"page_num"` // 页码
PageSize int `json:"page_size"` // 每页条数
IsExport uint32 `json:"is_export"` // 1-导出
}
// ChannelListResp 查询渠道列表-出参
type ChannelListResp struct {
List []MgChannel `json:"list"`
Count int `json:"count"`
PageSize int `json:"page_size"`
PageNum int `json:"page_num"`
}
// OrderListReq 查询订单列表-入参
type OrderListReq struct {
StartTime string `json:"start_time"` // 开始时间
EndTime string `json:"end_time"` // 结束时间
CancelStartTime string `json:"cancel_start_time"` // 退订开始时间
CancelEndTime string `json:"cancel_end_time"` // 退订结束时间
SkuCode int `json:"skuCode"` // 产品编号
Channel string `json:"channel"` // 渠道号
OrderSerial string `json:"order_serial"` // 订单流水号
OutTradeNo string `json:"outTradeNo"` // 外部订单号
ChannelTradeNo string `json:"channelTradeNo"` // 渠道订单号
Phone string `json:"phone"` // 手机号码
SM4PhoneNumber string `json:"sm4_phone_number"` // SM4加密手机号
State int `json:"state"` // 退订状态 0-查所有 1-未退订 2-已退订 3-1小时内退订
PageNum int `json:"page_num"` // 页码
PageSize int `json:"page_size"` // 每页条数
IsExport uint32 `json:"is_export"` // 1-导出
}
// OrderListResp 查询订单列表-出参
type OrderListResp struct {
List []MgOrder `json:"list"`
Count int `json:"count"`
PageSize int `json:"page_size"`
PageNum int `json:"page_num"`
}
// HistoricalSummaryListReq 历史汇总查询-入参
type HistoricalSummaryListReq struct {
StartTime string `json:"start_time"` // 开始时间 xxxx-xx-xx
EndTime string `json:"end_time"` // 结束时间 xxxx-xx-xx
SkuCode int `json:"skuCode"` // 产品编号
Channel string `json:"channel"` // 渠道号
PageNum int `json:"page_num"` // 页码
PageSize int `json:"page_size"` // 每页条数
IsExport uint32 `json:"is_export"` // 1-导出
//Province string `json:"province"` // 省份
}
// HistoricalSummaryListResp 历史汇总查询-出参
type HistoricalSummaryListResp struct {
List []MgHistoricalSummary `json:"list"`
Count int `json:"count"`
PageSize int `json:"page_size"`
PageNum int `json:"page_num"`
}
// RealtimeSummaryListReq 当日实时汇总查询-入参
type RealtimeSummaryListReq struct {
StartTime string `json:"start_time"` // 开始时间 00:00:00
EndTime string `json:"end_time"` // 结束时间 23:59:59
SkuCode int `json:"skuCode"` // 产品编号
Channel string `json:"channel"` // 渠道号
PageNum int `json:"page_num"` // 页码
PageSize int `json:"page_size"` // 每页条数
IsExport uint32 `json:"is_export"` // 1-导出
//Province string `json:"province"` // 省份
}
// RealtimeSummaryListResp 当日实时汇总查询-出参
type RealtimeSummaryListResp struct {
List []MgRealtimeSummary `json:"list"`
Count int `json:"count"`
PageSize int `json:"page_size"`
PageNum int `json:"page_num"`
}
// UserRetentionListReq 用户留存记录查询-入参
type UserRetentionListReq struct {
//Date string `json:"date"` // 月用户格式YYYY-MM
RetentionMonth string `json:"retention_month"` // 留存月份格式YYYY-MM
SkuCode int `json:"skuCode"` // 产品编号
Channel string `json:"channel"` // 渠道号
Province string `json:"province"` // 省份
PageNum int `json:"page_num"` // 页码
PageSize int `json:"page_size"` // 每页条数
IsExport uint32 `json:"is_export"` // 1-导出
}
// UserRetentionListResp 用户留存记录查询-出参
type UserRetentionListResp struct {
List []MgUserRetention `json:"list"`
Count int `json:"count"`
PageSize int `json:"page_size"`
PageNum int `json:"page_num"`
}
// UserDayRetentionListReq 用户留存记录(按天)查询-入参
type UserDayRetentionListReq struct {
RetentionMonth string `json:"retention_month" binding:"required"` // 留存月份格式YYYY-MM
SkuCode int `json:"skuCode" binding:"required"` // 产品编号
Channel string `json:"channel" binding:"required"` // 渠道号
PageNum int `json:"page_num"` // 页码
PageSize int `json:"page_size"` // 每页条数
OnlyFirstDay bool `json:"only_first_day"` // 是否只查询每个月1号的数据
IsExport uint32 `json:"is_export"` // 1-导出
}
// UserDayRetentionListResp 用户留存记录(按天)查询-出参
type UserDayRetentionListResp struct {
List []MgUserDayRetention `json:"list"`
Count int `json:"count"`
PageSize int `json:"page_size"`
PageNum int `json:"page_num"`
}
// SysChannelListReq 查询渠道列表入参
type SysChannelListReq struct {
PageNum int `json:"page_num"` // 页码
PageSize int `json:"page_size"` // 每页条数
}
// SysChannelListResp 查询渠道列表出参
type SysChannelListResp struct {
List []ChannelData `json:"list"`
Count int `json:"count"`
PageSize int `json:"page_size"`
PageNum int `json:"page_num"`
}
type ChannelData struct {
ChannelCode string `json:"channel_code"` // 渠道编码
}
// AddProductReq 添加新产品请求结构体
type AddProductReq struct {
Name string `json:"name" binding:"required"` // 产品名称
UniqueCode string `json:"unique_code" binding:"required"` // 产品唯一标识编码
SkuName string `json:"sku_name" binding:"required"` // SKU名称
BillingPointID int64 `json:"billing_point_id" binding:"required"` // 计费点ID
ChannelCode string `json:"channel_code" binding:"required"` // 渠道编码
ProductApiID string `json:"product_api_id"` // 产品ID接口用
ChannelApiID string `json:"channel_api_id" binding:"required"` // 渠道ID接口用
OfficialPage string `json:"official_page"` // 官方落地页
}
// AddProductResp 添加新产品响应结构体
type AddProductResp struct {
ID uint32 `json:"id"` // 新增产品的ID
}
// UpdateProductReq 修改产品请求结构体
type UpdateProductReq struct {
ID int64 `json:"id" binding:"required"` // 产品ID
Name string `json:"name"` // 产品名称
UniqueCode string `json:"unique_code"` // 产品唯一标识编码
SkuName string `json:"sku_name"` // SKU名称
BillingPointID int64 `json:"billing_point_id"` // 计费点ID
ChannelCode string `json:"channel_code"` // 渠道编码
ProductApiID string `json:"product_api_id"` // 产品ID接口用
ChannelApiID string `json:"channel_api_id"` // 渠道ID接口用
OfficialPage string `json:"official_page"` // 官方落地页
}
// UpdateProductResp 修改产品响应结构体
type UpdateProductResp struct {
ID uint32 `json:"id"` // 修改的产品ID
}
// DeleteProductReq 删除产品请求结构体
type DeleteProductReq struct {
ID int64 `json:"id" binding:"required"` // 产品ID
}
// AddChannelReq 创建渠道请求结构体
type AddChannelReq struct {
ProductID int `json:"product_id" binding:"required"` // 产品ID (必填)
MainChannelCode string `json:"main_channel_code" binding:"required"` // 主渠道编码 (必填)
SubChannelCode string `json:"sub_channel_code" binding:"required"` // 子渠道编码 (必填)
SubscribeURL string `json:"subscribe_url"` // 订购成功通知接口url (非必填)
UnsubscribeURL string `json:"unsubscribe_url"` // 退订通知接口url (非必填)
Status int `json:"status" binding:"required"` // 状态 (2: 停用, 1: 启用) (必填)
Remarks string `json:"remarks"` // 备注 (非必填)
}
// AddChannelResp 创建渠道响应结构体
type AddChannelResp struct {
ChannelID uint32 `json:"channel_id"` // 创建的渠道ID
}
// UpdateChannelReq 更新渠道请求结构体
type UpdateChannelReq struct {
ID int `json:"id" binding:"required"` // 渠道ID (必填)
MainChannelCode string `json:"main_channel_code"` // 主渠道编码 (非必填)
SubChannelCode string `json:"sub_channel_code"` // 子渠道编码 (非必填)
SubscribeURL string `json:"subscribe_url"` // 订购成功通知接口url (非必填)
UnsubscribeURL string `json:"unsubscribe_url"` // 退订通知接口url (非必填)
Status int `json:"status"` // 状态 (0: 停用, 1: 启用) (非必填)
Remarks string `json:"remarks"` // 备注 (非必填)
}
// UpdateChannelResp 更新渠道响应结构体
type UpdateChannelResp struct {
// Any relevant response fields can go here
}
// DeleteChannelReq 删除渠道请求结构体
type DeleteChannelReq struct {
ID int `json:"id" binding:"required"` // 渠道ID (必填)
}
// DeleteChannelResp 删除渠道响应结构体
type DeleteChannelResp struct {
// Any relevant response fields can go here
}
type HomepageDataSummaryReq struct {
StartTime string `json:"start_time"` // 查询开始时间
EndTime string `json:"end_time"` // 查询结束时间
ProductID int `json:"product_id"` // 产品ID
Channel string `json:"channel"` // 渠道名称
}
type DailyData struct {
Date string `json:"date"` // 日期
NewUserCount int64 `json:"new_user_count"` // 新增用户数
UnsubscribedUserCount int64 `json:"unsubscribed_user_count"` // 退订用户数
UnsubscribedWithinOneHour int64 `json:"unsubscribed_within_one_hour"` // 1小时内退订用户数
UnsubscribeRate string `json:"unsubscribe_rate"` // 退订率
UnsubscribeWithinOneHourRate string `json:"unsubscribe_within_one_hour_rate"` // 1小时内退订率
TotalCancelCount int64 `json:"total_cancel_count"` // 每日退订用户数合计
}
type HomepageDataSummaryResp struct {
Summary SummaryData `json:"summary"` // 汇总数据
DailyDataList []DailyData `json:"daily_data"` // 每天的数据列表
}
// SummaryData represents the overall summary data
type SummaryData struct {
TotalUserCount int `json:"total_user_count"` // 总用户数
UnsubscribedUserCount int `json:"unsubscribed_user_count"` // 退订用户数
OneHourUnsubscribedUserCount int `json:"one_hour_unsubscribed_user_count"` // 1小时内退订用户数
RetainedUserCount int `json:"retained_user_count"` // 留存用户数
UnsubscribeRate string `json:"unsubscribe_rate"` // 退订率 (字符串,保留两位小数,如 "26.35%")
OneHourUnsubscribeRate string `json:"one_hour_unsubscribe_rate"` // 1小时退订率 (字符串,保留两位小数,如 "26.35%")
RetentionRate string `json:"retention_rate"` // 留存率 (字符串,保留两位小数,如 "73.65%")
}
// RetentionMonthsReq 查询用户留存月份的请求结构体
type RetentionMonthsReq struct {
StartTime string `json:"start_time"` // 查询开始时间
EndTime string `json:"end_time"` // 查询结束时间
ProductID int `json:"product_id"` // 产品ID
Channel string `json:"channel"` // 渠道名称
}
// MonthlyRetention 表示每月留存数据
type MonthlyRetention struct {
Month string `json:"month"` // 年月
NewUserCount int `json:"new_user_count"` // 新用户数
ValidUsersCount int `json:"valid_users_count"` // 当月有效用户数
}
// RetentionMonthsResp 表示响应参数结构体
type RetentionMonthsResp struct {
TotalNewUsers int `json:"total_new_users"` // 总新用户数
TotalValidUsers int `json:"total_valid_users"` // 总有效用户数
MonthlyRetentionData []MonthlyRetention `json:"monthly_retention_data"` // 每月留存数据
}
// MiGuCaptchaRequest 调用下单接口(森越转发)
func MiGuCaptchaRequest(r *MiGuSendCaptchaReq) (MiGuRsp, error) {
var miGuResp MiGuRsp
data, err := json.Marshal(r)
if err != nil {
logger.Error("MiGuCaptchaRequest err:", err)
return miGuResp, err
}
fmt.Println("data json:", string(data))
client := http.Client{}
req, err := http.NewRequest("POST", MiGUSendCaptchaUrl, bytes.NewBuffer(data))
if err != nil {
logger.Error("MiGuCaptchaRequest err:", err)
return miGuResp, err
}
req.Header.Set("Content-Type", "application/json; charset=utf-8")
resp, err := client.Do(req)
if err != nil {
logger.Error("MiGuCaptchaRequest err:", err)
return miGuResp, err
}
body, err := io.ReadAll(resp.Body)
if err != nil {
logger.Error("MiGuCaptchaRequest err:", err)
return miGuResp, err
}
fmt.Println("body:", string(body))
defer resp.Body.Close()
err = json.Unmarshal(body, &miGuResp)
if err != nil {
logger.Error("MiGuCaptchaRequest err:", err)
return miGuResp, err
}
return miGuResp, nil
}
// MiGuCaptchaSubmit 调用提交接口(森越转发)
func MiGuCaptchaSubmit(r *MiGuSubmitOrderReq) (MiGuRsp, error) {
var miGuResp MiGuRsp
data, err := json.Marshal(r)
if err != nil {
logger.Error("oppoSendData err:", err)
return miGuResp, err
}
fmt.Println("data json:", string(data))
client := http.Client{}
req, err := http.NewRequest("POST", MiGUSubmitCaptchaUrl, bytes.NewBuffer(data))
if err != nil {
logger.Error("oppoSendData err:", err)
return miGuResp, err
}
req.Header.Set("Content-Type", "application/json; charset=utf-8")
resp, err := client.Do(req)
if err != nil {
logger.Error("oppoSendData err:", err)
return miGuResp, err
}
body, err := io.ReadAll(resp.Body)
if err != nil {
logger.Error("oppoSendData err:", err)
return miGuResp, err
}
fmt.Println("body:", string(body))
defer resp.Body.Close()
err = json.Unmarshal(body, &miGuResp)
if err != nil {
logger.Error("clueResp err:", err)
return miGuResp, err
}
return miGuResp, nil
}
// MiGuCheckOrder 查询是否已经退订接口(森越转发)
func MiGuCheckOrder(r *CheckOrderReq) (MiGuCheckRsp, error) {
var miGuResp MiGuCheckRsp
data, err := json.Marshal(r)
if err != nil {
logger.Error("oppoSendData err:", err)
return miGuResp, err
}
fmt.Println("data json:", string(data))
client := http.Client{}
req, err := http.NewRequest("POST", MiGUCheckOrderUrl, bytes.NewBuffer(data))
if err != nil {
logger.Error("oppoSendData err:", err)
return miGuResp, err
}
req.Header.Set("Content-Type", "application/json; charset=utf-8")
resp, err := client.Do(req)
if err != nil {
logger.Error("oppoSendData err:", err)
return miGuResp, err
}
body, err := io.ReadAll(resp.Body)
if err != nil {
logger.Error("oppoSendData err:", err)
return miGuResp, err
}
fmt.Println("body:", string(body))
defer resp.Body.Close()
err = json.Unmarshal(body, &miGuResp)
if err != nil {
logger.Error("clueResp err:", err)
return miGuResp, err
}
return miGuResp, nil
}
// MiGuQueryRightsInfo 查询用户会员权益订购信息接口(咪咕接口)
func MiGuQueryRightsInfo(r *QueryRightsInfoReq) (QueryRightsInfoResp, error) {
var miGuResp QueryRightsInfoResp
sm4Phone, err := tools.SM4Encrypt(SM4KEy, r.Mobile)
if err != nil {
logger.Error("SM4Encrypt err:", err)
return miGuResp, err
}
r.Mobile = sm4Phone
data, err := json.Marshal(r)
if err != nil {
logger.Error("MiGuQueryRightsInfo json.Marshal err:", err)
return miGuResp, err
}
fmt.Println("data json:", string(data))
client := http.Client{}
req, err := http.NewRequest("POST", MiGUQueryRightsInfoUrl, bytes.NewBuffer(data))
if err != nil {
logger.Error("MiGuQueryRightsInfo err:", err)
return miGuResp, err
}
req.Header.Set("Content-Type", "application/json; charset=utf-8")
resp, err := client.Do(req)
if err != nil {
logger.Error("MiGuQueryRightsInfo err:", err)
return miGuResp, err
}
body, err := io.ReadAll(resp.Body)
if err != nil {
logger.Error("MiGuQueryRightsInfo err:", err)
return miGuResp, err
}
fmt.Println("body:", string(body))
defer resp.Body.Close()
err = json.Unmarshal(body, &miGuResp)
if err != nil {
logger.Error("miGuResp err:", err)
return miGuResp, err
}
return miGuResp, nil
}
// NoticeSubChannel 回调通知接口 (GET 请求返回string类型响应头为text/plain)
func NoticeSubChannel(baseUrl, linkId, extData, status string) (string, error) {
// 构建 GET 请求的 URL
requestUrl, err := url.Parse(baseUrl)
if err != nil {
return "", fmt.Errorf("failed to parse baseUrl: %v", err)
}
// 添加查询参数
query := requestUrl.Query()
query.Set("orderId", linkId)
query.Set("extData", extData)
query.Set("status", status)
requestUrl.RawQuery = query.Encode()
fmt.Println("NoticeSubChannel url:", requestUrl.String())
// 发送 GET 请求
resp, err := http.Get(requestUrl.String())
if err != nil {
return "", fmt.Errorf("failed to send GET request: %v", err)
}
defer resp.Body.Close()
// 检查响应头是否为text/plain
//contentType := resp.Header.Get("Content-Type")
//if contentType != "text/plain; charset=utf-8" {
// return "", fmt.Errorf("unexpected content type: %s", contentType)
//}
// 读取响应体内容
body, err := io.ReadAll(resp.Body)
if err != nil {
return "", fmt.Errorf("failed to read response body: %v", err)
}
// 将响应体转换为string
responseString := string(body)
// 打印结果
fmt.Println("Notification response:", responseString)
return responseString, nil
}
var mu sync.Mutex
// GetOrderSerial generates a unique inventory serial number
func GetOrderSerial(db *gorm.DB) string {
const maxRetries = 5
mu.Lock()
defer mu.Unlock()
for retryCount := 0; retryCount < maxRetries; retryCount++ {
nowTime := time.Now()
// 使用日期格式精确到天
datePart := nowTime.Format("060102") // 格式为 YYMMDD
// 生成13位随机数
rand.Seed(nowTime.UnixNano() + int64(retryCount)) // 为了确保每次生成不同的随机数
randomNum := rand.Int63n(1e13) // 10位随机数范围从0到9999999999999
randomPart := fmt.Sprintf("%013d", randomNum) // 确保随机数是13位的前面补零
// 拼接日期部分和随机数部分
sn := fmt.Sprintf("%s%s", datePart, randomPart)
exist, err := QueryRecordExist(fmt.Sprintf("SELECT * FROM mg_transaction_log WHERE out_trade_no='%s'", sn), db)
if err != nil {
logger.Error("sn err:", err)
continue
}
if !exist {
return sn
}
}
return "" // 返回空字符串,如果在最大重试次数后仍未找到唯一编号
}
// GetExcelOrderSerial generates a unique inventory serial number based on subscribeTime
func GetExcelOrderSerial(db *gorm.DB, subscribeTime time.Time) string {
const maxRetries = 5
mu.Lock()
defer mu.Unlock()
for retryCount := 0; retryCount < maxRetries; retryCount++ {
// 使用 subscribeTime 的日期部分生成订单号
datePart := subscribeTime.Format("060102") // 格式为 YYMMDD
// 生成13位随机数
nowTime := time.Now()
rand.Seed(nowTime.UnixNano() + int64(retryCount)) // 确保每次生成不同的随机数
randomNum := rand.Int63n(1e13) // 生成范围为0到9999999999999的随机数
randomPart := fmt.Sprintf("%013d", randomNum) // 确保随机数是13位前面补零
// 拼接日期部分和随机数部分
sn := fmt.Sprintf("%s%s", datePart, randomPart)
// 查询订单号是否已存在
exist, err := QueryRecordExist(fmt.Sprintf("SELECT * FROM mg_transaction_log WHERE out_trade_no='%s'", sn), db)
if err != nil {
logger.Error("sn err:", err)
continue
}
if !exist {
return sn
}
}
return "" // 在最大重试次数后仍未找到唯一编号则返回空字符串
}
// IsValidChannel 判断渠道号是否为 12, 13, 14
func IsValidChannel(channel string) bool {
return channel == "0012" || channel == "0013" || channel == "0014"
}
// IsValidChannelEx 判断渠道号是否为子渠道
func IsValidChannelEx(channel string, db *gorm.DB) bool {
exist, err := QueryRecordExist(fmt.Sprintf("SELECT * FROM mg_channel WHERE sub_channel_code='%s'", channel), db)
if err != nil {
return false
}
return exist
}
// IsValidSkuCode 判断skuCode是否有效
func IsValidSkuCode(skuCode string) bool {
return skuCode == SKUCODE
}
// IsValidSkuCodeEx 判断skuCode是否有效
func IsValidSkuCodeEx(skuCode string, db *gorm.DB) bool {
exist, err := QueryRecordExist(fmt.Sprintf("SELECT * FROM mg_product WHERE unique_code='%s'", skuCode), db)
if err != nil {
return false
}
return exist
}
// IsWithinOneHour 判断订阅时间是否在1小时之内
func IsWithinOneHour(order MgOrder) bool {
if order.SubscribeTime == nil {
return false
}
// 获取当前时间
now := time.Now()
// 计算当前时间与订阅时间的差值
diff := now.Sub(*order.SubscribeTime)
// 判断是否在1小时以内
return diff <= time.Hour
}
// IsWithinOneHourCancel 判断订阅时间是否在取消订阅时间的1小时之内
func IsWithinOneHourCancel(subscribeTime time.Time, unsubscribeTime string) bool {
// 将 unsubscribeTimestring转换为 time.Time 类型
unsubTime, err := time.ParseInLocation("2006-01-02 15:04:05", unsubscribeTime, subscribeTime.Location())
if err != nil {
fmt.Println("Error parsing UnsubscribeTime:", err)
return false
}
// 计算取消订阅时间与订阅时间的差值
diff := unsubTime.Sub(subscribeTime)
// 判断是否在1小时以内且取消时间在订阅时间之后
return diff <= time.Hour && diff >= 0
}
// ParseYearMonth 解析年月格式字符串,返回年和月
func ParseYearMonth(retentionMonth string) (int, time.Month, error) {
var year int
var month int
_, err := fmt.Sscanf(retentionMonth, "%d-%d", &year, &month)
if err != nil {
return 0, 0, err
}
return year, time.Month(month), nil
}
// GetMainChannelCodeAndSkuCode 通过子渠道编号和sku查询主渠道编号及其sku
func GetMainChannelCodeAndSkuCode(channel, skuCode string, db *gorm.DB) (mainChannel MgChannel, mainSkuCode MgProduct, err error) {
// 查询是否有记录
var channelInfo MgChannel
err = db.Table("mg_channel").Where("sub_channel_code = ?", channel).First(&channelInfo).Error
if err != nil {
logger.Errorf("SubmitOrder query mg_transaction_log err:", err.Error())
return MgChannel{}, MgProduct{}, err
}
var productInfo MgProduct
err = db.Table("mg_product").Where("unique_code = ?", skuCode).First(&productInfo).Error
if err != nil {
logger.Errorf("SubmitOrder query mg_transaction_log err:", err.Error())
return MgChannel{}, MgProduct{}, err
}
return channelInfo, productInfo, nil
}
// GetChannelInfoByChannelCode 通过渠道编号查询渠道信息
func GetChannelInfoByChannelCode(channel string, db *gorm.DB) (MgChannel, error) {
// 查询是否有记录
var channelInfo MgChannel
err := db.Table("mg_channel").Where("sub_channel_code = ?", channel).First(&channelInfo).Error
if err != nil {
logger.Errorf("SubmitOrder query mg_transaction_log err:", err.Error())
return MgChannel{}, err
}
return channelInfo, nil
}
// 模拟的数据库操作
func updateOrderState(order MgOrder, cancelFlag int, unsubTime string) error {
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("CheckOrderState update mg_order err:", err.Error())
logger.Errorf("CheckOrderState update mg_order err:", err.Error())
logger.Errorf("CheckOrderState order_serial:", order.OrderSerial)
return err
}
return nil
}
// processBatch 处理批次
func processBatch(batch []MgOrder, wg *sync.WaitGroup) {
defer wg.Done()
for _, order := range batch {
for j := 0; j < 3; j++ {
var req QueryRightsInfoReq
req.AppChannelList = append(req.AppChannelList, ChannelCode)
req.Mobile = order.PhoneNumber
resp, err := MiGuQueryRightsInfo(&req)
if err != nil {
fmt.Println("CheckOrderState MiGuQueryRightsInfo err:", err.Error())
logger.Errorf("CheckOrderState MiGuQueryRightsInfo err:", err.Error())
continue
}
// 有退订数据
if len(resp.ResultData) != 0 {
if resp.ResultData[0].IsUnsub == 1 { // 已退订
var cancelFlag int
subscribeTime := order.CreatedAt
// 检查 subscribeTime 是否为 nil
if IsWithinOneHourCancel(subscribeTime, resp.ResultData[0].UnsubTime) {
cancelFlag = 1
}
err = updateOrderState(order, cancelFlag, resp.ResultData[0].UnsubTime)
if err != nil {
fmt.Println("CheckOrderState update mg_order err:", err.Error())
logger.Errorf("CheckOrderState update mg_order err:", err.Error())
logger.Errorf("CheckOrderState order_serial:", order.OrderSerial)
continue
}
break
} else if resp.ResultData[0].IsUnsub == 0 { // 没有退订
break
}
} else {
if j == 2 && resp.ReturnCode != "000010" && resp.Message != "服务调用失败" {
var cancelFlag int
subscribeTime := order.CreatedAt
unsubTime := time.Now().Format("2006-01-02 15:04:05")
// 检查 subscribeTime 是否为 nil
if IsWithinOneHourCancel(subscribeTime, unsubTime) {
cancelFlag = 1
}
err = updateOrderState(order, cancelFlag, unsubTime)
if err != nil {
fmt.Println("CheckOrderState update mg_order err:", err.Error())
logger.Errorf("CheckOrderState update mg_order err:", err.Error())
logger.Errorf("CheckOrderState order_serial:", order.OrderSerial)
continue
}
break
}
continue
}
}
}
}
// CheckAllOrderStateBatch 定时任务,批量检查历史订阅用户有无退订
func CheckAllOrderStateBatch() {
if database.Db == nil {
log.Println("Database connection is nil")
fmt.Println("Database connection is nil")
return
}
// 查询订单列表中未退订的用户查询其是否退订如果退订则更新退订时间判断是否为1小时内退订
var orderList []MgOrder
err := database.Db.Where("state = 1").Order("created_at desc").
Find(&orderList).Error
//err := database.Db.Where("state = 1 and created_at >= ? and created_at <= ?", "2024-12-01 00:00:00", "2024-12-30 23:59:59").Order("created_at desc").
// Find(&orderList).Error
if err != nil {
fmt.Println("query mg_order err:", err.Error())
return
}
// 控制每次处理的批次大小
batchSize := 100
totalOrders := len(orderList)
var wg sync.WaitGroup
// 按批次分割处理
for i := 0; i < totalOrders; i += batchSize {
end := i + batchSize
if end > totalOrders {
end = totalOrders
}
batch := orderList[i:end]
wg.Add(1)
go processBatch(batch, &wg)
// 控制批次之间的延时,避免同时过多请求
time.Sleep(500 * time.Millisecond) // 控制批次间隔
}
wg.Wait() // 等待所有 goroutines 完成
}
// CheckAllOrderState 定时任务,检查历史订阅用户有无退订
func CheckAllOrderState() {
if database.Db == nil {
log.Println("Database connection is nil")
fmt.Println("Database connection is nil")
return
}
// 查询订单列表中未退订的用户查询其是否退订如果退订则更新退订时间判断是否为1小时内退订
var orderList []MgOrder
err := database.Db.Where("state = 1").Order("created_at desc").
Find(&orderList).Error
//err := database.Db.Where("state = 1 and created_at >= ? and created_at <= ?", "2024-10-01 00:00:00", "2024-10-31 23:59:59").Order("created_at desc").
// Find(&orderList).Error
if err != nil {
fmt.Println("query mg_order err:", err.Error())
return
}
for i, _ := range orderList {
for j := 0; j < 3; j++ {
var req QueryRightsInfoReq
req.AppChannelList = append(req.AppChannelList, ChannelCode)
req.Mobile = orderList[i].PhoneNumber
resp, err := MiGuQueryRightsInfo(&req)
if err != nil {
fmt.Println("CheckOrderState MiGuQueryRightsInfo err:", err.Error())
logger.Errorf("CheckOrderState MiGuQueryRightsInfo err:", err.Error())
continue
}
// 有退订数据
if len(resp.ResultData) != 0 {
if resp.ResultData[0].IsUnsub == 1 { // 已退订
var cancelFlag int
subscribeTime := orderList[i].CreatedAt
// 检查 subscribeTime 是否为 nil
if IsWithinOneHourCancel(subscribeTime, resp.ResultData[0].UnsubTime) {
cancelFlag = 1
}
err = database.Db.Table("mg_order").Where("order_serial = ?", orderList[i].OrderSerial).Updates(map[string]interface{}{
"state": UnsubscribeOK,
"is_one_hour_cancel": cancelFlag,
"unsubscribe_time": resp.ResultData[0].UnsubTime,
"updated_at": time.Now(),
}).Error
if err != nil {
fmt.Println("CheckOrderState update mg_order err:", err.Error())
logger.Errorf("CheckOrderState update mg_order err:", err.Error())
logger.Errorf("CheckOrderState order_serial:", orderList[i].OrderSerial)
continue
}
break
} else if resp.ResultData[0].IsUnsub == 0 { // 没有退订
break
}
} else {
if j == 2 && resp.ReturnCode != "000010" && resp.Message != "服务调用失败" {
var cancelFlag int
subscribeTime := orderList[i].CreatedAt
unsubTime := time.Now().Format("2006-01-02 15:04:05")
// 检查 subscribeTime 是否为 nil
if IsWithinOneHourCancel(subscribeTime, unsubTime) {
cancelFlag = 1
}
err = database.Db.Table("mg_order").Where("order_serial = ?", orderList[i].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("CheckOrderState update mg_order err:", err.Error())
logger.Errorf("CheckOrderState update mg_order err:", err.Error())
logger.Errorf("CheckOrderState order_serial:", orderList[i].OrderSerial)
continue
}
break
}
continue
}
}
}
}
// CheckCancelOrderState 定时任务,检查历史退订阅用户是否有误判
func CheckCancelOrderState() {
if database.Db == nil {
log.Println("Database connection is nil")
fmt.Println("Database connection is nil")
return
}
// 查询订单列表已退订的用户
var orderList []MgOrder
// 获取当前时间前2个小时
twoDaysAgo := time.Now().Add(-48 * time.Hour)
err := database.Db.Where("state = 2").
Where("unsubscribe_time >= ?", twoDaysAgo).
Order("created_at desc").
Find(&orderList).Error
if err != nil {
fmt.Println("query mg_order err:", err.Error())
return
}
for i, _ := range orderList {
for j := 0; j < 5; j++ {
var req QueryRightsInfoReq
req.AppChannelList = append(req.AppChannelList, ChannelCode)
req.Mobile = orderList[i].PhoneNumber
resp, err := MiGuQueryRightsInfo(&req)
if err != nil {
fmt.Println("CheckOrderState MiGuQueryRightsInfo err:", err.Error())
logger.Errorf("CheckOrderState MiGuQueryRightsInfo err:", err.Error())
continue
}
// 有退订数据
if len(resp.ResultData) != 0 {
if resp.ResultData[0].IsUnsub == 1 { // 已退订
break
} else if resp.ResultData[0].IsUnsub == 0 { // 没有退订
fmt.Println("**********CheckCancelOrderState get:", orderList[i].PhoneNumber)
var unsubTime *time.Time = nil
err = database.Db.Table("mg_order").Where("order_serial = ?", orderList[i].OrderSerial).Updates(map[string]interface{}{
"state": SubscribeOK,
"is_one_hour_cancel": 0,
"unsubscribe_time": unsubTime,
"updated_at": time.Now(),
}).Error
break
}
} else {
if j == 1 {
break
}
continue
}
}
}
}
// CheckOneHourCancelOrderState 定时任务检查1小时内退订的用户是否有误判
func CheckOneHourCancelOrderState() {
if database.Db == nil {
log.Println("Database connection is nil")
fmt.Println("Database connection is nil")
return
}
// 查询订单列表1小时内退订的用户
var orderList []MgOrder
//err := database.Db.Where("is_one_hour_cancel = 1 and created_at >= ? and created_at <= ?", "2024-11-01 00:00:00", "2024-11-30 23:59:59").Order("created_at desc").
// Find(&orderList).Error
err := database.Db.Where("is_one_hour_cancel = 1").Order("created_at desc").
Find(&orderList).Error
if err != nil {
fmt.Println("query mg_order err:", err.Error())
return
}
for i, _ := range orderList {
for j := 0; j < 5; j++ {
var req QueryRightsInfoReq
req.AppChannelList = append(req.AppChannelList, ChannelCode)
req.Mobile = orderList[i].PhoneNumber
resp, err := MiGuQueryRightsInfo(&req)
if err != nil {
fmt.Println("CheckOrderState MiGuQueryRightsInfo err:", err.Error())
logger.Errorf("CheckOrderState MiGuQueryRightsInfo err:", err.Error())
continue
}
// 有退订数据
if len(resp.ResultData) != 0 {
if resp.ResultData[0].IsUnsub == 1 { // 已退订
break
} else if resp.ResultData[0].IsUnsub == 0 { // 没有退订
fmt.Println("**********CheckCancelOrderState get:", orderList[i].PhoneNumber)
var unsubTime *time.Time = nil
err = database.Db.Table("mg_order").Where("order_serial = ?", orderList[i].OrderSerial).Updates(map[string]interface{}{
"state": SubscribeOK,
"is_one_hour_cancel": 0,
"unsubscribe_time": unsubTime,
"updated_at": time.Now(),
}).Error
break
}
} else {
if j == 1 {
break
}
continue
}
}
}
}
// ExportHistoricalSummaryToExcel 历史汇总数据导出excel
func ExportHistoricalSummaryToExcel(data []MgHistoricalSummary, db *gorm.DB) (string, error) {
// 创建一个新的Excel文件
file := excelize.NewFile()
sheet := "Sheet1"
// 设置标题栏
titles := []string{"日期", "产品ID", "渠道编码", "提交数", "新用户数", "提交成功率", "1小时退订数", "1小时退订率",
"当日退订数", "当日退订率", "累计退订数", "累计退订率"}
for i, title := range titles {
cell, _ := excelize.CoordinatesToCellName(i+1, 1)
file.SetCellValue(sheet, cell, title)
}
// 设置所有单元格的样式: 居中、加边框
style, _ := file.NewStyle(`{"alignment":{"horizontal":"center","vertical":"center"},
"border":[{"type":"left","color":"000000","style":1},
{"type":"top","color":"000000","style":1},
{"type":"right","color":"000000","style":1},
{"type":"bottom","color":"000000","style":1}]}`)
// 设置单元格高度
file.SetRowHeight(sheet, 1, 20)
// 设置列宽
file.SetColWidth(sheet, "A", "A", 15)
file.SetColWidth(sheet, "B", "B", 18)
file.SetColWidth(sheet, "C", "C", 18)
file.SetColWidth(sheet, "F", "F", 15)
file.SetColWidth(sheet, "G", "G", 15)
file.SetColWidth(sheet, "H", "H", 15)
file.SetColWidth(sheet, "I", "I", 15)
file.SetColWidth(sheet, "J", "J", 15)
file.SetColWidth(sheet, "K", "K", 15)
file.SetColWidth(sheet, "L", "L", 15)
// 创建一个产品ID到名称的映射
productMap := make(map[int64]string)
for _, order := range data {
if _, exists := productMap[order.ProductID]; !exists {
var product MgProduct
// 查询产品信息
if err := db.First(&product, order.ProductID).Error; err == nil {
productMap[order.ProductID] = product.Name
} else {
productMap[order.ProductID] = "未知产品"
}
}
}
// 填充数据
for i, record := range data {
row := i + 2
productName := productMap[record.ProductID] // 获取产品名称
file.SetCellValue(sheet, "A"+strconv.Itoa(row), record.Date)
file.SetCellValue(sheet, "B"+strconv.Itoa(row), productName)
file.SetCellValue(sheet, "C"+strconv.Itoa(row), record.ChannelCode)
file.SetCellValue(sheet, "D"+strconv.Itoa(row), record.SubmissionCount)
file.SetCellValue(sheet, "E"+strconv.Itoa(row), record.NewUserCount)
file.SetCellValue(sheet, "F"+strconv.Itoa(row), record.SubmissionSuccessRate)
file.SetCellValue(sheet, "G"+strconv.Itoa(row), record.NewUserUnsubWithinHour)
file.SetCellValue(sheet, "H"+strconv.Itoa(row), record.NewUserUnsubWithinHourRate)
file.SetCellValue(sheet, "I"+strconv.Itoa(row), record.NewUserUnsubOnDay)
file.SetCellValue(sheet, "J"+strconv.Itoa(row), record.NewUserUnsubOnDayRate)
file.SetCellValue(sheet, "K"+strconv.Itoa(row), record.TotalNewUserUnsub)
file.SetCellValue(sheet, "L"+strconv.Itoa(row), record.TotalNewUserUnsubRate)
}
endRow := fmt.Sprintf("L%d", len(data)+1)
// 应用样式到整个表格
_ = file.SetCellStyle(sheet, "A1", endRow, style)
// 从配置文件读取保存路径和URL前缀
fileName := time.Now().Format("20060102150405") + "_历史汇总数据.xlsx"
url := MiGuExportUrl + fileName
// 保存Excel文件
if err := file.SaveAs(ExportFile + fileName); err != nil {
logger.Errorf("Failed to save Excel file: %v", err)
return "", err
}
return url, nil
}
// ExportRealtimeSummaryToExcel 当日汇总数据导出excel
func ExportRealtimeSummaryToExcel(data []MgRealtimeSummary, db *gorm.DB) (string, error) {
// 创建一个新的Excel文件
file := excelize.NewFile()
sheet := "Sheet1"
// 设置标题栏
titles := []string{"产品ID", "渠道编码", "提交数", "新用户数", "提交成功率", "1小时内退订数", "1小时内退订率", "当日退订数", "当日退订率"}
for i, title := range titles {
cell, _ := excelize.CoordinatesToCellName(i+1, 1)
file.SetCellValue(sheet, cell, title)
}
// 设置所有单元格的样式: 居中、加边框
style, _ := file.NewStyle(`{"alignment":{"horizontal":"center","vertical":"center"},
"border":[{"type":"left","color":"000000","style":1},
{"type":"top","color":"000000","style":1},
{"type":"right","color":"000000","style":1},
{"type":"bottom","color":"000000","style":1}]}`)
// 设置单元格高度
file.SetRowHeight(sheet, 1, 20)
// 设置列宽
file.SetColWidth(sheet, "A", "A", 18)
file.SetColWidth(sheet, "B", "B", 18)
file.SetColWidth(sheet, "E", "E", 15)
file.SetColWidth(sheet, "F", "F", 15)
file.SetColWidth(sheet, "G", "G", 15)
file.SetColWidth(sheet, "H", "H", 15)
file.SetColWidth(sheet, "I", "I", 15)
// 创建一个产品ID到名称的映射
productMap := make(map[int64]string)
for _, order := range data {
if _, exists := productMap[order.ProductID]; !exists {
var product MgProduct
// 查询产品信息
if err := db.First(&product, order.ProductID).Error; err == nil {
productMap[order.ProductID] = product.Name
} else {
productMap[order.ProductID] = "未知产品"
}
}
}
// 填充数据
for i, record := range data {
row := i + 2
productName := productMap[record.ProductID] // 获取产品名称
file.SetCellValue(sheet, "A"+strconv.Itoa(row), productName)
file.SetCellValue(sheet, "B"+strconv.Itoa(row), record.ChannelCode)
file.SetCellValue(sheet, "C"+strconv.Itoa(row), record.SubmissionCount)
file.SetCellValue(sheet, "D"+strconv.Itoa(row), record.NewUserCount)
file.SetCellValue(sheet, "E"+strconv.Itoa(row), record.SubmissionSuccessRate)
file.SetCellValue(sheet, "F"+strconv.Itoa(row), record.NewUserUnsubWithinHour)
file.SetCellValue(sheet, "G"+strconv.Itoa(row), record.NewUserUnsubWithinHourRate)
file.SetCellValue(sheet, "H"+strconv.Itoa(row), record.NewUserUnsubOnDay)
file.SetCellValue(sheet, "I"+strconv.Itoa(row), record.NewUserUnsubOnDayRate)
}
endRow := fmt.Sprintf("I%d", len(data)+1)
// 应用样式到整个表格
_ = file.SetCellStyle(sheet, "A1", endRow, style)
// 从配置文件读取保存路径和URL前缀
fileName := time.Now().Format("20060102150405") + "_当日实时汇总数据.xlsx"
url := MiGuExportUrl + fileName
// 保存Excel文件
if err := file.SaveAs(ExportFile + fileName); err != nil {
logger.Errorf("Failed to save Excel file: %v", err)
return "", err
}
return url, nil
}
// ExportOrderListToExcel 订单表导出excel
func ExportOrderListToExcel(orderList []MgOrder, db *gorm.DB) (string, error) {
// 创建一个新的Excel文件
file := excelize.NewFile()
sheet := "Sheet1"
// 设置标题栏
titles := []string{"产品", "渠道", "订单流水号", "手机号", "SM4手机号", "外部订单号", "渠道订单号", "是否1小时内退订", "用户订购状态", "订购时间", "退订时间"}
for i, title := range titles {
cell, _ := excelize.CoordinatesToCellName(i+1, 1)
file.SetCellValue(sheet, cell, title)
}
// 设置所有单元格的样式: 居中、加边框
style, _ := file.NewStyle(`{"alignment":{"horizontal":"center","vertical":"center"},
"border":[{"type":"left","color":"000000","style":1},
{"type":"top","color":"000000","style":1},
{"type":"right","color":"000000","style":1},
{"type":"bottom","color":"000000","style":1}]}`)
// 设置单元格高度
file.SetRowHeight(sheet, 1, 20)
// 设置列宽
file.SetColWidth(sheet, "A", "A", 18) // 产品
file.SetColWidth(sheet, "B", "B", 15) // 渠道
file.SetColWidth(sheet, "C", "C", 20) // 订单流水号
file.SetColWidth(sheet, "D", "D", 15) // 手机号
file.SetColWidth(sheet, "E", "E", 30) // SM4手机号
file.SetColWidth(sheet, "F", "F", 20) // 外部订单号
file.SetColWidth(sheet, "G", "G", 23) // 渠道订单号
file.SetColWidth(sheet, "H", "H", 15) // 是否1小时内退订
file.SetColWidth(sheet, "I", "I", 15) // 用户订购状态
file.SetColWidth(sheet, "J", "J", 20) // 订购时间
file.SetColWidth(sheet, "K", "K", 20) // 退订时间
// 创建一个产品ID到名称的映射
productMap := make(map[int64]string)
for _, order := range orderList {
if _, exists := productMap[order.ProductID]; !exists {
var product MgProduct
// 查询产品信息
if err := db.First(&product, order.ProductID).Error; err == nil {
productMap[order.ProductID] = product.Name
} else {
productMap[order.ProductID] = "未知产品"
}
}
}
// 填充数据
for i, order := range orderList {
row := i + 2
productName := productMap[order.ProductID] // 获取产品名称
file.SetCellValue(sheet, "A"+strconv.Itoa(row), productName)
file.SetCellValue(sheet, "B"+strconv.Itoa(row), order.ChannelCode)
file.SetCellValue(sheet, "C"+strconv.Itoa(row), order.OrderSerial)
file.SetCellValue(sheet, "D"+strconv.Itoa(row), order.PhoneNumber)
file.SetCellValue(sheet, "E"+strconv.Itoa(row), order.SM4PhoneNumber)
file.SetCellValue(sheet, "F"+strconv.Itoa(row), order.ExternalOrderID)
file.SetCellValue(sheet, "G"+strconv.Itoa(row), order.ChannelTradeNo)
// 判断时间是否为空,若为空则设置为""
if order.SubscribeTime != nil {
file.SetCellValue(sheet, "J"+strconv.Itoa(row), order.SubscribeTime.Format("2006-01-02 15:04:05"))
} else {
file.SetCellValue(sheet, "J"+strconv.Itoa(row), "")
}
// 判断退订时间是否为空,若为空则设置为""
if order.UnsubscribeTime != nil {
file.SetCellValue(sheet, "K"+strconv.Itoa(row), order.UnsubscribeTime.Format("2006-01-02 15:04:05"))
} else {
file.SetCellValue(sheet, "K"+strconv.Itoa(row), "")
}
// 退订状态转换为中文
subscriptionStatus := "未知状态"
if order.State == 1 {
subscriptionStatus = "订购成功"
} else if order.State == 2 {
subscriptionStatus = "已退订"
}
file.SetCellValue(sheet, "I"+strconv.Itoa(row), subscriptionStatus)
// 是否1小时内退订转换为中文
isOneHourCancel := "否"
if order.IsOneHourCancel == 1 {
isOneHourCancel = "是"
}
file.SetCellValue(sheet, "H"+strconv.Itoa(row), isOneHourCancel)
// 设置当前行的单元格样式
for col := 'A'; col <= 'K'; col++ {
cell, _ := excelize.CoordinatesToCellName(int(col-'A'+1), row)
file.SetCellStyle(sheet, cell, cell, style)
}
}
endRow := fmt.Sprintf("K%d", len(orderList)+1)
// 应用样式到整个表格
_ = file.SetCellStyle(sheet, "A1", endRow, style)
// 从配置文件读取保存路径和URL前缀
fileName := time.Now().Format("20060102150405") + "_订单列表.xlsx"
url := MiGuExportUrl + fileName
// 保存Excel文件
if err := file.SaveAs(ExportFile + fileName); err != nil {
logger.Errorf("Failed to save Excel file: %v", err)
return "", err
}
return url, nil
}
// ConvertStringToTime 将日期字符串转换为时间格式
func ConvertStringToTime(dateStr string) (string, error) {
// 解析输入字符串为时间
t, err := time.Parse("20060102", dateStr)
if err != nil {
return "", err // 返回错误
}
// 格式化为所需的时间格式
formattedTime := t.Format("2006-01-02 15:04:05")
return formattedTime, nil
}
// ExportHourSummaryToExcel 历史汇总查询(按小时导出excel
func ExportHourSummaryToExcel(data []MgHourSummary, sumData TotalHourSummary, db *gorm.DB) (string, error) {
// 创建一个新的Excel文件
file := excelize.NewFile()
sheet := "Sheet1"
nExcelStartRow := 0
// 设置标题栏
titles := []string{"小时", "产品", "渠道", "提交数", "新用户数", "提交成功率", "1小时退订数", "1小时退订率",
"当日退订数", "当日退订率", "累计退订数", "累计退订率"}
for i, title := range titles {
cell, _ := excelize.CoordinatesToCellName(i+1, 1)
file.SetCellValue(sheet, cell, title)
}
nExcelStartRow += 1
// 设置所有单元格的样式: 居中、加边框
style, _ := file.NewStyle(`{"alignment":{"horizontal":"center","vertical":"center"},
"border":[{"type":"left","color":"000000","style":1},
{"type":"top","color":"000000","style":1},
{"type":"right","color":"000000","style":1},
{"type":"bottom","color":"000000","style":1}]}`)
// 设置单元格高度
file.SetRowHeight(sheet, 1, 20)
// 设置列宽
file.SetColWidth(sheet, "A", "A", 15)
file.SetColWidth(sheet, "B", "B", 18)
file.SetColWidth(sheet, "C", "C", 18)
file.SetColWidth(sheet, "F", "F", 15)
file.SetColWidth(sheet, "G", "G", 15)
file.SetColWidth(sheet, "H", "H", 15)
file.SetColWidth(sheet, "I", "I", 15)
file.SetColWidth(sheet, "J", "J", 15)
file.SetColWidth(sheet, "K", "K", 15)
file.SetColWidth(sheet, "L", "L", 15)
// 创建一个产品ID到名称的映射
productMap := make(map[int64]string)
for _, order := range data {
if _, exists := productMap[order.ProductID]; !exists {
var product MgProduct
// 查询产品信息
if err := db.First(&product, order.ProductID).Error; err == nil {
productMap[order.ProductID] = product.Name
} else {
productMap[order.ProductID] = "未知产品"
}
}
}
// 填充数据
for i, record := range data {
row := i + 2
productName := productMap[record.ProductID] // 获取产品名称
file.SetCellValue(sheet, "A"+strconv.Itoa(row), record.Hour)
file.SetCellValue(sheet, "B"+strconv.Itoa(row), productName)
file.SetCellValue(sheet, "C"+strconv.Itoa(row), record.ChannelCode)
file.SetCellValue(sheet, "D"+strconv.Itoa(row), record.SubmissionCount)
file.SetCellValue(sheet, "E"+strconv.Itoa(row), record.NewUserCount)
file.SetCellValue(sheet, "F"+strconv.Itoa(row), record.SubmissionSuccessRate)
file.SetCellValue(sheet, "G"+strconv.Itoa(row), record.NewUserUnsubWithinHour)
file.SetCellValue(sheet, "H"+strconv.Itoa(row), record.NewUserUnsubWithinHourRate)
file.SetCellValue(sheet, "I"+strconv.Itoa(row), record.NewUserUnsubOnDay)
file.SetCellValue(sheet, "J"+strconv.Itoa(row), record.NewUserUnsubOnDayRate)
file.SetCellValue(sheet, "K"+strconv.Itoa(row), record.TotalNewUserUnsub)
file.SetCellValue(sheet, "L"+strconv.Itoa(row), record.TotalNewUserUnsubRate)
}
nExcelStartRow += len(data)
totalData := "订单数:" + strconv.FormatInt(int64(len(data)), 10)
end := []interface{}{totalData, "", "",
sumData.SubmissionCount,
sumData.NewUserCount,
sumData.SubmissionSuccessRate,
sumData.NewUserUnsubWithinHour,
sumData.NewUserUnsubWithinHourRate,
sumData.NewUserUnsubOnDay,
sumData.NewUserUnsubOnDayRate,
sumData.TotalNewUserUnsub,
sumData.TotalNewUserUnsubRate,
}
for i, _ := range end {
cell, _ := excelize.CoordinatesToCellName(1+i, nExcelStartRow+1)
err := file.SetCellValue(sheet, cell, end[i])
if err != nil {
logger.Errorf("file set value err:", err)
}
}
endRow := fmt.Sprintf("L%d", nExcelStartRow+1)
// 应用样式到整个表格
_ = file.SetCellStyle(sheet, "A1", endRow, style)
// 从配置文件读取保存路径和URL前缀
fileName := time.Now().Format("20060102150405") + "_历史汇总按小时.xlsx"
url := MiGuExportUrl + fileName
// 保存Excel文件
if err := file.SaveAs(ExportFile + fileName); err != nil {
logger.Errorf("Failed to save Excel file: %v", err)
return "", err
}
return url, nil
}