This commit is contained in:
2025-09-12 01:15:09 +08:00
parent c563b2266b
commit e05ad9e223
103 changed files with 20034 additions and 1041 deletions

View File

@@ -0,0 +1,434 @@
package entities
import (
"errors"
"time"
"github.com/google/uuid"
"gorm.io/gorm"
)
// StatisticsDashboard 仪表板配置实体
// 用于存储仪表板的配置信息
type StatisticsDashboard struct {
// 基础标识
ID string `gorm:"primaryKey;type:varchar(36)" json:"id" comment:"仪表板唯一标识"`
Name string `gorm:"type:varchar(100);not null" json:"name" comment:"仪表板名称"`
Description string `gorm:"type:text" json:"description" comment:"仪表板描述"`
UserRole string `gorm:"type:varchar(20);not null;index" json:"user_role" comment:"用户角色"`
IsDefault bool `gorm:"default:false" json:"is_default" comment:"是否为默认仪表板"`
IsActive bool `gorm:"default:true" json:"is_active" comment:"是否激活"`
// 仪表板配置
Layout string `gorm:"type:json" json:"layout" comment:"布局配置"`
Widgets string `gorm:"type:json" json:"widgets" comment:"组件配置"`
Settings string `gorm:"type:json" json:"settings" comment:"设置配置"`
RefreshInterval int `gorm:"default:300" json:"refresh_interval" comment:"刷新间隔(秒)"`
// 权限和访问控制
CreatedBy string `gorm:"type:varchar(36);not null" json:"created_by" comment:"创建者ID"`
AccessLevel string `gorm:"type:varchar(20);default:'private'" json:"access_level" comment:"访问级别"`
// 时间戳字段
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at" comment:"创建时间"`
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at" comment:"更新时间"`
DeletedAt gorm.DeletedAt `gorm:"index" json:"-" comment:"软删除时间"`
// 领域事件 (不持久化)
domainEvents []interface{} `gorm:"-" json:"-"`
}
// TableName 指定数据库表名
func (StatisticsDashboard) TableName() string {
return "statistics_dashboards"
}
// BeforeCreate GORM钩子创建前自动生成UUID
func (s *StatisticsDashboard) BeforeCreate(tx *gorm.DB) error {
if s.ID == "" {
s.ID = uuid.New().String()
}
return nil
}
// 实现 Entity 接口 - 提供统一的实体管理接口
// GetID 获取实体唯一标识
func (s *StatisticsDashboard) GetID() string {
return s.ID
}
// GetCreatedAt 获取创建时间
func (s *StatisticsDashboard) GetCreatedAt() time.Time {
return s.CreatedAt
}
// GetUpdatedAt 获取更新时间
func (s *StatisticsDashboard) GetUpdatedAt() time.Time {
return s.UpdatedAt
}
// Validate 验证仪表板配置信息
// 检查仪表板必填字段是否完整,确保数据的有效性
func (s *StatisticsDashboard) Validate() error {
if s.Name == "" {
return NewValidationError("仪表板名称不能为空")
}
if s.UserRole == "" {
return NewValidationError("用户角色不能为空")
}
if s.CreatedBy == "" {
return NewValidationError("创建者ID不能为空")
}
// 验证用户角色
if !s.IsValidUserRole() {
return NewValidationError("无效的用户角色")
}
// 验证访问级别
if !s.IsValidAccessLevel() {
return NewValidationError("无效的访问级别")
}
// 验证刷新间隔
if s.RefreshInterval < 30 {
return NewValidationError("刷新间隔不能少于30秒")
}
return nil
}
// IsValidUserRole 检查用户角色是否有效
func (s *StatisticsDashboard) IsValidUserRole() bool {
validRoles := []string{
"admin", // 管理员
"user", // 普通用户
"manager", // 经理
"analyst", // 分析师
}
for _, validRole := range validRoles {
if s.UserRole == validRole {
return true
}
}
return false
}
// IsValidAccessLevel 检查访问级别是否有效
func (s *StatisticsDashboard) IsValidAccessLevel() bool {
validLevels := []string{
"private", // 私有
"public", // 公开
"shared", // 共享
}
for _, validLevel := range validLevels {
if s.AccessLevel == validLevel {
return true
}
}
return false
}
// GetUserRoleName 获取用户角色的中文名称
func (s *StatisticsDashboard) GetUserRoleName() string {
roleNames := map[string]string{
"admin": "管理员",
"user": "普通用户",
"manager": "经理",
"analyst": "分析师",
}
if name, exists := roleNames[s.UserRole]; exists {
return name
}
return s.UserRole
}
// GetAccessLevelName 获取访问级别的中文名称
func (s *StatisticsDashboard) GetAccessLevelName() string {
levelNames := map[string]string{
"private": "私有",
"public": "公开",
"shared": "共享",
}
if name, exists := levelNames[s.AccessLevel]; exists {
return name
}
return s.AccessLevel
}
// NewStatisticsDashboard 工厂方法 - 创建仪表板配置
func NewStatisticsDashboard(name, description, userRole, createdBy string) (*StatisticsDashboard, error) {
if name == "" {
return nil, errors.New("仪表板名称不能为空")
}
if userRole == "" {
return nil, errors.New("用户角色不能为空")
}
if createdBy == "" {
return nil, errors.New("创建者ID不能为空")
}
dashboard := &StatisticsDashboard{
Name: name,
Description: description,
UserRole: userRole,
CreatedBy: createdBy,
IsDefault: false,
IsActive: true,
AccessLevel: "private",
RefreshInterval: 300, // 默认5分钟
domainEvents: make([]interface{}, 0),
}
// 验证仪表板
if err := dashboard.Validate(); err != nil {
return nil, err
}
// 添加领域事件
dashboard.addDomainEvent(&StatisticsDashboardCreatedEvent{
DashboardID: dashboard.ID,
Name: name,
UserRole: userRole,
CreatedBy: createdBy,
CreatedAt: time.Now(),
})
return dashboard, nil
}
// SetAsDefault 设置为默认仪表板
func (s *StatisticsDashboard) SetAsDefault() error {
if !s.IsActive {
return NewValidationError("只有激活状态的仪表板才能设置为默认")
}
s.IsDefault = true
// 添加领域事件
s.addDomainEvent(&StatisticsDashboardSetAsDefaultEvent{
DashboardID: s.ID,
SetAt: time.Now(),
})
return nil
}
// RemoveAsDefault 取消默认状态
func (s *StatisticsDashboard) RemoveAsDefault() error {
if !s.IsDefault {
return NewValidationError("当前仪表板不是默认仪表板")
}
s.IsDefault = false
// 添加领域事件
s.addDomainEvent(&StatisticsDashboardRemovedAsDefaultEvent{
DashboardID: s.ID,
RemovedAt: time.Now(),
})
return nil
}
// Activate 激活仪表板
func (s *StatisticsDashboard) Activate() error {
if s.IsActive {
return NewValidationError("仪表板已经是激活状态")
}
s.IsActive = true
// 添加领域事件
s.addDomainEvent(&StatisticsDashboardActivatedEvent{
DashboardID: s.ID,
ActivatedAt: time.Now(),
})
return nil
}
// Deactivate 停用仪表板
func (s *StatisticsDashboard) Deactivate() error {
if !s.IsActive {
return NewValidationError("仪表板已经是停用状态")
}
s.IsActive = false
// 如果是默认仪表板,需要先取消默认状态
if s.IsDefault {
s.IsDefault = false
}
// 添加领域事件
s.addDomainEvent(&StatisticsDashboardDeactivatedEvent{
DashboardID: s.ID,
DeactivatedAt: time.Now(),
})
return nil
}
// UpdateLayout 更新布局配置
func (s *StatisticsDashboard) UpdateLayout(layout string) error {
if layout == "" {
return NewValidationError("布局配置不能为空")
}
s.Layout = layout
// 添加领域事件
s.addDomainEvent(&StatisticsDashboardLayoutUpdatedEvent{
DashboardID: s.ID,
UpdatedAt: time.Now(),
})
return nil
}
// UpdateWidgets 更新组件配置
func (s *StatisticsDashboard) UpdateWidgets(widgets string) error {
if widgets == "" {
return NewValidationError("组件配置不能为空")
}
s.Widgets = widgets
// 添加领域事件
s.addDomainEvent(&StatisticsDashboardWidgetsUpdatedEvent{
DashboardID: s.ID,
UpdatedAt: time.Now(),
})
return nil
}
// UpdateSettings 更新设置配置
func (s *StatisticsDashboard) UpdateSettings(settings string) error {
s.Settings = settings
// 添加领域事件
s.addDomainEvent(&StatisticsDashboardSettingsUpdatedEvent{
DashboardID: s.ID,
UpdatedAt: time.Now(),
})
return nil
}
// UpdateRefreshInterval 更新刷新间隔
func (s *StatisticsDashboard) UpdateRefreshInterval(interval int) error {
if interval < 30 {
return NewValidationError("刷新间隔不能少于30秒")
}
oldInterval := s.RefreshInterval
s.RefreshInterval = interval
// 添加领域事件
s.addDomainEvent(&StatisticsDashboardRefreshIntervalUpdatedEvent{
DashboardID: s.ID,
OldInterval: oldInterval,
NewInterval: interval,
UpdatedAt: time.Now(),
})
return nil
}
// CanBeModified 检查仪表板是否可以被修改
func (s *StatisticsDashboard) CanBeModified() bool {
return s.IsActive
}
// CanBeDeleted 检查仪表板是否可以被删除
func (s *StatisticsDashboard) CanBeDeleted() bool {
return !s.IsDefault && s.IsActive
}
// ================ 领域事件管理 ================
// addDomainEvent 添加领域事件
func (s *StatisticsDashboard) addDomainEvent(event interface{}) {
if s.domainEvents == nil {
s.domainEvents = make([]interface{}, 0)
}
s.domainEvents = append(s.domainEvents, event)
}
// GetDomainEvents 获取领域事件
func (s *StatisticsDashboard) GetDomainEvents() []interface{} {
return s.domainEvents
}
// ClearDomainEvents 清除领域事件
func (s *StatisticsDashboard) ClearDomainEvents() {
s.domainEvents = make([]interface{}, 0)
}
// ================ 领域事件定义 ================
// StatisticsDashboardCreatedEvent 仪表板创建事件
type StatisticsDashboardCreatedEvent struct {
DashboardID string `json:"dashboard_id"`
Name string `json:"name"`
UserRole string `json:"user_role"`
CreatedBy string `json:"created_by"`
CreatedAt time.Time `json:"created_at"`
}
// StatisticsDashboardSetAsDefaultEvent 仪表板设置为默认事件
type StatisticsDashboardSetAsDefaultEvent struct {
DashboardID string `json:"dashboard_id"`
SetAt time.Time `json:"set_at"`
}
// StatisticsDashboardRemovedAsDefaultEvent 仪表板取消默认事件
type StatisticsDashboardRemovedAsDefaultEvent struct {
DashboardID string `json:"dashboard_id"`
RemovedAt time.Time `json:"removed_at"`
}
// StatisticsDashboardActivatedEvent 仪表板激活事件
type StatisticsDashboardActivatedEvent struct {
DashboardID string `json:"dashboard_id"`
ActivatedAt time.Time `json:"activated_at"`
}
// StatisticsDashboardDeactivatedEvent 仪表板停用事件
type StatisticsDashboardDeactivatedEvent struct {
DashboardID string `json:"dashboard_id"`
DeactivatedAt time.Time `json:"deactivated_at"`
}
// StatisticsDashboardLayoutUpdatedEvent 仪表板布局更新事件
type StatisticsDashboardLayoutUpdatedEvent struct {
DashboardID string `json:"dashboard_id"`
UpdatedAt time.Time `json:"updated_at"`
}
// StatisticsDashboardWidgetsUpdatedEvent 仪表板组件更新事件
type StatisticsDashboardWidgetsUpdatedEvent struct {
DashboardID string `json:"dashboard_id"`
UpdatedAt time.Time `json:"updated_at"`
}
// StatisticsDashboardSettingsUpdatedEvent 仪表板设置更新事件
type StatisticsDashboardSettingsUpdatedEvent struct {
DashboardID string `json:"dashboard_id"`
UpdatedAt time.Time `json:"updated_at"`
}
// StatisticsDashboardRefreshIntervalUpdatedEvent 仪表板刷新间隔更新事件
type StatisticsDashboardRefreshIntervalUpdatedEvent struct {
DashboardID string `json:"dashboard_id"`
OldInterval int `json:"old_interval"`
NewInterval int `json:"new_interval"`
UpdatedAt time.Time `json:"updated_at"`
}

View File

