进程托管

进程内托管

  • 在InProcess托管的情况下,CreateDefaultBuilder()方法调用UseIIS()方法并在IIS工作进程(w3wp.exe或iisexpress.exe)内托管应用程序。
  • 从性能角度,InProcess托管比OutOfProcess托管提供了更高的请求吞吐量。
  • 获取执行应用程序的进程名称

    1. var processName = System.Diagnostics.Process.GetCurrentProcess().ProcessName;
    2. await context.Response.WriteAsync(processName);-->iisexpress

    image.png

    进程外托管

    设置为项目本身,启动的是Kestrel作为托管服务器进行运行。
    image.png
    image.png

    什么时候用进程外托管

    其他场景下,我就推荐采用 OutOfProcess 模式了,比如:

  • 用于故障排除和调试故障服务器(例如,您可以在启用控制台日志记录,查看更加详细的信息)。

  • 同一个应用程序实现 100%兼容,无论是部署在 Windows 还是 Linux 上,Kestrel 的主要机制是可以处理所有平台上的 HTTP 请求。
  • 使用 InProcess 模型时,则不会使用 Kestrel 服务(这个在我的书中有详细说明),而是直接与 IIS 的请求管道中的模块进行通信。

    ASP.NET MVC

  • 创建ASP.NET Model-Control-View项目

  • 项目结构:先创建Models(Student对象类) 创建Controller

Student:

 public record Student(int age,string name,DateTime dt);

AlbertController:

public IActionResult Print(){  //Action方法
    Student stu = new Student(25,"AlbertZhao",DateTime.Now);
    return View(stu);
}

View层创建文件夹 名字和控制层的名字一样Albert文件夹
下面视图.cshtml名字和控制层里面的方法名相同 Print.cshtml

@model Student
<div>学生姓名:@Model.name</div>
<div>学生年龄:@Model.age</div>
<div>创建时间:@Model.dt</div>

image.png
如要查阅
Albert/Print即可(文件夹对应控制器的名字,网页对应里面方法的名字,不然会找不到) 文件夹路径

HOT RELOAD

image.png

WebAPI

ACTION方法返回值ActionResult
image.png

  [HttpGet]
        public ActionResult<string> PrintStudent(int id)
        {
            if(id == 1)
            {
                var person = new Person("Albert", "Zhao");
                return person.PrintFullNameById(1);
            }
            else
            {
                return NotFound("人员不存在");
            }
        }

Action方法参数
image.png
image.png
一定要注意请求报文体中Content-Type需要为application/json格式,数据必须是合法的json格式。
image.png

[HttpPost]
        public async Task<ActionResult<string>> AddPerson(Person person)
        {
            try
            {
                await System.IO.File.WriteAllTextAsync(@"F:\Test\test.txt", person.ToString());
                return "Add person successfully!";
            }
            catch (Exception)
            {
                return NotFound($"Failed!{person.ToString()}");
            }
        }

image.png
从QueryString里面拿id, 从报文体中Json传Person对象

 [HttpPut("{id}")]
        public string UpdatePerson([FromRoute(Name ="id")]int id,Person p)
        {
            return "更新成功";
        }

image.png

 [HttpPut("{id}")]
        public string UpdatePerson([FromRoute(Name ="id")]int id,Person p,[FromHeader(Name ="User-Agent")] string us)
        {
            return $"更新成功:{us}";
        }

image.png

ASP.NET Core前后端分离开发

分离的好处

image.png

后端代码

public record LoginRequest(string UserName,string Password);
public record ProcessInfo(long ID,string Name,long WorkingSet);
public record LoginResponse(bool OK, ProcessInfo[] ProcessInfos);

[HttpPost("{UserName}")]
        public ActionResult<LoginResponse> LoginByUserName(LoginRequest request)
        {
            if (request.UserName == "admin" && request.Password == "123")
            {
                var items = Process.GetProcesses().Select(p => new ProcessInfo(p.Id, p.ProcessName, p.WorkingSet64));
                return new LoginResponse(true, items.ToArray());
            }
            else
            {
                return new LoginResponse(false, null);
            }
        }

前端环境

image.png
image.png

前后端结合

image.png
image.png
image.png

CORS跨越安全策略

