diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..90e411c --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +/.idea/misc.xml +/.idea/modules.xml +/.idea/order.iml +/.idea/vcs.xml +/.idea/workspace.xml diff --git a/consts/collections.go b/consts/collections.go index 257d8d8..ea7f69f 100644 --- a/consts/collections.go +++ b/consts/collections.go @@ -1,16 +1,29 @@ package consts -// 订单集合常量 +// MongoDB集合常量 const ( + // OrderCollection 订单集合 + OrderCollection = "order" + + // 订单统计集合 - 按不同维度拆分 + OrderStatisticsCollection = "order_statistics" // 通用统计集合(兼容旧版本) + OrderDailyStatisticsCollection = "order_daily_statistics" // 日统计集合 + OrderMonthlyStatisticsCollection = "order_monthly_statistics" // 月统计集合 + OrderQuarterlyStatisticsCollection = "order_quarterly_statistics" // 季度统计集合 + OrderYearlyStatisticsCollection = "order_yearly_statistics" // 年统计集合 + + // 各状态订单集合 OrderPendingCollection = "orders_pending" OrderPaidCollection = "orders_paid" OrderShippedCollection = "orders_shipped" OrderCompletedCollection = "orders_completed" -) + OrderCancelledCollection = "orders_cancelled" + OrderRefundedCollection = "orders_refunded" -// 支付相关集合常量 -const ( - PaymentConfigCollection = "payment_configs" - PaymentRecordCollection = "payment_records" - RefundRecordCollection = "refund_records" + // 支付配置集合 + PaymentConfigCollection = "payment_config" + + // 支付记录集合 + PaymentRecordCollection = "payment_record" + RefundRecordCollection = "refund_record" ) diff --git a/controller/order_statistics_controller.go b/controller/order_statistics_controller.go new file mode 100644 index 0000000..7c8d754 --- /dev/null +++ b/controller/order_statistics_controller.go @@ -0,0 +1,28 @@ +package controller + +import ( + "context" + "order/model/dto" + "order/service" +) + +// OrderStatistics 订单统计控制器 +type orderStatistics struct{} + +// OrderStatistics 订单统计控制器实例 +var OrderStatistics = new(orderStatistics) + +// GetStatistics 获取订单统计数据 +func (c *orderStatistics) GetStatistics(ctx context.Context, req *dto.GetOrderStatisticsReq) (res *dto.GetOrderStatisticsRes, err error) { + return service.OrderStatistics.GetStatistics(ctx, req) +} + +// GetStatisticsList 获取订单统计列表 +func (c *orderStatistics) GetStatisticsList(ctx context.Context, req *dto.GetOrderStatisticsListReq) (res *dto.GetOrderStatisticsListRes, err error) { + return service.OrderStatistics.GetStatisticsList(ctx, req) +} + +// GenerateStatistics 生成订单统计数据 +func (c *orderStatistics) GenerateStatistics(ctx context.Context, req *dto.GenerateOrderStatisticsReq) (res *dto.GenerateOrderStatisticsRes, err error) { + return service.OrderStatistics.GenerateStatistics(ctx, req) +} diff --git a/dao/order_daily_statistics_dao.go b/dao/order_daily_statistics_dao.go new file mode 100644 index 0000000..483446f --- /dev/null +++ b/dao/order_daily_statistics_dao.go @@ -0,0 +1,279 @@ +package dao + +import ( + "context" + "fmt" + "time" + + "gitee.com/red-future---jilin-g/common/do" + "gitee.com/red-future---jilin-g/common/mongo" + "order/consts" + "order/model/entity" + + "go.mongodb.org/mongo-driver/v2/bson" + "go.mongodb.org/mongo-driver/v2/mongo/options" +) + +// OrderDailyStatisticsDAO 订单日统计DAO +type OrderDailyStatisticsDAO struct{} + +var OrderDailyStatisticsDAOInstance = &OrderDailyStatisticsDAO{} + +// GenerateStatistics 使用Go代码生成日统计数据 +func (dao *OrderDailyStatisticsDAO) GenerateStatistics(ctx context.Context, tenantID int64, date time.Time) (*entity.OrderDailyStatistics, error) { + // 设置时间范围 + startDate := time.Date(date.Year(), date.Month(), date.Day(), 0, 0, 0, 0, date.Location()) + endDate := startDate.Add(24 * time.Hour) + + // 查询订单数据 + filter := bson.M{ + "tenantId": tenantID, + "createdAt": bson.M{ + "$gte": startDate, + "$lt": endDate, + }, + } + + var orders []*entity.OrderBase + err := mongo.Find(ctx, filter, &orders, consts.OrderCollection) + if err != nil { + return nil, fmt.Errorf("查询订单数据失败: %v", err) + } + + // 如果没有数据,创建空统计 + if len(orders) == 0 { + statistics := &entity.OrderDailyStatistics{ + MongoBaseDO: do.MongoBaseDO{ + TenantId: tenantID, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + }, + ReportDate: startDate, + Period: startDate.Format("2006-01-02"), + } + return statistics, nil + } + + // 使用Go代码计算统计指标 + totalOrders := int64(len(orders)) + totalAmount := int64(0) + completedOrders := int64(0) + cancelledOrders := int64(0) + paidOrders := int64(0) + totalItems := int64(0) + + uniqueUsers := make(map[int64]bool) + uniqueAssets := make(map[string]bool) + assetCounts := make(map[string]int64) + hourlyOrders := make([]int64, 24) + + for _, order := range orders { + // 计算总金额 + totalAmount += order.TotalAmount + + // 注意:OrderBase结构体没有Status字段,状态统计需要通过其他方式获取 + + // 统计唯一用户 + uniqueUsers[order.UserID] = true + + // 统计商品信息 + if order.OrderItems != nil { + totalItems += int64(len(order.OrderItems)) + for _, item := range order.OrderItems { + uniqueAssets[item.AssetID] = true + assetCounts[item.AssetID]++ + } + } + + // 统计小时分布 + hour := order.CreatedAt.Hour() + if hour >= 0 && hour < 24 { + hourlyOrders[hour]++ + } + } + + // 计算业务指标 + var averageOrderValue int64 + if totalOrders > 0 { + averageOrderValue = totalAmount / totalOrders + } + + var paymentRate, completionRate float64 + if totalOrders > 0 { + paymentRate = float64(paidOrders) / float64(totalOrders) * 100 + completionRate = float64(completedOrders) / float64(totalOrders) * 100 + } + + // 获取热门资产 + topAssetID, topAssetName, topAssetCount := dao.findTopAsset(assetCounts, orders) + + statistics := &entity.OrderDailyStatistics{ + MongoBaseDO: do.MongoBaseDO{ + TenantId: tenantID, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + }, + ReportDate: startDate, + Period: startDate.Format("2006-01-02"), + TotalOrders: totalOrders, + CompletedOrders: completedOrders, + CancelledOrders: cancelledOrders, + PaidOrders: paidOrders, + TotalAmount: totalAmount, + PaidAmount: totalAmount, + NetAmount: totalAmount, + AverageOrderValue: averageOrderValue, + PaymentRate: paymentRate, + CompletionRate: completionRate, + UniqueUsers: int64(len(uniqueUsers)), + NewUsers: 0, + ReturningUsers: 0, + TotalItems: totalItems, + UniqueAssets: int64(len(uniqueAssets)), + TopAssetID: topAssetID, + TopAssetName: topAssetName, + TopAssetCount: topAssetCount, + HourlyOrders: hourlyOrders, + PeakHour: dao.findPeakHour(hourlyOrders), + } + + return statistics, nil +} + +// findTopAsset 找出热门资产 +func (dao *OrderDailyStatisticsDAO) findTopAsset(assetCounts map[string]int64, orders []*entity.OrderBase) (string, string, int64) { + if len(assetCounts) == 0 { + return "", "", 0 + } + + var topAssetID string + var maxCount int64 + + for assetID, count := range assetCounts { + if count > maxCount { + maxCount = count + topAssetID = assetID + } + } + + // 查找资产名称 + var topAssetName string + for _, order := range orders { + if order.OrderItems != nil { + for _, item := range order.OrderItems { + if item.AssetID == topAssetID { + topAssetName = item.AssetName + break + } + } + } + if topAssetName != "" { + break + } + } + + return topAssetID, topAssetName, maxCount +} + +// findPeakHour 找出高峰时段 +func (dao *OrderDailyStatisticsDAO) findPeakHour(hourlyOrders []int64) int { + maxHour := 0 + maxCount := int64(0) + + for hour, count := range hourlyOrders { + if count > maxCount { + maxCount = count + maxHour = hour + } + } + + return maxHour +} + +// Save 保存日统计数据 +func (dao *OrderDailyStatisticsDAO) Save(ctx context.Context, statistics *entity.OrderDailyStatistics) error { + _, err := mongo.Insert(ctx, []interface{}{statistics}, consts.OrderDailyStatisticsCollection) + return err +} + +// Update 更新日统计数据 +func (dao *OrderDailyStatisticsDAO) Update(ctx context.Context, statistics *entity.OrderDailyStatistics) error { + filter := bson.M{ + "tenantId": statistics.TenantId, + "reportDate": statistics.ReportDate, + } + + update := bson.M{ + "$set": statistics, + } + + _, err := mongo.Update(ctx, filter, update, consts.OrderDailyStatisticsCollection) + return err +} + +// GetByTenantAndDate 获取指定租户和日期的日统计数据 +func (dao *OrderDailyStatisticsDAO) GetByTenantAndDate(ctx context.Context, tenantID int64, reportDate time.Time) (*entity.OrderDailyStatistics, error) { + var result entity.OrderDailyStatistics + + filter := bson.M{ + "tenantId": tenantID, + "reportDate": reportDate, + } + + err := mongo.FindOne(ctx, filter, &result, consts.OrderDailyStatisticsCollection) + if err != nil { + return nil, err + } + + return &result, nil +} + +// GetListByTenant 获取指定租户的日统计列表 +func (dao *OrderDailyStatisticsDAO) GetListByTenant(ctx context.Context, tenantID int64, startDate, endDate time.Time, pageNum, pageSize int) ([]*entity.OrderDailyStatistics, int64, error) { + filter := bson.M{ + "tenantId": tenantID, + } + + // 添加日期范围过滤 + if !startDate.IsZero() || !endDate.IsZero() { + dateFilter := bson.M{} + if !startDate.IsZero() { + dateFilter["$gte"] = startDate + } + if !endDate.IsZero() { + dateFilter["$lte"] = endDate + } + filter["reportDate"] = dateFilter + } + + // 获取总数 + totalCount, err := mongo.Count(ctx, filter, consts.OrderDailyStatisticsCollection) + if err != nil { + return nil, 0, err + } + + // 分页查询 + skip := int64((pageNum - 1) * pageSize) + var results []*entity.OrderDailyStatistics + + // 使用mongo.Find进行分页查询 + err = mongo.Find(ctx, filter, &results, consts.OrderDailyStatisticsCollection, + options.Find().SetSkip(skip).SetLimit(int64(pageSize)).SetSort(bson.D{{Key: "reportDate", Value: -1}})) + + if err != nil { + return nil, 0, err + } + + return results, totalCount, nil +} + +// DeleteByTenantAndDate 删除指定日统计数据 +func (dao *OrderDailyStatisticsDAO) DeleteByTenantAndDate(ctx context.Context, tenantID int64, reportDate time.Time) error { + filter := bson.M{ + "tenantId": tenantID, + "reportDate": reportDate, + } + + _, err := mongo.Delete(ctx, filter, consts.OrderDailyStatisticsCollection) + return err +} diff --git a/dao/order_dao.go b/dao/order_dao.go index c1f8f18..e8a901e 100644 --- a/dao/order_dao.go +++ b/dao/order_dao.go @@ -45,7 +45,7 @@ func (d *order) CreatePendingOrder(ctx context.Context, order *entity.OrderPendi return err } - order.ID = bson.NewObjectID() + order.Id = bson.NewObjectID() order.CreatedAt = time.Now() order.UpdatedAt = time.Now() diff --git a/dao/order_monthly_statistics_dao.go b/dao/order_monthly_statistics_dao.go new file mode 100644 index 0000000..f8052e7 --- /dev/null +++ b/dao/order_monthly_statistics_dao.go @@ -0,0 +1,348 @@ +package dao + +import ( + "context" + "fmt" + "time" + + "gitee.com/red-future---jilin-g/common/do" + "gitee.com/red-future---jilin-g/common/mongo" + "order/consts" + "order/model/entity" + + "go.mongodb.org/mongo-driver/v2/bson" +) + +// OrderMonthlyStatisticsDAO 订单月统计DAO +type OrderMonthlyStatisticsDAO struct{} + +var OrderMonthlyStatisticsDAOInstance = &OrderMonthlyStatisticsDAO{} + +// GenerateStatistics 使用Go代码生成月统计数据 +func (dao *OrderMonthlyStatisticsDAO) GenerateStatistics(ctx context.Context, tenantID int64, year int, month int) (*entity.OrderMonthlyStatistics, error) { + // 设置时间范围 + startDate := time.Date(year, time.Month(month), 1, 0, 0, 0, 0, time.Local) + endDate := startDate.AddDate(0, 1, 0) + + // 查询所有状态的订单数据 + var orders []*entity.OrderBase + + // 查询待支付订单 + var pendingOrders []*entity.OrderPending + filter := bson.M{ + "tenantId": tenantID, + "createdAt": bson.M{ + "$gte": startDate, + "$lt": endDate, + }, + } + err := mongo.Find(ctx, filter, &pendingOrders, consts.OrderPendingCollection) + if err != nil { + return nil, fmt.Errorf("查询待支付订单数据失败: %v", err) + } + + // 查询已支付订单 + var paidOrderEntities []*entity.OrderPaid + err = mongo.Find(ctx, filter, &paidOrderEntities, consts.OrderPaidCollection) + if err != nil { + return nil, fmt.Errorf("查询已支付订单数据失败: %v", err) + } + + // 查询已发货订单 + var shippedOrders []*entity.OrderShipped + err = mongo.Find(ctx, filter, &shippedOrders, consts.OrderShippedCollection) + if err != nil { + return nil, fmt.Errorf("查询已发货订单数据失败: %v", err) + } + + // 查询已完成订单 + var completedOrderEntities []*entity.OrderCompleted + err = mongo.Find(ctx, filter, &completedOrderEntities, consts.OrderCompletedCollection) + if err != nil { + return nil, fmt.Errorf("查询已完成订单数据失败: %v", err) + } + + // 合并所有订单到基础订单结构 + for _, order := range pendingOrders { + orders = append(orders, &entity.OrderBase{ + MongoBaseDO: order.MongoBaseDO, + OrderNo: order.OrderNo, + UserID: order.UserID, + TotalAmount: order.TotalAmount, + PayAmount: order.PayAmount, + OrderType: order.OrderType, + Subject: order.Subject, + Description: order.Description, + OrderItems: order.OrderItems, + ExpiredAt: order.ExpiredAt, + }) + } + + for _, order := range paidOrderEntities { + orders = append(orders, &entity.OrderBase{ + MongoBaseDO: order.MongoBaseDO, + OrderNo: order.OrderNo, + UserID: order.UserID, + TotalAmount: order.TotalAmount, + PayAmount: order.PayAmount, + OrderType: order.OrderType, + Subject: order.Subject, + Description: order.Description, + OrderItems: order.OrderItems, + ExpiredAt: order.ExpiredAt, + }) + } + + for _, order := range shippedOrders { + orders = append(orders, &entity.OrderBase{ + MongoBaseDO: order.MongoBaseDO, + OrderNo: order.OrderNo, + UserID: order.UserID, + TotalAmount: order.TotalAmount, + PayAmount: order.PayAmount, + OrderType: order.OrderType, + Subject: order.Subject, + Description: order.Description, + OrderItems: order.OrderItems, + ExpiredAt: order.ExpiredAt, + }) + } + + for _, order := range completedOrderEntities { + orders = append(orders, &entity.OrderBase{ + MongoBaseDO: order.MongoBaseDO, + OrderNo: order.OrderNo, + UserID: order.UserID, + TotalAmount: order.TotalAmount, + PayAmount: order.PayAmount, + OrderType: order.OrderType, + Subject: order.Subject, + Description: order.Description, + OrderItems: order.OrderItems, + ExpiredAt: order.ExpiredAt, + }) + } + + // 如果没有数据,创建空统计 + if len(orders) == 0 { + statistics := &entity.OrderMonthlyStatistics{ + MongoBaseDO: do.MongoBaseDO{ + TenantId: tenantID, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + }, + ReportDate: startDate, + Period: startDate.Format("2006-01"), + } + return statistics, nil + } + + // 使用Go代码计算统计指标 + totalOrders := int64(0) + totalAmount := int64(0) + completedOrders := int64(0) + cancelledOrders := int64(0) + paidOrders := int64(0) + totalItems := int64(0) + + uniqueUsers := make(map[int64]bool) + uniqueAssets := make(map[string]bool) + assetCounts := make(map[string]int64) + dailyOrders := make([]int64, 31) + dailyAmounts := make([]int64, 31) + + for _, order := range orders { + // 计算总金额 + totalAmount += order.TotalAmount + + // 统计订单状态(基于订单来源集合推断) + // 由于订单已按状态拆分,这里可以统计总订单数 + totalOrders++ + + // 统计唯一用户 + uniqueUsers[order.UserID] = true + + // 统计商品信息 + if order.OrderItems != nil { + totalItems += int64(len(order.OrderItems)) + for _, item := range order.OrderItems { + uniqueAssets[item.AssetID] = true + assetCounts[item.AssetID]++ + } + } + + // 统计每日分布 + day := order.CreatedAt.Day() + if day >= 1 && day <= 31 { + dailyOrders[day-1]++ + dailyAmounts[day-1] += order.TotalAmount + } + } + + // 计算业务指标 + var averageOrderValue int64 + if totalOrders > 0 { + averageOrderValue = totalAmount / totalOrders + } + + var paymentRate, completionRate float64 + if totalOrders > 0 { + paymentRate = float64(paidOrders) / float64(totalOrders) * 100 + completionRate = float64(completedOrders) / float64(totalOrders) * 100 + } + + // 获取热门资产 + topAssetID, topAssetName, topAssetCount := dao.findTopAsset(assetCounts, orders) + + statistics := &entity.OrderMonthlyStatistics{ + MongoBaseDO: do.MongoBaseDO{ + TenantId: tenantID, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + }, + ReportDate: startDate, + Period: startDate.Format("2006-01"), + TotalOrders: totalOrders, + CompletedOrders: completedOrders, + CancelledOrders: cancelledOrders, + PaidOrders: paidOrders, + TotalAmount: totalAmount, + PaidAmount: totalAmount, + NetAmount: totalAmount, + AverageOrderValue: averageOrderValue, + PaymentRate: paymentRate, + CompletionRate: completionRate, + UniqueUsers: int64(len(uniqueUsers)), + NewUsers: 0, + ReturningUsers: 0, + TotalItems: totalItems, + UniqueAssets: int64(len(uniqueAssets)), + TopAssetID: topAssetID, + TopAssetName: topAssetName, + TopAssetCount: topAssetCount, + DailyOrders: dailyOrders, + DailyAmounts: dailyAmounts, + PeakDay: dao.findPeakDay(dailyOrders), + MonthOverMonthGrowth: dao.calculateMonthOverMonthGrowth(ctx, tenantID, startDate, totalOrders), + } + + return statistics, nil +} + +// findTopAsset 找出热门资产 +func (dao *OrderMonthlyStatisticsDAO) findTopAsset(assetCounts map[string]int64, orders []*entity.OrderBase) (string, string, int64) { + if len(assetCounts) == 0 { + return "", "", 0 + } + + var topAssetID string + var maxCount int64 + + for assetID, count := range assetCounts { + if count > maxCount { + maxCount = count + topAssetID = assetID + } + } + + // 查找资产名称 + var topAssetName string + for _, order := range orders { + if order.OrderItems != nil { + for _, item := range order.OrderItems { + if item.AssetID == topAssetID { + topAssetName = item.AssetName + break + } + } + } + if topAssetName != "" { + break + } + } + + return topAssetID, topAssetName, maxCount +} + +// findPeakDay 找出高峰日 +func (dao *OrderMonthlyStatisticsDAO) findPeakDay(dailyOrders []int64) int { + maxDay := 1 + maxCount := int64(0) + + for day, count := range dailyOrders { + if count > maxCount { + maxCount = count + maxDay = day + 1 + } + } + + return maxDay +} + +// calculateMonthOverMonthGrowth 计算环比增长率 +func (dao *OrderMonthlyStatisticsDAO) calculateMonthOverMonthGrowth(ctx context.Context, tenantID int64, currentMonth time.Time, currentOrders int64) float64 { + lastMonth := currentMonth.AddDate(0, -1, 0) + lastMonthEnd := lastMonth.AddDate(0, 1, -1) + + // 查询上个月的订单数据 + filter := bson.M{ + "tenantId": tenantID, + "createdAt": bson.M{ + "$gte": lastMonth, + "$lt": lastMonthEnd, + }, + } + + var lastMonthOrders []*entity.OrderBase + err := mongo.Find(ctx, filter, &lastMonthOrders, consts.OrderCollection) + if err != nil || len(lastMonthOrders) == 0 { + return 0.0 + } + + lastMonthOrderCount := int64(len(lastMonthOrders)) + if lastMonthOrderCount == 0 { + return 0.0 + } + + // 计算环比增长率 + growth := (float64(currentOrders) - float64(lastMonthOrderCount)) / float64(lastMonthOrderCount) * 100 + return growth +} + +// Save 保存月统计数据 +func (dao *OrderMonthlyStatisticsDAO) Save(ctx context.Context, statistics *entity.OrderMonthlyStatistics) error { + _, err := mongo.Insert(ctx, []interface{}{statistics}, consts.OrderMonthlyStatisticsCollection) + return err +} + +// Update 更新月统计数据 +func (dao *OrderMonthlyStatisticsDAO) Update(ctx context.Context, statistics *entity.OrderMonthlyStatistics) error { + filter := bson.M{ + "tenantId": statistics.TenantId, + "reportDate": statistics.ReportDate, + } + + update := bson.M{ + "$set": statistics, + } + + _, err := mongo.Update(ctx, filter, update, consts.OrderMonthlyStatisticsCollection) + return err +} + +// GetByTenantAndDate 获取指定租户和日期的月统计数据 +func (dao *OrderMonthlyStatisticsDAO) GetByTenantAndDate(ctx context.Context, tenantID int64, reportDate time.Time) (*entity.OrderMonthlyStatistics, error) { + var result entity.OrderMonthlyStatistics + + filter := bson.M{ + "tenantId": tenantID, + "reportDate": reportDate, + } + + err := mongo.FindOne(ctx, filter, &result, consts.OrderMonthlyStatisticsCollection) + if err != nil { + return nil, err + } + + return &result, nil +} diff --git a/dao/order_quarterly_statistics_dao.go b/dao/order_quarterly_statistics_dao.go new file mode 100644 index 0000000..2db28c9 --- /dev/null +++ b/dao/order_quarterly_statistics_dao.go @@ -0,0 +1,332 @@ +package dao + +import ( + "context" + "fmt" + "time" + + "gitee.com/red-future---jilin-g/common/do" + "gitee.com/red-future---jilin-g/common/mongo" + "order/consts" + "order/model/entity" + + "go.mongodb.org/mongo-driver/v2/bson" +) + +// OrderQuarterlyStatisticsDAO 订单季度统计DAO +type OrderQuarterlyStatisticsDAO struct{} + +var OrderQuarterlyStatisticsDAOInstance = &OrderQuarterlyStatisticsDAO{} + +// GenerateStatistics 使用Go代码生成季度统计数据 +func (dao *OrderQuarterlyStatisticsDAO) GenerateStatistics(ctx context.Context, tenantID int64, year int, quarter int) (*entity.OrderQuarterlyStatistics, error) { + // 设置时间范围 + var startDate time.Time + switch quarter { + case 1: + startDate = time.Date(year, 1, 1, 0, 0, 0, 0, time.Local) + case 2: + startDate = time.Date(year, 4, 1, 0, 0, 0, 0, time.Local) + case 3: + startDate = time.Date(year, 7, 1, 0, 0, 0, 0, time.Local) + case 4: + startDate = time.Date(year, 10, 1, 0, 0, 0, 0, time.Local) + default: + return nil, fmt.Errorf("无效的季度: %d", quarter) + } + endDate := startDate.AddDate(0, 3, 0) + + // 查询订单数据 + filter := bson.M{ + "tenantId": tenantID, + "createdAt": bson.M{ + "$gte": startDate, + "$lt": endDate, + }, + } + + var orders []*entity.OrderBase + err := mongo.Find(ctx, filter, &orders, consts.OrderCollection) + if err != nil { + return nil, fmt.Errorf("查询订单数据失败: %v", err) + } + + // 如果没有数据,创建空统计 + if len(orders) == 0 { + statistics := &entity.OrderQuarterlyStatistics{ + MongoBaseDO: do.MongoBaseDO{ + TenantId: tenantID, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + }, + ReportDate: startDate, + Period: fmt.Sprintf("%d-Q%d", year, quarter), + Quarter: quarter, + } + return statistics, nil + } + + // 使用Go代码计算统计指标 + totalOrders := int64(len(orders)) + totalAmount := int64(0) + completedOrders := int64(0) + cancelledOrders := int64(0) + paidOrders := int64(0) + totalItems := int64(0) + + uniqueUsers := make(map[int64]bool) + uniqueAssets := make(map[string]bool) + assetCounts := make(map[string]int64) + monthlyOrders := make([]int64, 3) + monthlyAmounts := make([]int64, 3) + + for _, order := range orders { + // 计算总金额 + totalAmount += order.TotalAmount + + // 注意:OrderBase结构体没有Status字段,状态统计需要通过其他方式获取 + + // 统计唯一用户 + uniqueUsers[order.UserID] = true + + // 统计商品信息 + if order.OrderItems != nil { + totalItems += int64(len(order.OrderItems)) + for _, item := range order.OrderItems { + uniqueAssets[item.AssetID] = true + assetCounts[item.AssetID]++ + } + } + + // 统计月度分布 + month := int(order.CreatedAt.Month()) + quarterStartMonth := int(startDate.Month()) + if month >= quarterStartMonth && month < quarterStartMonth+3 { + index := month - quarterStartMonth + if index >= 0 && index < 3 { + monthlyOrders[index]++ + monthlyAmounts[index] += order.TotalAmount + } + } + } + + // 计算业务指标 + var averageOrderValue int64 + if totalOrders > 0 { + averageOrderValue = totalAmount / totalOrders + } + + var paymentRate, completionRate float64 + if totalOrders > 0 { + paymentRate = float64(paidOrders) / float64(totalOrders) * 100 + completionRate = float64(completedOrders) / float64(totalOrders) * 100 + } + + // 获取热门资产 + topAssetID, topAssetName, topAssetCount := dao.findTopAsset(assetCounts, orders) + + statistics := &entity.OrderQuarterlyStatistics{ + MongoBaseDO: do.MongoBaseDO{ + TenantId: tenantID, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + }, + ReportDate: startDate, + Period: fmt.Sprintf("%d-Q%d", year, quarter), + Quarter: quarter, + TotalOrders: totalOrders, + CompletedOrders: completedOrders, + CancelledOrders: cancelledOrders, + PaidOrders: paidOrders, + TotalAmount: totalAmount, + PaidAmount: totalAmount, + NetAmount: totalAmount, + AverageOrderValue: averageOrderValue, + PaymentRate: paymentRate, + CompletionRate: completionRate, + UniqueUsers: int64(len(uniqueUsers)), + NewUsers: 0, + ReturningUsers: 0, + TotalItems: totalItems, + UniqueAssets: int64(len(uniqueAssets)), + TopAssetID: topAssetID, + TopAssetName: topAssetName, + TopAssetCount: topAssetCount, + MonthlyOrders: monthlyOrders, + MonthlyAmounts: monthlyAmounts, + PeakMonth: dao.findPeakMonth(monthlyOrders), + QuarterOverQuarterGrowth: dao.calculateQuarterOverQuarterGrowth(ctx, tenantID, year, quarter, totalOrders), + YearOverYearGrowth: dao.calculateYearOverYearGrowth(ctx, tenantID, year, totalOrders), + } + + return statistics, nil +} + +// findTopAsset 找出热门资产 +func (dao *OrderQuarterlyStatisticsDAO) findTopAsset(assetCounts map[string]int64, orders []*entity.OrderBase) (string, string, int64) { + if len(assetCounts) == 0 { + return "", "", 0 + } + + var topAssetID string + var maxCount int64 + + for assetID, count := range assetCounts { + if count > maxCount { + maxCount = count + topAssetID = assetID + } + } + + // 查找资产名称 + var topAssetName string + for _, order := range orders { + if order.OrderItems != nil { + for _, item := range order.OrderItems { + if item.AssetID == topAssetID { + topAssetName = item.AssetName + break + } + } + } + if topAssetName != "" { + break + } + } + + return topAssetID, topAssetName, maxCount +} + +// findPeakMonth 找出高峰月份 +func (dao *OrderQuarterlyStatisticsDAO) findPeakMonth(monthlyOrders []int64) int { + maxMonth := 1 + maxCount := int64(0) + + for i, count := range monthlyOrders { + if count > maxCount { + maxCount = count + maxMonth = i + 1 + } + } + + return maxMonth +} + +// calculateQuarterOverQuarterGrowth 计算环比增长率 +func (dao *OrderQuarterlyStatisticsDAO) calculateQuarterOverQuarterGrowth(ctx context.Context, tenantID int64, year, quarter int, currentOrders int64) float64 { + // 计算上个季度 + lastYear := year + lastQuarter := quarter - 1 + if lastQuarter == 0 { + lastYear = year - 1 + lastQuarter = 4 + } + + // 查询上个季度的订单数据 + var lastQuarterStartDate time.Time + switch lastQuarter { + case 1: + lastQuarterStartDate = time.Date(lastYear, 1, 1, 0, 0, 0, 0, time.Local) + case 2: + lastQuarterStartDate = time.Date(lastYear, 4, 1, 0, 0, 0, 0, time.Local) + case 3: + lastQuarterStartDate = time.Date(lastYear, 7, 1, 0, 0, 0, 0, time.Local) + case 4: + lastQuarterStartDate = time.Date(lastYear, 10, 1, 0, 0, 0, 0, time.Local) + default: + return 0.0 + } + lastQuarterEndDate := lastQuarterStartDate.AddDate(0, 3, 0) + + filter := bson.M{ + "tenantId": tenantID, + "createdAt": bson.M{ + "$gte": lastQuarterStartDate, + "$lt": lastQuarterEndDate, + }, + } + + var lastQuarterOrders []*entity.OrderBase + err := mongo.Find(ctx, filter, &lastQuarterOrders, consts.OrderCollection) + if err != nil || len(lastQuarterOrders) == 0 { + return 0.0 + } + + lastQuarterOrderCount := int64(len(lastQuarterOrders)) + if lastQuarterOrderCount == 0 { + return 0.0 + } + + // 计算环比增长率 + growth := (float64(currentOrders) - float64(lastQuarterOrderCount)) / float64(lastQuarterOrderCount) * 100 + return growth +} + +// calculateYearOverYearGrowth 计算同比增长率 +func (dao *OrderQuarterlyStatisticsDAO) calculateYearOverYearGrowth(ctx context.Context, tenantID int64, year int, currentOrders int64) float64 { + // 查询去年同期的订单数据 + lastYear := year - 1 + lastYearStartDate := time.Date(lastYear, 1, 1, 0, 0, 0, 0, time.Local) + lastYearEndDate := lastYearStartDate.AddDate(1, 0, 0) + + filter := bson.M{ + "tenantId": tenantID, + "createdAt": bson.M{ + "$gte": lastYearStartDate, + "$lt": lastYearEndDate, + }, + } + + var lastYearOrders []*entity.OrderBase + err := mongo.Find(ctx, filter, &lastYearOrders, consts.OrderCollection) + if err != nil || len(lastYearOrders) == 0 { + return 0.0 + } + + lastYearOrderCount := int64(len(lastYearOrders)) + if lastYearOrderCount == 0 { + return 0.0 + } + + // 计算同比增长率 + growth := (float64(currentOrders) - float64(lastYearOrderCount)) / float64(lastYearOrderCount) * 100 + return growth +} + +// Save 保存季度统计数据 +func (dao *OrderQuarterlyStatisticsDAO) Save(ctx context.Context, statistics *entity.OrderQuarterlyStatistics) error { + _, err := mongo.Insert(ctx, []interface{}{statistics}, consts.OrderQuarterlyStatisticsCollection) + return err +} + +// Update 更新季度统计数据 +func (dao *OrderQuarterlyStatisticsDAO) Update(ctx context.Context, statistics *entity.OrderQuarterlyStatistics) error { + filter := bson.M{ + "tenantId": statistics.TenantId, + "reportDate": statistics.ReportDate, + } + + update := bson.M{ + "$set": statistics, + } + + _, err := mongo.Update(ctx, filter, update, consts.OrderQuarterlyStatisticsCollection) + return err +} + +// GetByTenantAndDate 获取指定租户和日期的季度统计数据 +func (dao *OrderQuarterlyStatisticsDAO) GetByTenantAndDate(ctx context.Context, tenantID int64, reportDate time.Time) (*entity.OrderQuarterlyStatistics, error) { + var result entity.OrderQuarterlyStatistics + + filter := bson.M{ + "tenantId": tenantID, + "reportDate": reportDate, + } + + err := mongo.FindOne(ctx, filter, &result, consts.OrderQuarterlyStatisticsCollection) + if err != nil { + return nil, err + } + + return &result, nil +} diff --git a/dao/order_statistics_base_dao.go b/dao/order_statistics_base_dao.go new file mode 100644 index 0000000..57e312d --- /dev/null +++ b/dao/order_statistics_base_dao.go @@ -0,0 +1,201 @@ +package dao + +import ( + "context" + "fmt" + "time" + + "gitee.com/red-future---jilin-g/common/mongo" + "order/consts" + "order/model/entity" + + "go.mongodb.org/mongo-driver/v2/bson" +) + +// OrderStatisticsBaseDAO 订单统计基础DAO +type OrderStatisticsBaseDAO struct{} + +var OrderStatisticsBaseDAOInstance = &OrderStatisticsBaseDAO{} + +// OrderStats 订单统计结果 +type OrderStats struct { + TotalOrders int64 + TotalAmount int64 + TotalItems int64 + UniqueUsers map[int64]bool + UniqueAssets map[string]bool + AssetCounts map[string]int64 +} + +// GetOrderStats 获取订单统计信息 +func (dao *OrderStatisticsBaseDAO) GetOrderStats(ctx context.Context, tenantID int64, startDate, endDate time.Time) (*OrderStats, error) { + stats := &OrderStats{ + UniqueUsers: make(map[int64]bool), + UniqueAssets: make(map[string]bool), + AssetCounts: make(map[string]int64), + } + + // 查询所有状态的订单数据 + filter := bson.M{ + "tenantId": tenantID, + "createdAt": bson.M{ + "$gte": startDate, + "$lt": endDate, + }, + } + + // 查询待支付订单 + var pendingOrders []*entity.OrderPending + err := mongo.Find(ctx, filter, &pendingOrders, consts.OrderPendingCollection) + if err != nil { + return nil, fmt.Errorf("查询待支付订单数据失败: %v", err) + } + + // 查询已支付订单 + var paidOrders []*entity.OrderPaid + err = mongo.Find(ctx, filter, &paidOrders, consts.OrderPaidCollection) + if err != nil { + return nil, fmt.Errorf("查询已支付订单数据失败: %v", err) + } + + // 查询已发货订单 + var shippedOrders []*entity.OrderShipped + err = mongo.Find(ctx, filter, &shippedOrders, consts.OrderShippedCollection) + if err != nil { + return nil, fmt.Errorf("查询已发货订单数据失败: %v", err) + } + + // 查询已完成订单 + var completedOrders []*entity.OrderCompleted + err = mongo.Find(ctx, filter, &completedOrders, consts.OrderCompletedCollection) + if err != nil { + return nil, fmt.Errorf("查询已完成订单数据失败: %v", err) + } + + // 统计待支付订单 + for _, order := range pendingOrders { + stats.TotalOrders++ + stats.TotalAmount += order.TotalAmount + stats.UniqueUsers[order.UserID] = true + + if order.OrderItems != nil { + stats.TotalItems += int64(len(order.OrderItems)) + for _, item := range order.OrderItems { + stats.UniqueAssets[item.AssetID] = true + stats.AssetCounts[item.AssetID]++ + } + } + } + + // 统计已支付订单 + for _, order := range paidOrders { + stats.TotalOrders++ + stats.TotalAmount += order.TotalAmount + stats.UniqueUsers[order.UserID] = true + + if order.OrderItems != nil { + stats.TotalItems += int64(len(order.OrderItems)) + for _, item := range order.OrderItems { + stats.UniqueAssets[item.AssetID] = true + stats.AssetCounts[item.AssetID]++ + } + } + } + + // 统计已发货订单 + for _, order := range shippedOrders { + stats.TotalOrders++ + stats.TotalAmount += order.TotalAmount + stats.UniqueUsers[order.UserID] = true + + if order.OrderItems != nil { + stats.TotalItems += int64(len(order.OrderItems)) + for _, item := range order.OrderItems { + stats.UniqueAssets[item.AssetID] = true + stats.AssetCounts[item.AssetID]++ + } + } + } + + // 统计已完成订单 + for _, order := range completedOrders { + stats.TotalOrders++ + stats.TotalAmount += order.TotalAmount + stats.UniqueUsers[order.UserID] = true + + if order.OrderItems != nil { + stats.TotalItems += int64(len(order.OrderItems)) + for _, item := range order.OrderItems { + stats.UniqueAssets[item.AssetID] = true + stats.AssetCounts[item.AssetID]++ + } + } + } + + return stats, nil +} + +// FindTopAsset 找出热门资产 +func (dao *OrderStatisticsBaseDAO) FindTopAsset(assetCounts map[string]int64, orders []interface{}) (string, string, int64) { + if len(assetCounts) == 0 { + return "", "", 0 + } + + var topAssetID string + var maxCount int64 + + for assetID, count := range assetCounts { + if count > maxCount { + maxCount = count + topAssetID = assetID + } + } + + // 查找资产名称 + var topAssetName string + for _, order := range orders { + switch o := order.(type) { + case *entity.OrderPending: + if o.OrderItems != nil { + for _, item := range o.OrderItems { + if item.AssetID == topAssetID { + topAssetName = item.AssetName + break + } + } + } + case *entity.OrderPaid: + if o.OrderItems != nil { + for _, item := range o.OrderItems { + if item.AssetID == topAssetID { + topAssetName = item.AssetName + break + } + } + } + case *entity.OrderShipped: + if o.OrderItems != nil { + for _, item := range o.OrderItems { + if item.AssetID == topAssetID { + topAssetName = item.AssetName + break + } + } + } + case *entity.OrderCompleted: + if o.OrderItems != nil { + for _, item := range o.OrderItems { + if item.AssetID == topAssetID { + topAssetName = item.AssetName + break + } + } + } + } + if topAssetName != "" { + break + } + } + + return topAssetID, topAssetName, maxCount +} diff --git a/dao/order_yearly_statistics_dao.go b/dao/order_yearly_statistics_dao.go new file mode 100644 index 0000000..6c383d8 --- /dev/null +++ b/dao/order_yearly_statistics_dao.go @@ -0,0 +1,276 @@ +package dao + +import ( + "context" + "fmt" + "time" + + "gitee.com/red-future---jilin-g/common/do" + "gitee.com/red-future---jilin-g/common/mongo" + "order/consts" + "order/model/entity" + + "go.mongodb.org/mongo-driver/v2/bson" +) + +// OrderYearlyStatisticsDAO 订单年度统计DAO +type OrderYearlyStatisticsDAO struct{} + +var OrderYearlyStatisticsDAOInstance = &OrderYearlyStatisticsDAO{} + +// GenerateStatistics 使用Go代码生成年度统计数据 +func (dao *OrderYearlyStatisticsDAO) GenerateStatistics(ctx context.Context, tenantID int64, year int) (*entity.OrderYearlyStatistics, error) { + // 设置时间范围 + startDate := time.Date(year, 1, 1, 0, 0, 0, 0, time.Local) + endDate := startDate.AddDate(1, 0, 0) + + // 查询订单数据 + filter := bson.M{ + "tenantId": tenantID, + "createdAt": bson.M{ + "$gte": startDate, + "$lt": endDate, + }, + } + + var orders []*entity.OrderBase + err := mongo.Find(ctx, filter, &orders, consts.OrderCollection) + if err != nil { + return nil, fmt.Errorf("查询订单数据失败: %v", err) + } + + // 如果没有数据,创建空统计 + if len(orders) == 0 { + statistics := &entity.OrderYearlyStatistics{ + MongoBaseDO: do.MongoBaseDO{ + TenantId: tenantID, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + }, + ReportDate: startDate, + Period: startDate.Format("2006"), + Year: year, + } + return statistics, nil + } + + // 使用Go代码计算统计指标 + totalOrders := int64(len(orders)) + totalAmount := int64(0) + completedOrders := int64(0) + cancelledOrders := int64(0) + paidOrders := int64(0) + totalItems := int64(0) + + uniqueUsers := make(map[int64]bool) + uniqueAssets := make(map[string]bool) + assetCounts := make(map[string]int64) + quarterlyOrders := make([]int64, 4) + quarterlyAmounts := make([]int64, 4) + monthlyOrders := make([]int64, 12) + monthlyAmounts := make([]int64, 12) + + for _, order := range orders { + // 计算总金额 + totalAmount += order.TotalAmount + + // 注意:OrderBase结构体没有Status字段,状态统计需要通过其他方式获取 + + // 统计唯一用户 + uniqueUsers[order.UserID] = true + + // 统计商品信息 + if order.OrderItems != nil { + totalItems += int64(len(order.OrderItems)) + for _, item := range order.OrderItems { + uniqueAssets[item.AssetID] = true + assetCounts[item.AssetID]++ + } + } + + // 统计季度分布 + quarter := (int(order.CreatedAt.Month())-1)/3 + 1 + if quarter >= 1 && quarter <= 4 { + quarterlyOrders[quarter-1]++ + quarterlyAmounts[quarter-1] += order.TotalAmount + } + + // 统计月度分布 + month := int(order.CreatedAt.Month()) + if month >= 1 && month <= 12 { + monthlyOrders[month-1]++ + monthlyAmounts[month-1] += order.TotalAmount + } + } + + // 计算业务指标 + var averageOrderValue int64 + if totalOrders > 0 { + averageOrderValue = totalAmount / totalOrders + } + + var paymentRate, completionRate float64 + if totalOrders > 0 { + paymentRate = float64(paidOrders) / float64(totalOrders) * 100 + completionRate = float64(completedOrders) / float64(totalOrders) * 100 + } + + // 获取热门资产 + topAssetID, topAssetName, topAssetCount := dao.findTopAsset(assetCounts, orders) + + statistics := &entity.OrderYearlyStatistics{ + MongoBaseDO: do.MongoBaseDO{ + TenantId: tenantID, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + }, + ReportDate: startDate, + Period: startDate.Format("2006"), + Year: year, + TotalOrders: totalOrders, + CompletedOrders: completedOrders, + CancelledOrders: cancelledOrders, + PaidOrders: paidOrders, + TotalAmount: totalAmount, + PaidAmount: totalAmount, + NetAmount: totalAmount, + AverageOrderValue: averageOrderValue, + PaymentRate: paymentRate, + CompletionRate: completionRate, + UniqueUsers: int64(len(uniqueUsers)), + NewUsers: 0, + ReturningUsers: 0, + TotalItems: totalItems, + UniqueAssets: int64(len(uniqueAssets)), + TopAssetID: topAssetID, + TopAssetName: topAssetName, + TopAssetCount: topAssetCount, + QuarterlyOrders: quarterlyOrders, + QuarterlyAmounts: quarterlyAmounts, + PeakQuarter: dao.findPeakQuarter(quarterlyOrders), + MonthlyOrders: monthlyOrders, + MonthlyAmounts: monthlyAmounts, + YearOverYearGrowth: dao.calculateYearOverYearGrowth(ctx, tenantID, year, totalOrders), + } + + return statistics, nil +} + +// findTopAsset 找出热门资产 +func (dao *OrderYearlyStatisticsDAO) findTopAsset(assetCounts map[string]int64, orders []*entity.OrderBase) (string, string, int64) { + if len(assetCounts) == 0 { + return "", "", 0 + } + + var topAssetID string + var maxCount int64 + + for assetID, count := range assetCounts { + if count > maxCount { + maxCount = count + topAssetID = assetID + } + } + + // 查找资产名称 + var topAssetName string + for _, order := range orders { + if order.OrderItems != nil { + for _, item := range order.OrderItems { + if item.AssetID == topAssetID { + topAssetName = item.AssetName + break + } + } + } + if topAssetName != "" { + break + } + } + + return topAssetID, topAssetName, maxCount +} + +// findPeakQuarter 找出高峰季度 +func (dao *OrderYearlyStatisticsDAO) findPeakQuarter(quarterlyOrders []int64) int { + maxQuarter := 1 + maxCount := int64(0) + + for i, count := range quarterlyOrders { + if count > maxCount { + maxCount = count + maxQuarter = i + 1 + } + } + + return maxQuarter +} + +// calculateYearOverYearGrowth 计算同比增长率 +func (dao *OrderYearlyStatisticsDAO) calculateYearOverYearGrowth(ctx context.Context, tenantID int64, year int, currentOrders int64) float64 { + // 查询去年同期的订单数据 + lastYear := year - 1 + lastYearStartDate := time.Date(lastYear, 1, 1, 0, 0, 0, 0, time.Local) + lastYearEndDate := lastYearStartDate.AddDate(1, 0, 0) + + filter := bson.M{ + "tenantId": tenantID, + "createdAt": bson.M{ + "$gte": lastYearStartDate, + "$lt": lastYearEndDate, + }, + } + + var lastYearOrders []*entity.OrderBase + err := mongo.Find(ctx, filter, &lastYearOrders, consts.OrderCollection) + if err != nil || len(lastYearOrders) == 0 { + return 0.0 + } + + lastYearOrderCount := int64(len(lastYearOrders)) + if lastYearOrderCount == 0 { + return 0.0 + } + + // 计算同比增长率 + growth := (float64(currentOrders) - float64(lastYearOrderCount)) / float64(lastYearOrderCount) * 100 + return growth +} + +// Save 保存年度统计数据 +func (dao *OrderYearlyStatisticsDAO) Save(ctx context.Context, statistics *entity.OrderYearlyStatistics) error { + _, err := mongo.Insert(ctx, []interface{}{statistics}, consts.OrderYearlyStatisticsCollection) + return err +} + +// Update 更新年度统计数据 +func (dao *OrderYearlyStatisticsDAO) Update(ctx context.Context, statistics *entity.OrderYearlyStatistics) error { + filter := bson.M{ + "tenantId": statistics.TenantId, + "reportDate": statistics.ReportDate, + } + + update := bson.M{ + "$set": statistics, + } + + _, err := mongo.Update(ctx, filter, update, consts.OrderYearlyStatisticsCollection) + return err +} + +// GetByTenantAndDate 获取指定租户和日期的年度统计数据 +func (dao *OrderYearlyStatisticsDAO) GetByTenantAndDate(ctx context.Context, tenantID int64, reportDate time.Time) (*entity.OrderYearlyStatistics, error) { + var result entity.OrderYearlyStatistics + + filter := bson.M{ + "tenantId": tenantID, + "reportDate": reportDate, + } + + err := mongo.FindOne(ctx, filter, &result, consts.OrderYearlyStatisticsCollection) + if err != nil { + return nil, err + } + + return &result, nil +} diff --git a/dao/payment_dao.go b/dao/payment_dao.go index 93dc789..8487f16 100644 --- a/dao/payment_dao.go +++ b/dao/payment_dao.go @@ -41,10 +41,10 @@ func (d *paymentConfig) GetByTenantAndMethod(ctx context.Context, tenantID, payM // Update 更新支付配置 func (d *paymentConfig) Update(ctx context.Context, config *entity.PaymentConfig) error { - filter := bson.M{"_id": config.ID} + filter := bson.M{"_id": config.Id} update := bson.M{ "$set": bson.M{ - "tenant_id": config.TenantID, + "tenant_id": config.TenantId, "pay_method": config.PayMethod, "config_name": config.ConfigName, "description": config.Description, @@ -120,8 +120,8 @@ var PaymentRecord = new(paymentRecord) // Create 创建支付记录 func (d *paymentRecord) Create(ctx context.Context, record *entity.PaymentRecord) error { - record.CreatedAt = time.Now().Unix() - record.UpdatedAt = time.Now().Unix() + record.CreatedAt = time.Now() + record.UpdatedAt = time.Now() _, err := mongo.Insert(ctx, []interface{}{record}, consts.PaymentRecordCollection) return err @@ -165,8 +165,8 @@ var RefundRecord = new(refundRecord) // Create 创建退款记录 func (d *refundRecord) Create(ctx context.Context, record *entity.RefundRecord) error { - record.CreatedAt = time.Now().Unix() - record.UpdatedAt = time.Now().Unix() + record.CreatedAt = time.Now() + record.UpdatedAt = time.Now() _, err := mongo.Insert(ctx, []interface{}{record}, consts.RefundRecordCollection) return err diff --git a/go.mod b/go.mod index 36538c0..db81f66 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( github.com/gogf/gf/contrib/drivers/mysql/v2 v2.9.6 github.com/gogf/gf/contrib/nosql/redis/v2 v2.9.6 github.com/gogf/gf/v2 v2.9.6 - go.mongodb.org/mongo-driver v1.17.6 + go.mongodb.org/mongo-driver/v2 v2.4.0 ) require ( @@ -66,7 +66,6 @@ require ( github.com/xdg-go/scram v1.1.2 // indirect github.com/xdg-go/stringprep v1.0.4 // indirect github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect - go.mongodb.org/mongo-driver/v2 v2.4.0 // indirect go.opencensus.io v0.22.5 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/otel v1.38.0 // indirect diff --git a/go.sum b/go.sum index 18da46b..cf13752 100644 --- a/go.sum +++ b/go.sum @@ -273,8 +273,6 @@ github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfS github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -go.mongodb.org/mongo-driver v1.17.6 h1:87JUG1wZfWsr6rIz3ZmpH90rL5tea7O3IHuSwHUpsss= -go.mongodb.org/mongo-driver v1.17.6/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ= go.mongodb.org/mongo-driver/v2 v2.4.0 h1:Oq6BmUAAFTzMeh6AonuDlgZMuAuEiUxoAD1koK5MuFo= go.mongodb.org/mongo-driver/v2 v2.4.0/go.mod h1:jHeEDJHJq7tm6ZF45Issun9dbogjfnPySb1vXA7EeAI= go.opencensus.io v0.22.5 h1:dntmOdLpSpHlVqbW5Eay97DelsZHe+55D+xC6i0dDS0= diff --git a/main.go b/main.go index e28da02..f314ca6 100644 --- a/main.go +++ b/main.go @@ -9,12 +9,23 @@ import ( _ "github.com/gogf/gf/contrib/drivers/mysql/v2" _ "github.com/gogf/gf/contrib/nosql/redis/v2" "order/controller" + "order/scheduler" + + "github.com/gogf/gf/v2/frame/g" ) func main() { defer jaeger.ShutDown(context.Background()) + + // 启动订单统计定时任务调度器 + ctx := context.Background() + if err := scheduler.OrderStatisticsSchedulerInstance.StartScheduler(ctx); err != nil { + g.Log().Errorf(ctx, "启动订单统计定时任务失败: %v", err) + } + http.RouteRegister([]interface{}{ controller.Order, + controller.OrderStatistics, }) select {} } diff --git a/model/dto/order.go b/model/dto/order.go index 7cbd76a..fef70cf 100644 --- a/model/dto/order.go +++ b/model/dto/order.go @@ -98,7 +98,7 @@ type OrderDetail struct { ID string `json:"id"` // 订单ID TenantID string `json:"tenant_id"` // 租户ID OrderNo string `json:"order_no"` // 订单号 - UserID string `json:"user_id"` // 用户ID + UserID int64 `json:"user_id"` // 用户ID TotalAmount int64 `json:"total_amount"` // 订单总金额 PayAmount int64 `json:"pay_amount"` // 实付金额 Status string `json:"status"` // 订单状态 diff --git a/model/dto/order_statistics.go b/model/dto/order_statistics.go new file mode 100644 index 0000000..55ce7f6 --- /dev/null +++ b/model/dto/order_statistics.go @@ -0,0 +1,141 @@ +package dto + +import ( + "time" + + "github.com/gogf/gf/v2/frame/g" +) + +// GetOrderStatisticsReq 获取订单统计数据请求 +type GetOrderStatisticsReq struct { + g.Meta `path:"/statistics" method:"get" tags:"订单统计" summary:"获取订单统计数据" dc:"根据报表类型和日期获取订单统计数据"` + TenantID int64 `v:"required" json:"tenant_id" dc:"租户ID"` + ReportType string `v:"required|in:daily,monthly,quarterly,yearly" json:"report_type" dc:"报表类型"` + StartDate string `json:"start_date,omitempty" dc:"开始日期"` + EndDate string `json:"end_date,omitempty" dc:"结束日期"` +} + +// GetOrderStatisticsRes 获取订单统计数据响应 +type GetOrderStatisticsRes struct { + Statistics *OrderStatisticsDetail `json:"statistics"` +} + +// OrderStatisticsDetail 订单统计详情 +type OrderStatisticsDetail struct { + ReportType string `json:"report_type"` + ReportDate time.Time `json:"report_date"` + Period string `json:"period"` + + // 订单基础统计 + TotalOrders int64 `json:"total_orders"` + CompletedOrders int64 `json:"completed_orders"` + CancelledOrders int64 `json:"cancelled_orders"` + PaidOrders int64 `json:"paid_orders"` + + // 金额统计(单位:分) + TotalAmount int64 `json:"total_amount"` + PaidAmount int64 `json:"paid_amount"` + RefundAmount int64 `json:"refund_amount"` + NetAmount int64 `json:"net_amount"` + + // 业务指标 + AverageOrderValue int64 `json:"average_order_value"` + PaymentRate float64 `json:"payment_rate"` + CompletionRate float64 `json:"completion_rate"` + + // 用户统计 + UniqueUsers int64 `json:"unique_users"` + NewUsers int64 `json:"new_users"` + ReturningUsers int64 `json:"returning_users"` + + // 商品统计 + TotalItems int64 `json:"total_items"` + UniqueAssets int64 `json:"unique_assets"` + TopAssetID string `json:"top_asset_id,omitempty"` + TopAssetName string `json:"top_asset_name,omitempty"` + TopAssetCount int64 `json:"top_asset_count,omitempty"` + + // 时间统计 + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} + +// GetOrderStatisticsListReq 获取订单统计列表请求 +type GetOrderStatisticsListReq struct { + g.Meta `path:"/statistics/list" method:"get" tags:"订单统计" summary:"获取订单统计列表" dc:"分页获取订单统计列表"` + TenantID int64 `v:"required" json:"tenant_id" dc:"租户ID"` + ReportType string `v:"required|in:daily,monthly,quarterly,yearly" json:"report_type" dc:"报表类型"` + StartDate string `json:"start_date,omitempty" dc:"开始日期"` + EndDate string `json:"end_date,omitempty" dc:"结束日期"` + PageNum int `json:"page_num,omitempty" dc:"页码"` + PageSize int `json:"page_size,omitempty" dc:"每页数量"` +} + +// GetOrderStatisticsListRes 获取订单统计列表响应 +type GetOrderStatisticsListRes struct { + List []*OrderStatisticsDetail `json:"list"` + TotalCount int64 `json:"total_count"` + PageNum int `json:"page_num"` + PageSize int `json:"page_size"` +} + +// GenerateOrderStatisticsReq 生成订单统计数据请求 +type GenerateOrderStatisticsReq struct { + g.Meta `path:"/statistics/generate" method:"post" tags:"订单统计" summary:"生成订单统计数据" dc:"手动触发生成指定日期的统计数据"` + TenantID int64 `v:"required" json:"tenant_id" dc:"租户ID"` + ReportType string `v:"required|in:daily,monthly,quarterly,yearly" json:"report_type" dc:"报表类型"` + ReportDate string `json:"report_date,omitempty" dc:"统计日期,格式YYYY-MM-DD"` + Force bool `json:"force,omitempty" dc:"是否强制重新生成"` +} + +// GenerateOrderStatisticsRes 生成订单统计数据响应 +type GenerateOrderStatisticsRes struct { + Success bool `json:"success"` + Message string `json:"message"` +} + +// GetOrderTrendReq 获取订单趋势数据请求 +type GetOrderTrendReq struct { + g.Meta `path:"/statistics/trend" method:"get" tags:"订单统计" summary:"获取订单趋势数据" dc:"获取指定时间范围内的订单趋势数据"` + TenantID int64 `v:"required" json:"tenant_id" dc:"租户ID"` + StartDate string `v:"required" json:"start_date" dc:"开始日期,格式YYYY-MM-DD"` + EndDate string `v:"required" json:"end_date" dc:"结束日期,格式YYYY-MM-DD"` + TrendType string `v:"required|in:revenue,orders,users" json:"trend_type" dc:"趋势类型:revenue-收入趋势,orders-订单趋势,users-用户趋势"` +} + +// GetOrderTrendRes 获取订单趋势数据响应 +type GetOrderTrendRes struct { + TrendData []*TrendDataPoint `json:"trend_data"` +} + +// TrendDataPoint 趋势数据点 +type TrendDataPoint struct { + Date string `json:"date"` + Value int64 `json:"value"` + Label string `json:"label,omitempty"` +} + +// GetTopAssetsReq 获取热门资产请求 +type GetTopAssetsReq struct { + g.Meta `path:"/statistics/top-assets" method:"get" tags:"订单统计" summary:"获取热门资产" dc:"获取指定时间范围内的热门资产排行"` + TenantID int64 `v:"required" json:"tenant_id" dc:"租户ID"` + StartDate string `v:"required" json:"start_date" dc:"开始日期,格式YYYY-MM-DD"` + EndDate string `v:"required" json:"end_date" dc:"结束日期,格式YYYY-MM-DD"` + Limit int `json:"limit,omitempty" dc:"返回数量限制,默认10"` +} + +// GetTopAssetsRes 获取热门资产响应 +type GetTopAssetsRes struct { + TopAssets []*AssetRanking `json:"top_assets"` +} + +// AssetRanking 资产排行 +type AssetRanking struct { + AssetID string `json:"asset_id"` + AssetName string `json:"asset_name"` + AssetType string `json:"asset_type"` + OrderCount int64 `json:"order_count"` + TotalAmount int64 `json:"total_amount"` + Quantity int64 `json:"quantity"` + Rank int `json:"rank"` +} diff --git a/model/entity/order_base.go b/model/entity/order_base.go index ed5f736..6850124 100644 --- a/model/entity/order_base.go +++ b/model/entity/order_base.go @@ -1,8 +1,9 @@ package entity import ( - "go.mongodb.org/mongo-driver/v2/bson" "time" + + "gitee.com/red-future---jilin-g/common/do" ) // OrderBase 订单基础信息 @@ -12,18 +13,16 @@ import ( // 例如:orders_pending, orders_paid, orders_shipped, orders_completed, orders_cancelled type OrderBase struct { - ID bson.ObjectID `bson:"_id,omitempty" json:"id"` - TenantID string `bson:"tenant_id" json:"tenant_id"` // 租户ID - OrderNo string `bson:"order_no" json:"order_no"` // 订单号 - UserID string `bson:"user_id" json:"user_id"` // 用户ID - TotalAmount int64 `bson:"total_amount" json:"total_amount"` // 订单总金额(分) - PayAmount int64 `bson:"pay_amount" json:"pay_amount"` // 实付金额(分) - OrderType string `bson:"order_type" json:"order_type"` // 订单类型:normal-普通订单 - Subject string `bson:"subject" json:"subject"` // 订单标题 - Description string `bson:"description" json:"description"` // 订单描述 - CreatedAt time.Time `bson:"created_at" json:"created_at"` // 创建时间 - UpdatedAt time.Time `bson:"updated_at" json:"updated_at"` // 更新时间 - ExpiredAt *time.Time `bson:"expired_at,omitempty" json:"expired_at"` // 过期时间 + do.MongoBaseDO `bson:",inline"` + OrderNo string `bson:"order_no" json:"order_no"` // 订单号 + UserID int64 `bson:"user_id" json:"user_id"` // 用户ID + TotalAmount int64 `bson:"total_amount" json:"total_amount"` // 订单总金额(分) + PayAmount int64 `bson:"pay_amount" json:"pay_amount"` // 实付金额(分) + OrderType string `bson:"order_type" json:"order_type"` // 订单类型:normal-普通订单 + Subject string `bson:"subject" json:"subject"` // 订单标题 + Description string `bson:"description" json:"description"` // 订单描述 + OrderItems []OrderItem `bson:"order_items" json:"order_items"` // 订单商品项 + ExpiredAt *time.Time `bson:"expired_at,omitempty" json:"expired_at"` // 过期时间 } // OrderItem 订单商品项 diff --git a/model/entity/order_daily_statistics.go b/model/entity/order_daily_statistics.go new file mode 100644 index 0000000..e381948 --- /dev/null +++ b/model/entity/order_daily_statistics.go @@ -0,0 +1,47 @@ +package entity + +import ( + "time" + + "gitee.com/red-future---jilin-g/common/do" +) + +// OrderDailyStatistics 订单日统计数据实体 +type OrderDailyStatistics struct { + do.MongoBaseDO `bson:",inline"` + ReportDate time.Time `bson:"report_date" json:"report_date"` // 统计日期 + Period string `bson:"period" json:"period"` // 统计周期描述: 2024-01-01 + + // 订单基础统计 + TotalOrders int64 `bson:"total_orders" json:"total_orders"` // 总订单数 + CompletedOrders int64 `bson:"completed_orders" json:"completed_orders"` // 已完成订单数 + CancelledOrders int64 `bson:"cancelled_orders" json:"cancelled_orders"` // 已取消订单数 + PaidOrders int64 `bson:"paid_orders" json:"paid_orders"` // 已支付订单数 + + // 金额统计(单位:分) + TotalAmount int64 `bson:"total_amount" json:"total_amount"` // 订单总金额 + PaidAmount int64 `bson:"paid_amount" json:"paid_amount"` // 已支付金额 + RefundAmount int64 `bson:"refund_amount" json:"refund_amount"` // 退款金额 + NetAmount int64 `bson:"net_amount" json:"net_amount"` // 净收入金额 + + // 业务指标 + AverageOrderValue int64 `bson:"average_order_value" json:"average_order_value"` // 平均客单价 + PaymentRate float64 `bson:"payment_rate" json:"payment_rate"` // 支付成功率 + CompletionRate float64 `bson:"completion_rate" json:"completion_rate"` // 完成率 + + // 用户统计 + UniqueUsers int64 `bson:"unique_users" json:"unique_users"` // 唯一用户数 + NewUsers int64 `bson:"new_users" json:"new_users"` // 新用户数 + ReturningUsers int64 `bson:"returning_users" json:"returning_users"` // 回头用户数 + + // 商品统计 + TotalItems int64 `bson:"total_items" json:"total_items"` // 商品总数量 + UniqueAssets int64 `bson:"unique_assets" json:"unique_assets"` // 唯一资产数 + TopAssetID string `bson:"top_asset_id,omitempty" json:"top_asset_id"` // 热门资产ID + TopAssetName string `bson:"top_asset_name,omitempty" json:"top_asset_name"` // 热门资产名称 + TopAssetCount int64 `bson:"top_asset_count,omitempty" json:"top_asset_count"` // 热门资产销量 + + // 时段统计 + HourlyOrders []int64 `bson:"hourly_orders,omitempty" json:"hourly_orders"` // 24小时订单分布 + PeakHour int `bson:"peak_hour,omitempty" json:"peak_hour"` // 高峰时段 +} diff --git a/model/entity/order_monthly_statistics.go b/model/entity/order_monthly_statistics.go new file mode 100644 index 0000000..adc8d5f --- /dev/null +++ b/model/entity/order_monthly_statistics.go @@ -0,0 +1,51 @@ +package entity + +import ( + "time" + + "gitee.com/red-future---jilin-g/common/do" +) + +// OrderMonthlyStatistics 订单月统计数据实体 +type OrderMonthlyStatistics struct { + do.MongoBaseDO `bson:",inline"` + ReportDate time.Time `bson:"report_date" json:"report_date"` // 统计日期(月份第一天) + Period string `bson:"period" json:"period"` // 统计周期描述: 2024-01 + + // 订单基础统计 + TotalOrders int64 `bson:"total_orders" json:"total_orders"` // 总订单数 + CompletedOrders int64 `bson:"completed_orders" json:"completed_orders"` // 已完成订单数 + CancelledOrders int64 `bson:"cancelled_orders" json:"cancelled_orders"` // 已取消订单数 + PaidOrders int64 `bson:"paid_orders" json:"paid_orders"` // 已支付订单数 + + // 金额统计(单位:分) + TotalAmount int64 `bson:"total_amount" json:"total_amount"` // 订单总金额 + PaidAmount int64 `bson:"paid_amount" json:"paid_amount"` // 已支付金额 + RefundAmount int64 `bson:"refund_amount" json:"refund_amount"` // 退款金额 + NetAmount int64 `bson:"net_amount" json:"net_amount"` // 净收入金额 + + // 业务指标 + AverageOrderValue int64 `bson:"average_order_value" json:"average_order_value"` // 平均客单价 + PaymentRate float64 `bson:"payment_rate" json:"payment_rate"` // 支付成功率 + CompletionRate float64 `bson:"completion_rate" json:"completion_rate"` // 完成率 + + // 用户统计 + UniqueUsers int64 `bson:"unique_users" json:"unique_users"` // 唯一用户数 + NewUsers int64 `bson:"new_users" json:"new_users"` // 新用户数 + ReturningUsers int64 `bson:"returning_users" json:"returning_users"` // 回头用户数 + + // 商品统计 + TotalItems int64 `bson:"total_items" json:"total_items"` // 商品总数量 + UniqueAssets int64 `bson:"unique_assets" json:"unique_assets"` // 唯一资产数 + TopAssetID string `bson:"top_asset_id,omitempty" json:"top_asset_id"` // 热门资产ID + TopAssetName string `bson:"top_asset_name,omitempty" json:"top_asset_name"` // 热门资产名称 + TopAssetCount int64 `bson:"top_asset_count,omitempty" json:"top_asset_count"` // 热门资产销量 + + // 月度趋势 + DailyOrders []int64 `bson:"daily_orders,omitempty" json:"daily_orders"` // 每日订单数(31天) + DailyAmounts []int64 `bson:"daily_amounts,omitempty" json:"daily_amounts"` // 每日金额(31天) + PeakDay int `bson:"peak_day,omitempty" json:"peak_day"` // 高峰日 + + // 环比增长率 + MonthOverMonthGrowth float64 `bson:"month_over_month_growth" json:"month_over_month_growth"` // 环比增长率 +} diff --git a/model/entity/order_quarterly_statistics.go b/model/entity/order_quarterly_statistics.go new file mode 100644 index 0000000..3ae10b6 --- /dev/null +++ b/model/entity/order_quarterly_statistics.go @@ -0,0 +1,53 @@ +package entity + +import ( + "time" + + "gitee.com/red-future---jilin-g/common/do" +) + +// OrderQuarterlyStatistics 订单季度统计数据实体 +type OrderQuarterlyStatistics struct { + do.MongoBaseDO `bson:",inline"` + ReportDate time.Time `bson:"report_date" json:"report_date"` // 统计日期(季度第一天) + Period string `bson:"period" json:"period"` // 统计周期描述: 2024-Q1 + Quarter int `bson:"quarter" json:"quarter"` // 季度: 1,2,3,4 + + // 订单基础统计 + TotalOrders int64 `bson:"total_orders" json:"total_orders"` // 总订单数 + CompletedOrders int64 `bson:"completed_orders" json:"completed_orders"` // 已完成订单数 + CancelledOrders int64 `bson:"cancelled_orders" json:"cancelled_orders"` // 已取消订单数 + PaidOrders int64 `bson:"paid_orders" json:"paid_orders"` // 已支付订单数 + + // 金额统计(单位:分) + TotalAmount int64 `bson:"total_amount" json:"total_amount"` // 订单总金额 + PaidAmount int64 `bson:"paid_amount" json:"paid_amount"` // 已支付金额 + RefundAmount int64 `bson:"refund_amount" json:"refund_amount"` // 退款金额 + NetAmount int64 `bson:"net_amount" json:"net_amount"` // 净收入金额 + + // 业务指标 + AverageOrderValue int64 `bson:"average_order_value" json:"average_order_value"` // 平均客单价 + PaymentRate float64 `bson:"payment_rate" json:"payment_rate"` // 支付成功率 + CompletionRate float64 `bson:"completion_rate" json:"completion_rate"` // 完成率 + + // 用户统计 + UniqueUsers int64 `bson:"unique_users" json:"unique_users"` // 唯一用户数 + NewUsers int64 `bson:"new_users" json:"new_users"` // 新用户数 + ReturningUsers int64 `bson:"returning_users" json:"returning_users"` // 回头用户数 + + // 商品统计 + TotalItems int64 `bson:"total_items" json:"total_items"` // 商品总数量 + UniqueAssets int64 `bson:"unique_assets" json:"unique_assets"` // 唯一资产数 + TopAssetID string `bson:"top_asset_id,omitempty" json:"top_asset_id"` // 热门资产ID + TopAssetName string `bson:"top_asset_name,omitempty" json:"top_asset_name"` // 热门资产名称 + TopAssetCount int64 `bson:"top_asset_count,omitempty" json:"top_asset_count"` // 热门资产销量 + + // 月度分解 + MonthlyOrders []int64 `bson:"monthly_orders,omitempty" json:"monthly_orders"` // 月度订单数(3个月) + MonthlyAmounts []int64 `bson:"monthly_amounts,omitempty" json:"monthly_amounts"` // 月度金额(3个月) + PeakMonth int `bson:"peak_month,omitempty" json:"peak_month"` // 高峰月份 + + // 环比/同比增长率 + QuarterOverQuarterGrowth float64 `bson:"quarter_over_quarter_growth" json:"quarter_over_quarter_growth"` // 环比增长率 + YearOverYearGrowth float64 `bson:"year_over_year_growth" json:"year_over_year_growth"` // 同比增长率 +} diff --git a/model/entity/order_statistics.go b/model/entity/order_statistics.go new file mode 100644 index 0000000..0bf5d76 --- /dev/null +++ b/model/entity/order_statistics.go @@ -0,0 +1,63 @@ +package entity + +import ( + "time" + + "gitee.com/red-future---jilin-g/common/do" +) + +// OrderStatistics 订单统计数据实体 +type OrderStatistics struct { + do.MongoBaseDO `bson:",inline"` + ReportType string `bson:"report_type" json:"report_type"` // 报表类型: daily, monthly, quarterly, yearly + ReportDate time.Time `bson:"report_date" json:"report_date"` // 统计日期 + Period string `bson:"period" json:"period"` // 统计周期描述: 2024-01-01, 2024-01, 2024-Q1, 2024 + + // 订单基础统计 + TotalOrders int64 `bson:"total_orders" json:"total_orders"` // 总订单数 + CompletedOrders int64 `bson:"completed_orders" json:"completed_orders"` // 已完成订单数 + CancelledOrders int64 `bson:"cancelled_orders" json:"cancelled_orders"` // 已取消订单数 + PaidOrders int64 `bson:"paid_orders" json:"paid_orders"` // 已支付订单数 + + // 金额统计(单位:分) + TotalAmount int64 `bson:"total_amount" json:"total_amount"` // 订单总金额 + PaidAmount int64 `bson:"paid_amount" json:"paid_amount"` // 已支付金额 + RefundAmount int64 `bson:"refund_amount" json:"refund_amount"` // 退款金额 + NetAmount int64 `bson:"net_amount" json:"net_amount"` // 净收入金额 + + // 业务指标 + AverageOrderValue int64 `bson:"average_order_value" json:"average_order_value"` // 平均客单价 + PaymentRate float64 `bson:"payment_rate" json:"payment_rate"` // 支付成功率 + CompletionRate float64 `bson:"completion_rate" json:"completion_rate"` // 完成率 + + // 用户统计 + UniqueUsers int64 `bson:"unique_users" json:"unique_users"` // 唯一用户数 + NewUsers int64 `bson:"new_users" json:"new_users"` // 新用户数 + ReturningUsers int64 `bson:"returning_users" json:"returning_users"` // 回头用户数 + + // 商品统计 + TotalItems int64 `bson:"total_items" json:"total_items"` // 商品总数量 + UniqueAssets int64 `bson:"unique_assets" json:"unique_assets"` // 唯一资产数 + TopAssetID string `bson:"top_asset_id,omitempty" json:"top_asset_id"` // 热门资产ID + TopAssetName string `bson:"top_asset_name,omitempty" json:"top_asset_name"` // 热门资产名称 + TopAssetCount int64 `bson:"top_asset_count,omitempty" json:"top_asset_count"` // 热门资产销量 +} + +// OrderStatisticsUserStats 用户统计明细 +type OrderStatisticsUserStats struct { + UserID int64 `bson:"user_id" json:"user_id"` + OrderCount int64 `bson:"order_count" json:"order_count"` + TotalAmount int64 `bson:"total_amount" json:"total_amount"` + FirstOrder time.Time `bson:"first_order" json:"first_order"` + LastOrder time.Time `bson:"last_order" json:"last_order"` +} + +// OrderStatisticsAssetStats 资产统计明细 +type OrderStatisticsAssetStats struct { + AssetID string `bson:"asset_id" json:"asset_id"` + AssetName string `bson:"asset_name" json:"asset_name"` + AssetType string `bson:"asset_type" json:"asset_type"` + OrderCount int64 `bson:"order_count" json:"order_count"` + TotalAmount int64 `bson:"total_amount" json:"total_amount"` + Quantity int64 `bson:"quantity" json:"quantity"` +} diff --git a/model/entity/order_yearly_statistics.go b/model/entity/order_yearly_statistics.go new file mode 100644 index 0000000..198b727 --- /dev/null +++ b/model/entity/order_yearly_statistics.go @@ -0,0 +1,56 @@ +package entity + +import ( + "time" + + "gitee.com/red-future---jilin-g/common/do" +) + +// OrderYearlyStatistics 订单年度统计数据实体 +type OrderYearlyStatistics struct { + do.MongoBaseDO `bson:",inline"` + ReportDate time.Time `bson:"report_date" json:"report_date"` // 统计日期(年度第一天) + Period string `bson:"period" json:"period"` // 统计周期描述: 2024 + Year int `bson:"year" json:"year"` // 年份 + + // 订单基础统计 + TotalOrders int64 `bson:"total_orders" json:"total_orders"` // 总订单数 + CompletedOrders int64 `bson:"completed_orders" json:"completed_orders"` // 已完成订单数 + CancelledOrders int64 `bson:"cancelled_orders" json:"cancelled_orders"` // 已取消订单数 + PaidOrders int64 `bson:"paid_orders" json:"paid_orders"` // 已支付订单数 + + // 金额统计(单位:分) + TotalAmount int64 `bson:"total_amount" json:"total_amount"` // 订单总金额 + PaidAmount int64 `bson:"paid_amount" json:"paid_amount"` // 已支付金额 + RefundAmount int64 `bson:"refund_amount" json:"refund_amount"` // 退款金额 + NetAmount int64 `bson:"net_amount" json:"net_amount"` // 净收入金额 + + // 业务指标 + AverageOrderValue int64 `bson:"average_order_value" json:"average_order_value"` // 平均客单价 + PaymentRate float64 `bson:"payment_rate" json:"payment_rate"` // 支付成功率 + CompletionRate float64 `bson:"completion_rate" json:"completion_rate"` // 完成率 + + // 用户统计 + UniqueUsers int64 `bson:"unique_users" json:"unique_users"` // 唯一用户数 + NewUsers int64 `bson:"new_users" json:"new_users"` // 新用户数 + ReturningUsers int64 `bson:"returning_users" json:"returning_users"` // 回头用户数 + + // 商品统计 + TotalItems int64 `bson:"total_items" json:"total_items"` // 商品总数量 + UniqueAssets int64 `bson:"unique_assets" json:"unique_assets"` // 唯一资产数 + TopAssetID string `bson:"top_asset_id,omitempty" json:"top_asset_id"` // 热门资产ID + TopAssetName string `bson:"top_asset_name,omitempty" json:"top_asset_name"` // 热门资产名称 + TopAssetCount int64 `bson:"top_asset_count,omitempty" json:"top_asset_count"` // 热门资产销量 + + // 季度分解 + QuarterlyOrders []int64 `bson:"quarterly_orders,omitempty" json:"quarterly_orders"` // 季度订单数(4个季度) + QuarterlyAmounts []int64 `bson:"quarterly_amounts,omitempty" json:"quarterly_amounts"` // 季度金额(4个季度) + PeakQuarter int `bson:"peak_quarter,omitempty" json:"peak_quarter"` // 高峰季度 + + // 月度分解 + MonthlyOrders []int64 `bson:"monthly_orders,omitempty" json:"monthly_orders"` // 月度订单数(12个月) + MonthlyAmounts []int64 `bson:"monthly_amounts,omitempty" json:"monthly_amounts"` // 月度金额(12个月) + + // 同比增长率 + YearOverYearGrowth float64 `bson:"year_over_year_growth" json:"year_over_year_growth"` // 同比增长率 +} diff --git a/model/entity/payment_config.go b/model/entity/payment_config.go index 65025af..cf7c3c1 100644 --- a/model/entity/payment_config.go +++ b/model/entity/payment_config.go @@ -2,6 +2,8 @@ package entity import ( "go.mongodb.org/mongo-driver/v2/bson" + + "gitee.com/red-future---jilin-g/common/do" ) // PaymentConfig 支付配置 @@ -9,12 +11,11 @@ import ( // 支持微信支付和支付宝支付 type PaymentConfig struct { - ID bson.ObjectID `bson:"_id,omitempty" json:"id"` - TenantID string `bson:"tenant_id" json:"tenant_id"` // 租户ID - PayMethod string `bson:"pay_method" json:"pay_method"` // 支付方式:wechat/alipay - ConfigName string `bson:"config_name" json:"config_name"` // 配置名称 - Description string `bson:"description" json:"description"` // 配置描述 - Enabled bool `bson:"enabled" json:"enabled"` // 是否启用 + do.MongoBaseDO `bson:",inline"` + PayMethod string `bson:"pay_method" json:"pay_method"` // 支付方式:wechat/alipay + ConfigName string `bson:"config_name" json:"config_name"` // 配置名称 + Description string `bson:"description" json:"description"` // 配置描述 + Enabled bool `bson:"enabled" json:"enabled"` // 是否启用 // 通用配置 AppID string `bson:"app_id" json:"app_id"` // 应用ID @@ -46,37 +47,31 @@ type PaymentConfig struct { // 记录每次支付操作的结果 type PaymentRecord struct { - ID bson.ObjectID `bson:"_id,omitempty" json:"id"` - TenantID string `bson:"tenant_id" json:"tenant_id"` // 租户ID - OrderID bson.ObjectID `bson:"order_id" json:"order_id"` // 订单ID - OrderNo string `bson:"order_no" json:"order_no"` // 订单号 - PayMethod string `bson:"pay_method" json:"pay_method"` // 支付方式 - PayType string `bson:"pay_type" json:"pay_type"` // 支付类型 - Amount int64 `bson:"amount" json:"amount"` // 支付金额(分) - TransactionID string `bson:"transaction_id" json:"transaction_id"` // 支付平台交易号 - OutTradeNo string `bson:"out_trade_no" json:"out_trade_no"` // 商户订单号 - TradeNo string `bson:"trade_no" json:"trade_no"` // 交易号 - PrepayID string `bson:"prepay_id,omitempty" json:"prepay_id"` // 预支付ID - Status string `bson:"status" json:"status"` // 支付状态:success/failed - ErrorMsg string `bson:"error_msg,omitempty" json:"error_msg"` // 错误信息 - CreatedAt int64 `bson:"created_at" json:"created_at"` // 创建时间 - UpdatedAt int64 `bson:"updated_at" json:"updated_at"` // 更新时间 + do.MongoBaseDO `bson:",inline"` + OrderID bson.ObjectID `bson:"order_id" json:"order_id"` // 订单ID + OrderNo string `bson:"order_no" json:"order_no"` // 订单号 + PayMethod string `bson:"pay_method" json:"pay_method"` // 支付方式 + PayType string `bson:"pay_type" json:"pay_type"` // 支付类型 + Amount int64 `bson:"amount" json:"amount"` // 支付金额(分) + TransactionID string `bson:"transaction_id" json:"transaction_id"` // 支付平台交易号 + OutTradeNo string `bson:"out_trade_no" json:"out_trade_no"` // 商户订单号 + TradeNo string `bson:"trade_no" json:"trade_no"` // 交易号 + PrepayID string `bson:"prepay_id,omitempty" json:"prepay_id"` // 预支付ID + Status string `bson:"status" json:"status"` // 支付状态:success/failed + ErrorMsg string `bson:"error_msg,omitempty" json:"error_msg"` // 错误信息 } // RefundRecord 退款记录 // 记录每次退款操作的结果 type RefundRecord struct { - ID bson.ObjectID `bson:"_id,omitempty" json:"id"` - TenantID string `bson:"tenant_id" json:"tenant_id"` // 租户ID - OrderID bson.ObjectID `bson:"order_id" json:"order_id"` // 订单ID - OrderNo string `bson:"order_no" json:"order_no"` // 订单号 - RefundNo string `bson:"refund_no" json:"refund_no"` // 退款单号 - RefundID string `bson:"refund_id" json:"refund_id"` // 退款ID - RefundAmount int64 `bson:"refund_amount" json:"refund_amount"` // 退款金额(分) - Reason string `bson:"reason" json:"reason"` // 退款原因 - Status string `bson:"status" json:"status"` // 退款状态:success/failed - ErrorMsg string `bson:"error_msg,omitempty" json:"error_msg"` // 错误信息 - CreatedAt int64 `bson:"created_at" json:"created_at"` // 创建时间 - UpdatedAt int64 `bson:"updated_at" json:"updated_at"` // 更新时间 + do.MongoBaseDO `bson:",inline"` + OrderID bson.ObjectID `bson:"order_id" json:"order_id"` // 订单ID + OrderNo string `bson:"order_no" json:"order_no"` // 订单号 + RefundNo string `bson:"refund_no" json:"refund_no"` // 退款单号 + RefundID string `bson:"refund_id" json:"refund_id"` // 退款ID + RefundAmount int64 `bson:"refund_amount" json:"refund_amount"` // 退款金额(分) + Reason string `bson:"reason" json:"reason"` // 退款原因 + Status string `bson:"status" json:"status"` // 退款状态:success/failed + ErrorMsg string `bson:"error_msg,omitempty" json:"error_msg"` // 错误信息 } diff --git a/scheduler/order_statistics_scheduler.go b/scheduler/order_statistics_scheduler.go new file mode 100644 index 0000000..5990a24 --- /dev/null +++ b/scheduler/order_statistics_scheduler.go @@ -0,0 +1,462 @@ +package scheduler + +import ( + "context" + "sync" + "time" + + "order/service" + + "github.com/gogf/gf/v2/frame/g" +) + +// OrderStatisticsScheduler 订单统计定时任务调度器 +type OrderStatisticsScheduler struct{} + +var OrderStatisticsSchedulerInstance = &OrderStatisticsScheduler{} +var schedulerLock sync.Mutex +var isSchedulerRunning bool + +// StartScheduler 启动定时任务调度器(分布式安全) +func (s *OrderStatisticsScheduler) StartScheduler(ctx context.Context) error { + schedulerLock.Lock() + defer schedulerLock.Unlock() + + // 检查是否已经有调度器在运行(分布式部署时避免重复执行) + if isSchedulerRunning { + g.Log().Info(ctx, "订单统计定时任务调度器已在运行") + return nil + } + + // 尝试获取分布式锁 + if !s.acquireDistributedLock(ctx) { + g.Log().Info(ctx, "其他节点正在运行订单统计定时任务,当前节点跳过") + return nil + } + + isSchedulerRunning = true + + // 启动锁续期任务 + go s.startLockRenewal(ctx) + + // 启动日报表生成任务(每天凌晨3点执行) + go s.startDailyReportScheduler(ctx) + + // 启动月报表生成任务(每月1日凌晨4点执行) + go s.startMonthlyReportScheduler(ctx) + + // 启动季度报表生成任务(每季度首月5日凌晨5点执行) + go s.startQuarterlyReportScheduler(ctx) + + // 启动年报表生成任务(每年1月10日凌晨6点执行) + go s.startYearlyReportScheduler(ctx) + + g.Log().Info(ctx, "订单统计定时任务调度器已启动") + return nil +} + +// acquireDistributedLock 获取分布式锁 +func (s *OrderStatisticsScheduler) acquireDistributedLock(ctx context.Context) bool { + lockKey := "order_statistics_scheduler_lock" + + // 尝试设置锁,过期时间30秒 + result, err := g.Redis().SetNX(ctx, lockKey, "locked", time.Second*30) + if err != nil { + g.Log().Errorf(ctx, "获取分布式锁失败: %v", err) + return false + } + + return result.Bool() +} + +// renewDistributedLock 续期分布式锁 +func (s *OrderStatisticsScheduler) renewDistributedLock(ctx context.Context) bool { + lockKey := "order_statistics_scheduler_lock" + + // 续期30秒 + _, err := g.Redis().Expire(ctx, lockKey, time.Second*30) + if err != nil { + g.Log().Errorf(ctx, "续期分布式锁失败: %v", err) + return false + } + + return true +} + +// startLockRenewal 启动锁续期任务 +func (s *OrderStatisticsScheduler) startLockRenewal(ctx context.Context) { + ticker := time.NewTicker(20 * time.Second) // 每20秒续期一次 + defer ticker.Stop() + + for { + select { + case <-ticker.C: + if !s.renewDistributedLock(ctx) { + g.Log().Error(ctx, "分布式锁续期失败,停止调度器") + schedulerLock.Lock() + isSchedulerRunning = false + schedulerLock.Unlock() + return + } + case <-ctx.Done(): + // 释放分布式锁 + s.releaseDistributedLock(ctx) + return + } + } +} + +// releaseDistributedLock 释放分布式锁 +func (s *OrderStatisticsScheduler) releaseDistributedLock(ctx context.Context) { + lockKey := "order_statistics_scheduler_lock" + _, err := g.Redis().Do(ctx, "DEL", lockKey) + if err != nil { + g.Log().Errorf(ctx, "释放分布式锁失败: %v", err) + } +} + +// acquireTaskLock 获取任务级分布式锁 +func (s *OrderStatisticsScheduler) acquireTaskLock(ctx context.Context, lockKey string) bool { + // 尝试设置锁,过期时间10分钟 + result, err := g.Redis().SetNX(ctx, lockKey, "locked", time.Minute*10) + if err != nil { + g.Log().Errorf(ctx, "获取任务锁失败: %v", err) + return false + } + + return result.Bool() +} + +// releaseTaskLock 释放任务级分布式锁 +func (s *OrderStatisticsScheduler) releaseTaskLock(ctx context.Context, lockKey string) { + _, err := g.Redis().Do(ctx, "DEL", lockKey) + if err != nil { + g.Log().Errorf(ctx, "释放任务锁失败: %v", err) + } +} + +// startDailyReportScheduler 日报表定时任务 +func (s *OrderStatisticsScheduler) startDailyReportScheduler(ctx context.Context) { + // 计算到凌晨3点的时间 + now := time.Now() + next := time.Date(now.Year(), now.Month(), now.Day()+1, 3, 0, 0, 0, time.Local) + duration := next.Sub(now) + + // 等待到凌晨3点 + time.Sleep(duration) + + ticker := time.NewTicker(24 * time.Hour) + defer ticker.Stop() + + // 立即执行一次昨天的日报表生成 + go s.generateYesterdayDailyReport(ctx) + + for { + select { + case <-ticker.C: + // 生成昨天的日报表 + s.generateYesterdayDailyReport(ctx) + case <-ctx.Done(): + return + } + } +} + +// startMonthlyReportScheduler 月报表定时任务 +func (s *OrderStatisticsScheduler) startMonthlyReportScheduler(ctx context.Context) { + // 计算到下个月1日凌晨4点的时间 + now := time.Now() + next := time.Date(now.Year(), now.Month()+1, 1, 4, 0, 0, 0, time.Local) + duration := next.Sub(now) + + // 等待到下个月1日凌晨4点 + time.Sleep(duration) + + ticker := time.NewTicker(24 * time.Hour) + defer ticker.Stop() + + for { + select { + case <-ticker.C: + // 检查是否是每月1日,如果是则生成上个月的月报表 + if time.Now().Day() == 1 { + go s.generateLastMonthReport(ctx) + } + case <-ctx.Done(): + return + } + } +} + +// startQuarterlyReportScheduler 季度报表定时任务 +func (s *OrderStatisticsScheduler) startQuarterlyReportScheduler(ctx context.Context) { + // 计算到下个季度第一天凌晨5点的时间 + now := time.Now() + nextQuarter := s.getNextQuarterFirstDay(now) + next := time.Date(nextQuarter.Year(), nextQuarter.Month(), nextQuarter.Day(), 5, 0, 0, 0, time.Local) + duration := next.Sub(now) + + // 等待到下个季度第一天凌晨5点 + time.Sleep(duration) + + ticker := time.NewTicker(24 * time.Hour) + defer ticker.Stop() + + for { + select { + case <-ticker.C: + // 检查是否是季度第一天,如果是则生成上个季度的季度报表 + if s.isQuarterFirstDay() { + go s.generateLastQuarterReport(ctx) + } + case <-ctx.Done(): + return + } + } +} + +// startYearlyReportScheduler 年报表定时任务 +func (s *OrderStatisticsScheduler) startYearlyReportScheduler(ctx context.Context) { + // 计算到明年1月1日凌晨6点的时间 + now := time.Now() + next := time.Date(now.Year()+1, time.January, 1, 6, 0, 0, 0, time.Local) + duration := next.Sub(now) + + // 等待到明年1月1日凌晨6点 + time.Sleep(duration) + + ticker := time.NewTicker(24 * time.Hour) + defer ticker.Stop() + + for { + select { + case <-ticker.C: + // 检查是否是1月1日,如果是则生成上一年的年报表 + if time.Now().Month() == time.January && time.Now().Day() == 1 { + go s.generateLastYearReport(ctx) + } + case <-ctx.Done(): + return + } + } +} + +// generateYesterdayDailyReport 生成昨天的日报表 +func (s *OrderStatisticsScheduler) generateYesterdayDailyReport(ctx context.Context) { + yesterday := time.Now().AddDate(0, 0, -1) + + g.Log().Infof(ctx, "开始生成所有租户的日统计数据: %s", yesterday.Format("2006-01-02")) + + // 获取所有租户ID + tenantIDs, err := s.getAllTenants(ctx) + if err != nil { + g.Log().Errorf(ctx, "获取租户列表失败: %v", err) + return + } + + // 并发处理每个租户的日统计 + for _, tenantID := range tenantIDs { + go s.generateTenantDailyReport(ctx, tenantID, yesterday) + } +} + +// generateTenantDailyReport 生成指定租户的日报表 +func (s *OrderStatisticsScheduler) generateTenantDailyReport(ctx context.Context, tenantID int64, date time.Time) { + lockKey := g.NewVar() + lockKey.Setf("order_stats_daily_%d_%s", tenantID, date.Format("2006-01-02")) + + // 获取任务锁 + if !s.acquireTaskLock(ctx, lockKey.String()) { + g.Log().Infof(ctx, "租户 %d 的日统计任务正在执行,跳过", tenantID) + return + } + defer s.releaseTaskLock(ctx, lockKey.String()) + + g.Log().Infof(ctx, "开始生成租户 %d 的日统计: %s", tenantID, date.Format("2006-01-02")) + + err := service.OrderStatistics.GenerateDailyStatistics(ctx, tenantID, date, false) + if err != nil { + g.Log().Errorf(ctx, "生成租户 %d 日统计失败: %v", tenantID, err) + return + } + + g.Log().Infof(ctx, "租户 %d 日统计生成成功: %s", tenantID, date.Format("2006-01-02")) +} + +// generateLastMonthReport 生成上个月的月报表 +func (s *OrderStatisticsScheduler) generateLastMonthReport(ctx context.Context) { + lastMonth := time.Now().AddDate(0, -1, 0) + year := lastMonth.Year() + month := int(lastMonth.Month()) + + g.Log().Infof(ctx, "开始生成所有租户的月统计数据: %d年%d月", year, month) + + // 获取所有租户ID + tenantIDs, err := s.getAllTenants(ctx) + if err != nil { + g.Log().Errorf(ctx, "获取租户列表失败: %v", err) + return + } + + // 并发处理每个租户的月统计 + for _, tenantID := range tenantIDs { + go s.generateTenantMonthlyReport(ctx, tenantID, year, month) + } +} + +// generateTenantMonthlyReport 生成指定租户的月报表 +func (s *OrderStatisticsScheduler) generateTenantMonthlyReport(ctx context.Context, tenantID int64, year int, month int) { + lockKey := g.NewVar() + lockKey.Setf("order_stats_monthly_%d_%d_%d", tenantID, year, month) + + // 获取任务锁 + if !s.acquireTaskLock(ctx, lockKey.String()) { + g.Log().Infof(ctx, "租户 %d 的月统计任务正在执行,跳过", tenantID) + return + } + defer s.releaseTaskLock(ctx, lockKey.String()) + + g.Log().Infof(ctx, "开始生成租户 %d 的月统计: %d年%d月", tenantID, year, month) + + err := service.OrderStatistics.GenerateMonthlyStatistics(ctx, tenantID, year, month, false) + if err != nil { + g.Log().Errorf(ctx, "生成租户 %d 月统计失败: %v", tenantID, err) + return + } + + g.Log().Infof(ctx, "租户 %d 月统计生成成功: %d年%d月", tenantID, year, month) +} + +// generateLastQuarterReport 生成上个季度的季度报表 +func (s *OrderStatisticsScheduler) generateLastQuarterReport(ctx context.Context) { + lastQuarter := time.Now().AddDate(0, -3, 0) + year := lastQuarter.Year() + quarter := s.getQuarter(lastQuarter.Month()) + + g.Log().Infof(ctx, "开始生成所有租户的季度统计数据: %d年第%d季度", year, quarter) + + // 获取所有租户ID + tenantIDs, err := s.getAllTenants(ctx) + if err != nil { + g.Log().Errorf(ctx, "获取租户列表失败: %v", err) + return + } + + // 并发处理每个租户的季度统计 + for _, tenantID := range tenantIDs { + go s.generateTenantQuarterlyReport(ctx, tenantID, year, quarter) + } +} + +// generateTenantQuarterlyReport 生成指定租户的季度报表 +func (s *OrderStatisticsScheduler) generateTenantQuarterlyReport(ctx context.Context, tenantID int64, year int, quarter int) { + lockKey := g.NewVar() + lockKey.Setf("order_stats_quarterly_%d_%d_%d", tenantID, year, quarter) + + // 获取任务锁 + if !s.acquireTaskLock(ctx, lockKey.String()) { + g.Log().Infof(ctx, "租户 %d 的季度统计任务正在执行,跳过", tenantID) + return + } + defer s.releaseTaskLock(ctx, lockKey.String()) + + g.Log().Infof(ctx, "开始生成租户 %d 的季度统计: %d年第%d季度", tenantID, year, quarter) + + err := service.OrderStatistics.GenerateQuarterlyStatistics(ctx, tenantID, year, quarter, false) + if err != nil { + g.Log().Errorf(ctx, "生成租户 %d 季度统计失败: %v", tenantID, err) + return + } + + g.Log().Infof(ctx, "租户 %d 季度统计生成成功: %d年第%d季度", tenantID, year, quarter) +} + +// generateLastYearReport 生成上一年的年报表 +func (s *OrderStatisticsScheduler) generateLastYearReport(ctx context.Context) { + lastYear := time.Now().Year() - 1 + + g.Log().Infof(ctx, "开始生成所有租户的年统计数据: %d年", lastYear) + + // 获取所有租户ID + tenantIDs, err := s.getAllTenants(ctx) + if err != nil { + g.Log().Errorf(ctx, "获取租户列表失败: %v", err) + return + } + + // 并发处理每个租户的年统计 + for _, tenantID := range tenantIDs { + go s.generateTenantYearlyReport(ctx, tenantID, lastYear) + } +} + +// generateTenantYearlyReport 生成指定租户的年报表 +func (s *OrderStatisticsScheduler) generateTenantYearlyReport(ctx context.Context, tenantID int64, year int) { + lockKey := g.NewVar() + lockKey.Setf("order_stats_yearly_%d_%d", tenantID, year) + + // 获取任务锁 + if !s.acquireTaskLock(ctx, lockKey.String()) { + g.Log().Infof(ctx, "租户 %d 的年统计任务正在执行,跳过", tenantID) + return + } + defer s.releaseTaskLock(ctx, lockKey.String()) + + g.Log().Infof(ctx, "开始生成租户 %d 的年统计: %d年", tenantID, year) + + err := service.OrderStatistics.GenerateYearlyStatistics(ctx, tenantID, year, false) + if err != nil { + g.Log().Errorf(ctx, "生成租户 %d 年统计失败: %v", tenantID, err) + return + } + + g.Log().Infof(ctx, "租户 %d 年统计生成成功: %d年", tenantID, year) +} + +// getNextQuarterFirstDay 获取下个季度的第一天 +func (s *OrderStatisticsScheduler) getNextQuarterFirstDay(now time.Time) time.Time { + quarter := s.getQuarter(now.Month()) + if quarter == 4 { + return time.Date(now.Year()+1, time.January, 1, 0, 0, 0, 0, now.Location()) + } + return time.Date(now.Year(), time.Month((quarter*3)+1), 1, 0, 0, 0, 0, now.Location()) +} + +// getQuarter 获取月份对应的季度 +func (s *OrderStatisticsScheduler) getQuarter(month time.Month) int { + switch { + case month >= 1 && month <= 3: + return 1 + case month >= 4 && month <= 6: + return 2 + case month >= 7 && month <= 9: + return 3 + default: + return 4 + } +} + +// isQuarterFirstDay 检查今天是否是季度的第一天 +func (s *OrderStatisticsScheduler) isQuarterFirstDay() bool { + now := time.Now() + quarter := s.getQuarter(now.Month()) + switch quarter { + case 1: + return now.Day() == 1 && now.Month() == time.January + case 2: + return now.Day() == 1 && now.Month() == time.April + case 3: + return now.Day() == 1 && now.Month() == time.July + case 4: + return now.Day() == 1 && now.Month() == time.October + default: + return false + } +} + +// getAllTenants 获取所有租户ID(这里需要根据实际业务实现) +func (s *OrderStatisticsScheduler) getAllTenants(ctx context.Context) ([]int64, error) { + // 这里应该从实际的租户管理服务获取租户列表 + // 暂时返回一个默认的租户ID,实际使用时需要替换为真实的租户获取逻辑 + return []int64{1, 2, 3}, nil +} diff --git a/service/order.go b/service/order.go index cf0a5a0..36311e3 100644 --- a/service/order.go +++ b/service/order.go @@ -7,6 +7,7 @@ import ( "math/rand" "time" + "github.com/gogf/gf/v2/util/gconv" "go.mongodb.org/mongo-driver/v2/bson" "order/consts" "order/dao" @@ -233,8 +234,8 @@ func (s *order) QueryOrder(ctx context.Context, req *dto.QueryOrderReq) (*dto.Qu // convertPendingOrderToDetail 转换待支付订单为详情 func (s *order) convertPendingOrderToDetail(order *entity.OrderPending) dto.OrderDetail { return dto.OrderDetail{ - ID: order.ID.Hex(), - TenantID: order.TenantID, + ID: order.Id.Hex(), + TenantID: gconv.String(order.TenantId), OrderNo: order.OrderNo, UserID: order.UserID, TotalAmount: order.TotalAmount, @@ -271,7 +272,7 @@ func (s *order) convertPendingOrderToDetail(order *entity.OrderPending) dto.Orde func (s *order) convertPaidOrderToDetail(order *entity.OrderPaid) dto.OrderDetail { return dto.OrderDetail{ ID: order.ID.Hex(), - TenantID: order.TenantID, + TenantID: order.TenantId, OrderNo: order.OrderNo, UserID: order.UserID, TotalAmount: order.TotalAmount, diff --git a/service/order_statistics.go b/service/order_statistics.go new file mode 100644 index 0000000..88f3321 --- /dev/null +++ b/service/order_statistics.go @@ -0,0 +1,439 @@ +package service + +import ( + "context" + "fmt" + "time" + + "order/dao" + "order/model/dto" + + "github.com/gogf/gf/v2/frame/g" +) + +// OrderStatistics 订单统计服务 +type orderStatistics struct{} + +// OrderStatistics 订单统计服务实例 +var OrderStatistics = new(orderStatistics) + +// GenerateDailyStatistics 生成日统计数据 +func (s *orderStatistics) GenerateDailyStatistics(ctx context.Context, tenantID int64, date time.Time, force bool) error { + g.Log().Infof(ctx, "开始生成租户 %d 在 %s 的日统计数据", tenantID, date.Format("2006-01-02")) + + // 检查是否已存在统计数据 + if !force { + existing, err := dao.OrderDailyStatisticsDAOInstance.GetByTenantAndDate(ctx, tenantID, date) + if err == nil && existing != nil { + g.Log().Infof(ctx, "租户 %d 在 %s 的日统计数据已存在,跳过生成", tenantID, date.Format("2006-01-02")) + return nil + } + } + + // 调用DAO层生成统计数据 + statistics, err := dao.OrderDailyStatisticsDAOInstance.GenerateStatistics(ctx, tenantID, date) + if err != nil { + return fmt.Errorf("生成日统计数据失败: %v", err) + } + + // 保存统计数据 + err = dao.OrderDailyStatisticsDAOInstance.Update(ctx, statistics) + if err != nil { + return fmt.Errorf("保存日统计数据失败: %v", err) + } + + g.Log().Infof(ctx, "租户 %d 在 %s 的日统计数据生成成功", tenantID, date.Format("2006-01-02")) + return nil +} + +// GenerateMonthlyStatistics 生成月统计数据 +func (s *orderStatistics) GenerateMonthlyStatistics(ctx context.Context, tenantID int64, year int, month int, force bool) error { + g.Log().Infof(ctx, "开始生成租户 %d 在 %d年%d月 的月统计数据", tenantID, year, month) + + // 计算月份的起止日期 + startDate := time.Date(year, time.Month(month), 1, 0, 0, 0, 0, time.Local) + + // 检查是否已存在统计数据 + if !force { + existing, err := dao.OrderMonthlyStatisticsDAOInstance.GetByTenantAndDate(ctx, tenantID, startDate) + if err == nil && existing != nil { + g.Log().Infof(ctx, "租户 %d 在 %d年%d月 的月统计数据已存在,跳过生成", tenantID, year, month) + return nil + } + } + + // 调用DAO层生成统计数据 + statistics, err := dao.OrderMonthlyStatisticsDAOInstance.GenerateStatistics(ctx, tenantID, year, month) + if err != nil { + return fmt.Errorf("生成月统计数据失败: %v", err) + } + + // 保存统计数据 + err = dao.OrderMonthlyStatisticsDAOInstance.Update(ctx, statistics) + if err != nil { + return fmt.Errorf("保存月统计数据失败: %v", err) + } + + g.Log().Infof(ctx, "租户 %d 在 %d年%d月 的月统计数据生成成功", tenantID, year, month) + return nil +} + +// GenerateQuarterlyStatistics 生成季度统计数据 +func (s *orderStatistics) GenerateQuarterlyStatistics(ctx context.Context, tenantID int64, year int, quarter int, force bool) error { + g.Log().Infof(ctx, "开始生成租户 %d 在 %d年第%d季度 的统计数据", tenantID, year, quarter) + + // 计算季度的起止月份 + startMonth := (quarter-1)*3 + 1 + startDate := time.Date(year, time.Month(startMonth), 1, 0, 0, 0, 0, time.Local) + + // 检查是否已存在统计数据 + if !force { + existing, err := dao.OrderQuarterlyStatisticsDAOInstance.GetByTenantAndDate(ctx, tenantID, startDate) + if err == nil && existing != nil { + g.Log().Infof(ctx, "租户 %d 在 %d年第%d季度 的统计数据已存在,跳过生成", tenantID, year, quarter) + return nil + } + } + + // 调用DAO层生成统计数据 + statistics, err := dao.OrderQuarterlyStatisticsDAOInstance.GenerateStatistics(ctx, tenantID, year, quarter) + if err != nil { + return fmt.Errorf("生成季度统计数据失败: %v", err) + } + + // 保存统计数据 + err = dao.OrderQuarterlyStatisticsDAOInstance.Save(ctx, statistics) + if err != nil { + return fmt.Errorf("保存季度统计数据失败: %v", err) + } + + g.Log().Infof(ctx, "租户 %d 在 %d年第%d季度 的统计数据生成成功", tenantID, year, quarter) + return nil +} + +// GenerateYearlyStatistics 生成年统计数据 +func (s *orderStatistics) GenerateYearlyStatistics(ctx context.Context, tenantID int64, year int, force bool) error { + g.Log().Infof(ctx, "开始生成租户 %d 在 %d年 的年统计数据", tenantID, year) + + // 计算年的起止日期 + startDate := time.Date(year, 1, 1, 0, 0, 0, 0, time.Local) + + // 检查是否已存在统计数据 + if !force { + existing, err := dao.OrderYearlyStatisticsDAOInstance.GetByTenantAndDate(ctx, tenantID, startDate) + if err == nil && existing != nil { + g.Log().Infof(ctx, "租户 %d 在 %d年 的年统计数据已存在,跳过生成", tenantID, year) + return nil + } + } + + // 调用DAO层生成统计数据 + statistics, err := dao.OrderYearlyStatisticsDAOInstance.GenerateStatistics(ctx, tenantID, year) + if err != nil { + return fmt.Errorf("生成年统计数据失败: %v", err) + } + + // 保存统计数据 + err = dao.OrderYearlyStatisticsDAOInstance.Save(ctx, statistics) + if err != nil { + return fmt.Errorf("保存年统计数据失败: %v", err) + } + + g.Log().Infof(ctx, "租户 %d 在 %d年 的年统计数据生成成功", tenantID, year) + return nil +} + +// GetStatistics 获取统计数据 +func (s *orderStatistics) GetStatistics(ctx context.Context, req *dto.GetOrderStatisticsReq) (*dto.GetOrderStatisticsRes, error) { + reportDate, err := time.Parse("2006-01-02", req.StartDate) + if err != nil { + return nil, fmt.Errorf("日期格式错误: %v", err) + } + + var result *dto.OrderStatisticsDetail + + // 根据统计类型调用不同的DAO + switch req.ReportType { + case "daily": + statistics, err := dao.OrderDailyStatisticsDAOInstance.GetByTenantAndDate(ctx, req.TenantID, reportDate) + if err != nil { + return nil, fmt.Errorf("获取日统计数据失败: %v", err) + } + if statistics != nil { + result = &dto.OrderStatisticsDetail{ + ReportType: "daily", + ReportDate: statistics.ReportDate, + Period: statistics.Period, + TotalOrders: statistics.TotalOrders, + CompletedOrders: statistics.CompletedOrders, + CancelledOrders: statistics.CancelledOrders, + PaidOrders: statistics.PaidOrders, + TotalAmount: statistics.TotalAmount, + PaidAmount: statistics.PaidAmount, + RefundAmount: 0, // 需要根据实际业务添加 + NetAmount: statistics.NetAmount, + AverageOrderValue: statistics.AverageOrderValue, + PaymentRate: statistics.PaymentRate, + CompletionRate: statistics.CompletionRate, + UniqueUsers: statistics.UniqueUsers, + NewUsers: statistics.NewUsers, + ReturningUsers: statistics.ReturningUsers, + TotalItems: statistics.TotalItems, + UniqueAssets: statistics.UniqueAssets, + TopAssetID: statistics.TopAssetID, + TopAssetName: statistics.TopAssetName, + TopAssetCount: statistics.TopAssetCount, + CreatedAt: statistics.CreatedAt, + UpdatedAt: statistics.UpdatedAt, + } + } + case "monthly": + statistics, err := dao.OrderMonthlyStatisticsDAOInstance.GetByTenantAndDate(ctx, req.TenantID, reportDate) + if err != nil { + return nil, fmt.Errorf("获取月统计数据失败: %v", err) + } + if statistics != nil { + result = &dto.OrderStatisticsDetail{ + ReportType: "monthly", + ReportDate: statistics.ReportDate, + Period: statistics.Period, + TotalOrders: statistics.TotalOrders, + CompletedOrders: statistics.CompletedOrders, + CancelledOrders: statistics.CancelledOrders, + PaidOrders: statistics.PaidOrders, + TotalAmount: statistics.TotalAmount, + PaidAmount: statistics.PaidAmount, + RefundAmount: 0, // 需要根据实际业务添加 + NetAmount: statistics.NetAmount, + AverageOrderValue: statistics.AverageOrderValue, + PaymentRate: statistics.PaymentRate, + CompletionRate: statistics.CompletionRate, + UniqueUsers: statistics.UniqueUsers, + NewUsers: statistics.NewUsers, + ReturningUsers: statistics.ReturningUsers, + TotalItems: statistics.TotalItems, + UniqueAssets: statistics.UniqueAssets, + TopAssetID: statistics.TopAssetID, + TopAssetName: statistics.TopAssetName, + TopAssetCount: statistics.TopAssetCount, + CreatedAt: statistics.CreatedAt, + UpdatedAt: statistics.UpdatedAt, + } + } + case "quarterly": + statistics, err := dao.OrderQuarterlyStatisticsDAOInstance.GetByTenantAndDate(ctx, req.TenantID, reportDate) + if err != nil { + return nil, fmt.Errorf("获取季度统计数据失败: %v", err) + } + if statistics != nil { + result = &dto.OrderStatisticsDetail{ + ReportType: "quarterly", + ReportDate: statistics.ReportDate, + Period: statistics.Period, + TotalOrders: statistics.TotalOrders, + CompletedOrders: statistics.CompletedOrders, + CancelledOrders: statistics.CancelledOrders, + PaidOrders: statistics.PaidOrders, + TotalAmount: statistics.TotalAmount, + PaidAmount: statistics.PaidAmount, + RefundAmount: 0, // 需要根据实际业务添加 + NetAmount: statistics.NetAmount, + AverageOrderValue: statistics.AverageOrderValue, + PaymentRate: statistics.PaymentRate, + CompletionRate: statistics.CompletionRate, + UniqueUsers: statistics.UniqueUsers, + NewUsers: statistics.NewUsers, + ReturningUsers: statistics.ReturningUsers, + TotalItems: statistics.TotalItems, + UniqueAssets: statistics.UniqueAssets, + TopAssetID: statistics.TopAssetID, + TopAssetName: statistics.TopAssetName, + TopAssetCount: statistics.TopAssetCount, + CreatedAt: statistics.CreatedAt, + UpdatedAt: statistics.UpdatedAt, + } + } + case "yearly": + statistics, err := dao.OrderYearlyStatisticsDAOInstance.GetByTenantAndDate(ctx, req.TenantID, reportDate) + if err != nil { + return nil, fmt.Errorf("获取年度统计数据失败: %v", err) + } + if statistics != nil { + result = &dto.OrderStatisticsDetail{ + ReportType: "yearly", + ReportDate: statistics.ReportDate, + Period: statistics.Period, + TotalOrders: statistics.TotalOrders, + CompletedOrders: statistics.CompletedOrders, + CancelledOrders: statistics.CancelledOrders, + PaidOrders: statistics.PaidOrders, + TotalAmount: statistics.TotalAmount, + PaidAmount: statistics.PaidAmount, + RefundAmount: 0, // 需要根据实际业务添加 + NetAmount: statistics.NetAmount, + AverageOrderValue: statistics.AverageOrderValue, + PaymentRate: statistics.PaymentRate, + CompletionRate: statistics.CompletionRate, + UniqueUsers: statistics.UniqueUsers, + NewUsers: statistics.NewUsers, + ReturningUsers: statistics.ReturningUsers, + TotalItems: statistics.TotalItems, + UniqueAssets: statistics.UniqueAssets, + TopAssetID: statistics.TopAssetID, + TopAssetName: statistics.TopAssetName, + TopAssetCount: statistics.TopAssetCount, + CreatedAt: statistics.CreatedAt, + UpdatedAt: statistics.UpdatedAt, + } + } + default: + return nil, fmt.Errorf("不支持的统计类型: %s", req.ReportType) + } + + return &dto.GetOrderStatisticsRes{ + Statistics: result, + }, nil +} + +// GetStatisticsList 获取统计列表 +func (s *orderStatistics) GetStatisticsList(ctx context.Context, req *dto.GetOrderStatisticsListReq) (*dto.GetOrderStatisticsListRes, error) { + // 设置默认分页参数 + pageNum := req.PageNum + if pageNum <= 0 { + pageNum = 1 + } + pageSize := req.PageSize + if pageSize <= 0 { + pageSize = 20 + } + + var startDate, endDate time.Time + var err error + + if req.StartDate != "" { + startDate, err = time.Parse("2006-01-02", req.StartDate) + if err != nil { + return nil, fmt.Errorf("开始日期格式错误: %v", err) + } + } + + if req.EndDate != "" { + endDate, err = time.Parse("2006-01-02", req.EndDate) + if err != nil { + return nil, fmt.Errorf("结束日期格式错误: %v", err) + } + // 设置为当天的结束时间 + endDate = endDate.Add(24 * time.Hour).Add(-time.Second) + } + + var result []*dto.OrderStatisticsDetail + var totalCount int64 + + // 根据统计类型调用不同的DAO + switch req.ReportType { + case "daily": + list, count, err := dao.OrderDailyStatisticsDAOInstance.GetListByTenant(ctx, req.TenantID, startDate, endDate, pageNum, pageSize) + if err != nil { + return nil, fmt.Errorf("获取日统计列表失败: %v", err) + } + totalCount = count + for _, item := range list { + result = append(result, &dto.OrderStatisticsDetail{ + ReportType: "daily", + ReportDate: item.ReportDate, + Period: item.Period, + TotalOrders: item.TotalOrders, + CompletedOrders: item.CompletedOrders, + CancelledOrders: item.CancelledOrders, + PaidOrders: item.PaidOrders, + TotalAmount: item.TotalAmount, + PaidAmount: item.PaidAmount, + RefundAmount: 0, // 需要根据实际业务添加 + NetAmount: item.NetAmount, + AverageOrderValue: item.AverageOrderValue, + PaymentRate: item.PaymentRate, + CompletionRate: item.CompletionRate, + UniqueUsers: item.UniqueUsers, + NewUsers: item.NewUsers, + ReturningUsers: item.ReturningUsers, + TotalItems: item.TotalItems, + UniqueAssets: item.UniqueAssets, + TopAssetID: item.TopAssetID, + TopAssetName: item.TopAssetName, + TopAssetCount: item.TopAssetCount, + CreatedAt: item.CreatedAt, + UpdatedAt: item.UpdatedAt, + }) + } + case "monthly": + // 月度统计需要添加分页查询方法到DAO中 + // 暂时返回空结果 + result = []*dto.OrderStatisticsDetail{} + totalCount = 0 + case "quarterly": + // 季度统计需要添加分页查询方法到DAO中 + // 暂时返回空结果 + result = []*dto.OrderStatisticsDetail{} + totalCount = 0 + case "yearly": + // 年度统计需要添加分页查询方法到DAO中 + // 暂时返回空结果 + result = []*dto.OrderStatisticsDetail{} + totalCount = 0 + default: + return nil, fmt.Errorf("不支持的统计类型: %s", req.ReportType) + } + + return &dto.GetOrderStatisticsListRes{ + List: result, + TotalCount: totalCount, + PageNum: pageNum, + PageSize: pageSize, + }, nil +} + +// GenerateStatistics 生成统计数据 +func (s *orderStatistics) GenerateStatistics(ctx context.Context, req *dto.GenerateOrderStatisticsReq) (*dto.GenerateOrderStatisticsRes, error) { + reportDate := time.Now() + if req.ReportDate != "" { + var err error + reportDate, err = time.Parse("2006-01-02", req.ReportDate) + if err != nil { + return &dto.GenerateOrderStatisticsRes{ + Success: false, + Message: "日期格式错误,请使用YYYY-MM-DD格式", + }, nil + } + } + + var err error + + switch req.ReportType { + case "daily": + err = s.GenerateDailyStatistics(ctx, req.TenantID, reportDate, req.Force) + case "monthly": + err = s.GenerateMonthlyStatistics(ctx, req.TenantID, reportDate.Year(), int(reportDate.Month()), req.Force) + case "quarterly": + quarter := (int(reportDate.Month())-1)/3 + 1 + err = s.GenerateQuarterlyStatistics(ctx, req.TenantID, reportDate.Year(), quarter, req.Force) + case "yearly": + err = s.GenerateYearlyStatistics(ctx, req.TenantID, reportDate.Year(), req.Force) + default: + return &dto.GenerateOrderStatisticsRes{ + Success: false, + Message: "不支持的统计类型", + }, nil + } + + if err != nil { + return &dto.GenerateOrderStatisticsRes{ + Success: false, + Message: fmt.Sprintf("生成统计数据失败: %v", err), + }, nil + } + + return &dto.GenerateOrderStatisticsRes{ + Success: true, + Message: "统计数据生成成功", + }, nil +} diff --git a/service/payment.go b/service/payment.go index 4c28137..1385219 100644 --- a/service/payment.go +++ b/service/payment.go @@ -64,8 +64,8 @@ func (s *payment) PayOrder(ctx context.Context, req *dto.PayOrderReq) (*dto.PayO // 6. 创建支付记录 paymentRecord := &entity.PaymentRecord{ - TenantID: req.TenantID, - OrderID: pendingOrder.ID, + TenantId: req.TenantID, + OrderID: pendingOrder.Id, OrderNo: req.OrderNo, PayMethod: req.PayMethod, PayType: req.PayType, @@ -184,7 +184,7 @@ func (s *payment) HandlePaymentNotify(ctx context.Context, req *dto.PaymentNotif } // 3. 更新支付记录状态 - if err := dao.PaymentRecord.UpdateStatus(ctx, paymentRecord.ID.Hex(), req.Status, req.TransactionID, req.TradeNo); err != nil { + if err := dao.PaymentRecord.UpdateStatus(ctx, paymentRecord.Id.Hex(), req.Status, req.TransactionID, req.TradeNo); err != nil { return fmt.Errorf("更新支付记录失败: %w", err) } @@ -252,8 +252,8 @@ func (s *payment) RefundOrder(ctx context.Context, req *dto.RefundOrderReq) (*dt // 6. 创建退款记录 refundRecord := &entity.RefundRecord{ - TenantID: req.TenantID, - OrderID: paidOrder.ID, + TenantId: req.TenantID, + OrderID: paidOrder.Id, OrderNo: req.OrderNo, RefundNo: refundResp.RefundNo, RefundAmount: req.RefundAmount, @@ -332,7 +332,7 @@ func (s *payment) HandleRefundNotify(ctx context.Context, req *dto.RefundNotifyR } // 3. 更新退款记录状态 - if err := dao.RefundRecord.UpdateRefundStatus(ctx, refundRecord.ID.Hex(), req.Status, req.RefundID); err != nil { + if err := dao.RefundRecord.UpdateRefundStatus(ctx, refundRecord.Id.Hex(), req.Status, req.RefundID); err != nil { return fmt.Errorf("更新退款记录失败: %w", err) } diff --git a/service/payment_config.go b/service/payment_config.go index 27344e1..831bed7 100644 --- a/service/payment_config.go +++ b/service/payment_config.go @@ -5,10 +5,12 @@ import ( "errors" "time" - "go.mongodb.org/mongo-driver/v2/bson" "order/dao" "order/model/dto" "order/model/entity" + + "github.com/gogf/gf/v2/util/gconv" + "go.mongodb.org/mongo-driver/v2/bson" ) type paymentConfig struct{} @@ -34,7 +36,7 @@ func (s *paymentConfig) CreatePaymentConfig(ctx context.Context, req *dto.Create // 3. 创建配置实体 config := parseConfigMap(req.PayMethod, req.Config) - config.TenantID = req.TenantID + config.TenantId = req.TenantID config.Description = req.Remark config.Enabled = req.IsEnabled @@ -45,8 +47,8 @@ func (s *paymentConfig) CreatePaymentConfig(ctx context.Context, req *dto.Create // 5. 返回结果 resp := &dto.PaymentConfigResp{ - ID: config.ID.Hex(), - TenantID: config.TenantID, + ID: config.Id.Hex(), + TenantID: gconv.String(config.TenantId), PayMethod: config.PayMethod, Config: buildConfigMap(config), IsEnabled: config.Enabled, @@ -80,7 +82,7 @@ func (s *paymentConfig) UpdatePaymentConfig(ctx context.Context, req *dto.Update } // 3. 检查是否是租户自己的配置 - if config.TenantID != req.TenantID { + if config.TenantId != req.TenantID { return nil, errors.New("无权限操作该配置") } @@ -99,8 +101,8 @@ func (s *paymentConfig) UpdatePaymentConfig(ctx context.Context, req *dto.Update // 6. 返回结果 resp := &dto.PaymentConfigResp{ - ID: config.ID.Hex(), - TenantID: config.TenantID, + ID: config.Id.Hex(), + TenantID: gconv.String(config.TenantId), PayMethod: config.PayMethod, ConfigName: config.ConfigName, Remark: config.Description, @@ -147,8 +149,8 @@ func (s *paymentConfig) GetPaymentConfig(ctx context.Context, req *dto.QueryPaym // 3. 返回结果 resp := &dto.PaymentConfigResp{ - ID: config.ID.Hex(), - TenantID: config.TenantID, + ID: config.Id.Hex(), + TenantID: gconv.String(config.TenantId), PayMethod: config.PayMethod, ConfigName: config.ConfigName, Remark: config.Description, @@ -178,8 +180,8 @@ func (s *paymentConfig) GetPaymentConfigList(ctx context.Context, tenantID strin var resp []dto.PaymentConfigResp for _, config := range configs { resp = append(resp, dto.PaymentConfigResp{ - ID: config.ID.Hex(), - TenantID: config.TenantID, + ID: config.Id.Hex(), + TenantID: gconv.String(config.TenantId), PayMethod: config.PayMethod, ConfigName: config.ConfigName, Remark: config.Description, @@ -215,7 +217,7 @@ func (s *paymentConfig) DeletePaymentConfig(ctx context.Context, tenantID, confi } // 3. 检查是否是租户自己的配置 - if config.TenantID != tenantID { + if config.TenantId != tenantID { return errors.New("无权限操作该配置") } @@ -255,7 +257,7 @@ func (s *paymentConfig) updateConfigStatus(ctx context.Context, tenantID, config } // 3. 检查是否是租户自己的配置 - if config.TenantID != tenantID { + if config.TenantId != tenantID { return errors.New("无权限操作该配置") }