2025-07-11 09:37:06 +08:00

815 lines
30 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.DomainModels;
using VOL.Entity.Enum;
using VOL.Model.Norm.Response;
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 Exception("工作表中没有任何数据。");
}
// 获取 Excel 文件的表头(列名),假设第一行是表头
var headerRow = worksheet.Cells[1, 1, 1, worksheet.Dimension.End.Column];
var headers = headerRow.Select(cell => cell.Text.Trim()).ToList();
// 获取实体类的属性信息和 Display 属性的中文名
var properties = typeof(T).GetProperties();
var displayNames = GetDisplayNames<T>();
// 为 Display 名称构建映射表(避免每次都做遍历)
var displayNameToPropertyMap = properties
.Where(p => displayNames.ContainsKey(p.Name))
.ToDictionary(p => displayNames[p.Name], p => p);
// 从第二行开始解析数据(假设第一行是表头)
for (int row = 2; row <= worksheet.Dimension.End.Row; row++)
{
var dataObject = new T();
// 遍历每一列,将 Excel 中的数据映射到实体类的属性上
for (int col = 1; col <= worksheet.Dimension.End.Column; col++)
{
var header = headers[col - 1];
if (displayNameToPropertyMap.TryGetValue(header, out var property))
{
try
{
var cellValue = worksheet.Cells[row, col].GetValue<object>();
var propertyType = property.PropertyType;
// 处理 Excel 日期类型的特殊情况
if (propertyType == typeof(DateTime?))
{
if (cellValue is double numericValue)
{
// 如果 Excel 中的值是数字(日期),将其转换为 DateTime 类型
cellValue = DateTime.FromOADate(numericValue);
}
else if (cellValue is string stringValue)
{
// 如果 Excel 单元格是字符串类型的日期(如 '2000-08-01'
if (DateTime.TryParseExact(stringValue, "yyyy-MM-dd", CultureInfo.InvariantCulture, DateTimeStyles.None, out DateTime parsedDate))
{
cellValue = parsedDate;
}
else if (DateTime.TryParseExact(stringValue, "M/d/yyyy", CultureInfo.InvariantCulture, DateTimeStyles.None, out parsedDate))
{
cellValue = parsedDate;
}
else
{
// 无法解析为日期格式
throw new InvalidCastException($"无法将字符串 '{stringValue}' 转换为有效的日期。");
}
}
}
// 确保转换后的类型与目标属性匹配
if (cellValue != null)
{
// 如果属性是 DateTime 类型并且值已经是 DateTime无需再调用 Convert.ChangeType
if (propertyType == typeof(DateTime?) && cellValue is DateTime?)
{
// 如果是日期类型,直接赋值
property.SetValue(dataObject, (DateTime?)cellValue);
}
else
{
// 尝试转换数据类型
cellValue = Convert.ChangeType(cellValue, propertyType);
property.SetValue(dataObject, cellValue);
}
}
}
catch (Exception ex)
{
throw new InvalidCastException(
$"行 {row} 列 {col}:转换 Excel 单元格值 '{worksheet.Cells[row, col].Text}' 到属性 '{property.Name}' 失败。",
ex);
}
}
}
result.Add(dataObject);
}
}
return result;
}
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]);
}
/// <summary>
/// 心率个数
/// </summary>
/// <param name="heartRateDataList"></param>
/// <returns></returns>
public static List<SportsProportionData> GetHeartRateNumber(List<Ai_HeartRateData> heartRateDataList)
{
var result = new List<SportsProportionData>();
var maleData = heartRateDataList.Where(x => x.Sex == SexType.Male).ToList();
var femaleData = heartRateDataList.Where(x => x.Sex == SexType.Female).ToList();
var m_avg = maleData.Any() ? (int)maleData.Average(x => x.Value) : 0;
var f_avg = femaleData.Any() ? (int)femaleData.Average(x => x.Value) : 0;
var avgData = new SportsProportionData
{
Name = "平均",
Datas = new List<StudentSportsProportionData>
{
new StudentSportsProportionData { Title = "男", Value = m_avg },
new StudentSportsProportionData { Title = "女", Value = f_avg }
}
};
var m_max = maleData.Any() ? maleData.Max(x => x.Value) : 0;
var f_max = femaleData.Any() ? femaleData.Max(x => x.Value) : 0;
var maxData = new SportsProportionData
{
Name = "最高",
Datas = new List<StudentSportsProportionData>
{
new StudentSportsProportionData { Title = "男", Value = m_max },
new StudentSportsProportionData { Title = "女", Value = f_max }
}
};
var m_min = maleData.Any() ? maleData.Min(x => x.Value) : 0;
var f_min = femaleData.Any() ? femaleData.Min(x => x.Value) : 0;
var minData = new SportsProportionData
{
Name = "最低",
Datas = new List<StudentSportsProportionData>
{
new StudentSportsProportionData { Title = "男", Value = m_min },
new StudentSportsProportionData { Title = "女", Value = f_min }
}
};
result.Add(avgData);
result.Add(minData);
result.Add(maxData);
return result;
}
/// <summary>
/// 心率变化趋势
/// </summary>
/// <param name="heartRateDataList"></param>
/// <returns></returns>
public static List<SportsProportionData> GetHeartRateTrend(List<Ai_HeartRateData> heartRateDataList)
{
var result = new List<SportsProportionData>();
if (heartRateDataList == null || !heartRateDataList.Any())
return result;
var baseTime = heartRateDataList.Min(x => x.ScoreTime);
var heartRateWithMinutes = heartRateDataList
.Select(data => new
{
Data = data,
MinuteBucket = (int)(data.ScoreTime - baseTime).TotalMinutes
})
.ToList();
var maxMinute = heartRateWithMinutes.Max(x => x.MinuteBucket);
for (int minute = 0; minute <= maxMinute; minute++)
{
var minuteData = heartRateWithMinutes
.Where(x => x.MinuteBucket == minute)
.Select(x => x.Data)
.ToList();
if (minuteData.Any())
{
result.Add(new SportsProportionData()
{
Name = $"{minute + 1} 分钟",
Datas = new List<StudentSportsProportionData>
{
new StudentSportsProportionData {
Title = "心率",
Value = (int)minuteData.Average(x => x.Value)
}
}
});
}
}
return result;
}
/// <summary>
/// 通用百分比计算方法
/// </summary>
/// <param name="count"></param>
/// <param name="totalCount"></param>
/// <returns></returns>
public static double CalculatePercentage(int count, int totalCount)
{
if (totalCount == 0) return 0;
return Math.Round((double)count / totalCount * 100, 0);
}
}
}