194 lines
8.5 KiB
C#
Raw Normal View History

2026-08-11 23:41:18 +08:00
using EasyCaching.Core;
using YD_Event.Application.Auth;
using YD_Event.Application.Referee.Dtos;
using YD_Event.Core.Entities;
using YD_Event.Core.Enum;
namespace YD_Event.Application.Referee;
/// <summary>裁判库管理。</summary>
[ApiDescriptionSettings("打分系统")]
2026-08-11 23:41:18 +08:00
public class RefereeService : BaseService<YD_Event.Core.Entities.Referee>, ITransient
{
private readonly ISqlSugarRepository<YD_Event.Core.Entities.Referee> _repository;
private readonly ISqlSugarRepository<YD_Event.Core.Entities.AppUser> _userRepository;
private readonly IEasyCachingProvider _cache;
private readonly IIdGenerator _idGenerator;
private readonly AuthManager _authManager;
public RefereeService(ISqlSugarRepository<YD_Event.Core.Entities.Referee> repository,
ISqlSugarRepository<YD_Event.Core.Entities.AppUser> userRepository, IEasyCachingProvider cache,
IIdGenerator idGenerator, AuthManager authManager) : base(repository)
{
_repository = repository;
_userRepository = userRepository;
_cache = cache;
_idGenerator = idGenerator;
_authManager = authManager;
}
[DisplayName("裁判分页查询")]
2026-08-17 00:14:48 +08:00
[HttpGet("page")]
2026-08-11 23:41:18 +08:00
public async Task<PageResult<RefereeOutput>> Page([FromQuery] RefereeQueryInput input)
{
var query = _repository.AsQueryable()
.WhereIF(!string.IsNullOrWhiteSpace(input.Name), x => x.Name.Contains(input.Name!))
.WhereIF(!string.IsNullOrWhiteSpace(input.Mobile), x => x.Mobile.Contains(input.Mobile!))
.WhereIF(!string.IsNullOrWhiteSpace(input.RegionCode), x => x.ProvinceCode == input.RegionCode || x.CityCode == input.RegionCode || x.DistrictCode == input.RegionCode)
.WhereIF(input.Level.HasValue, x => x.Level == input.Level)
.WhereIF(input.Role.HasValue, x => (x.Roles & input.Role!.Value) == input.Role.Value)
.WhereIF(input.AuditStatus.HasValue, x => x.AuditStatus == input.AuditStatus)
.WhereIF(input.StartTime.HasValue, x => x.CreatedTime >= input.StartTime)
.WhereIF(input.EndTime.HasValue, x => x.CreatedTime < input.EndTime!.Value.Date.AddDays(1))
.OrderByDescending(x => x.CreatedTime);
var page = await query.ToPagedListAsync(input);
return new PageResult<RefereeOutput>
{
PageNo = page.PageNo, PageSize = page.PageSize, Total = page.Total,
Rows = page.Rows.Select(ToOutput).ToList()
};
}
[DisplayName("新增裁判")]
2026-08-17 00:14:48 +08:00
[HttpPost("add")]
2026-08-11 23:41:18 +08:00
public async Task Add([FromBody] SaveRefereeInput input)
{
await EnsureMobileAvailable(input.Mobile, null);
await VerifyCode(input.Mobile, input.VerificationCode);
2026-08-17 00:14:48 +08:00
var appUser = await FindAppUserAsync(input.Mobile);
2026-08-11 23:41:18 +08:00
var entity = input.Adapt<YD_Event.Core.Entities.Referee>();
entity.Id = _idGenerator.NextId();
2026-08-17 00:14:48 +08:00
entity.AppUserId = appUser?.Id;
// 后台管理员直接维护的裁判档案无需再次审批。
var now = DateTime.Now;
2026-08-17 00:14:48 +08:00
entity.AuditStatus = AuditStatus.Pass;
entity.AuditUserId = _authManager.UserId;
entity.AuditTime = now;
2026-08-11 23:41:18 +08:00
entity.Status = AvailabilityStatus.Enable;
entity.CreatedUserId = _authManager.UserId;
entity.CreatedTime = now;
2026-08-11 23:41:18 +08:00
await _repository.InsertAsync(entity);
await _cache.RemoveAsync($"sms:code:{input.Mobile}");
}
[DisplayName("修改裁判")]
2026-08-17 00:14:48 +08:00
[HttpPut("update")]
2026-08-11 23:41:18 +08:00
public async Task Update([FromBody] UpdateRefereeInput input)
{
var entity = await _repository.GetByIdAsync(input.Id);
if (entity == null || entity.DeleteMark) throw Oops.Bah("裁判不存在");
await EnsureMobileAvailable(input.Mobile, input.Id);
if (!string.Equals(entity.Mobile, input.Mobile, StringComparison.Ordinal))
{
await VerifyCode(input.Mobile, input.VerificationCode);
await _cache.RemoveAsync($"sms:code:{input.Mobile}");
}
2026-08-17 00:14:48 +08:00
var appUser = await FindAppUserAsync(input.Mobile);
2026-08-11 23:41:18 +08:00
input.Adapt(entity);
2026-08-17 00:14:48 +08:00
entity.AppUserId = appUser?.Id;
2026-08-11 23:41:18 +08:00
entity.UpdatedUserId = _authManager.UserId;
entity.UpdatedTime = DateTime.Now;
await _repository.UpdateAsync(entity);
}
[DisplayName("审核裁判"), HttpPost("audit")]
public async Task Audit([FromBody] AuditRefereeInput input)
{
if (input.AuditStatus != AuditStatus.Pass && input.AuditStatus != AuditStatus.Reject)
throw Oops.Bah("审核状态不正确");
var entity = await _repository.GetByIdAsync(input.Id);
if (entity == null || entity.DeleteMark) throw Oops.Bah("裁判不存在");
var now = DateTime.Now;
2026-08-11 23:41:18 +08:00
entity.AuditStatus = input.AuditStatus;
entity.AuditRemark = input.Remark;
entity.AuditUserId = _authManager.UserId;
entity.AuditTime = now;
2026-08-11 23:41:18 +08:00
entity.UpdatedUserId = _authManager.UserId;
entity.UpdatedTime = now;
2026-08-11 23:41:18 +08:00
await _repository.UpdateAsync(entity);
}
private async Task EnsureMobileAvailable(string mobile, long? exceptId)
{
2026-08-17 00:14:48 +08:00
// 不要把 Nullable.HasValue 分支写进 SqlSugar 表达式;空值时会生成 “<>” 附近的非法 SQL。
var refereeQuery = _repository.AsQueryable().Where(x => x.Mobile == mobile);
if (exceptId.HasValue)
refereeQuery = refereeQuery.Where(x => x.Id != exceptId.Value);
if (await refereeQuery.AnyAsync())
2026-08-11 23:41:18 +08:00
throw Oops.Bah("该手机号已存在于裁判库");
2026-08-17 00:14:48 +08:00
}
/// <summary>
2026-08-28 15:47:41 +08:00
/// 手机号已注册小程序账号时自动建立关联,未注册时允许裁判档案独立存在。
2026-08-17 00:14:48 +08:00
/// </summary>
private Task<YD_Event.Core.Entities.AppUser?> FindAppUserAsync(string mobile)
{
return _userRepository.AsQueryable().Where(x => x.Mobile == mobile).FirstAsync();
2026-08-11 23:41:18 +08:00
}
2026-08-28 15:47:41 +08:00
/// <summary>
/// 批量回填已存在裁判的AppUserId。按手机号匹配小程序用户,建立关联。
/// </summary>
[DisplayName("批量回填AppUserId")]
[HttpPost("backfill-appuser")]
public async Task<object> BackfillAppUserId()
{
var refereesWithoutAppUser = await _repository.AsQueryable()
.Where(x => x.AppUserId == null && !x.DeleteMark && !string.IsNullOrEmpty(x.Mobile))
.ToListAsync();
if (refereesWithoutAppUser.Count == 0)
{
return new { success = true, message = "无需回填", count = 0 };
}
var mobiles = refereesWithoutAppUser.Select(x => x.Mobile).Distinct().ToList();
var appUsers = await _userRepository.AsQueryable()
.Where(x => mobiles.Contains(x.Mobile))
.ToListAsync();
var mobileUserMap = appUsers.ToDictionary(x => x.Mobile, x => x.Id);
var updatedCount = 0;
foreach (var referee in refereesWithoutAppUser)
{
if (mobileUserMap.TryGetValue(referee.Mobile, out var appUserId))
{
referee.AppUserId = appUserId;
referee.UpdatedUserId = _authManager.UserId;
referee.UpdatedTime = DateTime.Now;
updatedCount++;
}
}
if (updatedCount > 0)
{
await _repository.UpdateRangeAsync(refereesWithoutAppUser.Where(x => x.AppUserId != null).ToList());
}
return new { success = true, message = $"成功回填{updatedCount}条记录", count = updatedCount };
}
2026-08-11 23:41:18 +08:00
private async Task VerifyCode(string mobile, string? code)
{
if (string.IsNullOrWhiteSpace(code)) throw Oops.Bah("请输入验证码");
var cached = await _cache.GetAsync<string>($"sms:code:{mobile}");
if (!cached.HasValue || cached.Value != code) throw Oops.Bah("验证码错误或已过期");
}
private static RefereeOutput ToOutput(YD_Event.Core.Entities.Referee x) => new()
{
2026-08-17 00:14:48 +08:00
Id = x.Id, AppUserId = x.AppUserId, Name = x.Name, Mobile = x.Mobile,
2026-08-11 23:41:18 +08:00
ProvinceCode = x.ProvinceCode, ProvinceName = x.ProvinceName,
CityCode = x.CityCode, CityName = x.CityName,
DistrictCode = x.DistrictCode, DistrictName = x.DistrictName,
RegionName = $"{x.ProvinceName}/{x.CityName}/{x.DistrictName}",
Level = x.Level, LevelName = x.Level.Description(), CertificateUrl = x.CertificateUrl,
Roles = x.Roles,
RoleNames = Enum.GetValues<RefereeRole>().Where(r => r != RefereeRole.None && x.Roles.HasFlag(r)).Select(r => r.Description()).ToList(),
AuditStatus = x.AuditStatus, AuditRemark = x.AuditRemark,
AuditUserId = x.AuditUserId, AuditTime = x.AuditTime, CreatedTime = x.CreatedTime
2026-08-11 23:41:18 +08:00
};
}