初始提交

This commit is contained in:
wanyongkang
2020-10-07 20:25:03 +08:00
commit d318014316
3809 changed files with 263103 additions and 0 deletions

View File

@@ -0,0 +1,16 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace Hncore.Infrastructure.WebApi
{
[Route("/check")]
public class CheckController: ControllerBase
{
[HttpGet]
[AllowAnonymous]
public IActionResult Get()
{
return Ok("ok");
}
}
}

View File

@@ -0,0 +1,124 @@
using System;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using Hncore.Infrastructure.EF;
using Hncore.Infrastructure.Extension;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.Razor;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewEngines;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Routing;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.Extensions.DependencyInjection;
namespace Hncore.Infrastructure.WebApi
{
[ApiController]
public class HncoreControllerBase : ControllerBase
{
protected ApiResult Success()
{
return new ApiResult(ResultCode.C_SUCCESS, "");
}
protected ApiResult Success<T>(T data, string message = "")
{
return new ApiResult(ResultCode.C_SUCCESS, message) {Data = data};
}
protected ApiResult Error(string message = "")
{
return new ApiResult(ResultCode.C_UNKNOWN_ERROR, message);
}
protected ApiResult Error(ResultCode code, string message = "")
{
return new ApiResult(code, message);
}
protected ApiResult UofCommit(string message = "")
{
RepositoryDbContext.SaveChanges();
return Success(message);
}
protected ApiResult UofCommit<T>(Func<T> func, string message = "")
{
RepositoryDbContext.SaveChanges();
return Success(func(), message);
}
protected async Task<ApiResult> UofCommitAsync(string message = "")
{
await RepositoryDbContext.SaveChangesAsync();
return Success(message);
}
protected async Task<ApiResult> UofCommitAsync(IDbContextTransaction trans, string message = "")
{
await RepositoryDbContext.SaveChangesAsync();
trans.Commit();
return Success(message);
}
protected async Task<ApiResult> UofCommitAsync<T>(Func<T> func, string message = "")
{
await RepositoryDbContext.SaveChangesAsync();
return Success(func(), message);
}
protected DbContext RepositoryDbContext =>
Request.HttpContext
.RequestServices
.GetService<IRepositoryDbContext>()
.DbContext;
protected async Task<string> RenderViewToStringAsync(string viewName, object model = null)
{
using (var sw = new StringWriter())
{
var actionContext = new ActionContext(HttpContext, new RouteData(), new ActionDescriptor());
var viewResult = HttpContext.RequestServices.GetService<IRazorViewEngine>()
.FindView(actionContext, viewName, false);
if (viewResult.View == null)
{
throw new ArgumentNullException($"未找到视图{viewName}");
}
var viewDictionary =
new ViewDataDictionary(new EmptyModelMetadataProvider(), new ModelStateDictionary())
{
Model = model
};
var viewContext = new ViewContext(
actionContext,
viewResult.View,
viewDictionary,
new TempDataDictionary(actionContext.HttpContext,
HttpContext.RequestServices.GetService<ITempDataProvider>()),
sw,
new HtmlHelperOptions()
);
await viewResult.View.RenderAsync(viewContext);
return sw.ToString().HtmlDecode();
}
}
}
}

View File

@@ -0,0 +1,35 @@
using System;
using System.Threading.Tasks;
using Hncore.Infrastructure.Common;
using Hncore.Infrastructure.Common.DingTalk;
using Hncore.Infrastructure.Extension;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace Hncore.Infrastructure.WebApi
{
[Route("/pod/[action]")]
public class PodHookController : ControllerBase
{
[HttpGet, AllowAnonymous]
public async Task<IActionResult> PreStop()
{
LogHelper.Warn("应用即将退出");
if (EnvironmentVariableHelper.IsAspNetCoreProduction)
{
await DingTalkHelper.SendMessage(new MarkDownModel()
{
markdown = new markdown()
{
title = "应用即将退出",
text = "### 应用即将退出\n\nhostname" + EnvironmentVariableHelper.HostName + "\n\n" +
DateTime.Now.Format("yyyy-MM-dd HH:mm:ss")
}
});
}
return Ok();
}
}
}

View File

