diff --git "a/\346\236\227\347\216\211\346\225\217/20240527_Filter\350\277\207\346\273\244\345\231\250.md" "b/\346\236\227\347\216\211\346\225\217/20240527_Filter\350\277\207\346\273\244\345\231\250.md" index a4483fe95fd2b05b25dbaa1186c50d248885f169..1da764e01abbf55e09bb09fdf882b996e26b8151 100644 --- "a/\346\236\227\347\216\211\346\225\217/20240527_Filter\350\277\207\346\273\244\345\231\250.md" +++ "b/\346\236\227\347\216\211\346\225\217/20240527_Filter\350\277\207\346\273\244\345\231\250.md" @@ -1,8 +1,12 @@ # 过滤器 命名空间:`Microsoft.AspNetCore.Mvc.Filters` ## ResultFailter 结果过滤器 +使数据以一定结构返回结果 -使数据以一定结构返回 +过滤器的实现逻辑如下: +1. 首先判断返回结果是否为ObjectResult类型。 +2. 如果是ObjectResult类型,则将结果替换为一个新的ObjectResult对象,对象包含了ApiResult对象作为返回结果。 +3. 如果不是ObjectResult类型,则直接返回一个新的ObjectResult对象,只包含了ApiResult的Code属性。 ```cs // Models 定义结果结构类 public class ApiResult @@ -20,15 +24,26 @@ public class ApiResponse : IAsyncResultFilter { public async Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next) { + // 判断结果是否为ObjectResult类型 if (context is ObjectResult objectResult) { // context.Result结束请求,并返回结果对象 - context.Result=new ObjectResult(new ApiResult + // context.Result=new ObjectResult(new ApiResult + // { + // Code=1000, + // Msg="成功", + // Data=objectResult.Value + // }); + + // 自定义Code与Msg的值 + // 从ViewData中获取自定义的Code和Msg属性值 + if (context.Controller is Controller controller) { - Code=1000, - Msg="成功", - Data=objectResult.Value - }); + apiResult.Code = (int)controller.ViewData["Code"]; + apiResult.Msg = (string)controller.ViewData["Msg"]; + // 创建一个新的ObjectResult对象,用于返回自定义的ApiResult对象 + context.Result = new ObjectResult(apiResult); + } } else { @@ -39,7 +54,7 @@ public class ApiResponse : IAsyncResultFilter } -// Startup.cs 使用过滤器 +// Startup.cs public void ConfigureServices(IServiceCollection services) { services.AddControllers(option=> @@ -47,8 +62,20 @@ public void ConfigureServices(IServiceCollection services) // 全局注册过滤器 option.Filters.Add(); }); - // 获取appseting.json里MySql的值 - _configuration.GetConnectionString("MySql"); +} + +// 通过[ApiResult]特性,在控制器中使用 +[HttpGet] +[ApiResult] +public IActionResult Get() +{ + // 模拟返回数据 + var data = new { Name = "John", Age = 30 }; + + // 设置自定义的Code和Msg属性值 + ViewData["Code"] = 200; + ViewData["Msg"] = "Custom message"; + return Ok(data); } ``` https://zzzcode.ai/efcore/code-explain @@ -112,6 +139,9 @@ public class ApiActionFilter : IAsyncActionFilter { return book; } + + + using Microsoft.AspNetCore.Mvc.Filters; namespace BookDemo; public sealed class ActionParameterFilter : ActionFilterAttribute diff --git "a/\346\236\227\347\216\211\346\225\217/20240611_\345\260\206\346\226\207\344\273\266\345\244\271\345\210\233\345\273\272\346\210\220Gitee\344\273\223\345\272\223.md" "b/\346\236\227\347\216\211\346\225\217/20240611_\345\260\206\346\226\207\344\273\266\345\244\271\345\210\233\345\273\272\346\210\220Gitee\344\273\223\345\272\223.md" index 033d10defcff20c28c972d5bf5c95cf09762b7ff..2647a9151c25b31b83b1512462b9a79e4ecb10c8 100644 --- "a/\346\236\227\347\216\211\346\225\217/20240611_\345\260\206\346\226\207\344\273\266\345\244\271\345\210\233\345\273\272\346\210\220Gitee\344\273\223\345\272\223.md" +++ "b/\346\236\227\347\216\211\346\225\217/20240611_\345\260\206\346\226\207\344\273\266\345\244\271\345\210\233\345\273\272\346\210\220Gitee\344\273\223\345\272\223.md" @@ -8,6 +8,8 @@ `git remote rm origin`取消文件夹远程连接 + +`git reset --hard 历史版本ID` 将项目回退到指定版本 # gitignore语法 在文件根目录下创建`.gitignore`,在该文件中定义的文件会被忽视 diff --git "a/\346\236\227\347\216\211\346\225\217/20240701_abp vnext\346\241\206\346\236\266.md" "b/\346\236\227\347\216\211\346\225\217/20240701_abp vnext\346\241\206\346\236\266.md" index 58cb1e999ff2ca6dce575e17d9e3187c6ec0141b..93e87bdc2e80157cfc114609b080133e9f3234ab 100644 --- "a/\346\236\227\347\216\211\346\225\217/20240701_abp vnext\346\241\206\346\236\266.md" +++ "b/\346\236\227\347\216\211\346\225\217/20240701_abp vnext\346\241\206\346\236\266.md" @@ -1,3 +1,18 @@ # ABP vNext -https://www.cnblogs.com/zzy-tongzhi-cnblog/p/16545513.html -ABP vNext 是一个基于 DDD 的框架,它提供了丰富的工具和结构,帮助开发者更容易地实现 DDD 原则,并构建更复杂、可维护的应用程序。 \ No newline at end of file +[结构中各层次的作用:](https://www.cnblogs.com/zzy-tongzhi-cnblog/p/16545513.html):`https://www.cnblogs.com/zzy-tongzhi-cnblog/p/16545513.html` +ABP vNext 是一个基于 DDD 的框架,它提供了丰富的工具和结构,帮助开发者更容易地实现 DDD 原则,并构建更复杂、可维护的应用程序。 +## *.Domain.Shared 领域共享层 +在里面定义的常量或变量不只在邻域层中使用 +## *.Domain 邻域层 核心层 +主要包含实体Entity,集合根,领域服务,值类型,存储接口Repository和解决方案的其他领域对象。 +## Application.Contracts +Application.Contracts项目主要包含IService应用服务接口和应用层的数据传输对象 (DTO)。它用于分离应用层的接口和实现。 +依赖 Domain +## *.Application 应用层 +项目包含Application.Contracts项目的IService应用服务接口实现. +依赖 Application.Contracts、Domain +## *.Infrastructure 基础设施 +对整个应用程序提供基础实现,例如仓储的实现、工作单元模式的实现、Redis缓存、队列服务等。 +## *.EntityFrameworkCore =》基础设施中抽离出来 +集成EF Core项目,它定义了DbContext并实现Domain.Repository存储层。 +依赖 Domain diff --git "a/\346\236\227\347\216\211\346\225\217/20240702_\351\241\271\347\233\256\345\210\206\345\261\202\346\236\266\346\236\204-\351\242\206\345\237\237\345\261\202Domain.md" "b/\346\236\227\347\216\211\346\225\217/20240702_\351\241\271\347\233\256\345\210\206\345\261\202\346\236\266\346\236\204-\351\242\206\345\237\237\345\261\202Domain.md" index 23779bf4794bcb133b7ba5a9aa9779af2940f540..16537c329244c186094c7dba57873a6ed38aa4ab 100644 --- "a/\346\236\227\347\216\211\346\225\217/20240702_\351\241\271\347\233\256\345\210\206\345\261\202\346\236\266\346\236\204-\351\242\206\345\237\237\345\261\202Domain.md" +++ "b/\346\236\227\347\216\211\346\225\217/20240702_\351\241\271\347\233\256\345\210\206\345\261\202\346\236\266\346\236\204-\351\242\206\345\237\237\345\261\202Domain.md" @@ -1,4 +1,4 @@ -# Domain +# Domain 领域层 文件结构: ```cs Admin2024.Domain{ @@ -54,6 +54,18 @@ public class AppUser:BaseEntity // 多个手机号,创建单独的UserTelephone表 // 头像 public string Avatar{get;set;}=null!; + + // 登录时的数据验证 + public void Registry() + { + var username = this.UserName.Trim(); + if (!string.IsNullOrEmpty(username) && username.Length >= 6) + { + this.UserName = username; + } + var pTypes = Encoding.UTF8.GetBytes(Password); + this.Password = Convert.ToBase64String(pTypes); + } } public class AppRole:BaseEntity diff --git "a/\346\236\227\347\216\211\346\225\217/20240703_NavicatPernum.cs" "b/\346\236\227\347\216\211\346\225\217/20240703_NavicatPernum.cs" deleted file mode 100644 index 779556f41837da88ba5be8e2dd33cbdb611439d5..0000000000000000000000000000000000000000 --- "a/\346\236\227\347\216\211\346\225\217/20240703_NavicatPernum.cs" +++ /dev/null @@ -1 +0,0 @@ -使用fluntapi定义表名,字段面 abc_op 配置与映射属性 \ No newline at end of file diff --git "a/\346\236\227\347\216\211\346\225\217/20240703_NavicatPernum.md" "b/\346\236\227\347\216\211\346\225\217/20240703_NavicatPernum.md" new file mode 100644 index 0000000000000000000000000000000000000000..e6391eff298f0fe575db084b41ddd1bf6187a18d --- /dev/null +++ "b/\346\236\227\347\216\211\346\225\217/20240703_NavicatPernum.md" @@ -0,0 +1,3 @@ +# NavicatPernum + +远程连接服务器中的数据库 \ No newline at end of file diff --git "a/\346\236\227\347\216\211\346\225\217/20240703_\344\272\221\346\234\215\345\212\241\345\231\250\344\270\255\345\256\211\350\243\205pg.md" "b/\346\236\227\347\216\211\346\225\217/20240703_\344\272\221\346\234\215\345\212\241\345\231\250\344\270\255\345\256\211\350\243\205pg.md" index 2b31a7078ab0bd949d3a339a0aa4e63751d5f8ab..38172a7a06b2edf2d1a2e27cfed22c3f72f8bd86 100644 --- "a/\346\236\227\347\216\211\346\225\217/20240703_\344\272\221\346\234\215\345\212\241\345\231\250\344\270\255\345\256\211\350\243\205pg.md" +++ "b/\346\236\227\347\216\211\346\225\217/20240703_\344\272\221\346\234\215\345\212\241\345\231\250\344\270\255\345\256\211\350\243\205pg.md" @@ -48,4 +48,9 @@ postgres=# exit 2. 创建表:`create table 表名(id serial PRIMARY KEY,字段名 类型 [特性]);` 3. 删除表:`drop table 表名;` 4. 插入数据:`insert into 表名 values(val1,val2,val3,md5(val4));` 【md5('123')加密数据】 -5. `select*from 表名` \ No newline at end of file +5. `select*from 表名` + +# 修改密码 +su postgres +psql +\password \ No newline at end of file diff --git "a/\346\236\227\347\216\211\346\225\217/20240703_\351\241\271\347\233\256\345\210\206\345\261\202\346\236\266\346\236\204-\345\237\272\347\241\200EFC\345\261\202.md" "b/\346\236\227\347\216\211\346\225\217/20240703_\351\241\271\347\233\256\345\210\206\345\261\202\346\236\266\346\236\204-\345\237\272\347\241\200EFC\345\261\202.md" index b92f5a3d8c0fdd2ad28378330f46731512111ff3..7790e78e34567552e749e9271b66efb9dfeb3b94 100644 --- "a/\346\236\227\347\216\211\346\225\217/20240703_\351\241\271\347\233\256\345\210\206\345\261\202\346\236\266\346\236\204-\345\237\272\347\241\200EFC\345\261\202.md" +++ "b/\346\236\227\347\216\211\346\225\217/20240703_\351\241\271\347\233\256\345\210\206\345\261\202\346\236\266\346\236\204-\345\237\272\347\241\200EFC\345\261\202.md" @@ -1,5 +1,8 @@ # EntityFrameworkCore -添加依赖包:`dotnet add package Microsoft.EntityFrameworkCore` +添加依赖包: +1. `dotnet add package Microsoft.EntityFrameworkCore` +2. Microsoft.EntityFrameworkCore.SqlServer +3. Npgsql.EntityFrameworkCore.PostgreSQL 服务器驱动pg 文件结构: ```cs EntityFrameworkCore{ @@ -16,30 +19,210 @@ EntityFrameworkCore{ } ``` 引入Domain程序:`dotnet add reference 程序路径`或`dotnet sln 操作程序路径 add reference 引入程序路径` -***DbContext.cs:** +# EntityConfiguration +存放各领域模型的模块配置,必需下载==Npgsql.EntityFrameworkCore.PostgreSQL==包,才能进行模型配置 +**BaseEntityConfiguration** +1. 定义通用属性的字段约束 +2. 定义为抽象类:可以直接调用 +3. 定义为虚方法:可以重写,在重写的方法中使用base.方法名() 拼接上原来的方法 ```cs -using Admain2024.Domain.Entity.System; +using Admin.Domain.Entity; using Microsoft.EntityFrameworkCore; -// 服务器驱动:pg Npgsql.EntityFrameworkCore.PostgreSQL -namespace Admain2024.EntityFramworkCore; -public class Admain2024DbContext : DbContext +namespace Admin.EntityFrameworkCore.EntityConfiguration; +public abstract class BaseEntityConfiguration : IEntityTypeConfiguration where TEntity : BaseEntity { - public DbSet AppRole { get; set; } + public virtual void Configure(Microsoft.EntityFrameworkCore.Metadata.Builders.EntityTypeBuilder builder) + { + builder.Property(p => p.Id) + .HasColumnName("id") + .HasComment("主键") + .HasColumnOrder(1); + + builder.Property(p => p.BaseCode) + .HasColumnName("base_code") + .HasComment("编号,可空") + .HasColumnOrder(2); + + builder.Property(p => p.IsActived) + .HasColumnName("is_actived") + .HasComment("是否启用,默认是") + .HasDefaultValue(true) + .HasColumnOrder(993); + + ....... + } +} +``` +**AppUserConfiguration** +```cs +using Admin.Domain.Entity.System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace Admin.EntityFrameworkCore.EntityConfiguration; + +public class AppUserConfiguration : BaseEntityConfiguration +{ + /// + /// 每个实体只需要配置单独的属性即可 + /// 继承Configuration类,重写方法 + /// + /// + public override void Configure(EntityTypeBuilder builder) + { + base.Configure(builder); + // 表名 + builder.ToTable("app_user"); + + // HasColumnOrder 一般从3开始 + builder.Property(e => e.UserName) + .HasColumnName("username") + .HasComment("用户名") + .HasColumnOrder(3); + + ...... + } +} +``` +# DbContext.cs +```cs +using Admin.Domain.Entity; +using Admin.Domain.Entity.System; +using Admin.Infrastructure; +using Microsoft.EntityFrameworkCore; + +namespace Admin.EntityFrameworkCore; +public class AdminDbContext : DbContext +{ + // 构造函数依赖注入,引入雪花ID + private readonly SnowflakeIdGenerator _sfg; + // 定义表 public DbSet AppUser { get; set; } - public DbSet AppPermission { get; set; } - public DbSet AppUserRole { get; set; } - public DbSet AppRolePermission { get; set; } - public DbSet AppResource { get; set; } - public DbSet AppOperation { get; set; } - public Admain2024DbContext(DbContextOptions options) : base(options) - { } + public DbSet AppRole { get; set; } + public AdminDbContext(DbContextOptions options, SnowflakeIdGenerator snowflakeIdGenerator) : base(options) + { + _sfg = snowflakeIdGenerator; + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + // 设置数据种子 + modelBuilder.Entity() + .HasData( + new AppUser + { + // 调用方法,生成唯一的id + Id = _sfg.NextId(), + UserName = "Wang", + Password = "123admin", + Salt = "盐", + Telephone = "121312423" + }, + ...... + ); + // 需要一个个添加配置 + // builder.ApplyConfiguration(new AppUserConfiguration()); + // 一次性应用 应用程序集EntityConfiguration【同一个命名空间下的文件】中的所有配置 + modelBuilder.ApplyConfigurationsFromAssembly(typeof(EntityConfiguration.AppUserConfiguration).Assembly); + } + + // 重写保存函数 同步情况 + public override int SaveChanges() + { + var entities = ChangeTracker.Entries().Where(x => x.Entity is BaseEntity); + + // 添加 + entities.Where(item => item.State == EntityState.Added).ToList().ForEach(x => + { + var entity = (BaseEntity)x.Entity; + + entity.CreatedAt = DateTime.UtcNow; + entity.UpdatedAt = DateTime.UtcNow; + }); + + // 修改 + entities.Where(item => item.State == EntityState.Modified).ToList().ForEach(x => + { + var entity = (BaseEntity)x.Entity; + + entity.UpdatedAt = DateTime.UtcNow; + }); + + return base.SaveChanges(); + } + + public override Task SaveChangesAsync(CancellationToken cancellationToken = default) + { + // 代码块与SaveChanges一致 + } } ``` 将DbContext添加到依赖注入容器,再生成迁移文件 `dotnet ef migrations add 迁移文件名 --project 文件存放路径 -s启动项目路径` `dotnet ef database update --project 文件存放路径 -s启动项目路径` +# Repository +实现仓储接口 +**EfRepository** +```cs +using Admin.Domain.Entity; +using Admin.Domain.Interface; +using Microsoft.EntityFrameworkCore; + +namespace Admin.EntityFrameworkCore.Repository; +// 实现仓储接口 +public class EfRepository : IRepository where T : BaseEntity +{ + private readonly AdminDbContext _db; + private readonly DbSet _tb; + private readonly IQueryable _tableASNoTracking; + public EfRepository(AdminDbContext db) + { + _db = db; + _tb = _db.Set(); + _tableASNoTracking = _tb.AsNoTracking(); + } + public async Task CreateAsync(T entity) + { + await _tb.AddAsync(entity); + await _db.SaveChangesAsync(); + return entity; + } + + public async Task DeleteAsync(long id) + { + var entity = await _tb.FindAsync(id); + if (entity == null) return null; + _tb.Remove(entity); + return entity; + } + + public Task DeleteAsync(T entity) + { + _tb.Remove(entity); + return Task.FromResult(entity); + } + + public Task> GetAllAsync() + { + throw new NotImplementedException(); + } + + public Task GetByIdAsync(long id) + { + throw new NotImplementedException(); + } + + public Task UpdateAsync(long id, T entity) + { + throw new NotImplementedException(); + } +} +``` + + ```cs diff --git "a/\346\236\227\347\216\211\346\225\217/20240704_\350\231\232\346\213\237\346\234\272.md" "b/\346\236\227\347\216\211\346\225\217/20240704_\350\231\232\346\213\237\346\234\272.md" index 31583caad7dac6f0d6f256d6d87544023b95ca46..fd9ebd017102c495a5cc7bde316032d0aa613026 100644 --- "a/\346\236\227\347\216\211\346\225\217/20240704_\350\231\232\346\213\237\346\234\272.md" +++ "b/\346\236\227\347\216\211\346\225\217/20240704_\350\231\232\346\213\237\346\234\272.md" @@ -1 +1,3 @@ [VM下载及安装教程](blog.csdn.net/weixin_44275259/article/details/108294627) + +[连接虚拟机上的SQL](https://jingyan.baidu.com/article/d2b1d102c1df8d5c7e37d488.html) \ No newline at end of file diff --git "a/\346\236\227\347\216\211\346\225\217/20240705_redis.md" "b/\346\236\227\347\216\211\346\225\217/20240705_redis.md" index c6ae0aee815eeaad66e8bfc7d746a5415b7b5704..5a77a90c8056e92874ca9379f07d06948d1304b0 100644 --- "a/\346\236\227\347\216\211\346\225\217/20240705_redis.md" +++ "b/\346\236\227\347\216\211\346\225\217/20240705_redis.md" @@ -1,12 +1,21 @@ -# 消息队列 - -# 数据种子 +# redis +是一个开源的==内存存储数据库== +消息队列 +[安装教程](https://blog.csdn.net/weixin_44799217/article/details/113812410) +[操作教程](https://blog.csdn.net/m0_72853403/article/details/134550752) ```cs -modelBuilder.Entity() -.HasData(new MyEntity{},...) -``` +import redis + +# 连接到本地Redis实例 +r = redis.Redis(host='localhost', port=6379, db=0) -JWT token session cookies +# 设置键值对 +r.set('key', 'value') + +# 获取键值对 +value = r.get('key') +print(value) +``` # Token 1、Token的引入:Token是在客户端频繁向服务端请求数据,服务端频繁的去数据库查询用户名和密码并进行对比,判断用户名和密码正确与否,并作出相应提示,在这样的背景下,Token便应运而生。 @@ -17,7 +26,3 @@ JWT token session cookies 客户端:客户端接收到用户名和密码后并判断,如果正确了就将本地获取sessionID作为Token返回给客户端,客户端以后只需带上请求数据即可。 分析:这种方式使用的好处是方便,不用存储数据,但是缺点就是当session过期后,客户端必须重新登录才能进行访问数据。 - -# 雪花算法生成主键 -Guid随机生成,有问题 -[教程](https://blog.csdn.net/zhaoyu008/article/details/136099679) \ No newline at end of file diff --git "a/\346\236\227\347\216\211\346\225\217/20240705_\346\265\213\350\257\225ASP.NET Core Web API.md" "b/\346\236\227\347\216\211\346\225\217/20240705_\346\265\213\350\257\225ASP.NET Core Web API.md" new file mode 100644 index 0000000000000000000000000000000000000000..8b2bb337056dfb28bdf2712390bb800583d2a4df --- /dev/null +++ "b/\346\236\227\347\216\211\346\225\217/20240705_\346\265\213\350\257\225ASP.NET Core Web API.md" @@ -0,0 +1,4 @@ +# 单元测试 + + +[文章借鉴](http://www.uml.org.cn/test/201901311.asp) \ No newline at end of file diff --git "a/\346\236\227\347\216\211\346\225\217/20240708_snowflake\351\233\252\350\212\261\347\256\227\346\263\225.md" "b/\346\236\227\347\216\211\346\225\217/20240708_snowflake\351\233\252\350\212\261\347\256\227\346\263\225.md" new file mode 100644 index 0000000000000000000000000000000000000000..b9696fef8fefcb7be9373f14a2075b102dce4106 --- /dev/null +++ "b/\346\236\227\347\216\211\346\225\217/20240708_snowflake\351\233\252\350\212\261\347\256\227\346\263\225.md" @@ -0,0 +1,150 @@ +# 雪花算法 snowflake +雪花算法使用64位long类型的数据存储id +```md +0 - 0000000000 0000000000 0000000000 0000000000 0 - 0000000000 - 000000000000 + +符号位 时间戳 机器码 序列号 +``` +1. 最高位表示==符号位==,其中0代表整数,1代表负数,而id一般都是正数,所以最高位为0。 +2. ==41位存储毫秒级时间戳==,这个时间截是存储时间截的差值(当前时间截 - 开始时间截) * 得到的值),这里的的开始时间截,一般是我们的ID生成器开始使用的时间,一般为项目创建时间 +3. 10位存储机器码,最多支持1024台机器,当并发量非常高,同时有多个请求在同一毫秒到达,可以根据机器码进行第二次生成。 +4. 12位存储序列号,当同一毫秒有多个请求访问到了同一台机器后,此时序列号就派上了用场,为这些请求进行第三次创建,最多每毫秒每台机器产生2的12次方也就是4096个id + +# 存储方式 +1. U表示该常数用无符号整型方式存储,相当于 unsigned int + 1. 数值后面加“U”和“u”的意义是该数值是unsigned型。 +2. 数值后面加“L”和“l”(小写的l)的意义是该数值是long型。 【5L的数据类型为long int。】 + 1. L表示该常数用长整型方式存储,相当于 long +3. F表示该常数用浮点方式存储,相当于 float三、自动类型转换 + + +这些后缀跟是在字面量(literal,代码中的数值、字符、字符串)后面 +```md +``` +# EFCore中使用雪花算法生成ID +**SnowflakeIdGenerator:** +```cs +namespace Admin.Infrastructure; +public class SnowflakeIdGenerator +{ + // long:64位有符号整数 <==>int64 + + // twepoch: 时间戳基准值,即起始时间戳,这里设置为2021年1月1日零点的时间戳。 + private static readonly long twepoch = 1609459200000L; // 时间戳基准值,2021-01-01 00:00:00 + + // workerIdBits: 机器ID所占位数,这里设置为5位。 + private static readonly long workerIdBits = 5L; + + // datacenterIdBits: 数据中心ID所占位数,这里也设置为5位。 + private static readonly long datacenterIdBits = 5L; + + // sequenceBits: 序列号所占位数,设置为12位。 + private static readonly long sequenceBits = 12L; + + // maxWorkerId、maxDatacenterId、sequenceMask分别为计算出的最大机器ID、最大数据中心ID和序列号的掩码。 + private static readonly long maxWorkerId = -1L ^ (-1L << (int)workerIdBits); + private static readonly long maxDatacenterId = -1L ^ (-1L << (int)datacenterIdBits); + private static readonly long sequenceMask = -1L ^ (-1L << (int)sequenceBits); + // workerId、datacenterId、sequence用于存储机器ID、数据中心ID和序列号。 + + private static long workerId; + private static long datacenterId; + private static long sequence = 0L; + // lastTimestamp用于存储上一个ID生成的时间戳。 + private static long lastTimestamp = -1L; + + // 接收机器ID和数据中心ID作为参数 + public SnowflakeIdGenerator(long workerId, long datacenterId) + { + if (workerId > maxWorkerId || workerId < 0) + { + throw new ArgumentException($"worker Id cannot be greater than {maxWorkerId} or less than 0"); + } + if (datacenterId > maxDatacenterId || datacenterId < 0) + { + throw new ArgumentException($"datacenter Id cannot be greater than {maxDatacenterId} or less than 0"); + } + + SnowflakeIdGenerator.workerId = workerId; + SnowflakeIdGenerator.datacenterId = datacenterId; + } + /* + 方法NextId():生成下一个唯一ID。在该方法内部,首先获取当前时间戳,然后进行一系列的判断和操作: + +如果当前时间戳小于上一个时间戳(出现了时间回退),则抛出异常。 +如果当前时间戳和上一个时间戳相同,则递增序列号,如果序列号达到上限,则等到下一个毫秒。 +如果时间戳发生变化,重置序列号为0。 +更新最后生成ID的时间戳。 +返回计算后的唯一ID。 + */ + public long NextId() + { + lock (this) + { + // GenerateTimestamp():生成当前时间戳(以毫秒为单位) + long timestamp = GenerateTimestamp(); + + if (timestamp < lastTimestamp) + { + throw new Exception($"Clock moved backwards for {lastTimestamp - timestamp} milliseconds"); + } + + if (lastTimestamp == timestamp) + { + sequence = (sequence + 1) & sequenceMask; + if (sequence == 0) + { + // NextMillis(long lastTimestamp):生成下一个时间戳,确保不小于上一个时间戳。 + // 如果生成的时间戳小于等于上一个时间戳,则循环生成直到获得大于上一个时间戳的时间戳。 + timestamp = NextMillis(lastTimestamp); + } + } + else + { + sequence = 0; + } + + lastTimestamp = timestamp; + + return ((timestamp - twepoch) << 22) | (datacenterId << 17) | (workerId << 12) | sequence; + } + } + + private long GenerateTimestamp() + { + return (long)(DateTime.UtcNow - new DateTime(1970, 1, 1)).TotalMilliseconds; + } + + private long NextMillis(long lastTimestamp) + { + long timestamp = GenerateTimestamp(); + while (timestamp <= lastTimestamp) + { + timestamp = GenerateTimestamp(); + } + return timestamp; + } +} +``` +**Startup**:在依赖注入容器中注入SnowflakeIdGenerator,以便在程序集中使用该算法生成id +```cs +long workerId = 1; // 定义机器ID +long datacenterId = 1; // 定义数据中心ID +services.AddSingleton(new SnowflakeIdGenerator(workerId, datacenterId)); + +// 生成的ID如下 +// 311928119286042624 +// 311928119286042625 +``` +**使用算法:** +```cs +// 使用构造函数注入类,NextId() 生成唯一id +private readonly SnowflakeIdGenerator _sfg; + +public FnName(SnowflakeIdGenerator snowflakeIdGenerator) +{ + _sfg = snowflakeIdGenerator; +} + +Id = _sfg.NextId() +``` \ No newline at end of file diff --git "a/\346\236\227\347\216\211\346\225\217/20240705_\346\265\213\350\257\225.md" "b/\346\236\227\347\216\211\346\225\217/20240708_\346\237\245\346\235\200\346\234\215\345\212\241\345\231\250\346\214\226\347\237\277\347\227\205\346\257\222.md" similarity index 100% rename from "\346\236\227\347\216\211\346\225\217/20240705_\346\265\213\350\257\225.md" rename to "\346\236\227\347\216\211\346\225\217/20240708_\346\237\245\346\235\200\346\234\215\345\212\241\345\231\250\346\214\226\347\237\277\347\227\205\346\257\222.md" diff --git "a/\346\236\227\347\216\211\346\225\217/20240708_\351\241\271\347\233\256\345\210\206\345\261\202\346\236\266\346\236\204-\345\237\272\347\241\200\350\256\276\346\226\275\345\261\202.md" "b/\346\236\227\347\216\211\346\225\217/20240708_\351\241\271\347\233\256\345\210\206\345\261\202\346\236\266\346\236\204-\345\237\272\347\241\200\350\256\276\346\226\275\345\261\202.md" new file mode 100644 index 0000000000000000000000000000000000000000..e81229e70a0de12bdc3fd7e3dbaaeea5346778f0 --- /dev/null +++ "b/\346\236\227\347\216\211\346\225\217/20240708_\351\241\271\347\233\256\345\210\206\345\261\202\346\236\266\346\236\204-\345\237\272\347\241\200\350\256\276\346\226\275\345\261\202.md" @@ -0,0 +1,4 @@ +# Mapper + +# SnowFlack +用法与同期笔记 [20240708_snowflake雪花算法](./20240708_snowflake雪花算法.md) 一致 \ No newline at end of file diff --git "a/\346\236\227\347\216\211\346\225\217/20240709_\351\241\271\347\233\256\344\273\223\345\272\223\345\210\206\346\224\257\345\256\232\344\271\211.md" "b/\346\236\227\347\216\211\346\225\217/20240709_\351\241\271\347\233\256\344\273\223\345\272\223\345\210\206\346\224\257\345\256\232\344\271\211.md" new file mode 100644 index 0000000000000000000000000000000000000000..90b17d3136effd93eac00eb7fe0652f2e9c878e1 --- /dev/null +++ "b/\346\236\227\347\216\211\346\225\217/20240709_\351\241\271\347\233\256\344\273\223\345\272\223\345\210\206\346\224\257\345\256\232\344\271\211.md" @@ -0,0 +1,9 @@ +# 项目分支 +一个较合适的项目仓库,应至少包含3个分支:master主分支, fixd修复分支, create开发分支 + +1. main(或master)分支:这是项目的主分支,用于存储已合并进生产环境的代码。 +2. develop分支:这是开发分支,用于整合所有未合并的拉取请求,应该是稳定的,可以作为其他分支合并的起点。 +3. feature分支:用于创建新的功能。 +4. bugfix分支:用于修复bug。 +5. release分支:在准备发布新版本之前,从develop分支分出来的,用于最后的代码调整和测试。 +6. hotfix分支:用于紧急修复生产环境中的bug。 \ No newline at end of file diff --git "a/\346\236\227\347\216\211\346\225\217/20240710_\345\205\263\344\272\216\344\270\273\351\224\256\347\232\204\345\256\232\344\271\211.md" "b/\346\236\227\347\216\211\346\225\217/20240710_\345\205\263\344\272\216\344\270\273\351\224\256\347\232\204\345\256\232\344\271\211.md" new file mode 100644 index 0000000000000000000000000000000000000000..2ef4466d3a026f0647056ffe279da22ea7d08122 --- /dev/null +++ "b/\346\236\227\347\216\211\346\225\217/20240710_\345\205\263\344\272\216\344\270\273\351\224\256\347\232\204\345\256\232\344\271\211.md" @@ -0,0 +1,17 @@ +## Guid +GUID优点: +GUID全局唯一,适合分布式系统,方便拆分或合并表 +无需数据库往返即可在客户端生成GUID +GUID是无法猜测的,某些情况下它们可能更安全(例如,如果最终用户看到一个实体的Id,他们就找不到另一个实体的Id) +GUID缺点: +GUID占16个字节,int占4个字节,long占8个字节 +GUID本质上不是连续的,这会导致聚集索引出现性能问题 +## IGuidGenerator +==ABP提供IGuidGenerator==默认生成顺序Guid值,解决了聚集索引的性能问题,建议用IGuidGenerator设置Id,如果你不设置Id,存储库默认会使用IGuidGenerator +## snowflake +雪花算法生成Id,用法在同期笔记 [20240708_snowflake雪花算法](./20240708_snowflake雪花算法.md) + +## 禁用警告 +设置禁用cs8064警告:#pragema warning disable cs8604 +要禁用警告的代码 +启用cs8064警告:#pragema warning restore cs8604 \ No newline at end of file diff --git "a/\346\236\227\347\216\211\346\225\217/20240710_\345\215\225\345\205\203\346\265\213\350\257\225.md" "b/\346\236\227\347\216\211\346\225\217/20240710_\345\215\225\345\205\203\346\265\213\350\257\225.md" new file mode 100644 index 0000000000000000000000000000000000000000..8c9d65581d123cb9ab22ffeb0d2df03ba2b35374 --- /dev/null +++ "b/\346\236\227\347\216\211\346\225\217/20240710_\345\215\225\345\205\203\346\265\213\350\257\225.md" @@ -0,0 +1,127 @@ + +# 创建单元测试项目 + +1. classlib类库项目 +2. xunit项目 `dotnet new xunit -n 项目名` +3. 将类库项目引用到测试项目中 +4. dotnet test + +==注意==:需要在两个项目中都添加依赖包 `Xunit`与`Moq`,且包版本号要一致 + +# 语法 + +```cs +using Xunit; + + + public class 类名 + { + [Fact] + public async Task 测试方法名() + { + // 模拟控制器中要使用的仓储接口 + var R = new Mock<仓储接口>(); + // 自定义一个返回结果【类型要与返回类型一致】 + R.Setup(r => r.仓储接口中的方法(参数)).ReturnsAsync(RA); + + // 模拟映射器 + var M = new MapperConfiguration(cfg => + { + // 创建映射规则 + cfg.CreateMap(); + }); + // 创建参数映射器 + var MP = new Mapper(M); + + // 实例控制器(构造函数的参数) =》依赖注入 + var C = new 控制器名称(R.Object, MP); + + // 调用控制器中要测试的方法 + var RU = await C.方法名(参数); + + // 进行判断 + // 非空判断 + Assert.NotNull(RU.Value); + // 模拟用户数据是否与结果数据一致 + Assert.Equal(RA.属性, RU.Value.属性); + } + + // Add more test methods to cover other scenarios + } +``` + +# 实战 +在测试项目中添加测试类:以本人仓库中的LibraryAPI项目为例 +```cs + // 构造函数注入 + public IRepositoryWrapper _repositoryWrapper { get; } + public IMapper _mapper { get; } + // 当存在多个构造函数时,指定要使用的构造函数 + // [ActivatorUtilitiesConstructor] + public AuthorController(IRepositoryWrapper repositoryWrapper, IMapper mapper) + { + // 使用仓储 + _repositoryWrapper = repositoryWrapper; + // 映射器:转化实体对象与DTO对象 + _mapper = mapper; + } + + public async Task> GetByIdAsync(Guid authorId) + { + // 空引用异常 + var author = await _repositoryWrapper.Author.GetByIdAsync(authorId); + if (author == null) return NotFound("用户id不存在"); + var rel = _mapper.Map(author); + return rel; + } + +``` +```cs +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; +// Xunit Moq +using Xunit; +using Microsoft.AspNetCore.Mvc; +using Moq; +using AutoMapper; +using LibraryAPI; +using LibraryAPI.Controller; +using LibraryAPI.Interface; +using LibraryAPI.Domain; +using LibraryAPI.Dto; +namespace LibraryAPI.Tests +{ + public class AuthorControllerTests + { + [Fact] + public async Task GetByIdAsync_AuthorExists_ReturnsAuthorDto() + { + // Arrange + var authorId = Guid.NewGuid(); + var mockedAuthor = new Author { Id = authorId, Name = "John Doe", Age = 10 }; + + var repositoryWrapperMock = new Mock(); + repositoryWrapperMock.Setup(r => r.Author.GetByIdAsync(authorId)).ReturnsAsync(mockedAuthor); + + var mapperConfiguration = new MapperConfiguration(cfg => + { + cfg.CreateMap(); + }); + var mapper = new Mapper(mapperConfiguration); + + var controller = new AuthorController(repositoryWrapperMock.Object, mapper); + + // Act + var result = await controller.GetByIdAsync(authorId); + + // Assert + Assert.NotNull(result.Value); + Assert.Equal(mockedAuthor.Age, result.Value.Age); + Assert.Equal(mockedAuthor.Name, result.Value.Name); + } + + // Add more test methods to cover other scenarios + } +} +``` \ No newline at end of file diff --git "a/\346\236\227\347\216\211\346\225\217/20240711_Ant Design\347\273\204\344\273\266\345\272\223.md" "b/\346\236\227\347\216\211\346\225\217/20240711_Ant Design\347\273\204\344\273\266\345\272\223.md" new file mode 100644 index 0000000000000000000000000000000000000000..7ef7d000cff7e26a9802dd2fd5fc97321918df00 --- /dev/null +++ "b/\346\236\227\347\216\211\346\225\217/20240711_Ant Design\347\273\204\344\273\266\345\272\223.md" @@ -0,0 +1,7 @@ + +token指定时间过期后,后台自动发送新请求获取新的token,无需用户重新登录 + +# Ant Design +[官方文档](https://antdv.com/docs/vue/getting-started-cn):`https://antdv.com/docs/vue/getting-started-cn` + +添加依赖:`yarn add ant-design-vue` \ No newline at end of file diff --git "a/\346\236\227\347\216\211\346\225\217/20240712_\345\211\215\347\253\257.md" "b/\346\236\227\347\216\211\346\225\217/20240712_\345\211\215\347\253\257.md" new file mode 100644 index 0000000000000000000000000000000000000000..7ef7d000cff7e26a9802dd2fd5fc97321918df00 --- /dev/null +++ "b/\346\236\227\347\216\211\346\225\217/20240712_\345\211\215\347\253\257.md" @@ -0,0 +1,7 @@ + +token指定时间过期后,后台自动发送新请求获取新的token,无需用户重新登录 + +# Ant Design +[官方文档](https://antdv.com/docs/vue/getting-started-cn):`https://antdv.com/docs/vue/getting-started-cn` + +添加依赖:`yarn add ant-design-vue` \ No newline at end of file diff --git "a/\346\236\227\347\216\211\346\225\217/Test.cs" "b/\346\236\227\347\216\211\346\225\217/Test.cs" deleted file mode 100644 index 24f37d64de74b8fb0a7e5e782e5173049fc1d8b4..0000000000000000000000000000000000000000 --- "a/\346\236\227\347\216\211\346\225\217/Test.cs" +++ /dev/null @@ -1,11 +0,0 @@ -namespace Test1; -public class Test -{ - public void Main() - { - // 使用方法 - var idGenerator = new SnowflakeIdGenerator(); - long id = idGenerator.NextId(); - Console.WriteLine("雪花算法" + id); - } -} \ No newline at end of file