第十六章 电商系统微服务实战(完结,总结以及之前所有内容会发到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 小结

通过这个电商系统的实战案例,我们展示了:

  1. 如何从需求分析到架构设计:基于DDD的服务拆分方法
  2. 如何实现核心业务逻辑:用户、商品、订单等核心服务的实现
  3. 如何处理复杂业务场景:Saga模式处理分布式事务
  4. 如何构建生产级系统:包含监控、部署、运维的完整方案

这个案例虽然简化了一些复杂场景,但保留了微服务架构的核心挑战和解决方案。希望这个实战案例能帮助你更好地理解和应用微服务架构。

记住,微服务不是银弹,它需要团队的成熟度和技术的支撑。在选择微服务架构时,要充分考虑团队的实际情况和项目的具体需求。

posted @ 2026-01-22 21:43  高宏顺  阅读(1)  评论(0)    收藏  举报