Files
user-system/internal/service/stats.go
long-agent 09beb173cc feat: complete production readiness improvements
- Fix DIP violations in service layer (device, stats, auth middleware)
- Add ReplaceUserRoles interface method for transaction safety
- Implement Magic Bytes validation for avatar uploads
- Standardize OAuth error handling with ErrOAuthProviderNotSupported
- Use crypto/rand for JWT secret generation instead of weak fixed key
- Apply code formatting with gofumpt and goimports
- Fix staticcheck issues (S1024, S1008, ST1005)
- Add comprehensive quality and functional test reports
- Achieve 36.3% test coverage (up from 16.3%)
- All E2E, integration, and business logic tests passing
2026-04-12 16:15:32 +08:00

135 lines
3.8 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package service
import (
"context"
"time"
"github.com/user-management-system/internal/domain"
)
// Interfaces for dependency inversion (DIP) — service layer depends on these abstractions, not concrete types.
type statsUserRepository interface {
List(ctx context.Context, offset, limit int) ([]*domain.User, int64, error)
ListByStatus(ctx context.Context, status domain.UserStatus, offset, limit int) ([]*domain.User, int64, error)
ListCreatedAfter(ctx context.Context, since time.Time, offset, limit int) ([]*domain.User, int64, error)
}
type statsLoginLogRepository interface {
CountByResultSince(ctx context.Context, success bool, since time.Time) int64
}
// StatsService 统计服务
type StatsService struct {
userRepo statsUserRepository
loginLogRepo statsLoginLogRepository
}
// NewStatsService 创建统计服务
func NewStatsService(
userRepo statsUserRepository,
loginLogRepo statsLoginLogRepository,
) *StatsService {
return &StatsService{
userRepo: userRepo,
loginLogRepo: loginLogRepo,
}
}
// UserStats 用户统计数据
type UserStats struct {
TotalUsers int64 `json:"total_users"`
ActiveUsers int64 `json:"active_users"`
InactiveUsers int64 `json:"inactive_users"`
LockedUsers int64 `json:"locked_users"`
DisabledUsers int64 `json:"disabled_users"`
NewUsersToday int64 `json:"new_users_today"`
NewUsersWeek int64 `json:"new_users_week"`
NewUsersMonth int64 `json:"new_users_month"`
}
// LoginStats 登录统计数据
type LoginStats struct {
LoginsTodaySuccess int64 `json:"logins_today_success"`
LoginsTodayFailed int64 `json:"logins_today_failed"`
LoginsWeek int64 `json:"logins_week"`
}
// DashboardStats 仪表盘综合统计
type DashboardStats struct {
Users UserStats `json:"users"`
Logins LoginStats `json:"logins"`
}
// GetUserStats 获取用户统计
func (s *StatsService) GetUserStats(ctx context.Context) (*UserStats, error) {
stats := &UserStats{}
// 统计总用户数
_, total, err := s.userRepo.List(ctx, 0, 1)
if err != nil {
return nil, err
}
stats.TotalUsers = total
// 按状态统计
statusCounts := map[domain.UserStatus]*int64{
domain.UserStatusActive: &stats.ActiveUsers,
domain.UserStatusInactive: &stats.InactiveUsers,
domain.UserStatusLocked: &stats.LockedUsers,
domain.UserStatusDisabled: &stats.DisabledUsers,
}
for status, countPtr := range statusCounts {
_, cnt, err := s.userRepo.ListByStatus(ctx, status, 0, 1)
if err == nil {
*countPtr = cnt
}
}
// 今日新增
stats.NewUsersToday = s.countNewUsers(ctx, daysAgo(0))
// 本周新增
stats.NewUsersWeek = s.countNewUsers(ctx, daysAgo(7))
// 本月新增
stats.NewUsersMonth = s.countNewUsers(ctx, daysAgo(30))
return stats, nil
}
// countNewUsers 统计指定时间之后的新增用户数
func (s *StatsService) countNewUsers(ctx context.Context, since time.Time) int64 {
_, count, err := s.userRepo.ListCreatedAfter(ctx, since, 0, 0)
if err != nil {
return 0
}
return count
}
// GetDashboardStats 获取仪表盘综合统计
func (s *StatsService) GetDashboardStats(ctx context.Context) (*DashboardStats, error) {
userStats, err := s.GetUserStats(ctx)
if err != nil {
return nil, err
}
loginStats := &LoginStats{}
// 今日登录成功/失败
today := daysAgo(0)
if s.loginLogRepo != nil {
loginStats.LoginsTodaySuccess = s.loginLogRepo.CountByResultSince(ctx, true, today)
loginStats.LoginsTodayFailed = s.loginLogRepo.CountByResultSince(ctx, false, today)
loginStats.LoginsWeek = s.loginLogRepo.CountByResultSince(ctx, true, daysAgo(7))
}
return &DashboardStats{
Users: *userStats,
Logins: *loginStats,
}, nil
}
// daysAgo 返回N天前的时间当天0点
func daysAgo(n int) time.Time {
now := time.Now()
start := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
return start.AddDate(0, 0, -n)
}