添加项目文件。
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Common
|
||||
{
|
||||
public class AppInstanceId
|
||||
{
|
||||
public string Id { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Common
|
||||
{
|
||||
public class BaseResponse<T> : BaseCommonResponse
|
||||
{
|
||||
public T Data { get; set; }
|
||||
}
|
||||
|
||||
public class ListResponse<T> : BaseCommonResponse
|
||||
{
|
||||
public IEnumerable<T> Data { get; set; }
|
||||
}
|
||||
|
||||
public class PageResponse<T> : BaseCommonResponse
|
||||
{
|
||||
public IEnumerable<T> Data { get; set; }
|
||||
public int Count { get; set; }
|
||||
|
||||
}
|
||||
|
||||
public class PageDataResponse<T> : BaseCommonResponse
|
||||
{
|
||||
public int Count { get; set; }
|
||||
public IEnumerable<T> Data { get; set; }
|
||||
}
|
||||
|
||||
public class BaseCommonResponse
|
||||
{
|
||||
public CommonResponseCodeEnum Code { get; set; }
|
||||
|
||||
public string Info { get; set; }
|
||||
public BaseCommonResponse(CommonResponseCodeEnum code = CommonResponseCodeEnum.Success, string info = null)
|
||||
{
|
||||
Code = code;
|
||||
Info = info;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,36 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using ToolGood.Words;
|
||||
|
||||
namespace Common.BlockWord
|
||||
{
|
||||
public static class BlockWordHelper
|
||||
{
|
||||
public static bool IsHaveBlockWord(string text)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(text))
|
||||
return false;
|
||||
StringSearch iwords = new StringSearch();
|
||||
iwords.SetKeywords(BlockWord.BlockWordValue.Split(','));
|
||||
return iwords.ContainsAny(text);
|
||||
}
|
||||
|
||||
public static string ReplaceBlockWord(string text, char replaceChar = '*')
|
||||
{
|
||||
StringSearch iwords = new StringSearch();
|
||||
iwords.SetKeywords(BlockWord.BlockWordValue.Split(','));
|
||||
return iwords.Replace(text, replaceChar);
|
||||
}
|
||||
public static List<string> GetBlockWord(string text)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(text))
|
||||
return null;
|
||||
StringSearch iwords = new StringSearch();
|
||||
iwords.SetKeywords(BlockWord.BlockWordValue.Split(','));
|
||||
return iwords.FindAll(text);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using System;
|
||||
|
||||
namespace Common
|
||||
{
|
||||
public class BusinessException : Exception
|
||||
{
|
||||
public int ErrorCode { get; }
|
||||
|
||||
public BusinessException() : base("业务处理失败") { }
|
||||
|
||||
public BusinessException(string message) : base(message) { }
|
||||
|
||||
public BusinessException(string message, int errorCode) : base(message)
|
||||
{
|
||||
ErrorCode = errorCode;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Cors" Version="2.3.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="9.0.7" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="NPinyin" Version="0.2.6321.26573" />
|
||||
<PackageReference Include="RestSharp" Version="112.1.0" />
|
||||
<PackageReference Include="ToolGood.Words" Version="3.1.0.3" />
|
||||
<PackageReference Include="XAct.Core.PCL" Version="0.0.5014" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\RedisHelper\RedisHelper.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,17 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Common.CommonModel
|
||||
{
|
||||
public class BaseIdName
|
||||
{
|
||||
/// <summary>
|
||||
/// Id
|
||||
/// </summary>
|
||||
public string Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Common.CommonModel
|
||||
{
|
||||
public class BaseListPageParam
|
||||
{
|
||||
private string _keyword = "";
|
||||
private int _page = 1;
|
||||
private int _pageSize = 10;
|
||||
public string KeyWord
|
||||
{
|
||||
get { return string.IsNullOrWhiteSpace(_keyword) ? "" : _keyword.Trim(); }
|
||||
set { _keyword = string.IsNullOrWhiteSpace(value) ? "" : value.Trim(); }
|
||||
}
|
||||
|
||||
|
||||
public int? PageIndex
|
||||
{
|
||||
get { return _page <= 1 ? 1 : _page; }
|
||||
set { _page = value ?? 0; }
|
||||
}
|
||||
|
||||
public int? PageSize
|
||||
{
|
||||
get { return _pageSize <= 1 ? 10 : _pageSize; }
|
||||
set { _pageSize = value ?? 0; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Common.Cors
|
||||
{
|
||||
|
||||
public static class CorsExtension
|
||||
{
|
||||
public static IServiceCollection AddCustomCors(this IServiceCollection services)
|
||||
{
|
||||
services.AddCors(options =>
|
||||
{
|
||||
options.AddPolicy("Cors", policy =>
|
||||
{
|
||||
policy
|
||||
|
||||
.SetIsOriginAllowed(_ => true)
|
||||
.AllowAnyHeader()
|
||||
.AllowAnyMethod()
|
||||
.AllowCredentials()
|
||||
;
|
||||
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
|
||||
public static void UseCustomCors(this IApplicationBuilder app)
|
||||
{
|
||||
app.UseCors("Cors");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Common
|
||||
{
|
||||
/// <summary>
|
||||
/// Enables the efficient, dynamic composition of query predicates.
|
||||
/// </summary>
|
||||
public static class LinqExtensions
|
||||
{
|
||||
public static Expression Property(this Expression expression, string propertyName)
|
||||
{
|
||||
return Expression.Property(expression, propertyName);
|
||||
}
|
||||
|
||||
public static Expression AndAlso(this Expression left, Expression right)
|
||||
{
|
||||
return Expression.AndAlso(left, right);
|
||||
}
|
||||
public static Expression Call(this Expression instance, string methodName, params Expression[] arguments)
|
||||
{
|
||||
return Expression.Call(instance, instance.Type.GetMethod(methodName), arguments);
|
||||
}
|
||||
public static Expression GreaterThan(this Expression left, Expression right)
|
||||
{
|
||||
return Expression.GreaterThan(left, right);
|
||||
}
|
||||
public static Expression<T> ToLambda<T>(this Expression body, params ParameterExpression[] parameters)
|
||||
{
|
||||
return Expression.Lambda<T>(body, parameters);
|
||||
}
|
||||
public static Expression<Func<T, bool>> True<T>() { return param => true; }
|
||||
public static Expression<Func<T, bool>> False<T>() { return param => false; }
|
||||
public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second)
|
||||
{
|
||||
return first.Compose(second, Expression.AndAlso);
|
||||
}
|
||||
public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second)
|
||||
{
|
||||
return first.Compose(second, Expression.OrElse);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Combines the first expression with the second using the specified merge function.
|
||||
/// </summary>
|
||||
static Expression<T> Compose<T>(this Expression<T> first, Expression<T> second, Func<Expression, Expression, Expression> merge)
|
||||
{
|
||||
var map = first.Parameters
|
||||
.Select((f, i) => new { f, s = second.Parameters[i] })
|
||||
.ToDictionary(p => p.s, p => p.f);
|
||||
var secondBody = ParameterRebinder.ReplaceParameters(map, second.Body);
|
||||
return Expression.Lambda<T>(merge(first.Body, secondBody), first.Parameters);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ParameterRebinder
|
||||
/// </summary>
|
||||
private class ParameterRebinder : ExpressionVisitor
|
||||
{
|
||||
/// <summary>
|
||||
/// The ParameterExpression map
|
||||
/// </summary>
|
||||
readonly Dictionary<ParameterExpression, ParameterExpression> map;
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ParameterRebinder"/> class.
|
||||
/// </summary>
|
||||
/// <param name="map">The map.</param>
|
||||
ParameterRebinder(Dictionary<ParameterExpression, ParameterExpression> map)
|
||||
{
|
||||
this.map = map ?? new Dictionary<ParameterExpression, ParameterExpression>();
|
||||
}
|
||||
/// <summary>
|
||||
/// Replaces the parameters.
|
||||
/// </summary>
|
||||
/// <param name="map">The map.</param>
|
||||
/// <param name="exp">The exp.</param>
|
||||
/// <returns>Expression</returns>
|
||||
public static Expression ReplaceParameters(Dictionary<ParameterExpression, ParameterExpression> map, Expression exp)
|
||||
{
|
||||
return new ParameterRebinder(map).Visit(exp);
|
||||
}
|
||||
/// <summary>
|
||||
/// Visits the parameter.
|
||||
/// </summary>
|
||||
/// <param name="p">The p.</param>
|
||||
/// <returns>Expression</returns>
|
||||
protected override Expression VisitParameter(ParameterExpression p)
|
||||
{
|
||||
ParameterExpression replacement;
|
||||
|
||||
if (map.TryGetValue(p, out replacement))
|
||||
{
|
||||
p = replacement;
|
||||
}
|
||||
return base.VisitParameter(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
using System;
|
||||
|
||||
namespace Common.Redis
|
||||
{
|
||||
public class RedisKey
|
||||
{
|
||||
public static string MatchingRoom => RedisKeyGenerator.GameKey("matching:room:level:{0}");
|
||||
|
||||
public static string UserCIDGID => RedisKeyGenerator.UserKey("uid:{0}");
|
||||
|
||||
public static string LoginCIDUser => RedisKeyGenerator.UserKey("cid:{0}");
|
||||
|
||||
public static string CIDUser => RedisKeyGenerator.UserKey("cid:{0}");
|
||||
|
||||
public static string VerificationCode => RedisKeyGenerator.UserKey("verification_code:{0}");
|
||||
|
||||
public static string ProductRedis => RedisKeyGenerator.UserKey("product:{0}");
|
||||
|
||||
public static string WeiXin_AccessToken => RedisKeyGenerator.CommonKey("wx_access_token");
|
||||
|
||||
public static string WeiXinUser_AccessToken => RedisKeyGenerator.CommonKey("wx_user_access_token:{0}");
|
||||
|
||||
public static string GameConfig => RedisKeyGenerator.GameKey("config");
|
||||
|
||||
public static string ActivityConfig => RedisKeyGenerator.GameKey("activity:config");
|
||||
|
||||
public static string TodayVisitsKey => RedisKeyGenerator.AdminKey("stats:visits:today");
|
||||
|
||||
public static string MonthVisitsKey => RedisKeyGenerator.AdminKey("stats:visits:month");
|
||||
|
||||
public static string TotalVisitsKey => RedisKeyGenerator.AdminKey("stats:visits:total");
|
||||
|
||||
public static string TotalTrafficKey => RedisKeyGenerator.AdminKey("stats:traffic:total");
|
||||
|
||||
public static string TodayTrafficKey => RedisKeyGenerator.AdminKey("stats:traffic:today");
|
||||
|
||||
public static string MonthTrafficKey => RedisKeyGenerator.AdminKey("stats:traffic:month");
|
||||
|
||||
public static string GameRoomIdKey => RedisKeyGenerator.GameKey("room:model:{0}_{1}");
|
||||
|
||||
public static string DistributedLockPrefix => RedisKeyGenerator.CommonKey("lock:");
|
||||
|
||||
public static string ChessRoomList => RedisKeyGenerator.GameKey("chess:room:list");
|
||||
|
||||
public static string ChessRoomKey => RedisKeyGenerator.GameKey("chess:room:{0}");
|
||||
|
||||
public static string SystemGoldDayKey => RedisKeyGenerator.GameKey("fish:gamestatic");
|
||||
|
||||
public static string CUSTOMER_ONLINE_KEY => RedisKeyGenerator.CustomerKey("online");
|
||||
|
||||
public static string FishingKillConfig => RedisKeyGenerator.GameKey("fishing:killconfig");
|
||||
|
||||
public static string FishSpawnConfig => RedisKeyGenerator.GameKey("fish:spawn:config:v1");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
namespace Common.Redis
|
||||
{
|
||||
public static class RedisKeyGenerator
|
||||
{
|
||||
private static readonly Dictionary<string, string> ServicePrefixes = new()
|
||||
{
|
||||
{ "user", "user-service" },
|
||||
{ "admin", "admin-service" },
|
||||
{ "game", "game-service" },
|
||||
{ "fish", "game-service" },
|
||||
{ "chess", "game-service" },
|
||||
{ "customer", "customer-service" },
|
||||
{ "common", "app-shared" }
|
||||
};
|
||||
|
||||
public static string GetKey(string serviceName, string key, params object[] args)
|
||||
{
|
||||
var prefix = GetPrefix(serviceName);
|
||||
var formattedKey = args.Length > 0 ? string.Format(key, args) : key;
|
||||
return $"{prefix}:{formattedKey}";
|
||||
}
|
||||
|
||||
private static string GetPrefix(string serviceName)
|
||||
{
|
||||
return ServicePrefixes.TryGetValue(serviceName.ToLower(), out var prefix)
|
||||
? prefix
|
||||
: "app-shared";
|
||||
}
|
||||
|
||||
public static string UserKey(string key, params object[] args) =>
|
||||
GetKey("user", key, args);
|
||||
|
||||
public static string AdminKey(string key, params object[] args) =>
|
||||
GetKey("admin", key, args);
|
||||
|
||||
public static string GameKey(string key, params object[] args) =>
|
||||
GetKey("game", key, args);
|
||||
|
||||
public static string CustomerKey(string key, params object[] args) =>
|
||||
GetKey("customer", key, args);
|
||||
|
||||
public static string CommonKey(string key, params object[] args) =>
|
||||
GetKey("common", key, args);
|
||||
}
|
||||
|
||||
public static class RedisKeyConstants
|
||||
{
|
||||
private const string ServicePrefix = "redis0524";
|
||||
|
||||
public static string MatchingRoom => $"{ServicePrefix}:matching:room:level_{{0}}";
|
||||
|
||||
public static string UserCIDGID => $"{ServicePrefix}:user:uid_{{0}}";
|
||||
|
||||
public static string LoginCIDUser => $"{ServicePrefix}:user:cid_{{0}}";
|
||||
|
||||
public static string CIDUser => $"{ServicePrefix}:user:cid_{{0}}";
|
||||
|
||||
public static string VerificationCode => $"{ServicePrefix}:verification_code:{{0}}";
|
||||
|
||||
public static string ProductRedis => $"{ServicePrefix}:product:{{0}}";
|
||||
|
||||
public static string WeiXin_AccessToken => $"{ServicePrefix}:wx_access_token";
|
||||
|
||||
public static string WeiXinUser_AccessToken => $"{ServicePrefix}:wx_user_access_token:{{0}}";
|
||||
|
||||
public static string GameConfig => $"{ServicePrefix}:game_config";
|
||||
|
||||
public static string TodayVisitsKey => $"{ServicePrefix}:TodayVisits";
|
||||
|
||||
public static string MonthVisitsKey => $"{ServicePrefix}:MonthVisits";
|
||||
|
||||
public static string TotalVisitsKey => $"{ServicePrefix}:TotalVisits";
|
||||
|
||||
public static string TotalTrafficKey => $"{ServicePrefix}:TotalTraffic";
|
||||
|
||||
public static string TodayTrafficKey => $"{ServicePrefix}:TodayTraffic";
|
||||
|
||||
public static string MonthTrafficKey => $"{ServicePrefix}:MonthTraffic";
|
||||
|
||||
public static string GameRoomIdKey => $"{ServicePrefix}:game:room:model:{{0}}_{{1}}";
|
||||
|
||||
public static string DistributedLockPrefix => $"{ServicePrefix}:lock:";
|
||||
|
||||
public static string ChessRoomList => $"{ServicePrefix}:chess:room:list";
|
||||
|
||||
public static string ChessRoomKey => $"{ServicePrefix}:chess:room:{{0}}";
|
||||
|
||||
public static string SystemGoldDayKey => $"{ServicePrefix}:fish:gamestatic";
|
||||
|
||||
public static string CustomerOnlineKey => "game0524:customer:online";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Common.Request
|
||||
{
|
||||
public interface IRequestApi
|
||||
{
|
||||
Task<string> GetAsync(string host, string resource, Dictionary<string, object> param,
|
||||
Dictionary<string, string> headerParms = null, int timeOut = 5);
|
||||
|
||||
Task<string> PostAsync(string host, string resource, object param, Dictionary<string, string> queryParam = null,
|
||||
Dictionary<string, string> headerParms = null, int timeOut = 5);
|
||||
|
||||
Task<string> PostFormDataAsync(string host, string resource, Dictionary<string, object> param);
|
||||
|
||||
|
||||
Task<bool> DownloadFile(string fileName, string host, string resource,
|
||||
Dictionary<string, object> param = null, Dictionary<string, string> headers = null);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Common.Utils;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using RestSharp;
|
||||
|
||||
namespace Common.Request
|
||||
{
|
||||
public class RequestApi : IRequestApi
|
||||
{
|
||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||
private ILogger<RequestApi> _logger;
|
||||
private Dictionary<string, string> defaultHeader = null;
|
||||
|
||||
public RequestApi(IHttpContextAccessor httpContextAccessor, ILogger<RequestApi> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
_httpContextAccessor = httpContextAccessor;
|
||||
defaultHeader = new Dictionary<string, string>();
|
||||
if (_httpContextAccessor.HttpContext != null && _httpContextAccessor.HttpContext.Request != null &&
|
||||
_httpContextAccessor.HttpContext.Request.Headers != null)
|
||||
{
|
||||
defaultHeader.Add("token", _httpContextAccessor.HttpContext.Request.Headers["token"].ToString() ?? "");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<string> GetAsync(string host, string resource, Dictionary<string, object> param = null,
|
||||
Dictionary<string, string> headers = null, int timeOut = 5)
|
||||
{
|
||||
var options = new RestClientOptions(host)
|
||||
{
|
||||
ThrowOnAnyError = true
|
||||
};
|
||||
var restClient = new RestClient(options);
|
||||
var request = new RestRequest(resource)
|
||||
{
|
||||
Timeout = TimeSpan.FromSeconds(timeOut)
|
||||
};
|
||||
if (param != null && param.Count > 0)
|
||||
{
|
||||
foreach (var item in param.Where(e => e.Value != null))
|
||||
{
|
||||
request.AddQueryParameter(item.Key, item.Value.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
if (headers != null)
|
||||
{
|
||||
defaultHeader = headers;
|
||||
}
|
||||
|
||||
|
||||
|
||||
request.AddHeaders(defaultHeader);
|
||||
var restResponse = await restClient.ExecuteGetAsync(request);
|
||||
return HandleResponse(restResponse, resource, param);
|
||||
}
|
||||
|
||||
public async Task<string> PostAsync(string host, string resource, object param,
|
||||
Dictionary<string, string> queryParams = null, Dictionary<string, string> headers = null, int timeOut = 5)
|
||||
{
|
||||
var options = new RestClientOptions(host)
|
||||
{
|
||||
ThrowOnAnyError = true
|
||||
};
|
||||
var restClient = new RestClient(options);
|
||||
var request = new RestRequest(resource)
|
||||
{
|
||||
Timeout = TimeSpan.FromSeconds(timeOut)
|
||||
};
|
||||
if (queryParams != null && queryParams.Count > 0)
|
||||
{
|
||||
foreach (var item in queryParams)
|
||||
{
|
||||
request.AddQueryParameter(item.Key, item.Value);
|
||||
}
|
||||
}
|
||||
|
||||
if (param != null)
|
||||
{
|
||||
request.AddJsonBody(param);
|
||||
}
|
||||
|
||||
if (headers != null)
|
||||
{
|
||||
defaultHeader = headers;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
|
||||
request.AddHeaders(defaultHeader);
|
||||
var restResponse = await restClient.ExecutePostAsync(request);
|
||||
return HandleResponse(restResponse, resource, param);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<string> PostFormDataAsync(string host, string resource, Dictionary<string, object> param)
|
||||
{
|
||||
var options = new RestClientOptions(host)
|
||||
{
|
||||
ThrowOnAnyError = true
|
||||
};
|
||||
var restClient = new RestClient(options);
|
||||
var request = new RestRequest(resource);
|
||||
if (param != null && param.Count > 0)
|
||||
{
|
||||
foreach (var item in param)
|
||||
{
|
||||
request.AddParameter(item.Key, item.Value.ToJson(), ParameterType.RequestBody);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
|
||||
request.AddHeaders(defaultHeader);
|
||||
var restResponse = await restClient.ExecutePostAsync(request);
|
||||
return HandleResponse(restResponse, resource, param);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
private string HandleResponse(RestResponse restResponse, string resource, object param)
|
||||
{
|
||||
if (!restResponse.IsSuccessful)
|
||||
{
|
||||
|
||||
return JsonConvert.SerializeObject(new {info = restResponse.ErrorMessage, code = -1});
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
JObject jt = Newtonsoft.Json.JsonConvert.DeserializeObject<JObject>(restResponse.Content);
|
||||
int code = jt["code"].ObjToInt();
|
||||
if (code != 0 && code != 401)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
return restResponse.Content;
|
||||
}
|
||||
|
||||
|
||||
public async Task<bool> DownloadFile(string fileName, string host, string resource,
|
||||
Dictionary<string, object> param = null, Dictionary<string, string> headers = null)
|
||||
{
|
||||
var options = new RestClientOptions(host)
|
||||
{
|
||||
ThrowOnAnyError = true,
|
||||
};
|
||||
var restClient = new RestClient(options);
|
||||
var request = new RestRequest(resource);
|
||||
if (param != null && param.Count > 0)
|
||||
{
|
||||
foreach (var item in param.Where(e => e.Value != null))
|
||||
{
|
||||
request.AddQueryParameter(item.Key, item.Value.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
if (headers != null)
|
||||
{
|
||||
defaultHeader = headers;
|
||||
}
|
||||
|
||||
|
||||
request.AddHeaders(defaultHeader);
|
||||
|
||||
var restResponse = await restClient.DownloadDataAsync(request);
|
||||
if (System.IO.File.Exists(fileName))
|
||||
{
|
||||
System.IO.File.Delete(fileName);
|
||||
}
|
||||
using (FileStream pFileStream = new FileStream(fileName, FileMode.OpenOrCreate))
|
||||
{
|
||||
pFileStream.Write(restResponse, 0, restResponse.Length);
|
||||
}
|
||||
|
||||
return System.IO.File.Exists(fileName);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Common
|
||||
{
|
||||
public enum CommonResponseCodeEnum
|
||||
{
|
||||
Error = -1,
|
||||
Exception = 500,
|
||||
NoAuth = 401,
|
||||
NoRole = 403,
|
||||
TokenExpire = 1000,
|
||||
|
||||
Success = 0,
|
||||
NoLogin = 401,
|
||||
TokenInvalid = 12,
|
||||
ParamError = 13,
|
||||
SignError = 14,
|
||||
ResultNull = 15,
|
||||
LowVersion = 16,
|
||||
ResultFail = -1
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Common.Utils
|
||||
{
|
||||
public static class ConstValues
|
||||
{
|
||||
|
||||
public const int LockSecond = 5;
|
||||
public const string LockNotice = "业务正在处理中,请稍后";
|
||||
|
||||
public const string DevModel = "dev";
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,611 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Reflection;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Xml;
|
||||
using Newtonsoft.Json;
|
||||
using NPinyin;
|
||||
namespace Common.Utils
|
||||
{
|
||||
public static class ConvertExtension
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="thisValue"></param>
|
||||
/// <returns></returns>
|
||||
public static int ObjToInt(this object thisValue)
|
||||
{
|
||||
int reval = 0;
|
||||
if (thisValue == null) return 0;
|
||||
if (thisValue != null && thisValue != DBNull.Value && int.TryParse(thisValue.ToString(), out reval))
|
||||
{
|
||||
return reval;
|
||||
}
|
||||
return reval;
|
||||
}
|
||||
public static bool ContainsAll<T>(this IEnumerable<T> source, IEnumerable<T> items)
|
||||
{
|
||||
if (source == null || items == null)
|
||||
return false;
|
||||
|
||||
var sourceCounts = source.GroupBy(x => x).ToDictionary(g => g.Key, g => g.Count());
|
||||
var itemsCounts = items.GroupBy(x => x).ToDictionary(g => g.Key, g => g.Count());
|
||||
|
||||
foreach (var item in itemsCounts)
|
||||
{
|
||||
if (!sourceCounts.TryGetValue(item.Key, out int count) || count < item.Value)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static IEnumerable<T> RemoveRange<T>(this IEnumerable<T> source, IEnumerable<T> items)
|
||||
{
|
||||
if (source == null)
|
||||
return Enumerable.Empty<T>();
|
||||
|
||||
if (items == null || !items.Any())
|
||||
return source;
|
||||
|
||||
var list = new List<T>(source);
|
||||
|
||||
foreach (var item in items)
|
||||
{
|
||||
int index = list.IndexOf(item);
|
||||
if (index >= 0)
|
||||
{
|
||||
list.RemoveAt(index);
|
||||
}
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="thisValue"></param>
|
||||
/// <param name="errorValue"></param>
|
||||
/// <returns></returns>
|
||||
public static int ObjToInt(this object thisValue, int errorValue)
|
||||
{
|
||||
int reval = 0;
|
||||
if (thisValue != null && thisValue != DBNull.Value && int.TryParse(thisValue.ToString(), out reval))
|
||||
{
|
||||
return reval;
|
||||
}
|
||||
return errorValue;
|
||||
}
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="thisValue"></param>
|
||||
/// <returns></returns>
|
||||
public static long ObjToLong(this object thisValue)
|
||||
{
|
||||
long reval = 0;
|
||||
if (thisValue == null) return 0;
|
||||
if (thisValue != null && thisValue != DBNull.Value && long.TryParse(thisValue.ToString(), out reval))
|
||||
{
|
||||
return reval;
|
||||
}
|
||||
return reval;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="thisValue"></param>
|
||||
/// <returns></returns>
|
||||
public static double ObjToMoney(this object thisValue)
|
||||
{
|
||||
double reval = 0;
|
||||
if (thisValue != null && thisValue != DBNull.Value && double.TryParse(thisValue.ToString(), out reval))
|
||||
{
|
||||
return reval;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
public static T GetRandomItem<T>(this IEnumerable<T> thisValue)
|
||||
{
|
||||
if (thisValue != null && thisValue.Count() > 0)
|
||||
{
|
||||
int index = Random.Shared.Next(thisValue.Count());
|
||||
return thisValue.ToList()[index];
|
||||
}
|
||||
else if (thisValue.Count() == 1)
|
||||
{
|
||||
return thisValue.ToList()[0];
|
||||
}
|
||||
else
|
||||
return default(T);
|
||||
|
||||
}
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="thisValue"></param>
|
||||
/// <param name="errorValue"></param>
|
||||
/// <returns></returns>
|
||||
public static double ObjToMoney(this object thisValue, double errorValue)
|
||||
{
|
||||
double reval = 0;
|
||||
if (thisValue != null && thisValue != DBNull.Value && double.TryParse(thisValue.ToString(), out reval))
|
||||
{
|
||||
return reval;
|
||||
}
|
||||
return errorValue;
|
||||
}
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="thisValue"></param>
|
||||
/// <returns></returns>
|
||||
public static string ObjToString(this object thisValue)
|
||||
{
|
||||
if (thisValue != null) return thisValue.ToString().Trim();
|
||||
return "";
|
||||
}
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="thisValue"></param>
|
||||
/// <returns></returns>
|
||||
public static bool IsNotEmptyOrNull(this IEnumerable<object> thisValue)
|
||||
{
|
||||
return thisValue != null && thisValue.Count() > 0; ;
|
||||
}
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="thisValue"></param>
|
||||
/// <param name="errorValue"></param>
|
||||
/// <returns></returns>
|
||||
public static string ObjToString(this object thisValue, string errorValue)
|
||||
{
|
||||
if (thisValue != null) return thisValue.ToString().Trim();
|
||||
return errorValue;
|
||||
}
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="thisValue"></param>
|
||||
/// <returns></returns>
|
||||
public static Decimal ObjToDecimal(this object thisValue)
|
||||
{
|
||||
Decimal reval = 0;
|
||||
if (thisValue != null && thisValue != DBNull.Value && decimal.TryParse(thisValue.ToString(), out reval))
|
||||
{
|
||||
return reval;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="thisValue"></param>
|
||||
/// <returns></returns>
|
||||
public static double ObjToDouble(this object thisValue)
|
||||
{
|
||||
double reval = 0;
|
||||
if (thisValue != null && thisValue != DBNull.Value && double.TryParse(thisValue.ToString(), out reval))
|
||||
{
|
||||
return reval;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="thisValue"></param>
|
||||
/// <param name="errorValue"></param>
|
||||
/// <returns></returns>
|
||||
public static Decimal ObjToDecimal(this object thisValue, decimal errorValue)
|
||||
{
|
||||
Decimal reval = 0;
|
||||
if (thisValue != null && thisValue != DBNull.Value && decimal.TryParse(thisValue.ToString(), out reval))
|
||||
{
|
||||
return reval;
|
||||
}
|
||||
return errorValue;
|
||||
}
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="thisValue"></param>
|
||||
/// <returns></returns>
|
||||
public static DateTime ObjToDate(this object thisValue)
|
||||
{
|
||||
DateTime reval = DateTime.MinValue;
|
||||
if (thisValue != null && thisValue != DBNull.Value && DateTime.TryParse(thisValue.ToString(), out reval))
|
||||
{
|
||||
reval = Convert.ToDateTime(thisValue);
|
||||
}
|
||||
return reval;
|
||||
}
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="thisValue"></param>
|
||||
/// <param name="errorValue"></param>
|
||||
/// <returns></returns>
|
||||
public static DateTime ObjToDate(this object thisValue, DateTime errorValue)
|
||||
{
|
||||
DateTime reval = DateTime.MinValue;
|
||||
if (thisValue != null && thisValue != DBNull.Value && DateTime.TryParse(thisValue.ToString(), out reval))
|
||||
{
|
||||
return reval;
|
||||
}
|
||||
return errorValue;
|
||||
}
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="thisValue"></param>
|
||||
/// <returns></returns>
|
||||
public static bool ObjToBool(this object thisValue)
|
||||
{
|
||||
bool reval = false;
|
||||
if (thisValue != null && thisValue != DBNull.Value && bool.TryParse(thisValue.ToString(), out reval))
|
||||
{
|
||||
return reval;
|
||||
}
|
||||
return reval;
|
||||
}
|
||||
|
||||
public static DateTime ConvertToDateTime(this long timeStamp)
|
||||
{
|
||||
var dto = DateTimeOffset.FromUnixTimeSeconds(timeStamp);
|
||||
return dto.ToLocalTime().DateTime;
|
||||
}
|
||||
public static DateTime ConvertToDateTime(this string timeStamp)
|
||||
{
|
||||
try
|
||||
{
|
||||
var dto = DateTimeOffset.FromUnixTimeSeconds(Convert.ToInt64(timeStamp));
|
||||
return dto.ToLocalTime().DateTime;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new FormatException($"无效的Unix时间戳: {timeStamp}", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static string DateToTimeStamp(this DateTime thisValue)
|
||||
{
|
||||
DateTimeOffset dto = new DateTimeOffset(thisValue);
|
||||
return dto.ToUnixTimeSeconds().ToString();
|
||||
}
|
||||
public static decimal ToCeilingFixed2(this decimal value)
|
||||
{
|
||||
try
|
||||
{
|
||||
return Convert.ToInt32(Math.Ceiling(Math.Round(value * 100, 2))) / (decimal)100.0;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return (decimal)0.0;
|
||||
}
|
||||
}
|
||||
public static decimal ToFloorFixed2(this decimal value)
|
||||
{
|
||||
try
|
||||
{
|
||||
return Convert.ToInt32(Math.Floor(Math.Round(value * 100, 2))) / (decimal)100.0;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return (decimal)0.0;
|
||||
}
|
||||
}
|
||||
public static double ToCeilingFixed2(this double value)
|
||||
{
|
||||
try
|
||||
{
|
||||
return Convert.ToInt32(Math.Ceiling(Math.Round(value * 100, 2))) / 100.0;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
public static double ToFloorFixed2(this double value)
|
||||
{
|
||||
try
|
||||
{
|
||||
return Convert.ToInt32(Math.Floor(Math.Round(value * 100, 2))) / 100.0;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return 0.0;
|
||||
}
|
||||
}
|
||||
public static string ConvertToPinyin(this string chinese)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(chinese))
|
||||
return "*";
|
||||
return Pinyin.GetPinyin(chinese).Trim().Replace(" ", "").ToUpper();
|
||||
}
|
||||
|
||||
public static int ToCeilingInt(this double value)
|
||||
{
|
||||
try
|
||||
{
|
||||
return Convert.ToInt32(Math.Ceiling(Math.Round(value, 1)));
|
||||
}
|
||||
catch
|
||||
{
|
||||
return Convert.ToInt32(value);
|
||||
}
|
||||
}
|
||||
public static int ToCeilingInt(this decimal value)
|
||||
{
|
||||
try
|
||||
{
|
||||
return Convert.ToInt32(Math.Ceiling(Math.Round(value, 1)));
|
||||
}
|
||||
catch
|
||||
{
|
||||
return Convert.ToInt32(value);
|
||||
}
|
||||
}
|
||||
|
||||
public static string[] Remove(this string[] value, string str)
|
||||
{
|
||||
List<string> valueList = value.ToList();
|
||||
if (!valueList.Contains(str))
|
||||
return value;
|
||||
valueList.Remove(str);
|
||||
return valueList.ToArray();
|
||||
}
|
||||
public static int[] Remove(this int[] value, int str)
|
||||
{
|
||||
List<int> valueList = value.ToList();
|
||||
if (!valueList.Contains(str))
|
||||
return value;
|
||||
valueList.Remove(str);
|
||||
return valueList.ToArray();
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
//
|
||||
// time:
|
||||
public static long ToTimeStamp(this DateTime time)
|
||||
{
|
||||
return new DateTimeOffset(time).ToUnixTimeSeconds();
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
// timeStamp:
|
||||
public static DateTime ToDateTime(this long timeStamp)
|
||||
{
|
||||
return DateTimeOffset.FromUnixTimeSeconds(timeStamp).ToLocalTime().DateTime;
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
// obj:
|
||||
public static string ToJson(this object obj)
|
||||
{
|
||||
return JsonConvert.SerializeObject(obj);
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
// str:
|
||||
public static T ToObject<T>(this string str)
|
||||
{
|
||||
return JsonConvert.DeserializeObject<T>(str);
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
// str:
|
||||
//
|
||||
// IgnoreCapitalized:
|
||||
public static string ToUrlEncode(this string str, bool IgnoreCapitalized = true)
|
||||
{
|
||||
return WebUtility.UrlEncode(IgnoreCapitalized ? str.ToLowerInvariant() : str);
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
// source:
|
||||
//
|
||||
// T:
|
||||
//
|
||||
// T:System.ArgumentException:
|
||||
public static T Clone<T>(T source)
|
||||
{
|
||||
return JsonConvert.DeserializeObject<T>(source.ToJson());
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
//
|
||||
// str:
|
||||
public static string ToUrlDecode(this string str)
|
||||
{
|
||||
return WebUtility.UrlDecode(str);
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
// str:
|
||||
public static string ToBase64String(this string str)
|
||||
{
|
||||
return Convert.ToBase64String(Encoding.Default.GetBytes(str));
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
// str:
|
||||
public static string ToParseBase64(this string str)
|
||||
{
|
||||
try
|
||||
{
|
||||
byte[] bytes = Convert.FromBase64String(str);
|
||||
return Encoding.Default.GetString(bytes);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
// value:
|
||||
public static double ToDoubleFixed2(this double value)
|
||||
{
|
||||
try
|
||||
{
|
||||
return (double)Convert.ToInt32(Math.Ceiling(Math.Round(value * 100.0, 2))) / 100.0;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
// value:
|
||||
public static decimal ToDecimalFixed2(this decimal value)
|
||||
{
|
||||
try
|
||||
{
|
||||
return (decimal)Convert.ToInt32(Math.Ceiling(Math.Round(value * 100m, 2))) / 100m;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return 0m;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
// value:
|
||||
public static double ToDoubleFloor2(this double value)
|
||||
{
|
||||
try
|
||||
{
|
||||
return (double)Convert.ToInt32(Math.Floor(Math.Round(value * 100.0, 2))) / 100.0;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
// str:
|
||||
//
|
||||
public static string ToReverse(this string str)
|
||||
{
|
||||
return new string(str.ToCharArray().AsEnumerable().Reverse().ToArray());
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
// value:
|
||||
public static decimal ToDecimalFloor2(this decimal value)
|
||||
{
|
||||
try
|
||||
{
|
||||
return (decimal)Convert.ToInt32(Math.Floor(Math.Round(value * 100m, 2))) / 100m;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return 0m;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
// content:
|
||||
public static string ToDecodeBase64(this string content)
|
||||
{
|
||||
byte[] bytes = Convert.FromBase64String(content);
|
||||
return Encoding.UTF8.GetString(bytes);
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
// mobile:
|
||||
public static string ToHidMobile(this string mobile)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(mobile))
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
if (IsHandset(mobile))
|
||||
{
|
||||
return mobile.Substring(0, 3) + "****" + mobile.Substring(mobile.Length - 4, 4);
|
||||
}
|
||||
|
||||
return mobile;
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
// str_handset:
|
||||
public static bool IsHandset(string str_handset)
|
||||
{
|
||||
return Regex.IsMatch(str_handset, "^1[3456789]\\d{9}$");
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
// list:
|
||||
//
|
||||
// T:
|
||||
public static bool IsNull<T>(this List<T> list)
|
||||
{
|
||||
if (list != null)
|
||||
{
|
||||
return !list.Any();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
// source:
|
||||
//
|
||||
// isReplace:
|
||||
//
|
||||
// isToLower:
|
||||
public static string ToSHA1Encrypt(this string source, bool isReplace = true, bool isToLower = false)
|
||||
{
|
||||
string text = BitConverter.ToString(SHA1.Create().ComputeHash(Encoding.UTF8.GetBytes(source)));
|
||||
if (isReplace)
|
||||
{
|
||||
text = text.Replace("-", "");
|
||||
}
|
||||
|
||||
if (isToLower)
|
||||
{
|
||||
text = text.ToLower();
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Common.Utils
|
||||
{
|
||||
public class DateTimeConverterUtf : JsonConverter<DateTime>
|
||||
{
|
||||
public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
return DateTime.Parse(reader.GetString()!);
|
||||
}
|
||||
|
||||
public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
|
||||
{
|
||||
var cstTime = TimeZoneInfo.ConvertTimeFromUtc(value,
|
||||
TimeZoneInfo.FindSystemTimeZoneById("Asia/Shanghai"));
|
||||
writer.WriteStringValue(cstTime.ToString("yyyy-MM-dd HH:mm:ss"));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
using System;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Common.Utils
|
||||
{
|
||||
public static class DateTimeTools
|
||||
{
|
||||
|
||||
public static DateTime GetDateTimeNow()
|
||||
{
|
||||
return DateTime.SpecifyKind(DateTime.Now, DateTimeKind.Utc);
|
||||
}
|
||||
|
||||
public static int GetTimeDiffDays(DateTime start, DateTime end)
|
||||
{
|
||||
string result = string.Empty;
|
||||
TimeSpan ts1 = new TimeSpan(start.Ticks);
|
||||
TimeSpan ts2 = new TimeSpan(end.Ticks);
|
||||
TimeSpan ts = ts1.Subtract(ts2).Duration();
|
||||
return ts.Days;
|
||||
}
|
||||
public class DatetimeJsonConverter : JsonConverter<DateTime>
|
||||
{
|
||||
public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
if (reader.TokenType == JsonTokenType.String)
|
||||
{
|
||||
if (DateTime.TryParse(reader.GetString(), out DateTime date))
|
||||
return date;
|
||||
}
|
||||
return reader.GetDateTime();
|
||||
}
|
||||
|
||||
public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
|
||||
{
|
||||
writer.WriteStringValue(value.ToString("yyyy-MM-dd HH:mm:ss"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Common.Utils
|
||||
{
|
||||
public static class Md5Helper
|
||||
{
|
||||
public static string GetMD5(string str)
|
||||
{
|
||||
byte[] sor = Encoding.UTF8.GetBytes(str);
|
||||
MD5 md5 = MD5.Create();
|
||||
byte[] result = md5.ComputeHash(sor);
|
||||
StringBuilder strbul = new StringBuilder(40);
|
||||
for (int i = 0; i < result.Length; i++)
|
||||
{
|
||||
strbul.Append(result[i].ToString("x2"));
|
||||
|
||||
}
|
||||
return strbul.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Common.Utils
|
||||
{
|
||||
public static class RandomHelper
|
||||
{
|
||||
//
|
||||
//
|
||||
// length:
|
||||
public static string RandomNumbers(int length = 4)
|
||||
{
|
||||
if (length <= 4)
|
||||
{
|
||||
length = 4;
|
||||
}
|
||||
|
||||
if (length >= 8)
|
||||
{
|
||||
length = 8;
|
||||
}
|
||||
|
||||
string value = 1.ToString().PadRight(length, '0');
|
||||
string value2 = 1.ToString().PadRight(length + 1, '0');
|
||||
return Random.Shared.Next(Convert.ToInt32(value), Convert.ToInt32(value2)).ToString();
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
// length:
|
||||
public static string RandomChars(int length = 4)
|
||||
{
|
||||
if (length <= 4)
|
||||
{
|
||||
length = 4;
|
||||
}
|
||||
|
||||
if (length >= 8)
|
||||
{
|
||||
length = 8;
|
||||
}
|
||||
|
||||
char[] array = new char[62]
|
||||
{
|
||||
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
|
||||
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
|
||||
'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T',
|
||||
'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd',
|
||||
'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
|
||||
'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x',
|
||||
'y', 'z'
|
||||
};
|
||||
string text = "";
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
int num = Random.Shared.Next(0, 61);
|
||||
text += array[num];
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
// length:
|
||||
public static string RandomNoRepeatNumber(int length)
|
||||
{
|
||||
int num = 0;
|
||||
string text = string.Empty;
|
||||
long num2 = DateTime.Now.Ticks + num;
|
||||
num++;
|
||||
Random random = new Random((int)(num2 & 0xFFFFFFFFu) | (int)(num2 >> num));
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
int num3 = random.Next();
|
||||
text += (char)(48 + (ushort)(num3 % 10));
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
// length:
|
||||
public static string RandomNoRepeatChars(int length)
|
||||
{
|
||||
int num = 0;
|
||||
string text = string.Empty;
|
||||
long num2 = DateTime.Now.Ticks + num;
|
||||
num++;
|
||||
Random random = new Random((int)(num2 & 0xFFFFFFFFu) | (int)(num2 >> num));
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
int num3 = random.Next();
|
||||
text += ((num3 % 2 != 0) ? ((char)(65 + (ushort)(num3 % 26))) : ((char)(48 + (ushort)(num3 % 10))));
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
// allChar:
|
||||
//
|
||||
// length:
|
||||
public static string GetRandomCode(string allChar, int length)
|
||||
{
|
||||
string[] array = allChar.Split(',');
|
||||
string text = "";
|
||||
int num = -1;
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
int num2 = Random.Shared.Next(array.Length - 1);
|
||||
while (num == num2)
|
||||
{
|
||||
num2 = Random.Shared.Next(array.Length - 1);
|
||||
}
|
||||
|
||||
num = num2;
|
||||
text += array[num2];
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using Newtonsoft.Json;
|
||||
using XSystem.Security.Cryptography;
|
||||
|
||||
namespace Common
|
||||
{
|
||||
public static class Tools
|
||||
{
|
||||
|
||||
private static string GetTimeString()
|
||||
{
|
||||
return DateTime.Now.ToString("yyyyMMddHHmmssfff");
|
||||
}
|
||||
public static string GetTimeStringRandom(string str = "")
|
||||
{
|
||||
return GetTimeString() + str;
|
||||
}
|
||||
|
||||
public static string LockKey(object param)
|
||||
{
|
||||
var source = JsonConvert.SerializeObject(param);
|
||||
MD5CryptoServiceProvider md5 = new MD5CryptoServiceProvider();
|
||||
byte[] bytes = Encoding.UTF8.GetBytes(source);
|
||||
string result = BitConverter.ToString(md5.ComputeHash(bytes));
|
||||
return "HSCW:RedisLock:" + result.Replace("-", "").ToLower();
|
||||
}
|
||||
|
||||
public static string GetSerialNumber()
|
||||
{
|
||||
return DateTime.Now.ToString("yyyyMMddHHmmssfff") + Random.Shared.Next(100000, 999999);
|
||||
}
|
||||
|
||||
public static string ReplaceWithSpecialChar(string value, int startLen = 1, int endLen = 1, char specialChar = '*')
|
||||
{
|
||||
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrEmpty(value))
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
if (value.Length < startLen + endLen)
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
string startStr = value.Substring(0, startLen);
|
||||
var len = value.Length - startLen - endLen;
|
||||
for (int i = 0; i < len; i++)
|
||||
{
|
||||
startStr += specialChar;
|
||||
}
|
||||
startStr += value.Substring(startLen + len, endLen);
|
||||
return startStr;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return value;
|
||||
}
|
||||
}
|
||||
public static string SubStringText(string content, int length)
|
||||
{
|
||||
if (string.IsNullOrEmpty(content))
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
if (content.Length <= length)
|
||||
{
|
||||
return content;
|
||||
}
|
||||
|
||||
return content.Substring(0, length);
|
||||
}
|
||||
public static string SubStringTextLast(string content, int length)
|
||||
{
|
||||
if (string.IsNullOrEmpty(content))
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
if (content.Length <= length)
|
||||
{
|
||||
return content;
|
||||
}
|
||||
|
||||
return content.Substring(content.Length - length);
|
||||
}
|
||||
|
||||
|
||||
public static List<string> PickupImgUrl(string html)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(html))
|
||||
return null;
|
||||
Regex regImg = new Regex(@"<img\b[^<>]*?\bsrc[\s\t\r\n]*=[\s\t\r\n]*[""']?[\s\t\r\n]*(?<imgUrl>[^\s\t\r\n""'<>]*)[^<>]*?/?[\s\t\r\n]*>", RegexOptions.IgnoreCase);
|
||||
MatchCollection matches = regImg.Matches(html);
|
||||
List<string> lstImg = new List<string>();
|
||||
foreach (Match match in matches)
|
||||
{
|
||||
lstImg.Add(match.Groups["imgUrl"].Value);
|
||||
}
|
||||
|
||||
return lstImg;
|
||||
}
|
||||
public static string NoHTML(string html)
|
||||
{
|
||||
string str = "";
|
||||
for (int i = 0; i < html.Length; i++)
|
||||
{
|
||||
if (Convert.ToInt32(Convert.ToChar(html.Substring(i, 1))) > Convert.ToInt32(Convert.ToChar(128)))
|
||||
{
|
||||
str += html.Substring(i, 1).ToString();
|
||||
}
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
|
||||
public static string RegexSubstring(string content, string startStr, string endStr)
|
||||
{
|
||||
Regex rex = new Regex("(?<=(" + startStr + "))[.\\s\\S]*?(?=(" + endStr + "))", RegexOptions.Multiline | RegexOptions.Singleline);
|
||||
return rex.Match(content).Value;
|
||||
}
|
||||
public static bool CheckIsUrl(string strValue)
|
||||
{
|
||||
return CheckIsFormat(@"^(http|https|ftp)\://[a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,3}(:[a-zA-Z0-9]*)?/?([a-zA-Z0-9\-\._\?\,\'/\\\+&$%\$#\=~])*$", strValue);
|
||||
}
|
||||
|
||||
private static bool CheckIsFormat(string strRegex, string strValue)
|
||||
{
|
||||
if (strValue != null && strValue.Trim() != "")
|
||||
{
|
||||
Regex re = new Regex(strRegex);
|
||||
if (re.IsMatch(strValue))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public static bool IsIdCard(string idCardNumber)
|
||||
{
|
||||
if (string.IsNullOrEmpty(idCardNumber))
|
||||
return false;
|
||||
idCardNumber = idCardNumber.ToUpper();
|
||||
|
||||
if (!Regex.IsMatch(idCardNumber, @"^[1-9]\d{5}(18|19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[\dXx]$"))
|
||||
return false;
|
||||
|
||||
|
||||
string birthDateString = idCardNumber.Substring(6, 8);
|
||||
char genderCheckDigitChar = idCardNumber[16];
|
||||
char checkDigit = idCardNumber[17];
|
||||
|
||||
|
||||
|
||||
if (!DateTime.TryParseExact(birthDateString, "yyyyMMdd", CultureInfo.InvariantCulture, DateTimeStyles.None, out DateTime birthDate))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
DateTime today = DateTime.Now.AddYears(18);
|
||||
if (birthDate > today)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (today.Year - birthDate.Year > 80)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!char.IsDigit(genderCheckDigitChar))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Common.Utils
|
||||
{
|
||||
public static class ValidationHelper
|
||||
{
|
||||
private static readonly Regex MobileRegex = new(@"^1[3-9]\d{9}$", RegexOptions.Compiled);
|
||||
|
||||
private static readonly Regex IdCardRegex = new(@"^[1-9]\d{5}(19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[\dXx]$", RegexOptions.Compiled);
|
||||
|
||||
private static readonly Regex UserNameRegex = new(@"^[\u4e00-\u9fa5a-zA-Z0-9_]{2,20}$", RegexOptions.Compiled);
|
||||
|
||||
public static bool IsValidMobile(string mobile)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(mobile))
|
||||
return false;
|
||||
return MobileRegex.IsMatch(mobile);
|
||||
}
|
||||
|
||||
public static bool IsValidIdCard(string idCard)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(idCard) || idCard.Length != 18)
|
||||
return false;
|
||||
|
||||
if (!IdCardRegex.IsMatch(idCard))
|
||||
return false;
|
||||
|
||||
return ValidateIdCardCheckSum(idCard);
|
||||
}
|
||||
|
||||
public static bool IsValidUserName(string userName)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(userName))
|
||||
return false;
|
||||
return UserNameRegex.IsMatch(userName);
|
||||
}
|
||||
|
||||
public static bool IsPositiveAmount(long amount)
|
||||
{
|
||||
return amount > 0;
|
||||
}
|
||||
|
||||
public static bool IsPositiveAmount(int amount)
|
||||
{
|
||||
return amount > 0;
|
||||
}
|
||||
|
||||
public static bool IsInRange(int count, int min, int max)
|
||||
{
|
||||
return count >= min && count <= max;
|
||||
}
|
||||
|
||||
public static bool IsValidLength(string value, int minLength, int maxLength)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
return false;
|
||||
return value.Length >= minLength && value.Length <= maxLength;
|
||||
}
|
||||
|
||||
public static bool IsValidPassword(string password)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(password) || password.Length < 6 || password.Length > 20)
|
||||
return false;
|
||||
return password.Any(char.IsLetter) && password.Any(char.IsDigit);
|
||||
}
|
||||
|
||||
private static bool ValidateIdCardCheckSum(string idCard)
|
||||
{
|
||||
int[] weight = { 7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2 };
|
||||
char[] checkCodes = { '1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2' };
|
||||
|
||||
int sum = 0;
|
||||
for (int i = 0; i < 17; i++)
|
||||
{
|
||||
sum += (idCard[i] - '0') * weight[i];
|
||||
}
|
||||
|
||||
char expectedCheckChar = checkCodes[sum % 11];
|
||||
char actualCheckChar = char.ToUpper(idCard[17]);
|
||||
|
||||
return expectedCheckChar == actualCheckChar;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Common.WX.Response
|
||||
{
|
||||
public class AccessTokenResponse : WXBaseResponse
|
||||
{
|
||||
/// <summary>
|
||||
/// access_token
|
||||
/// </summary>
|
||||
public string access_token { get; set; }
|
||||
public int expires_in { get; set; }
|
||||
|
||||
public string refresh_token { get; set; }
|
||||
public string openid { get; set; }
|
||||
public string scope { get; set; }
|
||||
/// <summary>
|
||||
/// is_snapshotuser
|
||||
/// </summary>
|
||||
public int is_snapshotuser { get; set; }
|
||||
public string unionid { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Common.WX.Response
|
||||
{
|
||||
public class GZHUserInfoResponse : WXBaseResponse
|
||||
{
|
||||
public string openid { get; set; }
|
||||
public string nickname { get; set; }
|
||||
public int sex { get; set; }
|
||||
public string province { get; set; }
|
||||
public string city { get; set; }
|
||||
public string country { get; set; }
|
||||
public string headimgurl { get; set; }
|
||||
public string[] privilege { get; set; }
|
||||
public string unionid { get; set; }
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Common.WX.Response
|
||||
{
|
||||
public class TicketResponse: WXBaseResponse
|
||||
{
|
||||
/// <summary>
|
||||
/// ticket
|
||||
/// </summary>
|
||||
public string ticket { get; set; }
|
||||
public int expires_in { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Common.WX.Response
|
||||
{
|
||||
public class WXBaseResponse
|
||||
{
|
||||
public int errcode { get; set; } = 0;
|
||||
public string errmsg { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
using Common.Redis;
|
||||
using Common.Request;
|
||||
using Common.Utils;
|
||||
using Common.WX.Response;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using RedisHelper;
|
||||
|
||||
namespace Common.WX
|
||||
{
|
||||
public class WXBaseHelper
|
||||
{
|
||||
private readonly IRequestApi _requestApi;
|
||||
private readonly ICache _cache;
|
||||
private readonly IConfiguration _configuration;
|
||||
private ILogger<WXBaseHelper> _logger;
|
||||
private readonly string appid;
|
||||
private readonly string secret;
|
||||
private readonly string wx_url;
|
||||
|
||||
public WXBaseHelper(IRequestApi requestApi, ICache cache, IConfiguration configuration, ILogger<WXBaseHelper> logger)
|
||||
{
|
||||
_requestApi = requestApi;
|
||||
_cache = cache;
|
||||
_configuration = configuration;
|
||||
_logger = logger;
|
||||
appid = _configuration["WXConfig:AppId"];
|
||||
secret = _configuration["WXConfig:Secret"];
|
||||
wx_url = _configuration["WXConfig:Url"];
|
||||
}
|
||||
|
||||
|
||||
public async Task<(bool isok,string msg,string accessToken)> GetAccessToken()
|
||||
{
|
||||
var accessToken = _cache.Get<string>(RedisKey.WeiXin_AccessToken);
|
||||
if (accessToken != null && !string.IsNullOrWhiteSpace(accessToken))
|
||||
{
|
||||
return (true,"", accessToken);
|
||||
|
||||
}
|
||||
var param = new Dictionary<string, object>
|
||||
{
|
||||
{ "appid", appid },
|
||||
{ "secret", secret },
|
||||
{ "grant_type", "client_credential" }
|
||||
};
|
||||
var result = await _requestApi.GetAsync(wx_url, "/cgi-bin/token", param);
|
||||
var res = result.ToObject<AccessTokenResponse>();
|
||||
if (res.errcode == 0)
|
||||
{
|
||||
_cache.Insert(RedisKey.WeiXin_AccessToken, res.access_token, 3600);
|
||||
return (true, "", res.access_token);
|
||||
}
|
||||
else
|
||||
return (false, res.errcode+"-"+ res.errmsg,null);
|
||||
}
|
||||
|
||||
public async Task<(bool isok, string msg, AccessTokenResponse response)> GetAccessTokenByCode(string js_code)
|
||||
{
|
||||
var codeRedis = _cache.Get<AccessTokenResponse>(string.Format(RedisKey.WeiXinUser_AccessToken, js_code));
|
||||
|
||||
if (codeRedis != null)
|
||||
{
|
||||
return (true, "", codeRedis);
|
||||
|
||||
}
|
||||
var param = new Dictionary<string, object>
|
||||
{
|
||||
{ "appid", appid },
|
||||
{ "secret", secret },
|
||||
{ "js_code", js_code },
|
||||
{ "grant_type", "authorization_code" }
|
||||
};
|
||||
var result = await _requestApi.GetAsync(wx_url, "/sns/jscode2session", param);
|
||||
var res = result.ToObject<AccessTokenResponse>();
|
||||
if (res.errcode == 0)
|
||||
{
|
||||
_cache.Insert(string.Format(RedisKey.WeiXinUser_AccessToken, js_code), res, 6000);
|
||||
return (true, res.errmsg, res);
|
||||
}
|
||||
else
|
||||
return (false, res.errmsg, null);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<FrameworkReference Include="Microsoft.AspNetCore.App" />
|
||||
<PackageReference Include="Autofac" Version="8.3.0" />
|
||||
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\RedisService\RedisService.csproj" />
|
||||
<ProjectReference Include="..\Common\Common.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,39 @@
|
||||
using Common;
|
||||
using CommonInfrastructure.Response;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
|
||||
namespace CommonInfrastructure
|
||||
{
|
||||
public class BaseController : Controller
|
||||
{
|
||||
public LoginUser loginUser { get; set; }
|
||||
|
||||
public override void OnActionExecuting(ActionExecutingContext filterContext)
|
||||
{
|
||||
var context = filterContext.HttpContext;
|
||||
|
||||
loginUser = new LoginUser();
|
||||
var request = context.Request;
|
||||
try
|
||||
{
|
||||
loginUser.UserId = context.User.Claims.FirstOrDefault(e => e.Type == "uid")?.Value;
|
||||
loginUser.UserName = context.User.Claims.FirstOrDefault(e => e.Type == "uname")?.Value;
|
||||
loginUser.DisplayName = context.User.Claims.FirstOrDefault(e => e.Type == "dname")?.Value;
|
||||
loginUser.Icon = context.User.Claims.FirstOrDefault(e => e.Type == "avatar")?.Value;
|
||||
loginUser.Number = context.User.Claims.FirstOrDefault(e => e.Type == "number")?.Value;
|
||||
|
||||
loginUser.Ip = context.Request.Headers["X-Forwarded-For"].FirstOrDefault()?.Split(',').FirstOrDefault()?.Trim();
|
||||
|
||||
if (string.IsNullOrEmpty(loginUser.Ip))
|
||||
{
|
||||
loginUser.Ip = context.Connection.RemoteIpAddress?.ToString() ?? "";
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
filterContext.Result = new JsonResult(new BaseCommonResponse() { Code = CommonResponseCodeEnum.ParamError, Info = "请求参数异常"+ex.Message });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
using Common;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace CommonInfrastructure
|
||||
{
|
||||
public static class ControllerExtension
|
||||
{
|
||||
public static BaseResponse<T> ToFailResult<T>(
|
||||
this Controller controller,
|
||||
string info) => new BaseResponse<T>() { Info = info, Code = CommonResponseCodeEnum.ResultFail };
|
||||
|
||||
public static ListResponse<T> ToListFailResult<T>(
|
||||
this Controller controller,
|
||||
string info) => new ListResponse<T>()
|
||||
{ Info = info, Code = CommonResponseCodeEnum.ResultFail };
|
||||
|
||||
public static PageResponse<T> ToPageFailResult<T>(
|
||||
this Controller controller,
|
||||
string info) => new PageResponse<T>()
|
||||
{
|
||||
Code = CommonResponseCodeEnum.ResultFail,
|
||||
Info = info
|
||||
};
|
||||
|
||||
public static BaseResponse<T> ToResult<T>(
|
||||
this Controller controller,
|
||||
T obj, string info = null) => new BaseResponse<T>() { Data = obj, Info = info, Code = CommonResponseCodeEnum.Success };
|
||||
|
||||
public static ListResponse<T> ToListResult<T>(
|
||||
this Controller controller,
|
||||
IEnumerable<T> list, string info = null) => new ListResponse<T>()
|
||||
{
|
||||
Data = list,
|
||||
Code = CommonResponseCodeEnum.Success,
|
||||
Info = info
|
||||
};
|
||||
|
||||
public static PageResponse<T> ToPageResult<T>(
|
||||
this Controller controller,
|
||||
IEnumerable<T> list, int count = 0, string info = null) => new PageResponse<T>()
|
||||
{
|
||||
Code = CommonResponseCodeEnum.Success,
|
||||
Info = info,
|
||||
Count = count,
|
||||
Data = list
|
||||
};
|
||||
|
||||
public static BaseResponse<T> ToNoLoginResult<T>(
|
||||
this Controller controller) => new BaseResponse<T>() { Info = "未登录", Code = CommonResponseCodeEnum.NoLogin };
|
||||
|
||||
public static BaseResponse<T> ToTokenInvalidResult<T>(
|
||||
this Controller controller) => new BaseResponse<T>() { Info = "token失效", Code = CommonResponseCodeEnum.TokenInvalid };
|
||||
|
||||
public static BaseResponse<T> ToParamErrorResult<T>(
|
||||
this Controller controller) => new BaseResponse<T>() { Info = "请求参数错误", Code = CommonResponseCodeEnum.ParamError };
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
using Autofac;
|
||||
using MongoRepository;
|
||||
|
||||
namespace CommonInfrastructure
|
||||
{
|
||||
public class AutofacModule : Module
|
||||
{
|
||||
protected override void Load(ContainerBuilder builder)
|
||||
{
|
||||
builder.RegisterGeneric(typeof(MongoBaseRepository<>))
|
||||
.As(typeof(IMongoBaseRepository<>))
|
||||
.InstancePerLifetimeScope();
|
||||
|
||||
builder.RegisterType<RedisService.RedisService>()
|
||||
.As<RedisService.IRedisService>()
|
||||
.InstancePerLifetimeScope();
|
||||
}
|
||||
|
||||
public static void RegisterServices(ContainerBuilder builder, Type serviceAssemblyType)
|
||||
{
|
||||
builder.RegisterAssemblyTypes(serviceAssemblyType.Assembly)
|
||||
.Where(t => t.Name.EndsWith("Service"))
|
||||
.AsImplementedInterfaces()
|
||||
.InstancePerLifetimeScope();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace CommonInfrastructure.Response
|
||||
{
|
||||
public class LoginUser
|
||||
{
|
||||
public string? UserId{ get; set; }
|
||||
public string? UserName { get; set; }
|
||||
public string Number { get; set; }
|
||||
|
||||
|
||||
public string? DisplayName { get; set; }
|
||||
|
||||
public string? Icon { get; set; }
|
||||
|
||||
public string Source { get; set; }
|
||||
public string Ip { get; set; }
|
||||
/// <summary>
|
||||
/// token
|
||||
/// </summary>
|
||||
public string Token { get; set; }
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace JWTHelper
|
||||
{
|
||||
public class JWTConfig
|
||||
{
|
||||
|
||||
public string TokenKey { get; set; }
|
||||
|
||||
public bool ValidateIssuer { get; set; }
|
||||
public string Issuer { get; set; }
|
||||
|
||||
|
||||
public bool ValidateAudience { get; set; }
|
||||
public string Audience { get; set; }
|
||||
|
||||
public bool ValidateIssuerSigningKey { get; set; }
|
||||
public string SecretKey { get; set; }
|
||||
|
||||
|
||||
public bool ValidateLifetime { get; set; }
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using Newtonsoft.Json;
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
|
||||
namespace JWTHelper
|
||||
{
|
||||
public static class JWTExtension
|
||||
{
|
||||
public static IServiceCollection AddCustomJwt(this IServiceCollection services,
|
||||
IConfiguration configuration)
|
||||
{
|
||||
|
||||
services.AddScoped<JwtTools>();
|
||||
services.Configure<JWTConfig>(options => { configuration.Bind("JwtConfig", options); });
|
||||
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
|
||||
services.AddAuthentication(options =>
|
||||
{
|
||||
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
|
||||
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
|
||||
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
|
||||
}).AddJwtBearer(options =>
|
||||
{
|
||||
var jwtSettings = new JWTConfig();
|
||||
configuration.Bind("JwtConfig", jwtSettings);
|
||||
options.Events = new JwtBearerEvents()
|
||||
{
|
||||
OnMessageReceived = context =>
|
||||
{
|
||||
var accessToken = context.Request.Query["access_token"];
|
||||
var path = context.HttpContext.Request.Path;
|
||||
if (!string.IsNullOrEmpty(accessToken) && (path.StartsWithSegments("/hubs/") || path.StartsWithSegments("/fishinghub") || path.StartsWithSegments("/fishingroomhub")))
|
||||
{
|
||||
context.Token = accessToken;
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
context.Token = context.HttpContext.Request.Headers[jwtSettings.TokenKey].ToString();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
context.Token = "";
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(context.Token))
|
||||
{
|
||||
// var key = JwtTools.GetTokenContent(context.Token, "key");
|
||||
var userId =JwtTools.GetTokenReverseContent(context.Token, "uid");
|
||||
if (string.IsNullOrEmpty(userId))
|
||||
{
|
||||
context.Token = "";
|
||||
}
|
||||
//if (string.IsNullOrEmpty(key) || string.IsNullOrEmpty(userId))
|
||||
//{
|
||||
// context.Token = "";
|
||||
//}
|
||||
//else
|
||||
//{
|
||||
// if (Md5Helper.Encode(userId)[..8] != key)
|
||||
// {
|
||||
// context.Token = "";
|
||||
// }
|
||||
|
||||
//}
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
},
|
||||
OnAuthenticationFailed = context =>
|
||||
{
|
||||
string s = context.Exception.Message;
|
||||
return Task.CompletedTask;
|
||||
},
|
||||
OnChallenge = context =>
|
||||
{
|
||||
context.HandleResponse();
|
||||
var payload = JsonConvert.SerializeObject(new { code = 401, msg = "fail",info= "很抱歉,您无权访问该接口" });
|
||||
context.Response.ContentType = "application/json";
|
||||
context.Response.StatusCode = StatusCodes.Status200OK;
|
||||
context.Response.WriteAsync(payload);
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
};
|
||||
var tokenValidationParameters = new TokenValidationParameters();
|
||||
|
||||
tokenValidationParameters.ValidateAudience = jwtSettings.ValidateAudience;
|
||||
tokenValidationParameters.ValidAudience = jwtSettings.Audience;
|
||||
|
||||
tokenValidationParameters.ValidateIssuer = jwtSettings.ValidateIssuer;
|
||||
tokenValidationParameters.ValidIssuer = jwtSettings.Issuer;
|
||||
|
||||
tokenValidationParameters.ValidateIssuerSigningKey = jwtSettings.ValidateIssuerSigningKey;
|
||||
tokenValidationParameters.IssuerSigningKey =
|
||||
new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSettings.SecretKey));
|
||||
|
||||
tokenValidationParameters.ValidateLifetime = jwtSettings.ValidateLifetime;
|
||||
options.TokenValidationParameters = tokenValidationParameters;
|
||||
});
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
|
||||
public static IServiceCollection AddJwt(this IServiceCollection services,
|
||||
IConfiguration configuration)
|
||||
{
|
||||
|
||||
services.AddScoped<JwtTools>();
|
||||
services.Configure<JWTConfig>(options => { configuration.Bind("JwtConfig", options); });
|
||||
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
|
||||
services.AddAuthentication(options =>
|
||||
{
|
||||
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
|
||||
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
|
||||
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
|
||||
}).AddJwtBearer(options =>
|
||||
{
|
||||
var jwtSettings = new JWTConfig();
|
||||
configuration.Bind("JwtConfig", jwtSettings);
|
||||
options.Events = new JwtBearerEvents()
|
||||
{
|
||||
OnMessageReceived = context =>
|
||||
{
|
||||
var accessToken = context.Request.Query["access_token"];
|
||||
var path = context.HttpContext.Request.Path;
|
||||
if (!string.IsNullOrEmpty(accessToken) && (path.StartsWithSegments("/hubs/") || path.StartsWithSegments("/fishinghub") || path.StartsWithSegments("/fishingroomhub")))
|
||||
{
|
||||
context.Token = accessToken;
|
||||
}
|
||||
else
|
||||
{
|
||||
context.Token = context.HttpContext.Request.Headers[jwtSettings.TokenKey].ToString();
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
},
|
||||
OnAuthenticationFailed = context =>
|
||||
{
|
||||
string s = context.Exception.Message;
|
||||
return Task.CompletedTask;
|
||||
},
|
||||
OnChallenge = context =>
|
||||
{
|
||||
context.HandleResponse();
|
||||
var payload = JsonConvert.SerializeObject(new { code = 401, msg = "fail",info= "很抱歉,您无权访问该接口" });
|
||||
context.Response.ContentType = "application/json";
|
||||
context.Response.StatusCode = StatusCodes.Status200OK;
|
||||
context.Response.WriteAsync(payload);
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
};
|
||||
var tokenValidationParameters = new TokenValidationParameters();
|
||||
|
||||
tokenValidationParameters.ValidateAudience = jwtSettings.ValidateAudience;
|
||||
tokenValidationParameters.ValidAudience = jwtSettings.Audience;
|
||||
|
||||
tokenValidationParameters.ValidateIssuer = jwtSettings.ValidateIssuer;
|
||||
tokenValidationParameters.ValidIssuer = jwtSettings.Issuer;
|
||||
|
||||
tokenValidationParameters.ValidateIssuerSigningKey = jwtSettings.ValidateIssuerSigningKey;
|
||||
tokenValidationParameters.IssuerSigningKey =
|
||||
new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSettings.SecretKey));
|
||||
|
||||
tokenValidationParameters.ValidateLifetime = jwtSettings.ValidateLifetime;
|
||||
options.TokenValidationParameters = tokenValidationParameters;
|
||||
});
|
||||
return services;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.15" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="9.0.7" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,125 @@
|
||||
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Linq;
|
||||
using System.Security.Claims;
|
||||
using System.Text;
|
||||
|
||||
namespace JWTHelper
|
||||
{
|
||||
public class JwtTools
|
||||
{
|
||||
private JWTConfig config;
|
||||
|
||||
public JwtTools(IOptions<JWTConfig> opts)
|
||||
{
|
||||
config = opts.Value;
|
||||
}
|
||||
public bool ValidateToken(string token)
|
||||
{
|
||||
try
|
||||
{
|
||||
SymmetricSecurityKey key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(config.SecretKey));
|
||||
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
|
||||
|
||||
var tokenValidationParameters = new TokenValidationParameters();
|
||||
|
||||
tokenValidationParameters.ValidateAudience = config.ValidateAudience;
|
||||
tokenValidationParameters.ValidAudience = config.Audience;
|
||||
|
||||
tokenValidationParameters.ValidateIssuer = config.ValidateIssuer;
|
||||
tokenValidationParameters.ValidIssuer = config.Issuer;
|
||||
|
||||
tokenValidationParameters.ValidateIssuerSigningKey = config.ValidateIssuerSigningKey;
|
||||
tokenValidationParameters.IssuerSigningKey =
|
||||
new SymmetricSecurityKey(Encoding.UTF8.GetBytes(config.SecretKey));
|
||||
|
||||
tokenValidationParameters.ValidateLifetime = config.ValidateLifetime;
|
||||
SecurityToken securityToken = null;
|
||||
var identiy = new JwtSecurityTokenHandler().ValidateToken(token, tokenValidationParameters, out securityToken);
|
||||
return identiy.Claims.Count(n => n.Type == "uid" || n.Type == "uname") >= 2;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
public string CreateToken(Claim[] claims)
|
||||
{
|
||||
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(config.SecretKey));
|
||||
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
|
||||
var token = new JwtSecurityToken(
|
||||
issuer: config.Issuer,
|
||||
// audience: jwtconfig.Audience,
|
||||
claims: claims,
|
||||
expires: DateTime.Now.AddMinutes(60),
|
||||
signingCredentials: creds
|
||||
);
|
||||
|
||||
return new JwtSecurityTokenHandler().WriteToken(token);
|
||||
}
|
||||
|
||||
|
||||
public static string GetTokenContent(string token, string key)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(token))
|
||||
{
|
||||
try
|
||||
{
|
||||
var securityTokenHandler = new JwtSecurityTokenHandler();
|
||||
|
||||
var claims = securityTokenHandler.ReadJwtToken(token).Payload;
|
||||
|
||||
return claims.Claims.FirstOrDefault(n => n.Type == key)?.Value ?? "";
|
||||
}
|
||||
catch
|
||||
{
|
||||
return "";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetTokenReverseContent(string token, string key)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(token))
|
||||
{
|
||||
try
|
||||
{
|
||||
var securityTokenHandler = new JwtSecurityTokenHandler();
|
||||
|
||||
var claims = securityTokenHandler.ReadJwtToken(token).Payload;
|
||||
|
||||
return new string((claims.Claims.FirstOrDefault(n => n.Type == key)?.Value ?? "").ToCharArray()
|
||||
.AsEnumerable().Reverse().ToArray());
|
||||
}
|
||||
catch
|
||||
{
|
||||
return "";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<Claim> GetTokenClaim(string token)
|
||||
{
|
||||
var securityTokenHandler = new JwtSecurityTokenHandler();
|
||||
|
||||
var claims = securityTokenHandler.ReadJwtToken(token).Payload;
|
||||
|
||||
return claims.Claims;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace JWTHelper
|
||||
{
|
||||
sealed class Md5Helper
|
||||
{
|
||||
public static string Encode(string ConvertString)
|
||||
{
|
||||
MD5CryptoServiceProvider md5 = new MD5CryptoServiceProvider();
|
||||
string t2 = BitConverter.ToString(md5.ComputeHash(UTF8Encoding.Default.GetBytes(ConvertString)), 4, 8);
|
||||
t2 = t2.Replace("-", "");
|
||||
return t2;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MongoRepository.Attributes
|
||||
{
|
||||
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
|
||||
public class BsonCollectionAttribute : Attribute
|
||||
{
|
||||
public string CollectionName { get; }
|
||||
public string DataBaseName { get; }
|
||||
|
||||
public BsonCollectionAttribute(string collectionName, string dataBaseName)
|
||||
{
|
||||
CollectionName = collectionName;
|
||||
DataBaseName = dataBaseName;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
using System;
|
||||
|
||||
namespace MongoRepository.Config
|
||||
{
|
||||
public interface IMongoDbSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// ServerUrl
|
||||
/// </summary>
|
||||
public string ServerUrl { get; set; }
|
||||
|
||||
public string Username { get; set; }
|
||||
|
||||
public string Password { get; set; }
|
||||
|
||||
public string Database { get; set; }
|
||||
}
|
||||
|
||||
public class MongoDbSettings : IMongoDbSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// ServerUrl
|
||||
/// </summary>
|
||||
public string ServerUrl { get; set; }
|
||||
|
||||
public string Username { get; set; }
|
||||
|
||||
public string Password { get; set; }
|
||||
|
||||
public string Database { get; set; } = "data_0524";
|
||||
}
|
||||
|
||||
public static class ServiceDatabases
|
||||
{
|
||||
public const string User = "data_0524_user";
|
||||
|
||||
public const string Admin = "data_0524_admin";
|
||||
|
||||
public const string Fish = "data_0524_fish";
|
||||
|
||||
public const string Chess = "data_0524_chess";
|
||||
|
||||
public const string Customer = "data_0524_customer";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
using MongoDB.Bson;
|
||||
using MongoDB.Driver;
|
||||
|
||||
namespace MongoRepository
|
||||
{
|
||||
public interface IMongoBaseRepository<TDocument> where TDocument : MongoBaseEntity
|
||||
{
|
||||
IQueryable<TDocument> AsQueryable();
|
||||
|
||||
IEnumerable<TDocument> FilterBy(Expression<Func<TDocument, bool>> filterExpression);
|
||||
|
||||
Task<List<TDocument>> FilterByAsync(
|
||||
Expression<Func<TDocument, bool>> filterExpression);
|
||||
Task<List<TDocument>> FilterByAsync(
|
||||
FilterDefinition<TDocument> filterExpression);
|
||||
IEnumerable<TDocument> FilterBy(FilterDefinition<TDocument> filterExpression);
|
||||
Task<long> CountAsync(
|
||||
Expression<Func<TDocument, bool>> filterExpression);
|
||||
|
||||
long Count(
|
||||
Expression<Func<TDocument, bool>> filterExpression);
|
||||
Task<long> CountAsync(
|
||||
FilterDefinition<TDocument> filterExpression);
|
||||
long Count(FilterDefinition<TDocument> filterExpression);
|
||||
|
||||
IEnumerable<TProjected> FilterBy<TProjected>(
|
||||
Expression<Func<TDocument, bool>> filterExpression,
|
||||
Expression<Func<TDocument, TProjected>> projectionExpression);
|
||||
|
||||
TDocument FindOne(Expression<Func<TDocument, bool>> filterExpression);
|
||||
|
||||
Task<TDocument> FindOneAsync(Expression<Func<TDocument, bool>> filterExpression);
|
||||
TDocument FindOne(FilterDefinition<TDocument> filterExpression);
|
||||
|
||||
Task<TDocument> FindOneAsync(FilterDefinition<TDocument> filterExpression);
|
||||
TDocument FindOne(FilterDefinition<TDocument> filterExpression, Expression<Func<TDocument, object>> orderExpression);
|
||||
|
||||
|
||||
Task<TDocument> FindOneAsync(FilterDefinition<TDocument> filterExpression, Expression<Func<TDocument, object>> orderExpression);
|
||||
|
||||
|
||||
TDocument FindOne(Expression<Func<TDocument, bool>> filterExpression, Expression<Func<TDocument, object>> orderExpression);
|
||||
|
||||
|
||||
Task<TDocument> FindOneAsync(Expression<Func<TDocument, bool>> filterExpression, Expression<Func<TDocument, object>> orderExpression);
|
||||
TDocument FindById(string id);
|
||||
|
||||
Task<TDocument> FindByIdAsync(string id);
|
||||
|
||||
(long count, List<TDocument> list) QueryPage(Expression<Func<TDocument, bool>> filterExpression, Expression<Func<TDocument, object>> orderExpression, bool desc, int pageSize, int pageIndex);
|
||||
Task<(long count, List<TDocument> list)> QueryPageAsync(Expression<Func<TDocument, bool>> filterExpression, int pageSize, int pageIndex);
|
||||
Task<(long count, List<TDocument> list)> QueryPageAsync(Expression<Func<TDocument, bool>> filterExpression, Expression<Func<TDocument, object>> orderExpression, bool desc, int pageSize, int pageIndex);
|
||||
|
||||
|
||||
Task<(long count, List<TDocument> list)> QueryPageAsync(Expression<Func<TDocument, bool>> filterExpression, Expression<Func<TDocument, object>> orderExpression, bool desc, int pageSize, int pageIndex, int skipIndex);
|
||||
|
||||
|
||||
Task<(long count, List<TDocument> list)> QueryPageAsync(FilterDefinition<TDocument> filterExpression, SortDefinition<TDocument> orderExpression, bool desc, int pageSize, int pageIndex);
|
||||
Task<(long count, List<TDocument> list)> QueryPageAsync(FilterDefinition<TDocument> filterExpression, SortDefinition<TDocument> orderExpression, bool desc, int pageSize, int pageIndex, int skipIndex);
|
||||
|
||||
|
||||
|
||||
(bool ok, string message) InsertOne(TDocument document);
|
||||
|
||||
Task<(bool ok, string message)> InsertOneAsync(TDocument document);
|
||||
|
||||
(bool ok, string message) InsertMany(ICollection<TDocument> documents);
|
||||
|
||||
Task<(bool ok, string message)> InsertManyAsync(ICollection<TDocument> documents);
|
||||
|
||||
(bool ok, string message) UpdateById(string id, UpdateDefinition<TDocument> updateDefinition);
|
||||
|
||||
Task<(bool ok, string message)> UpdateByIdAsync(string id, UpdateDefinition<TDocument> updateDefinition);
|
||||
|
||||
(bool ok, string message) UpdateMany(Expression<Func<TDocument, bool>> filterExpression, UpdateDefinition<TDocument> updateDefinition);
|
||||
|
||||
Task<(bool ok, string message)> UpdateManyAsync(Expression<Func<TDocument, bool>> filterExpression, UpdateDefinition<TDocument> updateDefinition);
|
||||
|
||||
(bool ok, string message) UpdateMany(FilterDefinition<TDocument> filterExpression, UpdateDefinition<TDocument> updateDefinition);
|
||||
Task<(bool ok, string message)> UpdateManyAsync(FilterDefinition<TDocument> filterExpression, UpdateDefinition<TDocument> updateDefinition);
|
||||
|
||||
|
||||
(bool ok, string message) ReplaceOne(TDocument document);
|
||||
|
||||
Task<(bool ok, string message)> ReplaceOneAsync(TDocument document);
|
||||
|
||||
(bool ok, string message) DeleteOne(Expression<Func<TDocument, bool>> filterExpression);
|
||||
|
||||
Task<(bool ok, string message)> DeleteOneAsync(Expression<Func<TDocument, bool>> filterExpression);
|
||||
|
||||
(bool ok, string message) DeleteById(string id);
|
||||
|
||||
Task<(bool ok, string message)> DeleteByIdAsync(string id);
|
||||
|
||||
(bool ok, string message) DeleteMany(Expression<Func<TDocument, bool>> filterExpression);
|
||||
|
||||
Task<(bool ok, string message)> DeleteManyAsync(Expression<Func<TDocument, bool>> filterExpression);
|
||||
|
||||
Task<List<BsonDocument>> Aggregate(FilterDefinition<TDocument> filterExpression, ProjectionDefinition<TDocument, BsonDocument> group);
|
||||
|
||||
|
||||
|
||||
Task<List<TDocument>> RandomData(Expression<Func<TDocument, bool>> filterExpression, int count);
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Common.Utils;
|
||||
using MongoDB.Bson.Serialization.Attributes;
|
||||
|
||||
namespace MongoRepository
|
||||
{
|
||||
public class MongoBaseEntity
|
||||
{
|
||||
[BsonElement("_id")]
|
||||
public string Id { get; set; } = Guid.NewGuid().ToString("N");
|
||||
|
||||
[BsonElement("isdelete")]
|
||||
public bool IsDelete { get; set; } = false;
|
||||
[Column(TypeName = "jsonb")]
|
||||
public OptionUserInfo? CreateInfo { get; set; }
|
||||
|
||||
|
||||
}
|
||||
[BsonIgnoreExtraElements]
|
||||
public class OptionUserInfo
|
||||
{
|
||||
public OptionUserInfo(string userId, string userName)
|
||||
{
|
||||
UserId = userId;
|
||||
UserName = userName;
|
||||
OptionTime = DateTime.Now.ToTimeStamp();
|
||||
}
|
||||
public OptionUserInfo()
|
||||
{
|
||||
}
|
||||
|
||||
public string UserId { get; set; }
|
||||
public string UserName { get; set; }
|
||||
public long OptionTime { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,702 @@
|
||||
using Common;
|
||||
using MongoDB.Bson;
|
||||
using MongoDB.Driver;
|
||||
using MongoDB.Driver.Linq;
|
||||
using MongoRepository.Attributes;
|
||||
using MongoRepository.Config;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace MongoRepository
|
||||
{
|
||||
public class MongoBaseRepository<TDocument> : IMongoBaseRepository<TDocument> where TDocument : MongoBaseEntity
|
||||
{
|
||||
public readonly IMongoCollection<TDocument> _collection;
|
||||
private readonly ILogger<MongoBaseRepository<TDocument>> _logger;
|
||||
private static readonly HashSet<string> _indexedCollections = new();
|
||||
|
||||
public MongoBaseRepository(IMongoDbSettings settings, ILogger<MongoBaseRepository<TDocument>> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
var mongoUrl = new MongoUrlBuilder($"{settings.ServerUrl.TrimEnd('/')}")
|
||||
{
|
||||
Username = settings.Username,
|
||||
Password = settings.Password,
|
||||
DatabaseName = GetDataBaseName(typeof(TDocument))
|
||||
}.ToMongoUrl();
|
||||
|
||||
var client = new MongoClient(mongoUrl);
|
||||
|
||||
EnsureDatabaseAndUser(client, settings, GetDataBaseName(typeof(TDocument)));
|
||||
|
||||
var database = client.GetDatabase(GetDataBaseName(typeof(TDocument)));
|
||||
_collection = database.GetCollection<TDocument>(GetCollectionName(typeof(TDocument)));
|
||||
|
||||
EnsureIndexes(database);
|
||||
}
|
||||
|
||||
private void EnsureIndexes(IMongoDatabase database)
|
||||
{
|
||||
var collectionName = GetCollectionName(typeof(TDocument));
|
||||
var key = $"{database.DatabaseNamespace.DatabaseName}:{collectionName}";
|
||||
|
||||
if (!_indexedCollections.Add(key))
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
if (collectionName == "user")
|
||||
{
|
||||
var bsonCollection = database.GetCollection<BsonDocument>(collectionName);
|
||||
var mobileIndex = new CreateIndexModel<BsonDocument>(
|
||||
Builders<BsonDocument>.IndexKeys.Ascending("BaseInfo.Mobile"),
|
||||
new CreateIndexOptions { Unique = true, Sparse = true, Name = "idx_baseinfo_mobile_unique" }
|
||||
);
|
||||
var idCardIndex = new CreateIndexModel<BsonDocument>(
|
||||
Builders<BsonDocument>.IndexKeys.Ascending("RealName.IDCard"),
|
||||
new CreateIndexOptions { Unique = true, Sparse = true, Name = "idx_realname_idcard_unique" }
|
||||
);
|
||||
bsonCollection.Indexes.CreateMany(new[] { mobileIndex, idCardIndex });
|
||||
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "MongoDB 索引创建异常(可能已存在): collection={Collection}", collectionName);
|
||||
}
|
||||
}
|
||||
|
||||
private void EnsureDatabaseAndUser(MongoClient client, IMongoDbSettings settings, string databaseName)
|
||||
{
|
||||
try
|
||||
{
|
||||
var databaseNames = client.ListDatabaseNames().ToList();
|
||||
|
||||
if (!databaseNames.Contains(databaseName))
|
||||
{
|
||||
var tempDatabase = client.GetDatabase(databaseName);
|
||||
var tempCollection = tempDatabase.GetCollection<BsonDocument>("temp_collection");
|
||||
|
||||
var tempDoc = new BsonDocument("temp", "init");
|
||||
tempCollection.InsertOne(tempDoc);
|
||||
tempCollection.DeleteOne(new BsonDocument("temp", "init"));
|
||||
}
|
||||
}
|
||||
catch (Exception ex) when (ex.GetType().Name == "MongoAuthenticationException")
|
||||
{
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "检查MongoDB数据库失败 database={DatabaseName}", databaseName);
|
||||
}
|
||||
}
|
||||
|
||||
private protected string GetCollectionName(Type documentType)
|
||||
{
|
||||
var collectionName = ((BsonCollectionAttribute)documentType.GetCustomAttributes(
|
||||
typeof(BsonCollectionAttribute),
|
||||
true)
|
||||
.FirstOrDefault())?.CollectionName;
|
||||
var formatStr = Tools.RegexSubstring(collectionName, "{", "}");
|
||||
if (!string.IsNullOrEmpty(collectionName) && !string.IsNullOrEmpty(formatStr))
|
||||
{
|
||||
collectionName = collectionName.Replace("{" + formatStr + "}", DateTime.Now.ToString(formatStr));
|
||||
}
|
||||
return collectionName;
|
||||
}
|
||||
|
||||
private protected string GetDataBaseName(Type documentType)
|
||||
{
|
||||
return ((BsonCollectionAttribute)documentType.GetCustomAttributes(
|
||||
typeof(BsonCollectionAttribute),
|
||||
true)
|
||||
.FirstOrDefault())?.DataBaseName;
|
||||
}
|
||||
public virtual IQueryable<TDocument> AsQueryable()
|
||||
{
|
||||
return _collection.AsQueryable();
|
||||
}
|
||||
|
||||
public virtual IMongoCollection<TDocument> GetCollection()
|
||||
{
|
||||
return _collection;
|
||||
}
|
||||
|
||||
|
||||
public virtual IEnumerable<TDocument> FilterBy(
|
||||
Expression<Func<TDocument, bool>> filterExpression)
|
||||
{
|
||||
return _collection.Find(filterExpression).ToEnumerable();
|
||||
}
|
||||
public virtual async Task<List<TDocument>> FilterByAsync(
|
||||
Expression<Func<TDocument, bool>> filterExpression)
|
||||
{
|
||||
return await _collection.Find(filterExpression).ToListAsync();
|
||||
}
|
||||
public virtual async Task<List<TDocument>> FilterByAsync(
|
||||
FilterDefinition<TDocument> filterExpression)
|
||||
{
|
||||
return await _collection.Find(filterExpression).ToListAsync();
|
||||
}
|
||||
public virtual IEnumerable<TDocument> FilterBy(
|
||||
FilterDefinition<TDocument> filterExpression)
|
||||
{
|
||||
return _collection.Find(filterExpression).ToEnumerable();
|
||||
}
|
||||
|
||||
public virtual IEnumerable<TProjected> FilterBy<TProjected>(
|
||||
Expression<Func<TDocument, bool>> filterExpression,
|
||||
Expression<Func<TDocument, TProjected>> projectionExpression)
|
||||
{
|
||||
return _collection.Find(filterExpression).Project(projectionExpression).ToEnumerable();
|
||||
}
|
||||
|
||||
public virtual TDocument FindOne(FilterDefinition<TDocument> filterExpression)
|
||||
{
|
||||
return _collection.Find(filterExpression).FirstOrDefault();
|
||||
}
|
||||
|
||||
|
||||
public virtual Task<TDocument> FindOneAsync(FilterDefinition<TDocument> filterExpression)
|
||||
{
|
||||
return Task.Run(() => _collection.Find(filterExpression).FirstOrDefaultAsync());
|
||||
}
|
||||
|
||||
|
||||
public virtual TDocument FindOne(Expression<Func<TDocument, bool>> filterExpression)
|
||||
{
|
||||
return _collection.Find(filterExpression).FirstOrDefault();
|
||||
}
|
||||
|
||||
|
||||
public virtual Task<TDocument> FindOneAsync(Expression<Func<TDocument, bool>> filterExpression)
|
||||
{
|
||||
return Task.Run(() => _collection.Find(filterExpression).FirstOrDefaultAsync());
|
||||
}
|
||||
|
||||
|
||||
public virtual TDocument FindOne(FilterDefinition<TDocument> filterExpression, Expression<Func<TDocument, object>> orderExpression)
|
||||
{
|
||||
return _collection.Find(filterExpression).SortByDescending(orderExpression).FirstOrDefault();
|
||||
}
|
||||
|
||||
|
||||
public virtual Task<TDocument> FindOneAsync(FilterDefinition<TDocument> filterExpression, Expression<Func<TDocument, object>> orderExpression)
|
||||
{
|
||||
return Task.Run(() => _collection.Find(filterExpression).SortByDescending(orderExpression).FirstOrDefaultAsync());
|
||||
}
|
||||
|
||||
|
||||
public virtual TDocument FindOne(Expression<Func<TDocument, bool>> filterExpression, Expression<Func<TDocument, object>> orderExpression)
|
||||
{
|
||||
return _collection.Find(filterExpression).SortByDescending(orderExpression).FirstOrDefault();
|
||||
}
|
||||
|
||||
|
||||
public virtual Task<TDocument> FindOneAsync(Expression<Func<TDocument, bool>> filterExpression, Expression<Func<TDocument, object>> orderExpression)
|
||||
{
|
||||
return Task.Run(() => _collection.Find(filterExpression).SortByDescending(orderExpression).FirstOrDefaultAsync());
|
||||
}
|
||||
|
||||
public virtual TDocument FindById(string id)
|
||||
{
|
||||
var filter = Builders<TDocument>.Filter.Eq(doc => doc.Id, id);
|
||||
return _collection.Find(filter).SingleOrDefault();
|
||||
}
|
||||
|
||||
public virtual Task<TDocument> FindByIdAsync(string id)
|
||||
{
|
||||
return Task.Run(() =>
|
||||
{
|
||||
var filter = Builders<TDocument>.Filter.Eq(doc => doc.Id, id);
|
||||
return _collection.Find(filter).SingleOrDefaultAsync();
|
||||
});
|
||||
}
|
||||
public virtual (long count, List<TDocument> list) QueryPage(Expression<Func<TDocument, bool>> filterExpression, Expression<Func<TDocument, object>> orderExpression, bool desc, int pageSize, int pageIndex)
|
||||
{
|
||||
pageSize = pageSize <= 0 ? 10 : pageSize;
|
||||
pageIndex = pageIndex <= 0 ? 1 : pageIndex;
|
||||
var count = _collection.Find(filterExpression).CountDocuments();
|
||||
if (count == 0)
|
||||
{
|
||||
return (0, new List<TDocument>());
|
||||
}
|
||||
var query = _collection.Find(filterExpression);
|
||||
if (desc)
|
||||
{
|
||||
query = query.SortByDescending(orderExpression);
|
||||
}
|
||||
else
|
||||
{
|
||||
query = query.SortBy(orderExpression);
|
||||
}
|
||||
var list = query.Skip((pageIndex - 1) * pageSize).Limit(pageSize).ToList();
|
||||
return (count, list);
|
||||
}
|
||||
public virtual async Task<(long count, List<TDocument> list)> QueryPageAsync(Expression<Func<TDocument, bool>> filterExpression, int pageSize, int pageIndex)
|
||||
{
|
||||
try
|
||||
{
|
||||
pageSize = pageSize <= 0 ? 10 : pageSize;
|
||||
pageIndex = pageIndex <= 0 ? 1 : pageIndex;
|
||||
var count = await _collection.Find(filterExpression).CountDocumentsAsync();
|
||||
if (count == 0)
|
||||
{
|
||||
return (0, new List<TDocument>());
|
||||
}
|
||||
|
||||
var query = _collection.Find(filterExpression);
|
||||
var list = await query.Skip((pageIndex - 1) * pageSize).Limit(pageSize).ToListAsync();
|
||||
return (count, list);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
public virtual async Task<(long count, List<TDocument> list)> QueryPageAsync(Expression<Func<TDocument, bool>> filterExpression,
|
||||
Expression<Func<TDocument, object>> orderExpression, bool desc, int pageSize, int pageIndex)
|
||||
{
|
||||
try
|
||||
{
|
||||
pageSize = pageSize <= 0 ? 10 : pageSize;
|
||||
pageIndex = pageIndex <= 0 ? 1 : pageIndex;
|
||||
var count = await _collection.Find(filterExpression).CountDocumentsAsync();
|
||||
if (count == 0)
|
||||
{
|
||||
return (0, new List<TDocument>());
|
||||
}
|
||||
|
||||
var query = _collection.Find(filterExpression);
|
||||
if (desc)
|
||||
{
|
||||
query = query.SortByDescending(orderExpression);
|
||||
}
|
||||
else
|
||||
{
|
||||
query = query.SortBy(orderExpression);
|
||||
}
|
||||
|
||||
var list = await query.Skip((pageIndex - 1) * pageSize).Limit(pageSize).ToListAsync();
|
||||
return (count, list);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual async Task<(long count, List<TDocument> list)> QueryPageAsync(Expression<Func<TDocument, bool>> filterExpression,
|
||||
Expression<Func<TDocument, object>> orderExpression, bool desc, int pageSize, int pageIndex, int skipCount)
|
||||
{
|
||||
try
|
||||
{
|
||||
pageSize = pageSize <= 0 ? 10 : pageSize;
|
||||
pageIndex = pageIndex <= 0 ? 1 : pageIndex;
|
||||
var count = await _collection.Find(filterExpression).CountDocumentsAsync();
|
||||
if (count == 0)
|
||||
{
|
||||
return (0, new List<TDocument>());
|
||||
}
|
||||
|
||||
var query = _collection.Find(filterExpression);
|
||||
if (desc)
|
||||
{
|
||||
query = query.SortByDescending(orderExpression);
|
||||
}
|
||||
else
|
||||
{
|
||||
query = query.SortBy(orderExpression);
|
||||
}
|
||||
|
||||
var list = await query.Skip((pageIndex - 1) * pageSize + skipCount).Limit(pageSize).ToListAsync();
|
||||
return (count - skipCount, list);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual async Task<(long count, List<TDocument> list)> QueryPageTwoOrderAsync(Expression<Func<TDocument, bool>> filterExpression,
|
||||
Expression<Func<TDocument, object>> orderExpression, Expression<Func<TDocument, object>> thenorderExpression, bool desc, int pageSize, int pageIndex)
|
||||
{
|
||||
try
|
||||
{
|
||||
pageSize = pageSize <= 0 ? 10 : pageSize;
|
||||
pageIndex = pageIndex <= 0 ? 1 : pageIndex;
|
||||
var count = await _collection.Find(filterExpression).CountDocumentsAsync();
|
||||
if (count == 0)
|
||||
{
|
||||
return (0, new List<TDocument>());
|
||||
}
|
||||
|
||||
var query = _collection.Find(filterExpression);
|
||||
if (desc)
|
||||
{
|
||||
query = query.SortByDescending(orderExpression).ThenByDescending(thenorderExpression);
|
||||
}
|
||||
else
|
||||
{
|
||||
query = query.SortBy(orderExpression).ThenByDescending(thenorderExpression);
|
||||
}
|
||||
|
||||
var list = await query.Skip((pageIndex - 1) * pageSize ).Limit(pageSize).ToListAsync();
|
||||
return (count , list);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
public virtual async Task<(long count, List<TDocument> list)> QueryPageAsync(
|
||||
FilterDefinition<TDocument> filterExpression, SortDefinition<TDocument> orderExpression, bool desc,
|
||||
int pageSize, int pageIndex)
|
||||
{
|
||||
try
|
||||
{
|
||||
pageSize = pageSize <= 0 ? 10 : pageSize;
|
||||
pageIndex = pageIndex <= 0 ? 1 : pageIndex;
|
||||
var count = await _collection.Find(filterExpression).CountDocumentsAsync();
|
||||
if (count == 0)
|
||||
{
|
||||
return (0, new List<TDocument>());
|
||||
}
|
||||
|
||||
var query = _collection.Find(filterExpression).Sort(orderExpression);
|
||||
|
||||
|
||||
var list = await query.Skip((pageIndex - 1) * pageSize).Limit(pageSize).ToListAsync();
|
||||
return (count, list);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public virtual async Task<(long count, List<TDocument> list)> QueryPageAsync(
|
||||
FilterDefinition<TDocument> filterExpression, SortDefinition<TDocument> orderExpression, bool desc,
|
||||
int pageSize, int pageIndex, int skipIndex)
|
||||
{
|
||||
try
|
||||
{
|
||||
pageSize = pageSize <= 0 ? 10 : pageSize;
|
||||
pageIndex = pageIndex <= 0 ? 1 : pageIndex;
|
||||
var count = await _collection.Find(filterExpression).CountDocumentsAsync();
|
||||
if (count == 0)
|
||||
{
|
||||
return (0, new List<TDocument>());
|
||||
}
|
||||
|
||||
var query = _collection.Find(filterExpression).Sort(orderExpression);
|
||||
|
||||
|
||||
var list = await query.Skip((pageIndex - 1) * pageSize + skipIndex).Limit(pageSize).ToListAsync();
|
||||
return (count - skipIndex, list);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public virtual (bool ok, string message) InsertOne(TDocument document)
|
||||
{
|
||||
try
|
||||
{
|
||||
_collection.InsertOne(document);
|
||||
return (true, null);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
return (false, ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
public virtual async Task<(bool ok, string message)> InsertOneAsync(TDocument document)
|
||||
{
|
||||
try
|
||||
{
|
||||
await Task.Run(() => _collection.InsertOneAsync(document));
|
||||
return (true, null);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
return (false, ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
public (bool ok, string message) InsertMany(ICollection<TDocument> documents)
|
||||
{
|
||||
try
|
||||
{
|
||||
_collection.InsertMany(documents);
|
||||
return (true, null);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
return (false, ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public virtual async Task<(bool ok, string message)> InsertManyAsync(ICollection<TDocument> documents)
|
||||
{
|
||||
try
|
||||
{
|
||||
await Task.Run(() => _collection.InsertManyAsync(documents));
|
||||
return (true, null);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
return (false, ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
public (bool ok, string message) ReplaceOne(TDocument document)
|
||||
{
|
||||
var filter = Builders<TDocument>.Filter.Eq(doc => doc.Id, document.Id);
|
||||
|
||||
try
|
||||
{
|
||||
_collection.FindOneAndReplace(filter, document);
|
||||
return (true, null);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
return (false, ex.Message);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public virtual (bool ok, string message) UpdateById(string id, UpdateDefinition<TDocument> updateDefinition)
|
||||
{
|
||||
var filter = Builders<TDocument>.Filter.Eq(doc => doc.Id, id);
|
||||
var result = _collection.UpdateOne(filter, updateDefinition);
|
||||
return (result.IsAcknowledged, null);
|
||||
}
|
||||
public virtual async Task<(bool ok, string message)> UpdateByIdAsync(string id, UpdateDefinition<TDocument> updateDefinition)
|
||||
{
|
||||
var filter = Builders<TDocument>.Filter.Eq(doc => doc.Id, id);
|
||||
try
|
||||
{
|
||||
var result = await _collection.UpdateOneAsync(filter, updateDefinition);
|
||||
return (result.IsAcknowledged, null);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
return (false, ex.Message);
|
||||
}
|
||||
}
|
||||
public virtual async Task<long> CountAsync(
|
||||
Expression<Func<TDocument, bool>> filterExpression)
|
||||
{
|
||||
return await _collection.Find(filterExpression).CountDocumentsAsync();
|
||||
}
|
||||
|
||||
public virtual long Count(
|
||||
Expression<Func<TDocument, bool>> filterExpression)
|
||||
{
|
||||
return _collection.Find(filterExpression).CountDocuments();
|
||||
}
|
||||
|
||||
|
||||
|
||||
public virtual async Task<long> CountAsync(
|
||||
FilterDefinition<TDocument> filterExpression)
|
||||
{
|
||||
return await _collection.Find(filterExpression).CountDocumentsAsync();
|
||||
}
|
||||
|
||||
public virtual long Count(
|
||||
FilterDefinition<TDocument> filterExpression)
|
||||
{
|
||||
return _collection.Find(filterExpression).CountDocuments();
|
||||
}
|
||||
public virtual (bool ok, string message) UpdateMany(Expression<Func<TDocument, bool>> filterExpression, UpdateDefinition<TDocument> updateDefinition)
|
||||
{
|
||||
var result = _collection.UpdateMany(filterExpression, updateDefinition);
|
||||
return (result.IsAcknowledged, null);
|
||||
}
|
||||
public virtual async Task<(bool ok, string message)> UpdateManyAsync(Expression<Func<TDocument, bool>> filterExpression, UpdateDefinition<TDocument> updateDefinition)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await _collection.UpdateManyAsync(filterExpression, updateDefinition);
|
||||
return (result.IsAcknowledged, null);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
return (false, ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
public virtual (bool ok, string message) UpdateMany(FilterDefinition<TDocument> filterExpression, UpdateDefinition<TDocument> updateDefinition)
|
||||
{
|
||||
var result = _collection.UpdateMany(filterExpression, updateDefinition);
|
||||
return (result.IsAcknowledged, null);
|
||||
}
|
||||
public virtual async Task<(bool ok, string message)> UpdateManyAsync(FilterDefinition<TDocument> filterExpression, UpdateDefinition<TDocument> updateDefinition)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await _collection.UpdateManyAsync(filterExpression, updateDefinition);
|
||||
return (result.IsAcknowledged, null);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
return (false, ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
public virtual async Task<(bool ok, string message)> ReplaceOneAsync(TDocument document)
|
||||
{
|
||||
var filter = Builders<TDocument>.Filter.Eq(doc => doc.Id, document.Id);
|
||||
try
|
||||
{
|
||||
await _collection.FindOneAndReplaceAsync(filter, document);
|
||||
|
||||
return (true, null);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
return (false, ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
public (bool ok, string message) DeleteOne(Expression<Func<TDocument, bool>> filterExpression)
|
||||
{
|
||||
try
|
||||
{
|
||||
_collection.FindOneAndDelete(filterExpression);
|
||||
return (true, null);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
return (false, ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<(bool ok, string message)> DeleteOneAsync(Expression<Func<TDocument, bool>> filterExpression)
|
||||
{
|
||||
try
|
||||
{
|
||||
await Task.Run(() => _collection.FindOneAndDeleteAsync(filterExpression));
|
||||
return (true, null);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
return (false, ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
public (bool ok, string message) DeleteById(string id)
|
||||
{
|
||||
var filter = Builders<TDocument>.Filter.Eq(doc => doc.Id, id);
|
||||
try
|
||||
{
|
||||
_collection.FindOneAndDelete(filter);
|
||||
return (true, null);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
return (false, ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<(bool ok, string message)> DeleteByIdAsync(string id)
|
||||
{
|
||||
var filter = Builders<TDocument>.Filter.Eq(doc => doc.Id, id);
|
||||
try
|
||||
{
|
||||
var deleted = await _collection.FindOneAndDeleteAsync(filter);
|
||||
if (deleted == null)
|
||||
return (false, "记录不存在或已删除");
|
||||
return (true, null);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
return (false, ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
public (bool ok, string message) DeleteMany(Expression<Func<TDocument, bool>> filterExpression)
|
||||
{
|
||||
try
|
||||
{
|
||||
_collection.DeleteMany(filterExpression);
|
||||
return (true, null);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
return (false, ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<(bool ok, string message)> DeleteManyAsync(Expression<Func<TDocument, bool>> filterExpression)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _collection.DeleteManyAsync(filterExpression);
|
||||
|
||||
return (true, null);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
return (false, ex.Message);
|
||||
}
|
||||
}
|
||||
public async Task<List<BsonDocument>> Aggregate(FilterDefinition<TDocument> filterExpression, ProjectionDefinition<TDocument, BsonDocument> group)
|
||||
{
|
||||
try
|
||||
{
|
||||
var res = await Task.Run(() => _collection.Aggregate().Match(filterExpression).Group(group).ToList());
|
||||
return res;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<List<TDocument>> RandomData(Expression<Func<TDocument, bool>> filterExpression, int count)
|
||||
{
|
||||
try
|
||||
{
|
||||
var res = await Task.Run(() =>
|
||||
_collection.AsQueryable().Where(filterExpression).Sample(count).ToList());
|
||||
return res;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MongoDB.Bson" Version="3.6.0" />
|
||||
<PackageReference Include="MongoDB.Driver" Version="3.6.0" />
|
||||
<PackageReference Include="MongoDB.Driver.Core" Version="2.30.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Common\Common.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,14 @@
|
||||
using System;
|
||||
|
||||
namespace RabbitMQHelper.Attributes
|
||||
{
|
||||
public class RabbitMQAttribute : Attribute
|
||||
{
|
||||
public string Topic { get; set; }
|
||||
|
||||
public RabbitMQAttribute(string topic)
|
||||
{
|
||||
this.Topic = topic;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using RabbitMQHelper.Attributes;
|
||||
using System.Reflection;
|
||||
|
||||
namespace RabbitMQHelper
|
||||
{
|
||||
public class ConsumerExecutorDescriptor
|
||||
{
|
||||
public MethodInfo MethodInfo { get; set; }
|
||||
|
||||
public TypeInfo ImplTypeInfo { get; set; }
|
||||
|
||||
public RabbitMQAttribute Attribute { get; set; }
|
||||
public string ClassName { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace RabbitMQHelper.Attributes
|
||||
{
|
||||
public interface IRabbitSubscribe
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace RabbitMQHelper
|
||||
{
|
||||
public interface ISendMQMessage
|
||||
{
|
||||
Task<bool> Send(string topic, object value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
using System;
|
||||
using EasyNetQ;
|
||||
using EasyNetQ.Topology;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace RabbitMQHelper
|
||||
{
|
||||
public static class InitRabbitMQSetup
|
||||
{
|
||||
public static void AddInitMQ(this IServiceCollection services, Action<RabbitConfig> setupAction, ILogger logger = null)
|
||||
{
|
||||
//#if NET5_0
|
||||
services.AddHttpContextAccessor();//<IHttpContextAccessor, HttpContextAccessor>();
|
||||
//#endif
|
||||
|
||||
var options = new RabbitConfig();
|
||||
setupAction(options);
|
||||
try
|
||||
{
|
||||
var bus = RabbitHutch.CreateBus($"host={options.Host};virtualHost={options.VirtualHost};username={options.UserName};password={options.Password}");
|
||||
bus.Advanced.Connected += delegate (object sender1, ConnectedEventArgs e1) { c_Conneted(sender1, e1, logger); };
|
||||
bus.Advanced.Disconnected += delegate (object sender1, DisconnectedEventArgs e1) { c_DisConnect(sender1, e1, logger); };
|
||||
services.AddSingleton<IBus>(bus);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new Exception("MQ初始化异常:" + ex.Message);
|
||||
}
|
||||
services.AddSingleton<IRabbitConfig>(sp => options);
|
||||
services.AddScoped<ISendMQMessage, SendMQMessage>();
|
||||
services.AddHostedService<MQHostedService>();
|
||||
|
||||
if (options.ListSubscribe != null)
|
||||
{
|
||||
foreach (var item in options.ListSubscribe)
|
||||
{
|
||||
if (options.DefaultRegisterWay.ToLower() == "singleton")
|
||||
{
|
||||
services.TryAddSingleton(item);
|
||||
}
|
||||
else if (options.DefaultRegisterWay.ToLower() == "scoped")
|
||||
{
|
||||
services.TryAddScoped(item);
|
||||
}
|
||||
else if (options.DefaultRegisterWay.ToLower() == "transient")
|
||||
{
|
||||
services.TryAddTransient(item);
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
services.TryAddSingleton(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
static void c_Conneted(object sender, EventArgs e, ILogger logger)
|
||||
{
|
||||
if (logger == null)
|
||||
{
|
||||
Console.WriteLine("RabbitMQ已链接");
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
static void c_DisConnect(object sender, EventArgs e, ILogger logger)
|
||||
{
|
||||
if (logger == null)
|
||||
{
|
||||
Console.WriteLine("RabbitMQ已断开");
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
using EasyNetQ;
|
||||
using EasyNetQ.Topology;
|
||||
using RabbitMQHelper.Attributes;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using RabbitMQHelper.Models;
|
||||
using MongoDB.Driver;
|
||||
|
||||
namespace RabbitMQHelper
|
||||
{
|
||||
public class MQHostedService : BackgroundService
|
||||
{
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
private readonly IBus _bus;
|
||||
private readonly IRabbitConfig _mongoDBConfig;
|
||||
private readonly IMongoCollection<ReceiveRecord> _collection;
|
||||
private static readonly object lockHelper = new object();
|
||||
public MQHostedService(IServiceProvider serviceProvider, IBus bus, IRabbitConfig mongoDBConfig)
|
||||
{
|
||||
_serviceProvider = serviceProvider;
|
||||
_bus = bus;
|
||||
_mongoDBConfig = mongoDBConfig;
|
||||
var database = new MongoClient(_mongoDBConfig.MongoConnectionString).GetDatabase(_mongoDBConfig.MongoDatabaseName);
|
||||
_collection = database.GetCollection<ReceiveRecord>("mq_receive_record");
|
||||
}
|
||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||
{
|
||||
var executorDescriptorList = new List<ConsumerExecutorDescriptor>();
|
||||
foreach (Type ty in _mongoDBConfig.ListSubscribe)
|
||||
{
|
||||
executorDescriptorList.AddRange(GetTopicAttributesDescription(ty.GetTypeInfo()));
|
||||
}
|
||||
|
||||
|
||||
var exchange = await _bus.Advanced.ExchangeDeclareAsync(_mongoDBConfig.DefaultExchange, ExchangeType.Topic);
|
||||
|
||||
foreach (var item in executorDescriptorList)
|
||||
{
|
||||
using (var scope = _serviceProvider.GetRequiredService<IServiceScopeFactory>().CreateScope())
|
||||
{
|
||||
var topic = item.Attribute.Topic;
|
||||
var provider = scope.ServiceProvider;
|
||||
var obj = ActivatorUtilities.GetServiceOrCreateInstance(provider, item.ImplTypeInfo.GetTypeInfo());
|
||||
|
||||
var subscriptId = $"{ item.ClassName.ToLower() }.{item.MethodInfo.Name.ToLower()}";
|
||||
|
||||
var queue = await _bus.Advanced.QueueDeclareAsync(subscriptId);
|
||||
|
||||
await _bus.Advanced.BindAsync(exchange, queue, topic, null);
|
||||
|
||||
_bus.Advanced.Consume<RabbitMessage>(queue, (msg, info) => Task.Factory.StartNew(async () =>
|
||||
{
|
||||
await ReceiveMessage(msg.Body, info.Queue, info.RoutingKey);
|
||||
lock (lockHelper)
|
||||
{
|
||||
object[] parameters = new object[] { msg.Body.Data, msg.Body.Headers };
|
||||
item.MethodInfo.Invoke(obj, parameters);
|
||||
}
|
||||
}));
|
||||
|
||||
//await _bus.PubSub.SubscribeAsync<RabbitMessage>(subscriptId, async msg =>
|
||||
//{
|
||||
// await ReceiveMessage(msg, subscriptId, topic);
|
||||
// object[] parameters = new object[] { msg.Data, msg.Headers };
|
||||
// item.MethodInfo.Invoke(obj, parameters);
|
||||
|
||||
//}, x => x.WithTopic(topic).w);
|
||||
}
|
||||
}
|
||||
await Task.Delay(Timeout.Infinite, stoppingToken);
|
||||
}
|
||||
public async Task ReceiveMessage(RabbitMessage msg,string subscriptId,string topic)
|
||||
{
|
||||
try {
|
||||
//var database = new MongoClient(_mongoDBConfig.MongoConnectionString).GetDatabase(_mongoDBConfig.MongoDatabaseName);
|
||||
//var collection = database.GetCollection<ReceiveRecord>("mq_receive_record");
|
||||
await _collection.InsertOneAsync(new ReceiveRecord(msg.RecordId, subscriptId, topic));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
private IEnumerable<ConsumerExecutorDescriptor> GetTopicAttributesDescription(TypeInfo typeInfo)
|
||||
{
|
||||
foreach (var method in typeInfo.DeclaredMethods)
|
||||
{
|
||||
var topicAttr = method.GetCustomAttributes<RabbitMQAttribute>(true);
|
||||
var topicAttributes = topicAttr as IList<RabbitMQAttribute> ?? topicAttr.ToList();
|
||||
|
||||
if (!topicAttributes.Any())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var attr in topicAttributes)
|
||||
{
|
||||
yield return InitDescriptor(attr, method, typeInfo, typeInfo.Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
private ConsumerExecutorDescriptor InitDescriptor(RabbitMQAttribute attr, MethodInfo methodInfo, TypeInfo implType, string name)
|
||||
{
|
||||
var descriptor = new ConsumerExecutorDescriptor
|
||||
{
|
||||
Attribute = attr,
|
||||
MethodInfo = methodInfo,
|
||||
ImplTypeInfo = implType,
|
||||
ClassName = name
|
||||
};
|
||||
return descriptor;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
using System;
|
||||
|
||||
|
||||
namespace RabbitMQHelper.Models
|
||||
{
|
||||
public class PublishRecord
|
||||
{
|
||||
public PublishRecord(string publishId, string topic,object message,bool success)
|
||||
{
|
||||
this.PublishId = publishId;
|
||||
this.Topic = topic;
|
||||
this.MessageContent = message;
|
||||
this.Success = success;
|
||||
}
|
||||
public string id { get; set; } = Guid.NewGuid().ToString("N");
|
||||
public string PublishId { get; set; }
|
||||
public string Topic { get; set; }
|
||||
public object MessageContent { get; set; }
|
||||
public bool Success { get; set; }
|
||||
public DateTime PublishTime { get; set; } = DateTime.Now;
|
||||
}
|
||||
|
||||
public class ReceiveRecord
|
||||
{
|
||||
public ReceiveRecord(string publishId, string subscriptId, string topic)
|
||||
{
|
||||
this.PublishId = publishId;
|
||||
this.SubscriptId = subscriptId;
|
||||
this.Topic = topic;
|
||||
}
|
||||
|
||||
public string id { get; set; } = Guid.NewGuid().ToString("N");
|
||||
public string PublishId { get; set; }
|
||||
public string SubscriptId { get; set; }
|
||||
public string Topic { get; set; }
|
||||
public DateTime ReceiveTime { get; set; } = DateTime.Now;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"iisSettings": {
|
||||
"windowsAuthentication": false,
|
||||
"anonymousAuthentication": true,
|
||||
"iisExpress": {
|
||||
"applicationUrl": "http://localhost:60480/",
|
||||
"sslPort": 44373
|
||||
}
|
||||
},
|
||||
"profiles": {
|
||||
"IIS Express": {
|
||||
"commandName": "IISExpress",
|
||||
"launchBrowser": true,
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"RabbitMQHelper": {
|
||||
"commandName": "Project",
|
||||
"launchBrowser": true,
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
},
|
||||
"applicationUrl": "https://localhost:5001;http://localhost:5000"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace RabbitMQHelper
|
||||
{
|
||||
public interface IRabbitConfig
|
||||
{
|
||||
string Host { get; set; }
|
||||
string VirtualHost { get; set; }
|
||||
string UserName { get; set; }
|
||||
string Password { get; set; }
|
||||
List<Type> ListSubscribe { get; set; }
|
||||
string MongoDatabaseName { get; set; }
|
||||
string MongoConnectionString { get; set; }
|
||||
string DefaultExchange { get; set; }
|
||||
|
||||
string DefaultRegisterWay { get; set; }
|
||||
}
|
||||
public class RabbitConfig: IRabbitConfig
|
||||
{
|
||||
public string Host { get; set; } = "49.233.122.91";
|
||||
public string VirtualHost { get; set; } = "/";
|
||||
public string UserName { get; set; } = "guest";
|
||||
public string Password { get; set; } = "guest";
|
||||
public List<Type> ListSubscribe { get; set; }
|
||||
public string MongoDatabaseName { get; set; }
|
||||
public string MongoConnectionString { get; set; }
|
||||
public string DefaultExchange { get; set; } = "hscw.default.topic";
|
||||
|
||||
|
||||
public string DefaultRegisterWay { get; set; } = "singleton";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="DotNetCore.CAP" Version="8.3.4" />
|
||||
<PackageReference Include="EasyNetQ" Version="7.8.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Http" Version="2.3.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="9.0.7" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="9.0.4" />
|
||||
<PackageReference Include="MongoDB.Driver" Version="3.6.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,17 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace RabbitMQHelper
|
||||
{
|
||||
public class RabbitMessage
|
||||
{
|
||||
public RabbitMessage(object data, Dictionary<string, string> headers = null)
|
||||
{
|
||||
this.Data = data;
|
||||
this.Headers = headers;
|
||||
}
|
||||
public string RecordId { get; set; } = Guid.NewGuid().ToString("N");
|
||||
public object Data { get; set; }
|
||||
public Dictionary<string,string> Headers { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using EasyNetQ;
|
||||
using EasyNetQ.Topology;
|
||||
using RabbitMQHelper.Models;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using MongoDB.Driver;
|
||||
|
||||
namespace RabbitMQHelper
|
||||
{
|
||||
public class SendMQMessage:ISendMQMessage
|
||||
{
|
||||
private readonly IBus _bus;
|
||||
private readonly ILogger<SendMQMessage> _logger;
|
||||
private readonly IRabbitConfig _mongoDBConfig;
|
||||
private readonly IMongoCollection<PublishRecord> _collection;
|
||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||
|
||||
public SendMQMessage(IBus bus, ILogger<SendMQMessage> logger, IRabbitConfig mongoDBConfig, IHttpContextAccessor httpContextAccessor)
|
||||
{
|
||||
_bus = bus;
|
||||
_logger = logger;
|
||||
_mongoDBConfig = mongoDBConfig;
|
||||
_httpContextAccessor = httpContextAccessor;
|
||||
var database = new MongoClient(_mongoDBConfig.MongoConnectionString).GetDatabase(_mongoDBConfig.MongoDatabaseName);
|
||||
_collection = database.GetCollection<PublishRecord>("mq_publish_record");
|
||||
}
|
||||
public async Task<bool> Send(string topic, object value)
|
||||
{
|
||||
if (!_bus.Advanced.IsConnected)
|
||||
{
|
||||
|
||||
return false;
|
||||
}
|
||||
Dictionary<string, string> headers = new Dictionary<string, string>();
|
||||
|
||||
if (_httpContextAccessor.HttpContext != null && _httpContextAccessor.HttpContext.Request != null && _httpContextAccessor.HttpContext.Request.Headers != null)
|
||||
{
|
||||
if (_httpContextAccessor.HttpContext.Request.Headers.ContainsKey("token"))
|
||||
{
|
||||
headers.Add("token", _httpContextAccessor.HttpContext.Request.Headers["token"].ToString() ?? "");
|
||||
}
|
||||
|
||||
;
|
||||
}
|
||||
bool success = false;
|
||||
|
||||
var model = new RabbitMessage(value, headers);
|
||||
|
||||
var exchange = await _bus.Advanced.ExchangeDeclareAsync(_mongoDBConfig.DefaultExchange, ExchangeType.Topic);
|
||||
|
||||
var message = new Message<RabbitMessage>(model);
|
||||
await _bus.Advanced.PublishAsync<RabbitMessage>(exchange, topic, false, message).ContinueWith(m =>
|
||||
{
|
||||
if (m.IsCompleted && m.IsCompletedSuccessfully)
|
||||
{
|
||||
success = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogError(m.Exception, "RabbitMQ消息推送失败 topic={Topic}", topic);
|
||||
}
|
||||
});
|
||||
try
|
||||
{
|
||||
await _collection.InsertOneAsync(new PublishRecord(model.RecordId, topic, value, success));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "RabbitMQ推送记录写入异常 topic={Topic}", topic);
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace RedisHelper
|
||||
{
|
||||
public class DistributedLock : IDistributedLock
|
||||
{
|
||||
private readonly ICache _cache;
|
||||
private readonly ILogger<DistributedLock> _logger;
|
||||
|
||||
public DistributedLock(ICache cache, ILogger<DistributedLock> logger)
|
||||
{
|
||||
_cache = cache;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<T> TryExecuteAsync<T>(string key, Func<Task<T>> action, int expiry = 30, bool retry = false, int retryDelay = 100)
|
||||
{
|
||||
string lockKey = GetLockKey(key);
|
||||
string lockValue = Guid.NewGuid().ToString();
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (TryAcquireLock(lockKey, lockValue, expiry))
|
||||
{
|
||||
try
|
||||
{
|
||||
return await action();
|
||||
}
|
||||
finally
|
||||
{
|
||||
ReleaseLock(lockKey, lockValue);
|
||||
}
|
||||
}
|
||||
|
||||
if (!retry)
|
||||
{
|
||||
return default(T);
|
||||
}
|
||||
|
||||
await Task.Delay(retryDelay);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> TryExecuteAsync(string key, Func<Task> action, int expiry = 30, bool retry = false, int retryDelay = 100)
|
||||
{
|
||||
string lockKey = GetLockKey(key);
|
||||
string lockValue = Guid.NewGuid().ToString();
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (TryAcquireLock(lockKey, lockValue, expiry))
|
||||
{
|
||||
try
|
||||
{
|
||||
await action();
|
||||
return true;
|
||||
}
|
||||
finally
|
||||
{
|
||||
ReleaseLock(lockKey, lockValue);
|
||||
}
|
||||
}
|
||||
|
||||
if (!retry)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
await Task.Delay(retryDelay);
|
||||
}
|
||||
}
|
||||
|
||||
public T TryExecute<T>(string key, Func<T> action, int expiry = 30, bool retry = false, int retryDelay = 100)
|
||||
{
|
||||
string lockKey = GetLockKey(key);
|
||||
string lockValue = Guid.NewGuid().ToString();
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (TryAcquireLock(lockKey, lockValue, expiry))
|
||||
{
|
||||
try
|
||||
{
|
||||
return action();
|
||||
}
|
||||
finally
|
||||
{
|
||||
ReleaseLock(lockKey, lockValue);
|
||||
}
|
||||
}
|
||||
|
||||
if (!retry)
|
||||
{
|
||||
return default(T);
|
||||
}
|
||||
|
||||
Thread.Sleep(retryDelay);
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryExecute(string key, Action action, int expiry = 30, bool retry = false, int retryDelay = 100)
|
||||
{
|
||||
string lockKey = GetLockKey(key);
|
||||
string lockValue = Guid.NewGuid().ToString();
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (TryAcquireLock(lockKey, lockValue, expiry))
|
||||
{
|
||||
try
|
||||
{
|
||||
action();
|
||||
return true;
|
||||
}
|
||||
finally
|
||||
{
|
||||
ReleaseLock(lockKey, lockValue);
|
||||
}
|
||||
}
|
||||
|
||||
if (!retry)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Thread.Sleep(retryDelay);
|
||||
}
|
||||
}
|
||||
|
||||
private bool TryAcquireLock(string lockKey, string lockValue, int expiry)
|
||||
{
|
||||
try
|
||||
{
|
||||
var redis = _cache.GetFullRedis();
|
||||
|
||||
bool acquired = redis.Add(lockKey, lockValue, expiry);
|
||||
|
||||
if (acquired)
|
||||
{
|
||||
}
|
||||
|
||||
return acquired;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, $"获取分布式锁时发生错�? {lockKey}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void ReleaseLock(string lockKey, string lockValue)
|
||||
{
|
||||
try
|
||||
{
|
||||
var redis = _cache.GetFullRedis();
|
||||
|
||||
var currentValue = redis.Get<string>(lockKey);
|
||||
|
||||
if (currentValue == lockValue)
|
||||
{
|
||||
redis.Remove(lockKey);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, $"释放分布式锁时发生错�? {lockKey}");
|
||||
}
|
||||
}
|
||||
|
||||
private string GetLockKey(string key)
|
||||
{
|
||||
return $"{key}";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
|
||||
using NewLife.Caching;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace RedisHelper
|
||||
{
|
||||
public interface ICache
|
||||
{
|
||||
FullRedis GetFullRedis();
|
||||
|
||||
void StartPipeline();
|
||||
|
||||
T Get<T>(string key);
|
||||
|
||||
IDictionary<string, T> GetAll<T>(List<string> keys);
|
||||
int Remove(string key);
|
||||
void Clear();
|
||||
|
||||
|
||||
|
||||
void Clear(string pattern, int count = 1000);
|
||||
|
||||
bool Insert<T>(string key, T data);
|
||||
|
||||
|
||||
bool Insert<T>(string key, T data, int expireSeconds);
|
||||
|
||||
|
||||
bool Insert<T>(string key, T data, TimeSpan expireSeconds);
|
||||
|
||||
|
||||
long IncrBy(string key, long value);
|
||||
|
||||
bool SetAdd<T>(string key, T value);
|
||||
|
||||
bool SetContains<T>(string key, T value);
|
||||
|
||||
bool SetRemove<T>(string key, T value);
|
||||
|
||||
long SetCount(string key);
|
||||
|
||||
List<T> SetMembers<T>(string key);
|
||||
|
||||
bool InitDataToList<T>(string key, List<T> t);
|
||||
|
||||
|
||||
|
||||
IEnumerable<string> GetPatternKey(string pattern, int count = 1000);
|
||||
|
||||
|
||||
IEnumerable<T> GetPatternList<T>(string pattern, int count = 1000);
|
||||
|
||||
bool UpdateDataToList<T>(string key, T t, Func<T, bool> predicate);
|
||||
|
||||
bool Exists(string key);
|
||||
|
||||
bool AddToList<T>(string key, T t);
|
||||
|
||||
bool RemoveFromList<T>(string key, Func<T, bool> predicate);
|
||||
|
||||
|
||||
IList<T> GetList<T>(string key);
|
||||
|
||||
|
||||
bool InitDataToDictionary<T>(string key, IDictionary<string, T> t);
|
||||
|
||||
|
||||
bool UpdateDataToDictionary<T>(string key, T t, string dataKey);
|
||||
|
||||
|
||||
bool RemoveFromDictionary<T>(string key, string dataKey);
|
||||
|
||||
Tuple<IEnumerable<T>,int> GetPageDescendingListFromDictionary<T, Tkey>(string key, int pageIndex, int pageSize,
|
||||
Func<T, bool> filter,
|
||||
Func<T, Tkey> sort);
|
||||
|
||||
Tuple<IEnumerable<T>, int> GetPageDescendingListFromDictionary<T, Tkey>(string key, int pageIndex,
|
||||
int pageSize, Func<T, bool> filter,
|
||||
Func<T, Tkey> sort, int skipCount);
|
||||
|
||||
Tuple<IEnumerable<T>,int> GetPageListFromDictionary<T, Tkey>(string key, int pageIndex, int pageSize,
|
||||
Func<T, bool> filter,
|
||||
Func<T, Tkey> sort);
|
||||
|
||||
Tuple<IEnumerable<T>,int> GetPageListFromDictionary<T, Tkey>(string key, int pageIndex, int pageSize,
|
||||
Func<T, bool> filter,
|
||||
Func<T, Tkey> sort, int skipCount);
|
||||
|
||||
|
||||
|
||||
IEnumerable<T> GetListFromDictionary<T, Tkey>(string key, int pageIndex, int pageSize,
|
||||
Func<T, bool> filter,
|
||||
Func<T, Tkey> sort);
|
||||
|
||||
|
||||
IEnumerable<T> GetDescendingListFromDictionary<T, Tkey>(string key, int pageIndex, int pageSize,
|
||||
Func<T, bool> filter,
|
||||
Func<T, Tkey> sort);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
T GetFromDictionary<T>(string key, string dataKey);
|
||||
|
||||
|
||||
ICollection<T> GetListFromDictionary<T>(string key);
|
||||
|
||||
|
||||
IEnumerable<T> GetListFromDictionaryByFilter<T>(string key, Func<T, bool> filter);
|
||||
|
||||
int GetListCountFromDictionaryByFilter<T>(string key, Func<T, bool> filter);
|
||||
|
||||
bool AddTempContent<T>(string tempId, T data, TimeSpan timeSpan);
|
||||
|
||||
|
||||
T GetTempContent<T>(string tempId);
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace RedisHelper
|
||||
{
|
||||
public interface IDistributedLock
|
||||
{
|
||||
Task<T> TryExecuteAsync<T>(string key, Func<Task<T>> action, int expiry = 30, bool retry = false, int retryDelay = 100);
|
||||
|
||||
Task<bool> TryExecuteAsync(string key, Func<Task> action, int expiry = 30, bool retry = false, int retryDelay = 100);
|
||||
|
||||
T TryExecute<T>(string key, Func<T> action, int expiry = 30, bool retry = false, int retryDelay = 100);
|
||||
|
||||
bool TryExecute(string key, Action action, int expiry = 30, bool retry = false, int retryDelay = 100);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,440 @@
|
||||
using NewLife.Caching;
|
||||
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace RedisHelper
|
||||
{
|
||||
public class RedisCache : ICache
|
||||
{
|
||||
public FullRedis fullRedis = null;
|
||||
private readonly ILogger<RedisCache> _logger;
|
||||
|
||||
public RedisCache(RedisUrl config, ILogger<RedisCache> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
fullRedis = FullRedis.Create(config.url);
|
||||
fullRedis.MaxMessageSize = 16 * 1024 * 1024;
|
||||
}
|
||||
|
||||
public FullRedis GetFullRedis()
|
||||
{
|
||||
return fullRedis;
|
||||
}
|
||||
|
||||
~RedisCache()
|
||||
{
|
||||
fullRedis.Dispose();
|
||||
}
|
||||
public void StartPipeline()
|
||||
{
|
||||
fullRedis.StartPipeline();
|
||||
}
|
||||
|
||||
public long IncrBy(string key, long value)
|
||||
{
|
||||
return fullRedis.Increment(key, value);
|
||||
}
|
||||
|
||||
public bool SetAdd<T>(string key, T value)
|
||||
{
|
||||
var result = fullRedis.SADD(key, value);
|
||||
return result > 0;
|
||||
}
|
||||
|
||||
public bool SetContains<T>(string key, T value)
|
||||
{
|
||||
return fullRedis.SISMEMBER(key, value) > 0;
|
||||
}
|
||||
|
||||
public bool SetRemove<T>(string key, T value)
|
||||
{
|
||||
return fullRedis.SREM(key, value) > 0;
|
||||
}
|
||||
|
||||
public long SetCount(string key)
|
||||
{
|
||||
return fullRedis.SCARD(key);
|
||||
}
|
||||
|
||||
public List<T> SetMembers<T>(string key)
|
||||
{
|
||||
return fullRedis.SMEMBERS<T>(key).ToList();
|
||||
}
|
||||
|
||||
private static readonly System.Text.Json.JsonSerializerOptions _jsonOptions = new()
|
||||
{
|
||||
PropertyNameCaseInsensitive = true,
|
||||
WriteIndented = false
|
||||
};
|
||||
|
||||
private static bool IsSimpleType(Type t) =>
|
||||
t == typeof(string) || t == typeof(long) || t == typeof(int) || t == typeof(bool) || t == typeof(double);
|
||||
|
||||
public T Get<T>(string key)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (IsSimpleType(typeof(T)))
|
||||
return fullRedis.Get<T>(key);
|
||||
|
||||
var json = fullRedis.Get<string>(key);
|
||||
if (json == null) return default;
|
||||
return System.Text.Json.JsonSerializer.Deserialize<T>(json, _jsonOptions);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return default;
|
||||
}
|
||||
}
|
||||
public IDictionary<string,T> GetAll<T>(List<string> keys)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (IsSimpleType(typeof(T)))
|
||||
return fullRedis.GetAll<T>(keys);
|
||||
|
||||
var strDict = fullRedis.GetAll<string>(keys);
|
||||
if (strDict == null) return null;
|
||||
var result = new Dictionary<string, T>();
|
||||
foreach (var kv in strDict)
|
||||
{
|
||||
if (kv.Value != null)
|
||||
result[kv.Key] = System.Text.Json.JsonSerializer.Deserialize<T>(kv.Value, _jsonOptions);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public bool Insert<T>(string key, T data)
|
||||
{
|
||||
if (IsSimpleType(typeof(T)))
|
||||
return fullRedis.Set<T>(key, data);
|
||||
|
||||
var json = System.Text.Json.JsonSerializer.Serialize(data, _jsonOptions);
|
||||
return fullRedis.Set<string>(key, json);
|
||||
}
|
||||
|
||||
public bool Insert<T>(string key, T data, int expireSeconds)
|
||||
{
|
||||
if (IsSimpleType(typeof(T)))
|
||||
return fullRedis.Set<T>(key, data, expireSeconds);
|
||||
|
||||
var json = System.Text.Json.JsonSerializer.Serialize(data, _jsonOptions);
|
||||
return fullRedis.Set<string>(key, json, expireSeconds);
|
||||
}
|
||||
|
||||
public bool Insert<T>(string key, T data, TimeSpan expireSeconds)
|
||||
{
|
||||
if (IsSimpleType(typeof(T)))
|
||||
return fullRedis.Set<T>(key, data, expireSeconds);
|
||||
|
||||
var json = System.Text.Json.JsonSerializer.Serialize(data, _jsonOptions);
|
||||
return fullRedis.Set<string>(key, json, expireSeconds);
|
||||
}
|
||||
|
||||
public int Remove(string key)
|
||||
{
|
||||
return fullRedis.Remove(key);
|
||||
}
|
||||
|
||||
|
||||
public void Clear(string pattern, int count = 1000)
|
||||
{
|
||||
try
|
||||
{
|
||||
var keys = fullRedis.Search(pattern, count).ToArray();
|
||||
if (keys.Any())
|
||||
{
|
||||
fullRedis.Remove(keys);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.LogError(e, "按表达式清理Redis键失败 pattern={Pattern} count={Count}", pattern, count);
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<string> GetPatternKey(string pattern, int count = 1000)
|
||||
{
|
||||
return fullRedis.Search(pattern, count).ToArray();
|
||||
}
|
||||
|
||||
public IEnumerable<T> GetPatternList<T>(string pattern, int count = 1000)
|
||||
{
|
||||
var lstKey = fullRedis.Search(pattern, count).ToArray();
|
||||
|
||||
return lstKey.Select(item => fullRedis.Get<T>(item)).ToList();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
fullRedis.Clear();
|
||||
}
|
||||
|
||||
public bool Exists(string key)
|
||||
{
|
||||
return fullRedis.ContainsKey(key);
|
||||
}
|
||||
|
||||
public IList<T> GetList<T>(string key)
|
||||
{
|
||||
return fullRedis.GetList<T>(key);
|
||||
}
|
||||
|
||||
public bool AddToList<T>(string key, T t)
|
||||
{
|
||||
try
|
||||
{
|
||||
var lst = fullRedis.GetList<T>(key);
|
||||
lst.Add(t);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public bool InitDataToList<T>(string key, List<T> t)
|
||||
{
|
||||
try
|
||||
{
|
||||
fullRedis.Remove(key);
|
||||
var lst = fullRedis.GetList<T>(key);
|
||||
lst.Clear();
|
||||
foreach (var item in t)
|
||||
{
|
||||
lst.Add(item);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public bool UpdateDataToList<T>(string key, T t, Func<T, bool> predicate)
|
||||
{
|
||||
try
|
||||
{
|
||||
var lst = fullRedis.GetList<T>(key);
|
||||
|
||||
var data = lst.Where(predicate);
|
||||
foreach (var item in data)
|
||||
{
|
||||
lst.Remove(item);
|
||||
|
||||
}
|
||||
|
||||
lst.Add(t);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public bool RemoveFromList<T>(string key, Func<T, bool> predicate)
|
||||
{
|
||||
var result = false;
|
||||
var lst = fullRedis.GetList<T>(key);
|
||||
var data = lst.Where(predicate);
|
||||
if (data != null)
|
||||
{
|
||||
foreach (var item in data)
|
||||
{
|
||||
result = lst.Remove(item);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public bool InitDataToDictionary<T>(string key, IDictionary<string, T> t)
|
||||
{
|
||||
try
|
||||
{
|
||||
fullRedis.Remove(key);
|
||||
var lst = fullRedis.GetDictionary<T>(key);
|
||||
// lst.Clear();
|
||||
foreach (var item in t)
|
||||
{
|
||||
lst.Add(item.Key, item.Value);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public bool UpdateDataToDictionary<T>(string key, T t, string dataKey)
|
||||
{
|
||||
try
|
||||
{
|
||||
var lst = fullRedis.GetDictionary<T>(key);
|
||||
lst.Remove(dataKey);
|
||||
lst.Add(dataKey, t);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public bool RemoveFromDictionary<T>(string key, string dataKey)
|
||||
{
|
||||
var lst = fullRedis.GetDictionary<T>(key);
|
||||
return lst.Remove(dataKey);
|
||||
}
|
||||
|
||||
|
||||
public T GetFromDictionary<T>(string key, string dataKey)
|
||||
{
|
||||
var lst = fullRedis.GetDictionary<T>(key);
|
||||
if (lst.ContainsKey(dataKey))
|
||||
{
|
||||
return lst[dataKey];
|
||||
}
|
||||
else
|
||||
{
|
||||
return default(T);
|
||||
}
|
||||
}
|
||||
|
||||
public ICollection<T> GetListFromDictionary<T>(string key)
|
||||
{
|
||||
var dict = fullRedis.GetDictionary<T>(key);
|
||||
return dict.Values;
|
||||
}
|
||||
|
||||
public IEnumerable<T> GetListFromDictionaryByFilter<T>(string key, Func<T, bool> filter)
|
||||
{
|
||||
var dict = fullRedis.GetDictionary<T>(key);
|
||||
return dict.Values.Where(filter);
|
||||
}
|
||||
|
||||
|
||||
public int GetListCountFromDictionaryByFilter<T>(string key, Func<T, bool> filter)
|
||||
{
|
||||
var dict = fullRedis.GetDictionary<T>(key);
|
||||
return dict.Values.Where(filter).Count();
|
||||
}
|
||||
|
||||
|
||||
|
||||
public Tuple<IEnumerable<T>,int> GetPageDescendingListFromDictionary<T,Tkey>(string key, int pageIndex, int pageSize, Func<T, bool> filter,
|
||||
Func<T,Tkey> sort)
|
||||
{
|
||||
var dict = fullRedis.GetDictionary<T>(key);
|
||||
var count = dict.Values.Where(filter).Count();
|
||||
var data= dict.Values.Where(filter)
|
||||
.OrderByDescending(sort)
|
||||
.Skip((pageIndex - 1) * pageSize).Take(pageSize);
|
||||
return new Tuple<IEnumerable<T>, int>(data, count);
|
||||
}
|
||||
|
||||
public Tuple<IEnumerable<T>,int> GetPageDescendingListFromDictionary<T,Tkey>(string key, int pageIndex, int pageSize, Func<T, bool> filter,
|
||||
Func<T,Tkey> sort,int skipCount)
|
||||
{
|
||||
var dict = fullRedis.GetDictionary<T>(key);
|
||||
var count = dict.Values.Where(filter).Count();
|
||||
var data= dict.Values.Where(filter)
|
||||
.OrderByDescending(sort)
|
||||
.Skip((pageIndex - 1) * pageSize+skipCount).Take(pageSize);
|
||||
return new Tuple<IEnumerable<T>, int>(data, count);
|
||||
}
|
||||
|
||||
|
||||
public Tuple<IEnumerable<T>, int> GetPageListFromDictionary<T, Tkey>(string key, int pageIndex, int pageSize,
|
||||
Func<T, bool> filter,
|
||||
Func<T, Tkey> sort)
|
||||
{
|
||||
var dict = fullRedis.GetDictionary<T>(key);
|
||||
var count = dict.Values.Where(filter).Count();
|
||||
|
||||
var data = dict.Values.Where(filter)
|
||||
.OrderBy(sort)
|
||||
.Skip((pageIndex - 1) * pageSize).Take(pageSize);
|
||||
|
||||
return new Tuple<IEnumerable<T>, int>(data, count);
|
||||
}
|
||||
|
||||
|
||||
public Tuple<IEnumerable<T>, int> GetPageListFromDictionary<T, Tkey>(string key, int pageIndex, int pageSize,
|
||||
Func<T, bool> filter,
|
||||
Func<T, Tkey> sort,int skipCount)
|
||||
{
|
||||
var dict = fullRedis.GetDictionary<T>(key);
|
||||
var count = dict.Values.Where(filter).Count();
|
||||
|
||||
var data = dict.Values.Where(filter)
|
||||
.OrderBy(sort)
|
||||
.Skip((pageIndex - 1) * pageSize+skipCount).Take(pageSize);
|
||||
|
||||
return new Tuple<IEnumerable<T>, int>(data, count);
|
||||
}
|
||||
|
||||
public IEnumerable<T> GetListFromDictionary<T, Tkey>(string key, int pageIndex, int pageSize,
|
||||
Func<T, bool> filter,
|
||||
Func<T, Tkey> sort)
|
||||
{
|
||||
var dict = fullRedis.GetDictionary<T>(key);
|
||||
var count = dict.Values.Where(filter).Count();
|
||||
|
||||
var data = dict.Values.Where(filter)
|
||||
.OrderBy(sort)
|
||||
.Skip((pageIndex - 1) * pageSize).Take(pageSize);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
|
||||
public IEnumerable<T> GetDescendingListFromDictionary<T,Tkey>(string key, int pageIndex, int pageSize, Func<T, bool> filter,
|
||||
Func<T,Tkey> sort)
|
||||
{
|
||||
var dict = fullRedis.GetDictionary<T>(key);
|
||||
var data= dict.Values.Where(filter)
|
||||
.OrderByDescending(sort)
|
||||
.Skip((pageIndex - 1) * pageSize).Take(pageSize);
|
||||
return data;
|
||||
}
|
||||
|
||||
|
||||
private string Temp="HS:Temp:{0}";
|
||||
|
||||
public bool AddTempContent<T>(string tempId, T data, TimeSpan timeSpan)
|
||||
{
|
||||
string key = string.Format(Temp, tempId);
|
||||
|
||||
return Insert(key, data, timeSpan);
|
||||
|
||||
}
|
||||
|
||||
|
||||
public T GetTempContent<T>(string tempId)
|
||||
{
|
||||
string key = string.Format(Temp, tempId);
|
||||
return Get<T>(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
namespace RedisHelper
|
||||
{
|
||||
public class RedisConfig
|
||||
{
|
||||
public string Host { get; set; }
|
||||
|
||||
public int Port { get; set; }
|
||||
public string Password { get; set; }
|
||||
public string UserName { get; set; } = "";
|
||||
public int Database { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Server={Host}:{Port};Password={Password};UserName={UserName};Db={Database}";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="8.0.2" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="9.0.7" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.7" />
|
||||
<PackageReference Include="NewLife.Redis" Version="6.2.2025.503" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,49 @@
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace RedisHelper
|
||||
{
|
||||
public static class RedisServiceExtension
|
||||
{
|
||||
public static IServiceCollection AddRedisCache(this IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
services.AddSingleton<RedisUrl>(opts =>
|
||||
{
|
||||
var setting = new RedisConfig();
|
||||
configuration.Bind("RedisConfig", setting);
|
||||
return new RedisUrl { url = setting.ToString() };
|
||||
});
|
||||
services.AddScoped<ICache, RedisCache>();
|
||||
services.AddScoped<IDistributedLock, DistributedLock>();
|
||||
return services;
|
||||
}
|
||||
|
||||
|
||||
public static IServiceCollection AddRedisCacheSingleton(this IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
services.AddSingleton<RedisUrl>(opts =>
|
||||
{
|
||||
var setting = new RedisConfig();
|
||||
configuration.Bind("RedisConfig", setting);
|
||||
return new RedisUrl { url = setting.ToString() };
|
||||
});
|
||||
services.AddSingleton<ICache, RedisCache>();
|
||||
services.AddSingleton<IDistributedLock, DistributedLock>();
|
||||
return services;
|
||||
}
|
||||
|
||||
|
||||
public static IServiceCollection AddRedisCache(this IServiceCollection services, string url)
|
||||
{
|
||||
services.AddSingleton<RedisUrl>(options => new RedisUrl { url = url });
|
||||
services.AddSingleton<ICache, RedisCache>();
|
||||
services.AddSingleton<IDistributedLock, DistributedLock>();
|
||||
return services;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
namespace RedisHelper
|
||||
{
|
||||
public class RedisUrl
|
||||
{
|
||||
public RedisUrl()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
//public RedisUrl(string url)
|
||||
//{
|
||||
// _url = url;
|
||||
//}
|
||||
public string url { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
using Cronos;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
|
||||
namespace ScheduledService;
|
||||
|
||||
public class CronJobService : IHostedService, IDisposable
|
||||
{
|
||||
private System.Timers.Timer? _timer;
|
||||
private readonly CronExpression _expression;
|
||||
private readonly TimeZoneInfo _timeZoneInfo;
|
||||
private readonly ILogger<CronJobService> _logger;
|
||||
|
||||
public CronJobService(string cronExpression, TimeZoneInfo timeZoneInfo, ILogger<CronJobService>? logger = null)
|
||||
{
|
||||
_expression = CronExpression.Parse(cronExpression, CronFormat.IncludeSeconds);
|
||||
_timeZoneInfo = timeZoneInfo;
|
||||
_logger = logger ?? NullLogger<CronJobService>.Instance;
|
||||
}
|
||||
|
||||
public virtual async Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
await ScheduleJob(cancellationToken);
|
||||
}
|
||||
|
||||
protected virtual async Task ScheduleJob(CancellationToken cancellationToken)
|
||||
{
|
||||
var next = _expression.GetNextOccurrence(DateTimeOffset.Now, _timeZoneInfo);
|
||||
if (next.HasValue)
|
||||
{
|
||||
var delay = next.Value - DateTimeOffset.Now;
|
||||
if (delay.TotalMilliseconds <= 0)
|
||||
{
|
||||
await ScheduleJob(cancellationToken);
|
||||
return;
|
||||
}
|
||||
|
||||
_timer = new System.Timers.Timer(delay.TotalMilliseconds);
|
||||
_timer.Elapsed += async (sender, args) =>
|
||||
{
|
||||
_timer?.Dispose();
|
||||
_timer = null;
|
||||
|
||||
try
|
||||
{
|
||||
if (!cancellationToken.IsCancellationRequested)
|
||||
await DoWork(cancellationToken);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "定时任务 DoWork 执行异常");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (!cancellationToken.IsCancellationRequested)
|
||||
await ScheduleJob(cancellationToken);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "定时任务 ScheduleJob 调度异常");
|
||||
}
|
||||
};
|
||||
_timer.Start();
|
||||
}
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
|
||||
public virtual async Task DoWork(CancellationToken cancellationToken)
|
||||
{
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
|
||||
public virtual async Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
_timer?.Stop();
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
|
||||
public virtual void Dispose()
|
||||
{
|
||||
_timer?.Dispose();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace ScheduledService;
|
||||
|
||||
public interface IScheduleConfig<T>
|
||||
{
|
||||
string CronExpression { get; set; }
|
||||
TimeZoneInfo TimeZoneInfo { get; set; }
|
||||
}
|
||||
|
||||
public class ScheduleConfig<T> : IScheduleConfig<T>
|
||||
{
|
||||
public string CronExpression { get; set; } = "* * * * *";
|
||||
public TimeZoneInfo TimeZoneInfo { get; set; } = TimeZoneInfo.Local;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Cronos" Version="0.8.4" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="9.0.7" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,28 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
namespace ScheduledService;
|
||||
|
||||
public static class ScheduledServiceExtensions
|
||||
{
|
||||
public static IServiceCollection AddCronJob<T>(this IServiceCollection services, Action<IScheduleConfig<T>> options) where T : CronJobService
|
||||
{
|
||||
if (options == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(options), "Options cannot be null");
|
||||
}
|
||||
|
||||
var config = new ScheduleConfig<T>();
|
||||
options.Invoke(config);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(config.CronExpression))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(ScheduleConfig<T>.CronExpression), "Cron expression cannot be null or empty");
|
||||
}
|
||||
|
||||
services.AddSingleton<IScheduleConfig<T>>(config);
|
||||
services.AddHostedService<T>();
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
using System.Reflection;
|
||||
using Autofac;
|
||||
using CommonService;
|
||||
using MongoRepository;
|
||||
using RedisService;
|
||||
|
||||
namespace SharpAbp.Infrastructure.Api.Autofac
|
||||
{
|
||||
public abstract class ApplicationModule : global::Autofac.Module
|
||||
{
|
||||
protected abstract Assembly CurrentAssembly { get; }
|
||||
|
||||
protected virtual Assembly[] GetServiceAssemblies()
|
||||
{
|
||||
return new[] { CurrentAssembly };
|
||||
}
|
||||
|
||||
protected virtual bool RegisterMongoBaseService => true;
|
||||
|
||||
protected override void Load(ContainerBuilder builder)
|
||||
{
|
||||
builder.RegisterGeneric(typeof(MongoBaseRepository<>))
|
||||
.As(typeof(IMongoBaseRepository<>))
|
||||
.InstancePerLifetimeScope();
|
||||
|
||||
if (RegisterMongoBaseService)
|
||||
{
|
||||
builder.RegisterGeneric(typeof(MongoBaseService<>))
|
||||
.As(typeof(IMongoBaseService<>))
|
||||
.InstancePerLifetimeScope();
|
||||
}
|
||||
|
||||
builder.RegisterType<RedisService.RedisService>()
|
||||
.As<RedisService.IRedisService>()
|
||||
.InstancePerLifetimeScope();
|
||||
|
||||
var serviceAssemblies = GetServiceAssemblies();
|
||||
foreach (var assembly in serviceAssemblies)
|
||||
{
|
||||
builder.RegisterAssemblyTypes(assembly)
|
||||
.Where(t => t.Name.EndsWith("Service") && !t.IsAbstract)
|
||||
.AsImplementedInterfaces()
|
||||
.InstancePerLifetimeScope();
|
||||
}
|
||||
|
||||
foreach (var assembly in serviceAssemblies)
|
||||
{
|
||||
builder.RegisterAssemblyTypes(assembly)
|
||||
.Where(t => t.Name.EndsWith("Repository") && !t.IsAbstract)
|
||||
.AsImplementedInterfaces()
|
||||
.InstancePerLifetimeScope();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
namespace SharpAbp.Infrastructure.Api.Configuration
|
||||
{
|
||||
public class JwtConfig
|
||||
{
|
||||
public string TokenKey { get; set; } = "token";
|
||||
public string Issuer { get; set; } = "admin";
|
||||
public string Audience { get; set; } = "";
|
||||
public string SecretKey { get; set; } = "";
|
||||
public bool ValidateIssuer { get; set; } = false;
|
||||
public bool ValidateAudience { get; set; } = false;
|
||||
public bool ValidateIssuerSigningKey { get; set; } = false;
|
||||
public bool ValidateLifetime { get; set; } = false;
|
||||
}
|
||||
|
||||
public class CapConfig
|
||||
{
|
||||
public string Host { get; set; } = "localhost";
|
||||
public string UserName { get; set; } = "admin";
|
||||
public string Password { get; set; } = "";
|
||||
public string VirtualHost { get; set; } = "/";
|
||||
public string PGLogHost { get; set; } = "";
|
||||
}
|
||||
|
||||
public class RedisConfig
|
||||
{
|
||||
public string Host { get; set; } = "localhost";
|
||||
public int Port { get; set; } = 6379;
|
||||
public string UserName { get; set; } = "";
|
||||
public string Password { get; set; } = "";
|
||||
public int Database { get; set; } = 0;
|
||||
}
|
||||
|
||||
public class WXConfig
|
||||
{
|
||||
public string AppId { get; set; } = "";
|
||||
public string Secret { get; set; } = "";
|
||||
public string Url { get; set; } = "";
|
||||
}
|
||||
|
||||
public class AppSettings
|
||||
{
|
||||
public JwtConfig JwtConfig { get; set; } = new();
|
||||
public string PgSqlConnection { get; set; } = "";
|
||||
public CapConfig CapConfig { get; set; } = new();
|
||||
public RedisConfig RedisConfig { get; set; } = new();
|
||||
public MongoDbSettings MongoDbSettings { get; set; } = new();
|
||||
public WXConfig WXConfig { get; set; } = new();
|
||||
}
|
||||
|
||||
public class MongoDbSettings
|
||||
{
|
||||
public string Username { get; set; } = "";
|
||||
public string Password { get; set; } = "";
|
||||
public string ServerUrl { get; set; } = "";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
namespace SharpAbp.Infrastructure.Api.Configuration
|
||||
{
|
||||
public static class ConfigurationHelper
|
||||
{
|
||||
public static string GetString(string key, string defaultValue = null)
|
||||
{
|
||||
var envValue = Environment.GetEnvironmentVariable(key);
|
||||
if (!string.IsNullOrEmpty(envValue))
|
||||
{
|
||||
return envValue;
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
public static int GetInt(string key, int defaultValue = 0)
|
||||
{
|
||||
var envValue = Environment.GetEnvironmentVariable(key);
|
||||
if (int.TryParse(envValue, out var result))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
public static bool GetBool(string key, bool defaultValue = false)
|
||||
{
|
||||
var envValue = Environment.GetEnvironmentVariable(key);
|
||||
if (bool.TryParse(envValue, out var result))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
public static string GetConnectionString(string type)
|
||||
{
|
||||
return type.ToLower() switch
|
||||
{
|
||||
"mongodb" => GetMongoDBConnectionString(),
|
||||
"redis" => GetRedisConnectionString(),
|
||||
"rabbitmq" => GetRabbitMQConnectionString(),
|
||||
"mysql" => GetMySQLConnectionString(),
|
||||
_ => throw new ArgumentException($"Unknown connection type: {type}")
|
||||
};
|
||||
}
|
||||
|
||||
private static string GetMongoDBConnectionString()
|
||||
{
|
||||
var host = GetString("MONGODB_HOST", "localhost");
|
||||
var port = GetString("MONGODB_PORT", "27017");
|
||||
var user = GetString("MONGODB_USER", "");
|
||||
var password = GetString("MONGODB_PASSWORD", "");
|
||||
var database = GetString("MONGODB_DATABASE", "data_0524");
|
||||
|
||||
if (!string.IsNullOrEmpty(user) && !string.IsNullOrEmpty(password))
|
||||
{
|
||||
return $"mongodb://{user}:{password}@{host}:{port}/{database}";
|
||||
}
|
||||
return $"mongodb://{host}:{port}/{database}";
|
||||
}
|
||||
|
||||
private static string GetRedisConnectionString()
|
||||
{
|
||||
var host = GetString("REDIS_HOST", "localhost");
|
||||
var port = GetString("REDIS_PORT", "6379");
|
||||
var password = GetString("REDIS_PASSWORD", "");
|
||||
var database = GetString("REDIS_DB", "0");
|
||||
|
||||
var connectionString = $"{host}:{port}";
|
||||
if (!string.IsNullOrEmpty(password))
|
||||
{
|
||||
connectionString += $",password={password}";
|
||||
}
|
||||
connectionString += $",defaultDatabase={database}";
|
||||
|
||||
return connectionString;
|
||||
}
|
||||
|
||||
private static string GetRabbitMQConnectionString()
|
||||
{
|
||||
var host = GetString("RABBITMQ_HOST", "localhost");
|
||||
var port = GetString("RABBITMQ_PORT", "5672");
|
||||
var user = GetString("RABBITMQ_USER", "guest");
|
||||
var password = GetString("RABBITMQ_PASSWORD", "guest");
|
||||
|
||||
return $"amqp://{user}:{password}@{host}:{port}";
|
||||
}
|
||||
|
||||
private static string GetMySQLConnectionString()
|
||||
{
|
||||
var host = GetString("MYSQL_HOST", "localhost");
|
||||
var port = GetString("MYSQL_PORT", "3306");
|
||||
var user = GetString("MYSQL_USER", "root");
|
||||
var password = GetString("MYSQL_PASSWORD", "");
|
||||
var database = GetString("MYSQL_DATABASE", "data_0524");
|
||||
|
||||
return $"Server={host};Port={port};Database={database};User={user};Password={password};";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
using System.Linq;
|
||||
using System.Security.Claims;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace SharpAbp.Infrastructure.Api.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
public abstract class BaseController : Controller
|
||||
{
|
||||
public LoginUser loginUser
|
||||
{
|
||||
get
|
||||
{
|
||||
return GetLoginUser();
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual LoginUser GetLoginUser()
|
||||
{
|
||||
var claims = User?.Claims?.ToList();
|
||||
if (claims == null || claims.Count == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var userId = claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier)?.Value;
|
||||
var userName = claims.FirstOrDefault(c => c.Type == ClaimTypes.Name)?.Value;
|
||||
var role = claims.FirstOrDefault(c => c.Type == ClaimTypes.Role)?.Value;
|
||||
|
||||
return new LoginUser
|
||||
{
|
||||
userId = userId,
|
||||
userName = userName,
|
||||
role = role
|
||||
};
|
||||
}
|
||||
|
||||
protected virtual string GetClientIp()
|
||||
{
|
||||
var ip = HttpContext.Request.Headers["X-Forwarded-For"].FirstOrDefault();
|
||||
if (string.IsNullOrEmpty(ip))
|
||||
{
|
||||
ip = HttpContext.Connection.RemoteIpAddress?.ToString();
|
||||
}
|
||||
return ip ?? "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
public class LoginUser
|
||||
{
|
||||
public string userId { get; set; }
|
||||
|
||||
public string userName { get; set; }
|
||||
|
||||
public string role { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Common;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace SharpAbp.Infrastructure.Api.Extensions
|
||||
{
|
||||
public static class ControllerExtensions
|
||||
{
|
||||
public static BaseResponse<T> ToFailResult<T>(
|
||||
this Controller controller,
|
||||
string info) => new BaseResponse<T>() { Info = info, Code = CommonResponseCodeEnum.ResultFail };
|
||||
|
||||
public static ListResponse<T> ToListFailResult<T>(
|
||||
this Controller controller,
|
||||
string info) => new ListResponse<T>()
|
||||
{ Info = info, Code = CommonResponseCodeEnum.ResultFail };
|
||||
|
||||
public static PageResponse<T> ToPageFailResult<T>(
|
||||
this Controller controller,
|
||||
string info) => new PageResponse<T>()
|
||||
{
|
||||
Code = CommonResponseCodeEnum.ResultFail,
|
||||
Info = info
|
||||
};
|
||||
|
||||
public static BaseResponse<T> ToResult<T>(
|
||||
this Controller controller,
|
||||
T obj, string info = null) => new BaseResponse<T>()
|
||||
{
|
||||
Data = obj,
|
||||
Info = info,
|
||||
Code = CommonResponseCodeEnum.Success
|
||||
};
|
||||
|
||||
public static ListResponse<T> ToListResult<T>(
|
||||
this Controller controller,
|
||||
IEnumerable<T> list, string info = null) => new ListResponse<T>()
|
||||
{
|
||||
Data = list,
|
||||
Code = CommonResponseCodeEnum.Success,
|
||||
Info = info
|
||||
};
|
||||
|
||||
public static PageResponse<T> ToPageResult<T>(
|
||||
this Controller controller,
|
||||
IEnumerable<T> list, int count = 0, string info = null) => new PageResponse<T>()
|
||||
{
|
||||
Code = CommonResponseCodeEnum.Success,
|
||||
Info = info,
|
||||
Count = count,
|
||||
Data = list
|
||||
};
|
||||
|
||||
public static BaseResponse<T> ToNoLoginResult<T>(
|
||||
this Controller controller) => new BaseResponse<T>()
|
||||
{
|
||||
Info = "未登录",
|
||||
Code = CommonResponseCodeEnum.NoLogin
|
||||
};
|
||||
|
||||
public static BaseResponse<T> ToTokenInvalidResult<T>(
|
||||
this Controller controller) => new BaseResponse<T>()
|
||||
{
|
||||
Info = "token失效",
|
||||
Code = CommonResponseCodeEnum.TokenInvalid
|
||||
};
|
||||
|
||||
public static BaseResponse<T> ToParamErrorResult<T>(
|
||||
this Controller controller,
|
||||
string info = "请求参数错误") => new BaseResponse<T>()
|
||||
{
|
||||
Info = info,
|
||||
Code = CommonResponseCodeEnum.ParamError
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
using System.Net;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace SharpAbp.Infrastructure.Api.Middleware
|
||||
{
|
||||
public class GlobalExceptionMiddleware
|
||||
{
|
||||
private readonly RequestDelegate _next;
|
||||
private readonly ILogger<GlobalExceptionMiddleware> _logger;
|
||||
|
||||
public GlobalExceptionMiddleware(RequestDelegate next, ILogger<GlobalExceptionMiddleware> logger)
|
||||
{
|
||||
_next = next;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task InvokeAsync(HttpContext context)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _next(context);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await HandleExceptionAsync(context, ex);
|
||||
}
|
||||
}
|
||||
|
||||
private Task HandleExceptionAsync(HttpContext context, Exception ex)
|
||||
{
|
||||
if (ex is Common.BusinessException bizEx)
|
||||
{
|
||||
|
||||
return WriteJsonResponse(context, Common.CommonResponseCodeEnum.Error, bizEx.Message);
|
||||
}
|
||||
|
||||
if (ex is UnauthorizedAccessException)
|
||||
{
|
||||
|
||||
return WriteJsonResponse(context, Common.CommonResponseCodeEnum.NoAuth, "未授权访问");
|
||||
}
|
||||
|
||||
if (ex is ArgumentException argEx)
|
||||
{
|
||||
|
||||
return WriteJsonResponse(context, Common.CommonResponseCodeEnum.ParamError, argEx.Message);
|
||||
}
|
||||
|
||||
if (ex is TimeoutException || ex is OperationCanceledException)
|
||||
{
|
||||
|
||||
return WriteJsonResponse(context, Common.CommonResponseCodeEnum.Error, "操作超时,请稍后重试: " + ex.Message);
|
||||
}
|
||||
|
||||
_logger.LogError(ex, "系统异常: {Message}", ex.Message);
|
||||
return WriteJsonResponse(context, Common.CommonResponseCodeEnum.Exception, ex.Message);
|
||||
}
|
||||
|
||||
private static Task WriteJsonResponse(HttpContext context, Common.CommonResponseCodeEnum code, string message)
|
||||
{
|
||||
context.Response.StatusCode = 200;
|
||||
context.Response.ContentType = "application/json";
|
||||
return context.Response.WriteAsync(JsonConvert.SerializeObject(new
|
||||
{
|
||||
code = code,
|
||||
info = message,
|
||||
data = (object?)null
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
public static class GlobalExceptionMiddlewareExtensions
|
||||
{
|
||||
public static IApplicationBuilder UseGlobalExceptionHandler(this IApplicationBuilder app)
|
||||
{
|
||||
return app.UseMiddleware<GlobalExceptionMiddleware>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace SharpAbp.Infrastructure.Api.Resilience
|
||||
{
|
||||
public enum CircuitState
|
||||
{
|
||||
Closed,
|
||||
|
||||
Open,
|
||||
|
||||
HalfOpen
|
||||
}
|
||||
|
||||
public class CircuitBreakerOptions
|
||||
{
|
||||
public int FailureThreshold { get; set; } = 5;
|
||||
|
||||
public int BreakDurationSeconds { get; set; } = 30;
|
||||
|
||||
public int HalfOpenAttempts { get; set; } = 3;
|
||||
|
||||
public int SuccessThreshold { get; set; } = 2;
|
||||
}
|
||||
|
||||
public class CircuitBreaker
|
||||
{
|
||||
private readonly CircuitBreakerOptions _options;
|
||||
private readonly ILogger _logger;
|
||||
private int _failureCount;
|
||||
private int _successCount;
|
||||
private int _halfOpenAttempts;
|
||||
private CircuitState _state = CircuitState.Closed;
|
||||
private DateTime _lastFailureTime;
|
||||
private readonly object _lock = new();
|
||||
|
||||
public CircuitBreaker(string name, CircuitBreakerOptions options, ILogger logger)
|
||||
{
|
||||
Name = name;
|
||||
_options = options;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public string Name { get; }
|
||||
public CircuitState State => _state;
|
||||
|
||||
public async Task<T> ExecuteAsync<T>(Func<Task<T>> action)
|
||||
{
|
||||
if (!CanExecute())
|
||||
{
|
||||
throw new CircuitBreakerOpenException($"Circuit breaker '{Name}' is open");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var result = await action();
|
||||
OnSuccess();
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
OnFailure(ex);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task ExecuteAsync(Func<Task> action)
|
||||
{
|
||||
if (!CanExecute())
|
||||
{
|
||||
throw new CircuitBreakerOpenException($"Circuit breaker '{Name}' is open");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await action();
|
||||
OnSuccess();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
OnFailure(ex);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private bool CanExecute()
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
switch (_state)
|
||||
{
|
||||
case CircuitState.Closed:
|
||||
return true;
|
||||
|
||||
case CircuitState.Open:
|
||||
var elapsed = (DateTime.UtcNow - _lastFailureTime).TotalSeconds;
|
||||
if (elapsed >= _options.BreakDurationSeconds)
|
||||
{
|
||||
_state = CircuitState.HalfOpen;
|
||||
_halfOpenAttempts = 0;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
case CircuitState.HalfOpen:
|
||||
return _halfOpenAttempts < _options.HalfOpenAttempts;
|
||||
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSuccess()
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
switch (_state)
|
||||
{
|
||||
case CircuitState.Closed:
|
||||
_failureCount = 0;
|
||||
break;
|
||||
|
||||
case CircuitState.HalfOpen:
|
||||
_successCount++;
|
||||
_halfOpenAttempts++;
|
||||
if (_successCount >= _options.SuccessThreshold)
|
||||
{
|
||||
_state = CircuitState.Closed;
|
||||
_failureCount = 0;
|
||||
_successCount = 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnFailure(Exception ex)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
_failureCount++;
|
||||
_lastFailureTime = DateTime.UtcNow;
|
||||
|
||||
switch (_state)
|
||||
{
|
||||
case CircuitState.Closed:
|
||||
if (_failureCount >= _options.FailureThreshold)
|
||||
{
|
||||
_state = CircuitState.Open;
|
||||
}
|
||||
break;
|
||||
|
||||
case CircuitState.HalfOpen:
|
||||
_state = CircuitState.Open;
|
||||
_halfOpenAttempts = _options.HalfOpenAttempts;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class CircuitBreakerOpenException : Exception
|
||||
{
|
||||
public CircuitBreakerOpenException(string message) : base(message) { }
|
||||
}
|
||||
|
||||
public class CircuitBreakerFactory
|
||||
{
|
||||
private readonly ConcurrentDictionary<string, CircuitBreaker> _breakers = new();
|
||||
private readonly ILoggerFactory _loggerFactory;
|
||||
|
||||
public CircuitBreakerFactory(ILoggerFactory loggerFactory)
|
||||
{
|
||||
_loggerFactory = loggerFactory;
|
||||
}
|
||||
|
||||
public CircuitBreaker GetOrCreate(string name, CircuitBreakerOptions options = null)
|
||||
{
|
||||
return _breakers.GetOrAdd(name, n =>
|
||||
{
|
||||
var opts = options ?? new CircuitBreakerOptions();
|
||||
var logger = _loggerFactory.CreateLogger<CircuitBreaker>();
|
||||
return new CircuitBreaker(n, opts, logger);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<RootNamespace>SharpAbp.Infrastructure.Api</RootNamespace>
|
||||
<AssemblyName>SharpAbp.Infrastructure.Api</AssemblyName>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<FrameworkReference Include="Microsoft.AspNetCore.App" />
|
||||
<PackageReference Include="Autofac" Version="8.3.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\CommonService\CommonService.csproj" />
|
||||
<ProjectReference Include="..\..\RedisService\RedisService.csproj" />
|
||||
<ProjectReference Include="..\Common\Common.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
Reference in New Issue
Block a user