省市区

This commit is contained in:
马梓彭 2026-03-18 01:30:27 +08:00
parent 47190401fe
commit 1664b9c70d
4 changed files with 316 additions and 36 deletions

View File

@ -1,11 +1,34 @@
using System.ComponentModel.DataAnnotations;
using SqlSugar;
namespace YD_Event.Application.Region.Dtos
{
/// <summary>
/// 区域分页查询输入
/// </summary>
public class RegionPageInput : Pagination
{
/// <summary>
/// 区域名称(模糊查询)
/// </summary>
public string? Name { get; set; }
/// <summary>
/// 区域编码
/// </summary>
public string? Code { get; set; }
}
/// <summary>
/// 行政区域数据传输对象
/// </summary>
public class RegionDto
{
/// <summary>
/// 主键ID
/// </summary>
public long Id { get; set; }
/// <summary>
/// 区域编码
/// </summary>
@ -15,5 +38,41 @@ namespace YD_Event.Application.Region.Dtos
/// 区域名称
/// </summary>
public string Name { get; set; }
/// <summary>
/// 父级编码 (省份为大区Code,城市为省份Code,区县为城市Code)
/// </summary>
public string ParentCode { get; set; }
}
}
/// <summary>
/// 新增/更新行政区域输入
/// </summary>
public class RegionInput
{
/// <summary>
/// 主键ID (更新时必填)
/// </summary>
public long Id { get; set; }
/// <summary>
/// 区域编码
/// </summary>
[Required(ErrorMessage = "区域编码不能为空")]
[MaxLength(50, ErrorMessage = "区域编码最大长度为50")]
public string Code { get; set; }
/// <summary>
/// 区域名称
/// </summary>
[Required(ErrorMessage = "区域名称不能为空")]
[MaxLength(50, ErrorMessage = "区域名称最大长度为50")]
public string Name { get; set; }
/// <summary>
/// 父级编码
/// </summary>
[MaxLength(50, ErrorMessage = "父级编码最大长度为50")]
public string ParentCode { get; set; }
}
}

View File

@ -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
{
/// <summary>
/// 行政区域服务
/// </summary>
[ApiDescriptionSettings("系统管理", Name = "Region", Order = 100)]
public class RegionService : ITransient, IDynamicApiController
{
private readonly ISqlSugarRepository<SysProvince> _provinceRepository;
private readonly ISqlSugarRepository<SysCity> _cityRepository;
private readonly ISqlSugarRepository<SysDistrict> _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<SysProvince> provinceRepository,
ISqlSugarRepository<SysCity> cityRepository,
ISqlSugarRepository<SysDistrict> districtRepository)
ISqlSugarRepository<SysDistrict> districtRepository,
IEasyCachingProvider easyCachingProvider,
IIdGenerator idGenerator)
{
_provinceRepository = provinceRepository;
_cityRepository = cityRepository;
_districtRepository = districtRepository;
_easyCachingProvider = easyCachingProvider;
_idGenerator = idGenerator;
}
/// <summary>
/// 获取所有省份
/// 获取所有省份 (带缓存)
/// </summary>
/// <returns></returns>
[DisplayName("获取所有省份")]
[AllowAnonymous]
public async Task<List<RegionDto>> GetProvinces()
{
var provinces = await _provinceRepository.GetListAsync();
return provinces.Adapt<List<RegionDto>>();
var cacheValue = await _easyCachingProvider.GetAsync<List<RegionDto>>(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;
}
/// <summary>
/// 根据省份代码获取城市列表
/// 根据省份代码获取城市列表 (带缓存)
/// </summary>
/// <param name="provinceCode"></param>
/// <param name="provinceCode">省份代码</param>
/// <returns></returns>
[DisplayName("根据省份代码获取城市列表")]
public async Task<List<RegionDto>> GetCitiesByProvince(string provinceCode)
[AllowAnonymous]
public async Task<List<RegionDto>> GetCitiesByProvince([FromQuery] string provinceCode)
{
if (string.IsNullOrWhiteSpace(provinceCode)) throw Oops.Oh("省份代码不能为空");
string cacheKey = $"{CacheKey_Cities}{provinceCode}";
var cacheValue = await _easyCachingProvider.GetAsync<List<RegionDto>>(cacheKey);
if (cacheValue.HasValue) return cacheValue.Value;
var cities = await _cityRepository.GetListAsync(c => c.ProvinceCode == provinceCode);
return cities.Adapt<List<RegionDto>>();
// 防穿透:如果查询为空,也缓存一个空列表,时间较短
if (!cities.Any())
{
await _easyCachingProvider.SetAsync(cacheKey, new List<RegionDto>(), TimeSpan.FromMinutes(5));
return new List<RegionDto>();
}
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;
}
/// <summary>
/// 根据城市代码获取区县列表
/// 根据城市代码获取区县列表 (带缓存)
/// </summary>
/// <param name="cityCode"></param>
/// <param name="cityCode">城市代码</param>
/// <returns></returns>
[DisplayName("根据城市代码获取区县列表")]
public async Task<List<RegionDto>> GetDistrictsByCity(string cityCode)
[AllowAnonymous]
public async Task<List<RegionDto>> GetDistrictsByCity([FromQuery] string cityCode)
{
if (string.IsNullOrWhiteSpace(cityCode)) throw Oops.Oh("城市代码不能为空");
string cacheKey = $"{CacheKey_Districts}{cityCode}";
var cacheValue = await _easyCachingProvider.GetAsync<List<RegionDto>>(cacheKey);
if (cacheValue.HasValue) return cacheValue.Value;
var districts = await _districtRepository.GetListAsync(d => d.CityCode == cityCode);
return districts.Adapt<List<RegionDto>>();
if (!districts.Any())
{
await _easyCachingProvider.SetAsync(cacheKey, new List<RegionDto>(), TimeSpan.FromMinutes(5));
return new List<RegionDto>();
}
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;
}
/// <summary>
/// 分页查询省份信息
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[DisplayName("分页查询省份信息")]
[HttpPost("page/provinces")]
public async Task<SqlSugar.PageResult<RegionDto>> 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;
}
/// <summary>
/// 添加省份
/// </summary>
[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);
}
/// <summary>
/// 更新省份
/// </summary>
[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);
}
/// <summary>
/// 删除省份
/// </summary>
[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);
}
}
}
}

