440 lines
14 KiB
C#
Raw Permalink 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.

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<ApiResponse<List<BannerDto>>> 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<List<BannerDto>>.Success(items);
}
public async Task<ApiResponse<BannerDto>> CreateBannerAsync(CreateBannerRequest request)
{
if (string.IsNullOrWhiteSpace(request.Image))
{
return ApiResponse<BannerDto>.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<BannerDto>.Success(MapToDto(entity));
}
public async Task<ApiResponse<BannerDto>> UpdateBannerAsync(long id, CreateBannerRequest request)
{
var entity = await _db.Banners.FirstOrDefaultAsync(b => b.Id == id);
if (entity == null)
{
return ApiResponse<BannerDto>.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<BannerDto>.Success(MapToDto(entity));
}
public async Task<ApiResponse> 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<ApiResponse<List<PartnerDto>>> 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<List<PartnerDto>>.Success(items);
}
public async Task<ApiResponse<PartnerDto>> CreatePartnerAsync(CreatePartnerRequest request)
{
if (string.IsNullOrWhiteSpace(request.Name))
{
return ApiResponse<PartnerDto>.Fail("名称不能为空");
}
if (string.IsNullOrWhiteSpace(request.Logo))
{
return ApiResponse<PartnerDto>.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<PartnerDto>.Success(MapToDto(entity));
}
public async Task<ApiResponse<PartnerDto>> UpdatePartnerAsync(long id, CreatePartnerRequest request)
{
var entity = await _db.Partners.FirstOrDefaultAsync(p => p.Id == id);
if (entity == null)
{
return ApiResponse<PartnerDto>.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<PartnerDto>.Success(MapToDto(entity));
}
public async Task<ApiResponse> 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<ApiResponse<PagedResult<ConsultationDto>>> 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<PagedResult<ConsultationDto>>.Success(
PagedResult<ConsultationDto>.Create(items, total, request.PageIndex, request.PageSize));
}
public async Task<ApiResponse<ConsultationDto>> CreateConsultationAsync(CreateConsultationRequest request)
{
if (string.IsNullOrWhiteSpace(request.Name))
{
return ApiResponse<ConsultationDto>.Fail("姓名不能为空");
}
if (string.IsNullOrWhiteSpace(request.Contact))
{
return ApiResponse<ConsultationDto>.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<ConsultationDto>.Success(MapToDto(entity), "提交成功");
}
public async Task<ApiResponse> 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<ApiResponse> 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
};
}
}