EasySharding.EFCore 如何设计使用一套代码完成的EFCore Migration 构建Saas系统多租户不同业务需求且满足租户自定义分库分表、数据迁移能力?

摘要:
._ hashchangedid==_ hashchangedo;}}ShardingModelCacheKey设计了一个变量值,该变量值被记录和比较。Hashchangedid用于确定已更改ID的哈希代码值。因此,将来只需要ModelCacheKeyEqual的返回值来告诉您何时应该创建迁移数据。为了为后续业务提供支持,这一步似乎还可以。然而,存在许多问题?如何维护迁移文件?如何应对多租户交错无序的迁移?通过命令添加模型类并生成迁移迁移文件。第一代程序不会出错,但当我们的shardingInfo发生变化时,Cre

下面用一篇文章来完成这些事情

多租户系统的设计单纯的来说业务,一套Saas多租户的系统,面临很多业务复杂性,不同的租户存在不同的业务需求,大部分相同的表结构,那么如何使用EFCore来完成这样的设计呢?满足不同需求的数据库结构迁移

这里我准备设计一套中间件来完成大部分分库分表的工作,然后可以通过自定义的Migration 数据库文件来迁移构建不同的租户数据库和表,抛开业务处理不谈,单纯提供给业务处理扩展为前提的设计,姑且把这个中间件命名为:

EasySharding

原理:数据库Migation创建是利用 ModelCacheKeyFactory监控ModelCacheKey的模型是否存在变化来完成Create,并不是每次都需要Create

ModelCacheKeyFactory 通过 ModelCacheKey 的 Equals 方法返回的 Bool值 来确定是否需要Create

所以我们通过自定的类ShardingModelCacheKeyFactory来重写ModelCacheKeyFactory 的Create方法 ,ShardingModelCacheKey来重写ModelCacheKey的Equal方法

EasySharding.EFCore 如何设计使用一套代码完成的EFCore Migration 构建Saas系统多租户不同业务需求且满足租户自定义分库分表、数据迁移能力?第1张EasySharding.EFCore 如何设计使用一套代码完成的EFCore Migration 构建Saas系统多租户不同业务需求且满足租户自定义分库分表、数据迁移能力?第2张
 public class ShardingModelCacheKeyFactory<T> : ModelCacheKeyFactory where T : DbContext, IShardingDbContext
    {

        public ShardingModelCacheKeyFactory(ModelCacheKeyFactoryDependencies dependencies) : base(dependencies)
        {
        }
        /// <summary>
        /// 重写创建 
        /// </summary>
        /// <param name="context"></param>
        /// <returns></returns>
        public override object Create(DbContext context)
        {
            var dbContext = context as T;
            var key = string.Format("{0}{1}", dbContext?.ShardingInfo?.DatabaseTagName, dbContext?.ShardingInfo?.StufixTableName);
            var strchangekey = string.IsNullOrEmpty(key) ? "0" : key;
            return new ShardingModelCacheKey<T>(dbContext, strchangekey);
        }


    }
ShardingModelCacheKeyFactory
EasySharding.EFCore 如何设计使用一套代码完成的EFCore Migration 构建Saas系统多租户不同业务需求且满足租户自定义分库分表、数据迁移能力?第3张EasySharding.EFCore 如何设计使用一套代码完成的EFCore Migration 构建Saas系统多租户不同业务需求且满足租户自定义分库分表、数据迁移能力?第4张
 internal class ShardingModelCacheKey<T> : ModelCacheKey where T : DbContext, IShardingDbContext
    {
        private readonly T _context;
        /// <summary>
        /// _hashchangedid 
        /// </summary>
        private readonly string _hashchangedid;
        public ShardingModelCacheKey(T context, string hashchangedid) : base(context)
        {
            this._context = context;
            this._hashchangedid = hashchangedid;
        }


        public override int GetHashCode()
        {

            var hashCode = base.GetHashCode();

            if (_hashchangedid != null)
            {
                hashCode ^= _hashchangedid.GetHashCode();
            }
            else
            {
                //构成已经默认了 一般不得出发异常
                throw new Exception("this is no tenantid");
            }

            return hashCode;
        }


        /// <summary>
        /// 判断模型更改缓存是否需要创建Migration
        /// </summary>
        /// <param name="other"></param>
        /// <returns></returns>
        protected override bool Equals(ModelCacheKey other)
        {
            return base.Equals(other) && (other as ShardingModelCacheKey<T>)?._hashchangedid == _hashchangedid;
        }
    }
