// 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; } }