gomod引用
This commit is contained in:
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
/.idea/misc.xml
|
||||||
|
/.idea/modules.xml
|
||||||
|
/.idea/order.iml
|
||||||
|
/.idea/vcs.xml
|
||||||
|
/.idea/workspace.xml
|
||||||
@@ -1,16 +1,29 @@
|
|||||||
package consts
|
package consts
|
||||||
|
|
||||||
// 订单集合常量
|
// MongoDB集合常量
|
||||||
const (
|
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"
|
OrderPendingCollection = "orders_pending"
|
||||||
OrderPaidCollection = "orders_paid"
|
OrderPaidCollection = "orders_paid"
|
||||||
OrderShippedCollection = "orders_shipped"
|
OrderShippedCollection = "orders_shipped"
|
||||||
OrderCompletedCollection = "orders_completed"
|
OrderCompletedCollection = "orders_completed"
|
||||||
)
|
OrderCancelledCollection = "orders_cancelled"
|
||||||
|
OrderRefundedCollection = "orders_refunded"
|
||||||
|
|
||||||
// 支付相关集合常量
|
// 支付配置集合
|
||||||
const (
|
PaymentConfigCollection = "payment_config"
|
||||||
PaymentConfigCollection = "payment_configs"
|
|
||||||
PaymentRecordCollection = "payment_records"
|
// 支付记录集合
|
||||||
RefundRecordCollection = "refund_records"
|
PaymentRecordCollection = "payment_record"
|
||||||
|
RefundRecordCollection = "refund_record"
|
||||||
)
|
)
|
||||||
|
|||||||
28
controller/order_statistics_controller.go
Normal file
28
controller/order_statistics_controller.go
Normal file
@@ -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)
|
||||||
|
}
|
||||||
279
dao/order_daily_statistics_dao.go
Normal file
279
dao/order_daily_statistics_dao.go
Normal file
@@ -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
|
||||||
|
}
|
||||||
@@ -45,7 +45,7 @@ func (d *order) CreatePendingOrder(ctx context.Context, order *entity.OrderPendi
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
order.ID = bson.NewObjectID()
|
order.Id = bson.NewObjectID()
|
||||||
order.CreatedAt = time.Now()
|
order.CreatedAt = time.Now()
|
||||||
order.UpdatedAt = time.Now()
|
order.UpdatedAt = time.Now()
|
||||||
|
|
||||||
|
|||||||
348
dao/order_monthly_statistics_dao.go
Normal file
348
dao/order_monthly_statistics_dao.go
Normal file
@@ -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
|
||||||
|
}
|
||||||
332
dao/order_quarterly_statistics_dao.go
Normal file
332
dao/order_quarterly_statistics_dao.go
Normal file
@@ -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
|
||||||
|
}
|
||||||
201
dao/order_statistics_base_dao.go
Normal file
201
dao/order_statistics_base_dao.go
Normal file
@@ -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
|
||||||
|
}
|
||||||
276
dao/order_yearly_statistics_dao.go
Normal file
276
dao/order_yearly_statistics_dao.go
Normal file
@@ -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
|
||||||
|
}
|
||||||
@@ -41,10 +41,10 @@ func (d *paymentConfig) GetByTenantAndMethod(ctx context.Context, tenantID, payM
|
|||||||
|
|
||||||
// Update 更新支付配置
|
// Update 更新支付配置
|
||||||
func (d *paymentConfig) Update(ctx context.Context, config *entity.PaymentConfig) error {
|
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{
|
update := bson.M{
|
||||||
"$set": bson.M{
|
"$set": bson.M{
|
||||||
"tenant_id": config.TenantID,
|
"tenant_id": config.TenantId,
|
||||||
"pay_method": config.PayMethod,
|
"pay_method": config.PayMethod,
|
||||||
"config_name": config.ConfigName,
|
"config_name": config.ConfigName,
|
||||||
"description": config.Description,
|
"description": config.Description,
|
||||||
@@ -120,8 +120,8 @@ var PaymentRecord = new(paymentRecord)
|
|||||||
|
|
||||||
// Create 创建支付记录
|
// Create 创建支付记录
|
||||||
func (d *paymentRecord) Create(ctx context.Context, record *entity.PaymentRecord) error {
|
func (d *paymentRecord) Create(ctx context.Context, record *entity.PaymentRecord) error {
|
||||||
record.CreatedAt = time.Now().Unix()
|
record.CreatedAt = time.Now()
|
||||||
record.UpdatedAt = time.Now().Unix()
|
record.UpdatedAt = time.Now()
|
||||||
|
|
||||||
_, err := mongo.Insert(ctx, []interface{}{record}, consts.PaymentRecordCollection)
|
_, err := mongo.Insert(ctx, []interface{}{record}, consts.PaymentRecordCollection)
|
||||||
return err
|
return err
|
||||||
@@ -165,8 +165,8 @@ var RefundRecord = new(refundRecord)
|
|||||||
|
|
||||||
// Create 创建退款记录
|
// Create 创建退款记录
|
||||||
func (d *refundRecord) Create(ctx context.Context, record *entity.RefundRecord) error {
|
func (d *refundRecord) Create(ctx context.Context, record *entity.RefundRecord) error {
|
||||||
record.CreatedAt = time.Now().Unix()
|
record.CreatedAt = time.Now()
|
||||||
record.UpdatedAt = time.Now().Unix()
|
record.UpdatedAt = time.Now()
|
||||||
|
|
||||||
_, err := mongo.Insert(ctx, []interface{}{record}, consts.RefundRecordCollection)
|
_, err := mongo.Insert(ctx, []interface{}{record}, consts.RefundRecordCollection)
|
||||||
return err
|
return err
|
||||||
|
|||||||
3
go.mod
3
go.mod
@@ -7,7 +7,7 @@ require (
|
|||||||
github.com/gogf/gf/contrib/drivers/mysql/v2 v2.9.6
|
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/contrib/nosql/redis/v2 v2.9.6
|
||||||
github.com/gogf/gf/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 (
|
require (
|
||||||
@@ -66,7 +66,6 @@ require (
|
|||||||
github.com/xdg-go/scram v1.1.2 // indirect
|
github.com/xdg-go/scram v1.1.2 // indirect
|
||||||
github.com/xdg-go/stringprep v1.0.4 // indirect
|
github.com/xdg-go/stringprep v1.0.4 // indirect
|
||||||
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // 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.opencensus.io v0.22.5 // indirect
|
||||||
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||||
go.opentelemetry.io/otel v1.38.0 // indirect
|
go.opentelemetry.io/otel v1.38.0 // indirect
|
||||||
|
|||||||
2
go.sum
2
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.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.2.1/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=
|
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 h1:Oq6BmUAAFTzMeh6AonuDlgZMuAuEiUxoAD1koK5MuFo=
|
||||||
go.mongodb.org/mongo-driver/v2 v2.4.0/go.mod h1:jHeEDJHJq7tm6ZF45Issun9dbogjfnPySb1vXA7EeAI=
|
go.mongodb.org/mongo-driver/v2 v2.4.0/go.mod h1:jHeEDJHJq7tm6ZF45Issun9dbogjfnPySb1vXA7EeAI=
|
||||||
go.opencensus.io v0.22.5 h1:dntmOdLpSpHlVqbW5Eay97DelsZHe+55D+xC6i0dDS0=
|
go.opencensus.io v0.22.5 h1:dntmOdLpSpHlVqbW5Eay97DelsZHe+55D+xC6i0dDS0=
|
||||||
|
|||||||
11
main.go
11
main.go
@@ -9,12 +9,23 @@ import (
|
|||||||
_ "github.com/gogf/gf/contrib/drivers/mysql/v2"
|
_ "github.com/gogf/gf/contrib/drivers/mysql/v2"
|
||||||
_ "github.com/gogf/gf/contrib/nosql/redis/v2"
|
_ "github.com/gogf/gf/contrib/nosql/redis/v2"
|
||||||
"order/controller"
|
"order/controller"
|
||||||
|
"order/scheduler"
|
||||||
|
|
||||||
|
"github.com/gogf/gf/v2/frame/g"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
defer jaeger.ShutDown(context.Background())
|
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{}{
|
http.RouteRegister([]interface{}{
|
||||||
controller.Order,
|
controller.Order,
|
||||||
|
controller.OrderStatistics,
|
||||||
})
|
})
|
||||||
select {}
|
select {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ type OrderDetail struct {
|
|||||||
ID string `json:"id"` // 订单ID
|
ID string `json:"id"` // 订单ID
|
||||||
TenantID string `json:"tenant_id"` // 租户ID
|
TenantID string `json:"tenant_id"` // 租户ID
|
||||||
OrderNo string `json:"order_no"` // 订单号
|
OrderNo string `json:"order_no"` // 订单号
|
||||||
UserID string `json:"user_id"` // 用户ID
|
UserID int64 `json:"user_id"` // 用户ID
|
||||||
TotalAmount int64 `json:"total_amount"` // 订单总金额
|
TotalAmount int64 `json:"total_amount"` // 订单总金额
|
||||||
PayAmount int64 `json:"pay_amount"` // 实付金额
|
PayAmount int64 `json:"pay_amount"` // 实付金额
|
||||||
Status string `json:"status"` // 订单状态
|
Status string `json:"status"` // 订单状态
|
||||||
|
|||||||
141
model/dto/order_statistics.go
Normal file
141
model/dto/order_statistics.go
Normal file
@@ -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"`
|
||||||
|
}
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
package entity
|
package entity
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"go.mongodb.org/mongo-driver/v2/bson"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"gitee.com/red-future---jilin-g/common/do"
|
||||||
)
|
)
|
||||||
|
|
||||||
// OrderBase 订单基础信息
|
// OrderBase 订单基础信息
|
||||||
@@ -12,18 +13,16 @@ import (
|
|||||||
// 例如:orders_pending, orders_paid, orders_shipped, orders_completed, orders_cancelled
|
// 例如:orders_pending, orders_paid, orders_shipped, orders_completed, orders_cancelled
|
||||||
|
|
||||||
type OrderBase struct {
|
type OrderBase struct {
|
||||||
ID bson.ObjectID `bson:"_id,omitempty" json:"id"`
|
do.MongoBaseDO `bson:",inline"`
|
||||||
TenantID string `bson:"tenant_id" json:"tenant_id"` // 租户ID
|
OrderNo string `bson:"order_no" json:"order_no"` // 订单号
|
||||||
OrderNo string `bson:"order_no" json:"order_no"` // 订单号
|
UserID int64 `bson:"user_id" json:"user_id"` // 用户ID
|
||||||
UserID string `bson:"user_id" json:"user_id"` // 用户ID
|
TotalAmount int64 `bson:"total_amount" json:"total_amount"` // 订单总金额(分)
|
||||||
TotalAmount int64 `bson:"total_amount" json:"total_amount"` // 订单总金额(分)
|
PayAmount int64 `bson:"pay_amount" json:"pay_amount"` // 实付金额(分)
|
||||||
PayAmount int64 `bson:"pay_amount" json:"pay_amount"` // 实付金额(分)
|
OrderType string `bson:"order_type" json:"order_type"` // 订单类型:normal-普通订单
|
||||||
OrderType string `bson:"order_type" json:"order_type"` // 订单类型:normal-普通订单
|
Subject string `bson:"subject" json:"subject"` // 订单标题
|
||||||
Subject string `bson:"subject" json:"subject"` // 订单标题
|
Description string `bson:"description" json:"description"` // 订单描述
|
||||||
Description string `bson:"description" json:"description"` // 订单描述
|
OrderItems []OrderItem `bson:"order_items" json:"order_items"` // 订单商品项
|
||||||
CreatedAt time.Time `bson:"created_at" json:"created_at"` // 创建时间
|
ExpiredAt *time.Time `bson:"expired_at,omitempty" json:"expired_at"` // 过期时间
|
||||||
UpdatedAt time.Time `bson:"updated_at" json:"updated_at"` // 更新时间
|
|
||||||
ExpiredAt *time.Time `bson:"expired_at,omitempty" json:"expired_at"` // 过期时间
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// OrderItem 订单商品项
|
// OrderItem 订单商品项
|
||||||
|
|||||||
47
model/entity/order_daily_statistics.go
Normal file
47
model/entity/order_daily_statistics.go
Normal file
@@ -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"` // 高峰时段
|
||||||
|
}
|
||||||
51
model/entity/order_monthly_statistics.go
Normal file
51
model/entity/order_monthly_statistics.go
Normal file
@@ -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"` // 环比增长率
|
||||||
|
}
|
||||||
53
model/entity/order_quarterly_statistics.go
Normal file
53
model/entity/order_quarterly_statistics.go
Normal file
@@ -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"` // 同比增长率
|
||||||
|
}
|
||||||
63
model/entity/order_statistics.go
Normal file
63
model/entity/order_statistics.go
Normal file
@@ -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"`
|
||||||
|
}
|
||||||
56
model/entity/order_yearly_statistics.go
Normal file
56
model/entity/order_yearly_statistics.go
Normal file
@@ -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"` // 同比增长率
|
||||||
|
}
|
||||||
@@ -2,6 +2,8 @@ package entity
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"go.mongodb.org/mongo-driver/v2/bson"
|
"go.mongodb.org/mongo-driver/v2/bson"
|
||||||
|
|
||||||
|
"gitee.com/red-future---jilin-g/common/do"
|
||||||
)
|
)
|
||||||
|
|
||||||
// PaymentConfig 支付配置
|
// PaymentConfig 支付配置
|
||||||
@@ -9,12 +11,11 @@ import (
|
|||||||
// 支持微信支付和支付宝支付
|
// 支持微信支付和支付宝支付
|
||||||
|
|
||||||
type PaymentConfig struct {
|
type PaymentConfig struct {
|
||||||
ID bson.ObjectID `bson:"_id,omitempty" json:"id"`
|
do.MongoBaseDO `bson:",inline"`
|
||||||
TenantID string `bson:"tenant_id" json:"tenant_id"` // 租户ID
|
PayMethod string `bson:"pay_method" json:"pay_method"` // 支付方式:wechat/alipay
|
||||||
PayMethod string `bson:"pay_method" json:"pay_method"` // 支付方式:wechat/alipay
|
ConfigName string `bson:"config_name" json:"config_name"` // 配置名称
|
||||||
ConfigName string `bson:"config_name" json:"config_name"` // 配置名称
|
Description string `bson:"description" json:"description"` // 配置描述
|
||||||
Description string `bson:"description" json:"description"` // 配置描述
|
Enabled bool `bson:"enabled" json:"enabled"` // 是否启用
|
||||||
Enabled bool `bson:"enabled" json:"enabled"` // 是否启用
|
|
||||||
|
|
||||||
// 通用配置
|
// 通用配置
|
||||||
AppID string `bson:"app_id" json:"app_id"` // 应用ID
|
AppID string `bson:"app_id" json:"app_id"` // 应用ID
|
||||||
@@ -46,37 +47,31 @@ type PaymentConfig struct {
|
|||||||
// 记录每次支付操作的结果
|
// 记录每次支付操作的结果
|
||||||
|
|
||||||
type PaymentRecord struct {
|
type PaymentRecord struct {
|
||||||
ID bson.ObjectID `bson:"_id,omitempty" json:"id"`
|
do.MongoBaseDO `bson:",inline"`
|
||||||
TenantID string `bson:"tenant_id" json:"tenant_id"` // 租户ID
|
OrderID bson.ObjectID `bson:"order_id" json:"order_id"` // 订单ID
|
||||||
OrderID bson.ObjectID `bson:"order_id" json:"order_id"` // 订单ID
|
OrderNo string `bson:"order_no" json:"order_no"` // 订单号
|
||||||
OrderNo string `bson:"order_no" json:"order_no"` // 订单号
|
PayMethod string `bson:"pay_method" json:"pay_method"` // 支付方式
|
||||||
PayMethod string `bson:"pay_method" json:"pay_method"` // 支付方式
|
PayType string `bson:"pay_type" json:"pay_type"` // 支付类型
|
||||||
PayType string `bson:"pay_type" json:"pay_type"` // 支付类型
|
Amount int64 `bson:"amount" json:"amount"` // 支付金额(分)
|
||||||
Amount int64 `bson:"amount" json:"amount"` // 支付金额(分)
|
TransactionID string `bson:"transaction_id" json:"transaction_id"` // 支付平台交易号
|
||||||
TransactionID string `bson:"transaction_id" json:"transaction_id"` // 支付平台交易号
|
OutTradeNo string `bson:"out_trade_no" json:"out_trade_no"` // 商户订单号
|
||||||
OutTradeNo string `bson:"out_trade_no" json:"out_trade_no"` // 商户订单号
|
TradeNo string `bson:"trade_no" json:"trade_no"` // 交易号
|
||||||
TradeNo string `bson:"trade_no" json:"trade_no"` // 交易号
|
PrepayID string `bson:"prepay_id,omitempty" json:"prepay_id"` // 预支付ID
|
||||||
PrepayID string `bson:"prepay_id,omitempty" json:"prepay_id"` // 预支付ID
|
Status string `bson:"status" json:"status"` // 支付状态:success/failed
|
||||||
Status string `bson:"status" json:"status"` // 支付状态:success/failed
|
ErrorMsg string `bson:"error_msg,omitempty" json:"error_msg"` // 错误信息
|
||||||
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"` // 更新时间
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// RefundRecord 退款记录
|
// RefundRecord 退款记录
|
||||||
// 记录每次退款操作的结果
|
// 记录每次退款操作的结果
|
||||||
|
|
||||||
type RefundRecord struct {
|
type RefundRecord struct {
|
||||||
ID bson.ObjectID `bson:"_id,omitempty" json:"id"`
|
do.MongoBaseDO `bson:",inline"`
|
||||||
TenantID string `bson:"tenant_id" json:"tenant_id"` // 租户ID
|
OrderID bson.ObjectID `bson:"order_id" json:"order_id"` // 订单ID
|
||||||
OrderID bson.ObjectID `bson:"order_id" json:"order_id"` // 订单ID
|
OrderNo string `bson:"order_no" json:"order_no"` // 订单号
|
||||||
OrderNo string `bson:"order_no" json:"order_no"` // 订单号
|
RefundNo string `bson:"refund_no" json:"refund_no"` // 退款单号
|
||||||
RefundNo string `bson:"refund_no" json:"refund_no"` // 退款单号
|
RefundID string `bson:"refund_id" json:"refund_id"` // 退款ID
|
||||||
RefundID string `bson:"refund_id" json:"refund_id"` // 退款ID
|
RefundAmount int64 `bson:"refund_amount" json:"refund_amount"` // 退款金额(分)
|
||||||
RefundAmount int64 `bson:"refund_amount" json:"refund_amount"` // 退款金额(分)
|
Reason string `bson:"reason" json:"reason"` // 退款原因
|
||||||
Reason string `bson:"reason" json:"reason"` // 退款原因
|
Status string `bson:"status" json:"status"` // 退款状态:success/failed
|
||||||
Status string `bson:"status" json:"status"` // 退款状态:success/failed
|
ErrorMsg string `bson:"error_msg,omitempty" json:"error_msg"` // 错误信息
|
||||||
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"` // 更新时间
|
|
||||||
}
|
}
|
||||||
|
|||||||
462
scheduler/order_statistics_scheduler.go
Normal file
462
scheduler/order_statistics_scheduler.go
Normal file
@@ -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
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"math/rand"
|
"math/rand"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/gogf/gf/v2/util/gconv"
|
||||||
"go.mongodb.org/mongo-driver/v2/bson"
|
"go.mongodb.org/mongo-driver/v2/bson"
|
||||||
"order/consts"
|
"order/consts"
|
||||||
"order/dao"
|
"order/dao"
|
||||||
@@ -233,8 +234,8 @@ func (s *order) QueryOrder(ctx context.Context, req *dto.QueryOrderReq) (*dto.Qu
|
|||||||
// convertPendingOrderToDetail 转换待支付订单为详情
|
// convertPendingOrderToDetail 转换待支付订单为详情
|
||||||
func (s *order) convertPendingOrderToDetail(order *entity.OrderPending) dto.OrderDetail {
|
func (s *order) convertPendingOrderToDetail(order *entity.OrderPending) dto.OrderDetail {
|
||||||
return dto.OrderDetail{
|
return dto.OrderDetail{
|
||||||
ID: order.ID.Hex(),
|
ID: order.Id.Hex(),
|
||||||
TenantID: order.TenantID,
|
TenantID: gconv.String(order.TenantId),
|
||||||
OrderNo: order.OrderNo,
|
OrderNo: order.OrderNo,
|
||||||
UserID: order.UserID,
|
UserID: order.UserID,
|
||||||
TotalAmount: order.TotalAmount,
|
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 {
|
func (s *order) convertPaidOrderToDetail(order *entity.OrderPaid) dto.OrderDetail {
|
||||||
return dto.OrderDetail{
|
return dto.OrderDetail{
|
||||||
ID: order.ID.Hex(),
|
ID: order.ID.Hex(),
|
||||||
TenantID: order.TenantID,
|
TenantID: order.TenantId,
|
||||||
OrderNo: order.OrderNo,
|
OrderNo: order.OrderNo,
|
||||||
UserID: order.UserID,
|
UserID: order.UserID,
|
||||||
TotalAmount: order.TotalAmount,
|
TotalAmount: order.TotalAmount,
|
||||||
|
|||||||
439
service/order_statistics.go
Normal file
439
service/order_statistics.go
Normal file
@@ -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
|
||||||
|
}
|
||||||
@@ -64,8 +64,8 @@ func (s *payment) PayOrder(ctx context.Context, req *dto.PayOrderReq) (*dto.PayO
|
|||||||
|
|
||||||
// 6. 创建支付记录
|
// 6. 创建支付记录
|
||||||
paymentRecord := &entity.PaymentRecord{
|
paymentRecord := &entity.PaymentRecord{
|
||||||
TenantID: req.TenantID,
|
TenantId: req.TenantID,
|
||||||
OrderID: pendingOrder.ID,
|
OrderID: pendingOrder.Id,
|
||||||
OrderNo: req.OrderNo,
|
OrderNo: req.OrderNo,
|
||||||
PayMethod: req.PayMethod,
|
PayMethod: req.PayMethod,
|
||||||
PayType: req.PayType,
|
PayType: req.PayType,
|
||||||
@@ -184,7 +184,7 @@ func (s *payment) HandlePaymentNotify(ctx context.Context, req *dto.PaymentNotif
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 3. 更新支付记录状态
|
// 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)
|
return fmt.Errorf("更新支付记录失败: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -252,8 +252,8 @@ func (s *payment) RefundOrder(ctx context.Context, req *dto.RefundOrderReq) (*dt
|
|||||||
|
|
||||||
// 6. 创建退款记录
|
// 6. 创建退款记录
|
||||||
refundRecord := &entity.RefundRecord{
|
refundRecord := &entity.RefundRecord{
|
||||||
TenantID: req.TenantID,
|
TenantId: req.TenantID,
|
||||||
OrderID: paidOrder.ID,
|
OrderID: paidOrder.Id,
|
||||||
OrderNo: req.OrderNo,
|
OrderNo: req.OrderNo,
|
||||||
RefundNo: refundResp.RefundNo,
|
RefundNo: refundResp.RefundNo,
|
||||||
RefundAmount: req.RefundAmount,
|
RefundAmount: req.RefundAmount,
|
||||||
@@ -332,7 +332,7 @@ func (s *payment) HandleRefundNotify(ctx context.Context, req *dto.RefundNotifyR
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 3. 更新退款记录状态
|
// 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)
|
return fmt.Errorf("更新退款记录失败: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,10 +5,12 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"go.mongodb.org/mongo-driver/v2/bson"
|
|
||||||
"order/dao"
|
"order/dao"
|
||||||
"order/model/dto"
|
"order/model/dto"
|
||||||
"order/model/entity"
|
"order/model/entity"
|
||||||
|
|
||||||
|
"github.com/gogf/gf/v2/util/gconv"
|
||||||
|
"go.mongodb.org/mongo-driver/v2/bson"
|
||||||
)
|
)
|
||||||
|
|
||||||
type paymentConfig struct{}
|
type paymentConfig struct{}
|
||||||
@@ -34,7 +36,7 @@ func (s *paymentConfig) CreatePaymentConfig(ctx context.Context, req *dto.Create
|
|||||||
|
|
||||||
// 3. 创建配置实体
|
// 3. 创建配置实体
|
||||||
config := parseConfigMap(req.PayMethod, req.Config)
|
config := parseConfigMap(req.PayMethod, req.Config)
|
||||||
config.TenantID = req.TenantID
|
config.TenantId = req.TenantID
|
||||||
config.Description = req.Remark
|
config.Description = req.Remark
|
||||||
config.Enabled = req.IsEnabled
|
config.Enabled = req.IsEnabled
|
||||||
|
|
||||||
@@ -45,8 +47,8 @@ func (s *paymentConfig) CreatePaymentConfig(ctx context.Context, req *dto.Create
|
|||||||
|
|
||||||
// 5. 返回结果
|
// 5. 返回结果
|
||||||
resp := &dto.PaymentConfigResp{
|
resp := &dto.PaymentConfigResp{
|
||||||
ID: config.ID.Hex(),
|
ID: config.Id.Hex(),
|
||||||
TenantID: config.TenantID,
|
TenantID: gconv.String(config.TenantId),
|
||||||
PayMethod: config.PayMethod,
|
PayMethod: config.PayMethod,
|
||||||
Config: buildConfigMap(config),
|
Config: buildConfigMap(config),
|
||||||
IsEnabled: config.Enabled,
|
IsEnabled: config.Enabled,
|
||||||
@@ -80,7 +82,7 @@ func (s *paymentConfig) UpdatePaymentConfig(ctx context.Context, req *dto.Update
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 3. 检查是否是租户自己的配置
|
// 3. 检查是否是租户自己的配置
|
||||||
if config.TenantID != req.TenantID {
|
if config.TenantId != req.TenantID {
|
||||||
return nil, errors.New("无权限操作该配置")
|
return nil, errors.New("无权限操作该配置")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,8 +101,8 @@ func (s *paymentConfig) UpdatePaymentConfig(ctx context.Context, req *dto.Update
|
|||||||
|
|
||||||
// 6. 返回结果
|
// 6. 返回结果
|
||||||
resp := &dto.PaymentConfigResp{
|
resp := &dto.PaymentConfigResp{
|
||||||
ID: config.ID.Hex(),
|
ID: config.Id.Hex(),
|
||||||
TenantID: config.TenantID,
|
TenantID: gconv.String(config.TenantId),
|
||||||
PayMethod: config.PayMethod,
|
PayMethod: config.PayMethod,
|
||||||
ConfigName: config.ConfigName,
|
ConfigName: config.ConfigName,
|
||||||
Remark: config.Description,
|
Remark: config.Description,
|
||||||
@@ -147,8 +149,8 @@ func (s *paymentConfig) GetPaymentConfig(ctx context.Context, req *dto.QueryPaym
|
|||||||
|
|
||||||
// 3. 返回结果
|
// 3. 返回结果
|
||||||
resp := &dto.PaymentConfigResp{
|
resp := &dto.PaymentConfigResp{
|
||||||
ID: config.ID.Hex(),
|
ID: config.Id.Hex(),
|
||||||
TenantID: config.TenantID,
|
TenantID: gconv.String(config.TenantId),
|
||||||
PayMethod: config.PayMethod,
|
PayMethod: config.PayMethod,
|
||||||
ConfigName: config.ConfigName,
|
ConfigName: config.ConfigName,
|
||||||
Remark: config.Description,
|
Remark: config.Description,
|
||||||
@@ -178,8 +180,8 @@ func (s *paymentConfig) GetPaymentConfigList(ctx context.Context, tenantID strin
|
|||||||
var resp []dto.PaymentConfigResp
|
var resp []dto.PaymentConfigResp
|
||||||
for _, config := range configs {
|
for _, config := range configs {
|
||||||
resp = append(resp, dto.PaymentConfigResp{
|
resp = append(resp, dto.PaymentConfigResp{
|
||||||
ID: config.ID.Hex(),
|
ID: config.Id.Hex(),
|
||||||
TenantID: config.TenantID,
|
TenantID: gconv.String(config.TenantId),
|
||||||
PayMethod: config.PayMethod,
|
PayMethod: config.PayMethod,
|
||||||
ConfigName: config.ConfigName,
|
ConfigName: config.ConfigName,
|
||||||
Remark: config.Description,
|
Remark: config.Description,
|
||||||
@@ -215,7 +217,7 @@ func (s *paymentConfig) DeletePaymentConfig(ctx context.Context, tenantID, confi
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 3. 检查是否是租户自己的配置
|
// 3. 检查是否是租户自己的配置
|
||||||
if config.TenantID != tenantID {
|
if config.TenantId != tenantID {
|
||||||
return errors.New("无权限操作该配置")
|
return errors.New("无权限操作该配置")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -255,7 +257,7 @@ func (s *paymentConfig) updateConfigStatus(ctx context.Context, tenantID, config
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 3. 检查是否是租户自己的配置
|
// 3. 检查是否是租户自己的配置
|
||||||
if config.TenantID != tenantID {
|
if config.TenantId != tenantID {
|
||||||
return errors.New("无权限操作该配置")
|
return errors.New("无权限操作该配置")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user