@@ -0,0 +1,274 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using Hncore.Infrastructure.Data;
using Hncore.Infrastructure.Extension;
using Newtonsoft.Json;
namespace Hncore.Infrastructure.WebApi
{
public class ApiResult
{
public ApiResult()
{
}
public ApiResult(object data):this(ResultCode.C_SUCCESS,"")
{
Data = data;
}
public ApiResult(ResultCode code = ResultCode.C_SUCCESS, string message = "")
{
Code = code;
Message = message;
}
[JsonProperty("Code")] public ResultCode Code { get; private set; }
[JsonProperty("Message")] public string Message { get; private set; } = "";
[JsonProperty("Data")] public virtual object Data { get; set; }
}
public class ApiResult<T>: ApiResult where T : class, new()
{
[JsonProperty("Data")] public new T Data { get; set; }
private static readonly Dictionary<Enum, string> Dic;
static ApiResult()
{
Dic = ObjectExtension.ToDescriptionDictionary<ResultCode>();
}
public ApiResult()
{
}
public ApiResult(T data) : this(ResultCode.C_SUCCESS, "")
{
Data = data;
}
public ApiResult(ResultCode code = ResultCode.C_SUCCESS, string message = "") : base(code, message)
{
}
//public ApiResult(ResultCode code = ResultCode.C_SUCCESS, string message = ""):base(code,message)
//{
// Code = code;
// if (string.IsNullOrEmpty(message) && Dic.ContainsKey(Code))
// {
// Message = Dic[Code];
// }
// else
// {
// Message = message;
// }
//}
}
//public class ApiResult : ApiResult<object>
//{
// public ApiResult(ResultCode code = ResultCode.C_SUCCESS, string message = "") : base(code, message)
// {
// }
//}
public class ApiResultPaged<T> : ApiResult<T> where T : class, new()
{
[JsonProperty("TotalCount")] public int TotalCount { get; set; }
public ApiResultPaged(ResultCode code = ResultCode.C_SUCCESS, string message = "") : base(code, message)
{
}
}
public static class PageDataExt
{
public static ApiResultPaged<List<T>> ToApiResult<T>(this PageData<T> pageData) where T : class, new()
{
return new ApiResultPaged<List<T>>()
{
TotalCount = pageData.RowCount,
Data = pageData.List
};
}
public static ApiResultPaged<List<T2>> ToApiResult<T1,T2>(this PageData<T1> pageData) where T2 : class, new()
{
return new ApiResultPaged<List<T2>>()
{
TotalCount = pageData.RowCount,
Data = pageData.List.MapsTo<T2>().ToList()
};
}
}
public enum ResultCode
{
/// <summary>
/// 未知错误
/// </summary>
[Description("服务正在更新中,请稍后再试")] C_UNKNOWN_ERROR = 0,
/// <summary>
/// 成功
/// </summary>
[Description("成功")] C_SUCCESS = 10000,
/// <summary>
/// 验证码
/// </summary>
[Description("验证码错误")] C_VERIFY_CODE_ERROR = 10001,
/// <summary>
/// 参数
/// </summary>
[Description("服务正在更新中,请稍后再试")] C_PARAM_ERROR = 10002,
/// <summary>
/// 登录名
/// </summary>
[Description("登录名错误")] C_LONGIN_NAME_ERROR = 10003,
/// <summary>
/// 密码
/// </summary>
[Description("密码错误")] C_PASSWORD_ERROR = 10004,
/// <summary>
/// 无效操作
/// </summary>
[Description("非法操作")] C_INVALID_ERROR = 10005,
/// <summary>
/// 文件
/// </summary>
[Description("文件错误")] C_FILE_ERROR = 10006,
/// <summary>
/// 已存在错误
/// </summary>
[Description("资源已存在错误")] C_ALREADY_EXISTS_ERROR = 10007,
/// <summary>
/// 资源无法访问:不是资源的拥有者
/// </summary>
[Description("不是资源的拥有者,资源无法访问")] C_OWNER_ERROR = 10008,
/// <summary>
/// 资源不存在
/// </summary>
[Description("资源不存在")] C_NOT_EXISTS_ERROR = 10009,
/// <summary>
/// 新建角色出错
/// </summary>
[Description("创建角色出错")] C_ROLE_CREATE_ERROR = 10010,
/// <summary>
/// 新建权限出错
/// </summary>
[Description("新建权限错误")] C_PERMISSION_CREATE_ERROR = 10011,
/// <summary>
/// 绑定角色和权限出错
/// </summary>
[Description("绑定角色和权限出错")] C_ROLE_PERMISSION_CREATE_ERROR = 10012,
/// <summary>
/// 服务器繁忙,请稍后再试!
/// </summary>
[Description("服务器繁忙")] C_Server_Is_Busy = 10013,
/// <summary>
/// 访问被禁止
/// </summary>
[Description("禁止访问")] C_Access_Forbidden = 10014,
/// <summary>
/// 非法操作
/// </summary>
[Description("非法操作")] C_Illegal_Operation = 10015,
/// <summary>
/// 无效的openID
/// </summary>
[Description("OpenID无效")] C_OPENID_ERROR = 10016,
/// <summary>
/// 返回错误,但无需理会
/// </summary>
[Description("可忽略的错误")] C_IGNORE_ERROR = 10017,
/// <summary>
/// 用户信息错误
/// </summary>
[Description("用户信息错误")] C_USERINFO_ERROR = 10018,
/// <summary>
/// 用户需要认证
/// </summary>
[Description("用户需要认证")] C_USER_SELECT_ERROR = 10019,
/// <summary>
/// 过期
/// </summary>
[Description("超时错误")] C_TIMEOUT_ERROR = 10020,
/// <summary>
/// 手机和验证码不匹配
/// </summary>
[Description("手机和验证码不匹配")] C_PHONE_CODE_ERROR = 10021,
/// <summary>
/// 微信没有选择楼
/// </summary>
[Description("微信没有选择楼")] C_WX_UNIT_UNSELECT_ERROR = 10022,
/// <summary>
/// 黑名单错误
/// </summary>
[Description("黑名单错误")] C_BLACKLIST_ERROR = 10023,
/// <summary>
/// 支付失败
/// </summary>
[Description("支付失败")] C_PAY_FAIL = 10024,
/// <summary>
/// 重复支付
/// </summary>
[Description("重复支付")] RepeatPay= 10025,
/// <summary>
/// 重定向
/// </summary>
[Description("重定向")] C_REDIRECT_URL = 100302,
[Description("用户重定向")] C_USER_REDIRECT_URL = 900302,
[Description("人脸已经存在")] C_FACEKEY_EXIST_ERROR = 900303,
[Description("人脸角度不正确")] C_FACE_ANGLE_ERROR = 900304,
[Description("退款失败")] C_PAY_Refund = 900305,
/// <summary>
/// 用户支付中
/// </summary>
[Description("用户支付中")] C_USERPAYING = 900306,
[Description("审核中")] C_VISITOR_CHECKING = 11001,
[Description("已过期")] C_VISITOR_OUTTIME = 11002,
[Description("未到期")] C_VISITOR_NOTYETDUE = 11003,
}
}

View File

@@ -0,0 +1,126 @@
using Hncore.Infrastructure.Extension;
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
namespace Hncore.Infrastructure.WebApi
{
/// <summary>
/// 请求顶级父类
/// </summary>
public class RequestBase
{
[JsonProperty("TenantId")]
[FromQuery(Name = "TenantId")]
public int? __tenantId { get; set; }
/// <summary>
/// 隶属物业数据库ID
/// </summary>
[JsonIgnore]
public int TenantId
{
get => __tenantId.ToInt();
set => __tenantId = value;
}
[JsonProperty("OperaterId")]
[FromQuery(Name = "OperaterId")]
public int? __operaterId { get; set; }
/// <summary>
/// 当前操作员数据库ID
/// </summary>
[JsonIgnore]
public int OperaterId
{
get => __operaterId.ToInt();
set => __operaterId = value;
}
[JsonProperty("ProjectCode")]
[FromQuery(Name = "ProjectCode")]
public int? __projectCode { get; set; }
/// <summary>
/// 隶属项目编码
/// </summary>
[JsonIgnore]
public int ProjectCode
{
get => __projectCode.ToInt();
set => __projectCode = value;
}
}
/// <summary>
/// 泛型请求父类(主要用于在请求时携带数据)
/// </summary>
/// <typeparam name="T">携带的数据类型</typeparam>
public class RequestBase<T> : RequestBase
{
/// <summary>
/// 请求携带的数据对象
/// </summary>
public T Data { get; set; }
}
/// <summary>
/// 分页请求父类(主要用于分页请求操作)
/// </summary>
///
public class PageRequestBase : RequestBase
{
[JsonProperty("PageIndex")]
[FromQuery(Name = "PageIndex")]
public int? __pageIndex { get; set; } = 1;
/// <summary>
/// 当前页码
/// </summary>
[JsonIgnore]
public int PageIndex
{
get => __pageIndex.ToInt();
set => __pageIndex = value;
}
[JsonProperty("PageSize")]
[FromQuery(Name = "PageSize")]
public int? __pageSize { get; set; } = 50;
/// <summary>
/// 每页条目数
/// </summary>
[JsonIgnore]
public int PageSize
{
get => __pageSize.ToInt();
set => __pageSize = value;
}
public string KeyWord { get; set; }
}
/// <summary>
/// 泛型分页请求父类(在分页请求的基础之上携带数据)
/// </summary>
/// <typeparam name="T">携带的数据类型</typeparam>
public class PageRequestBase<T> : PageRequestBase
{
/// <summary>
/// 请求携带的数据对象
/// </summary>
public T Data { get; set; }
}
/// <summary>
/// 主键ID查询请求类(主要用于根据一个主键ID查询单条数据的情况)
/// </summary>
public class QueryByIdRequest : RequestBase
{
/// <summary>
/// 记录的数据库主键ID
/// </summary>
public int Id { get; set; }
}
}

View File

