diff --git a/YD_Event.Application/Region/Dtos/RegionDto.cs b/YD_Event.Application/Region/Dtos/RegionDto.cs index 80b06b7..99ccd24 100644 --- a/YD_Event.Application/Region/Dtos/RegionDto.cs +++ b/YD_Event.Application/Region/Dtos/RegionDto.cs @@ -1,11 +1,34 @@ +using System.ComponentModel.DataAnnotations; +using SqlSugar; namespace YD_Event.Application.Region.Dtos { + /// + /// 区域分页查询输入 + /// + public class RegionPageInput : Pagination + { + /// + /// 区域名称(模糊查询) + /// + public string? Name { get; set; } + + /// + /// 区域编码 + /// + public string? Code { get; set; } + } + /// /// 行政区域数据传输对象 /// public class RegionDto { + /// + /// 主键ID + /// + public long Id { get; set; } + /// /// 区域编码 /// @@ -15,5 +38,41 @@ namespace YD_Event.Application.Region.Dtos /// 区域名称 /// public string Name { get; set; } + + /// + /// 父级编码 (省份为大区Code,城市为省份Code,区县为城市Code) + /// + public string ParentCode { get; set; } } -} + + /// + /// 新增/更新行政区域输入 + /// + public class RegionInput + { + /// + /// 主键ID (更新时必填) + /// + public long Id { get; set; } + + /// + /// 区域编码 + /// + [Required(ErrorMessage = "区域编码不能为空")] + [MaxLength(50, ErrorMessage = "区域编码最大长度为50")] + public string Code { get; set; } + + /// + /// 区域名称 + /// + [Required(ErrorMessage = "区域名称不能为空")] + [MaxLength(50, ErrorMessage = "区域名称最大长度为50")] + public string Name { get; set; } + + /// + /// 父级编码 + /// + [MaxLength(50, ErrorMessage = "父级编码最大长度为50")] + public string ParentCode { get; set; } + } +} \ No newline at end of file diff --git a/YD_Event.Application/Region/RegionService.cs b/YD_Event.Application/Region/RegionService.cs index b70e1da..2aeaf30 100644 --- a/YD_Event.Application/Region/RegionService.cs +++ b/YD_Event.Application/Region/RegionService.cs @@ -3,63 +3,227 @@ using Mapster; using System.ComponentModel; using Furion.DynamicApiController; using YD_Event.Application.Region.Dtos; +using Microsoft.AspNetCore.Mvc; +using EasyCaching.Core; +using SqlSugar; +using Furion.FriendlyException; +using Microsoft.AspNetCore.Authorization; +using YD_Event.Core.Shared; namespace YD_Event.Application.Region { /// /// 行政区域服务 /// + [ApiDescriptionSettings("系统管理", Name = "Region", Order = 100)] public class RegionService : ITransient, IDynamicApiController { private readonly ISqlSugarRepository _provinceRepository; private readonly ISqlSugarRepository _cityRepository; private readonly ISqlSugarRepository _districtRepository; + private readonly IEasyCachingProvider _easyCachingProvider; + private readonly IIdGenerator _idGenerator; + + // 缓存键定义 + private const string CacheKey_Provinces = "Region:Provinces"; + private const string CacheKey_Cities = "Region:Cities:"; + private const string CacheKey_Districts = "Region:Districts:"; public RegionService( ISqlSugarRepository provinceRepository, ISqlSugarRepository cityRepository, - ISqlSugarRepository districtRepository) + ISqlSugarRepository districtRepository, + IEasyCachingProvider easyCachingProvider, + IIdGenerator idGenerator) { _provinceRepository = provinceRepository; _cityRepository = cityRepository; _districtRepository = districtRepository; + _easyCachingProvider = easyCachingProvider; + _idGenerator = idGenerator; } /// - /// 获取所有省份 + /// 获取所有省份 (带缓存) /// /// [DisplayName("获取所有省份")] + [AllowAnonymous] public async Task> GetProvinces() { - var provinces = await _provinceRepository.GetListAsync(); - return provinces.Adapt>(); + var cacheValue = await _easyCachingProvider.GetAsync>(CacheKey_Provinces); + if (cacheValue.HasValue) return cacheValue.Value; + + var provinces = await _provinceRepository.GetListAsync(x => true); + var dtos = provinces.Select(x => new RegionDto + { + Id = x.Id, + Code = x.ProvinceCode, + Name = x.ProvinceName, + ParentCode = x.RegionCode + }).ToList(); + + // 防击穿与雪崩:设置随机过期时间 (12-24小时) + var expire = TimeSpan.FromHours(new Random().Next(12, 24)); + await _easyCachingProvider.SetAsync(CacheKey_Provinces, dtos, expire); + + return dtos; } /// - /// 根据省份代码获取城市列表 + /// 根据省份代码获取城市列表 (带缓存) /// - /// + /// 省份代码 /// [DisplayName("根据省份代码获取城市列表")] - public async Task> GetCitiesByProvince(string provinceCode) + [AllowAnonymous] + public async Task> GetCitiesByProvince([FromQuery] string provinceCode) { + if (string.IsNullOrWhiteSpace(provinceCode)) throw Oops.Oh("省份代码不能为空"); + + string cacheKey = $"{CacheKey_Cities}{provinceCode}"; + var cacheValue = await _easyCachingProvider.GetAsync>(cacheKey); + if (cacheValue.HasValue) return cacheValue.Value; + var cities = await _cityRepository.GetListAsync(c => c.ProvinceCode == provinceCode); - return cities.Adapt>(); + // 防穿透:如果查询为空,也缓存一个空列表,时间较短 + if (!cities.Any()) + { + await _easyCachingProvider.SetAsync(cacheKey, new List(), TimeSpan.FromMinutes(5)); + return new List(); + } + + var dtos = cities.Select(x => new RegionDto + { + Id = x.Id, + Code = x.CityCode, + Name = x.CityName, + ParentCode = x.ProvinceCode + }).ToList(); + + await _easyCachingProvider.SetAsync(cacheKey, dtos, TimeSpan.FromHours(new Random().Next(12, 24))); + return dtos; } - - /// - /// 根据城市代码获取区县列表 + /// 根据城市代码获取区县列表 (带缓存) /// - /// + /// 城市代码 /// [DisplayName("根据城市代码获取区县列表")] - public async Task> GetDistrictsByCity(string cityCode) + [AllowAnonymous] + public async Task> GetDistrictsByCity([FromQuery] string cityCode) { + if (string.IsNullOrWhiteSpace(cityCode)) throw Oops.Oh("城市代码不能为空"); + + string cacheKey = $"{CacheKey_Districts}{cityCode}"; + var cacheValue = await _easyCachingProvider.GetAsync>(cacheKey); + if (cacheValue.HasValue) return cacheValue.Value; + var districts = await _districtRepository.GetListAsync(d => d.CityCode == cityCode); - return districts.Adapt>(); + if (!districts.Any()) + { + await _easyCachingProvider.SetAsync(cacheKey, new List(), TimeSpan.FromMinutes(5)); + return new List(); + } + + var dtos = districts.Select(x => new RegionDto + { + Id = x.Id, + Code = x.DistrictCode, + Name = x.DistrictName, + ParentCode = x.CityCode + }).ToList(); + + await _easyCachingProvider.SetAsync(cacheKey, dtos, TimeSpan.FromHours(new Random().Next(12, 24))); + return dtos; + } + + /// + /// 分页查询省份信息 + /// + /// + /// + [DisplayName("分页查询省份信息")] + [HttpPost("page/provinces")] + public async Task> GetProvincePage([FromBody] RegionPageInput input) + { + var result = await _provinceRepository.AsQueryable() + .WhereIF(!string.IsNullOrWhiteSpace(input.Name), x => x.ProvinceName.Contains(input.Name)) + .WhereIF(!string.IsNullOrWhiteSpace(input.Code), x => x.ProvinceCode.Contains(input.Code)) + .OrderBy(x => x.ProvinceCode) + .Select(x => new RegionDto + { + Id = x.Id, + Code = x.ProvinceCode, + Name = x.ProvinceName, + ParentCode = x.RegionCode + }) + .ToPagedListAsync(input); + + return result; + } + + /// + /// 添加省份 + /// + [DisplayName("添加省份")] + [UnitOfWork] + public async Task AddProvince(RegionInput input) + { + var exists = await _provinceRepository.IsAnyAsync(x => x.ProvinceCode == input.Code); + if (exists) throw Oops.Oh("省份编码已存在"); + + var entity = new SysProvince + { + Id = _idGenerator.NewLong(), + ProvinceCode = input.Code, + ProvinceName = input.Name, + RegionCode = input.ParentCode, + CreatedTime = DateTime.Now + }; + + await _provinceRepository.InsertAsync(entity); + await _easyCachingProvider.RemoveAsync(CacheKey_Provinces); + } + + /// + /// 更新省份 + /// + [DisplayName("更新省份")] + [UnitOfWork] + public async Task UpdateProvince(RegionInput input) + { + var entity = await _provinceRepository.GetByIdAsync(input.Id); + if (entity == null) throw Oops.Oh("省份不存在"); + + var exists = await _provinceRepository.IsAnyAsync(x => x.ProvinceCode == input.Code && x.Id != input.Id); + if (exists) throw Oops.Oh("省份编码已存在"); + + entity.ProvinceCode = input.Code; + entity.ProvinceName = input.Name; + entity.RegionCode = input.ParentCode; + entity.UpdatedTime = DateTime.Now; + + await _provinceRepository.UpdateAsync(entity); + await _easyCachingProvider.RemoveAsync(CacheKey_Provinces); + } + + /// + /// 删除省份 + /// + [DisplayName("删除省份")] + [UnitOfWork] + public async Task DeleteProvince([FromQuery] long id) + { + var entity = await _provinceRepository.GetByIdAsync(id); + if (entity == null) throw Oops.Oh("省份不存在"); + + var hasCity = await _cityRepository.IsAnyAsync(x => x.ProvinceCode == entity.ProvinceCode); + if (hasCity) throw Oops.Oh("该省份下存在城市,无法删除"); + + await _provinceRepository.DeleteAsync(entity); + await _easyCachingProvider.RemoveAsync(CacheKey_Provinces); } } -} +} \ No newline at end of file diff --git a/YD_Event.Application/YD_Event.Application.xml b/YD_Event.Application/YD_Event.Application.xml index 4a0b9c3..c1c5818 100644 --- a/YD_Event.Application/YD_Event.Application.xml +++ b/YD_Event.Application/YD_Event.Application.xml @@ -4536,11 +4536,31 @@ 赛事开始时间 应退金额 + + + 区域分页查询输入 + + + + + 区域名称(模糊查询) + + + + + 区域编码 + + 行政区域数据传输对象 + + + 主键ID + + 区域编码 @@ -4551,6 +4571,36 @@ 区域名称 + + + 父级编码 (省份为大区Code,城市为省份Code,区县为城市Code) + + + + + 新增/更新行政区域输入 + + + + + 主键ID (更新时必填) + + + + + 区域编码 + + + + + 区域名称 + + + + + 父级编码 + + 行政区域服务 @@ -4558,24 +4608,46 @@ - 获取所有省份 + 获取所有省份 (带缓存) - 根据省份代码获取城市列表 + 根据省份代码获取城市列表 (带缓存) - + 省份代码 - 根据城市代码获取区县列表 + 根据城市代码获取区县列表 (带缓存) - + 城市代码 + + + 分页查询省份信息 + + + + + + + 添加省份 + + + + + 更新省份 + + + + + 删除省份 + + 角色名称 diff --git a/YD_Event.Core/YD_Event.Core.xml b/YD_Event.Core/YD_Event.Core.xml index b90c0e6..eb69859 100644 --- a/YD_Event.Core/YD_Event.Core.xml +++ b/YD_Event.Core/YD_Event.Core.xml @@ -3011,11 +3011,6 @@ 城市表 - - - 城市Id - - 省份Code @@ -3061,11 +3056,6 @@ 区县表 - - - 区县Id - - 城市Code @@ -3401,11 +3391,6 @@ 省份表 - - - 省份Id - - 省份Code