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 ContentService : IContentService { private readonly MilliSimDbContext _db; private readonly ICurrentUserService _currentUser; private readonly IOperationLogService _operationLog; private readonly IFileStorageService _fileStorage; private readonly INotificationService _notification; private readonly IUploadTrackingService _uploadTracking; public ContentService( MilliSimDbContext db, ICurrentUserService currentUser, IOperationLogService operationLog, IFileStorageService fileStorage, INotificationService notification, IUploadTrackingService uploadTracking) { _db = db; _currentUser = currentUser; _operationLog = operationLog; _fileStorage = fileStorage; _notification = notification; _uploadTracking = uploadTracking; } // ===== 轮播图 ===== public async Task>> GetBannersAsync(bool onlyActive = false) { var query = _db.Banners.AsQueryable(); if (onlyActive) { query = query.Where(b => b.Status == UserStatus.Active); } // 先查询实体,再在内存中转换为DTO(因为需要调用GetSignedUrl生成签名URL) var entities = await query .OrderBy(b => b.SortOrder) .ThenByDescending(b => b.CreatedAt) .ToListAsync(); // 返回时将对象键转换为签名URL,确保前端能访问COS私有文件 var items = entities.Select(b => new BannerDto { Id = b.Id, Title = b.Title, Image = _fileStorage.GetSignedUrl(b.Image), Link = b.Link, SortOrder = b.SortOrder, Status = b.Status }).ToList(); return ApiResponse>.Success(items); } public async Task> CreateBannerAsync(CreateBannerRequest request) { if (string.IsNullOrWhiteSpace(request.Image)) { return ApiResponse.Fail("图片不能为空"); } var entity = new Banner { Title = request.Title, // 保存时将签名URL转换为对象键存储,避免签名URL过期导致数据丢失 Image = _fileStorage.ExtractKey(request.Image), Link = request.Link, SortOrder = request.SortOrder, Status = request.Status }; _db.Banners.Add(entity); await _db.SaveChangesAsync(); // 消费上传跟踪 await _uploadTracking.ConsumeAsync(entity.Image); await _operationLog.LogAsync("内容-轮播图", "创建", $"创建轮播图:{request.Title},ID: {entity.Id}"); return ApiResponse.Success(MapToDto(entity)); } public async Task> UpdateBannerAsync(long id, CreateBannerRequest request) { var entity = await _db.Banners.FirstOrDefaultAsync(b => b.Id == id); if (entity == null) { return ApiResponse.Fail("轮播图不存在", 404); } var oldImageKey = entity.Image; var newImageKey = _fileStorage.ExtractKey(request.Image); entity.Title = request.Title; // 保存时将签名URL转换为对象键存储,避免签名URL过期导致数据丢失 entity.Image = newImageKey; entity.Link = request.Link; entity.SortOrder = request.SortOrder; entity.Status = request.Status; entity.UpdatedAt = DateTime.Now; await _db.SaveChangesAsync(); // 图片替换:删除旧文件 if (!string.IsNullOrEmpty(oldImageKey) && oldImageKey != newImageKey) { await _fileStorage.DeleteAsync(oldImageKey); } // 消费新上传的文件 await _uploadTracking.ConsumeAsync(newImageKey); await _operationLog.LogAsync("内容-轮播图", "更新", $"更新轮播图 ID: {id}"); return ApiResponse.Success(MapToDto(entity)); } public async Task DeleteBannerAsync(long id) { var entity = await _db.Banners.FirstOrDefaultAsync(b => b.Id == id); if (entity == null) { return ApiResponse.Fail("轮播图不存在", 404); } entity.IsDeleted = true; entity.UpdatedAt = DateTime.Now; await _db.SaveChangesAsync(); await _operationLog.LogAsync("内容-轮播图", "删除", $"删除轮播图 ID: {id}"); return ApiResponse.Success("删除成功"); } // ===== 合作伙伴 ===== public async Task>> GetPartnersAsync(bool onlyVisible = false) { var query = _db.Partners.AsQueryable(); if (onlyVisible) { query = query.Where(p => p.IsVisible); } // 先查询实体,再在内存中转换为DTO(因为需要调用GetSignedUrl生成签名URL) var entities = await query .OrderBy(p => p.SortOrder) .ThenByDescending(p => p.CreatedAt) .ToListAsync(); // 返回时将对象键转换为签名URL var items = entities.Select(p => new PartnerDto { Id = p.Id, Name = p.Name, Logo = _fileStorage.GetSignedUrl(p.Logo), Link = p.Link, SortOrder = p.SortOrder, IsVisible = p.IsVisible }).ToList(); return ApiResponse>.Success(items); } public async Task> CreatePartnerAsync(CreatePartnerRequest request) { if (string.IsNullOrWhiteSpace(request.Name)) { return ApiResponse.Fail("名称不能为空"); } if (string.IsNullOrWhiteSpace(request.Logo)) { return ApiResponse.Fail("Logo 不能为空"); } var entity = new Partner { Name = request.Name, // 保存时将签名URL转换为对象键存储 Logo = _fileStorage.ExtractKey(request.Logo), Link = request.Link, SortOrder = request.SortOrder, IsVisible = request.IsVisible }; _db.Partners.Add(entity); await _db.SaveChangesAsync(); // 消费上传跟踪 await _uploadTracking.ConsumeAsync(entity.Logo); await _operationLog.LogAsync("内容-合作伙伴", "创建", $"创建合作伙伴:{request.Name},ID: {entity.Id}"); return ApiResponse.Success(MapToDto(entity)); } public async Task> UpdatePartnerAsync(long id, CreatePartnerRequest request) { var entity = await _db.Partners.FirstOrDefaultAsync(p => p.Id == id); if (entity == null) { return ApiResponse.Fail("合作伙伴不存在", 404); } var oldLogoKey = entity.Logo; var newLogoKey = _fileStorage.ExtractKey(request.Logo); entity.Name = request.Name; // 保存时将签名URL转换为对象键存储 entity.Logo = newLogoKey; entity.Link = request.Link; entity.SortOrder = request.SortOrder; entity.IsVisible = request.IsVisible; entity.UpdatedAt = DateTime.Now; await _db.SaveChangesAsync(); // Logo 替换:删除旧文件 if (!string.IsNullOrEmpty(oldLogoKey) && oldLogoKey != newLogoKey) { await _fileStorage.DeleteAsync(oldLogoKey); } // 消费新上传的文件 await _uploadTracking.ConsumeAsync(newLogoKey); await _operationLog.LogAsync("内容-合作伙伴", "更新", $"更新合作伙伴 ID: {id}"); return ApiResponse.Success(MapToDto(entity)); } public async Task DeletePartnerAsync(long id) { var entity = await _db.Partners.FirstOrDefaultAsync(p => p.Id == id); if (entity == null) { return ApiResponse.Fail("合作伙伴不存在", 404); } entity.IsDeleted = true; entity.UpdatedAt = DateTime.Now; await _db.SaveChangesAsync(); await _operationLog.LogAsync("内容-合作伙伴", "删除", $"删除合作伙伴 ID: {id}"); return ApiResponse.Success("删除成功"); } // ===== 咨询消息 ===== public async Task>> GetConsultationsAsync(PageRequest request, bool? isRead = null) { var query = _db.Consultations.AsQueryable(); if (!string.IsNullOrWhiteSpace(request.Keyword)) { var keyword = request.Keyword.Trim(); query = query.Where(c => c.Name.Contains(keyword) || c.Contact.Contains(keyword) || c.Content.Contains(keyword)); } // 按已读/未读状态过滤 if (isRead.HasValue) { query = query.Where(c => c.IsRead == isRead.Value); } var total = await query.LongCountAsync(); var items = await query .OrderByDescending(c => c.CreatedAt) .Skip((request.PageIndex - 1) * request.PageSize) .Take(request.PageSize) .Select(c => new ConsultationDto { Id = c.Id, Name = c.Name, Contact = c.Contact, Content = c.Content, IsRead = c.IsRead, CreatedAt = c.CreatedAt }) .ToListAsync(); return ApiResponse>.Success( PagedResult.Create(items, total, request.PageIndex, request.PageSize)); } public async Task> CreateConsultationAsync(CreateConsultationRequest request) { if (string.IsNullOrWhiteSpace(request.Name)) { return ApiResponse.Fail("姓名不能为空"); } if (string.IsNullOrWhiteSpace(request.Contact)) { return ApiResponse.Fail("联系方式不能为空"); } var entity = new Consultation { Name = request.Name, Contact = request.Contact, Content = request.Content, IsRead = false }; _db.Consultations.Add(entity); await _db.SaveChangesAsync(); // 通知所有管理员/销售:有新咨询需要处理 try { var staffUsers = await _db.Users .Where(u => u.Role == UserRole.Admin || u.Role == UserRole.Sales) .Select(u => u.Id) .ToListAsync(); var contentPreview = string.IsNullOrWhiteSpace(request.Content) ? "(无内容)" : (request.Content.Length > 50 ? request.Content[..50] + "..." : request.Content); foreach (var staffId in staffUsers) { await _notification.CreateNotificationAsync( NotificationType.System, "新咨询消息", $"{request.Name}({request.Contact})提交了一条咨询:{contentPreview}", userId: staffId, eventId: $"consultation-{entity.Id}-notify-{staffId}"); } } catch { // 通知发送失败不影响咨询提交结果 } return ApiResponse.Success(MapToDto(entity), "提交成功"); } public async Task MarkAsReadAsync(long id) { var entity = await _db.Consultations.FirstOrDefaultAsync(c => c.Id == id); if (entity == null) { return ApiResponse.Fail("咨询消息不存在", 404); } if (!entity.IsRead) { entity.IsRead = true; entity.UpdatedAt = DateTime.Now; await _db.SaveChangesAsync(); } return ApiResponse.Success("已标记为已读"); } public async Task DeleteConsultationAsync(long id) { var entity = await _db.Consultations.FirstOrDefaultAsync(c => c.Id == id); if (entity == null) { return ApiResponse.Fail("咨询消息不存在", 404); } entity.IsDeleted = true; entity.UpdatedAt = DateTime.Now; await _db.SaveChangesAsync(); await _operationLog.LogAsync("内容-咨询", "删除", $"删除咨询消息 ID: {id}"); return ApiResponse.Success("删除成功"); } // ===== 私有映射方法 ===== // 改为非静态方法,以便调用 _fileStorage.GetSignedUrl 生成签名URL private BannerDto MapToDto(Banner entity) { return new BannerDto { Id = entity.Id, Title = entity.Title, // 返回时将对象键转换为签名URL,确保前端能访问COS私有文件 Image = _fileStorage.GetSignedUrl(entity.Image), Link = entity.Link, SortOrder = entity.SortOrder, Status = entity.Status }; } // 改为非静态方法,以便调用 _fileStorage.GetSignedUrl 生成签名URL private PartnerDto MapToDto(Partner entity) { return new PartnerDto { Id = entity.Id, Name = entity.Name, // 返回时将对象键转换为签名URL Logo = _fileStorage.GetSignedUrl(entity.Logo), Link = entity.Link, SortOrder = entity.SortOrder, IsVisible = entity.IsVisible }; } private static ConsultationDto MapToDto(Consultation entity) { return new ConsultationDto { Id = entity.Id, Name = entity.Name, Contact = entity.Contact, Content = entity.Content, IsRead = entity.IsRead, CreatedAt = entity.CreatedAt }; } }