ShardingModelCacheKey

设计一个变量值,通过记录并比较 _hashchangedid 一个改变的标识的hashcode值来确定,所以后续只需要 ModelCacheKey Equal 的返回值 来告诉你什么时候应该发生Migration数据迁移创建了,为后续的业务提供支持

做到这一步骤看起来一切都是ok的,然而有很多问题?怎么按照租户去生成库或表,不同租户表结构不同怎么办?Migration迁移文件怎么维护?多个租户Migration交错混乱怎么办?

为了满足不同租户的需求,为此设计了一个ShardingInfo的类对租户提供可改变的数据库上下文对象以及数据库或表区别的类来告诉ModelCacheKey 不同的变化,为了提供更多的场景,这里提供租户 可以分库,可以分表、亦可以区分数据行,都需要结合业务实现,这里不做过多讨论

EasySharding.EFCore 如何设计使用一套代码完成的EFCore Migration 构建Saas系统多租户不同业务需求且满足租户自定义分库分表、数据迁移能力?第5张EasySharding.EFCore 如何设计使用一套代码完成的EFCore Migration 构建Saas系统多租户不同业务需求且满足租户自定义分库分表、数据迁移能力?第6张
 /// <summary>
    /// 黎又铭 2021.10.30
    /// </summary>
   public class ShardingInfo
    {
        /// <summary>
        /// 数据库地址 租户可分库
        /// </summary>
        public string ConStr { get; set; }
        /// <summary>
        /// 数据库名称标识,分库特殊意义,唯一,结合StufixTableName来识别Migration文件变化缓存hashcode值
        /// </summary>
        public string DatabaseTagName { get; set; }
        /// <summary>
        /// 分表处理  租户可分表
        /// </summary>
        public string StufixTableName { get; set; }
        /// <summary>
        /// 租户也可分数据行
        /// </summary>
        public string TenantId { get; set; }
    }
View Code

设计这个类很有必要,第一为了提供给数据库上下文扩展变化,第二利用它的字段来确定变化,第三后续根据它来完成Migiration的差异变化

接下来就是需要根据ShardingInfo的变化来创建不同的的表结构了,怎么来实现呢?

添加模型类,通过命令生成Migration迁移文件,程序第一次生成不会有错,而当我们的ShardingInfo发生变化出发CreateMigration的时候表就会存在发生二次创建错误,原因是我们记录了创建变化而没有记录Migration文件的变化关联

所以这里还需要处理一个关键类,重写MigrationsAssembly的CreateMigration方法,将ShardingInfo变化告诉Migration文件,所以要做到这一步骤,还需要对每次数据迁移变化的Migration文件进行改造以及CodeFirst中自定义的数据表结构稍微修改下

EasySharding.EFCore 如何设计使用一套代码完成的EFCore Migration 构建Saas系统多租户不同业务需求且满足租户自定义分库分表、数据迁移能力?第7张EasySharding.EFCore 如何设计使用一套代码完成的EFCore Migration 构建Saas系统多租户不同业务需求且满足租户自定义分库分表、数据迁移能力?第8张
 public ShardingMigrationAssembly(ICurrentDbContext currentContext, IDbContextOptions options, IMigrationsIdGenerator idGenerator, IDiagnosticsLogger<DbLoggerCategory.Migrations> logger) : base(currentContext, options, idGenerator, logger)
        {
            context = currentContext.Context;
        }
        public override Migration CreateMigration(TypeInfo migrationClass, string activeProvider)
        {

            if (activeProvider == null)
                throw new ArgumentNullException($"{nameof(activeProvider)} argument is null");

            var hasCtorWithSchema = migrationClass
.GetConstructor(new[] { typeof(ShardingInfo) }) != null;

            if (hasCtorWithSchema && context is IShardingDbContext tenantDbContext)
            {
                var instance = (Migration)Activator.CreateInstance(migrationClass.AsType(), tenantDbContext?.ShardingInfo);
                instance.ActiveProvider = activeProvider;
                return instance;
            }
            return base.CreateMigration(migrationClass, activeProvider);


        }
