318 lines
11 KiB
C#
Raw 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 YD_Event.Application.Project.Dtos;
using YD_Event.Core.Entities;
using Mapster;
using SqlSugar;
using Microsoft.Extensions.Logging;
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<ProjectType> _typeRepository;
private readonly ISqlSugarRepository<EventProject> _eventProjectRepository;
private readonly ISqlSugarRepository<EventParticipationRecord> _participationRecordRepository;
private readonly IIdGenerator _idGenerator;
private readonly ILogger<ProjectService> _logger;
public ProjectService(
ISqlSugarRepository<YD_Event.Core.Entities.Project> repository,
ISqlSugarRepository<ProjectGroup> groupRepository,
ISqlSugarRepository<ProjectType> typeRepository,
ISqlSugarRepository<EventProject> eventProjectRepository,
ISqlSugarRepository<EventParticipationRecord> participationRecordRepository,
IIdGenerator idGenerator,
ILogger<ProjectService> logger) : base(repository)
{
_repository = repository;
_groupRepository = groupRepository;
_typeRepository = typeRepository;
_eventProjectRepository = eventProjectRepository;
_participationRecordRepository = participationRecordRepository;
_idGenerator = idGenerator;
_logger = logger;
}
/// <summary>
/// 项目分页查询
/// </summary>
[DisplayName("项目分页查询")]
public async Task<PageResult<ProjectDto>> Page(ProjectQueryInput input)
{
var query = _repository.AsQueryable()
.LeftJoin<ProjectType>((p, pt) => p.ProjectTypeId == pt.Id)
.WhereIF(!string.IsNullOrWhiteSpace(input.ProjectName), (p, pt) => p.Name.Contains(input.ProjectName))
.WhereIF(input.ProjectTypeId.HasValue, (p, pt) => p.ProjectTypeId == input.ProjectTypeId)
.OrderByDescending((p, pt) => p.Id);
var result = await query.Select((p, pt) => new ProjectDto
{
Id = p.Id,
Name = p.Name,
ProjectTypeId = p.ProjectTypeId,
ProjectTypeName = pt.Name,
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);
var projectIds = result.Rows.Select(x => x.Id).ToList();
if (projectIds.Any())
{
var stats = await GetProjectStatisticsBatchAsync(projectIds, ProjectStatisticType.CompletionCount);
foreach (var item in result.Rows)
{
if (stats.TryGetValue(item.Id, out var count))
{
item.CompletionCount = count;
}
}
}
return result;
}
/// <summary>
/// 添加项目
/// </summary>
[DisplayName("添加项目")]
[UnitOfWork]
public async Task Add(AddProjectInput input)
{
var isExist = await _repository.IsAnyAsync(x => x.Name == input.Name);
if (isExist) throw new InvalidOperationException("该项目名称已存在");
var entity = input.Adapt<YD_Event.Core.Entities.Project>();
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<ProjectGroup>();
group.Id = _idGenerator.NewLong();
group.ProjectId = entity.Id;
return group;
}).ToList();
await _groupRepository.InsertRangeAsync(groups);
}
}
/// <summary>
/// 更新项目
/// </summary>
[DisplayName("更新项目")]
[UnitOfWork]
public async Task Update(UpdateProjectInput input)
{
var entity = await _repository.GetByIdAsync(input.Id);
if (entity == null) throw new InvalidOperationException("项目不存在");
if (entity.Name != input.Name)
{
var isExist = await _repository.IsAnyAsync(x => x.Name == input.Name && x.Id != input.Id);
if (isExist) throw new InvalidOperationException("该项目名称已存在");
}
input.Adapt(entity);
entity.UpdatedTime = DateTime.Now;
await _repository.UpdateAsync(entity);
await _groupRepository.DeleteAsync(x => x.ProjectId == entity.Id);
if (input.Groups != null && input.Groups.Any())
{
var groups = input.Groups.Select(g =>
{
var group = g.Adapt<ProjectGroup>();
group.Id = _idGenerator.NewLong();
group.ProjectId = entity.Id;
return group;
}).ToList();
await _groupRepository.InsertRangeAsync(groups);
}
}
/// <summary>
/// 删除项目
/// </summary>
[DisplayName("删除项目")]
[UnitOfWork]
public async Task Delete(List<long> ids)
{
await _repository.DeleteAsync(x => ids.Contains(x.Id));
await _groupRepository.DeleteAsync(x => ids.Contains(x.ProjectId));
}
/// <summary>
/// 获取项目详情
/// </summary>
[DisplayName("获取项目详情")]
public async Task<ProjectDto> Detail(long id)
{
var p = await _repository.GetByIdAsync(id);
if (p == null) throw new InvalidOperationException("项目不存在");
var dto = p.Adapt<ProjectDto>();
var type = await _typeRepository.GetByIdAsync(p.ProjectTypeId);
if (type != null)
{
dto.ProjectTypeName = type.Name;
}
var groups = await _groupRepository.GetListAsync(x => x.ProjectId == id);
dto.Groups = groups.Adapt<List<ProjectGroupDto>>();
dto.CompletionCount = await GetProjectStatisticAsync(id, ProjectStatisticType.CompletionCount);
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;
}
}