In order to let everyone better understand the use of summerBoot , I created a sample project- SummerBootAdmin , a general back-end management framework based on the separation of front-end and back-end, you can check the code of this project to better understand how to use summerBoot .
Combine the advanced concepts of SpringBoot with the simplicity and elegance of C#, declarative programming, focus on"what to do"rather than"how to do it", and write code at a higher level.SummerBoot is committed to creating an easy-to-use and easy-to-maintain humanized framework, so that everyone can get off work early to do what they like.
This is a fully declarative framework that implements various calls in the form of annotations + interfaces, including but not limited to database, http, cache, etc.The framework will automatically generate interface implementation classes through Reflection Emit technology.
Group number : 799648362
SummerBoot in your project .
PM> Install-Package SummerBoot
net core 3.1, net 6,net 8
- Thanks for the ide license provided by jetbrain
- SummerBoot (中文名:夏日启动)
- Core idea
- Framework description
- Join the QQ group for feedback and suggestions
- Getting Started
- support frame
- document directory
- SummerBoot uses repository for database operations
- Preparation
- 1.Registration service
- 2.Define a database entity class
- 3.Write a controller to automatically generate database tables through entity classes
- 4.Define storage interface
- 5.Add, delete, modify and Query, all support asynchronous synchronization
- 5.1 Added
- 5.2 delete
- 5.3 update
- 5.4 Query
- 5.5 Transaction Support
- 5.6 Custom implementation classes in special cases
- 5.6.1 Define an interface inherited from IBaseRepository , and define your own methods in the interface
- 5.6.2 Add an implementation class, inherited from the CustomBaseRepository class and the custom ICustomCustomerRepository interface, and add the AutoRegister annotation to the implementation class.
- 5.6.3 Example of use
- 6 Automatically generate entity classes based on database tables, or automatically generate ddl statements for database tables based on entity classes
- SummerBoot uses feign to make http calls
- 1.Registration service
- 2.Define the interface
- 3.Set the request header (header)
- 4.Custom Interceptor
- 5.Define the method
- 5.1 Common parameters in the method
- 5.2 Special parameters in the method
- 5.2.1 Parameters add Query annotations
- 5.2.2 Parameters add Body (BodySerializationKind.Form) annotations
- 5.2.3 Parameters add Body (BodySerializationKind.Json) annotations
- 5.2.4 Use the special class HeaderCollection as a method parameter to add request headers in batches
- 5.2.5 Use the special class BasicAuthorization as a method parameter to add the Authorization request header for basic authentication
- 5.2.6 Use the special class MultipartItem as a method parameter, and mark the Multipart annotation on the method to upload the attachment
- 5.2.7 Use the class Stream as the return type of the method to receive streaming data, such as downloading files.
- 5.2.8 Use the class HttpResponseMessage as the return type of the method to get the most original response message.
- 5.2.9 Use the class Task as the return type of the method, that is, no return value is required.
- 6.Microservice - access to nacos
- 7.Using cookies in context
- SummerBoot uses cache for cache operations
summerBoot has developed its own ORM module based on the work unit and warehousing mode, that is, repository, which supports multiple databases and multiple links, including addition, deletion , modification and query operations of five common database types (sqlserver, mysql, oracle, sqlite, pgsql) , if there are other For database requirements, you can refer to the above 5 source codes and contribute codes to this project.
You need to install the corresponding database dependency package through nuget , such as Microsoft.Data.SqlClient of SqlServer , Mysql.data of mysql , Oracle.ManagedDataAccess.Core of oracle, Npgsql of pgsql
The repository supports multiple databases and multiple links.In the repository, we call a link a database unit.The following example demonstrates adding two database units to a project.The first is the mysql database type, and the second is the sqlserver database type ., add a database unit through the AddDatabaseUnit method, the parameters are the dbConnection class of the corresponding database and the work unit interface (the work unit interface is used for transactions), the framework provides 9 work unit interfaces IUnitOfWork1~IUnitOfWork9 by default, of course, you can also customize the work unit interface , you only need to inherit the interface from IUnitOfWork .Because there are multiple database units, the warehouse needs to be bound to the corresponding database unit.You can bind a single warehouse through BindRepository , or add a custom annotation on the warehouse (this annotation needs to be inherited from AutoRepositoryAttribute , which is provided by the framework by default.AutoRepository1Attribute~AutoRepository9Attribute), and then use the BindRepositoriesWithAttribute method to bind warehouses in batches.At the same time, you can add pre-insert callbacks and pre-update callbacks in the unit (for example, it can be used to add creation time and update time), add custom type mappings, and add custom field mappings Handler, add table name mapping, add field name mapping (table name mapping and field name mapping can be used in situations where database fields and entity class fields are different, such as oracle database, table name field names are all uppercase, but entity classes are Pascal named), etc.
builder.Services.AddSummerBoot();builder.Services.AddSummerBootRepository(it =>{varmysqlDbConnectionString=builder.Configuration.GetValue<string>("mysqlDbConnectionString");//Add the first mysql type database unitit.AddDatabaseUnit<MySqlConnection,IUnitOfWork1>(mysqlDbConnectionString,
x =>{//Add field name mapping//x.ColumnNameMapping = (columnName) =>//{// return columnName.ToUpper();//};//Add table name mapping//x.TableNameMapping = (tableName) =>//{// return tableName.ToUpper();//};//Bind a single Repository//x.BindRepository<IMysqlCustomerRepository,Customer>();//Batch binding Repositorys through custom annotationsx.BindRepositoriesWithAttribute<AutoRepository1Attribute>();//Bind database generation interfacex.BindDbGeneratorType<IDbGenerator1>();//Callback before binding insertx.BeforeInsert+= entity =>{if(entityisBaseEntitybaseEntity){baseEntity.CreateOn=DateTime.Now;}};//Callback before binding updatex.BeforeUpdate+= entity =>{if(entityisBaseEntitybaseEntity){baseEntity.LastUpdateOn=DateTime.Now;}};//Add custom type mapping//x.SetParameterTypeMap(typeof(DateTime), DbType.DateTime2);//Add custom field mapping handler//x.SetTypeHandler(typeof(Guid), new GuidTypeHandler());});//Add a second database unit of type sqlservervarsqlServerDbConnectionString=builder.Configuration.GetValue<string>("sqlServerDbConnectionString");it.AddDatabaseUnit<SqlConnection,IUnitOfWork2>(sqlServerDbConnectionString,
x =>{x.BindRepositoriesWithAttribute<AutoRepository2Attribute>();x.BindDbGeneratorType<IDbGenerator2>();});});Most of the entity class annotations come from the system namespace System.ComponentModel.DataAnnotations and System.ComponentModel.DataAnnotations.Schema , such as table name Table, column name Column, primary key Key, primary key self-incrementing DatabaseGenerated (DatabaseGeneratedOption.Identity), column name Column, This field NotMapped is not mapped, and Description is used to generate annotations when entity classes generate database tables.At the same time, some annotations are customized, such as ignoring the column IgnoreWhenUpdateAttribute when updating (mainly used for fields that do not need to be updated during update) , let's define a Customer entity class
[Description("Member")]publicclassCustomer{[Key,DatabaseGenerated(DatabaseGeneratedOption.Identity)]publicintId{set;get;}///<summary>/// Last update time///</summary>[Description("Last update time")]publicDateTime?LastUpdateOn{get;set;}///<summary>/// Last updated by///</summary>[Description("Last Updater")]publicstringLastUpdateBy{get;set;}///<summary>/// Creation time///</summary>[IgnoreWhenUpdate][Description("Creation Time")]publicDateTime?CreateOn{get;set;}///<summary>/// founder///</summary>[IgnoreWhenUpdate][Description("Creator")]publicstringCreateBy{get;set;}///<summary>/// is it effective///</summary>[Description("Is it valid")]publicint?Active{get;set;}[Description("Name")]publicstringName{set;get;}[Description("age")]publicintAge{set;get;}[Description("Membership Number")]publicstringCustomerNo{set;get;}[Description("total consumption amount")]publicdecimalTotalConsumptionAmount{set;get;}}SummerBoot comes with a basic entity class BaseEntity (oracle is OracleBaseEntity).The entity class includes five fields: self-increasing id, creator, creation time, updater, update time, and whether it is valid.It is recommended that the entity class directly inherit BaseEntity , then The above Customer can be abbreviated as:
[Description("Member")]publicclassCustomer:BaseEntity{[Description("Name")]publicstringName{set;get;}[Description("age")]publicintAge{set;get;}[Description("Membership Number")]publicstringCustomerNo{set;get;}[Description("total consumption amount")]publicdecimalTotalConsumptionAmount{set;get;}}[ApiController][Route("[controller]/[action]")]publicclassGenerateTableController:Controller{privatereadonlyIDbGenerator1dbGenerator1;publicGenerateTableController(IDbGenerator1dbGenerator1){this.dbGenerator1=dbGenerator1;}[HttpGet]publicIActionResultIndex(){varresults=dbGenerator1.GenerateSql(newList<Type>(){typeof(Customer)});foreach(varresultinresults){dbGenerator1.ExecuteGenerateSql(result);}returnContent("ok");}}Access the index interface, and the framework will automatically generate a Customer table with annotations.
The storage interface needs to be inherited from the IBaseRepository interface, and the interface is annotated with AutoRepository1 for automatic registration.
[AutoRepository1]publicinterfaceICustomerRepository:IBaseRepository<Customer>{}5.1.1 The interface has its own Insert method, which can insert a single entity or a list of entities
If the primary key name of the entity class is Id, and there is a Key annotation, and it is self-increasing, as follows:
[Key,DatabaseGenerated(DatabaseGeneratedOption.Identity)]publicintId{set;get;}Then after insertion, the framework will automatically assign a value to the ID field of the entity, which is an auto-incremented ID value.
[ApiController][Route("[controller]/[action]")]publicclassCustomerController:Controller{privatereadonlyICustomerRepositorycustomerRepository;publicCustomerController(ICustomerRepositorycustomerRepository){this.customerRepository=customerRepository;_}[HttpGet]publicasyncTask<IActionResult>Insert(){varcustomer=newCustomer(){Name="testCustomer"};awaitcustomerRepository.InsertAsync(customer);varcustomer2=newCustomer(){Name="testCustomer2"};varcustomer3=newCustomer(){Name="testCustomer3"};varcustomerList=newList<Customer>(){customer2,customer3};awaitcustomerRepository.InsertAsync(customerList);returnContent("ok");}}5.1.2 Fast batch insertion, the storage interface comes with the FastBatchInsert method, which can quickly insert the entity list.
In the case of fast batch insertion, the framework will not automatically assign a value to the ID field of the entity.At the same time, if the database is mysql , there are some special circumstances.First, the driver library must have MySqlConnector .This library can coexist with mysql.data and will not Conflicts, so there is no need to worry, and the database connection string must be followed by"; AllowLoadLocalInfile =true", and at the same time execute"set global local_infile =1"on the mysql database to enable batch upload.SQLite does not support batch fast insert.
varcustomer2=newCustomer(){Name="testCustomer2"};varcustomer3=newCustomer(){Name="testCustomer3"};varcustomerList=newList<Customer>(){customer2,customer3};customerRepository.FastBatchInsert(customerList);5.2.1 The interface comes with a Delete method, which can delete a single entity or a list of entities
customerRepository.Delete(customer);customerRepository.Delete(customerList);5.2.2 Also supports deletion based on lambda expressions, returning the number of affected rows, for example
vardeleteCount=customerRepository.Delete(it =>it.Age>5);5.3.1 The interface comes with an Update method, which can update a single entity or a list of entities
Update according to the key primary key.If the primary key is combined, key annotations can be added to multiple fields.
customerRepository.Update(customer);customerRepository.Update(customerList);The generated update sql contains only the set fields and returns the number of affected rows, for example
varupdateCount=customerRepository.Where(it =>it.Name=="testCustomer").SetValue(it =>it.Age,5).SetValue(it =>it.TotalConsumptionAmount,100).ExecuteUpdate();It supports normal query and paged query, and there are two ways to query.
//regular queryvarallCustomers=awaitcustomerRepository.GetAllAsync();varcustomerById=awaitcustomerRepository.GetAsync(1);varcustomers=awaitcustomerRepository.Where(it =>it.Age>5).ToListAsync();varmaxTotalConsumptionAmount=awaitcustomerRepository.MaxAsync(it =>it.TotalConsumptionAmount);varnames=newList<string>(){"testCustomer","testCustomer3"};varcustomers2=awaitcustomerRepository.Where(it =>names.Contains(it.Name)).ToListAsync();varfirstItem=awaitcustomerRepository.FirstOrDefaultAsync(it =>it.Name=="testCustomer");// paginationvarpageResult=awaitcustomerRepository.Where(it =>it.Age>5).Skip(0).Take(10).ToPageAsync();varpageable=newPageable(1,10);varpageResult2=awaitcustomerRepository.ToPageAsync(pageable);Supports InnerJoin, LeftJoin, RightJoin, WhereIf, OrWhereIf and other extensions, supports up to 4 table joint queries, and supports returning entity types or anonymous types.
//Back to listvarorderList8=awaitorderHeaderRepository.InnerJoin(newOrderDetail(), it =>it.T1.Id==it.T2.OrderHeaderId).InnerJoin(newCustomer(), it =>it.T1.CustomerId==it.T3.Id).InnerJoin(newAddress(),it=>it.T4.CustomerId==it.T3.Id&&it.T4.CreateOn==date3).OrderBy(it =>it.T2.Quantity).Where(it =>it.T3.CustomerNo=="A1").Select(it =>newOrderDto(){ProductName=it.T2.ProductName,Quantity=it.T2.Quantity,CustomerNo=it.T3.CustomerNo,Age=it.T3.Age,CustomerCity=it.T4.City}, x =>x.T1).ToListAsync();varresult7=awaitcustomerRepository.LeftJoin(newAddress(), it =>it.T1.Id==it.T2.CustomerId).Where(it =>it.T1.CustomerNo=="A2").OrWhereIf(false, it =>it.T2.City=="B").Select(it =>new{it.T1.CustomerNo,it.T2.City}).ToListAsync();// paginationvarpageable=newPageable(1,5);varorderPageList=orderHeaderRepository.LeftJoin(newOrderDetail(), it =>it.T1.Id==it.T2.OrderHeaderId).LeftJoin(newCustomer(), it =>it.T1.CustomerId==it.T3.Id).LeftJoin(newAddress(), it =>it.T3.Id==it.T4.CustomerId).OrderBy(it =>it.T2.Quantity).Where(it =>it.T1.State==1).Select(it =>newOrderDto(){ProductName=it.T2.ProductName,Quantity=it.T2.Quantity,CustomerNo=it.T3.CustomerNo,Age=it.T3.Age,CustomerCity=it.T4.City}, x =>x.T1).ToPage(pageable);5.4.2 Define methods directly in the interface, and add annotations to the methods, such as Select, Update, Delete
Then write sql statements in Select, Update, Delete , such as
[AutoRepository1]publicinterfaceICustomerRepository:IBaseRepository<Customer>{//async[Select("select od.productName from customer c join orderHeader oh on c.id= oh.customerid"+"join orderDetail od on oh.id= od.OrderHeaderId where c.name=@name")]Task<List<CustomerBuyProduct>>QueryAllBuyProductByNameAsync(stringname);[Select("select * from customer where age>@age order by id")]Task<Page<Customer>>GetCustomerByPageAsync(IPageablepageable,intage);//sync[Select("select od.productName from customer c join orderHeader oh on c.id= oh.customerid"+"join orderDetail od on oh.id= od.OrderHeaderId where c.name=@name")]List<CustomerBuyProduct>QueryAllBuyProductByName(stringname);[Select("select * from customer where age>@age order by id")]Page<Customer>GetCustomerByPage(IPageablepageable,intage);}Instructions:
varresult=awaitcustomerRepository.QueryAllBuyProductByNameAsync("testCustomer");//pagevarpageable=newPageable(1,10);varpage=customerRepository.GetCustomerByPage(pageable,5);Note: 5.4.2 Pagination support in the query, the return value of the method is wrapped by the Page class, and the method parameter must include the paging parameter IPageable , and the sql statement must also have order by, for example:
[Select("select * from customer where age>@age order by id")]Page<Customer>GetCustomerByPage(IPageablepageable,intage);The sql in the annotation supports reading from the configuration The configured json is as follows:
{
"mysqlSql": {
"QueryListSql":"select * from customer",
"QueryByPageSql":"select * from customer order by age",
"UpdateByNameSql":"update customer set age=@age where name=@name",
"DeleteByNameSql":"delete from customer where name=@name"
}
}The configuration items are wrapped by ${}, and the interface is as follows:
[AutoRepository]publicinterfaceICustomerTestConfigurationRepository:IBaseRepository<Customer>{//asynchronous[Select("${ mysqlSql:QueryListSql }")]Task<List<Customer>>QueryListAsync();[Select("${ mysqlSql:QueryByPageSql }")]Task<Page<Customer>>QueryByPageAsync(IPageablepageable);//asynchronous[Update("${ mysqlSql:UpdateByNameSql }")]Task<int>UpdateByNameAsync(stringname,intage);[Delete("${ mysqlSql:DeleteByNameSql }")]Task<int>DeleteByNameAsync(stringname);//Synchronize[Select("${ mysqlSql:QueryListSql }")]List<Customer>QueryList();[Select("${ mysqlSql:QueryByPageSql }")]Page<Customer>QueryByPage(IPageablepageable);//asynchronous[Update("${ mysqlSql:UpdateByNameSql }")]intUpdateByName(stringname,intage);[Delete("${ mysqlSql:DeleteByNameSql }")]intDeleteByName(stringname);}Wrap a single query condition with {{}}, and only one variable can be included in a condition.At the same time, when defining a method, the parameter is defined as WhereItem <T> , and T is a generic parameter, indicating the real parameter type.In this way , summerboot will automatically process the query conditions.The processing rules are as follows.If the active of whereItem is true, the condition is activated, and the query conditions wrapped in {{ }} in the sql statement will expand and participate in the query.If active is false, the sql The query condition wrapped in {{ }} in the statement is automatically replaced with an empty string and does not participate in the query.In order to make whereItem more useful, the WhereBuilder method is provided.The usage example is as follows:
//definition[AutoRepository]publicinterfaceICustomerRepository:IBaseRepository<Customer>{[Select("select * from customer where 1=1 {{ and name = @name}}{{ and age = @age}}")]Task<List<CustomerBuyProduct>>GetCustomerByConditionAsync(WhereItem<string>name,WhereItem<int>age);[Select("select * from customer where 1=1 {{ and name = @name}}{{ and age = @age}} order by id")]Task<Page<Customer>>GetCustomerByPageByConditionAsync(IPageablepageable,WhereItem<string>name,WhereItem<int>age);}//usevarnameEmpty=WhereBuilder.Empty<string>();//var nameEmpty = new WhereItem<string>(false,"");varageEmpty=WhereBuilder.Empty<int>();varnameWhereItem=WhereBuilder.HasValue("page5");// var nameWhereItem = WhereItem<string>(true, "page5");varageWhereItem=WhereBuilder.HasValue(5);varpageable=newPageable(1,10);varbindResult=customerRepository.GetCustomerByCondition(nameWhereItem,ageEmpty);Assert.Single(bindResult);varbindResult2=customerRepository.GetCustomerByCondition(nameEmpty,ageEmpty);Assert.Equal(102,bindResult2.Count);varbindResult5=customerRepository.GetCustomerByPageByCondition(pageable,nameWhereItem,ageEmpty);Assert.Single(bindResult5.Data);varbindResult6=customerRepository.GetCustomerByPageByCondition(pageable,nameEmpty,ageEmpty);Use the work unit interface IUnitOfWork to implement database transactions.While injecting the custom storage interface, it also injects the IUnitOfWork interface corresponding to the database unit.Here it is IUnitOfWork1.The usage is as follows
[ApiController][Route("[controller]/[action]")]publicclassCustomerController:Controller{privatereadonlyICustomerRepositorycustomerRepository;privatereadonlyIUnitOfWork1unitOfWork1;publicCustomerController(ICustomerRepositorycustomerRepository,IUnitOfWork1unitOfWork1){this.customerRepository=customerRepository;_this.unitOfWork1 =unitOfWork1;}[HttpGet]publicasyncTask<IActionResult>UnitOfWorkTest(){try{// start transactionunitOfWork1.BeginTransaction();varcustomer=newCustomer(){Name="testCustomer"};awaitcustomerRepository.InsertAsync(customer);varcustomer2=newCustomer(){Name="testCustomer2"};varcustomer3=newCustomer(){Name="testCustomer3"};varcustomerList=newList<Customer>(){customer2,customer3};awaitcustomerRepository.InsertAsync(customerList);awaitcustomerRepository.DeleteAsync(it =>it.Age==0);//commit transactionunitOfWork1.Commit();}catch(Exceptione){// rollback transactionunitOfWork1.RollBack();throw;}returnContent("ok");}}5.6.1 Define an interface inherited from IBaseRepository , and define your own methods in the interface
Note that there is no need to add AutoRepository annotations to this interface at this time
publicinterfaceICustomCustomerRepository:IBaseRepository<Customer>{Task<List<Customer>>GetCustomersAsync(stringname);Task<Customer>GetCustomerAsync(stringname);Task<int>UpdateCustomerNameAsync(stringoldName,stringnewName);Task<int>CustomQueryAsync();}5.6.2 Add an implementation class, inherited from the CustomBaseRepository class and the custom ICustomCustomerRepository interface, and add the AutoRegister annotation to the implementation class.
the AutoRegister annotation are the type of the custom interface ICustomCustomerRepository and the declaration cycle ServiceLifetime of the service (the cycle defaults to the scope level).The purpose of adding the AutoRegister annotation is to allow the framework to automatically register the custom interface and the corresponding custom class in the IOC container.It can be directly injected and used.CustomBaseRepository comes with Execute, QueryFirstOrDefault and QueryList and other methods.If you want to contact the lower-level dbConnection for query, refer to the CustomQueryAsync method below.First, OpenDb () opens the database connection, and then query.The query must contain On the database unit information this.databaseUnit and transaction:dbTransaction these two parameters, CloseDb () closes the database connection after the query is completed;
[AutoRegister(typeof(ICustomCustomerRepository))]publicclassCustomCustomerRepository:CustomBaseRepository<Customer>,ICustomCustomerRepository{publicCustomCustomerRepository(IUnitOfWork1uow):base(uow,uow.DbFactory){}publicasyncTask<Customer>GetCustomerAsync(stringname){varresult=awaitthis.QueryFirstOrDefaultAsync<Customer>("select * from customer where name=@name",new{name});returnresult;}publicasyncTask<List<Customer>>GetCustomersAsync(stringname){varresult=awaitthis.QueryListAsync<Customer>("select * from customer where name=@name",new{name});returnresult;}publicasyncTask<int>UpdateCustomerNameAsync(stringoldName,stringnewName){varresult=awaitthis.ExecuteAsync("update customer set name=@newName where name=@oldName",new{newName,oldName});returnresult;}publicasyncTask<int>CustomQueryAsync() _
{this.OpenDb();vargrid=awaitthis.dbConnection.QueryMultipleAsync(this.databaseUnit,"select id from customer",transaction:dbTransaction);varids=grid.Read<int>().ToList();this.CloseDb();returnids[0];}}[ApiController][Route("[controller]/[action]")]publicclassCustomerController:Controller{privatereadonlyICustomerRepositorycustomerRepository;privatereadonlyIUnitOfWork1unitOfWork1;privatereadonlyICustomCustomerRepositorycustomCustomerRepository;publicCustomerController(ICustomerRepositorycustomerRepository,IUnitOfWork1unitOfWork1,ICustomCustomerRepositorycustomCustomerRepository){this.customerRepository=customerRepository;_this.unitOfWork1 =unitOfWork1;this.customCustomerRepository=customCustomerRepository;_}[HttpGet]publicasyncTask<IActionResult>CustomClass(){try{// start transactionunitOfWork1.BeginTransaction();varcustomer=newCustomer(){Name="testCustomer"};awaitcustomerRepository.InsertAsync(customer);varcustomer2=newCustomer(){Name="testCustomer2"};varcustomer3=newCustomer(){Name="testCustomer3"};varcustomerList=newList<Customer>(){customer2,customer3};awaitcustomerRepository.InsertAsync(customerList);varresult1=awaitcustomCustomerRepository.GetCustomerAsync("testCustomer");varresult2=awaitcustomCustomerRepository.CustomQueryAsync();varresult3=awaitcustomCustomerRepository.UpdateCustomerNameAsync("testCustomer3","testCustomer33");varresult4=awaitcustomCustomerRepository.GetCustomersAsync("testCustomer");//commit transactionunitOfWork1.Commit();}catch(Exceptione){// rollback transactionunitOfWork1.RollBack();throw;}returnContent("ok");}}6 Automatically generate entity classes based on database tables, or automatically generate ddl statements for database tables based on entity classes
Namespaces in sqlserver are schemas, namespaces in oracle are schemas, and namespaces in sqlite and mysql are databases. If you want to define tables under different namespaces, just add [Table("CustomerWithSchema", Schema ="test")] annotation.
[Table("CustomerWithSchema",Schema="test")]publicclassCustomerWithSchema{publicstringName{set;get;}publicintAge{set;get;}}For usage, please refer to the previous 3 examples.Here, mysql is taken as an example.The generated sql is as follows:
CREATETABLEtestSummerboot.`Customer` (
`Name`textNULL,
`Age`intNOT NULL,
`CustomerNo`textNULL,
`TotalConsumptionAmount`decimal(18,2) NOT NULL,
`Id`intNOT NULL AUTO_INCREMENT,
`LastUpdateOn` datetime NULL,
`LastUpdateBy`textNULL,
`CreateOn` datetime NULL,
`CreateBy`textNULL,
`Active`intNULL,
PRIMARY KEY (`Id`)
)
ALTERTABLE testSummerboot.`Customer` COMMENT ='Member'ALTERTABLE testSummerboot.`Customer` MODIFY `Name`textNULL COMMENT 'Name'ALTERTABLE testSummerboot.`Customer` MODIFY `Age`intNOT NULL COMMENT 'age'ALTERTABLE testSummerboot.`Customer` MODIFY `CustomerNo`textNULL COMMENT 'member number'ALTERTABLE testSummerboot.`Customer` MODIFY `TotalConsumptionAmount`decimal(18,2) NOT NULL COMMENT 'total consumption amount'ALTERTABLE testSummerboot.`Customer` MODIFY `Id`intNOT NULL AUTO_INCREMENT COMMENT 'Id'ALTERTABLE testSummerboot.`Customer` MODIFY `LastUpdateOn` datetime NULL COMMENT 'last update time'ALTERTABLE testSummerboot.`Customer` MODIFY `LastUpdateBy`textNULL COMMENT 'last updated by'ALTERTABLE testSummerboot.`Customer` MODIFY `CreateOn` datetime NULL COMMENT 'Creation time'ALTERTABLE testSummerboot.`Customer` MODIFY `CreateBy`textNULL COMMENT 'Created by'ALTERTABLE testSummerboot.`Customer` MODIFY `Active`intNULL COMMENT 'Valid or not'The generated sql is sql for adding new fields or sql for updating comments .In order to avoid data loss, there will be no sql for deleting fields.It is more convenient for daily use to divide the operation of generating sql and executing sql into two parts.You can quickly get the sql to be executed and check it.After confirming that there is no problem, you can save it and leave it to the dba for review when the application is officially released
The column annotation is uniformly used here, such as [Column("Age",TypeName ="float")]
[Description("Member")]publicclassCustomer:BaseEntity{[Description("Name")]publicstringName{set;get;}[Description("age")][Column("Age",TypeName="float")]publicintAge{set;get;}[Description("Membership Number")]publicstringCustomerNo{set;get;}[Description("total consumption amount")]publicdecimalTotalConsumptionAmount{set;get;}}The generated sql is as follows
CREATETABLEtestSummerboot.`Customer` (
`Name`textNULL,
`Age` float NOT NULL,
`CustomerNo`textNULL,
`TotalConsumptionAmount`decimal(18,2) NOT NULL,
`Id`intNOT NULL AUTO_INCREMENT,
`LastUpdateOn` datetime NULL,
`LastUpdateBy`textNULL,
`CreateOn` datetime NULL,
`CreateBy`textNULL,
`Active`intNULL,
PRIMARY KEY (`Id`)
)
Inject the database unit corresponding to the IDbGenerator interface, here is the IDbGenerator1 interface, call the GenerateCsharpClass method to generate the text of the c# class, the parameters are the collection of database table names and the namespace of the generated entity class, the code is as follows
[ApiController][Route("[controller]/[action]")]publicclassGenerateTableController:Controller{privatereadonlyIDbGenerator1dbGenerator1;publicGenerateTableController(IDbGenerator1dbGenerator1){this.dbGenerator1=dbGenerator1;}[HttpGet]publicasyncTask<IActionResult>GenerateClass(){vargenerateClasses=dbGenerator1.GenerateCsharpClass(newList<string>(){"Customer"},"Test.Model");returnContent("ok");}[HttpGet]publicIActionResultIndex(){varresults=dbGenerator1.GenerateSql(newList<Type>(){typeof(Customer)});foreach(varresultinresults){dbGenerator1.ExecuteGenerateSql(result);}returnContent("ok");}}Call the GenerateClass interface, the generated c# entity class is as follows , just create a new class file and paste the text into it
usingSystem;usingSystem.ComponentModel.DataAnnotations;usingSystem.ComponentModel.DataAnnotations.Schema;namespaceTest.Model{///<summary>///member///</summary>[Table("Customer")]publicclassCustomer{///<summary>///Name///</summary>[Column("Name")]publicstringName{get;set;}///<summary>///age///</summary>[Column("Age")]publicintAge{get;set;}///<summary>///Member ID///</summary>[Column("CustomerNo")]publicstringCustomerNo{get;set;}///<summary>///total consumption amount///</summary>[Column("TotalConsumptionAmount")]publicdecimalTotalConsumptionAmount{get;set;}///<summary>///Id///</summary>[Key][DatabaseGenerated(DatabaseGeneratedOption.Identity)][Column("Id")]publicintId{get;set;}///<summary>///Last update time///</summary>[Column("LastUpdateOn")]publicDateTime?LastUpdateOn{get;set;}///<summary>///Last updated by///</summary>[Column("LastUpdateBy")]publicstringLastUpdateBy{get;set;}///<summary>///Creation time///</summary>[Column("CreateOn")]publicDateTime?CreateOn{get;set;}///<summary>///founder///</summary>[Column("CreateBy")]publicstringCreateBy{get;set;}///<summary>///is it effective///</summary>[Column("Active")]publicint?Active{get;set;}}}The bottom layer of feign is based on httpClient .
services.AddSummerBoot();services.AddSummerBootFeign();Define an interface, and add FeignClient annotation to the interface.In the FeignClient annotation, you can customize the public part of the http interface url - url (the url requested by the entire interface is composed of the url in FeignClient plus the path in the method), whether to ignore the remote interface https certificate verification - IsIgnoreHttpsCertificateValidate , interface timeout - Timeout (unit s), custom interceptor - InterceptorType .
[FeignClient(Url="http://localhost:5001/home",IsIgnoreHttpsCertificateValidate=true,InterceptorType=typeof(MyRequestInterceptor),Timeout=100)]publicinterfaceITestFeign{[GetMapping("/query")]Task<Test>TestQuery([Query]Testtt);}At the same time, the url and path can be obtained by reading the configuration, and the configuration items are wrapped by ${}.The json of the configuration is as follows:
{
"configurationTest": {
"url":"http://localhost:5001/home",
"path":"/query"
}
}The interface is as follows:
[FeignClient(Url="${ configurationTest:url }")]publicinterfaceITestFeignWithConfiguration{[GetMapping("${ configurationTest:path }")]Task<Test>TestQuery([Query]Testtt);}Sometimes we only want to use the path in the method as a complete url to initiate an http request, then we can define the interface as follows, set UsePathAsUrl to true (the default is false)
[FeignClient(Url="http://localhost:5001/home")]publicinterfaceITestFeign{[PostMapping("http://localhost:5001/home/ json",UsePathAsUrl=true)]TaskTestUsePathAsUrl([Body(BodySerializationKind.Json)]Testtt);}You can choose to add Headers annotation on the interface, which means that all http requests under this interface will carry the request header in the annotation.The parameter of Headers is a variable-length string type parameter.At the same time, Headers can also be added to the method, which means that when the method is called, the request header will be added.The Headers parameter on the interface can be superimposed with the Headers parameter on the method.At the same time Variables can be used in headers, and the placeholders for variables are {{}}, such as
[FeignClient(Url="http://localhost:5001/home",IsIgnoreHttpsCertificateValidate=true,InterceptorType=typeof(MyRequestInterceptor),Timeout=100)][Headers("a:a","b:b")]publicinterfaceITestFeign{[GetMapping("/ testGet")]Task<Test>TestAsync();[GetMapping("/ testGetWithHeaders")][Headers("c:c")]Task<Test>TestWithHeadersAsync();//header replacement[Headers("a:{ {methodName}}")][PostMapping("/ abc")]Task<Test>TestHeaderAsync(stringmethodName);}awaitTestFeign.TestAsync()>>>get, http://localhost:5001/home/testGet, headers are"a:a"and"b:b"awaitTestFeign.TestWithHeadersAsync()>>>get, http://localhost:5001/home/testGetWithHeaders, headers are"a:a","b:b"and"c:c"awaitTestFeign.TestHeaderAsync("abc");>>>post, http://localhost:5001/home/abc, and the request header is"a:abc"Custom interceptors are effective for all methods under the interface.The application scenario of the interceptor is mainly to do some operations before the request.For example, before requesting a third-party business interface, you need to log in to the third-party system first, then you can use it in the interceptor First request the third-party login interface, after obtaining the credentials, put them in the header, the interceptor needs to implement the IRequestInterceptor interface, the example is as follows
//loginFeign client for login[FeignClient(Url="http://localhost:5001/login",IsIgnoreHttpsCertificateValidate=true,Timeout=100)]publicinterfaceILoginFeign{[PostMapping("/login")]Task<LoginResultDto>LoginAsync([Body()]LoginDtologinDto);}//Then customize the login interceptorpublicclassLoginInterceptor:IRequestInterceptor{privatereadonlyILoginFeignloginFeign;privatereadonlyIConfigurationconfiguration;publicLoginInterceptor(ILoginFeignloginFeign,IConfigurationconfiguration){this.loginFeign=loginFeign;this.configuration=configuration;}publicasyncTaskApplyAsync(RequestTemplaterequestTemplate){varusername=configuration.GetSection("username").Value;varpassword=configuration.GetSection("password").Value;varloginResultDto=awaitthis.loginFeign.LoginAsync(newLoginDto(){Name=username,Password=password});if(loginResultDto!=null){requestTemplate.Headers.Add("Authorization",newList<string>(){"Bearer"+loginResultDto.Token});}awaitTask.CompletedTask;}}//testFegn client that accesses the business interface , and define the interceptor on the client as loginInterceptor[FeignClient(Url="http://localhost:5001/home",IsIgnoreHttpsCertificateValidate=true,InterceptorType=typeof(LoginInterceptor),Timeout=100)]publicinterfaceITestFeign{[GetMapping("/ testGet")]Task<Test>TestAsync();}awaitTestFeign.TestAsync();>>>get to http://localhost:5001/home/testGet, header is"Authorization:Bearer abc"IgnoreInterceptor to the method , then the request initiated by this method will ignore the interceptor, such as
//testFegn client that accesses the business interface , and define the interceptor on the client as loginInterceptor[FeignClient(Url="http://localhost:5001/home",IsIgnoreHttpsCertificateValidate=true,InterceptorType=typeof(LoginInterceptor),Timeout=100)]publicinterfaceITestFeign{[GetMapping("/ testGet")][IgnoreInterceptor]Task<Test>TestAsync();}awaitTestFeign.TestAsync();>>>get to http://localhost:5001/home/testGet, no headerYou can also add a global interceptor.When registering AddSummerBootFeign , the calling method is as follows:
services.AddSummerBootFeign(it =>{it.SetGlobalInterceptor(typeof(GlobalInterceptor));});Each method should add annotations to represent the type of request and the url to be accessed .There are 4 built-in annotations, GetMapping , PostMapping , PutMapping , DeleteMapping , and the return value of the method must be Task<> type
[FeignClient(Url="http://localhost:5001/home",IsIgnoreHttpsCertificateValidate=true,InterceptorType=typeof(MyRequestInterceptor),Timeout=100)]publicinterfaceITestFeign{[GetMapping("/ testGet")]Task<Test>TestAsync();[PostMapping("/ testPost")]Task<Test>TestPostAsync();[PutMapping("/ testPut")]Task<Test>TestPutAsync();[DeleteMapping("/ testDelete")]Task<Test>TestDeleteAsync();}If the parameter has no special annotation, or is not a special class, it will be used as a dynamic parameter to participate in the replacement of variables in the url and header (if the parameter is a class, read the attribute value of the class), and the variables in the url and header use placeholder { {}}, if the variable name is inconsistent with the parameter name, you can use the AliasAs annotation (which can be used on parameters or class attributes) to specify an alias, such as
[FeignClient(Url="http://localhost:5001/home",IsIgnoreHttpsCertificateValidate=true,InterceptorType=typeof(MyRequestInterceptor),Timeout=100)]publicinterfaceITestFeign{// url replacement[PostMapping("/{{ methodName }}")]Task<Test>TestAsync(stringmethodName);//header replacement[Headers("a:{ {methodName}}")][PostMapping("/ abc")]Task<Test>TestHeaderAsync(stringmethodName);// AliasAs specifies the alias[Headers("a:{ {methodName}}")][PostMapping("/ abc")]Task<Test>TestAliasAsAsync([AliasAs("methodName")]stringname);}awaitTestFeign.TestAsync("abc");>>>post to http://localhost:5001/home/abcawaitTestFeign.TestAliasAsAsync("abc");>>>post, http://localhost:5001/home/abcawaitTestFeign.TestHeaderAsync("abc");>>>post, http://localhost:5001/home/abc, and the request header is"a:abc"After the query annotation is added to the parameter, the parameter value will be added to the url in the form of key1=value1&key2=value2 .
[FeignClient(Url="http://localhost:5001/home",IsIgnoreHttpsCertificateValidate=true,InterceptorType=typeof(MyRequestInterceptor),Timeout=100)]publicinterfaceITestFeign{[GetMapping("/ TestQuery")]Task<Test>TestQuery([Query]stringname);[GetMapping("/ TestQueryWithClass")]Task<Test>TestQueryWithClass([Query]Testtt);}awaitTestFeign.TestQuery("abc");>>>get, http://localhost:5001/home/TestQuery?name=abcawaitTestFeign.TestQueryWithClass(newTest(){Name="abc",Age=3});>>>get, http://localhost:5001/home/TestQueryWithClass?Name=abc&Age=35.2.1.1 The Query annotation is used with the Embedded annotation, and the Embedded annotation class can be added as a parameter as a whole
publicclassEmbeddedTest2{publicintAge{get;set;}}publicclassEmbeddedTest3{publicstringName{get;set;}[Embedded]publicEmbeddedTest2Test{get;set;}}[FeignClient(Url="http://localhost:5001/home")]publicinterfaceITestFeign{///<summary>/// Test the Embedded annotation, indicating whether the parameter is embedded, the test is embedded///</summary>///<param name ="tt"></param>///<returns></returns>[GetMapping("/ testEmbedded")]Task<string>TestEmbedded([Query]EmbeddedTest3tt);}awaittestFeign.TestEmbedded(newEmbeddedTest3(){Name="sb",Test=newEmbeddedTest2(){Age=3}});>>>get, http://localhost:5001/home/testEmbedded?Name=sb&Test=%7B%22Age%22%3A%223%22%7DIf there is no Embedded annotation, the request becomes
>>>get, http://localhost:5001/home/testEmbedded?Name=sb&Age=3It is equivalent to simulating the form submission in html.The parameter value will be URL-encoded and added to the payload (body) in the form of key1=value1&key2=value2.
[FeignClient(Url="http://localhost:5001/home",IsIgnoreHttpsCertificateValidate=true,InterceptorType=typeof(MyRequestInterceptor),Timeout=100)]publicinterfaceITestFeign{[PostMapping("/form")]Task<Test>TestForm([Body(BodySerializationKind.Form)]Testtt);}awaitTestFeign.TestForm(newTest(){Name="abc",Age=3});>>>post, http://localhost:5001/home/form, and the value in the body is Name= abc&Age =3That is, submit it in the form of application/ json , and the parameter value will be serialized by json and added to the payload (body).Similarly, if the field in the class has an alias, you can also use the AliasAs annotation.
[FeignClient(Url="http://localhost:5001/home",IsIgnoreHttpsCertificateValidate=true,InterceptorType=typeof(MyRequestInterceptor),Timeout=100)]publicinterfaceITestFeign{[PostMapping("/ json")]Task<Test>TestJson([Body(BodySerializationKind.Json)]Testtt);}awaitTestFeign.TestJson(newTest(){Name="abc",Age=3});>>>post, http://localhost:5001/home/json, and the value in the body is {"Name":"abc","Age":3}5.2.4 Use the special class HeaderCollection as a method parameter to add request headers in batches
[FeignClient(Url="http://localhost:5001/home",IsIgnoreHttpsCertificateValidate=true,InterceptorType=typeof(MyRequestInterceptor),Timeout=100)]publicinterfaceITestFeign{[PostMapping("/ json")]Task<Test>TestJson([Body(BodySerializationKind.Json)]Testtt,HeaderCollectionheaders);}varheaderCollection=newHeaderCollection(){newKeyValuePair<string,string>("a","a"),newKeyValuePair<string,string>("b","b")};awaitTestFeign.TestJson(newTest(){Name="abc",Age=3},headerCollection);>>>post, http://localhost:5001/home/json, and the value in the body is {"Name":"abc","Age":3}, and the header is"a:a"and"b: b"5.2.5 Use the special class BasicAuthorization as a method parameter to add the Authorization request header for basic authentication
[FeignClient(Url="http://localhost:5001/home",IsIgnoreHttpsCertificateValidate=true,InterceptorType=typeof(MyRequestInterceptor),Timeout=100)]publicinterfaceITestFeign{[GetMapping("/ testBasicAuthorization")]Task<Test>TestBasicAuthorization(BasicAuthorizationbasicAuthorization);}varusername="abc";varpassword="123";awaitTestFeign.TestBasicAuthorization(newBasicAuthorization(username,password));>>>get, http://localhost:5001/home/testBasicAuthorization, header is"Authorization: Basic YWJjOjEyMw =="5.2.6 Use the special class MultipartItem as a method parameter, and mark the Multipart annotation on the method to upload the attachment
[FeignClient(Url="http://localhost:5001/home",IsIgnoreHttpsCertificateValidate=true,InterceptorType=typeof(MyRequestInterceptor),Timeout=100)]publicinterfaceITestFeign{//Only upload the file[Multipart][PostMapping("/multipart")]Task<Test>MultipartTest(MultipartItemitem);//While uploading the attachment, you can also attach parameters[Multipart][PostMapping("/multipart")]Task<Test>MultipartTest([Body(BodySerializationKind.Form)]Testtt,MultipartItemitem);}// Only upload the filevarbasePath=Path.Combine(AppContext.BaseDirectory,"123.txt");varname="file";varfileName="123.txt";//Method 1, use byteArrayvarbyteArray=File.ReadAllBytes(basePath);varresult=awaittestFeign.MultipartTest(newMultipartItem(byteArray,name,fileName));//Method 2, use streamvarfileStream=newFileInfo(basePath).OpenRead();varresult=awaittestFeign.MultipartTest(newMultipartItem(fileStream,name,fileName));//Method 3, use fileInfovarresult=awaittestFeign.MultipartTest(newMultipartItem(newFileInfo(basePath),name,fileName));>>>post, http://localhost:5001/home/multipart, with attachments in the body//While uploading the attachment, you can also attach parametersvarbasePath= Path.Combine(AppContext.BaseDirectory,"123.txt");var name ="file";varfileName="123.txt";//Method 1, use byteArrayvarbyteArray=File.ReadAllBytes(basePath);varresult=awaittestFeign.MultipartTest(newTest(){Name="sb",Age=3},newMultipartItem(byteArray,name,fileName));//Method 2, use streamvarfileStream=newFileInfo(basePath).OpenRead();varresult=awaittestFeign.MultipartTest(newTest(){Name="sb",Age=3},newMultipartItem(fileStream,name,fileName));//Method 3, use fileInfovarresult=awaittestFeign.MultipartTest(newTest(){Name="sb",Age=3},newMultipartItem(newFileInfo(basePath),name,fileName));>>>post, http://localhost:5001/home/multipart, and the value in the body is Name= abc&Age =3, and it has attachments5.2.7 Use the class Stream as the return type of the method to receive streaming data, such as downloading files.
[FeignClient(Url="http://localhost:5001/home",IsIgnoreHttpsCertificateValidate=true,InterceptorType=typeof(MyRequestInterceptor),Timeout=100)]publicinterfaceITestFeign{[GetMapping("/ downLoadWithStream")]Task<Stream>TestDownLoadWithStream();}usingvarstreamResult=awaittestFeign.TestDownLoadStream();usingvarnewfile=newFileInfo("D:\\123.txt").OpenWrite();streamResult.CopyTo(newfile);>>>get, http://localhost:5001/home/downLoadWithStream, the return value is streaming data, and then it can be saved as a file.5.2.8 Use the class HttpResponseMessage as the return type of the method to get the most original response message.
[FeignClient(Url="http://localhost:5001/home",IsIgnoreHttpsCertificateValidate=true,InterceptorType=typeof(MyRequestInterceptor),Timeout=100)]publicinterfaceITestFeign{[GetMapping("/test")]Task<HttpResponseMessage>Test();}varrawResult=awaittestFeign.Test();>>>get, http://localhost:5001/home/Test, the return value is the original return data of httpclient .[FeignClient(Url="http://localhost:5001/home",IsIgnoreHttpsCertificateValidate=true,InterceptorType=typeof(MyRequestInterceptor),Timeout=100)]publicinterfaceITestFeign{[GetMapping("/test")]TaskTest();}awaittestFeign.Test();>>>get, http://localhost:5001/home/Test, ignore the return valuein appsettings.json / appsettings.Development.json configuration file
"nacos": {
//Username and password are required only when nacos enables authentication, otherwise they are empty."username": "",
"password": "",
//--------Using nacos , serviceAddress and namespaceId are required------// nacos service address, such as http://172.16.189.242:8848"serviceAddress":"http://172.16.189.242:8848/",
// Namespace id, such as 832e754e-e845-47db-8acc-46ae3819b638 or public"namespaceId":"dfd8de72-e5ec-4595-91d4-49382f500edf",
//--------If you just access the microservices in nacos , you only need to configure lbStrategy , defaultNacosGroupName and defaultNacosNamespaceId are optional ------//Client load balancing algorithm, there are multiple instances under one service, lbStrategy is used to select instances under the service, the default is Random (random), you can also choose WeightRandom (random after being weighted according to the service weight)"lbStrategy":"Random",
// defaultNacosGroupName , optional, is the default value of NacosGroupName in the FeignClient annotation , if it is empty, it defaults to DEFAULT_GROUP"defaultNacosGroupName":"",
// defaultNacosNamespaceId , optional, is the default value of NacosNamespaceId in the FeignClient annotation , if it is empty, it defaults to public"defaultNacosNamespaceId":"",
//--------If you need to use the nacos configuration center, ConfigurationOption is required,Allows listening to multiple configurations------"configurationOption": [
{
//Configuration namespaceId"namespaceId": "f3dfa56a-a72c-4035-9612-1f9a8ca6f1d2",
//Configuration grouping"groupName": "DEFAULT_GROUP",
//configured dataId ,"dataId": "def"
},
{
"namespaceId": "public",
//配置的分组"groupName": "DEFAULT_GROUP",
//配置的dataId,"dataId": "abc"
}
],
//-------If you want to register this application as a service instance, all parameters need to be configured --------------//Do you want to register the application as a service instance"registerInstance": true ,
//The name of the service to be registered"serviceName":"test",
//Service group name"groupName":"DEFAULT_GROUP",
//Weight, there are multiple instances under one service, the higher the weight, the greater the probability of accessing the instance, for example, if some instances are located on a server with high configuration, then the weight can be higher, and more traffic will be diverted to the instance, which is the same as the above The parameter lbStrategy is set to WeightRandom for use with"weight": 1 ,
//The external network protocol of this application, http or https"protocol":"http",
//The external port number of this application, such as 5000"port": 5000
}Accessing the nacos configuration center is very simple, just add a line .UseNacosConfiguration () in Program.cs , currently supports json format, xml format and yaml format.
net core3.1 example is as follows
publicstaticIHostBuilderCreateHostBuilder(string[]args)=>Host.CreateDefaultBuilder(args).UseNacosConfiguration().ConfigureWebHostDefaults(webBuilder =>{webBuilder.UseStartup<Startup>().UseUrls("http://*:5001");});The net6 example is as follows
varbuilder=WebApplication.CreateBuilder(args);builder.Host.UseNacosConfiguration();If the current application is registered as a microservice instance, then this step is over, and feign will automatically register the application as a microservice instance according to the configuration in the configuration file.If this application wants to call the microservice interface, please see 6.3.2
services.AddSummerBoot();services.AddSummerBootFeign(it =>{it.AddNacos(Configuration);});Set the name of the microservice ServiceName , the group name NacosGroupName (you can fill in the global default group name in the configuration file nacos:defaultNacosGroupName , if you don’t fill it in, it defaults to DEFAULT_GROUP), the namespace NacosNamespaceId (you can fill in the global default namespace in the configuration file nacos:defaultNacosNamespaceId , If not filled, it defaults to public), and MicroServiceMode is set to true.The url does not need to be configured, and the rest is the same as the normal feign interface.
[FeignClient(ServiceName="test",MicroServiceMode=true,NacosGroupName="DEFAULT_GROUP",NacosNamespaceId="dfd8de72-e5ec-4595-91d4-49382f500edf")]publicinterfaceIFeignService{[GetMapping("/home/index")]Task<string>TestGet();}At the same time , ServiceName , NacosGroupName , and NacosNamespaceId also support reading from configuration files, such as
{
"ServiceName":"test",
"NacosGroupName":"DEFAULT_GROUP",
"NacosNamespaceId":"dfd8de72-e5ec-4595-91d4-49382f500edf"
}[FeignClient(ServiceName="${ ServiceName }",MicroServiceMode=true,NacosGroupName="${ NacosGroupName }",NacosNamespaceId="${ NacosNamespaceId }")]publicinterfaceIFeignService{[GetMapping("/home/index")]Task<string>TestGet();}In the unit of work mode in feign, cookies can be set in the context, so that the interface will automatically bring cookies when the interface initiates an http request in the context.To use the unit of work mode, you need to inject the IFeignUnitOfWork interface, and then operate as follows :
varfeignUnitOfWork=serviceProvider.GetRequiredService<IFeignUnitOfWork>();//Open the contextfeignUnitOfWork.BeginCookie();//add cookiefeignUnitOfWork.AddCookie("http://localhost:5001/home/TestCookieContainer2","abc =1");awaittestFeign.TestCookieContainer2();//end contextfeignUnitOfWork.StopCookie();At the same time, if the interface returns the cookie setting information, the work unit will also save the cookie, and when the interface in the context scope initiates http access, it will automatically bring the cookie information.A typical scenario is that we first After the first interface is logged in, the interface will return the cookie to us.When we visit the subsequent interface, we must bring the cookie returned to us by the first interface.:
varfeignUnitOfWork=serviceProvider.GetRequiredService<IFeignUnitOfWork>();//Open the contextfeignUnitOfWork.BeginCookie();// get cookie after loginawaittestFeign.LoginAsync("sb","123");//Automaticallybring the cookie after login when requesting
awaittestFeign.TestCookieContainer3();//end contextfeignUnitOfWork.StopCookie();The cache is divided into memory cache and redis cache.The memory cache registration method is as follows:
services.AddSummerBoot();services.AddSummerBootCache(it =>it.UseMemory());The redis cache registration method is as follows, connectionString is the redis connection string:
services.AddSummerBoot();services.AddSummerBootCache(it =>{it.UseRedis(connectionString);});The ICache interface mainly has the following methods and the corresponding asynchronous methods
///<summary>/// Absolute time cache, the cache value becomes invalid after a fixed time///</summary>///<typeparam name ="T"></typeparam>///<param name ="key"></param>///<param name ="value"></param>///<param name ="absoluteExpiration"></param>///<returns></returns>boolSetValueWithAbsolute<T>(stringkey,Tvalue,TimeSpanabsoluteExpiration);///<summary>/// Sliding time cache, if there is a hit within the time, the time will continue to be extended, and the cache value will be invalid if it is not hit///</summary>///<typeparam name ="T"></typeparam>///<param name ="key"></param>///<param name ="value"></param>///<param name ="slidingExpiration"></param>///<returns></returns>boolSetValueWithSliding<T>(stringkey,Tvalue,TimeSpanslidingExpiration);///<summary>/// get value///</summary>///<typeparam name ="T"></typeparam>///<param name ="key"></param>///<returns></returns>CacheEntity<T>GetValue<T>(stringkey);///<summary>/// remove value///</summary>///<param name ="key"></param>///<returns></returns>boolRemove(stringkey);varcache=serviceProvider.GetRequiredService<ICache>();//Seta fixed time cache
cache.SetValueWithAbsolute("test","test",TimeSpan.FromSeconds(3));//Set sliding time cachevarcache=serviceProvider.GetRequiredService<ICache>();cache.SetValueWithSliding("test","test",TimeSpan.FromSeconds(3));// get cachevarvalue=cache.GetValue<string>("test");// remove cachecache.Remove("test");Human-friendly design in #SummerBoot function that comes with net core mvc .What if we want to configure the ip and port of the web application in appsettings.json ? Write directly in appsettings.json
{
"urls":"http://localhost:7002;http://localhost:7012"
}
2.The AutoRegister annotation is used to let the framework automatically register the interface and the implementation class of the interface into the IOC container, and mark it on the implementation class.The parameters of the annotation are the type of the custom interface corresponding to this class and the service life cycle ServiceLifetime (the cycle defaults to scope level), the usage is as follows:
publicinterfaceITest{}[AutoRegister(typeof(ITest),ServiceLifetime.Transient)]publicclassTest:ITest{}3.ApiResult interface return value packaging class, including code, msg and data, 3 fields, so that the return value of the entire system is unified and orderly, which is conducive to the unified interception and unified operation of the front end.The way to use it is as follows:
[HttpPost("CreateServerConfigAsync")]publicasyncTask<ApiResult<bool>>CreateServerConfigAsync(ServerConfigDtodto){varresult=awaitserverConfigService.CreateServerConfigAsync(dto);returnApiResult<bool>.Ok(result);}4.Some enhanced operations for net core mvc , including the global error interceptor, and the processing after the interface parameter verification fails, cooperate with ApiResult , so that when the system reports an error, it can also return uniformly.The usage is as follows.First, register this in startUp Service, note that it should be placed after mvc registration:
services.AddControllersWithViews();services.AddSummerBootMvcExtension(it =>{// Whether to enable global error handlingit.UseGlobalExceptionHandle=true;//Whether to enable parameter verification processingit.UseValidateParameterHandle=true;});4.1 The effect of using the global error interceptor We can throw an error directly in the business code, and the global error interceptor will catch the error, and then return it to the front end in a unified format.The business code is as follows:
privatevoidValidateData(EnvConfigDtodto){if(dto==null){thrownewArgumentNullException("Argument cannot be empty");}if(dto.ServerConfigs==null||dto.ServerConfigs.Count==0){thrownewArgumentNullException("There is no server configured in the environment");}}If an error is reported in the business code, the return value is as follows:
{"code":40000,"msg":"Value cannot be null.(Parameter 'There is no server configured in the environment')","data":null}4.2 The effect of processing after the interface parameter verification fails add verification annotations in the parameter dto of the interface , the code is as follows
publicclassEnvConfigDto:BaseEntity{///<summary>/// Environment name///</summary>[Required(AllowEmptyStrings=false,ErrorMessage="Environment name cannot be empty")]publicstringName{get;set;}///<summary>/// The corresponding server in the environment///</summary>[NotMapped]publicList<int>ServerConfigs _ {get;set;}}If the parameter verification fails, the return value is as follows:
{"code":40000,"msg":"Environment name cannot be empty","data":null}5.QueryCondition , the combination of lambda query conditions, solves the pain point of filtering and querying from the front-end .In addition to the basic And and Or methods , a more humanized method is added.Generally, the attributes in the dto passed from the front-end have string types.If they have values, they are added to the query condition, so two methods are specially extracted, including AndIfStringIsNotEmpty (if the string is not empty, the and operation is performed, otherwise the original expression is returned), OrIfStringIsNotEmpty (if the string is not empty, then Perform or operation, otherwise return to the original expression), At the same time , the attributes in dto may also be of nullable type, that is, nullable type, such as int? test represents whether the user fills in a certain filter condition, if hasValue is added to the query condition, so two methods are specially extracted, AndIfNullableHasValue (If the nullable value is not empty, the AND operation is performed, otherwise the original expression is returned), OrIfNullableHasValue (if the nullable value is not empty, the AND operation is performed, otherwise the original expression is returned) usage is as follows:
// dtopublicclassServerConfigPageDto:IPageable{publicintPageNumber{get;set;}publicintPageSize{get;set;}///<summary>/// ip address///</summary>publicstringIp{get;set;}///<summary>/// connection name///</summary>publicstringConnectionName{get;set;}publicint?Test{get;set;}}//conditionvarqueryCondition=QueryCondition.True<ServerConfig>().And(it =>it.Active==1)//If the string is not empty, perform the and operation, otherwise return the original expression.AndIfStringIsNotEmpty(dto.Ip, it =>it.Ip.Contains(dto.Ip))//If the nullable value is not empty, perform the and operation, otherwise return to the original expression.AndIfNullableHasValue(dto.Test, it =>it.Test==dto.Test).AndIfStringIsNotEmpty(dto.ConnectionName, it =>it.ConnectionName.Contains(dto.ConnectionName));varqueryResult=awaitserverConfigRepository.Where(queryCondition).Skip((dto.PageNumber-1)*dto.PageSize).Take(dto.PageSize).ToPageAsync();