做法:

  • JsonP
  • Web-CORS启用,告诉前端页面后端服务器没问题:不要把前端服务器作为代理,进行跨转,前端Server压力会很大。

image.png

ASP.NET Core-依赖注入

image.png
Model-FileService.cs

namespace AlbertZhao.cn.Models
{
    public class FileService
    {
        public async Task SaveContent2File(string content)
        {
            await File.AppendAllTextAsync(@"F:\Test\test.txt", content);
        }

DIExtension-DI_FileService.cs

using AlbertZhao.cn.Models;

namespace Microsoft.Extensions.DependencyInjection
{
    public static class DI_FileService
    {
        public static void AddFileService(this IServiceCollection services)
        {
            services.AddScoped<FileService>();
        }
    }
}

Program.cs-注入

//依赖注入
builder.Services.AddFileService();

Controller层使用-FileController.cs

using AlbertZhao.cn.Models;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;

namespace AlbertZhao.cn.Controllers
{
    [Route("api/[controller]/[action]")]
    [ApiController]
    public class FileController : ControllerBase
    {
        private readonly FileService fileService;
        public FileController(FileService fileService)
        {
            this.fileService = fileService; 
        }

        [HttpPost]
        public async Task WriteContent(string content)
        {
            await fileService.SaveContent2File(content);

        }
    }
}

image.png

 //对于请求慢的服务,可以通过此种方式来避免干扰到FileService的启动
        [HttpGet]
        public int GetFileCount([FromServices]ScanDir scanDir,int extraCount)
        {
            return scanDir.PrintFileCount()+ extraCount;
        }

案例-开发模块化的服务注册框架

image.png
image.png
RunModuleInitializers方法:https://github.com/yangzhongke/NETBookMaterials/blob/main/%E6%9C%80%E5%90%8E%E5%A4%A7%E9%A1%B9%E7%9B%AE%E4%BB%A3%E7%A0%81/YouZack-VNext/Zack.Commons/ModuleInitializerExtensions.cs
通过递归获得程序集-GetAllReferencedAssemblies:https://github.com/yangzhongke/NETBookMaterials/blob/main/%E6%9C%80%E5%90%8E%E5%A4%A7%E9%A1%B9%E7%9B%AE%E4%BB%A3%E7%A0%81/YouZack-VNext/Zack.Commons/ReflectionHelper.cs

在Program.cs里:
//初始化DI容器 https://github/com/yangzhongke/NETBookMaterials/
var assemblies = ReflectionHelper.GetAllReferencedAssemblies();
// Controllers 直接就可以拿到服务
builder.Services.RunModuleInitializers(assemblies);

在引用的项目中(要启动的服务)
namespace _220104_WebApiLibrary01
{
    //这个类实现Zack.Commons.IModuleInitializer接口
    //完成服务的自注册
    public class ModuleInitializerHelper : IModuleInitializer
    {
        public void Initialize(IServiceCollection services)
        {
            services.AddScoped<Student>();
            services.AddScoped<Teacher>();
        }
    }
}

namespace _220104_WebApiLibrary01
{
    public class Student
    {
        public string Hello()
        {
            return "Studnet Hi";
        }
    }
}

ASP.NET Core-缓存

什么是缓存

image.png

  • 缓存命中 获取ID=1的数据,返回,则命中
  • 缓存命中率
  • 缓存数据不一致 缓存和数据源数据不一致

image.png

客户端缓存

image.png

服务器端响应缓存-包在服务器端-鸡肋没啥用

image.png
image.png
image.png

内存缓存

image.png
image.png

内存缓存的两种过期策略:绝对过期时间、滑动过期时间

绝对过期时间 GetOrCreateAsync()方法的回调函数中的e(ICacheEntry类的参数),e的属性AbsoluteExpirationRelativeToNow用来设定时间。
滑动过期时间SlidingExpiration 只要在失效时间内命中,就会继续增加失效时间(几乎没有业务场景需要)
混合模式:设定长的绝对过期时间 短的滑动过期时间

namespace AlbertZhao.cn.Controllers
{
    [Route("api/[controller]/[action]")]
    [ApiController]
    public class DataManagerController : ControllerBase
    {
        private readonly IMemoryCache memoryCache;
        private readonly ILogger<DataManagerController> logger;