@@ -0,0 +1,219 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using JWT;
namespace Hncore.Infrastructure.WebApi
{
public class ValidatorOption
{
public bool ValidateLifetime = true;
}
public sealed class EtorJwtValidator : IJwtValidator
{
private readonly IJsonSerializer _jsonSerializer;
private readonly IDateTimeProvider _dateTimeProvider;
private readonly ValidatorOption _option;
/// <summary>
/// Creates an instance of <see cref="JwtValidator" />
/// </summary>
/// <param name="jsonSerializer">The Json Serializer</param>
/// <param name="dateTimeProvider">The DateTime Provider</param>
public EtorJwtValidator(IJsonSerializer jsonSerializer, IDateTimeProvider dateTimeProvider,ValidatorOption option)
{
_jsonSerializer = jsonSerializer;
_dateTimeProvider = dateTimeProvider;
_option = option;
}
/// <inheritdoc />
/// <exception cref="ArgumentException" />
/// <exception cref="SignatureVerificationException" />
public void Validate(string payloadJson, string decodedCrypto, string decodedSignature)
{
var ex = GetValidationException(payloadJson, decodedCrypto, decodedSignature);
if (ex != null)
throw ex;
}
/// <inheritdoc />
/// <exception cref="ArgumentException" />
/// <exception cref="SignatureVerificationException" />
public void Validate(string payloadJson, string decodedCrypto, string[] decodedSignatures)
{
var ex = GetValidationException(payloadJson, decodedCrypto, decodedSignatures);
if (ex != null)
throw ex;
}
/// <summary>
/// Given the JWT, verifies its signature correctness without throwing an exception but returning it instead
/// </summary>
/// <param name="payloadJson">>An arbitrary payload (already serialized to JSON)</param>
/// <param name="decodedCrypto">Decoded body</param>
/// <param name="decodedSignature">Decoded signature</param>
/// <param name="ex">Validation exception, if any</param>
/// <returns>True if exception is JWT is valid and exception is null, otherwise false</returns>
public bool TryValidate(string payloadJson, string decodedCrypto, string decodedSignature, out Exception ex)
{
ex = GetValidationException(payloadJson, decodedCrypto, decodedSignature);
return ex is null;
}
/// <summary>
/// Given the JWT, verifies its signatures correctness without throwing an exception but returning it instead
/// </summary>
/// <param name="payloadJson">>An arbitrary payload (already serialized to JSON)</param>
/// <param name="decodedCrypto">Decoded body</param>
/// <param name="decodedSignature">Decoded signatures</param>
/// <param name="ex">Validation exception, if any</param>
/// <returns>True if exception is JWT is valid and exception is null, otherwise false</returns>
public bool TryValidate(string payloadJson, string decodedCrypto, string[] decodedSignature, out Exception ex)
{
ex = GetValidationException(payloadJson, decodedCrypto, decodedSignature);
return ex is null;
}
private Exception GetValidationException(string payloadJson, string decodedCrypto, string decodedSignature)
{
if (String.IsNullOrWhiteSpace(payloadJson))
return new ArgumentException(nameof(payloadJson));
if (String.IsNullOrWhiteSpace(decodedCrypto))
return new ArgumentException(nameof(decodedCrypto));
if (String.IsNullOrWhiteSpace(decodedSignature))
return new ArgumentException(nameof(decodedSignature));
if (!CompareCryptoWithSignature(decodedCrypto, decodedSignature))
return new SignatureVerificationException(decodedCrypto, decodedSignature);
return GetValidationException(payloadJson);
}
private Exception GetValidationException(string payloadJson, string decodedCrypto, string[] decodedSignatures)
{
if (String.IsNullOrWhiteSpace(payloadJson))
return new ArgumentException(nameof(payloadJson));
if (String.IsNullOrWhiteSpace(decodedCrypto))
return new ArgumentException(nameof(decodedCrypto));
if (AreAllDecodedSignaturesNullOrWhiteSpace(decodedSignatures))
return new ArgumentException(nameof(decodedSignatures));
if (!IsAnySignatureValid(decodedCrypto, decodedSignatures))
return new SignatureVerificationException(decodedCrypto, decodedSignatures);
return GetValidationException(payloadJson);
}
private Exception GetValidationException(string payloadJson)
{
if (!_option.ValidateLifetime)
{
return null;
}
var payloadData = _jsonSerializer.Deserialize<Dictionary<string, object>>(payloadJson);
var now = _dateTimeProvider.GetNow();
var secondsSinceEpoch = UnixEpoch.GetSecondsSince(now);
return ValidateExpClaim(payloadData, secondsSinceEpoch) ?? ValidateNbfClaim(payloadData, secondsSinceEpoch);
}
private static bool AreAllDecodedSignaturesNullOrWhiteSpace(string[] decodedSignatures) =>
decodedSignatures.All(sgn => String.IsNullOrWhiteSpace(sgn));
private static bool IsAnySignatureValid(string decodedCrypto, string[] decodedSignatures) =>
decodedSignatures.Any(decodedSignature => CompareCryptoWithSignature(decodedCrypto, decodedSignature));
/// <remarks>In the future this method can be opened for extension so made protected virtual</remarks>
private static bool CompareCryptoWithSignature(string decodedCrypto, string decodedSignature)
{
if (decodedCrypto.Length != decodedSignature.Length)
return false;
var decodedCryptoBytes = Encoding.UTF8.GetBytes(decodedCrypto);
var decodedSignatureBytes = Encoding.UTF8.GetBytes(decodedSignature);
byte result = 0;
for (var i = 0; i < decodedCrypto.Length; i++)
{
result |= (byte) (decodedCryptoBytes[i] ^ decodedSignatureBytes[i]);
}
return result == 0;
}
/// <summary>
/// Verifies the 'exp' claim.
/// </summary>
/// <remarks>See https://tools.ietf.org/html/rfc7515#section-4.1.4</remarks>
/// <exception cref="SignatureVerificationException" />
/// <exception cref="TokenExpiredException" />
private static Exception ValidateExpClaim(IDictionary<string, object> payloadData, double secondsSinceEpoch)
{
if (!payloadData.TryGetValue("exp", out var expObj))
return null;
if (expObj is null)
return new SignatureVerificationException("Claim 'exp' must be a number.");
double expValue;
try
{
expValue = Convert.ToDouble(expObj);
}
catch
{
return new SignatureVerificationException("Claim 'exp' must be a number.");
}
if (secondsSinceEpoch >= expValue)
{
return new TokenExpiredException("Token has expired.");
}
return null;
}
/// <summary>
/// Verifies the 'nbf' claim.
/// </summary>
/// <remarks>See https://tools.ietf.org/html/rfc7515#section-4.1.5</remarks>
/// <exception cref="SignatureVerificationException" />
private static Exception ValidateNbfClaim(IReadOnlyDictionary<string, object> payloadData,
double secondsSinceEpoch)
{
if (!payloadData.TryGetValue("nbf", out var nbfObj))
return null;
if (nbfObj is null)
return new SignatureVerificationException("Claim 'nbf' must be a number.");
double nbfValue;
try
{
nbfValue = Convert.ToDouble(nbfObj);
}
catch
{
return new SignatureVerificationException("Claim 'nbf' must be a number.");
}
if (secondsSinceEpoch < nbfValue)
{
return new SignatureVerificationException("Token is not yet valid.");
}
return null;
}
}
}

View File