ShardingMigrationAssembly

将变化告诉Migration迁移文件,好在迁移文件中做对于修改 下面是一个Demo Migrationi迁移文件

EasySharding.EFCore 如何设计使用一套代码完成的EFCore Migration 构建Saas系统多租户不同业务需求且满足租户自定义分库分表、数据迁移能力?第9张EasySharding.EFCore 如何设计使用一套代码完成的EFCore Migration 构建Saas系统多租户不同业务需求且满足租户自定义分库分表、数据迁移能力?第10张
 public partial class initdata : Migration
    {
        private readonly ShardingInfo  _shardingInfo;
        public initdata(ShardingInfo shardingInfo)
        {
            _shardingInfo = shardingInfo;
        }
        protected override void Up(MigrationBuilder migrationBuilder)
        {
            migrationBuilder.AlterDatabase()
                .Annotation("MySql:CharSet", "utf8mb4");

            migrationBuilder.CreateTable(
                name: $"OneDayTable{_shardingInfo.GetName()}" ,
                columns: table => new
                {
                    Id = table.Column<int>(type: "int", nullable: false)
                        .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
                    Day = table.Column<string>(type: "longtext", nullable: true)
                        .Annotation("MySql:CharSet", "utf8mb4")
                },
                constraints: table =>
                {
                    table.PrimaryKey("PK_OneDayTables", x => x.Id);
                })
                .Annotation("MySql:CharSet", "utf8mb4");

            migrationBuilder.CreateTable(
                name: $"Test{_shardingInfo.GetName()}",
                columns: table => new
                {
                    Id = table.Column<int>(type: "int", nullable: false)
                        .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
                    TestName = table.Column<string>(type: "longtext", nullable: true)
                        .Annotation("MySql:CharSet", "utf8mb4")
                },
                constraints: table =>
                {
                    table.PrimaryKey("PK_Tests", x => x.Id);
                }
               // schema: _shardingInfo.StufixTableName
                )
                .Annotation("MySql:CharSet", "utf8mb4");
        }

        protected override void Down(MigrationBuilder migrationBuilder)
        {
            migrationBuilder.DropTable(
                name: $"OneDayTable{_shardingInfo.GetName()}");

            migrationBuilder.DropTable(
                name: $"Test{_shardingInfo.GetName()}");
        }
    }
initdata

通过构造函数接受迁移变化类,就可以告诉它不同的变化生成不同的表了

做到这里似乎可以生成了,这里还需要注意Migraion文件迁移表__efmigrationshistory的变化问题,需要为不同的表结构变化生成不同的 __efmigrationshistory 历史记录表,防止同一套系统中不同的表结构迁移被覆盖的情况

需要注意的是这里的记录表需要结合变化类ShardingInfo文件来完成

  builder.MigrationsHistoryTable($"__EFMigrationsHistory_{base.ShardingInfo.GetName()}");

可以参见下这里:https://docs.microsoft.com/zh-cn/ef/core/managing-schemas/migrations/history-table

我还不得不为它提供一些方便的扩展方法来更好的完成这个操作,例如 IEntityTypeConfiguration 的处理,可能就是为了少写结构构造函数,尴尬~

EasySharding.EFCore 如何设计使用一套代码完成的EFCore Migration 构建Saas系统多租户不同业务需求且满足租户自定义分库分表、数据迁移能力?第11张EasySharding.EFCore 如何设计使用一套代码完成的EFCore Migration 构建Saas系统多租户不同业务需求且满足租户自定义分库分表、数据迁移能力?第12张
public class ShardingEntityTypeConfigurationAbstract<T> : IEntityTypeConfiguration<T> where T : class
    {
        /// <summary>
        /// 这个字段将提供扩展自定义规则的后缀名称
        /// </summary>
        public string _suffix { get; set; } = string.Empty;
        
        public ShardingEntityTypeConfigurationAbstract(string suffix)
        {
            this._suffix = suffix;


        }
        public ShardingEntityTypeConfigurationAbstract()
        { 
        }
        public virtual void Configure(EntityTypeBuilder<T> builder)
        {
           
        }
    }