        public DataManagerController(IMemoryCache memoryCache, ILogger<DataManagerController> logger)
        {
            this.memoryCache = memoryCache;
            this.logger = logger;
        }

        [HttpGet]
        public async Task<ActionResult<Student?>> GetStuByID(int id)
        {
            logger.LogInformation("开始查询数据....");

            //GetOrCreateAsync天然避免缓存穿透,里面会将空值作为一个有效值
            Student? stu = await memoryCache.GetOrCreateAsync("Student_" + id, async e =>
            {
                //避免缓存雪崩
                e.AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(Random.Shared.Next(5, 10));

                //从数据库拿取数据
                using (var ctx = new AlbertDbContext())
                {
                    logger.LogInformation("从数据库查询数据....");
                    Student? stu = ctx.Students.Where(e=>e.ID == id).FirstOrDefault();
                    logger.LogInformation($"从数据库查询的结果是:{(stu == null ? "null" : stu)}");
                    return stu;
                } 
            });
            if(stu == null)
            {
                return NotFound("查询的学生不存在");
            }
            else
            {
                return stu;
            }
        }     
    }
}

分布式缓存

如果没有必要就用内存缓存,内存缓存已经很好很好了,只有当分布式设备集群服务器数量很多而数据库只有一台的情况下可以考虑分布式缓存。
iShot2022-01-12 21.23.59.png
iShot2022-01-12 21.41.34.png
image.png
image.png
image.png

//启用分布式缓存Redis
builder.Services.AddStackExchangeRedisCache(options => {
    options.Configuration = "localhost";
    options.InstanceName = "albertzhaoz_";
});

using AlbertZhao.cn.DbContextExtension;
using AlbertZhao.cn.Models;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Caching.Distributed;
using System.Text.Json;
using System.Linq;

namespace AlbertZhao.cn.Controllers
{
    [Route("api/[controller]/[action]")]
    [ApiController]
    public class DataManagerController : ControllerBase
    {
        private readonly ILogger<DataManagerController> logger;
        private readonly IDistributedCache distributedCache;
        public DataManagerController(IMemoryCache memoryCache, ILogger<DataManagerController> logger, IDistributedCache distributedCache)
        {
            this.memoryCache = memoryCache;
            this.logger = logger;
            this.distributedCache = distributedCache;
        }

        [HttpGet]
        public async Task<ActionResult<Student?>> GetStuRedisById(int id)
        {
            logger.LogInformation(id.ToString());
            Student? student = null;
            var s = await distributedCache.GetStringAsync("albertzhaoz" + id);
            if (s == null)
            {
                logger.LogInformation("从数据库中获取");
                if (id == 1)
                {
                    student = new Student()
                    {
                        ID = 1,
                        Name = "Albertzhao",
                        Age = 26,
                        SubName = "Albert",
                        Score = 100
                    };
                }
                else if (id == 2)
                {
                    student = new Student()
                    {
                        ID = 2,
                        Name = "Yangzhongke",
                        Age = 43,
                        SubName = "yang",
                        Score = 100
                    };
                }
                else
                {
                    student = null;
                }
                logger.LogInformation(JsonSerializer.Serialize(student));
                await distributedCache.SetStringAsync("albertzhaoz" + id, JsonSerializer.Serialize(student),
                new DistributedCacheEntryOptions()
                {
                    // 过期时间
                    AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(Random.Shared.Next(5, 10))
                });
            }
            else
            {
                logger.LogInformation("从Redis缓存中获取");
                student = JsonSerializer.Deserialize<Student?>(s);
            }

            if (student == null)
            {
                return NotFound("没有此学生数据");
            }
            else
            {
                return student;
            }
        }

    }
}

缓存帮助类

http://albertzhao.cn中Albert.Common包封装了缓存帮助类,便于操作和避免一些滞后类型的传入。
MemoryHelper内存缓存帮助类:主要是避免一些延迟执行的问题,直接禁用

using Albert.Commons.Interfaces;
using Microsoft.Extensions.Caching.Memory;
using System.Collections;

namespace Albert.Commons.Helpers
{
    /// <summary>
    /// 用ASP.NET的IMemoryCache实现的内存缓存
    /// </summary>
    public class MemoryCacheHelper : IMemoryCacheHelper
    {
        private readonly IMemoryCache memoryCache;
        public MemoryCacheHelper(IMemoryCache memoryCache)
        {
            this.memoryCache = memoryCache;
        }

//验证数据类型
        private static void ValidateValueType<TResult>()
        {
            //因为IEnumerable、IQueryable等有延迟执行的问题,造成麻烦,因此禁止用这些类型
            Type typeResult = typeof(TResult);
            if (typeResult.IsGenericType)//如果是IEnumerable<String>这样的泛型类型,则把String这样的具体类型信息去掉,再比较
            {
                typeResult = typeResult.GetGenericTypeDefinition();
            }
            //注意用相等比较,不要用IsAssignableTo
            if (typeResult == typeof(IEnumerable<>) || typeResult == typeof(IEnumerable)
                || typeResult == typeof(IAsyncEnumerable<TResult>)
                || typeResult == typeof(IQueryable<TResult>) || typeResult == typeof(IQueryable))
            {
                throw new InvalidOperationException($"TResult of {typeResult} is not allowed, please use List<T> or T[] instead.");
            }
        }

//设置过期时间,通过随机生成算法NextDouble更加随机
        private static void InitCacheEntry(ICacheEntry entry, int baseExpireSeconds)
        {
            //过期时间.Random.Shared 是.NET6新增的
            double sec = Random.Shared.NextDouble(baseExpireSeconds, baseExpireSeconds * 2);
            TimeSpan expiration = TimeSpan.FromSeconds(sec);
            entry.AbsoluteExpirationRelativeToNow = expiration;
        }