@@ -0,0 +1,50 @@
using System;
using Hncore.Infrastructure.Common;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.Filters;
namespace Hncore.Infrastructure.WebApi
{
public abstract class AuthBase : Attribute, IAuthorizationFilter, IResourceFilter
{
public abstract void OnAuthorization(AuthorizationFilterContext context);
public void OnResourceExecuting(ResourceExecutingContext context)
{
if (!context.HasPassed() && !context.AllowAnonymous())
{
context.Reject();
}
}
public void OnResourceExecuted(ResourceExecutedContext context)
{
}
/// <summary>
/// 内部接口签名
/// </summary>
/// <param name="timestamp"></param>
/// <param name="randomstr"></param>
/// <returns></returns>
public static string CreateInternalApiSign(long timestamp, string randomstr)
{
string secret =
"1CD985F202645678FF1CE16BC14BCB9E.74562B91A1E851E9CEC9DA8BCE313DFE.EA6B2EFBDD4255A9F1B3BBC6399B58F4";
return SecurityHelper.GetMd5Hash($"{timestamp}{randomstr}{secret}");
}
/// <summary>
/// 创建第三方开放接口签名
/// </summary>
/// <param name="timestamp"></param>
/// <param name="randomstr"></param>
/// <param name="appKey"></param>
/// <returns></returns>
public static string CreateOpenApiSign(long timestamp, string randomstr, string appKey)
{
return SecurityHelper.GetMd5Hash($"{timestamp}{randomstr}{appKey}");
}
}
}

View File

@@ -0,0 +1,180 @@
using System.Linq;
using Hncore.Infrastructure.Extension;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Authorization;
using Microsoft.AspNetCore.Mvc.Filters;
namespace Hncore.Infrastructure.WebApi
{
public static class HttpContextExt
{
/// <summary>
/// 是否允许匿名访问
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
public static bool AllowAnonymous(this AuthorizationFilterContext context)
{
if (context.HttpContext.Items.ContainsKey("AllowAnonymous")
&& context.HttpContext.Items["AllowAnonymous"].ToBool())
{
return true;
}
if (context.Filters.Any(item => item is IAllowAnonymousFilter))
{
context.HttpContext.Items["AllowAnonymous"] = true;
return true;
}
return false;
}
/// <summary>
/// 是否允许匿名访问
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
public static bool AllowAnonymous(this ResourceExecutingContext context)
{
if (context.HttpContext.Items.ContainsKey("AllowAnonymous")
&& context.HttpContext.Items["AllowAnonymous"].ToBool())
{
return true;
}
if (context.Filters.Any(item => item is IAllowAnonymousFilter))
{
context.HttpContext.Items["AllowAnonymous"] = true;
return true;
}
return false;
}
/// <summary>
/// 是否已通过验证
/// </summary>
/// <returns></returns>
public static bool HasPassed(this AuthorizationFilterContext context)
{
if (context.HttpContext.Items.ContainsKey("AuthPassed") && context.HttpContext.Items["AuthPassed"].ToBool())
{
return true;
}
return false;
}
/// <summary>
/// 是否已通过验证
/// </summary>
/// <returns></returns>
public static bool HasPassed(this ResourceExecutingContext context)
{
if (context.HttpContext.Items.ContainsKey("AuthPassed") && context.HttpContext.Items["AuthPassed"].ToBool())
{
return true;
}
return false;
}
/// <summary>
/// 通过验证
/// </summary>
/// <param name="context"></param>
/// <param name="filterName">过滤器名称</param>
/// <returns></returns>
public static void SetPassed(this AuthorizationFilterContext context, string filterName)
{
context.HttpContext.Items["AuthPassed"] = true;
context.HttpContext.Items["AuthPassedFilterName"] = filterName;
}
/// <summary>
/// 拒绝通过
/// </summary>
/// <param name="context"></param>
/// <param name="reason">拒绝原因</param>
public static void Reject(this AuthorizationFilterContext context, string reason = "")
{
context.HttpContext.Response.StatusCode = 401;
context.Result = new JsonResult(new ApiResult(ResultCode.C_Access_Forbidden, reason));
}
/// <summary>
/// 拒绝通过并跳转
/// </summary>
/// <param name="context"></param>
/// <param name="reason"></param>
public static void RejectToRedirect(this AuthorizationFilterContext context, string url = "")
{
//context.HttpContext.Response.StatusCode = 401;
context.Result = new RedirectResult(url);
}
/// <summary>
/// 拒绝通过
/// </summary>
/// <param name="context"></param>
/// <param name="reason">拒绝原因</param>
public static void Reject(this ResourceExecutingContext context, string reason = "")
{
context.HttpContext.Response.StatusCode = 401;
context.Result = new JsonResult(new ApiResult(ResultCode.C_Access_Forbidden, reason));
}
/// <summary>
/// 是否包含内部调用验证信息
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
public static bool HasInternalApiAuthInfo(this AuthorizationFilterContext context)
{
if (!context.HttpContext.Request.Headers.ContainsKey("timestamp")
|| !context.HttpContext.Request.Headers.ContainsKey("randomstr")
|| !context.HttpContext.Request.Headers.ContainsKey("internalsign"))
{
return false;
}
return true;
}
/// <summary>
/// 是否包含第三方开放验证信息
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
public static bool HasOpenApiAuthInfo(this AuthorizationFilterContext context)
{
if (!context.HttpContext.Request.Headers.ContainsKey("timestamp")
|| !context.HttpContext.Request.Headers.ContainsKey("randomstr")
|| !context.HttpContext.Request.Headers.ContainsKey("appid")
|| !context.HttpContext.Request.Headers.ContainsKey("sign"))
{
return false;
}
return true;
}
/// <summary>
/// 是否包含token验证信息
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
public static bool HasTokenAuthInfo(this AuthorizationFilterContext context)
{
if (!context.HttpContext.Request.Headers.ContainsKey("token")&&!context.HttpContext.Request.Cookies.ContainsKey("token"))
{
return false;
}
return true;
}
}
}

View File

@@ -0,0 +1,70 @@
using System;
using System.Linq;
using System.Net.Http;
using Hncore.Infrastructure.Common;
using Hncore.Infrastructure.Data;
using Hncore.Infrastructure.Extension;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Authorization;
using Microsoft.AspNetCore.Mvc.Filters;
namespace Hncore.Infrastructure.WebApi
{
public class InternalApiAuthAttribute : AuthBase,IOrderedFilter
{
public int Order =>0;
public override void OnAuthorization(AuthorizationFilterContext context)
{
if (context.AllowAnonymous()
|| context.HasPassed()
|| !context.HasInternalApiAuthInfo())
{
return;
}
long.TryParse(context.HttpContext.Request.Headers["timestamp"], out long timestamp);
string randomstr = context.HttpContext.Request.Headers["randomstr"];
string sign = context.HttpContext.Request.Headers["internalsign"];
if (EnvironmentVariableHelper.IsAspNetCoreProduction)
{
long secondDiff = DateTimeHelper.ToUnixTimestamp(DateTime.Now) - timestamp;
if (secondDiff > 600 || secondDiff < -600)
{
context.Reject("时间戳已过期");
}
}
if (!String.Equals(sign, AuthBase.CreateInternalApiSign(timestamp, randomstr)
, StringComparison.CurrentCultureIgnoreCase))
{
context.Reject("签名错误");
}
context.SetPassed("InternalApiAuth");
}
}
public static class InternalApiAuthExt
{
public static HttpClient CreateInternalAuthClient(this IHttpClientFactory httpClientFactory)
{
var httpclient = httpClientFactory.CreateClient(TimeSpan.FromSeconds(10));
long timestamp = DateTimeHelper.ToUnixTimestamp(DateTime.Now);
string randomstr = Guid.NewGuid().ToString();
var sign = AuthBase.CreateInternalApiSign(timestamp, randomstr);
httpclient.DefaultRequestHeaders.Add("timestamp", timestamp.ToString());
httpclient.DefaultRequestHeaders.Add("randomstr", randomstr);
httpclient.DefaultRequestHeaders.Add("internalsign", sign);
return httpclient;
}
}
}

