2026-03-18 00:05:53 +08:00
|
|
|
using YD_Event.Core.Entities;
|
|
|
|
|
using Mapster;
|
|
|
|
|
using System.ComponentModel;
|
|
|
|
|
using Furion.DynamicApiController;
|
|
|
|
|
using YD_Event.Application.Region.Dtos;
|
2026-03-18 01:30:27 +08:00
|
|
|
using Microsoft.AspNetCore.Mvc;
|
|
|
|
|
using EasyCaching.Core;
|
|
|
|
|
using SqlSugar;
|
|
|
|
|
using Furion.FriendlyException;
|
|
|
|
|
using Microsoft.AspNetCore.Authorization;
|
|
|
|
|
using YD_Event.Core.Shared;
|
2026-03-18 00:05:53 +08:00
|
|
|
|
|
|
|
|
namespace YD_Event.Application.Region
|
|
|
|
|
{
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 行政区域服务
|
|
|
|
|
/// </summary>
|
|
|
|
|
public class RegionService : ITransient, IDynamicApiController
|
|
|
|
|
{
|
|
|
|
|
private readonly ISqlSugarRepository<SysProvince> _provinceRepository;
|
|
|
|
|
private readonly ISqlSugarRepository<SysCity> _cityRepository;
|
|
|
|
|
private readonly ISqlSugarRepository<SysDistrict> _districtRepository;
|
2026-03-18 01:30:27 +08:00
|
|
|
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:";
|
2026-03-18 00:05:53 +08:00
|
|
|
|
|
|
|
|
public RegionService(
|
|
|
|
|
ISqlSugarRepository<SysProvince> provinceRepository,
|
|
|
|
|
ISqlSugarRepository<SysCity> cityRepository,
|
2026-03-18 01:30:27 +08:00
|
|
|
ISqlSugarRepository<SysDistrict> districtRepository,
|
|
|
|
|
IEasyCachingProvider easyCachingProvider,
|
|
|
|
|
IIdGenerator idGenerator)
|
2026-03-18 00:05:53 +08:00
|
|
|
{
|
|
|
|
|
_provinceRepository = provinceRepository;
|
|
|
|
|
_cityRepository = cityRepository;
|
|
|
|
|
_districtRepository = districtRepository;
|
2026-03-18 01:30:27 +08:00
|
|
|
_easyCachingProvider = easyCachingProvider;
|
|
|
|
|
_idGenerator = idGenerator;
|
2026-03-18 00:05:53 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
2026-03-18 01:30:27 +08:00
|
|
|
/// 获取所有省份 (带缓存)
|
2026-03-18 00:05:53 +08:00
|
|
|
/// </summary>
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
[DisplayName("获取所有省份")]
|
2026-03-18 01:30:27 +08:00
|
|
|
[AllowAnonymous]
|
2026-03-18 00:05:53 +08:00
|
|
|
public async Task<List<RegionDto>> GetProvinces()
|
|
|
|
|
{
|
2026-03-18 01:30:27 +08:00
|
|
|
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;
|
2026-03-18 00:05:53 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
2026-03-18 01:30:27 +08:00
|
|
|
/// 根据省份代码获取城市列表 (带缓存)
|
2026-03-18 00:05:53 +08:00
|
|
|
/// </summary>
|
2026-03-18 01:30:27 +08:00
|
|
|
/// <param name="provinceCode">省份代码</param>
|
2026-03-18 00:05:53 +08:00
|
|
|
/// <returns></returns>
|
|
|
|
|
[DisplayName("根据省份代码获取城市列表")]
|
2026-03-18 01:30:27 +08:00
|
|
|
[AllowAnonymous]
|
|
|
|
|
public async Task<List<RegionDto>> GetCitiesByProvince([FromQuery] string provinceCode)
|
2026-03-18 00:05:53 +08:00
|
|
|
{
|
2026-03-18 01:30:27 +08:00
|
|
|
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;
|
|
|
|
|
|
2026-03-18 00:05:53 +08:00
|
|
|
var cities = await _cityRepository.GetListAsync(c => c.ProvinceCode == provinceCode);
|
2026-03-18 01:30:27 +08:00
|
|
|
// 防穿透:如果查询为空,也缓存一个空列表,时间较短
|
|
|
|
|
if (!cities.Any())
|
|
|
|
|
{
|
|
|
|
|
await _easyCachingProvider.SetAsync(cacheKey, new List<RegionDto>(), TimeSpan.FromMinutes(5));
|
|
|
|
|
return new List<RegionDto>();
|
|
|
|
|
}
|
2026-03-18 00:05:53 +08:00
|
|
|
|
2026-03-18 01:30:27 +08:00
|
|
|
var dtos = cities.Select(x => new RegionDto
|
|
|
|
|
{
|
|
|
|
|
Id = x.Id,
|
|
|
|
|
Code = x.CityCode,
|
|
|
|
|
Name = x.CityName,
|
|
|
|
|
ParentCode = x.ProvinceCode
|
|
|
|
|
}).ToList();
|
2026-03-18 00:05:53 +08:00
|
|
|
|
2026-03-18 01:30:27 +08:00
|
|
|
await _easyCachingProvider.SetAsync(cacheKey, dtos, TimeSpan.FromHours(new Random().Next(12, 24)));
|
|
|
|
|
return dtos;
|
|
|
|
|
}
|
2026-03-18 00:05:53 +08:00
|
|
|
|
|
|
|
|
/// <summary>
|
2026-03-18 01:30:27 +08:00
|
|
|
/// 根据城市代码获取区县列表 (带缓存)
|
2026-03-18 00:05:53 +08:00
|
|
|
/// </summary>
|
2026-03-18 01:30:27 +08:00
|
|
|
/// <param name="cityCode">城市代码</param>
|
2026-03-18 00:05:53 +08:00
|
|
|
/// <returns></returns>
|
|
|
|
|
[DisplayName("根据城市代码获取区县列表")]
|
2026-03-18 01:30:27 +08:00
|
|
|
[AllowAnonymous]
|
|
|
|
|
public async Task<List<RegionDto>> GetDistrictsByCity([FromQuery] string cityCode)
|
2026-03-18 00:05:53 +08:00
|
|
|
{
|
2026-03-18 01:30:27 +08:00
|
|
|
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;
|
|
|
|
|
|
2026-03-18 00:05:53 +08:00
|
|
|
var districts = await _districtRepository.GetListAsync(d => d.CityCode == cityCode);
|
2026-03-18 01:30:27 +08:00
|
|
|
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);
|
2026-03-18 00:05:53 +08:00
|
|
|
}
|
|
|
|
|
}
|
2026-03-18 01:30:27 +08:00
|
|
|
}
|