@@ -0,0 +1,244 @@
package entities
import (
"errors"
"fmt"
"time"
"github.com/google/uuid"
"gorm.io/gorm"
)
// StatisticsMetric 统计指标实体
// 用于存储各种统计指标数据,支持多维度统计
type StatisticsMetric struct {
// 基础标识
ID string `gorm:"primaryKey;type:varchar(36)" json:"id" comment:"统计指标唯一标识"`
MetricType string `gorm:"type:varchar(50);not null;index" json:"metric_type" comment:"指标类型"`
MetricName string `gorm:"type:varchar(100);not null" json:"metric_name" comment:"指标名称"`
Dimension string `gorm:"type:varchar(50)" json:"dimension" comment:"统计维度"`
Value float64 `gorm:"type:decimal(20,4);not null" json:"value" comment:"指标值"`
Metadata string `gorm:"type:json" json:"metadata" comment:"额外维度信息"`
Date time.Time `gorm:"type:date;index" json:"date" comment:"统计日期"`
// 时间戳字段
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at" comment:"创建时间"`
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at" comment:"更新时间"`
DeletedAt gorm.DeletedAt `gorm:"index" json:"-" comment:"软删除时间"`
// 领域事件 (不持久化)
domainEvents []interface{} `gorm:"-" json:"-"`
}
// TableName 指定数据库表名
func (StatisticsMetric) TableName() string {
return "statistics_metrics"
}
// BeforeCreate GORM钩子创建前自动生成UUID
func (s *StatisticsMetric) BeforeCreate(tx *gorm.DB) error {
if s.ID == "" {
s.ID = uuid.New().String()
}
return nil
}
// 实现 Entity 接口 - 提供统一的实体管理接口
// GetID 获取实体唯一标识
func (s *StatisticsMetric) GetID() string {
return s.ID
}
// GetCreatedAt 获取创建时间
func (s *StatisticsMetric) GetCreatedAt() time.Time {
return s.CreatedAt
}
// GetUpdatedAt 获取更新时间
func (s *StatisticsMetric) GetUpdatedAt() time.Time {
return s.UpdatedAt
}
// Validate 验证统计指标信息
// 检查统计指标必填字段是否完整,确保数据的有效性
func (s *StatisticsMetric) Validate() error {
if s.MetricType == "" {
return NewValidationError("指标类型不能为空")
}
if s.MetricName == "" {
return NewValidationError("指标名称不能为空")
}
if s.Value < 0 {
return NewValidationError("指标值不能为负数")
}
if s.Date.IsZero() {
return NewValidationError("统计日期不能为空")
}
// 验证指标类型
if !s.IsValidMetricType() {
return NewValidationError("无效的指标类型")
}
return nil
}
// IsValidMetricType 检查指标类型是否有效
func (s *StatisticsMetric) IsValidMetricType() bool {
validTypes := []string{
"api_calls", // API调用统计
"users", // 用户统计
"finance", // 财务统计
"products", // 产品统计
"certification", // 认证统计
}
for _, validType := range validTypes {
if s.MetricType == validType {
return true
}
}
return false
}
// GetMetricTypeName 获取指标类型的中文名称
func (s *StatisticsMetric) GetMetricTypeName() string {
typeNames := map[string]string{
"api_calls": "API调用统计",
"users": "用户统计",
"finance": "财务统计",
"products": "产品统计",
"certification": "认证统计",
}
if name, exists := typeNames[s.MetricType]; exists {
return name
}
return s.MetricType
}
// GetFormattedValue 获取格式化的指标值
func (s *StatisticsMetric) GetFormattedValue() string {
// 根据指标类型格式化数值
switch s.MetricType {
case "api_calls", "users":
return fmt.Sprintf("%.0f", s.Value)
case "finance":
return fmt.Sprintf("%.2f", s.Value)
default:
return fmt.Sprintf("%.4f", s.Value)
}
}
// NewStatisticsMetric 工厂方法 - 创建统计指标
func NewStatisticsMetric(metricType, metricName, dimension string, value float64, date time.Time) (*StatisticsMetric, error) {
if metricType == "" {
return nil, errors.New("指标类型不能为空")
}
if metricName == "" {
return nil, errors.New("指标名称不能为空")
}
if value < 0 {
return nil, errors.New("指标值不能为负数")
}
if date.IsZero() {
return nil, errors.New("统计日期不能为空")
}
metric := &StatisticsMetric{
MetricType: metricType,
MetricName: metricName,
Dimension: dimension,
Value: value,
Date: date,
domainEvents: make([]interface{}, 0),
}
// 验证指标
if err := metric.Validate(); err != nil {
return nil, err
}
// 添加领域事件
metric.addDomainEvent(&StatisticsMetricCreatedEvent{
MetricID: metric.ID,
MetricType: metricType,
MetricName: metricName,
Value: value,
CreatedAt: time.Now(),
})
return metric, nil
}
// UpdateValue 更新指标值
func (s *StatisticsMetric) UpdateValue(newValue float64) error {
if newValue < 0 {
return NewValidationError("指标值不能为负数")
}
oldValue := s.Value
s.Value = newValue
// 添加领域事件
s.addDomainEvent(&StatisticsMetricUpdatedEvent{
MetricID: s.ID,
OldValue: oldValue,
NewValue: newValue,
UpdatedAt: time.Now(),
})
return nil
}
// ================ 领域事件管理 ================
// addDomainEvent 添加领域事件
func (s *StatisticsMetric) addDomainEvent(event interface{}) {
if s.domainEvents == nil {
s.domainEvents = make([]interface{}, 0)
}
s.domainEvents = append(s.domainEvents, event)
}
// GetDomainEvents 获取领域事件
func (s *StatisticsMetric) GetDomainEvents() []interface{} {
return s.domainEvents
}
// ClearDomainEvents 清除领域事件
func (s *StatisticsMetric) ClearDomainEvents() {
s.domainEvents = make([]interface{}, 0)
}
// ================ 领域事件定义 ================
// StatisticsMetricCreatedEvent 统计指标创建事件
type StatisticsMetricCreatedEvent struct {
MetricID string `json:"metric_id"`
MetricType string `json:"metric_type"`
MetricName string `json:"metric_name"`
Value float64 `json:"value"`
CreatedAt time.Time `json:"created_at"`
}
// StatisticsMetricUpdatedEvent 统计指标更新事件
type StatisticsMetricUpdatedEvent struct {
MetricID string `json:"metric_id"`
OldValue float64 `json:"old_value"`
NewValue float64 `json:"new_value"`
UpdatedAt time.Time `json:"updated_at"`
}
// ValidationError 验证错误
type ValidationError struct {
Message string
}
func (e *ValidationError) Error() string {
return e.Message
}
func NewValidationError(message string) *ValidationError {
return &ValidationError{Message: message}
}

View File

@@ -0,0 +1,343 @@
package entities
import (
"errors"
"time"
"github.com/google/uuid"
"gorm.io/gorm"
)
// StatisticsReport 统计报告实体
// 用于存储生成的统计报告数据
type StatisticsReport struct {
// 基础标识
ID string `gorm:"primaryKey;type:varchar(36)" json:"id" comment:"报告唯一标识"`
ReportType string `gorm:"type:varchar(50);not null;index" json:"report_type" comment:"报告类型"`
Title string `gorm:"type:varchar(200);not null" json:"title" comment:"报告标题"`
Content string `gorm:"type:json" json:"content" comment:"报告内容"`
Period string `gorm:"type:varchar(20)" json:"period" comment:"统计周期"`
UserRole string `gorm:"type:varchar(20)" json:"user_role" comment:"用户角色"`
Status string `gorm:"type:varchar(20);default:'draft'" json:"status" comment:"报告状态"`
// 报告元数据
GeneratedBy string `gorm:"type:varchar(36)" json:"generated_by" comment:"生成者ID"`
GeneratedAt *time.Time `json:"generated_at" comment:"生成时间"`
ExpiresAt *time.Time `json:"expires_at" comment:"过期时间"`
// 时间戳字段
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at" comment:"创建时间"`
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at" comment:"更新时间"`
DeletedAt gorm.DeletedAt `gorm:"index" json:"-" comment:"软删除时间"`
// 领域事件 (不持久化)
domainEvents []interface{} `gorm:"-" json:"-"`
}
// TableName 指定数据库表名
func (StatisticsReport) TableName() string {
return "statistics_reports"
}
// BeforeCreate GORM钩子创建前自动生成UUID
func (s *StatisticsReport) BeforeCreate(tx *gorm.DB) error {
if s.ID == "" {
s.ID = uuid.New().String()
}
return nil
}
// 实现 Entity 接口 - 提供统一的实体管理接口
// GetID 获取实体唯一标识
func (s *StatisticsReport) GetID() string {
return s.ID
}
// GetCreatedAt 获取创建时间
func (s *StatisticsReport) GetCreatedAt() time.Time {
return s.CreatedAt
}
// GetUpdatedAt 获取更新时间
func (s *StatisticsReport) GetUpdatedAt() time.Time {
return s.UpdatedAt
}
// Validate 验证统计报告信息
// 检查统计报告必填字段是否完整,确保数据的有效性
func (s *StatisticsReport) Validate() error {
if s.ReportType == "" {
return NewValidationError("报告类型不能为空")
}
if s.Title == "" {
return NewValidationError("报告标题不能为空")
}
if s.Period == "" {
return NewValidationError("统计周期不能为空")
}
// 验证报告类型
if !s.IsValidReportType() {
return NewValidationError("无效的报告类型")
}
// 验证报告状态
if !s.IsValidStatus() {
return NewValidationError("无效的报告状态")
}
return nil
}
// IsValidReportType 检查报告类型是否有效
func (s *StatisticsReport) IsValidReportType() bool {
validTypes := []string{
"dashboard", // 仪表板报告
"summary", // 汇总报告
"detailed", // 详细报告
"custom", // 自定义报告
}
for _, validType := range validTypes {
if s.ReportType == validType {
return true
}
}
return false
}
// IsValidStatus 检查报告状态是否有效
func (s *StatisticsReport) IsValidStatus() bool {
validStatuses := []string{
"draft", // 草稿
"generating", // 生成中
"completed", // 已完成
"failed", // 生成失败
"expired", // 已过期
}
for _, validStatus := range validStatuses {
if s.Status == validStatus {
return true
}
}
return false
}
// GetReportTypeName 获取报告类型的中文名称
func (s *StatisticsReport) GetReportTypeName() string {
typeNames := map[string]string{
"dashboard": "仪表板报告",
"summary": "汇总报告",
"detailed": "详细报告",
"custom": "自定义报告",
}
if name, exists := typeNames[s.ReportType]; exists {
return name
}
return s.ReportType
}
// GetStatusName 获取报告状态的中文名称
func (s *StatisticsReport) GetStatusName() string {
statusNames := map[string]string{
"draft": "草稿",
"generating": "生成中",
"completed": "已完成",
"failed": "生成失败",
"expired": "已过期",
}
if name, exists := statusNames[s.Status]; exists {
return name
}
return s.Status
}
// NewStatisticsReport 工厂方法 - 创建统计报告
func NewStatisticsReport(reportType, title, period, userRole string) (*StatisticsReport, error) {
if reportType == "" {
return nil, errors.New("报告类型不能为空")
}
if title == "" {
return nil, errors.New("报告标题不能为空")
}
if period == "" {
return nil, errors.New("统计周期不能为空")
}
report := &StatisticsReport{
ReportType: reportType,
Title: title,
Period: period,
UserRole: userRole,
Status: "draft",
domainEvents: make([]interface{}, 0),
}
// 验证报告
if err := report.Validate(); err != nil {
return nil, err
}
// 添加领域事件
report.addDomainEvent(&StatisticsReportCreatedEvent{
ReportID: report.ID,
ReportType: reportType,
Title: title,
Period: period,
CreatedAt: time.Now(),
})
return report, nil
}
// StartGeneration 开始生成报告
func (s *StatisticsReport) StartGeneration(generatedBy string) error {
if s.Status != "draft" {
return NewValidationError("只有草稿状态的报告才能开始生成")
}
s.Status = "generating"
s.GeneratedBy = generatedBy
now := time.Now()
s.GeneratedAt = &now
// 添加领域事件
s.addDomainEvent(&StatisticsReportGenerationStartedEvent{
ReportID: s.ID,
GeneratedBy: generatedBy,
StartedAt: now,
})
return nil
}
// CompleteGeneration 完成报告生成
func (s *StatisticsReport) CompleteGeneration(content string) error {
if s.Status != "generating" {
return NewValidationError("只有生成中状态的报告才能完成生成")
}
s.Status = "completed"
s.Content = content
// 设置过期时间默认7天
expiresAt := time.Now().Add(7 * 24 * time.Hour)
s.ExpiresAt = &expiresAt
// 添加领域事件
s.addDomainEvent(&StatisticsReportCompletedEvent{
ReportID: s.ID,
CompletedAt: time.Now(),
})
return nil
}
// FailGeneration 报告生成失败
func (s *StatisticsReport) FailGeneration(reason string) error {
if s.Status != "generating" {
return NewValidationError("只有生成中状态的报告才能标记为失败")
}
s.Status = "failed"
// 添加领域事件
s.addDomainEvent(&StatisticsReportFailedEvent{
ReportID: s.ID,
Reason: reason,
FailedAt: time.Now(),
})
return nil
}
// IsExpired 检查报告是否已过期
func (s *StatisticsReport) IsExpired() bool {
if s.ExpiresAt == nil {
return false
}
return time.Now().After(*s.ExpiresAt)
}
// MarkAsExpired 标记报告为过期
func (s *StatisticsReport) MarkAsExpired() error {
if s.Status != "completed" {
return NewValidationError("只有已完成状态的报告才能标记为过期")
}
s.Status = "expired"
// 添加领域事件
s.addDomainEvent(&StatisticsReportExpiredEvent{
ReportID: s.ID,
ExpiredAt: time.Now(),
})
return nil
}
// CanBeRegenerated 检查报告是否可以重新生成
func (s *StatisticsReport) CanBeRegenerated() bool {
return s.Status == "failed" || s.Status == "expired"
}
// ================ 领域事件管理 ================
// addDomainEvent 添加领域事件
func (s *StatisticsReport) addDomainEvent(event interface{}) {
if s.domainEvents == nil {
s.domainEvents = make([]interface{}, 0)
}
s.domainEvents = append(s.domainEvents, event)
}
// GetDomainEvents 获取领域事件
func (s *StatisticsReport) GetDomainEvents() []interface{} {
return s.domainEvents
}
// ClearDomainEvents 清除领域事件
func (s *StatisticsReport) ClearDomainEvents() {
s.domainEvents = make([]interface{}, 0)
}
// ================ 领域事件定义 ================
// StatisticsReportCreatedEvent 统计报告创建事件
type StatisticsReportCreatedEvent struct {
ReportID string `json:"report_id"`
ReportType string `json:"report_type"`
Title string `json:"title"`
Period string `json:"period"`
CreatedAt time.Time `json:"created_at"`
}
// StatisticsReportGenerationStartedEvent 统计报告生成开始事件
type StatisticsReportGenerationStartedEvent struct {
ReportID string `json:"report_id"`
GeneratedBy string `json:"generated_by"`
StartedAt time.Time `json:"started_at"`
}
// StatisticsReportCompletedEvent 统计报告完成事件
type StatisticsReportCompletedEvent struct {
ReportID string `json:"report_id"`
CompletedAt time.Time `json:"completed_at"`
}
// StatisticsReportFailedEvent 统计报告失败事件
type StatisticsReportFailedEvent struct {
ReportID string `json:"report_id"`
Reason string `json:"reason"`
FailedAt time.Time `json:"failed_at"`
}
// StatisticsReportExpiredEvent 统计报告过期事件
type StatisticsReportExpiredEvent struct {
ReportID string `json:"report_id"`
ExpiredAt time.Time `json:"expired_at"`
}

