第十六章 电商系统微服务实战(完结,总结以及之前所有内容会发到github)
历史关于.net 的文档 全部上传到Github上了 qxnm
第十六章 电商系统微服务实战
理论学得再多,不如亲手做一个项目。这一章,我将带你从零开始构建一个真实的电商微服务系统。这不是玩具项目,而是考虑了生产环境各种复杂情况的真实案例。
16.1 需求分析与架构设计
16.1.1 真实电商系统的复杂性
很多人以为电商系统就是"商品+购物车+订单",但真实的企业级电商系统要复杂得多:
核心业务流程
graph TD
用户[用户注册/登录] --> 浏览[浏览商品]
浏览 --> 搜索[搜索/筛选]
搜索 --> 详情[商品详情]
详情 --> 加购物车[加入购物车]
加购物车 --> 结算[结算页面]
结算 --> 创建订单[创建订单]
创建订单 --> 支付[支付处理]
支付 --> 库存[库存扣减]
支付 --> 物流[物流发货]
物流 --> 完成[订单完成]
创建订单 --> 优惠券[优惠券校验]
创建订单 --> 会员[会员折扣]
支付 --> 积分[积分抵扣]
支付 --> 营销[营销活动]
非功能性需求
- 高并发:双11期间每秒数万订单
- 高可用:99.99%可用性,任何环节都不能单点故障
- 数据一致性:订单、库存、支付必须一致
- 性能:页面加载<2秒,接口响应<500ms
- 安全:防刷单、防SQL注入、防XSS攻击
16.1.2 领域识别与服务拆分
基于DDD的战略设计,我们识别出以下核心领域:
// 用户限界上下文
namespace UserContext.Domain
{
public class User : AggregateRoot<UserId>
{
public string Email { get; private set; }
public string PasswordHash { get; private set; }
public UserProfile Profile { get; private set; }
public List<Address> Addresses { get; private set; }
public UserLevel Level { get; private set; }
public int Points { get; private set; }
// 业务行为
public void Register(string email, string password) { /* ... */ }
public void AddAddress(Address address) { /* ... */ }
public void UpgradeLevel() { /* ... */ }
public void AddPoints(int points) { /* ... */ }
}
public class Address : Entity
{
public string Province { get; private set; }
public string City { get; private set; }
public string District { get; private set; }
public string Detail { get; private set; }
public string Phone { get; private set; }
public bool IsDefault { get; private set; }
public void SetAsDefault() { /* ... */ }
}
}
// 商品限界上下文
namespace ProductContext.Domain
{
public class Product : AggregateRoot<ProductId>
{
public string Name { get; private set; }
public string Description { get; private set; }
public Money Price { get; private set; }
public CategoryId CategoryId { get; private set; }
public List<ProductImage> Images { get; private set; }
public ProductStatus Status { get; private set; }
public ProductAttributes Attributes { get; private set; }
// 复杂业务逻辑
public void UpdatePrice(Money newPrice, string reason) { /* ... */ }
public void AddImage(string url, bool isMain = false) { /* ... */ }
public void Publish() { /* ... */ }
public void AddSku(Sku sku) { /* ... */ }
}
public class Sku : Entity
{
public string Code { get; private set; }
public Money Price { get; private set; }
public Dictionary<string, string> Specifications { get; private set; }
public int StockQuantity { get; private set; }
public void ReserveStock(int quantity) { /* ... */ }
public void ReleaseStock(int quantity) { /* ... */ }
}
}
// 订单限界上下文
namespace OrderContext.Domain
{
public class Order : AggregateRoot<OrderId>
{
public CustomerId CustomerId { get; private set; }
public List<OrderItem> Items { get; private set; }
public Money TotalPrice { get; private set; }
public Money DiscountAmount { get; private set; }
public OrderStatus Status { get; private set; }
public ShippingAddress ShippingAddress { get; private set; }
public PaymentInfo PaymentInfo { get; private set; }
// 复杂订单逻辑
public void ApplyCoupon(Coupon coupon) { /* ... */ }
public void UsePoints(int points) { /* ... */ }
public void Confirm() { /* ... */ }
public void Ship(string trackingNumber) { /* ... */ }
public void Cancel(string reason) { /* ... */ }
}
public class OrderItem : Entity
{
public ProductId ProductId { get; private set; }
public SkuId SkuId { get; private set; }
public string ProductName { get; private set; }
public Money UnitPrice { get; private set; }
public int Quantity { get; private set; }
public Money SubTotal => UnitPrice * Quantity;
public void ChangeQuantity(int newQuantity) { /* ... */ }
}
}
16.1.3 服务架构设计
graph TB
subgraph "前端层"
WEB[Web商城]
APP[移动App]
ADMIN[管理后台]
end
subgraph "API网关层"
GATEWAY[API网关/Ocelot]
AUTH[认证中心/IdentityServer4]
end
subgraph "业务服务层"
USER[用户服务
UserService]
PRODUCT[商品服务
ProductService]
CART[购物车服务
CartService]
ORDER[订单服务
OrderService]
PAYMENT[支付服务
PaymentService]
INVENTORY[库存服务
InventoryService]
SHIPPING[物流服务
ShippingService]
MARKETING[营销服务
MarketingService]
SEARCH[搜索服务
SearchService]
end
subgraph "基础设施层"
MQ[RabbitMQ]
CACHE[Redis集群]
SEARCH_ENGINE[Elasticsearch]
FILE[MinIO文件存储]
end
subgraph "数据存储层"
USER_DB[(用户数据库)]
PRODUCT_DB[(商品数据库)]
ORDER_DB[(订单数据库)]
INVENTORY_DB[(库存数据库)]
PAYMENT_DB[(支付数据库)]
end
WEB --> GATEWAY
APP --> GATEWAY
ADMIN --> GATEWAY
GATEWAY --> AUTH
GATEWAY --> USER
GATEWAY --> PRODUCT
GATEWAY --> CART
GATEWAY --> ORDER
GATEWAY --> SEARCH
ORDER --> USER
ORDER --> PRODUCT
ORDER --> INVENTORY
ORDER --> PAYMENT
ORDER --> MARKETING
ORDER --> SHIPPING
CART --> PRODUCT
CART --> USER
PAYMENT --> MQ
INVENTORY --> MQ
SHIPPING --> MQ
USER --> USER_DB
PRODUCT --> PRODUCT_DB
ORDER --> ORDER_DB
INVENTORY --> INVENTORY_DB
PAYMENT --> PAYMENT_DB
PRODUCT --> CACHE
ORDER --> CACHE
SEARCH --> SEARCH_ENGINE
PRODUCT --> FILE
16.2 核心业务实现
16.2.1 用户服务实现
// 用户服务 - 领域层
namespace ECommerce.UserService.Domain
{
public class User : AggregateRoot<UserId>
{
private readonly List<Address> _addresses = new();
public string Email { get; private set; }
public string PasswordHash { get; private set; }
public UserProfile Profile { get; private set; }
public IReadOnlyCollection<Address> Addresses => _addresses.AsReadOnly();
public UserLevel Level { get; private set; }
public int Points { get; private set; }
public DateTime CreatedAt { get; private set; }
public DateTime? LastLoginAt { get; private set; }
private User() { }
public static User Create(string email, string passwordHash, string name)
{
if (string.IsNullOrWhiteSpace(email))
throw new BusinessRuleException("邮箱不能为空");
if (!IsValidEmail(email))
throw new BusinessRuleException("邮箱格式不正确");
var user = new User
{
Id = new UserId(Guid.NewGuid()),
Email = email.ToLower(),
PasswordHash = passwordHash,
Profile = new UserProfile(name),
Level = UserLevel.Bronze,
Points = 0,
CreatedAt = DateTime.UtcNow
};
user.AddDomainEvent(new UserRegisteredEvent(user.Id, user.Email, name));
return user;
}
public void UpdateProfile(string name, string phone, string avatar)
{
Profile = Profile.Update(name, phone, avatar);
AddDomainEvent(new UserProfileUpdatedEvent(Id, name, phone, avatar));
}
public void AddAddress(string province, string city, string district,
string detail, string phone, string contactName, bool isDefault = false)
{
var address = new Address(province, city, district, detail, phone, contactName);
if (isDefault)
{
foreach (var addr in _addresses)
{
addr.SetAsDefault(false);
}
}
_addresses.Add(address);
AddDomainEvent(new AddressAddedEvent(Id, address.Id, contactName));
}
public void AddPoints(int points, string reason)
{
if (points <= 0)
throw new BusinessRuleException("积分必须为正数");
Points += points;
// 检查等级升级
var newLevel = CalculateLevel(Points);
if (newLevel > Level)
{
Level = newLevel;
AddDomainEvent(new UserLevelUpgradedEvent(Id, Level, Points));
}
AddDomainEvent(new PointsAddedEvent(Id, points, reason));
}
private static UserLevel CalculateLevel(int points)
{
return points switch
{
< 1000 => UserLevel.Bronze,
< 5000 => UserLevel.Silver,
< 20000 => UserLevel.Gold,
_ => UserLevel.Diamond
};
}
private static bool IsValidEmail(string email)
{
return Regex.IsMatch(email,
@"^[^@\s]+@[^@\s]+\.[^@\s]+$",
RegexOptions.IgnoreCase);
}
}
public class Address : Entity
{
public string Province { get; private set; }
public string City { get; private set; }
public string District { get; private set; }
public string Detail { get; private set; }
public string Phone { get; private set; }
public string ContactName { get; private set; }
public bool IsDefault { get; private set; }
internal Address(string province, string city, string district,
string detail, string phone, string contactName)
{
Province = province;
City = city;
District = district;
Detail = detail;
Phone = phone;
ContactName = contactName;
IsDefault = false;
}
internal void SetAsDefault(bool isDefault)
{
IsDefault = isDefault;
}
}
}
// 用户服务 - 应用层
namespace ECommerce.UserService.Application
{
public class UserApplicationService
{
private readonly IUserRepository _userRepository;
private readonly IUnitOfWork _unitOfWork;
private readonly IPasswordHasher<User> _passwordHasher;
private readonly IEventBus _eventBus;
private readonly ILogger<UserApplicationService> _logger;
public async Task<RegisterUserResult> RegisterUserAsync(RegisterUserCommand command)
{
_logger.LogInformation("用户注册开始: {Email}", command.Email);
// 检查邮箱是否已存在
var existingUser = await _userRepository.GetByEmailAsync(command.Email);
if (existingUser != null)
{
return RegisterUserResult.Fail("邮箱已被注册");
}
// 创建用户
var passwordHash = _passwordHasher.HashPassword(null, command.Password);
var user = User.Create(command.Email, passwordHash, command.Name);
await _userRepository.AddAsync(user);
await _unitOfWork.CommitAsync();
_logger.LogInformation("用户注册成功: {UserId}", user.Id.Value);
return RegisterUserResult.Success(user.Id);
}
public async Task<LoginResult> LoginAsync(LoginCommand command)
{
var user = await _userRepository.GetByEmailAsync(command.Email);
if (user == null)
{
return LoginResult.Fail("邮箱或密码错误");
}
var result = _passwordHasher.VerifyHashedPassword(user, user.PasswordHash, command.Password);
if (result == PasswordVerificationResult.Failed)
{
return LoginResult.Fail("邮箱或密码错误");
}
// 生成JWT令牌
var token = GenerateJwtToken(user);
// 更新最后登录时间
user.LastLoginAt = DateTime.UtcNow;
await _userRepository.UpdateAsync(user);
await _unitOfWork.CommitAsync();
_logger.LogInformation("用户登录成功: {UserId}", user.Id.Value);
return LoginResult.Success(token, user);
}
private string GenerateJwtToken(User user)
{
var claims = new List<Claim>
{
new Claim(ClaimTypes.NameIdentifier, user.Id.Value.ToString()),
new Claim(ClaimTypes.Email, user.Email),
new Claim(ClaimTypes.Name, user.Profile.Name),
new Claim("level", user.Level.ToString()),
new Claim("points", user.Points.ToString())
};
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("your-secret-key"));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken(
issuer: "ecommerce",
audience: "ecommerce-users",
claims: claims,
expires: DateTime.UtcNow.AddHours(24),
signingCredentials: creds);
return new JwtSecurityTokenHandler().WriteToken(token);
}
}
}
16.2.2 商品服务实现
// 商品服务 - 领域层
namespace ECommerce.ProductService.Domain
{
public class Product : AggregateRoot<ProductId>
{
private readonly List<ProductImage> _images = new();
private readonly List<Sku> _skus = new();
public string Name { get; private set; }
public string Description { get; private set; }
public Money Price { get; private set; }
public CategoryId CategoryId { get; private set; }
public BrandId BrandId { ge服务架构设计t; private set; }
public IReadOnlyCollection<ProductImage> Images => _images.AsReadOnly();
public IReadOnlyCollection<Sku> Skus => _skus.AsReadOnly();
public ProductStatus Status { get; private set; }
public ProductAttributes Attributes { get; private set; }
public float Rating { get; private set; }
public int ReviewCount { get; private set; }
private Product() { }
public static Product Create(string name, string description, Money price,
CategoryId categoryId, BrandId brandId)
{
if (string.IsNullOrWhiteSpace(name))
throw new BusinessRuleException("商品名称不能为空");
if (price.Amount <= 0)
throw new BusinessRuleException("商品价格必须大于0");
var product = new Product
{
Id = new ProductId(Guid.NewGuid()),
Name = name.Trim(),
Description = description?.Trim(),
Price = price,
CategoryId = categoryId,
BrandId = brandId,
Status = ProductStatus.Draft,
Attributes = new ProductAttributes(),
Rating = 0,
ReviewCount = 0
};
product.AddDomainEvent(new ProductCreatedEvent(product.Id, name, price));
return product;
}
public void UpdatePrice(Money newPrice, string reason, UserId operatorId)
{
if (newPrice.Amount <= 0)
throw new BusinessRuleException("商品价格必须大于0");
var oldPrice = Price;
Price = newPrice;
AddDomainEvent(new ProductPriceChangedEvent(Id, oldPrice, newPrice, reason, operatorId));
}
public void AddImage(string url, string altText = "", bool isMain = false)
{
if (string.IsNullOrWhiteSpace(url))
throw new BusinessRuleException("图片URL不能为空");
if (isMain)
{
foreach (var img in _images)
{
img.SetAsMain(false);
}
}
var image = new ProductImage(url, altText, isMain);
_images.Add(image);
AddDomainEvent(new ProductImageAddedEvent(Id, image.Id, url));
}
public void AddSku(string code, Money price, Dictionary<string, string> specifications, int stockQuantity)
{
if (_skus.Any(s => s.Code == code))
throw new BusinessRuleException($"SKU编码 {code} 已存在");
var sku = new Sku(code, price, specifications, stockQuantity);
_skus.Add(sku);
AddDomainEvent(new SkuAddedEvent(Id, sku.Id, code, price));
}
public void Publish(UserId operatorId)
{
if (Status != ProductStatus.Draft)
throw new BusinessRuleException("只有草稿状态的商品才能发布");
if (!_images.Any())
throw new BusinessRuleException("商品必须至少有一张图片");
if (!_skus.Any())
throw new BusinessRuleException("商品必须至少有一个SKU");
Status = ProductStatus.Published;
AddDomainEvent(new ProductPublishedEvent(Id, operatorId));
}
public void UpdateReviewStats(float newRating)
{
var totalRating = Rating * ReviewCount + newRating;
ReviewCount++;
Rating = totalRating / ReviewCount;
AddDomainEvent(new ProductReviewStatsUpdatedEvent(Id, Rating, ReviewCount));
}
}
public class Sku : Entity
{
public string Code { get; private set; }
public Money Price { get; private set; }
public Dictionary<string, string> Specifications { get; private set; }
public int StockQuantity { get; private set; }
public int ReservedQuantity { get; private set; }
public int AvailableQuantity => StockQuantity - ReservedQuantity;
internal Sku(string code, Money price, Dictionary<string, string> specifications, int stockQuantity)
{
Code = code;
Price = price;
Specifications = specifications ?? new Dictionary<string, string>();
StockQuantity = stockQuantity;
ReservedQuantity = 0;
}
public void ReserveStock(int quantity)
{
if (quantity <= 0)
throw new BusinessRuleException("预留数量必须为正数");
if (AvailableQuantity < quantity)
throw new BusinessRuleException($"库存不足,可用库存: {AvailableQuantity}");
ReservedQuantity += quantity;
}
public void ReleaseStock(int quantity)
{
if (quantity <= 0)
throw new BusinessRuleException("释放数量必须为正数");
if (ReservedQuantity < quantity)
throw new BusinessRuleException($"预留库存不足,已预留: {ReservedQuantity}");
ReservedQuantity -= quantity;
}
public void ConfirmStockDeduction(int quantity)
{
if (quantity <= 0)
throw new BusinessRuleException("确认数量必须为正数");
if (ReservedQuantity < quantity)
throw new BusinessRuleException($"预留库存不足,已预留: {ReservedQuantity}");
ReservedQuantity -= quantity;
StockQuantity -= quantity;
}
public void AddStock(int quantity)
{
if (quantity <= 0)
throw new BusinessRuleException("增加数量必须为正数");
StockQuantity += quantity;
}
}
}
16.2.3 订单服务实现
// 订单服务 - Saga编排器
namespace ECommerce.OrderService.Application
{
public class OrderSagaOrchestrator
{
private readonly IOrderRepository _orderRepository;
private readonly IProductServiceClient _productServiceClient;
private readonly IInventoryServiceClient _inventoryServiceClient;
private readonly IPaymentServiceClient _paymentServiceClient;
private readonly IMarketingServiceClient _marketingServiceClient;
private readonly IEventBus _eventBus;
private readonly ILogger<OrderSagaOrchestrator> _logger;
public async Task<CreateOrderResult> CreateOrderAsync(CreateOrderCommand command)
{
var sagaId = Guid.NewGuid();
_logger.LogInformation("开始订单创建Saga: {SagaId}", sagaId);
try
{
// 步骤1:验证和准备工作
var preparationResult = await PrepareOrderAsync(command);
if (!preparationResult.Success)
{
return CreateOrderResult.Fail(preparationResult.Error);
}
// 步骤2:创建订单(本地事务)
var order = Order.Create(
command.CustomerId,
preparationResult.Items,
preparationResult.TotalPrice,
preparationResult.ShippingAddress);
// 应用优惠券
if (command.CouponId.HasValue)
{
order.ApplyCoupon(preparationResult.Coupon, preparationResult.DiscountAmount);
}
// 使用积分
if (command.UsePoints > 0)
{
order.UsePoints(command.UsePoints, preparationResult.PointsDiscount);
}
await _orderRepository.AddAsync(order);
await _orderRepository.UnitOfWork.CommitAsync();
// 步骤3:预留库存(跨服务调用)
var stockReservationResult = await ReserveInventoryAsync(order);
if (!stockReservationResult.Success)
{
await CancelOrderAsync(order, stockReservationResult.Error);
return CreateOrderResult.Fail(stockReservationResult.Error);
}
// 步骤4:发布订单创建事件
await _eventBus.PublishAsync(new OrderCreatedEvent(
order.Id,
order.CustomerId,
order.Items.Select(i => new OrderItemDto(i.ProductId, i.SkuId, i.Quantity, i.UnitPrice)).ToList(),
order.TotalPrice,
order.FinalPrice,
DateTime.UtcNow));
_logger.LogInformation("订单创建Saga成功: {OrderId}", order.Id.Value);
return CreateOrderResult.Success(order.Id, order.FinalPrice);
}
catch (Exception ex)
{
_logger.LogError(ex, "订单创建Saga失败: {SagaId}", sagaId);
return CreateOrderResult.Fail("系统错误,请稍后重试");
}
}
private async Task<PreparationResult> PrepareOrderAsync(CreateOrderCommand command)
{
// 验证商品信息
var productChecks = command.Items.Select(async item =>
{
var product = await _productServiceClient.GetProductAsync(item.ProductId);
if (product == null)
return (false, $"商品 {item.ProductId} 不存在");
if (product.Status != ProductStatus.Published)
return (false, $"商品 {product.Name} 已下架");
return (true, string.Empty);
});
var results = await Task.WhenAll(productChecks);
var failed = results.FirstOrDefault(r => !r.Item1);
if (failed != default)
{
return PreparationResult.Fail(failed.Item2);
}
// 计算价格
var items = new List<OrderItem>();
var totalPrice = Money.Zero;
foreach (var item in command.Items)
{
var product = await _productServiceClient.GetProductAsync(item.ProductId);
var sku = product.Skus.FirstOrDefault(s => s.Id == item.SkuId);
if (sku == null)
return PreparationResult.Fail($"SKU不存在");
var orderItem = new OrderItem(
item.ProductId,
item.SkuId,
product.Name,
sku.Price,
item.Quantity);
items.Add(orderItem);
totalPrice += orderItem.SubTotal;
}
// 验证优惠券
Money discountAmount = Money.Zero;
Coupon coupon = null;
if (command.CouponId.HasValue)
{
coupon = await _marketingServiceClient.GetCouponAsync(command.CouponId.Value);
if (coupon == null || !coupon.IsValidFor(totalPrice))
{
return PreparationResult.Fail("优惠券无效");
}
discountAmount = coupon.CalculateDiscount(totalPrice);
}
// 验证积分
Money pointsDiscount = Money.Zero;
if (command.UsePoints > 0)
{
pointsDiscount = Money.Create(command.UsePoints / 100m); // 100积分=1元
if (pointsDiscount > totalPrice * 0.3m) // 最多抵扣30%
{
return PreparationResult.Fail("积分抵扣超出限制");
}
}
return PreparationResult.Success(items, totalPrice, discountAmount,
pointsDiscount, coupon, command.ShippingAddress);
}
private async Task<ReservationResult> ReserveInventoryAsync(Order order)
{
var reservationRequests = order.Items.Select(item =>
new StockReservationRequest(item.ProductId, item.SkuId, item.Quantity)
).ToList();
var reservationResult = await _inventoryServiceClient.ReserveStockAsync(
order.Id,
reservationRequests);
if (!reservationResult.Success)
{
return ReservationResult.Fail(reservationResult.Error);
}
return ReservationResult.Success();
}
private async Task CancelOrderAsync(Order order, string reason)
{
order.Cancel(reason);
await _orderRepository.UpdateAsync(order);
await _orderRepository.UnitOfWork.CommitAsync();
// 发布订单取消事件
await _eventBus.PublishAsync(new OrderCancelledEvent(
order.Id,
reason,
DateTime.UtcNow));
}
}
}
16.3 技术选型与实现
16.3.1 项目结构
src/
├── Services/
│ ├── UserService/
│ │ ├── src/
│ │ │ ├── UserService.Api/ # API层
│ │ │ ├── UserService.Application/ # 应用层
│ │ │ ├── UserService.Domain/ # 领域层
│ │ │ ├── UserService.Infrastructure/# 基础设施层
│ │ │ └── UserService.Shared/ # 共享组件
│ │ ├── tests/
│ │ │ ├── UserService.UnitTests/
│ │ │ └── UserService.IntegrationTests/
│ │ ├── Dockerfile
│ │ └── UserService.csproj
│ ├── ProductService/
│ ├── OrderService/
│ ├── PaymentService/
│ └── InventoryService/
├── ApiGateways/
│ └── MainGateway/
├── Shared/
│ ├── SharedKernel/ # 共享内核
│ ├── Messaging/ # 消息传递
│ └── Monitoring/ # 监控组件
tests/
├── IntegrationTests/
└── EndToEndTests/
docker-compose.yml
docker-compose.override.yml
k8s/
├── namespaces/
├── deployments/
├── services/
└── ingresses/
16.3.2 关键配置
// Program.cs - 用户服务配置
var builder = WebApplication.CreateBuilder(args);
// 服务配置
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
// 数据库配置
builder.Services.AddDbContext<UserDbContext>(options =>
options.UseNpgsql(builder.Configuration.GetConnectionString("Default")));
// 身份认证
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = builder.Configuration["Jwt:Issuer"],
ValidAudience = builder.Configuration["Jwt:Audience"],
IssuerSigningKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(builder.Configuration["Jwt:SecretKey"]))
};
});
// 依赖注入
builder.Services.AddScoped<IUserRepository, UserRepository>();
builder.Services.AddScoped<IUnitOfWork, UnitOfWork>();
builder.Services.AddScoped<UserApplicationService>();
builder.Services.AddSingleton<IPasswordHasher<User>, PasswordHasher<User>>();
// 消息队列
builder.Services.AddMassTransit(x =>
{
x.UsingRabbitMq((context, cfg) =>
{
cfg.Host(builder.Configuration["RabbitMQ:Host"], h =>
{
h.Username(builder.Configuration["RabbitMQ:Username"]);
h.Password(builder.Configuration["RabbitMQ:Password"]);
});
cfg.ConfigureEndpoints(context);
});
});
// 监控
builder.Services.AddOpenTelemetry()
.WithTracing(traceBuilder =>
{
traceBuilder
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddEntityFrameworkCoreInstrumentation()
.AddSource("UserService")
.AddJaegerExporter();
});
builder.Services.AddHealthChecks()
.AddDbContextCheck<UserDbContext>()
.AddRedis(builder.Configuration["Redis:ConnectionString"]);
// 缓存
builder.Services.AddStackExchangeRedisCache(options =>
{
options.Configuration = builder.Configuration["Redis:ConnectionString"];
});
var app = builder.Build();
// 管道配置
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.MapHealthChecks("/health");
app.Run();
16.3.3 Docker配置
# Dockerfile - 用户服务
FROM mcr.microsoft.com/dotnet/aspnet:8.0-alpine AS base
WORKDIR /app
EXPOSE 80
FROM mcr.microsoft.com/dotnet/sdk:8.0-alpine AS build
WORKDIR /src
COPY ["Services/UserService/UserService.csproj", "Services/UserService/"]
COPY ["Shared/SharedKernel/SharedKernel.csproj", "Shared/SharedKernel/"]
COPY ["Shared/Messaging/Messaging.csproj", "Shared/Messaging/"]
RUN dotnet restore "Services/UserService/UserService.csproj"
COPY . .
WORKDIR "/src/Services/UserService"
RUN dotnet build "UserService.csproj" -c Release -o /app/build
FROM build AS publish
RUN dotnet publish "UserService.csproj" -c Release -o /app/publish
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
# 创建非root用户
RUN addgroup -g 1000 -S appuser && \
adduser -u 1000 -S appuser -G appuser
USER appuser
ENTRYPOINT ["dotnet", "UserService.dll"]
16.4 部署与运维
16.4.1 Kubernetes部署
# 用户服务部署配置
apiVersion: apps/v1
kind: Deployment
metadata:
name: user-service
namespace: ecommerce
spec:
replicas: 3
selector:
matchLabels:
app: user-service
template:
metadata:
labels:
app: user-service
version: v1
spec:
containers:
- name: user-service
image: ecommerce/user-service:1.0.0
ports:
- containerPort: 80
env:
- name: ASPNETCORE_ENVIRONMENT
value: "Production"
- name: ConnectionStrings__Default
valueFrom:
secretKeyRef:
name: user-service-secrets
key: database-connection
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"
livenessProbe:
httpGet:
path: /health
port: 80
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /health/ready
port: 80
initialDelaySeconds: 5
periodSeconds: 5
---
apiVersion: v1
kind: Service
metadata:
name: user-service
namespace: ecommerce
spec:
selector:
app: user-service
ports:
- port: 80
targetPort: 80
type: ClusterIP
16.4.2 监控配置
# Prometheus监控配置
apiVersion: v1
kind: ServiceMonitor
metadata:
name: ecommerce-services
namespace: ecommerce
spec:
selector:
matchLabels:
monitoring: enabled
endpoints:
- port: metrics
interval: 30s
path: /metrics
---
# Grafana仪表盘配置
apiVersion: v1
kind: ConfigMap
metadata:
name: ecommerce-dashboard
namespace: monitoring
data:
dashboard.json: |
{
"dashboard": {
"title": "电商系统监控",
"panels": [
{
"title": "订单量",
"type": "graph",
"targets": [
{
"expr": "rate(orders_created_total[5m])",
"legendFormat": "订单/秒"
}
]
},
{
"title": "响应时间",
"type": "graph",
"targets": [
{
"expr": "histogram_quantile(0.95, http_request_duration_seconds_bucket)",
"legendFormat": "95分位"
}
]
}
]
}
}
16.5 小结
通过这个电商系统的实战案例,我们展示了:
- 如何从需求分析到架构设计:基于DDD的服务拆分方法
- 如何实现核心业务逻辑:用户、商品、订单等核心服务的实现
- 如何处理复杂业务场景:Saga模式处理分布式事务
- 如何构建生产级系统:包含监控、部署、运维的完整方案
这个案例虽然简化了一些复杂场景,但保留了微服务架构的核心挑战和解决方案。希望这个实战案例能帮助你更好地理解和应用微服务架构。
记住,微服务不是银弹,它需要团队的成熟度和技术的支撑。在选择微服务架构时,要充分考虑团队的实际情况和项目的具体需求。

浙公网安备 33010602011771号