2023-03-10 23:50:10 +08:00

17 KiB
Raw Blame History

title, date, author, top_img
title date author top_img
ASP.Net 6 2021-03-23 10:30:31 文永达 https://gcore.jsdelivr.net/gh/volantis-x/cdn-wallpaper/abstract/67239FBB-E15D-4F4F-8EE8-0F1C9F3C4E7C.jpeg

ASP.Net 6

Web API 项目初始化搭建

首先打开Visual Studio 2022然后选择创建新项目

之后筛选下拉框选择如红框标注

image-20230301104542343

起一个项目名称及选择项目位置,下一步

image-20230301104644030

框架选择.Net 6.0(长期支持)

选择启用Docker为了之后可以部署到Docker容器

启用OpenAPI支持是为了可以输出Swagger接口文档但如果使用Furion框架的话需要勾掉

顶级语句是无需在Program.cs中显式包含Main方法可以使用顶级语句功能最大程度地减少必须编写的代码

image-20230301104855227

点击创建即可

image-20230301105745182

集成Furion框架

在NuGet包管理器中搜索Furion

image-20230301110105535

选择安装的项目,然后安装即可

可能遇到的问题

包降级

image-20230301110232202

将提示的NuGet包升级到 前者的版本即可,比如图内的 Swashbuckle.AspNetCore 原有的版本是 6.2.3 那么升级到 6.5.0即可

部署到Docker

安装.Net SDK 6.0环境

sudo rpm -Uvh https://packages.microsoft.com/config/centos/7/packages-microsoft-prod.rpm
sudo yum install dotnet-sdk-6.0
dotnet --info

Visual Studio添加Docker支持

image-20221121144928205

Linux下构建Docker镜像

docker image build -f ./XiaodaERP/Dockerfile -t aspnetcore .
docker images

运行Docker镜像

docker run --name=aspnetcore -p 9001:80 -d aspnetcore
docker ps
cd /usr/local/jenkins_home/workspace/XiaodaERP_NetCore
echo $PWD
docker image build -f ./XiaodaERP/Dockerfile -t xiaodaerp/netcore .
docker images 
docker run --name xiaodaerp/netcore -p 7274:80 -d xiaodaerp/netcore

顶级语句配置Program.cs

取消默认JSON首字母小写命名

builder.Services.AddControllers().AddJsonOptions(options => {
    options.JsonSerializerOptions.PropertyNamingPolicy = null;
});

Json序列化时忽略属性为null的值

builder.Services.AddControllers().AddJsonOptions(options => {
    options.JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull;
});

Json序列化时日期类型格式化输出

builder.Services.AddControllers().AddJsonOptions(options =>
{
    options.JsonSerializerOptions.Converters.Add(new SystemTextJsonDateTimeJsonConverter("yyyy-MM-dd HH:mm:ss"));
});

集成Furion框架

var builder = WebApplication.CreateBuilder(args).Inject();
builder.Services.AddControllers().AddInject();
app.UseInject();

使用Autofac自动注入Service

通过NuGet包管理器 安装NuGet包

image-20221130161234399

image-20221130161319595

Autofac

Autofac.Extensions.DependencyInjection

Autofac.Extras.DynamicProxy

新建ServiceAutofac.cs

using System.Reflection;

namespace XiaodaERP
{
    public class ServiceAutofac
    {
        /// <summary>
        /// 获取程序集名称
        /// </summary>
        /// <returns></returns>
        public static string GetAssemblyName()
        {
            return Assembly.GetExecutingAssembly().GetName().Name;
        }
    }
}

Program.cs配置

builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory());
builder.Host.ConfigureContainer<ContainerBuilder>(builder =>
{
    Assembly assembly = Assembly.Load(ServiceAutofac.GetAssemblyName());//注入Service程序集 可以是其他程序集
    builder.RegisterAssemblyTypes(assembly)
    .AsImplementedInterfaces()
    .InstancePerDependency();
});