View File

@@ -0,0 +1,572 @@
package events
import (
"encoding/json"
"time"
"github.com/google/uuid"
)
// StatisticsEventType 统计事件类型
type StatisticsEventType string
const (
// 指标相关事件
MetricCreatedEventType StatisticsEventType = "statistics.metric.created"
MetricUpdatedEventType StatisticsEventType = "statistics.metric.updated"
MetricAggregatedEventType StatisticsEventType = "statistics.metric.aggregated"
// 报告相关事件
ReportCreatedEventType StatisticsEventType = "statistics.report.created"
ReportGenerationStartedEventType StatisticsEventType = "statistics.report.generation_started"
ReportCompletedEventType StatisticsEventType = "statistics.report.completed"
ReportFailedEventType StatisticsEventType = "statistics.report.failed"
ReportExpiredEventType StatisticsEventType = "statistics.report.expired"
// 仪表板相关事件
DashboardCreatedEventType StatisticsEventType = "statistics.dashboard.created"
DashboardUpdatedEventType StatisticsEventType = "statistics.dashboard.updated"
DashboardActivatedEventType StatisticsEventType = "statistics.dashboard.activated"
DashboardDeactivatedEventType StatisticsEventType = "statistics.dashboard.deactivated"
)
// BaseStatisticsEvent 统计事件基础结构
type BaseStatisticsEvent struct {
ID string `json:"id"`
Type string `json:"type"`
Version string `json:"version"`
Timestamp time.Time `json:"timestamp"`
Source string `json:"source"`
AggregateID string `json:"aggregate_id"`
AggregateType string `json:"aggregate_type"`
Metadata map[string]interface{} `json:"metadata"`
Payload interface{} `json:"payload"`
// DDD特有字段
DomainVersion string `json:"domain_version"`
CausationID string `json:"causation_id"`
CorrelationID string `json:"correlation_id"`
}
// 实现 Event 接口
func (e *BaseStatisticsEvent) GetID() string {
return e.ID
}
func (e *BaseStatisticsEvent) GetType() string {
return e.Type
}
func (e *BaseStatisticsEvent) GetVersion() string {
return e.Version
}
func (e *BaseStatisticsEvent) GetTimestamp() time.Time {
return e.Timestamp
}
func (e *BaseStatisticsEvent) GetPayload() interface{} {
return e.Payload
}
func (e *BaseStatisticsEvent) GetMetadata() map[string]interface{} {
return e.Metadata
}
func (e *BaseStatisticsEvent) GetSource() string {
return e.Source
}
func (e *BaseStatisticsEvent) GetAggregateID() string {
return e.AggregateID
}
func (e *BaseStatisticsEvent) GetAggregateType() string {
return e.AggregateType
}
func (e *BaseStatisticsEvent) GetDomainVersion() string {
return e.DomainVersion
}
func (e *BaseStatisticsEvent) GetCausationID() string {
return e.CausationID
}
func (e *BaseStatisticsEvent) GetCorrelationID() string {
return e.CorrelationID
}
func (e *BaseStatisticsEvent) Marshal() ([]byte, error) {
return json.Marshal(e)
}
func (e *BaseStatisticsEvent) Unmarshal(data []byte) error {
return json.Unmarshal(data, e)
}
// ================ 指标相关事件 ================
// MetricCreatedEvent 指标创建事件
type MetricCreatedEvent struct {
*BaseStatisticsEvent
MetricID string `json:"metric_id"`
MetricType string `json:"metric_type"`
MetricName string `json:"metric_name"`
Value float64 `json:"value"`
Dimension string `json:"dimension"`
Date time.Time `json:"date"`
}
func NewMetricCreatedEvent(metricID, metricType, metricName, dimension string, value float64, date time.Time, correlationID string) *MetricCreatedEvent {
return &MetricCreatedEvent{
BaseStatisticsEvent: &BaseStatisticsEvent{
ID: uuid.New().String(),
Type: string(MetricCreatedEventType),
Version: "1.0",
Timestamp: time.Now(),
Source: "statistics-service",
AggregateID: metricID,
AggregateType: "StatisticsMetric",
DomainVersion: "1.0",
CorrelationID: correlationID,
Metadata: map[string]interface{}{
"metric_id": metricID,
"metric_type": metricType,
"metric_name": metricName,
"dimension": dimension,
},
},
MetricID: metricID,
MetricType: metricType,
MetricName: metricName,
Value: value,
Dimension: dimension,
Date: date,
}
}
func (e *MetricCreatedEvent) GetPayload() interface{} {
return map[string]interface{}{
"metric_id": e.MetricID,
"metric_type": e.MetricType,
"metric_name": e.MetricName,
"value": e.Value,
"dimension": e.Dimension,
"date": e.Date,
}
}
// MetricUpdatedEvent 指标更新事件
type MetricUpdatedEvent struct {
*BaseStatisticsEvent
MetricID string `json:"metric_id"`
OldValue float64 `json:"old_value"`
NewValue float64 `json:"new_value"`
UpdatedAt time.Time `json:"updated_at"`
}
func NewMetricUpdatedEvent(metricID string, oldValue, newValue float64, correlationID string) *MetricUpdatedEvent {
return &MetricUpdatedEvent{
BaseStatisticsEvent: &BaseStatisticsEvent{
ID: uuid.New().String(),
Type: string(MetricUpdatedEventType),
Version: "1.0",
Timestamp: time.Now(),
Source: "statistics-service",
AggregateID: metricID,
AggregateType: "StatisticsMetric",
DomainVersion: "1.0",
CorrelationID: correlationID,
Metadata: map[string]interface{}{
"metric_id": metricID,
},
},
MetricID: metricID,
OldValue: oldValue,
NewValue: newValue,
UpdatedAt: time.Now(),
}
}
func (e *MetricUpdatedEvent) GetPayload() interface{} {
return map[string]interface{}{
"metric_id": e.MetricID,
"old_value": e.OldValue,
"new_value": e.NewValue,
"updated_at": e.UpdatedAt,
}
}
// MetricAggregatedEvent 指标聚合事件
type MetricAggregatedEvent struct {
*BaseStatisticsEvent
MetricType string `json:"metric_type"`
Dimension string `json:"dimension"`
AggregatedAt time.Time `json:"aggregated_at"`
RecordCount int `json:"record_count"`
TotalValue float64 `json:"total_value"`
}
func NewMetricAggregatedEvent(metricType, dimension string, recordCount int, totalValue float64, correlationID string) *MetricAggregatedEvent {
return &MetricAggregatedEvent{
BaseStatisticsEvent: &BaseStatisticsEvent{
ID: uuid.New().String(),
Type: string(MetricAggregatedEventType),
Version: "1.0",
Timestamp: time.Now(),
Source: "statistics-service",
AggregateID: uuid.New().String(),
AggregateType: "StatisticsMetric",
DomainVersion: "1.0",
CorrelationID: correlationID,
Metadata: map[string]interface{}{
"metric_type": metricType,
"dimension": dimension,
},
},
MetricType: metricType,
Dimension: dimension,
AggregatedAt: time.Now(),
RecordCount: recordCount,
TotalValue: totalValue,
}
}
func (e *MetricAggregatedEvent) GetPayload() interface{} {
return map[string]interface{}{
"metric_type": e.MetricType,
"dimension": e.Dimension,
"aggregated_at": e.AggregatedAt,
"record_count": e.RecordCount,
"total_value": e.TotalValue,
}
}
// ================ 报告相关事件 ================
// ReportCreatedEvent 报告创建事件
type ReportCreatedEvent struct {
*BaseStatisticsEvent
ReportID string `json:"report_id"`
ReportType string `json:"report_type"`
Title string `json:"title"`
Period string `json:"period"`
UserRole string `json:"user_role"`
}
func NewReportCreatedEvent(reportID, reportType, title, period, userRole, correlationID string) *ReportCreatedEvent {
return &ReportCreatedEvent{
BaseStatisticsEvent: &BaseStatisticsEvent{
ID: uuid.New().String(),
Type: string(ReportCreatedEventType),
Version: "1.0",
Timestamp: time.Now(),
Source: "statistics-service",
AggregateID: reportID,
AggregateType: "StatisticsReport",
DomainVersion: "1.0",
CorrelationID: correlationID,
Metadata: map[string]interface{}{
"report_id": reportID,
"report_type": reportType,
"user_role": userRole,
},
},
ReportID: reportID,
ReportType: reportType,
Title: title,
Period: period,
UserRole: userRole,
}
}
func (e *ReportCreatedEvent) GetPayload() interface{} {
return map[string]interface{}{
"report_id": e.ReportID,
"report_type": e.ReportType,
"title": e.Title,
"period": e.Period,
"user_role": e.UserRole,
}
}
// ReportGenerationStartedEvent 报告生成开始事件
type ReportGenerationStartedEvent struct {
*BaseStatisticsEvent
ReportID string `json:"report_id"`
GeneratedBy string `json:"generated_by"`
StartedAt time.Time `json:"started_at"`
}
func NewReportGenerationStartedEvent(reportID, generatedBy, correlationID string) *ReportGenerationStartedEvent {
return &ReportGenerationStartedEvent{
BaseStatisticsEvent: &BaseStatisticsEvent{
ID: uuid.New().String(),
Type: string(ReportGenerationStartedEventType),
Version: "1.0",
Timestamp: time.Now(),
Source: "statistics-service",
AggregateID: reportID,
AggregateType: "StatisticsReport",
DomainVersion: "1.0",
CorrelationID: correlationID,
Metadata: map[string]interface{}{
"report_id": reportID,
"generated_by": generatedBy,
},
},
ReportID: reportID,
GeneratedBy: generatedBy,
StartedAt: time.Now(),
}
}
func (e *ReportGenerationStartedEvent) GetPayload() interface{} {
return map[string]interface{}{
"report_id": e.ReportID,
"generated_by": e.GeneratedBy,
"started_at": e.StartedAt,
}
}
// ReportCompletedEvent 报告完成事件
type ReportCompletedEvent struct {
*BaseStatisticsEvent
ReportID string `json:"report_id"`
CompletedAt time.Time `json:"completed_at"`
ContentSize int `json:"content_size"`
}
func NewReportCompletedEvent(reportID string, contentSize int, correlationID string) *ReportCompletedEvent {
return &ReportCompletedEvent{
BaseStatisticsEvent: &BaseStatisticsEvent{
ID: uuid.New().String(),
Type: string(ReportCompletedEventType),
Version: "1.0",
Timestamp: time.Now(),
Source: "statistics-service",
AggregateID: reportID,
AggregateType: "StatisticsReport",
DomainVersion: "1.0",
CorrelationID: correlationID,
Metadata: map[string]interface{}{
"report_id": reportID,
},
},
ReportID: reportID,
CompletedAt: time.Now(),
ContentSize: contentSize,
}
}
func (e *ReportCompletedEvent) GetPayload() interface{} {
return map[string]interface{}{
"report_id": e.ReportID,
"completed_at": e.CompletedAt,
"content_size": e.ContentSize,
}
}
// ReportFailedEvent 报告失败事件
type ReportFailedEvent struct {
*BaseStatisticsEvent
ReportID string `json:"report_id"`
Reason string `json:"reason"`
FailedAt time.Time `json:"failed_at"`
}
func NewReportFailedEvent(reportID, reason, correlationID string) *ReportFailedEvent {
return &ReportFailedEvent{
BaseStatisticsEvent: &BaseStatisticsEvent{
ID: uuid.New().String(),
Type: string(ReportFailedEventType),
Version: "1.0",
Timestamp: time.Now(),
Source: "statistics-service",
AggregateID: reportID,
AggregateType: "StatisticsReport",
DomainVersion: "1.0",
CorrelationID: correlationID,
Metadata: map[string]interface{}{
"report_id": reportID,
},
},
ReportID: reportID,
Reason: reason,
FailedAt: time.Now(),
}
}
func (e *ReportFailedEvent) GetPayload() interface{} {
return map[string]interface{}{
"report_id": e.ReportID,
"reason": e.Reason,
"failed_at": e.FailedAt,
}
}
// ================ 仪表板相关事件 ================
// DashboardCreatedEvent 仪表板创建事件
type DashboardCreatedEvent struct {
*BaseStatisticsEvent
DashboardID string `json:"dashboard_id"`
Name string `json:"name"`
UserRole string `json:"user_role"`
CreatedBy string `json:"created_by"`
}
func NewDashboardCreatedEvent(dashboardID, name, userRole, createdBy, correlationID string) *DashboardCreatedEvent {
return &DashboardCreatedEvent{
BaseStatisticsEvent: &BaseStatisticsEvent{
ID: uuid.New().String(),
Type: string(DashboardCreatedEventType),
Version: "1.0",
Timestamp: time.Now(),
Source: "statistics-service",
AggregateID: dashboardID,
AggregateType: "StatisticsDashboard",
DomainVersion: "1.0",
CorrelationID: correlationID,
Metadata: map[string]interface{}{
"dashboard_id": dashboardID,
"user_role": userRole,
"created_by": createdBy,
},
},
DashboardID: dashboardID,
Name: name,
UserRole: userRole,
CreatedBy: createdBy,
}
}
func (e *DashboardCreatedEvent) GetPayload() interface{} {
return map[string]interface{}{
"dashboard_id": e.DashboardID,
"name": e.Name,
"user_role": e.UserRole,
"created_by": e.CreatedBy,
}
}
// DashboardUpdatedEvent 仪表板更新事件
type DashboardUpdatedEvent struct {
*BaseStatisticsEvent
DashboardID string `json:"dashboard_id"`
UpdatedBy string `json:"updated_by"`
UpdatedAt time.Time `json:"updated_at"`
Changes map[string]interface{} `json:"changes"`
}
func NewDashboardUpdatedEvent(dashboardID, updatedBy string, changes map[string]interface{}, correlationID string) *DashboardUpdatedEvent {
return &DashboardUpdatedEvent{
BaseStatisticsEvent: &BaseStatisticsEvent{
ID: uuid.New().String(),
Type: string(DashboardUpdatedEventType),
Version: "1.0",
Timestamp: time.Now(),
Source: "statistics-service",
AggregateID: dashboardID,
AggregateType: "StatisticsDashboard",
DomainVersion: "1.0",
CorrelationID: correlationID,
Metadata: map[string]interface{}{
"dashboard_id": dashboardID,
"updated_by": updatedBy,
},
},
DashboardID: dashboardID,
UpdatedBy: updatedBy,
UpdatedAt: time.Now(),
Changes: changes,
}
}
func (e *DashboardUpdatedEvent) GetPayload() interface{} {
return map[string]interface{}{
"dashboard_id": e.DashboardID,
"updated_by": e.UpdatedBy,
"updated_at": e.UpdatedAt,
"changes": e.Changes,
}
}
// DashboardActivatedEvent 仪表板激活事件
type DashboardActivatedEvent struct {
*BaseStatisticsEvent
DashboardID string `json:"dashboard_id"`
ActivatedBy string `json:"activated_by"`
ActivatedAt time.Time `json:"activated_at"`
}
func NewDashboardActivatedEvent(dashboardID, activatedBy, correlationID string) *DashboardActivatedEvent {
return &DashboardActivatedEvent{
BaseStatisticsEvent: &BaseStatisticsEvent{
ID: uuid.New().String(),
Type: string(DashboardActivatedEventType),
Version: "1.0",
Timestamp: time.Now(),
Source: "statistics-service",
AggregateID: dashboardID,
AggregateType: "StatisticsDashboard",
DomainVersion: "1.0",
CorrelationID: correlationID,
Metadata: map[string]interface{}{
"dashboard_id": dashboardID,
"activated_by": activatedBy,
},
},
DashboardID: dashboardID,
ActivatedBy: activatedBy,
ActivatedAt: time.Now(),
}
}
func (e *DashboardActivatedEvent) GetPayload() interface{} {
return map[string]interface{}{
"dashboard_id": e.DashboardID,
"activated_by": e.ActivatedBy,
"activated_at": e.ActivatedAt,
}
}
// DashboardDeactivatedEvent 仪表板停用事件
type DashboardDeactivatedEvent struct {
*BaseStatisticsEvent
DashboardID string `json:"dashboard_id"`
DeactivatedBy string `json:"deactivated_by"`
DeactivatedAt time.Time `json:"deactivated_at"`
}
func NewDashboardDeactivatedEvent(dashboardID, deactivatedBy, correlationID string) *DashboardDeactivatedEvent {
return &DashboardDeactivatedEvent{
BaseStatisticsEvent: &BaseStatisticsEvent{
ID: uuid.New().String(),
Type: string(DashboardDeactivatedEventType),
Version: "1.0",
Timestamp: time.Now(),
Source: "statistics-service",
AggregateID: dashboardID,
AggregateType: "StatisticsDashboard",
DomainVersion: "1.0",
CorrelationID: correlationID,
Metadata: map[string]interface{}{
"dashboard_id": dashboardID,
"deactivated_by": deactivatedBy,
},
},
DashboardID: dashboardID,
DeactivatedBy: deactivatedBy,
DeactivatedAt: time.Now(),
}
}
func (e *DashboardDeactivatedEvent) GetPayload() interface{} {
return map[string]interface{}{
"dashboard_id": e.DashboardID,
"deactivated_by": e.DeactivatedBy,
"deactivated_at": e.DeactivatedAt,
}
}