        public TResult? GetOrCreate<TResult>(string cacheKey, Func<ICacheEntry, TResult?> valueFactory, int baseExpireSeconds = 60)
        {
            ValidateValueType<TResult>();
            //因为IMemoryCache保存的是一个CacheEntry,所以null值也认为是合法的,因此返回null不会有“缓存穿透”的问题
            //不调用系统内置的CacheExtensions.GetOrCreate,而是直接用GetOrCreate的代码,这样免得包装一次委托
            if (!memoryCache.TryGetValue(cacheKey, out TResult result))
            {
                using ICacheEntry entry = memoryCache.CreateEntry(cacheKey);
                InitCacheEntry(entry, baseExpireSeconds);
                result = valueFactory(entry)!;
                entry.Value = result;
            }
            return result;
        }

        public async Task<TResult?> GetOrCreateAsync<TResult>(string cacheKey, Func<ICacheEntry, Task<TResult?>> valueFactory, int baseExpireSeconds = 60)
        {
            ValidateValueType<TResult>();
            if (!memoryCache.TryGetValue(cacheKey, out TResult result))
            {
                using ICacheEntry entry = memoryCache.CreateEntry(cacheKey);
                InitCacheEntry(entry, baseExpireSeconds);
                result = (await valueFactory(entry))!;
                entry.Value = result;
            }
            return result;
        }

        public void Remove(string cacheKey)
        {
            memoryCache.Remove(cacheKey);
        }
    }
}
using Albert.Commons.Interfaces;
using Microsoft.Extensions.Caching.Distributed;
using System.Text;
using System.Text.Json;

namespace Albert.Commons.Helpers
{
    public class DistributedCacheHelper : IDistributedCacheHelper
    {
        private readonly IDistributedCache distCache;

        public DistributedCacheHelper(IDistributedCache distCache)
        {
            this.distCache = distCache;
        }

        private static DistributedCacheEntryOptions CreateOptions(int baseExpireSeconds)
        {
            //过期时间.Random.Shared 是.NET6新增的
            double sec = Random.Shared.NextDouble(baseExpireSeconds, baseExpireSeconds * 2);
            TimeSpan expiration = TimeSpan.FromSeconds(sec);
            DistributedCacheEntryOptions options = new DistributedCacheEntryOptions();
            options.AbsoluteExpirationRelativeToNow = expiration;
            return options;
        }

