2026-05-06 13:59:17 +08:00

752 lines
29 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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();
}
}
/// <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))
{
var worksheet = package.Workbook.Worksheets[1]; // 第一个工作表
if (worksheet.Dimension == null)
throw new InvalidOperationException("工作表中没有任何数据。");
// 获取表头(第一行)
var headerRow = worksheet.Cells[1, 1, 1, worksheet.Dimension.End.Column];
var headers = headerRow.Select(cell => cell.Text?.Trim()).ToList();
// 获取实体属性与显示名称的映射
var displayNameToPropertyMap = GetDisplayNameToPropertyMap<T>();
// 从第二行开始解析数据
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];
if (string.IsNullOrEmpty(header))
continue;
if (!displayNameToPropertyMap.TryGetValue(header, out var property))
continue; // 找不到对应属性,忽略该列
try
{
var cellValue = worksheet.Cells[row, col].GetValue<object>();
var propertyType = property.PropertyType;
// ----- 日期类型处理(支持 DateTime / DateTime?)-----
if (propertyType == typeof(DateTime) || propertyType == typeof(DateTime?))
{
DateTime? parsedDate = null;
// 情况1:Excel 日期存储为 OADate 数字
if (cellValue is double numericValue)
{
parsedDate = DateTime.FromOADate(numericValue);
}
// 情况2:字符串格式的日期
else if (cellValue is string dateStr && !string.IsNullOrWhiteSpace(dateStr))
{
// 支持多种常见日期格式
string[] formats = new[]
{
"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))
{
parsedDate = dt;
}
else if (DateTime.TryParse(dateStr, out dt)) // 兜底尝试系统解析
{
parsedDate = dt;
}
else
{
throw new InvalidCastException($"无法将字符串 '{dateStr}' 转换为有效的日期。");
}
}
// 情况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;
}
// ----- 其他类型(int, double, string, bool, 枚举等)-----
if (cellValue != null)
{
// 目标类型是枚举
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);
}
}
catch (Exception ex)
{
throw new InvalidCastException(
$"行 {row} 列 {col}(列名: {header}):转换 Excel 单元格值 '{worksheet.Cells[row, col].Text}' 到属性 '{property.Name}' 失败。",
ex);
}
}
result.Add(dataObject);
}
}
return result;
}
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;
}
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.");
}
}
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}";
}
/// <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]);
}
}
}