View File

@@ -0,0 +1,88 @@
using Hncore.Infrastructure.Common;
using Hncore.Infrastructure.Core.Web;
using Microsoft.AspNetCore.Mvc.Filters;
using System;
using System.Linq;
namespace Hncore.Infrastructure.WebApi.Filter
{
public enum LimitDimension
{
Ip,
TenantId,
AppId
}
public class LimitQosAttribute : AuthBase
{
private int _timeWindow;
private int _count;
private LimitDimension[] _dimensions;
public LimitQosAttribute(int timeWindow, int count, params LimitDimension[] dimensions)
{
_timeWindow = timeWindow;
_count = count;
_dimensions = dimensions;
}
public override void OnAuthorization(AuthorizationFilterContext context)
{
string url = context.HttpContext.Request.Path.ToString().ToLower();
string cacheKey = $"limitqos:{url}";
if (_dimensions.Contains(LimitDimension.Ip))
{
string ip = context.HttpContext.GetUserIp();
cacheKey += $":{ip}";
}
if (_dimensions.Contains(LimitDimension.TenantId))
{
var mangeInfo = context.HttpContext.Request.GetManageUserInfo();
if (mangeInfo == null)
{
return;
}
cacheKey += $":{mangeInfo.TenantId}";
}
if (_dimensions.Contains(LimitDimension.AppId))
{
if (!context.HttpContext.Items.ContainsKey("OpenAppId"))
{
return;
}
cacheKey += $":{context.HttpContext.Items["OpenAppId"]}";
}
var luaScript = $"return redis.call('CL.THROTTLE','{cacheKey}','{_count}','{_count}','{_timeWindow}','1')";
try
{
var res = RedisHelper.Eval(luaScript, cacheKey);
if (res is Array)
{
var arr = res as Array;
if (arr.Length == 5)
{
if (Convert.ToInt32(arr.GetValue(0)) != 0)
{
context.Reject($"超出{_timeWindow}秒{_count}次的请求限制");
}
}
}
}
catch (Exception e)
{
LogHelper.Error("reids异常", e);
}
}
}
}

View File

