2025-12-12 16:20:47 +08:00
|
|
|
|
package service
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
|
"context"
|
|
|
|
|
|
"fmt"
|
2025-12-15 09:02:30 +08:00
|
|
|
|
"sync"
|
2025-12-12 16:20:47 +08:00
|
|
|
|
"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
|
|
|
|
|
|
}
|
2025-12-15 09:02:30 +08:00
|
|
|
|
|
|
|
|
|
|
// OrderStatisticsScheduler 订单统计定时任务调度器
|
|
|
|
|
|
type OrderStatisticsScheduler struct{}
|
|
|
|
|
|
|
|
|
|
|
|
var orderStatisticsSchedulerInstance = &OrderStatisticsScheduler{}
|
|
|
|
|
|
var statisticsSchedulerLock sync.Mutex
|
|
|
|
|
|
var isStatisticsSchedulerRunning bool
|
|
|
|
|
|
|
|
|
|
|
|
// StartOrderStatisticsScheduler 启动订单统计定时任务调度器
|
|
|
|
|
|
func StartOrderStatisticsScheduler(ctx context.Context) error {
|
|
|
|
|
|
return orderStatisticsSchedulerInstance.StartScheduler(ctx)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// StartScheduler 启动定时任务调度器(分布式安全)
|
|
|
|
|
|
func (s *OrderStatisticsScheduler) StartScheduler(ctx context.Context) error {
|
|
|
|
|
|
statisticsSchedulerLock.Lock()
|
|
|
|
|
|
defer statisticsSchedulerLock.Unlock()
|
|
|
|
|
|
|
|
|
|
|
|
// 检查是否已经有调度器在运行(分布式部署时避免重复执行)
|
|
|
|
|
|
if isStatisticsSchedulerRunning {
|
|
|
|
|
|
g.Log().Info(ctx, "订单统计定时任务调度器已在运行")
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 尝试获取分布式锁
|
|
|
|
|
|
if !s.acquireDistributedLock(ctx) {
|
|
|
|
|
|
g.Log().Info(ctx, "其他节点正在运行订单统计定时任务,当前节点跳过")
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
isStatisticsSchedulerRunning = 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秒
|
|
|
|
|
|
success, err := g.Redis().SetNX(ctx, lockKey, "locked")
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
g.Log().Errorf(ctx, "获取分布式锁失败: %v", err)
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 设置过期时间
|
|
|
|
|
|
if success {
|
|
|
|
|
|
_, err = g.Redis().Do(ctx, "EXPIRE", lockKey, 30)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
g.Log().Errorf(ctx, "设置锁过期时间失败: %v", err)
|
|
|
|
|
|
// 删除已设置的锁
|
|
|
|
|
|
g.Redis().Do(ctx, "DEL", lockKey)
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return success
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// renewDistributedLock 续期分布式锁
|
|
|
|
|
|
func (s *OrderStatisticsScheduler) renewDistributedLock(ctx context.Context) bool {
|
|
|
|
|
|
lockKey := "order_statistics_scheduler_lock"
|
|
|
|
|
|
|
|
|
|
|
|
// 续期30秒
|
|
|
|
|
|
_, err := g.Redis().Do(ctx, "EXPIRE", lockKey, 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, "分布式锁续期失败,停止调度器")
|
|
|
|
|
|
statisticsSchedulerLock.Lock()
|
|
|
|
|
|
isStatisticsSchedulerRunning = false
|
|
|
|
|
|
statisticsSchedulerLock.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分钟
|
|
|
|
|
|
success, err := g.Redis().SetNX(ctx, lockKey, "locked")
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
g.Log().Errorf(ctx, "获取任务锁失败: %v", err)
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 设置过期时间
|
|
|
|
|
|
if success {
|
|
|
|
|
|
_, err = g.Redis().Do(ctx, "EXPIRE", lockKey, 600) // 10分钟=600秒
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
g.Log().Errorf(ctx, "设置锁过期时间失败: %v", err)
|
|
|
|
|
|
// 删除已设置的锁
|
|
|
|
|
|
g.Redis().Do(ctx, "DEL", lockKey)
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return success
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 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 := fmt.Sprintf("order_stats_daily_%d_%s", tenantID, date.Format("2006-01-02"))
|
|
|
|
|
|
|
|
|
|
|
|
// 获取任务锁
|
|
|
|
|
|
if !s.acquireTaskLock(ctx, lockKey) {
|
|
|
|
|
|
g.Log().Infof(ctx, "租户 %d 的日统计任务正在执行,跳过", tenantID)
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
defer s.releaseTaskLock(ctx, lockKey)
|
|
|
|
|
|
|
|
|
|
|
|
g.Log().Infof(ctx, "开始生成租户 %d 的日统计: %s", tenantID, date.Format("2006-01-02"))
|
|
|
|
|
|
|
|
|
|
|
|
err := 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 := fmt.Sprintf("order_stats_monthly_%d_%d_%d", tenantID, year, month)
|
|
|
|
|
|
|
|
|
|
|
|
// 获取任务锁
|
|
|
|
|
|
if !s.acquireTaskLock(ctx, lockKey) {
|
|
|
|
|
|
g.Log().Infof(ctx, "租户 %d 的月统计任务正在执行,跳过", tenantID)
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
defer s.releaseTaskLock(ctx, lockKey)
|
|
|
|
|
|
|
|
|
|
|
|
g.Log().Infof(ctx, "开始生成租户 %d 的月统计: %d年%d月", tenantID, year, month)
|
|
|
|
|
|
|
|
|
|
|
|
err := 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 := fmt.Sprintf("order_stats_quarterly_%d_%d_%d", tenantID, year, quarter)
|
|
|
|
|
|
|
|
|
|
|
|
// 获取任务锁
|
|
|
|
|
|
if !s.acquireTaskLock(ctx, lockKey) {
|
|
|
|
|
|
g.Log().Infof(ctx, "租户 %d 的季度统计任务正在执行,跳过", tenantID)
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
defer s.releaseTaskLock(ctx, lockKey)
|
|
|
|
|
|
|
|
|
|
|
|
g.Log().Infof(ctx, "开始生成租户 %d 的季度统计: %d年第%d季度", tenantID, year, quarter)
|
|
|
|
|
|
|
|
|
|
|
|
err := 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 := fmt.Sprintf("order_stats_yearly_%d_%d", tenantID, year)
|
|
|
|
|
|
|
|
|
|
|
|
// 获取任务锁
|
|
|
|
|
|
if !s.acquireTaskLock(ctx, lockKey) {
|
|
|
|
|
|
g.Log().Infof(ctx, "租户 %d 的年统计任务正在执行,跳过", tenantID)
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
defer s.releaseTaskLock(ctx, lockKey)
|
|
|
|
|
|
|
|
|
|
|
|
g.Log().Infof(ctx, "开始生成租户 %d 的年统计: %d年", tenantID, year)
|
|
|
|
|
|
|
|
|
|
|
|
err := 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
|
|
|
|
|
|
}
|