View File

@@ -0,0 +1,301 @@
package queries
import (
"fmt"
"time"
)
// StatisticsQuery 统计查询对象
type StatisticsQuery struct {
// 基础查询条件
MetricType string `json:"metric_type" form:"metric_type"` // 指标类型
MetricName string `json:"metric_name" form:"metric_name"` // 指标名称
Dimension string `json:"dimension" form:"dimension"` // 统计维度
StartDate time.Time `json:"start_date" form:"start_date"` // 开始日期
EndDate time.Time `json:"end_date" form:"end_date"` // 结束日期
// 分页参数
Limit int `json:"limit" form:"limit"` // 限制数量
Offset int `json:"offset" form:"offset"` // 偏移量
// 排序参数
SortBy string `json:"sort_by" form:"sort_by"` // 排序字段
SortOrder string `json:"sort_order" form:"sort_order"` // 排序顺序 (asc/desc)
// 过滤条件
MinValue float64 `json:"min_value" form:"min_value"` // 最小值
MaxValue float64 `json:"max_value" form:"max_value"` // 最大值
// 聚合参数
AggregateBy string `json:"aggregate_by" form:"aggregate_by"` // 聚合维度 (hour/day/week/month)
GroupBy string `json:"group_by" form:"group_by"` // 分组维度
}
// StatisticsReportQuery 统计报告查询对象
type StatisticsReportQuery struct {
// 基础查询条件
ReportType string `json:"report_type" form:"report_type"` // 报告类型
UserRole string `json:"user_role" form:"user_role"` // 用户角色
Status string `json:"status" form:"status"` // 报告状态
Period string `json:"period" form:"period"` // 统计周期
StartDate time.Time `json:"start_date" form:"start_date"` // 开始日期
EndDate time.Time `json:"end_date" form:"end_date"` // 结束日期
// 分页参数
Limit int `json:"limit" form:"limit"` // 限制数量
Offset int `json:"offset" form:"offset"` // 偏移量
// 排序参数
SortBy string `json:"sort_by" form:"sort_by"` // 排序字段
SortOrder string `json:"sort_order" form:"sort_order"` // 排序顺序 (asc/desc)
// 过滤条件
GeneratedBy string `json:"generated_by" form:"generated_by"` // 生成者ID
AccessLevel string `json:"access_level" form:"access_level"` // 访问级别
}
// StatisticsDashboardQuery 统计仪表板查询对象
type StatisticsDashboardQuery struct {
// 基础查询条件
UserRole string `json:"user_role" form:"user_role"` // 用户角色
IsDefault *bool `json:"is_default" form:"is_default"` // 是否默认
IsActive *bool `json:"is_active" form:"is_active"` // 是否激活
AccessLevel string `json:"access_level" form:"access_level"` // 访问级别
// 分页参数
Limit int `json:"limit" form:"limit"` // 限制数量
Offset int `json:"offset" form:"offset"` // 偏移量
// 排序参数
SortBy string `json:"sort_by" form:"sort_by"` // 排序字段
SortOrder string `json:"sort_order" form:"sort_order"` // 排序顺序 (asc/desc)
// 过滤条件
CreatedBy string `json:"created_by" form:"created_by"` // 创建者ID
Name string `json:"name" form:"name"` // 仪表板名称
}
// RealtimeStatisticsQuery 实时统计查询对象
type RealtimeStatisticsQuery struct {
// 查询条件
MetricType string `json:"metric_type" form:"metric_type"` // 指标类型
TimeRange string `json:"time_range" form:"time_range"` // 时间范围 (last_hour/last_day/last_week)
// 过滤条件
Dimension string `json:"dimension" form:"dimension"` // 统计维度
}
// HistoricalStatisticsQuery 历史统计查询对象
type HistoricalStatisticsQuery struct {
// 查询条件
MetricType string `json:"metric_type" form:"metric_type"` // 指标类型
StartDate time.Time `json:"start_date" form:"start_date"` // 开始日期
EndDate time.Time `json:"end_date" form:"end_date"` // 结束日期
Period string `json:"period" form:"period"` // 统计周期
// 分页参数
Limit int `json:"limit" form:"limit"` // 限制数量
Offset int `json:"offset" form:"offset"` // 偏移量
// 聚合参数
AggregateBy string `json:"aggregate_by" form:"aggregate_by"` // 聚合维度
GroupBy string `json:"group_by" form:"group_by"` // 分组维度
// 过滤条件
Dimension string `json:"dimension" form:"dimension"` // 统计维度
MinValue float64 `json:"min_value" form:"min_value"` // 最小值
MaxValue float64 `json:"max_value" form:"max_value"` // 最大值
}
// DashboardDataQuery 仪表板数据查询对象
type DashboardDataQuery struct {
// 查询条件
UserRole string `json:"user_role" form:"user_role"` // 用户角色
Period string `json:"period" form:"period"` // 统计周期
// 时间范围
StartDate time.Time `json:"start_date" form:"start_date"` // 开始日期
EndDate time.Time `json:"end_date" form:"end_date"` // 结束日期
// 过滤条件
MetricTypes []string `json:"metric_types" form:"metric_types"` // 指标类型列表
Dimensions []string `json:"dimensions" form:"dimensions"` // 统计维度列表
}
// ReportGenerationQuery 报告生成查询对象
type ReportGenerationQuery struct {
// 报告配置
ReportType string `json:"report_type" form:"report_type"` // 报告类型
Title string `json:"title" form:"title"` // 报告标题
Period string `json:"period" form:"period"` // 统计周期
UserRole string `json:"user_role" form:"user_role"` // 用户角色
// 时间范围
StartDate time.Time `json:"start_date" form:"start_date"` // 开始日期
EndDate time.Time `json:"end_date" form:"end_date"` // 结束日期
// 过滤条件
Filters map[string]interface{} `json:"filters" form:"filters"` // 过滤条件
// 生成配置
GeneratedBy string `json:"generated_by" form:"generated_by"` // 生成者ID
Format string `json:"format" form:"format"` // 输出格式 (json/pdf/excel)
}
// ExportQuery 导出查询对象
type ExportQuery struct {
// 导出配置
Format string `json:"format" form:"format"` // 导出格式 (excel/csv/pdf)
MetricType string `json:"metric_type" form:"metric_type"` // 指标类型
// 时间范围
StartDate time.Time `json:"start_date" form:"start_date"` // 开始日期
EndDate time.Time `json:"end_date" form:"end_date"` // 结束日期
// 过滤条件
Dimension string `json:"dimension" form:"dimension"` // 统计维度
GroupBy string `json:"group_by" form:"group_by"` // 分组维度
// 导出配置
IncludeCharts bool `json:"include_charts" form:"include_charts"` // 是否包含图表
Columns []string `json:"columns" form:"columns"` // 导出列
}
// Validate 验证统计查询对象
func (q *StatisticsQuery) Validate() error {
if q.MetricType == "" {
return fmt.Errorf("指标类型不能为空")
}
if q.StartDate.IsZero() || q.EndDate.IsZero() {
return fmt.Errorf("开始日期和结束日期不能为空")
}
if q.StartDate.After(q.EndDate) {
return fmt.Errorf("开始日期不能晚于结束日期")
}
if q.Limit <= 0 {
q.Limit = 20 // 默认限制
}
if q.Limit > 1000 {
q.Limit = 1000 // 最大限制
}
if q.SortOrder != "" && q.SortOrder != "asc" && q.SortOrder != "desc" {
q.SortOrder = "desc" // 默认降序
}
return nil
}
// Validate 验证统计报告查询对象
func (q *StatisticsReportQuery) Validate() error {
if q.Limit <= 0 {
q.Limit = 20 // 默认限制
}
if q.Limit > 1000 {
q.Limit = 1000 // 最大限制
}
if q.SortOrder != "" && q.SortOrder != "asc" && q.SortOrder != "desc" {
q.SortOrder = "desc" // 默认降序
}
return nil
}
// Validate 验证统计仪表板查询对象
func (q *StatisticsDashboardQuery) Validate() error {
if q.Limit <= 0 {
q.Limit = 20 // 默认限制
}
if q.Limit > 1000 {
q.Limit = 1000 // 最大限制
}
if q.SortOrder != "" && q.SortOrder != "asc" && q.SortOrder != "desc" {
q.SortOrder = "desc" // 默认降序
}
return nil
}
// Validate 验证实时统计查询对象
func (q *RealtimeStatisticsQuery) Validate() error {
if q.MetricType == "" {
return fmt.Errorf("指标类型不能为空")
}
if q.TimeRange == "" {
q.TimeRange = "last_hour" // 默认最近1小时
}
validTimeRanges := []string{"last_hour", "last_day", "last_week"}
for _, validRange := range validTimeRanges {
if q.TimeRange == validRange {
return nil
}
}
return fmt.Errorf("无效的时间范围: %s", q.TimeRange)
}
// Validate 验证历史统计查询对象
func (q *HistoricalStatisticsQuery) Validate() error {
if q.MetricType == "" {
return fmt.Errorf("指标类型不能为空")
}
if q.StartDate.IsZero() || q.EndDate.IsZero() {
return fmt.Errorf("开始日期和结束日期不能为空")
}
if q.StartDate.After(q.EndDate) {
return fmt.Errorf("开始日期不能晚于结束日期")
}
if q.Limit <= 0 {
q.Limit = 20 // 默认限制
}
if q.Limit > 1000 {
q.Limit = 1000 // 最大限制
}
return nil
}
// Validate 验证仪表板数据查询对象
func (q *DashboardDataQuery) Validate() error {
if q.UserRole == "" {
return fmt.Errorf("用户角色不能为空")
}
if q.Period == "" {
q.Period = "today" // 默认今天
}
return nil
}
// Validate 验证报告生成查询对象
func (q *ReportGenerationQuery) Validate() error {
if q.ReportType == "" {
return fmt.Errorf("报告类型不能为空")
}
if q.Title == "" {
return fmt.Errorf("报告标题不能为空")
}
if q.Period == "" {
return fmt.Errorf("统计周期不能为空")
}
if q.UserRole == "" {
return fmt.Errorf("用户角色不能为空")
}
return nil
}
// Validate 验证导出查询对象
func (q *ExportQuery) Validate() error {
if q.Format == "" {
return fmt.Errorf("导出格式不能为空")
}
if q.MetricType == "" {
return fmt.Errorf("指标类型不能为空")
}
if q.StartDate.IsZero() || q.EndDate.IsZero() {
return fmt.Errorf("开始日期和结束日期不能为空")
}
if q.StartDate.After(q.EndDate) {
return fmt.Errorf("开始日期不能晚于结束日期")
}
validFormats := []string{"excel", "csv", "pdf"}
for _, validFormat := range validFormats {
if q.Format == validFormat {
return nil
}
}
return fmt.Errorf("无效的导出格式: %s", q.Format)
}