        public TResult? GetOrCreate<TResult>(string cacheKey, Func<DistributedCacheEntryOptions, TResult?> valueFactory, int expireSeconds = 60)
        {
            string jsonStr = distCache.GetString(cacheKey);
            //缓存中不存在
            if (string.IsNullOrEmpty(jsonStr))
            {
                var options = CreateOptions(expireSeconds);
                TResult? result = valueFactory(options);//如果数据源中也没有查到,可能会返回null
                //null会被json序列化为字符串"null",所以可以防范“缓存穿透”
                string jsonOfResult = JsonSerializer.Serialize(result,
                    typeof(TResult));
                distCache.SetString(cacheKey, jsonOfResult, options);
                return result;
            }
            else
            {
                //"null"会被反序列化为null
                //TResult如果是引用类型,就有为null的可能性;如果TResult是值类型
                //在写入的时候肯定写入的是0、1之类的值,反序列化出来不会是null
                //所以如果obj这里为null,那么存进去的时候一定是引用类型
                distCache.Refresh(cacheKey);//刷新,以便于滑动过期时间延期
                return JsonSerializer.Deserialize<TResult>(jsonStr)!;
            }
        }

        public async Task<TResult?> GetOrCreateAsync<TResult>(string cacheKey, Func<DistributedCacheEntryOptions, Task<TResult?>> valueFactory, int expireSeconds = 60)
        {
            string jsonStr = await distCache.GetStringAsync(cacheKey);
            if (string.IsNullOrEmpty(jsonStr))
            {
                var options = CreateOptions(expireSeconds);
                TResult? result = await valueFactory(options);
                string jsonOfResult = JsonSerializer.Serialize(result,
                    typeof(TResult));
                await distCache.SetStringAsync(cacheKey, jsonOfResult, options);
                return result;
            }
            else
            {
                await distCache.RefreshAsync(cacheKey);
                return JsonSerializer.Deserialize<TResult>(jsonStr)!;
            }
        }

        public void Remove(string cacheKey)
        {
            distCache.Remove(cacheKey);
        }

        public Task RemoveAsync(string cacheKey)
        {
            return distCache.RemoveAsync(cacheKey);
        }
    }
}

中间件

在ASP.NET Core中,中间件是一个可以处理HTTP请求或者响应的软件管道。在大型团队中,中间件应该以NuGet包的形式提供,由Nuget处理更新,通过尽量将中间件拆得足够小,以提供每个中间件独立更新的能力。
image.png

自定义异常显示中间件-UseDeveloperExceptionPage

app.UseDeveloperExceptionPage(new developerExceptionPageOptions(){SourceCodeLineCount = 3}); 显示发生异常的前后三行代码
尽可能的放在其他中间件的前面

默认文件中间件-UseDefaultFiles

app.UseDefaultFiles(),此中间件必须要在静态文件中间件之前。
默认中间件会去查找的地址信息顺序:index.htm index.html default.htm default.html 这些需要放到wwwroot文件夹下

静态文件中间件-UseStaticFiles

静态文件的默认目录是wwwroot, 在wwwroot目录下添加静态资源文件 favico.ico
在Configure方法中调用静态文件中间件: app.UseStaticFiles(); 访问:http://localhost:14086/favicon.ico 即可获得图片资源(html,cs等都一样)

自定义默认文件

ytapp.UseDefaultFiles(new DefaultFilesOptions() { DefaultFileNames = { “default1.html” } });

UseFileServer中间件

此中间件结合了UseStaticFiles() UseDefaultFiles() UseDirectoryBrowser()中间件的功能yt
此中间件支持目录浏览,并运行用户查看指定目录中的文件
app.UseFileServer(new FileServerOptions() { DefaultFileNames = { “default1.html” } });

IWebHostEnvironment env服务常用方法

IsDevelopment 开发环境
IsEnvironment(“环境名”) 自定义环境 UAT用户验收测试环境 QA质量保证环境 Integration集成环境
IsProduction 生产环境
IsStaging 模拟环境

Filter组件

JWT(Json Web Token)

定义

image.png

优点

  • 状态保存在客户端,而非服务器端,天然适合分布式系统
  • 签名算法保证了客户端无法数据造假
  • 性能更高,不需要和中心状态服务器通讯,纯内存计算。

    ASP.NET Core JWT的基本使用

