using YD_Event.Application.Project.Dtos;
using YD_Event.Core.Entities;
using YD_Event.Core.Enum;
using Mapster;
using SqlSugar;
using Microsoft.Extensions.Logging;
using Easy.Core;
namespace YD_Event.Application.Project;
///
/// 项目管理
///
public class ProjectService : BaseService, ITransient
{
private readonly ISqlSugarRepository _repository;
private readonly ISqlSugarRepository _groupRepository;
private readonly ISqlSugarRepository _eventProjectRepository;
private readonly ISqlSugarRepository _participationRecordRepository;
private readonly IIdGenerator _idGenerator;
private readonly ILogger _logger;
public ProjectService(
ISqlSugarRepository repository,
ISqlSugarRepository groupRepository,
ISqlSugarRepository eventProjectRepository,
ISqlSugarRepository participationRecordRepository,
IIdGenerator idGenerator,
ILogger logger) : base(repository)
{
_repository = repository;
_groupRepository = groupRepository;
_eventProjectRepository = eventProjectRepository;
_participationRecordRepository = participationRecordRepository;
_idGenerator = idGenerator;
_logger = logger;
}
///
/// 项目分页查询
///
[DisplayName("项目分页查询")]
public async Task> Page(ProjectQueryInput input)
{
var query = _repository.AsQueryable()
.WhereIF(!string.IsNullOrWhiteSpace(input.ProjectName), p => p.Name.Contains(input.ProjectName))
.WhereIF(input.ProjectType.HasValue, p => p.ProjectType == (ProjectTypeEnum)input.ProjectType.Value)
.OrderByDescending(p => p.Id);
var list = await query.Select(p => new ProjectDto
{
Id = p.Id,
Name = p.Name,
ProjectType = (long)p.ProjectType,
CompletionCountType = p.CompletionCountType,
CompletionCount = p.CompletionCount,
CompletionCountMin = p.CompletionCountMin,
CompletionCountMax = p.CompletionCountMax,
Fee = p.Fee,
DurationMinutes = p.DurationMinutes,
DurationSeconds = p.DurationSeconds,
VenueCount = p.VenueCount,
SortIndex = p.SortIndex,
Status = p.Status,
DeleteMark = p.DeleteMark,
CreatedUserId = p.CreatedUserId,
CreatedTime = p.CreatedTime,
UpdatedUserId = p.UpdatedUserId,
UpdatedTime = p.UpdatedTime
})
.ToPagedListAsync(input);
foreach (var item in list.Rows)
{
item.ProjectTypeName = ((ProjectTypeEnum)item.ProjectType).Description();
// 生成完成人数显示文本
if (item.CompletionCountType == 0)
{
item.CompletionCountDisplay = item.CompletionCount.HasValue ? $"{item.CompletionCount.Value}人" : "0人";
}
else
{
var min = item.CompletionCountMin ?? 0;
var max = item.CompletionCountMax ?? 0;
item.CompletionCountDisplay = $"{min}-{max}人";
}
}
return list;
}
///
/// 添加项目
///
[DisplayName("添加项目")]
[UnitOfWork]
public async Task Add(AddProjectInput input)
{
// 验证完成人数字段
if (input.CompletionCountType == 0)
{
// 单一值模式
if (!input.CompletionCount.HasValue || input.CompletionCount.Value < 0)
{
throw Oops.Bah("请填写有效的完成人数");
}
}
else
{
// 范围值模式
if (!input.CompletionCountMin.HasValue || !input.CompletionCountMax.HasValue)
{
throw Oops.Bah("请填写完成人数最小值和最大值");
}
if (input.CompletionCountMin.Value < 0 || input.CompletionCountMax.Value < 0)
{
throw Oops.Bah("完成人数不能为负数");
}
if (input.CompletionCountMin.Value > input.CompletionCountMax.Value)
{
throw Oops.Bah("完成人数最小值不能大于最大值");
}
}
var entity = input.Adapt();
entity.Id = _idGenerator.NewLong();
entity.CreatedTime = DateTime.Now;
entity.Status = Core.Enum.AvailabilityStatus.Enable;
await _repository.InsertAsync(entity);
if (input.Groups != null && input.Groups.Any())
{
var groups = input.Groups.Select(g =>
{
var group = g.Adapt();
group.Id = _idGenerator.NewLong();
group.ProjectId = entity.Id;
return group;
}).ToList();
await _groupRepository.InsertRangeAsync(groups);
}
}
///
/// 更新项目
///
[DisplayName("更新项目")]
[UnitOfWork]
public async Task Update(UpdateProjectInput input)
{
var entity = await _repository.GetByIdAsync(input.Id);
if (entity == null) throw new InvalidOperationException("项目不存在");
// 验证完成人数字段
if (input.CompletionCountType == 0)
{
// 单一值模式
if (!input.CompletionCount.HasValue || input.CompletionCount.Value < 0)
{
throw Oops.Bah("请填写有效的完成人数");
}
}
else
{
// 范围值模式
if (!input.CompletionCountMin.HasValue || !input.CompletionCountMax.HasValue)
{
throw Oops.Bah("请填写完成人数最小值和最大值");
}
if (input.CompletionCountMin.Value < 0 || input.CompletionCountMax.Value < 0)
{
throw Oops.Bah("完成人数不能为负数");
}
if (input.CompletionCountMin.Value > input.CompletionCountMax.Value)
{
throw Oops.Bah("完成人数最小值不能大于最大值");
}
}
input.Adapt(entity);
entity.UpdatedTime = DateTime.Now;
await _repository.UpdateAsync(entity);
await _groupRepository.SoftDeleteAsync(x => x.ProjectId == entity.Id);
if (input.Groups != null && input.Groups.Any())
{
var groups = input.Groups.Select(g =>
{
var group = g.Adapt();
group.Id = _idGenerator.NewLong();
group.ProjectId = entity.Id;
return group;
}).ToList();
await _groupRepository.InsertRangeAsync(groups);
}
}
///
/// 删除项目
///
[DisplayName("删除项目")]
[UnitOfWork]
public async Task Delete(List ids)
{
_logger.LogInformation("开始删除项目:项目ID列表={Ids}", string.Join(",", ids));
// 检查项目是否被赛事使用
var hasEventProjects = await _eventProjectRepository.IsAnyAsync(x => ids.Contains(x.ProjectId));
if (hasEventProjects)
{
_logger.LogWarning("删除失败:部分项目正在被赛事使用");
throw new InvalidOperationException("部分项目正在被赛事使用,无法删除。请先从赛事中移除这些项目。");
}
// 检查是否有报名数据关联
var eventProjectIds = await _eventProjectRepository.AsQueryable()
.Where(x => ids.Contains(x.ProjectId))
.Select(x => x.Id)
.ToListAsync();
if (eventProjectIds.Any())
{
var hasParticipations = await _participationRecordRepository.IsAnyAsync(x =>
eventProjectIds.Contains(x.ProjectId) && !x.DeleteMark);
if (hasParticipations)
{
_logger.LogWarning("删除失败:部分项目已有报名数据");
throw new InvalidOperationException("部分项目已有报名数据,为保证数据完整性,无法删除。");
}
}
// 执行删除 - 软删
await _repository.SoftDeleteByIdsAsync(ids);
await _groupRepository.SoftDeleteAsync(x => ids.Contains(x.ProjectId));
_logger.LogInformation("删除项目成功:项目ID列表={Ids}", string.Join(",", ids));
}
///
/// 获取项目详情
///
[DisplayName("获取项目详情")]
public async Task Detail(long id)
{
var p = await _repository.GetByIdAsync(id);
if (p == null) throw new InvalidOperationException("项目不存在");
var dto = p.Adapt();
dto.ProjectType = (long)p.ProjectType;
dto.ProjectTypeName = p.ProjectType.Description();
// 生成完成人数显示文本
if (dto.CompletionCountType == 0)
{
dto.CompletionCountDisplay = dto.CompletionCount.HasValue ? $"{dto.CompletionCount.Value}人" : "0人";
}
else
{
var min = dto.CompletionCountMin ?? 0;
var max = dto.CompletionCountMax ?? 0;
dto.CompletionCountDisplay = $"{min}-{max}人";
}
var groups = await _groupRepository.GetListAsync(x => x.ProjectId == id);
dto.Groups = groups.Adapt>();
return dto;
}
///
/// 项目统计类型
///
public enum ProjectStatisticType
{
///
/// 参赛人数
///
ParticipationCount = 1,
///
/// 完成人数
///
CompletionCount = 2
}
///
/// 获取项目统计数据(单个项目)
///
/// 项目ID
/// 统计类型
/// 统计数值
[DisplayName("获取项目统计数据")]
public async Task GetProjectStatisticAsync(long projectId, ProjectStatisticType statisticType)
{
if (projectId <= 0)
{
_logger.LogWarning("Invalid projectId: {ProjectId}", projectId);
return 0;
}
var eventProjectIds = await _eventProjectRepository.AsQueryable()
.Where(ep => ep.ProjectId == projectId)
.Select(ep => ep.Id)
.ToListAsync();
if (!eventProjectIds.Any())
{
return 0;
}
var query = _participationRecordRepository.AsQueryable()
.Where(pr => eventProjectIds.Contains(pr.ProjectId) && !pr.DeleteMark);
switch (statisticType)
{
case ProjectStatisticType.ParticipationCount:
query = query.Where(pr => pr.ParticipationStatus == ParticipationStatus.Participated || pr.ParticipationStatus == ParticipationStatus.Completed);
break;
case ProjectStatisticType.CompletionCount:
query = query.Where(pr => pr.ParticipationStatus == ParticipationStatus.Completed);
break;
default:
_logger.LogWarning("Unknown statistic type: {StatisticType}", statisticType);
return 0;
}
var count = await query.CountAsync();
return count;
}
///
/// 批量获取项目统计数据(性能优化,避免N+1查询)
///
/// 项目ID列表
/// 统计类型
/// 字典,Key为项目ID,Value为统计数值
[DisplayName("批量获取项目统计数据")]
public async Task> GetProjectStatisticsBatchAsync(List projectIds, ProjectStatisticType statisticType)
{
var result = new Dictionary();
if (projectIds == null || !projectIds.Any())
{
return result;
}
foreach (var projectId in projectIds)
{
result[projectId] = 0;
}
var eventProjects = await _eventProjectRepository.AsQueryable()
.Where(ep => projectIds.Contains(ep.ProjectId))
.Select(ep => new { ep.Id, ep.ProjectId })
.ToListAsync();
if (!eventProjects.Any())
{
return result;
}
var eventProjectIds = eventProjects.Select(ep => ep.Id).ToList();
var query = _participationRecordRepository.AsQueryable()
.Where(pr => eventProjectIds.Contains(pr.ProjectId) && !pr.DeleteMark);
switch (statisticType)
{
case ProjectStatisticType.ParticipationCount:
query = query.Where(pr => pr.ParticipationStatus == ParticipationStatus.Participated || pr.ParticipationStatus == ParticipationStatus.Completed);
break;
case ProjectStatisticType.CompletionCount:
query = query.Where(pr => pr.ParticipationStatus == ParticipationStatus.Completed);
break;
default:
_logger.LogWarning("Unknown statistic type: {StatisticType}", statisticType);
return result;
}
var records = await query.ToListAsync();
foreach (var record in records)
{
var projectId = eventProjects.FirstOrDefault(ep => ep.Id == record.ProjectId)?.ProjectId;
if (projectId.HasValue && result.ContainsKey(projectId.Value))
{
result[projectId.Value]++;
}
}
return result;
}
}