@@ -0,0 +1,148 @@
using Hncore.Infrastructure.Common;
using Hncore.Infrastructure.Extension;
using Hncore.Infrastructure.Serializer;
using JWT;
using JWT.Serializers;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json;
using System;
using System.Net.Http;
namespace Hncore.Infrastructure.WebApi
{
public class ManageAuthAttribute : AuthBase, IOrderedFilter
{
public int Order =>1;
public override void OnAuthorization(AuthorizationFilterContext context)
{
if (context.AllowAnonymous()
|| context.HasPassed()
|| !context.HasTokenAuthInfo())
{
return;
}
if (context.HttpContext.Request.GetManageUserInfo() == null)
{
context.Reject();
}
context.SetPassed("ManageAuth");
}
}
public class ManageUserInfo
{
[JsonProperty("LoginName")] public string LoginName { get; set; }
[JsonProperty("RoleName")] public string RoleName { get; set; }
[JsonProperty("OperaterID")] public int OperaterId { get; set; }
[JsonProperty("TenantId")] public int TenantId { get; set; }
[JsonProperty("DataDomain")] public int StoreId { get; set; }
[JsonProperty("exp")] public long ExpiredTimestamp { get; set; }
[JsonProperty("iat")] public long IssueTimestamp { get; set; }
[JsonProperty("OpenId")] public string OpenId { get; set; }
}
public static class HttpRequestExt
{
private static string _secret = "etor_yh_lzh_20f_2017_PETER";
public static void SetManageUserInfo(this HttpRequest request, ManageUserInfo manageUserInfo)
{
request.HttpContext.Items["ManageUserInfo"] = manageUserInfo;
}
public static ManageUserInfo GetManageUserInfo(this HttpRequest request)
{
if (!request.Headers.ContainsKey("token"))
{
return null;
}
if (request.HttpContext.Items.ContainsKey("ManageUserInfo"))
{
return request.HttpContext.Items["ManageUserInfo"] as ManageUserInfo;
}
string token = request.Headers["token"];
string storeId = request.Headers["sid"];
string payload = string.Empty;
try
{
IJsonSerializer serializer = new JsonNetSerializer();
IDateTimeProvider provider = new UtcDateTimeProvider();
IJwtValidator validator = new JwtValidator(serializer, provider);
IBase64UrlEncoder urlEncoder = new JwtBase64UrlEncoder();
IJwtDecoder decoder = new JwtDecoder(serializer, validator, urlEncoder);
payload = decoder.Decode(token, _secret, verify: true);
if (string.IsNullOrEmpty(payload))
{
return null;
}
try
{
ManageUserInfo manageUserInfo = payload.FromJsonTo<ManageUserInfo>();
if (manageUserInfo == null || manageUserInfo.TenantId == 0)
{
return null;
}
if (manageUserInfo.IssueTimestamp == 0
|| DateTimeHelper.UnixTimeStampToDateTime(manageUserInfo.IssueTimestamp) <
DateTime.Now.AddHours(-4))
{
return null;
}
if(storeId.Has())
{
manageUserInfo.StoreId = Convert.ToInt32(storeId);
}
request.SetManageUserInfo(manageUserInfo);
return manageUserInfo;
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
return null;
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
return null;
}
}
public static HttpClient CreateManageAuthHttpClient(this HttpRequest request)
{
var httpclient = request.HttpContext.RequestServices
.GetService<IHttpClientFactory>()
.CreateClient(TimeSpan.FromMinutes(1));
httpclient.DefaultRequestHeaders.Add("token", request.Headers["token"].ToString());
httpclient.DefaultRequestHeaders.Add("sid", request.Headers["sid"].ToString());
return httpclient;
}
}
}

View File

@@ -0,0 +1,166 @@
using Hncore.Infrastructure.Common;
using Hncore.Infrastructure.Extension;
using Hncore.Infrastructure.Serializer;
using JWT;
using JWT.Serializers;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Net.Http.Headers;
using Newtonsoft.Json;
using System;
using System.Net.Http;
namespace Hncore.Infrastructure.WebApi
{
public class UserAuthAttribute : AuthBase, IOrderedFilter
{
public int Order => 0;
public override void OnAuthorization(AuthorizationFilterContext context)
{
if (context.HasPassed())//context.AllowAnonymous()||
{
return;
}
if (context.HttpContext.Request.GetUserInfo() == null)
{
// context.Reject();
context.HttpContext.Response.Cookies.Delete("token");
context.HttpContext.Response.Cookies.Delete("userInfo");
var userAgent = context.HttpContext.Request.Headers[HeaderNames.UserAgent].ToString().ToLower();
if (userAgent.IndexOf("micromessenger") == -1)
{
context.RejectToRedirect("/User/WebLogin");
}
else
{
var url = context.HttpContext.Request.GetUrl().UrlEncode();
context.RejectToRedirect($"/User/MP_GetUserInfo?appid=wx18e5b4f42773c3ec&callbakUrl={url}");
}
}
context.SetPassed("UserAuth");
}
}
public class AppUserInfo
{
[JsonProperty("LoginName")] public string LoginName { get; set; }
[JsonProperty("Name")] public string Name { get; set; }
[JsonProperty("RoleName")] public string RoleName { get; set; }
[JsonProperty("UserId")] public int UserId { get; set; }
[JsonProperty("TenantId")] public int TenantId { get; set; }
[JsonProperty("DataDomain")] public int[] DataDomain { get; set; }
[JsonProperty("exp")] public long ExpiredTimestamp { get; set; }
[JsonProperty("iat")] public long IssueTimestamp { get; set; }
[JsonProperty("OpenId")] public string OpenId { get; set; }
[JsonProperty("AppType")] public string AppType { get; set; }
[JsonProperty("AppId")] public string AppId { get; set; }
[JsonProperty("StoreId")] public int StoreId { get; set; }
}
public static class HttpRequestExt1
{
private static string _secret = "hncore_yh_lzh_20f_2017_PETER";
public static void SetUserInfo(this HttpRequest request, AppUserInfo manageUserInfo)
{
request.HttpContext.Items["UserInfo"] = manageUserInfo;
}
public static AppUserInfo GetUserInfo(this HttpRequest request)
{
if (!request.Headers.ContainsKey("token")&& !request.Cookies.ContainsKey("token"))
{
return null;
}
if (request.HttpContext.Items.ContainsKey("UserInfo"))
{
return request.HttpContext.Items["UserInfo"] as AppUserInfo;
}
if(!request.Cookies.TryGetValue("token",out string token))
{
token = request.Headers["token"];
}
string payload = string.Empty;
try
{
IJsonSerializer serializer = new JsonNetSerializer();
IDateTimeProvider provider = new UtcDateTimeProvider();
IJwtValidator validator = new JwtValidator(serializer, provider);
IBase64UrlEncoder urlEncoder = new JwtBase64UrlEncoder();
IJwtDecoder decoder = new JwtDecoder(serializer, validator, urlEncoder);
payload = decoder.Decode(token, _secret, verify: true);
if (string.IsNullOrEmpty(payload))
{
request.HttpContext.Response.Cookies.Delete("token");
request.HttpContext.Response.Cookies.Delete("userInfo");
return null;
}
try
{
AppUserInfo manageUserInfo = payload.FromJsonTo<AppUserInfo>();
if (manageUserInfo == null)
{
return null;
}
if (manageUserInfo.IssueTimestamp == 0
|| DateTimeHelper.UnixTimeStampToDateTime(manageUserInfo.IssueTimestamp) <
DateTime.Now.AddHours(-4))
{
return null;
}
request.SetUserInfo(manageUserInfo);
return manageUserInfo;
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
request.HttpContext.Response.Cookies.Delete("token");
request.HttpContext.Response.Cookies.Delete("userInfo");
return null;
}
}
catch(Exception ex)
{
Console.WriteLine(ex.Message);
request.HttpContext.Response.Cookies.Delete("token");
request.HttpContext.Response.Cookies.Delete("userInfo");
return null;
}
}
public static HttpClient CreateManageAuthHttpClient(this HttpRequest request)
{
var httpclient = request.HttpContext.RequestServices
.GetService<IHttpClientFactory>()
.CreateClient(TimeSpan.FromMinutes(1));
httpclient.DefaultRequestHeaders.Add("token", request.Headers["token"].ToString());
return httpclient;
}
}
}

View File

@@ -0,0 +1,106 @@
using Swashbuckle.AspNetCore.Swagger;
using Swashbuckle.AspNetCore.SwaggerGen;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Text;
namespace Hncore.Infrastructure.WebApi.Filter
{
public class SwaggerAddEnumDescriptionsDocumentFilter : IDocumentFilter
{
public void Apply(SwaggerDocument swaggerDoc, DocumentFilterContext context)
{
// add enum descriptions to result models
// 将枚举加到返回对象的描述中json.definitions对象里的枚举
foreach (KeyValuePair<string, Schema> schemaDictionaryItem in swaggerDoc.Definitions)
{
Schema schema = schemaDictionaryItem.Value;
foreach (KeyValuePair<string, Schema> propertyDictionaryItem in schema.Properties)
{
Schema property = propertyDictionaryItem.Value;
IList<object> propertyEnums = property.Enum;
if (propertyEnums != null && propertyEnums.Count > 0)
{
property.Description += DescribeEnum(propertyEnums);
}
}
}
// add enum descriptions to input parameters
if (swaggerDoc.Paths.Count > 0)
{
foreach (PathItem pathItem in swaggerDoc.Paths.Values)
{
DescribeEnumParameters(pathItem.Parameters);
// head, patch, options, delete left out
List<Operation> possibleParameterisedOperations = new List<Operation> { pathItem.Get, pathItem.Post, pathItem.Put };
possibleParameterisedOperations.FindAll(x => x != null).ForEach(x => DescribeEnumParameters(x.Parameters));
}
}
}
private void DescribeEnumParameters(IList<IParameter> parameters)
{
if (parameters != null)
{
foreach (var param in parameters)
{
if (param.In == "path")
{
var nonParam = (NonBodyParameter)param;
IList<object> paramEnums = nonParam.Enum;
if (paramEnums != null && paramEnums.Count > 0)
{
param.Description +=""+ DescribeEnum(paramEnums);
}
}
if (param.In == "body")
{
var bodyParam = (BodyParameter)param;
Schema property = bodyParam.Schema;
IList<object> propertyEnums = property.Enum;
if (propertyEnums != null && propertyEnums.Count > 0)
{
property.Description += "" + DescribeEnum(propertyEnums);
}
}
if (param.In == "query")
{
var nonParam = (NonBodyParameter)param;
IList<object> paramEnums = nonParam.Enum;
if (paramEnums != null && paramEnums.Count > 0)
{
param.Description += "" + DescribeEnum(paramEnums);
}
}
}
}
}
/// <summary>
/// 枚举转换成值和描述
/// </summary>
/// <param name="enums"></param>
/// <returns></returns>
private string DescribeEnum(IList<object> enums)
{
List<string> enumDescriptions = new List<string>();
foreach (object item in enums)
{
var type = item.GetType();
var objArr = type.GetField(item.ToString()).GetCustomAttributes(typeof(DisplayAttribute), true);
if (objArr != null && objArr.Length > 0)
{
DisplayAttribute da = objArr[0] as DisplayAttribute;
enumDescriptions.Add($"{(int)item} {da.Name}");
}
else
{
enumDescriptions.Add(string.Format("{0} = {1}", (int)item, Enum.GetName(item.GetType(), item)));
}
}
return string.Join(", ", enumDescriptions.ToArray());
}
}
}

View File

@@ -0,0 +1,53 @@
using Microsoft.AspNetCore.JsonPatch.Operations;
using Swashbuckle.AspNetCore.Swagger;
using Swashbuckle.AspNetCore.SwaggerGen;
using System.ComponentModel.DataAnnotations;
using System.Linq;
namespace Hncore.Infrastructure.WebApi.Filter
{
public class SwaggerOperationFilter : IOperationFilter
{
/// <summary>
/// 应用过滤器
/// </summary>
/// <param name="operation"></param>
/// <param name="context"></param>
public void Apply(Swashbuckle.AspNetCore.Swagger.Operation operation, OperationFilterContext context)
{
#region Swagger版本描述处理
foreach (var parameter in operation.Parameters.OfType<NonBodyParameter>())
{
//var description = context.ApiDescription.ParameterDescriptions.First(p => p.Name == parameter.Name);
if (parameter.Name == "version")
{
parameter.Description = "填写版本号如:1、2";
parameter.Default = context.ApiDescription.GroupName.Replace("v", "");
}
//if (parameter.Enum!=null&&parameter.Enum.Count > 0)
//{
// string desc = "";
// foreach(var item in parameter.Enum)
// {
// var type = item.GetType();
// var objArr=type.GetField(item.ToString()).GetCustomAttributes(typeof(DisplayAttribute), true);
// if (objArr != null && objArr.Length > 0)
// {
// DisplayAttribute da = objArr[0] as DisplayAttribute;
// desc += $"{item} {da.Name}";
// }
// }
// parameter.Description = desc;
//}
}
#endregion
var auth = context.MethodInfo
.GetCustomAttributes(true)
.OfType<ManageAuthAttribute>();
}
}
}

View File

@@ -0,0 +1,46 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using System;
using System.Collections.Generic;
using System.Net;
using System.Text;
namespace Hncore.Infrastructure.WebApi.Filter
{
public class ValidateModelAttribute : ActionFilterAttribute
{
/// <summary>
/// netcore 会自动判断context.ModelState.IsValid
/// 如果是false自动返回BadRequestObjectResult
/// 需要在OnResultExecuting重写返回值
/// </summary>
/// <param name="context"></param>
public override void OnActionExecuting(ActionExecutingContext context)
{
//var httpContext = context.HttpContext;
//if (context.ModelState.IsValid == false)
//{
// context.Result = new JsonResult(context.ModelState);
// //httpContext.Response = httpContext.Request.CreateErrorResponse(HttpStatusCode.BadRequest, context.ModelState);
//}
}
public override void OnResultExecuting(ResultExecutingContext context)
{
var httpContext = context.HttpContext;
if (context.ModelState.IsValid == false)
{
var message = new List<string>(); ;
foreach(var item in context.ModelState.Values)
{
foreach (var error in item.Errors)
{
message.Add( error.ErrorMessage );
}
}
var apiRes = new ApiResult(ResultCode.C_PARAM_ERROR, string.Join("|", message));
context.Result = new JsonResult(apiRes);
//httpContext.Response = httpContext.Request.CreateErrorResponse(HttpStatusCode.BadRequest, context.ModelState);
}
}
}
}

View File

@@ -0,0 +1,7 @@
namespace Hncore.Infrastructure.WebApi
{
internal class GlobalData
{
public static bool UseGlobalManageAuthFilter { get; set; }
}
}

View File

@@ -0,0 +1,103 @@
using System;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Hncore.Infrastructure.Common;
using Hncore.Infrastructure.Data;
using Hncore.Infrastructure.Extension;
using Hncore.Infrastructure.OpenApi;
using Hncore.Infrastructure.Serializer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Hncore.Infrastructure.Core.Web;
namespace Hncore.Infrastructure.WebApi
{
/// <summary>
/// 统一错误异常处理中间件类
/// </summary>
///
public class ErrorHandlingMiddleware
{
private readonly RequestDelegate next;
public ErrorHandlingMiddleware(RequestDelegate next)
{
this.next = next;
}
public async Task Invoke(HttpContext context)
{
try
{
await next(context);
}
catch (Exception ex)
{
string requestMsg = "请求URL" + context.Request.GetAbsoluteUri() + "";
requestMsg += "\nMethod" + context.Request.Method + "\n";
if (context.Request.Method.ToLower() != "get")
{
var requestBody = await context.Request.ReadBodyAsStringAsync();
requestMsg += "Body\n" + requestBody +
"\n------------------------\n";
}
else
{
requestMsg += "\n------------------------\n";
}
await HandleExceptionAsync(context, ex, requestMsg);
}
}
private static Task HandleExceptionAsync(HttpContext context, Exception ex,
string requestMsg)
{
ResultCode code = ResultCode.C_UNKNOWN_ERROR;
string msg = "";
if (ex is BusinessException bex)
{
code = bex.Code;
msg = bex.Message;
LogHelper.Error($"业务异常,{msg}", requestMsg + ex);
}
else
{
if (EnvironmentVariableHelper.IsAspNetCoreProduction)
{
msg = "系统繁忙,请稍后再试";
}
else
{
msg = ex.Message;
}
LogHelper.Error($"未知异常,{ex.Message}", requestMsg + ex);
}
var data = new ApiResult(code, msg);
var result = data.ToJson();
context.Response.ContentType = "application/json;charset=utf-8";
return context.Response.WriteAsync(result);
}
}
public static class ErrorHandlingExtensions
{
public static IApplicationBuilder UseErrorHandling(this IApplicationBuilder builder)
{
return builder.UseMiddleware<ErrorHandlingMiddleware>();
}
}
}

View File

@@ -0,0 +1,76 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using Hncore.Infrastructure.Common;
using Hncore.Infrastructure.Common.DingTalk;
using Hncore.Infrastructure.Extension;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Logging;
using NLog.Extensions.Logging;
namespace Hncore.Infrastructure.WebApi
{
/// <summary>
/// 应用程序构建器扩展类
/// </summary>
///
public static class ApplicationBuilderExtend
{
/// <summary>
/// 初始化应用程序构建器
/// </summary>
/// <param name="app">当前应用程序构建器对象</param>
/// <param name="loggerFactory">日志工厂对象</param>
/// <returns>初始化后的应用程序构建器对象</returns>
/// <param name="applicationLifetime">应用程序生命周期对象</param>
///
public static IApplicationBuilder Init(this IApplicationBuilder app, ILoggerFactory loggerFactory,
IApplicationLifetime applicationLifetime)
{
loggerFactory.AddNLog(); //启用Nlog日志插件
app.UseErrorHandling(); //添加统一错误异常处理中间件(一个自定义类)
//启用Cors(跨域请求)支持(默认关闭状态)
app.UseCors(builder => builder
.SetIsOriginAllowed(host => true) //允许所有来源
.AllowAnyMethod() //允许任何请求方法(GET、POST、PUT、DELETE等)
.AllowAnyHeader() //允许任何请求头信息
.AllowCredentials() //允许跨域凭据
// .SetPreflightMaxAge(TimeSpan.FromDays(30)) //指定可以缓存预检请求的响应的时间为30天
.WithExposedHeaders("X-Suggested-Filename", "set-user-token", "set-user")
);
app.UseMvc(); //启用MVC
//向应用程序生命周期的“应用程序已完全启动”事件注册回调函数
applicationLifetime.ApplicationStarted.Register(OnAppStarted);
return app;
}
/// <summary>
/// 应用程序完全启动完成处理回调函数
/// </summary>
///
private static void OnAppStarted()
{
//if (EnvironmentVariableHelper.IsAspNetCoreProduction)
//{
// DingTalkHelper.SendMessage(new MarkDownModel()
// {
// markdown = new markdown()
// {
// title = "应用已启动",
// text = "### 应用已启动\n\nhostname" + EnvironmentVariableHelper.HostName + "\n\n" +
// DateTime.Now.Format("yyyy-MM-dd HH:mm:ss")
// }
// });
//}
}
}
}

View File

@@ -0,0 +1,42 @@
using System;
using System.Runtime;
using Hncore.Infrastructure.Serializer;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
namespace Hncore.Infrastructure.WebApi
{
public static class HostingEnvironmentExtend
{
public static IConfigurationRoot UseAppsettings(this IHostingEnvironment env)
{
Console.WriteLine("环境:" + Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT"));
#if DEBUG
Console.WriteLine("模式DEBUG");
#endif
#if RELEASE
Console.WriteLine("模式RELEASE");
#endif
Console.WriteLine("GC模式:" + new
{
IsServerGC = GCSettings.IsServerGC,
LargeObjectHeapCompactionMode = GCSettings.LargeObjectHeapCompactionMode.ToString(),
LatencyMode = GCSettings.LatencyMode.ToString()
}.ToJson(true));
System.Text.Encoding.RegisterProvider(System.Text.CodePagesEncodingProvider.Instance);
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: false)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: false)
.AddEnvironmentVariables();
var config = builder.Build();
return config;
}
}
}