    ```python 创建一个新类库项目 xxx.xxx.Common
  1. 完成AppsettingsHelper.cs 安装Nuget: Microsoft.Extensions.Configuration Microsoft.Extensions.Configuration.Json Microsoft.Extensions.Configuration.Abstractions Microsoft.Extensions.Configuration.Binder

using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration.Json; using System.Net.NetworkInformation;

namespace SwiftCode.BBS.Common.Helper { public class AppSettingsHelper { private static IConfiguration Configuration { get; set; }

    private static string ContentPath { get;set; }

    public AppSettingsHelper(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public AppSettingsHelper(string contentPath)
    {
        var appsettingsPath = "appsettings.json";

        //如果配置文件是根据环境变量来区分的,可以这样配置
        //appsettingsPath = $"appsettings.{Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT")}.json";

        Configuration = new ConfigurationBuilder()
            .SetBasePath(contentPath)
            .Add(new JsonConfigurationSource { Path= appsettingsPath,Optional=false,ReloadOnChange=true })
            .Build();

        ContentPath = contentPath;
    }

    /// <summary>
    /// 封装要操作的字符
    /// </summary>
    /// <param name="sections"></param>
    /// <returns></returns>
    public static string app(params string[] sections)
    {
        try
        {
            if (sections.Any())
            {
                return Configuration[string.Join(":", sections)];
            }
        }
        catch (Exception)
        {

        }
        return "";
    }

    /// <summary>
    /// 递归获取配置信息数组
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="sections"></param>
    /// <returns></returns>
    public static List<T> app<T>(params string[] sections)
    {
        List<T> list = new List<T>();
        Configuration.Bind(string.Join(":", sections), list);
        return list;
    }
}

}

2.完成JwtHelper类库 Nuget安装: System.IdentityModel.Tokens

using Microsoft.IdentityModel.Tokens; using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; using System.Text;

namespace SwiftCode.BBS.Common.Helper { public class JwtHelper {

    /// <summary>
    /// 颁发JWT字符串
    /// </summary>
    /// <param name="tokenModel"></param>
    /// <returns></returns>
    public static string IssueJwt(TokenModelJwt tokenModel)
    {
        string iss = AppSettingsHelper.app(new string[] { "Audience", "Issuer" });
        string aud = AppSettingsHelper.app(new string[] { "Audience", "Audience" });
        string secret = AppSettingsHelper.app(new string[] { "Audience", "Secret" });

        var claims = new List<Claim>
            {
             /*
             * 特别重要:
               1、这里将用户的部分信息,比如 uid 存到了Claim 中,如果你想知道如何在其他地方将这个 uid从 Token 中取出来,请看下边的SerializeJwt() 方法,或者在整个解决方案,搜索这个方法,看哪里使用了!
               2、你也可以研究下 HttpContext.User.Claims ,具体的你可以看看 Policys/PermissionHandler.cs 类中是如何使用的。
             */
             new Claim(JwtRegisteredClaimNames.Jti, tokenModel.Uid.ToString()),
            new Claim(JwtRegisteredClaimNames.Iat, $"{new DateTimeOffset(DateTime.Now).ToUnixTimeSeconds()}"),
            new Claim(JwtRegisteredClaimNames.Nbf,$"{new DateTimeOffset(DateTime.Now).ToUnixTimeSeconds()}") ,
            //这个就是过期时间,目前是过期1000秒,可自定义,注意JWT有自己的缓冲过期时间
            new Claim (JwtRegisteredClaimNames.Exp,$"{new DateTimeOffset(DateTime.Now.AddSeconds(1000)).ToUnixTimeSeconds()}"),
            new Claim(ClaimTypes.Expiration, DateTime.Now.AddSeconds(1000).ToString()),
            new Claim(JwtRegisteredClaimNames.Iss,iss),
            new Claim(JwtRegisteredClaimNames.Aud,aud),

           };

        // 可以将一个用户的多个角色全部赋予;
        claims.AddRange(tokenModel.Role.Split(',').Select(s => new Claim(ClaimTypes.Role, s)));

        //秘钥 (SymmetricSecurityKey 对安全性的要求,密钥的长度太短会报出异常)
        var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secret));
        var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);

        var jwt = new JwtSecurityToken(
            issuer: iss,
            claims: claims,
            signingCredentials: creds);

        var jwtHandler = new JwtSecurityTokenHandler();
        var encodedJwt = jwtHandler.WriteToken(jwt);

        return encodedJwt;
    }

    /// <summary>
    /// 解析
    /// </summary>
    /// <param name="jwtStr"></param>
    /// <returns></returns>
    public static TokenModelJwt SerializeJwt(string jwtStr)
    {
        var jwtHandler = new JwtSecurityTokenHandler();
        TokenModelJwt tokenModelJwt = new TokenModelJwt();

        // token校验
        if (!string.IsNullOrEmpty(jwtStr) && jwtHandler.CanReadToken(jwtStr))
        {

            JwtSecurityToken jwtToken = jwtHandler.ReadJwtToken(jwtStr);

            object role;

            jwtToken.Payload.TryGetValue(ClaimTypes.Role, out role);

            tokenModelJwt = new TokenModelJwt
            {
                Uid = Convert.ToInt64(jwtToken.Id),
                Role = role == null ? "" : role.ToString()
            };
        }
        return tokenModelJwt;
    }
}

/// <summary>
/// 令牌
/// </summary>
public class TokenModelJwt
{
    /// <summary>
    /// Id
    /// </summary>
    public long Uid { get; set; }
    /// <summary>
    /// 角色
    /// </summary>
    public string Role { get; set; }
    /// <summary>
    /// 职能
    /// </summary>
    public string Work { get; set; }

}

}

  1. 在Program里注入AppsettingsHelper类 builder.Services.AddSingleton(new AppSettingsHelper(builder.Configuration));

  2. 写信息到API目录下的appsettings.json “Audience”: { “Secret”: “sdfsdfsrty45634kkhllghtdgdfss34faefadfgr”, “Issuer”: “SwiftCode.BBS”, “Audience”: “wr” }

  3. 获取JWT,在API项目中创建一个LoginController ///

    /// LoginController /// [Route(“api/[controller]/[action]”)] [ApiController] public class LoginController : ControllerBase {

     /// <summary>
     /// GetJwtAdminStr
     /// </summary>
     /// <param name="role"></param>
     /// <returns></returns>
     [HttpGet]
     public async Task<object> GetJwtAdminStr(string role)
     {
         // 将用户id和角色名,作为单独的自定义变量封装进 token 字符串中。
         TokenModelJwt tokenModel = new TokenModelJwt { Uid = 1, Role = role };
         var jwtStr = JwtHelper.IssueJwt(tokenModel);//登录,获取到一定规则的 Token 令牌
         var suc = true;
         return Ok(new
         {
             success = suc,
             token = jwtStr
         });
     }
    

    }

```python
Header和Payload