ShardingEntityTypeConfigurationAbstract
EasySharding.EFCore 如何设计使用一套代码完成的EFCore Migration 构建Saas系统多租户不同业务需求且满足租户自定义分库分表、数据迁移能力?第13张EasySharding.EFCore 如何设计使用一套代码完成的EFCore Migration 构建Saas系统多租户不同业务需求且满足租户自定义分库分表、数据迁移能力?第14张
 public class TestMap : ShardingEntityTypeConfigurationAbstract<Test>
    {

        public override void Configure(EntityTypeBuilder<Test> builder)
        {
            builder.ToTable($"Test"+ _suffix);

        }
    }
TestMap

最后为了支持EFCore的查询我们还需要处理查询DbSet<Test> 属性,为了让我们在调用查询的时候不用去考虑当前分表的是哪一个分表,只需要关注 Tests本身 不用去管变化,扩展了ToTableSharding的方法

 public DbSet<Test> Tests { get; set; }

结合上诉处理,我在模型创建重载里面这样处理如下:自定义的模型对象关系配置

 modelBuilder.ApplyConfiguration(new ShardingEntityTypeConfigurationAbstract<Test>(ShardingInfo.GetName()));
            modelBuilder.ApplyConfiguration(new ShardingEntityTypeConfigurationAbstract<OneDayTable>(ShardingInfo.GetName()));
            modelBuilder.ApplyConfiguration(new ShardingEntityTypeConfigurationAbstract<TestTwo>());

查询对象表关系配置

modelBuilder.Entity<Test>().ToTableSharding("Test", ShardingInfo);
            modelBuilder.Entity<OneDayTable>().ToTableSharding("OneDayTable", ShardingInfo);
            modelBuilder.Entity<TestTwo>().ToTable("TestTwo");

这个时候我们是不是该注入DbContext上下文对象了,这里修改了一些东西,定义业务上下文对象BusinessContext ,这里需要继承分库上下文对象

EasySharding.EFCore 如何设计使用一套代码完成的EFCore Migration 构建Saas系统多租户不同业务需求且满足租户自定义分库分表、数据迁移能力?第15张EasySharding.EFCore 如何设计使用一套代码完成的EFCore Migration 构建Saas系统多租户不同业务需求且满足租户自定义分库分表、数据迁移能力?第16张
 public class BusinessContext : ShardingDbContext
    {


        IConfiguration _configuration;


        #region Migrations 修改文件

        #endregion
        public DbSet<Test> Tests { get; set; }
        public DbSet<OneDayTable> OneDayTables { get; set; }

        public DbSet<TestTwo> TestTwos { get; set; }
        public BusinessContext(DbContextOptions<BusinessContext> options, ShardingInfo shardingInfo, IConfiguration configuration) : base(options, shardingInfo)
        {
            _configuration = configuration;
        }

        public BusinessContext(ShardingInfo shardingInfo, IConfiguration configuration) : base(shardingInfo)
        {
            _configuration = configuration;
        }

}
BusinessContext

这里我提供了一个构造函数来接受创建自定义变化的上下文对象,并且在扩展变化的构造函数ShardingDbContext中执行了一次Migrate来促发更改迁移,这个自定义的上下文对象在创建的时候促发Migrate,然后根据传递的变化文件的hashcode值来确定是否需要CreateMigration操作

为了让调用看起来不会有那么的new BusinessContext(new Sharding{  });这样的操作,何况存在多个数据库上下文对象的情况,这样就不漂亮了,所以稍加修改了下:

EasySharding.EFCore 如何设计使用一套代码完成的EFCore Migration 构建Saas系统多租户不同业务需求且满足租户自定义分库分表、数据迁移能力?第17张EasySharding.EFCore 如何设计使用一套代码完成的EFCore Migration 构建Saas系统多租户不同业务需求且满足租户自定义分库分表、数据迁移能力?第18张
 public class ShardingConnection<TContext> where TContext : DbContext
    {
        public TContext con;
        public ShardingConnection(TContext context)
        {
            con = context;
        }
        public TContext GetContext()
        {
            return con;
        }
        public TContext GetShardingContext(ShardingInfo shardingInfo)
        {
           
            return (TContext)Activator.CreateInstance(typeof(TContext), shardingInfo,null);

        }
    }
ShardingConnection

定义了ShardingConnection泛型类来获取未区分的表结构连接或创建区分的表结构上下文对象,让后DI注入下,这样写起来好像就ok了