View File

@@ -0,0 +1,113 @@
using System;
using Hncore.Infrastructure.Autofac;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json;
namespace Hncore.Infrastructure.WebApi
{
/// <summary>
/// 服务集合对象(可以理解为内置的依赖注入容器)功能扩展类
/// </summary>
///
public static class ServiceCollectionExtend
{
/// <summary>
/// 通用初始化方法(被各个微服务项目所引用)
/// </summary>
/// <param name="services">服务集合对象</param>
/// <param name="configuration">配置信息对象</param>
/// <param name="version">.net core兼容版本</param>
/// <param name="serviceOption">服务自定义选项对象</param>
/// <returns>服务提供者对象</returns>
///
public static IServiceProvider Init(this IServiceCollection services, IConfiguration configuration,
CompatibilityVersion version, ServiceOption serviceOption = null)
{
//启用选项配置服务
services.AddOptions();
//将配置信息对象以单例模式添加到服务集合中
services.AddSingleton(configuration);
// services.AddCors();
var mvcbuilder = services
.AddMvc(options =>
{
options.EnableEndpointRouting = false; //关闭终端点路由
if (serviceOption != null && serviceOption.UseGlobalManageAuthFilter)
{
//如果配置并传递了自定义选项中的全局授权过滤器就将全局授权过滤器添加到MVC全局过滤器链中让其生效
options.Filters.Add(new ManageAuthAttribute());
GlobalData.UseGlobalManageAuthFilter = true;
}
else
{
GlobalData.UseGlobalManageAuthFilter = false;
}
})
.SetCompatibilityVersion(version) //设置.net core兼容版本号
.AddJsonOptions(options =>
{
//使用NewtonsoftJson插件替换掉系统默认提供的JSON插件
options.SerializerSettings.ContractResolver =
new Newtonsoft.Json.Serialization.DefaultContractResolver();
//定义日期格式化策略
options.SerializerSettings.DateFormatHandling = DateFormatHandling.MicrosoftDateFormat;
options.SerializerSettings.DateTimeZoneHandling = DateTimeZoneHandling.Local;
//定义循环引用处理策略(就是要序列化的对象类A中引用了BB引用了C……转了一圈儿之后又直接或间接引用回了A就称为循环引用)
options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
//序列化或反序列化时,可接受的日期时间字符串格式
options.SerializerSettings.DateFormatString = "yyyy-MM-dd HH:mm:ss";
if (serviceOption != null && serviceOption.IgnoreJsonNullValue)
{
//如果配置并传递了自定义选项中的忽略空值选项就在操作JSON时忽略掉空值数据
//忽略掉空值的意思就是如果对象中的某个属性值为null它将不会出现在最终序列化后的JSON字符串中
options.SerializerSettings.NullValueHandling = NullValueHandling.Ignore;
}
});
services.AddApiVersioning(option =>
{
//设置API版本信息
option.ReportApiVersions = true; //在向客户端响应的响应头中显示受支持的API版本信息
option.AssumeDefaultVersionWhenUnspecified = true; //如果客户端未提供并指定要调用的API版本就以下方的默认版本为准
option.DefaultApiVersion = new ApiVersion(1, 0); //默认版本号
});
//启用HTTP请求上下文访问器(用来访问类似传统MVC中的那个HttpContext对象)
services.AddHttpContextAccessor();
//构建并返回服务提供对象(在Build方法中主要用Autofac插件替换掉了默认的依赖注入插件并做一些自动扫描配置)
return new MvcAutoRegister().Build(services, mvcbuilder);
}
}
/// <summary>
/// 服务自定义配置类(承载一些自定义配置)
/// </summary>
///
public class ServiceOption
{
/// <summary>
/// 是否启用全局授权过滤器(默认关闭)
/// </summary>
public bool UseGlobalManageAuthFilter { get; set; } = false;
/// <summary>
/// 是否在生成的JSON字符串中忽略掉为null的属性或字段(默认为false意思就是就算字段为null也会出现在最终序列化后的JSON字符串中)
/// </summary>
public bool IgnoreJsonNullValue { get; set; } = false;
}
}

