添加项目文件。
This commit is contained in:
1 parent
6a017c9547
commit
58abbb5b4f
86 files changed
+8729
No files matched your search
@@ -0,0 +1,99 @@
|
||||
using System;
|
||||
|
||||
namespace MAX.Core.Caches;
|
||||
|
||||
/// <summary>
|
||||
/// 缓存
|
||||
/// </summary>
|
||||
public interface ICache
|
||||
{
|
||||
/// <summary>
|
||||
/// 是否存在指定键的缓存
|
||||
/// </summary>
|
||||
/// <param name="key">缓存键</param>
|
||||
bool Exists(string key);
|
||||
/// <summary>
|
||||
/// 从缓存中获取数据
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
/// <returns></returns>
|
||||
T Get<T>(string key);
|
||||
/// <summary>
|
||||
/// 从缓存中获取数据,如果不存在,则执行获取数据操作并添加到缓存中
|
||||
/// </summary>
|
||||
/// <typeparam name="T">缓存数据类型</typeparam>
|
||||
/// <param name="key">缓存键</param>
|
||||
/// <param name="func">获取数据操作</param>
|
||||
T Get<T>(string key, Func<T> func);
|
||||
/// <summary>
|
||||
/// 当缓存数据不存在则添加,已存在不会添加,添加成功返回true
|
||||
/// </summary>
|
||||
/// <typeparam name="T">缓存数据类型</typeparam>
|
||||
/// <param name="key">缓存键</param>
|
||||
/// <param name="value">值</param>
|
||||
bool TryAdd<T>(string key, T value);
|
||||
/// <summary>
|
||||
/// 覆盖添加
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="key"></param>
|
||||
/// <param name="value"></param>
|
||||
/// <returns></returns>
|
||||
bool OverlayAdd<T>(string key, T value);
|
||||
/// <summary>
|
||||
/// 移除缓存
|
||||
/// </summary>
|
||||
/// <param name="key">缓存键</param>
|
||||
void Remove(string key);
|
||||
/// <summary>
|
||||
/// 清空缓存
|
||||
/// </summary>
|
||||
void Clear();
|
||||
}
|
||||
|
||||
public interface ICache<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// 是否存在指定键的缓存
|
||||
/// </summary>
|
||||
/// <param name="key">缓存键</param>
|
||||
bool Exists(string key);
|
||||
|
||||
/// <summary>
|
||||
/// 从缓存中获取数据,如果不存在,则执行获取数据操作并添加到缓存中
|
||||
/// </summary>
|
||||
/// <typeparam name="T">缓存数据类型</typeparam>
|
||||
/// <param name="key">缓存键</param>
|
||||
T Get(string key);
|
||||
/// <summary>
|
||||
/// 从缓存中获取数据,如果不存在,则执行获取数据操作并添加到缓存中
|
||||
/// </summary>
|
||||
/// <typeparam name="T">缓存数据类型</typeparam>
|
||||
/// <param name="key">缓存键</param>
|
||||
/// <param name="func">获取数据操作</param>
|
||||
T Get(string key, Func<T> func);
|
||||
/// <summary>
|
||||
/// 当缓存数据不存在则添加,已存在不会添加,添加成功返回true
|
||||
/// </summary>
|
||||
/// <typeparam name="T">缓存数据类型</typeparam>
|
||||
/// <param name="key">缓存键</param>
|
||||
/// <param name="value">值</param>
|
||||
bool TryAdd(string key, T value);
|
||||
/// <summary>
|
||||
/// 覆盖添加
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="key"></param>
|
||||
/// <param name="value"></param>
|
||||
/// <returns></returns>
|
||||
bool OverlayAdd(string key, T value);
|
||||
/// <summary>
|
||||
/// 移除缓存
|
||||
/// </summary>
|
||||
/// <param name="key">缓存键</param>
|
||||
void Remove(string key);
|
||||
/// <summary>
|
||||
/// 清空缓存
|
||||
/// </summary>
|
||||
void Clear();
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
|
||||
using MAX.Core.DependencyInjection;
|
||||
using MAX.Core.Infrastructure;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Linq;
|
||||
|
||||
namespace MAX.Core.Caches;
|
||||
|
||||
/// <summary>
|
||||
/// 内存缓存
|
||||
/// </summary>
|
||||
public class MemoryCache : ICache, ISingletonDependency
|
||||
{
|
||||
//private static readonly Object _locker = new object();
|
||||
private readonly ConcurrentDictionary<string, object> _cache = new();
|
||||
|
||||
public virtual void Clear()
|
||||
{
|
||||
_cache.Clear();
|
||||
}
|
||||
|
||||
public virtual bool Exists(string key)
|
||||
{
|
||||
return _cache.ContainsKey(key);
|
||||
}
|
||||
public virtual T Get<T>(string key)
|
||||
{
|
||||
if (Exists(key))
|
||||
{
|
||||
return (T)_cache[key];
|
||||
}
|
||||
return default;
|
||||
}
|
||||
public virtual TResult Get<TResult>(string key, Func<TResult> func)
|
||||
{
|
||||
if (Exists(key))
|
||||
{
|
||||
return (TResult)_cache[key];
|
||||
}
|
||||
else
|
||||
{
|
||||
if (func != null)
|
||||
{
|
||||
TResult t = func();
|
||||
TryAdd(key, t);
|
||||
return t;
|
||||
}
|
||||
}
|
||||
return default;
|
||||
}
|
||||
|
||||
public virtual bool OverlayAdd<T>(string key, T value)
|
||||
{
|
||||
if (Exists(key))
|
||||
{
|
||||
_cache[key] = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
_cache.TryAdd(key, value);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public virtual void Remove(string key)
|
||||
{
|
||||
_cache.TryRemove(key, out _);
|
||||
}
|
||||
|
||||
public virtual bool TryAdd<T>(string key, T value)
|
||||
{
|
||||
_cache.AddOrUpdate(key, value, (oldkey, oldvalue) => value);
|
||||
return true;
|
||||
}
|
||||
/// <summary>
|
||||
/// 获取所有Key
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public string[] AllKeys()
|
||||
{
|
||||
return _cache.Keys.ToArray();
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// 单例缓存抽象类
|
||||
/// </summary>
|
||||
/// <typeparam name="TModel">单例对象</typeparam>
|
||||
/// <typeparam name="TValue">缓存类型</typeparam>
|
||||
public abstract class SingleInstanceLittleCache<TModel, TValue> : SingleInstance<TModel>, ICache<TValue> where TModel : class
|
||||
{
|
||||
private readonly ConcurrentDictionary<string, TValue> _cache = new();
|
||||
/// <summary>
|
||||
/// 自定义前缀
|
||||
/// </summary>
|
||||
//public abstract string KeyPrefix { get; }
|
||||
//private string FormatKey(string key)
|
||||
//{
|
||||
// return KeyPrefix + key;
|
||||
//}
|
||||
|
||||
public virtual void Clear()
|
||||
{
|
||||
_cache.Clear();
|
||||
}
|
||||
|
||||
public virtual bool Exists(string key)
|
||||
{
|
||||
return _cache.ContainsKey(key);
|
||||
}
|
||||
public virtual TValue Get(string key)
|
||||
{
|
||||
if (Exists(key))
|
||||
{
|
||||
return _cache[key];
|
||||
}
|
||||
return default;
|
||||
}
|
||||
public virtual TValue Get(string key, Func<TValue> func)
|
||||
{
|
||||
if (Exists(key))
|
||||
{
|
||||
return _cache[key];
|
||||
}
|
||||
else
|
||||
{
|
||||
if (func != null)
|
||||
{
|
||||
TValue t = func();
|
||||
TryAdd(key, t);
|
||||
return t;
|
||||
}
|
||||
}
|
||||
return default;
|
||||
}
|
||||
|
||||
public virtual bool OverlayAdd(string key, TValue value)
|
||||
{
|
||||
if (Exists(key))
|
||||
{
|
||||
_cache[key] = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
_cache.TryAdd(key, value);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public virtual void Remove(string key)
|
||||
{
|
||||
_cache.TryRemove(key, out _);
|
||||
}
|
||||
|
||||
public virtual bool TryAdd(string key, TValue value)
|
||||
{
|
||||
_cache.AddOrUpdate(key, value, (oldkey, oldvalue) => value);
|
||||
return true;
|
||||
}
|
||||
/// <summary>
|
||||
/// 获取所有Key
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public string[] AllKeys()
|
||||
{
|
||||
return _cache.Keys.ToArray();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
namespace MAX.Core.Commons;
|
||||
|
||||
/// <summary>
|
||||
/// 公共Value
|
||||
/// </summary>
|
||||
/// <typeparam name="TValue"></typeparam>
|
||||
public class CommonValue<TValue>
|
||||
{
|
||||
public CommonValue() { }
|
||||
public CommonValue(TValue value)
|
||||
{
|
||||
Value = value;
|
||||
}
|
||||
public TValue Value { get; set; }
|
||||
}
|
||||
/// <summary>
|
||||
/// 公共TextValue
|
||||
/// </summary>
|
||||
/// <typeparam name="TValue"></typeparam>
|
||||
public class CommonTextValue<TValue> : CommonValue<TValue>
|
||||
{
|
||||
public string Text { get; set; }
|
||||
}
|
||||
/// <summary>
|
||||
/// 公共TextValue
|
||||
/// </summary>
|
||||
public class CommonTextValue : CommonTextValue<string>
|
||||
{
|
||||
|
||||
}
|
||||
/// <summary>
|
||||
/// 默认公共键值对 Key Value
|
||||
/// </summary>
|
||||
public class CommonKeyValue : CommonValue<string>
|
||||
{
|
||||
public string Key { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
using System;
|
||||
|
||||
namespace MAX.Core.Commons;
|
||||
|
||||
/// <summary>
|
||||
/// 通用返回
|
||||
/// </summary>
|
||||
public class Result
|
||||
{
|
||||
/// <summary>
|
||||
/// 返回结果是否成功
|
||||
/// </summary>
|
||||
public bool Success { get; set; } = false;
|
||||
/// <summary>
|
||||
/// 返回消息
|
||||
/// </summary>
|
||||
public string Msg { get; set; } = "";
|
||||
/// <summary>
|
||||
/// 返回内容
|
||||
/// </summary>
|
||||
public object Data { get; set; } = null;
|
||||
|
||||
/// <summary>
|
||||
/// 返回状态码
|
||||
/// </summary>
|
||||
public int Code { get; set; }
|
||||
|
||||
}
|
||||
/// <summary>
|
||||
/// 通用返回 指定Data类型
|
||||
/// </summary>
|
||||
/// <typeparam name="TData">数据类型</typeparam>
|
||||
public class Result<TData> : Result
|
||||
{
|
||||
/// <summary>
|
||||
/// 返回内容
|
||||
/// </summary>
|
||||
public new TData Data { get; set; }
|
||||
}
|
||||
/// <summary>
|
||||
/// 通用返回
|
||||
/// </summary>
|
||||
/// <typeparam name="TStatus">状态类型枚举</typeparam>
|
||||
/// <typeparam name="TData">数据类型</typeparam>
|
||||
public class Result<TStatus, TData> : Result where TStatus : Enum
|
||||
{
|
||||
/// <summary>
|
||||
/// 状态
|
||||
/// </summary>
|
||||
public TStatus Status { get; set; }
|
||||
/// <summary>
|
||||
/// 返回内容
|
||||
/// </summary>
|
||||
public new TData Data { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
|
||||
namespace MAX.Core.Cryptography;
|
||||
|
||||
public class Base64Helper
|
||||
{
|
||||
/// <summary>
|
||||
/// Base64加密
|
||||
/// </summary>
|
||||
/// <param name="codeName">加密采用的编码方式</param>
|
||||
/// <param name="source">待加密的明文</param>
|
||||
/// <returns></returns>
|
||||
public static string Encode(string source, Encoding encode)
|
||||
{
|
||||
string encodeValue = "";
|
||||
byte[] bytes = encode.GetBytes(source);
|
||||
try
|
||||
{
|
||||
encodeValue = Convert.ToBase64String(bytes);
|
||||
}
|
||||
catch
|
||||
{
|
||||
encodeValue = source;
|
||||
}
|
||||
return encodeValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Base64加密,采用utf8编码方式加密
|
||||
/// </summary>
|
||||
/// <param name="source">待加密的明文</param>
|
||||
/// <returns>加密后的字符串</returns>
|
||||
public static string Encode(string source)
|
||||
{
|
||||
return Encode(source, Encoding.UTF8);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Base64解密
|
||||
/// </summary>
|
||||
/// <param name="codeName">解密采用的编码方式,注意和加密时采用的方式一致</param>
|
||||
/// <param name="result">待解密的密文</param>
|
||||
/// <returns>解密后的字符串</returns>
|
||||
public static string Decode(string result, Encoding encode)
|
||||
{
|
||||
string decode = "";
|
||||
byte[] bytes = Convert.FromBase64String(result);
|
||||
try
|
||||
{
|
||||
decode = encode.GetString(bytes);
|
||||
}
|
||||
catch
|
||||
{
|
||||
decode = result;
|
||||
}
|
||||
return decode;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Base64解密,采用utf8编码方式解密
|
||||
/// </summary>
|
||||
/// <param name="result">待解密的密文</param>
|
||||
/// <returns>解密后的字符串</returns>
|
||||
public static string Decode(string result)
|
||||
{
|
||||
return Decode(result, Encoding.UTF8);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,215 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace MAX.Core.Cryptography;
|
||||
|
||||
public sealed class DESHelper
|
||||
{
|
||||
|
||||
//默认密钥向量
|
||||
private static readonly byte[] IVKey = { 36, 109, 97, 120, 111, 110, 120, 36 };
|
||||
/// <summary>
|
||||
/// DES加密字符串
|
||||
/// </summary>
|
||||
/// <param name="encryptString">待加密的字符串</param>
|
||||
/// <param name="encryptKey">加密密钥,要求为8位</param>
|
||||
/// <returns>加密成功返回加密后的字符串,失败返回源串</returns>
|
||||
public static string Encrypt(string encryptString, string encryptKey)
|
||||
{
|
||||
try
|
||||
{
|
||||
encryptKey = GetSubString(encryptKey, 8, "");
|
||||
encryptKey = encryptKey.PadRight(8, ' ');
|
||||
byte[] rgbKey = Encoding.UTF8.GetBytes(encryptKey.Substring(0, 8));
|
||||
byte[] rgbIV = IVKey;
|
||||
byte[] inputByteArray = Encoding.UTF8.GetBytes(encryptString);
|
||||
DES dCSP = DES.Create();
|
||||
MemoryStream mStream = new();
|
||||
CryptoStream cStream = new(mStream, dCSP.CreateEncryptor(rgbKey, rgbIV), CryptoStreamMode.Write);
|
||||
cStream.Write(inputByteArray, 0, inputByteArray.Length);
|
||||
cStream.FlushFinalBlock();
|
||||
return Convert.ToBase64String(mStream.ToArray());
|
||||
}
|
||||
catch
|
||||
{
|
||||
return encryptString;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// DES解密字符串
|
||||
/// </summary>
|
||||
/// <param name="decryptString">待解密的字符串</param>
|
||||
/// <param name="decryptKey">解密密钥,要求为8位,和加密密钥相同</param>
|
||||
/// <returns>解密成功返回解密后的字符串,失败返源串</returns>
|
||||
public static string Decrypt(string decryptString, string decryptKey)
|
||||
{
|
||||
try
|
||||
{
|
||||
decryptKey = GetSubString(decryptKey, 0, 8, "");
|
||||
decryptKey = decryptKey.PadRight(8, ' ');
|
||||
byte[] rgbKey = Encoding.UTF8.GetBytes(decryptKey);
|
||||
byte[] rgbIV = IVKey;
|
||||
byte[] inputByteArray = Convert.FromBase64String(decryptString);
|
||||
DES DCSP = DES.Create();
|
||||
|
||||
MemoryStream mStream = new();
|
||||
CryptoStream cStream = new(mStream, DCSP.CreateDecryptor(rgbKey, rgbIV), CryptoStreamMode.Write);
|
||||
cStream.Write(inputByteArray, 0, inputByteArray.Length);
|
||||
cStream.FlushFinalBlock();
|
||||
return Encoding.UTF8.GetString(mStream.ToArray());
|
||||
}
|
||||
catch
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#region 截取字符串函数
|
||||
|
||||
/// <summary>
|
||||
/// 截取字符串函数
|
||||
/// </summary>
|
||||
/// <param name="Str">所要截取的字符串</param>
|
||||
/// <param name="Num">截取字符串的长度</param>
|
||||
/// <returns></returns>
|
||||
public static string GetSubString(string Str, int Num)
|
||||
{
|
||||
if (Str is null or "")
|
||||
{
|
||||
return "";
|
||||
}
|
||||
string outstr = string.Empty;
|
||||
int n = 0;
|
||||
foreach (char ch in Str)
|
||||
{
|
||||
n += Encoding.Default.GetByteCount(ch.ToString());
|
||||
|
||||
if (n > Num)
|
||||
{
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
outstr += ch;
|
||||
}
|
||||
}
|
||||
|
||||
return outstr;
|
||||
}
|
||||
/// <summary>
|
||||
/// 截取字符串函数,如果超过指定长度则将超出的部分用指定字符串代替
|
||||
/// </summary>
|
||||
/// <param name="Str">所要截取的字符串</param>
|
||||
/// <param name="Num">截取字符串的长度</param>
|
||||
/// <param name="LastStr">截取字符串后省略部分的字符串</param>
|
||||
/// <returns></returns>
|
||||
public static string GetSubString(string Str, int Num, string LastStr)
|
||||
{
|
||||
return GetSubString(Str, 0, Num, LastStr);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 截取字符串,超过部分用指定字符串替换
|
||||
|
||||
/* 宗波于 2010-6-12 日添加, 来源于DiscuzNt */
|
||||
/// <summary>
|
||||
/// 取指定长度的字符串
|
||||
/// </summary>
|
||||
/// <param name="p_SrcString">要检查的字符串</param>
|
||||
/// <param name="p_StartIndex">起始位置</param>
|
||||
/// <param name="p_Length">指定长度</param>
|
||||
/// <param name="p_TailString">用于替换的字符串</param>
|
||||
/// <returns>截取后的字符串</returns>
|
||||
public static string GetSubString(string p_SrcString, int p_StartIndex, int p_Length, string p_TailString)
|
||||
{
|
||||
string myResult = p_SrcString;
|
||||
|
||||
byte[] bComments = Encoding.UTF8.GetBytes(p_SrcString);
|
||||
foreach (char c in Encoding.UTF8.GetChars(bComments))
|
||||
{ //当是日文或韩文时(注:中文的范围:\u4e00 - \u9fa5, 日文在\u0800 - \u4e00, 韩文为\xAC00-\xD7A3)
|
||||
if (c is (> '\u0800' and < '\u4e00') or (> '\xAC00' and < '\xD7A3'))
|
||||
{
|
||||
//if (System.Text.RegularExpressions.Regex.IsMatch(p_SrcString, "[\u0800-\u4e00]+") || System.Text.RegularExpressions.Regex.IsMatch(p_SrcString, "[\xAC00-\xD7A3]+"))
|
||||
//当截取的起始位置超出字段串长度时
|
||||
if (p_StartIndex >= p_SrcString.Length)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
else
|
||||
{
|
||||
return p_SrcString.Substring(p_StartIndex,
|
||||
p_Length + p_StartIndex > p_SrcString.Length ? p_SrcString.Length - p_StartIndex : p_Length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (p_Length >= 0)
|
||||
{
|
||||
byte[] bsSrcString = Encoding.Default.GetBytes(p_SrcString);
|
||||
|
||||
//当字符串长度大于起始位置
|
||||
if (bsSrcString.Length > p_StartIndex)
|
||||
{
|
||||
int p_EndIndex = bsSrcString.Length;
|
||||
|
||||
//当要截取的长度在字符串的有效长度范围内
|
||||
if (bsSrcString.Length > p_StartIndex + p_Length)
|
||||
{
|
||||
p_EndIndex = p_Length + p_StartIndex;
|
||||
}
|
||||
else
|
||||
{ //当不在有效范围内时,只取到字符串的结尾
|
||||
|
||||
p_Length = bsSrcString.Length - p_StartIndex;
|
||||
p_TailString = "";
|
||||
}
|
||||
|
||||
int nRealLength = p_Length;
|
||||
int[] anResultFlag = new int[p_Length];
|
||||
byte[] bsResult = null;
|
||||
|
||||
int nFlag = 0;
|
||||
for (int i = p_StartIndex; i < p_EndIndex; i++)
|
||||
{
|
||||
if (bsSrcString[i] > 127)
|
||||
{
|
||||
nFlag++;
|
||||
if (nFlag == 3)
|
||||
{
|
||||
nFlag = 1;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
nFlag = 0;
|
||||
}
|
||||
|
||||
anResultFlag[i] = nFlag;
|
||||
}
|
||||
|
||||
if (bsSrcString[p_EndIndex - 1] > 127 && anResultFlag[p_Length - 1] == 1)
|
||||
{
|
||||
nRealLength = p_Length + 1;
|
||||
}
|
||||
|
||||
bsResult = new byte[nRealLength];
|
||||
|
||||
Array.Copy(bsSrcString, p_StartIndex, bsResult, 0, nRealLength);
|
||||
|
||||
myResult = Encoding.Default.GetString(bsResult);
|
||||
myResult += p_TailString;
|
||||
}
|
||||
}
|
||||
|
||||
return myResult;
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
using System;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace MAX.Core.Cryptography;
|
||||
|
||||
public class MD5Helper
|
||||
{
|
||||
/// <summary>
|
||||
/// MD5加密
|
||||
/// </summary>
|
||||
/// <param name="instring"></param>
|
||||
/// <returns></returns>
|
||||
public static string Md5_32(string instring)
|
||||
{
|
||||
return _Md532(instring);
|
||||
}
|
||||
public static string Md5_Lower32(string instring)
|
||||
{
|
||||
return _Md532(instring, true);
|
||||
}
|
||||
public static string _Md532(string instring, bool IsLower = false)
|
||||
{
|
||||
byte[] result = Encoding.UTF8.GetBytes(string.IsNullOrEmpty(instring) ? "" : instring.Trim());
|
||||
MD5 md5 = MD5.Create();
|
||||
byte[] output = md5.ComputeHash(result);
|
||||
string value = BitConverter.ToString(output).Replace("-", "");
|
||||
if (IsLower)
|
||||
{
|
||||
return value.ToLower();
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// MD5加密
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static long GetLong(byte[] result)
|
||||
{
|
||||
return
|
||||
Convert.ToInt64(
|
||||
BitConverter.ToString(MD5.Create().ComputeHash(result), 4,
|
||||
8).Replace("-", ""), 16);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// MD5 16位加密转long
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <returns></returns>
|
||||
public static long GetLong(string url)
|
||||
{
|
||||
if (string.IsNullOrEmpty(url))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return Convert.ToInt64(BitConverter.ToString(MD5.Create().ComputeHash(Encoding.Default.GetBytes(url)), 4, 8).Replace("-", ""), 16);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
using System.Text;
|
||||
|
||||
namespace MAX.Core.Cryptography;
|
||||
|
||||
/// <summary>
|
||||
/// Url 加密 解密
|
||||
/// </summary>
|
||||
public class UrlHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Url加密
|
||||
/// </summary>
|
||||
/// <param name="codeName">加密采用的编码方式</param>
|
||||
/// <param name="source">待加密的明文</param>
|
||||
/// <returns></returns>
|
||||
public static string Encode(string source, Encoding encode)
|
||||
{
|
||||
return System.Web.HttpUtility.UrlEncode(source, encode);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Url加密,采用utf8编码方式加密
|
||||
/// </summary>
|
||||
/// <param name="source">待加密的明文</param>
|
||||
/// <returns>加密后的字符串</returns>
|
||||
public static string Encode(string source)
|
||||
{
|
||||
return Encode(source, Encoding.UTF8);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Url解密
|
||||
/// </summary>
|
||||
/// <param name="codeName">解密采用的编码方式,注意和加密时采用的方式一致</param>
|
||||
/// <param name="result">待解密的密文</param>
|
||||
/// <returns>解密后的字符串</returns>
|
||||
public static string Decode(string result, Encoding encode)
|
||||
{
|
||||
return System.Web.HttpUtility.UrlDecode(result, Encoding.UTF8);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Url解密,采用utf8编码方式解密
|
||||
/// </summary>
|
||||
/// <param name="result">待解密的密文</param>
|
||||
/// <returns>解密后的字符串</returns>
|
||||
public static string Decode(string result)
|
||||
{
|
||||
return Decode(result, Encoding.UTF8);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace MAX.Core.DependencyInjection;
|
||||
|
||||
/// <summary>
|
||||
/// 依赖注册器:把当前模块的服务注册到 <see cref="IServiceRegistrar"/>。
|
||||
/// 底层使用哪个容器由宿主项目决定,MAX.Core 不引用任何容器。
|
||||
/// </summary>
|
||||
public interface IDependencyRegistrar
|
||||
{
|
||||
/// <summary>
|
||||
/// 注册服务
|
||||
/// </summary>
|
||||
void Register(IServiceRegistrar registrar);
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace MAX.Core.DependencyInjection;
|
||||
|
||||
/// <summary>
|
||||
/// 作用域(每个 Scope/请求一个实例)
|
||||
/// </summary>
|
||||
public interface IScopedDependency
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
using System;
|
||||
|
||||
namespace MAX.Core.DependencyInjection;
|
||||
|
||||
/// <summary>
|
||||
/// 依赖注册抽象(由 MAX.Core 自己定义,不依赖任何第三方 DI 容器)。
|
||||
/// 具体实现由宿主项目提供,内部适配外部容器(Autofac、Microsoft.Extensions.DependencyInjection 等)。
|
||||
/// </summary>
|
||||
public interface IServiceRegistrar
|
||||
{
|
||||
/// <summary>
|
||||
/// 注册单例:<typeparamref name="TImplementation"/> 实现 <typeparamref name="TService"/>
|
||||
/// </summary>
|
||||
void RegisterSingleton<TService, TImplementation>() where TService : class where TImplementation : class, TService;
|
||||
|
||||
/// <summary>
|
||||
/// 注册单例实例
|
||||
/// </summary>
|
||||
void RegisterSingleton<TService>(TService instance) where TService : class;
|
||||
|
||||
/// <summary>
|
||||
/// 注册单例(匿名工厂)
|
||||
/// </summary>
|
||||
void RegisterSingleton<TService>(Func<IServiceProvider, TService> factory) where TService : class;
|
||||
|
||||
/// <summary>
|
||||
/// 注册单例(key):<typeparamref name="TImplementation"/> 实现 <typeparamref name="TService"/>
|
||||
/// </summary>
|
||||
void RegisterSingleton<TService, TImplementation>(object? serviceKey) where TService : class where TImplementation : class, TService;
|
||||
|
||||
/// <summary>
|
||||
/// 注册单例实例(key)
|
||||
/// </summary>
|
||||
void RegisterSingleton<TService>(object? serviceKey, TService instance) where TService : class;
|
||||
|
||||
/// <summary>
|
||||
/// 注册单例(key + 匿名工厂)
|
||||
/// </summary>
|
||||
void RegisterSingleton<TService>(object? serviceKey, Func<IServiceProvider, object?, TService> factory) where TService : class;
|
||||
|
||||
/// <summary>
|
||||
/// 注册作用域生命周期
|
||||
/// </summary>
|
||||
void RegisterScoped<TService, TImplementation>() where TService : class where TImplementation : class, TService;
|
||||
|
||||
/// <summary>
|
||||
/// 注册作用域生命周期(匿名工厂)
|
||||
/// </summary>
|
||||
void RegisterScoped<TService>(Func<IServiceProvider, TService> factory) where TService : class;
|
||||
|
||||
/// <summary>
|
||||
/// 注册作用域生命周期(key):<typeparamref name="TImplementation"/> 实现 <typeparamref name="TService"/>
|
||||
/// </summary>
|
||||
void RegisterScoped<TService, TImplementation>(object? serviceKey) where TService : class where TImplementation : class, TService;
|
||||
|
||||
/// <summary>
|
||||
/// 注册作用域生命周期(key + 匿名工厂)
|
||||
/// </summary>
|
||||
void RegisterScoped<TService>(object? serviceKey, Func<IServiceProvider, object?, TService> factory) where TService : class;
|
||||
|
||||
/// <summary>
|
||||
/// 注册瞬时生命周期
|
||||
/// </summary>
|
||||
void RegisterTransient<TService, TImplementation>() where TService : class where TImplementation : class, TService;
|
||||
|
||||
/// <summary>
|
||||
/// 注册瞬时生命周期(匿名工厂)
|
||||
/// </summary>
|
||||
void RegisterTransient<TService>(Func<IServiceProvider, TService> factory) where TService : class;
|
||||
|
||||
/// <summary>
|
||||
/// 注册瞬时生命周期(key):<typeparamref name="TImplementation"/> 实现 <typeparamref name="TService"/>
|
||||
/// </summary>
|
||||
void RegisterTransient<TService, TImplementation>(object? serviceKey) where TService : class where TImplementation : class, TService;
|
||||
|
||||
/// <summary>
|
||||
/// 注册瞬时生命周期(key + 匿名工厂)
|
||||
/// </summary>
|
||||
void RegisterTransient<TService>(object? serviceKey, Func<IServiceProvider, object?, TService> factory) where TService : class;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace MAX.Core.DependencyInjection;
|
||||
|
||||
/// <summary>
|
||||
/// 单例
|
||||
/// </summary>
|
||||
public interface ISingletonDependency
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace MAX.Core.DependencyInjection;
|
||||
|
||||
/// <summary>
|
||||
/// 每次实例化
|
||||
/// </summary>
|
||||
public interface ITransientDependency
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
//using MAX.Core.Autofac;
|
||||
//using MAX.Core.Infrastructure;
|
||||
//using System;
|
||||
//using System.Collections.Concurrent;
|
||||
|
||||
//namespace MAX.Core.DependencyInjection;
|
||||
|
||||
//public interface ILazyServiceProvider
|
||||
//{
|
||||
// object LazyGetService(Type serviceType);
|
||||
// T LazyGetService<T>();
|
||||
//}
|
||||
|
||||
//public class LazyServiceProvider : SingleInstance<LazyServiceProvider>, ILazyServiceProvider
|
||||
//{
|
||||
// protected ConcurrentDictionary<Type, Lazy<object>> CachedServices { get; }
|
||||
// private LazyServiceProvider()
|
||||
// {
|
||||
// CachedServices = new ConcurrentDictionary<Type, Lazy<object>>();
|
||||
// }
|
||||
// public virtual T LazyGetService<T>()
|
||||
// {
|
||||
// return (T)LazyGetService(typeof(T));
|
||||
// }
|
||||
|
||||
// public virtual object LazyGetService(Type serviceType)
|
||||
// {
|
||||
// return GetService(serviceType);
|
||||
// }
|
||||
// public virtual object GetService(Type serviceType)
|
||||
// {
|
||||
// return CachedServices.GetOrAdd(
|
||||
// serviceType,
|
||||
// _ => new Lazy<object>(() => IocManager.Resolve(serviceType))
|
||||
// ).Value;
|
||||
// }
|
||||
//}
|
||||
@@ -0,0 +1,218 @@
|
||||
using MAX.Core.Helpers;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace MAX.Core.Extensions;
|
||||
|
||||
/// <summary>
|
||||
/// 系统扩展 - 类型转换
|
||||
/// </summary>
|
||||
public static partial class Extensions
|
||||
{
|
||||
/// <summary>
|
||||
/// IsNullOrEmpty
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
public static bool IsNullOrEmpty(this string input)
|
||||
{
|
||||
return string.IsNullOrEmpty(input);
|
||||
}
|
||||
/// <summary>
|
||||
/// 安全转换为字符串,去除两端空格,当值为null时返回""
|
||||
/// </summary>
|
||||
/// <param name="input">输入值</param>
|
||||
public static string SafeString(this object input)
|
||||
{
|
||||
return input?.ToString().Trim() ?? string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转换为bool
|
||||
/// </summary>
|
||||
/// <param name="obj">数据</param>
|
||||
public static bool ToBool(this string obj, bool defvalue = false)
|
||||
{
|
||||
return Helpers.Convert.ToBool(obj, defvalue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转换为可空bool
|
||||
/// </summary>
|
||||
/// <param name="obj">数据</param>
|
||||
public static bool? ToBoolOrNull(this string obj)
|
||||
{
|
||||
return Helpers.Convert.ToBoolOrNull(obj);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转换为int
|
||||
/// </summary>
|
||||
/// <param name="obj">数据</param>
|
||||
public static int ToInt(this string obj, int defvalue = 0)
|
||||
{
|
||||
return Helpers.Convert.ToInt(obj, defvalue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转换为可空int
|
||||
/// </summary>
|
||||
/// <param name="obj">数据</param>
|
||||
public static int? ToIntOrNull(this string obj)
|
||||
{
|
||||
return Helpers.Convert.ToIntOrNull(obj);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转换为long
|
||||
/// </summary>
|
||||
/// <param name="obj">数据</param>
|
||||
public static long ToLong(this string obj, long defvalue = 0)
|
||||
{
|
||||
return Helpers.Convert.ToLong(obj, defvalue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转换为可空long
|
||||
/// </summary>
|
||||
/// <param name="obj">数据</param>
|
||||
public static long? ToLongOrNull(this string obj)
|
||||
{
|
||||
return Helpers.Convert.ToLongOrNull(obj);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转换为double
|
||||
/// </summary>
|
||||
/// <param name="obj">数据</param>
|
||||
public static double ToDouble(this string obj, double defvalue = 0)
|
||||
{
|
||||
return Helpers.Convert.ToDouble(obj, defvalue: defvalue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转换为可空double
|
||||
/// </summary>
|
||||
/// <param name="obj">数据</param>
|
||||
public static double? ToDoubleOrNull(this string obj)
|
||||
{
|
||||
return Helpers.Convert.ToDoubleOrNull(obj);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转换为decimal
|
||||
/// </summary>
|
||||
/// <param name="obj">数据</param>
|
||||
public static decimal ToDecimal(this string obj, decimal defvalue = 0)
|
||||
{
|
||||
return Helpers.Convert.ToDecimal(obj, defvalue: defvalue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转换为可空decimal
|
||||
/// </summary>
|
||||
/// <param name="obj">数据</param>
|
||||
public static decimal? ToDecimalOrNull(this string obj)
|
||||
{
|
||||
return Helpers.Convert.ToDecimalOrNull(obj);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转换为日期
|
||||
/// </summary>
|
||||
/// <param name="obj">数据</param>
|
||||
public static DateTime ToDate(this string obj)
|
||||
{
|
||||
return Helpers.Convert.ToDate(obj);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转换为可空日期
|
||||
/// </summary>
|
||||
/// <param name="obj">数据</param>
|
||||
public static DateTime? ToDateOrNull(this string obj)
|
||||
{
|
||||
return Helpers.Convert.ToDateOrNull(obj);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转换为Guid
|
||||
/// </summary>
|
||||
/// <param name="obj">数据</param>
|
||||
public static Guid ToGuid(this string obj)
|
||||
{
|
||||
return Helpers.Convert.ToGuid(obj);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转换为可空Guid
|
||||
/// </summary>
|
||||
/// <param name="obj">数据</param>
|
||||
public static Guid? ToGuidOrNull(this string obj)
|
||||
{
|
||||
return Helpers.Convert.ToGuidOrNull(obj);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转换为Guid集合
|
||||
/// </summary>
|
||||
/// <param name="obj">数据,范例: "83B0233C-A24F-49FD-8083-1337209EBC9A,EAB523C6-2FE7-47BE-89D5-C6D440C3033A"</param>
|
||||
public static List<Guid> ToGuidList(this string obj)
|
||||
{
|
||||
return Helpers.Convert.ToGuidList(obj);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转换为Guid集合
|
||||
/// </summary>
|
||||
/// <param name="obj">字符串集合</param>
|
||||
public static List<Guid> ToGuidList(this IList<string> obj)
|
||||
{
|
||||
if (obj == null)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
return obj.Select(t => t.ToGuid()).ToList();
|
||||
}
|
||||
/// <summary>
|
||||
/// 转换枚举描述
|
||||
/// </summary>
|
||||
/// <param name="obj"></param>
|
||||
/// <returns></returns>
|
||||
public static string ToDescription(this Enum obj)
|
||||
{
|
||||
return EnumHelper.GetDescription(obj.GetType(), obj);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 枚举值 → 单个 EnumItem
|
||||
/// </summary>
|
||||
/// <typeparam name="TEnum"></typeparam>
|
||||
/// <param name="value"></param>
|
||||
/// <returns></returns>
|
||||
public static EnumItem<TEnum> ToItem<TEnum>(this TEnum value) where TEnum : struct, Enum
|
||||
{
|
||||
return new EnumItem<TEnum>(value);
|
||||
}
|
||||
/// <summary>
|
||||
/// 是否单一标记
|
||||
/// </summary>
|
||||
/// <param name="obj"></param>
|
||||
/// <returns></returns>
|
||||
public static bool IsSingleFlag(Enum obj)
|
||||
{
|
||||
return EnumHelper.IsSingleFlag(obj);
|
||||
}
|
||||
/// <summary>
|
||||
/// 获取第一个标记
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="obj"></param>
|
||||
/// <returns></returns>
|
||||
public static Enum GetFirstFlag(this Enum obj)
|
||||
{
|
||||
return EnumHelper.GetFirstFlag(obj);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
using MAX.Core.Cryptography;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace MAX.Core.Extensions;
|
||||
|
||||
public static class DictionaryExtension
|
||||
{
|
||||
/// <summary>
|
||||
/// 字典转url Get 参数
|
||||
/// </summary>
|
||||
/// <param name="parameters"></param>
|
||||
/// <returns></returns>
|
||||
public static string ToUrlParams(this IDictionary<string, string> parameters, bool IsUrlEncode = true)
|
||||
{
|
||||
IDictionary<string, string> sortedParams = new SortedDictionary<string, string>(parameters);
|
||||
IEnumerator<KeyValuePair<string, string>> dem = sortedParams.GetEnumerator();
|
||||
StringBuilder sb = new("");
|
||||
while (dem.MoveNext())
|
||||
{
|
||||
string key = dem.Current.Key;
|
||||
string value = dem.Current.Value;
|
||||
if (!string.IsNullOrEmpty(key))
|
||||
{
|
||||
sb.Append(key).Append("=").Append(IsUrlEncode ? UrlHelper.Encode(value) : value).Append("&");
|
||||
|
||||
}
|
||||
}
|
||||
string content = sb.ToString().Substring(0, sb.Length - 1);
|
||||
|
||||
return content;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
using System;
|
||||
|
||||
namespace MAX.Core.Helpers;
|
||||
|
||||
/// <summary>
|
||||
/// 常用公共操作
|
||||
/// </summary>
|
||||
public static partial class Common
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取类型
|
||||
/// </summary>
|
||||
/// <typeparam name="T">类型</typeparam>
|
||||
public static Type GetType<T>()
|
||||
{
|
||||
return GetType(typeof(T));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取类型
|
||||
/// </summary>
|
||||
/// <param name="type">类型</param>
|
||||
public static Type GetType(Type type)
|
||||
{
|
||||
return Nullable.GetUnderlyingType(type) ?? type;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 换行符
|
||||
/// </summary>
|
||||
public static string Line => Environment.NewLine;
|
||||
|
||||
|
||||
#if NET5_0
|
||||
/// <summary>
|
||||
/// 是否Linux操作系统
|
||||
/// </summary>
|
||||
public static bool IsLinux => RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
|
||||
|
||||
/// <summary>
|
||||
/// 是否Windows操作系统
|
||||
/// </summary>
|
||||
public static bool IsWindows => RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
|
||||
|
||||
/// <summary>
|
||||
/// 是否苹果操作系统
|
||||
/// </summary>
|
||||
public static bool IsOsx => RuntimeInformation.IsOSPlatform(OSPlatform.OSX);
|
||||
|
||||
/// <summary>
|
||||
/// 当前操作系统
|
||||
/// </summary>
|
||||
public static string System => IsWindows ? "Windows" : IsLinux ? "Linux" : IsOsx ? "OSX" : string.Empty;
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
using System;
|
||||
|
||||
namespace MAX.Core.Helpers;
|
||||
|
||||
public class CommonItem : IComparable<CommonItem>
|
||||
{
|
||||
/// <summary>
|
||||
/// 初始化
|
||||
/// </summary>
|
||||
/// <param name="text">文本</param>
|
||||
/// <param name="value">值</param>
|
||||
/// <param name="sortId">排序号</param>
|
||||
/// <param name="group">组</param>
|
||||
/// <param name="disabled">禁用</param>
|
||||
public CommonItem(string text, object value, string des = "", int? sortId = null, string group = null, bool? disabled = null)
|
||||
{
|
||||
Text = text;
|
||||
Value = value;
|
||||
Description = des;
|
||||
SortId = sortId;
|
||||
Group = group;
|
||||
Disabled = disabled;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 文本
|
||||
/// </summary>
|
||||
public string Text { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 值
|
||||
/// </summary>
|
||||
public object Value { get; protected set; }
|
||||
/// <summary>
|
||||
/// 描述
|
||||
/// </summary>
|
||||
public string Description { get; set; }
|
||||
/// <summary>
|
||||
/// 排序号
|
||||
/// </summary>
|
||||
public int? SortId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 组
|
||||
/// </summary>
|
||||
public string Group { get; }
|
||||
/// <summary>
|
||||
/// 是否选中
|
||||
/// </summary>
|
||||
public bool IsSelected { get; set; }
|
||||
/// <summary>
|
||||
/// 禁用
|
||||
/// </summary>
|
||||
public bool? Disabled { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 比较
|
||||
/// </summary>
|
||||
/// <param name="other">其它列表项</param>
|
||||
public int CompareTo(CommonItem other)
|
||||
{
|
||||
return string.Compare(Text, other.Text, StringComparison.CurrentCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public class CommonItem<T> : CommonItem
|
||||
{
|
||||
public new T Value
|
||||
{
|
||||
get { return (T)base.Value; }
|
||||
set { base.Value = value; }
|
||||
}
|
||||
public CommonItem(string text, T value, string des = "", int? sortId = null, string group = null, bool? disabled = null) : base(text, value, des, sortId, group, disabled)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,385 @@
|
||||
using MAX.Core.Extensions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace MAX.Core.Helpers;
|
||||
|
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field)]
|
||||
public class ConvertAttribute : Attribute
|
||||
{
|
||||
public Type ConverterType { get; set; }
|
||||
public ConvertAttribute(Type type)
|
||||
{
|
||||
ConverterType = type;
|
||||
}
|
||||
}
|
||||
public interface IConverter
|
||||
{
|
||||
object Converter(object input);
|
||||
}
|
||||
/// <summary>
|
||||
/// 类型转换
|
||||
/// </summary>
|
||||
public static partial class Convert
|
||||
{
|
||||
/// <summary>
|
||||
/// 转换为32位整型
|
||||
/// </summary>
|
||||
/// <param name="input">输入值</param>
|
||||
public static int ToInt(object input, int defvalue = 0)
|
||||
{
|
||||
return ToIntOrNull(input) ?? defvalue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转换为32位可空整型
|
||||
/// </summary>
|
||||
/// <param name="input">输入值</param>
|
||||
public static int? ToIntOrNull(object input)
|
||||
{
|
||||
bool success = int.TryParse(input.SafeString(), out int result);
|
||||
if (success)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
double? temp = ToDoubleOrNull(input, 0);
|
||||
if (temp == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return System.Convert.ToInt32(temp);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转换为64位整型
|
||||
/// </summary>
|
||||
/// <param name="input">输入值</param>
|
||||
public static long ToLong(object input, long defvalue = 0)
|
||||
{
|
||||
return ToLongOrNull(input) ?? defvalue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转换为64位可空整型
|
||||
/// </summary>
|
||||
/// <param name="input">输入值</param>
|
||||
public static long? ToLongOrNull(object input)
|
||||
{
|
||||
bool success = long.TryParse(input.SafeString(), out long result);
|
||||
if (success)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
decimal? temp = ToDecimalOrNull(input, 0);
|
||||
if (temp == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return System.Convert.ToInt64(temp);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转换为32位浮点型,并按指定小数位舍入
|
||||
/// </summary>
|
||||
/// <param name="input">输入值</param>
|
||||
/// <param name="digits">小数位数</param>
|
||||
public static float ToFloat(object input, int? digits = null)
|
||||
{
|
||||
return ToFloatOrNull(input, digits) ?? 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转换为32位可空浮点型,并按指定小数位舍入
|
||||
/// </summary>
|
||||
/// <param name="input">输入值</param>
|
||||
/// <param name="digits">小数位数</param>
|
||||
public static float? ToFloatOrNull(object input, int? digits = null)
|
||||
{
|
||||
bool success = float.TryParse(input.SafeString(), out float result);
|
||||
if (!success)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (digits == null)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
return (float)Math.Round(result, digits.Value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转换为64位浮点型,并按指定小数位舍入
|
||||
/// </summary>
|
||||
/// <param name="input">输入值</param>
|
||||
/// <param name="digits">小数位数</param>
|
||||
public static double ToDouble(object input, int? digits = null, double defvalue = 0)
|
||||
{
|
||||
return ToDoubleOrNull(input, digits) ?? defvalue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转换为64位可空浮点型,并按指定小数位舍入
|
||||
/// </summary>
|
||||
/// <param name="input">输入值</param>
|
||||
/// <param name="digits">小数位数</param>
|
||||
public static double? ToDoubleOrNull(object input, int? digits = null)
|
||||
{
|
||||
if (input is bool b)
|
||||
{
|
||||
return b ? 1 : 0;
|
||||
}
|
||||
bool success = double.TryParse(input.SafeString(), out double result);
|
||||
if (!success)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (digits == null)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
return Math.Round(result, digits.Value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转换为128位浮点型,并按指定小数位舍入
|
||||
/// </summary>
|
||||
/// <param name="input">输入值</param>
|
||||
/// <param name="digits">小数位数</param>
|
||||
public static decimal ToDecimal(object input, int? digits = null, decimal defvalue = 0)
|
||||
{
|
||||
return ToDecimalOrNull(input, digits) ?? defvalue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转换为128位可空浮点型,并按指定小数位舍入
|
||||
/// </summary>
|
||||
/// <param name="input">输入值</param>
|
||||
/// <param name="digits">小数位数</param>
|
||||
public static decimal? ToDecimalOrNull(object input, int? digits = null)
|
||||
{
|
||||
bool success = decimal.TryParse(input.SafeString(), out decimal result);
|
||||
if (!success)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (digits == null)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
return Math.Round(result, digits.Value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转换为布尔值
|
||||
/// </summary>
|
||||
/// <param name="input">输入值</param>
|
||||
public static bool ToBool(object input, bool defvalue = false)
|
||||
{
|
||||
return ToBoolOrNull(input) ?? defvalue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转换为可空布尔值
|
||||
/// </summary>
|
||||
/// <param name="input">输入值</param>
|
||||
public static bool? ToBoolOrNull(object input)
|
||||
{
|
||||
bool? value = GetBool(input);
|
||||
if (value != null)
|
||||
{
|
||||
return value.Value;
|
||||
}
|
||||
|
||||
return bool.TryParse(input.SafeString(), out bool result) ? result : null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取布尔值
|
||||
/// </summary>
|
||||
private static bool? GetBool(object input)
|
||||
{
|
||||
return input.SafeString().ToLower() switch
|
||||
{
|
||||
"0" => false,
|
||||
"否" => false,
|
||||
"不" => false,
|
||||
"no" => false,
|
||||
"fail" => false,
|
||||
"1" => true,
|
||||
"是" => true,
|
||||
"ok" => true,
|
||||
"yes" => true,
|
||||
_ => null,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转换为日期
|
||||
/// </summary>
|
||||
/// <param name="input">输入值</param>
|
||||
public static DateTime ToDate(object input)
|
||||
{
|
||||
return ToDateOrNull(input) ?? DateTime.MinValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转换为可空日期
|
||||
/// </summary>
|
||||
/// <param name="input">输入值</param>
|
||||
public static DateTime? ToDateOrNull(object input)
|
||||
{
|
||||
return DateTime.TryParse(input.SafeString(), out DateTime result) ? result : null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转换为Guid
|
||||
/// </summary>
|
||||
/// <param name="input">输入值</param>
|
||||
public static Guid ToGuid(object input)
|
||||
{
|
||||
return ToGuidOrNull(input) ?? Guid.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转换为可空Guid
|
||||
/// </summary>
|
||||
/// <param name="input">输入值</param>
|
||||
public static Guid? ToGuidOrNull(object input)
|
||||
{
|
||||
return Guid.TryParse(input.SafeString(), out Guid result) ? result : null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转换为Guid集合
|
||||
/// </summary>
|
||||
/// <param name="input">以逗号分隔的Guid集合字符串,范例:83B0233C-A24F-49FD-8083-1337209EBC9A,EAB523C6-2FE7-47BE-89D5-C6D440C3033A</param>
|
||||
public static List<Guid> ToGuidList(string input)
|
||||
{
|
||||
return ToList<Guid>(input);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 泛型集合转换
|
||||
/// </summary>
|
||||
/// <typeparam name="T">目标元素类型</typeparam>
|
||||
/// <param name="input">以逗号分隔的元素集合字符串,范例:83B0233C-A24F-49FD-8083-1337209EBC9A,EAB523C6-2FE7-47BE-89D5-C6D440C3033A</param>
|
||||
public static List<T> ToList<T>(string input)
|
||||
{
|
||||
List<T> result = [];
|
||||
if (string.IsNullOrWhiteSpace(input))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
string[] array = input.Split(',');
|
||||
result.AddRange(from each in array where !string.IsNullOrWhiteSpace(each) select To<T>(each));
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 通用泛型转换
|
||||
/// </summary>
|
||||
/// <typeparam name="T">目标类型</typeparam>
|
||||
/// <param name="input">输入值</param>
|
||||
public static T To<T>(object input)
|
||||
{
|
||||
Type type = Common.GetType<T>();
|
||||
return (T)To(type, input);
|
||||
}
|
||||
public static object To(Type type, object input)
|
||||
{
|
||||
object defValue = null;
|
||||
if (type != null)
|
||||
{
|
||||
defValue = type.IsValueType ? Activator.CreateInstance(type) : null;
|
||||
}
|
||||
if (input == null)
|
||||
{
|
||||
return defValue;
|
||||
}
|
||||
|
||||
if (input is string && string.IsNullOrWhiteSpace(input.ToString()))
|
||||
{
|
||||
return defValue;
|
||||
}
|
||||
|
||||
string typeName = type.Name.ToLower();
|
||||
try
|
||||
{
|
||||
if (typeName == "string")
|
||||
{
|
||||
return input.ToString();
|
||||
}
|
||||
|
||||
if (typeName == "guid")
|
||||
{
|
||||
return new Guid(input.ToString());
|
||||
}
|
||||
|
||||
if (type.IsEnum)
|
||||
{
|
||||
return EnumHelper.Parse(type, input);
|
||||
}
|
||||
|
||||
if (typeof(IConverter).IsAssignableFrom(type))
|
||||
{
|
||||
object instance = Activator.CreateInstance(type);
|
||||
return ((IConverter)instance).Converter(input);
|
||||
}
|
||||
if (input is IConvertible)
|
||||
{
|
||||
return System.Convert.ChangeType(input, type);
|
||||
}
|
||||
|
||||
return input;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return defValue;
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// 转换为字节数组
|
||||
/// </summary>
|
||||
/// <param name="input">输入值</param>
|
||||
public static byte[] ToBytes(string input)
|
||||
{
|
||||
return ToBytes(input, Encoding.UTF8);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转换为字节数组
|
||||
/// </summary>
|
||||
/// <param name="input">输入值</param>
|
||||
/// <param name="encoding">字符编码</param>
|
||||
public static byte[] ToBytes(string input, Encoding encoding)
|
||||
{
|
||||
return string.IsNullOrWhiteSpace(input) ? new byte[] { } : encoding.GetBytes(input);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,110 @@
|
||||
using System;
|
||||
|
||||
namespace MAX.Core.Helpers;
|
||||
|
||||
public class DateHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Unix纪元时间
|
||||
/// </summary>
|
||||
private static readonly DateTime UnixEpoch = new(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
|
||||
|
||||
/// <summary>
|
||||
/// 将时间转换为秒级时间戳
|
||||
/// </summary>
|
||||
/// <param name="dateTime"></param>
|
||||
/// <returns></returns>
|
||||
public static long GetUnixTimestampInSeconds(DateTime dateTime)
|
||||
{
|
||||
TimeSpan timeSpan = dateTime.ToUniversalTime() - UnixEpoch;
|
||||
return (long)timeSpan.TotalSeconds;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将时间转换为毫秒级时间戳
|
||||
/// </summary>
|
||||
/// <param name="dateTime"></param>
|
||||
/// <returns></returns>
|
||||
public static long GetUnixTimestampInMilliseconds(DateTime dateTime)
|
||||
{
|
||||
TimeSpan timeSpan = dateTime.ToUniversalTime() - UnixEpoch;
|
||||
return (long)timeSpan.TotalMilliseconds;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将时间转换为微秒时间戳
|
||||
/// </summary>
|
||||
/// <param name="dateTime"></param>
|
||||
/// <returns></returns>
|
||||
public static long GetUnixTimestampInMicroseconds(DateTime dateTime)
|
||||
{
|
||||
long us = dateTime.ToUniversalTime().Ticks - UnixEpoch.Ticks;
|
||||
return us;
|
||||
}
|
||||
/// <summary>
|
||||
/// 将时间Ticks转换为毫微秒时间戳
|
||||
/// </summary>
|
||||
/// <param name="dateTime"></param>
|
||||
/// <returns></returns>
|
||||
public static long GetUnixTimestampInMicroseconds(long ticks)
|
||||
{
|
||||
long us = ticks - UnixEpoch.Ticks;
|
||||
return us;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将秒级时间戳转换为时间
|
||||
/// </summary>
|
||||
/// <param name="timestamp"></param>
|
||||
/// <returns></returns>
|
||||
public static DateTime ConvertFromUnixTimestampInSeconds(long timestamp)
|
||||
{
|
||||
return UnixEpoch.AddSeconds(timestamp).ToLocalTime();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将毫秒级时间戳转换为时间
|
||||
/// </summary>
|
||||
/// <param name="timestamp"></param>
|
||||
/// <returns></returns>
|
||||
public static DateTime ConvertFromUnixTimestampInMilliseconds(long timestamp)
|
||||
{
|
||||
return UnixEpoch.AddMilliseconds(timestamp).ToLocalTime();
|
||||
}
|
||||
/// <summary>
|
||||
/// 将微秒级时间戳转换为时间
|
||||
/// </summary>
|
||||
/// <param name="timestamp"></param>
|
||||
/// <returns></returns>
|
||||
public static DateTime ConvertFromUnixTimestampInMicroseconds(long timestamp)
|
||||
{
|
||||
long us = timestamp + UnixEpoch.Ticks;
|
||||
return new DateTime(us);
|
||||
}
|
||||
/// <summary>
|
||||
/// 获取当前时间的秒级时间戳
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static long GetCurrentUnixTimestampInSeconds()
|
||||
{
|
||||
return GetUnixTimestampInSeconds(DateTime.UtcNow);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前时间的毫秒级时间戳
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static long GetCurrentUnixTimestampInMilliseconds()
|
||||
{
|
||||
return GetUnixTimestampInMilliseconds(DateTime.UtcNow);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前时间的微秒级时间戳
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static long GetCurrentUnixTimestampInMicroseconds()
|
||||
{
|
||||
return GetUnixTimestampInMicroseconds(DateTime.UtcNow);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,341 @@
|
||||
using MAX.Core.Extensions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace MAX.Core.Helpers;
|
||||
|
||||
/// <summary>
|
||||
/// 枚举操作
|
||||
/// </summary>
|
||||
public static partial class EnumHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取实例
|
||||
/// </summary>
|
||||
/// <typeparam name="TEnum">枚举类型</typeparam>
|
||||
/// <param name="member">成员名或值,范例:Enum1枚举有成员A=0,则传入"A"或"0"获取 Enum1.A</param>
|
||||
public static TEnum Parse<TEnum>(object member)
|
||||
{
|
||||
string value = member.SafeString();
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
if (typeof(TEnum).IsGenericType)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
throw new ArgumentNullException(nameof(member));
|
||||
}
|
||||
return (TEnum)Enum.Parse(Common.GetType<TEnum>(), value, true);
|
||||
}
|
||||
public static object Parse(Type enumObj, object member)
|
||||
{
|
||||
string value = member.SafeString();
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
if (enumObj.IsGenericType)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
throw new ArgumentNullException(nameof(member));
|
||||
}
|
||||
return Enum.Parse(enumObj, value, true);
|
||||
}
|
||||
public static TEnum ParseByDescription<TEnum>(string description)
|
||||
{
|
||||
return (TEnum)ParseByDescription(Common.GetType<TEnum>(), description);
|
||||
}
|
||||
public static object ParseByDescription(Type enumObj, string description)
|
||||
{
|
||||
List<EnumItem> listitem = GetItems(enumObj);
|
||||
foreach (EnumItem item in listitem)
|
||||
{
|
||||
if (item.Description == description)
|
||||
{
|
||||
return Enum.Parse(enumObj, item.Numeric.ToString(), true);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
/// <summary>
|
||||
/// 获取成员名
|
||||
/// </summary>
|
||||
/// <typeparam name="TEnum">枚举类型</typeparam>
|
||||
/// <param name="member">成员名、值、实例均可,范例:Enum1枚举有成员A=0,则传入Enum1.A或0,获取成员名"A"</param>
|
||||
public static string GetName<TEnum>(object member)
|
||||
{
|
||||
return GetName(Common.GetType<TEnum>(), member);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取成员名
|
||||
/// </summary>
|
||||
/// <param name="type">枚举类型</param>
|
||||
/// <param name="member">成员名、值、实例均可</param>
|
||||
public static string GetName(Type type, object member)
|
||||
{
|
||||
if (type == null)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
if (member == null)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
if (member is string)
|
||||
{
|
||||
return member.ToString();
|
||||
}
|
||||
|
||||
if (type.GetTypeInfo().IsEnum == false)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
return Enum.GetName(type, member);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取成员值
|
||||
/// </summary>
|
||||
/// <typeparam name="TEnum">枚举类型</typeparam>
|
||||
/// <param name="member">成员名、值、实例均可,范例:Enum1枚举有成员A=0,可传入"A"、0、Enum1.A,获取值0</param>
|
||||
public static int GetValue<TEnum>(object member)
|
||||
{
|
||||
return GetValue(Common.GetType<TEnum>(), member);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取成员值
|
||||
/// </summary>
|
||||
/// <param name="type">枚举类型</param>
|
||||
/// <param name="member">成员名、值、实例均可</param>
|
||||
public static int GetValue(Type type, object member)
|
||||
{
|
||||
string value = member.SafeString();
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(member));
|
||||
}
|
||||
|
||||
return (int)Enum.Parse(type, member.ToString(), true);
|
||||
}
|
||||
/// <summary>
|
||||
/// 获取描述,使用System.ComponentModel.Description特性设置描述
|
||||
/// </summary>
|
||||
/// <typeparam name="TEnum">枚举类型</typeparam>
|
||||
/// <param name="member">成员名、值、实例均可</param>
|
||||
public static string GetDescription<TEnum>(object member)
|
||||
{
|
||||
return ReflectionHelper.GetDescription<TEnum>(GetName<TEnum>(member));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取描述,使用System.ComponentModel.Description特性设置描述
|
||||
/// </summary>
|
||||
/// <param name="type">枚举类型</param>
|
||||
/// <param name="member">成员名、值、实例均可</param>
|
||||
public static string GetDescription(Type type, object member)
|
||||
{
|
||||
return ReflectionHelper.GetDescription(type, GetName(type, member));
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 获取项集合,文本设置为Description,值为Value
|
||||
/// </summary>
|
||||
/// <typeparam name="TEnum">枚举类型</typeparam>
|
||||
public static List<EnumItem> GetItems<TEnum>()
|
||||
{
|
||||
return GetItems(typeof(TEnum));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取项集合,文本设置为Description,值为Value
|
||||
/// </summary>
|
||||
/// <param name="type">枚举类型</param>
|
||||
public static List<EnumItem> GetItems(Type type)
|
||||
{
|
||||
type = Common.GetType(type);
|
||||
if (type.IsEnum == false)
|
||||
{
|
||||
throw new InvalidOperationException($"类型 {type} 不是枚举");
|
||||
}
|
||||
|
||||
List<EnumItem> result = [];
|
||||
foreach (FieldInfo field in type.GetFields())
|
||||
{
|
||||
AddItem(type, result, field);
|
||||
}
|
||||
|
||||
return result.OrderBy(t => t.Numeric).ToList();
|
||||
}
|
||||
|
||||
public static CommonItem GetItem(object obj)
|
||||
{
|
||||
CommonItem item = null;
|
||||
if (obj is Enum em)
|
||||
{
|
||||
//item.Text = em.get
|
||||
string text = em.ToString();
|
||||
int value = GetValue(obj.GetType(), obj);
|
||||
string description = em.ToDescription();
|
||||
item = new CommonItem(text, value, des: description);
|
||||
}
|
||||
return item;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加描述项
|
||||
/// </summary>
|
||||
private static void AddItem(Type type, ICollection<EnumItem> result, FieldInfo field)
|
||||
{
|
||||
if (!field.FieldType.IsEnum)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
string text = field.Name;
|
||||
int value = GetValue(type, field.Name);
|
||||
string description = ReflectionHelper.GetDescription(field);
|
||||
result.Add(new EnumItem(text, value, description));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取名称集合
|
||||
/// </summary>
|
||||
/// <typeparam name="TEnum">枚举类型</typeparam>
|
||||
public static List<string> GetNames<TEnum>()
|
||||
{
|
||||
return GetNames(typeof(TEnum));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取名称集合
|
||||
/// </summary>
|
||||
/// <param name="type">枚举类型</param>
|
||||
public static List<string> GetNames(Type type)
|
||||
{
|
||||
type = Common.GetType(type);
|
||||
if (type.IsEnum == false)
|
||||
{
|
||||
throw new InvalidOperationException($"类型 {type} 不是枚举");
|
||||
}
|
||||
|
||||
List<string> result = [];
|
||||
foreach (FieldInfo field in type.GetFields())
|
||||
{
|
||||
if (!field.FieldType.IsEnum)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
result.Add(field.Name);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 获取名称集合
|
||||
/// </summary>
|
||||
/// <typeparam name="TEnum">枚举类型</typeparam>
|
||||
public static List<string> GetDescriptions<TEnum>()
|
||||
{
|
||||
return GetDescriptions(typeof(TEnum));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取名称集合
|
||||
/// </summary>
|
||||
/// <param name="type">枚举类型</param>
|
||||
public static List<string> GetDescriptions(Type type)
|
||||
{
|
||||
type = Common.GetType(type);
|
||||
if (type.IsEnum == false)
|
||||
{
|
||||
throw new InvalidOperationException($"类型 {type} 不是枚举");
|
||||
}
|
||||
|
||||
List<string> result = [];
|
||||
foreach (FieldInfo field in type.GetFields())
|
||||
{
|
||||
if (!field.FieldType.IsEnum)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
string description = ReflectionHelper.GetDescription(field);
|
||||
result.Add(description);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
/// <summary>
|
||||
/// 是否单一标记
|
||||
/// </summary>
|
||||
/// <param name="obj"></param>
|
||||
/// <returns></returns>
|
||||
public static bool IsSingleFlag(Enum obj)
|
||||
{
|
||||
int intValue = GetValue(obj.GetType(), obj);
|
||||
return (intValue > 0) && ((intValue & (intValue - 1)) == 0);
|
||||
}
|
||||
/// <summary>
|
||||
/// 获取第一个标记
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="value"></param>
|
||||
/// <returns></returns>
|
||||
public static T GetFirstFlag<T>(T value) where T : Enum
|
||||
{
|
||||
long v = System.Convert.ToInt64(value);
|
||||
long first = v & -v;
|
||||
return Enum.GetValues(typeof(T)).Cast<T>().First(flag => System.Convert.ToInt64(flag) == first);
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Enum展示类型
|
||||
/// </summary>
|
||||
public enum EnumDisplayType
|
||||
{
|
||||
Text,
|
||||
Value,
|
||||
Description,
|
||||
CommonItem
|
||||
}
|
||||
public class EnumItem
|
||||
{
|
||||
public string Name { get; init; }
|
||||
public int Numeric { get; init; }
|
||||
public string Description { get; init; }
|
||||
|
||||
public EnumItem(string name, int numeric, string description)
|
||||
{
|
||||
Name = name;
|
||||
Numeric = numeric;
|
||||
Description = description;
|
||||
}
|
||||
}
|
||||
public class EnumItem<TEnum> : EnumItem where TEnum : struct, Enum
|
||||
{
|
||||
public TEnum Value { get; }
|
||||
|
||||
public EnumItem(TEnum value)
|
||||
: base(value.ToString(), System.Convert.ToInt32(value), GetDescription(value))
|
||||
{
|
||||
Value = value;
|
||||
}
|
||||
private static string GetDescription(TEnum value)
|
||||
{
|
||||
var field = typeof(TEnum).GetField(value.ToString())!;
|
||||
var attr = field.GetCustomAttribute<DescriptionAttribute>();
|
||||
return attr?.Description ?? string.Empty;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MAX.Core.Helpers;
|
||||
public class EnvironmentHelper
|
||||
{
|
||||
private static string? _currentEnvironment;
|
||||
|
||||
// 获取当前环境
|
||||
public static string CurrentEnvironment
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_currentEnvironment == null)
|
||||
{
|
||||
_currentEnvironment = DetermineEnvironment();
|
||||
}
|
||||
return _currentEnvironment;
|
||||
}
|
||||
}
|
||||
public static bool IsTest => CurrentEnvironment.Equals("Test", StringComparison.OrdinalIgnoreCase);
|
||||
public static bool IsDevelopment => CurrentEnvironment.Equals("Development", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
public static bool IsProduction => CurrentEnvironment.Equals("Production", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
private static string DetermineEnvironment()
|
||||
{
|
||||
// 1. 检查环境变量
|
||||
var envVar = Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT") ??
|
||||
Environment.GetEnvironmentVariable("ENVIRONMENT");
|
||||
|
||||
if (!string.IsNullOrEmpty(envVar))
|
||||
{
|
||||
return envVar;
|
||||
}
|
||||
|
||||
|
||||
// 检查当前机器名
|
||||
var machineName = Environment.MachineName;
|
||||
if (machineName.Contains("DEV", StringComparison.OrdinalIgnoreCase) ||
|
||||
machineName.Contains("TEST", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return machineName.Contains("TEST") ? "Test" : "Development";
|
||||
}
|
||||
|
||||
// 4. 默认返回 Production
|
||||
return "Production";
|
||||
}
|
||||
|
||||
// 获取环境描述
|
||||
public static string EnvironmentDescription
|
||||
{
|
||||
get
|
||||
{
|
||||
if (IsTest) return "测试环境";
|
||||
if (IsDevelopment) return "开发环境";
|
||||
if (IsProduction) return "生产环境";
|
||||
return $"未知环境 ({CurrentEnvironment})";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MAX.Core.Helpers;
|
||||
|
||||
/// <summary>
|
||||
/// 文件和流操作
|
||||
/// </summary>
|
||||
public static partial class FileHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// 流转换为字节流
|
||||
/// </summary>
|
||||
/// <param name="stream">流</param>
|
||||
public static async Task<byte[]> ToBytesAsync(Stream stream)
|
||||
{
|
||||
stream.Seek(0, SeekOrigin.Begin);
|
||||
byte[] buffer = new byte[stream.Length];
|
||||
await stream.ReadAsync(buffer, 0, buffer.Length);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 流转换为字节流
|
||||
/// </summary>
|
||||
/// <param name="stream">流</param>
|
||||
public static byte[] ToBytes(Stream stream)
|
||||
{
|
||||
stream.Seek(0, SeekOrigin.Begin);
|
||||
byte[] buffer = new byte[stream.Length];
|
||||
stream.Read(buffer, 0, buffer.Length);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 字符串转换成字节数组
|
||||
/// </summary>
|
||||
/// <param name="data">数据,默认字符编码utf-8</param>
|
||||
public static byte[] ToBytes(string data)
|
||||
{
|
||||
return ToBytes(data, Encoding.UTF8);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 字符串转换成字节数组
|
||||
/// </summary>
|
||||
/// <param name="data">数据</param>
|
||||
/// <param name="encoding">字符编码</param>
|
||||
public static byte[] ToBytes(string data, Encoding encoding)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(data))
|
||||
{
|
||||
return new byte[] { };
|
||||
}
|
||||
|
||||
return encoding.GetBytes(data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将文件读取到字节流中
|
||||
/// </summary>
|
||||
/// <param name="filePath">文件的绝对路径</param>
|
||||
public static byte[] Read(string filePath)
|
||||
{
|
||||
if (!File.Exists(filePath))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
FileInfo fileInfo = new(filePath);
|
||||
using BinaryReader reader = new(fileInfo.Open(FileMode.Open));
|
||||
return reader.ReadBytes((int)fileInfo.Length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 流转换成字符串
|
||||
/// </summary>
|
||||
/// <param name="stream">流</param>
|
||||
/// <param name="encoding">字符编码</param>
|
||||
/// <param name="bufferSize">缓冲区大小</param>
|
||||
/// <param name="isCloseStream">读取完成是否释放流,默认为true</param>
|
||||
public static string ToString(Stream stream, Encoding encoding = null, int bufferSize = 1024 * 2, bool isCloseStream = true)
|
||||
{
|
||||
if (stream == null)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
encoding ??= Encoding.UTF8;
|
||||
|
||||
if (stream.CanRead == false)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
using StreamReader reader = new(stream, encoding, true, bufferSize, !isCloseStream);
|
||||
if (stream.CanSeek)
|
||||
{
|
||||
stream.Seek(0, SeekOrigin.Begin);
|
||||
}
|
||||
|
||||
string result = reader.ReadToEnd();
|
||||
if (stream.CanSeek)
|
||||
{
|
||||
stream.Seek(0, SeekOrigin.Begin);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 流转换成字符串
|
||||
/// </summary>
|
||||
/// <param name="stream">流</param>
|
||||
/// <param name="encoding">字符编码</param>
|
||||
/// <param name="bufferSize">缓冲区大小</param>
|
||||
/// <param name="isCloseStream">读取完成是否释放流,默认为true</param>
|
||||
public static async Task<string> ToStringAsync(Stream stream, Encoding encoding = null, int bufferSize = 1024 * 2, bool isCloseStream = true)
|
||||
{
|
||||
if (stream == null)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
encoding ??= Encoding.UTF8;
|
||||
|
||||
if (stream.CanRead == false)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
using StreamReader reader = new(stream, encoding, true, bufferSize, !isCloseStream);
|
||||
if (stream.CanSeek)
|
||||
{
|
||||
stream.Seek(0, SeekOrigin.Begin);
|
||||
}
|
||||
|
||||
string result = await reader.ReadToEndAsync();
|
||||
if (stream.CanSeek)
|
||||
{
|
||||
stream.Seek(0, SeekOrigin.Begin);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 复制流并转换成字符串
|
||||
/// </summary>
|
||||
/// <param name="stream">流</param>
|
||||
/// <param name="encoding">字符编码</param>
|
||||
public static async Task<string> CopyToStringAsync(Stream stream, Encoding encoding = null)
|
||||
{
|
||||
if (stream == null)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
encoding ??= Encoding.UTF8;
|
||||
|
||||
if (stream.CanRead == false)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
using MemoryStream memoryStream = new();
|
||||
using StreamReader reader = new(memoryStream, encoding);
|
||||
if (stream.CanSeek)
|
||||
{
|
||||
stream.Seek(0, SeekOrigin.Begin);
|
||||
}
|
||||
|
||||
stream.CopyTo(memoryStream);
|
||||
if (memoryStream.CanSeek)
|
||||
{
|
||||
memoryStream.Seek(0, SeekOrigin.Begin);
|
||||
}
|
||||
|
||||
string result = await reader.ReadToEndAsync();
|
||||
if (stream.CanSeek)
|
||||
{
|
||||
stream.Seek(0, SeekOrigin.Begin);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,45 @@
|
||||
namespace MAX.Core.Helpers;
|
||||
|
||||
/// <summary>
|
||||
/// 标识生成器
|
||||
/// </summary>
|
||||
public static partial class IdHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// 标识
|
||||
/// </summary>
|
||||
private static string _id;
|
||||
|
||||
/// <summary>
|
||||
/// 设置标识
|
||||
/// </summary>
|
||||
/// <param name="id">Id</param>
|
||||
public static void SetId(string id)
|
||||
{
|
||||
_id = id;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 重置标识
|
||||
/// </summary>
|
||||
public static void Reset()
|
||||
{
|
||||
_id = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 用Guid创建标识,去掉分隔符
|
||||
/// </summary>
|
||||
public static string Guid()
|
||||
{
|
||||
return string.IsNullOrWhiteSpace(_id) ? System.Guid.NewGuid().ToString("N") : _id;
|
||||
}
|
||||
|
||||
///// <summary>
|
||||
///// 获取Guid
|
||||
///// </summary>
|
||||
//public static Guid GetGuid()
|
||||
//{
|
||||
// return string.IsNullOrWhiteSpace(_id) ? System.Guid.NewGuid() : _id.ToGuid();
|
||||
//}
|
||||
}
|
||||
@@ -0,0 +1,369 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
|
||||
namespace MAX.Core.Helpers;
|
||||
|
||||
/// <summary>
|
||||
/// This is a generic parameters base class used for Dialog Parameters and Navigation Parameters.
|
||||
/// </summary>
|
||||
public abstract class ParametersBase : IEnumerable<KeyValuePair<string, object>>
|
||||
{
|
||||
private readonly List<KeyValuePair<string, object>> _entries = [];
|
||||
|
||||
/// <summary>
|
||||
/// Default constructor.
|
||||
/// </summary>
|
||||
protected ParametersBase()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a list of parameters.
|
||||
/// </summary>
|
||||
/// <param name="query">Query string to be parsed.</param>
|
||||
protected ParametersBase(string query)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(query))
|
||||
{
|
||||
int num = query.Length;
|
||||
for (int i = ((query.Length > 0) && (query[0] == '?')) ? 1 : 0; i < num; i++)
|
||||
{
|
||||
int startIndex = i;
|
||||
int num4 = -1;
|
||||
while (i < num)
|
||||
{
|
||||
char ch = query[i];
|
||||
if (ch == '=')
|
||||
{
|
||||
if (num4 < 0)
|
||||
{
|
||||
num4 = i;
|
||||
}
|
||||
}
|
||||
else if (ch == '&')
|
||||
{
|
||||
break;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
string key = null;
|
||||
string value;
|
||||
if (num4 >= 0)
|
||||
{
|
||||
key = query.Substring(startIndex, num4 - startIndex);
|
||||
value = query.Substring(num4 + 1, i - num4 - 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
value = query.Substring(startIndex, i - startIndex);
|
||||
}
|
||||
|
||||
if (key != null)
|
||||
{
|
||||
Add(Uri.UnescapeDataString(key), Uri.UnescapeDataString(value));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Searches Parameter collection and returns value if Collection contains key.
|
||||
/// Otherwise returns null.
|
||||
/// </summary>
|
||||
/// <param name="key">The key for the value to be returned.</param>
|
||||
/// <returns>The value of the parameter referenced by the key; otherwise <c>null</c>.</returns>
|
||||
public object this[string key]
|
||||
{
|
||||
get
|
||||
{
|
||||
foreach (KeyValuePair<string, object> entry in _entries)
|
||||
{
|
||||
if (string.Compare(entry.Key, key, StringComparison.Ordinal) == 0)
|
||||
{
|
||||
return entry.Value;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The count, or number, of parameters in collection.
|
||||
/// </summary>
|
||||
public int Count => _entries.Count;
|
||||
|
||||
/// <summary>
|
||||
/// Returns an IEnumerable of the Keys in the collection.
|
||||
/// </summary>
|
||||
public IEnumerable<string> Keys =>
|
||||
_entries.Select(x => x.Key);
|
||||
|
||||
/// <summary>
|
||||
/// Adds the key and value to the parameters collection.
|
||||
/// </summary>
|
||||
/// <param name="key">The key to reference this value in the parameters collection.</param>
|
||||
/// <param name="value">The value of the parameter to store.</param>
|
||||
public void Add(string key, object value) =>
|
||||
_entries.Add(new KeyValuePair<string, object>(key, value));
|
||||
|
||||
|
||||
public void Remove(string key)
|
||||
{
|
||||
_entries.RemoveAll(x => x.Key == key);
|
||||
}
|
||||
|
||||
public void Update(string key, object value)
|
||||
{
|
||||
_entries.RemoveAll(x => x.Key == key);
|
||||
Add(key, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks collection for presence of key.
|
||||
/// </summary>
|
||||
/// <param name="key">The key to check in the collection.</param>
|
||||
/// <returns><c>true</c> if key exists; else returns <c>false</c>.</returns>
|
||||
public bool ContainsKey(string key)
|
||||
{
|
||||
return _entries.Any(x => string.Compare(x.Key, key, StringComparison.Ordinal) == 0);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets an enumerator for the KeyValuePairs in parameter collection.
|
||||
/// </summary>
|
||||
/// <returns>Enumerator.</returns>
|
||||
public IEnumerator<KeyValuePair<string, object>> GetEnumerator() =>
|
||||
_entries.GetEnumerator();
|
||||
|
||||
/// <summary>
|
||||
/// Returns the value of the member referenced by key.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of object to be returned.</typeparam>
|
||||
/// <param name="key">The key for the value to be returned.</param>
|
||||
/// <returns>Returns a matching parameter of <typeparamref name="T"/> if one exists in the Collection.</returns>
|
||||
public T GetValue<T>(string key) =>
|
||||
_entries.GetValue<T>(key);
|
||||
|
||||
/// <summary>
|
||||
/// Returns an IEnumerable of all parameters.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type for the values to be returned.</typeparam>
|
||||
/// <param name="key">The key for the values to be returned.</param>
|
||||
///<returns>Returns a IEnumerable of all the instances of type <typeparamref name="T"/>.</returns>
|
||||
public IEnumerable<T> GetValues<T>(string key) =>
|
||||
_entries.GetValues<T>(key);
|
||||
|
||||
/// <summary>
|
||||
/// Checks to see if the parameter collection contains the value.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type for the values to be returned.</typeparam>
|
||||
/// <param name="key">The key for the value to be returned.</param>
|
||||
/// <param name="value">Value of the returned parameter if it exists.</param>
|
||||
public bool TryGetValue<T>(string key, out T value) =>
|
||||
_entries.TryGetValue(key, out value);
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() =>
|
||||
GetEnumerator();
|
||||
|
||||
/// <summary>
|
||||
/// Converts parameter collection to a parameter string.
|
||||
/// </summary>
|
||||
/// <returns>A string representation of the parameters.</returns>
|
||||
public override string ToString()
|
||||
{
|
||||
StringBuilder queryBuilder = new();
|
||||
|
||||
if (_entries.Count > 0)
|
||||
{
|
||||
queryBuilder.Append('?');
|
||||
bool first = true;
|
||||
|
||||
foreach (KeyValuePair<string, object> kvp in _entries)
|
||||
{
|
||||
if (!first)
|
||||
{
|
||||
queryBuilder.Append('&');
|
||||
}
|
||||
else
|
||||
{
|
||||
first = false;
|
||||
}
|
||||
|
||||
queryBuilder.Append(Uri.EscapeDataString(kvp.Key));
|
||||
queryBuilder.Append('=');
|
||||
queryBuilder.Append(Uri.EscapeDataString(kvp.Value != null ? kvp.Value.ToString() : ""));
|
||||
}
|
||||
}
|
||||
|
||||
return queryBuilder.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a collection of parameters to the local parameter list.
|
||||
/// </summary>
|
||||
/// <param name="parameters">An IEnumerable of KeyValuePairs to add to the current parameter list.</param>
|
||||
public void FromParameters(IEnumerable<KeyValuePair<string, object>> parameters) =>
|
||||
_entries.AddRange(parameters);
|
||||
}
|
||||
|
||||
|
||||
public static class ParametersExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Searches <paramref name="parameters"/> for <paramref name="key"/>
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the parameter to return</typeparam>
|
||||
/// <param name="parameters">A collection of parameters to search</param>
|
||||
/// <param name="key">The key of the parameter to find</param>
|
||||
/// <returns>A matching value of <typeparamref name="T"/> if it exists</returns>
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public static T GetValue<T>(this IEnumerable<KeyValuePair<string, object>> parameters, string key) =>
|
||||
(T)GetValue(parameters, key, typeof(T));
|
||||
|
||||
/// <summary>
|
||||
/// Searches <paramref name="parameters"/> for value referenced by <paramref name="key"/>
|
||||
/// </summary>
|
||||
/// <param name="parameters">A collection of parameters to search</param>
|
||||
/// <param name="key">The key of the parameter to find</param>
|
||||
/// <param name="type">The type of the parameter to return</param>
|
||||
/// <returns>A matching value of <paramref name="type"/> if it exists</returns>
|
||||
/// <exception cref="InvalidCastException">Unable to convert the value of Type</exception>
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public static object GetValue(this IEnumerable<KeyValuePair<string, object>> parameters, string key, Type type)
|
||||
{
|
||||
foreach (KeyValuePair<string, object> kvp in parameters)
|
||||
{
|
||||
if (string.Compare(kvp.Key, key, StringComparison.Ordinal) == 0)
|
||||
{
|
||||
if (TryGetValueInternal(kvp, type, out object value))
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
throw new InvalidCastException($"Unable to convert the value of Type '{kvp.Value.GetType().FullName}' to '{type.FullName}' for the key '{key}' ");
|
||||
}
|
||||
}
|
||||
|
||||
return GetDefault(type);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Searches <paramref name="parameters"/> for value referenced by <paramref name="key"/>
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the parameter to return</typeparam>
|
||||
/// <param name="parameters">A collection of parameters to search</param>
|
||||
/// <param name="key">The key of the parameter to find</param>
|
||||
/// <param name="value">The value of parameter to return</param>
|
||||
/// <returns>Success if value is found; otherwise returns <c>false</c></returns>
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public static bool TryGetValue<T>(this IEnumerable<KeyValuePair<string, object>> parameters, string key, out T value)
|
||||
{
|
||||
Type type = typeof(T);
|
||||
|
||||
foreach (KeyValuePair<string, object> kvp in parameters)
|
||||
{
|
||||
if (string.Compare(kvp.Key, key, StringComparison.Ordinal) == 0)
|
||||
{
|
||||
bool success = TryGetValueInternal(kvp, typeof(T), out object valueAsObject);
|
||||
value = (T)valueAsObject;
|
||||
return success;
|
||||
}
|
||||
}
|
||||
|
||||
value = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Searches <paramref name="parameters"/> for value referenced by <paramref name="key"/>
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the parameter to return</typeparam>
|
||||
/// <param name="parameters">A collection of parameters to search</param>
|
||||
/// <param name="key">The key of the parameter to find</param>
|
||||
/// <returns>An IEnumerable{T} of all the values referenced by key</returns>
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public static IEnumerable<T> GetValues<T>(this IEnumerable<KeyValuePair<string, object>> parameters, string key)
|
||||
{
|
||||
List<T> values = [];
|
||||
Type type = typeof(T);
|
||||
|
||||
foreach (KeyValuePair<string, object> kvp in parameters)
|
||||
{
|
||||
if (string.Compare(kvp.Key, key, StringComparison.Ordinal) == 0)
|
||||
{
|
||||
TryGetValueInternal(kvp, type, out object value);
|
||||
values.Add((T)value);
|
||||
}
|
||||
}
|
||||
|
||||
return values.AsEnumerable();
|
||||
}
|
||||
|
||||
private static bool TryGetValueInternal(KeyValuePair<string, object> kvp, Type type, out object value)
|
||||
{
|
||||
value = GetDefault(type);
|
||||
bool success = false;
|
||||
if (kvp.Value == null)
|
||||
{
|
||||
success = true;
|
||||
}
|
||||
else if (kvp.Value.GetType() == type)
|
||||
{
|
||||
success = true;
|
||||
value = kvp.Value;
|
||||
}
|
||||
else if (type.IsAssignableFrom(kvp.Value.GetType()))
|
||||
{
|
||||
success = true;
|
||||
value = kvp.Value;
|
||||
}
|
||||
else if (type.IsEnum)
|
||||
{
|
||||
string valueAsString = kvp.Value.ToString();
|
||||
if (Enum.IsDefined(type, valueAsString))
|
||||
{
|
||||
success = true;
|
||||
value = Enum.Parse(type, valueAsString);
|
||||
}
|
||||
else if (int.TryParse(valueAsString, out int numericValue))
|
||||
{
|
||||
success = true;
|
||||
value = Enum.ToObject(type, numericValue);
|
||||
}
|
||||
}
|
||||
|
||||
if (!success && type.GetInterface("System.IConvertible") != null)
|
||||
{
|
||||
success = true;
|
||||
value = System.Convert.ChangeType(kvp.Value, type);
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks to see if key exists in parameter collection
|
||||
/// </summary>
|
||||
/// <param name="parameters">IEnumerable to search</param>
|
||||
/// <param name="key">The key to search the <paramref name="parameters"/> for existence</param>
|
||||
/// <returns><c>true</c> if key exists; <c>false</c> otherwise</returns>
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public static bool ContainsKey(this IEnumerable<KeyValuePair<string, object>> parameters, string key) =>
|
||||
parameters.Any(x => string.Compare(x.Key, key, StringComparison.Ordinal) == 0);
|
||||
|
||||
private static object GetDefault(Type type)
|
||||
{
|
||||
if (type.IsValueType)
|
||||
{
|
||||
return Activator.CreateInstance(type);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,618 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace MAX.Core.Helpers;
|
||||
|
||||
/// <summary>
|
||||
/// 反射操作
|
||||
/// </summary>
|
||||
public static class ReflectionHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取类型描述,使用DescriptionAttribute设置描述
|
||||
/// </summary>
|
||||
/// <typeparam name="T">类型</typeparam>
|
||||
public static string GetDescription<T>()
|
||||
{
|
||||
return GetDescription(Common.GetType<T>());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取类型成员描述,使用DescriptionAttribute设置描述
|
||||
/// </summary>
|
||||
/// <typeparam name="T">类型</typeparam>
|
||||
/// <param name="memberName">成员名称</param>
|
||||
public static string GetDescription<T>(string memberName)
|
||||
{
|
||||
return GetDescription(Common.GetType<T>(), memberName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取类型成员描述,使用DescriptionAttribute设置描述
|
||||
/// </summary>
|
||||
/// <param name="type">类型</param>
|
||||
/// <param name="memberName">成员名称</param>
|
||||
public static string GetDescription(Type type, string memberName)
|
||||
{
|
||||
if (type == null)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(memberName))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
return GetDescription(type.GetTypeInfo().GetMember(memberName).FirstOrDefault());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取类型成员描述,使用DescriptionAttribute设置描述
|
||||
/// </summary>
|
||||
/// <param name="member">成员</param>
|
||||
public static string GetDescription(MemberInfo member)
|
||||
{
|
||||
if (member == null)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
return member.GetCustomAttribute<DescriptionAttribute>() is DescriptionAttribute attribute ? attribute.Description : member.Name;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取显示名称,使用DisplayNameAttribute设置显示名称
|
||||
/// </summary>
|
||||
/// <typeparam name="T">类型</typeparam>
|
||||
public static string GetDisplayName<T>()
|
||||
{
|
||||
return GetDisplayName(Common.GetType<T>());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取显示名称,使用DisplayAttribute或DisplayNameAttribute设置显示名称
|
||||
/// </summary>
|
||||
public static string GetDisplayName(MemberInfo member)
|
||||
{
|
||||
if (member == null)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
if (member.GetCustomAttribute<DisplayAttribute>() is DisplayAttribute displayAttribute)
|
||||
{
|
||||
return displayAttribute.Name;
|
||||
}
|
||||
|
||||
if (member.GetCustomAttribute<DisplayNameAttribute>() is DisplayNameAttribute displayNameAttribute)
|
||||
{
|
||||
return displayNameAttribute.DisplayName;
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取显示名称或描述,使用DisplayNameAttribute设置显示名称,使用DescriptionAttribute设置描述
|
||||
/// </summary>
|
||||
/// <typeparam name="T">类型</typeparam>
|
||||
public static string GetDisplayNameOrDescription<T>()
|
||||
{
|
||||
return GetDisplayNameOrDescription(Common.GetType<T>());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取属性显示名称或描述,使用DisplayAttribute或DisplayNameAttribute设置显示名称,使用DescriptionAttribute设置描述
|
||||
/// </summary>
|
||||
public static string GetDisplayNameOrDescription(MemberInfo member)
|
||||
{
|
||||
string result = GetDisplayName(member);
|
||||
return string.IsNullOrWhiteSpace(result) ? GetDescription(member) : result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类型列表
|
||||
/// </summary>
|
||||
/// <typeparam name="TFind">查找类型</typeparam>
|
||||
/// <param name="assemblies">待查找的程序集列表</param>
|
||||
public static List<Type> FindTypes<TFind>(params Assembly[] assemblies)
|
||||
{
|
||||
Type findType = typeof(TFind);
|
||||
return FindTypes(findType, assemblies);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类型列表
|
||||
/// </summary>
|
||||
/// <param name="findType">查找类型</param>
|
||||
/// <param name="assemblies">待查找的程序集列表</param>
|
||||
public static List<Type> FindTypes(Type findType, params Assembly[] assemblies)
|
||||
{
|
||||
List<Type> result = [];
|
||||
foreach (Assembly assembly in assemblies)
|
||||
{
|
||||
result.AddRange(GetTypes(findType, assembly));
|
||||
}
|
||||
|
||||
return result.Distinct().ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取类型列表
|
||||
/// </summary>
|
||||
private static List<Type> GetTypes(Type findType, Assembly assembly)
|
||||
{
|
||||
List<Type> result = [];
|
||||
if (assembly == null)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
Type[] types;
|
||||
try
|
||||
{
|
||||
types = assembly.GetTypes();
|
||||
}
|
||||
catch (ReflectionTypeLoadException ex)
|
||||
{
|
||||
Debug.WriteLine(ex);
|
||||
return result;
|
||||
}
|
||||
foreach (Type type in types)
|
||||
{
|
||||
AddType(result, findType, type);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加类型
|
||||
/// </summary>
|
||||
private static void AddType(List<Type> result, Type findType, Type type)
|
||||
{
|
||||
if (type.IsInterface || type.IsAbstract)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (findType.IsAssignableFrom(type) == false && MatchGeneric(findType, type) == false)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
result.Add(type);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 泛型匹配
|
||||
/// </summary>
|
||||
private static bool MatchGeneric(Type findType, Type type)
|
||||
{
|
||||
if (findType.IsGenericTypeDefinition == false)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Type definition = findType.GetGenericTypeDefinition();
|
||||
foreach (Type implementedInterface in type.FindInterfaces((filter, criteria) => true, null))
|
||||
{
|
||||
if (implementedInterface.IsGenericType == false)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
return definition.IsAssignableFrom(implementedInterface.GetGenericTypeDefinition());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取实现了接口的所有实例
|
||||
/// </summary>
|
||||
/// <typeparam name="TInterface">接口类型</typeparam>
|
||||
/// <param name="assemblies">待查找的程序集列表</param>
|
||||
public static List<TInterface> GetInstancesByInterface<TInterface>(params Assembly[] assemblies)
|
||||
{
|
||||
return FindTypes<TInterface>(assemblies)
|
||||
.Select(t => CreateInstance<TInterface>(t)).ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 动态创建实例
|
||||
/// </summary>
|
||||
/// <typeparam name="T">目标类型</typeparam>
|
||||
/// <param name="type">类型</param>
|
||||
/// <param name="parameters">传递给构造函数的参数</param>
|
||||
public static T CreateInstance<T>(Type type, params object[] parameters)
|
||||
{
|
||||
return Convert.To<T>(Activator.CreateInstance(type, parameters));
|
||||
}
|
||||
|
||||
public static Type GetType(string typeName)
|
||||
{
|
||||
|
||||
Type type = null;
|
||||
|
||||
Assembly[] assemblyArray = AppDomain.CurrentDomain.GetAssemblies();
|
||||
|
||||
int assemblyArrayLength = assemblyArray.Length;
|
||||
|
||||
for (int i = 0; i < assemblyArrayLength; ++i)
|
||||
{
|
||||
|
||||
type = assemblyArray[i].GetType(typeName);
|
||||
|
||||
if (type != null)
|
||||
{
|
||||
return type;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < assemblyArrayLength; ++i)
|
||||
{
|
||||
Type[] typeArray = assemblyArray[i].GetTypes();
|
||||
int typeArrayLength = typeArray.Length;
|
||||
for (int j = 0; j < typeArrayLength; ++j)
|
||||
{
|
||||
if (typeArray[j].Name.Equals(typeName))
|
||||
{
|
||||
return typeArray[j];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return type;
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取程序集
|
||||
/// </summary>
|
||||
/// <param name="assemblyName">程序集名称</param>
|
||||
public static Assembly GetAssembly(string assemblyName)
|
||||
{
|
||||
return Assembly.Load(new AssemblyName(assemblyName));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否布尔类型
|
||||
/// </summary>
|
||||
/// <param name="member">成员</param>
|
||||
public static bool IsBool(MemberInfo member)
|
||||
{
|
||||
if (member == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return member.MemberType switch
|
||||
{
|
||||
MemberTypes.TypeInfo => member.ToString() == "System.Boolean",
|
||||
MemberTypes.Property => IsBool((PropertyInfo)member),
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否布尔类型
|
||||
/// </summary>
|
||||
private static bool IsBool(PropertyInfo property)
|
||||
{
|
||||
return property.PropertyType == typeof(bool) || property.PropertyType == typeof(bool?);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否枚举类型
|
||||
/// </summary>
|
||||
/// <param name="member">成员</param>
|
||||
public static bool IsEnum(MemberInfo member)
|
||||
{
|
||||
if (member == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return member.MemberType switch
|
||||
{
|
||||
MemberTypes.TypeInfo => ((TypeInfo)member).IsEnum,
|
||||
MemberTypes.Property => IsEnum((PropertyInfo)member),
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否枚举类型
|
||||
/// </summary>
|
||||
private static bool IsEnum(PropertyInfo property)
|
||||
{
|
||||
if (property.PropertyType.GetTypeInfo().IsEnum)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
Type value = Nullable.GetUnderlyingType(property.PropertyType);
|
||||
if (value == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return value.GetTypeInfo().IsEnum;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否日期类型
|
||||
/// </summary>
|
||||
/// <param name="member">成员</param>
|
||||
public static bool IsDate(MemberInfo member)
|
||||
{
|
||||
if (member == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return member.MemberType switch
|
||||
{
|
||||
MemberTypes.TypeInfo => member.ToString() == "System.DateTime",
|
||||
MemberTypes.Property => IsDate((PropertyInfo)member),
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否日期类型
|
||||
/// </summary>
|
||||
private static bool IsDate(PropertyInfo property)
|
||||
{
|
||||
if (property.PropertyType == typeof(DateTime))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (property.PropertyType == typeof(DateTime?))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否整型
|
||||
/// </summary>
|
||||
/// <param name="member">成员</param>
|
||||
public static bool IsInt(MemberInfo member)
|
||||
{
|
||||
if (member == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return member.MemberType switch
|
||||
{
|
||||
MemberTypes.TypeInfo => member.ToString() is "System.Int32" or "System.Int16" or "System.Int64",
|
||||
MemberTypes.Property => IsInt((PropertyInfo)member),
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否整型
|
||||
/// </summary>
|
||||
private static bool IsInt(PropertyInfo property)
|
||||
{
|
||||
if (property.PropertyType == typeof(int))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (property.PropertyType == typeof(int?))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (property.PropertyType == typeof(short))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (property.PropertyType == typeof(short?))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (property.PropertyType == typeof(long))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (property.PropertyType == typeof(long?))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否数值类型
|
||||
/// </summary>
|
||||
/// <param name="member">成员</param>
|
||||
public static bool IsNumber(MemberInfo member)
|
||||
{
|
||||
if (member == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (IsInt(member))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return member.MemberType switch
|
||||
{
|
||||
MemberTypes.TypeInfo => member.ToString() is "System.Double" or "System.Decimal" or "System.Single",
|
||||
MemberTypes.Property => IsNumber((PropertyInfo)member),
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否数值类型
|
||||
/// </summary>
|
||||
private static bool IsNumber(PropertyInfo property)
|
||||
{
|
||||
if (property.PropertyType == typeof(double))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (property.PropertyType == typeof(double?))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (property.PropertyType == typeof(decimal))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (property.PropertyType == typeof(decimal?))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (property.PropertyType == typeof(float))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (property.PropertyType == typeof(float?))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否集合
|
||||
/// </summary>
|
||||
/// <param name="type">类型</param>
|
||||
public static bool IsCollection(Type type)
|
||||
{
|
||||
if (type.IsArray)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return IsGenericCollection(type);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否泛型集合
|
||||
/// </summary>
|
||||
/// <param name="type">类型</param>
|
||||
public static bool IsGenericCollection(Type type)
|
||||
{
|
||||
if (!type.IsGenericType)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Type typeDefinition = type.GetGenericTypeDefinition();
|
||||
return typeDefinition == typeof(IEnumerable<>)
|
||||
|| typeDefinition == typeof(IReadOnlyCollection<>)
|
||||
|| typeDefinition == typeof(IReadOnlyList<>)
|
||||
|| typeDefinition == typeof(ICollection<>)
|
||||
|| typeDefinition == typeof(IList<>)
|
||||
|| typeDefinition == typeof(List<>);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从目录中获取所有程序集
|
||||
/// </summary>
|
||||
/// <param name="directoryPath">目录绝对路径</param>
|
||||
public static List<Assembly> GetAssemblies(string directoryPath)
|
||||
{
|
||||
return Directory.GetFiles(directoryPath, "*.*", SearchOption.AllDirectories).ToList()
|
||||
.Where(t => t.EndsWith(".exe") || t.EndsWith(".dll"))
|
||||
.Select(path => Assembly.Load(new AssemblyName(path))).ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取公共属性列表
|
||||
/// </summary>
|
||||
/// <param name="instance">实例</param>
|
||||
public static List<CommonItem> GetPublicProperties(object instance)
|
||||
{
|
||||
PropertyInfo[] properties = instance.GetType().GetProperties();
|
||||
return properties.ToList().Select(t => new CommonItem(t.Name, t.GetValue(instance))).ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取顶级基类
|
||||
/// </summary>
|
||||
/// <typeparam name="T">类型</typeparam>
|
||||
public static Type GetTopBaseType<T>()
|
||||
{
|
||||
return GetTopBaseType(typeof(T));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取顶级基类
|
||||
/// </summary>
|
||||
/// <param name="type">类型</param>
|
||||
public static Type GetTopBaseType(Type type)
|
||||
{
|
||||
if (type == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (type.IsInterface)
|
||||
{
|
||||
return type;
|
||||
}
|
||||
|
||||
if (type.BaseType == typeof(object))
|
||||
{
|
||||
return type;
|
||||
}
|
||||
|
||||
return GetTopBaseType(type.BaseType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取元素类型,如果是集合,返回集合的元素类型
|
||||
/// </summary>
|
||||
/// <param name="type">类型</param>
|
||||
public static Type GetElementType(Type type)
|
||||
{
|
||||
if (IsCollection(type) == false)
|
||||
{
|
||||
return type;
|
||||
}
|
||||
|
||||
if (type.IsArray)
|
||||
{
|
||||
return type.GetElementType();
|
||||
}
|
||||
|
||||
Type[] genericArgumentsTypes = type.GetTypeInfo().GetGenericArguments();
|
||||
if (genericArgumentsTypes == null || genericArgumentsTypes.Length == 0)
|
||||
{
|
||||
throw new ArgumentException("泛型类型参数不能为空");
|
||||
}
|
||||
|
||||
return genericArgumentsTypes[0];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace MAX.Core.Helpers;
|
||||
|
||||
public class UriHelper
|
||||
{
|
||||
#region 对外公开方法
|
||||
/// <summary>
|
||||
/// 带有前缀的域名
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <returns></returns>
|
||||
public static string GetSchemeDomain(string url)
|
||||
{
|
||||
Uri uri = new(url);
|
||||
return $"{uri.Scheme}://{uri.Authority}";
|
||||
}
|
||||
/// <summary>
|
||||
/// 获取域名
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <returns></returns>
|
||||
public static string GetDomain(string url)
|
||||
{
|
||||
Uri uri = new(url);
|
||||
return uri.Host;
|
||||
}
|
||||
/// <summary>
|
||||
/// 获取 URL
|
||||
/// </summary>
|
||||
/// <param name="url">URL 地址</param>
|
||||
/// <returns></returns>
|
||||
public static string GetUrl(string url)
|
||||
{
|
||||
Uri uri = new(url);
|
||||
return uri.AbsolutePath;
|
||||
}
|
||||
/// <summary>
|
||||
/// URL 解码
|
||||
/// </summary>
|
||||
/// <param name="url">URL 地址</param>
|
||||
/// <returns></returns>
|
||||
public static string UrlDecode(string url)
|
||||
{
|
||||
return System.Web.HttpUtility.UrlDecode(url);
|
||||
}
|
||||
/// <summary>
|
||||
/// URL 解码
|
||||
/// </summary>
|
||||
/// <param name="url">URL 地址</param>
|
||||
/// <param name="encoding">编码格式</param>
|
||||
/// <returns></returns>
|
||||
public static string UrlDecode(string url, Encoding encoding)
|
||||
{
|
||||
return System.Web.HttpUtility.UrlDecode(url, encoding);
|
||||
}
|
||||
/// <summary>
|
||||
/// URL 编码
|
||||
/// </summary>
|
||||
/// <param name="url">URL 地址</param>
|
||||
/// <returns></returns>
|
||||
public static string UrlEncode(string url)
|
||||
{
|
||||
return System.Web.HttpUtility.UrlEncode(url);
|
||||
}
|
||||
/// <summary>
|
||||
/// URL 编码
|
||||
/// </summary>
|
||||
/// <param name="url">URL 地址</param>
|
||||
/// <param name="encoding">编码格式</param>
|
||||
/// <returns></returns>
|
||||
public static string UrlEncode(string url, Encoding encoding)
|
||||
{
|
||||
return System.Web.HttpUtility.UrlEncode(url, encoding);
|
||||
}
|
||||
/// <summary>
|
||||
/// 获取参数列表
|
||||
/// </summary>
|
||||
/// <param name="url">Url 地址</param>
|
||||
/// <returns></returns>
|
||||
public static List<string> GetQueryList(string url)
|
||||
{
|
||||
return GetQueryList(url, Encoding.UTF8);
|
||||
}
|
||||
/// <summary>
|
||||
/// 获取参数列表
|
||||
/// </summary>
|
||||
/// <param name="url">Url 地址</param>
|
||||
/// <param name="encoding">编码格式</param>
|
||||
/// <returns></returns>
|
||||
public static List<string> GetQueryList(string url, Encoding encoding)
|
||||
{
|
||||
Uri uri = new(url);
|
||||
string query = uri.PathAndQuery;
|
||||
if (query.IndexOf(".") > 0)
|
||||
{
|
||||
query = query.Substring(0, query.IndexOf("."));
|
||||
}
|
||||
|
||||
List<string> dataList = [];
|
||||
string[] queryDataList = query.Split(new string[] { "/" }, StringSplitOptions.RemoveEmptyEntries);
|
||||
if (queryDataList != null && queryDataList.Length > 0)
|
||||
{
|
||||
foreach (string queryData in queryDataList)
|
||||
{
|
||||
dataList.Add(System.Web.HttpUtility.UrlDecode(queryData, encoding));
|
||||
}
|
||||
}
|
||||
return dataList;
|
||||
}
|
||||
/// <summary>
|
||||
/// 获取参数字典
|
||||
/// </summary>
|
||||
/// <param name="url">Url 地址</param>
|
||||
/// <returns></returns>
|
||||
public static Dictionary<string, string> GetQueryDict(string url)
|
||||
{
|
||||
return GetQueryDict(url, Encoding.UTF8);
|
||||
}
|
||||
/// <summary>
|
||||
/// 获取参数字典
|
||||
/// </summary>
|
||||
/// <param name="url">Url 地址</param>
|
||||
/// <param name="encoding">编码格式</param>
|
||||
/// <returns></returns>
|
||||
public static Dictionary<string, string> GetQueryDict(string url, Encoding encoding)
|
||||
{
|
||||
Uri uri = new(url);
|
||||
string query = uri.Query.TrimStart(new char[] { '?' });
|
||||
|
||||
Dictionary<string, string> dataDict = [];
|
||||
string[] queryDataList = query.Split(new string[] { "&" }, StringSplitOptions.RemoveEmptyEntries);
|
||||
if (queryDataList != null && queryDataList.Length > 0)
|
||||
{
|
||||
foreach (string queryData in queryDataList)
|
||||
{
|
||||
string[] keyValue = queryData.Split(new string[] { "=" }, StringSplitOptions.RemoveEmptyEntries);
|
||||
if (!dataDict.ContainsKey(keyValue[0]))
|
||||
{
|
||||
dataDict.Add(keyValue[0].ToLower(), System.Web.HttpUtility.UrlDecode(keyValue[1], encoding));
|
||||
}
|
||||
}
|
||||
}
|
||||
return dataDict;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using MAX.Core.DependencyInjection;
|
||||
|
||||
namespace MAX.Core.Infrastructure;
|
||||
|
||||
/// <summary>
|
||||
/// 启动器:实现 <see cref="IDependencyRegistrar"/>,由宿主项目统一发现并按 <see cref="Priority"/> 排序执行。
|
||||
/// </summary>
|
||||
public interface IStartup : IDependencyRegistrar
|
||||
{
|
||||
/// <summary>
|
||||
/// 优先级(优先级越高越先执行)
|
||||
/// </summary>
|
||||
int Priority { get; }
|
||||
/// <summary>
|
||||
/// 初始化
|
||||
/// </summary>
|
||||
void Initialize();
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
namespace MAX.Core.Infrastructure;
|
||||
|
||||
/// <summary>
|
||||
/// 单例,需要在当前类中定义
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public class SingleInstance<T> where T : class
|
||||
{
|
||||
private static readonly object lockObj = new();
|
||||
private static T mySelf;//= default(T);
|
||||
public static T Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (lockObj)
|
||||
{
|
||||
mySelf ??= InstanceCreator.CreateInstance<T>();
|
||||
}
|
||||
|
||||
return mySelf;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class InstanceCreator
|
||||
{
|
||||
public static T CreateInstance<T>()
|
||||
{
|
||||
Type type = typeof(T);
|
||||
try
|
||||
{
|
||||
return (T)type.Assembly.CreateInstance(type.FullName, true, BindingFlags.NonPublic | BindingFlags.Instance, null, null, null, null);
|
||||
}
|
||||
catch (MissingMethodException ex)
|
||||
{
|
||||
throw new Exception($"{ex.Message}\n(单例模式下,构造函数必须为 private)");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
|
||||
namespace MAX.Core.Logger;
|
||||
|
||||
/// <summary>
|
||||
/// Implementation of <see cref="ILoggerBase"/> that logs into a <see cref="TextWriter"/>.
|
||||
/// </summary>
|
||||
public class ConsoleLogger : ILoggerFacade
|
||||
{
|
||||
private readonly bool _debug;
|
||||
private readonly bool _console;
|
||||
|
||||
public bool IsDebugEnabled => true;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of <see cref="ConsoleLogger"/> that writes to
|
||||
/// the console output.
|
||||
/// </summary>
|
||||
public ConsoleLogger(bool debug = true, bool console = true)
|
||||
{
|
||||
_debug = debug;
|
||||
_console = console;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
///// <summary>
|
||||
///// Disposes the associated <see cref="TextWriter"/>.
|
||||
///// </summary>
|
||||
///// <param name="disposing">When <see langword="true"/>, disposes the associated <see cref="TextWriter"/>.</param>
|
||||
//protected virtual void Dispose(bool disposing)
|
||||
//{
|
||||
// if (disposing)
|
||||
// {
|
||||
// writer?.Dispose();
|
||||
// }
|
||||
//}
|
||||
|
||||
///<summary>
|
||||
///Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
|
||||
///</summary>
|
||||
/// <remarks>Calls <see cref="Dispose(bool)"/></remarks>.
|
||||
///<filterpriority>2</filterpriority>
|
||||
public void Dispose()
|
||||
{
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
public void Debug(Exception ex, string message)
|
||||
{
|
||||
Log(message, LogLevel.Debug);
|
||||
}
|
||||
|
||||
public void Debug(string message)
|
||||
{
|
||||
Log(message, LogLevel.Debug);
|
||||
}
|
||||
|
||||
public void Info(Exception ex, string message)
|
||||
{
|
||||
Log(message, LogLevel.Info);
|
||||
}
|
||||
|
||||
public void Info(string message)
|
||||
{
|
||||
Log(message, LogLevel.Info);
|
||||
}
|
||||
|
||||
public void Warn(Exception ex, string message)
|
||||
{
|
||||
Log(message, LogLevel.Warn);
|
||||
}
|
||||
|
||||
public void Warn(string message)
|
||||
{
|
||||
Log(message, LogLevel.Warn);
|
||||
}
|
||||
|
||||
public void Error(Exception ex, string message)
|
||||
{
|
||||
Log(message, LogLevel.Error);
|
||||
}
|
||||
|
||||
public void Error(string message)
|
||||
{
|
||||
Log(message, LogLevel.Error);
|
||||
}
|
||||
|
||||
public void Fatal(Exception ex, string message)
|
||||
{
|
||||
Log(message, LogLevel.Fatal);
|
||||
}
|
||||
|
||||
public void Fatal(string message)
|
||||
{
|
||||
Log(message, LogLevel.Fatal);
|
||||
}
|
||||
|
||||
public void Error(Exception ex)
|
||||
{
|
||||
Log(ex.Message, LogLevel.Error);
|
||||
}
|
||||
|
||||
public void Log(string message, LogLevel level)
|
||||
{
|
||||
string messageToLog = string.Format(CultureInfo.InvariantCulture, "{1}: {2}. Timestamp:{0:u}.", DateTime.Now,
|
||||
level.ToString().ToUpper(CultureInfo.InvariantCulture), message);
|
||||
//Trace.WriteLine(messageToLog);
|
||||
if (_debug)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine(messageToLog);
|
||||
}
|
||||
if (_console)
|
||||
{
|
||||
Console.WriteLine(messageToLog);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using MAX.Core.Infrastructure;
|
||||
|
||||
namespace MAX.Core.Logger;
|
||||
|
||||
/// <summary>自定义日志记录器接口</summary>
|
||||
public interface ICustomLogger
|
||||
{
|
||||
/// <summary>记录日志信息</summary>
|
||||
/// <param name="group">日志分组</param>
|
||||
/// <param name="message">日志内容</param>
|
||||
void Log(string group, string message);
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
using System;
|
||||
|
||||
namespace MAX.Core.Logger;
|
||||
|
||||
/// <summary>
|
||||
/// 日志操作
|
||||
/// </summary>
|
||||
public interface ILoggerFacade
|
||||
{
|
||||
/// <summary>
|
||||
/// 调试级别是否启用
|
||||
/// </summary>
|
||||
bool IsDebugEnabled { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 调试
|
||||
/// </summary>
|
||||
void Debug(Exception ex, string message);
|
||||
/// <summary>
|
||||
/// 调试
|
||||
/// </summary>
|
||||
/// <param name="message">日志消息</param>
|
||||
void Debug(string message);
|
||||
/// <summary>
|
||||
/// 信息
|
||||
/// </summary>
|
||||
void Info(Exception ex, string message);
|
||||
/// <summary>
|
||||
/// 信息
|
||||
/// </summary>
|
||||
/// <param name="message">日志消息</param>
|
||||
void Info(string message);
|
||||
/// <summary>
|
||||
/// 警告
|
||||
/// </summary>
|
||||
void Warn(Exception ex, string message);
|
||||
/// <summary>
|
||||
/// 警告
|
||||
/// </summary>
|
||||
/// <param name="message">日志消息</param>
|
||||
void Warn(string message);
|
||||
/// <summary>
|
||||
/// 错误
|
||||
/// </summary>
|
||||
void Error(Exception ex, string message);
|
||||
/// <summary>
|
||||
/// 错误
|
||||
/// </summary>
|
||||
/// <param name="message">日志消息</param>
|
||||
void Error(string message);
|
||||
/// <summary>
|
||||
/// 致命错误
|
||||
/// </summary>
|
||||
void Fatal(Exception ex, string message);
|
||||
/// <summary>
|
||||
/// 致命错误
|
||||
/// </summary>
|
||||
/// <param name="message">日志消息</param>
|
||||
void Fatal(string message);
|
||||
void Error(Exception ex);
|
||||
|
||||
/// <summary>
|
||||
/// 写入日志
|
||||
/// </summary>
|
||||
/// <param name="message"></param>
|
||||
/// <param name="level"></param>
|
||||
void Log(string message, LogLevel level);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
namespace MAX.Core.Logger;
|
||||
|
||||
/// <summary>
|
||||
/// 日志等级
|
||||
/// </summary>
|
||||
public enum LogLevel
|
||||
{
|
||||
Trace = 0,
|
||||
Debug = 1,
|
||||
Info = 2,
|
||||
Warn = 3,
|
||||
Error = 4,
|
||||
Fatal = 5,
|
||||
None = 6
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<NoWarn>NU1608;CS1574;CS1573;CS1572;CS1571;CS1711;CS1570;CS1591</NoWarn>
|
||||
<GenerateDocumentationFile>True</GenerateDocumentationFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MediatR" Version="9.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,197 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace MAX.Core.Reflections;
|
||||
|
||||
public partial class AppTypeFinder : Finder
|
||||
{
|
||||
public static AppTypeFinder Instance { get; } = new AppTypeFinder();
|
||||
|
||||
/// <summary>
|
||||
/// 所有程序集
|
||||
/// </summary>
|
||||
public static List<Assembly> Assemblies => Instance.GetAssemblies();
|
||||
|
||||
public static Exception LoadDLL(string dllPath, out Assembly assembly)
|
||||
{
|
||||
return Instance.LoadAssemblyToAppDomain(dllPath, out assembly);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 程序集是否匹配
|
||||
/// </summary>
|
||||
protected override bool Match(string assemblyName)
|
||||
{
|
||||
if (base.Match(assemblyName))
|
||||
{
|
||||
if (AvailablePrefix().Exists(assemblyName.StartsWith))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return Regex.IsMatch(assemblyName, "^(MAX\\.)", RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
protected virtual List<string> AvailablePrefix()
|
||||
{
|
||||
return ["MAX."];
|
||||
}
|
||||
|
||||
public static IEnumerable<Type> GetAllTypes()
|
||||
{
|
||||
List<Type> types = [];
|
||||
foreach (Assembly item in Assemblies)
|
||||
{
|
||||
try
|
||||
{
|
||||
types.AddRange(item.GetTypes());
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
return types;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找当前程序集中所有继承或实现了指定类型的非抽象类。
|
||||
/// </summary>
|
||||
/// <param name="targetType">
|
||||
/// 要匹配的目标类型,可以是:
|
||||
/// <list type="bullet">
|
||||
/// <item><description>普通接口类型(例如 <c>IMyService</c>)</description></item>
|
||||
/// <item><description>开放泛型接口类型(例如 <c>IRepository<></c>)</description></item>
|
||||
/// <item><description>普通基类类型(例如 <c>BaseHandler</c>)</description></item>
|
||||
/// <item><description>开放泛型基类类型(例如 <c>BaseHandler<></c>)</description></item>
|
||||
/// </list>
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// 所有满足条件的具体类(非抽象类)的 <see cref="Type"/> 数组。
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// 当 <paramref name="targetType"/> 为 <c>null</c> 时抛出。
|
||||
/// </exception>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// 此方法会遍历 <c>GetAllTypes()</c> 返回的所有类型,并筛选出:
|
||||
/// <list type="bullet">
|
||||
/// <item><description>实现了指定接口的类(包括开放泛型接口的封闭实现)</description></item>
|
||||
/// <item><description>继承了指定基类的类(包括开放泛型基类的封闭实现)</description></item>
|
||||
/// </list>
|
||||
/// </para>
|
||||
/// <para>示例:</para>
|
||||
/// <code>
|
||||
/// // 查找所有实现了 IEnumDisplayHandler<T> 的类
|
||||
/// var handlers = FindAllDerivedTypes(typeof(IEnumDisplayHandler<>));
|
||||
///
|
||||
/// // 查找所有实现了 IMyService 的类
|
||||
/// var services = FindAllDerivedTypes(typeof(IMyService));
|
||||
///
|
||||
/// // 查找所有继承 BaseHandler 的类
|
||||
/// var subclasses = FindAllDerivedTypes(typeof(BaseHandler));
|
||||
///
|
||||
/// // 查找所有继承开放泛型基类 BaseHandler<T> 的类
|
||||
/// var genericSubclasses = FindAllDerivedTypes(typeof(BaseHandler<>));
|
||||
/// </code>
|
||||
/// </remarks>
|
||||
public static Type[] FindAllDerivedTypes(Type targetType)
|
||||
{
|
||||
if (targetType == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(targetType));
|
||||
}
|
||||
|
||||
return GetAllTypes().Where(t =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!t.IsClass || t.IsAbstract)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// 开放泛型接口
|
||||
if (targetType.IsInterface && targetType.IsGenericTypeDefinition)
|
||||
{
|
||||
return t.GetInterfaces().Any(i =>
|
||||
i.IsGenericType && i.GetGenericTypeDefinition() == targetType);
|
||||
}
|
||||
|
||||
// 普通接口
|
||||
if (targetType.IsInterface)
|
||||
{
|
||||
return targetType.IsAssignableFrom(t);
|
||||
}
|
||||
|
||||
// 开放泛型基类
|
||||
if (targetType.IsGenericTypeDefinition)
|
||||
{
|
||||
return IsSubclassOfRawGeneric(t, targetType);
|
||||
}
|
||||
|
||||
// 普通基类
|
||||
return targetType.IsAssignableFrom(t) && t != targetType;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
})
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查指定类型是否继承自某个开放泛型基类。
|
||||
/// </summary>
|
||||
/// <param name="type">要检查的类型。</param>
|
||||
/// <param name="generic">开放泛型基类类型,例如 <c>BaseHandler<></c>。</param>
|
||||
/// <returns>
|
||||
/// 如果 <paramref name="type"/> 的继承链中存在指定的开放泛型基类,则返回 <c>true</c>;否则返回 <c>false</c>。
|
||||
/// </returns>
|
||||
/// <remarks>
|
||||
/// 此方法会沿着 <paramref name="type"/> 的继承链向上遍历,
|
||||
/// 并在每一级检查是否为指定的开放泛型基类定义。
|
||||
/// </remarks>
|
||||
private static bool IsSubclassOfRawGeneric(Type type, Type generic)
|
||||
{
|
||||
while (type != null && type != typeof(object))
|
||||
{
|
||||
Type cur = type.IsGenericType ? type.GetGenericTypeDefinition() : type;
|
||||
if (generic == cur)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
type = type.BaseType;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 泛型包装方法,查找所有继承或实现了指定类型 <typeparamref name="T"/> 的非抽象类。
|
||||
/// </summary>
|
||||
/// <typeparam name="T">
|
||||
/// 目标类型,可以是接口、开放泛型接口、基类或开放泛型基类。
|
||||
/// </typeparam>
|
||||
/// <returns>
|
||||
/// 所有满足条件的具体类(非抽象类)的 <see cref="Type"/> 数组。
|
||||
/// </returns>
|
||||
/// <remarks>
|
||||
/// 调用示例:
|
||||
/// <code>
|
||||
/// var handlers = FindAllDerivedTypes<IEnumDisplayHandler<>>();
|
||||
/// var services = FindAllDerivedTypes<IMyService>();
|
||||
/// var subclasses = FindAllDerivedTypes<BaseHandler>();
|
||||
/// var genericSubclasses = FindAllDerivedTypes<BaseHandler<>>();
|
||||
/// </code>
|
||||
/// </remarks>
|
||||
public static Type[] FindAllDerivedTypes<T>() => FindAllDerivedTypes(typeof(T));
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
using MAX.Core.Helpers;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace MAX.Core.Reflections;
|
||||
|
||||
public class Finder
|
||||
{
|
||||
/// <summary>
|
||||
/// 跳过的程序集
|
||||
/// </summary>
|
||||
private const string SkipAssemblies = "^System|^Microsoft|^mscorlib|^netstandard|^Autofac|^AutoMapper|^EntityFramework|^Newtonsoft|^Castle|^NLog|^Pomelo|^AspectCore|^Xunit|^Nito|^Npgsql|^Exceptionless|^MySqlConnector|^Anonymously|^libuv|^api-ms|^clrcompression|^clretwrc|^clrjit|^coreclr|^dbgshim|^e_sqlite3|^hostfxr|^hostpolicy|^MessagePack|^mscordaccore|^mscordbi|^mscorrc|^sni|^sos|^SOS\\.NETCore|^sos_amd64|^SQLitePCLRaw|^StackExchange|^Swashbuckle|^WindowsBase|^ucrtbase|^DotNetCore\\.CAP|^MongoDB|^Confluent\\.Kafka|^librdkafka|^EasyCaching|^RabbitMQ|^Consul|^Dapper|^EnyimMemcachedCore|^Pipelines|^DnsClient|^IdentityModel|^zlib|^Runtime\\.|^Google|^Grpc|^Internal\\.|^Azure|^DevExpress\\.|^FreeSql\\.|^PresentationFramework|^PresentationCore|^Prism|^DirectWriteForwarder|^MAX\\.Common|^ReflectionHelperTypesPublicWrappers|^UIAutomation|^WindowsFormsIntegration";
|
||||
|
||||
/// <summary>
|
||||
/// 获取程序集列表
|
||||
/// </summary>
|
||||
public virtual List<Assembly> GetAssemblies()
|
||||
{
|
||||
LoadAssemblies(AppDomain.CurrentDomain.BaseDirectory);
|
||||
return GetAssembliesFromCurrentAppDomain();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 加载程序集到当前应用程序域
|
||||
/// </summary>
|
||||
/// <param name="path">目录绝对路径</param>
|
||||
protected void LoadAssemblies(string path)
|
||||
{
|
||||
foreach (string file in Directory.GetFiles(path, "*.dll"))
|
||||
{
|
||||
if (Match(Path.GetFileName(file)) == false)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
LoadAssemblyToAppDomain(file, out _);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 程序集是否匹配
|
||||
/// </summary>
|
||||
protected virtual bool Match(string assemblyName)
|
||||
{
|
||||
return Regex.IsMatch(assemblyName, SkipAssemblies, RegexOptions.IgnoreCase | RegexOptions.Compiled) == false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将程序集添加当前应用程序域
|
||||
/// </summary>
|
||||
protected Exception LoadAssemblyToAppDomain(string file, out Assembly assembly)
|
||||
{
|
||||
try
|
||||
{
|
||||
//Debug.WriteLine($"Load Assembly : {Path.GetFileName(file)}");
|
||||
AssemblyName assemblyName = AssemblyName.GetAssemblyName(file);
|
||||
assembly = AppDomain.CurrentDomain.Load(assemblyName);
|
||||
return null;
|
||||
}
|
||||
catch (FileNotFoundException)
|
||||
{
|
||||
// 如果找不到文件,回退到 LoadFrom 方式
|
||||
try
|
||||
{
|
||||
assembly = Assembly.LoadFrom(file);
|
||||
return null;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
assembly = null;
|
||||
return ex;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
assembly = null;
|
||||
return ex;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从当前应用程序域获取程序集列表
|
||||
/// </summary>
|
||||
private List<Assembly> GetAssembliesFromCurrentAppDomain()
|
||||
{
|
||||
List<Assembly> result = [];
|
||||
foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
|
||||
{
|
||||
if (Match(assembly))
|
||||
{
|
||||
result.Add(assembly);
|
||||
}
|
||||
}
|
||||
return result.Distinct().ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 程序集是否匹配
|
||||
/// </summary>
|
||||
private bool Match(Assembly assembly)
|
||||
{
|
||||
return !Regex.IsMatch(assembly.FullName, SkipAssemblies, RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类型列表
|
||||
/// </summary>
|
||||
/// <typeparam name="T">查找类型</typeparam>
|
||||
/// <param name="assemblies">在指定的程序集列表中查找</param>
|
||||
public List<Type> Find<T>(List<Assembly> assemblies = null)
|
||||
{
|
||||
return Find(typeof(T), assemblies);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类型列表
|
||||
/// </summary>
|
||||
/// <param name="findType">查找类型</param>
|
||||
/// <param name="assemblies">在指定的程序集列表中查找</param>
|
||||
public List<Type> Find(Type findType, List<Assembly> assemblies = null)
|
||||
{
|
||||
assemblies ??= GetAssemblies();
|
||||
return ReflectionHelper.FindTypes(findType, assemblies.ToArray());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/***
|
||||
* _ooOoo_
|
||||
* o8888888o
|
||||
* 88" . "88
|
||||
* (| -_- |)
|
||||
* O\ = /O
|
||||
* ____/`---'\____
|
||||
* . ' \\| |// `.
|
||||
* / \\||| : |||// \
|
||||
* / _||||| -:- |||||- \
|
||||
* | | \\\ - /// | |
|
||||
* | \_| ''\---/'' | |
|
||||
* \ .-\__ `-` ___/-. /
|
||||
* ___`. .' /--.--\ `. . __
|
||||
* ."" '< `.___\_<|>_/___.' >'"".
|
||||
* | | : `- \`.;`\ _ /`;.`/ - ` : | |
|
||||
* \ \ `-. \_ __\ /__ _/ .-` / /
|
||||
* ======`-.____`-.___\_____/___.-`____.-'======
|
||||
* `=---='
|
||||
*
|
||||
* .............................................
|
||||
* 佛祖保佑 永无BUG
|
||||
* 快加工资 不改需求
|
||||
*/
|
||||
|
||||
using MAX.Core.DependencyInjection;
|
||||
using MAX.Core.Infrastructure;
|
||||
|
||||
namespace MAX.Core;
|
||||
|
||||
public class Startup : IStartup
|
||||
{
|
||||
public int Priority => int.MaxValue;
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public void Register(IServiceRegistrar registrar)
|
||||
{
|
||||
// 示例:恢复 LazyServiceProvider 后,可在这里注册为单例
|
||||
// registrar.RegisterSingleton<ILazyServiceProvider, LazyServiceProvider>();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<Project>
|
||||
|
||||
<!-- See https://aka.ms/dotnet/msbuild/customize for more details on customizing your build -->
|
||||
<PropertyGroup>
|
||||
<DefaultTargetFramework>net10.0</DefaultTargetFramework>
|
||||
<TargetFramework>$(DefaultTargetFramework)</TargetFramework>
|
||||
<LangVersion>12.0</LangVersion>
|
||||
<SuppressTfmSupportBuildWarnings>true</SuppressTfmSupportBuildWarnings>
|
||||
<CheckEolTargetFramework>false</CheckEolTargetFramework>
|
||||
<SuppressNETCoreSdkPreviewMessage>true</SuppressNETCoreSdkPreviewMessage>
|
||||
<Nullable>annotations</Nullable>
|
||||
<NoWarn>CS1591</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="PolySharp" Version="1.16.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,30 @@
|
||||
namespace StoreHub.Framework.Commons
|
||||
{
|
||||
public class ApiResult<T>
|
||||
{
|
||||
public int Code { get; set; }
|
||||
|
||||
public T? Data { get; set; }
|
||||
|
||||
public string? Msg { get; set; }
|
||||
}
|
||||
|
||||
public static class ApiResult
|
||||
{
|
||||
public static ApiResult<T> Success<T>(T data, string? msg = null)
|
||||
=> new()
|
||||
{
|
||||
Code = 200,
|
||||
Data = data,
|
||||
Msg = msg ?? "success"
|
||||
};
|
||||
|
||||
public static ApiResult<object?> Error(string msg, int code = 500)
|
||||
=> new()
|
||||
{
|
||||
Code = code,
|
||||
Data = null,
|
||||
Msg = msg
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace StoreHub.Framework.DependencyInjection
|
||||
{
|
||||
public static class FreeSqlDB
|
||||
{
|
||||
public static IServiceCollection AddFreeSqlDB(this IServiceCollection services)
|
||||
{
|
||||
Func<IServiceProvider, IFreeSql> fsqlFactory = r =>
|
||||
{
|
||||
var config = r.GetRequiredService<IConfiguration>();
|
||||
var connection = config.GetConnectionString("DefaultConnection");
|
||||
IFreeSql fsql = new FreeSql.FreeSqlBuilder()
|
||||
.UseConnectionString(FreeSql.DataType.MySql, connection)
|
||||
.UseAdoConnectionPool(true)
|
||||
.Build();
|
||||
return fsql;
|
||||
};
|
||||
services.AddSingleton<IFreeSql>(fsqlFactory);
|
||||
return services;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
using Asp.Versioning.OpenApi;
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using Microsoft.OpenApi;
|
||||
using StoreHub.Framework.Security;
|
||||
using System.Security.Claims;
|
||||
using System.Text;
|
||||
|
||||
namespace StoreHub.Framework.DependencyInjection;
|
||||
|
||||
/// <summary>
|
||||
/// JWT 认证服务注册入口(由宿主 Program.cs 调用)。
|
||||
/// </summary>
|
||||
public static class JwtServiceCollectionExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// 注册 JWT 认证:
|
||||
/// <list type="number">
|
||||
/// <item>绑定 <c>Jwt</c> 配置节到 <see cref="JwtOptions"/>,并在启动时校验签名密钥长度;</item>
|
||||
/// <item>注册 <see cref="ITokenService"/>(令牌生成/校验)与 <see cref="ICurrentUser"/>(当前用户访问器);</item>
|
||||
/// <item>注册 <see cref="JwtBearerDefaults"/> 认证,认证失败/授权失败统一返回 <see cref="StoreHub.Framework.Commons.ApiResult{T}"/> 格式;</item>
|
||||
/// <item>注册 <see cref="IOnlineUserStore"/>(基于 <c>IMemoryCache</c> 的在线会话存储):单点登录、在线人员查询、退出销毁;</item>
|
||||
/// <item>为 OpenAPI 文档声明 Bearer(JWT) 安全方案,并对标记了 [Authorize] 的接口声明需要携带令牌。</item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 需要在请求管道中调用 <c>app.UseAuthentication()</c>(位于 <c>app.UseAuthorization()</c> 之前)。
|
||||
/// </remarks>
|
||||
public static IServiceCollection AddStoreHubJwt(this IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(services);
|
||||
ArgumentNullException.ThrowIfNull(configuration);
|
||||
|
||||
// JwtOptions:绑定 Jwt 配置节,启动时校验 HS256 签名密钥长度
|
||||
services.AddOptions<JwtOptions>()
|
||||
.Bind(configuration.GetSection(JwtOptions.SectionName))
|
||||
.Validate(
|
||||
o => !string.IsNullOrWhiteSpace(o.SigningKey)
|
||||
&& Encoding.UTF8.GetByteCount(o.SigningKey) >= 32,
|
||||
"Jwt:SigningKey 长度不足,HS256 签名密钥至少需要 32 字节(256位)。")
|
||||
.ValidateOnStart();
|
||||
|
||||
services.AddHttpContextAccessor();
|
||||
|
||||
// 令牌服务与当前用户访问器
|
||||
services.AddSingleton<ITokenService, TokenService>();
|
||||
services.AddScoped<ICurrentUser, CurrentUser>();
|
||||
|
||||
// 在线会话存储(单点登录):基于 IMemoryCache 的进程内缓存,用于在线人员管理与退出销毁
|
||||
services.AddMemoryCache();
|
||||
services.AddSingleton<IOnlineUserStore, OnlineUserStore>();
|
||||
|
||||
// JwtBearer 认证
|
||||
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
|
||||
.AddJwtBearer(options =>
|
||||
{
|
||||
JwtOptions jwt = configuration.GetSection(JwtOptions.SectionName).Get<JwtOptions>() ?? new JwtOptions();
|
||||
options.TokenValidationParameters = jwt.ToTokenValidationParameters();
|
||||
options.Events = new JwtBearerEvents
|
||||
{
|
||||
OnChallenge = JwtBearerResponseHandler.OnChallenge,
|
||||
OnForbidden = JwtBearerResponseHandler.OnForbidden,
|
||||
// 单点登录校验:会话必须仍有效(未退出、未被新登录顶替)
|
||||
OnTokenValidated = context =>
|
||||
{
|
||||
IOnlineUserStore onlineUserStore = context.HttpContext.RequestServices.GetRequiredService<IOnlineUserStore>();
|
||||
ClaimsPrincipal? principal = context.Principal;
|
||||
string? userIdValue = principal?.FindFirst(JwtClaimTypes.UserId)?.Value;
|
||||
string? jti = principal?.FindFirst(JwtClaimTypes.Jti)?.Value;
|
||||
|
||||
if (!int.TryParse(userIdValue, out int userId)
|
||||
|| string.IsNullOrWhiteSpace(jti)
|
||||
|| !onlineUserStore.IsTokenActive(userId, jti))
|
||||
{
|
||||
context.Fail(new SecurityTokenException("登录已失效,请重新登录"));
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
// OpenAPI:声明 Bearer(JWT) 安全方案(Scalar 页面即可显示 Authorize/输入 token),
|
||||
// 并仅对标记了 [Authorize] 的接口声明需要携带令牌(与宿主的 AddOpenApi 配置合并生效)
|
||||
services.Configure<VersionedOpenApiOptions>(options =>
|
||||
{
|
||||
// 在 OpenAPI 文档中声明 Bearer(JWT) 安全方案
|
||||
options.Document.AddDocumentTransformer((document, _, _) =>
|
||||
{
|
||||
document.Components ??= new OpenApiComponents();
|
||||
document.Components.SecuritySchemes ??= new Dictionary<string, IOpenApiSecurityScheme>();
|
||||
document.Components.SecuritySchemes.TryAdd("Bearer", new OpenApiSecurityScheme
|
||||
{
|
||||
Type = SecuritySchemeType.Http,
|
||||
Scheme = "bearer",
|
||||
BearerFormat = "JWT",
|
||||
Description = "登录接口返回的 accessToken,直接粘贴令牌内容即可(无需手动加 Bearer 前缀)。",
|
||||
});
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
|
||||
// 仅对标记了 [Authorize] 的接口声明需要携带令牌
|
||||
options.Document.AddOperationTransformer((operation, context, _) =>
|
||||
{
|
||||
IList<object> metadata = context.Description.ActionDescriptor.EndpointMetadata;
|
||||
bool requiresAuth = metadata.OfType<IAuthorizeData>().Any()
|
||||
&& !metadata.OfType<IAllowAnonymous>().Any();
|
||||
if (requiresAuth)
|
||||
{
|
||||
operation.Security =
|
||||
[
|
||||
new OpenApiSecurityRequirement
|
||||
{
|
||||
[new OpenApiSecuritySchemeReference("Bearer", context.Document, "")] = [],
|
||||
},
|
||||
];
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
});
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
using MAX.Core.DependencyInjection;
|
||||
using MAX.Core.Infrastructure;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using System.Diagnostics;
|
||||
using System.Reflection;
|
||||
|
||||
namespace StoreHub.Framework.DependencyInjection
|
||||
{
|
||||
/// <summary>
|
||||
/// IOC 注册入口(由宿主 Program.cs 调用)
|
||||
/// </summary>
|
||||
public static class ServiceCollectionExtensions
|
||||
{
|
||||
private static readonly Type[] DependencyMarkers =
|
||||
[
|
||||
typeof(ISingletonDependency),
|
||||
typeof(IScopedDependency),
|
||||
typeof(ITransientDependency),
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// 注册应用服务:
|
||||
/// <list type="number">
|
||||
/// <item>自动注册所有实现 <see cref="ISingletonDependency"/> / <see cref="IScopedDependency"/> / <see cref="ITransientDependency"/> 的类型;</item>
|
||||
/// <item>发现所有 <see cref="IStartup"/>,按 <see cref="IStartup.Priority"/> 降序执行 Initialize + Register。</item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
public static IServiceCollection AddStoreHub(this IServiceCollection services)
|
||||
{
|
||||
IEnumerable<Assembly> assemblies = GetApplicationAssemblies();
|
||||
|
||||
RegisterDependencyMarkers(services, assemblies);
|
||||
RunStartups(services, assemblies);
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 自动注册标记接口(ISingletonDependency / IScopedDependency / ITransientDependency)的实现类型
|
||||
/// </summary>
|
||||
private static void RegisterDependencyMarkers(IServiceCollection services, IEnumerable<Assembly> assemblies)
|
||||
{
|
||||
foreach (Type type in GetConcreteTypes(assemblies))
|
||||
{
|
||||
Debug.WriteLine(type.Namespace);
|
||||
Type? marker = DependencyMarkers.FirstOrDefault(m => m.IsAssignableFrom(type));
|
||||
if (marker is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
ServiceLifetime lifetime = marker == typeof(ISingletonDependency) ? ServiceLifetime.Singleton
|
||||
: marker == typeof(IScopedDependency) ? ServiceLifetime.Scoped
|
||||
: ServiceLifetime.Transient;
|
||||
|
||||
// 注册为自身
|
||||
services.Add(new ServiceDescriptor(type, type, lifetime));
|
||||
|
||||
// 注册为实现的接口(排除标记接口与系统接口)
|
||||
foreach (Type iface in type.GetInterfaces())
|
||||
{
|
||||
if (DependencyMarkers.Contains(iface) || IsSystemInterface(iface))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
services.Add(new ServiceDescriptor(iface, type, lifetime));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 发现所有 <see cref="IStartup"/> 实现,按 <see cref="IStartup.Priority"/> 降序执行
|
||||
/// </summary>
|
||||
private static void RunStartups(IServiceCollection services, IEnumerable<Assembly> assemblies)
|
||||
{
|
||||
IServiceRegistrar registrar = new ServiceRegistrarProxy(services);
|
||||
|
||||
foreach (IStartup startup in GetConcreteTypes(assemblies)
|
||||
.Where(t => typeof(IStartup).IsAssignableFrom(t))
|
||||
.Select(t => (IStartup)Activator.CreateInstance(t, true)!)
|
||||
.OrderByDescending(s => s.Priority))
|
||||
{
|
||||
startup.Initialize();
|
||||
startup.Register(registrar);
|
||||
}
|
||||
}
|
||||
|
||||
private static IEnumerable<Type> GetConcreteTypes(IEnumerable<Assembly> assemblies)
|
||||
{
|
||||
return assemblies
|
||||
.SelectMany(SafeGetTypes)
|
||||
.Where(t => t.IsClass && !t.IsAbstract && !t.IsGenericTypeDefinition);
|
||||
}
|
||||
|
||||
private static IEnumerable<Type> SafeGetTypes(Assembly assembly)
|
||||
{
|
||||
try
|
||||
{
|
||||
return assembly.GetTypes();
|
||||
}
|
||||
catch (ReflectionTypeLoadException ex)
|
||||
{
|
||||
return ex.Types.OfType<Type>();
|
||||
}
|
||||
}
|
||||
|
||||
private static IEnumerable<Assembly> GetApplicationAssemblies()
|
||||
{
|
||||
// 使用 AppTypeFinder:扫描应用输出目录 + 已加载程序集,
|
||||
return StoreHubAppTypeFinder.Current.GetAssemblies();
|
||||
}
|
||||
|
||||
private static bool IsSystemInterface(Type type)
|
||||
{
|
||||
return type.Namespace is null
|
||||
|| type.Namespace.StartsWith("System", StringComparison.Ordinal)
|
||||
|| type.Namespace.StartsWith("Microsoft", StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
using MAX.Core.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace StoreHub.Framework.DependencyInjection
|
||||
{
|
||||
/// <summary>
|
||||
/// 将 <see cref="IServiceRegistrar"/> 适配到 Microsoft.Extensions.DependencyInjection
|
||||
/// </summary>
|
||||
internal sealed class ServiceRegistrarProxy : IServiceRegistrar
|
||||
{
|
||||
private readonly IServiceCollection _services;
|
||||
|
||||
public ServiceRegistrarProxy(IServiceCollection services)
|
||||
{
|
||||
_services = services;
|
||||
}
|
||||
|
||||
public void RegisterSingleton<TService, TImplementation>() where TService : class where TImplementation : class, TService
|
||||
{
|
||||
_services.AddSingleton<TService, TImplementation>();
|
||||
}
|
||||
|
||||
public void RegisterSingleton<TService>(TService instance) where TService : class
|
||||
{
|
||||
_services.AddSingleton(instance);
|
||||
}
|
||||
|
||||
public void RegisterSingleton<TService>(Func<IServiceProvider, TService> factory) where TService : class
|
||||
{
|
||||
_services.AddSingleton(factory);
|
||||
}
|
||||
|
||||
public void RegisterSingleton<TService, TImplementation>(object? serviceKey) where TService : class where TImplementation : class, TService
|
||||
{
|
||||
_services.AddKeyedSingleton<TService, TImplementation>(serviceKey);
|
||||
}
|
||||
|
||||
public void RegisterSingleton<TService>(object? serviceKey, TService instance) where TService : class
|
||||
{
|
||||
_services.AddKeyedSingleton(serviceKey, instance);
|
||||
}
|
||||
|
||||
public void RegisterSingleton<TService>(object? serviceKey, Func<IServiceProvider, object?, TService> factory) where TService : class
|
||||
{
|
||||
_services.AddKeyedSingleton(serviceKey, factory);
|
||||
}
|
||||
|
||||
public void RegisterScoped<TService, TImplementation>() where TService : class where TImplementation : class, TService
|
||||
{
|
||||
_services.AddScoped<TService, TImplementation>();
|
||||
}
|
||||
|
||||
public void RegisterScoped<TService>(Func<IServiceProvider, TService> factory) where TService : class
|
||||
{
|
||||
_services.AddScoped(factory);
|
||||
}
|
||||
|
||||
public void RegisterScoped<TService, TImplementation>(object? serviceKey) where TService : class where TImplementation : class, TService
|
||||
{
|
||||
_services.AddKeyedScoped<TService, TImplementation>(serviceKey);
|
||||
}
|
||||
|
||||
public void RegisterScoped<TService>(object? serviceKey, Func<IServiceProvider, object?, TService> factory) where TService : class
|
||||
{
|
||||
_services.AddKeyedScoped(serviceKey, factory);
|
||||
}
|
||||
|
||||
public void RegisterTransient<TService, TImplementation>() where TService : class where TImplementation : class, TService
|
||||
{
|
||||
_services.AddTransient<TService, TImplementation>();
|
||||
}
|
||||
|
||||
public void RegisterTransient<TService>(Func<IServiceProvider, TService> factory) where TService : class
|
||||
{
|
||||
_services.AddTransient(factory);
|
||||
}
|
||||
|
||||
public void RegisterTransient<TService, TImplementation>(object? serviceKey) where TService : class where TImplementation : class, TService
|
||||
{
|
||||
_services.AddKeyedTransient<TService, TImplementation>(serviceKey);
|
||||
}
|
||||
|
||||
public void RegisterTransient<TService>(object? serviceKey, Func<IServiceProvider, object?, TService> factory) where TService : class
|
||||
{
|
||||
_services.AddKeyedTransient(serviceKey, factory);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using MAX.Core.Reflections;
|
||||
|
||||
namespace StoreHub.Framework.DependencyInjection
|
||||
{
|
||||
/// <summary>
|
||||
/// 本应用的类型查找器:在 AppTypeFinder 默认前缀(MAX. / Nexus.)之外,
|
||||
/// 增加 StoreHub、KJD 前缀,用于发现宿主和各业务模块的程序集。
|
||||
/// </summary>
|
||||
internal sealed class StoreHubAppTypeFinder : AppTypeFinder
|
||||
{
|
||||
public static StoreHubAppTypeFinder Current { get; } = new();
|
||||
|
||||
protected override List<string> AvailablePrefix()
|
||||
{
|
||||
return ["StoreHub"];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
|
||||
namespace StoreHub.Framework.Helpers;
|
||||
|
||||
public static class ObjectHelper
|
||||
{
|
||||
private static readonly ConcurrentDictionary<string, PropertyInfo> CachedObjectProperties =
|
||||
new();
|
||||
|
||||
public static void TrySetProperty<TObject, TValue>(
|
||||
TObject obj,
|
||||
Expression<Func<TObject, TValue>> propertySelector,
|
||||
Func<TValue> valueFactory,
|
||||
params Type[] ignoreAttributeTypes
|
||||
)
|
||||
{
|
||||
TrySetProperty(obj, propertySelector, x => valueFactory(), ignoreAttributeTypes);
|
||||
}
|
||||
|
||||
public static void TrySetProperty<TObject, TValue>(
|
||||
TObject obj,
|
||||
Expression<Func<TObject, TValue>> propertySelector,
|
||||
Func<TObject, TValue> valueFactory,
|
||||
params Type[] ignoreAttributeTypes
|
||||
)
|
||||
{
|
||||
string cacheKey =
|
||||
$"{obj?.GetType().FullName}-"
|
||||
+ $"{propertySelector}-"
|
||||
+ $"{(ignoreAttributeTypes != null ? "-" + string.Join("-", ignoreAttributeTypes.Select(x => x.FullName)) : "")}";
|
||||
|
||||
PropertyInfo property = CachedObjectProperties.GetOrAdd(
|
||||
cacheKey,
|
||||
(name) =>
|
||||
{
|
||||
if (propertySelector.Body.NodeType != ExpressionType.MemberAccess)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
MemberExpression memberExpression = (MemberExpression)propertySelector.Body;
|
||||
|
||||
PropertyInfo? propertyInfo = obj?.GetType()
|
||||
.GetProperties()
|
||||
.FirstOrDefault(
|
||||
x =>
|
||||
x.Name == memberExpression.Member.Name
|
||||
&& x.GetSetMethod(true) != null
|
||||
);
|
||||
|
||||
if (propertyInfo == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (
|
||||
ignoreAttributeTypes != null
|
||||
&& ignoreAttributeTypes.Any(
|
||||
ignoreAttribute => propertyInfo.IsDefined(ignoreAttribute, true)
|
||||
)
|
||||
)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return propertyInfo;
|
||||
}
|
||||
);
|
||||
|
||||
property?.SetValue(obj, valueFactory(obj));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
using MAX.Core.Commons;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
|
||||
namespace StoreHub.Framework.Helpers;
|
||||
|
||||
public class WinCmdHelper
|
||||
{
|
||||
#region 执行Dos命令
|
||||
|
||||
/// <summary>
|
||||
/// 执行Dos命令
|
||||
/// </summary>
|
||||
/// <param name="cmd">Dos命令及参数</param>
|
||||
/// <param name="isShowCmdWindow">是否显示cmd窗口</param>
|
||||
/// <param name="isCloseCmdProcess">执行完毕后是否关闭cmd进程</param>
|
||||
/// <returns>成功返回true,失败返回false</returns>
|
||||
public static Result ExcuteDosCommand(string cmd, string workdir, bool isShowCmdWindow = false, bool isWaitForExit = false, bool isCloseCmdProcess = true)
|
||||
{
|
||||
Result result = new();
|
||||
try
|
||||
{
|
||||
StringBuilder errString = new();
|
||||
StringBuilder outString = new();
|
||||
Process p = new();
|
||||
p.StartInfo.FileName = "cmd";
|
||||
p.StartInfo.WorkingDirectory = workdir;
|
||||
p.StartInfo.Arguments = "/c " + cmd;
|
||||
p.StartInfo.StandardOutputEncoding = Encoding.UTF8;
|
||||
p.StartInfo.StandardErrorEncoding = Encoding.UTF8;
|
||||
p.StartInfo.UseShellExecute = false;
|
||||
p.StartInfo.RedirectStandardOutput = true;
|
||||
p.StartInfo.RedirectStandardError = true;
|
||||
p.StartInfo.CreateNoWindow = !isShowCmdWindow;
|
||||
p.OutputDataReceived += new DataReceivedEventHandler(delegate (object sender, DataReceivedEventArgs e)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(e.Data))
|
||||
{
|
||||
outString.AppendLine(e.Data);
|
||||
Debug.WriteLine(e.Data);
|
||||
}
|
||||
});
|
||||
p.ErrorDataReceived += new DataReceivedEventHandler(delegate (object sender, DataReceivedEventArgs e)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(e.Data))
|
||||
{
|
||||
errString.AppendLine(e.Data);
|
||||
}
|
||||
});
|
||||
p.Start();
|
||||
p.BeginOutputReadLine();
|
||||
p.BeginErrorReadLine();
|
||||
if (isWaitForExit)
|
||||
{
|
||||
p.WaitForExit();
|
||||
}
|
||||
|
||||
if (isCloseCmdProcess)
|
||||
{
|
||||
p.Close();
|
||||
}
|
||||
if (errString.Length <= 0)
|
||||
{
|
||||
result.Success = true;
|
||||
result.Data = outString.ToString();
|
||||
}
|
||||
Console.WriteLine(string.Format("成功执行Dos命令[{0}]!", cmd));
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine("执行命令失败,请检查输入的命令是否正确:" + ex.Message, ex);
|
||||
result.Msg = ex.Message;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static void P_ErrorDataReceived(object sender, DataReceivedEventArgs e)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 判断指定的进程是否在运行中
|
||||
|
||||
/// <summary>
|
||||
/// 判断指定的进程是否在运行中
|
||||
/// </summary>
|
||||
/// <param name="processName">要判断的进程名称,不包括扩展名exe</param>
|
||||
/// <param name="processFileName">进程文件的完整路径</param>
|
||||
/// <returns>存在返回true,否则返回false</returns>
|
||||
public static bool CheckProcessExists(string processName, string processFileName)
|
||||
{
|
||||
Process[] processes = Process.GetProcessesByName(processName);
|
||||
foreach (Process p in processes)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(processFileName))
|
||||
{
|
||||
if (processFileName == p?.MainModule?.FileName)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 结束指定的windows进程
|
||||
|
||||
/// <summary>
|
||||
/// 结束指定的windows进程,如果进程存在
|
||||
/// </summary>
|
||||
/// <param name="processName">进程名称,不包含扩展名</param>
|
||||
/// <param name="processFileName">进程文件完整路径,如果为空则删除所有进程名为processName参数值的进程</param>
|
||||
public static bool KillProcessExists(string processName, string processFileName)
|
||||
{
|
||||
try
|
||||
{
|
||||
Process[] processes = Process.GetProcessesByName(processName);
|
||||
foreach (Process p in processes)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(processFileName))
|
||||
{
|
||||
if (processFileName == p.MainModule.FileName)
|
||||
{
|
||||
p.Kill();
|
||||
p.Close();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
p.Kill();
|
||||
p.Close();
|
||||
}
|
||||
}
|
||||
Console.WriteLine(string.Format("成功结束[{0}]进程!", processes));
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine("结束指定的Widnows进程异常:" + ex.Message, ex);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
using MAX.Core.Infrastructure;
|
||||
using MAX.Core.Logger;
|
||||
using NLog;
|
||||
using System;
|
||||
using LLogLevel = MAX.Core.Logger.LogLevel;
|
||||
using NLogLevel = NLog.LogLevel;
|
||||
|
||||
namespace StoreHub.Framework.Logger;
|
||||
|
||||
public class NLogger : SingleInstance<NLogger>, ILoggerFacade
|
||||
{
|
||||
private readonly ILogger _log;
|
||||
|
||||
private NLogger()
|
||||
{
|
||||
_log = LogManager.GetLogger("GlobalLogger");
|
||||
}
|
||||
|
||||
public bool IsDebugEnabled { get; private set; } = true;
|
||||
|
||||
public void Debug(Exception ex, string message)
|
||||
{
|
||||
Execute(NLogLevel.Debug, ex, message);
|
||||
}
|
||||
|
||||
public void Debug(string message)
|
||||
{
|
||||
Execute(NLogLevel.Debug, message);
|
||||
}
|
||||
public void Error(Exception ex)
|
||||
{
|
||||
Execute(NLogLevel.Error, ex);
|
||||
}
|
||||
public void Error(Exception ex, string message)
|
||||
{
|
||||
Execute(NLogLevel.Error, ex, message);
|
||||
}
|
||||
|
||||
public void Error(string message)
|
||||
{
|
||||
Execute(NLogLevel.Error, message);
|
||||
}
|
||||
|
||||
public void Fatal(Exception ex, string message)
|
||||
{
|
||||
Execute(NLogLevel.Fatal, ex, message);
|
||||
}
|
||||
|
||||
public void Fatal(string message)
|
||||
{
|
||||
Execute(NLogLevel.Fatal, message);
|
||||
}
|
||||
|
||||
public void Info(Exception ex, string message)
|
||||
{
|
||||
Execute(NLogLevel.Info, ex, message);
|
||||
}
|
||||
|
||||
public void Info(string message)
|
||||
{
|
||||
Execute(NLogLevel.Info, message);
|
||||
}
|
||||
|
||||
public void Warn(Exception ex, string message)
|
||||
{
|
||||
Execute(NLogLevel.Warn, ex, message);
|
||||
}
|
||||
|
||||
public void Warn(string message)
|
||||
{
|
||||
Execute(NLogLevel.Warn, message);
|
||||
}
|
||||
public void Log(string message, LLogLevel level)
|
||||
{
|
||||
NLogLevel lv = NLogLevel.Info;
|
||||
switch (level)
|
||||
{
|
||||
case LLogLevel.Trace:
|
||||
lv = NLogLevel.Trace;
|
||||
break;
|
||||
case LLogLevel.Debug:
|
||||
lv = NLogLevel.Debug;
|
||||
break;
|
||||
case LLogLevel.Info:
|
||||
lv = NLogLevel.Info;
|
||||
break;
|
||||
case LLogLevel.Warn:
|
||||
lv = NLogLevel.Warn;
|
||||
break;
|
||||
case LLogLevel.Error:
|
||||
lv = NLogLevel.Error;
|
||||
break;
|
||||
case LLogLevel.Fatal:
|
||||
lv = NLogLevel.Fatal;
|
||||
break;
|
||||
case LLogLevel.None:
|
||||
lv = NLogLevel.Off;
|
||||
break;
|
||||
}
|
||||
Execute(lv, message);
|
||||
}
|
||||
/// <summary>
|
||||
/// 是否启用
|
||||
/// </summary>
|
||||
private bool Enabled(NLogLevel level)
|
||||
{
|
||||
if (level > NLogLevel.Debug)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return IsDebugEnabled;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 执行
|
||||
/// </summary>
|
||||
private void Execute(NLogLevel level, Exception ex, string message = "")
|
||||
{
|
||||
if (Enabled(level))
|
||||
{
|
||||
_log.Log(level, ex, message, null);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 执行
|
||||
/// </summary>
|
||||
private void Execute(NLogLevel level, string message)
|
||||
{
|
||||
if (Enabled(level))
|
||||
{
|
||||
_log.Log(level, message);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
using FreeSql.DataAnnotations;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace StoreHub.Framework.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// 用户实体
|
||||
/// </summary>
|
||||
//[Table(Name = "user")]
|
||||
public class User
|
||||
{
|
||||
/// <summary>
|
||||
/// 用户ID,主键,自增
|
||||
/// </summary>
|
||||
[Column(Name = "id", IsPrimary = true, IsIdentity = true)]
|
||||
public int Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 用户昵称
|
||||
/// </summary>
|
||||
[Column(Name = "nick_name", DbType = "varchar(50)")]
|
||||
public string NickName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 用户名称(登录账户)
|
||||
/// </summary>
|
||||
[Column(Name = "user_name", DbType = "varchar(20)")]
|
||||
public string UserName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 登录密码
|
||||
/// </summary>
|
||||
[Column(Name = "password", DbType = "varchar(32)")]
|
||||
public string Password { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 身份证号
|
||||
/// </summary>
|
||||
[Column(Name = "card_id", DbType = "varchar(50)")]
|
||||
public string CardId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 用户分组:3主号 4客服 5加盟 8登录 9采购 10加盟 11上货,默认10
|
||||
/// </summary>
|
||||
[Column(Name = "group_id")]
|
||||
public byte GroupId { get; set; } = 10;
|
||||
|
||||
/// <summary>
|
||||
/// 父账户用户名
|
||||
/// </summary>
|
||||
[Column(Name = "parent_user_name", DbType = "varchar(20)")]
|
||||
public string ParentUserName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// OEM编号,仅OEM用户有值
|
||||
/// </summary>
|
||||
[Column(Name = "oem_id", DbType = "varchar(20)")]
|
||||
public string OemId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 手机号
|
||||
/// </summary>
|
||||
[Column(Name = "mobile", DbType = "varchar(20)")]
|
||||
public string Mobile { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 添加时间
|
||||
/// </summary>
|
||||
[Column(Name = "add_time")]
|
||||
public DateTime? AddTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 修改时间
|
||||
/// </summary>
|
||||
[Column(Name = "mdy_time")]
|
||||
public DateTime? MdyTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 公司名称
|
||||
/// </summary>
|
||||
[Column(Name = "company_name", DbType = "varchar(50)")]
|
||||
public string CompanyName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 备注
|
||||
/// </summary>
|
||||
[Column(Name = "notes", DbType = "varchar(50)")]
|
||||
public string Notes { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 超管等级:0普通用户 1超级管理员,默认0
|
||||
/// </summary>
|
||||
[Column(Name = "sa_level")]
|
||||
public short SaLevel { get; set; } = 0;
|
||||
|
||||
/// <summary>
|
||||
/// 账户权限
|
||||
/// </summary>
|
||||
[Column(Name = "authority", DbType = "varchar(255)")]
|
||||
public string Authority { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 子账号被禁止展示的功能页面
|
||||
/// </summary>
|
||||
[Column(Name = "forbidden_page", DbType = "varchar(500)")]
|
||||
public string ForbiddenPage { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 账户积分,1积分=1元,默认0.00
|
||||
/// </summary>
|
||||
[Column(Name = "points", DbType = "decimal(10,2)")]
|
||||
public decimal Points { get; set; } = 0.00m;
|
||||
|
||||
/// <summary>
|
||||
/// 积分历史累计充值金额
|
||||
/// </summary>
|
||||
[Column(Name = "recharge_points_total", DbType = "decimal(10,2)")]
|
||||
public decimal? RechargePointsTotal { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 店铺每续费一个月需要的积分(单位:元),默认30
|
||||
/// </summary>
|
||||
[Column(Name = "shop_points_per_month")]
|
||||
public int ShopPointsPerMonth { get; set; } = 30;
|
||||
|
||||
/// <summary>
|
||||
/// 可用余额,默认0.00
|
||||
/// </summary>
|
||||
[Column(Name = "balance", DbType = "decimal(10,2)")]
|
||||
public decimal? Balance { get; set; } = 0.00m;
|
||||
|
||||
/// <summary>
|
||||
/// 累计充值金额,默认0.00
|
||||
/// </summary>
|
||||
[Column(Name = "recharge_amount_total", DbType = "decimal(10,2)")]
|
||||
public decimal? RechargeAmountTotal { get; set; } = 0.00m;
|
||||
|
||||
/// <summary>
|
||||
/// 每单中转仓快递费用(单位:元,两位小数),默认2.20
|
||||
/// </summary>
|
||||
[Column(Name = "postage_per_order", DbType = "decimal(10,2)")]
|
||||
public decimal? PostagePerOrder { get; set; } = 2.20m;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
using FreeSql;
|
||||
using MAX.Core.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using StoreHub.Framework.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace StoreHub.Framework.Repositorys
|
||||
{
|
||||
public interface IUserRepository : IBaseRepository<User>
|
||||
{
|
||||
|
||||
}
|
||||
public class SqlRepository : WxRepository<User>, IUserRepository, IScopedDependency
|
||||
{
|
||||
public SqlRepository([FromKeyedServices("KJDDB")] IFreeSql fsql) : base(fsql)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
public abstract class WxRepository<TEntity> : BaseRepository<TEntity>
|
||||
where TEntity : class
|
||||
{
|
||||
protected WxRepository(IFreeSql fsql)
|
||||
: base(fsql)
|
||||
{
|
||||
fsql.CodeFirst.ConfigEntity<TEntity>(a =>
|
||||
{
|
||||
a.Name($"wxxd.{typeof(TEntity).Name}");
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using System.Globalization;
|
||||
using System.Security.Claims;
|
||||
|
||||
namespace StoreHub.Framework.Security;
|
||||
|
||||
/// <summary>
|
||||
/// 从 <see cref="IHttpContextAccessor"/> 读取当前请求认证票据的默认实现。
|
||||
/// </summary>
|
||||
public class CurrentUser : ICurrentUser
|
||||
{
|
||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||
|
||||
public CurrentUser(IHttpContextAccessor httpContextAccessor)
|
||||
{
|
||||
_httpContextAccessor = httpContextAccessor;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当前请求的 <see cref="ClaimsPrincipal"/>
|
||||
/// </summary>
|
||||
private ClaimsPrincipal? Principal => _httpContextAccessor.HttpContext?.User;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsAuthenticated => Principal?.Identity?.IsAuthenticated == true;
|
||||
|
||||
/// <inheritdoc />
|
||||
public int? Id
|
||||
{
|
||||
get
|
||||
{
|
||||
string? value = GetClaimValue(JwtClaimTypes.UserId);
|
||||
return int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out int id) ? id : null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string? UserName => GetClaimValue(JwtClaimTypes.UserName);
|
||||
|
||||
/// <inheritdoc />
|
||||
public string? NickName => GetClaimValue(JwtClaimTypes.NickName);
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<Claim> Claims => Principal?.Claims ?? [];
|
||||
|
||||
/// <inheritdoc />
|
||||
public string? GetClaimValue(string claimType)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(claimType))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return Principal?.FindFirst(claimType)?.Value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public T? GetClaimValue<T>(string claimType)
|
||||
{
|
||||
string? value = GetClaimValue(claimType);
|
||||
if (value is null)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return (T)Convert.ChangeType(value, typeof(T), CultureInfo.InvariantCulture);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return default;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
using System.Security.Claims;
|
||||
|
||||
namespace StoreHub.Framework.Security;
|
||||
|
||||
/// <summary>
|
||||
/// 当前登录用户访问器:从当前请求的认证票据中读取用户信息。
|
||||
/// </summary>
|
||||
public interface ICurrentUser
|
||||
{
|
||||
/// <summary>
|
||||
/// 当前请求是否已认证
|
||||
/// </summary>
|
||||
bool IsAuthenticated { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 当前用户ID(未登录为 null)
|
||||
/// </summary>
|
||||
int? Id { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 当前登录账户名(未登录为 null)
|
||||
/// </summary>
|
||||
string? UserName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 当前用户昵称(未登录为 null)
|
||||
/// </summary>
|
||||
string? NickName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 当前用户的全部声明
|
||||
/// </summary>
|
||||
IEnumerable<Claim> Claims { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定类型的声明值
|
||||
/// </summary>
|
||||
string? GetClaimValue(string claimType);
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定类型的声明值并转换为目标类型;不存在或转换失败返回默认值
|
||||
/// </summary>
|
||||
T? GetClaimValue<T>(string claimType);
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
namespace StoreHub.Framework.Security;
|
||||
|
||||
/// <summary>
|
||||
/// 在线用户会话存储:基于 <see cref="Microsoft.Extensions.Caching.Memory.IMemoryCache"/> 的进程内实现,
|
||||
/// 用于单点登录(同一用户仅保留最新会话)、在线人员管理与退出销毁。
|
||||
/// </summary>
|
||||
public interface IOnlineUserStore
|
||||
{
|
||||
/// <summary>
|
||||
/// 用户登录/刷新会话:同用户的新登录会覆盖旧会话,旧令牌立即失效(单点登录)。
|
||||
/// </summary>
|
||||
void SignIn(OnlineUserInfo info);
|
||||
|
||||
/// <summary>
|
||||
/// 判断指定用户、指定令牌会话是否仍然有效(未被退出、未被新登录顶替)。
|
||||
/// </summary>
|
||||
bool IsTokenActive(int userId, string jti);
|
||||
|
||||
/// <summary>
|
||||
/// 退出登录:销毁该用户的在线会话,令牌立即失效。
|
||||
/// </summary>
|
||||
void SignOut(int userId);
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前在线人员列表。
|
||||
/// </summary>
|
||||
IReadOnlyList<OnlineUserInfo> GetOnlineUsers();
|
||||
|
||||
/// <summary>
|
||||
/// 当前在线人数。
|
||||
/// </summary>
|
||||
int Count { get; }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
using System.Security.Claims;
|
||||
|
||||
namespace StoreHub.Framework.Security;
|
||||
|
||||
/// <summary>
|
||||
/// 令牌生成结果
|
||||
/// </summary>
|
||||
public class TokenResult
|
||||
{
|
||||
/// <summary>
|
||||
/// 令牌类型(Bearer)
|
||||
/// </summary>
|
||||
public string TokenType { get; set; } = "Bearer";
|
||||
|
||||
/// <summary>
|
||||
/// 访问令牌
|
||||
/// </summary>
|
||||
public string Token { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 过期时间(本地时间)
|
||||
/// </summary>
|
||||
public DateTime ExpiresAt { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// JWT 令牌服务:生成与校验访问令牌。
|
||||
/// </summary>
|
||||
public interface ITokenService
|
||||
{
|
||||
/// <summary>
|
||||
/// 根据声明集合生成访问令牌
|
||||
/// </summary>
|
||||
TokenResult CreateToken(IEnumerable<Claim> claims);
|
||||
|
||||
/// <summary>
|
||||
/// 校验令牌,校验失败返回 null
|
||||
/// </summary>
|
||||
Task<ClaimsPrincipal?> ValidateTokenAsync(string token);
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using StoreHub.Framework.Commons;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace StoreHub.Framework.Security;
|
||||
|
||||
/// <summary>
|
||||
/// JwtBearer 认证失败/授权失败时,统一返回 <see cref="ApiResult{T}"/> 格式的 JSON 响应。
|
||||
/// </summary>
|
||||
internal static class JwtBearerResponseHandler
|
||||
{
|
||||
private static readonly JsonSerializerOptions JsonOptions = new()
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// 未携带令牌或令牌无效(401)
|
||||
/// </summary>
|
||||
public static Task OnChallenge(JwtBearerChallengeContext context)
|
||||
{
|
||||
context.HandleResponse();
|
||||
return WriteAsync(context.HttpContext, StatusCodes.Status401Unauthorized, "未授权:请先登录或携带有效的访问令牌");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 已认证但无权限(403)
|
||||
/// </summary>
|
||||
public static Task OnForbidden(ForbiddenContext context)
|
||||
{
|
||||
return WriteAsync(context.HttpContext, StatusCodes.Status403Forbidden, "禁止访问:当前账号没有权限执行该操作");
|
||||
}
|
||||
|
||||
private static async Task WriteAsync(HttpContext httpContext, int statusCode, string message)
|
||||
{
|
||||
httpContext.Response.StatusCode = statusCode;
|
||||
httpContext.Response.ContentType = "application/json; charset=utf-8";
|
||||
|
||||
string body = JsonSerializer.Serialize(ApiResult.Error(message, statusCode), JsonOptions);
|
||||
await httpContext.Response.WriteAsync(body, Encoding.UTF8);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
namespace StoreHub.Framework.Security;
|
||||
|
||||
/// <summary>
|
||||
/// JWT 自定义声明类型常量。
|
||||
/// </summary>
|
||||
public static class JwtClaimTypes
|
||||
{
|
||||
/// <summary>用户ID</summary>
|
||||
public const string UserId = "uid";
|
||||
|
||||
/// <summary>登录账户</summary>
|
||||
public const string UserName = "user_name";
|
||||
|
||||
/// <summary>用户昵称</summary>
|
||||
public const string NickName = "nick_name";
|
||||
|
||||
/// <summary>令牌唯一标识(jti,用于单点登录/退出校验)</summary>
|
||||
public const string Jti = "jti";
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using System.Text;
|
||||
|
||||
namespace StoreHub.Framework.Security;
|
||||
|
||||
/// <summary>
|
||||
/// JWT 配置选项,默认绑定配置节 <c>Jwt</c>。
|
||||
/// </summary>
|
||||
public class JwtOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// 配置节名称
|
||||
/// </summary>
|
||||
public const string SectionName = "Jwt";
|
||||
|
||||
/// <summary>
|
||||
/// 签发者
|
||||
/// </summary>
|
||||
public string Issuer { get; set; } = "StoreHub";
|
||||
|
||||
/// <summary>
|
||||
/// 受众
|
||||
/// </summary>
|
||||
public string Audience { get; set; } = "StoreHub.Client";
|
||||
|
||||
/// <summary>
|
||||
/// 签名密钥(HS256,至少 32 字节)
|
||||
/// </summary>
|
||||
public string SigningKey { get; set; } = "StoreHub-Development-SigningKey-0123456789abcdef0123456789abcdef";
|
||||
|
||||
/// <summary>
|
||||
/// 令牌有效期(分钟)
|
||||
/// </summary>
|
||||
public int ExpiresMinutes { get; set; } = 120;
|
||||
|
||||
/// <summary>
|
||||
/// 时钟偏移(分钟),容忍客户端与服务器的时间偏差
|
||||
/// </summary>
|
||||
public int ClockSkewMinutes { get; set; } = 5;
|
||||
|
||||
/// <summary>
|
||||
/// 是否校验签发者
|
||||
/// </summary>
|
||||
public bool ValidateIssuer { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 是否校验受众
|
||||
/// </summary>
|
||||
public bool ValidateAudience { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 是否校验有效期
|
||||
/// </summary>
|
||||
public bool ValidateLifetime { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 根据当前选项构建 <see cref="TokenValidationParameters"/>,用于 JwtBearer 中间件与令牌校验。
|
||||
/// </summary>
|
||||
public TokenValidationParameters ToTokenValidationParameters()
|
||||
{
|
||||
byte[] keyBytes = Encoding.UTF8.GetBytes(SigningKey);
|
||||
return new TokenValidationParameters
|
||||
{
|
||||
ValidateIssuerSigningKey = true,
|
||||
IssuerSigningKey = new SymmetricSecurityKey(keyBytes),
|
||||
ValidateIssuer = ValidateIssuer,
|
||||
ValidIssuer = Issuer,
|
||||
ValidateAudience = ValidateAudience,
|
||||
ValidAudience = Audience,
|
||||
ValidateLifetime = ValidateLifetime,
|
||||
ClockSkew = TimeSpan.FromMinutes(ClockSkewMinutes),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
namespace StoreHub.Framework.Security;
|
||||
|
||||
/// <summary>
|
||||
/// 在线用户会话信息(单点登录:同一用户仅保留一个有效会话)。
|
||||
/// </summary>
|
||||
public class OnlineUserInfo
|
||||
{
|
||||
/// <summary>用户ID</summary>
|
||||
public int UserId { get; set; }
|
||||
|
||||
/// <summary>登录账户</summary>
|
||||
public string UserName { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>用户昵称</summary>
|
||||
public string NickName { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>当前有效会话的令牌唯一标识(jti)</summary>
|
||||
public string Jti { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>登录时间(本地时间)</summary>
|
||||
public DateTime LoginTime { get; set; }
|
||||
|
||||
/// <summary>会话过期时间(本地时间,与令牌 exp 一致)</summary>
|
||||
public DateTime ExpiresAt { get; set; }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
namespace StoreHub.Framework.Security;
|
||||
|
||||
/// <summary>
|
||||
/// 基于 <see cref="IMemoryCache"/> 的在线用户会话存储。
|
||||
/// </summary>
|
||||
public class OnlineUserStore : IOnlineUserStore
|
||||
{
|
||||
private const string CacheKeyPrefix = "online:user:";
|
||||
private readonly IMemoryCache _cache;
|
||||
// 在线人员索引:IMemoryCache 不提供公共枚举 API,这里用字典维护便于查询在线列表
|
||||
private readonly ConcurrentDictionary<int, OnlineUserInfo> _onlineIndex = new();
|
||||
|
||||
public OnlineUserStore(IMemoryCache cache)
|
||||
{
|
||||
_cache = cache;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SignIn(OnlineUserInfo info)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(info);
|
||||
|
||||
// 以 userId 为键覆盖写入:旧会话(jti)被顶替,实现单点登录
|
||||
_cache.Set(GetKey(info.UserId), info, new MemoryCacheEntryOptions
|
||||
{
|
||||
// 缓存条目与令牌同时过期,过期后自动失效
|
||||
AbsoluteExpiration = info.ExpiresAt,
|
||||
});
|
||||
_onlineIndex[info.UserId] = info;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsTokenActive(int userId, string jti)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(jti))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return _cache.TryGetValue(GetKey(userId), out OnlineUserInfo? info)
|
||||
&& info is not null
|
||||
&& string.Equals(info.Jti, jti, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SignOut(int userId)
|
||||
{
|
||||
_cache.Remove(GetKey(userId));
|
||||
_onlineIndex.TryRemove(userId, out _);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IReadOnlyList<OnlineUserInfo> GetOnlineUsers()
|
||||
{
|
||||
DateTime now = DateTime.Now;
|
||||
List<OnlineUserInfo> result = [];
|
||||
foreach (KeyValuePair<int, OnlineUserInfo> pair in _onlineIndex)
|
||||
{
|
||||
if (pair.Value.ExpiresAt > now)
|
||||
{
|
||||
result.Add(pair.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
_onlineIndex.TryRemove(pair.Key, out _);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public int Count => GetOnlineUsers().Count;
|
||||
|
||||
private static string GetKey(int userId) => $"{CacheKeyPrefix}{userId}";
|
||||
}
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.IdentityModel.JsonWebTokens;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using System.Security.Claims;
|
||||
using System.Text;
|
||||
|
||||
namespace StoreHub.Framework.Security;
|
||||
|
||||
/// <summary>
|
||||
/// 基于 <see cref="JwtOptions"/> 的 JWT 令牌服务。
|
||||
/// </summary>
|
||||
public class TokenService : ITokenService
|
||||
{
|
||||
private readonly JwtOptions _options;
|
||||
private readonly IOnlineUserStore _onlineUserStore;
|
||||
|
||||
public TokenService(IOptions<JwtOptions> options, IOnlineUserStore onlineUserStore)
|
||||
{
|
||||
_options = options.Value;
|
||||
_onlineUserStore = onlineUserStore;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public TokenResult CreateToken(IEnumerable<Claim> claims)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(claims);
|
||||
|
||||
DateTime now = DateTime.Now;
|
||||
DateTime expires = now.AddMinutes(_options.ExpiresMinutes);
|
||||
string jti = Guid.NewGuid().ToString("N");
|
||||
|
||||
List<Claim> allClaims = [.. claims, new Claim(JwtClaimTypes.Jti, jti)];
|
||||
|
||||
SymmetricSecurityKey key = new(Encoding.UTF8.GetBytes(_options.SigningKey));
|
||||
JsonWebTokenHandler handler = new();
|
||||
|
||||
string token = handler.CreateToken(new SecurityTokenDescriptor
|
||||
{
|
||||
Issuer = _options.Issuer,
|
||||
Audience = _options.Audience,
|
||||
Claims = ToClaimDictionary(allClaims),
|
||||
NotBefore = now,
|
||||
Expires = expires,
|
||||
SigningCredentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256),
|
||||
});
|
||||
|
||||
// 注册在线会话(单点登录:同用户的新登录会顶替旧会话,旧令牌立即失效)
|
||||
RegisterSession(allClaims, jti, now, expires);
|
||||
|
||||
return new TokenResult
|
||||
{
|
||||
Token = token,
|
||||
ExpiresAt = expires,
|
||||
};
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<ClaimsPrincipal?> ValidateTokenAsync(string token)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(token))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
JsonWebTokenHandler handler = new();
|
||||
TokenValidationResult result = await handler.ValidateTokenAsync(token, _options.ToTokenValidationParameters());
|
||||
if (!result.IsValid)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
ClaimsPrincipal principal = new(result.ClaimsIdentity);
|
||||
|
||||
// 单点登录校验:会话必须仍然有效(未被退出、未被新登录顶替)
|
||||
string? userIdValue = principal.FindFirst(JwtClaimTypes.UserId)?.Value;
|
||||
string? jti = principal.FindFirst(JwtClaimTypes.Jti)?.Value;
|
||||
if (!int.TryParse(userIdValue, out int userId) || string.IsNullOrWhiteSpace(jti) || !_onlineUserStore.IsTokenActive(userId, jti))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return principal;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将令牌声明注册为在线会话。
|
||||
/// </summary>
|
||||
private void RegisterSession(IEnumerable<Claim> claims, string jti, DateTime loginTime, DateTime expiresAt)
|
||||
{
|
||||
string? userIdValue = claims.FirstOrDefault(c => c.Type == JwtClaimTypes.UserId)?.Value;
|
||||
if (!int.TryParse(userIdValue, out int userId))
|
||||
{
|
||||
return; // 未携带用户ID的令牌不纳入在线会话管理
|
||||
}
|
||||
|
||||
_onlineUserStore.SignIn(new OnlineUserInfo
|
||||
{
|
||||
UserId = userId,
|
||||
UserName = claims.FirstOrDefault(c => c.Type == JwtClaimTypes.UserName)?.Value ?? string.Empty,
|
||||
NickName = claims.FirstOrDefault(c => c.Type == JwtClaimTypes.NickName)?.Value ?? string.Empty,
|
||||
Jti = jti,
|
||||
LoginTime = loginTime,
|
||||
ExpiresAt = expiresAt,
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将声明集合转换为 <see cref="SecurityTokenDescriptor.Claims"/> 所需的字典;
|
||||
/// 相同类型的多个声明合并为数组。
|
||||
/// </summary>
|
||||
private static Dictionary<string, object> ToClaimDictionary(IEnumerable<Claim> claims)
|
||||
{
|
||||
Dictionary<string, object> result = new(StringComparer.Ordinal);
|
||||
foreach (IGrouping<string, Claim> group in claims.GroupBy(c => c.Type, StringComparer.Ordinal))
|
||||
{
|
||||
object[] values = group.Select(c => (object)c.Value).ToArray();
|
||||
result[group.Key] = values.Length == 1 ? values[0] : values;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
using FreeSql;
|
||||
using MAX.Core.DependencyInjection;
|
||||
using MAX.Core.Infrastructure;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using StoreHub.Framework.Repositorys;
|
||||
|
||||
namespace StoreHub.Framework
|
||||
{
|
||||
public class Startup : IStartup
|
||||
{
|
||||
public int Priority => int.MaxValue - 1;
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public void Register(IServiceRegistrar registrar)
|
||||
{
|
||||
Func<IServiceProvider, IFreeSql> userFsqlFactory = r =>
|
||||
{
|
||||
var config = r.GetRequiredService<IConfiguration>();
|
||||
var connection = config.GetConnectionString("UserConnection");
|
||||
IFreeSql fsql = new FreeSql.FreeSqlBuilder()
|
||||
.UseConnectionString(FreeSql.DataType.MySql, connection)
|
||||
.UseAdoConnectionPool(true)
|
||||
.Build();
|
||||
return fsql;
|
||||
};
|
||||
registrar.RegisterSingleton<IFreeSql>("UserDB", (s, k) =>
|
||||
{
|
||||
return userFsqlFactory(s);
|
||||
});
|
||||
|
||||
|
||||
Func<IServiceProvider, IFreeSql> KJDFsqlFactory = r =>
|
||||
{
|
||||
var config = r.GetRequiredService<IConfiguration>();
|
||||
var connection = config.GetConnectionString("KJDConnection");
|
||||
IFreeSql fsql = new FreeSql.FreeSqlBuilder()
|
||||
.UseConnectionString(FreeSql.DataType.MySql, connection)
|
||||
.UseAdoConnectionPool(true)
|
||||
.Build();
|
||||
return fsql;
|
||||
};
|
||||
registrar.RegisterSingleton<IFreeSql>("KJDDB", (s, k) =>
|
||||
{
|
||||
return KJDFsqlFactory(s);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<FrameworkReference Include="Microsoft.AspNetCore.App" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Core\MAX.Core\MAX.Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Asp.Versioning.Mvc.ApiExplorer" Version="10.2.1" />
|
||||
<PackageReference Include="Asp.Versioning.OpenApi" Version="10.2.1" />
|
||||
<PackageReference Include="FreeSql" Version="3.5.311" />
|
||||
<PackageReference Include="FreeSql.Provider.MySql" Version="3.5.311" />
|
||||
<PackageReference Include="FreeSql.Repository" Version="3.5.311" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="10.0.10" />
|
||||
<PackageReference Include="NLog" Version="6.1.4" />
|
||||
<PackageReference Include="Riok.Mapperly" Version="4.3.1" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,26 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using StoreHub.Framework.Commons;
|
||||
using StoreHub.KJDWebService.Services;
|
||||
|
||||
namespace StoreHub.KJDWebService.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[ApiVersion("1.0")]
|
||||
[Route("api/v{version:apiVersion}/[controller]/[action]")]
|
||||
public class Login : ControllerBase
|
||||
{
|
||||
private readonly DemoService _userService;
|
||||
|
||||
public Login(DemoService userService)
|
||||
{
|
||||
_userService = userService;
|
||||
}
|
||||
[HttpGet]
|
||||
public ApiResult<bool> Loinout()
|
||||
{
|
||||
_userService.Login();
|
||||
return ApiResult.Success(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"profiles": {
|
||||
"KJD_WebService": {
|
||||
"commandName": "Project",
|
||||
"launchBrowser": true,
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
},
|
||||
"applicationUrl": "https://localhost:57509;http://localhost:57510"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using FreeSql;
|
||||
using MAX.Core.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using StoreHub.Framework.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace StoreHub.Framework.Repositorys
|
||||
{
|
||||
|
||||
public class SqlRepository : IScopedDependency
|
||||
{
|
||||
public IAdo Ado { get; }
|
||||
public SqlRepository([FromKeyedServices("KJDDB")] IFreeSql fsql)
|
||||
{
|
||||
Ado = fsql.Ado;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
using MAX.Core.DependencyInjection;
|
||||
using StoreHub.Framework.Repositorys;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace StoreHub.KJDWebService.Services
|
||||
{
|
||||
public class DemoService : ITransientDependency
|
||||
{
|
||||
private readonly SqlRepository _sqlRepository;
|
||||
|
||||
public DemoService(SqlRepository sqlRepository)
|
||||
{
|
||||
_sqlRepository = sqlRepository;
|
||||
}
|
||||
public bool Test()
|
||||
{
|
||||
_sqlRepository.Ado.ExecuteDataTable("select * from user");
|
||||
return true;
|
||||
}
|
||||
public bool Test2()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using MAX.Core.DependencyInjection;
|
||||
using MAX.Core.Infrastructure;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace StoreHub.KJDWebService
|
||||
{
|
||||
internal class Startup : MAX.Core.Infrastructure.IStartup
|
||||
{
|
||||
public int Priority => 0;
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public void Register(IServiceRegistrar registrar)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<FrameworkReference Include="Microsoft.AspNetCore.App" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\StoreHub.Framework\StoreHub.Framework.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Folder Include="Models\" />
|
||||
<Folder Include="Dtos\" />
|
||||
<Folder Include="Commons\" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,17 @@
|
||||
namespace StoreHub.Services.Dtos;
|
||||
|
||||
/// <summary>
|
||||
/// 用户信息 DTO(对外暴露的用户数据,不包含密码等敏感字段)。
|
||||
/// </summary>
|
||||
public class UserDto
|
||||
{
|
||||
/// <summary>用户ID</summary>
|
||||
public int Id { get; set; }
|
||||
|
||||
/// <summary>登录账户</summary>
|
||||
public string UserName { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>用户昵称</summary>
|
||||
public string NickName { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
using Riok.Mapperly.Abstractions;
|
||||
using StoreHub.Framework.Models;
|
||||
using StoreHub.Services.Dtos;
|
||||
|
||||
namespace StoreHub.Services.Mappers;
|
||||
|
||||
/// <summary>
|
||||
/// User 实体 → UserDto 映射(Riok.Mapperly 源码生成)。
|
||||
/// UserDto 只暴露部分字段,因此仅要求目标成员都被映射(忽略源端未映射警告)。
|
||||
/// </summary>
|
||||
[Mapper(RequiredMappingStrategy = RequiredMappingStrategy.Target)]
|
||||
public static partial class UserMapper
|
||||
{
|
||||
/// <summary>
|
||||
/// 将用户实体映射为用户 DTO。
|
||||
/// </summary>
|
||||
public static partial UserDto ToDto(User user);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<FrameworkReference Include="Microsoft.AspNetCore.App" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\StoreHub.Framework\StoreHub.Framework.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,64 @@
|
||||
using FreeSql;
|
||||
using MAX.Core.Cryptography;
|
||||
using MAX.Core.DependencyInjection;
|
||||
using StoreHub.Framework.Models;
|
||||
using StoreHub.Framework.Repositorys;
|
||||
using StoreHub.Services.Dtos;
|
||||
using StoreHub.Services.Mappers;
|
||||
|
||||
namespace StoreHub.Services;
|
||||
|
||||
/// <summary>
|
||||
/// 用户业务服务:登录校验、用户查询等用例。
|
||||
/// </summary>
|
||||
public interface IUserService
|
||||
{
|
||||
/// <summary>
|
||||
/// 校验用户名/密码,成功返回用户 DTO,失败返回 null。
|
||||
/// </summary>
|
||||
Task<UserDto?> LoginAsync(string userName, string password);
|
||||
|
||||
/// <summary>
|
||||
/// 根据用户ID获取用户 DTO,不存在返回 null。
|
||||
/// </summary>
|
||||
Task<UserDto?> GetByIdAsync(int id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 用户业务服务实现(<see cref="IScopedDependency"/>,由 AddStoreHub 自动注册)。
|
||||
/// </summary>
|
||||
public class UserService : IUserService, IScopedDependency
|
||||
{
|
||||
private readonly IUserRepository _userRepository;
|
||||
|
||||
public UserService(IUserRepository userRepository)
|
||||
{
|
||||
_userRepository = userRepository;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<UserDto?> LoginAsync(string userName, string password)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(userName) || string.IsNullOrWhiteSpace(password))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
var sql = _userRepository.Where(u => u.UserName == userName).ToSql();
|
||||
User? user = await _userRepository.Where(u => u.UserName == userName).FirstAsync();
|
||||
string pwd = MD5Helper.Md5_32(password);
|
||||
if (user is null || !string.Equals(user.Password, pwd, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return UserMapper.ToDto(user);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<UserDto?> GetByIdAsync(int id)
|
||||
{
|
||||
User? user = await _userRepository.Where(u => u.Id == id).FirstAsync();
|
||||
return user is null ? null : UserMapper.ToDto(user);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
<Solution>
|
||||
<Folder Name="/Core/">
|
||||
<Project Path="Core/MAX.Core/MAX.Core.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/_SolutionItems/">
|
||||
<File Path="Directory.Build.props" />
|
||||
</Folder>
|
||||
<Project Path="StoreHub.Framework/StoreHub.Framework.csproj" />
|
||||
<Project Path="StoreHub.KJDWebService/StoreHub.KJDWebService.csproj" />
|
||||
<Project Path="StoreHub.Services/StoreHub.Services.csproj" />
|
||||
<Project Path="StoreHub/StoreHub.csproj" />
|
||||
</Solution>
|
||||
@@ -0,0 +1,112 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using StoreHub.Framework.Commons;
|
||||
using StoreHub.Framework.Security;
|
||||
using StoreHub.Services;
|
||||
using StoreHub.Services.Dtos;
|
||||
using System.Security.Claims;
|
||||
|
||||
namespace StoreHub.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[ApiVersion("1.0")]
|
||||
[Route("api/v{version:apiVersion}/[controller]/[action]")]
|
||||
public class AuthController : ControllerBase
|
||||
{
|
||||
private readonly IUserService _userService;
|
||||
private readonly ITokenService _tokenService;
|
||||
private readonly ICurrentUser _currentUser;
|
||||
private readonly IOnlineUserStore _onlineUserStore;
|
||||
|
||||
public AuthController(IUserService userService, ITokenService tokenService, ICurrentUser currentUser, IOnlineUserStore onlineUserStore)
|
||||
{
|
||||
_userService = userService;
|
||||
_tokenService = tokenService;
|
||||
_currentUser = currentUser;
|
||||
_onlineUserStore = onlineUserStore;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 登录:校验用户名/密码,签发 JWT(单点登录:同用户的新登录会使旧令牌失效)
|
||||
/// </summary>
|
||||
[HttpPost]
|
||||
[AllowAnonymous]
|
||||
public async Task<ApiResult<object?>> Login([FromBody] LoginRequest request)
|
||||
{
|
||||
if (request is null || string.IsNullOrWhiteSpace(request.UserName) || string.IsNullOrWhiteSpace(request.Password))
|
||||
{
|
||||
return ApiResult.Error("用户名或密码不能为空", 400);
|
||||
}
|
||||
|
||||
UserDto? user = await _userService.LoginAsync(request.UserName, request.Password);
|
||||
if (user is null)
|
||||
{
|
||||
return ApiResult.Error("用户名或密码错误", 401);
|
||||
}
|
||||
|
||||
var claims = new List<Claim>
|
||||
{
|
||||
new(JwtClaimTypes.UserId, user.Id.ToString()),
|
||||
new(JwtClaimTypes.UserName, user.UserName),
|
||||
new(JwtClaimTypes.NickName, user.NickName),
|
||||
};
|
||||
|
||||
TokenResult token = _tokenService.CreateToken(claims);
|
||||
return ApiResult.Success<object?>(token);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 退出登录:销毁当前用户的服务端在线会话,令牌立即失效
|
||||
/// </summary>
|
||||
[HttpPost]
|
||||
[Authorize]
|
||||
public ApiResult<object?> Logout()
|
||||
{
|
||||
if (_currentUser.Id is int userId)
|
||||
{
|
||||
_onlineUserStore.SignOut(userId);
|
||||
}
|
||||
|
||||
return ApiResult.Success<object?>(null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前登录用户信息(需携带 Bearer Token)
|
||||
/// </summary>
|
||||
[HttpGet]
|
||||
[Authorize]
|
||||
public ApiResult<object?> Me()
|
||||
{
|
||||
return ApiResult.Success<object?>(new
|
||||
{
|
||||
UserId = _currentUser.Id,
|
||||
UserName = _currentUser.UserName,
|
||||
NickName = _currentUser.NickName,
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在线人员列表(需携带 Bearer Token)
|
||||
/// </summary>
|
||||
[HttpGet]
|
||||
[Authorize]
|
||||
public ApiResult<object?> OnlineUsers()
|
||||
{
|
||||
IReadOnlyList<OnlineUserInfo> users = _onlineUserStore.GetOnlineUsers();
|
||||
return ApiResult.Success<object?>(new
|
||||
{
|
||||
Count = users.Count,
|
||||
Users = users,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public class LoginRequest
|
||||
{
|
||||
public string UserName { get; set; } = string.Empty;
|
||||
|
||||
public string Password { get; set; } = string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using StoreHub.Framework.Repositorys;
|
||||
using System.ComponentModel;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace StoreHub.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[Description("测试温度")]
|
||||
[ApiVersion("1.0")]
|
||||
[Route("api/v{version:apiVersion}/[controller]")]
|
||||
public class UserController : ControllerBase
|
||||
{
|
||||
private readonly IUserRepository _userRepository;
|
||||
|
||||
public UserController(IUserRepository userRepository)
|
||||
{
|
||||
_userRepository = userRepository;
|
||||
}
|
||||
[HttpGet]
|
||||
public string GetUser()
|
||||
{
|
||||
return JsonSerializer.Serialize(_userRepository.Select.First());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using StoreHub.Framework.Commons;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace StoreHub.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[Description("测试温度")]
|
||||
[ApiVersion("1.0")]
|
||||
[Route("api/v{version:apiVersion}/[controller]")]
|
||||
public class WeatherForecastController : ControllerBase
|
||||
{
|
||||
private static readonly string[] Summaries =
|
||||
[
|
||||
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
|
||||
];
|
||||
|
||||
[HttpGet(Name = "GetWeatherForecast")]
|
||||
public ApiResult<IEnumerable<WeatherForecast>> Get()
|
||||
{
|
||||
IEnumerable<WeatherForecast> data = Enumerable.Range(1, 5).Select(index => new WeatherForecast
|
||||
{
|
||||
Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
|
||||
TemperatureC = Random.Shared.Next(-20, 55),
|
||||
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
|
||||
})
|
||||
.ToArray();
|
||||
|
||||
return ApiResult.Success(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using StoreHub.Framework.Commons;
|
||||
|
||||
namespace StoreHub.Controllers.v2
|
||||
{
|
||||
[ApiController]
|
||||
[ApiVersion("2.0")]
|
||||
[Route("api/v{version:apiVersion}/[controller]")]
|
||||
public class WeatherForecastController : ControllerBase
|
||||
{
|
||||
private static readonly string[] Summaries =
|
||||
[
|
||||
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
|
||||
];
|
||||
|
||||
[HttpGet(Name = "GetWeatherForecastV2")]
|
||||
public ApiResult<IEnumerable<WeatherForecastV2>> Get()
|
||||
{
|
||||
IEnumerable<WeatherForecastV2> data = Enumerable.Range(1, 10).Select(index => new WeatherForecastV2
|
||||
{
|
||||
Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
|
||||
TemperatureC = Random.Shared.Next(-20, 55),
|
||||
FeelsLikeC = Random.Shared.Next(-20, 55),
|
||||
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
|
||||
})
|
||||
.ToArray();
|
||||
|
||||
return ApiResult.Success(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
using Asp.Versioning;
|
||||
using Scalar.AspNetCore;
|
||||
using StoreHub.Framework.DependencyInjection;
|
||||
|
||||
public partial class Program
|
||||
{
|
||||
private static void Main(string[] args)
|
||||
{
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
// IOC 注册:自动注册 ISingletonDependency / IScopedDependency / ITransientDependency,并执行所有 IStartup(实现见 StoreHub.Framework)
|
||||
builder.Services.AddStoreHub();
|
||||
builder.Services.AddFreeSqlDB();
|
||||
// JWT 验证:注册 JwtBearer 认证 + 令牌服务 + OpenAPI Bearer 安全方案(配置节 Jwt)
|
||||
builder.Services.AddStoreHubJwt(builder.Configuration);
|
||||
// Add services to the container.
|
||||
|
||||
builder.Services.AddControllers();
|
||||
// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi
|
||||
builder.Services.AddApiVersioning(options =>
|
||||
{
|
||||
options.DefaultApiVersion = new ApiVersion(1, 0);
|
||||
options.ReportApiVersions = true;
|
||||
options.ApiVersionReader = new UrlSegmentApiVersionReader();
|
||||
}).AddMvc().AddApiExplorer(options =>
|
||||
{
|
||||
options.GroupNameFormat = "'v'V";
|
||||
options.SubstituteApiVersionInUrl = true;
|
||||
}).AddOpenApi();
|
||||
|
||||
#region DB
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
// Configure the HTTP request pipeline.
|
||||
if (app.Environment.IsDevelopment())
|
||||
{
|
||||
app.MapOpenApi().WithDocumentPerVersion();
|
||||
app.MapScalarApiReference(options =>
|
||||
{
|
||||
|
||||
options.Title = "商铺管理API V1";
|
||||
options.AddDocuments(new[] { "v1", "v2" });
|
||||
});
|
||||
}
|
||||
|
||||
app.UseHttpsRedirection();
|
||||
|
||||
app.UseAuthentication();
|
||||
|
||||
app.UseAuthorization();
|
||||
|
||||
app.MapControllers();
|
||||
|
||||
app.Run();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/launchsettings.json",
|
||||
"profiles": {
|
||||
"http": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "scalar/v1",
|
||||
"applicationUrl": "http://localhost:5295",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"https": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "scalar/v1",
|
||||
"applicationUrl": "https://localhost:7241;http://localhost:5295",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.10" />
|
||||
<PackageReference Include="Scalar.AspNetCore" Version="2.16.18" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\StoreHub.Framework\StoreHub.Framework.csproj" />
|
||||
<ProjectReference Include="..\StoreHub.KJDWebService\StoreHub.KJDWebService.csproj" />
|
||||
<ProjectReference Include="..\StoreHub.Services\StoreHub.Services.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,47 @@
|
||||
@StoreHub_HostAddress = http://localhost:5295
|
||||
|
||||
# ============ 认证(JWT) ============
|
||||
|
||||
### 登录:返回 accessToken(单点登录,同用户新登录会使旧 token 失效)
|
||||
# @name login
|
||||
POST {{StoreHub_HostAddress}}/api/v1/Auth/Login
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"userName": "18990011234",
|
||||
"password": "18990011234"
|
||||
}
|
||||
|
||||
### 获取当前登录用户信息(需携带 Bearer Token,先执行上面的 login)
|
||||
GET {{StoreHub_HostAddress}}/api/v1/Auth/Me
|
||||
Authorization: Bearer {{login.response.body.$.data.token}}
|
||||
|
||||
### 退出登录:销毁服务端会话,token 立即失效
|
||||
POST {{StoreHub_HostAddress}}/api/v1/Auth/Logout
|
||||
Authorization: Bearer {{login.response.body.$.data.token}}
|
||||
|
||||
### 在线人员列表(需携带 Bearer Token)
|
||||
GET {{StoreHub_HostAddress}}/api/v1/Auth/OnlineUsers
|
||||
Authorization: Bearer {{login.response.body.$.data.token}}
|
||||
|
||||
# ============ 示例接口 ============
|
||||
|
||||
### 模板自带的 WeatherForecast
|
||||
GET {{StoreHub_HostAddress}}/api/v1/weatherforecast/
|
||||
Accept: application/json
|
||||
|
||||
POST {{StoreHub_HostAddress}}/api/v1/Auth/Login
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
//LoginRequest
|
||||
}
|
||||
|
||||
###
|
||||
|
||||
POST {{StoreHub_HostAddress}}/api/v1/Auth/Login
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
//LoginRequest
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace StoreHub
|
||||
{
|
||||
public class WeatherForecast
|
||||
{
|
||||
public DateOnly Date { get; set; }
|
||||
|
||||
public int TemperatureC { get; set; }
|
||||
|
||||
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
|
||||
|
||||
public string? Summary { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
namespace StoreHub
|
||||
{
|
||||
public class WeatherForecastV2
|
||||
{
|
||||
public DateOnly Date { get; set; }
|
||||
|
||||
public int TemperatureC { get; set; }
|
||||
|
||||
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
|
||||
|
||||
public int FeelsLikeC { get; set; }
|
||||
|
||||
public string? Summary { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"ConnectionStrings": {
|
||||
"DefaultConnection": "Server=localhost;Port=3306;Database=erp;Uid=root;Pwd=123456;",
|
||||
"UserConnection": "Server=localhost;Port=3306;Database=wxxd;Uid=root;Pwd=123456;",
|
||||
"KJDConnection": "Server=localhost;Port=3306;Database=mysql;Uid=root;Pwd=123456;"
|
||||
},
|
||||
"Jwt": {
|
||||
"Issuer": "StoreHub",
|
||||
"Audience": "StoreHub.Client",
|
||||
"SigningKey": "StoreHub--Smxf8DtV8a5dFdaCVCXtFfWbmMRfw53ectEjr8JvhUjGpPP7fuBdRn",
|
||||
"ExpiresMinutes": 120,
|
||||
"ClockSkewMinutes": 5,
|
||||
"ValidateIssuer": true,
|
||||
"ValidateAudience": true,
|
||||
"ValidateLifetime": true
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
}
|
||||
Reference in new issue
Block a user