下面来实现看看,这里定义了分库 两个库easysharding 和 easysharding1  ,easysharding 按 oranizeA 分了表, easysharding1 按 oranizeB 和当前日期分了表

贴上测试代码:

EasySharding.EFCore 如何设计使用一套代码完成的EFCore Migration 构建Saas系统多租户不同业务需求且满足租户自定义分库分表、数据迁移能力?第19张EasySharding.EFCore 如何设计使用一套代码完成的EFCore Migration 构建Saas系统多租户不同业务需求且满足租户自定义分库分表、数据迁移能力?第20张
  public class HomeController : Controller
    {
        ShardingConnection<BusinessContext> context;
        public HomeController(ShardingConnection<BusinessContext> shardingConnection)
        {
            context = shardingConnection;
            
        }
        [HttpGet]
        public IActionResult Index(string id)
        {
            var con = context.GetContext();
            con.Tests.Add(new MySql.Test { Id = new Random().Next(0, 10000), TestName = "11111111" });
            con.SaveChanges();
            var c = con.Tests.AsNoTracking().ToList();

            var con1 = context.GetShardingContext(new ShardingInfo
            {
                DatabaseTagName = $"easysharding",
                StufixTableName = $"oranizeA",
                ConStr = $"server=192.168.0.208;port=3306;user=root;password=N5_?MCaE$wDD;database=easysharding;SslMode=none;",
            });

            con1.Tests.Add(new MySql.Test { Id = new Random().Next(0, 10000), TestName = "oranizeA" });
            con1.SaveChanges();
            var c1 = con1.Tests.AsNoTracking().ToList();


            var con2 = context.GetShardingContext(new ShardingInfo
            {
                DatabaseTagName = $"easysharding1",
                StufixTableName = $"oranizeB",
                ConStr = $"server=192.168.0.208;port=3306;user=root;password=N5_?MCaE$wDD;database=easysharding1;SslMode=none;",
            });

            con2.Tests.Add(new MySql.Test { Id = new Random().Next(0, 10000), TestName = "oranizeB" });
            con2.SaveChanges();
            var c2 = con2.Tests.AsNoTracking().ToList();
            
            //针对一些需求,需要每天生成表的
            var con3 = context.GetShardingContext(new ShardingInfo
            {
                DatabaseTagName = $"easysharding1",
                StufixTableName = $"{string.Format("{0:yyyyMMdd}",DateTime.Now)}",
                ConStr = $"server=192.168.0.208;port=3306;user=root;password=N5_?MCaE$wDD;database=easysharding1;SslMode=none;",
            });

            con3.Tests.Add(new MySql.Test { Id = new Random().Next(0, 10000), TestName = "日期日期日期" });
            con3.SaveChanges();
            var c3 = con3.Tests.AsNoTracking().ToList();


            return Ok();
        }
    }
测试代码

查看结果,添加查询都到了对应的库或表里面,注意前面说到的查询 Tests 始终如一,但是数据却来之不同的库 不同的表了,有点那个么个意思了~

EasySharding.EFCore 如何设计使用一套代码完成的EFCore Migration 构建Saas系统多租户不同业务需求且满足租户自定义分库分表、数据迁移能力?第21张

EasySharding.EFCore 如何设计使用一套代码完成的EFCore Migration 构建Saas系统多租户不同业务需求且满足租户自定义分库分表、数据迁移能力?第22张

EasySharding.EFCore 如何设计使用一套代码完成的EFCore Migration 构建Saas系统多租户不同业务需求且满足租户自定义分库分表、数据迁移能力?第23张

 接下来看下数据库迁移情况,忽略我前面测试创建的表

EasySharding.EFCore 如何设计使用一套代码完成的EFCore Migration 构建Saas系统多租户不同业务需求且满足租户自定义分库分表、数据迁移能力?第24张

 这样看起来似乎就没有问题了吗?其实还是存在问题,其一,没有完成前面说的 不同数据库表结构不同,不同租户表结构不同的要求,其二,如果业务中更新的表是通用的表结构迁移文件,在不同租户访问触发migraion文件改变导致 创建的表结构已经存在的问题,为了解决这2个问题又不得不处理下了,上图中其实细心发现  testtwo这个表结构只在easysharding1库中存在,testtwo可视为对某一个差异结构变化而特殊生成的migraton迁移文件,从而满足上面的要求,定义自己的规则方法MigrateWhenNoSharding来确定这个变化对那些变化执行