View File

@@ -0,0 +1,107 @@
package repositories
import (
"context"
"time"
"tyapi-server/internal/domains/statistics/entities"
)
// StatisticsRepository 统计指标仓储接口
type StatisticsRepository interface {
// 基础CRUD操作
Save(ctx context.Context, metric *entities.StatisticsMetric) error
FindByID(ctx context.Context, id string) (*entities.StatisticsMetric, error)
FindByType(ctx context.Context, metricType string, limit, offset int) ([]*entities.StatisticsMetric, error)
Update(ctx context.Context, metric *entities.StatisticsMetric) error
Delete(ctx context.Context, id string) error
// 按类型和日期范围查询
FindByTypeAndDateRange(ctx context.Context, metricType string, startDate, endDate time.Time) ([]*entities.StatisticsMetric, error)
FindByTypeDimensionAndDateRange(ctx context.Context, metricType, dimension string, startDate, endDate time.Time) ([]*entities.StatisticsMetric, error)
FindByTypeNameAndDateRange(ctx context.Context, metricType, metricName string, startDate, endDate time.Time) ([]*entities.StatisticsMetric, error)
// 聚合查询
GetAggregatedMetrics(ctx context.Context, metricType, dimension string, startDate, endDate time.Time) (map[string]float64, error)
GetMetricsByDimension(ctx context.Context, dimension string, startDate, endDate time.Time) ([]*entities.StatisticsMetric, error)
// 统计查询
CountByType(ctx context.Context, metricType string) (int64, error)
CountByTypeAndDateRange(ctx context.Context, metricType string, startDate, endDate time.Time) (int64, error)
// 批量操作
BatchSave(ctx context.Context, metrics []*entities.StatisticsMetric) error
BatchDelete(ctx context.Context, ids []string) error
// 清理操作
DeleteByDateRange(ctx context.Context, startDate, endDate time.Time) error
DeleteByTypeAndDateRange(ctx context.Context, metricType string, startDate, endDate time.Time) error
}
// StatisticsReportRepository 统计报告仓储接口
type StatisticsReportRepository interface {
// 基础CRUD操作
Save(ctx context.Context, report *entities.StatisticsReport) error
FindByID(ctx context.Context, id string) (*entities.StatisticsReport, error)
FindByUser(ctx context.Context, userID string, limit, offset int) ([]*entities.StatisticsReport, error)
FindByStatus(ctx context.Context, status string) ([]*entities.StatisticsReport, error)
Update(ctx context.Context, report *entities.StatisticsReport) error
Delete(ctx context.Context, id string) error
// 按类型查询
FindByType(ctx context.Context, reportType string, limit, offset int) ([]*entities.StatisticsReport, error)
FindByTypeAndPeriod(ctx context.Context, reportType, period string, limit, offset int) ([]*entities.StatisticsReport, error)
// 按日期范围查询
FindByDateRange(ctx context.Context, startDate, endDate time.Time, limit, offset int) ([]*entities.StatisticsReport, error)
FindByUserAndDateRange(ctx context.Context, userID string, startDate, endDate time.Time, limit, offset int) ([]*entities.StatisticsReport, error)
// 统计查询
CountByUser(ctx context.Context, userID string) (int64, error)
CountByType(ctx context.Context, reportType string) (int64, error)
CountByStatus(ctx context.Context, status string) (int64, error)
// 批量操作
BatchSave(ctx context.Context, reports []*entities.StatisticsReport) error
BatchDelete(ctx context.Context, ids []string) error
// 清理操作
DeleteExpiredReports(ctx context.Context, expiredBefore time.Time) error
DeleteByStatus(ctx context.Context, status string) error
}
// StatisticsDashboardRepository 统计仪表板仓储接口
type StatisticsDashboardRepository interface {
// 基础CRUD操作
Save(ctx context.Context, dashboard *entities.StatisticsDashboard) error
FindByID(ctx context.Context, id string) (*entities.StatisticsDashboard, error)
FindByUser(ctx context.Context, userID string, limit, offset int) ([]*entities.StatisticsDashboard, error)
FindByUserRole(ctx context.Context, userRole string, limit, offset int) ([]*entities.StatisticsDashboard, error)
Update(ctx context.Context, dashboard *entities.StatisticsDashboard) error
Delete(ctx context.Context, id string) error
// 按角色查询
FindByRole(ctx context.Context, userRole string, limit, offset int) ([]*entities.StatisticsDashboard, error)
FindDefaultByRole(ctx context.Context, userRole string) (*entities.StatisticsDashboard, error)
FindActiveByRole(ctx context.Context, userRole string, limit, offset int) ([]*entities.StatisticsDashboard, error)
// 按状态查询
FindByStatus(ctx context.Context, isActive bool, limit, offset int) ([]*entities.StatisticsDashboard, error)
FindByAccessLevel(ctx context.Context, accessLevel string, limit, offset int) ([]*entities.StatisticsDashboard, error)
// 统计查询
CountByUser(ctx context.Context, userID string) (int64, error)
CountByRole(ctx context.Context, userRole string) (int64, error)
CountByStatus(ctx context.Context, isActive bool) (int64, error)
// 批量操作
BatchSave(ctx context.Context, dashboards []*entities.StatisticsDashboard) error
BatchDelete(ctx context.Context, ids []string) error
// 特殊操作
SetDefaultDashboard(ctx context.Context, dashboardID string) error
RemoveDefaultDashboard(ctx context.Context, userRole string) error
ActivateDashboard(ctx context.Context, dashboardID string) error
DeactivateDashboard(ctx context.Context, dashboardID string) error
}

View File

@@ -0,0 +1,388 @@
package services
import (
"context"
"fmt"
"time"
"go.uber.org/zap"
"tyapi-server/internal/domains/statistics/entities"
"tyapi-server/internal/domains/statistics/repositories"
)
// StatisticsAggregateService 统计聚合服务接口
// 负责统计数据的聚合和计算
type StatisticsAggregateService interface {
// 实时统计
UpdateRealtimeMetric(ctx context.Context, metricType, metricName string, value float64) error
GetRealtimeMetrics(ctx context.Context, metricType string) (map[string]float64, error)
// 历史统计聚合
AggregateHourlyMetrics(ctx context.Context, date time.Time) error
AggregateDailyMetrics(ctx context.Context, date time.Time) error
AggregateWeeklyMetrics(ctx context.Context, date time.Time) error
AggregateMonthlyMetrics(ctx context.Context, date time.Time) error
// 统计查询
GetMetricsByType(ctx context.Context, metricType string, startDate, endDate time.Time) ([]*entities.StatisticsMetric, error)
GetMetricsByDimension(ctx context.Context, metricType, dimension string, startDate, endDate time.Time) ([]*entities.StatisticsMetric, error)
// 统计计算
CalculateGrowthRate(ctx context.Context, metricType, metricName string, currentPeriod, previousPeriod time.Time) (float64, error)
CalculateTrend(ctx context.Context, metricType, metricName string, startDate, endDate time.Time) (string, error)
}
// StatisticsAggregateServiceImpl 统计聚合服务实现
type StatisticsAggregateServiceImpl struct {
metricRepo repositories.StatisticsRepository
logger *zap.Logger
}
// NewStatisticsAggregateService 创建统计聚合服务
func NewStatisticsAggregateService(
metricRepo repositories.StatisticsRepository,
logger *zap.Logger,
) StatisticsAggregateService {
return &StatisticsAggregateServiceImpl{
metricRepo: metricRepo,
logger: logger,
}
}
// UpdateRealtimeMetric 更新实时统计指标
func (s *StatisticsAggregateServiceImpl) UpdateRealtimeMetric(ctx context.Context, metricType, metricName string, value float64) error {
if metricType == "" {
return fmt.Errorf("指标类型不能为空")
}
if metricName == "" {
return fmt.Errorf("指标名称不能为空")
}
// 创建或更新实时指标
metric, err := entities.NewStatisticsMetric(metricType, metricName, "realtime", value, time.Now())
if err != nil {
s.logger.Error("创建统计指标失败",
zap.String("metric_type", metricType),
zap.String("metric_name", metricName),
zap.Error(err))
return fmt.Errorf("创建统计指标失败: %w", err)
}
// 保存到数据库
err = s.metricRepo.Save(ctx, metric)
if err != nil {
s.logger.Error("保存统计指标失败",
zap.String("metric_id", metric.ID),
zap.Error(err))
return fmt.Errorf("保存统计指标失败: %w", err)
}
s.logger.Info("实时统计指标更新成功",
zap.String("metric_type", metricType),
zap.String("metric_name", metricName),
zap.Float64("value", value))
return nil
}
// GetRealtimeMetrics 获取实时统计指标
func (s *StatisticsAggregateServiceImpl) GetRealtimeMetrics(ctx context.Context, metricType string) (map[string]float64, error) {
if metricType == "" {
return nil, fmt.Errorf("指标类型不能为空")
}
// 获取今天的实时指标
today := time.Now().Truncate(24 * time.Hour)
tomorrow := today.Add(24 * time.Hour)
metrics, err := s.metricRepo.FindByTypeAndDateRange(ctx, metricType, today, tomorrow)
if err != nil {
s.logger.Error("查询实时统计指标失败",
zap.String("metric_type", metricType),
zap.Error(err))
return nil, fmt.Errorf("查询实时统计指标失败: %w", err)
}
// 转换为map格式
result := make(map[string]float64)
for _, metric := range metrics {
if metric.Dimension == "realtime" {
result[metric.MetricName] = metric.Value
}
}
return result, nil
}
// AggregateHourlyMetrics 聚合小时级统计指标
func (s *StatisticsAggregateServiceImpl) AggregateHourlyMetrics(ctx context.Context, date time.Time) error {
s.logger.Info("开始聚合小时级统计指标", zap.Time("date", date))
// 获取指定小时的所有实时指标
startTime := date.Truncate(time.Hour)
endTime := startTime.Add(time.Hour)
// 聚合不同类型的指标
metricTypes := []string{"api_calls", "users", "finance", "products", "certification"}
for _, metricType := range metricTypes {
err := s.aggregateMetricsByType(ctx, metricType, startTime, endTime, "hourly")
if err != nil {
s.logger.Error("聚合小时级指标失败",
zap.String("metric_type", metricType),
zap.Error(err))
return fmt.Errorf("聚合小时级指标失败: %w", err)
}
}
s.logger.Info("小时级统计指标聚合完成", zap.Time("date", date))
return nil
}
// AggregateDailyMetrics 聚合日级统计指标
func (s *StatisticsAggregateServiceImpl) AggregateDailyMetrics(ctx context.Context, date time.Time) error {
s.logger.Info("开始聚合日级统计指标", zap.Time("date", date))
// 获取指定日期的所有小时级指标
startTime := date.Truncate(24 * time.Hour)
endTime := startTime.Add(24 * time.Hour)
// 聚合不同类型的指标
metricTypes := []string{"api_calls", "users", "finance", "products", "certification"}
for _, metricType := range metricTypes {
err := s.aggregateMetricsByType(ctx, metricType, startTime, endTime, "daily")
if err != nil {
s.logger.Error("聚合日级指标失败",
zap.String("metric_type", metricType),
zap.Error(err))
return fmt.Errorf("聚合日级指标失败: %w", err)
}
}
s.logger.Info("日级统计指标聚合完成", zap.Time("date", date))
return nil
}
// AggregateWeeklyMetrics 聚合周级统计指标
func (s *StatisticsAggregateServiceImpl) AggregateWeeklyMetrics(ctx context.Context, date time.Time) error {
s.logger.Info("开始聚合周级统计指标", zap.Time("date", date))
// 获取指定周的所有日级指标
startTime := date.Truncate(24 * time.Hour)
endTime := startTime.Add(7 * 24 * time.Hour)
// 聚合不同类型的指标
metricTypes := []string{"api_calls", "users", "finance", "products", "certification"}
for _, metricType := range metricTypes {
err := s.aggregateMetricsByType(ctx, metricType, startTime, endTime, "weekly")
if err != nil {
s.logger.Error("聚合周级指标失败",
zap.String("metric_type", metricType),
zap.Error(err))
return fmt.Errorf("聚合周级指标失败: %w", err)
}
}
s.logger.Info("周级统计指标聚合完成", zap.Time("date", date))
return nil
}
// AggregateMonthlyMetrics 聚合月级统计指标
func (s *StatisticsAggregateServiceImpl) AggregateMonthlyMetrics(ctx context.Context, date time.Time) error {
s.logger.Info("开始聚合月级统计指标", zap.Time("date", date))
// 获取指定月的所有日级指标
startTime := date.Truncate(24 * time.Hour)
endTime := startTime.AddDate(0, 1, 0)
// 聚合不同类型的指标
metricTypes := []string{"api_calls", "users", "finance", "products", "certification"}
for _, metricType := range metricTypes {
err := s.aggregateMetricsByType(ctx, metricType, startTime, endTime, "monthly")
if err != nil {
s.logger.Error("聚合月级指标失败",
zap.String("metric_type", metricType),
zap.Error(err))
return fmt.Errorf("聚合月级指标失败: %w", err)
}
}
s.logger.Info("月级统计指标聚合完成", zap.Time("date", date))
return nil
}
// GetMetricsByType 根据类型获取统计指标
func (s *StatisticsAggregateServiceImpl) GetMetricsByType(ctx context.Context, metricType string, startDate, endDate time.Time) ([]*entities.StatisticsMetric, error) {
if metricType == "" {
return nil, fmt.Errorf("指标类型不能为空")
}
metrics, err := s.metricRepo.FindByTypeAndDateRange(ctx, metricType, startDate, endDate)
if err != nil {
s.logger.Error("查询统计指标失败",
zap.String("metric_type", metricType),
zap.Time("start_date", startDate),
zap.Time("end_date", endDate),
zap.Error(err))
return nil, fmt.Errorf("查询统计指标失败: %w", err)
}
return metrics, nil
}
// GetMetricsByDimension 根据维度获取统计指标
func (s *StatisticsAggregateServiceImpl) GetMetricsByDimension(ctx context.Context, metricType, dimension string, startDate, endDate time.Time) ([]*entities.StatisticsMetric, error) {
if metricType == "" {
return nil, fmt.Errorf("指标类型不能为空")
}
if dimension == "" {
return nil, fmt.Errorf("统计维度不能为空")
}
metrics, err := s.metricRepo.FindByTypeDimensionAndDateRange(ctx, metricType, dimension, startDate, endDate)
if err != nil {
s.logger.Error("查询统计指标失败",
zap.String("metric_type", metricType),
zap.String("dimension", dimension),
zap.Time("start_date", startDate),
zap.Time("end_date", endDate),
zap.Error(err))
return nil, fmt.Errorf("查询统计指标失败: %w", err)
}
return metrics, nil
}
// CalculateGrowthRate 计算增长率
func (s *StatisticsAggregateServiceImpl) CalculateGrowthRate(ctx context.Context, metricType, metricName string, currentPeriod, previousPeriod time.Time) (float64, error) {
if metricType == "" || metricName == "" {
return 0, fmt.Errorf("指标类型和名称不能为空")
}
// 获取当前周期的指标值
currentMetrics, err := s.metricRepo.FindByTypeNameAndDateRange(ctx, metricType, metricName, currentPeriod, currentPeriod.Add(24*time.Hour))
if err != nil {
return 0, fmt.Errorf("查询当前周期指标失败: %w", err)
}
// 获取上一周期的指标值
previousMetrics, err := s.metricRepo.FindByTypeNameAndDateRange(ctx, metricType, metricName, previousPeriod, previousPeriod.Add(24*time.Hour))
if err != nil {
return 0, fmt.Errorf("查询上一周期指标失败: %w", err)
}
// 计算总值
var currentValue, previousValue float64
for _, metric := range currentMetrics {
currentValue += metric.Value
}
for _, metric := range previousMetrics {
previousValue += metric.Value
}
// 计算增长率
if previousValue == 0 {
if currentValue > 0 {
return 100, nil // 从0增长到正数增长率为100%
}
return 0, nil // 都是0增长率为0%
}
growthRate := ((currentValue - previousValue) / previousValue) * 100
return growthRate, nil
}
// CalculateTrend 计算趋势
func (s *StatisticsAggregateServiceImpl) CalculateTrend(ctx context.Context, metricType, metricName string, startDate, endDate time.Time) (string, error) {
if metricType == "" || metricName == "" {
return "", fmt.Errorf("指标类型和名称不能为空")
}
// 获取时间范围内的指标
metrics, err := s.metricRepo.FindByTypeNameAndDateRange(ctx, metricType, metricName, startDate, endDate)
if err != nil {
return "", fmt.Errorf("查询指标失败: %w", err)
}
if len(metrics) < 2 {
return "insufficient_data", nil // 数据不足
}
// 按时间排序
sortMetricsByDate(metrics)
// 计算趋势
firstValue := metrics[0].Value
lastValue := metrics[len(metrics)-1].Value
if lastValue > firstValue {
return "increasing", nil // 上升趋势
} else if lastValue < firstValue {
return "decreasing", nil // 下降趋势
} else {
return "stable", nil // 稳定趋势
}
}
// aggregateMetricsByType 按类型聚合指标
func (s *StatisticsAggregateServiceImpl) aggregateMetricsByType(ctx context.Context, metricType string, startTime, endTime time.Time, dimension string) error {
// 获取源数据(实时或小时级数据)
sourceDimension := "realtime"
if dimension == "daily" {
sourceDimension = "hourly"
} else if dimension == "weekly" || dimension == "monthly" {
sourceDimension = "daily"
}
// 查询源数据
sourceMetrics, err := s.metricRepo.FindByTypeDimensionAndDateRange(ctx, metricType, sourceDimension, startTime, endTime)
if err != nil {
return fmt.Errorf("查询源数据失败: %w", err)
}
// 按指标名称分组聚合
metricGroups := make(map[string][]*entities.StatisticsMetric)
for _, metric := range sourceMetrics {
metricGroups[metric.MetricName] = append(metricGroups[metric.MetricName], metric)
}
// 聚合每个指标
for metricName, metrics := range metricGroups {
var totalValue float64
for _, metric := range metrics {
totalValue += metric.Value
}
// 创建聚合后的指标
aggregatedMetric, err := entities.NewStatisticsMetric(metricType, metricName, dimension, totalValue, startTime)
if err != nil {
return fmt.Errorf("创建聚合指标失败: %w", err)
}
// 保存聚合指标
err = s.metricRepo.Save(ctx, aggregatedMetric)
if err != nil {
return fmt.Errorf("保存聚合指标失败: %w", err)
}
}
return nil
}
// sortMetricsByDate 按日期排序指标
func sortMetricsByDate(metrics []*entities.StatisticsMetric) {
// 简单的冒泡排序
n := len(metrics)
for i := 0; i < n-1; i++ {
for j := 0; j < n-i-1; j++ {
if metrics[j].Date.After(metrics[j+1].Date) {
metrics[j], metrics[j+1] = metrics[j+1], metrics[j]
}
}
}
}

