// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
using NewLife.Remoting;
using NewLife.Serialization;
namespace Admin.NET.Core.Service;
///
/// 系统行政区划服务 🧩
///
[ApiDescriptionSettings(Order = 310, Description = "行政区划")]
public class SysRegionService : IDynamicApiController, ITransient
{
private readonly SqlSugarRepository _sysRegionRep;
private readonly SysConfigService _sysConfigService;
public SysRegionService(SqlSugarRepository sysRegionRep, SysConfigService sysConfigService)
{
_sysRegionRep = sysRegionRep;
_sysConfigService = sysConfigService;
}
///
/// 获取行政区划分页列表 🔖
///
///
///
[DisplayName("获取行政区划分页列表")]
public async Task> Page(PageRegionInput input)
{
return await _sysRegionRep.AsQueryable()
.WhereIF(input.Pid > 0, u => u.Pid == input.Pid || u.Id == input.Pid)
.WhereIF(!string.IsNullOrWhiteSpace(input.Name), u => u.Name.Contains(input.Name))
.WhereIF(!string.IsNullOrWhiteSpace(input.Code), u => u.Code.Contains(input.Code))
.ToPagedListAsync(input.Page, input.PageSize);
}
///
/// 获取行政区划列表 🔖
///
///
///
[DisplayName("获取行政区划列表")]
public async Task> GetList([FromQuery] RegionInput input)
{
return await _sysRegionRep.GetListAsync(u => u.Pid == input.Id);
}
///
/// 查询行政区划列表 🔖
/// post参数方便参数扩展,调用api不用大浮动修复,参数如果是对象不建议用[FromQuery]方式传参
///
///
///
[ApiDescriptionSettings(Name = "Query"), HttpPost]
[DisplayName("查询行政区划列表")]
public async Task> QueryList(QueryRegionInput input)
{
return await _sysRegionRep.AsQueryable()
.WhereIF(input.Pid.HasValue, u => u.Pid == input.Pid)
.WhereIF(!string.IsNullOrWhiteSpace(input.Type), u => u.Type == input.Type)
.WhereIF(!string.IsNullOrWhiteSpace(input.Name), u => u.Name.Contains(input.Name))
.WhereIF(!string.IsNullOrWhiteSpace(input.Code), u => u.Code.Contains(input.Code))
.ToListAsync();
}
///
/// 增加行政区划 🔖
///
///
///
[ApiDescriptionSettings(Name = "Add"), HttpPost]
[DisplayName("增加行政区划")]
public async Task AddRegion(AddRegionInput input)
{
input.Code = input.Code?.Trim() ?? "";
if (input.Code.Length != 12 && input.Code.Length != 9 && input.Code.Length != 6) throw Oops.Oh(ErrorCodeEnum.R2003);
if (input.Pid != 0)
{
var pRegion = await _sysRegionRep.GetByIdAsync(input.Pid);
pRegion ??= await _sysRegionRep.GetFirstAsync(u => u.Code == input.Pid.ToString());
if (pRegion == null) throw Oops.Oh(ErrorCodeEnum.D2000);
input.Pid = pRegion.Id;
}
var isExist = await _sysRegionRep.IsAnyAsync(u => u.Name == input.Name && u.Code == input.Code);
if (isExist) throw Oops.Oh(ErrorCodeEnum.R2002);
var sysRegion = input.Adapt();
var newRegion = await _sysRegionRep.AsInsertable(sysRegion).ExecuteReturnEntityAsync();
return newRegion.Id;
}
///
/// 更新行政区划 🔖
///
///
///
[ApiDescriptionSettings(Name = "Update"), HttpPost]
[DisplayName("更新行政区划")]
public async Task UpdateRegion(UpdateRegionInput input)
{
input.Code = input.Code?.Trim() ?? "";
if (input.Code.Length != 12 && input.Code.Length != 9 && input.Code.Length != 6) throw Oops.Oh(ErrorCodeEnum.R2003);
var sysRegion = await _sysRegionRep.GetByIdAsync(input.Id) ?? throw Oops.Oh(ErrorCodeEnum.D1002);
if (sysRegion.Pid != input.Pid && input.Pid != 0)
{
var pRegion = await _sysRegionRep.GetByIdAsync(input.Pid);
pRegion ??= await _sysRegionRep.GetFirstAsync(u => u.Code == input.Pid.ToString());
if (pRegion == null) throw Oops.Oh(ErrorCodeEnum.D2000);
input.Pid = pRegion.Id;
var regionTreeList = await _sysRegionRep.AsQueryable().ToChildListAsync(u => u.Pid, input.Id, true);
var childIdList = regionTreeList.Select(u => u.Id).ToList();
if (childIdList.Contains(input.Pid)) throw Oops.Oh(ErrorCodeEnum.R2004);
}
if (input.Id == input.Pid) throw Oops.Oh(ErrorCodeEnum.R2001);
var isExist = await _sysRegionRep.IsAnyAsync(u => (u.Name == input.Name && u.Code == input.Code) && u.Id != sysRegion.Id);
if (isExist) throw Oops.Oh(ErrorCodeEnum.R2002);
//// 父Id不能为自己的子节点
//var regionTreeList = await _sysRegionRep.AsQueryable().ToChildListAsync(u => u.Pid, input.Id, true);
//var childIdList = regionTreeList.Select(u => u.Id).ToList();
//if (childIdList.Contains(input.Pid))
// throw Oops.Oh(ErrorCodeEnum.R2001);
await _sysRegionRep.AsUpdateable(input.Adapt()).IgnoreColumns(true).ExecuteCommandAsync();
}
///
/// 删除行政区划 🔖
///
///
///
[ApiDescriptionSettings(Name = "Delete"), HttpPost]
[DisplayName("删除行政区划")]
public async Task DeleteRegion(DeleteRegionInput input)
{
var regionTreeList = await _sysRegionRep.AsQueryable().ToChildListAsync(u => u.Pid, input.Id, true);
var regionIdList = regionTreeList.Select(u => u.Id).ToList();
await _sysRegionRep.DeleteAsync(u => regionIdList.Contains(u.Id));
}
///
/// 同步行政区划(民政部) 🔖
///
///
[DisplayName("同步行政区划(民政部)")]
public async Task SyncRegionMzb()
{
var syncLevel = await _sysConfigService.GetConfigValueByCode(ConfigConst.SysRegionSyncLevel);
if (syncLevel is < 1 or > 5) syncLevel = 3; // 默认区县级
var httpRemoteService = App.GetRequiredService();
var html = await httpRemoteService.GetAsStringAsync("http://xzqh.mca.gov.cn/map");
var municipalityList = new List { "北京", "天津", "上海", "重庆" };
var provList = Regex.Match(html, @"(?<=var json = )(\[\{.*?\}\])(?=;)").Value.ToJsonEntity>>();
foreach (var dict1 in provList)
{
var list = new List();
var provName = dict1.GetValueOrDefault("shengji");
var province = new SysRegion
{
Id = YitIdHelper.NextId(),
Name = Regex.Replace(provName, "[((].*?[))]", ""),
Code = dict1.GetValueOrDefault("quHuaDaiMa"),
CityCode = dict1.GetValueOrDefault("quhao"),
Level = 1,
Pid = 0,
};
if (municipalityList.Any(m => province.Name.StartsWith(m))) province.Name += "(省)";
list.Add(province);
if (syncLevel <= 1) continue;
var prefList = await GetSelectList(provName);
foreach (var dict2 in prefList)
{
var prefName = dict2.GetValueOrDefault("diji");
var city = new SysRegion
{
Id = YitIdHelper.NextId(),
Code = dict2.GetValueOrDefault("quHuaDaiMa"),
CityCode = dict2.GetValueOrDefault("quhao"),
Pid = province.Id,
Name = prefName,
Level = 2
};
if (municipalityList.Any(m => city.Name.StartsWith(m))) city.Name += "(地)";
list.Add(city);
if (syncLevel <= 2) continue;
var countyList = await GetSelectList(provName, prefName);
foreach (var dict3 in countyList)
{
var countyName = dict3.GetValueOrDefault("xianji");
var county = new SysRegion
{
Id = YitIdHelper.NextId(),
Code = dict3.GetValueOrDefault("quHuaDaiMa"),
CityCode = dict3.GetValueOrDefault("quhao"),
Name = countyName,
Pid = city.Id,
Level = 3
};
list.Add(county);
}
}
await _sysRegionRep.AsDeleteable().ExecuteCommandAsync();
await _sysRegionRep.Context.Fastest().BulkCopyAsync(list);
}
// 获取选择数据
async Task>> GetSelectList(string prov, string prefecture = null)
{
var json = await httpRemoteService.PostAsStringAsync("http://xzqh.mca.gov.cn/selectJson", builder => builder.SetJsonContent(new
{
shengji = prov,
diji = prefecture,
}));
return json.ToJsonEntity>>();
}
}
///
/// 同步行政区划(高德) 🔖
///
///
///
[DisplayName("同步行政区划(高德)")]
public async Task SyncRegionGD(string key)
{
if (string.IsNullOrWhiteSpace(key) || key.Length < 30)
throw Oops.Oh("请正确输入高德地图开发者 Key 值");
var syncLevel = await _sysConfigService.GetConfigValueByCode(ConfigConst.SysRegionSyncLevel);
if (syncLevel is < 1 or > 5) syncLevel = 3; // 默认区县级
var httpRemoteService = App.GetRequiredService();
var res = await httpRemoteService.GetAsync($"https://restapi.amap.com/v3/config/district?subdistrict={syncLevel}&key={key}");
if (!res.IsSuccessStatusCode) return;
var gdResponse = JSON.Deserialize>>(res.Content.ReadAsStringAsync().Result);
if (gdResponse.info != "OK" || gdResponse.districts == null || gdResponse.districts.Count < 1) return;
var regionList = new List();
foreach (var item in gdResponse.districts)
{
GetChildren(regionList, item.districts, 1, 0); // 排除一级目录(国家)
}
await _sysRegionRep.AsDeleteable().ExecuteCommandAsync();
await _sysRegionRep.Context.Fastest().BulkCopyAsync(regionList);
}
private void GetChildren(List regionList, List responses, int level, long pid)
{
foreach (var region in responses)
{
var sysRegion = new SysRegion { Id = YitIdHelper.NextId(), Pid = pid, Name = region.name, Code = region.adcode, CityCode = region.adcode, Level = level };
regionList.Add(sysRegion);
if (region.districts.Count > 0)
GetChildren(regionList, region.districts, level++, sysRegion.Id);
}
}
///
/// 同步行政区划数据(国家地名信息库,最多支持2级深度) 🔖
///
///
[DisplayName("同步行政区划数据(国家地名信息库)")]
public async Task SyncRegionMca(long code)
{
var url = $"https://dmfw.mca.gov.cn/9095/xzqh/getList?code={code}&maxLevel=4";
var httpClient = new HttpClient();
var regionLevel0 = await httpClient.GetAsync(url);
if (regionLevel0 == null) return 0;
var areaList = new List();
if (regionLevel0.Code != "00" && regionLevel0.Level > 0 && !string.IsNullOrEmpty(regionLevel0.Name))
{
areaList.Add(new SysRegion
{
Id = Convert.ToInt64(regionLevel0.Code),
Pid = 0,
Code = regionLevel0.Code,
Name = regionLevel0.Name,
Type = regionLevel0.Type,
Level = regionLevel0.Level,
});
}
foreach (var regionLevel1 in regionLevel0.Children)
{
var region1 = new SysRegion
{
Id = Convert.ToInt64(regionLevel1.Code),
Pid = Convert.ToInt64(regionLevel0.Code),
Code = regionLevel1.Code,
Name = regionLevel1.Name,
Type = regionLevel1.Type,
Level = regionLevel1.Level,
};
if (areaList.Any(u => u.Id == region1.Id))
Console.WriteLine($"1 级:{region1.Id} - {region1.Name} 已存在");
else
areaList.Add(region1);
foreach (var regionLevel2 in regionLevel1.Children)
{
var region2 = new SysRegion
{
Id = Convert.ToInt64(regionLevel2.Code),
Pid = Convert.ToInt64(regionLevel1.Code),
Code = regionLevel2.Code,
Name = regionLevel2.Name,
Type = regionLevel2.Type,
Level = regionLevel2.Level,
};
if (areaList.Any(u => u.Id == region2.Id))
Console.WriteLine($"2 级:{region2.Id} - {region2.Name} 已存在");
else
areaList.Add(region2);
foreach (var regionLevel3 in regionLevel2.Children)
{
var region3 = new SysRegion
{
Id = Convert.ToInt64(regionLevel3.Code),
Pid = Convert.ToInt64(regionLevel2.Code),
Code = regionLevel3.Code,
Name = regionLevel3.Name,
Type = regionLevel3.Type,
Level = regionLevel3.Level,
};
if (areaList.Any(u => u.Id == region3.Id))
Console.WriteLine($"3 级:{region3.Id} - {region3.Name}");
else
areaList.Add(region3);
foreach (var regionLevel4 in regionLevel3.Children)
{
var region4 = new SysRegion
{
Id = Convert.ToInt64(regionLevel4.Code),
Pid = Convert.ToInt64(regionLevel3.Code),
Code = regionLevel4.Code,
Name = regionLevel4.Name,
Type = regionLevel4.Type,
Level = regionLevel4.Level,
};
areaList.Add(region4);
}
}
}
}
if (code == 0)
await _sysRegionRep.AsDeleteable().ExecuteCommandAsync();
else if (await _sysRegionRep.IsAnyAsync(u => u.Id == code)) // 如果存在指定行政区划则删除
await DeleteRegion(new DeleteRegionInput { Id = code });
return await _sysRegionRep.AsInsertable(areaList).ExecuteCommandAsync();
}
///
/// 同步行政区划数据(天地图行政区划) 🔖
///
///
[DisplayName("同步行政区划数据(天地图行政区划)")]
public async Task SyncRegionTianditu(TiandituInput input)
{
// 接口说明及地址:http://lbs.tianditu.gov.cn/server/administrative2.html
var url = $"http://api.tianditu.gov.cn/v2/administrative?keyword={input.Keyword}&childLevel={input.ChildLevel}&extensions={input.Extensions}&tk={input.Tk}";
var httpClient = new HttpClient();
var res = await httpClient.GetAsync(url);
if (res == null) return 0;
var parent = res.District[0];
var areaList = new List()
{
new SysRegion
{
Id = Convert.ToInt64(parent.Gb),
Pid = 0,
Code = parent.Gb,
Name = parent.Name,
Level = parent.Level,
Longitude = parent.Center.Lng,
Latitude = parent.Center.Lat
}
};
foreach (var item in parent.Children)
{
var region = new SysRegion
{
Id = Convert.ToInt64(item.Gb),
Pid = Convert.ToInt64(parent.Gb),
Code = item.Gb,
Name = item.Name,
Level = item.Level,
Longitude = item.Center.Lng,
Latitude = item.Center.Lat
};
areaList.Add(region);
foreach (var child in item.Children)
{
areaList.Add(new SysRegion
{
Id = Convert.ToInt64(child.Gb),
Pid = region.Id,
Code = child.Gb,
Name = child.Name,
Level = child.Level,
Longitude = child.Center.Lng,
Latitude = child.Center.Lat
});
}
}
// 若存在指定行政区划则删除
if (await _sysRegionRep.IsAnyAsync(u => u.Name.Contains(input.Keyword) || u.Id.ToString() == input.Keyword))
{
var region = await _sysRegionRep.GetFirstAsync(u => u.Name.Contains(input.Keyword) || u.Id.ToString() == input.Keyword);
await DeleteRegion(new DeleteRegionInput { Id = region.Id });
}
return await _sysRegionRep.AsInsertable(areaList).ExecuteCommandAsync();
}
///
/// 生成组织架构 🔖
///
///
///
[DisplayName("生成组织架构")]
public async Task GenOrg(GenOrgInput input)
{
var region = await _sysRegionRep.GetByIdAsync(input.Id);
var orgRep = _sysRegionRep.ChangeRepository>();
if (!await orgRep.IsAnyAsync(u => u.Id == region.Pid))
region.Pid = 0;
var regionList = await GetRegionListByLevel(region, input.Level);
var orgList = regionList.Adapt>();
await orgRep.InsertOrUpdateAsync(orgList);
}
///
/// 根据层级获取行政区划数据
///
///
///
///
private async Task> GetRegionListByLevel(SysRegion region, int level)
{
var regionList = new List();
if (level > 5) level = 5;
regionList.Add(region);
if (level == 1) return regionList;
var regionList2 = await GetList(new RegionInput { Id = region.Id });
regionList.AddRange(regionList2);
if (level == 2) return regionList;
foreach (var item in regionList2)
{
var regionList3 = await GetList(new RegionInput { Id = item.Id });
if (regionList3 == null) continue;
regionList.AddRange(regionList3);
if (level == 3) continue;
foreach (var item3 in regionList3)
{
var regionList4 = await GetList(new RegionInput { Id = item3.Id });
if (regionList4 == null) continue;
regionList.AddRange(regionList4);
if (level == 4) continue;
foreach (var item4 in regionList4)
{
var regionList5 = await GetList(new RegionInput { Id = item4.Id });
if (regionList5 == null) continue;
regionList.AddRange(regionList5);
}
}
}
return regionList;
}
}