EasySharding.EFCore 如何设计使用一套代码完成的EFCore Migration 构建Saas系统多租户不同业务需求且满足租户自定义分库分表、数据迁移能力?第25张EasySharding.EFCore 如何设计使用一套代码完成的EFCore Migration 构建Saas系统多租户不同业务需求且满足租户自定义分库分表、数据迁移能力?第26张
if (_shardingInfo.MigrateWhenNoSharding())
            {

                migrationBuilder.CreateTable(
                    name: "TestTwo",
                    columns: table => new
                    {
                        Id = table.Column<int>(type: "int", nullable: false)
                            .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
                        TestName = table.Column<string>(type: "longtext", nullable: true)
                            .Annotation("MySql:CharSet", "utf8mb4")
                    },
                    constraints: table =>
                    {
                        table.PrimaryKey("PK_TestTwo", x => x.Id);
                    })
                    .Annotation("MySql:CharSet", "utf8mb4");
            }
testtwo

处理好这一步,基本就完成了

参见:

https://docs.microsoft.com/zh-cn/dotnet/api/microsoft.entityframeworkcore.infrastructure.modelcachekey?view=efcore-5.0

https://docs.microsoft.com/zh-cn/dotnet/api/microsoft.entityframeworkcore.infrastructure.modelcachekeyfactory.create?view=efcore-5.0

https://www.thinktecture.com/en/entity-framework-core/changing-db-migration-schema-at-runtime-in-2-1/

项目GitHub 地址:https://github.com/woshilangdanger/easysharding.efcore

免责声明:文章转载自《EasySharding.EFCore 如何设计使用一套代码完成的EFCore Migration 构建Saas系统多租户不同业务需求且满足租户自定义分库分表、数据迁移能力?》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇selenium实现淘宝的商品爬取mysql查询特定时间段内的数据下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

JAVA 使用Comparator接口实现自定义排序

1、原则 Comparator接口可以实现自定义排序,实现Comparator接口时,要重写compare方法: int compare(Object o1, Object o2) 返回一个基本类型的整型 如果要按照升序排序,则o1 小于o2,返回-1(负数),相等返回0,01大于02返回1(正数) 如果要按照降序排序,则o1 小于o2,返回1(正数),相...

Java Properties 类读取配置文件信息

在我们平时写程序的时候,有些参数是经常改变的,而这种改变不是我们预知的。比如说我们开发了一个操作数据库的模块,在开发的时候我们连接本地的数据库那么IP,数据库名称,表名称,数据库主机等信息是我们本地的,要使得这个操作数据的模块具有通用性,那么以上信息就不能写死在程序里。通常我们的做法是用配置文件来解决。 各种语言都有自己所支持的配置文件类型。比如Pytho...

C#中将字符串转成 Base64 编码(小技巧)

/// <summary>/// /// </summary>/// <param name="Str"></param>/// <returns></returns>public string ToBase64Str(string Str){byte[] b = System.Te...

mysql主从之多线程复制

多线程复制 mysql 主从复制原理: 1. master 节点上的binlogdump 线程,在slave 与其正常连接的情况下,将binlog 发送到slave 上。 2. slave 节点的I/O Thread ,通过读取master 节点binlog 日志名称以及偏移量信息将其拷贝到本地relay log 日志文件。 3. slave 节点的SQL...

mysql安装使用

  linux系统 mysql-5.7.14-linux.zip部署包支持在CentOS 6.x/7.x 服务器硬盘大小要求     a) /data/mysql_data  如果存在该独立分区,要求该分区 >10G b) 如果仅存在 /data 分区, 要求该分区 >10G c) 否则,要求根分区/ > 10G MySQL_INST...

HTTP Bearer认证及JWT的使用

                            一、自定义CheckJWTAttribute特性方式 之前使用的是这种方式,根据jwt原理自定义生成JWT、验证jwt,感觉挺好。原理就是自定义一个拦截器(特性),拦截器对每个请求都优先进行处理,认证成功的进行下一步操作。 1、定义JWTPayload类 using System; namespace...