添加项目文件。
This commit is contained in:
15
Atomx.Utils/Atomx.Utils.csproj
Normal file
15
Atomx.Utils/Atomx.Utils.csproj
Normal file
@@ -0,0 +1,15 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="IdGen" Version="3.0.7" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Http" Version="2.3.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="10.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
188
Atomx.Utils/Extension/DateTimeExtension.cs
Normal file
188
Atomx.Utils/Extension/DateTimeExtension.cs
Normal file
@@ -0,0 +1,188 @@
|
||||
namespace Atomx.Utils.Extension
|
||||
{
|
||||
public static class DateTimeExtension
|
||||
{
|
||||
/// <summary>
|
||||
/// 时间距离现在多久
|
||||
/// </summary>
|
||||
/// <param name="time"></param>
|
||||
/// <returns></returns>
|
||||
public static string DateFromNow(this DateTime time)
|
||||
{
|
||||
if (time == DateTime.MinValue)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
TimeSpan timeSpan = DateTime.Now - time;
|
||||
if (Math.Floor(timeSpan.TotalDays) > 365)
|
||||
{
|
||||
return Math.Floor(timeSpan.TotalDays) / 365 + "年前";
|
||||
}
|
||||
else if (Math.Floor(timeSpan.TotalDays) > 30)
|
||||
{
|
||||
return Math.Floor(timeSpan.TotalDays) / 30 + "月前";
|
||||
}
|
||||
else if (Math.Floor(timeSpan.TotalDays) > 7)
|
||||
{
|
||||
return Math.Floor(timeSpan.TotalDays) / 7 + "周前";
|
||||
}
|
||||
else if (Math.Floor(timeSpan.TotalDays) > 1)
|
||||
{
|
||||
return Math.Floor(timeSpan.TotalDays) + "天前";
|
||||
}
|
||||
else if (Math.Floor(timeSpan.TotalHours) > 1)
|
||||
{
|
||||
return Math.Floor(timeSpan.TotalHours) + "小时前";
|
||||
}
|
||||
else if (Math.Floor(timeSpan.TotalMinutes) > 1)
|
||||
{
|
||||
return Math.Floor(timeSpan.TotalMinutes) + "分钟前";
|
||||
}
|
||||
else
|
||||
{
|
||||
return Math.Floor(timeSpan.TotalSeconds) + "秒前";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 字符串时间转换DateTime
|
||||
/// </summary>
|
||||
/// <param name="text"></param>
|
||||
/// <returns></returns>
|
||||
public static DateTime ToDateTime(this string text)
|
||||
{
|
||||
DateTime.TryParse(text, out DateTime time);
|
||||
return time;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 字符串时间转换DateTime
|
||||
/// </summary>
|
||||
/// <param name="text"></param>
|
||||
/// <returns></returns>
|
||||
public static DateTime ToUtcDateTime(this string text)
|
||||
{
|
||||
var state = DateTime.TryParse(text, out DateTime time);
|
||||
if (state)
|
||||
{
|
||||
return DateTime.SpecifyKind(time, DateTimeKind.Utc);
|
||||
}
|
||||
return DateTime.MinValue;
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取当月第一天0时0分0秒
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static DateTime GetMonthStartDate()
|
||||
{
|
||||
return DateTime.Now.AddDays(1 - DateTime.Now.Day).Date;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取当月最后一天23时59分59秒
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static DateTime GetMonthEndDate()
|
||||
{
|
||||
return DateTime.Now.AddDays(1 - DateTime.Now.Day).Date.AddMonths(1).AddSeconds(-1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前时间的周一开始时间
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static DateTime GetWeekStartDate()
|
||||
{
|
||||
DateTime today = DateTime.Today;
|
||||
return today.AddDays(-(int)today.DayOfWeek + (int)DayOfWeek.Monday).Date;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前时间所在的周的最后一天结束时间
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static DateTime GetWeekEndDate()
|
||||
{
|
||||
DateTime today = DateTime.Today;
|
||||
return today.AddDays(-(int)today.DayOfWeek + (int)DayOfWeek.Monday).AddDays(7).AddSeconds(-1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转换成每日开始时间
|
||||
/// </summary>
|
||||
/// <param name="time"></param>
|
||||
/// <returns></returns>
|
||||
public static DateTime ConvertStartDateTime(this DateTime time)
|
||||
{
|
||||
return time.Date;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转换成每日开始UTC时间
|
||||
/// </summary>
|
||||
/// <param name="time"></param>
|
||||
/// <returns></returns>
|
||||
public static DateTime ConvertStartDateUTCTime(this DateTime time)
|
||||
{
|
||||
return DateTime.SpecifyKind(time.Date, DateTimeKind.Utc);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 转换成每日结束时间
|
||||
/// </summary>
|
||||
/// <param name="time"></param>
|
||||
/// <returns></returns>
|
||||
public static DateTime ConvertEndDateTime(this DateTime time)
|
||||
{
|
||||
return time.Date.AddDays(1).AddSeconds(-1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转换成每日结束UTC时间
|
||||
/// </summary>
|
||||
/// <param name="time"></param>
|
||||
/// <returns></returns>
|
||||
public static DateTime ConvertEndDateUTCTime(this DateTime time)
|
||||
{
|
||||
return DateTime.SpecifyKind(time.Date.AddDays(1).AddSeconds(-1), DateTimeKind.Utc);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// long --> DateTime
|
||||
/// </summary>
|
||||
/// <param name="d"></param>
|
||||
/// <returns></returns>
|
||||
public static DateTime ConvertLongToDateTime(this long d)
|
||||
{
|
||||
DateTime dtStart = TimeZoneInfo.ConvertTimeToUtc(new DateTime(1970, 1, 1));
|
||||
long lTime = long.Parse(d + "0000");
|
||||
TimeSpan toNow = new TimeSpan(lTime);
|
||||
DateTime dtResult = dtStart.Add(toNow);
|
||||
return dtResult;
|
||||
}
|
||||
|
||||
public static DateTime NumberToDateTime(this string number)
|
||||
{
|
||||
var year = number.Substring(0, 4);
|
||||
//Console.WriteLine($"年:{year}");
|
||||
var month = number.Substring(4, 2);
|
||||
//Console.WriteLine($"月:{month}");
|
||||
var day = number.Substring(6, 2);
|
||||
//Console.WriteLine($"日:{day}");
|
||||
var hour = number.Substring(8, 2);
|
||||
//Console.WriteLine($"时:{hour}");
|
||||
var minute = number.Substring(10, 2);
|
||||
//Console.WriteLine($"分:{minute}");
|
||||
var second = number.Substring(12);
|
||||
//Console.WriteLine($"秒:{second}");
|
||||
|
||||
var datetime = $"{year}-{month}-{day} {hour}:{minute}:{second}";
|
||||
|
||||
DateTime date = DateTime.Parse(datetime);
|
||||
return date;
|
||||
}
|
||||
}
|
||||
}
|
||||
37
Atomx.Utils/Extension/IdCodeExtension.cs
Normal file
37
Atomx.Utils/Extension/IdCodeExtension.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using System.Text;
|
||||
|
||||
namespace Atomx.Utils.Extension
|
||||
{
|
||||
public static class IdCodeExtension
|
||||
{
|
||||
private static readonly char[] Base62Chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz".ToCharArray();
|
||||
|
||||
/// <summary>
|
||||
/// 将long类型ID编码为10位Base62邀请码
|
||||
/// </summary>
|
||||
public static string IdToCode(this long id)
|
||||
{
|
||||
ulong value = (ulong)id;
|
||||
var sb = new StringBuilder();
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
sb.Insert(0, Base62Chars[(int)(value % 62)]);
|
||||
value /= 62;
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将10位Base62邀请码还原为long类型ID
|
||||
/// </summary>
|
||||
public static long CodeToId(this string code)
|
||||
{
|
||||
ulong value = 0;
|
||||
foreach (var c in code)
|
||||
{
|
||||
value = value * 62 + (ulong)Array.IndexOf(Base62Chars, c);
|
||||
}
|
||||
return (long)value;
|
||||
}
|
||||
}
|
||||
}
|
||||
81
Atomx.Utils/Extension/NumberExtension.cs
Normal file
81
Atomx.Utils/Extension/NumberExtension.cs
Normal file
@@ -0,0 +1,81 @@
|
||||
namespace Atomx.Utils.Extension
|
||||
{
|
||||
public static class NumberExtension
|
||||
{
|
||||
/// <summary>
|
||||
/// 字符串转 int64位数字,转换失败则返回0
|
||||
/// </summary>
|
||||
/// <param name="text"></param>
|
||||
/// <returns></returns>
|
||||
public static long ToLong(this string text)
|
||||
{
|
||||
long.TryParse(text, out long num);
|
||||
return num;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 字符转Int32位数字,转换失败则返回0
|
||||
/// </summary>
|
||||
/// <param name="text"></param>
|
||||
/// <returns></returns>
|
||||
public static int ToInt(this string text)
|
||||
{
|
||||
int.TryParse(text, out int num);
|
||||
return num;
|
||||
}
|
||||
|
||||
public static decimal ToDecimal(this string text)
|
||||
{
|
||||
decimal.TryParse(text, out decimal num);
|
||||
return num;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 字符串转 int64位数字,转换失败则返回0
|
||||
/// </summary>
|
||||
/// <param name="text"></param>
|
||||
/// <returns></returns>
|
||||
public static double ToDouble(this string text)
|
||||
{
|
||||
double.TryParse(text, out double num);
|
||||
return num;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 移除小数点后面的0
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
/// <returns></returns>
|
||||
public static decimal RemoveZero(this decimal value)
|
||||
{
|
||||
var text = value.ToString();
|
||||
text = text.TrimEnd('0').TrimStart('.');
|
||||
return Convert.ToDecimal(text);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除无意义的0
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
/// <returns></returns>
|
||||
public static string RemoveTrailingZeros(this decimal value)
|
||||
{
|
||||
string str = value.ToString();
|
||||
if (str.Contains('.'))
|
||||
{
|
||||
str = str.TrimEnd('0');
|
||||
if (str.EndsWith("."))
|
||||
{
|
||||
str = str.Substring(0, str.Length - 1);
|
||||
}
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
public static long DateToNumber(this DateTime data)
|
||||
{
|
||||
return data.ToString("yyyyMMddHHmmss").ToLong();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
46
Atomx.Utils/Extension/ObjectExtension.cs
Normal file
46
Atomx.Utils/Extension/ObjectExtension.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
using System.Reflection;
|
||||
using System.Xml.Serialization;
|
||||
|
||||
namespace Atomx.Utils.Extension
|
||||
{
|
||||
public static class ObjectExtension
|
||||
{
|
||||
/// <summary>
|
||||
/// 利用反射实现深拷贝
|
||||
/// </summary>
|
||||
/// <param name="_object"></param>
|
||||
/// <returns></returns>
|
||||
public static object DeepCopy(object _object)
|
||||
{
|
||||
Type T = _object.GetType();
|
||||
object o = Activator.CreateInstance(T);
|
||||
PropertyInfo[] PI = T.GetProperties();
|
||||
for (int i = 0; i < PI.Length; i++)
|
||||
{
|
||||
PropertyInfo P = PI[i];
|
||||
P.SetValue(o, P.GetValue(_object));
|
||||
}
|
||||
return o;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 深度克隆对象
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="obj"></param>
|
||||
/// <returns></returns>
|
||||
public static T Clone<T>(this T obj)
|
||||
{
|
||||
T ret = default;
|
||||
if (obj != null)
|
||||
{
|
||||
XmlSerializer cloner = new XmlSerializer(typeof(T));
|
||||
MemoryStream stream = new MemoryStream();
|
||||
cloner.Serialize(stream, obj);
|
||||
stream.Seek(0, SeekOrigin.Begin);
|
||||
ret = (T)cloner.Deserialize(stream);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
86
Atomx.Utils/Extension/PathExtension.cs
Normal file
86
Atomx.Utils/Extension/PathExtension.cs
Normal file
@@ -0,0 +1,86 @@
|
||||
using System.Text;
|
||||
|
||||
namespace Atomx.Utils.Extension
|
||||
{
|
||||
public static class PathExtension
|
||||
{
|
||||
/// <summary>
|
||||
/// 是否是绝对路径
|
||||
/// windows下判断 路径是否包含 ":"
|
||||
/// Mac OS、Linux下判断 路径是否包含 "\"
|
||||
/// </summary>
|
||||
/// <param name="path"></param>
|
||||
/// <returns></returns>
|
||||
public static bool IsAbsolute(this string path)
|
||||
{
|
||||
return Path.VolumeSeparatorChar == ':' ? path.IndexOf(Path.VolumeSeparatorChar) > 0 : path.IndexOf('\\') > 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取文件绝对路径
|
||||
/// </summary>
|
||||
/// <param name="path"></param>
|
||||
/// <returns></returns>
|
||||
public static string MapPath(string root, string path)
|
||||
{
|
||||
|
||||
return path.IsAbsolute() ? path : Path.Combine(root, path.TrimStart('~', '/').Replace('/', Path.DirectorySeparatorChar));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检测指定路径是否存在
|
||||
/// </summary>
|
||||
/// <param name="path">路径</param>
|
||||
/// <param name="isDirectory">是否是目录</param>
|
||||
/// <returns></returns>
|
||||
public static bool IsExist(string root, string path, bool isDirectory)
|
||||
{
|
||||
if (isDirectory)
|
||||
{
|
||||
return Directory.Exists(path.IsAbsolute() ? path : MapPath(root, path));
|
||||
}
|
||||
else
|
||||
{
|
||||
return File.Exists(path.IsAbsolute() ? path : MapPath(root, path));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 根据指定目录,当前日期和扩展名,获取一个完整的存储路径和名称
|
||||
/// </summary>
|
||||
/// <param name="path">指定存储文件根目录,默认 upload,</param>
|
||||
/// <param name="fixedpath">是否固定存储在根目录,如果不固定,将自动根据时间生成子文件夹进行存储</param>
|
||||
/// <param name="name">存储文件名,如果为空将自动生成文件名</param>
|
||||
/// <param name="extension">文件存储后缀,不能为空</param>
|
||||
/// <returns></returns>
|
||||
public static string GetFileNameByData(string path, bool fixedpath, string name, string extension)
|
||||
{
|
||||
if (string.IsNullOrEmpty(extension)) throw new ArgumentNullException("extension");
|
||||
if (!extension.StartsWith(".")) extension = "." + extension;
|
||||
|
||||
if (string.IsNullOrEmpty(path))
|
||||
{
|
||||
path = "/uploads";
|
||||
}
|
||||
if (path.StartsWith("/")) path = path.Substring(1, path.Length - 1);
|
||||
if (path.EndsWith("/")) path = path.Substring(0, path.Length - 1);
|
||||
|
||||
StringBuilder fileName = new StringBuilder(path);
|
||||
if (!fixedpath)
|
||||
{
|
||||
fileName.AppendFormat("/{0}/{1}/", DateTime.Now.ToString("yyyyMM"), DateTime.Now.ToString("dd"));
|
||||
}
|
||||
if (string.IsNullOrEmpty(name))
|
||||
{
|
||||
Random random = new Random(Guid.NewGuid().GetHashCode());
|
||||
fileName.Append(DateTime.Now.ToString("yyyyMMddHHmmss") + random.Next(1000, 10000));
|
||||
}
|
||||
else
|
||||
{
|
||||
fileName.Append("/" + name);
|
||||
}
|
||||
return fileName.ToString() + extension;
|
||||
}
|
||||
}
|
||||
}
|
||||
60
Atomx.Utils/Extension/RegexExtension.cs
Normal file
60
Atomx.Utils/Extension/RegexExtension.cs
Normal file
@@ -0,0 +1,60 @@
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Atomx.Utils.Extension
|
||||
{
|
||||
public class RegexExtension
|
||||
{
|
||||
/// <summary>
|
||||
/// 判断是否是电子邮件格式
|
||||
/// </summary>
|
||||
/// <param name="email"></param>
|
||||
/// <returns></returns>
|
||||
public static bool IsEmail(string email)
|
||||
{
|
||||
return Regex.IsMatch(email, @"^\S+@\S+\.\S+$");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断手机号码是否符合规则,不考虑国家代码的情况下
|
||||
/// </summary>
|
||||
/// <param name="mobile"></param>
|
||||
/// <returns></returns>
|
||||
public static bool IsValidMobile(string mobile)
|
||||
{
|
||||
string pattern = "^\\d{7,15}$";
|
||||
return Regex.IsMatch(mobile, pattern);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断手机号码是否符合规则,不考虑国家代码的情况下
|
||||
/// </summary>
|
||||
/// <param name="mobile"></param>
|
||||
/// <returns></returns>
|
||||
public static bool IsValidGlobalMobile(string mobile)
|
||||
{
|
||||
// 正则表达式规则解释:
|
||||
// ^ 开始
|
||||
// \\+ 匹配一个加号 (E.164要求号码以加号开始)
|
||||
// \\d{1,3} 匹配1到3位数字,即国家代码
|
||||
// \\d{7,15} 匹配7到15位数字,即电话号码
|
||||
// $ 结束
|
||||
string pattern = "^\\+\\d{1,3}\\d{7,15}$";
|
||||
return Regex.IsMatch(mobile, pattern);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证账号是否是邮箱地址或者手机号
|
||||
/// </summary>
|
||||
/// <param name="account"></param>
|
||||
/// <returns></returns>
|
||||
public static bool IsValidAccount(string account)
|
||||
{
|
||||
string pattern = @"^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$";
|
||||
if (Regex.IsMatch(account, pattern))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return IsValidMobile(account);
|
||||
}
|
||||
}
|
||||
}
|
||||
195
Atomx.Utils/Extension/StringExtension.cs
Normal file
195
Atomx.Utils/Extension/StringExtension.cs
Normal file
@@ -0,0 +1,195 @@
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Web;
|
||||
|
||||
namespace Atomx.Utils.Extension
|
||||
{
|
||||
public static class StringExtension
|
||||
{
|
||||
// <summary>
|
||||
/// MD5加密,输出的字母是大写
|
||||
/// </summary>
|
||||
/// <param name="text"></param>
|
||||
/// <returns></returns>
|
||||
public static string ToMD5(this string text)
|
||||
{
|
||||
MD5 md5 = MD5.Create();
|
||||
return BitConverter.ToString(md5.ComputeHash(Encoding.UTF8.GetBytes(text))).Replace("-", "");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 字符串加密为通用的密码
|
||||
/// </summary>
|
||||
/// <param name="text"></param>
|
||||
/// <returns></returns>
|
||||
public static string ToMd5Password(this string text)
|
||||
{
|
||||
var md5 = text.ToMD5().ToMD5();
|
||||
return md5;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 随机数字字符串
|
||||
/// </summary>
|
||||
/// <param name="length"></param>
|
||||
/// <returns></returns>
|
||||
public static string GetRandomNumberString(int length)
|
||||
{
|
||||
string buffer = "0123456789";
|
||||
StringBuilder sb = new StringBuilder();
|
||||
Random r = new Random();
|
||||
int range = buffer.Length;
|
||||
for (var i = 0; i < length; i++)
|
||||
{
|
||||
sb.Append(buffer.Substring(r.Next(range), 1));
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 随机数字字符串
|
||||
/// </summary>
|
||||
/// <param name="length"></param>
|
||||
/// <returns></returns>
|
||||
public static string GetRandomString(int length)
|
||||
{
|
||||
string buffer = "abcdefghijklmnopqrstuvwxyz";
|
||||
StringBuilder sb = new StringBuilder();
|
||||
Random r = new Random();
|
||||
int range = buffer.Length;
|
||||
for (var i = 0; i < length; i++)
|
||||
{
|
||||
sb.Append(buffer.Substring(r.Next(range), 1));
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 获取URL参数字段值
|
||||
/// </summary>
|
||||
/// <param name="queryString"></param>
|
||||
/// <param name="paramName"></param>
|
||||
/// <returns></returns>
|
||||
public static string GetQueryString(this string queryString, string paramName)
|
||||
{
|
||||
if (string.IsNullOrEmpty(queryString) || string.IsNullOrEmpty(paramName))
|
||||
{
|
||||
return "";
|
||||
}
|
||||
string paramValue = HttpUtility.ParseQueryString(queryString).Get(paramName)?.ToString() ?? string.Empty;
|
||||
return paramValue ?? "";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转换URL参数
|
||||
/// </summary>
|
||||
/// <param name="source"></param>
|
||||
/// <returns></returns>
|
||||
public static string BuildQueryString(this object source)
|
||||
{
|
||||
var buff = new StringBuilder(string.Empty);
|
||||
if (source == null)
|
||||
return buff.ToString();
|
||||
|
||||
|
||||
var modelDict = source.GetType().GetProperties().ToDictionary(a => a.Name.ToLower());
|
||||
foreach (var item in modelDict)
|
||||
{
|
||||
if (item.Value.PropertyType != typeof(DateTime?[]))
|
||||
{
|
||||
System.Reflection.PropertyInfo? pi = null;
|
||||
if (modelDict.TryGetValue(item.Key, out pi))
|
||||
{
|
||||
object? obj = pi.GetValue(source, null);
|
||||
if (pi != null && obj != null)
|
||||
{
|
||||
var value = obj.ToString();
|
||||
if (!string.IsNullOrEmpty(value))
|
||||
{
|
||||
buff.Append($"{item.Key}={value}&");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
System.Reflection.PropertyInfo? pi = null;
|
||||
if (modelDict.TryGetValue(item.Key, out pi))
|
||||
{
|
||||
object? obj = pi.GetValue(source, null);
|
||||
if (pi != null && obj != null)
|
||||
{
|
||||
var data = obj as DateTime?[];
|
||||
if (data != null)
|
||||
{
|
||||
long start = 0;
|
||||
long end = 0;
|
||||
if (data[0].HasValue) {
|
||||
var time1 = data[0].Value.ConvertStartDateTime();
|
||||
start = time1.DateToNumber();
|
||||
}
|
||||
if (data[1].HasValue)
|
||||
{
|
||||
var time1 = data[1].Value.ConvertEndDateTime();
|
||||
end = time1.DateToNumber();
|
||||
}
|
||||
if (start > 0 && end > 0)
|
||||
{
|
||||
buff.Append($"{item.Key}={start}-{end}&");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return buff.ToString().Trim('&');
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// url参数转换成字典
|
||||
/// </summary>
|
||||
/// <param name="formData"></param>
|
||||
/// <returns></returns>
|
||||
public static Dictionary<string, object> ToUrlDictionary(this string formData)
|
||||
{
|
||||
if (string.IsNullOrEmpty(formData))
|
||||
{
|
||||
return new Dictionary<string, object>();
|
||||
}
|
||||
try
|
||||
{
|
||||
//将参数存入字符数组
|
||||
string[] dataArry = formData.Split('&');
|
||||
|
||||
//定义字典,将参数按照键值对存入字典中
|
||||
Dictionary<string, object> dataDic = new Dictionary<string, object>();
|
||||
//遍历字符数组
|
||||
for (int i = 0; i <= dataArry.Length - 1; i++)
|
||||
{
|
||||
//当前参数值
|
||||
string dataParm = dataArry[i];
|
||||
//"="的索引值
|
||||
int dIndex = dataParm.IndexOf("=");
|
||||
//参数名作为key
|
||||
string key = dataParm.Substring(0, dIndex);
|
||||
//参数值作为Value
|
||||
string value = dataParm.Substring(dIndex + 1, dataParm.Length - dIndex - 1);
|
||||
//将编码后的Value解码
|
||||
string deValue = HttpUtility.UrlDecode(value, Encoding.GetEncoding("utf-8"));
|
||||
if (key != "__VIEWSTATE")
|
||||
{
|
||||
//将参数以键值对存入字典
|
||||
dataDic.Add(key, deValue);
|
||||
}
|
||||
}
|
||||
|
||||
return dataDic;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return new Dictionary<string, object>();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
189
Atomx.Utils/Extension/UploadExtension.cs
Normal file
189
Atomx.Utils/Extension/UploadExtension.cs
Normal file
@@ -0,0 +1,189 @@
|
||||
using Atomx.Utils.Json;
|
||||
using Atomx.Utils.Models;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using System.Net;
|
||||
|
||||
namespace Atomx.Utils.Extension
|
||||
{
|
||||
public class UploadExtension
|
||||
{
|
||||
/// <summary>
|
||||
/// 上传
|
||||
/// </summary>
|
||||
/// <param name="file">文件</param>
|
||||
/// <param name="rootPath">站点根目录</param>
|
||||
/// <param name="path">存储目录</param>
|
||||
/// <param name="fixedPath">是否固定存储在根目录,如果不固定,将自动根据时间生成子文件夹进行存储</param>
|
||||
/// <param name="fileName">文件名</param>
|
||||
/// <param name="currentPath">覆盖上传的路径</param>
|
||||
/// <param name="extension">文件类型</param>
|
||||
/// <returns></returns>
|
||||
public Result<string> LocalUpload(IFormFile file, string rootPath, string path, bool fixedPath, string fileName, string currentPath, string extension)
|
||||
{
|
||||
var result = new Result<string>();
|
||||
var filepath = string.Empty;
|
||||
if (!string.IsNullOrEmpty(currentPath))
|
||||
{
|
||||
filepath = currentPath;
|
||||
}
|
||||
else
|
||||
{
|
||||
filepath = PathExtension.GetFileNameByData(path, fixedPath, fileName, extension);
|
||||
}
|
||||
|
||||
var savepath = Path.Combine(rootPath, filepath.Replace("/", "\\"));
|
||||
var newpath = Path.GetDirectoryName(savepath);
|
||||
|
||||
if (string.IsNullOrEmpty(newpath))
|
||||
{
|
||||
result.Message = "Storage path exception";
|
||||
result.Success = false;
|
||||
return result;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (!Directory.Exists(newpath))
|
||||
{
|
||||
Directory.CreateDirectory(newpath);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
result.Message = "Folder failure is established";
|
||||
result.Success = false;
|
||||
return result;
|
||||
|
||||
}
|
||||
try
|
||||
{
|
||||
using (FileStream fs = File.Create(savepath))
|
||||
{
|
||||
file.CopyTo(fs);
|
||||
fs.Flush();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
result.Message = ex.Message;
|
||||
result.Success = false;
|
||||
}
|
||||
result.Data = filepath;
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 上传
|
||||
/// </summary>
|
||||
/// <param name="file">文件</param>
|
||||
/// <param name="rootPath">站点根目录</param>
|
||||
/// <param name="path">存储目录</param>
|
||||
/// <param name="fixedPath">是否固定存储在根目录,如果不固定,将自动根据时间生成子文件夹进行存储</param>
|
||||
/// <param name="fileName">文件名</param>
|
||||
/// <param name="currentPath">覆盖上传的路径</param>
|
||||
/// <param name="extension">文件类型</param>
|
||||
/// <returns></returns>
|
||||
public async Task<Result<string>> LocalUploadEncryptAsync(IFormFile file, string rootPath, string path, bool fixedPath, string fileName, string currentPath, string extension)
|
||||
{
|
||||
var result = new Result<string>();
|
||||
var filepath = string.Empty;
|
||||
if (!string.IsNullOrEmpty(currentPath))
|
||||
{
|
||||
filepath = currentPath;
|
||||
}
|
||||
else
|
||||
{
|
||||
filepath = PathExtension.GetFileNameByData(path, fixedPath, fileName, extension);
|
||||
}
|
||||
|
||||
var savepath = Path.Combine(rootPath, filepath.Replace("/", "\\"));
|
||||
var newpath = Path.GetDirectoryName(savepath);
|
||||
if(string.IsNullOrEmpty(newpath))
|
||||
{
|
||||
result.Message = "Storage path exception";
|
||||
result.Success = false;
|
||||
return result;
|
||||
}
|
||||
try
|
||||
{
|
||||
if (!Directory.Exists(newpath))
|
||||
{
|
||||
Directory.CreateDirectory(newpath);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
result.Message = "Folder failure is established";
|
||||
result.Success = false;
|
||||
return result;
|
||||
|
||||
}
|
||||
try
|
||||
{
|
||||
var stream = file.OpenReadStream();
|
||||
byte[] bytes = new byte[stream.Length];
|
||||
await stream.ReadAsync(bytes, 0, bytes.Length);
|
||||
for (int i = 0; i < bytes.Length; i++)
|
||||
{
|
||||
++bytes[i];
|
||||
}
|
||||
|
||||
var fsw = new FileStream(savepath, FileMode.Create, FileAccess.Write);
|
||||
fsw.Write(bytes, 0, bytes.Length);
|
||||
fsw.Close();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
result.Message = ex.Message;
|
||||
result.Success = false;
|
||||
}
|
||||
result.Data = filepath;
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 远程上传文件
|
||||
/// </summary>
|
||||
/// <param name="file"></param>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="rootpath"></param>
|
||||
/// <param name="fixedpath"></param>
|
||||
/// <param name="filename"></param>
|
||||
/// <param name="currentpath"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<UploadResult> RemoteUploadAsync(IFormFile file, string url, string rootpath, bool fixedpath, string filename, string currentpath)
|
||||
{
|
||||
var result = new UploadResult();
|
||||
var dic = new Dictionary<string, string>();
|
||||
dic.Add("rootpath", rootpath);
|
||||
dic.Add("fixedpath", fixedpath.ToString().ToLower());
|
||||
dic.Add("filename", filename);
|
||||
dic.Add("currentpath", currentpath);
|
||||
using (var client = new HttpClient())
|
||||
{
|
||||
using (var content = new MultipartFormDataContent())
|
||||
{
|
||||
|
||||
content.Add(new StreamContent(file.OpenReadStream()), "file", file.FileName);
|
||||
foreach (var item in dic)
|
||||
{
|
||||
content.Add(new StringContent(item.Value), item.Key);
|
||||
}
|
||||
var response = await client.PostAsync(url, content);
|
||||
if (response.StatusCode == HttpStatusCode.OK)
|
||||
{
|
||||
var uploadresult = await response.Content.ReadAsStringAsync();
|
||||
result = uploadresult.FromJson<UploadResult>();
|
||||
}
|
||||
else
|
||||
{
|
||||
//result.Code = UploadCodes.Failure;
|
||||
//result.Message = "remote server is error";
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
285
Atomx.Utils/Files/FileTypes.cs
Normal file
285
Atomx.Utils/Files/FileTypes.cs
Normal file
@@ -0,0 +1,285 @@
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Atomx.Utils.Files
|
||||
{
|
||||
/// <summary>
|
||||
/// 常用文件头识别工具(改进版:修复编译器警告、改善流读取安全性与类型判断)
|
||||
/// </summary>
|
||||
public static class FileTypes
|
||||
{
|
||||
// 保持原有 public 字段签名以兼容现有代码
|
||||
public static readonly Dictionary<string, byte[]> ImageHeader = new();
|
||||
public static readonly Dictionary<string, object> FilesHeader = new();
|
||||
public static readonly Dictionary<string, object> VideoHeader = new();
|
||||
|
||||
static FileTypes()
|
||||
{
|
||||
ImageHeader.Add("gif", new byte[] { 71, 73, 70, 56, 57, 97 });
|
||||
ImageHeader.Add("bmp", new byte[] { 66, 77 });
|
||||
ImageHeader.Add("jpg", new byte[] { 255, 216, 255 });
|
||||
ImageHeader.Add("png", new byte[] { 137, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, 73, 72, 68, 82 });
|
||||
|
||||
FilesHeader.Add("pdf", new byte[] { 37, 80, 68, 70, 45, 49, 46, 53 });
|
||||
FilesHeader.Add("docx", new object[] { new byte[] { 80, 75, 3, 4, 20, 0, 6, 0, 8, 0, 0, 0, 33 }, new Regex(@"word/_rels/document\.xml\.rels", RegexOptions.IgnoreCase) });
|
||||
FilesHeader.Add("xlsx", new object[] { new byte[] { 80, 75, 3, 4, 20, 0, 6, 0, 8, 0, 0, 0, 33 }, new Regex(@"xl/_rels/workbook\.xml\.rels", RegexOptions.IgnoreCase) });
|
||||
FilesHeader.Add("pptx", new object[] { new byte[] { 80, 75, 3, 4, 20, 0, 6, 0, 8, 0, 0, 0, 33 }, new Regex(@"ppt/_rels/presentation\.xml\.rels", RegexOptions.IgnoreCase) });
|
||||
FilesHeader.Add("doc", new object[] { new byte[] { 208, 207, 17, 224, 161, 177, 26, 225 }, new Regex(@"microsoft( office)? word(?![\s\S]*?microsoft)", RegexOptions.IgnoreCase) });
|
||||
FilesHeader.Add("xls", new object[] { new byte[] { 208, 207, 17, 224, 161, 177, 26, 225 }, new Regex(@"microsoft( office)? excel(?![\s\S]*?microsoft)", RegexOptions.IgnoreCase) });
|
||||
FilesHeader.Add("ppt", new object[] { new byte[] { 208, 207, 17, 224, 161, 177, 26, 225 }, new Regex(@"c.u.r.r.e.n.t. .u.s.e.r(?![\s\S]*?[a-z])", RegexOptions.IgnoreCase) });
|
||||
|
||||
FilesHeader.Add("avi", new byte[] { 65, 86, 73, 32 });
|
||||
FilesHeader.Add("mpg", new byte[] { 0, 0, 1, 0xBA });
|
||||
FilesHeader.Add("mpeg", new byte[] { 0, 0, 1, 0xB3 });
|
||||
FilesHeader.Add("rar", new byte[] { 82, 97, 114, 33, 26, 7 });
|
||||
FilesHeader.Add("zip", new byte[] { 80, 75, 3, 4 });
|
||||
|
||||
VideoHeader.Add("avi", new byte[] { 65, 86, 73, 32 });
|
||||
VideoHeader.Add("mpg", new byte[] { 0, 0, 1, 0xBA });
|
||||
VideoHeader.Add("mpeg", new byte[] { 0, 0, 1, 0xB3 });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检测视频类型扩展方法
|
||||
/// </summary>
|
||||
public static string VideoType(this Stream stream)
|
||||
{
|
||||
if (stream == null) throw new ArgumentNullException(nameof(stream));
|
||||
foreach (var kv in VideoHeader)
|
||||
{
|
||||
if (TryMatchHeader(stream, kv.Value, out var matched))
|
||||
{
|
||||
if (matched)
|
||||
return kv.Key;
|
||||
}
|
||||
}
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检测常规文件类型扩展方法
|
||||
/// </summary>
|
||||
public static string FileType(this Stream stream)
|
||||
{
|
||||
if (stream == null) throw new ArgumentNullException(nameof(stream));
|
||||
foreach (var kv in FilesHeader)
|
||||
{
|
||||
if (TryMatchHeader(stream, kv.Value, out var matched))
|
||||
{
|
||||
if (matched)
|
||||
return kv.Key;
|
||||
}
|
||||
}
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检测图片类型扩展方法
|
||||
/// </summary>
|
||||
public static string ImageType(this Stream stream)
|
||||
{
|
||||
if (stream == null) throw new ArgumentNullException(nameof(stream));
|
||||
foreach (var kv in ImageHeader)
|
||||
{
|
||||
var header = kv.Value;
|
||||
if (header == null || header.Length == 0) continue;
|
||||
|
||||
if (ReadAndCompare(stream, header))
|
||||
return kv.Key;
|
||||
}
|
||||
|
||||
// 额外尝试判断是否是纯文本(防止误判),若为纯文本则返回空
|
||||
// 只读取流内容进行检查,谨慎处理大流(可能会分配较大内存)
|
||||
try
|
||||
{
|
||||
var content = stream.ReadAllBytesAndRestorePosition();
|
||||
if (content.Length > 0)
|
||||
{
|
||||
var encodings = new[] { Encoding.ASCII, Encoding.UTF8 };
|
||||
foreach (var enc in encodings)
|
||||
{
|
||||
var text = enc.GetString(content);
|
||||
if (Regex.IsMatch(text, @"^[^\u0000-\u0008\u000B-\u000C\u000E-\u001F]*$"))
|
||||
{
|
||||
// 认为是文本,非图片
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
// Windows-936 编码仅在支持 CodePages 的情况下尝试使用
|
||||
try
|
||||
{
|
||||
var cp936 = Encoding.GetEncoding(936);
|
||||
var text936 = cp936.GetString(content);
|
||||
if (Regex.IsMatch(text936, @"^[^\u0000-\u0008\u000B-\u000C\u000E-\u001F]*$"))
|
||||
return string.Empty;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// 在不支持 CodePages 的平台上忽略
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// 忽略检查错误,回退为无法识别
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将流完整读取为字节数组(更高效且保持原位置)
|
||||
/// </summary>
|
||||
public static byte[] StreamToBytes(this Stream stream)
|
||||
{
|
||||
if (stream == null) throw new ArgumentNullException(nameof(stream));
|
||||
return stream.ReadAllBytesAndRestorePosition();
|
||||
}
|
||||
|
||||
// ---------- 辅助方法 ----------
|
||||
|
||||
/// <summary>
|
||||
/// 根据字节头或复杂描述(object[])进行匹配判断。
|
||||
/// object 类型支持:
|
||||
/// - byte[] : 仅比较头部字节
|
||||
/// - object[] : 第一个元素为 byte[] 头部;第二个元素可以为 Regex 或 int(表示尾部偏移),后面可跟要比较的尾部 byte[] 列表
|
||||
/// </summary>
|
||||
private static bool TryMatchHeader(Stream stream, object value, out bool matched)
|
||||
{
|
||||
matched = false;
|
||||
if (value == null) return false;
|
||||
|
||||
if (value is byte[] headerOnly)
|
||||
{
|
||||
matched = ReadAndCompare(stream, headerOnly);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (value is object[] arr && arr.Length > 0 && arr[0] is byte[] header)
|
||||
{
|
||||
// 先比较头部
|
||||
if (!ReadAndCompare(stream, header))
|
||||
return true; // header no match -> not this type
|
||||
|
||||
// 若仅头部匹配,后续根据第二元素进一步校验
|
||||
if (arr.Length >= 2)
|
||||
{
|
||||
var second = arr[1];
|
||||
if (second is Regex regex)
|
||||
{
|
||||
var content = stream.ReadAllBytesAndRestorePosition();
|
||||
var text = Encoding.ASCII.GetString(content);
|
||||
matched = regex.IsMatch(text);
|
||||
return true;
|
||||
}
|
||||
else if (second is int tailOffset)
|
||||
{
|
||||
// arr[2..] 为若干尾部字节数组,比对任一相等则通过
|
||||
for (int i = 2; i < arr.Length; i++)
|
||||
{
|
||||
if (arr[i] is byte[] tailBytes)
|
||||
{
|
||||
if (ReadAndCompareTail(stream, tailOffset, tailBytes))
|
||||
{
|
||||
matched = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
matched = false;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 未知第二元素类型,视为仅头匹配
|
||||
matched = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
matched = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从流头部读取与目标字节数组比较,比较完成后恢复流位置
|
||||
/// </summary>
|
||||
private static bool ReadAndCompare(Stream stream, byte[] target)
|
||||
{
|
||||
if (target == null || target.Length == 0) return false;
|
||||
var originalPos = stream.CanSeek ? stream.Position : (long?)null;
|
||||
try
|
||||
{
|
||||
var buffer = new byte[target.Length];
|
||||
if (!ReadFull(stream, buffer, 0, buffer.Length))
|
||||
return false;
|
||||
return buffer.SequenceEqual(target);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (originalPos.HasValue)
|
||||
stream.Position = originalPos.Value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从流尾部根据偏移读取并比较
|
||||
/// </summary>
|
||||
private static bool ReadAndCompareTail(Stream stream, int offsetFromEnd, byte[] target)
|
||||
{
|
||||
if (target == null || target.Length == 0) return false;
|
||||
if (!stream.CanSeek) return false;
|
||||
var originalPos = stream.Position;
|
||||
try
|
||||
{
|
||||
if (stream.Length < offsetFromEnd + target.Length) return false;
|
||||
stream.Position = stream.Length - offsetFromEnd;
|
||||
var buffer = new byte[target.Length];
|
||||
if (!ReadFull(stream, buffer, 0, buffer.Length)) return false;
|
||||
return buffer.SequenceEqual(target);
|
||||
}
|
||||
finally
|
||||
{
|
||||
stream.Position = originalPos;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从流中读取指定长度的数据,直到读取到要求长度或 EOF(返回是否读取到完整长度)
|
||||
/// </summary>
|
||||
private static bool ReadFull(Stream stream, byte[] buffer, int offset, int count)
|
||||
{
|
||||
if (count <= 0) return true;
|
||||
int read;
|
||||
int total = 0;
|
||||
while (total < count && (read = stream.Read(buffer, offset + total, count - total)) > 0)
|
||||
{
|
||||
total += read;
|
||||
}
|
||||
return total == count;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将流全部读取为字节数组并尝试恢复原始 Position(若支持)
|
||||
/// </summary>
|
||||
private static byte[] ReadAllBytesAndRestorePosition(this Stream stream)
|
||||
{
|
||||
var originalPos = stream.CanSeek ? stream.Position : (long?)null;
|
||||
try
|
||||
{
|
||||
using var ms = new MemoryStream();
|
||||
stream.CopyTo(ms);
|
||||
return ms.ToArray();
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (originalPos.HasValue)
|
||||
stream.Position = originalPos.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
47
Atomx.Utils/Images/ImageType.cs
Normal file
47
Atomx.Utils/Images/ImageType.cs
Normal file
@@ -0,0 +1,47 @@
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace Atomx.Utils.Images
|
||||
{
|
||||
/// <summary>
|
||||
/// 图片类型
|
||||
/// </summary>
|
||||
[Obsolete("废弃")]
|
||||
public enum ImageType
|
||||
{
|
||||
/// <summary>
|
||||
/// .jpg
|
||||
/// </summary>
|
||||
[Description(".jpg")]
|
||||
Jpg,
|
||||
/// <summary>
|
||||
/// .jpeg
|
||||
/// </summary>
|
||||
[Description(".jpeg")]
|
||||
Jpeg,
|
||||
/// <summary>
|
||||
/// .png
|
||||
/// </summary>
|
||||
[Description(".png")]
|
||||
Png,
|
||||
/// <summary>
|
||||
/// .gif
|
||||
/// </summary>
|
||||
[Description(".gif")]
|
||||
Gif,
|
||||
/// <summary>
|
||||
/// .svg
|
||||
/// </summary>
|
||||
[Description(".svg")]
|
||||
Svg,
|
||||
/// <summary>
|
||||
/// .bmp
|
||||
/// </summary>
|
||||
[Description(".bmp")]
|
||||
Bmp,
|
||||
/// <summary>
|
||||
/// .webp
|
||||
/// </summary>
|
||||
[Description(".webp")]
|
||||
Webp
|
||||
}
|
||||
}
|
||||
36
Atomx.Utils/Json/Converts/ClaimConverter.cs
Normal file
36
Atomx.Utils/Json/Converts/ClaimConverter.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
using System.Security.Claims;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Atomx.Utils.Json.Converts
|
||||
{
|
||||
public class ClaimConverter : JsonConverter<Claim>
|
||||
{
|
||||
public override bool CanConvert(Type typeToConvert)
|
||||
{
|
||||
return typeToConvert == typeof(Claim);
|
||||
}
|
||||
|
||||
public override Claim Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
using (var jsonDocument = JsonDocument.ParseValue(ref reader))
|
||||
{
|
||||
var text = jsonDocument.RootElement.GetRawText();
|
||||
var data = JsonSerializer.Deserialize<ClaimLite>(text, new JsonSerializerOptions() { PropertyNameCaseInsensitive = true });
|
||||
return new Claim(data?.Type ?? string.Empty, data?.Value ?? string.Empty, data?.ValueType ?? string.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
public override void Write(Utf8JsonWriter writer, Claim value, JsonSerializerOptions options)
|
||||
{
|
||||
JsonSerializer.Serialize(writer, value);
|
||||
}
|
||||
}
|
||||
|
||||
public class ClaimLite
|
||||
{
|
||||
public string Type { get; set; } = string.Empty;
|
||||
public string Value { get; set; } = string.Empty;
|
||||
public string ValueType { get; set; } = string.Empty;
|
||||
}
|
||||
}
|
||||
33
Atomx.Utils/Json/Converts/DateTimeJsonConverter.cs
Normal file
33
Atomx.Utils/Json/Converts/DateTimeJsonConverter.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Atomx.Utils.Json.Converts
|
||||
{
|
||||
public class DateTimeJsonConverter : System.Text.Json.Serialization.JsonConverter<DateTime>
|
||||
{
|
||||
readonly string _dateFormatString;
|
||||
public DateTimeJsonConverter()
|
||||
{
|
||||
_dateFormatString = "yyyy-MM-dd HH:mm:ss";
|
||||
}
|
||||
|
||||
public DateTimeJsonConverter(string dateFormatString)
|
||||
{
|
||||
_dateFormatString = dateFormatString;
|
||||
}
|
||||
|
||||
public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
var text = reader.GetString();
|
||||
if (string.IsNullOrEmpty(text))
|
||||
{
|
||||
return default;
|
||||
}
|
||||
return DateTime.Parse(text);
|
||||
}
|
||||
|
||||
public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
|
||||
{
|
||||
writer.WriteStringValue(value.ToString("s"));
|
||||
}
|
||||
}
|
||||
}
|
||||
33
Atomx.Utils/Json/Converts/LongJsonConverter.cs
Normal file
33
Atomx.Utils/Json/Converts/LongJsonConverter.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
using System.Buffers;
|
||||
using System.Buffers.Text;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Atomx.Utils.Json.Converts
|
||||
{
|
||||
public class LongJsonConverter : System.Text.Json.Serialization.JsonConverter<long>
|
||||
{
|
||||
public override long Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
if (reader.TokenType == JsonTokenType.String)
|
||||
{
|
||||
ReadOnlySpan<byte> span = reader.HasValueSequence ? reader.ValueSequence.ToArray() : reader.ValueSpan;
|
||||
if (Utf8Parser.TryParse(span, out long number, out int bytesConsumed) && span.Length == bytesConsumed)
|
||||
{
|
||||
return number;
|
||||
}
|
||||
|
||||
if (long.TryParse(reader.GetString(), out number))
|
||||
{
|
||||
return number;
|
||||
}
|
||||
}
|
||||
|
||||
return reader.GetInt64();
|
||||
}
|
||||
|
||||
public override void Write(Utf8JsonWriter writer, long value, JsonSerializerOptions options)
|
||||
{
|
||||
writer.WriteStringValue(value.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
26
Atomx.Utils/Json/JsonOptions.cs
Normal file
26
Atomx.Utils/Json/JsonOptions.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
using Atomx.Utils.Json.Converts;
|
||||
using System.Text.Encodings.Web;
|
||||
using System.Text.Json;
|
||||
using System.Text.Unicode;
|
||||
|
||||
namespace Atomx.Utils.Json
|
||||
{
|
||||
public class JsonOptions
|
||||
{
|
||||
public static JsonSerializerOptions DefaultOptions()
|
||||
{
|
||||
|
||||
var opts = new JsonSerializerOptions
|
||||
{
|
||||
PropertyNameCaseInsensitive = true,
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
WriteIndented = true,
|
||||
Encoder = JavaScriptEncoder.Create(UnicodeRanges.All)
|
||||
};
|
||||
opts.Converters.Add(new LongJsonConverter());
|
||||
opts.Converters.Add(new DateTimeJsonConverter());
|
||||
opts.Converters.Add(new ClaimConverter());
|
||||
return opts;
|
||||
}
|
||||
}
|
||||
}
|
||||
30
Atomx.Utils/Json/JsonSerialize.cs
Normal file
30
Atomx.Utils/Json/JsonSerialize.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Atomx.Utils.Json
|
||||
{
|
||||
public static class JsonSerialize
|
||||
{
|
||||
public static string ToJson(this object obj, JsonSerializerOptions? options = null)
|
||||
{
|
||||
var opts = JsonOptions.DefaultOptions();
|
||||
if (options != null)
|
||||
{
|
||||
opts = options;
|
||||
}
|
||||
var data = JsonSerializer.Serialize(obj, opts);
|
||||
return data;
|
||||
}
|
||||
|
||||
public static T? FromJson<T>(this string json, JsonSerializerOptions? options = null)
|
||||
{
|
||||
var opts = JsonOptions.DefaultOptions();
|
||||
if (options != null)
|
||||
{
|
||||
opts = options;
|
||||
}
|
||||
|
||||
var data = JsonSerializer.Deserialize<T>(json, opts);
|
||||
return data;
|
||||
}
|
||||
}
|
||||
}
|
||||
25
Atomx.Utils/Models/Result.cs
Normal file
25
Atomx.Utils/Models/Result.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
namespace Atomx.Utils.Models
|
||||
{
|
||||
public class Result<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// 数据
|
||||
/// </summary>
|
||||
public T? Data { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 消息
|
||||
/// </summary>
|
||||
public string Message { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 业务代码
|
||||
/// </summary>
|
||||
public int Code { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 业务是否成功
|
||||
/// </summary>
|
||||
public bool Success { get; set; }
|
||||
}
|
||||
}
|
||||
19
Atomx.Utils/Models/UploadResult.cs
Normal file
19
Atomx.Utils/Models/UploadResult.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Atomx.Utils.Models
|
||||
{
|
||||
public class UploadResult
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public string Url { get; set; } = string.Empty;
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public long Size { get; set; }
|
||||
public string Type { get; set; } = string.Empty;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user