zoukankan      html  css  js  c++  java
  • [开源]快速构建一个WebApi项目

    1. 项目代码:MasterChief.DotNet.ProjectTemplate.WebApi
    2. 示例代码:https://github.com/YanZhiwei/MasterChief.ProjectTemplate.WebApiSample
    3. Nuget : Install-Package MasterChief.DotNet.ProjectTemplate.WebApi
    4. 实现WebApi开发中诸如授权验证,缓存,参数验证,异常处理等,方便快速构建项目而无需过多关心技术细节;
    5. 欢迎Star,欢迎Issues;

    目录

    Created by gh-md-toc

    授权

    1. 授权接口,通过该接口自定义授权实现,项目默认实现基于Jwt授权

      /// <summary>
      ///     WebApi 授权接口
      /// </summary>
      public interface IApiAuthorize
      {
          /// <summary>
          ///     检查请求签名合法性
          /// </summary>
          /// <param name="signature">加密签名字符串</param>
          /// <param name="timestamp">时间戳</param>
          /// <param name="nonce">随机数</param>
          /// <param name="appConfig">应用接入配置信息</param>
          /// <returns>CheckResult</returns>
          CheckResult CheckRequestSignature(string signature, string timestamp, string nonce, AppConfig appConfig);
       
       
          /// <summary>
          ///     创建合法用户获取访问令牌接口数据
          /// </summary>
          /// <param name="identityUser">IdentityUser</param>
          /// <param name="appConfig">AppConfig</param>
          /// <returns>IdentityToken</returns>
          ApiResult<IdentityToken> CreateIdentityToken(IdentityUser identityUser, AppConfig appConfig);
      }
      
    2. 基于Jwt授权实现

      /// <summary>
      ///     基于Jwt 授权实现
      /// </summary>
      public sealed class JwtApiAuthorize : IApiAuthorize
      {
          /// <summary>
          ///     检查请求签名合法性
          /// </summary>
          /// <param name="signature">加密签名字符串</param>
          /// <param name="timestamp">时间戳</param>
          /// <param name="nonce">随机数</param>
          /// <param name="appConfig">应用接入配置信息</param>
          /// <returns>CheckResult</returns>
          public CheckResult CheckRequestSignature(string signature, string timestamp, string nonce, AppConfig appConfig)
          {
              ValidateOperator.Begin()
                  .NotNullOrEmpty(signature, "加密签名字符串")
                  .NotNullOrEmpty(timestamp, "时间戳")
                  .NotNullOrEmpty(nonce, "随机数")
                  .NotNull(appConfig, "AppConfig");
              var appSecret = appConfig.AppSecret;
              var signatureExpired = appConfig.SignatureExpiredMinutes;
              string[] data = {appSecret, timestamp, nonce};
              Array.Sort(data);
              var signatureText = string.Join("", data);
              signatureText = Md5Encryptor.Encrypt(signatureText);
       
              if (!signature.CompareIgnoreCase(signatureText) && CheckHelper.IsNumber(timestamp))
                  return CheckResult.Success();
              var timestampMillis =
                  UnixEpochHelper.DateTimeFromUnixTimestampMillis(timestamp.ToDoubleOrDefault());
              var minutes = DateTime.UtcNow.Subtract(timestampMillis).TotalMinutes;
       
              return minutes > signatureExpired ? CheckResult.Fail("签名时间戳失效") : CheckResult.Success();
          }
       
          /// <summary>
          ///     创建合法用户获取访问令牌接口数据
          /// </summary>
          /// <param name="identityUser">IdentityUser</param>
          /// <param name="appConfig">AppConfig</param>
          /// <returns>IdentityToken</returns>
          public ApiResult<IdentityToken> CreateIdentityToken(IdentityUser identityUser, AppConfig appConfig)
          {
              ValidateOperator.Begin()
                  .NotNull(identityUser, "IdentityUser")
                  .NotNull(appConfig, "AppConfig");
              var payload = new Dictionary<string, object>
              {
                  {"iss", identityUser.UserId},
                  {"iat", UnixEpochHelper.GetCurrentUnixTimestamp().TotalSeconds}
              };
              var identityToken = new IdentityToken
              {
                  AccessToken = CreateIdentityToken(appConfig.SharedKey, payload),
                  ExpiresIn = appConfig.TokenExpiredDay * 24 * 3600
              };
              return ApiResult<IdentityToken>.Success(identityToken);
          }
       
          /// <summary>
          ///     创建Token
          /// </summary>
          /// <param name="secret">密钥</param>
          /// <param name="payload">负载数据</param>
          /// <returns>Token令牌</returns>
          public static string CreateIdentityToken(string secret, Dictionary<string, object> payload)
          {
              ValidateOperator.Begin().NotNull(payload, "负载数据").NotNullOrEmpty(secret, "密钥");
              IJwtAlgorithm algorithm = new HMACSHA256Algorithm();
              IJsonSerializer serializer = new JsonNetSerializer();
              IBase64UrlEncoder urlEncoder = new JwtBase64UrlEncoder();
              IJwtEncoder encoder = new JwtEncoder(algorithm, serializer, urlEncoder);
              return encoder.Encode(payload, secret);
          }
      }
      

    鉴权

    1. Token令牌鉴定接口,通过该接口可以自定义扩展实现方式,项目默认实现基于Jwt鉴权

      /// <summary>
      ///     webApi 验证系统基本接口
      /// </summary>
      public interface IApiAuthenticate
      {
          #region Methods
       
          /// <summary>
          ///     验证Token令牌是否合法
          /// </summary>
          /// <param name="token">令牌</param>
          /// <param name="appConfig">AppConfig</param>
          /// <returns>CheckResult</returns>
          ApiResult<string> CheckIdentityToken(string token, AppConfig appConfig);
       
          #endregion Methods
      }
      
    2. 基于Jwt鉴权实现

      /// <summary>
      ///     基于Jwt 授权验证实现
      /// </summary>
      public sealed class JwtApiAuthenticate : IApiAuthenticate
      {
          /// <summary>
          ///     检查Token是否合法
          /// </summary>
          /// <param name="token">用户令牌</param>
          /// <param name="appConfig">AppConfig</param>
          /// <returns></returns>
          public ApiResult<string> CheckIdentityToken(string token, AppConfig appConfig)
          {
              ValidateOperator.Begin()
                  .NotNullOrEmpty(token, "Token")
                  .NotNull(appConfig, "AppConfig");
              try
              {
                  var tokenText = ParseTokens(token, appConfig.SharedKey);
                  if (string.IsNullOrEmpty(tokenText))
                      return ApiResult<string>.Fail("用户令牌Token为空");
       
                  dynamic root = JObject.Parse(tokenText);
                  string userid = root.iss;
                  double iat = root.iat;
                  var validTokenExpired =
                      new TimeSpan((int) (UnixEpochHelper.GetCurrentUnixTimestamp().TotalSeconds - iat))
                          .TotalDays > appConfig.TokenExpiredDay;
                  return validTokenExpired
                      ? ApiResult<string>.Fail($"用户ID{userid}令牌失效")
                      : ApiResult<string>.Success(userid);
              }
              catch (FormatException)
              {
                  return ApiResult<string>.Fail("用户令牌非法");
              }
              catch (SignatureVerificationException)
              {
                  return ApiResult<string>.Fail("用户令牌非法");
              }
          }
       
          /// <summary>
          ///     转换Token
          /// </summary>
          /// <param name="token">令牌</param>
          /// <param name="secret">密钥</param>
          /// <returns>Token以及负载数据</returns>
          private string ParseTokens(string token, string secret)
          {
              ValidateOperator.Begin()
                  .NotNullOrEmpty(token, "令牌")
                  .NotNullOrEmpty(secret, "密钥");
       
              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);
              return decoder.Decode(token, secret, true);
          }
      }
      

    授权与鉴权使用

    1. 授权使用,通过Controller构造函数方式,代码如下

      /// <summary>
      ///     Api授权
      /// </summary>
      public abstract class AuthorizeController : ApiBaseController
      {
          #region Constructors
       
          /// <summary>
          ///     构造函数
          /// </summary>
          /// <param name="apiAuthorize">IApiAuthorize</param>
          /// <param name="appCfgService">IAppConfigService</param>
          protected AuthorizeController(IApiAuthorize apiAuthorize, IAppConfigService appCfgService)
          {
              ValidateOperator.Begin()
                  .NotNull(apiAuthorize, "IApiAuthorize")
                  .NotNull(appCfgService, "IAppConfigService");
              ApiAuthorize = apiAuthorize;
              AppCfgService = appCfgService;
          }
       
          #endregion Constructors
       
          #region Fields
       
          /// <summary>
          ///     授权接口
          /// </summary>
          protected readonly IApiAuthorize ApiAuthorize;
       
          /// <summary>
          ///     请求通道配置信息,可以从文件或者数据库获取
          /// </summary>
          protected readonly IAppConfigService AppCfgService;
       
          #endregion Fields
       
          #region Methods
       
          /// <summary>
          ///     创建合法用户的Token
          /// </summary>
          /// <param name="userId">用户Id</param>
          /// <param name="passWord">用户密码</param>
          /// <param name="signature">加密签名字符串</param>
          /// <param name="timestamp">时间戳</param>
          /// <param name="nonce">随机数</param>
          /// <param name="appid">应用接入ID</param>
          /// <returns>OperatedResult</returns>
          protected virtual ApiResult<IdentityToken> CreateIdentityToken(string userId, string passWord,
              string signature, string timestamp,
              string nonce, Guid appid)
          {
              #region  参数检查
       
              var checkResult = CheckRequest(userId, passWord, signature, timestamp, nonce, appid);
       
              if (!checkResult.State)
                  return ApiResult<IdentityToken>.Fail(checkResult.Message);
       
              #endregion
       
              #region 用户鉴权
       
              var getIdentityUser = GetIdentityUser(userId, passWord);
       
              if (!getIdentityUser.State) return ApiResult<IdentityToken>.Fail(getIdentityUser.Message);
       
              #endregion
       
              #region 请求通道检查
       
              var getAppConfig = AppCfgService.Get(appid);
       
              if (!getAppConfig.State) return ApiResult<IdentityToken>.Fail(getAppConfig.Message);
              var appConfig = getAppConfig.Data;
       
              #endregion
       
              #region 检查请求签名检查
       
              var checkSignatureResult = ApiAuthorize.CheckRequestSignature(signature, timestamp, nonce, appConfig);
              if (!checkSignatureResult.State) return ApiResult<IdentityToken>.Fail(checkSignatureResult.Message);
       
              #endregion
       
              #region 生成基于Jwt Token
       
              var getTokenResult = ApiAuthorize.CreateIdentityToken(getIdentityUser.Data, getAppConfig.Data);
              if (!getTokenResult.State) return ApiResult<IdentityToken>.Fail(getTokenResult.Message);
       
              return ApiResult<IdentityToken>.Success(getTokenResult.Data);
       
              #endregion
          }
       
       
          /// <summary>
          ///     检查用户的合法性
          /// </summary>
          /// <param name="userId">用户Id</param>
          /// <param name="passWord">用户密码</param>
          /// <returns>UserInfo</returns>
          protected abstract CheckResult<IdentityUser> GetIdentityUser(string userId, string passWord);
       
          private CheckResult CheckRequest(string userId, string passWord, string signature, string timestamp,
              string nonce, Guid appid)
          {
              if (string.IsNullOrEmpty(userId) || string.IsNullOrEmpty(passWord))
                  return CheckResult.Fail("用户名或密码为空");
       
              if (string.IsNullOrEmpty(signature))
                  return CheckResult.Fail("请求签名为空");
       
              if (string.IsNullOrEmpty(timestamp))
                  return CheckResult.Fail("时间戳为空");
       
              if (string.IsNullOrEmpty(nonce))
                  return CheckResult.Fail("随机数为空");
       
              if (appid == Guid.Empty)
                  return CheckResult.Fail("应用接入ID非法");
       
              return CheckResult.Success();
          }
       
          #endregion Methods
      }
      
    2. 鉴权使用,通过AuthorizationFilterAttribute形式,标注请求是否需要鉴权

      /// <summary>
       ///     WebApi 授权验证实现
       /// </summary>
       [AttributeUsage(AttributeTargets.Method)]
       public abstract class AuthenticateAttribute : AuthorizationFilterAttribute
       {
           #region Constructors
          
           /// <summary>
           ///     构造函数
           /// </summary>
           /// <param name="apiAuthenticate">IApiAuthenticate</param>
           /// <param name="appCfgService">appCfgService</param>
           protected AuthenticateAttribute(IApiAuthenticate apiAuthenticate, IAppConfigService appCfgService)
           {
               ValidateOperator.Begin()
                   .NotNull(apiAuthenticate, "IApiAuthenticate")
                   .NotNull(appCfgService, "IAppConfigService");
               ApiAuthenticate = apiAuthenticate;
               AppCfgService = appCfgService;
           }
       
           #endregion Constructors
       
           #region Fields
       
           /// <summary>
           ///     授权验证接口
           /// </summary>
           protected readonly IApiAuthenticate ApiAuthenticate;
       
           /// <summary>
           ///     请求通道配置信息,可以从文件或者数据库获取
           /// </summary>
           protected readonly IAppConfigService AppCfgService;
       
           #endregion Fields
       
           #region Methods
       
           /// <summary>
           ///     验证Token令牌是否合法
           /// </summary>
           /// <param name="token">令牌</param>
           /// <param name="appid">应用ID</param>
           /// <returns>CheckResult</returns>
           protected virtual ApiResult<string> CheckIdentityToken(string token, Guid appid)
           {
               #region 请求参数检查
       
               var checkResult = CheckRequest(token, appid);
       
               if (!checkResult.State)
                   return ApiResult<string>.Fail(checkResult.Message);
       
               #endregion
       
               #region 请求通道检查
       
               var getAppConfig = AppCfgService.Get(appid);
       
               if (!getAppConfig.State) return ApiResult<string>.Fail(getAppConfig.Message);
               var appConfig = getAppConfig.Data;
       
               #endregion
       
               return ApiAuthenticate.CheckIdentityToken(token, appConfig);
           }
       
           private CheckResult CheckRequest(string token, Guid appid)
           {
               if (string.IsNullOrEmpty(token))
                   return CheckResult.Fail("用户令牌为空");
               return Guid.Empty == appid ? CheckResult.Fail("应用ID非法") : CheckResult.Success();
           }
       
           #endregion Methods
       }
      

    基于请求缓存处理

    1. 通过ICacheProvider接口,可以扩展缓存数据方式;

    2. 通过配置DependsOnIdentity参数,可以配置是否依赖Token令牌进行缓存;

    3. 通过配置CacheMinutes参数,可以指定具体接口缓存时间,当设置0的时候不启用缓存;

    4. 通过实现ControllerCacheAttribute,可以在不同项目快速达到接口缓存功能;

      public class RequestCacheAttribute : ControllerCacheAttribute
      {
          public RequestCacheAttribute(int cacheMinutes) : this(cacheMinutes, true, new LocalCacheProvider())
          {
          }
       
          public RequestCacheAttribute(int cacheMinutes, bool dependsOnIdentity, ICacheProvider cacheProvider) : base(
              cacheMinutes, dependsOnIdentity, cacheProvider)
          {
          }
       
          protected override bool CheckedResponseAvailable(HttpActionContext context, string responseText)
          {
              return !string.IsNullOrEmpty(responseText) && context != null;
          }
       
          protected override string GetIdentityToken(HttpActionContext actionContext)
          {
              return actionContext.Request.GetUriOrHeaderValue("Access_token").ToStringOrDefault(string.Empty);
          }
      }
      

    异常处理

    1. 通过实现ControllerExceptionAttribute,可以轻松简单构建接口请求时候异常发生,并通过HttpRequestRaw requestRaw参数,可以获取非常详尽的请求信息;

      public sealed class ExceptionLogAttribute : ControllerExceptionAttribute
      {
          public override void OnActionExceptioning(HttpActionExecutedContext actionExecutedContext, string actionName,
              HttpStatusCode statusCode,
              HttpRequestRaw requestRaw)
          {
              var response = new HttpResponseMessage
              {
                  Content = new StringContent("发生故障,请稍后重试!"),
                  StatusCode = statusCode
              };
              actionExecutedContext.Response = response;
          }
      }
      

    参数验证

    1. 通过实现ValidateModelAttribute,以及DataAnnotations快速构建请求参数验证

    2. 请求参数只需要DataAnnotations标注即可;

      public sealed class ArticleRequest
      {
          [Required(ErrorMessage = "缺少文章ID")]
          public int Id
          {
              get;
              set;
          }
       
      }
      
    3. 项目实现ValidateModelAttribute,可以自定义构建参数处理方式

      /// <summary>
      /// 请求参数
      /// </summary>
      public sealed class ValidateRequestAttribute : ValidateModelAttribute
      {
          public override void OnParameterIsNulling(HttpActionContext actionContext)
          {
              actionContext.Response =
                  actionContext.Request.CreateResponse(HttpStatusCode.BadRequest, OperatedResult<string>.Fail("请求参数非法。"));
          }
       
          public override void OnParameterInvaliding(HttpActionContext actionContext, ValidationFailedResult result)
          {
              var message = result.Data.FirstOrDefault()?.Message;
              actionContext.Response =
                  actionContext.Request.CreateResponse(HttpStatusCode.BadRequest, OperatedResult<string>.Fail(message));
          }
      }
      
  • 相关阅读:
    es5预览本地文件、es6练习代码演示案例
    Java实现 LeetCode 838 推多米诺(暴力模拟)
    Java实现 LeetCode 838 推多米诺(暴力模拟)
    Java实现 LeetCode 838 推多米诺(暴力模拟)
    Java实现 LeetCode 837 新21点(DP)
    Java实现 LeetCode 837 新21点(DP)
    Java实现 LeetCode 837 新21点(DP)
    Java实现 LeetCode 836 矩形重叠(暴力)
    Subversion under Linux [Reprint]
    Subversion how[Reprint]
  • 原文地址:https://www.cnblogs.com/MeetYan/p/WebApi.html
Copyright © 2011-2022 走看看