View File

@@ -0,0 +1,71 @@
using Microsoft.AspNetCore.Http;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
namespace Hncore.Infrastructure.Core.Web
{
public static class HttpContextExtension
{
public static string GetUserIp(this HttpContext context)
{
var ip = context.Request.Headers["X-Forwarded-For"].FirstOrDefault();
if (string.IsNullOrEmpty(ip))
{
ip = context.Connection.RemoteIpAddress.ToString();
}
return ip;
}
public static string GetAbsoluteUri(this HttpRequest request)
{
return new StringBuilder()
.Append(request.Scheme)
.Append("://")
.Append(request.Host)
.Append(request.PathBase)
.Append(request.Path)
.Append(request.QueryString)
.ToString();
}
public static async Task<string> ReadBodyAsStringAsync(this HttpRequest request)
{
request.EnableBuffering();
var requestReader = new StreamReader(request.Body);
var requestBody = await requestReader.ReadToEndAsync();
request.Body.Position = 0;
return requestBody;
}
}
public static class IsLocalExtension
{
private const string NullIpAddress = "::1";
public static bool IsLocal(this HttpRequest req)
{
var connection = req.HttpContext.Connection;
if (connection.RemoteIpAddress.IsSet())
{
return connection.LocalIpAddress.IsSet()
? connection.RemoteIpAddress.Equals(connection.LocalIpAddress)
: IPAddress.IsLoopback(connection.RemoteIpAddress);
}
return true;
}
private static bool IsSet(this IPAddress address)
{
return address != null && address.ToString() != NullIpAddress;
}
}
}