// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 // // 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 // // 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! using IPTools.Core; using Magicodes.ExporterAndImporter.Core.Models; using System.Xml; using System.Xml.Linq; using System.Xml.Serialization; namespace Admin.NET.Core; /// /// 通用工具类 /// public static class CommonUtil { private static readonly SysCacheService SysCacheService = App.GetRequiredService(); private static readonly SysFileService SysFileService = App.GetRequiredService(); private static readonly SqlSugarRepository SysDictDataRep = App.GetRequiredService>(); /// /// 根据字符串获取固定整型哈希值 /// /// /// /// public static long GetFixedHashCode(string str, long startNumber = 0) { if (string.IsNullOrWhiteSpace(str)) return 0; unchecked { int hash1 = (5381 << 16) + 5381; int hash2 = hash1; for (int i = 0; i < str.Length; i += 2) { hash1 = ((hash1 << 5) + hash1) ^ str[i]; if (i == str.Length - 1) break; hash2 = ((hash2 << 5) + hash2) ^ str[i + 1]; } return startNumber + Math.Abs(hash1 + (hash2 * 1566083941)); } } /// /// 生成百分数 /// /// /// /// public static string ExecPercent(decimal passCount, decimal allCount) { string res = ""; if (allCount > 0) { var value = (double)Math.Round(passCount / allCount * 100, 1); if (value < 0) res = Math.Round(value + 5 / Math.Pow(10, 0 + 1), 0, MidpointRounding.AwayFromZero).ToString(); else res = Math.Round(value, 0, MidpointRounding.AwayFromZero).ToString(); } if (res == "") res = "0"; return res + "%"; } /// /// 获取服务地址 /// /// public static string GetLocalhost() { string result = $"{App.HttpContext.Request.Scheme}://{App.HttpContext.Request.Host.Value}"; // 代理模式:获取真正的本机地址 // X-Original-Host=原始请求 // X-Forwarded-Server=从哪里转发过来 if (App.HttpContext.Request.Headers.ContainsKey("Origin")) // 配置成完整的路径如(结尾不要带"/"),比如 https://www.abc.com result = $"{App.HttpContext.Request.Headers["Origin"]}"; else if (App.HttpContext.Request.Headers.ContainsKey("X-Original")) // 配置成完整的路径如(结尾不要带"/"),比如 https://www.abc.com result = $"{App.HttpContext.Request.Headers["X-Original"]}"; else if (App.HttpContext.Request.Headers.ContainsKey("X-Original-Host")) result = $"{App.HttpContext.Request.Scheme}://{App.HttpContext.Request.Headers["X-Original-Host"]}"; return result + (string.IsNullOrWhiteSpace(App.Settings.VirtualPath) ? "" : App.Settings.VirtualPath); } /// /// 对象序列化XML /// /// /// /// public static string SerializeObjectToXml(T obj) { if (obj == null) return string.Empty; var xs = new XmlSerializer(obj.GetType()); var stream = new MemoryStream(); var setting = new XmlWriterSettings { Encoding = new UTF8Encoding(false), // 不包含BOM Indent = true // 设置格式化缩进 }; using (var writer = XmlWriter.Create(stream, setting)) { var ns = new XmlSerializerNamespaces(); ns.Add("", ""); // 去除默认命名空间 xs.Serialize(writer, obj, ns); } return Encoding.UTF8.GetString(stream.ToArray()); } /// /// 字符串转XML格式 /// /// /// public static XElement SerializeStringToXml(string xmlStr) { try { return XElement.Parse(xmlStr); } catch { return null; } } /// /// 导出模板Excel /// /// public static async Task ExportExcelTemplate(string fileName = null) where T : class, new() { IImporter importer = new ExcelImporter(); var res = await importer.GenerateTemplateBytes(); return new FileContentResult(res, "application/octet-stream") { FileDownloadName = $"{(string.IsNullOrEmpty(fileName) ? typeof(T).Name : fileName)}.xlsx" }; } /// /// 导出数据excel /// /// public static async Task ExportExcelData(ICollection data, string fileName = null) where T : class, new() { var export = new ExcelExporter(); var res = await export.ExportAsByteArray(data); return new FileContentResult(res, "application/octet-stream") { FileDownloadName = $"{(string.IsNullOrEmpty(fileName) ? typeof(T).Name : fileName)}.xlsx" }; } /// /// 导出数据excel,包括字典转换 /// /// public static async Task ExportExcelData(ISugarQueryable query, Func action = null) where TSource : class, new() where TTarget : class, new() { var propMappings = GetExportPropertMap(); var data = query.ToList(); //相同属性复制值,字典值转换 var result = new List(); foreach (var item in data) { var newData = new TTarget(); foreach (var dict in propMappings) { var targetProp = dict.Value.Item3; if (targetProp != null) { var propertyInfo = dict.Value.Item2; var sourceVal = propertyInfo.GetValue(item, null); if (sourceVal == null) { continue; } var map = dict.Value.Item1; if (map != null && map.TryGetValue(sourceVal, out string newVal1)) { targetProp.SetValue(newData, newVal1); } else { if (targetProp.PropertyType.FullName == propertyInfo.PropertyType.FullName) { targetProp.SetValue(newData, sourceVal); } else { var newVal = sourceVal.ToString().ParseTo(targetProp.PropertyType); targetProp.SetValue(newData, newVal); } } } if (action != null) { newData = action(item, newData); } } result.Add(newData); } var export = new ExcelExporter(); var res = await export.ExportAsByteArray(result); return new FileContentResult(res, "application/octet-stream") { FileDownloadName = typeof(TTarget).Name + ".xlsx" }; } /// /// 导入数据Excel /// /// /// public static async Task> ImportExcelData([Required] IFormFile file) where T : class, new() { IImporter importer = new ExcelImporter(); var res = await importer.Import(file.OpenReadStream()); var message = string.Empty; if (!res.HasError) return res.Data; if (res.Exception != null) message += $"\r\n{res.Exception.Message}"; foreach (DataRowErrorInfo drErrorInfo in res.RowErrors) { int rowNum = drErrorInfo.RowIndex; foreach (var item in drErrorInfo.FieldErrors) message += $"\r\n{item.Key}:{item.Value}(文件第{drErrorInfo.RowIndex}行)"; } message += "\r\n字段缺失:" + string.Join(",", res.TemplateErrors.Select(m => m.RequireColumnName).ToList()); throw Oops.Oh("导入异常:" + message); } /// /// 导入Excel数据并错误标记 /// /// /// /// /// public static async Task> ImportExcelData([Required] IFormFile file, Func, ImportResult> importResultCallback = null) where T : class, new() { IImporter importer = new ExcelImporter(); var resultStream = new MemoryStream(); var res = await importer.Import(file.OpenReadStream(), resultStream, importResultCallback); resultStream.Seek(0, SeekOrigin.Begin); var userId = App.User?.FindFirst(ClaimConst.UserId)?.Value; SysCacheService.Remove(CacheConst.KeyExcelTemp + userId); SysCacheService.Set(CacheConst.KeyExcelTemp + userId, resultStream, TimeSpan.FromMinutes(5)); var message = string.Empty; if (!res.HasError) return res.Data; if (res.Exception != null) message += $"\r\n{res.Exception.Message}"; foreach (DataRowErrorInfo drErrorInfo in res.RowErrors) { message = drErrorInfo.FieldErrors.Aggregate(message, (current, item) => current + $"\r\n{item.Key}:{item.Value}(文件第{drErrorInfo.RowIndex}行)"); } if (res.TemplateErrors.Count > 0) message += "\r\n字段缺失:" + string.Join(",", res.TemplateErrors.Select(m => m.RequireColumnName).ToList()); if (message.Length > 200) message = message.Substring(0, 200) + "...\r\n异常过多,建议下载错误标记文件查看详细错误信息并重新导入。"; throw Oops.Oh("导入异常:" + message); } /// /// 导入数据Excel /// /// /// /// public static async Task> ImportExcelDataAsync([Required] IFormFile file) where T : class, new() { var newFile = await SysFileService.UploadFile(new UploadFileInput { File = file }); var filePath = Path.Combine(App.WebHostEnvironment.WebRootPath, newFile.FilePath!, newFile.Id + newFile.Suffix); IImporter importer = new ExcelImporter(); var res = await importer.Import(filePath); // 删除文件 _ = SysFileService.DeleteFile(new DeleteFileInput { Id = newFile.Id }); if (res == null) throw Oops.Oh("导入数据为空"); if (res.Exception != null) throw Oops.Oh("导入异常:" + res.Exception); if (res.TemplateErrors?.Count > 0) throw Oops.Oh("模板异常:" + res.TemplateErrors.Select(x => $"[{x.RequireColumnName}]{x.Message}").Join("\n")); return res.Data.ToList(); } // 例:List ls = CommonUtil.ParseList(importResult.Data); /// /// 对象转换 含字典转换 /// /// /// /// /// /// public static List ParseList(IEnumerable data, Func action = null) where TTarget : new() { var propMappings = GetImportPropertMap(); // 相同属性复制值,字典值转换 var result = new List(); foreach (var item in data) { var newData = new TTarget(); foreach (var dict in propMappings) { var targeProp = dict.Value.Item3; if (targeProp != null) { var propertyInfo = dict.Value.Item2; var sourceVal = propertyInfo.GetValue(item, null); if (sourceVal == null) continue; var map = dict.Value.Item1; if (map != null && map.ContainsKey(sourceVal.ToString())) { var newVal = map[sourceVal.ToString()]; targeProp.SetValue(newData, newVal); } else { if (targeProp.PropertyType.FullName == propertyInfo.PropertyType.FullName) { targeProp.SetValue(newData, sourceVal); } else { var newVal = sourceVal.ToString().ParseTo(targeProp.PropertyType); targeProp.SetValue(newData, newVal); } } } } if (action != null) newData = action(item, newData); if (newData != null) result.Add(newData); } return result; } /// /// 获取导入属性映射 /// /// /// /// 整理导入对象的 属性名称, 字典数据,原属性信息,目标属性信息 private static Dictionary, PropertyInfo, PropertyInfo>> GetImportPropertMap() where TTarget : new() { // 整理导入对象的属性名称,<字典数据,原属性信息,目标属性信息> var propMappings = new Dictionary, PropertyInfo, PropertyInfo>>(); var dictService = App.GetRequiredService>(); var tSourceProps = typeof(TSource).GetProperties().ToList(); var tTargetProps = typeof(TTarget).GetProperties().ToDictionary(u => u.Name); foreach (var propertyInfo in tSourceProps) { var attrs = propertyInfo.GetCustomAttribute(); if (attrs != null && !string.IsNullOrWhiteSpace(attrs.TypeCode)) { var targetProp = tTargetProps[attrs.TargetPropName]; var mappingValues = dictService.Context.Queryable((u, a) => new JoinQueryInfos(JoinType.Inner, u.Id == a.DictTypeId)) .Where(u => u.Code == attrs.TypeCode) .Where((u, a) => u.Status == StatusEnum.Enable && a.Status == StatusEnum.Enable) .Select((u, a) => new { Label = a.Label, Value = a.Value }).ToList() .ToDictionary(u => u.Label, u => u.Value.ParseTo(targetProp.PropertyType)); propMappings.Add(propertyInfo.Name, new Tuple, PropertyInfo, PropertyInfo>(mappingValues, propertyInfo, targetProp)); } else { propMappings.Add(propertyInfo.Name, new Tuple, PropertyInfo, PropertyInfo>( null, propertyInfo, tTargetProps.ContainsKey(propertyInfo.Name) ? tTargetProps[propertyInfo.Name] : null)); } } return propMappings; } /// /// 获取导出属性映射 /// /// /// /// 整理导入对象的 属性名称, 字典数据,原属性信息,目标属性信息 private static Dictionary, PropertyInfo, PropertyInfo>> GetExportPropertMap() where TTarget : new() { // 整理导入对象的属性名称,<字典数据,原属性信息,目标属性信息> var propMappings = new Dictionary, PropertyInfo, PropertyInfo>>(); var targetProps = typeof(TTarget).GetProperties().ToList(); var sourceProps = typeof(TSource).GetProperties().ToDictionary(u => u.Name); foreach (var propertyInfo in targetProps) { var attrs = propertyInfo.GetCustomAttribute(); if (attrs != null && !string.IsNullOrWhiteSpace(attrs.TypeCode)) { var targetProp = sourceProps[attrs.TargetPropName]; var mappingValues = SysDictDataRep.Context.Queryable((u, a) => new JoinQueryInfos(JoinType.Inner, u.Id == a.DictTypeId)) .Where(u => u.Code == attrs.TypeCode) .Where((u, a) => u.Status == StatusEnum.Enable && a.Status == StatusEnum.Enable) .Select((u, a) => new { a.Label, a.Value }).ToList() .ToDictionary(u => u.Value.ParseTo(targetProp.PropertyType), u => u.Label); propMappings.Add(propertyInfo.Name, new Tuple, PropertyInfo, PropertyInfo>(mappingValues, targetProp, propertyInfo)); } else { propMappings.Add(propertyInfo.Name, new Tuple, PropertyInfo, PropertyInfo>( null, sourceProps.TryGetValue(propertyInfo.Name, out PropertyInfo prop) ? prop : null, propertyInfo)); } } return propMappings; } /// /// 获取属性映射 /// /// /// 整理导入对象的 属性名称, 字典数据,原属性信息,目标属性信息 private static Dictionary> GetExportDictMap() where TTarget : new() { // 整理导入对象的属性名称,目标属性名,字典Code var propMappings = new Dictionary>(); var tTargetProps = typeof(TTarget).GetProperties(); foreach (var propertyInfo in tTargetProps) { var attrs = propertyInfo.GetCustomAttribute(); if (attrs != null && !string.IsNullOrWhiteSpace(attrs.TypeCode)) { propMappings.Add(propertyInfo.Name, new Tuple(attrs.TargetPropName, attrs.TypeCode)); } } return propMappings; } /// /// 解析IP地址 /// /// /// public static (string ipLocation, double? longitude, double? latitude) GetIpAddress(string ip) { try { var ipInfo = IpTool.SearchWithI18N(ip); // 国际化查询,默认中文 中文zh-CN、英文en var addressList = new List() { ipInfo.Country, ipInfo.Province, ipInfo.City, ipInfo.NetworkOperator }; return (string.Join(" ", addressList.Where(u => u != "0" && !string.IsNullOrWhiteSpace(u)).ToList()), ipInfo.Longitude, ipInfo.Latitude); // 去掉0及空并用空格连接 } catch { // 不做处理 } return ("未知", 0, 0); } /// /// 获取客户端设备信息(操作系统+浏览器) /// /// /// public static string GetClientDeviceInfo(string userAgent) { try { if (userAgent != null) { var client = Parser.GetDefault().Parse(userAgent); if (client.Device.IsSpider) return "爬虫"; return $"{client.OS.Family} {client.OS.Major} {client.OS.Minor}" + $"|{client.UA.Family} {client.UA.Major}.{client.UA.Minor} / {client.Device.Family}"; } } catch { } return "未知"; } }