领域驱动,Domain Driven Design。
一张桌子有4条腿。程序员可能的第一反应是通过Table
类和Leg
类描述两者之间的关系。一个人戴着一顶帽子,程序员可能的第一反应是一个Person
类有一个Hat
属性。
这似乎是顺其自然的、合乎事理的。但是,大家是否会觉得这是一种Code Driven Implementation
?的确,领域驱动最终会落实到代码层面,但领域驱动的意义在于:在写代码之前,我们先理解领域,理解业务概念、约束、行为、规则等等。
领域驱动似乎传递着*问题第一代码第二*、*市场第一代码第二*、*业务第一代码第二*的理念。程序员不再是拿着锤子到处找钉子的人,而是让自己首先成为一个领域专家,或者成为一个领域专家的倾听者。程序员不仅对代码负责,还对利益相关方负责,对业务负责。程序员关注的焦点不仅在解决问题本身,而是是否真的解决了问题。
当我们谈领域驱动,其实与编程语言、代码、数据库、微服务都无关,而是一种更高层面的设计。
聚合根标记接口
public interface IAggregateRoot{}
什么是聚合根?理解聚它首先要理解聚合Aggregate
。假设有一个银行转账场景,程序员A和领域专家B正在进行对话:
A:我们目前想解决的问题是什么?
B:做一个客户转账的功能。
A:能具体说一下吗?
B: 客户X给客户Y转账100元,X的账户上少了100元,Y的账户上增加100元。我们希望所有的转账记录都可以被追溯。
接着,A和B在白板上进行了一次事件风暴Event Storming
。首先,在列出了影响系统状态的事件。
事件作用在哪里?谁触发事件?触发什么事件?
显然,事件围绕账户而进行。在DDD
中适用聚合Aggregate
这个概念对相关性事件进行逻辑划分。
有两种做法。一种是把聚合看作名词,比如在这里定义Account
类。
public class Account : IAggregateRoot{}
以上的Account
就是聚合,可以被持久化到数据库,我们目前的架构采用的就是这种方式。
另一种做法是把聚合看作动词。其中有两个关键的视角:组成和规则。
聚合组成:
转账的唯一性:每次转账都有其唯一编号。创建TransferNumber
用来生成唯一编号
出账: 从一个账户中扣除转账金额。创建Debit
类用来描述出账
入账: 把转账金额存入某个账户。创建Credit
类用来描述入账
账户:出账和入账都用到了账户。创建AccountNumber
类用来描述账户
聚合规则:
//每次转账唯一编号的封装
public class TranferNumber
{
public string Value {get; private set;}
public Guid EntityId {get; private set;}
public TranferNumber(string value, Guid entityId)
{
Value = value;
EntityId = entityId;
}
}
//出账
public class Debit
{
public decimal Value {get; private set; }
public AccountNumber Account {get; private set; }
public Debit(decimal value, AccountNumber account)
{
Value = value;
Account = account;
value.Must(v => v >=0);//business rule
account.MustNotBeNull();//ensure value object is valid
}
}
//入账
public class Credit
{
public decimal Value {get; private set; }
public AccountNumber Account {get; private set; }
public Credit(decimal value, AccountNumber account)
{
value.Must(v => v >=0);//business rule
account.MustNotBeNull();//ensure value object is valid
Value = value;
Account = account;
}
}
//账号的封装
public class AccountNumber : IEquatable<AccountNumber>
{
public string Number {get; private set;}
public AccountNumber(string number)
{
Number = number;
}
public bool Equals(AccountNumber other) => other !=null && Number == other.Number;
}
public class TransferedRegistered
{
public Guid EntityId {get; set;}
public string TransferNumber {get; set;}
public string DebitAccountNo {get;set;}
public string CreditAccountNo {get;set;}
public DateTimeOffset CreatedOn {get;set;} = DateTimeOffset.Now;
}
//Aggregate Root
public static class TransferAccount
{
public static TransferRegistered Create(TransferNumber number, Debit debit, Credit credit)
{
number.MustNotBeNull();
debit.MustNotBeNull();
credit.MustNotBeNull();
debit.Account.MustNotBeNull(credit.Account);
var ev = new TransferedRegistered();
ev.EntityId = number.EntityId;
ev.TransferNumber = number.Value;
en.Amount = debit.Value;
ev.DebitAccountNo = debit.Account.Number;
ev.CreditAccountNo = credit.Account.Number;
return ev;
}
}
以上TransferAccount
就是聚合,聚合无需持久化到数据库,状态的改变需要持久化到数据库。
领域接口和抽象实现
public interface IEntity{}
public interface IEntity<TKey> : IEntity{}
public abstract class Entity : IEntity
{
//保存所有的事件,针对领域的事件在领域内部调用
private List<IDomainEvent> _domainEvents;
public IReadOnlyCollection<IDomainEvent> DomainEvents => _domainEvents?.AsReadOnly();
public void AddDomainEvent(IDomainEvent eventItem)
{
_domainEvents = _domainEvents ?? new List<IDomainEvent>();
_domainEvents.Add(eventItem);
}
public void RemoveDomainEvent(IDomainEvent eventItem)
{
_domainEvents?.Remove(eventItem);
}
public void ClearDomainEvents()
{
_domainEvents?.Clear();
}
}
领域事件接口和领域事件处理接口
using MediatR;
public interface IDomainEvent : INotification{}
pubic interface IDomainEventHandler<TDomainEvent> : INotificationHandler<TDomainEvent> where TDomainEvent : IDomainEvent{}
值对象: 如果两个值对象的值都一样,这两个值对象可以相互替换
public abstract class ValueObject{}
工作者单元接口
public interface IUnitOfWork : IDisposable
{
Task<int> SaveChangesAsync(CancellationToken cancellationToken = default);
Task<bool> SaveEntitiesAsync(CancellationToken cancellationToken = default);
}
事务接口
public interface ITransaction
{
IDbContextTransaction GetCurrentTransaction();
bool HasActiveTransaction { get; }
Task<IDbContextTransaction> BeginTransactionAsync();
Task CommitTransactionAsync(IDbContextTransaction transaction);
void RollbackTransaction();
}
自定义上下文
public class EFContext : DbContext, IUnitOfWork, ITransaction
{
protected IMediator _mediator;
ICapPublisher _capBus;
public EFContext(DbContextOptions options, IMediator mediator, ICapPublisher capBus) : base(options)
{
_mediator = mediator;
_capBus = capBus;
}
public async Task<bool> SaveEntitiesAsync(CancellationToken cancellationToken = default)
{
var result = await base.SaveChangesAsync(cancellationToken);
await _mediator.DispatchDomainEventsAsync(this);
return true;
}
...
}
仓储接口
public interface IRepository<TEntity> where TEntity : Entity, IAggregateRoot{}
public interface IRepository<TEntity, TKey> : IRepository<TEntity> where TEntity : Entity<TKey>, IAggregateRoot{}
仓储抽象实现
public abstract class Repository<TEntity, TDbContext> : IRepository<TEntity> where TEntity : Entity, IAggregateRoot where TDbContext : EFContext
{
}
管道行为
在管道内提交事务
//在MedatR的IPipelineBehavior基础上注入上下文
public class TransactionBehavior<TDbContext, TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse> where TDbContext : EFContext
{
ILogger _logger;
TDbContext _dbContext;
public TransactionBehavior(TDbContext dbContext, ILogger logger)
{
_dbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
public async Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next)
{
var response = default(TResponse);
var typeName = request.GetGenericTypeName();//object类型的TRequest的扩展方法,获取泛型类型名称
try
{
if (_dbContext.HasActiveTransaction)
{
return await next();
}
var strategy = _dbContext.Database.CreateExecutionStrategy();
await strategy.ExecuteAsync(async () =>
{
Guid transactionId;
using (var transaction = await _dbContext.BeginTransactionAsync())
using (_logger.BeginScope("TransactionContext:{TransactionId}", transaction.TransactionId))
{
_logger.LogInformation("----- 开始事务 {TransactionId} ({@Command})", transaction.TransactionId, typeName, request);
response = await next();
_logger.LogInformation("----- 提交事务 {TransactionId} {CommandName}", transaction.TransactionId, typeName);
await _dbContext.CommitTransactionAsync(transaction);
transactionId = transaction.TransactionId;
}
});
return response;
}
catch (Exception ex)
{
_logger.LogError(ex, "处理事务出错 {CommandName} ({@Command})", typeName, request);
throw;
}
}
}
扩展方法,获取泛型类型名称:
public static class GenericTypeExtensions
{
public static string GetGenericTypeName(this Type type)
{
var typeName = string.Empty;
if (type.IsGenericType)
{
var genericTypes = string.Join(",", type.GetGenericArguments().Select(t => t.Name).ToArray());
typeName = $"{type.Name.Remove(type.Name.IndexOf('`'))}<{genericTypes}>";
}
else
{
typeName = type.Name;
}
return typeName;
}
public static string GetGenericTypeName(this object @object)
{
return @object.GetType().GetGenericTypeName();
}
}
异常处理接口:IknownException
public interface IKnownException
{
string Message { get; }
int ErrorCode { get; }
object[] ErrorData { get; }
}
异常处理接口实现:KnownException
public class KnownException : IKnownException
{
public string Message { get; private set; }
public int ErrorCode { get; private set; }
public object[] ErrorData { get; private set; }
public readonly static IKnownException Unknown = new KnownException { Message = "未知错误", ErrorCode = 9999 };
public static IKnownException FromKnownException(IKnownException exception)
{
return new KnownException { Message = exception.Message, ErrorCode = exception.ErrorCode, ErrorData = exception.ErrorData };
}
}
引用
引用领域抽象层。
聚合
public class Order : Entity<long>, IAggregateRoot
{
public string UserId {get; private set;}
public string UserName {get; private set;}
public int ItemCount {get; private set;}
public Address Address {get; private set;}
public Order (string userId, string userName, int itemCount, Address address)
{
this.UserName = userName;
this.UserId = userId;
this.ItemCount = itemCount;
this.AddDomainEvent(new OrderCreatedDomainEvent(this));
}
public void ChangeAddress(Address address)
{
this.Address = address;
//TODO:这里也可以添加事件
}
}
领域事件
public class OrderCreatedDomainEvent : IDomainEvent
{
public Order Order {get; private set;}
public OrderCreatedDomainEvent(Order order)
{
this.Order = order;
}
}
引用
上下文
public class OrderingContext : EFContext
{
public OrderingContext(DbContextOptions options, IMediator mediator, ICapPublisher capBus) : base(options, mediator, capBus){}
public DbSet<Order> Orders { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder){
modelBuilder.ApplyConfiguration(new OrderEntityTypeConfiguration());
base.OnModelCreating(modelBuilder);
}
}
领域约束
class OrderEntityTypeConfiguration : IEntityTypeConfiguration<Order>
{
public void Configure(EntityTypeBuilder<Order> builder)
{
builder.HasKey(p => p.Id);
builder.ToTable("order");
builder.Property(p => p.UserId).HasMaxLength(20);
builder.Property(p => p.UserName).HasMaxLength(30);
builder.OwnsOne(o => o.Address, a =>
{
a.WithOwner();
a.Property(p => p.City).HasMaxLength(20);
a.Property(p => p.Street).HasMaxLength(50);
a.Property(p => p.ZipCode).HasMaxLength(10);
});
}
}
管道
public class OrderingContextTransactionBehavior<TRequest, TResponse> : TransactionBehavior<OrderingContext, TRequest, TResponse>
{
public OrderingContextTransactionBehavior(OrderingContext dbContext, ILogger<OrderingContextTransactionBehavior<TRequest, TResponse>> logger) : base(dbContext, logger)
{
}
}
引用
IServiceCollection
的扩展领域事件的处理链路
IRequest
public class CreateOrderCommand : IRequest<long>
{
public CreateOrderCommand(int itemCount)
{
ItemCount = itemCount;
}
public long ItemCount {get; private set;}
}
IRequest
传入 [HttpPost]
public async Task<long> CreateOrder([FromBody]CreateorderCommand cmd)
{
return await _medator.Send(cmd, HttpContext.RequestAborted);
}
IRequestHandler
对IRequest
进行处理 public class CreateOrderCommandHandler : IRequestHandler<CreateOrderCommand, long>
{
IOrderRepository _orderRepository;
ICapPublisher _capPublisher;
public CreateOrderCommandHandler(IOrderRepository orderRepository, ICapPublisher capPublisher)
{
_orderRepository = orderRepository;
_capPublisher = capPublisher;
}
public async Task<long> Handle(CreateOrderCommand request, CancellationToken cancellationToken)
{
var address = new Address("wen san lu", "hangzhou", "310000");
var order = new Order("xiaohong1999", "xiaohong", 25, address);
_orderRepository.Add(order);
await _orderRepository.UnitOfWork.SaveEntitiesAsync(cancellationToken);
return order.Id;
}
}
Order
的同时添加了领域事件 public class Order : Entity<long>, IAggregateRoot
{
public Order(string userId, string userName, int itemCount, Address address)
{
this.UserId = userId;
this.UserName = userName;
this.Address = address;
this.ItemCount = itemCount;
this.AddDomainEvent(new OrderCreatedDomainEvent(this));//添加领域事件
}
}
Entity
抽象基类定义了对事件的处理 public abstract class Entity : IEntity
{
private List<IDomainEvent> _domainEvents;
public IReadOnlyCollection<IDomainEvent> DomainEvents => _domainEvents?.AsReadOnly();
public void AddDomainEvent(IDomainEvent eventItem)
{
_domainEvents = _domainEvents ?? new List<IDomainEvent>();
_domainEvents.Add(eventItem);
}
public void RemoveDomainEvent(IDomainEvent eventItem)
{
_domainEvents?.Remove(eventItem);
}
public void ClearDomainEvents()
{
_domainEvents?.Clear();
}
}
基础设施抽象层的EFContext
在把数据持久化到数据库的同时会发布领域事件
public class EFContext : DbContext, IUnitOfWork, ITransaction
{
public async Task<bool> SaveEntitiesAsync(CancellationToken cancellationToken = default)
{
var result = await base.SaveChangesAsync(cancellationToken);
await _mediator.DispatchDomainEventsAsync(this);//把事件发布出去
return true;
}
}
基础设施抽象层定义了对MedatR
的扩展方法,本质上是把上下文所有领域的事件发布出去。
public static async Task DispatchDomainEventsAsync(this IMediator mediator, DbContext ctx)
{
var domainEntities = ctx.ChangeTracker
.Entries<Entity>()
.Where(x => x.Entity.DomainEvents != null && x.Entity.DomainEvents.Any());
var domainEvents = domainEntities
.SelectMany(x => x.Entity.DomainEvents)
.ToList();
domainEntities.ToList()
.ForEach(entity => entity.Entity.ClearDomainEvents());
foreach (var domainEvent in domainEvents)
await mediator.Publish(domainEvent);
}
public class OrderCreatedDomainEventHandler : IDomainEventHandler<OrderCreatedDomainEvent>
{
ICapPublisher _capPublisher;
public OrderCreatedDomainEventHandler(ICapPublisher capPublisher)
{
_capPublisher = capPublisher;
}
public async Task Handle(OrderCreatedDomainEvent notification, CancellationToken cancellationToken)
{
await _capPublisher.PublishAsync("OrderCreated", new OrderCreatedIntegrationEvent(notification.Order.Id));
}
}
这样,领域事件就被发布到Event Bus
上,与外界产生了交互,很有可能与外界的另外一个微服务产生交互。
在各个微服务的数据库中一般有2张表,一张用来记录发布事件,另一张用来记录订阅事件。这些事件会随着领域事件的发布持久化到数据库,CAP可以做到业务逻辑和发布订阅事件的一致性。
在应用层或接口层,定义一个集成事件。
public class OrderCreatedIntegrationEvent
{
public OrderCreatedIntegrationEvent(long orderId) => OrderId = orderId;
public long OrderId { get; }
}
public class OrderPaymentSucceededIntegrationEvent
{
public OrderPaymentSucceededIntegrationEvent(long orderId) => OrderId = orderId;
public long OrderId { get; }
}
在应用层或接口层,定义IDomainEventHandler<IDomainEvent>
发布集成事件。
public class OrderCreatedDomainEventHandler : IDomainEventHandler<OrderCreatedDomainEvent>
{
//中国开源社区,用于消息的发布和订阅,可以发布到RabbitMQ或者Kafa等
ICapPublisher _capPublisher;
public OrderCreatedDomainEventHandler(ICapPublisher capPublisher)
{
_capPublisher = capPublisher;
}
public async Task Handle(OrderCreatedDomainEvent notification, CancellationToken cancellationToken)
{
await _capPublisher.PublishAsync("OrderCreated", new OrderCreatedIntegrationEvent(notification.Order.Id));
}
}
在应用层或接口层,定义订阅接口。
public interface ISubscriberService
{
void OrderPaymentSucceeded(OrderPaymentSucceededIntegrationEvent @event);
}
订阅接口实现。
public class SubscriberService : ISubscriberService, ICapSubscribe
{
IMediator _mediator;
public SubscriberService(IMediator mediator)
{
_mediator = mediator;
}
[CapSubscribe("OrderPaymentSucceeded")]
public void OrderPaymentSucceeded(OrderPaymentSucceededIntegrationEvent @event)
{
//Do SomeThing
}
[CapSubscribe("OrderCreated")]
public void OrderCreated(OrderCreatedIntegrationEvent @event)
{
//Do SomeThing
}
}
安装RabbitMQ
。
CAP的事件如何与业务逻辑放在同一个事务呢?在基础设施抽象层的EFContext
中定义如下:
public class EFContext : DbContext, IUnitOfWork, ITransaction
{
protected IMediator _mediator;
ICapPublisher _capBus;
public EFContext(DbContextOptions options, IMediator mediator, ICapPublisher capBus) : base(options)
{
_mediator = mediator;
_capBus = capBus;
}
......
public Task<IDbContextTransaction> BeginTransactionAsync()
{
if (_currentTransaction != null) return null;
_currentTransaction = Database.BeginTransaction(_capBus, autoCommit: false);
return Task.FromResult(_currentTransaction);
}
......
}
这样保证了CAP事件的提交回滚与业务事务的提交回滚保持一致。
在应用层或接口层,注册CAP使用RabbitMQ
。
public static IServiceCollection AddEventBus(this IServiceCollection services, IConfiguration configuration)
{
services.AddTransient<ISubscriberService, SubscriberService>();//有关CAP的接口和实现
services.AddCap(options =>
{
options.UseEntityFramework<OrderingContext>();
options.UseRabbitMQ(options =>
{
configuration.GetSection("RabbitMQ").Bind(options);
});
//options.UseDashboard();
});
return services;
}
在appsettings.json
中:
"Mysql": "server=localhost;port=3306;user id=root;password=123456;database=geektime;charset=utf8mb4;ConnectionReset=false;",
"RabbitMQ": {
"HostName": "localhost",
"UserName": "guest",
"Password": "guest",
"VirtualHost": "/",//将RabbitMQ空间划分为不同的空间,每个空间可以被认为是一个租户。相同的VirtualHost可以被认为是一个集群
"ExchangeName": "queue"
}
在Startup.cs
中注册:
services.AddEventBus(Configuration);
gRPC
内部服务间的通讯利器gRPC
是一个远程调用框架,由Google
公司发起并开源。通过gRPC
让我们可以像调用本地类一样调用远程的服务。提供几乎所有语言的实现。基于HTTP/2,开放协议,受到广泛支持,易于实现和集成。默认使用Protocol Buffers
序列化,性能较于RESTful Json
好很多。工具链成熟,代码生成便捷,开箱即用。支持双向流式的请求和响应,对批处理、低延时场景友好。gRPC
使用自制证书,使用非加密的HTTP2
。
.NET
提供基于HttpClient
的原生框架实现。提供原生的ASP.NET Core
集成库。提供完整的代码生成工具。Visual Studio
和Visual Studio Code
提供proto
文件的智能提示。可以通过命令行工具创建服务。
服务端引用包:
Grpc.AspNetCore
客户端核心包:
Google.Protobuf
Grpc.Net.Client
Grpc.Net.ClientFactory
Grpc.Tools
:提供命令行工具使用的包,用来基于protocol
文件生成服务代码。.proto
文件定义包、库名,定义服务service
,定义输入输出模型message
。然后借助Grpc.Tools
生成服务端和客户端代码。gRPC
异常处理引用包:
Grpc.Core.RpcException
Grpc.Core.Interceptors.Interceptor
服务端
order.proto
syntax = "proto3";//协议版本
option csharp_namespace="GrpcServices";//命名空间
package GrpcServices;
service OrderGrpc {//服务
rpc CreateOrder(CreateOrderCommand) returns (CreateOrderResult);
}
message CreateOrderCommand { //输入,字段的顺序决定了序列化顺序
string buyerId = 1;
int32 productId = 2;
double unitPrice = 3;
double discount = 4;
int32 units = 5;
}
message CreateOrderResult {//响应
int32 orderId = 1;
}
会自动生成Order
和OrderGrpc
两个类。
OrderService.cs
public class OrderService : OrderGrpc.OrderGrpcBase
{
public override Task<CreateOrderResult> CreateOrder(CreateOrderCommand request, ServerCallContext context)
{
return Task.FromResult(new CreateOrderResult {OrderId=24;});
}
}
Startup.cs
sevices.AddGrpc(options => {
options.EnaleDetailErrors = false;
options.Intercoptions.Add<ExceptionInterceptor>();
});
app.UseEndpoints(endpoints => {
endpoints.MapGrpcService<OrderService>();
});
appSettings.json
"Kestrel":{
"Endpoints":{
"Http":{
"Url":"http://+:5000"
},
"Https":{
"Url":"http://+:5001"
},
"Http2":{
"Url":"http://+:5002",
"Protocols":"Http2"
}
}
}
客户端
项目文件中引用服务端的order.proto
,这样可以自动生成客户端代码。
<ItemGroup>
<Protobuf Include="order.proto" GrpService="Client" />
</ItemGroup>
Startup.cs
AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport",true);//允许使用不加密的HTTP/2协议
services.AddGrpcClient<OrderGrpc.OrderGrpcClient>(options => {
//options.Address= new Uri("https://localhost:5000");//服务端地址
options.Address= new Uri("https://localhost:5002");//不配置证书使用Grpc
}).ConfigurePrimaryHttpMessageHandler(provider => {
var handler = new SocketHttpHandler();
handler.SslOptions.RemoteCeretificateValidationCallback = (a, b, c, d) => true;
return handler;
});
app.UseEndpoints(endpoints => {
endpoints.MapGet("/", async context => {
OrderGrpcClient service = context.RequestServices.GetService<OrderGrpcClient>();
try
{
var r = service.CreateOrder(new CreateOrderCommand {BuyerId=""});
}
catch(Exception ex)
{
}
});
});
Polly
失败重试和熔断机制组件包:
Polly
Polly.Extensions.Http
Microsoft.Extensions.Http.Polly
Polly
的能力
Polly
的使用步骤
适合失败重试的场景
服务失败时短暂的,可自愈的
服务时幂等的,重复调用不会有副作用
网络闪断
部分服务节点异常
最佳实践
DDOS
攻击BFF
BFF
是指Backend For Frontend
,负责认证授权,负责服务聚合,目标是为前端提供服务。网关和BFF
的职责可以是重叠的。
打造网关
Ocelot
ocelot.json
Ocelot
服务Ocelot
中间件网关和负载均衡的区别
方面 | 网关 | 负载均衡 |
---|---|---|
OSI模型 | 第7层 | 第4层 |
基于url的路由 | 可以 | 不可以 |
基于cookie的路由 | 可以 | 不可以 |
web防火墙 | 可以 | 不可以 |
地域性 | 任意ip | 云服务商下的某个ip |
项目介绍
Ordering.API
"applicationUrl":"https://localhost.5001;http://localhost:5000"
public class TestController : ControllerBase
{
[HttpGet]
public IActionResult Abc()
{
return Content("Ordering.API");
}
}
Mobile.ApiAggregator
"applicationUrl":"https://localhost.5005;http://localhost:5004"
public class TestController : ControllerBase
{
[HttpGet]
public IActionResult Abc()
{
return Content("Mobile.ApiAggregator");
}
}
Mobile.Gateway
网关项目appSettings.json
"Apollo": {
"AppId": "geektime-mobile-gateway",
"Env": "DEV",
"MetaServer": "http://192.168.67.76:8080",
"ConfigServer": [ "http://192.168.67.76:8080" ]
},
"AllowedHosts": "*",
"ReRoutes": [
{
"DownstreamPathTemplate": "/api/{everything}",
"DownstreamScheme": "http",
"DownstreamHostAndPorts": [
{
"Host": "localhost",
"Port": 5004
}
],
"UpstreamPathTemplate": "/mobileAgg/api/{everything}",
"UpstreamHttpMethod": []
},
{
"DownstreamPathTemplate": "/api/{everything}",
"DownstreamScheme": "http",
"DownstreamHostAndPorts": [
{
"Host": "localhost",
"Port": 5000
}
],
"UpstreamPathTemplate": "/mobile/api/{everything}",
"UpstreamHttpMethod": []
}
],
"GlobalConfiguration": {
"RequestIdKey": "OcRequestId",
"AdministrationPath": "/administration"
},
"SecurityKey": "aabbccddffskldjfklajskdlfjlas234234234"
Startup.cs
services.AddOcelot(Configuration);
app.UseOcelot().Wait();
TestController.cs
public IActionResult Abc()
{
return Content("GeekTime.Mobile.Gateway");
}
如何防御
Cookie
存储或传输身份信息AntiforgeryToken
机制GET
作为业务操作的请求方法两种选择
ValidateAntiForgeryToken
AutoValidateAntiforgeryToken
举例
[ValidateAntiForgeryToken]
public IActionResult CreateOrder(string itemId, int count)
{
_logger.LogInformation("创建了订单itemId:{itemId},count:{count}", itemId, count);
return Content("Order Created");
}
防范错误
LoalRedirect
处理重定向:适合重定向仅限于本站的情况举例
public async Task<IActionResult> Login([FromServices]IAntiforgery antiforgery, string name, string password, string returnUrl)
{
HttpContext.Response.Cookies.Append("CSRF-TOKEN", antiforgery.GetTokens(HttpContext).RequestToken, new Microsoft.AspNetCore.Http.CookieOptions { HttpOnly = false });
var identity = new ClaimsIdentity(CookieAuthenticationDefaults.AuthenticationScheme);//一定要声明AuthenticationScheme
identity.AddClaim(new Claim("Name", "小王"));
await this.HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(identity));
if (string.IsNullOrEmpty(returnUrl))
{
return Content("登录成功");
}
try
{
var uri = new Uri(returnUrl);
///uri.Host 根据配置表数据库表数据来验证
return Redirect(returnUrl);
}
catch
{
return Redirect("/");
}
//return Redirect(returnUrl);
}
防范措施
UrlEncoder
,JavaScriptEncoder
HtmlString
和HtmlHelper.Raw
Cookie
设置为HttpOnly
Path
传递带有不受信的字符,使用Query
进行传递举例
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options =>
{
options.LoginPath = "/home/login";
options.Cookie.HttpOnly = true;//这里的设置生效
});
同源
HTTP/HTTPS
)CORS
是什么
HTTP
请求CORS
请求头
Origin
请求源Access-Control-Reqyest-Method
Access-Control-Request-Headers
CORS
响应头
Access-Control-Allow-Origin
Access-Control-Allow-Credentials
Access-Control-Expose-Headers
Access-Control-Max-Age
Access-Control-Allow-Methods
Access-Control-Allow-Headers
默认支持的Expose Headers
Cache-Control
Content-Language
Content-Type
Expires
Last-Modified
Pragma
举例
services.AddCors(options =>
{
options.AddPolicy("api", builder =>
{
builder.WithOrigins("https://localhost:5001").AllowAnyHeader().AllowCredentials().WithExposedHeaders("abc");
builder.SetIsOriginAllowed(orgin => true).AllowCredentials().AllowAnyHeader();
});
});
app.UseCors();
缓存的场景
DNS
缓存缓存策略
缓存位置
要点
Key
的生成策略,表示数据范围业务含义几个问题
内存缓存和分布式缓存的区别