                object role;

                jwtToken.Payload.TryGetValue(ClaimTypes.Role, out role);

                tokenModelJwt = new TokenModelJwt
                {
                    Uid = Convert.ToInt64(jwtToken.Id),
                    Role = role == null ? "" : role.ToString()
                };
            }
            return tokenModelJwt;
        }
    }

    /// <summary>
    /// 令牌
    /// </summary>
    public class TokenModelJwt
    {
        /// <summary>
        /// Id
        /// </summary>
        public long Uid { get; set; }
        /// <summary>
        /// 角色
        /// </summary>
        public string Role { get; set; }
        /// <summary>
        /// 职能
        /// </summary>
        public string Work { get; set; }

    }

ASP.NET Core对于JWT的封装

Nuget安装
Microsoft.AspNetCore.Authentication.JwtBearer

配置认证
// AuAuthentication认证 Microsoft.AspNetCore.Authentication.JwtBearer
builder.Services.AddAuthentication(options =>
{
    options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(o =>
{
    //读取配置文件
    var audienceConfig = builder.Configuration.GetSection("Audience");
    var symmetricKeyAsBase64 = audienceConfig["Secret"];
    var keyByteArray = Encoding.ASCII.GetBytes(symmetricKeyAsBase64);
    var signingKey = new SymmetricSecurityKey(keyByteArray);

    o.TokenValidationParameters = new TokenValidationParameters
    {
        ValidateIssuerSigningKey = true,
        IssuerSigningKey = signingKey,
        ValidateIssuer = true,
        ValidIssuer = audienceConfig["Issuer"],//发行人
        ValidateAudience = true,
        ValidAudience = audienceConfig["Audience"],//订阅人
        ValidateLifetime = true,
        ClockSkew = TimeSpan.Zero,//这个是缓冲过期时间,也就是说,即使我们配置了过期时间,这里也要考虑进去,过期时间+缓冲,默认好像是7分钟,你可以直接设置为0
        RequireExpirationTime = true,
    };
});

配置授权
// Authorize Policy 授权 System.IdentityModel.Tokens.Jwt
builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("Admin", policy => policy.RequireRole("Admin").Build()); // Admin角色
    options.AddPolicy("System", policy => policy.RequireRole("System").Build());// System角色
    options.AddPolicy("SystemOrAdmin", policy => policy.RequireRole("Admin", "System"));//或的关系
    options.AddPolicy("SystemAndAdmin", policy => policy.RequireRole("Admin").RequireRole("System"));//且的关系
});

