394 lines
15 KiB
C#
394 lines
15 KiB
C#
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||
//
|
||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||
//
|
||
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
|
||
|
||
using AngleSharp;
|
||
using AngleSharp.Html.Dom;
|
||
using NewLife.Remoting;
|
||
|
||
namespace Admin.NET.Core.Service;
|
||
|
||
/// <summary>
|
||
/// 系统行政区域服务 🧩
|
||
/// </summary>
|
||
[ApiDescriptionSettings(Order = 310, Description = "行政区域")]
|
||
public class SysRegionService : IDynamicApiController, ITransient
|
||
{
|
||
private readonly SqlSugarRepository<SysRegion> _sysRegionRep;
|
||
private readonly SysConfigService _sysConfigService;
|
||
|
||
// Url地址-国家统计局行政区域2023年
|
||
private readonly string _url = "http://www.stats.gov.cn/sj/tjbz/tjyqhdmhcxhfdm/2023/index.html";
|
||
|
||
public SysRegionService(SqlSugarRepository<SysRegion> sysRegionRep, SysConfigService sysConfigService)
|
||
{
|
||
_sysRegionRep = sysRegionRep;
|
||
_sysConfigService = sysConfigService;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取行政区域分页列表 🔖
|
||
/// </summary>
|
||
/// <param name="input"></param>
|
||
/// <returns></returns>
|
||
[DisplayName("获取行政区域分页列表")]
|
||
public async Task<SqlSugarPagedList<SysRegion>> 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);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取行政区域列表 🔖
|
||
/// </summary>
|
||
/// <param name="input"></param>
|
||
/// <returns></returns>
|
||
[DisplayName("获取行政区域列表")]
|
||
public async Task<List<SysRegion>> GetList([FromQuery] RegionInput input)
|
||
{
|
||
return await _sysRegionRep.GetListAsync(u => u.Pid == input.Id);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 增加行政区域 🔖
|
||
/// </summary>
|
||
/// <param name="input"></param>
|
||
/// <returns></returns>
|
||
[ApiDescriptionSettings(Name = "Add"), HttpPost]
|
||
[DisplayName("增加行政区域")]
|
||
public async Task<long> 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<SysRegion>();
|
||
var newRegion = await _sysRegionRep.AsInsertable(sysRegion).ExecuteReturnEntityAsync();
|
||
return newRegion.Id;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 更新行政区域 🔖
|
||
/// </summary>
|
||
/// <param name="input"></param>
|
||
/// <returns></returns>
|
||
[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);
|
||
if (sysRegion == null)
|
||
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<SysRegion>()).IgnoreColumns(true).ExecuteCommandAsync();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 删除行政区域 🔖
|
||
/// </summary>
|
||
/// <param name="input"></param>
|
||
/// <returns></returns>
|
||
[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));
|
||
}
|
||
|
||
/// <summary>
|
||
/// 同步行政区域(国家统计局) 🔖
|
||
/// </summary>
|
||
/// <returns></returns>
|
||
[DisplayName("同步行政区域(国家统计局)")]
|
||
public async Task SyncRegionStats(SyncInput input)
|
||
{
|
||
var syncLevel = await _sysConfigService.GetConfigValueByCode<int>(ConfigConst.SysRegionSyncLevel);
|
||
if (syncLevel < 1 || syncLevel > 5)
|
||
syncLevel = 3;//默认区县级
|
||
|
||
var context = BrowsingContext.New(AngleSharp.Configuration.Default.WithDefaultLoader());
|
||
var dom = await context.OpenAsync(_url);
|
||
|
||
// 省级
|
||
var itemList = dom.QuerySelectorAll("table.provincetable tr.provincetr td a");
|
||
if (itemList.Length == 0)
|
||
throw Oops.Oh(ErrorCodeEnum.R2005);
|
||
|
||
await _sysRegionRep.DeleteAsync(u => u.Id > 0);
|
||
|
||
foreach (IHtmlAnchorElement item in itemList)
|
||
{
|
||
if (!string.IsNullOrWhiteSpace(input.Province) && item.TextContent != input.Province) continue;
|
||
var list = new List<SysRegion>();
|
||
|
||
var region = new SysRegion
|
||
{
|
||
//Id = YitIdHelper.NextId(),
|
||
Pid = 0,
|
||
Name = item.TextContent,
|
||
Remark = item.Href,
|
||
Level = 1,
|
||
};
|
||
list.Add(region);
|
||
|
||
////// 市级
|
||
if (string.IsNullOrEmpty(item.Href)) continue;
|
||
var dom1 = await context.OpenAsync(item.Href);
|
||
var itemList1 = dom1.QuerySelectorAll("table.citytable tr.citytr td a");
|
||
for (var i1 = 0; i1 < itemList1.Length; i1 += 2)
|
||
{
|
||
var item1 = (IHtmlAnchorElement)itemList1[i1 + 1];
|
||
if (!string.IsNullOrWhiteSpace(input.City) && item1.TextContent != input.City) continue;
|
||
|
||
string cityCode = itemList1[i1].TextContent;
|
||
// 若URL中查询的一级行政区域缺少Code则通过二级区域填充
|
||
if (list.Count == 1 && !string.IsNullOrEmpty(cityCode))
|
||
{
|
||
region.Code = cityCode.Substring(0, 2).PadRight(cityCode.Length, '0');
|
||
region.Id = long.Parse(region.Code);
|
||
}
|
||
// 同步层级为“1-省级”退出
|
||
if (syncLevel < 2)
|
||
break;
|
||
|
||
var region1 = new SysRegion
|
||
{
|
||
Id = long.Parse(cityCode),
|
||
Pid = region.Id,
|
||
Name = item1.TextContent,
|
||
Code = itemList1[i1].TextContent,
|
||
Remark = item1.Href,
|
||
Level = 2,
|
||
};
|
||
|
||
list.Add(region1);
|
||
|
||
////// 区县级
|
||
if (string.IsNullOrEmpty(item1.Href) || syncLevel <= 2) continue;
|
||
var dom2 = await context.OpenAsync(item1.Href);
|
||
var itemList2 = dom2.QuerySelectorAll("table.countytable tr.countytr td a");
|
||
for (var i2 = 0; i2 < itemList2.Length; i2 += 2)
|
||
{
|
||
var item2 = (IHtmlAnchorElement)itemList2[i2 + 1];
|
||
var region2 = new SysRegion
|
||
{
|
||
Id = long.Parse(itemList2[i2].TextContent),
|
||
Pid = region1.Id,
|
||
Name = item2.TextContent,
|
||
Code = itemList2[i2].TextContent,
|
||
Remark = item2.Href,
|
||
Level = 3,
|
||
};
|
||
list.Add(region2);
|
||
|
||
////// 街道级
|
||
if (string.IsNullOrEmpty(item2.Href) || syncLevel <= 3) continue;
|
||
var dom3 = await context.OpenAsync(item2.Href);
|
||
var itemList3 = dom3.QuerySelectorAll("table.towntable tr.towntr td a");
|
||
for (var i3 = 0; i3 < itemList3.Length; i3 += 2)
|
||
{
|
||
var item3 = (IHtmlAnchorElement)itemList3[i3 + 1];
|
||
var region3 = new SysRegion
|
||
{
|
||
Id = long.Parse(itemList3[i3].TextContent),
|
||
Pid = region2.Id,
|
||
Name = item3.TextContent,
|
||
Code = itemList3[i3].TextContent,
|
||
Remark = item3.Href,
|
||
Level = 4,
|
||
};
|
||
list.Add(region3);
|
||
|
||
////// 村级
|
||
if (string.IsNullOrEmpty(item3.Href) || syncLevel <= 4) continue;
|
||
var dom4 = await context.OpenAsync(item3.Href);
|
||
var itemList4 = dom4.QuerySelectorAll("table.villagetable tr.villagetr td");
|
||
for (var i4 = 0; i4 < itemList4.Length; i4 += 3)
|
||
{
|
||
list.Add(new SysRegion
|
||
{
|
||
Id = long.Parse(itemList4[i4].TextContent),
|
||
Pid = region3.Id,
|
||
Name = itemList4[i4 + 2].TextContent,
|
||
Code = itemList4[i4].TextContent,
|
||
CityCode = itemList4[i4 + 1].TextContent,
|
||
Level = 5,
|
||
});
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
//按省份同步快速写入提升同步效率,全部一次性写入容易出现从统计局获取数据失败
|
||
_sysRegionRep.Context.Fastest<SysRegion>().BulkCopy(list);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 同步行政区划数据(国家地名信息库,最多支持2级深度)
|
||
/// </summary>
|
||
/// <returns></returns>
|
||
[DisplayName("同步行政区划数据(国家地名信息库)")]
|
||
public async Task<int> SyncRegionMca(long code)
|
||
{
|
||
var url = $"https://dmfw.mca.gov.cn/9095/xzqh/getList?code={code}&maxLevel=2";
|
||
|
||
var httpClient = new HttpClient();
|
||
var res = await httpClient.GetAsync<SysRegion>(url);
|
||
if (res == null) return 0;
|
||
|
||
var areaList = new List<SysRegion>()
|
||
{
|
||
new SysRegion
|
||
{
|
||
Id = Convert.ToInt64(res.Code),
|
||
Pid = 0,
|
||
Code = res.Code,
|
||
Name = res.Name,
|
||
Type = res.Type,
|
||
Level = res.Level,
|
||
}
|
||
};
|
||
|
||
foreach (var item in res.Children)
|
||
{
|
||
var region = new SysRegion
|
||
{
|
||
Id = Convert.ToInt64(item.Code),
|
||
Pid = Convert.ToInt64(res.Code),
|
||
Code = item.Code,
|
||
Name = item.Name,
|
||
Type = item.Type,
|
||
Level = item.Level,
|
||
};
|
||
areaList.Add(region);
|
||
|
||
foreach (var child in item.Children)
|
||
{
|
||
areaList.Add(new SysRegion
|
||
{
|
||
Id = Convert.ToInt64(child.Code),
|
||
Pid = region.Id,
|
||
Code = child.Code,
|
||
Name = child.Name,
|
||
Type = child.Type,
|
||
Level = child.Level,
|
||
});
|
||
}
|
||
}
|
||
|
||
await _sysRegionRep.AsDeleteable().ExecuteCommandAsync();
|
||
return await _sysRegionRep.AsInsertable(areaList).ExecuteCommandAsync();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成组织架构
|
||
/// </summary>
|
||
/// <param name="input"></param>
|
||
/// <returns></returns>
|
||
[DisplayName("生成组织架构")]
|
||
public async Task GenOrg(GenOrgInput input)
|
||
{
|
||
var region = await _sysRegionRep.GetByIdAsync(input.Id);
|
||
var orgRep = _sysRegionRep.ChangeRepository<SqlSugarRepository<SysOrg>>();
|
||
if (!await orgRep.IsAnyAsync(u => u.Id == region.Pid))
|
||
region.Pid = 0;
|
||
|
||
var regionList = await GetRegionListByLevel(region, input.Level);
|
||
var orgList = regionList.Adapt<List<SysOrg>>();
|
||
await orgRep.InsertOrUpdateAsync(orgList);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 根据层级获取行政区域数据
|
||
/// </summary>
|
||
/// <param name="region"></param>
|
||
/// <param name="level"></param>
|
||
/// <returns></returns>
|
||
private async Task<List<SysRegion>> GetRegionListByLevel(SysRegion region, int level)
|
||
{
|
||
var regionList = new List<SysRegion>();
|
||
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;
|
||
}
|
||
} |