View File

@@ -0,0 +1,510 @@
package services
import (
"context"
"fmt"
"math"
"time"
"go.uber.org/zap"
"tyapi-server/internal/domains/statistics/entities"
"tyapi-server/internal/domains/statistics/repositories"
)
// StatisticsCalculationService 统计计算服务接口
// 负责各种统计计算和分析
type StatisticsCalculationService interface {
// 基础统计计算
CalculateTotal(ctx context.Context, metricType, metricName string, startDate, endDate time.Time) (float64, error)
CalculateAverage(ctx context.Context, metricType, metricName string, startDate, endDate time.Time) (float64, error)
CalculateMax(ctx context.Context, metricType, metricName string, startDate, endDate time.Time) (float64, error)
CalculateMin(ctx context.Context, metricType, metricName string, startDate, endDate time.Time) (float64, error)
// 高级统计计算
CalculateGrowthRate(ctx context.Context, metricType, metricName string, currentPeriod, previousPeriod time.Time) (float64, error)
CalculateTrend(ctx context.Context, metricType, metricName string, startDate, endDate time.Time) (string, error)
CalculateCorrelation(ctx context.Context, metricType1, metricName1, metricType2, metricName2 string, startDate, endDate time.Time) (float64, error)
// 业务指标计算
CalculateSuccessRate(ctx context.Context, startDate, endDate time.Time) (float64, error)
CalculateConversionRate(ctx context.Context, startDate, endDate time.Time) (float64, error)
CalculateRetentionRate(ctx context.Context, startDate, endDate time.Time) (float64, error)
// 时间序列分析
CalculateMovingAverage(ctx context.Context, metricType, metricName string, startDate, endDate time.Time, windowSize int) ([]float64, error)
CalculateSeasonality(ctx context.Context, metricType, metricName string, startDate, endDate time.Time) (map[string]float64, error)
}
// StatisticsCalculationServiceImpl 统计计算服务实现
type StatisticsCalculationServiceImpl struct {
metricRepo repositories.StatisticsRepository
logger *zap.Logger
}
// NewStatisticsCalculationService 创建统计计算服务
func NewStatisticsCalculationService(
metricRepo repositories.StatisticsRepository,
logger *zap.Logger,
) StatisticsCalculationService {
return &StatisticsCalculationServiceImpl{
metricRepo: metricRepo,
logger: logger,
}
}
// CalculateTotal 计算总值
func (s *StatisticsCalculationServiceImpl) CalculateTotal(ctx context.Context, metricType, metricName string, startDate, endDate time.Time) (float64, error) {
if metricType == "" || metricName == "" {
return 0, fmt.Errorf("指标类型和名称不能为空")
}
metrics, err := s.metricRepo.FindByTypeNameAndDateRange(ctx, metricType, metricName, startDate, endDate)
if err != nil {
s.logger.Error("查询指标失败",
zap.String("metric_type", metricType),
zap.String("metric_name", metricName),
zap.Error(err))
return 0, fmt.Errorf("查询指标失败: %w", err)
}
var total float64
for _, metric := range metrics {
total += metric.Value
}
s.logger.Info("计算总值完成",
zap.String("metric_type", metricType),
zap.String("metric_name", metricName),
zap.Float64("total", total))
return total, nil
}
// CalculateAverage 计算平均值
func (s *StatisticsCalculationServiceImpl) CalculateAverage(ctx context.Context, metricType, metricName string, startDate, endDate time.Time) (float64, error) {
if metricType == "" || metricName == "" {
return 0, fmt.Errorf("指标类型和名称不能为空")
}
metrics, err := s.metricRepo.FindByTypeNameAndDateRange(ctx, metricType, metricName, startDate, endDate)
if err != nil {
s.logger.Error("查询指标失败",
zap.String("metric_type", metricType),
zap.String("metric_name", metricName),
zap.Error(err))
return 0, fmt.Errorf("查询指标失败: %w", err)
}
if len(metrics) == 0 {
return 0, nil
}
var total float64
for _, metric := range metrics {
total += metric.Value
}
average := total / float64(len(metrics))
s.logger.Info("计算平均值完成",
zap.String("metric_type", metricType),
zap.String("metric_name", metricName),
zap.Float64("average", average))
return average, nil
}
// CalculateMax 计算最大值
func (s *StatisticsCalculationServiceImpl) CalculateMax(ctx context.Context, metricType, metricName string, startDate, endDate time.Time) (float64, error) {
if metricType == "" || metricName == "" {
return 0, fmt.Errorf("指标类型和名称不能为空")
}
metrics, err := s.metricRepo.FindByTypeNameAndDateRange(ctx, metricType, metricName, startDate, endDate)
if err != nil {
s.logger.Error("查询指标失败",
zap.String("metric_type", metricType),
zap.String("metric_name", metricName),
zap.Error(err))
return 0, fmt.Errorf("查询指标失败: %w", err)
}
if len(metrics) == 0 {
return 0, nil
}
max := metrics[0].Value
for _, metric := range metrics {
if metric.Value > max {
max = metric.Value
}
}
s.logger.Info("计算最大值完成",
zap.String("metric_type", metricType),
zap.String("metric_name", metricName),
zap.Float64("max", max))
return max, nil
}
// CalculateMin 计算最小值
func (s *StatisticsCalculationServiceImpl) CalculateMin(ctx context.Context, metricType, metricName string, startDate, endDate time.Time) (float64, error) {
if metricType == "" || metricName == "" {
return 0, fmt.Errorf("指标类型和名称不能为空")
}
metrics, err := s.metricRepo.FindByTypeNameAndDateRange(ctx, metricType, metricName, startDate, endDate)
if err != nil {
s.logger.Error("查询指标失败",
zap.String("metric_type", metricType),
zap.String("metric_name", metricName),
zap.Error(err))
return 0, fmt.Errorf("查询指标失败: %w", err)
}
if len(metrics) == 0 {
return 0, nil
}
min := metrics[0].Value
for _, metric := range metrics {
if metric.Value < min {
min = metric.Value
}
}
s.logger.Info("计算最小值完成",
zap.String("metric_type", metricType),
zap.String("metric_name", metricName),
zap.Float64("min", min))
return min, nil
}
// CalculateGrowthRate 计算增长率
func (s *StatisticsCalculationServiceImpl) CalculateGrowthRate(ctx context.Context, metricType, metricName string, currentPeriod, previousPeriod time.Time) (float64, error) {
if metricType == "" || metricName == "" {
return 0, fmt.Errorf("指标类型和名称不能为空")
}
// 获取当前周期的总值
currentTotal, err := s.CalculateTotal(ctx, metricType, metricName, currentPeriod, currentPeriod.Add(24*time.Hour))
if err != nil {
return 0, fmt.Errorf("计算当前周期总值失败: %w", err)
}
// 获取上一周期的总值
previousTotal, err := s.CalculateTotal(ctx, metricType, metricName, previousPeriod, previousPeriod.Add(24*time.Hour))
if err != nil {
return 0, fmt.Errorf("计算上一周期总值失败: %w", err)
}
// 计算增长率
if previousTotal == 0 {
if currentTotal > 0 {
return 100, nil // 从0增长到正数增长率为100%
}
return 0, nil // 都是0增长率为0%
}
growthRate := ((currentTotal - previousTotal) / previousTotal) * 100
s.logger.Info("计算增长率完成",
zap.String("metric_type", metricType),
zap.String("metric_name", metricName),
zap.Float64("growth_rate", growthRate))
return growthRate, nil
}
// CalculateTrend 计算趋势
func (s *StatisticsCalculationServiceImpl) CalculateTrend(ctx context.Context, metricType, metricName string, startDate, endDate time.Time) (string, error) {
if metricType == "" || metricName == "" {
return "", fmt.Errorf("指标类型和名称不能为空")
}
metrics, err := s.metricRepo.FindByTypeNameAndDateRange(ctx, metricType, metricName, startDate, endDate)
if err != nil {
s.logger.Error("查询指标失败",
zap.String("metric_type", metricType),
zap.String("metric_name", metricName),
zap.Error(err))
return "", fmt.Errorf("查询指标失败: %w", err)
}
if len(metrics) < 2 {
return "insufficient_data", nil // 数据不足
}
// 按时间排序
sortMetricsByDateCalc(metrics)
// 计算趋势
firstValue := metrics[0].Value
lastValue := metrics[len(metrics)-1].Value
var trend string
if lastValue > firstValue {
trend = "increasing" // 上升趋势
} else if lastValue < firstValue {
trend = "decreasing" // 下降趋势
} else {
trend = "stable" // 稳定趋势
}
s.logger.Info("计算趋势完成",
zap.String("metric_type", metricType),
zap.String("metric_name", metricName),
zap.String("trend", trend))
return trend, nil
}
// CalculateCorrelation 计算相关性
func (s *StatisticsCalculationServiceImpl) CalculateCorrelation(ctx context.Context, metricType1, metricName1, metricType2, metricName2 string, startDate, endDate time.Time) (float64, error) {
if metricType1 == "" || metricName1 == "" || metricType2 == "" || metricName2 == "" {
return 0, fmt.Errorf("指标类型和名称不能为空")
}
// 获取两个指标的数据
metrics1, err := s.metricRepo.FindByTypeNameAndDateRange(ctx, metricType1, metricName1, startDate, endDate)
if err != nil {
return 0, fmt.Errorf("查询指标1失败: %w", err)
}
metrics2, err := s.metricRepo.FindByTypeNameAndDateRange(ctx, metricType2, metricName2, startDate, endDate)
if err != nil {
return 0, fmt.Errorf("查询指标2失败: %w", err)
}
if len(metrics1) != len(metrics2) || len(metrics1) < 2 {
return 0, fmt.Errorf("数据点数量不足或不对称")
}
// 计算皮尔逊相关系数
correlation := s.calculatePearsonCorrelation(metrics1, metrics2)
s.logger.Info("计算相关性完成",
zap.String("metric1", metricType1+"."+metricName1),
zap.String("metric2", metricType2+"."+metricName2),
zap.Float64("correlation", correlation))
return correlation, nil
}
// CalculateSuccessRate 计算成功率
func (s *StatisticsCalculationServiceImpl) CalculateSuccessRate(ctx context.Context, startDate, endDate time.Time) (float64, error) {
// 获取成功调用次数
successTotal, err := s.CalculateTotal(ctx, "api_calls", "success_count", startDate, endDate)
if err != nil {
return 0, fmt.Errorf("计算成功调用次数失败: %w", err)
}
// 获取总调用次数
totalCalls, err := s.CalculateTotal(ctx, "api_calls", "total_count", startDate, endDate)
if err != nil {
return 0, fmt.Errorf("计算总调用次数失败: %w", err)
}
if totalCalls == 0 {
return 0, nil
}
successRate := (successTotal / totalCalls) * 100
s.logger.Info("计算成功率完成",
zap.Float64("success_rate", successRate))
return successRate, nil
}
// CalculateConversionRate 计算转化率
func (s *StatisticsCalculationServiceImpl) CalculateConversionRate(ctx context.Context, startDate, endDate time.Time) (float64, error) {
// 获取认证用户数
certifiedUsers, err := s.CalculateTotal(ctx, "users", "certified_count", startDate, endDate)
if err != nil {
return 0, fmt.Errorf("计算认证用户数失败: %w", err)
}
// 获取总用户数
totalUsers, err := s.CalculateTotal(ctx, "users", "total_count", startDate, endDate)
if err != nil {
return 0, fmt.Errorf("计算总用户数失败: %w", err)
}
if totalUsers == 0 {
return 0, nil
}
conversionRate := (certifiedUsers / totalUsers) * 100
s.logger.Info("计算转化率完成",
zap.Float64("conversion_rate", conversionRate))
return conversionRate, nil
}
// CalculateRetentionRate 计算留存率
func (s *StatisticsCalculationServiceImpl) CalculateRetentionRate(ctx context.Context, startDate, endDate time.Time) (float64, error) {
// 获取活跃用户数
activeUsers, err := s.CalculateTotal(ctx, "users", "active_count", startDate, endDate)
if err != nil {
return 0, fmt.Errorf("计算活跃用户数失败: %w", err)
}
// 获取总用户数
totalUsers, err := s.CalculateTotal(ctx, "users", "total_count", startDate, endDate)
if err != nil {
return 0, fmt.Errorf("计算总用户数失败: %w", err)
}
if totalUsers == 0 {
return 0, nil
}
retentionRate := (activeUsers / totalUsers) * 100
s.logger.Info("计算留存率完成",
zap.Float64("retention_rate", retentionRate))
return retentionRate, nil
}
// CalculateMovingAverage 计算移动平均
func (s *StatisticsCalculationServiceImpl) CalculateMovingAverage(ctx context.Context, metricType, metricName string, startDate, endDate time.Time, windowSize int) ([]float64, error) {
if metricType == "" || metricName == "" {
return nil, fmt.Errorf("指标类型和名称不能为空")
}
if windowSize <= 0 {
return nil, fmt.Errorf("窗口大小必须大于0")
}
metrics, err := s.metricRepo.FindByTypeNameAndDateRange(ctx, metricType, metricName, startDate, endDate)
if err != nil {
s.logger.Error("查询指标失败",
zap.String("metric_type", metricType),
zap.String("metric_name", metricName),
zap.Error(err))
return nil, fmt.Errorf("查询指标失败: %w", err)
}
if len(metrics) < windowSize {
return nil, fmt.Errorf("数据点数量不足")
}
// 按时间排序
sortMetricsByDateCalc(metrics)
// 计算移动平均
var movingAverages []float64
for i := windowSize - 1; i < len(metrics); i++ {
var sum float64
for j := i - windowSize + 1; j <= i; j++ {
sum += metrics[j].Value
}
average := sum / float64(windowSize)
movingAverages = append(movingAverages, average)
}
s.logger.Info("计算移动平均完成",
zap.String("metric_type", metricType),
zap.String("metric_name", metricName),
zap.Int("window_size", windowSize),
zap.Int("result_count", len(movingAverages)))
return movingAverages, nil
}
// CalculateSeasonality 计算季节性
func (s *StatisticsCalculationServiceImpl) CalculateSeasonality(ctx context.Context, metricType, metricName string, startDate, endDate time.Time) (map[string]float64, error) {
if metricType == "" || metricName == "" {
return nil, fmt.Errorf("指标类型和名称不能为空")
}
metrics, err := s.metricRepo.FindByTypeNameAndDateRange(ctx, metricType, metricName, startDate, endDate)
if err != nil {
s.logger.Error("查询指标失败",
zap.String("metric_type", metricType),
zap.String("metric_name", metricName),
zap.Error(err))
return nil, fmt.Errorf("查询指标失败: %w", err)
}
if len(metrics) < 7 {
return nil, fmt.Errorf("数据点数量不足至少需要7个数据点")
}
// 按星期几分组
weeklyAverages := make(map[string][]float64)
for _, metric := range metrics {
weekday := metric.Date.Weekday().String()
weeklyAverages[weekday] = append(weeklyAverages[weekday], metric.Value)
}
// 计算每个星期几的平均值
seasonality := make(map[string]float64)
for weekday, values := range weeklyAverages {
var sum float64
for _, value := range values {
sum += value
}
seasonality[weekday] = sum / float64(len(values))
}
s.logger.Info("计算季节性完成",
zap.String("metric_type", metricType),
zap.String("metric_name", metricName),
zap.Int("weekday_count", len(seasonality)))
return seasonality, nil
}
// calculatePearsonCorrelation 计算皮尔逊相关系数
func (s *StatisticsCalculationServiceImpl) calculatePearsonCorrelation(metrics1, metrics2 []*entities.StatisticsMetric) float64 {
n := len(metrics1)
if n < 2 {
return 0
}
// 计算均值
var sum1, sum2 float64
for i := 0; i < n; i++ {
sum1 += metrics1[i].Value
sum2 += metrics2[i].Value
}
mean1 := sum1 / float64(n)
mean2 := sum2 / float64(n)
// 计算协方差和方差
var numerator, denominator1, denominator2 float64
for i := 0; i < n; i++ {
diff1 := metrics1[i].Value - mean1
diff2 := metrics2[i].Value - mean2
numerator += diff1 * diff2
denominator1 += diff1 * diff1
denominator2 += diff2 * diff2
}
// 计算相关系数
if denominator1 == 0 || denominator2 == 0 {
return 0
}
correlation := numerator / math.Sqrt(denominator1*denominator2)
return correlation
}
// sortMetricsByDateCalc 按日期排序指标
func sortMetricsByDateCalc(metrics []*entities.StatisticsMetric) {
// 简单的冒泡排序
n := len(metrics)
for i := 0; i < n-1; i++ {
for j := 0; j < n-i-1; j++ {
if metrics[j].Date.After(metrics[j+1].Date) {
metrics[j], metrics[j+1] = metrics[j+1], metrics[j]
}
}
}
}

