752 lines
29 KiB
C#
Raw Normal View History

2025-06-06 16:00:39 +08:00
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using OfficeOpenXml;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using VOL.Core.Extensions;
using VOL.Core.Services;
using VOL.Entity.Enum;
namespace VOL.Core.Utilities
{
public static class Tool
{
public static string Upload(IFormFile file, string filePath)
{
string fullPath = filePath.MapPath(true);
//string fileName = $"{DateTime.Now.ToString("yyyyMMddHHmmssfff")}.{file.FileName.Split(".")[1]}";
//string fileName = file.FileName.Split(".")[0];
try
{
if (!Directory.Exists(fullPath)) Directory.CreateDirectory(fullPath);
if (File.Exists(file.FileName))
File.Delete(file.FileName);
using (var stream = new FileStream(fullPath + file.FileName, FileMode.Create))
{
file.CopyTo(stream);
}
}
catch (Exception ex)
{
Logger.Error($"上传文件失败:Tool.Upload,路径:{filePath},失败文件:{file},{ex.Message + ex.StackTrace}");
}
return $"{filePath}{file.FileName}";
}
//public static void BatchUpload(List<IFormFile> files, string filePath)
//{
// string fullPath = filePath.MapPath(true);
// //string fileName = $"{DateTime.Now.ToString("yyyyMMddHHmmssfff")}.{file.FileName.Split(".")[1]}";
// try
// {
// if (!Directory.Exists(fullPath)) Directory.CreateDirectory(fullPath);
// foreach (var file in files)
// {
// string fileName = file.FileName.Split(".")[0];
// if (File.Exists(fileName))
// File.Delete(fileName);
// using (var stream = new FileStream(fullPath + fileName, FileMode.Create))
// {
// file.CopyTo(stream);
// }
// }
// }
// catch (Exception ex)
// {
// Logger.Error($"上传文件失败:Tool.Upload,路径:{filePath},失败文件:{files},{ex.Message + ex.StackTrace}");
// }
// //return $"{filePath}{fileName}{file.FileName.Split(".")[1]}";
//}
public static byte[] UploadFile(IFormFile file)
{
if (file != null && file.Length > 0)
{
using (var memoryStream = new MemoryStream())
{
file.CopyTo(memoryStream);
var fileData = memoryStream.ToArray();
return fileData;
}
}
else
{
throw new Exception("未选择文件或文件大小为零。");
}
}
public static byte[] ExportToExcel<T>(Dictionary<string, List<T>> sheetDataList, List<string> fields)
{
using (var package = new ExcelPackage(new FileInfo("filename.xlsx")))
{
var sheetCount = sheetDataList.Count;
foreach (var sheet in sheetDataList)
{
var sheetData = sheetDataList[sheet.Key];
var sheetName = sheet.Key;
var worksheet = package.Workbook.Worksheets.Add(sheetName);
ConfigureWorksheet<T>(worksheet, sheetData, fields);
}
return package.GetAsByteArray();
}
}
2026-05-06 13:59:17 +08:00
2025-06-06 16:00:39 +08:00
/// <summary>
/// 通用导入Excel
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="fileStream"></param>
/// <returns></returns>
public static List<T> ConvertExcelToList<T>(Stream fileStream) where T : new()
{
var result = new List<T>();
using (var package = new ExcelPackage(fileStream))
{
2026-05-06 13:59:17 +08:00
var worksheet = package.Workbook.Worksheets[1]; // 第一个工作表
2025-06-06 16:00:39 +08:00
if (worksheet.Dimension == null)
2026-05-06 13:59:17 +08:00
throw new InvalidOperationException("工作表中没有任何数据。");
2025-06-06 16:00:39 +08:00
2026-05-06 13:59:17 +08:00
// 获取表头(第一行)
2025-06-06 16:00:39 +08:00
var headerRow = worksheet.Cells[1, 1, 1, worksheet.Dimension.End.Column];
2026-05-06 13:59:17 +08:00
var headers = headerRow.Select(cell => cell.Text?.Trim()).ToList();
2025-06-06 16:00:39 +08:00
2026-05-06 13:59:17 +08:00
// 获取实体属性与显示名称的映射
var displayNameToPropertyMap = GetDisplayNameToPropertyMap<T>();
2025-06-06 16:00:39 +08:00
2026-05-06 13:59:17 +08:00
// 从第二行开始解析数据
2025-06-06 16:00:39 +08:00
for (int row = 2; row <= worksheet.Dimension.End.Row; row++)
{
var dataObject = new T();
for (int col = 1; col <= worksheet.Dimension.End.Column; col++)
{
var header = headers[col - 1];
2026-05-06 13:59:17 +08:00
if (string.IsNullOrEmpty(header))
continue;
if (!displayNameToPropertyMap.TryGetValue(header, out var property))
continue; // 找不到对应属性,忽略该列
try
2025-06-06 16:00:39 +08:00
{
2026-05-06 13:59:17 +08:00
var cellValue = worksheet.Cells[row, col].GetValue<object>();
var propertyType = property.PropertyType;
// ----- 日期类型处理(支持 DateTime / DateTime?)-----
if (propertyType == typeof(DateTime) || propertyType == typeof(DateTime?))
2025-06-06 16:00:39 +08:00
{
2026-05-06 13:59:17 +08:00
DateTime? parsedDate = null;
2025-06-06 16:00:39 +08:00
2026-05-06 13:59:17 +08:00
// 情况1:Excel 日期存储为 OADate 数字
if (cellValue is double numericValue)
{
parsedDate = DateTime.FromOADate(numericValue);
}
// 情况2:字符串格式的日期
else if (cellValue is string dateStr && !string.IsNullOrWhiteSpace(dateStr))
2025-06-06 16:00:39 +08:00
{
2026-05-06 13:59:17 +08:00
// 支持多种常见日期格式
string[] formats = new[]
2025-06-06 16:00:39 +08:00
{
2026-05-06 13:59:17 +08:00
"yyyy/M/d", // 2025/4/28
"yyyy/MM/dd", // 2025/04/28
"M/d/yyyy", // 4/28/2025
"yyyy-MM-dd", // 2025-04-28
"dd/MM/yyyy", // 28/04/2025
"yyyyMMdd", // 20250428
"d/M/yyyy" // 28/4/2025
};
if (DateTime.TryParseExact(dateStr, formats, CultureInfo.InvariantCulture,
DateTimeStyles.None, out DateTime dt))
2025-06-06 16:00:39 +08:00
{
2026-05-06 13:59:17 +08:00
parsedDate = dt;
2025-06-06 16:00:39 +08:00
}
2026-05-06 13:59:17 +08:00
else if (DateTime.TryParse(dateStr, out dt)) // 兜底尝试系统解析
2025-06-06 16:00:39 +08:00
{
2026-05-06 13:59:17 +08:00
parsedDate = dt;
2025-06-06 16:00:39 +08:00
}
else
{
2026-05-06 13:59:17 +08:00
throw new InvalidCastException($"无法将字符串 '{dateStr}' 转换为有效的日期。");
2025-06-06 16:00:39 +08:00
}
}
2026-05-06 13:59:17 +08:00
// 情况3:已经是 DateTime 实例(极少情况)
else if (cellValue is DateTime dtValue)
{
parsedDate = dtValue;
}
// 根据属性是否可空赋值
if (propertyType == typeof(DateTime))
{
if (parsedDate.HasValue)
property.SetValue(dataObject, parsedDate.Value);
else
throw new InvalidCastException($"单元格值为空,无法赋给非空 DateTime 属性 '{property.Name}'。");
}
else // DateTime?
{
property.SetValue(dataObject, parsedDate);
}
// 日期处理完成,跳过后续通用类型转换
continue;
2025-06-06 16:00:39 +08:00
}
2026-05-06 13:59:17 +08:00
// ----- 其他类型(int, double, string, bool, 枚举等)-----
if (cellValue != null)
2025-06-06 16:00:39 +08:00
{
2026-05-06 13:59:17 +08:00
// 目标类型是枚举
if (propertyType.IsEnum)
{
// 尝试按字符串或数值转换
if (cellValue is string enumStr)
cellValue = Enum.Parse(propertyType, enumStr, true);
else
cellValue = Enum.ToObject(propertyType, Convert.ChangeType(cellValue, Enum.GetUnderlyingType(propertyType)));
}
else
{
// 常规类型转换(如 string -> int, double -> int 等)
cellValue = Convert.ChangeType(cellValue, propertyType);
}
property.SetValue(dataObject, cellValue);
2025-06-06 16:00:39 +08:00
}
}
2026-05-06 13:59:17 +08:00
catch (Exception ex)
{
throw new InvalidCastException(
$"行 {row} 列 {col}(列名: {header}):转换 Excel 单元格值 '{worksheet.Cells[row, col].Text}' 到属性 '{property.Name}' 失败。",
ex);
}
2025-06-06 16:00:39 +08:00
}
result.Add(dataObject);
}
}
return result;
}
2026-05-06 13:59:17 +08:00
private static Dictionary<string, PropertyInfo> GetDisplayNameToPropertyMap<T>()
{
var properties = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance);
var dict = new Dictionary<string, PropertyInfo>(StringComparer.OrdinalIgnoreCase);
foreach (var prop in properties)
{
// 优先获取 DisplayAttribute 的 Name
var displayAttr = prop.GetCustomAttribute<DisplayAttribute>();
if (displayAttr != null && !string.IsNullOrEmpty(displayAttr.Name))
{
dict[displayAttr.Name] = prop;
continue;
}
// 其次获取 DisplayNameAttribute
var displayNameAttr = prop.GetCustomAttribute<DisplayNameAttribute>();
if (displayNameAttr != null && !string.IsNullOrEmpty(displayNameAttr.DisplayName))
{
dict[displayNameAttr.DisplayName] = prop;
continue;
}
// 最后使用属性原名
dict[prop.Name] = prop;
}
return dict;
}
2025-06-06 16:00:39 +08:00
private static Dictionary<string, string> GetDisplayNames<T>()
{
var displayNames = new Dictionary<string, string>();
var properties = typeof(T).GetProperties();
foreach (var property in properties)
{
var displayNameAttribute = property.GetCustomAttribute<DisplayAttribute>();
if (displayNameAttribute != null)
{
var propertyName = property.Name;
var displayName = displayNameAttribute.Name;
displayNames[propertyName] = displayName;
}
}
return displayNames;
}
/// <summary>
/// 导出Excel
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="worksheet"></param>
/// <param name="sheetData"></param>
/// <param name="fields"></param>
private static void ConfigureWorksheet<T>(ExcelWorksheet worksheet, IEnumerable<T> sheetData, List<string> fields)
{
// 添加表头
for (int i = 0; i < fields.Count; i++)
{
var fieldName = fields[i];
var displayName = GetDisplayName<T>(fieldName);
worksheet.Cells[1, i + 1].Value = displayName;
}
// 添加数据行
var dataList = sheetData.ToList();
for (int rowIndex = 0; rowIndex < dataList.Count; rowIndex++)
{
var rowData = dataList[rowIndex];
for (int colIndex = 0; colIndex < fields.Count; colIndex++)
{
var fieldName = fields[colIndex];
var value = GetPropertyValue(rowData, fieldName);
// 设置值时处理日期时间
if (value is DateTime dateTimeValue)
{
worksheet.Cells[rowIndex + 2, colIndex + 1].Value = dateTimeValue;
worksheet.Cells[rowIndex + 2, colIndex + 1].Style.Numberformat.Format = "yyyy-mm-dd hh:mm:ss";
}
else
{
worksheet.Cells[rowIndex + 2, colIndex + 1].Value = value;
}
}
}
// 自动调整列宽
worksheet.Cells.AutoFitColumns();
}
private static string GetDisplayName<T>(string propertyName)
{
var property = typeof(T).GetProperty(propertyName);
if (property != null)
{
var displayNameAttribute = property.GetCustomAttributes(typeof(DisplayAttribute), false).FirstOrDefault() as DisplayAttribute;
if (displayNameAttribute != null)
{
return displayNameAttribute.Name;
}
}
return propertyName;
}
private static object GetPropertyValue<T>(T obj, string propertyName)
{
var property = typeof(T).GetProperty(propertyName);
return property?.GetValue(obj);
}
/// <summary>
/// 获取类中所有得列名
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public static List<string> GetPropertyNames<T>()
{
var propertyNames = new List<string>();
var properties = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (var property in properties)
{
// 获取属性上的 DisplayAttribute 特性
var displayAttribute = property.GetCustomAttribute<DisplayAttribute>();
// 检查 DisplayAttribute 特性的名称是否为 "IgnoreColumnAttribute",如果是,则忽略该属性
if (displayAttribute != null && displayAttribute.Name.ToLower() == "ignorecolumnattribute")
{
continue;
}
propertyNames.Add(property.Name);
}
return propertyNames;
}
public static string NumberToChinese(this int number)
{
// 定义中文数字数组
string[] chineseNumbers = { "零", "一", "二", "三", "四", "五", "六", "七", "八", "九" };
// 如果数字在 0 到 9 之间,则直接返回中文数字
if (number >= 0 && number <= 9)
{
return chineseNumbers[number];
}
else
{
// 如果数字不在 0 到 9 之间,则抛出异常
throw new ArgumentOutOfRangeException(nameof(number), "Number must be between 0 and 9.");
}
}
2025-06-06 16:55:14 +08:00
public static string IncrementId(string maxId)
{
// 获取当前日期的前缀(年年年年+月月+日日)
string datePrefix = DateTime.Now.ToString("yyyyMMdd");
// 提取后4位编号部分
string numberPart = maxId.Substring(maxId.Length - 4);
// 将编号部分转为整数并加1
if (!int.TryParse(numberPart, out int number))
{
throw new InvalidOperationException("编号部分格式不正确,应为数字");
}
// 检查编号是否超出范围
if (number >= 9999)
{
throw new InvalidOperationException("编号已达到最大值");
}
// 返回新的编号,保持当前日期前缀和递增后的编号
return $"{datePrefix}{(number + 1):D4}";
}
2025-06-06 16:00:39 +08:00
/// <summary>
/// 获取集合中位数
/// </summary>
/// <param name="values"></param>
/// <returns></returns>
public static double Median(this List<float> values)
{
var sortedValues = values.OrderBy(x => x).ToList();
int count = sortedValues.Count;
if (count % 2 == 0)
{
return (sortedValues[count / 2 - 1] + sortedValues[count / 2]) / 2.0;
}
else
{
return sortedValues[count / 2];
}
}
/// <summary>
/// 获取集合中位数
/// </summary>
/// <param name="values"></param>
/// <returns></returns>
public static double? Median(this List<double?> values)
{
// 过滤掉 null 值
var sortedValues = values.Where(x => x.HasValue)
.Select(x => x.Value)
.OrderBy(x => x)
.ToList();
// 如果列表为空,返回 null
if (sortedValues.Count == 0)
return null;
int count = sortedValues.Count;
// 计算中位数
if (count % 2 == 0)
{
return (sortedValues[count / 2 - 1] + sortedValues[count / 2]) / 2.0;
}
else
{
return sortedValues[count / 2];
}
}
/// <summary>
/// 获取取样时间
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
/// <exception cref="ArgumentException"></exception>
public static DateTime GetDateTimeFromFixedAsh(this string input)
{
string timePart;
// 判断字符串中是否包含 '-',以确定截取的位置
int dashIndex = input.IndexOf('-');
if (dashIndex != -1)
{
// 截取 '-' 之前的部分
timePart = input.Substring(6, dashIndex - 6);
}
else
{
// 如果没有 '-',则直接截取后面的8位数字
timePart = input.Substring(6, 8);
}
// 将截取的时间字符串转换为 DateTime 类型
DateTime result;
if (DateTime.TryParseExact(timePart, "yyyyMMdd", null, System.Globalization.DateTimeStyles.None, out result))
{
return result;
}
else
{
throw new ArgumentException("时间转换失败");
}
}
/// <summary>
/// 获取枚举所有描述
/// </summary>
/// <typeparam name="T"></typeparam>
/// <typeparam name="TEnum"></typeparam>
/// <param name="createItemFunc"></param>
/// <returns></returns>
public static List<T> GetEnumDescriptions<T, TEnum>(Func<int, string, T> createItemFunc) where TEnum : Enum
{
var enumDescriptions = new List<T>();
foreach (TEnum value in Enum.GetValues(typeof(TEnum)))
{
int intValue = Convert.ToInt32(value);
if (intValue >= 1)
{
var descriptionAttribute = value.GetType()
.GetField(value.ToString())
.GetCustomAttributes(typeof(DescriptionAttribute), false)
.FirstOrDefault() as DescriptionAttribute;
if (descriptionAttribute != null)
{
var item = createItemFunc(intValue, descriptionAttribute.Description);
enumDescriptions.Add(item);
}
}
}
return enumDescriptions;
}
/// <summary>
/// 获取枚举描述
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
public static string GetDescription(this Enum value)
{
if (value == null)
{
throw new ArgumentNullException(nameof(value), "Enum value cannot be null");
}
// 获取字段信息
FieldInfo field = value.GetType().GetField(value.ToString());
if (field == null)
{
// 如果字段信息为空,返回默认的描述
return "";
}
// 获取 DescriptionAttribute 特性
var attributes = field.GetCustomAttributes(typeof(DescriptionAttribute), false);
var attribute = attributes.FirstOrDefault() as DescriptionAttribute;
// 如果特性为空,返回空字符串
return attribute?.Description ?? "";
}
/// <summary>
/// 测试类型枚举转换
/// </summary>
/// <param name="itemCode"></param>
/// <returns></returns>
//public static CategoryEnumType? GetCategoryEnumTypeByItemCode(string itemCode)
//{
// if (Enum.TryParse(typeof(Ai_CategoryEnumType), itemCode, true, out var aiCategoryEnum))
// {
// var aiCategoryName = Enum.GetName(typeof(Ai_CategoryEnumType), aiCategoryEnum);
// if (Enum.TryParse(typeof(CategoryEnumType), aiCategoryName, true, out var categoryEnum))
// {
// return (CategoryEnumType)categoryEnum;
// }
// }
// return null; // 如果找不到对应的映射值,返回 null 或者抛出异常
//}
public static byte[] DicExportToExcel(Dictionary<string, List<Dictionary<string, string>>> sheetDataList)
{
using (var package = new ExcelPackage())
{
foreach (var sheet in sheetDataList)
{
var sheetName = sheet.Key;
var sheetData = sheet.Value;
var worksheet = package.Workbook.Worksheets.Add(sheetName);
DicConfigureWorksheet(worksheet, sheetData);
}
return package.GetAsByteArray();
}
}
private static void DicConfigureWorksheet(ExcelWorksheet worksheet, List<Dictionary<string, string>> sheetData)
{
// 假设所有字典在List中都有相同的Key集合,我们可以从第一个元素获取表头
if (sheetData.Count > 0)
{
int rowIndex = 1; // 从第一行开始
var firstRow = sheetData[0];
// 添加表头
int columnIndex = 1; // 从第一列开始
foreach (var header in firstRow)
{
worksheet.Cells[1, columnIndex++].Value = header.Key;
}
// 添加数据行
foreach (var rowDict in sheetData)
{
rowIndex++;
columnIndex = 1; // 重置列索引
foreach (var cell in rowDict)
{
worksheet.Cells[rowIndex, columnIndex].Value = cell.Value;
// 检查是否需要设置日期时间格式
if (DateTime.TryParse(cell.Value, out DateTime dateTimeValue))
{
worksheet.Cells[rowIndex, columnIndex].Style.Numberformat.Format = "yyyy-mm-dd hh:mm:ss";
}
columnIndex++;
}
}
// 自动调整列宽
worksheet.Cells[worksheet.Dimension.Address].AutoFitColumns();
}
}
public static DateTime GetQuarterStartDate(int quarter, int year)
{
return quarter switch
{
1 => new DateTime(year, 1, 1),
2 => new DateTime(year, 4, 1),
3 => new DateTime(year, 7, 1),
4 => new DateTime(year, 10, 1),
_ => throw new ArgumentOutOfRangeException()
};
}
public static DateTime GetQuarterEndDate(int quarter, int year)
{
return quarter switch
{
1 => new DateTime(year, 3, 31),
2 => new DateTime(year, 6, 30),
3 => new DateTime(year, 9, 30),
4 => new DateTime(year, 12, 31),
_ => throw new ArgumentOutOfRangeException()
};
}
public static IQueryable<T> WhereIF<T>(this IQueryable<T> query, bool condition, Expression<Func<T, bool>> predicate)
{
return condition ? query.Where(predicate) : query;
}
/// <summary>
/// DateTime转时间戳
/// </summary>
/// <param name="dateTime"></param>
/// <returns></returns>
public static long ToUnixTimeMilliseconds(this DateTime dateTime)
{
return new DateTimeOffset(dateTime).ToUnixTimeMilliseconds();
}
public static string GetEnumNameByValue(int value)
{
if (Enum.IsDefined(typeof(SportsTestItemType), value))
{
return ((SportsTestItemType)value).ToString();
}
return ""; // Return an empty string if the value is not defined in the enum
}
/// <summary>
/// 获取分数等级
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="score"></param>
/// <returns></returns>
public static string GetRank<T>(this T score) where T : struct, IComparable
{
// 将 score 转换为 double 进行比较
double value = Convert.ToDouble(score);
return value switch
{
>= 90 => "优秀",
>= 80 => "良好",
>= 60 => "及格",
_ => "不及格"
};
}
/// <summary>
/// 根据等级名称返回枚举
/// </summary>
/// <param name="rankName"></param>
/// <returns></returns>
/// <exception cref="ArgumentException"></exception>
public static AchievementRank GetEnumByRankName(string rankName)
{
return rankName switch
{
"优秀" => AchievementRank.Excellent,
"良好" => AchievementRank.Fine,
"及格" => AchievementRank.Pass,
"不及格" => AchievementRank.Fail,
_ => throw new ArgumentException("Invalid rank name", nameof(rankName)) // Throw exception for invalid rank name
};
}
public static string ToChineseNumber(this int number)
{
string[] chineseNumbers = { "零", "一", "二", "三", "四", "五", "六", "七", "八", "九" };
if (number <= 0)
throw new ArgumentException("Number must be greater than zero.");
if (number < 10)
return chineseNumbers[number];
if (number < 20)
return "十" + (number % 10 == 0 ? "" : chineseNumbers[number % 10]);
int tens = number / 10;
int units = number % 10;
return chineseNumbers[tens] + "十" + (units == 0 ? "" : chineseNumbers[units]);
}
}
}