注入Entity Framework Core 6 DbContext上下文

builder.Services.AddDbContext<OracleDbContext>(options =>
options.UseOracle(builder.Configuration.GetConnectionString("OracleDbContext")));

builder.Services.AddDbContext<SqlServerDbContext>(options =>     			  options.UseSqlServer(builder.Configuration.GetConnectionString("SqlServerDbContext")));

使用JWT进行授权与认证

安装NuGet包

Microsoft.AspNetCore.Authentication.JwtBearer

image-20221206130808039

appsettings.json配置文件中配置

"Authentication": {
    "SecretKey": "nadjhfgkadshgoihfkajhkjdhsfaidkuahfhdksjaghidshyaukfhdjks",
    "Issuer": "www.xiaoda",
    "Audience": "www.xiaoda"
 }

Program.cs顶级语句配置

// 使用Autofac自动注入Service
builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory());
builder.Host.ConfigureContainer<ContainerBuilder>(builder =>
{
    Assembly assembly = Assembly.Load(ServiceAutofac.GetAssemblyName());//注入Service程序集 可以是其他程序集
    builder.RegisterAssemblyTypes(assembly)
    .AsImplementedInterfaces()
    .InstancePerDependency();
    // 在IOC容器中注入
    // 用于Jwt的各种操作
    builder.RegisterType<JwtSecurityTokenHandler>().InstancePerLifetimeScope();
    // 支持泛型存入Jwt
    builder.RegisterType<TokenHelper>().InstancePerLifetimeScope();
});

//JWT认证
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(options =>
{
    //取出私钥
    var secretByte = Encoding.UTF8.GetBytes(builder.Configuration["Authentication:SecretKey"]);
    options.TokenValidationParameters = new TokenValidationParameters()
    {
        //验证发布者
        ValidateIssuer = true,
        ValidIssuer = builder.Configuration["Authentication:Issuer"],
        //验证接受者
        ValidateAudience = true,
        ValidAudience = builder.Configuration["Authentication:Audience"],
        //验证是否过期
        ValidateLifetime = true,
        //验证私钥
        IssuerSigningKey = new SymmetricSecurityKey(secretByte)
    };

});
// 顺序不能颠倒
// 你是谁 授权
app.UseAuthentication();
// 你可以干什么 验证
app.UseAuthorization();

新建TokenHelper.cs工具类

using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Reflection;
using System.Security.Claims;
using System.Text;
using XiaodaERP.Models;

namespace XiaodaERP.Utils
{
    public class TokenHelper
    {
        private readonly IConfiguration _configuration;
        private readonly JwtSecurityTokenHandler _jwtSecurityTokenHandler;
        public TokenHelper(IConfiguration configuration, JwtSecurityTokenHandler jwtSecurityTokenHandler)
        { 
            this._configuration = configuration;
            this._jwtSecurityTokenHandler = jwtSecurityTokenHandler;
        }
        public static string? Token { get; set; }
        // 生成Token
        public string CreateJwtToken<T>(T user)
        {
            // 生成JWT
            // Header选择签名算法
            var signingAlogorithm = SecurityAlgorithms.HmacSha256;
            // Payload存放用户信息放用户ID用户名
            var claimList = this.CreateClaimList(user);
            //Signature
            //取出私钥并以utf8编码字节输出
            var secretByte = Encoding.UTF8.GetBytes(_configuration["Authentication:SecretKey"]);
            //使用非对称算法对私钥进行加密
            var signingKey = new SymmetricSecurityKey(secretByte);
            //使用HmacSha256来验证加密后的私钥生成数字签名
            var signingCredentials = new SigningCredentials(signingKey, signingAlogorithm);
            //生成Token
            var Token = new JwtSecurityToken(
                    issuer: _configuration["Authentication:Issuer"],        //发布者
                    audience: _configuration["Authentication:Audience"],    //接收者
                    claims: claimList,                                         //存放的用户信息
                    notBefore: DateTime.UtcNow,                             //发布时间
                    expires: DateTime.UtcNow.AddMinutes(30),                      //有效期设置为1天
                    signingCredentials                                      //数字签名
                );
            //生成字符串token
            var TokenStr = new JwtSecurityTokenHandler().WriteToken(Token);
            return TokenStr;
        }
		// 获取Token Payload信息
        public T GetToken<T>(string token)
        {
            Type t = typeof(T);
            object obj = Activator.CreateInstance(t);
            var b = _jwtSecurityTokenHandler.ReadJwtToken(token);
            foreach (var item in b.Claims)
            {
                PropertyInfo propertyInfo = t.GetProperty(item.Type);
                if (propertyInfo != null && propertyInfo.CanRead)
                {
                    propertyInfo.SetValue(obj, item.Value, null);
                }
            }
            return (T)obj;
        }
		// 根据类生成Token 断言列表
        private List<Claim> CreateClaimList<T>(T authUser)
        {
            var Class = typeof(T);
            List<Claim> claimList = new();
            foreach (var item in Class.GetProperties())
            {
                // 不将PassWord放入Token中
                if (item.Name == "PassWord")
                {
                    continue;
                }
                // 将UserName属性名重命名为username存入Token中
                if (item.Name == "UserName")
                {
                    claimList.Add(new Claim("username", Convert.ToString(item.GetValue(authUser))));
                    continue;
                }
                claimList.Add(new Claim(item.Name, Convert.ToString(item.GetValue(authUser))));
            }
            return claimList;
        }
    }
}

