diff --git "a/\346\236\227\347\216\211\346\225\217/20240521_\345\221\275\344\273\244.md" "b/\346\236\227\347\216\211\346\225\217/20240521_dotnet\345\221\275\344\273\244.md" similarity index 63% rename from "\346\236\227\347\216\211\346\225\217/20240521_\345\221\275\344\273\244.md" rename to "\346\236\227\347\216\211\346\225\217/20240521_dotnet\345\221\275\344\273\244.md" index d40b38f83b3e76b09fe08158076e747171a1c89a..b921352d8bbd6cda7735d85250c8113b7e0259ca 100644 --- "a/\346\236\227\347\216\211\346\225\217/20240521_\345\221\275\344\273\244.md" +++ "b/\346\236\227\347\216\211\346\225\217/20240521_dotnet\345\221\275\344\273\244.md" @@ -5,6 +5,11 @@ 4. `dotnet restore/build` 还原/编译 自动下载依赖包 5. `dotnet watch --project 文件路径` 监视项目(热重载) 6. `dotnet clean` +7. 新建解决方案`dotnet new sln [-n/-o MySolution]` +8. 将项目添加到解决方案中`dotnet sln add 项目1路径 项目2路径` +9. 列出所有项目`dotnet sln list` +10. `dotnet add 操作项目路径 reference 引用项目路径` 将项目引用到项目 +11. `dotnet add reference 引用项目路径` # mini api ```cs diff --git "a/\346\236\227\347\216\211\346\225\217/20240527_\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" similarity index 94% rename from "\346\236\227\347\216\211\346\225\217/20240527_\350\277\207\346\273\244\345\231\250.md" rename to "\346\236\227\347\216\211\346\225\217/20240527_Filter\350\277\207\346\273\244\345\231\250.md" index 901f4a3eca4cb6fabe6d555f325743360c78de94..a4483fe95fd2b05b25dbaa1186c50d248885f169 100644 --- "a/\346\236\227\347\216\211\346\225\217/20240527_\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,4 +1,5 @@ # 过滤器 +命名空间:`Microsoft.AspNetCore.Mvc.Filters` ## ResultFailter 结果过滤器 使数据以一定结构返回 @@ -21,6 +22,7 @@ public class ApiResponse : IAsyncResultFilter { if (context is ObjectResult objectResult) { + // context.Result结束请求,并返回结果对象 context.Result=new ObjectResult(new ApiResult { Code=1000, @@ -69,6 +71,7 @@ public class ApiActionFilter : IAsyncActionFilter { if (param.ParameterType==typeof(string)) { + // ActionArguments得到所有要传入Action的参数 var argument=context.ActionArguments[param.Name]; // 是否包含空格 if (argument.ToString().Contains(" ")) diff --git "a/\346\236\227\347\216\211\346\225\217/20240530_\344\270\252\346\200\247\344\273\223\345\202\250.md" "b/\346\236\227\347\216\211\346\225\217/20240530_\344\270\252\346\200\247\344\273\223\345\202\250.md" index 6c71383ee951d34637976d4aa06a7d6c7d445307..5d34903faecf790d6ad79d5788e951349e58fa06 100644 --- "a/\346\236\227\347\216\211\346\225\217/20240530_\344\270\252\346\200\247\344\273\223\345\202\250.md" +++ "b/\346\236\227\347\216\211\346\225\217/20240530_\344\270\252\346\200\247\344\273\223\345\202\250.md" @@ -80,8 +80,6 @@ public class BookData } } ``` -## Services文件夹 -创建个性仓储 ### 接口 IXXXRepository 定义操作数据的方法:增删改查 @@ -139,8 +137,15 @@ public class Startup } } ``` -### 实现接口 +## Services文件夹 +创建个性仓储 +实现接口中的方法,对数据进行操作 +需使用Db,domain,Interface +数据操作方法: +1. FirstOrDefault(ele=>ele.Id=Id) 筛选数据,获取单条 +2. Any(ele) 只要有一个存在就返回true +3. Where(ele) 筛选符合条件的数据 **AuthorRepository.cs** ```cs // 继承接口,实现对应的方法 @@ -235,6 +240,7 @@ public class BookRepository : IBookRepository **AuthorController** ```cs using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.JsonPatch; // 自动对模型进行验证,失败返回400 [ApiController] @@ -311,19 +317,25 @@ public class AuthorController:ControllerBase } // 部分修改 +// Microsoft.AspNetCore.Mvc.NewtonsoftJson +// Microsoft.AspNetCore.JsonPatch [HttpPatch("{authorId}")] - public IActionResult PartPutAuthor(int authorId,JsonPatchDocument patchDocument) + // JsonPatchDocument为JSON Patch文档对象,值从请求正文中获取 + public IActionResult PartPutAuthor(int authorId,JsonPatchDocument patchDocument) { + // 部分更新时,请求正文由JSON信息转换为JSON Patch文档对象 var author=authorRepository.GetAuthor(authorId); if(author==null)return NotFound(); - var auhtorToPatch=new authorForCreateDto + var auhtorToPatch=new AuthorForCreateDto { Name=author.Name, Price=author.Price, }; + // ApplyTo 将修改操作应用到新建的对象上,将可能出现的错误记录到ModelStateDictionary JsonPatchDocument.ApplyTo(auhtorToPatch,ModelState); + // 如果验证不通过,返回400状态码 if(!ModelState.IsValid)return BadRequest(ModelState); authorRepository.PutAuthor(authorId,auhtorToPatch); diff --git "a/\346\236\227\347\216\211\346\225\217/20240531_\351\200\232\347\224\250\344\273\223\345\202\250.md" "b/\346\236\227\347\216\211\346\225\217/20240531_\351\200\232\347\224\250\344\273\223\345\202\250.md" index 168d293cc2051682c261214aad86f3c979247dac..84816d781c9fd4288fc092b7b1687923ca7aba0a 100644 --- "a/\346\236\227\347\216\211\346\225\217/20240531_\351\200\232\347\224\250\344\273\223\345\202\250.md" +++ "b/\346\236\227\347\216\211\346\225\217/20240531_\351\200\232\347\224\250\344\273\223\345\202\250.md" @@ -7,4 +7,676 @@ GUID 作为主键的好处:唯一性、无需往返数据库、无法被猜出 GUID 作为主键的缺点:存储空间大(16字节)、没有顺序 (ABP 生成器解决了guid无序问题。) ID 作为主键的好处:存储空间小,int类型只存储4字节,long也就8字节 -ID 作为主键的缺点:主键Id要往返数据库、id自增号容易被猜测出来 \ No newline at end of file +ID 作为主键的缺点:主键Id要往返数据库、id自增号容易被猜测出来 +# 通用仓储 +## 基础知识 +1. 异步方法:Task<返回值> 方法名Async(); +2. lanbda表达式:Expression> expression +## 依赖包 +1. `dotnet add package Microsoft.EntityFrameworkCore` +2. `dotnet add package Microsoft.EntityFrameworkCore.SqlServer` +3. `dotnet add package Microsoft.EntityFrameworkCore.Design` +4. `dotnet add package Microsoft.EntityFrameworkCore.Tools` +5. `dotnet add package AutoMapper` +6. `dotnet add package Microsoft.AspNetCore.JsonPatch` +## 基础类 +**Program:** +```cs +public class Program +{ + public static void Main(string[] args) + { + CreateHostBuilder(args).Build().Run(); + } + public static IHostBuilder CreateHostBuilder(string[] args) + { + return Host.CreateDefaultBuilder(args) + .ConfigureWebHostDefaults(builder => + { + builder.UseStartup(); + }); + } +} +``` +**Startup:** +```cs +using LibraryAPI.Db; +using LibraryAPI.Interface; +using LibraryAPI.Services; +using Microsoft.EntityFrameworkCore.SqlServer; +using Microsoft.EntityFrameworkCore; +using AutoMapper; +using LibraryAPI.Filter; + +public class Startup +{ + // 通过构造函数注入,使用IConfiguration获取配置文件信息 + private readonly IConfiguration _configuration; + public Startup(IConfiguration configuration) + { + _configuration = configuration; + } + // 配置应用程序的请求处理管道,管道由一系列中间件组成 + public void Configure(IApplicationBuilder app) + { + // app提供用于构建请求处理管道的方法 + // 定义路由 + app.UseRouting(); + // 定义请求终点 + app.UseEndpoints(endpoints => + { + // 将请求映射到控制器 + endpoints.MapControllers(); + }); + } + // 配置服务容器 + public void ConfigureServices(IServiceCollection services) + { + services.AddControllers(); + services.AddDbContext(options => + { + options.UseSqlServer(_configuration.GetConnectionString("DefaultConnection")); + }); + // 在程序中使用仓储包装器接口 + services.AddScoped(); + + // 将映射器添加到依赖注入容器 + // 会扫描Profile的派生类,将结果生成映射规则 + // 将IMapper接口【用于完成映射操作的接口】添加到依赖注入容器 + // typeof(Startup):代表 Startup 类的类型。AddAutoMapper 方法使用 Startup 类的类型来扫描应用程序的程序集,并自动发现和注册映射配置 + services.AddAutoMapper(typeof(Startup)); + + // 将过滤器添加到依赖注入容器中,通过[ServiceFilter]特性使用过滤器 + services.AddScoped(); + } +} +``` +## Domain +定义实体类 +```cs +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +public class Author +{ + public Guid Id { get; set; } + + [Required(ErrorMessage = "Name is required")] + [StringLength(8, MinimumLength = 1, ErrorMessage = "Name length must be between 1 and 8")] + public string Name { get; set; } + + [Range(1, 100, ErrorMessage = "Age must be between 1 and 100")] + public int Age { get; set; } + public ICollection Books { get; set; } = new List(); +} + +public class Book +{ + public Guid Id { get; set; } + public string Title { get; set; } + public double Price { get; set; } + + [ForeignKey("AuthorId")] + public Author Author { get; set; } + public Guid AuthorId { get; set; } +} +``` +## Dto +用于增删改查的实体类 +```cs +public class AuthorDto +{ + public string Name { get; set; } + public int Age { get; set; } + // Dto类通常不应该包含行为,因此不应该包含ICollection类型的属性 +} + +public class BookDto +{ + public string Title { get; set; } + public double Price { get; set; } + public Guid AuthorId { get; set; } + // 不包含Author属性,因为Dto类通常不包含复杂的关联对象 +} +``` +## Db +数据库上下文 +```cs +using LibraryAPI.Domain; +using Microsoft.EntityFrameworkCore; + +public class LibraryDbContext : DbContext +{ + // 定义数据库表 + public DbSet Authors { get; set; } + public DbSet Books { get; set; } + // 在应用程序中使用它 + public LibraryDbContext(DbContextOptions options) : base(options) + { } + // 重写OnModelCreating,插入数据种子 + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + modelBuilder.Entity().HasData( + new Author + { + Id = new Guid("26345683-8456-3462-4562-156783213892"), + Name = "Ling", + Age = 30, + }, + new Author + { + Id = Guid.NewGuid(), + Name = "Wang", + Age = 30, + } + ); + modelBuilder.Entity().HasData( + new Book + { + Id = new Guid("29701784-8456-3462-4562-156783213892"), + Title = "Test", + Price = 25.5, + AuthorId = new Guid("26345683-8456-3462-4562-156783213892"), + }, + new Book + { + Id = Guid.NewGuid(), + Title = "KGERD", + Price = 39, + AuthorId = new Guid("26345683-8456-3462-4562-156783213892"), + } + ); + } +} +``` +## Helpers +```cs +``` +## Interface +**IRepositoryBase:** +```cs +using System.Linq.Expressions; +/* +定义除了要使用Id外的所有方法 + +定义一个异步方法:Task<返回值> 方法名Async(); + +异步方法,方法名以 Async 结尾 +Task表示该方法是一个异步方法 + */ +public interface IRepositoryBase +{ + // 获取所有类型为T的实体,返回IEnumerable集合 + Task> GetAllAsync(); + // 根据指定条件查询,expression定义了用于筛选的条件 + Task> GetByConditionAsync(Expression> expression); + // 添加 entity表示该类型 + // 只准备实体不保存到数据库 + void Create(T entity); + void Update(T entity); + void Delete(T entity); + // 将对数据的操作映射到数据库,返回bool判断是否成功 + Task SaveAsync(); +} +``` +**IRepositoryBase2:** +```cs +// 定义需要使用ID的方法 +// 具有两个类型参数T,TId +public interface IRepositoryBase2 +{ + // 获取指定Id的数据,放回该类型 + Task GetByIdAsync(TId id); + // 判断Id数据是否存在,返回bool + Task IsExistAsync(TId id); +} +``` +**IAuthorRepository:** +```cs +using LibraryAPI.Domain; +using LibraryAPI.Helpers; + +// 定义Auhtor接口,继承所有仓储接口IRepositoryBase,IRepositoryBase2 +public interface IAuthorRepository : IRepositoryBase, IRepositoryBase2 +{ + // 可以定义单独的方法 + // 定义获取 分页查询数据 的方法 + Task> GetByPagedQueryAsync(AuthorResourceParameters parameters); +} +``` +**IBookRepository:** +```cs +using LibraryAPI.Domain; +using LibraryAPI.Helpers; + +// 定义Auhtor接口,继承所有仓储接口IRepositoryBase,IRepositoryBase2 +public interface IBookRepository : IRepositoryBase, IRepositoryBase2 +{ + // 可以定义单独的方法 + Book GetBookByAuthorId(Guid authorId, Guid bookId); + Task> GetByPagedQueryAsync(BookResourceParameter parameter); +} +``` +**IRepositoryWrapper:** +```cs +// 定义仓储包装器,提供所有仓储接口的统一访问方式 +public interface IRepositoryWrapper +{ + // 定义类型为接口的属性 + IBookRepository Book { get; } + IAuthorRepository Author { get; } +} +``` +## Service +实现接口 +**RepositoryBase:** +```cs +using LibraryAPI.Interface; +using LibraryAPI.Db; +using System.Linq.Expressions; +// 使用DbContext +using Microsoft.EntityFrameworkCore; + +// 定义基础类,实现所有的仓储接口 +// 使用约束条件where T : class,限定T必须是一个类 +public class RepositoryBase : IRepositoryBase, IRepositoryBase2 where T : class +{ + // 访问数据库上下文 + public DbContext _dbContext { get; set; } + // public DbSet _dbSet { get { return _dbContext.Set(); } } + public RepositoryBase(DbContext dbContext) + { + _dbContext = dbContext; + } + public void Create(T entity) + { + // Set() 获取类型为T的所有DbSet对象 + _dbContext.Set().Add(entity); + } + public void Delete(T entity) + { + _dbContext.Set().Remove(entity); + } + + public void Update(T entity) + { + _dbContext.Set().Update(entity); + } + // 如果有使用异步方法,就要添加async/await + public async Task IsExistAsync(TId id) + { + // 将数据与null对比,判断是否存在 + return await _dbContext.Set().FindAsync(id) != null; + } + public async Task GetByIdAsync(TId id) + { + return await _dbContext.Set().FindAsync(id); + } + // 异步地返回一个包含所有对象的IEnumerable集合 + public Task> GetAllAsync() + { + // AsEnumerable():将DbSet对象转换为IEnumerable集合 + return Task.FromResult(_dbContext.Set().AsEnumerable()); + } + + public Task> GetByConditionAsync(Expression> expression) + { + return Task.FromResult(_dbContext.Set().Where(expression).AsEnumerable()); + } + // 将操作映射到数据库,返回bool + public async Task SaveAsync() + { + return await _dbContext.SaveChangesAsync() > 0; + } +} +``` +**AuthorRepository:** +```cs +using LibraryAPI.Domain; +using LibraryAPI.Interface; +using LibraryAPI.Helpers; +using Microsoft.EntityFrameworkCore; + +// 继承RepositoryBase,IAuthorRepository +public class AuthorRepository : RepositoryBase, IAuthorRepository +{ + public AuthorRepository(DbContext dbContext) : base(dbContext) + { } + // 调用PageList获取分页查询的元数据 + public async Task> GetByPagedQueryAsync(AuthorResourceParameters parameters) + { + // IQueryable为泛型,将操作缓存到表达式树中,再去数据库执行操作 + // IEnumerable会先将数据获取到内存中,再在内存中操作数据 + IQueryable queryableAuthors = _dbContext.Set(); + // 过滤,没有就使用默认数据 + if (parameters.Age != 0) + { + queryableAuthors = queryableAuthors.Where(x => x.Age == parameters.Age); + } + // 搜索 + if (!string.IsNullOrEmpty(parameters.SearchQuery)) + { + queryableAuthors = queryableAuthors.Where(x => x.Name.Contains(parameters.SearchQuery)); + } + // 排序 + if (parameters.SortBy == "Name") + { + queryableAuthors = queryableAuthors.OrderBy(x => x.Name); + } + // var totalCount = queryableAuthors.Count(); + // var item = queryableAuthors.Skip(parameters.PageSize * (parameters.PageIndex - 1)).Take(parameters.PageSize).ToList(); + // 方法的返回类型为Task>,但实际返回的是PagedList对象。 + // 需要将返回类型修改为Task>,可以使用 "Task.FromResult" 方法将PagedList对象包装成Task返回。 + // return new PagedList(item, totalCount, parameters.PageSize, parameters.PageIndex); + // return await Task.FromResult(new PagedList(item, totalCount, parameters.PageSize, parameters.PageIndex)); + + return await PagedList.CreateAsync(queryableAuthors, parameters.PageSize, parameters.PageIndex); + } +} +``` +**BookRepository:** +```cs +using LibraryAPI.Domain; +using LibraryAPI.Helpers; +using LibraryAPI.Interface; +using Microsoft.EntityFrameworkCore; +public class BookRepository : RepositoryBase, IBookRepository +{ + public BookRepository(DbContext dbContext) : base(dbContext) + { } + public Book GetBookByAuthorId(Guid authorId, Guid bookId) + { + return _dbContext.Set().SingleOrDefault(x => x.AuthorId == authorId && x.Id == bookId); + } + public Task> GetByPagedQueryAsync(BookResourceParameter parameter) + { + IQueryable querableBooks = _dbContext.Set(); + return PagedList.CreateAsync(querableBooks, parameter.PageSize, parameter.PageIndex); + } +} +``` +**RepositoryWrapper:** +```cs +using LibraryAPI.Interface; +using LibraryAPI.Db; + +public class RepositoryWrapper : IRepositoryWrapper +{ + // 私有字段表示仓储 + private IAuthorRepository _authorRepository = null; + private IBookRepository _bookRepository = null; + + // 实现接口属性,通过 =>语法 定义 + // 并检查私有字段是否为空,如果为空,则实例化新的AuthorRepository对象,并将数据库上下文作为参数传入 + // 通过调用Author,Book属性来访问对应的仓储操作 + public IAuthorRepository Author => _authorRepository ?? new AuthorRepository(_libraryDbContext); + public IBookRepository Book => _bookRepository ?? new BookRepository(_libraryDbContext); + + // 构造函数注入 + public LibraryDbContext _libraryDbContext { get; } + public RepositoryWrapper(LibraryDbContext libraryDbContext) + { + // 定义包含LibraryDbContext类型参数的构造函数,用该对象实例化所有仓储类,并将该参数传递给RepositoryBase + _libraryDbContext = libraryDbContext; + } +} +``` +## Controller +**AuthorController:** +```cs +using Microsoft.AspNetCore.Mvc; +using LibraryAPI.Interface; +using LibraryAPI.Dto; +using LibraryAPI.Helpers; +using LibraryAPI.Domain; +using AutoMapper; +using Microsoft.AspNetCore.JsonPatch; +// JsonConvert +using Newtonsoft.Json; + +[ApiController] +[Route("api/[controller]")] +public class AuthorController : ControllerBase +{ + // 构造函数注入 + public IRepositoryWrapper _repositoryWrapper { get; } + public IMapper _mapper { get; } + public AuthorController(IRepositoryWrapper repositoryWrapper, IMapper mapper) + { + // 使用仓储 + _repositoryWrapper = repositoryWrapper; + // 映射器:转化实体对象与DTO对象 + _mapper = mapper; + } + // 分页查询 + [HttpGet(Name = nameof(GetAuthorsAsync))] + // 当参数的值要从URL中获取时,要设置[FromQuery] 特性 + public async Task>> GetAuthorsAsync([FromQuery] AuthorResourceParameters parameters) + { + // 获取到分页元数据以及分页查询到的数据 + var pagedList = await _repositoryWrapper.Author.GetByPagedQueryAsync(parameters); + // 定义要显示在响应头的分页查询元对象:totalCount,pageSize,pageIndex,currentPage,下一页url,上一页的url + var paginationMetadata = new + { + totalCount = pagedList.TotalCount, + pageSize = pagedList.PageSize, + currentPage = pagedList.CurrentPage, + totalPage = pagedList.TotalPage, + // age = parameters.Age, + // searchQuery = parameters.SearchQuery, + // sortBy = parameters.SortBy, + // Url.Link调用指定Action + previousePageLink = pagedList.HasPrevious ? Url.Link(nameof(GetAuthorsAsync), new + { + pageIndex = pagedList.CurrentPage - 1, + pageSize = pagedList.PageSize, + // 由于数据在分页前进行的过滤,因此过滤信息也属于元信息的一部分 + age = parameters.Age, + searchQuery = parameters.SearchQuery, + sortBy = parameters.SortBy + }) : null, + nextPageLink = pagedList.HasNext ? Url.Link(nameof(GetAuthorsAsync), new + { + pageIndex = pagedList.CurrentPage + 1, + pageSize = pagedList.PageSize, + age = parameters.Age, + searchQuery = parameters.SearchQuery, + sortBy = parameters.SortBy + }) : null + }; + // 将对象添加到响应头 + Response.Headers.Add("X-Pagination", JsonConvert.SerializeObject(paginationMetadata)); + + var authorListDto = _mapper.Map>(pagedList); + return authorListDto.ToList(); + } + [HttpGet("{authorId}", Name = nameof(GetByIdAsync))] + public async Task> GetByIdAsync(Guid authorId) + { + var author = await _repositoryWrapper.Author.GetByIdAsync(authorId); + var rel = _mapper.Map(author); + return rel; + } + + [HttpPost] + public async Task CreateAuthor(AuthorForCreationDto authorForCreationDto) + { + // 将DTO转化为实体对象,并添加ID + var author = _mapper.Map(authorForCreationDto); + author.Id = Guid.NewGuid(); + + // 对数据库操作 + _repositoryWrapper.Author.Create(author); + var rel = await _repositoryWrapper.Author.SaveAsync(); + if (!rel) return NotFound("创建失败"); + + // 将 实体对象 转化为DTO + var authorCreated = _mapper.Map(author); + return CreatedAtRoute(nameof(GetByIdAsync), new { authorId = author.Id }, authorCreated); + } + + [HttpDelete("{authorId}")] + public async Task DelAuthor(Guid authorId) + { + var author = await _repositoryWrapper.Author.GetByIdAsync(authorId); + if (author == null) return NotFound("Author不存在"); + _repositoryWrapper.Author.Delete(author); + + var rel = await _repositoryWrapper.Author.SaveAsync(); + if (!rel) return NotFound("创建失败"); + return NoContent(); + } + + [HttpPut("{authorId}")] + public async Task UpdateAuthor(Guid authorId, AuthorForCreationDto authorForCreationDto) + { + var author = await _repositoryWrapper.Author.GetByIdAsync(authorId); + if (author == null) return NotFound("Author不存在"); + _mapper.Map(authorForCreationDto, author); + _repositoryWrapper.Author.Update(author); + + var rel = await _repositoryWrapper.Author.SaveAsync(); + if (!rel) return NotFound("修改失败"); + return NoContent(); + } + + // 部分更新 + [HttpPatch("{authorId}")] + // JsonPatchDocument为JSON Patch文档对象,值从请求正文中获取 + // 部分更新时,请求正文由JSON信息转换为JSON Patch文档对象 + public async Task PartiallyUpdateAuthorAsync(Guid authorId, JsonPatchDocument patchDocument) + { + var author = await _repositoryWrapper.Author.GetByIdAsync(authorId); + if (author == null) return NotFound("Author不存在"); + var authorDto = _mapper.Map(author); + + // ApplyTo 将修改操作应用到新建的对象上,将AuthorForCreationDto应用到Author + // 将可能出现的错误记录到ModelStateDictionary + patchDocument.ApplyTo(authorDto, ModelState); + // 如果数据验证不通过,返回400状态码 + if (!ModelState.IsValid) return BadRequest(ModelState); + // 由于源类型和目标类型都是在映射中指定的,所以这里不需要显式地指定它们。 + _mapper.Map(authorDto, author); + + _repositoryWrapper.Author.Update(author); + var rel = await _repositoryWrapper.Author.SaveAsync(); + if (!rel) return NotFound("修改失败"); + return NoContent(); + } +} +``` +**BookController:** +```cs +using Microsoft.AspNetCore.Mvc; +using LibraryAPI.Interface; +using LibraryAPI.Dto; +using LibraryAPI.Domain; +using LibraryAPI.Filter; +using LibraryAPI.Helpers; +using AutoMapper; +using Newtonsoft.Json; + +[ApiController] +// 使用过滤器 +[ServiceFilter(typeof(ChecAuthorExistFilterAttribute))] +[Route("api/author/{authorId}/[controller]")] +public class BookController : ControllerBase +{ + // 构造函数注入 + public IRepositoryWrapper _repositoryWrapper { get; } + public IMapper _mapper { get; } + public BookController(IRepositoryWrapper repositoryWrapper, IMapper mapper) + { + // 使用仓储 + _repositoryWrapper = repositoryWrapper; + // 映射器:转化实体对象与DTO对象 + _mapper = mapper; + } + + [HttpGet(Name = nameof(GetBookAsync))] + public async Task>> GetBookAsync(Guid authorId, [FromQuery] BookResourceParameter parameters) + { + // 由于BookController的每个Action都需要验证指定作者是否存在,所以在过滤器中将重复的代码提取出来 + // if (!await _repositoryWrapper.Author.IsExistAsync(authorId)) return NotFound(); + var pageList = await _repositoryWrapper.Book.GetByPagedQueryAsync(parameters); + var paginationMetadata = new + { + pageSize = pageList.PageSize, + totalCount = pageList.TotalCount, + totalPage = pageList.TotalPage, + currentPage = pageList.CurrentPage, + // Url.Link(nameof(动作名),new{参数名=值})调用指定Action + previousPageLink = pageList.HasPrevious ? Url.Link(nameof(GetBookAsync), new + { + pageSize = pageList.PageSize, + pageIndex = pageList.CurrentPage - 1 + }) : null, + nextPageLink = pageList.HasNext ? Url.Link(nameof(GetBookAsync), new + { + pageSize = pageList.PageSize, + pageIndex = pageList.CurrentPage + 1 + }) : null + }; + Response.Headers.Add("X-Pagination", JsonConvert.SerializeObject(paginationMetadata)); + var bookList = _mapper.Map>(pageList); + return bookList.ToList(); + } + + [HttpGet("{bookId}")] + public ActionResult GetByIdAsync(Guid authorId, Guid bookId) + { + var book = _repositoryWrapper.Book.GetBookByAuthorId(authorId, bookId); + if (book == null) return NotFound("ID not found"); + var bookDto = _mapper.Map(book); + return bookDto; + } + [HttpDelete("{bookId}")] + public async Task DelAuthor(Guid authorId, Guid bookId) + { + var tempBook = _repositoryWrapper.Book.GetBookByAuthorId(authorId, bookId); + if (tempBook == null) return NotFound("ID not found"); + var book = _mapper.Map(tempBook); + + _repositoryWrapper.Book.Delete(book); + var rel = await _repositoryWrapper.Book.SaveAsync(); + + if (!rel) return NotFound("创建失败"); + return NoContent(); + } + + [HttpPut("{bookId}")] + public async Task UpdateAuthor(Guid authorId, Guid bookId, BookForCreationDto bookForCreationDto) + { + var book = _repositoryWrapper.Book.GetBookByAuthorId(authorId, bookId); + if (book == null) return NotFound("ID not found"); + // 将更新的值映射到源对象上 + _mapper.Map(bookForCreationDto, book); + _repositoryWrapper.Book.Update(book); + var rel = await _repositoryWrapper.Book.SaveAsync(); + + if (!rel) return NotFound("更新失败"); + return NoContent(); + } + + [HttpPost] + public async Task CreateAuthor(Guid authorId, BookForCreationDto bookForCreationDto) + { + // 将DTO转化为实体对象,并添加ID + var book = _mapper.Map(bookForCreationDto); + book.Id = Guid.NewGuid(); + book.AuthorId = authorId; + + // 对数据库操作 + _repositoryWrapper.Book.Create(book); + var rel = await _repositoryWrapper.Book.SaveAsync(); + + if (!rel) return NotFound("创建失败"); + + // 将 实体对象 转化为DTO + var bookCreated = _mapper.Map(book); + return CreatedAtRoute(nameof(GetByIdAsync), new { authorId = book.AuthorId, bookId = book.Id }, bookCreated); + } +} +``` \ No newline at end of file diff --git "a/\346\236\227\347\216\211\346\225\217/20240604_linq\350\257\255\346\263\225.md" "b/\346\236\227\347\216\211\346\225\217/20240603_linq\350\257\255\346\263\225.md" similarity index 100% rename from "\346\236\227\347\216\211\346\225\217/20240604_linq\350\257\255\346\263\225.md" rename to "\346\236\227\347\216\211\346\225\217/20240603_linq\350\257\255\346\263\225.md" diff --git "a/\346\236\227\347\216\211\346\225\217/20240603_\344\270\252\346\200\247\344\273\223\345\202\250.md" "b/\346\236\227\347\216\211\346\225\217/20240603_\344\270\252\346\200\247\344\273\223\345\202\250.md" deleted file mode 100644 index e0023cd3f6642e890d1ca5d9de29a040212c0498..0000000000000000000000000000000000000000 --- "a/\346\236\227\347\216\211\346\225\217/20240603_\344\270\252\346\200\247\344\273\223\345\202\250.md" +++ /dev/null @@ -1,249 +0,0 @@ -# 基础代码 -***Program.cs***: -```cs -namespace DemoName; -public class Program -{ - public static void Main(string[] args) - { - CreateHostBuilder(args).Build().Run(); - } - public static IHostBuilder CreateHostBuilder(string[] args) - { - return Host.CreateDefaultBuilder(args) - .ConfigureWebHostDefaults(build=> - { - build.UseStartup(); - }); - } -} -``` - -***Startup.cs***: -```cs -namespace DemoName; -public class Startup -{ - public void Configure(IApplicationBUilder app) - { - app.UseRouting(); - app.UseEndpoints(ep=> - { - ep.MapControllers(); - }); - } - public void ConfigureServices(IServiceCollection service) - { - service.AddControllers(); - } -} -``` - -# Domain 模块文件夹 -定义数据库中表的模块类,两个独立的类,通过查询连接起来 -***Author.cs***: -```cs -namespace DemoName.Domain; - -public class Author -{ - public Guid Id { get; set; } - public string Name { get; set; } - public int Age { get; set; } -} -``` -***Book.cs*** -```cs -namespace DemoName.Domain; - -public class Book -{ - public Guid Id { get; set; } - public string Title { get; set; } - public int Price { get; set; } - public string Description { get; set; } - // 外键 - public Guid AuthorId{get;set;} -} -``` -# Db 数据文件夹 -添加初始数据,根据模块类创建对应集合 -需使用Domain -***DemoMockData*** -```cs -namespace DemoName.Db; -using DemoName.Domain; - -public class DemoMockData -{ - // 声明一个当前类的静态对象【只读】,供外界使用 - public static DemoMockData Current{get;}=new DemoMockData(); - // 创建集合 - public List Authors{get;set;} - public List Books{get;set;} - // 通过构造函数添加数据种子 - public DemoMockData(){ - Authors=new List{ - new Author{ - Id=new Guid("xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx") - }, - new Author{...}, - }; - Books=new List{ - new Book{...}, - new Book{...}, - }; - } -} -``` -# Interface 接口文件夹 -定义方法,需使用Domain -***IAuthorRepository*** -```cs -namespace DemoName.Interface; -using DemoName.Domain; - -public interface IAuthorRepository -{ - // 返回值 方法名(参数) - IEnumerable GetAuthors(); - Author GetAuthor(Guid authorId); - bool IsAuthorExists(Guid authorId); - void AddAuthor(Author author); - void UpdateAuthor(Guid authorId, AuthorForCreate auhtorCreate); - void DelAuthor(Author author); -} -``` -# Service 接口实现文件夹 -实现接口中的方法,对数据进行操作 -需使用Db,domain,Interface - -数据操作方法: -1. FirstOrDefault(ele=>ele.Id=Id) 筛选数据,获取单条 -2. Any(ele) 只要有一个存在就返回true -3. Where(ele) 筛选符合条件的数据 - - -***AuthorRepository*** -```cs -namespace DemoName.Service; -using DemoName.Db; -using DemoName.Domain; -using DemoName.Interface; - -// LibraryData.Current.Authors 操作Author集合 -public class AuthorRepository:IAuthorRepository -{ - // 泛型接口 - public IEnumerable GetAuthors() - { - return LibraryData.Current.Authors; - } - public Author GetAuthor(Guid authorId) - { - return LibraryData.Current.Authors.FirstOrDefault(au=>au.Id==authorId); - } - // 判断该对象是否存在 - public bool IsAuthorExists(Guid authorId) - { - return LibraryData.Current.Authors.Any(au=>au.Id==authorId); - } - public void AddAuthor(Author author) - { - author.Id = Guid.NewGuid(); - LibraryData.Current.Authors.Add(author); - } - public void UpdateAuthor(Guid authorId, AuthorForCreate auhtorCreate) - { - // 根据id获取到数据,并重新覆盖它的数据 - var author = GetAuthor(authorId); - author.Name = auhtorCreate.Name; - } - public void DelAuthor(Author author) - { - LibraryData.Current.Authors.Remove(author); - } -} -``` -```cs -// 在程序中使用接口 -service.AddScoped(); -``` -# Controller 控制台文件夹 -创建路由 -需使用Domain,Dto,Interface,Microsoft.AspNetCore.Mvc -返回方法: -1. NotFound() -2. NoContent() - - -***AuthorController.cs*** -```cs -namespace DemoName.Contorller; -using DemoName.Domain; -using DemoName.Interface; -using DemoName.Dto; -using Microsoft.AspNetCore.Mvc; - -[Route("api/[controller]")] -[ApiController] -public class AuthorController:ControllerBase -{ - // 构造函数注入 - // 通过依赖注入使用接口,并调用接口中实现定义的方法 - public IAuthorRepository AuthorRepository{get;} - public AuthorController(IAuthorRepository authorRepository) - { - AuthorRepository=authorRepository - } - - // 定义请求方法,返回类型 - // ActionResult>返回一个Author集合 - [HttpGet] - public ActionResult> GetAuthors() - { - return AuthorRepository.GetAuthors().ToList(); - } - - // 定义动态参数,并给路由命名 - // ActionResult返回一个Author对象 - [HttpGet("{authorId}",Name=nameof(GetAuthor))] - public ActionResult GetAuthor(Guid authorId) - { - if(!AuthorRepository.IsAuthorExists(authorId)) return NotFound(); - var author= AuthorRepository.GetAuthor(authorId); - return author; - } - - // 在Domain文件夹中添加用于 添加方法的AuthorForCreating模型【不包含id】 - [HttpPost] - public IActionResult CreateAuthor(AuthorForCreating authorForCreating) - { - // 将create类转为模块类Author - var author=new Author{ - Name=authorForCreating.Name, - ... - }; - AuthorRepository.AddAuthor(author); - // 返回添加后的数据 - return CreateAuthor(nameof(GetAuthor),new{authorId==author.Id},author); - } - - [HttpDel("{authorId}")] - public IActionResult DelAuthor(Guid authorId) - { - if(!AuthorRepository.IsAuthorExists(authorId)) return NotFound(); - var author=AuthorRepository.GetAuthor(authorId); - if(author==null) return NotFound(); - AuthorRepository.DelAuthor(author); - } - - [HttpPut("{authorId}")] - public IActionResult PutAuthor(Guid authorId,AuthorForCreating authorForCreating) - { - if(!AuthorRepository.IsAuthorExists(authorId)) return NotFound(); - AuthorRepository.UpdateAuthor(authorId,authorForCreating); - return NoContent(); - } -} -``` \ No newline at end of file diff --git "a/\346\236\227\347\216\211\346\225\217/20240604_plantUML.md" "b/\346\236\227\347\216\211\346\225\217/20240604_plantUML\347\273\230\345\233\276\345\267\245\345\205\267.md" similarity index 100% rename from "\346\236\227\347\216\211\346\225\217/20240604_plantUML.md" rename to "\346\236\227\347\216\211\346\225\217/20240604_plantUML\347\273\230\345\233\276\345\267\245\345\205\267.md" diff --git "a/\346\236\227\347\216\211\346\225\217/20240613_AutoMapper.md" "b/\346\236\227\347\216\211\346\225\217/20240613_AutoMapper\346\230\240\345\260\204\345\231\250.md" similarity index 89% rename from "\346\236\227\347\216\211\346\225\217/20240613_AutoMapper.md" rename to "\346\236\227\347\216\211\346\225\217/20240613_AutoMapper\346\230\240\345\260\204\345\231\250.md" index 657937980f9635dec22ac193c9a502f3cca94468..da4faa352b3c6c7343cec91a58b5b435adaf2df2 100644 --- "a/\346\236\227\347\216\211\346\225\217/20240613_AutoMapper.md" +++ "b/\346\236\227\347\216\211\346\225\217/20240613_AutoMapper\346\230\240\345\260\204\345\231\250.md" @@ -48,7 +48,9 @@ public class LibraryMappingProfile : Profile ``` ## Controller文件夹 1. 通过构造函数注入IMapper映射器,使用对象映射 -2. 转换:`Map<目标>(源);` +2. 转换: + 1. `Map<目标>(源);` + 2. `Map(源对象,目标对象,源对象类型,目标对象类型)`:将源映射到一个已经存在的对象上 ```cs public class AuthorController : ControllerBase { @@ -59,7 +61,8 @@ public class AuthorController : ControllerBase // 映射器:转化实体对象与DTO对象 _mapper = mapper; } - + + // 法一: [HttpGet] public async Task>> GetAuthorsAsync() { @@ -68,9 +71,12 @@ public class AuthorController : ControllerBase // 进行对象映射,将获取到的作者实体列表 authors 映射为作者DTO var authorDtoList = _mapper.Map>(authors); - - return authorDtoList.ToList(); } + +// 法二: + [HttpPut("{bookId}")] + _mapper.Map(bookForUpdateDto, book, typeof(BookForUpdateDto), typeof(Book)); + _repositoryWrapper.Book.Update(book); } ``` # C# XML Document插件 diff --git "a/\346\236\227\347\216\211\346\225\217/20240614_flunt api.md" "b/\346\236\227\347\216\211\346\225\217/20240614_flunt api.md" index 720bf11487353db9e56455bc462f7ad7b5abec21..2614962802517e2eecdaa7b3af499dbcbf1eb67b 100644 --- "a/\346\236\227\347\216\211\346\225\217/20240614_flunt api.md" +++ "b/\346\236\227\347\216\211\346\225\217/20240614_flunt api.md" @@ -1,4 +1,4 @@ -fluent +# fluent api 通过自定义类(继承自DbContext )的OnModelCreating方法访问FluentAPI ```cs diff --git "a/\346\236\227\347\216\211\346\225\217/20240617_element\347\273\204\344\273\266\345\272\223.md" "b/\346\236\227\347\216\211\346\225\217/20240617_element\347\273\204\344\273\266\345\272\223.md" new file mode 100644 index 0000000000000000000000000000000000000000..1630bd2aa628f72e8407bb439512140e90a0b594 --- /dev/null +++ "b/\346\236\227\347\216\211\346\225\217/20240617_element\347\273\204\344\273\266\345\272\223.md" @@ -0,0 +1,2 @@ +# element ui 组件库 +[官网地址](https://element-plus.org/zh-CN/#/zh-CN):https://element-plus.org/zh-CN/#/zh-CN \ No newline at end of file diff --git "a/\346\236\227\347\216\211\346\225\217/20240617_\350\200\203\350\257\225\351\241\271\347\233\256.md" "b/\346\236\227\347\216\211\346\225\217/20240617_\350\200\203\350\257\225\351\241\271\347\233\256.md" deleted file mode 100644 index 54bf0a7e797dcbf7ab82f9478a11023a31c2c7e9..0000000000000000000000000000000000000000 --- "a/\346\236\227\347\216\211\346\225\217/20240617_\350\200\203\350\257\225\351\241\271\347\233\256.md" +++ /dev/null @@ -1,2 +0,0 @@ - - // Administrator=>.nuget=>packages \ No newline at end of file diff --git "a/\346\236\227\347\216\211\346\225\217/20240618_\346\263\233\345\236\213.md" "b/\346\236\227\347\216\211\346\225\217/20240618_\346\263\233\345\236\213.md" new file mode 100644 index 0000000000000000000000000000000000000000..8e842479d7a0be674dde14adccc53b07454a3e43 --- /dev/null +++ "b/\346\236\227\347\216\211\346\225\217/20240618_\346\263\233\345\236\213.md" @@ -0,0 +1,7 @@ +# 泛型 + +**IQueryable和IEnumerable的区别:** +1. IQueryable继承自IEnumerable +2. IQueryable中有表达式树,会将操作先缓存到表达式树中. 当对数据库操作真正发生时,它才会将表达式树执行来获取数据。 + 这也就是说,比如选择top 2两行数据, 它会先在表达式树中缓存这个过滤取top 2的操作。待到操作数据库时,它就会在数据库中筛选top 2数据。 =》 IQueryable 有延时加载机制, 它直接从数据库中筛选数据. +3. IEnumerable,会事先把所有的数据从数据库获取,放到内存中,在内存中对数据进行筛选 \ No newline at end of file diff --git "a/\346\236\227\347\216\211\346\225\217/20240618_\350\200\203\350\257\225\351\241\271\347\233\256.md" "b/\346\236\227\347\216\211\346\225\217/20240618_\350\200\203\350\257\225\351\241\271\347\233\256.md" deleted file mode 100644 index 54bf0a7e797dcbf7ab82f9478a11023a31c2c7e9..0000000000000000000000000000000000000000 --- "a/\346\236\227\347\216\211\346\225\217/20240618_\350\200\203\350\257\225\351\241\271\347\233\256.md" +++ /dev/null @@ -1,2 +0,0 @@ - - // Administrator=>.nuget=>packages \ No newline at end of file diff --git "a/\346\236\227\347\216\211\346\225\217/20240624_\345\210\206\351\241\265\346\237\245\350\257\242.md" "b/\346\236\227\347\216\211\346\225\217/20240624_\345\210\206\351\241\265\346\237\245\350\257\242.md" new file mode 100644 index 0000000000000000000000000000000000000000..3b42f97e13f9f69050bfd086d167bc52c5cca164 --- /dev/null +++ "b/\346\236\227\347\216\211\346\225\217/20240624_\345\210\206\351\241\265\346\237\245\350\257\242.md" @@ -0,0 +1,133 @@ +# 分页查询 +1. 创建 分页查询的参数规范模块ResourceParameters +2. 创建 分页查询的元数据类PageList,定义静态方法Create【返回查询到的数据,并调用构造函数】 +3. 定义接口方法 返回元数据,ResourceParameters为参数,调用PageList.Create获取元数据 +4. 控制台使用`[FromQuery]`特性,接收ResourceParameters参数,调用接口方法,获取到元数据,定义响应头对象,将对象添加到响应头,转化获取到的元数据类型,相应到客户端 + +# 参数规范 +pageSize记录数,MaxPageSize最大记录数 +```cs +// 分页查询 +// 模块定义:页码,记录数 +public class AuthorResourceParameters +{ + // 默认查询1页,10条 + // 最大数据条数=》不发生改变 + public const int MaxPagesSize = 20; + // 页码 + public int PageIndex { get; set; } = 1; + // 记录数 + private int _pageSize = 10; + // 设置条数规则 + public int PageSize + { + get { return _pageSize; } + set + { + // 当值大于最大数据条,则使用最大数据条 + _pageSize = value > MaxPagesSize ? MaxPagesSize : value; + } + } +} +``` +# 元数据 +1. TotalCount总数据条,TotalPage总页码,PageSize记录数,CurrentPage当前页码,HasNext是否有下一页,HasPrevious是否有上一页 +2. 定义构造函数给属性赋值 +3. 定义静态方法,分页查询数据,并调用构造函数,返回PageList元数据对象 +```cs +// 添加分页元数据,获取分页信息 +public class PagedList : List +{ + // 数据总数,记录数 + public int TotalCount { get; private set; } + public int PageSize { get; private set; } + // 总页面,当前页码 + public int TotalPage { get; private set; } // 总条数/记录数 + public int CurrentPage { get; private set; } + // 是否有 上一页,下一页=>定义条件 + public bool HasPrevious => CurrentPage > 1; + public bool HasNext => CurrentPage < TotalPage; + + // 创建构造函数,给自定义属性赋值 + public PagedList(List item, int totalCount, int pageSize, int pageIndex) + { + TotalCount = totalCount; + PageSize = pageSize; + CurrentPage = pageIndex; + TotalPage = (int)Math.Ceiling((double)totalCount / pageSize); + AddRange(item); + } + + // 定义为静态方法,返回PageList以及分页查询到的数据 + public static async Task> CreateAsync(IQueryable source, int pageSize, int pageIndex) + { + // 获取指定数据 + var totalCount = source.Count(); + // 当存在参数时,执行分页查询 Skip(跳过几条).Take(拿取几条) + // LINQ 方法不能直接应用于 Task 类型的对象上,需额外定义变量接受对象,再调用该对象 + var item = source.Skip(pageSize * (pageIndex - 1)).Take(pageSize).ToList(); + // 调用构造函数获取数据 + var list = new PagedList(item, totalCount, pageSize, pageIndex); + return await Task.FromResult(list); + } +} +``` +# 接口 +1. 返回PageList 元数据,分页属性为参数 +```cs + Task> GetByPagedQueryAsync(AuthorResourceParameters parameters); + + + // 调用PageList获取分页查询的元数据 + public async Task> GetByPagedQueryAsync(AuthorResourceParameters parameters) + { + // IQueryable为泛型,将操作缓存到表达式树中,再去数据库执行操作 + // IEnumerable会先将数据获取到内存中,再在内存中操作数据 + IQueryable queryableAuthors = _dbContext.Set(); + // var totalCount = queryableAuthors.Count(); + // var item = queryableAuthors.Skip(parameters.PageSize * (parameters.PageIndex - 1)).Take(parameters.PageSize).ToList(); + // 方法的返回类型为Task>,但实际返回的是PagedList对象。 + // 需要将返回类型修改为Task>,可以使用 "Task.FromResult" 方法将PagedList对象包装成Task返回。 + // return new PagedList(item, totalCount, parameters.PageSize, parameters.PageIndex); + // return await Task.FromResult(new PagedList(item, totalCount, parameters.PageSize, parameters.PageIndex)); + return await PagedList.CreateAsync(queryableAuthors, parameters.PageSize, parameters.PageIndex); + } +``` +# 控制台 +`{{url}}/author?pageindex=1&pagesize=20`发送请求 +1. 当参数的值要从URL中获取时,要设置`[FromQuery]`特性 +2. `Url.Link(nameof(动作名),new{参数名=值})`调用指定Action +```cs + // 分页查询 + [HttpGet(Name = nameof(GetAuthorsAsync))] + public async Task>> GetAuthorsAsync([FromQuery] AuthorResourceParameters parameters) + { + // 获取到分页元数据以及分页查询到的数据 + var pagedList = await _repositoryWrapper.Author.GetByPagedQueryAsync(parameters); + // 定义要显示在响应头的分页查询元对象:totalCount,pageSize,pageIndex,currentPage,下一页url,上一页的url + var paginationMetadata = new + { + totalCount = pagedList.TotalCount, + pageSize = pagedList.PageSize, + currentPage = pagedList.CurrentPage, + totalPage = pagedList.TotalPage, + // Url.Link调用指定Action + previousePageLink = pagedList.HasPrevious ? Url.Link(nameof(GetAuthorsAsync), new + { + PageIndex = pagedList.CurrentPage - 1, + PageSize = pagedList.PageSize, + }) : null, + nextPageLink = pagedList.HasNext ? Url.Link(nameof(GetAuthorsAsync), new + { + PageIndex = pagedList.CurrentPage + 1, + PageSize = pagedList.PageSize, + }) : null + }; + + // 将对象添加到响应头 + Response.Headers.Add("X-Pagination", JsonConvert.SerializeObject(paginationMetadata)); + + var authorListDto = _mapper.Map>(pagedList); + return authorListDto.ToList(); + } +``` \ No newline at end of file diff --git "a/\346\236\227\347\216\211\346\225\217/20240625_\350\277\207\346\273\244\346\237\245\350\257\242.md" "b/\346\236\227\347\216\211\346\225\217/20240625_\350\277\207\346\273\244\346\237\245\350\257\242.md" new file mode 100644 index 0000000000000000000000000000000000000000..757548ca6823694aa03d059bfc6913be001abfcf --- /dev/null +++ "b/\346\236\227\347\216\211\346\225\217/20240625_\350\277\207\346\273\244\346\237\245\350\257\242.md" @@ -0,0 +1,37 @@ +# 过滤 搜索 排序 +**AuthorResourceParameters**:在author的路由参数定义类中,添加如下字段 +```cs + // 添加过滤,定义属性Age + // 过滤:通过给定值对资源的一个或多个属性进行限制 + public int Age { get; set; } + // 添加搜索功能,搜索全列表信息 + // 搜索:根据指定关键字对所有属性值进行匹配 + public string? SearchQuery { get; set; } + // 添加排序 + // 当获取资源时,如果没有显示指定排序字段,默认以Name进行排序 + public string SortBy { get; set; } = "Name"; +``` +**AuthorRepository**:实现仓储接口的类中修改以下方法 +```cs + public async Task> GetByPagedQueryAsync(AuthorResourceParameters parameters) + { + IQueryable queryableAuthors = _dbContext.Set(); + // 过滤,没有就使用默认数据 + // {{url}}/author?age=99 + if (parameters.Age != 0) + { + queryableAuthors = queryableAuthors.Where(x => x.Age == parameters.Age); + } + // 搜索 {{url}}/author?searchquery=9 + if (!string.IsNullOrEmpty(parameters.SearchQuery)) + { + queryableAuthors = queryableAuthors.Where(x => x.Name.Contains(parameters.SearchQuery)); + } + // 排序 + if (parameters.SortBy == "Name") + { + queryableAuthors = queryableAuthors.OrderBy(x => x.Name); + } + return await PagedList.CreateAsync(queryableAuthors, parameters.PageSize, parameters.PageIndex); + } +``` \ No newline at end of file diff --git "a/\346\236\227\347\216\211\346\225\217/20240701_DDD\346\236\266\346\236\204\350\256\276\350\256\241\346\226\271\346\263\225\350\256\272.md" "b/\346\236\227\347\216\211\346\225\217/20240701_DDD\346\236\266\346\236\204\350\256\276\350\256\241\346\226\271\346\263\225\350\256\272.md" new file mode 100644 index 0000000000000000000000000000000000000000..4a7b9f6c347d5f76a70947901bc8c5e1f3c7d634 --- /dev/null +++ "b/\346\236\227\347\216\211\346\225\217/20240701_DDD\346\236\266\346\236\204\350\256\276\350\256\241\346\226\271\346\263\225\350\256\272.md" @@ -0,0 +1,95 @@ +# DDD (Domain Driven Design) +![传统三层架构](https://img2023.cnblogs.com/blog/1677460/202302/1677460-20230209194207497-1227078460.png) +![DDD架构](https://img2023.cnblogs.com/blog/1677460/202302/1677460-20230207201219722-896580178.png) +![区别](https://img2023.cnblogs.com/blog/1677460/202302/1677460-20230209202017843-28958256.png) +# 充血模型与贫血模型 +## 贫血模型 +具有一堆==属性和set get方法==,通过pojo对象上==看不出业务有哪些逻辑==。一个pojo可能被多个模块调用,只能去上层各种各样的service来调用,这样以后当梳理这个实体有什么业务,只能一层一层去搜service,也就是==贫血失忆症==,不够面向对象。 + +## 充血模型 +比如如下user用户有改密码,改手机号,修改登录失败次数等操作,都内聚在这个user实体中,每个==实体的业务都是清晰==的,充血模型的内存计算会多一些,内聚核心业务逻辑处理。通过user实体就能看出有哪些业务 +```cs + +public class AppUser : BaseEntity +{ + public string UserName; + public string Password; + // 改用户名 + private void SaveUserName(string userName) + { + if (userName == null) + { + throw new Exception("用户名不为空"); + }; + + UserName = userName; + } + // 改密码 + public void SavePassword(string password) + { + if (password == null) + { + throw new Exception("密码不为空"); + } + Password = password; + } + public AppUser(string userName, string password) + { + SaveUserName(userName); + SavePassword(password); + } +} +``` +# 实体和值对象 +## 实体 +在DDD中实体表现为实体类通常采用==充血模型==,与该实体相关的业务逻辑都在这个实体类中实现,跨多个实体的领域逻辑则在领域服务中实现 +实体以 DO(领域对象)的形式存在,每个实体对象都有唯一的 ID。 +## 值对象 +值对象本质上就是一个集,用于描述实体的一些属性集,被实体引用。这个集合里面有若干个用于==描述目的、具有整体概念和不可修改==的属性。在领域建模的过程中,值对象可以保证属性归类的清晰和概念的完整性,避免属性零碎 +![示例图](https://img-blog.csdnimg.cn/ce24755aef134b3c99dd72915e464f35.png) +```cs +public class Address extends ValueObject { + private String province;//省 + private String city;//市 + private String region;//区 + public Address(String province, String city, String region) { + if (StringUtils.isBlank(province)){ + Assert.throwException("province不能为空!"); + } + if (StringUtils.isBlank(city)){ + Assert.throwException("city不能为空!"); + } + if (StringUtils.isBlank(region)){ + Assert.throwException("region不能为空!"); + } + this.province = province; + this.city = city; + this.region = region; + } +} +``` +DDD从邻域模型出发,而不是先设计数据模型 +# 聚合 +实体和值对象是很基础的领域对象。实体一般对应业务对象,它具有业务属性和业务行为;而值对象主要是属性集合,对实体的状态和特征进行描述。但实体和值对象都只是个体化的对象,它们的行为表现出来的是个体的能力。==让实体和值对象协同工作的组织就是聚合==。它用来确保这些邻域对象在实现共同的业务逻辑时保证数据的一致性。==聚合就是由业务和逻辑紧密关联的实体和值对象组合而成的==,聚合是数据修改和持久化的基本单元,每一个聚合对应一个仓储,实现数据的持久化。聚合在 DDD 分层架构里属于==领域层==,领域层包含了多个聚合,共同实现核心业务逻辑。 + +跨多个实体=》邻域服务,跨多个聚合=》应用服务 +![](https://img-blog.csdnimg.cn/bf750a5d31ca4475b01a515027feb36b.png) +## 聚合根 +如果把聚合比作组织,那聚合根就是这个组织的负责人。聚合根也称为==根实体==,它不仅是实体,还是聚合的管理者。 +首先它作为实体本身,拥有实体的属性和业务行为,==实现自身的业务逻辑==。 +其次它作为聚合的管理者,在聚合内部负责==协调实体和值对象按照固定的业务规则协同完成共同的业务逻辑==。 +最后在聚合之间,它还是聚合对外的接口人,以聚合根 ID 关联的方式接受外部任务和请求,在上下文内实现聚合之间的业务协同。也就是说,==聚合之间通过聚合根 ID 关联引用==,如果需要访问其它聚合的实体,就要先访问聚合根,再导航到聚合内部实体,外部对象不能直接访问聚合内实体。 +# 邻域事件 +领域事件可以是==业务流程的一个步骤==,如果发生某种事件后,会触发进一步的操作,那么这个事件很可能就是领域事件。【输错三次密码,锁定用户】 + +一个完整的领域事件 = 事件发布 + 事件存储 + 事件分发 + 事件处理。 +事件发布:构建一个事件,需要唯一标识,然后发布; +事件存储:发布事件前需要存储,因为接收后的事件也会存储,可用于重试或对账等;就是每次执行一次具体的操作时,把行为记录下来,执行持久化。 +事件分发:服务内的应用服务或者领域服务直接发布给订阅者,服务外需要借助消息中间件,比如Kafka,RabbitMQ等,支持同步或者异步。 +事件处理:先将事件存储,然后再处理。 +当然了,实际开发中事件存储和事件处理不是必须的。 +# DDD分层架构 +DDD 的分层架构在不断发展。最早是传统的四层架构;再后来领域层和应用层之间增加了上下文环境(Context)层,五层架构(DCI)就此形成了。 +![](https://img-blog.csdnimg.cn/73fa736951524ed3accbd30774f19cd0.png) +## 用户接口层 +前端应用和微服务之间服务访问和数据交换的桥梁。或获取应用服务的数据后,进行数据组装,向前端提供数据服务。主要服务形态是 Facade 服务。Facade 服务分为接口和实现两个部分。完成服务定向,DO 与 DTO 数据的转换和组装,实现前端与应用层数据的转换和交换。 \ No newline at end of file diff --git "a/\346\236\227\347\216\211\346\225\217/20240701_RBAC\346\250\241\345\236\213.md" "b/\346\236\227\347\216\211\346\225\217/20240701_RBAC\346\250\241\345\236\213.md" new file mode 100644 index 0000000000000000000000000000000000000000..4ac97fc8192b297e34d580dc347c3086d8522467 --- /dev/null +++ "b/\346\236\227\347\216\211\346\225\217/20240701_RBAC\346\250\241\345\236\213.md" @@ -0,0 +1,52 @@ +# 权限模型 +![示意图](https://img2020.cnblogs.com/blog/1159660/202106/1159660-20210615113528093-1497316812.png) +1. 用户:发起操作的主体 +2. 角色:连接用户与权限的关系,起桥梁作用 +3. 权限:用户可以访问的资源 + 1. 页面:菜单,用户登录系统看到的页面,一级菜单与二级菜单,只要用户有一级菜单跟二级菜单的权限就可以访问页面 + 2. 操作:功能按钮,增删改查审核,检验是否有该权限,有就进行下一步操作,没有就提示无权限 + 3. 数据:用户在同一页面看到不同的数据 + +## RBAC0模型 +![RBAC0](https://img2020.cnblogs.com/blog/1159660/202106/1159660-20210615113916222-1368461760.png) +## RBAC1模型 +引入角色继承(Hierarchical Role)概念,使角色有上下级关系,单继承 +![RBAC1](https://img2020.cnblogs.com/blog/1159660/202106/1159660-20210615114211303-1306296030.png) +## RBAC2模型 +基于核心模型的基础上,进行角色约束控制,添加责任分离关系【静态责任分离、动态分离】=》用户激活一个角色遵行的强制性规则 +==主要包括约束:== +1. **互斥角色**:各自权限相互制约的两个角色,同一个用户只能分配到一组互斥角色中的一个【会计《=》审核员】,体现了职责分离 +2. **基数约束**:用户分配到的角色数量受限,角色拥有的用户数量受限;角色对应的访问权限数量受限 +3. **先决条件角色**:用户想获得某个上级角色,需先获得其上级角色的下一级角色 +## RBAC3模型 +最全面的权限管理,基于以上所有模型的整合 +## 用户组 +平台用户基数过大时,管理员给每个用户分配角色,工作量会过大。把相同属性的用户归类到某用户组,管理员直接给用户组分配角色,当用户组有其他成员加入后,自动获得该角色,退出时同时撤销角色。 +1. **具有上下级的用户组**:如部位与职位,客服部、后勤部,有客服权限、上架权限 +2. **普通用户组**:没有上下级,可以跨职位,跨部门 +### 组织 +把组织与角色进行关联,用户加入组织,获得该组织的角色 +控制数据权限,把角色关联到组织,该角色就可以看到该组织下的数据权限 +![组织架构图](https://img2020.cnblogs.com/blog/1159660/202106/1159660-20210615114902513-59793365.png) +### 职位 +一个组织下有多个职位,每个职位的权限是不同的,职位高的权限多 +![职位图](https://img2020.cnblogs.com/blog/1159660/202106/1159660-20210615114940631-1802913337.jpg) +### 含组织/职位/用户组的模型 +![模型权限](https://img2020.cnblogs.com/blog/1159660/202106/1159660-20210615164256264-1038865648.jpg) +用户类型单一的情况下,权限系统会变得很复杂此时系统架构为分布式系统,将权限系统独立出来,负责所有的系统的权限控制,配置其他系统的角色和权限 +# 授权流程 +给用户授予角色 +1. **手动授权**:管理员登录权限中心为用户授权 + 1. 给用户添加角色:在用户管理页面,点击用户授予角色,可以一次添加多个角色 + 2. 给角色添加用户:角色管理页面,点击角色,选择多个用户 +2. **审批授权**:用户通过OA流程【Office Automation办公自动化流程】申请角色,上级审批,即可获得该角色 +# 表结构 +![多系统下的表结构](https://img2020.cnblogs.com/blog/1159660/202106/1159660-20210615164516053-1644052804.png) +# 权限框架 +Apache Shrio +Spring Security +# 安全原则 +RBAC支持公认的安全原则:最小特权原则、责任分离原则和数据抽象原则。 +1. **最小特权原则**:限制角色权限的多少和大小实现,分配给用户对应角色的权限不超过用户完成其任务的需要 +2. **责任分离原则**:完成敏感任务过程中分配两个责任上互相约束的两个角色【例如在清查账目时,只需要设置财务管理员和会计两个角色参加就可以了。】 +3. **数据抽象原则**:借助抽象许可权【如在账目管理活动中,可以使用信用、借方等抽象许可权,而不是使用操作系统提供的读、写、执行等具体的许可权。】但RBAC并不强迫实现这些原则,安全管理员可以允许配置RBAC模型使它不支持这些原则。因此,RBAC支持数据抽象的程度与RBAC模型的实现细节有关。 \ No newline at end of file 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" new file mode 100644 index 0000000000000000000000000000000000000000..58cb1e999ff2ca6dce575e17d9e3187c6ec0141b --- /dev/null +++ "b/\346\236\227\347\216\211\346\225\217/20240701_abp vnext\346\241\206\346\236\266.md" @@ -0,0 +1,3 @@ +# ABP vNext +https://www.cnblogs.com/zzy-tongzhi-cnblog/p/16545513.html +ABP vNext 是一个基于 DDD 的框架,它提供了丰富的工具和结构,帮助开发者更容易地实现 DDD 原则,并构建更复杂、可维护的应用程序。 \ No newline at end of file diff --git "a/\346\236\227\347\216\211\346\225\217/20240701_\351\241\271\347\233\256\345\210\206\345\261\202\346\236\266\346\236\204-\345\210\235\345\247\213\345\210\233\345\273\272.md" "b/\346\236\227\347\216\211\346\225\217/20240701_\351\241\271\347\233\256\345\210\206\345\261\202\346\236\266\346\236\204-\345\210\235\345\247\213\345\210\233\345\273\272.md" new file mode 100644 index 0000000000000000000000000000000000000000..4bf168b0a18a56f8ab0e0033253a604e912b1948 --- /dev/null +++ "b/\346\236\227\347\216\211\346\225\217/20240701_\351\241\271\347\233\256\345\210\206\345\261\202\346\236\266\346\236\204-\345\210\235\345\247\213\345\210\233\345\273\272.md" @@ -0,0 +1,73 @@ +## DDD架构 +DDD的四层架构:用户接口层、应用层、领域层和基础设施层, +在解决方案中添加项目:`dotnet sln add 项目路径 ` +![DDD架构](https://img2023.cnblogs.com/blog/1677460/202302/1677460-20230207201219722-896580178.png) + +## 基础知识 +1. `dotnet add 操作项目路径 reference 引用项目路径` 将项目引用到项目 +2. `dotnet add reference 引用项目路径` +## *.Api 接口服务层 +`dotnet add new webapi -n XX` +构建 RESTful 应用程序,提供HTTP服务。管道中间件、过滤器、跨域、路由、模型验证等都在此配置。【入口文件、依赖注入容器】 +## *.Application 应用层 +`dotnet add new classlib -n XX` +不会包含任何与业务有关的逻辑信息,将使用CQRS的设计模式。安全认证、权限校验、事务控制、发送或订阅领域事件等都可在此层做处理。 +## *.Application.Contracts 定义接口 + +## *.Domain 邻域层 核心层 +`dotnet add new classlib -n XX` +实现核心的业务逻辑!内部主要包含Entity(实体)、Domain Event(领域事件)、Domain Service(领域服务)等。 +[Domain中的表及其解释](https://www.cnblogs.com/Dragon_Compass/p/17962586) +Entity=>System存放系统级文件=>*Role.cs/*User.cs +**permission权限:** +1. 资源 AppResource: 菜单 +2. 操作性 AppOperation + 1. r 读取 + 2. w add添加 ,update修改 + 3. x 任务权限 【eg:每天3点总结余额】 + 4. 导出/导入 + 5. 打印 +3. 数据 + +```cs +public class AppPermission:BaseEntity +{ + public Guid ResourceId{get;set;}=null!; + public Guid OperationId{get;set;}=null!; + // 导航属性,直接获取Id所对应的属性 + // 建立外键连接【已经弃用】 + // public virtual AppResource AppResource{get;set;} + // public virtual AppOperation AppOperation{get;set;} +} + +public class AppOperation:BaseEntity +{ + // 操作名称 eg:读取(查找、过滤)、新增、修改、导入、导出、打印、最大化、放大 + public string OperationName{get;set;}=null!; + public string? Descript{get;set;}=null!; +} + + +public class AppResource:BaseEntity +{ + public string ResourceName{get;set;}=null!; + public string Url{get;set;}=null!; +} +``` + +```cs +public class AppUser +``` +## *.Domain.Shared 领域共享层 +在里面定义的常量或变量不只在邻域层中使用 +## *.Infrastructure 基础设施 +`dotnet add new classlib -n XX` +对整个应用程序提供基础实现,例如仓储的实现、工作单元模式的实现、Redis缓存、队列服务等。 +## *.EntityFrameworkCore =》基础设施中抽离出来 +存放与EntityFrameworkCore依赖包相关的文件【DbContext,Migrations】 + +`dotnet add new classlib -n XX` + + + +https://www.cnblogs.com/huangmingji/p/13812191.html \ No newline at end of file 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" new file mode 100644 index 0000000000000000000000000000000000000000..23779bf4794bcb133b7ba5a9aa9779af2940f540 --- /dev/null +++ "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" @@ -0,0 +1,64 @@ +# Domain +文件结构: +```cs +Admin2024.Domain{ + Entity{ + System{ 存放实体类 + *User.cs, 用户 + *Role.cs, 角色 + *UserRole.cs, 用户角色连接表 + *Permission.cs, 权限 + *Operation.cs, 操作权限 + *Resource.cs, 资源权限 + *RolePermission.cs 角色权限连接表 + }, + BaseEntity.cs 基础实体类=》其他实体都继承这个 + } + Interface{ 定义通用仓储接口 + IRepository.cs + } +} +``` +Entity=>BaseEntity.cs 其他实体继承该类,且该类不为一个实体【定义为abstract】 +```cs +public abstract class BaseEntity +{ + // 组件Id + public Guid Id{get;set;} + // 软删除 + public bool IsDeleted{get;set;} + // 是否激活 + public bool IsActived{get;set;} + // 创建时间 + public DateTime CreatedAt{get;set;} + // 谁创建 + public Guid CreatedBy{get;set;} + // 修改时间 + public DateTime UpdateAt{get;set;} + // 谁修改 + public Guid UpdateBy{get;set;} + // 列表显示顺序,默认为0 5>2 + public int DisplayOrder{get;set;} + // 备注 + public string? Remarks{get;set;} +} +``` +```cs +public class AppUser:BaseEntity +{ + public string UserName{get;set;}=null!; + public string Password{get;set;}=null!; + public string NickName{get;set;}=null!; + // 仅一个手机号 + public string Telephone{get;set;}=null!; + // 多个手机号,创建单独的UserTelephone表 + // 头像 + public string Avatar{get;set;}=null!; +} + +public class AppRole:BaseEntity +{ + public string RoleName{get;set;}=null!; + public string? Descript{get;set;}=null!; +} +``` 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" new file mode 100644 index 0000000000000000000000000000000000000000..779556f41837da88ba5be8e2dd33cbdb611439d5 --- /dev/null +++ "b/\346\236\227\347\216\211\346\225\217/20240703_NavicatPernum.cs" @@ -0,0 +1 @@ +使用fluntapi定义表名,字段面 abc_op 配置与映射属性 \ 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" new file mode 100644 index 0000000000000000000000000000000000000000..2b31a7078ab0bd949d3a339a0aa4e63751d5f8ab --- /dev/null +++ "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" @@ -0,0 +1,51 @@ +# Tabby中安装 +使用apt-get更新软件包列表:`sudo apt-get update` +安装pg服务器软件包:`sudo apt-get install postgresql postgresql-contrib` +启动pg服务:`sudo systemctl start postgresql` +设置开机自启:`sudo systemctl enable postgresql` +连接到pg数据库:`sudo -i -u postgres psql` +# 启用远程连接 +使用Tap自动补全 +查看pg版本号:`psql --version` + +1. 编辑pg配置文件`postgresql.conf`: `sudo nano /ect/postgresql/版本号/main/postgresql.conf` + 1. `sudo nano 文件路径`表示使用nano编辑器进行文件编辑C;tr+O+Enter保存文件,Ctr+X关闭文件并退出 + 2. `sudo vim FileUrl` 使用vim编辑器:Esc => :w =>Enter 保存文件,:q => Enter 关闭文件并退出编辑器 +2. 找到`#listen_addresses = 'localhost'` 修改`localhost`为`*`,表示,允许来自所有主机的连接。保存并关闭文件 +3. 编辑客户端认证配置文件`pg_hba.conf`:`sudo nano /etc/postgresql/15/main/pg_hba.conf` + 1. 末尾添加 `host all all 0.0.0.0/0 md5` 允许所有主机以密码验证访问所有数据库【md5免密登录】 +4. 重启pg服务,使更改生效:`sudo systemctl restart postgresql` + +## 使用强密码保护pg数据库 +1. 创建新的pg数据库用户并分配密码:`create user litter-cat with password 123456` +2. 更新`pg_hba.conf`:`host all all 0.0.0.0/0 scram-sha-256` 要求用户使用密码进行身份验证 +3. 重新加载配置:`sudo systemctl restart postgresql` + +# 基础命令 +1. \d [指定表名] 数据表 +2. \du 用户 +3. \l [指定数据库名] 数据库 +4. \q 退出 +5. exit 退出 + +# 操作pg +## 创建角色 +1. 连接到pg数据库:`sudo -i -u postgres psql` +2. 创建角色:`create role 角色名 superuser password '123456' login replication createdb createrole` + 1. superuser 超级管理员 + 2. 密码需用`""`或`''`包裹 + 3. login replication createdb createrole 为权限 +```cs +postgres=# \du +postgres=# create role root superuser password '123456' login replication createdb createrole; +CREATE ROLE +postgres=# create database my_first_db owner root; +CREATE DATABASE +postgres=# exit +``` +## 创建表 +1. 连接数据库:`\c 数据库名` +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 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" new file mode 100644 index 0000000000000000000000000000000000000000..b92f5a3d8c0fdd2ad28378330f46731512111ff3 --- /dev/null +++ "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" @@ -0,0 +1,178 @@ +# EntityFrameworkCore +添加依赖包:`dotnet add package Microsoft.EntityFrameworkCore` +文件结构: +```cs +EntityFrameworkCore{ + *DbContext.cs 数据上下文,配置实体模型, + EntityConfiguration{ + System{ + + }, + BaseEntityConfiguration.cs + }, + Repository{ + EfRepository.cs 实现Domain.Interface中的IRepository.cs接口 + } +} +``` +引入Domain程序:`dotnet add reference 程序路径`或`dotnet sln 操作程序路径 add reference 引入程序路径` +***DbContext.cs:** +```cs +using Admain2024.Domain.Entity.System; +using Microsoft.EntityFrameworkCore; +// 服务器驱动:pg Npgsql.EntityFrameworkCore.PostgreSQL +namespace Admain2024.EntityFramworkCore; + +public class Admain2024DbContext : DbContext +{ + public DbSet AppRole { get; set; } + 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) + { } +} +``` +将DbContext添加到依赖注入容器,再生成迁移文件 +`dotnet ef migrations add 迁移文件名 --project 文件存放路径 -s启动项目路径` +`dotnet ef database update --project 文件存放路径 -s启动项目路径` + + + +```cs +namespace Admain2024.Infrastrastucture.Persistence; +using System.Reflection; +// dotnet add 要操作的项目路径 reference 要引入的项目路径 +using Admain2024.Domain.Entity.System; +using Admain2024.Domain.Entity; +using Microsoft.EntityFrameworkCore; +// 数据库上下文,建表、插入数据 +public class Admain2024DbContext : DbContext +{ + // 因为C#8.0 引入的一个新特性,编译器将会对不应为Null,但实际可能赋值为Null的情况提供警告,所以,这里就得这么用 + public DbSet AppUser => Set(); + // public DbSet AppUser() + // { + // return Set(); + // } + public DbSet AppRole => Set(); + public DbSet AppUserRole => Set(); + public DbSet AppDepartment => Set(); + public Admain2024DbContext(DbContextOptions options) : base(options) + { } + // 配置实体模型 + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + // ApplyConfigurationsFromAssembly 将这些配置类应用到modelBuilder + modelBuilder.ApplyConfigurationsFromAssembly( + // Assembly属性 获取包含DbContext类的程序集, + // Assembly.GetExecutingAssembly 获取当前程序集中的所有实体配置类 ==》将所有实体配置一次性应用到DbContext中 + Assembly.GetExecutingAssembly(), + // 通过谓词函数判断配置类是否符合预期条件:实现泛型接口IEntityTypeConfiguration<>,泛型参数继承BaseEntity类 + // 谓词函数=》lambda表达式,用于过滤符合特定条件的实体配置类。接收一个Type作为参数,代表程序集中的每个类型 + // GetInterfaces 获取当前类型实现的所有接口 Any判断是否存在符合条件的接口 + t => t.GetInterfaces().Any(i => + // IsGenericType属性 判断接口是否是泛型 + i.IsGenericType && + // GetGenericTypeDefinition方法 获取泛型接口的原始定义 + // 使用==与typeof(IEntityTypeConfiguration<>)比较,判断该接口是否是泛型接口IEntityTypeConfiguration<>的实现 + i.GetGenericTypeDefinition() == typeof(IEntityTypeConfiguration<>) && + // IsAssignableFrom 判断BaseEntity是否可以赋值给接口的泛型参数类型(即实体类) + typeof(BaseEntity).IsAssignableFrom(i.GenericTypeArguments[0]) + ) + ); + // 将模型配置应用到数据库上下文中 + base.OnModelCreating(modelBuilder); + } + // SaveChanges将对实体进行的所有更改保存到数据库中 + // 重写方法对实体的保存行为进行了定制化处理 + /* + 这段代码的作用是在保存实体到数据库之前,自动设置实体的创建时间和更新时间,以确保这些时间戳属性在数据库中正确地记录实体的创建和更新时间。 + 这种做法可以在应用程序中统一处理实体的时间戳属性,避免在每次保存实体时手动设置这些属性。 + */ + public override int SaveChanges() + { + // ChangeTracker.Entries 获取所有正在跟踪的实体条目 + // 筛选出状态为 Added(新增)且实现了 BaseEntity 类的实体。 + ChangeTracker.Entries().Where(item => item.State == EntityState.Added && item.Entity is BaseEntity).ToList().ForEach(item => + { + // 对于这些新增的实体,将它们的 CreatedAt 和 UpdateAt 属性设置为当前的 UTC 时间 + var entity = (BaseEntity)item.Entity; + entity.CreatedAt = DateTime.UtcNow; + entity.UpdateAt = DateTime.UtcNow; + }); + // 选出状态为 Modified(修改)且实现了 BaseEntity 类的实体 + ChangeTracker.Entries().Where(item => item.State == EntityState.Modified && item.Entity is BaseEntity).ToList().ForEach(item => + { + // UpdateAt 属性更新为当前的 UTC 时间。 + var entity = (BaseEntity)item.Entity; + entity.UpdateAt = DateTime.UtcNow; + }); + // 将更改保存到数据库,并返回保存操作受影响的行数。 + return base.SaveChanges(); + } +} +``` +```cs +using Admain2024.Domain.Entity.System; + +namespace Admain2024.Infrastrastucture.Persistence; + +public static class Admain2024DbContextSeed +{ + public static void SeedInitDataAsync(Admain2024DbContext dbContext) + { + if (!dbContext.AppUser.Any()) + { + var role = new AppRole + { + RoleName = "超级管理员", + Descript = "超级管理员", + IsDeleted = false, + IsActived = true, + CreatedAt = DateTime.UtcNow, + UpdateAt = DateTime.UtcNow, + // CreatedBy="未知", + // UpdateAt="未知", + DisplayOrder = 0, + Remarks = "这是一个初始化的经理角色" + }; + dbContext.AppRole.Add(role); + + var user = new AppUser + { + UserName = "超级管理员", + NickName = "小叮当", + Telephone = "123456789012", + Password = "123456", + Avatar = "头像链接", + IsDeleted = false, + IsActived = true, + CreatedAt = DateTime.UtcNow, + UpdateAt = DateTime.UtcNow, + DisplayOrder = 0, + Remarks = "这是一个初始化的经理角色" + }; + dbContext.AppUser.Add(user); + dbContext.SaveChanges(); + + var userRole = new AppUserRole + { + RoleId = role.Id, + UserId = user.Id, + IsDeleted = false, + IsActived = true, + CreatedAt = DateTime.UtcNow, + UpdateAt = DateTime.UtcNow, + DisplayOrder = 0, + Remarks = "连接角色与用户的表" + }; + dbContext.AppUserRole.Add(userRole); + dbContext.SaveChanges(); + } + } +} +``` \ 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-\346\216\245\345\217\243\345\261\202Api.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-\346\216\245\345\217\243\345\261\202Api.md" new file mode 100644 index 0000000000000000000000000000000000000000..a60593afc61a08214d56622c006aeaf6dcc856d4 --- /dev/null +++ "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-\346\216\245\345\217\243\345\261\202Api.md" @@ -0,0 +1,4 @@ +# Api +为webapi项目,存放接口文件,依赖注入容器,控制器 +安装依赖:`Microsoft.EntityFrameworkCore.Design + Microsoft.EntityFrameworkCore.Tools` +需要引入EntityFrameworkCore层 diff --git "a/\346\236\227\347\216\211\346\225\217/20240704_Linux\350\257\255\346\263\225.md" "b/\346\236\227\347\216\211\346\225\217/20240704_Linux\350\257\255\346\263\225.md" new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 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" new file mode 100644 index 0000000000000000000000000000000000000000..31583caad7dac6f0d6f256d6d87544023b95ca46 --- /dev/null +++ "b/\346\236\227\347\216\211\346\225\217/20240704_\350\231\232\346\213\237\346\234\272.md" @@ -0,0 +1 @@ +[VM下载及安装教程](blog.csdn.net/weixin_44275259/article/details/108294627) diff --git "a/\346\236\227\347\216\211\346\225\217/20240704_\351\241\271\347\233\256\345\210\206\345\261\202\346\236\266\346\236\204-\345\272\224\347\224\250\345\261\202Application.md" "b/\346\236\227\347\216\211\346\225\217/20240704_\351\241\271\347\233\256\345\210\206\345\261\202\346\236\266\346\236\204-\345\272\224\347\224\250\345\261\202Application.md" new file mode 100644 index 0000000000000000000000000000000000000000..d079600f87d554c7c389801b5b46dd137d0ee79c --- /dev/null +++ "b/\346\236\227\347\216\211\346\225\217/20240704_\351\241\271\347\233\256\345\210\206\345\261\202\346\236\266\346\236\204-\345\272\224\347\224\250\345\261\202Application.md" @@ -0,0 +1,23 @@ +# Application.Contract +定义用于增删改查的实体类,以及业务接口 +文件结构: +```cs +Application.Contract{ + *{ + *Dto.cs, + Create*Dto.cs, + Update*Dto.cs, + I*AppService.cs 定义业务接口 + } +} +``` +# Application + +文件结构: +```cs +Application{ + *{ + *AppService.cs 实现Application.Contract中的IAppUserService.cs接口 + } +} +``` \ 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" new file mode 100644 index 0000000000000000000000000000000000000000..c6ae0aee815eeaad66e8bfc7d746a5415b7b5704 --- /dev/null +++ "b/\346\236\227\347\216\211\346\225\217/20240705_redis.md" @@ -0,0 +1,23 @@ +# 消息队列 + +# 数据种子 +```cs +modelBuilder.Entity() +.HasData(new MyEntity{},...) +``` + +JWT token session cookies +# Token +1、Token的引入:Token是在客户端频繁向服务端请求数据,服务端频繁的去数据库查询用户名和密码并进行对比,判断用户名和密码正确与否,并作出相应提示,在这样的背景下,Token便应运而生。 + +2、Token的定义:Token是服务端生成的一串字符串,以作客户端进行请求的一个令牌,当第一次登录后,服务器生成一个Token便将此Token返回给客户端,以后客户端只需带上这个Token前来请求数据即可,无需再次带上用户名和密码 + +用session值作为Token +客户端:客户端只需携带用户名和密码登陆即可。 +客户端:客户端接收到用户名和密码后并判断,如果正确了就将本地获取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\225.md" "b/\346\236\227\347\216\211\346\225\217/20240705_\346\265\213\350\257\225.md" new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 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" new file mode 100644 index 0000000000000000000000000000000000000000..24f37d64de74b8fb0a7e5e782e5173049fc1d8b4 --- /dev/null +++ "b/\346\236\227\347\216\211\346\225\217/Test.cs" @@ -0,0 +1,11 @@ +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