View File

@@ -0,0 +1,582 @@
package services
import (
"context"
"encoding/json"
"fmt"
"time"
"go.uber.org/zap"
"tyapi-server/internal/domains/statistics/entities"
"tyapi-server/internal/domains/statistics/repositories"
)
// StatisticsReportService 报告生成服务接口
// 负责统计报告的生成和管理
type StatisticsReportService interface {
// 报告生成
GenerateDashboardReport(ctx context.Context, userRole string, period string) (*entities.StatisticsReport, error)
GenerateSummaryReport(ctx context.Context, period string, startDate, endDate time.Time) (*entities.StatisticsReport, error)
GenerateDetailedReport(ctx context.Context, reportType string, startDate, endDate time.Time, filters map[string]interface{}) (*entities.StatisticsReport, error)
// 报告管理
GetReport(ctx context.Context, reportID string) (*entities.StatisticsReport, error)
GetReportsByUser(ctx context.Context, userID string, limit, offset int) ([]*entities.StatisticsReport, error)
DeleteReport(ctx context.Context, reportID string) error
// 报告状态管理
StartReportGeneration(ctx context.Context, reportID, generatedBy string) error
CompleteReportGeneration(ctx context.Context, reportID string, content string) error
FailReportGeneration(ctx context.Context, reportID string, reason string) error
// 报告清理
CleanupExpiredReports(ctx context.Context) error
}
// StatisticsReportServiceImpl 报告生成服务实现
type StatisticsReportServiceImpl struct {
reportRepo repositories.StatisticsReportRepository
metricRepo repositories.StatisticsRepository
calcService StatisticsCalculationService
logger *zap.Logger
}
// NewStatisticsReportService 创建报告生成服务
func NewStatisticsReportService(
reportRepo repositories.StatisticsReportRepository,
metricRepo repositories.StatisticsRepository,
calcService StatisticsCalculationService,
logger *zap.Logger,
) StatisticsReportService {
return &StatisticsReportServiceImpl{
reportRepo: reportRepo,
metricRepo: metricRepo,
calcService: calcService,
logger: logger,
}
}
// GenerateDashboardReport 生成仪表板报告
func (s *StatisticsReportServiceImpl) GenerateDashboardReport(ctx context.Context, userRole string, period string) (*entities.StatisticsReport, error) {
if userRole == "" {
return nil, fmt.Errorf("用户角色不能为空")
}
if period == "" {
return nil, fmt.Errorf("统计周期不能为空")
}
// 创建报告实体
title := fmt.Sprintf("%s仪表板报告 - %s", s.getRoleDisplayName(userRole), s.getPeriodDisplayName(period))
report, err := entities.NewStatisticsReport("dashboard", title, period, userRole)
if err != nil {
s.logger.Error("创建仪表板报告失败",
zap.String("user_role", userRole),
zap.String("period", period),
zap.Error(err))
return nil, fmt.Errorf("创建仪表板报告失败: %w", err)
}
// 保存报告
err = s.reportRepo.Save(ctx, report)
if err != nil {
s.logger.Error("保存仪表板报告失败",
zap.String("report_id", report.ID),
zap.Error(err))
return nil, fmt.Errorf("保存仪表板报告失败: %w", err)
}
s.logger.Info("仪表板报告创建成功",
zap.String("report_id", report.ID),
zap.String("user_role", userRole),
zap.String("period", period))
return report, nil
}
// GenerateSummaryReport 生成汇总报告
func (s *StatisticsReportServiceImpl) GenerateSummaryReport(ctx context.Context, period string, startDate, endDate time.Time) (*entities.StatisticsReport, error) {
if period == "" {
return nil, fmt.Errorf("统计周期不能为空")
}
// 创建报告实体
title := fmt.Sprintf("汇总报告 - %s (%s 至 %s)",
s.getPeriodDisplayName(period),
startDate.Format("2006-01-02"),
endDate.Format("2006-01-02"))
report, err := entities.NewStatisticsReport("summary", title, period, "admin")
if err != nil {
s.logger.Error("创建汇总报告失败",
zap.String("period", period),
zap.Error(err))
return nil, fmt.Errorf("创建汇总报告失败: %w", err)
}
// 生成报告内容
content, err := s.generateSummaryContent(ctx, startDate, endDate)
if err != nil {
s.logger.Error("生成汇总报告内容失败",
zap.String("report_id", report.ID),
zap.Error(err))
return nil, fmt.Errorf("生成汇总报告内容失败: %w", err)
}
// 完成报告生成
err = report.CompleteGeneration(content)
if err != nil {
s.logger.Error("完成汇总报告生成失败",
zap.String("report_id", report.ID),
zap.Error(err))
return nil, fmt.Errorf("完成汇总报告生成失败: %w", err)
}
// 保存报告
err = s.reportRepo.Save(ctx, report)
if err != nil {
s.logger.Error("保存汇总报告失败",
zap.String("report_id", report.ID),
zap.Error(err))
return nil, fmt.Errorf("保存汇总报告失败: %w", err)
}
s.logger.Info("汇总报告生成成功",
zap.String("report_id", report.ID),
zap.String("period", period))
return report, nil
}
// GenerateDetailedReport 生成详细报告
func (s *StatisticsReportServiceImpl) GenerateDetailedReport(ctx context.Context, reportType string, startDate, endDate time.Time, filters map[string]interface{}) (*entities.StatisticsReport, error) {
if reportType == "" {
return nil, fmt.Errorf("报告类型不能为空")
}
// 创建报告实体
title := fmt.Sprintf("详细报告 - %s (%s 至 %s)",
reportType,
startDate.Format("2006-01-02"),
endDate.Format("2006-01-02"))
report, err := entities.NewStatisticsReport("detailed", title, "custom", "admin")
if err != nil {
s.logger.Error("创建详细报告失败",
zap.String("report_type", reportType),
zap.Error(err))
return nil, fmt.Errorf("创建详细报告失败: %w", err)
}
// 生成报告内容
content, err := s.generateDetailedContent(ctx, reportType, startDate, endDate, filters)
if err != nil {
s.logger.Error("生成详细报告内容失败",
zap.String("report_id", report.ID),
zap.Error(err))
return nil, fmt.Errorf("生成详细报告内容失败: %w", err)
}
// 完成报告生成
err = report.CompleteGeneration(content)
if err != nil {
s.logger.Error("完成详细报告生成失败",
zap.String("report_id", report.ID),
zap.Error(err))
return nil, fmt.Errorf("完成详细报告生成失败: %w", err)
}
// 保存报告
err = s.reportRepo.Save(ctx, report)
if err != nil {
s.logger.Error("保存详细报告失败",
zap.String("report_id", report.ID),
zap.Error(err))
return nil, fmt.Errorf("保存详细报告失败: %w", err)
}
s.logger.Info("详细报告生成成功",
zap.String("report_id", report.ID),
zap.String("report_type", reportType))
return report, nil
}
// GetReport 获取报告
func (s *StatisticsReportServiceImpl) GetReport(ctx context.Context, reportID string) (*entities.StatisticsReport, error) {
if reportID == "" {
return nil, fmt.Errorf("报告ID不能为空")
}
report, err := s.reportRepo.FindByID(ctx, reportID)
if err != nil {
s.logger.Error("查询报告失败",
zap.String("report_id", reportID),
zap.Error(err))
return nil, fmt.Errorf("查询报告失败: %w", err)
}
return report, nil
}
// GetReportsByUser 获取用户的报告列表
func (s *StatisticsReportServiceImpl) GetReportsByUser(ctx context.Context, userID string, limit, offset int) ([]*entities.StatisticsReport, error) {
if userID == "" {
return nil, fmt.Errorf("用户ID不能为空")
}
reports, err := s.reportRepo.FindByUser(ctx, userID, limit, offset)
if err != nil {
s.logger.Error("查询用户报告失败",
zap.String("user_id", userID),
zap.Error(err))
return nil, fmt.Errorf("查询用户报告失败: %w", err)
}
return reports, nil
}
// DeleteReport 删除报告
func (s *StatisticsReportServiceImpl) DeleteReport(ctx context.Context, reportID string) error {
if reportID == "" {
return fmt.Errorf("报告ID不能为空")
}
err := s.reportRepo.Delete(ctx, reportID)
if err != nil {
s.logger.Error("删除报告失败",
zap.String("report_id", reportID),
zap.Error(err))
return fmt.Errorf("删除报告失败: %w", err)
}
s.logger.Info("报告删除成功", zap.String("report_id", reportID))
return nil
}
// StartReportGeneration 开始报告生成
func (s *StatisticsReportServiceImpl) StartReportGeneration(ctx context.Context, reportID, generatedBy string) error {
if reportID == "" {
return fmt.Errorf("报告ID不能为空")
}
if generatedBy == "" {
return fmt.Errorf("生成者ID不能为空")
}
report, err := s.reportRepo.FindByID(ctx, reportID)
if err != nil {
return fmt.Errorf("查询报告失败: %w", err)
}
err = report.StartGeneration(generatedBy)
if err != nil {
s.logger.Error("开始报告生成失败",
zap.String("report_id", reportID),
zap.Error(err))
return fmt.Errorf("开始报告生成失败: %w", err)
}
err = s.reportRepo.Save(ctx, report)
if err != nil {
s.logger.Error("保存报告状态失败",
zap.String("report_id", reportID),
zap.Error(err))
return fmt.Errorf("保存报告状态失败: %w", err)
}
s.logger.Info("报告生成开始",
zap.String("report_id", reportID),
zap.String("generated_by", generatedBy))
return nil
}
// CompleteReportGeneration 完成报告生成
func (s *StatisticsReportServiceImpl) CompleteReportGeneration(ctx context.Context, reportID string, content string) error {
if reportID == "" {
return fmt.Errorf("报告ID不能为空")
}
if content == "" {
return fmt.Errorf("报告内容不能为空")
}
report, err := s.reportRepo.FindByID(ctx, reportID)
if err != nil {
return fmt.Errorf("查询报告失败: %w", err)
}
err = report.CompleteGeneration(content)
if err != nil {
s.logger.Error("完成报告生成失败",
zap.String("report_id", reportID),
zap.Error(err))
return fmt.Errorf("完成报告生成失败: %w", err)
}
err = s.reportRepo.Save(ctx, report)
if err != nil {
s.logger.Error("保存报告内容失败",
zap.String("report_id", reportID),
zap.Error(err))
return fmt.Errorf("保存报告内容失败: %w", err)
}
s.logger.Info("报告生成完成", zap.String("report_id", reportID))
return nil
}
// FailReportGeneration 报告生成失败
func (s *StatisticsReportServiceImpl) FailReportGeneration(ctx context.Context, reportID string, reason string) error {
if reportID == "" {
return fmt.Errorf("报告ID不能为空")
}
report, err := s.reportRepo.FindByID(ctx, reportID)
if err != nil {
return fmt.Errorf("查询报告失败: %w", err)
}
err = report.FailGeneration(reason)
if err != nil {
s.logger.Error("标记报告生成失败",
zap.String("report_id", reportID),
zap.Error(err))
return fmt.Errorf("标记报告生成失败: %w", err)
}
err = s.reportRepo.Save(ctx, report)
if err != nil {
s.logger.Error("保存报告状态失败",
zap.String("report_id", reportID),
zap.Error(err))
return fmt.Errorf("保存报告状态失败: %w", err)
}
s.logger.Info("报告生成失败",
zap.String("report_id", reportID),
zap.String("reason", reason))
return nil
}
// CleanupExpiredReports 清理过期报告
func (s *StatisticsReportServiceImpl) CleanupExpiredReports(ctx context.Context) error {
s.logger.Info("开始清理过期报告")
// 获取所有已完成的报告
reports, err := s.reportRepo.FindByStatus(ctx, "completed")
if err != nil {
s.logger.Error("查询已完成报告失败", zap.Error(err))
return fmt.Errorf("查询已完成报告失败: %w", err)
}
var deletedCount int
for _, report := range reports {
if report.IsExpired() {
err = report.MarkAsExpired()
if err != nil {
s.logger.Error("标记报告过期失败",
zap.String("report_id", report.ID),
zap.Error(err))
continue
}
err = s.reportRepo.Save(ctx, report)
if err != nil {
s.logger.Error("保存过期报告状态失败",
zap.String("report_id", report.ID),
zap.Error(err))
continue
}
deletedCount++
}
}
s.logger.Info("过期报告清理完成", zap.Int("deleted_count", deletedCount))
return nil
}
// generateSummaryContent 生成汇总报告内容
func (s *StatisticsReportServiceImpl) generateSummaryContent(ctx context.Context, startDate, endDate time.Time) (string, error) {
content := make(map[string]interface{})
// API调用统计
apiCallsTotal, err := s.calcService.CalculateTotal(ctx, "api_calls", "total_count", startDate, endDate)
if err != nil {
s.logger.Warn("计算API调用总数失败", zap.Error(err))
}
apiCallsSuccess, err := s.calcService.CalculateTotal(ctx, "api_calls", "success_count", startDate, endDate)
if err != nil {
s.logger.Warn("计算API调用成功数失败", zap.Error(err))
}
// 用户统计
usersTotal, err := s.calcService.CalculateTotal(ctx, "users", "total_count", startDate, endDate)
if err != nil {
s.logger.Warn("计算用户总数失败", zap.Error(err))
}
usersCertified, err := s.calcService.CalculateTotal(ctx, "users", "certified_count", startDate, endDate)
if err != nil {
s.logger.Warn("计算认证用户数失败", zap.Error(err))
}
// 财务统计
financeTotal, err := s.calcService.CalculateTotal(ctx, "finance", "total_amount", startDate, endDate)
if err != nil {
s.logger.Warn("计算财务总额失败", zap.Error(err))
}
content["api_calls"] = map[string]interface{}{
"total": apiCallsTotal,
"success": apiCallsSuccess,
"rate": s.calculateRate(apiCallsSuccess, apiCallsTotal),
}
content["users"] = map[string]interface{}{
"total": usersTotal,
"certified": usersCertified,
"rate": s.calculateRate(usersCertified, usersTotal),
}
content["finance"] = map[string]interface{}{
"total_amount": financeTotal,
}
content["period"] = map[string]interface{}{
"start_date": startDate.Format("2006-01-02"),
"end_date": endDate.Format("2006-01-02"),
}
content["generated_at"] = time.Now().Format("2006-01-02 15:04:05")
// 转换为JSON字符串
jsonContent, err := json.Marshal(content)
if err != nil {
return "", fmt.Errorf("序列化报告内容失败: %w", err)
}
return string(jsonContent), nil
}
// generateDetailedContent 生成详细报告内容
func (s *StatisticsReportServiceImpl) generateDetailedContent(ctx context.Context, reportType string, startDate, endDate time.Time, filters map[string]interface{}) (string, error) {
content := make(map[string]interface{})
// 根据报告类型生成不同的内容
switch reportType {
case "api_calls":
content = s.generateApiCallsDetailedContent(ctx, startDate, endDate, filters)
case "users":
content = s.generateUsersDetailedContent(ctx, startDate, endDate, filters)
case "finance":
content = s.generateFinanceDetailedContent(ctx, startDate, endDate, filters)
default:
return "", fmt.Errorf("不支持的报告类型: %s", reportType)
}
content["report_type"] = reportType
content["period"] = map[string]interface{}{
"start_date": startDate.Format("2006-01-02"),
"end_date": endDate.Format("2006-01-02"),
}
content["generated_at"] = time.Now().Format("2006-01-02 15:04:05")
// 转换为JSON字符串
jsonContent, err := json.Marshal(content)
if err != nil {
return "", fmt.Errorf("序列化报告内容失败: %w", err)
}
return string(jsonContent), nil
}
// generateApiCallsDetailedContent 生成API调用详细内容
func (s *StatisticsReportServiceImpl) generateApiCallsDetailedContent(ctx context.Context, startDate, endDate time.Time, filters map[string]interface{}) map[string]interface{} {
content := make(map[string]interface{})
// 获取API调用统计数据
totalCalls, _ := s.calcService.CalculateTotal(ctx, "api_calls", "total_count", startDate, endDate)
successCalls, _ := s.calcService.CalculateTotal(ctx, "api_calls", "success_count", startDate, endDate)
failedCalls, _ := s.calcService.CalculateTotal(ctx, "api_calls", "failed_count", startDate, endDate)
avgResponseTime, _ := s.calcService.CalculateAverage(ctx, "api_calls", "response_time", startDate, endDate)
content["total_calls"] = totalCalls
content["success_calls"] = successCalls
content["failed_calls"] = failedCalls
content["success_rate"] = s.calculateRate(successCalls, totalCalls)
content["avg_response_time"] = avgResponseTime
return content
}
// generateUsersDetailedContent 生成用户详细内容
func (s *StatisticsReportServiceImpl) generateUsersDetailedContent(ctx context.Context, startDate, endDate time.Time, filters map[string]interface{}) map[string]interface{} {
content := make(map[string]interface{})
// 获取用户统计数据
totalUsers, _ := s.calcService.CalculateTotal(ctx, "users", "total_count", startDate, endDate)
certifiedUsers, _ := s.calcService.CalculateTotal(ctx, "users", "certified_count", startDate, endDate)
activeUsers, _ := s.calcService.CalculateTotal(ctx, "users", "active_count", startDate, endDate)
content["total_users"] = totalUsers
content["certified_users"] = certifiedUsers
content["active_users"] = activeUsers
content["certification_rate"] = s.calculateRate(certifiedUsers, totalUsers)
content["retention_rate"] = s.calculateRate(activeUsers, totalUsers)
return content
}
// generateFinanceDetailedContent 生成财务详细内容
func (s *StatisticsReportServiceImpl) generateFinanceDetailedContent(ctx context.Context, startDate, endDate time.Time, filters map[string]interface{}) map[string]interface{} {
content := make(map[string]interface{})
// 获取财务统计数据
totalAmount, _ := s.calcService.CalculateTotal(ctx, "finance", "total_amount", startDate, endDate)
rechargeAmount, _ := s.calcService.CalculateTotal(ctx, "finance", "recharge_amount", startDate, endDate)
deductAmount, _ := s.calcService.CalculateTotal(ctx, "finance", "deduct_amount", startDate, endDate)
content["total_amount"] = totalAmount
content["recharge_amount"] = rechargeAmount
content["deduct_amount"] = deductAmount
content["net_amount"] = rechargeAmount - deductAmount
return content
}
// calculateRate 计算比率
func (s *StatisticsReportServiceImpl) calculateRate(numerator, denominator float64) float64 {
if denominator == 0 {
return 0
}
return (numerator / denominator) * 100
}
// getRoleDisplayName 获取角色显示名称
func (s *StatisticsReportServiceImpl) getRoleDisplayName(role string) string {
roleNames := map[string]string{
"admin": "管理员",
"user": "用户",
"manager": "经理",
"analyst": "分析师",
}
if name, exists := roleNames[role]; exists {
return name
}
return role
}
// getPeriodDisplayName 获取周期显示名称
func (s *StatisticsReportServiceImpl) getPeriodDisplayName(period string) string {
periodNames := map[string]string{
"today": "今日",
"week": "本周",
"month": "本月",
"quarter": "本季度",
"year": "本年",
}
if name, exists := periodNames[period]; exists {
return name
}
return period
}