View File

@ -4536,11 +4536,31 @@
<param name="eventStartTime">赛事开始时间</param>
<returns>应退金额</returns>
</member>
<member name="T:YD_Event.Application.Region.Dtos.RegionPageInput">
<summary>
区域分页查询输入
</summary>
</member>
<member name="P:YD_Event.Application.Region.Dtos.RegionPageInput.Name">
<summary>
区域名称(模糊查询)
</summary>
</member>
<member name="P:YD_Event.Application.Region.Dtos.RegionPageInput.Code">
<summary>
区域编码
</summary>
</member>
<member name="T:YD_Event.Application.Region.Dtos.RegionDto">
<summary>
行政区域数据传输对象
</summary>
</member>
<member name="P:YD_Event.Application.Region.Dtos.RegionDto.Id">
<summary>
主键ID
</summary>
</member>
<member name="P:YD_Event.Application.Region.Dtos.RegionDto.Code">
<summary>
区域编码
@ -4551,6 +4571,36 @@
区域名称
</summary>
</member>
<member name="P:YD_Event.Application.Region.Dtos.RegionDto.ParentCode">
<summary>
父级编码 (省份为大区Code,城市为省份Code,区县为城市Code)
</summary>
</member>
<member name="T:YD_Event.Application.Region.Dtos.RegionInput">
<summary>
新增/更新行政区域输入
</summary>
</member>
<member name="P:YD_Event.Application.Region.Dtos.RegionInput.Id">
<summary>
主键ID (更新时必填)
</summary>
</member>
<member name="P:YD_Event.Application.Region.Dtos.RegionInput.Code">
<summary>
区域编码
</summary>
</member>
<member name="P:YD_Event.Application.Region.Dtos.RegionInput.Name">
<summary>
区域名称
</summary>
</member>
<member name="P:YD_Event.Application.Region.Dtos.RegionInput.ParentCode">
<summary>
父级编码
</summary>
</member>
<member name="T:YD_Event.Application.Region.RegionService">
<summary>
行政区域服务
@ -4558,24 +4608,46 @@
</member>
<member name="M:YD_Event.Application.Region.RegionService.GetProvinces">
<summary>
获取所有省份
获取所有省份 (带缓存)
</summary>
<returns></returns>
</member>
<member name="M:YD_Event.Application.Region.RegionService.GetCitiesByProvince(System.String)">
<summary>
根据省份代码获取城市列表
根据省份代码获取城市列表 (带缓存)
</summary>
<param name="provinceCode"></param>
<param name="provinceCode">省份代码</param>
<returns></returns>
</member>
<member name="M:YD_Event.Application.Region.RegionService.GetDistrictsByCity(System.String)">
<summary>
根据城市代码获取区县列表
根据城市代码获取区县列表 (带缓存)
</summary>
<param name="cityCode"></param>
<param name="cityCode">城市代码</param>
<returns></returns>
</member>
<member name="M:YD_Event.Application.Region.RegionService.GetProvincePage(YD_Event.Application.Region.Dtos.RegionPageInput)">
<summary>
分页查询省份信息
</summary>
<param name="input"></param>
<returns></returns>
</member>
<member name="M:YD_Event.Application.Region.RegionService.AddProvince(YD_Event.Application.Region.Dtos.RegionInput)">
<summary>
添加省份
</summary>
</member>
<member name="M:YD_Event.Application.Region.RegionService.UpdateProvince(YD_Event.Application.Region.Dtos.RegionInput)">
<summary>
更新省份
</summary>
</member>
<member name="M:YD_Event.Application.Region.RegionService.DeleteProvince(System.Int64)">
<summary>
删除省份
</summary>
</member>
<member name="P:YD_Event.Application.Role.Dtos.AddSysRoleInput.Name">
<summary>
角色名称

View File

@ -3011,11 +3011,6 @@
城市表
</summary>
</member>
<member name="P:YD_Event.Core.Entities.SysCity.CityId">
<summary>
城市Id
</summary>
</member>
<member name="P:YD_Event.Core.Entities.SysCity.ProvinceCode">
<summary>
省份Code
@ -3061,11 +3056,6 @@
区县表
</summary>
</member>
<member name="P:YD_Event.Core.Entities.SysDistrict.DistrictId">
<summary>
区县Id
</summary>
</member>
<member name="P:YD_Event.Core.Entities.SysDistrict.CityCode">
<summary>
城市Code
@ -3401,11 +3391,6 @@
省份表
</summary>
</member>
<member name="P:YD_Event.Core.Entities.SysProvince.ProvinceId">
<summary>
省份Id
</summary>
</member>
<member name="P:YD_Event.Core.Entities.SysProvince.ProvinceCode">
<summary>
省份Code