在登录方法中加入

public ViewUser Login(string UserName, string PassWord)
{
    var res = _sqlServerDbContext.Users.Include(user => user.Role).FirstOrDefault(x => x.UserName == UserName);
    if (res != null) 
    {
        if (res.PassWord == Md5Encoding(PassWord))
        {
            // 生成JWT
            var TokenStr = _tokenHelper.CreateJwtToken(res);
            var config = new MapperConfiguration(cfg => cfg.CreateMap<User, ViewUser>()
                                                 .ForMember(d => d.username, opt => opt.MapFrom(src => src.UserName))
                                                 .AfterMap((src, des) => des.Roles = new Role[1] { src.Role })
                                                 .AfterMap((src, des) => des.Token = "bearer " + TokenStr) // 需要加上bearer
                                                 .AfterMap((src, des) => des.HomePath = "/dashboard/analysis")
                                                 .AfterMap((src, des) => des.password = null));
            var mapper = config.CreateMapper();
            return mapper.Map<ViewUser>(res);
        }
    }
    return null;
}

WebAPI 需要认证的加上[Authorize]注解,注意登录不能加

[AuthFilter]
[HttpPost(Name = "login")]
public ResultUtil Login(ViewUser viewUser) =>
    ResultUtil.ok(_userService.Login(viewUser.username, viewUser.password));
// 需要认证的API
[Authorize]
[AuthFilter]
[HttpGet(Name = "getUserInfo")]
public ResultUtil GetUserInfo()
{
    Token = HttpContext.Request.Headers["Authorization"];
    Token = Token.Split(" ")[1];
    TokenHelper.Token = Token;
    ViewUser us = _tokenHelper.GetToken<ViewUser>(Token);
    return ResultUtil.ok(us);
}

访问登录接口

image-20221206131847333

访问需要认证的接口需要把Token放在请求头中如果不携带Token访问则报401

image-20221206132042533

请求头Key 为 Authorization

访问成功

image-20221206132124913

面向切面编程(AOP)

三大拦截器

AuthorizeAttribute

认证拦截器

ActionFilterAttribute

方法拦截器

using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.Filters;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.IdentityModel.Tokens.Jwt;
using Castle.Core.Internal;
using XiaodaERP.Models;
using XiaodaERP.Utils;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;