开启认证
app.UseAuthentication();
开启授权
app.UseAuthorization();

解决JWT无法提前撤回的难题

解决多个客户端不能同时登录问题,需要进行下线操作(含恶意用户截获JWT)。
image.png
image.png
image.png

托管服务

image.png

//创建一个读取文件的托管服务类ReadFileHostedService
//该类继承BackgroundService类(Nuget包 Microsoft.Extensions.Hosting.Abstractions)
public class ReadFileHostedService : BackgroundService
    {
        protected override async Task ExecuteAsync(CancellationToken stoppingToken)
        {
            Console.WriteLine("HostedService Starting");
            //await Task.Delay(3000);
            string content = await                          File.ReadAllTextAsync("F:\\Code_Repo\\01_CSharp\\SwiftCode.BBS.API\\SwiftCode.BBS.API\\LICENSE.txt");
            Console.WriteLine("文件读取完成");
            await Task.Delay(3000);
            Console.WriteLine(content);
        }
    }

// Program.cs里面启动Hosting Service
// 启动托管服务
builder.Services.AddHostedService<ReadFileHostedService>();

image.png
image.png

 public class ReadFileHostedService : BackgroundService
    {
        // 将注册服务放入到ScopeFactory中
        // private readonly IServiceScopeFactory serviceScopeFactory;

        private readonly ILogger<ReadFileHostedService> logger;
        public ReadFileHostedService(ILogger<ReadFileHostedService> logger)
        {
            this.logger = logger;
            //this.serviceScopeFactory.CreateScope
        }

        protected override async Task ExecuteAsync(CancellationToken stoppingToken)
        {
            try
            {
                logger.LogInformation("TestLog");
                Console.WriteLine("HostedService Starting");
                //await Task.Delay(3000);
                string content = await File.ReadAllTextAsync("F:\\Code_Repo\\01_CSharp\\SwiftCode.BBS.API\\SwiftCode.BBS.API\\LICENSE.txt");
                Console.WriteLine("文件读取完成");
                await Task.Delay(4000);
                Console.WriteLine(content);
            }
            catch (Exception ex)
            {
                logger.LogError(ex.Message);
            }

        }
    }

托管服务案列-数据的定时导出

Hangfire 定时操作的框架

  • Hangfire框架使用说明
  • Hangfire官方网站 ```csharp using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using SwiftCode.BBS.APIDbContext; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks;

namespace SwiftCode.BBS.Services { public class ScheduledService : BackgroundService { private readonly IServiceScope serviceScope;

    public ScheduledService(IServiceScopeFactory serviceScopeFactory)
    {
        // 创建一个服务,这个服务可以包含其他服务
        // serviceScope.
        this.serviceScope = serviceScopeFactory.CreateScope();
    }

    // 当调用CreateScope必须要释放
    public override void Dispose()
    {
        this.serviceScope.Dispose();
        base.Dispose();
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        try
        {
            using AlbertDbContext ctx = new AlbertDbContext();
            while (!stoppingToken.IsCancellationRequested)
            {
                Order order1 = await ctx.Orders.Include(o => o.Delivery)
     .FirstAsync(o => o.Name.Contains("充电器"));
                Console.WriteLine($"名称:{order1.Name},单号:{order1.Delivery.Number}");
                await Task.Delay(3000);
            }
        }
        catch (Exception)
        {
            throw;
        }

    }
}

} ```

测试工具

Postman

image.png
image.png