diff --git a/app/admin/apis/migumanage/migu_admin.go b/app/admin/apis/migumanage/migu_admin.go index 1b608a3..f82efc8 100644 --- a/app/admin/apis/migumanage/migu_admin.go +++ b/app/admin/apis/migumanage/migu_admin.go @@ -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 历史汇总(按小时) diff --git a/app/admin/models/migu_music.go b/app/admin/models/migu_music.go index 28b89f6..371e2c7 100644 --- a/app/admin/models/migu_music.go +++ b/app/admin/models/migu_music.go @@ -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 diff --git a/app/admin/router/migu.go b/app/admin/router/migu.go index 236b48e..02e5f6f 100644 --- a/app/admin/router/migu.go +++ b/app/admin/router/migu.go @@ -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导入订单退订数据 } }