using Microsoft.EntityFrameworkCore; using MilliSim.Common.Dtos; using MilliSim.Common.Entities; using MilliSim.Common.Models; using MilliSim.Data; using MilliSim.Services.Infrastructure; namespace MilliSim.Services; public class DashboardService : IDashboardService { private readonly MilliSimDbContext _db; private readonly ICurrentUserService _currentUser; private readonly IFileStorageService _fileStorage; public DashboardService(MilliSimDbContext db, ICurrentUserService currentUser, IFileStorageService fileStorage) { _db = db; _currentUser = currentUser; _fileStorage = fileStorage; } public async Task> GetDashboardDataAsync() { var userId = _currentUser.UserId; // ===== Stats ===== var projectCount = await _db.Projects.CountAsync(); var customerCount = await _db.Users.CountAsync(u => u.Role == UserRole.Customer); var todayStart = DateTime.Now.Date; var todayEnd = todayStart.AddDays(1); var todayVisits = await _db.OperationLogs .CountAsync(l => l.CreatedAt >= todayStart && l.CreatedAt < todayEnd); var activeAuthorizations = await _db.Authorizations .CountAsync(a => a.Status == AuthorizationStatus.Active); var unreadMessages = await _db.Notifications .Where(n => n.UserId == null || n.UserId == userId) .Where(n => !_db.NotificationReads.Any(r => r.NotificationId == n.Id && r.UserId == userId)) .CountAsync(); // ===== Trends: 近 7 天 ===== var startDate = DateTime.Now.Date.AddDays(-6); var endDate = DateTime.Now.Date.AddDays(1); var visits = await _db.OperationLogs .Where(l => l.CreatedAt >= startDate && l.CreatedAt < endDate) .GroupBy(l => l.CreatedAt.Date) .Select(g => new { Date = g.Key, Count = g.Count() }) .ToListAsync(); var experiences = await _db.ExperienceLogs .Where(l => l.CreatedAt >= startDate && l.CreatedAt < endDate) .GroupBy(l => l.CreatedAt.Date) .Select(g => new { Date = g.Key, Count = g.Count() }) .ToListAsync(); var trends = new List(); for (var i = 0; i < 7; i++) { var date = startDate.AddDays(i); trends.Add(new TrendData { Date = date.ToString("MM-dd"), Visits = visits.FirstOrDefault(v => v.Date == date)?.Count ?? 0, Experiences = experiences.FirstOrDefault(e => e.Date == date)?.Count ?? 0 }); } // 如果没有真实数据则返回模拟数据 if (trends.Sum(t => t.Visits + t.Experiences) == 0) { var random = new Random(); foreach (var t in trends) { t.Visits = random.Next(50, 200); t.Experiences = random.Next(10, 80); } } // ===== CategoryDistribution: 按分类统计项目数 ===== var categoryDistribution = await _db.Projects .Join(_db.ProjectCategories, p => p.CategoryId, c => c.Id, (p, c) => new { c.Name }) .GroupBy(x => x.Name) .Select(g => new CategoryDistribution { Name = g.Key, Count = g.Count() }) .ToListAsync(); // ===== RecentActivities: 当前用户最近 10 条操作日志 ===== var recentLogEntities = await _db.OperationLogs .Where(l => l.UserId == userId) .OrderByDescending(l => l.CreatedAt) .Take(10) .ToListAsync(); var recentUserIds = recentLogEntities.Select(l => l.UserId).Distinct().ToList(); var recentNicknames = await _db.Users .Where(u => recentUserIds.Contains(u.Id)) .ToDictionaryAsync(u => u.Id, u => u.Nickname); var recentActivities = recentLogEntities.Select(l => new OperationLogDto { Id = l.Id, UserId = l.UserId, Username = l.Username, Nickname = recentNicknames.GetValueOrDefault(l.UserId) ?? string.Empty, Module = l.Module, Operation = l.Operation, Detail = l.Detail, Ip = l.Ip, CreatedAt = l.CreatedAt }).ToList(); // ===== HotProjects: 按 ExperienceCount 倒序取 6 个(封面需 COS 签名) ===== var hotProjectEntities = await _db.Projects .Include(p => p.Category) .OrderByDescending(p => p.ExperienceCount) .Take(6) .ToListAsync(); var hotProjects = hotProjectEntities.Select(p => new ProjectDto { Id = p.Id, Name = p.Name, CategoryId = p.CategoryId, CategoryName = p.Category != null ? p.Category.Name : string.Empty, CoverImage = _fileStorage.GetSignedUrl(p.CoverImage), Description = p.Description, Status = p.Status, IsFeatured = p.IsFeatured, ViewCount = p.ViewCount, ExperienceCount = p.ExperienceCount, FavoriteCount = p.FavoriteCount, CreatedAt = p.CreatedAt }).ToList(); var data = new DashboardData { Stats = new DashboardStats { ProjectCount = projectCount, CustomerCount = customerCount, TodayVisits = todayVisits, ActiveAuthorizations = activeAuthorizations, UnreadMessages = unreadMessages }, Trends = trends, CategoryDistribution = categoryDistribution, RecentActivities = recentActivities, HotProjects = hotProjects }; return ApiResponse.Success(data); } }