332 lines
11 KiB
C#
Raw Normal View History

2026-03-01 22:32:44 +08:00
using YD_Event.Application.Project.Dtos;
using YD_Event.Core.Entities;
2026-04-12 16:11:20 +08:00
using YD_Event.Core.Enum;
2026-03-08 15:46:19 +08:00
using Mapster;
using SqlSugar;
using Microsoft.Extensions.Logging;
2026-04-12 16:11:20 +08:00
using Easy.Core;
2026-03-01 22:32:44 +08:00
namespace YD_Event.Application.Project;
/// <summary>
/// 项目管理
/// </summary>
public class ProjectService : BaseService<YD_Event.Core.Entities.Project>, ITransient
{
private readonly ISqlSugarRepository<YD_Event.Core.Entities.Project> _repository;
private readonly ISqlSugarRepository<ProjectGroup> _groupRepository;
private readonly ISqlSugarRepository<EventProject> _eventProjectRepository;
private readonly ISqlSugarRepository<EventParticipationRecord> _participationRecordRepository;
2026-03-01 22:32:44 +08:00
private readonly IIdGenerator _idGenerator;
private readonly ILogger<ProjectService> _logger;
2026-03-01 22:32:44 +08:00
public ProjectService(
ISqlSugarRepository<YD_Event.Core.Entities.Project> repository,
ISqlSugarRepository<ProjectGroup> groupRepository,
ISqlSugarRepository<EventProject> eventProjectRepository,
ISqlSugarRepository<EventParticipationRecord> participationRecordRepository,
IIdGenerator idGenerator,
ILogger<ProjectService> logger) : base(repository)
2026-03-01 22:32:44 +08:00
{
_repository = repository;
_groupRepository = groupRepository;
_eventProjectRepository = eventProjectRepository;
_participationRecordRepository = participationRecordRepository;
2026-03-01 22:32:44 +08:00
_idGenerator = idGenerator;
_logger = logger;
2026-03-01 22:32:44 +08:00
}
/// <summary>
/// 项目分页查询
/// </summary>
[DisplayName("项目分页查询")]
2026-03-08 15:46:19 +08:00
public async Task<PageResult<ProjectDto>> Page(ProjectQueryInput input)
2026-03-01 22:32:44 +08:00
{
2026-03-08 15:46:19 +08:00
var query = _repository.AsQueryable()
2026-04-12 16:11:20 +08:00
.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);
2026-03-08 15:46:19 +08:00
2026-04-12 16:11:20 +08:00
var list = await query.Select(p => new ProjectDto
2026-03-08 15:46:19 +08:00
{
Id = p.Id,
Name = p.Name,
2026-04-12 16:11:20 +08:00
ProjectType = (long)p.ProjectType,
2026-03-08 15:46:19 +08:00
CompletionCount = p.CompletionCount,
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);
2026-04-12 16:11:20 +08:00
foreach (var item in list.Rows)
{
item.ProjectTypeName = ((ProjectTypeEnum)item.ProjectType).Description();
}
return list;
2026-03-01 22:32:44 +08:00
}
/// <summary>
/// 添加项目
/// </summary>
[DisplayName("添加项目")]
[UnitOfWork]
public async Task Add(AddProjectInput input)
{
2026-03-18 16:25:49 +08:00
var isExist = await _repository.IsAnyAsync(x => x.Name == input.Name);
if (isExist) throw new InvalidOperationException("该项目名称已存在");
2026-03-01 22:32:44 +08:00
var entity = input.Adapt<YD_Event.Core.Entities.Project>();
2026-03-08 15:46:19 +08:00
entity.Id = _idGenerator.NewLong();
entity.CreatedTime = DateTime.Now;
entity.Status = Core.Enum.AvailabilityStatus.Enable;
2026-03-01 22:32:44 +08:00
await _repository.InsertAsync(entity);
if (input.Groups != null && input.Groups.Any())
{
2026-03-08 15:46:19 +08:00
var groups = input.Groups.Select(g =>
2026-03-01 22:32:44 +08:00
{
2026-03-08 15:46:19 +08:00
var group = g.Adapt<ProjectGroup>();
group.Id = _idGenerator.NewLong();
group.ProjectId = entity.Id;
return group;
}).ToList();
2026-03-01 22:32:44 +08:00
await _groupRepository.InsertRangeAsync(groups);
}
}
/// <summary>
/// 更新项目
/// </summary>
[DisplayName("更新项目")]
[UnitOfWork]
public async Task Update(UpdateProjectInput input)
{
var entity = await _repository.GetByIdAsync(input.Id);
2026-03-08 15:46:19 +08:00
if (entity == null) throw new InvalidOperationException("项目不存在");
2026-03-01 22:32:44 +08:00
if (entity.Name != input.Name)
{
2026-03-18 16:25:49 +08:00
var isExist = await _repository.IsAnyAsync(x => x.Name == input.Name && x.Id != input.Id);
if (isExist) throw new InvalidOperationException("该项目名称已存在");
2026-03-01 22:32:44 +08:00
}
input.Adapt(entity);
2026-03-08 15:46:19 +08:00
entity.UpdatedTime = DateTime.Now;
2026-03-01 22:32:44 +08:00
await _repository.UpdateAsync(entity);
await _groupRepository.DeleteAsync(x => x.ProjectId == entity.Id);
if (input.Groups != null && input.Groups.Any())
{
2026-03-08 15:46:19 +08:00
var groups = input.Groups.Select(g =>
2026-03-01 22:32:44 +08:00
{
2026-03-08 15:46:19 +08:00
var group = g.Adapt<ProjectGroup>();
group.Id = _idGenerator.NewLong();
group.ProjectId = entity.Id;
return group;
}).ToList();
2026-03-01 22:32:44 +08:00
await _groupRepository.InsertRangeAsync(groups);
}
}
/// <summary>
/// 删除项目
/// </summary>
[DisplayName("删除项目")]
[UnitOfWork]
2026-03-08 15:46:19 +08:00
public async Task Delete(List<long> ids)
2026-03-01 22:32:44 +08:00
{
2026-04-10 22:39:32 +08:00
_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("部分项目已有报名数据,为保证数据完整性,无法删除。");
}
}
// 执行删除
2026-03-01 22:32:44 +08:00
await _repository.DeleteAsync(x => ids.Contains(x.Id));
await _groupRepository.DeleteAsync(x => ids.Contains(x.ProjectId));
2026-04-10 22:39:32 +08:00
_logger.LogInformation("删除项目成功:项目ID列表={Ids}", string.Join(",", ids));
2026-03-01 22:32:44 +08:00
}
/// <summary>
/// 获取项目详情
/// </summary>
[DisplayName("获取项目详情")]
2026-03-08 15:46:19 +08:00
public async Task<ProjectDto> Detail(long id)
2026-03-01 22:32:44 +08:00
{
var p = await _repository.GetByIdAsync(id);
2026-03-08 15:46:19 +08:00
if (p == null) throw new InvalidOperationException("项目不存在");
2026-03-01 22:32:44 +08:00
var dto = p.Adapt<ProjectDto>();
2026-04-12 16:11:20 +08:00
dto.ProjectType = (long)p.ProjectType;
dto.ProjectTypeName = p.ProjectType.Description();
2026-03-01 22:32:44 +08:00
2026-03-08 15:46:19 +08:00
var groups = await _groupRepository.GetListAsync(x => x.ProjectId == id);
dto.Groups = groups.Adapt<List<ProjectGroupDto>>();
2026-03-01 22:32:44 +08:00
return dto;
}
/// <summary>
/// 项目统计类型
/// </summary>
public enum ProjectStatisticType
{
/// <summary>
/// 参赛人数
/// </summary>
ParticipationCount = 1,
/// <summary>
/// 完成人数
/// </summary>
CompletionCount = 2
}
/// <summary>
/// 获取项目统计数据(单个项目)
/// </summary>
/// <param name="projectId">项目ID</param>
/// <param name="statisticType">统计类型</param>
/// <returns>统计数值</returns>
[DisplayName("获取项目统计数据")]
public async Task<int> 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;
}
/// <summary>
/// 批量获取项目统计数据(性能优化,避免N+1查询)
/// </summary>
/// <param name="projectIds">项目ID列表</param>
/// <param name="statisticType">统计类型</param>
/// <returns>字典,Key为项目ID,Value为统计数值</returns>
[DisplayName("批量获取项目统计数据")]
public async Task<Dictionary<long, int>> GetProjectStatisticsBatchAsync(List<long> projectIds, ProjectStatisticType statisticType)
{
var result = new Dictionary<long, int>();
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;
}
2026-03-01 22:32:44 +08:00
}