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; /// 裁判库管理。 [ApiDescriptionSettings("打分系统")] public class RefereeService : BaseService, ITransient { private readonly ISqlSugarRepository _repository; private readonly ISqlSugarRepository _userRepository; private readonly IEasyCachingProvider _cache; private readonly IIdGenerator _idGenerator; private readonly AuthManager _authManager; public RefereeService(ISqlSugarRepository repository, ISqlSugarRepository userRepository, IEasyCachingProvider cache, IIdGenerator idGenerator, AuthManager authManager) : base(repository) { _repository = repository; _userRepository = userRepository; _cache = cache; _idGenerator = idGenerator; _authManager = authManager; } [DisplayName("裁判分页查询")] [HttpGet("page")] public async Task> 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 { PageNo = page.PageNo, PageSize = page.PageSize, Total = page.Total, Rows = page.Rows.Select(ToOutput).ToList() }; } [DisplayName("新增裁判")] [HttpPost("add")] public async Task Add([FromBody] SaveRefereeInput input) { await EnsureMobileAvailable(input.Mobile, null); await VerifyCode(input.Mobile, input.VerificationCode); var appUser = await FindAppUserAsync(input.Mobile); var entity = input.Adapt(); entity.Id = _idGenerator.NextId(); entity.AppUserId = appUser?.Id; // 后台管理员直接维护的裁判档案无需再次审批。 var now = DateTime.Now; entity.AuditStatus = AuditStatus.Pass; entity.AuditUserId = _authManager.UserId; entity.AuditTime = now; entity.Status = AvailabilityStatus.Enable; entity.CreatedUserId = _authManager.UserId; entity.CreatedTime = now; await _repository.InsertAsync(entity); await _cache.RemoveAsync($"sms:code:{input.Mobile}"); } [DisplayName("修改裁判")] [HttpPut("update")] 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}"); } var appUser = await FindAppUserAsync(input.Mobile); input.Adapt(entity); entity.AppUserId = appUser?.Id; 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; entity.AuditStatus = input.AuditStatus; entity.AuditRemark = input.Remark; entity.AuditUserId = _authManager.UserId; entity.AuditTime = now; entity.UpdatedUserId = _authManager.UserId; entity.UpdatedTime = now; await _repository.UpdateAsync(entity); } private async Task EnsureMobileAvailable(string mobile, long? exceptId) { // 不要把 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()) throw Oops.Bah("该手机号已存在于裁判库"); } /// /// 手机号已注册小程序账号时自动建立关联,未注册时允许裁判档案独立存在。 /// private Task FindAppUserAsync(string mobile) { return _userRepository.AsQueryable().Where(x => x.Mobile == mobile).FirstAsync(); } /// /// 批量回填已存在裁判的AppUserId。按手机号匹配小程序用户,建立关联。 /// [DisplayName("批量回填AppUserId")] [HttpPost("backfill-appuser")] public async Task 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 }; } private async Task VerifyCode(string mobile, string? code) { if (string.IsNullOrWhiteSpace(code)) throw Oops.Bah("请输入验证码"); var cached = await _cache.GetAsync($"sms:code:{mobile}"); if (!cached.HasValue || cached.Value != code) throw Oops.Bah("验证码错误或已过期"); } private static RefereeOutput ToOutput(YD_Event.Core.Entities.Referee x) => new() { Id = x.Id, AppUserId = x.AppUserId, Name = x.Name, Mobile = x.Mobile, 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().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 }; }