2828 lines
87 KiB
Go
2828 lines
87 KiB
Go
package migumanage
|
||
|
||
import (
|
||
"errors"
|
||
"fmt"
|
||
"github.com/gin-gonic/gin"
|
||
"github.com/go-admin-team/go-admin-core/logger"
|
||
"github.com/go-admin-team/go-admin-core/sdk/pkg/response"
|
||
"go-admin/app/admin/models"
|
||
"net/http"
|
||
"sort"
|
||
"strconv"
|
||
"strings"
|
||
"sync"
|
||
"time"
|
||
)
|
||
|
||
// 以下是后台部分接口
|
||
|
||
// TransactionList 查询交易流水记录
|
||
// @Summary 查询交易流水记录
|
||
// @Tags 2024-咪咕-管理后台
|
||
// @Produce json
|
||
// @Accept json
|
||
// @Param request body models.TransactionListReq true "查询交易流水记录"
|
||
// @Success 200 {object} models.TransactionListResp
|
||
// @Router /api/v1/admin/transaction/list [post]
|
||
func (e MiGuDeployService) TransactionList(c *gin.Context) {
|
||
fmt.Println("TransactionList")
|
||
err := e.MakeContext(c).MakeOrm().Errors
|
||
if err != nil {
|
||
fmt.Println("MakeContext err:", err)
|
||
e.Logger.Error(err)
|
||
return
|
||
}
|
||
|
||
req := &models.TransactionListReq{}
|
||
if c.ShouldBindJSON(req) != nil {
|
||
logger.Errorf("para err")
|
||
response.Error(c, http.StatusBadRequest, errors.New("para err"), "参数错误")
|
||
return
|
||
}
|
||
|
||
resp := models.TransactionListResp{
|
||
PageNum: req.PageNum,
|
||
}
|
||
|
||
pageNum := req.PageNum - 1
|
||
if pageNum < 0 {
|
||
pageNum = 0
|
||
}
|
||
if req.PageSize == 0 {
|
||
req.PageSize = 10
|
||
}
|
||
resp.PageSize = req.PageSize
|
||
if e.Orm == nil {
|
||
fmt.Println("Orm is nil")
|
||
}
|
||
var count int64
|
||
qs := e.Orm.Model(&models.MgTransactionLog{})
|
||
|
||
// 手机号码
|
||
if req.Phone != "" {
|
||
qs = qs.Where("phone_number = ?", req.Phone)
|
||
}
|
||
// 渠道号
|
||
if req.Channel != "" {
|
||
qs = qs.Where("channel_code = ?", req.Channel)
|
||
}
|
||
// 产品编号
|
||
if req.SkuCode != 0 {
|
||
qs = qs.Where("product_id = ?", req.SkuCode)
|
||
}
|
||
// 交易结果
|
||
if req.Result != "" {
|
||
if req.Result == "1" { // 成功
|
||
qs = qs.Where("result = ?", "00000")
|
||
} else if req.Result == "2" { // 失败
|
||
qs = qs.Where("result != ?", "00000")
|
||
}
|
||
}
|
||
// 开始时间和结束时间
|
||
if req.StartTime != "" && req.EndTime != "" {
|
||
qs = qs.Where("created_at BETWEEN ? AND ?", req.StartTime, req.EndTime)
|
||
}
|
||
|
||
err = qs.Count(&count).Error
|
||
if err != nil {
|
||
logger.Errorf("count err:", err)
|
||
response.Error(c, http.StatusInternalServerError, err, "查询失败")
|
||
return
|
||
}
|
||
|
||
var transactionLogs []models.MgTransactionLog
|
||
err = qs.Order("created_at desc").Offset(pageNum * req.PageSize).Limit(req.PageSize).Find(&transactionLogs).Error
|
||
if err != nil {
|
||
logger.Errorf("TransactionList err:%#v", err)
|
||
response.Error(c, http.StatusInternalServerError, err, "查询失败")
|
||
return
|
||
}
|
||
|
||
resp.List = transactionLogs
|
||
resp.Count = int(count)
|
||
|
||
e.OK(resp, "")
|
||
}
|
||
|
||
// ProductList 查询权益产品
|
||
// @Summary 查询权益产品
|
||
// @Tags 2024-咪咕-管理后台
|
||
// @Produce json
|
||
// @Accept json
|
||
// @Param request body models.ProductListReq true "查询权益产品"
|
||
// @Success 200 {object} models.ProductListResp
|
||
// @Router /api/v1/admin/product/list [post]
|
||
func (e MiGuDeployService) ProductList(c *gin.Context) {
|
||
fmt.Println("ProductList")
|
||
err := e.MakeContext(c).MakeOrm().Errors
|
||
if err != nil {
|
||
fmt.Println("MakeContext err:", err)
|
||
e.Logger.Error(err)
|
||
return
|
||
}
|
||
|
||
req := &models.ProductListReq{}
|
||
if c.ShouldBindJSON(req) != nil {
|
||
logger.Errorf("para err")
|
||
response.Error(c, http.StatusBadRequest, errors.New("para err"), "参数错误")
|
||
return
|
||
}
|
||
|
||
resp := models.ProductListResp{
|
||
PageNum: req.PageNum,
|
||
}
|
||
|
||
pageNum := req.PageNum - 1
|
||
if pageNum < 0 {
|
||
pageNum = 0
|
||
}
|
||
if req.PageSize == 0 {
|
||
req.PageSize = 10
|
||
}
|
||
resp.PageSize = req.PageSize
|
||
if e.Orm == nil {
|
||
fmt.Println("Orm is nil")
|
||
}
|
||
var count int64
|
||
qs := e.Orm.Model(&models.MgProduct{})
|
||
err = qs.Count(&count).Error
|
||
if err != nil {
|
||
logger.Errorf("count err:", err)
|
||
response.Error(c, http.StatusInternalServerError, err, "查询失败")
|
||
return
|
||
}
|
||
|
||
var productList []models.MgProduct
|
||
err = qs.Order("id ").Offset(pageNum * req.PageSize).Limit(req.PageSize).Find(&productList).Error
|
||
if err != nil {
|
||
logger.Errorf("ProductList err:%#v", err)
|
||
response.Error(c, http.StatusInternalServerError, err, "查询失败")
|
||
return
|
||
}
|
||
|
||
resp.List = productList
|
||
|
||
e.OK(resp, "")
|
||
}
|
||
|
||
// ChannelList 查询渠道列表
|
||
// @Summary 查询渠道列表
|
||
// @Tags 2024-咪咕-管理后台
|
||
// @Produce json
|
||
// @Accept json
|
||
// @Param request body models.ChannelListReq true "查询渠道列表"
|
||
// @Success 200 {object} models.ChannelListResp
|
||
// @Router /api/v1/admin/channel/list [post]
|
||
func (e MiGuDeployService) ChannelList(c *gin.Context) {
|
||
fmt.Println("ProductList")
|
||
err := e.MakeContext(c).MakeOrm().Errors
|
||
if err != nil {
|
||
fmt.Println("MakeContext err:", err)
|
||
e.Logger.Error(err)
|
||
return
|
||
}
|
||
|
||
req := &models.ChannelListReq{}
|
||
if c.ShouldBindJSON(req) != nil {
|
||
logger.Errorf("para err")
|
||
response.Error(c, http.StatusBadRequest, errors.New("para err"), "参数错误")
|
||
return
|
||
}
|
||
|
||
resp := models.ChannelListResp{
|
||
PageNum: req.PageNum,
|
||
}
|
||
|
||
pageNum := req.PageNum - 1
|
||
if pageNum < 0 {
|
||
pageNum = 0
|
||
}
|
||
if req.PageSize == 0 {
|
||
req.PageSize = 10
|
||
}
|
||
resp.PageSize = req.PageSize
|
||
if e.Orm == nil {
|
||
fmt.Println("Orm is nil")
|
||
}
|
||
var count int64
|
||
qs := e.Orm.Model(&models.MgChannel{})
|
||
err = qs.Count(&count).Error
|
||
if err != nil {
|
||
logger.Errorf("count err:", err)
|
||
response.Error(c, http.StatusInternalServerError, err, "查询失败")
|
||
return
|
||
}
|
||
|
||
var channelList []models.MgChannel
|
||
err = qs.Order("id ").Offset(pageNum * req.PageSize).Limit(req.PageSize).Find(&channelList).Error
|
||
if err != nil {
|
||
logger.Errorf("ProductList err:%#v", err)
|
||
response.Error(c, http.StatusInternalServerError, err, "查询失败")
|
||
return
|
||
}
|
||
|
||
resp.List = channelList
|
||
|
||
e.OK(resp, "")
|
||
}
|
||
|
||
// OrderList 查询订单列表
|
||
// @Summary 查询订单列表
|
||
// @Tags 2024-咪咕-管理后台
|
||
// @Produce json
|
||
// @Accept json
|
||
// @Param request body models.OrderListReq true "查询订单列表"
|
||
// @Success 200 {object} models.OrderListResp
|
||
// @Router /api/v1/admin/order/list [post]
|
||
func (e MiGuDeployService) OrderList(c *gin.Context) {
|
||
fmt.Println("OrderList")
|
||
err := e.MakeContext(c).MakeOrm().Errors
|
||
if err != nil {
|
||
fmt.Println("MakeContext err:", err)
|
||
e.Logger.Error(err)
|
||
return
|
||
}
|
||
|
||
req := &models.OrderListReq{}
|
||
if c.ShouldBindJSON(req) != nil {
|
||
logger.Errorf("para err")
|
||
response.Error(c, http.StatusBadRequest, errors.New("para err"), "参数错误")
|
||
return
|
||
}
|
||
|
||
resp := models.OrderListResp{
|
||
PageNum: req.PageNum,
|
||
}
|
||
|
||
pageNum := req.PageNum - 1
|
||
if pageNum < 0 {
|
||
pageNum = 0
|
||
}
|
||
if req.PageSize == 0 {
|
||
req.PageSize = 10
|
||
}
|
||
resp.PageSize = req.PageSize
|
||
if e.Orm == nil {
|
||
fmt.Println("Orm is nil")
|
||
}
|
||
|
||
qs := e.Orm.Model(&models.MgOrder{})
|
||
// 产品编号
|
||
if req.SkuCode != 0 {
|
||
qs = qs.Where("product_id = ?", req.SkuCode)
|
||
}
|
||
// 渠道号
|
||
if req.Channel != "" {
|
||
qs = qs.Where("channel_code = ?", req.Channel)
|
||
}
|
||
// 订单流水号
|
||
if req.OrderSerial != "" {
|
||
qs = qs.Where("order_serial = ?", req.OrderSerial)
|
||
}
|
||
// 外部订单号
|
||
if req.OutTradeNo != "" {
|
||
qs = qs.Where("external_order_id = ?", req.OutTradeNo)
|
||
}
|
||
// 渠道订单号
|
||
if req.ChannelTradeNo != "" {
|
||
qs = qs.Where("channel_trade_no = ?", req.ChannelTradeNo)
|
||
}
|
||
// 手机号码
|
||
if req.Phone != "" {
|
||
qs = qs.Where("phone_number = ?", req.Phone)
|
||
}
|
||
// SM4加密手机号
|
||
if req.SM4PhoneNumber != "" {
|
||
qs = qs.Where("sm4_phone_number = ?", req.SM4PhoneNumber)
|
||
}
|
||
// 退订状态
|
||
if req.State != 0 {
|
||
if req.State == 3 { // 1小时内退订
|
||
qs = qs.Where("is_one_hour_cancel = ?", 1)
|
||
} else {
|
||
state := 1 // 订阅
|
||
if req.State == 1 {
|
||
state = 2 // 退订
|
||
}
|
||
qs = qs.Where("state = ?", state)
|
||
}
|
||
}
|
||
// 开始时间和结束时间
|
||
if req.StartTime != "" && req.EndTime != "" {
|
||
qs = qs.Where("subscribe_time BETWEEN ? AND ?", req.StartTime, req.EndTime)
|
||
}
|
||
|
||
// 退订时间不为空
|
||
if req.CancelStartTime != "" && req.CancelEndTime != "" {
|
||
qs = qs.Where("unsubscribe_time BETWEEN ? AND ?", req.CancelStartTime, req.CancelEndTime)
|
||
}
|
||
|
||
var count int64
|
||
err = qs.Count(&count).Error
|
||
if err != nil {
|
||
logger.Errorf("count err:", err)
|
||
response.Error(c, http.StatusInternalServerError, err, "查询失败")
|
||
return
|
||
}
|
||
|
||
var orderList []models.MgOrder
|
||
if req.IsExport == 1 {
|
||
err = qs.Order("created_at desc").Find(&orderList).Error
|
||
} else {
|
||
err = qs.Order("created_at desc").Offset(pageNum * req.PageSize).Limit(req.PageSize).Find(&orderList).Error
|
||
}
|
||
if err != nil {
|
||
logger.Errorf("OrderList err:%#v", err)
|
||
response.Error(c, http.StatusInternalServerError, err, "查询失败")
|
||
return
|
||
}
|
||
|
||
// 判断是否导出Excel
|
||
if req.IsExport == 1 {
|
||
// 调用导出Excel函数
|
||
url, err := models.ExportOrderListToExcel(orderList, e.Orm)
|
||
if err != nil {
|
||
response.Error(c, http.StatusInternalServerError, err, "导出失败")
|
||
return
|
||
}
|
||
|
||
// 返回导出文件的URL地址
|
||
response.OK(c, map[string]string{"export_url": url}, "导出成功")
|
||
return
|
||
}
|
||
|
||
resp.List = orderList
|
||
resp.Count = int(count)
|
||
|
||
e.OK(resp, "")
|
||
}
|
||
|
||
func (e MiGuDeployService) HistoricalSummaryListOld(c *gin.Context) {
|
||
fmt.Println("HistoricalSummaryList")
|
||
err := e.MakeContext(c).MakeOrm().Errors
|
||
if err != nil {
|
||
fmt.Println("MakeContext err:", err)
|
||
e.Logger.Error(err)
|
||
return
|
||
}
|
||
|
||
// 请求参数绑定
|
||
req := &models.HistoricalSummaryListReq{}
|
||
if c.ShouldBindJSON(req) != nil {
|
||
logger.Errorf("para err")
|
||
response.Error(c, http.StatusBadRequest, errors.New("para err"), "参数错误")
|
||
return
|
||
}
|
||
|
||
resp := models.HistoricalSummaryListResp{
|
||
PageNum: req.PageNum,
|
||
}
|
||
|
||
// 分页处理
|
||
pageNum := req.PageNum - 1
|
||
if pageNum < 0 {
|
||
pageNum = 0
|
||
}
|
||
if req.PageSize == 0 {
|
||
req.PageSize = 10
|
||
}
|
||
resp.PageSize = req.PageSize
|
||
|
||
if e.Orm == nil {
|
||
fmt.Println("Orm is nil")
|
||
}
|
||
|
||
// 构建查询
|
||
var historicalSummaryList []models.MgHistoricalSummary
|
||
var count int64
|
||
|
||
// 设置开始时间和结束时间
|
||
startTime := req.StartTime
|
||
endTime := req.EndTime
|
||
if startTime == "" {
|
||
startTime = "1970-01-01 00:00:00"
|
||
}
|
||
if endTime == "" {
|
||
endTime = time.Now().Format("2006-01-02 15:04:05")
|
||
}
|
||
|
||
// 使用左连接查询
|
||
//qs := e.Orm.Model(&models.MgOrder{}).
|
||
// Select(`DATE_FORMAT(mg_order.subscribe_time, '%Y-%m-%d') AS date,
|
||
// mg_order.product_id,
|
||
// mg_order.channel_code,
|
||
// (SELECT COUNT(*)
|
||
// FROM mg_transaction_log
|
||
// WHERE verification_code != ''
|
||
// AND channel_code = mg_order.channel_code
|
||
// AND DATE(created_at) = DATE(mg_order.subscribe_time)) AS submission_count,
|
||
// COUNT(CASE WHEN mg_order.is_one_hour_cancel = 1 THEN 1 END) AS new_user_unsub_within_hour,
|
||
// COUNT(CASE WHEN mg_order.state = 2 THEN 1 END) AS new_user_unsub_on_day,
|
||
// COUNT(*) AS new_user_count,
|
||
// CONCAT(ROUND(COUNT(CASE WHEN mg_order.is_one_hour_cancel = 1 THEN 1 END) * 100.0 /
|
||
// NULLIF(COUNT(*), 0), 2), '%') AS new_user_unsub_within_hour_rate,
|
||
// CONCAT(ROUND(COUNT(CASE WHEN mg_order.state = 2 THEN 1 END) * 100.0 /
|
||
// NULLIF(COUNT(*), 0), 2), '%') AS new_user_unsub_on_day_rate`).
|
||
// Where("mg_order.subscribe_time >= ? AND mg_order.subscribe_time <= ?", startTime, endTime).
|
||
// Group("DATE(mg_order.subscribe_time), mg_order.product_id, mg_order.channel_code").
|
||
// Order("mg_order.product_id, mg_order.channel_code, mg_order.subscribe_time")
|
||
|
||
//qs := e.Orm.Model(&models.MgOrder{}).
|
||
// Select(`DATE_FORMAT(mg_order.subscribe_time, '%Y-%m-%d') AS date,
|
||
// mg_order.product_id,
|
||
// mg_order.channel_code,
|
||
// (SELECT COUNT(*)
|
||
// FROM mg_transaction_log
|
||
// WHERE verification_code != ''
|
||
// AND channel_code = mg_order.channel_code
|
||
// AND DATE(created_at) = DATE(mg_order.subscribe_time)) AS submission_count,
|
||
// COUNT(CASE WHEN mg_order.is_one_hour_cancel = 1 THEN 1 END) AS new_user_unsub_within_hour,
|
||
// COUNT(CASE WHEN mg_order.state = 2 AND mg_order.unsubscribe_time >= ? AND mg_order.unsubscribe_time <= ? THEN 1 END) AS new_user_unsub_on_day,
|
||
// COUNT(*) AS new_user_count,
|
||
// CONCAT(ROUND(COUNT(CASE WHEN mg_order.is_one_hour_cancel = 1 THEN 1 END) * 100.0 /
|
||
// NULLIF(COUNT(*), 0), 2), '%') AS new_user_unsub_within_hour_rate,
|
||
// CONCAT(ROUND(COUNT(CASE WHEN mg_order.state = 2 AND mg_order.unsubscribe_time >= ? AND mg_order.unsubscribe_time <= ? THEN 1 END) * 100.0 /
|
||
// NULLIF(COUNT(*), 0), 2), '%') AS new_user_unsub_on_day_rate,
|
||
// IFNULL(
|
||
// CONCAT(
|
||
// LEAST(
|
||
// ROUND(COUNT(*) * 100.0 / NULLIF(
|
||
// (SELECT COUNT(*)
|
||
// FROM mg_transaction_log
|
||
// WHERE verification_code != ''
|
||
// AND channel_code = mg_order.channel_code
|
||
// AND DATE(created_at) = DATE(mg_order.subscribe_time)), 0), 2),
|
||
// 100.00
|
||
// ),
|
||
// '%'
|
||
// ),
|
||
// '0.00%'
|
||
// ) AS submission_success_rate`,
|
||
// startTime, endTime, startTime, endTime).
|
||
// Where("mg_order.subscribe_time >= ? AND mg_order.subscribe_time <= ?", startTime, endTime).
|
||
// Group("DATE(mg_order.subscribe_time), mg_order.product_id, mg_order.channel_code").
|
||
// Order("mg_order.product_id, mg_order.channel_code, mg_order.subscribe_time DESC")
|
||
|
||
qs := e.Orm.Model(&models.MgOrder{}).
|
||
Select(`
|
||
DATE_FORMAT(mg_order.subscribe_time, '%Y-%m-%d') AS date,
|
||
mg_order.product_id,
|
||
mg_order.channel_code,
|
||
IFNULL(submission_count.submission_count, 0) AS submission_count,
|
||
COUNT(CASE WHEN mg_order.is_one_hour_cancel = 1 THEN 1 END) AS new_user_unsub_within_hour,
|
||
COUNT(CASE WHEN mg_order.state = 2 AND DATE(mg_order.unsubscribe_time) = DATE(mg_order.subscribe_time) THEN 1 END) AS new_user_unsub_on_day,
|
||
COUNT(*) AS new_user_count,
|
||
SUM(CASE WHEN mg_order.state = 2 THEN 1 ELSE 0 END) AS total_new_user_unsub,
|
||
CONCAT(ROUND(COUNT(CASE WHEN mg_order.is_one_hour_cancel = 1 THEN 1 END) * 100.0 / NULLIF(COUNT(*), 0), 2), '%') AS new_user_unsub_within_hour_rate,
|
||
CONCAT(ROUND(COUNT(CASE WHEN mg_order.state = 2 AND DATE(mg_order.unsubscribe_time) = DATE(mg_order.subscribe_time) THEN 1 END) * 100.0 / NULLIF(COUNT(*), 0), 2), '%') AS new_user_unsub_on_day_rate,
|
||
CONCAT(ROUND(SUM(CASE WHEN mg_order.state = 2 THEN 1 ELSE 0 END) * 100.0 / NULLIF(COUNT(*), 0), 2), '%') AS total_new_user_unsub_rate,
|
||
IFNULL(
|
||
CONCAT(
|
||
LEAST(
|
||
ROUND(COUNT(*) * 100.0 / NULLIF(submission_count.submission_count, 0), 2),
|
||
100.00
|
||
),
|
||
'%'
|
||
),
|
||
'0.00%'
|
||
) AS submission_success_rate
|
||
`).
|
||
// 使用 Joins 来替代 LeftJoin 进行左连接
|
||
Joins(`
|
||
LEFT JOIN (
|
||
SELECT
|
||
channel_code,
|
||
DATE(created_at) AS created_date,
|
||
COUNT(*) AS submission_count
|
||
FROM mg_transaction_log
|
||
WHERE verification_code != ''
|
||
GROUP BY channel_code, created_date
|
||
) AS submission_count
|
||
ON submission_count.channel_code = mg_order.channel_code
|
||
AND submission_count.created_date = DATE(mg_order.subscribe_time)
|
||
`).
|
||
Where("mg_order.subscribe_time BETWEEN ? AND ?", startTime, endTime).
|
||
Group("DATE(mg_order.subscribe_time), mg_order.product_id, mg_order.channel_code").
|
||
Order("mg_order.product_id, mg_order.channel_code, mg_order.subscribe_time DESC")
|
||
|
||
// 添加过滤条件
|
||
if req.SkuCode != 0 {
|
||
qs = qs.Where("product_id = ?", req.SkuCode)
|
||
}
|
||
if req.Channel != "" {
|
||
qs = qs.Where("channel_code = ?", req.Channel)
|
||
}
|
||
|
||
// 查询总记录数
|
||
err = qs.Count(&count).Error
|
||
if err != nil {
|
||
logger.Errorf("count err: %#v", err)
|
||
response.Error(c, http.StatusInternalServerError, err, "查询失败")
|
||
return
|
||
}
|
||
|
||
// 判断是否导出Excel
|
||
if req.IsExport == 1 {
|
||
// 执行查询
|
||
err = qs.Find(&historicalSummaryList).Error
|
||
} else {
|
||
// 执行查询
|
||
err = qs.Offset(pageNum * req.PageSize).Limit(req.PageSize).Find(&historicalSummaryList).Error
|
||
}
|
||
if err != nil {
|
||
logger.Errorf("HistoricalSummaryList query err: %#v", err)
|
||
response.Error(c, http.StatusInternalServerError, err, "查询失败")
|
||
return
|
||
}
|
||
|
||
// 判断是否导出Excel
|
||
if req.IsExport == 1 {
|
||
// 调用导出Excel函数
|
||
url, err := models.ExportHistoricalSummaryToExcel(historicalSummaryList, e.Orm)
|
||
if err != nil {
|
||
response.Error(c, http.StatusInternalServerError, err, "导出失败")
|
||
return
|
||
}
|
||
|
||
// 返回导出文件的URL地址
|
||
response.OK(c, map[string]string{"export_url": url}, "导出成功")
|
||
return
|
||
}
|
||
|
||
// 返回结果
|
||
resp.List = historicalSummaryList
|
||
resp.Count = int(count)
|
||
|
||
e.OK(resp, "")
|
||
}
|
||
|
||
// HistoricalSummaryListNew 历史汇总查询
|
||
// @Summary 历史汇总查询
|
||
// @Tags 2024-咪咕-管理后台
|
||
// @Produce json
|
||
// @Accept json
|
||
// @Param request body models.HistoricalSummaryListReq true "历史汇总查询"
|
||
// @Success 200 {object} models.HistoricalSummaryListResp
|
||
// @Router /api/v1/admin/historical_summary/list [post]
|
||
func (e MiGuDeployService) HistoricalSummaryListNew(c *gin.Context) {
|
||
fmt.Println("HistoricalSummaryList")
|
||
|
||
err := e.MakeContext(c).MakeOrm().Errors
|
||
if err != nil {
|
||
fmt.Println("MakeContext err:", err)
|
||
e.Logger.Error(err)
|
||
return
|
||
}
|
||
|
||
// 请求参数绑定
|
||
req := &models.HistoricalSummaryListReq{}
|
||
if c.ShouldBindJSON(req) != nil {
|
||
logger.Errorf("para err")
|
||
response.Error(c, http.StatusBadRequest, errors.New("para err"), "参数错误")
|
||
return
|
||
}
|
||
|
||
resp := models.HistoricalSummaryListResp{
|
||
PageNum: req.PageNum,
|
||
}
|
||
|
||
// 分页处理
|
||
pageNum := req.PageNum - 1
|
||
if pageNum < 0 {
|
||
pageNum = 0
|
||
}
|
||
if req.PageSize == 0 {
|
||
req.PageSize = 10
|
||
}
|
||
resp.PageSize = req.PageSize
|
||
|
||
// 获取日期范围
|
||
minDate, maxDate, nCount, err := e.getDateRange(req)
|
||
if err != nil {
|
||
response.Error(c, http.StatusInternalServerError, err, "获取日期范围失败")
|
||
return
|
||
}
|
||
if nCount == 0 {
|
||
e.OK(resp, "")
|
||
return
|
||
}
|
||
|
||
var startTime, endTime string
|
||
if req.IsExport == 1 {
|
||
if startTime != "" && endTime != "" {
|
||
startDate, err := time.Parse("2006-01-02", startTime)
|
||
if err != nil {
|
||
response.Error(c, http.StatusInternalServerError, err, "获取日期范围失败")
|
||
return
|
||
}
|
||
endDate, err := time.Parse("2006-01-02", endTime)
|
||
if err != nil {
|
||
response.Error(c, http.StatusInternalServerError, err, "获取日期范围失败")
|
||
return
|
||
}
|
||
|
||
startTime = startDate.Format("2006-01-02") + " 00:00:00"
|
||
endTime = endDate.Format("2006-01-02") + " 23:59:59"
|
||
} else {
|
||
startTime = minDate + " 00:00:00"
|
||
endTime = maxDate + " 23:59:59"
|
||
}
|
||
} else {
|
||
// 处理分页日期范围
|
||
startTime, endTime, err = e.processTimeRange(req.StartTime, req.EndTime, minDate, maxDate, pageNum)
|
||
if err != nil {
|
||
response.Error(c, http.StatusBadRequest, err, "时间范围无效")
|
||
return
|
||
}
|
||
}
|
||
|
||
fmt.Println("Start Time:", startTime)
|
||
fmt.Println("End Time:", endTime)
|
||
|
||
// 查询数据
|
||
data, err := e.queryHistoricalSummary(startTime, endTime, req)
|
||
if err != nil {
|
||
response.Error(c, http.StatusInternalServerError, err, "查询失败")
|
||
return
|
||
}
|
||
|
||
if req.IsExport != 1 {
|
||
// 补充缺失的日期
|
||
dateList, err := e.getDateList(startTime, endTime)
|
||
if err != nil {
|
||
response.Error(c, http.StatusBadRequest, err, "日期格式无效")
|
||
return
|
||
}
|
||
|
||
finalList := e.fillMissingDates(dateList, data)
|
||
resp.List = finalList
|
||
} else {
|
||
resp.List = data
|
||
}
|
||
|
||
// 计算总条数
|
||
if req.StartTime != "" && req.EndTime != "" {
|
||
// 解析日期
|
||
startDate, _ := time.Parse(models.MiGuTimeFormat, req.StartTime)
|
||
endDate, _ := time.Parse(models.MiGuTimeFormat, req.EndTime)
|
||
|
||
// 计算日期差
|
||
daysDiff := int(endDate.Sub(startDate).Hours() / 24)
|
||
resp.Count = daysDiff + 1
|
||
} else {
|
||
resp.Count = nCount
|
||
}
|
||
|
||
// 导出Excel
|
||
if req.IsExport == 1 {
|
||
url, err := models.ExportHistoricalSummaryToExcel(resp.List, e.Orm)
|
||
if err != nil {
|
||
response.Error(c, http.StatusInternalServerError, err, "导出失败")
|
||
return
|
||
}
|
||
response.OK(c, map[string]string{"export_url": url}, "导出成功")
|
||
return
|
||
}
|
||
|
||
// 返回结果
|
||
e.OK(resp, "")
|
||
}
|
||
|
||
// 获取最早和最晚日期
|
||
func (e MiGuDeployService) getDateRange(req *models.HistoricalSummaryListReq) (string, string, int, error) {
|
||
var result struct {
|
||
MinDate string `json:"min_date"`
|
||
MaxDate string `json:"max_date"`
|
||
}
|
||
|
||
qs := e.Orm.Model(&models.MgOrder{}).
|
||
Select("MIN(DATE(mg_order.subscribe_time)) AS min_date, MAX(DATE(mg_order.subscribe_time)) AS max_date")
|
||
if req.SkuCode != 0 {
|
||
qs = qs.Where("product_id = ?", req.SkuCode)
|
||
}
|
||
if req.Channel != "" {
|
||
qs = qs.Where("channel_code = ?", req.Channel)
|
||
}
|
||
|
||
err := qs.Scan(&result).Error
|
||
if err != nil {
|
||
return "", "", 0, err
|
||
}
|
||
|
||
if result.MinDate == "" || result.MaxDate == "" {
|
||
return "", "", 0, nil
|
||
}
|
||
|
||
// 解析日期
|
||
minDate, err := time.Parse("2006-01-02", result.MinDate)
|
||
if err != nil {
|
||
return "", "", 0, fmt.Errorf("failed to parse min_date: %v", err)
|
||
}
|
||
|
||
maxDate, err := time.Parse("2006-01-02", result.MaxDate)
|
||
if err != nil {
|
||
return "", "", 0, fmt.Errorf("failed to parse max_date: %v", err)
|
||
}
|
||
|
||
// 计算日期差
|
||
daysDiff := int(maxDate.Sub(minDate).Hours() / 24)
|
||
if daysDiff != 0 {
|
||
daysDiff += 1
|
||
}
|
||
|
||
return result.MinDate, result.MaxDate, daysDiff, nil
|
||
}
|
||
|
||
// 处理分页日期范围
|
||
func (e MiGuDeployService) processTimeRange(startTime, endTime, minDate, maxDate string, pageNum int) (string, string, error) {
|
||
var err error
|
||
var startDate, endDate time.Time
|
||
|
||
if startTime != "" && endTime != "" {
|
||
startDate, err = time.Parse(models.MiGuTimeFormat, startTime)
|
||
if err != nil {
|
||
return "", "", fmt.Errorf("startTime 格式无效")
|
||
}
|
||
endDate, err = time.Parse(models.MiGuTimeFormat, endTime)
|
||
if err != nil {
|
||
return "", "", fmt.Errorf("endTime 格式无效")
|
||
}
|
||
} else {
|
||
if minDate == "" || maxDate == "" {
|
||
return "", "", fmt.Errorf("没有数据")
|
||
}
|
||
startDate, err = time.Parse("2006-01-02", minDate)
|
||
if err != nil {
|
||
return "", "", fmt.Errorf("minDate 格式无效")
|
||
}
|
||
|
||
endDate, err = time.Parse("2006-01-02", maxDate)
|
||
if err != nil {
|
||
return "", "", fmt.Errorf("maxDate 格式无效")
|
||
}
|
||
}
|
||
|
||
pageStartDate := endDate.AddDate(0, 0, -(pageNum+1)*10+1)
|
||
pageEndDate := pageStartDate.AddDate(0, 0, 9)
|
||
|
||
if pageStartDate.Before(startDate) {
|
||
pageStartDate = startDate
|
||
}
|
||
|
||
if pageEndDate.After(endDate) {
|
||
pageEndDate = endDate
|
||
}
|
||
|
||
startTime = pageStartDate.Format("2006-01-02") + " 00:00:00"
|
||
endTime = pageEndDate.Format("2006-01-02") + " 23:59:59"
|
||
|
||
return startTime, endTime, nil
|
||
}
|
||
|
||
//// 查询历史数据
|
||
//func (e MiGuDeployService) queryHistoricalSummary(startTime, endTime string, req *models.HistoricalSummaryListReq) ([]models.MgHistoricalSummary, error) {
|
||
// qs := e.Orm.Model(&models.MgOrder{}).
|
||
// Select(`DATE_FORMAT(mg_order.subscribe_time, '%Y-%m-%d') AS date,
|
||
// mg_order.product_id,
|
||
// mg_order.channel_code,
|
||
// (SELECT COUNT(*)
|
||
// FROM mg_transaction_log
|
||
// WHERE verification_code != ''
|
||
// AND channel_code = mg_order.channel_code
|
||
// AND DATE(created_at) = DATE(mg_order.subscribe_time)) AS submission_count,
|
||
// COUNT(CASE WHEN mg_order.is_one_hour_cancel = 1 THEN 1 END) AS new_user_unsub_within_hour,
|
||
// COUNT(CASE WHEN mg_order.state = 2 AND mg_order.unsubscribe_time >= ? AND mg_order.unsubscribe_time <= ? THEN 1 END) AS new_user_unsub_on_day,
|
||
// COUNT(*) AS new_user_count,
|
||
// CONCAT(ROUND(COUNT(CASE WHEN mg_order.is_one_hour_cancel = 1 THEN 1 END) * 100.0 /
|
||
// NULLIF(COUNT(*), 0), 2), '%') AS new_user_unsub_within_hour_rate,
|
||
// CONCAT(ROUND(COUNT(CASE WHEN mg_order.state = 2 AND mg_order.unsubscribe_time >= ? AND mg_order.unsubscribe_time <= ? THEN 1 END) * 100.0 /
|
||
// NULLIF(COUNT(*), 0), 2), '%') AS new_user_unsub_on_day_rate,
|
||
// IFNULL(
|
||
// CONCAT(
|
||
// LEAST(
|
||
// ROUND(COUNT(*) * 100.0 / NULLIF(
|
||
// (SELECT COUNT(*)
|
||
// FROM mg_transaction_log
|
||
// WHERE verification_code != ''
|
||
// AND channel_code = mg_order.channel_code
|
||
// AND DATE(created_at) = DATE(mg_order.subscribe_time)), 0), 2),
|
||
// 100.00
|
||
// ),
|
||
// '%'
|
||
// ),
|
||
// '0.00%'
|
||
// ) AS submission_success_rate`,
|
||
// startTime, time.Now(), startTime, time.Now()).
|
||
// Where("mg_order.subscribe_time >= ? AND mg_order.subscribe_time <= ?", startTime, endTime).
|
||
// Group("DATE(mg_order.subscribe_time), mg_order.product_id, mg_order.channel_code").
|
||
// Order("mg_order.product_id, mg_order.channel_code, mg_order.subscribe_time DESC")
|
||
//
|
||
// if req.SkuCode != 0 {
|
||
// qs = qs.Where("product_id = ?", req.SkuCode)
|
||
// }
|
||
// if req.Channel != "" {
|
||
// qs = qs.Where("channel_code = ?", req.Channel)
|
||
// }
|
||
//
|
||
// var err error
|
||
// var data []models.MgHistoricalSummary
|
||
// if req.IsExport == 1 {
|
||
// err = qs.Find(&data).Error
|
||
// } else {
|
||
// err = qs.Limit(req.PageSize).Find(&data).Error
|
||
// }
|
||
// if err != nil {
|
||
// return nil, err
|
||
// }
|
||
//
|
||
// return data, nil
|
||
//}
|
||
|
||
//// 查询历史数据
|
||
//func (e MiGuDeployService) queryHistoricalSummary(startTime, endTime string, req *models.HistoricalSummaryListReq) ([]models.MgHistoricalSummary, error) {
|
||
// qs := e.Orm.Model(&models.MgOrder{}).
|
||
// Select(`
|
||
// DATE_FORMAT(mg_order.subscribe_time, '%Y-%m-%d') AS date,
|
||
// mg_order.product_id,
|
||
// mg_order.channel_code,
|
||
// (
|
||
// SELECT COUNT(*)
|
||
// FROM mg_transaction_log
|
||
// WHERE verification_code != ''
|
||
// AND channel_code = mg_order.channel_code
|
||
// AND DATE(created_at) = DATE(mg_order.subscribe_time)
|
||
// ) AS submission_count,
|
||
// COUNT(CASE WHEN mg_order.is_one_hour_cancel = 1 THEN 1 END) AS new_user_unsub_within_hour,
|
||
// COUNT(CASE WHEN mg_order.state = 2 AND DATE(mg_order.unsubscribe_time) = DATE(mg_order.subscribe_time) THEN 1 END) AS new_user_unsub_on_day,
|
||
// COUNT(*) AS new_user_count,
|
||
// SUM(CASE WHEN mg_order.state = 2 THEN 1 ELSE 0 END) AS total_new_user_unsub,
|
||
// CONCAT(ROUND(COUNT(CASE WHEN mg_order.is_one_hour_cancel = 1 THEN 1 END) * 100.0 / NULLIF(COUNT(*), 0), 2), '%') AS new_user_unsub_within_hour_rate,
|
||
// CONCAT(ROUND(COUNT(CASE WHEN mg_order.state = 2 AND DATE(mg_order.unsubscribe_time) = DATE(mg_order.subscribe_time) THEN 1 END) * 100.0 / NULLIF(COUNT(*), 0), 2), '%') AS new_user_unsub_on_day_rate,
|
||
// CONCAT(ROUND(SUM(CASE WHEN mg_order.state = 2 THEN 1 ELSE 0 END) * 100.0 / NULLIF(COUNT(*), 0), 2), '%') AS total_new_user_unsub_rate,
|
||
// IFNULL(
|
||
// CONCAT(
|
||
// LEAST(
|
||
// ROUND(COUNT(*) * 100.0 / NULLIF(
|
||
// (SELECT COUNT(*)
|
||
// FROM mg_transaction_log
|
||
// WHERE verification_code != ''
|
||
// AND channel_code = mg_order.channel_code
|
||
// AND DATE(created_at) = DATE(mg_order.subscribe_time)), 0), 2),
|
||
// 100.00
|
||
// ),
|
||
// '%'
|
||
// ),
|
||
// '0.00%'
|
||
// ) AS submission_success_rate
|
||
// `).
|
||
// Where("mg_order.subscribe_time BETWEEN ? AND ?", startTime, endTime).
|
||
// Group("DATE(mg_order.subscribe_time), mg_order.product_id, mg_order.channel_code").
|
||
// Order("mg_order.product_id, mg_order.channel_code, mg_order.subscribe_time DESC")
|
||
//
|
||
// if req.SkuCode != 0 {
|
||
// qs = qs.Where("product_id = ?", req.SkuCode)
|
||
// }
|
||
// if req.Channel != "" {
|
||
// qs = qs.Where("channel_code = ?", req.Channel)
|
||
// }
|
||
//
|
||
// var data []models.MgHistoricalSummary
|
||
// var err error
|
||
// if req.IsExport == 1 {
|
||
// err = qs.Find(&data).Error
|
||
// } else {
|
||
// err = qs.Limit(req.PageSize).Find(&data).Error
|
||
// }
|
||
// if err != nil {
|
||
// return nil, err
|
||
// }
|
||
//
|
||
// return data, nil
|
||
//}
|
||
|
||
// 查询历史数据
|
||
func (e MiGuDeployService) queryHistoricalSummary(startTime, endTime string, req *models.HistoricalSummaryListReq) ([]models.MgHistoricalSummary, error) {
|
||
qs := e.Orm.Model(&models.MgOrder{}).
|
||
Select(`
|
||
DATE_FORMAT(mg_order.subscribe_time, '%Y-%m-%d') AS date,
|
||
mg_order.product_id,
|
||
mg_order.channel_code,
|
||
IFNULL(submission_count.submission_count, 0) AS submission_count,
|
||
COUNT(CASE WHEN mg_order.is_one_hour_cancel = 1 THEN 1 END) AS new_user_unsub_within_hour,
|
||
COUNT(CASE WHEN mg_order.state = 2 AND DATE(mg_order.unsubscribe_time) = DATE(mg_order.subscribe_time) THEN 1 END) AS new_user_unsub_on_day,
|
||
COUNT(*) AS new_user_count,
|
||
SUM(CASE WHEN mg_order.state = 2 THEN 1 ELSE 0 END) AS total_new_user_unsub,
|
||
CONCAT(ROUND(COUNT(CASE WHEN mg_order.is_one_hour_cancel = 1 THEN 1 END) * 100.0 / NULLIF(COUNT(*), 0), 2), '%') AS new_user_unsub_within_hour_rate,
|
||
CONCAT(ROUND(COUNT(CASE WHEN mg_order.state = 2 AND DATE(mg_order.unsubscribe_time) = DATE(mg_order.subscribe_time) THEN 1 END) * 100.0 / NULLIF(COUNT(*), 0), 2), '%') AS new_user_unsub_on_day_rate,
|
||
CONCAT(ROUND(SUM(CASE WHEN mg_order.state = 2 THEN 1 ELSE 0 END) * 100.0 / NULLIF(COUNT(*), 0), 2), '%') AS total_new_user_unsub_rate,
|
||
IFNULL(
|
||
CONCAT(
|
||
LEAST(
|
||
ROUND(COUNT(*) * 100.0 / NULLIF(submission_count.submission_count, 0), 2),
|
||
100.00
|
||
),
|
||
'%'
|
||
),
|
||
'0.00%'
|
||
) AS submission_success_rate
|
||
`).
|
||
// 使用 Joins 来替代 LeftJoin 进行左连接
|
||
Joins(`
|
||
LEFT JOIN (
|
||
SELECT
|
||
channel_code,
|
||
DATE(created_at) AS created_date,
|
||
COUNT(*) AS submission_count
|
||
FROM mg_transaction_log
|
||
WHERE verification_code != ''
|
||
GROUP BY channel_code, created_date
|
||
) AS submission_count
|
||
ON submission_count.channel_code = mg_order.channel_code
|
||
AND submission_count.created_date = DATE(mg_order.subscribe_time)
|
||
`).
|
||
Where("mg_order.subscribe_time BETWEEN ? AND ?", startTime, endTime).
|
||
Group("DATE(mg_order.subscribe_time), mg_order.product_id, mg_order.channel_code").
|
||
Order("mg_order.product_id, mg_order.channel_code, mg_order.subscribe_time DESC")
|
||
|
||
// 根据请求条件过滤
|
||
if req.SkuCode != 0 {
|
||
qs = qs.Where("mg_order.product_id = ?", req.SkuCode)
|
||
}
|
||
if req.Channel != "" {
|
||
qs = qs.Where("mg_order.channel_code = ?", req.Channel)
|
||
}
|
||
|
||
var data []models.MgHistoricalSummary
|
||
var err error
|
||
if req.IsExport == 1 {
|
||
// 导出数据不限制分页
|
||
err = qs.Find(&data).Error
|
||
} else {
|
||
// 非导出时进行分页查询
|
||
err = qs.Limit(req.PageSize).Find(&data).Error
|
||
}
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
return data, nil
|
||
}
|
||
|
||
// 获取日期列表
|
||
func (e MiGuDeployService) getDateList(startTime, endTime string) ([]string, error) {
|
||
var dateList []string
|
||
|
||
currentDate, err := time.Parse("2006-01-02 15:04:05", startTime)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("时间格式无效")
|
||
}
|
||
|
||
endDate, err := time.Parse("2006-01-02 15:04:05", endTime)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("结束时间格式无效")
|
||
}
|
||
|
||
for currentDate.Before(endDate) {
|
||
dateList = append(dateList, currentDate.Format("2006-01-02"))
|
||
currentDate = currentDate.AddDate(0, 0, 1)
|
||
}
|
||
|
||
// 反转切片,确保日期是倒序的
|
||
for i := 0; i < len(dateList)/2; i++ {
|
||
j := len(dateList) - i - 1
|
||
dateList[i], dateList[j] = dateList[j], dateList[i]
|
||
}
|
||
|
||
return dateList, nil
|
||
}
|
||
|
||
// 填充缺失日期
|
||
func (e MiGuDeployService) fillMissingDates(dateList []string, data []models.MgHistoricalSummary) []models.MgHistoricalSummary {
|
||
dateMap := make(map[string]models.MgHistoricalSummary)
|
||
for _, d := range data {
|
||
dateMap[d.Date] = d
|
||
}
|
||
|
||
var result []models.MgHistoricalSummary
|
||
for _, date := range dateList {
|
||
if summary, ok := dateMap[date]; ok {
|
||
result = append(result, summary)
|
||
} else {
|
||
result = append(result, models.MgHistoricalSummary{Date: date}) // 日期没有数据时,返回空数据
|
||
}
|
||
}
|
||
return result
|
||
}
|
||
|
||
// 获取日期列表
|
||
func (e MiGuDeployService) getDateListOnHome(startTime, endTime string) ([]string, error) {
|
||
var dateList []string
|
||
|
||
currentDate, err := time.Parse("2006-01-02 15:04:05", startTime)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("时间格式无效")
|
||
}
|
||
|
||
endDate, err := time.Parse("2006-01-02 15:04:05", endTime)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("结束时间格式无效")
|
||
}
|
||
|
||
for currentDate.Before(endDate) {
|
||
dateList = append(dateList, currentDate.Format("2006-01-02"))
|
||
currentDate = currentDate.AddDate(0, 0, 1)
|
||
}
|
||
|
||
return dateList, nil
|
||
}
|
||
|
||
// 填充缺失日期
|
||
func (e MiGuDeployService) fillMissingDatesOnHome(dateList []string, data []models.DailyData) []models.DailyData {
|
||
dateMap := make(map[string]models.DailyData)
|
||
for _, d := range data {
|
||
dateMap[d.Date] = d
|
||
}
|
||
|
||
var result []models.DailyData
|
||
for _, date := range dateList {
|
||
if summary, ok := dateMap[date]; ok {
|
||
result = append(result, summary)
|
||
} else {
|
||
result = append(result, models.DailyData{
|
||
Date: date,
|
||
NewUserCount: 0,
|
||
UnsubscribedUserCount: 0,
|
||
UnsubscribedWithinOneHour: 0,
|
||
UnsubscribeRate: "0.00%",
|
||
UnsubscribeWithinOneHourRate: "0.00%",
|
||
TotalCancelCount: 0,
|
||
}) // 日期没有数据时,返回空数据
|
||
}
|
||
}
|
||
return result
|
||
}
|
||
|
||
// 获取最早和最晚日期
|
||
func (e MiGuDeployService) getDateRangeOnHome(req *models.HomepageDataSummaryReq) (time.Time, time.Time, int, error) {
|
||
var minDate, maxDate time.Time
|
||
var result struct {
|
||
MinDate string `json:"min_date"`
|
||
MaxDate string `json:"max_date"`
|
||
}
|
||
|
||
qs := e.Orm.Model(&models.MgOrder{}).
|
||
Select("MIN(DATE(mg_order.subscribe_time)) AS min_date, MAX(DATE(mg_order.subscribe_time)) AS max_date")
|
||
if req.ProductID != 0 {
|
||
qs = qs.Where("product_id = ?", req.ProductID)
|
||
}
|
||
if req.Channel != "" {
|
||
qs = qs.Where("channel_code = ?", req.Channel)
|
||
}
|
||
|
||
err := qs.Scan(&result).Error
|
||
if err != nil {
|
||
return minDate, maxDate, 0, err
|
||
}
|
||
|
||
if result.MinDate == "" || result.MaxDate == "" {
|
||
return minDate, maxDate, 0, err
|
||
}
|
||
|
||
// 解析日期
|
||
minDate, err = time.Parse("2006-01-02", result.MinDate)
|
||
if err != nil {
|
||
return minDate, maxDate, 0, fmt.Errorf("failed to parse min_date: %v", err)
|
||
}
|
||
|
||
maxDate, err = time.Parse("2006-01-02", result.MaxDate)
|
||
if err != nil {
|
||
return minDate, maxDate, 0, fmt.Errorf("failed to parse max_date: %v", err)
|
||
}
|
||
|
||
// 计算日期差
|
||
daysDiff := int(maxDate.Sub(minDate).Hours() / 24)
|
||
if daysDiff != 0 {
|
||
daysDiff += 1
|
||
}
|
||
|
||
return minDate, maxDate, daysDiff, nil
|
||
}
|
||
|
||
// RealtimeSummaryList 当日实时汇总
|
||
// @Summary 当日实时汇总
|
||
// @Tags 2024-咪咕-管理后台
|
||
// @Produce json
|
||
// @Accept json
|
||
// @Param request body models.RealtimeSummaryListReq true "当日实时汇总"
|
||
// @Success 200 {object} models.RealtimeSummaryListResp
|
||
// @Router /api/v1/admin/realtime_summary/list [post]
|
||
func (e MiGuDeployService) RealtimeSummaryList(c *gin.Context) {
|
||
fmt.Println("RealtimeSummaryList")
|
||
err := e.MakeContext(c).MakeOrm().Errors
|
||
if err != nil {
|
||
fmt.Println("MakeContext err:", err)
|
||
e.Logger.Error(err)
|
||
return
|
||
}
|
||
|
||
req := &models.RealtimeSummaryListReq{}
|
||
if c.ShouldBindJSON(req) != nil {
|
||
logger.Errorf("para err")
|
||
response.Error(c, http.StatusBadRequest, errors.New("para err"), "参数错误")
|
||
return
|
||
}
|
||
|
||
resp := models.RealtimeSummaryListResp{
|
||
PageNum: req.PageNum,
|
||
}
|
||
|
||
// 分页处理
|
||
pageNum := req.PageNum - 1
|
||
if pageNum < 0 {
|
||
pageNum = 0
|
||
}
|
||
if req.PageSize == 0 {
|
||
req.PageSize = 10
|
||
}
|
||
resp.PageSize = req.PageSize
|
||
|
||
if e.Orm == nil {
|
||
fmt.Println("Orm is nil")
|
||
}
|
||
|
||
// 获取当天的起始时间和结束时间
|
||
today := time.Now().Format("2006-01-02") // 获取当天日期字符串 "yyyy-MM-dd"
|
||
startDate := today + " 00:00:00"
|
||
endDate := today + " 23:59:59"
|
||
|
||
// 汇总查询,包括当日新增用户数
|
||
var count int64
|
||
var realtimeSummaryList []models.MgRealtimeSummary
|
||
|
||
qs := e.Orm.Model(&models.MgOrder{}).
|
||
Select(`product_id, channel_code,
|
||
(SELECT COUNT(*)
|
||
FROM mg_transaction_log
|
||
WHERE verification_code != ''
|
||
AND channel_code = mg_order.channel_code
|
||
AND created_at >= ?
|
||
AND created_at <= ?) AS submission_count,
|
||
COUNT(CASE WHEN mg_order.is_one_hour_cancel = 1 THEN 1 END) AS new_user_unsub_within_hour,
|
||
COUNT(CASE WHEN mg_order.state = 2 THEN 1 END) AS new_user_unsub_on_day,
|
||
COUNT(CASE WHEN mg_order.created_at >= ? AND mg_order.created_at <= ? THEN 1 END) AS new_user_count,
|
||
CONCAT(ROUND(COUNT(CASE WHEN mg_order.is_one_hour_cancel = 1 THEN 1 END) * 100.0 /
|
||
NULLIF(COUNT(CASE WHEN mg_order.created_at >= ? AND mg_order.created_at <= ? THEN 1 END), 0), 2), '%') AS new_user_unsub_within_hour_rate,
|
||
CONCAT(ROUND(COUNT(CASE WHEN mg_order.state = 2 THEN 1 END) * 100.0 /
|
||
NULLIF(COUNT(CASE WHEN mg_order.created_at >= ? AND mg_order.created_at <= ? THEN 1 END), 0), 2), '%') AS new_user_unsub_on_day_rate`,
|
||
startDate, endDate, startDate, endDate, startDate, endDate, startDate, endDate).
|
||
Where("mg_order.created_at >= ? AND mg_order.created_at <= ?", startDate, endDate).
|
||
Group("product_id, channel_code").
|
||
Order("product_id, channel_code").
|
||
Offset(pageNum * req.PageSize).Limit(req.PageSize)
|
||
|
||
// 产品编号
|
||
if req.SkuCode != 0 {
|
||
qs = qs.Where("product_id = ?", req.SkuCode)
|
||
}
|
||
// 渠道号
|
||
if req.Channel != "" {
|
||
qs = qs.Where("channel_code = ?", req.Channel)
|
||
}
|
||
|
||
// 获取数据
|
||
err = qs.Find(&realtimeSummaryList).Error
|
||
if err != nil {
|
||
logger.Errorf("RealtimeSummaryList query err: %#v", err)
|
||
response.Error(c, http.StatusInternalServerError, err, "查询失败")
|
||
return
|
||
}
|
||
|
||
for i, value := range realtimeSummaryList {
|
||
if value.SubmissionCount > 0 { // 避免除以零的错误
|
||
successRate := (float64(value.NewUserCount) / float64(value.SubmissionCount)) * 100
|
||
realtimeSummaryList[i].SubmissionSuccessRate = fmt.Sprintf("%.2f%%", successRate) // 格式化为百分比字符串
|
||
} else {
|
||
realtimeSummaryList[i].SubmissionSuccessRate = "0.00%" // 提交数为 0 时,成功率为 0
|
||
}
|
||
}
|
||
|
||
// 查询总记录数
|
||
es := e.Orm.Model(&models.MgOrder{}).
|
||
Select(`product_id, channel_code,
|
||
(SELECT COUNT(*)
|
||
FROM mg_transaction_log
|
||
WHERE verification_code != ''
|
||
AND channel_code = mg_order.channel_code
|
||
AND created_at >= ?
|
||
AND created_at <= ?) AS submission_count,
|
||
COUNT(CASE WHEN mg_order.is_one_hour_cancel = 1 THEN 1 END) AS new_user_unsub_within_hour,
|
||
COUNT(CASE WHEN mg_order.state = 2 THEN 1 END) AS new_user_unsub_on_day,
|
||
COUNT(CASE WHEN mg_order.created_at >= ? AND mg_order.created_at <= ? THEN 1 END) AS new_user_count,
|
||
CONCAT(ROUND(COUNT(CASE WHEN mg_order.is_one_hour_cancel = 1 THEN 1 END) * 100.0 /
|
||
NULLIF(COUNT(CASE WHEN mg_order.created_at >= ? AND mg_order.created_at <= ? THEN 1 END), 0), 2), '%') AS new_user_unsub_within_hour_rate,
|
||
CONCAT(ROUND(COUNT(CASE WHEN mg_order.state = 2 THEN 1 END) * 100.0 /
|
||
NULLIF(COUNT(CASE WHEN mg_order.created_at >= ? AND mg_order.created_at <= ? THEN 1 END), 0), 2), '%') AS new_user_unsub_on_day_rate`,
|
||
startDate, endDate, startDate, endDate, startDate, endDate, startDate, endDate).
|
||
Where("mg_order.created_at >= ? AND mg_order.created_at <= ?", startDate, endDate).
|
||
Group("product_id, channel_code").
|
||
Order("product_id, channel_code")
|
||
err = es.Count(&count).Error
|
||
if err != nil {
|
||
logger.Errorf("count err: %#v", err)
|
||
response.Error(c, http.StatusInternalServerError, err, "查询失败")
|
||
return
|
||
}
|
||
|
||
// 判断是否导出Excel
|
||
if req.IsExport == 1 {
|
||
// 调用导出Excel函数
|
||
url, err := models.ExportRealtimeSummaryToExcel(realtimeSummaryList, e.Orm)
|
||
if err != nil {
|
||
response.Error(c, http.StatusInternalServerError, err, "导出失败")
|
||
return
|
||
}
|
||
|
||
// 返回导出文件的URL地址
|
||
response.OK(c, map[string]string{"export_url": url}, "导出成功")
|
||
return
|
||
}
|
||
|
||
// 返回数据
|
||
resp.List = realtimeSummaryList
|
||
resp.Count = int(count)
|
||
|
||
e.OK(resp, "")
|
||
}
|
||
|
||
// UserRetentionList 用户留存记录
|
||
// @Summary 用户留存记录
|
||
// @Tags 2024-咪咕-管理后台
|
||
// @Produce json
|
||
// @Accept json
|
||
// @Param request body models.UserRetentionListReq true "用户留存记录"
|
||
// @Success 200 {object} models.UserRetentionListResp
|
||
// @Router /api/v1/admin/user_retention/list [post]
|
||
func (e MiGuDeployService) UserRetentionList(c *gin.Context) {
|
||
// 创建上下文和 ORM
|
||
ctx := e.MakeContext(c)
|
||
if err := ctx.MakeOrm().Errors; err != nil {
|
||
e.Logger.Error(err)
|
||
response.Error(c, http.StatusInternalServerError, err, "数据库错误")
|
||
return
|
||
}
|
||
|
||
// 解析请求参数
|
||
req := &models.UserRetentionListReq{}
|
||
if err := c.ShouldBindJSON(req); err != nil {
|
||
response.Error(c, http.StatusBadRequest, err, "参数错误")
|
||
return
|
||
}
|
||
|
||
resp := models.UserRetentionListResp{
|
||
PageNum: req.PageNum,
|
||
}
|
||
|
||
// 分页处理
|
||
pageNum := req.PageNum - 1
|
||
if pageNum < 0 {
|
||
pageNum = 0
|
||
}
|
||
if req.PageSize == 0 {
|
||
req.PageSize = 10
|
||
}
|
||
resp.PageSize = req.PageSize
|
||
|
||
qs := e.Orm.Model(&models.MgOrder{})
|
||
|
||
//// 获取开始和结束时间
|
||
//retentionMonth := req.RetentionMonth // 例如 "2024-10"
|
||
//if retentionMonth != "" {
|
||
// //retentionMonth = time.Now().Format("2006-01")
|
||
//
|
||
// // 解析出年和月
|
||
// year, month, err := models.ParseYearMonth(retentionMonth)
|
||
// if err != nil {
|
||
// response.Error(c, http.StatusBadRequest, err, "日期格式错误")
|
||
// return
|
||
// }
|
||
//
|
||
// // 计算该月份的第一天和最后一天
|
||
// startDate := time.Date(year, month, 1, 0, 0, 0, 0, time.UTC) // 当月第一天
|
||
// endDate := startDate.AddDate(0, 1, 0).Add(-time.Second) // 当月最后一天的最后一毫秒
|
||
// qs = qs.Where("subscribe_time >= ? AND subscribe_time <= ?", startDate, endDate)
|
||
//}
|
||
|
||
// 处理产品编号 (SkuCode) 过滤条件
|
||
if req.SkuCode != 0 {
|
||
qs = qs.Where("product_id = ?", req.SkuCode)
|
||
}
|
||
|
||
// 处理渠道名称 (Channel) 过滤条件
|
||
if req.Channel != "" {
|
||
qs = qs.Where("channel_code = ?", req.Channel)
|
||
}
|
||
|
||
// 定义返回的响应结构
|
||
var retentionList []models.MgUserRetention
|
||
|
||
// 查询用户留存数据
|
||
err := qs.
|
||
Select(`DATE_FORMAT(subscribe_time, '%Y-%m') AS retention_month,
|
||
COUNT(phone_number) AS new_user_count,
|
||
COUNT(CASE WHEN state = 1 THEN phone_number END) AS retained_user_count,
|
||
channel_code, product_id,
|
||
IFNULL(FORMAT(COUNT(CASE WHEN state = 1 THEN phone_number END) / NULLIF(COUNT(phone_number), 0) * 100, 2), 0) AS retention_rate`).
|
||
Group("retention_month, channel_code, product_id").
|
||
Order("retention_month").
|
||
Find(&retentionList).Error
|
||
|
||
if err != nil {
|
||
e.Logger.Errorf("UserRetentionList query error: %#v", err)
|
||
response.Error(c, http.StatusInternalServerError, err, "查询失败")
|
||
return
|
||
}
|
||
|
||
// 如果传递了月份参数,则在内存中过滤对应月份的数据
|
||
if req.RetentionMonth != "" {
|
||
var filteredList []models.MgUserRetention
|
||
for _, item := range retentionList {
|
||
if item.RetentionMonth == req.RetentionMonth {
|
||
filteredList = append(filteredList, item)
|
||
}
|
||
}
|
||
retentionList = filteredList
|
||
}
|
||
|
||
for i := range retentionList {
|
||
retentionList[i].RetentionRate += "%"
|
||
}
|
||
|
||
// 查询对应月份的最近2个月1号留存数据
|
||
// 现在遍历 retentionList,查询每个月份对应的最近1个月和最近2个月的留存数据
|
||
for i := range retentionList {
|
||
// 获取当前记录的 RetentionMonth
|
||
year, month, _ := models.ParseYearMonth(retentionList[i].RetentionMonth)
|
||
|
||
// 计算当前月1号和上个月1号
|
||
currentYear := time.Now().Year()
|
||
currentMonthFirstDay := time.Date(year, month, 1, 0, 0, 0, 0, time.Local) // 统计月的1号
|
||
currentMonthNextFirstDay := time.Date(year, month+1, 1, 0, 0, 0, 0, time.Local) // 统计月的次月1号
|
||
lastMonthFirstDay := time.Date(time.Now().Year(), time.Now().Month(), 1, 0, 0, 0, 0, time.Local) // 当前月1号
|
||
lastTwoMonthFirstDay := lastMonthFirstDay.AddDate(0, -1, 0) // 上个月1号
|
||
|
||
// 计算需要的时间变量:当月最后一天、上月2号、上月最后一天、上上月2号等
|
||
lastMonthSecondDay := lastMonthFirstDay.AddDate(0, 0, 1) // 上个月2号
|
||
lastTwoMonthSecondDay := lastTwoMonthFirstDay.AddDate(0, 0, 1) // 上上个月2号
|
||
|
||
// 获取最近1个月留存用户数
|
||
var lastMonthRetentionCount int64
|
||
if currentYear == year && month == time.Now().Month() {
|
||
lastMonthRetentionCount = 0
|
||
} else {
|
||
err = e.Orm.Model(&models.MgOrder{}).
|
||
Where("state = 1 AND created_at between ? and ?", currentMonthFirstDay, currentMonthNextFirstDay).
|
||
Or("state = 2 AND unsubscribe_time > ? AND created_at between ? and ?", lastMonthSecondDay, currentMonthFirstDay, currentMonthNextFirstDay).
|
||
Count(&lastMonthRetentionCount).Error
|
||
}
|
||
|
||
// 获取最近2个月留存用户数
|
||
var lastTwoMonthRetentionCount int64
|
||
if currentYear == year && month == time.Now().Month() {
|
||
lastTwoMonthRetentionCount = 0
|
||
} else if currentYear == year && month == time.Now().Month()-1 {
|
||
lastTwoMonthRetentionCount = 0
|
||
} else {
|
||
err = e.Orm.Model(&models.MgOrder{}).
|
||
Where("state = 1 AND created_at between ? and ?", currentMonthFirstDay, currentMonthNextFirstDay).
|
||
Or("state = 2 AND unsubscribe_time > ? AND created_at between ? and ?", lastTwoMonthSecondDay, currentMonthFirstDay, currentMonthNextFirstDay).
|
||
Count(&lastTwoMonthRetentionCount).Error
|
||
}
|
||
|
||
// 设置最近1个月和最近2个月的留存数
|
||
retentionList[i].LastMonthDate = lastMonthFirstDay.Format("2006-01-02")
|
||
retentionList[i].LastTwoMonthDate = lastTwoMonthFirstDay.Format("2006-01-02")
|
||
retentionList[i].LastMonthRetentionCount = int(lastMonthRetentionCount)
|
||
retentionList[i].LastTwoMonthRetentionCount = int(lastTwoMonthRetentionCount)
|
||
|
||
// 计算最近1个月和最近2个月的留存率
|
||
if retentionList[i].NewUserCount > 0 {
|
||
lastMonthRetentionRate := float64(lastMonthRetentionCount) / float64(retentionList[i].NewUserCount) * 100
|
||
lastTwoMonthRetentionRate := float64(lastTwoMonthRetentionCount) / float64(retentionList[i].NewUserCount) * 100
|
||
|
||
retentionList[i].LastMonthRetentionRate = fmt.Sprintf("%.2f%%", lastMonthRetentionRate)
|
||
retentionList[i].LastTwoMonthRetentionRate = fmt.Sprintf("%.2f%%", lastTwoMonthRetentionRate)
|
||
}
|
||
}
|
||
|
||
resp.List = retentionList
|
||
resp.Count = len(retentionList) // Count should reflect the actual number of entries
|
||
|
||
// 返回结果
|
||
response.OK(c, resp, "成功")
|
||
}
|
||
|
||
// UserDayRetentionList 用户留存记录(按天)
|
||
// @Summary 用户留存记录(按天)
|
||
// @Tags 2024-咪咕-管理后台
|
||
// @Produce json
|
||
// @Accept json
|
||
// @Param request body models.UserDayRetentionListReq true "用户留存记录(按天)"
|
||
// @Success 200 {object} models.UserDayRetentionListResp
|
||
// @Router /api/v1/admin/user_day_retention/list [post]
|
||
func (e MiGuDeployService) UserDayRetentionList(c *gin.Context) {
|
||
// 创建上下文和 ORM
|
||
ctx := e.MakeContext(c)
|
||
if err := ctx.MakeOrm().Errors; err != nil {
|
||
e.Logger.Error(err)
|
||
response.Error(c, http.StatusInternalServerError, err, "数据库错误")
|
||
return
|
||
}
|
||
|
||
// 解析请求参数
|
||
req := &models.UserDayRetentionListReq{}
|
||
if err := c.ShouldBindJSON(req); err != nil {
|
||
response.Error(c, http.StatusBadRequest, err, "参数错误")
|
||
return
|
||
}
|
||
|
||
resp := models.UserDayRetentionListResp{
|
||
PageNum: req.PageNum,
|
||
}
|
||
|
||
// 分页处理
|
||
pageNum := req.PageNum - 1
|
||
if pageNum < 0 {
|
||
pageNum = 0
|
||
}
|
||
if req.PageSize == 0 {
|
||
req.PageSize = 10
|
||
}
|
||
resp.PageSize = req.PageSize
|
||
|
||
// 获取新增用户数(在RetentionMonth的所有用户)
|
||
var newUserCount int64
|
||
err := e.Orm.Table("mg_order").
|
||
Where("created_at >= ?", req.RetentionMonth+"-01 00:00:00").
|
||
Where("created_at < ?", req.RetentionMonth+"-31 23:59:59").
|
||
Count(&newUserCount).Error
|
||
if err != nil {
|
||
e.Logger.Error(err)
|
||
response.Error(c, http.StatusInternalServerError, err, "获取新增用户数失败")
|
||
return
|
||
}
|
||
|
||
// 如果没有新增用户,则直接返回空结果
|
||
if newUserCount == 0 {
|
||
resp.List = []models.MgUserDayRetention{}
|
||
resp.Count = 0
|
||
e.OK(resp, "success")
|
||
return
|
||
}
|
||
|
||
// 计算下个月的1号(留存查询从下个月1号开始)
|
||
parsedMonth, err := time.Parse("2006-01", req.RetentionMonth)
|
||
if err != nil {
|
||
e.Logger.Error(err)
|
||
response.Error(c, http.StatusBadRequest, err, "解析RetentionMonth失败")
|
||
return
|
||
}
|
||
|
||
// 获取下个月的1号
|
||
nextMonth := parsedMonth.AddDate(0, 1, 0) // 加一个月
|
||
startDate := nextMonth.Format("2006-01-02") // 下个月1号
|
||
|
||
// 获取当前日期,作为退出条件
|
||
currentDate := time.Now().Format("2006-01-02")
|
||
|
||
// 获取当前记录的 RetentionMonth
|
||
year, month, _ := models.ParseYearMonth(req.RetentionMonth)
|
||
|
||
// 计算当前月1号和上个月1号
|
||
currentMonthFirstDay := time.Date(year, month, 1, 0, 0, 0, 0, time.Local) // 统计月的1号
|
||
currentMonthNextFirstDay := time.Date(year, month+1, 1, 0, 0, 0, 0, time.Local) // 统计月的次月1号
|
||
|
||
// 初始化结果列表
|
||
var retentionData []models.MgUserDayRetention
|
||
|
||
// 使用 goroutine 处理并发查询
|
||
var wg sync.WaitGroup
|
||
// 创建一个 channel 来接收每一天的查询结果
|
||
resultChan := make(chan models.MgUserDayRetention, 100)
|
||
|
||
// 控制并发数的最大值,避免数据库过载
|
||
sem := make(chan struct{}, 10) // 同时最多 10 个 goroutine 执行
|
||
|
||
// 根据 OnlyFirstDay 参数调整查询逻辑
|
||
if req.OnlyFirstDay {
|
||
// 如果只查询每个月1号的数据
|
||
for {
|
||
// 增加日期(开始日期自增一个月的1号)
|
||
startDateTime, _ := time.Parse("2006-01-02", startDate)
|
||
|
||
// 如果当前日期大于等于startDate,且是查询下一个月的1号,则退出
|
||
if startDateTime.After(time.Now()) {
|
||
break
|
||
}
|
||
|
||
wg.Add(1)
|
||
sem <- struct{}{} // 占一个信号,表示有一个 goroutine 正在执行
|
||
|
||
go func(date string) {
|
||
defer wg.Done()
|
||
defer func() { <-sem }() // 释放一个信号,表示该 goroutine 执行完毕
|
||
|
||
var retainedUserCount int64
|
||
var unsubOnDay int64
|
||
|
||
// 查询该天的留存用户数
|
||
err = e.Orm.Model(&models.MgOrder{}).
|
||
Where("state = 1 AND created_at between ? and ?", currentMonthFirstDay, currentMonthNextFirstDay).
|
||
Or("state = 2 AND unsubscribe_time > ? AND created_at between ? and ?", date+" 23:59:59", currentMonthFirstDay, currentMonthNextFirstDay).
|
||
Count(&retainedUserCount).Error
|
||
|
||
// 查询该天的退订用户数
|
||
err = e.Orm.Table("mg_order").
|
||
Where("unsubscribe_time >= ?", date+" 00:00:00").
|
||
Where("unsubscribe_time <= ?", date+" 23:59:59").
|
||
Where("created_at >= ?", currentMonthFirstDay).
|
||
Where("created_at < ?", currentMonthNextFirstDay).
|
||
Where("state = 2"). // 状态为2表示退订
|
||
Count(&unsubOnDay).Error
|
||
|
||
if err != nil {
|
||
e.Logger.Error(err)
|
||
return
|
||
}
|
||
|
||
// 留存率计算:如果新增用户数为0,留存率为0
|
||
var retentionRate string
|
||
if newUserCount > 0 {
|
||
retentionRate = fmt.Sprintf("%.2f%%", float64(retainedUserCount)*100/float64(newUserCount))
|
||
} else {
|
||
retentionRate = "0.00%"
|
||
}
|
||
|
||
// 将查询结果发送到 resultChan
|
||
resultChan <- models.MgUserDayRetention{
|
||
Date: date,
|
||
RetainedUserCount: int(retainedUserCount),
|
||
RetentionRate: retentionRate,
|
||
UserUnsubOnDay: int(unsubOnDay),
|
||
}
|
||
}(startDate)
|
||
|
||
// 增加日期(开始日期自增一个月)
|
||
startDate = startDateTime.AddDate(0, 1, 0).Format("2006-01-02")
|
||
}
|
||
} else {
|
||
// 按天查询,直到留存数为 0 或查询到当前日期
|
||
for {
|
||
// 增加日期(开始日期自增一天)
|
||
startDateTime, _ := time.Parse("2006-01-02", startDate)
|
||
if startDate > currentDate {
|
||
break
|
||
}
|
||
|
||
wg.Add(1)
|
||
sem <- struct{}{} // 占一个信号,表示有一个 goroutine 正在执行
|
||
|
||
go func(date string) {
|
||
defer wg.Done()
|
||
defer func() { <-sem }() // 释放一个信号,表示该 goroutine 执行完毕
|
||
|
||
var retainedUserCount int64
|
||
var unsubOnDay int64
|
||
|
||
// 查询该天的留存用户数
|
||
err = e.Orm.Model(&models.MgOrder{}).
|
||
Where("state = 1 AND created_at between ? and ?", currentMonthFirstDay, currentMonthNextFirstDay).
|
||
Or("state = 2 AND unsubscribe_time > ? AND created_at between ? and ?", date+" 23:59:59", currentMonthFirstDay, currentMonthNextFirstDay).
|
||
Count(&retainedUserCount).Error
|
||
|
||
// 查询该天的退订用户数
|
||
err = e.Orm.Table("mg_order").
|
||
Where("unsubscribe_time >= ?", date+" 00:00:00").
|
||
Where("unsubscribe_time <= ?", date+" 23:59:59").
|
||
Where("created_at >= ?", currentMonthFirstDay).
|
||
Where("created_at < ?", currentMonthNextFirstDay).
|
||
Where("state = 2"). // 状态为2表示退订
|
||
Count(&unsubOnDay).Error
|
||
|
||
if err != nil {
|
||
e.Logger.Error(err)
|
||
return
|
||
}
|
||
|
||
// 留存率计算:如果新增用户数为0,留存率为0
|
||
var retentionRate string
|
||
if newUserCount > 0 {
|
||
retentionRate = fmt.Sprintf("%.2f%%", float64(retainedUserCount)*100/float64(newUserCount))
|
||
} else {
|
||
retentionRate = "0.00%"
|
||
}
|
||
|
||
// 将查询结果发送到 resultChan
|
||
resultChan <- models.MgUserDayRetention{
|
||
Date: date,
|
||
RetainedUserCount: int(retainedUserCount),
|
||
RetentionRate: retentionRate,
|
||
UserUnsubOnDay: int(unsubOnDay),
|
||
}
|
||
}(startDate)
|
||
|
||
// 增加日期(开始日期自增一天)
|
||
startDate = startDateTime.AddDate(0, 0, 1).Format("2006-01-02")
|
||
}
|
||
}
|
||
|
||
// 等待所有 goroutine 执行完毕
|
||
wg.Wait()
|
||
close(resultChan)
|
||
|
||
// 从 channel 中获取所有查询结果,并汇总
|
||
for result := range resultChan {
|
||
retentionData = append(retentionData, result)
|
||
}
|
||
|
||
// 排序(按日期升序)
|
||
sort.SliceStable(retentionData, func(i, j int) bool {
|
||
return retentionData[i].Date > retentionData[j].Date
|
||
})
|
||
|
||
// 分页处理
|
||
totalRecords := len(retentionData) // 总记录数
|
||
startIdx := pageNum * req.PageSize
|
||
endIdx := startIdx + req.PageSize
|
||
|
||
if startIdx > totalRecords {
|
||
resp.List = []models.MgUserDayRetention{}
|
||
resp.Count = totalRecords
|
||
e.OK(resp, "success")
|
||
return
|
||
}
|
||
|
||
if endIdx > totalRecords {
|
||
endIdx = totalRecords
|
||
}
|
||
|
||
// 返回分页后的数据
|
||
resp.List = retentionData[startIdx:endIdx]
|
||
resp.Count = totalRecords
|
||
|
||
e.OK(resp, "success")
|
||
}
|
||
|
||
// SysChannelList 查询系统渠道编码
|
||
// @Summary 查询系统渠道编码
|
||
// @Tags 2024-咪咕-管理后台
|
||
// @Produce json
|
||
// @Accept json
|
||
// @Param request body models.SysChannelListReq true "查询系统渠道编码"
|
||
// @Success 200 {object} models.SysChannelListResp
|
||
// @Router /api/v1/admin/sys_channel/list [post]
|
||
func (e MiGuDeployService) SysChannelList(c *gin.Context) {
|
||
err := e.MakeContext(c).MakeOrm().Errors
|
||
if err != nil {
|
||
e.Logger.Error(err)
|
||
response.Error(c, http.StatusInternalServerError, err, "数据库连接失败")
|
||
return
|
||
}
|
||
|
||
req := &models.SysChannelListReq{}
|
||
if c.ShouldBindJSON(req) != nil {
|
||
e.Logger.Error("参数解析失败")
|
||
response.Error(c, http.StatusBadRequest, errors.New("参数错误"), "参数错误")
|
||
return
|
||
}
|
||
|
||
resp := models.SysChannelListResp{
|
||
PageNum: req.PageNum,
|
||
PageSize: req.PageSize,
|
||
}
|
||
|
||
pageNum := req.PageNum - 1
|
||
if pageNum < 0 {
|
||
pageNum = 0
|
||
}
|
||
if req.PageSize == 0 {
|
||
req.PageSize = 10
|
||
}
|
||
|
||
var channels []models.ChannelData
|
||
|
||
// 1. 查询mg_product表的ChannelCode
|
||
var productChannelCodes []string
|
||
var mgOrders []models.MgProduct
|
||
err = e.Orm.Model(&models.MgProduct{}).
|
||
Select("channel_api_id").
|
||
Find(&mgOrders).Error
|
||
|
||
if err != nil {
|
||
e.Logger.Error("查询产品渠道编码失败:", err)
|
||
response.Error(c, http.StatusInternalServerError, err, "查询失败")
|
||
return
|
||
}
|
||
|
||
// 2. 拆分逗号分隔的ChannelCode,并去除空白
|
||
for _, order := range mgOrders {
|
||
channelCodes := strings.Split(order.ChannelApiID, ",")
|
||
for _, code := range channelCodes {
|
||
code = strings.TrimSpace(code) // 去掉前后空白
|
||
if code != "" {
|
||
productChannelCodes = append(productChannelCodes, code)
|
||
}
|
||
}
|
||
}
|
||
|
||
// 3. 查询mg_channel表的MainChannelCode和SubChannelCode
|
||
var mgChannels []models.MgChannel
|
||
err = e.Orm.Model(&models.MgChannel{}).
|
||
Select("main_channel_code, sub_channel_code").
|
||
Find(&mgChannels).Error
|
||
|
||
if err != nil {
|
||
e.Logger.Error("查询渠道列表失败:", err)
|
||
response.Error(c, http.StatusInternalServerError, err, "查询失败")
|
||
return
|
||
}
|
||
|
||
// 4. 构建所有组合,并剔除重复的渠道编码
|
||
channelSet := make(map[string]struct{})
|
||
|
||
// 添加产品表中的渠道编码
|
||
for _, code := range productChannelCodes {
|
||
channelSet[code] = struct{}{}
|
||
}
|
||
|
||
// 添加渠道表中的主渠道和子渠道编码
|
||
for _, channel := range mgChannels {
|
||
channelSet[channel.MainChannelCode] = struct{}{}
|
||
channelSet[channel.SubChannelCode] = struct{}{}
|
||
}
|
||
|
||
// 5. 转换为ChannelData结构体并分页
|
||
for code := range channelSet {
|
||
if code != "" { // 跳过空的channel_code
|
||
channels = append(channels, models.ChannelData{
|
||
ChannelCode: code,
|
||
})
|
||
}
|
||
}
|
||
|
||
// 总数
|
||
resp.Count = len(channels)
|
||
|
||
// 分页
|
||
startIndex := pageNum * req.PageSize
|
||
endIndex := startIndex + req.PageSize
|
||
if startIndex > len(channels) {
|
||
startIndex = len(channels)
|
||
}
|
||
if endIndex > len(channels) {
|
||
endIndex = len(channels)
|
||
}
|
||
|
||
resp.List = channels[startIndex:endIndex]
|
||
|
||
e.OK(resp, "")
|
||
}
|
||
|
||
// HomepageDataSummary 查询首页汇总数据
|
||
// @Summary 查询首页汇总数据
|
||
// @Tags 2024-咪咕-管理后台
|
||
// @Produce json
|
||
// @Accept json
|
||
// @Param request body models.HomepageDataSummaryReq true "查询首页汇总数据"
|
||
// @Success 200 {object} models.HomepageDataSummaryResp
|
||
// @Router /api/v1/admin/home/data [post]
|
||
func (e MiGuDeployService) HomepageDataSummary(c *gin.Context) {
|
||
fmt.Println("HomepageDataSummary")
|
||
err := e.MakeContext(c).MakeOrm().Errors
|
||
if err != nil {
|
||
fmt.Println("MakeContext err:", err)
|
||
e.Logger.Error(err)
|
||
return
|
||
}
|
||
|
||
// 请求参数绑定
|
||
req := &models.HomepageDataSummaryReq{}
|
||
if c.ShouldBindJSON(req) != nil {
|
||
logger.Errorf("para err")
|
||
response.Error(c, http.StatusBadRequest, errors.New("para err"), "参数错误")
|
||
return
|
||
}
|
||
|
||
resp := models.HomepageDataSummaryResp{}
|
||
var summaryData models.SummaryData
|
||
|
||
// 设置开始时间和结束时间
|
||
startTime := req.StartTime
|
||
endTime := req.EndTime
|
||
if startTime == "" {
|
||
startTime = "1970-01-01 00:00:00"
|
||
}
|
||
if endTime == "" {
|
||
endTime = time.Now().Format("2006-01-02 15:04:05")
|
||
}
|
||
|
||
// 查询汇总数据
|
||
qs := e.Orm.Model(&models.MgOrder{}).
|
||
Select(`COUNT(*) AS total_user_count,
|
||
COUNT(CASE WHEN state = 2 THEN 1 END) AS unsubscribed_user_count,
|
||
COUNT(CASE WHEN is_one_hour_cancel = 1 THEN 1 END) AS one_hour_unsubscribed_user_count`).
|
||
Where("subscribe_time >= ? AND subscribe_time <= ?", startTime, endTime)
|
||
|
||
// 添加产品和渠道的过滤条件
|
||
if req.ProductID != 0 {
|
||
qs = qs.Where("product_id = ?", req.ProductID)
|
||
}
|
||
if req.Channel != "" {
|
||
qs = qs.Where("channel_code = ?", req.Channel)
|
||
}
|
||
|
||
err = qs.Find(&summaryData).Error
|
||
if err != nil {
|
||
logger.Errorf("HomepageDataSummary query err: %#v", err)
|
||
response.Error(c, http.StatusInternalServerError, err, "查询失败")
|
||
return
|
||
}
|
||
|
||
// 计算留存用户数
|
||
summaryData.RetainedUserCount = summaryData.TotalUserCount - summaryData.UnsubscribedUserCount
|
||
|
||
// 计算留存率
|
||
if summaryData.TotalUserCount > 0 {
|
||
summaryData.RetentionRate = fmt.Sprintf("%.2f%%", float64(summaryData.RetainedUserCount)*100.0/float64(summaryData.TotalUserCount))
|
||
} else {
|
||
summaryData.RetentionRate = "0.00%"
|
||
}
|
||
|
||
// 计算退订率
|
||
if summaryData.TotalUserCount > 0 {
|
||
summaryData.UnsubscribeRate = fmt.Sprintf("%.2f%%", float64(summaryData.UnsubscribedUserCount)*100.0/float64(summaryData.TotalUserCount))
|
||
summaryData.OneHourUnsubscribeRate = fmt.Sprintf("%.2f%%", float64(summaryData.OneHourUnsubscribedUserCount)*100.0/float64(summaryData.TotalUserCount))
|
||
} else {
|
||
summaryData.UnsubscribeRate = "0.00%"
|
||
summaryData.OneHourUnsubscribeRate = "0.00%"
|
||
}
|
||
|
||
resp.Summary = summaryData
|
||
|
||
// 查询每日数据
|
||
var dailyDataList []models.DailyData
|
||
dailyQuery := e.Orm.Model(&models.MgOrder{}).
|
||
Select(`DATE_FORMAT(subscribe_time, '%Y-%m-%d') AS date,
|
||
COUNT(*) AS new_user_count,
|
||
COUNT(CASE WHEN state = 2 THEN 1 END) AS unsubscribed_user_count,
|
||
COUNT(CASE WHEN is_one_hour_cancel = 1 THEN 1 END) AS unsubscribed_within_one_hour`).
|
||
Where("subscribe_time >= ? AND subscribe_time <= ?", startTime, endTime)
|
||
|
||
// 处理产品编号 (SkuCode) 过滤条件
|
||
if req.ProductID != 0 {
|
||
dailyQuery = dailyQuery.Where("product_id = ?", req.ProductID)
|
||
}
|
||
|
||
// 处理渠道名称 (Channel) 过滤条件
|
||
if req.Channel != "" {
|
||
dailyQuery = dailyQuery.Where("channel_code = ?", req.Channel)
|
||
}
|
||
|
||
err = dailyQuery.Group("DATE(subscribe_time)").
|
||
Order("DATE(subscribe_time)").
|
||
Find(&dailyDataList).Error
|
||
if err != nil {
|
||
logger.Errorf("DailyData query err: %#v", err)
|
||
response.Error(c, http.StatusInternalServerError, err, "查询失败")
|
||
return
|
||
}
|
||
|
||
// 计算每日退订率
|
||
for i := range dailyDataList {
|
||
if dailyDataList[i].NewUserCount > 0 {
|
||
dailyDataList[i].UnsubscribeRate = fmt.Sprintf("%.2f%%", float64(dailyDataList[i].UnsubscribedUserCount)*100.0/float64(dailyDataList[i].NewUserCount))
|
||
dailyDataList[i].UnsubscribeWithinOneHourRate = fmt.Sprintf("%.2f%%", float64(dailyDataList[i].UnsubscribedWithinOneHour)*100.0/float64(dailyDataList[i].NewUserCount))
|
||
} else {
|
||
dailyDataList[i].UnsubscribeRate = "0.00%"
|
||
dailyDataList[i].UnsubscribeWithinOneHourRate = "0.00%"
|
||
}
|
||
}
|
||
|
||
// 获取日期范围
|
||
minDate, maxDate, nCount, err := e.getDateRangeOnHome(req)
|
||
if err != nil {
|
||
response.Error(c, http.StatusInternalServerError, err, "获取日期范围失败")
|
||
return
|
||
}
|
||
if nCount == 0 {
|
||
e.OK(resp, "")
|
||
return
|
||
}
|
||
|
||
if req.StartTime != "" && req.EndTime != "" {
|
||
startDate, err := time.Parse(models.MiGuTimeFormat, req.StartTime)
|
||
if err != nil {
|
||
response.Error(c, http.StatusInternalServerError, err, "获取日期范围失败")
|
||
return
|
||
}
|
||
endDate, err := time.Parse(models.MiGuTimeFormat, req.EndTime)
|
||
if err != nil {
|
||
response.Error(c, http.StatusInternalServerError, err, "获取日期范围失败")
|
||
return
|
||
}
|
||
|
||
if startDate.Before(minDate) {
|
||
startDate = minDate
|
||
}
|
||
|
||
if endDate.After(maxDate) {
|
||
endDate = maxDate
|
||
}
|
||
|
||
startTime = startDate.Format("2006-01-02") + " 00:00:00"
|
||
endTime = endDate.Format("2006-01-02") + " 23:59:59"
|
||
} else {
|
||
startTime = minDate.Format("2006-01-02") + " 00:00:00"
|
||
endTime = time.Now().Format(models.MiGuTimeFormat)
|
||
}
|
||
|
||
// 补充缺失的日期
|
||
dateList, err := e.getDateListOnHome(startTime, endTime)
|
||
if err != nil {
|
||
response.Error(c, http.StatusBadRequest, err, "日期格式无效")
|
||
return
|
||
}
|
||
|
||
finalList := e.fillMissingDatesOnHome(dateList, dailyDataList)
|
||
dailyDataList = finalList
|
||
|
||
// 查询每日退订合计
|
||
// 增加TotalCancelCount字段的查询
|
||
var dailyCancelDataList []struct {
|
||
Date string `json:"date"` // 日期
|
||
TotalCancelCount int64 `json:"total_cancel_count"` // 每日退订用户数合计
|
||
}
|
||
|
||
cancelQuery := e.Orm.Model(&models.MgOrder{}).
|
||
Select(`DATE_FORMAT(unsubscribe_time, '%Y-%m-%d') AS date, COUNT(*) AS total_cancel_count`).
|
||
Where("state = 2 AND unsubscribe_time IS NOT NULL AND unsubscribe_time >= ? AND unsubscribe_time <= ?", startTime, endTime)
|
||
|
||
// 处理产品编号 (SkuCode) 过滤条件
|
||
if req.ProductID != 0 {
|
||
cancelQuery = cancelQuery.Where("product_id = ?", req.ProductID)
|
||
}
|
||
|
||
// 处理渠道名称 (Channel) 过滤条件
|
||
if req.Channel != "" {
|
||
cancelQuery = cancelQuery.Where("channel_code = ?", req.Channel)
|
||
}
|
||
err = cancelQuery.Group("DATE(unsubscribe_time)").
|
||
Order("DATE(unsubscribe_time)").
|
||
Find(&dailyCancelDataList).Error
|
||
if err != nil {
|
||
logger.Errorf("DailyCancelData query err: %#v", err)
|
||
response.Error(c, http.StatusInternalServerError, err, "查询失败")
|
||
return
|
||
}
|
||
|
||
for i := range dailyDataList {
|
||
for _, cancelData := range dailyCancelDataList {
|
||
if dailyDataList[i].Date == cancelData.Date {
|
||
dailyDataList[i].TotalCancelCount = cancelData.TotalCancelCount
|
||
break
|
||
}
|
||
}
|
||
}
|
||
|
||
resp.DailyDataList = dailyDataList
|
||
|
||
// 返回结果
|
||
e.OK(resp, "")
|
||
}
|
||
|
||
// CalculateRevenueAnalysis 营收分析
|
||
// @Summary 营收分析
|
||
// @Tags 2024-咪咕-管理后台
|
||
// @Produce json
|
||
// @Accept json
|
||
// @Param request body models.RetentionMonthsReq true "营收分析"
|
||
// @Success 200 {object} models.RetentionMonthsResp
|
||
// @Router /api/v1/admin/home/revenue_analysis [post]
|
||
func (e MiGuDeployService) CalculateRevenueAnalysis(c *gin.Context) {
|
||
fmt.Println("CalculateRevenueAnalysis")
|
||
err := e.MakeContext(c).MakeOrm().Errors
|
||
if err != nil {
|
||
fmt.Println("MakeContext err:", err)
|
||
e.Logger.Error(err)
|
||
return
|
||
}
|
||
|
||
// 绑定请求参数
|
||
req := &models.RetentionMonthsReq{}
|
||
if err := c.ShouldBindJSON(req); err != nil {
|
||
response.Error(c, http.StatusBadRequest, err, "参数错误")
|
||
return
|
||
}
|
||
|
||
// 构建查询
|
||
query := e.Orm.Model(&models.MgOrder{})
|
||
|
||
// 查询条件
|
||
if req.StartTime != "" && req.EndTime != "" {
|
||
query = query.Where("subscribe_time >= ? AND subscribe_time <= ?", req.StartTime, req.EndTime)
|
||
}
|
||
if req.ProductID != 0 {
|
||
query = query.Where("product_id = ?", req.ProductID)
|
||
}
|
||
if req.Channel != "" {
|
||
query = query.Where("channel_code = ?", req.Channel)
|
||
}
|
||
|
||
// 获取每个月的新用户数和有效用户数
|
||
var result []models.MonthlyRetention
|
||
err = query.
|
||
Select(`DATE_FORMAT(subscribe_time, '%Y-%m') AS month,
|
||
COUNT(DISTINCT phone_number) AS new_user_count,
|
||
COUNT(DISTINCT CASE
|
||
WHEN unsubscribe_time IS NULL OR TIMESTAMPDIFF(HOUR, subscribe_time, unsubscribe_time) > 24
|
||
THEN phone_number END) AS valid_users_count`).
|
||
Group("month").
|
||
Order("month").
|
||
Find(&result).Error
|
||
if err != nil {
|
||
response.Error(c, http.StatusInternalServerError, err, "查询失败")
|
||
return
|
||
}
|
||
|
||
// 查询每个月的上个月未退订用户数
|
||
for i := 0; i < len(result); i++ {
|
||
// 获取当前记录的月份
|
||
currentMonth := result[i].Month
|
||
|
||
// 计算上个月的月份字符串
|
||
currentMonthDate, _ := time.Parse("2006-01", currentMonth)
|
||
lastMonth := currentMonthDate.AddDate(0, -1, 0).Format("2006-01")
|
||
|
||
// 查询上个月未退订的用户数
|
||
var lastMonthValidUsersCount int64
|
||
err = e.Orm.Model(&models.MgOrder{}).
|
||
Where("unsubscribe_time IS NULL").
|
||
Where("DATE_FORMAT(subscribe_time, '%Y-%m') = ?", lastMonth).
|
||
Count(&lastMonthValidUsersCount).Error
|
||
if err != nil {
|
||
response.Error(c, http.StatusInternalServerError, err, "上个月数据查询失败")
|
||
return
|
||
}
|
||
|
||
// 将上个月的未退订用户数累加到当前月的有效用户数
|
||
result[i].ValidUsersCount += int(lastMonthValidUsersCount)
|
||
}
|
||
|
||
// 计算总的新用户数和有效用户数
|
||
totalNewUserCount := 0
|
||
totalValidUsers := 0
|
||
for _, data := range result {
|
||
totalNewUserCount += data.NewUserCount
|
||
totalValidUsers += data.ValidUsersCount
|
||
}
|
||
|
||
// 返回结果
|
||
resp := models.RetentionMonthsResp{
|
||
TotalNewUsers: totalNewUserCount, // 添加总新用户数
|
||
TotalValidUsers: totalValidUsers, // 添加有效用户数
|
||
MonthlyRetentionData: result,
|
||
}
|
||
response.OK(c, resp, "查询成功")
|
||
}
|
||
|
||
// AddChannel 新增渠道
|
||
// @Summary 新增渠道
|
||
// @Tags 2024-咪咕-管理后台
|
||
// @Produce json
|
||
// @Accept json
|
||
// @Param request body models.AddChannelReq true "新增渠道"
|
||
// @Success 200 {object} models.AddChannelResp
|
||
// @Router /api/v1/admin/channel/add [post]
|
||
func (e MiGuDeployService) AddChannel(c *gin.Context) {
|
||
fmt.Println("AddChannel called")
|
||
err := e.MakeContext(c).MakeOrm().Errors
|
||
if err != nil {
|
||
e.Logger.Error(err)
|
||
response.Error(c, http.StatusInternalServerError, err, "创建上下文失败")
|
||
return
|
||
}
|
||
|
||
req := &models.AddChannelReq{}
|
||
if err := c.ShouldBindJSON(req); err != nil {
|
||
e.Logger.Errorf("para err: %v", err)
|
||
response.Error(c, http.StatusBadRequest, errors.New("参数错误"), "参数错误")
|
||
return
|
||
}
|
||
|
||
// Validate required fields
|
||
if req.ProductID == 0 || req.MainChannelCode == "" || req.SubscribeURL == "" {
|
||
response.Error(c, http.StatusBadRequest, errors.New("必填字段缺失"), "必填字段缺失")
|
||
return
|
||
}
|
||
|
||
channel := models.MgChannel{
|
||
ProductID: req.ProductID,
|
||
MainChannelCode: req.MainChannelCode,
|
||
SubChannelCode: req.SubChannelCode,
|
||
SubscribeURL: req.SubscribeURL,
|
||
UnsubscribeURL: req.UnsubscribeURL,
|
||
Status: req.Status,
|
||
Remarks: req.Remarks,
|
||
}
|
||
|
||
if err := e.Orm.Create(&channel).Error; err != nil {
|
||
e.Logger.Errorf("create channel err: %v", err)
|
||
response.Error(c, http.StatusInternalServerError, err, "创建失败")
|
||
return
|
||
}
|
||
|
||
resp := models.AddChannelResp{ChannelID: channel.ID}
|
||
e.OK(resp, "渠道创建成功")
|
||
}
|
||
|
||
//// UpdateChannel 更新渠道
|
||
//// @Summary 更新渠道
|
||
//// @Tags 2024-咪咕-管理后台
|
||
//// @Produce json
|
||
//// @Accept json
|
||
//// @Param request body models.UpdateChannelReq true "更新渠道请求"
|
||
//// @Success 200 {object} models.UpdateChannelResp
|
||
//// @Router /api/v1/admin/channel/update [post]
|
||
//func (e MiGuDeployService) UpdateChannel(c *gin.Context) {
|
||
// fmt.Println("UpdateChannel called")
|
||
// err := e.MakeContext(c).MakeOrm().Errors
|
||
// if err != nil {
|
||
// e.Logger.Error(err)
|
||
// response.Error(c, http.StatusInternalServerError, err, "创建上下文失败")
|
||
// return
|
||
// }
|
||
//
|
||
// req := &models.UpdateChannelReq{}
|
||
// if err := c.ShouldBindJSON(req); err != nil {
|
||
// e.Logger.Errorf("para err: %v", err)
|
||
// response.Error(c, http.StatusBadRequest, errors.New("参数错误"), "参数错误")
|
||
// return
|
||
// }
|
||
//
|
||
// // Validate required fields
|
||
// if req.ID == 0 {
|
||
// response.Error(c, http.StatusBadRequest, errors.New("必填字段缺失"), "必填字段缺失")
|
||
// return
|
||
// }
|
||
//
|
||
// channel := models.MgChannel{}
|
||
// if err := e.Orm.First(&channel, req.ID).Error; err != nil {
|
||
// e.Logger.Errorf("channel not found err: %v", err)
|
||
// response.Error(c, http.StatusNotFound, err, "渠道未找到")
|
||
// return
|
||
// }
|
||
//
|
||
// // Update fields
|
||
// if req.MainChannelCode != "" {
|
||
// channel.MainChannelCode = req.MainChannelCode
|
||
// }
|
||
// if req.SubChannelCode != "" {
|
||
// channel.SubChannelCode = req.SubChannelCode
|
||
// }
|
||
// if req.SubscribeURL != "" {
|
||
// channel.SubscribeURL = req.SubscribeURL
|
||
// }
|
||
// if req.UnsubscribeURL != "" {
|
||
// channel.UnsubscribeURL = req.UnsubscribeURL
|
||
// }
|
||
// if req.Status != 0 {
|
||
// channel.Status = req.Status
|
||
// }
|
||
// channel.Remarks = req.Remarks
|
||
//
|
||
// if err := e.Orm.Save(&channel).Error; err != nil {
|
||
// e.Logger.Errorf("update channel err: %v", err)
|
||
// response.Error(c, http.StatusInternalServerError, err, "更新失败")
|
||
// return
|
||
// }
|
||
//
|
||
// e.OK(nil, "渠道更新成功")
|
||
//}
|
||
//
|
||
//// DeleteChannel 删除渠道
|
||
//// @Summary 删除渠道
|
||
//// @Tags 2024-咪咕-管理后台
|
||
//// @Produce json
|
||
//// @Accept json
|
||
//// @Param request body models.DeleteChannelReq true "删除渠道请求"
|
||
//// @Success 200 {object} models.DeleteChannelResp
|
||
//// @Router /api/v1/admin/channel/delete [post]
|
||
//func (e MiGuDeployService) DeleteChannel(c *gin.Context) {
|
||
// fmt.Println("DeleteChannel called")
|
||
// err := e.MakeContext(c).MakeOrm().Errors
|
||
// if err != nil {
|
||
// e.Logger.Error(err)
|
||
// response.Error(c, http.StatusInternalServerError, err, "创建上下文失败")
|
||
// return
|
||
// }
|
||
//
|
||
// req := &models.DeleteChannelReq{}
|
||
// if err := c.ShouldBindJSON(req); err != nil {
|
||
// e.Logger.Errorf("para err: %v", err)
|
||
// response.Error(c, http.StatusBadRequest, errors.New("参数错误"), "参数错误")
|
||
// return
|
||
// }
|
||
//
|
||
// // Validate required fields
|
||
// if req.ID == 0 {
|
||
// response.Error(c, http.StatusBadRequest, errors.New("必填字段缺失"), "必填字段缺失")
|
||
// return
|
||
// }
|
||
//
|
||
// if err := e.Orm.Delete(&models.MgChannel{}, req.ID).Error; err != nil {
|
||
// e.Logger.Errorf("delete channel err: %v", err)
|
||
// response.Error(c, http.StatusInternalServerError, err, "删除失败")
|
||
// return
|
||
// }
|
||
//
|
||
// e.OK(nil, "渠道删除成功")
|
||
//}
|
||
|
||
// AddProduct 添加新产品
|
||
// @Summary 添加新产品
|
||
// @Tags 2024-咪咕-管理后台
|
||
// @Produce json
|
||
// @Accept json
|
||
// @Param request body models.AddProductReq true "新增产品"
|
||
// @Success 200 {object} models.AddProductResp
|
||
// @Router /api/v1/admin/product/add [post]
|
||
func (e MiGuDeployService) AddProduct(c *gin.Context) {
|
||
fmt.Println("AddProduct")
|
||
err := e.MakeContext(c).MakeOrm().Errors
|
||
if err != nil {
|
||
e.Logger.Error(err)
|
||
response.Error(c, http.StatusInternalServerError, err, "创建上下文失败")
|
||
return
|
||
}
|
||
|
||
req := &models.AddProductReq{}
|
||
if err := c.ShouldBindJSON(req); err != nil {
|
||
logger.Errorf("para err")
|
||
response.Error(c, http.StatusBadRequest, errors.New("参数错误"), "参数错误")
|
||
return
|
||
}
|
||
|
||
product := models.MgProduct{
|
||
Name: req.Name,
|
||
UniqueCode: req.UniqueCode,
|
||
SkuName: req.SkuName,
|
||
BillingPointID: req.BillingPointID,
|
||
ChannelCode: req.ChannelCode,
|
||
ProductApiID: req.ProductApiID,
|
||
ChannelApiID: req.ChannelApiID,
|
||
OfficialPage: req.OfficialPage,
|
||
}
|
||
|
||
if err := e.Orm.Create(&product).Error; err != nil {
|
||
logger.Errorf("AddProduct err:%#v", err)
|
||
response.Error(c, http.StatusInternalServerError, err, "创建失败")
|
||
return
|
||
}
|
||
|
||
e.OK(models.AddProductResp{ID: product.ID}, "创建成功")
|
||
}
|
||
|
||
//// UpdateProduct 修改产品
|
||
//// @Summary 修改产品
|
||
//// @Tags 2024-咪咕-管理后台
|
||
//// @Produce json
|
||
//// @Accept json
|
||
//// @Param request body models.UpdateProductReq true "修改产品"
|
||
//// @Success 200 {object} models.UpdateProductResp
|
||
//// @Router /api/v1/admin/product/update [post]
|
||
//func (e MiGuDeployService) UpdateProduct(c *gin.Context) {
|
||
// fmt.Println("UpdateProduct")
|
||
// err := e.MakeContext(c).MakeOrm().Errors
|
||
// if err != nil {
|
||
// e.Logger.Error(err)
|
||
// response.Error(c, http.StatusInternalServerError, err, "创建上下文失败")
|
||
// return
|
||
// }
|
||
//
|
||
// req := &models.UpdateProductReq{}
|
||
// if err := c.ShouldBindJSON(req); err != nil {
|
||
// logger.Errorf("para err")
|
||
// response.Error(c, http.StatusBadRequest, errors.New("参数错误"), "参数错误")
|
||
// return
|
||
// }
|
||
//
|
||
// product := models.MgProduct{}
|
||
// if err := e.Orm.First(&product, req.ID).Error; err != nil {
|
||
// logger.Errorf("Product not found: %#v", err)
|
||
// response.Error(c, http.StatusNotFound, err, "产品未找到")
|
||
// return
|
||
// }
|
||
//
|
||
// // 更新产品信息
|
||
// product.Name = req.Name
|
||
// product.UniqueCode = req.UniqueCode
|
||
// product.SkuName = req.SkuName
|
||
// product.BillingPointID = req.BillingPointID
|
||
// product.ChannelCode = req.ChannelCode
|
||
// product.ProductApiID = req.ProductApiID
|
||
// product.ChannelApiID = req.ChannelApiID
|
||
// product.OfficialPage = req.OfficialPage
|
||
//
|
||
// if err := e.Orm.Save(&product).Error; err != nil {
|
||
// logger.Errorf("UpdateProduct err:%#v", err)
|
||
// response.Error(c, http.StatusInternalServerError, err, "更新失败")
|
||
// return
|
||
// }
|
||
//
|
||
// e.OK(models.UpdateProductResp{ID: product.ID}, "更新成功")
|
||
//}
|
||
//
|
||
//// DeleteProduct 删除产品
|
||
//// @Summary 删除产品
|
||
//// @Tags 2024-咪咕-管理后台
|
||
//// @Produce json
|
||
//// @Accept json
|
||
//// @Param request body models.DeleteProductReq true "删除产品"
|
||
//// @Success 200 {object} nil
|
||
//// @Router /api/v1/admin/product/delete [post]
|
||
//func (e MiGuDeployService) DeleteProduct(c *gin.Context) {
|
||
// fmt.Println("DeleteProduct")
|
||
// err := e.MakeContext(c).MakeOrm().Errors
|
||
// if err != nil {
|
||
// e.Logger.Error(err)
|
||
// response.Error(c, http.StatusInternalServerError, err, "创建上下文失败")
|
||
// return
|
||
// }
|
||
//
|
||
// req := &models.DeleteProductReq{}
|
||
// if err := c.ShouldBindJSON(req); err != nil {
|
||
// logger.Errorf("para err")
|
||
// response.Error(c, http.StatusBadRequest, errors.New("参数错误"), "参数错误")
|
||
// return
|
||
// }
|
||
//
|
||
// if err := e.Orm.Delete(&models.MgProduct{}, req.ID).Error; err != nil {
|
||
// logger.Errorf("DeleteProduct err:%#v", err)
|
||
// response.Error(c, http.StatusInternalServerError, err, "删除失败")
|
||
// return
|
||
// }
|
||
//
|
||
// e.OK("", "删除成功")
|
||
//}
|
||
|
||
//// ImportExcelToMgOrderHandler 处理Excel文件导入请求
|
||
//// @Summary 导入订单Excel文件
|
||
//// @Tags 2024-咪咕-管理后台
|
||
//// @Accept multipart/form-data
|
||
//// @Produce json
|
||
//// @Param file formData file true "Excel文件"
|
||
//// @Success 200 {object} map[string]string{"message": "导入成功"}
|
||
//// @Router /api/v1/admin/order/import [post]
|
||
//func (e MiGuDeployService) ImportExcelToMgOrderHandler(c *gin.Context) {
|
||
// err := e.MakeContext(c).MakeOrm().Errors
|
||
// if err != nil {
|
||
// e.Logger.Error(err)
|
||
// response.Error(c, http.StatusInternalServerError, err, "创建上下文失败")
|
||
// return
|
||
// }
|
||
//
|
||
// // 从请求中获取文件
|
||
// file, err := c.FormFile("file")
|
||
// if err != nil {
|
||
// c.JSON(http.StatusBadRequest, gin.H{"error": "无法读取文件"})
|
||
// return
|
||
// }
|
||
//
|
||
// // 打开上传的文件
|
||
// fileStream, err := file.Open()
|
||
// if err != nil {
|
||
// c.JSON(http.StatusInternalServerError, gin.H{"error": "无法打开文件"})
|
||
// return
|
||
// }
|
||
// defer fileStream.Close()
|
||
//
|
||
// // 创建 CSV 阅读器
|
||
// reader := csv.NewReader(fileStream)
|
||
// reader.LazyQuotes = true
|
||
//
|
||
// // 跳过 CSV 文件的标题行
|
||
// if _, err := reader.Read(); err != nil {
|
||
// c.JSON(http.StatusInternalServerError, gin.H{"error": "无法读取CSV标题行"})
|
||
// return
|
||
// }
|
||
//
|
||
// //nRow := 0
|
||
// // 逐行读取 CSV 并插入数据库
|
||
// for {
|
||
// //nRow++
|
||
// row, err := reader.Read()
|
||
// if err == io.EOF {
|
||
// break
|
||
// }
|
||
// if err != nil {
|
||
// c.JSON(http.StatusInternalServerError, gin.H{"error": "读取CSV文件失败"})
|
||
// return
|
||
// }
|
||
//
|
||
// // 检查数据是否齐全
|
||
// if len(row) < 4 {
|
||
// continue // 跳过数据不全的行
|
||
// }
|
||
//
|
||
// // 解析订阅时间
|
||
// subscribeTime, err := time.Parse("2006-01-02 15:04:05", row[3])
|
||
// if err != nil {
|
||
// fmt.Printf("解析时间错误: %v\n", err)
|
||
// continue
|
||
// }
|
||
//
|
||
// const cutoffTimeStr = "2024-10-18 18:58:00"
|
||
// cutoffTime, _ := time.Parse("2006-01-02 15:04:05", cutoffTimeStr)
|
||
// // 判断是否超过截止时间
|
||
// if subscribeTime.After(cutoffTime) {
|
||
// fmt.Printf("跳过超过截止时间的记录: %v\n", subscribeTime)
|
||
// continue
|
||
// }
|
||
//
|
||
// // 将时间转换为 UTC+08:00
|
||
// // 将时间往前推8小时
|
||
// localTime := subscribeTime.Add(-8 * time.Hour)
|
||
//
|
||
// // 创建MgOrder对象
|
||
// order := models.MgOrderCopy{
|
||
// ProductID: 1,
|
||
// ChannelCode: "6015150807",
|
||
// OrderSerial: models.GetExcelOrderSerial(e.Orm, subscribeTime),
|
||
// SubscribeTime: &localTime,
|
||
// PhoneNumber: row[0],
|
||
// ChannelTradeNo: row[1],
|
||
// ExternalOrderID: row[2],
|
||
// State: 1,
|
||
// }
|
||
//
|
||
// order.CreatedAt = localTime
|
||
// order.UpdatedAt = localTime
|
||
// order.SM4PhoneNumber, _ = tools.SM4Encrypt(models.SM4KEy, order.PhoneNumber)
|
||
//
|
||
// // 插入到数据库
|
||
// if err := e.Orm.Create(&order).Error; err != nil {
|
||
// fmt.Printf("插入订单数据失败: %v\n", err)
|
||
// continue
|
||
// }
|
||
//
|
||
// fmt.Println("order is:", order)
|
||
// //if nRow > 4 {
|
||
// // break
|
||
// //}
|
||
// }
|
||
//
|
||
// // 返回成功消息
|
||
// c.JSON(http.StatusOK, gin.H{"message": "导入成功"})
|
||
//}
|
||
//
|
||
//// ImportExcelToMgOrderHandlerUpdate 处理Excel文件导入请求
|
||
//// @Summary 导入订单Excel退订文件
|
||
//// @Tags 2024-咪咕-管理后台
|
||
//// @Accept multipart/form-data
|
||
//// @Produce json
|
||
//// @Param file formData file true "Excel文件"
|
||
//// @Success 200 {object} map[string]string{"message": "导入成功"}
|
||
//// @Router /api/v1/admin/order/import_update [post]
|
||
//func (e MiGuDeployService) ImportExcelToMgOrderHandlerUpdate(c *gin.Context) {
|
||
// err := e.MakeContext(c).MakeOrm().Errors
|
||
// if err != nil {
|
||
// e.Logger.Error(err)
|
||
// response.Error(c, http.StatusInternalServerError, err, "创建上下文失败")
|
||
// return
|
||
// }
|
||
//
|
||
// // 从请求中获取文件
|
||
// file, err := c.FormFile("file")
|
||
// if err != nil {
|
||
// c.JSON(http.StatusBadRequest, gin.H{"error": "无法读取文件"})
|
||
// return
|
||
// }
|
||
//
|
||
// // 打开上传的文件
|
||
// fileStream, err := file.Open()
|
||
// if err != nil {
|
||
// c.JSON(http.StatusInternalServerError, gin.H{"error": "无法打开文件"})
|
||
// return
|
||
// }
|
||
// defer fileStream.Close()
|
||
//
|
||
// // 创建 CSV 阅读器
|
||
// reader := csv.NewReader(fileStream)
|
||
// reader.LazyQuotes = true
|
||
//
|
||
// // 跳过 CSV 文件的标题行
|
||
// if _, err := reader.Read(); err != nil {
|
||
// c.JSON(http.StatusInternalServerError, gin.H{"error": "无法读取CSV标题行"})
|
||
// return
|
||
// }
|
||
//
|
||
// //nRow := 0
|
||
// // 逐行读取 CSV 并插入数据库
|
||
// for {
|
||
// //nRow++
|
||
// row, err := reader.Read()
|
||
// if err == io.EOF {
|
||
// break
|
||
// }
|
||
// if err != nil {
|
||
// c.JSON(http.StatusInternalServerError, gin.H{"error": "读取CSV文件失败"})
|
||
// return
|
||
// }
|
||
//
|
||
// // 检查数据是否齐全
|
||
// if len(row) < 3 {
|
||
// continue // 跳过数据不全的行
|
||
// }
|
||
//
|
||
// // 将时间往前推8小时
|
||
// //localTime := subscribeTime.Add(-8 * time.Hour)
|
||
//
|
||
// if !(row[0] != "" && len(row[0]) == 11) {
|
||
// continue
|
||
// }
|
||
//
|
||
// if row[0] == "15812800163" {
|
||
// fmt.Println("found phone number: 15812800163")
|
||
// break
|
||
// }
|
||
//
|
||
// unsubscribeTime, _ := models.ConvertStringToTime(row[2])
|
||
//
|
||
// err = e.Orm.Table("mg_order_copy").Where("phone_number = ?", row[0]).Updates(map[string]interface{}{
|
||
// "state": models.UnsubscribeOK,
|
||
// "unsubscribe_time": unsubscribeTime,
|
||
// "updated_at": unsubscribeTime,
|
||
// }).Error
|
||
// if err != nil {
|
||
// fmt.Println("CheckOrderState update mg_order err:", err.Error())
|
||
// continue
|
||
// }
|
||
// //if nRow > 4 {
|
||
// // break
|
||
// //}
|
||
// }
|
||
//
|
||
// // 返回成功消息
|
||
// c.JSON(http.StatusOK, gin.H{"message": "导入成功"})
|
||
//}
|
||
|
||
// HourSummaryList 历史汇总(按小时)
|
||
// @Summary 历史汇总(按小时)
|
||
// @Tags 2024-咪咕-管理后台
|
||
// @Produce json
|
||
// @Accept json
|
||
// @Param request body models.HistoricalSummaryListReq true "历史汇总(按小时)"
|
||
// @Success 200 {object} models.HourSummaryListResp
|
||
// @Router /api/v1/admin/hour_summary/list [post]
|
||
func (e MiGuDeployService) HourSummaryList(c *gin.Context) {
|
||
fmt.Println("HourSummaryList")
|
||
|
||
err := e.MakeContext(c).MakeOrm().Errors
|
||
if err != nil {
|
||
fmt.Println("MakeContext err:", err)
|
||
e.Logger.Error(err)
|
||
return
|
||
}
|
||
|
||
// 请求参数绑定
|
||
req := &models.HistoricalSummaryListReq{}
|
||
if c.ShouldBindJSON(req) != nil {
|
||
logger.Errorf("para err")
|
||
response.Error(c, http.StatusBadRequest, errors.New("para err"), "参数错误")
|
||
return
|
||
}
|
||
|
||
resp := models.HourSummaryListResp{
|
||
PageNum: req.PageNum,
|
||
}
|
||
|
||
// 分页处理
|
||
pageNum := req.PageNum - 1
|
||
if pageNum < 0 {
|
||
pageNum = 0
|
||
}
|
||
if req.PageSize == 0 {
|
||
req.PageSize = 10
|
||
}
|
||
resp.PageSize = req.PageSize
|
||
|
||
// 设置开始时间和结束时间
|
||
startTime := req.StartTime
|
||
endTime := req.EndTime
|
||
if startTime == "" {
|
||
startTime = "1970-01-01 00:00:00"
|
||
}
|
||
if endTime == "" {
|
||
endTime = time.Now().Format("2006-01-02 15:04:05")
|
||
}
|
||
|
||
data, sumData, nCount, err := e.queryHistoricalDataByHour(startTime, endTime, req)
|
||
if err != nil {
|
||
response.Error(c, http.StatusInternalServerError, err, "查询失败")
|
||
return
|
||
}
|
||
|
||
resp.List = data
|
||
|
||
// 批量赋值操作
|
||
var totalData models.TotalHourSummary
|
||
totalData.SubmissionCount = sumData.SubmissionCount
|
||
totalData.NewUserCount = sumData.NewUserCount
|
||
totalData.SubmissionSuccessRate = sumData.SubmissionSuccessRate
|
||
totalData.NewUserUnsubWithinHour = sumData.NewUserUnsubWithinHour
|
||
totalData.NewUserUnsubWithinHourRate = sumData.NewUserUnsubWithinHourRate
|
||
totalData.NewUserUnsubOnDay = sumData.NewUserUnsubOnDay
|
||
totalData.NewUserUnsubOnDayRate = sumData.NewUserUnsubOnDayRate
|
||
totalData.TotalNewUserUnsub = sumData.TotalNewUserUnsub
|
||
totalData.TotalNewUserUnsubRate = sumData.TotalNewUserUnsubRate
|
||
|
||
if req.IsExport == 1 {
|
||
url, err := models.ExportHourSummaryToExcel(data, totalData, e.Orm)
|
||
if err != nil {
|
||
response.Error(c, http.StatusInternalServerError, err, "导出失败")
|
||
return
|
||
}
|
||
response.OK(c, map[string]string{"export_url": url}, "导出成功")
|
||
return
|
||
}
|
||
|
||
resp.SummaryData = &totalData
|
||
resp.Count = nCount
|
||
|
||
// 返回结果
|
||
e.OK(resp, "")
|
||
}
|
||
|
||
// 查询历史数据(按小时),并计算退订率和提交成功率
|
||
func (e MiGuDeployService) queryHistoricalDataByHour(startTime, endTime string, req *models.HistoricalSummaryListReq) ([]models.MgHourSummary, *models.MgHourSummary, int, error) {
|
||
// 构建SQL查询字符串
|
||
sql := `
|
||
SELECT * FROM (
|
||
SELECT
|
||
hour,
|
||
product_id,
|
||
channel_code,
|
||
SUM(new_user_count) AS new_user_count,
|
||
SUM(new_user_unsub_within_hour) AS new_user_unsub_within_hour,
|
||
SUM(new_user_unsub_on_day) AS new_user_unsub_on_day,
|
||
SUM(total_new_user_unsub) AS total_new_user_unsub,
|
||
SUM(submission_count) AS submission_count,
|
||
IF(SUM(submission_count) > 0,
|
||
CONCAT(ROUND(SUM(new_user_count) / SUM(submission_count) * 100, 2), '%'),
|
||
'0.00%'
|
||
) AS submission_success_rate,
|
||
IF(SUM(new_user_count) > 0,
|
||
CONCAT(ROUND(SUM(new_user_unsub_within_hour) / SUM(new_user_count) * 100, 2), '%'),
|
||
'0.00%'
|
||
) AS new_user_unsub_within_hour_rate,
|
||
IF(SUM(new_user_count) > 0,
|
||
CONCAT(ROUND(SUM(new_user_unsub_on_day) / SUM(new_user_count) * 100, 2), '%'),
|
||
'0.00%'
|
||
) AS new_user_unsub_on_day_rate,
|
||
IF(SUM(new_user_count) > 0,
|
||
CONCAT(ROUND(SUM(total_new_user_unsub) / SUM(new_user_count) * 100, 2), '%'),
|
||
'0.00%'
|
||
) AS total_new_user_unsub_rate
|
||
FROM (
|
||
-- 第一个查询:mg_order 数据
|
||
SELECT
|
||
HOUR(mg_order.created_at) AS hour,
|
||
product_id,
|
||
channel_code,
|
||
COUNT(*) AS new_user_count,
|
||
SUM(CASE WHEN mg_order.is_one_hour_cancel = 1 THEN 1 ELSE 0 END) AS new_user_unsub_within_hour,
|
||
SUM(CASE WHEN mg_order.state = 2 AND DATE(mg_order.unsubscribe_time) = DATE(mg_order.subscribe_time) THEN 1 ELSE 0 END) AS new_user_unsub_on_day,
|
||
SUM(CASE WHEN mg_order.state = 2 THEN 1 ELSE 0 END) AS total_new_user_unsub,
|
||
0 AS submission_count
|
||
FROM
|
||
mg_order
|
||
WHERE
|
||
mg_order.created_at BETWEEN ? AND ?
|
||
GROUP BY
|
||
HOUR(mg_order.created_at)
|
||
UNION ALL
|
||
-- 第二个查询:mg_order 总计数据
|
||
SELECT
|
||
'Total' AS hour,
|
||
product_id,
|
||
channel_code,
|
||
COUNT(*) AS new_user_count,
|
||
SUM(CASE WHEN mg_order.is_one_hour_cancel = 1 THEN 1 ELSE 0 END) AS new_user_unsub_within_hour,
|
||
SUM(CASE WHEN mg_order.state = 2 AND DATE(mg_order.unsubscribe_time) = DATE(mg_order.subscribe_time) THEN 1 ELSE 0 END) AS new_user_unsub_on_day,
|
||
SUM(CASE WHEN mg_order.state = 2 THEN 1 ELSE 0 END) AS total_new_user_unsub,
|
||
0 AS submission_count
|
||
FROM
|
||
mg_order
|
||
WHERE
|
||
mg_order.created_at BETWEEN ? AND ?
|
||
|
||
UNION ALL
|
||
-- 第三个查询:mg_transaction_log 数据
|
||
SELECT
|
||
HOUR(mg_transaction_log.created_at) AS hour,
|
||
product_id AS product_id,
|
||
channel_code AS channel_code,
|
||
0 AS new_user_count,
|
||
0 AS new_user_unsub_within_hour,
|
||
0 AS new_user_unsub_on_day,
|
||
0 AS total_new_user_unsub,
|
||
COUNT(*) AS submission_count
|
||
FROM
|
||
mg_transaction_log
|
||
WHERE
|
||
mg_transaction_log.created_at BETWEEN ? AND ?
|
||
AND verification_code != ''
|
||
GROUP BY
|
||
HOUR(mg_transaction_log.created_at)
|
||
|
||
UNION ALL
|
||
-- 第四个查询:mg_transaction_log 总计数据
|
||
SELECT
|
||
'Total' AS hour,
|
||
product_id AS product_id,
|
||
channel_code AS channel_code,
|
||
0 AS new_user_count,
|
||
0 AS new_user_unsub_within_hour,
|
||
0 AS new_user_unsub_on_day,
|
||
0 AS total_new_user_unsub,
|
||
COUNT(*) AS submission_count
|
||
FROM
|
||
mg_transaction_log
|
||
WHERE
|
||
mg_transaction_log.created_at BETWEEN ? AND ?
|
||
AND verification_code != ''
|
||
) AS combined_data
|
||
GROUP BY hour
|
||
ORDER BY hour
|
||
) AS paginated_data;
|
||
`
|
||
|
||
// 执行查询
|
||
var data []models.MgHourSummary
|
||
err := e.Orm.Raw(sql, startTime, endTime, startTime, endTime, startTime, endTime, startTime, endTime).Scan(&data).Error
|
||
if err != nil {
|
||
return nil, nil, 0, err
|
||
}
|
||
|
||
// 剔除 "Total" 数据
|
||
var filteredData []models.MgHourSummary
|
||
var summaryData *models.MgHourSummary
|
||
for _, item := range data {
|
||
if item.Hour == "Total" {
|
||
summaryData = &item // 保存汇总数据
|
||
} else {
|
||
filteredData = append(filteredData, item) // 只保留小时数据
|
||
}
|
||
}
|
||
|
||
// 按小时排序(确保 hour 是数字,排序正确)
|
||
sort.Slice(filteredData, func(i, j int) bool {
|
||
// 将字符串类型的 hour 转换为整数进行比较
|
||
hourI, errI := strconv.Atoi(filteredData[i].Hour)
|
||
hourJ, errJ := strconv.Atoi(filteredData[j].Hour)
|
||
|
||
// 如果转换失败,按原样比较;否则按数字顺序排序
|
||
if errI != nil && errJ != nil {
|
||
return filteredData[i].Hour > filteredData[j].Hour
|
||
}
|
||
if errI != nil {
|
||
// 如果 i 的 hour 无法转换为数字,则认为它大于 j
|
||
return false
|
||
}
|
||
if errJ != nil {
|
||
// 如果 j 的 hour 无法转换为数字,则认为它大于 i
|
||
return true
|
||
}
|
||
return hourI > hourJ
|
||
})
|
||
|
||
// 如果是导出数据,直接返回所有数据
|
||
if req.IsExport == 1 {
|
||
return filteredData, summaryData, 0, nil
|
||
}
|
||
|
||
// 分页处理
|
||
start := (req.PageNum - 1) * req.PageSize
|
||
end := start + req.PageSize
|
||
if end > len(filteredData) {
|
||
end = len(filteredData)
|
||
}
|
||
if start > len(filteredData) {
|
||
start = 0
|
||
}
|
||
paginatedData := filteredData[start:end]
|
||
|
||
// 返回分页数据
|
||
return paginatedData, summaryData, len(filteredData), nil
|
||
}
|