1、通过excel导入咪咕音乐数据接口;

2、优化"营收分析"、"用户留存记录(按天)"接口,增加产品id和渠道的筛选;
This commit is contained in:
chenlin 2025-03-28 13:59:48 +08:00
parent 6c9047a4b2
commit 71cb0c34e5
3 changed files with 287 additions and 228 deletions

View File

@ -1,13 +1,16 @@
package migumanage
import (
"encoding/csv"
"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"
"go-admin/tools"
"gorm.io/gorm"
"io"
"net/http"
"sort"
"strconv"
@ -1508,6 +1511,8 @@ func (e MiGuDeployService) UserDayRetentionList(c *gin.Context) {
err := e.Orm.Table("mg_order").
Where("created_at >= ?", req.RetentionMonth+"-01 00:00:00").
Where("created_at < ?", req.RetentionMonth+"-31 23:59:59").
Where("product_id = ?", req.SkuCode). // 添加SkuCode条件
Where("channel_code = ?", req.Channel). // 添加Channel条件
Count(&newUserCount).Error
if err != nil {
e.Logger.Error(err)
@ -1602,6 +1607,8 @@ func (e MiGuDeployService) UserDayRetentionList(c *gin.Context) {
// 查询该天的留存用户数
localErr = e.Orm.Model(&models.MgOrder{}).
Where("state = 1 AND created_at between ? and ?", currentMonthFirstDay, currentMonthNextFirstDay).
Where("product_id = ?", req.SkuCode). // 添加SkuCode条件
Where("channel_code = ?", req.Channel). // 添加Channel条件
Or("state = 2 AND unsubscribe_time > ? AND created_at between ? and ?", date+" 23:59:59", currentMonthFirstDay, currentMonthNextFirstDay).
Count(&retainedUserCount).Error
@ -1611,7 +1618,9 @@ func (e MiGuDeployService) UserDayRetentionList(c *gin.Context) {
Where("unsubscribe_time <= ?", date+" 23:59:59").
Where("created_at >= ?", currentMonthFirstDay).
Where("created_at < ?", currentMonthNextFirstDay).
Where("state = 2"). // 状态为2表示退订
Where("state = 2"). // 状态为2表示退订
Where("product_id = ?", req.SkuCode). // 添加SkuCode条件
Where("channel_code = ?", req.Channel). // 添加Channel条件
Count(&unsubOnDay).Error
if localErr != nil {
@ -1662,6 +1671,8 @@ func (e MiGuDeployService) UserDayRetentionList(c *gin.Context) {
// 查询该天的留存用户数
err = e.Orm.Model(&models.MgOrder{}).
Where("state = 1 AND created_at between ? and ?", currentMonthFirstDay, currentMonthNextFirstDay).
Where("product_id = ?", req.SkuCode). // 添加SkuCode条件
Where("channel_code = ?", req.Channel). // 添加Channel条件
Or("state = 2 AND unsubscribe_time > ? AND created_at between ? and ?", date+" 23:59:59", currentMonthFirstDay, currentMonthNextFirstDay).
Count(&retainedUserCount).Error
@ -1673,7 +1684,9 @@ func (e MiGuDeployService) UserDayRetentionList(c *gin.Context) {
Where("unsubscribe_time <= ?", date+" 23:59:59").
Where("created_at >= ?", currentMonthFirstDay).
Where("created_at < ?", currentMonthNextFirstDay).
Where("state = 2"). // 状态为2表示退订
Where("state = 2"). // 状态为2表示退订
Where("product_id = ?", req.SkuCode). // 添加SkuCode条件
Where("channel_code = ?", req.Channel). // 添加Channel条件
Count(&unsubOnDay).Error
if localErr != nil {
@ -2206,14 +2219,14 @@ func (e MiGuDeployService) CalculateRevenueAnalysis(c *gin.Context) {
tempCurrentMonth := result[i].Month
// 查询当前月之前所有月份未退订的用户数
totalValidUsers, err := GetTotalValidUsers(e.Orm, tempCurrentMonth)
totalValidUsers, err := GetTotalValidUsers(e.Orm, tempCurrentMonth, req.ProductID, req.Channel)
if err != nil {
response.Error(c, http.StatusInternalServerError, err, "未退订用户查询失败")
return
}
// 查询当前月之前所有月份在本月2号之后退订的用户数
totalUnsubscribedUsers, err := GetTotalUnsubscribedUsers(e.Orm, tempCurrentMonth)
totalUnsubscribedUsers, err := GetTotalUnsubscribedUsers(e.Orm, tempCurrentMonth, req.ProductID, req.Channel)
if err != nil {
response.Error(c, http.StatusInternalServerError, err, "退订用户查询失败")
return
@ -2286,16 +2299,34 @@ func generateMonthRange(start, end string) []string {
}
// GetTotalValidUsers 计算所有之前月份的未退订用户总数
func GetTotalValidUsers(db *gorm.DB, currentMonth string) (int, error) {
func GetTotalValidUsers(db *gorm.DB, currentMonth string, productID int, channel string) (int, error) {
var totalValidUsers int
err := db.Raw(`
// 基本查询条件
query := `
SELECT
COUNT(*)
FROM mg_order
WHERE unsubscribe_time IS NULL
AND DATE_FORMAT(subscribe_time, '%Y-%m') < ?`, currentMonth).Scan(&totalValidUsers).Error
AND DATE_FORMAT(subscribe_time, '%Y-%m') < ?
`
// 动态添加 productID 和 channel 查询条件
var args []interface{}
args = append(args, currentMonth)
if productID != 0 {
query += " AND product_id = ?"
args = append(args, productID)
}
if channel != "" {
query += " AND channel_code = ?"
args = append(args, channel)
}
// 执行查询
err := db.Raw(query, args...).Scan(&totalValidUsers).Error
if err != nil {
return 0, err
}
@ -2304,20 +2335,37 @@ func GetTotalValidUsers(db *gorm.DB, currentMonth string) (int, error) {
}
// GetTotalUnsubscribedUsers 计算所有之前月份在本月2号之后退订的用户总数
func GetTotalUnsubscribedUsers(db *gorm.DB, currentMonth string) (int, error) {
func GetTotalUnsubscribedUsers(db *gorm.DB, currentMonth string, productID int, channel string) (int, error) {
var totalUnsubscribedUsers int
// 计算当前月份的 2 号 00:00:00
currentMonthFirstDay := currentMonth + "-02 00:00:00"
err := db.Raw(`
// 基本查询条件
query := `
SELECT
COUNT(*)
FROM mg_order
WHERE unsubscribe_time >= ?
AND DATE_FORMAT(subscribe_time, '%Y-%m') < ?`,
currentMonthFirstDay, currentMonth).Scan(&totalUnsubscribedUsers).Error
AND DATE_FORMAT(subscribe_time, '%Y-%m') < ?
`
// 动态添加 productID 和 channel 查询条件
var args []interface{}
args = append(args, currentMonthFirstDay, currentMonth)
if productID != 0 {
query += " AND product_id = ?"
args = append(args, productID)
}
if channel != "" {
query += " AND channel_code = ?"
args = append(args, channel)
}
// 执行查询
err := db.Raw(query, args...).Scan(&totalUnsubscribedUsers).Error
if err != nil {
return 0, err
}
@ -2605,206 +2653,215 @@ func (e MiGuDeployService) AddProduct(c *gin.Context) {
// 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": "导入成功"})
//}
// 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) < 3 {
continue // 跳过数据不全的行
}
// 解析订阅时间
subscribeTime, err := time.Parse("2006-01-02 15:04:05", row[1])
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)
tempOrderNo := models.GetExcelOrderSerial(e.Orm, subscribeTime)
// 创建MgOrder对象
order := models.MgOrderCopy{
ProductID: 2,
ChannelCode: "00211NV",
OrderSerial: tempOrderNo,
SubscribeTime: &localTime,
PhoneNumber: row[0],
ChannelTradeNo: tempOrderNo,
ExternalOrderID: tempOrderNo,
State: 1,
}
if row[2] == "未包月" { // 1小时内退订
order.IsOneHourCancel = 1
order.State = 2
unsubscribeTime := localTime.Add(30 * time.Minute)
order.UnsubscribeTime = &unsubscribeTime
}
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 历史汇总(按小时)

View File

@ -288,25 +288,27 @@ func checkSubscriptionStatus(token string, order MgOrder) bool {
if resp.Status == "0" {
return true
} else if resp.Status == "1" {
subscribeTime := order.CreatedAt
unsubTime := time.Now().Format("2006-01-02 15:04:05")
cancelFlag := 0
if order.State == SubscribeOK {
subscribeTime := order.CreatedAt
unsubTime := time.Now().Format("2006-01-02 15:04:05")
cancelFlag := 0
if IsWithinOneHourCancel(subscribeTime, unsubTime) {
cancelFlag = 1
}
if IsWithinOneHourCancel(subscribeTime, unsubTime) {
cancelFlag = 1
}
err = database.Db.Table("mg_order").Where("order_serial = ?", order.OrderSerial).Updates(map[string]interface{}{
"state": UnsubscribeOK,
"is_one_hour_cancel": cancelFlag,
"unsubscribe_time": unsubTime,
"updated_at": time.Now(),
}).Error
if err != nil {
fmt.Println("Failed to update order:", err.Error())
logger.Error("Failed to update order:", err.Error())
err = database.Db.Table("mg_order").Where("order_serial = ?", order.OrderSerial).Updates(map[string]interface{}{
"state": UnsubscribeOK,
"is_one_hour_cancel": cancelFlag,
"unsubscribe_time": unsubTime,
"updated_at": time.Now(),
}).Error
if err != nil {
fmt.Println("Failed to update order:", err.Error())
logger.Error("Failed to update order:", err.Error())
}
return false
}
return false
}
}
return false

View File

@ -33,7 +33,7 @@ func registerMiGuControlManageRouter(v1 *gin.RouterGroup, authMiddleware *jwt.Gi
api.POST("home/revenue_analysis", apiMiGu.CalculateRevenueAnalysis) // 营收分析
api.POST("historical_summary/list", apiMiGu.HistoricalSummaryListOld) // 历史汇总查询
//api.POST("order/import", apiMiGu.ImportExcelToMgOrderHandler) // 通过excel导入订单数据
api.POST("order/import", apiMiGu.ImportExcelToMgOrderHandler) // 通过excel导入订单数据
//api.POST("order/import_update", apiMiGu.ImportExcelToMgOrderHandlerUpdate) // 通过excel导入订单退订数据
}
}