namespace XiaodaERP.Attributes
{
    public class AuthFilter : ActionFilterAttribute
    {
        //private readonly TokenHelper _tokenHelper;
        //public AuthFilter(TokenHelper tokenHelper)
        //{
        //    this._tokenHelper = tokenHelper;
        //}
        private readonly SqlServerDbContext _sqlServerDbContext;
        public AuthFilter(SqlServerDbContext sqlServerDbContext)
        {
            this._sqlServerDbContext = sqlServerDbContext;
        }

        private SysActionLog sysActionLog = new()
        {
            ActionId = Guid.NewGuid().ToString().Replace("-", "").ToUpper()
        };
        public override void OnActionExecuting(ActionExecutingContext context)
        {
            var descriptor = context.ActionDescriptor as ControllerActionDescriptor;
            string param = string.Empty;
            string globalParam = string.Empty;

            var jsonSetting = new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore };
            foreach (var arg in context.ActionArguments)
            {
                string value = Newtonsoft.Json.JsonConvert.SerializeObject(arg.Value, Formatting.None, jsonSetting);
                param += $"{arg.Key} : {value} \r\n";
                globalParam += value;
            }
            // 方法名
            Console.WriteLine(descriptor.ActionName);
            // 参数值拼接
            Console.WriteLine(globalParam);
            // 参数名 与 值
            Console.WriteLine(param);
            sysActionLog.ActionName = descriptor.ActionName;
            sysActionLog.RequestParams = param;
        }

        public override void OnActionExecuted(ActionExecutedContext context)
        {
            // 获取请求Host
            Console.WriteLine(context.HttpContext.Request.Host);
            sysActionLog.RequestHost = context.HttpContext.Request.Host.ToString();
            // 获取请求方法
            Console.WriteLine(context.HttpContext.Request.Method);
            sysActionLog.RequestMethod = context.HttpContext.Request.Method;
            // 获取请求Url
            Console.WriteLine(context.HttpContext.Request.Path);
            sysActionLog.RequestPath = context.HttpContext.Request.Path.ToString();
            // 获取应答返回状态码
            Console.WriteLine(context.HttpContext.Response.StatusCode);
            if (context.HttpContext.Request.Path.Equals("/api/User/login"))
            {
                sysActionLog.ActionTime = DateTime.Now;
            }
            else
            {
                string Token = context.HttpContext.Request.Headers["Authorization"];
                // Token失效
                if (Token.IsNullOrEmpty())
                {

                }
                else
                {
                    Token = Token.Split(" ")[1];
                    TokenHelper.Token = Token;
                    ViewUser us = new TokenHelper(new JwtSecurityTokenHandler()).GetToken<ViewUser>(Token);
                    Console.WriteLine(us.UserId);
                    sysActionLog.ActionUserId = us.UserId;
                    Console.WriteLine(us.username);
                    sysActionLog.ActionUserName = us.username;
                }            
                sysActionLog.ActionTime = DateTime.Now;
            }
            _sqlServerDbContext.SysActionLogs.Add(sysActionLog);
            _sqlServerDbContext.SaveChanges();
        }
    }
}

接口上使用

//[AuthFilter]
[TypeFilter(typeof(AuthFilter))]
[HttpPost(Name = "login")]
public ResultUtil Login(ViewUser viewUser) =>
    ResultUtil.ok(_userService.Login(viewUser.username, viewUser.password));

[Authorize]
//[AuthFilter] // 注解为拦截器类名
[TypeFilter(typeof(AuthFilter))] // 因为主键中使用了构造器依赖注入所以需要使用TypeFilter并需要在顶级语句中注入 AuthFilter
[HttpGet(Name = "getUserInfo")]
public ResultUtil GetUserInfo()
{
    Token = HttpContext.Request.Headers["Authorization"];
    Token = Token.Split(" ")[1];
    TokenHelper.Token = Token;
    ViewUser us = _tokenHelper.GetToken<ViewUser>(Token);
    return ResultUtil.ok(us);
}

顶级语句中注入

builder.Services.AddScoped<AuthFilter>();

ExceptionFilterAttribute

异常拦截器