9.1 项目概述

项目介绍

本章将通过一个完整的电商管理系统来演示Spring Boot的实际应用。该系统包含用户管理、商品管理、订单管理、支付集成等核心功能。

技术栈

  • 后端框架:Spring Boot 3.1.0
  • 数据库:MySQL 8.0 + Redis 7.0
  • 安全框架:Spring Security + JWT
  • 数据访问:Spring Data JPA + MyBatis-Plus
  • 消息队列:RabbitMQ
  • 搜索引擎:Elasticsearch
  • 文件存储:MinIO
  • 监控工具:Prometheus + Grafana
  • API文档:Swagger 3.0

项目结构

ecommerce-system/
├── src/main/java/com/example/ecommerce/
│   ├── EcommerceApplication.java
│   ├── config/                    # 配置类
│   ├── controller/                # 控制器
│   ├── service/                   # 业务逻辑
│   ├── repository/                # 数据访问
│   ├── entity/                    # 实体类
│   ├── dto/                       # 数据传输对象
│   ├── security/                  # 安全相关
│   ├── common/                    # 公共组件
│   ├── exception/                 # 异常处理
│   └── utils/                     # 工具类
├── src/main/resources/
│   ├── application.yml
│   ├── application-dev.yml
│   ├── application-prod.yml
│   └── db/migration/              # 数据库迁移脚本
└── src/test/                      # 测试代码

9.2 核心实体设计

用户实体

@Entity
@Table(name = "users")
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class User {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(unique = true, nullable = false)
    private String username;
    
    @Column(unique = true, nullable = false)
    private String email;
    
    @Column(nullable = false)
    private String password;
    
    @Column(name = "phone_number")
    private String phoneNumber;
    
    @Column(name = "full_name")
    private String fullName;
    
    @Enumerated(EnumType.STRING)
    private UserStatus status;
    
    @Enumerated(EnumType.STRING)
    private UserRole role;
    
    @Column(name = "avatar_url")
    private String avatarUrl;
    
    @Column(name = "last_login_time")
    private LocalDateTime lastLoginTime;
    
    @Column(name = "create_time")
    private LocalDateTime createTime;
    
    @Column(name = "update_time")
    private LocalDateTime updateTime;
    
    @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
    private List<Address> addresses;
    
    @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
    private List<Order> orders;
    
    @PrePersist
    protected void onCreate() {
        createTime = LocalDateTime.now();
        updateTime = LocalDateTime.now();
    }
    
    @PreUpdate
    protected void onUpdate() {
        updateTime = LocalDateTime.now();
    }
}

public enum UserStatus {
    ACTIVE, INACTIVE, SUSPENDED, DELETED
}

public enum UserRole {
    ADMIN, CUSTOMER, MERCHANT
}

商品实体

@Entity
@Table(name = "products")
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Product {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(nullable = false)
    private String name;
    
    @Column(columnDefinition = "TEXT")
    private String description;
    
    @Column(nullable = false, precision = 10, scale = 2)
    private BigDecimal price;
    
    @Column(name = "original_price", precision = 10, scale = 2)
    private BigDecimal originalPrice;
    
    @Column(nullable = false)
    private Integer stock;
    
    @Column(name = "sold_count")
    private Integer soldCount = 0;
    
    @Column(name = "main_image")
    private String mainImage;
    
    @ElementCollection
    @CollectionTable(name = "product_images", joinColumns = @JoinColumn(name = "product_id"))
    @Column(name = "image_url")
    private List<String> images;
    
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "category_id")
    private Category category;
    
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "brand_id")
    private Brand brand;
    
    @Enumerated(EnumType.STRING)
    private ProductStatus status;
    
    @Column(name = "weight")
    private Double weight;
    
    @Column(name = "dimensions")
    private String dimensions;
    
    @ElementCollection
    @CollectionTable(name = "product_attributes", joinColumns = @JoinColumn(name = "product_id"))
    @MapKeyColumn(name = "attribute_name")
    @Column(name = "attribute_value")
    private Map<String, String> attributes;
    
    @Column(name = "create_time")
    private LocalDateTime createTime;
    
    @Column(name = "update_time")
    private LocalDateTime updateTime;
    
    @PrePersist
    protected void onCreate() {
        createTime = LocalDateTime.now();
        updateTime = LocalDateTime.now();
    }
    
    @PreUpdate
    protected void onUpdate() {
        updateTime = LocalDateTime.now();
    }
}

public enum ProductStatus {
    ACTIVE, INACTIVE, OUT_OF_STOCK, DISCONTINUED
}

订单实体

@Entity
@Table(name = "orders")
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Order {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(name = "order_number", unique = true, nullable = false)
    private String orderNumber;
    
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "user_id", nullable = false)
    private User user;
    
    @OneToMany(mappedBy = "order", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
    private List<OrderItem> orderItems;
    
    @Column(name = "total_amount", nullable = false, precision = 10, scale = 2)
    private BigDecimal totalAmount;
    
    @Column(name = "discount_amount", precision = 10, scale = 2)
    private BigDecimal discountAmount = BigDecimal.ZERO;
    
    @Column(name = "shipping_fee", precision = 10, scale = 2)
    private BigDecimal shippingFee = BigDecimal.ZERO;
    
    @Column(name = "final_amount", nullable = false, precision = 10, scale = 2)
    private BigDecimal finalAmount;
    
    @Enumerated(EnumType.STRING)
    private OrderStatus status;
    
    @Enumerated(EnumType.STRING)
    @Column(name = "payment_status")
    private PaymentStatus paymentStatus;
    
    @Enumerated(EnumType.STRING)
    @Column(name = "shipping_status")
    private ShippingStatus shippingStatus;
    
    @Embedded
    private ShippingAddress shippingAddress;
    
    @Column(name = "payment_method")
    private String paymentMethod;
    
    @Column(name = "payment_time")
    private LocalDateTime paymentTime;
    
    @Column(name = "shipping_time")
    private LocalDateTime shippingTime;
    
    @Column(name = "delivery_time")
    private LocalDateTime deliveryTime;
    
    @Column(name = "create_time")
    private LocalDateTime createTime;
    
    @Column(name = "update_time")
    private LocalDateTime updateTime;
    
    @PrePersist
    protected void onCreate() {
        createTime = LocalDateTime.now();
        updateTime = LocalDateTime.now();
        if (orderNumber == null) {
            orderNumber = generateOrderNumber();
        }
    }
    
    @PreUpdate
    protected void onUpdate() {
        updateTime = LocalDateTime.now();
    }
    
    private String generateOrderNumber() {
        return "ORD" + System.currentTimeMillis() + 
               String.format("%04d", new Random().nextInt(10000));
    }
}

public enum OrderStatus {
    PENDING, CONFIRMED, PROCESSING, SHIPPED, DELIVERED, CANCELLED, REFUNDED
}

public enum PaymentStatus {
    PENDING, PAID, FAILED, REFUNDED
}

public enum ShippingStatus {
    PENDING, PROCESSING, SHIPPED, DELIVERED
}

@Embeddable
@Data
public class ShippingAddress {
    @Column(name = "recipient_name")
    private String recipientName;
    
    @Column(name = "recipient_phone")
    private String recipientPhone;
    
    @Column(name = "province")
    private String province;
    
    @Column(name = "city")
    private String city;
    
    @Column(name = "district")
    private String district;
    
    @Column(name = "detail_address")
    private String detailAddress;
    
    @Column(name = "postal_code")
    private String postalCode;
}

9.3 业务服务实现

用户服务

@Service
@Transactional
public class UserService {
    
    @Autowired
    private UserRepository userRepository;
    
    @Autowired
    private PasswordEncoder passwordEncoder;
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    @Autowired
    private EmailService emailService;
    
    /**
     * 用户注册
     */
    public UserDTO register(UserRegistrationDTO registrationDTO) {
        // 检查用户名和邮箱是否已存在
        if (userRepository.existsByUsername(registrationDTO.getUsername())) {
            throw new BusinessException("用户名已存在");
        }
        
        if (userRepository.existsByEmail(registrationDTO.getEmail())) {
            throw new BusinessException("邮箱已被注册");
        }
        
        // 创建用户
        User user = User.builder()
            .username(registrationDTO.getUsername())
            .email(registrationDTO.getEmail())
            .password(passwordEncoder.encode(registrationDTO.getPassword()))
            .fullName(registrationDTO.getFullName())
            .phoneNumber(registrationDTO.getPhoneNumber())
            .status(UserStatus.ACTIVE)
            .role(UserRole.CUSTOMER)
            .build();
        
        User savedUser = userRepository.save(user);
        
        // 发送欢迎邮件
        emailService.sendWelcomeEmail(savedUser.getEmail(), savedUser.getFullName());
        
        // 记录注册事件
        recordUserEvent(savedUser.getId(), "USER_REGISTERED");
        
        return convertToDTO(savedUser);
    }
    
    /**
     * 用户登录
     */
    @Transactional(readOnly = true)
    public LoginResponseDTO login(LoginRequestDTO loginRequest) {
        User user = userRepository.findByUsername(loginRequest.getUsername())
            .orElseThrow(() -> new BusinessException("用户名或密码错误"));
        
        if (!passwordEncoder.matches(loginRequest.getPassword(), user.getPassword())) {
            throw new BusinessException("用户名或密码错误");
        }
        
        if (user.getStatus() != UserStatus.ACTIVE) {
            throw new BusinessException("账户已被禁用");
        }
        
        // 更新最后登录时间
        user.setLastLoginTime(LocalDateTime.now());
        userRepository.save(user);
        
        // 生成JWT Token
        String token = jwtTokenProvider.generateToken(user);
        
        // 缓存用户信息
        cacheUserInfo(user);
        
        // 记录登录事件
        recordUserEvent(user.getId(), "USER_LOGIN");
        
        return LoginResponseDTO.builder()
            .token(token)
            .user(convertToDTO(user))
            .build();
    }
    
    /**
     * 获取用户信息
     */
    @Cacheable(value = "users", key = "#userId")
    @Transactional(readOnly = true)
    public UserDTO getUserById(Long userId) {
        User user = userRepository.findById(userId)
            .orElseThrow(() -> new BusinessException("用户不存在"));
        
        return convertToDTO(user);
    }
    
    /**
     * 更新用户信息
     */
    @CacheEvict(value = "users", key = "#userId")
    public UserDTO updateUser(Long userId, UserUpdateDTO updateDTO) {
        User user = userRepository.findById(userId)
            .orElseThrow(() -> new BusinessException("用户不存在"));
        
        // 更新用户信息
        if (StringUtils.hasText(updateDTO.getFullName())) {
            user.setFullName(updateDTO.getFullName());
        }
        
        if (StringUtils.hasText(updateDTO.getPhoneNumber())) {
            user.setPhoneNumber(updateDTO.getPhoneNumber());
        }
        
        if (StringUtils.hasText(updateDTO.getAvatarUrl())) {
            user.setAvatarUrl(updateDTO.getAvatarUrl());
        }
        
        User savedUser = userRepository.save(user);
        
        // 记录更新事件
        recordUserEvent(userId, "USER_UPDATED");
        
        return convertToDTO(savedUser);
    }
    
    /**
     * 修改密码
     */
    public void changePassword(Long userId, ChangePasswordDTO changePasswordDTO) {
        User user = userRepository.findById(userId)
            .orElseThrow(() -> new BusinessException("用户不存在"));
        
        if (!passwordEncoder.matches(changePasswordDTO.getOldPassword(), user.getPassword())) {
            throw new BusinessException("原密码错误");
        }
        
        user.setPassword(passwordEncoder.encode(changePasswordDTO.getNewPassword()));
        userRepository.save(user);
        
        // 记录密码修改事件
        recordUserEvent(userId, "PASSWORD_CHANGED");
        
        // 发送密码修改通知邮件
        emailService.sendPasswordChangeNotification(user.getEmail(), user.getFullName());
    }
    
    /**
     * 分页查询用户
     */
    @Transactional(readOnly = true)
    public Page<UserDTO> findUsers(UserSearchCriteria criteria, Pageable pageable) {
        Specification<User> spec = UserSpecification.build(criteria);
        Page<User> userPage = userRepository.findAll(spec, pageable);
        
        return userPage.map(this::convertToDTO);
    }
    
    private void cacheUserInfo(User user) {
        String cacheKey = "user:info:" + user.getId();
        redisTemplate.opsForValue().set(cacheKey, convertToDTO(user), Duration.ofHours(1));
    }
    
    private void recordUserEvent(Long userId, String eventType) {
        // 记录用户事件到消息队列或日志系统
        UserEvent event = UserEvent.builder()
            .userId(userId)
            .eventType(eventType)
            .timestamp(LocalDateTime.now())
            .build();
        
        // 发送到消息队列
        rabbitTemplate.convertAndSend("user.events", event);
    }
    
    private UserDTO convertToDTO(User user) {
        return UserDTO.builder()
            .id(user.getId())
            .username(user.getUsername())
            .email(user.getEmail())
            .fullName(user.getFullName())
            .phoneNumber(user.getPhoneNumber())
            .status(user.getStatus())
            .role(user.getRole())
            .avatarUrl(user.getAvatarUrl())
            .lastLoginTime(user.getLastLoginTime())
            .createTime(user.getCreateTime())
            .build();
    }
}

商品服务

@Service
@Transactional
public class ProductService {
    
    @Autowired
    private ProductRepository productRepository;
    
    @Autowired
    private CategoryRepository categoryRepository;
    
    @Autowired
    private BrandRepository brandRepository;
    
    @Autowired
    private ElasticsearchTemplate elasticsearchTemplate;
    
    @Autowired
    private FileStorageService fileStorageService;
    
    /**
     * 创建商品
     */
    public ProductDTO createProduct(ProductCreateDTO createDTO) {
        // 验证分类和品牌
        Category category = categoryRepository.findById(createDTO.getCategoryId())
            .orElseThrow(() -> new BusinessException("分类不存在"));
        
        Brand brand = brandRepository.findById(createDTO.getBrandId())
            .orElseThrow(() -> new BusinessException("品牌不存在"));
        
        // 创建商品
        Product product = Product.builder()
            .name(createDTO.getName())
            .description(createDTO.getDescription())
            .price(createDTO.getPrice())
            .originalPrice(createDTO.getOriginalPrice())
            .stock(createDTO.getStock())
            .category(category)
            .brand(brand)
            .status(ProductStatus.ACTIVE)
            .weight(createDTO.getWeight())
            .dimensions(createDTO.getDimensions())
            .attributes(createDTO.getAttributes())
            .build();
        
        // 处理图片上传
        if (createDTO.getMainImage() != null) {
            String mainImageUrl = fileStorageService.uploadImage(createDTO.getMainImage());
            product.setMainImage(mainImageUrl);
        }
        
        if (createDTO.getImages() != null && !createDTO.getImages().isEmpty()) {
            List<String> imageUrls = createDTO.getImages().stream()
                .map(fileStorageService::uploadImage)
                .collect(Collectors.toList());
            product.setImages(imageUrls);
        }
        
        Product savedProduct = productRepository.save(product);
        
        // 同步到Elasticsearch
        syncToElasticsearch(savedProduct);
        
        return convertToDTO(savedProduct);
    }
    
    /**
     * 更新商品
     */
    @CacheEvict(value = "products", key = "#productId")
    public ProductDTO updateProduct(Long productId, ProductUpdateDTO updateDTO) {
        Product product = productRepository.findById(productId)
            .orElseThrow(() -> new BusinessException("商品不存在"));
        
        // 更新商品信息
        if (StringUtils.hasText(updateDTO.getName())) {
            product.setName(updateDTO.getName());
        }
        
        if (StringUtils.hasText(updateDTO.getDescription())) {
            product.setDescription(updateDTO.getDescription());
        }
        
        if (updateDTO.getPrice() != null) {
            product.setPrice(updateDTO.getPrice());
        }
        
        if (updateDTO.getStock() != null) {
            product.setStock(updateDTO.getStock());
            
            // 检查库存状态
            if (updateDTO.getStock() == 0) {
                product.setStatus(ProductStatus.OUT_OF_STOCK);
            } else if (product.getStatus() == ProductStatus.OUT_OF_STOCK) {
                product.setStatus(ProductStatus.ACTIVE);
            }
        }
        
        Product savedProduct = productRepository.save(product);
        
        // 同步到Elasticsearch
        syncToElasticsearch(savedProduct);
        
        return convertToDTO(savedProduct);
    }
    
    /**
     * 商品搜索
     */
    @Transactional(readOnly = true)
    public Page<ProductDTO> searchProducts(ProductSearchCriteria criteria, Pageable pageable) {
        if (StringUtils.hasText(criteria.getKeyword())) {
            // 使用Elasticsearch进行全文搜索
            return searchProductsWithElasticsearch(criteria, pageable);
        } else {
            // 使用数据库查询
            return searchProductsWithDatabase(criteria, pageable);
        }
    }
    
    /**
     * 获取商品详情
     */
    @Cacheable(value = "products", key = "#productId")
    @Transactional(readOnly = true)
    public ProductDetailDTO getProductDetail(Long productId) {
        Product product = productRepository.findByIdWithDetails(productId)
            .orElseThrow(() -> new BusinessException("商品不存在"));
        
        // 增加浏览次数
        incrementViewCount(productId);
        
        return convertToDetailDTO(product);
    }
    
    /**
     * 减少库存
     */
    @Transactional
    public void decreaseStock(Long productId, Integer quantity) {
        Product product = productRepository.findByIdForUpdate(productId)
            .orElseThrow(() -> new BusinessException("商品不存在"));
        
        if (product.getStock() < quantity) {
            throw new BusinessException("库存不足");
        }
        
        product.setStock(product.getStock() - quantity);
        product.setSoldCount(product.getSoldCount() + quantity);
        
        if (product.getStock() == 0) {
            product.setStatus(ProductStatus.OUT_OF_STOCK);
        }
        
        productRepository.save(product);
        
        // 同步到Elasticsearch
        syncToElasticsearch(product);
    }
    
    /**
     * 增加库存(退货时使用)
     */
    @Transactional
    public void increaseStock(Long productId, Integer quantity) {
        Product product = productRepository.findById(productId)
            .orElseThrow(() -> new BusinessException("商品不存在"));
        
        product.setStock(product.getStock() + quantity);
        product.setSoldCount(Math.max(0, product.getSoldCount() - quantity));
        
        if (product.getStatus() == ProductStatus.OUT_OF_STOCK && product.getStock() > 0) {
            product.setStatus(ProductStatus.ACTIVE);
        }
        
        productRepository.save(product);
        
        // 同步到Elasticsearch
        syncToElasticsearch(product);
    }
    
    private Page<ProductDTO> searchProductsWithElasticsearch(ProductSearchCriteria criteria, Pageable pageable) {
        // 构建Elasticsearch查询
        BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery();
        
        // 关键词搜索
        if (StringUtils.hasText(criteria.getKeyword())) {
            queryBuilder.must(QueryBuilders.multiMatchQuery(criteria.getKeyword())
                .field("name", 2.0f)
                .field("description", 1.0f)
                .type(MultiMatchQueryBuilder.Type.BEST_FIELDS));
        }
        
        // 分类过滤
        if (criteria.getCategoryId() != null) {
            queryBuilder.filter(QueryBuilders.termQuery("categoryId", criteria.getCategoryId()));
        }
        
        // 品牌过滤
        if (criteria.getBrandId() != null) {
            queryBuilder.filter(QueryBuilders.termQuery("brandId", criteria.getBrandId()));
        }
        
        // 价格范围过滤
        if (criteria.getMinPrice() != null || criteria.getMaxPrice() != null) {
            RangeQueryBuilder priceRange = QueryBuilders.rangeQuery("price");
            if (criteria.getMinPrice() != null) {
                priceRange.gte(criteria.getMinPrice());
            }
            if (criteria.getMaxPrice() != null) {
                priceRange.lte(criteria.getMaxPrice());
            }
            queryBuilder.filter(priceRange);
        }
        
        // 状态过滤
        queryBuilder.filter(QueryBuilders.termQuery("status", ProductStatus.ACTIVE.name()));
        
        // 执行搜索
        SearchRequest searchRequest = new SearchRequest("products");
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        sourceBuilder.query(queryBuilder);
        sourceBuilder.from((int) pageable.getOffset());
        sourceBuilder.size(pageable.getPageSize());
        
        // 添加排序
        if (pageable.getSort().isSorted()) {
            for (Sort.Order order : pageable.getSort()) {
                sourceBuilder.sort(order.getProperty(), 
                    order.isAscending() ? SortOrder.ASC : SortOrder.DESC);
            }
        }
        
        searchRequest.source(sourceBuilder);
        
        try {
            SearchResponse response = elasticsearchTemplate.search(searchRequest, RequestOptions.DEFAULT);
            
            List<ProductDTO> products = Arrays.stream(response.getHits().getHits())
                .map(hit -> {
                    Long productId = Long.valueOf(hit.getId());
                    return convertToDTO(productRepository.findById(productId).orElse(null));
                })
                .filter(Objects::nonNull)
                .collect(Collectors.toList());
            
            long total = response.getHits().getTotalHits().value;
            
            return new PageImpl<>(products, pageable, total);
        } catch (IOException e) {
            log.error("Elasticsearch搜索失败", e);
            // 降级到数据库搜索
            return searchProductsWithDatabase(criteria, pageable);
        }
    }
    
    private Page<ProductDTO> searchProductsWithDatabase(ProductSearchCriteria criteria, Pageable pageable) {
        Specification<Product> spec = ProductSpecification.build(criteria);
        Page<Product> productPage = productRepository.findAll(spec, pageable);
        
        return productPage.map(this::convertToDTO);
    }
    
    private void syncToElasticsearch(Product product) {
        try {
            ProductDocument document = ProductDocument.builder()
                .id(product.getId().toString())
                .name(product.getName())
                .description(product.getDescription())
                .price(product.getPrice())
                .categoryId(product.getCategory().getId())
                .brandId(product.getBrand().getId())
                .status(product.getStatus().name())
                .build();
            
            IndexRequest request = new IndexRequest("products")
                .id(document.getId())
                .source(objectMapper.writeValueAsString(document), XContentType.JSON);
            
            elasticsearchTemplate.index(request, RequestOptions.DEFAULT);
        } catch (Exception e) {
            log.error("同步商品到Elasticsearch失败: {}", product.getId(), e);
        }
    }
    
    private void incrementViewCount(Long productId) {
        String key = "product:view:" + productId;
        redisTemplate.opsForValue().increment(key);
        redisTemplate.expire(key, Duration.ofDays(1));
    }
    
    private ProductDTO convertToDTO(Product product) {
        if (product == null) return null;
        
        return ProductDTO.builder()
            .id(product.getId())
            .name(product.getName())
            .description(product.getDescription())
            .price(product.getPrice())
            .originalPrice(product.getOriginalPrice())
            .stock(product.getStock())
            .soldCount(product.getSoldCount())
            .mainImage(product.getMainImage())
            .status(product.getStatus())
            .categoryName(product.getCategory().getName())
            .brandName(product.getBrand().getName())
            .createTime(product.getCreateTime())
            .build();
    }
    
    private ProductDetailDTO convertToDetailDTO(Product product) {
        return ProductDetailDTO.builder()
            .id(product.getId())
            .name(product.getName())
            .description(product.getDescription())
            .price(product.getPrice())
            .originalPrice(product.getOriginalPrice())
            .stock(product.getStock())
            .soldCount(product.getSoldCount())
            .mainImage(product.getMainImage())
            .images(product.getImages())
            .status(product.getStatus())
            .weight(product.getWeight())
            .dimensions(product.getDimensions())
            .attributes(product.getAttributes())
            .category(convertCategoryToDTO(product.getCategory()))
            .brand(convertBrandToDTO(product.getBrand()))
            .createTime(product.getCreateTime())
            .build();
    }
}

订单服务

@Service
@Transactional
public class OrderService {
    
    @Autowired
    private OrderRepository orderRepository;
    
    @Autowired
    private ProductService productService;
    
    @Autowired
    private UserService userService;
    
    @Autowired
    private PaymentService paymentService;
    
    @Autowired
    private InventoryService inventoryService;
    
    @Autowired
    private RabbitTemplate rabbitTemplate;
    
    /**
     * 创建订单
     */
    public OrderDTO createOrder(Long userId, OrderCreateDTO createDTO) {
        // 验证用户
        UserDTO user = userService.getUserById(userId);
        
        // 验证商品和库存
        List<OrderItemDTO> orderItems = validateAndCalculateOrderItems(createDTO.getItems());
        
        // 计算订单金额
        BigDecimal totalAmount = orderItems.stream()
            .map(item -> item.getPrice().multiply(BigDecimal.valueOf(item.getQuantity())))
            .reduce(BigDecimal.ZERO, BigDecimal::add);
        
        BigDecimal discountAmount = calculateDiscount(userId, totalAmount);
        BigDecimal shippingFee = calculateShippingFee(createDTO.getShippingAddress(), totalAmount);
        BigDecimal finalAmount = totalAmount.subtract(discountAmount).add(shippingFee);
        
        // 创建订单
        Order order = Order.builder()
            .user(User.builder().id(userId).build())
            .totalAmount(totalAmount)
            .discountAmount(discountAmount)
            .shippingFee(shippingFee)
            .finalAmount(finalAmount)
            .status(OrderStatus.PENDING)
            .paymentStatus(PaymentStatus.PENDING)
            .shippingStatus(ShippingStatus.PENDING)
            .shippingAddress(createDTO.getShippingAddress())
            .build();
        
        Order savedOrder = orderRepository.save(order);
        
        // 创建订单项
        List<OrderItem> orderItemEntities = orderItems.stream()
            .map(item -> OrderItem.builder()
                .order(savedOrder)
                .product(Product.builder().id(item.getProductId()).build())
                .quantity(item.getQuantity())
                .price(item.getPrice())
                .totalPrice(item.getPrice().multiply(BigDecimal.valueOf(item.getQuantity())))
                .build())
            .collect(Collectors.toList());
        
        savedOrder.setOrderItems(orderItemEntities);
        
        // 预扣库存
        try {
            inventoryService.reserveStock(orderItems);
        } catch (Exception e) {
            throw new BusinessException("库存预扣失败: " + e.getMessage());
        }
        
        // 发送订单创建事件
        publishOrderEvent(savedOrder.getId(), "ORDER_CREATED");
        
        // 设置订单超时取消任务
        scheduleOrderCancellation(savedOrder.getId());
        
        return convertToDTO(savedOrder);
    }
    
    /**
     * 支付订单
     */
    public PaymentResultDTO payOrder(Long orderId, PaymentRequestDTO paymentRequest) {
        Order order = orderRepository.findById(orderId)
            .orElseThrow(() -> new BusinessException("订单不存在"));
        
        if (order.getStatus() != OrderStatus.PENDING) {
            throw new BusinessException("订单状态不允许支付");
        }
        
        if (order.getPaymentStatus() != PaymentStatus.PENDING) {
            throw new BusinessException("订单已支付或支付失败");
        }
        
        try {
            // 调用支付服务
            PaymentResultDTO paymentResult = paymentService.processPayment(
                order.getOrderNumber(),
                order.getFinalAmount(),
                paymentRequest
            );
            
            if (paymentResult.isSuccess()) {
                // 支付成功,更新订单状态
                order.setPaymentStatus(PaymentStatus.PAID);
                order.setStatus(OrderStatus.CONFIRMED);
                order.setPaymentTime(LocalDateTime.now());
                order.setPaymentMethod(paymentRequest.getPaymentMethod());
                
                orderRepository.save(order);
                
                // 确认库存扣减
                inventoryService.confirmStockReservation(order.getOrderItems());
                
                // 发送支付成功事件
                publishOrderEvent(orderId, "ORDER_PAID");
                
                // 发送订单确认邮件
                sendOrderConfirmationEmail(order);
                
            } else {
                // 支付失败,释放库存
                order.setPaymentStatus(PaymentStatus.FAILED);
                orderRepository.save(order);
                
                inventoryService.releaseStockReservation(order.getOrderItems());
                
                // 发送支付失败事件
                publishOrderEvent(orderId, "ORDER_PAYMENT_FAILED");
            }
            
            return paymentResult;
            
        } catch (Exception e) {
            log.error("订单支付处理异常: {}", orderId, e);
            
            // 支付异常,释放库存
            order.setPaymentStatus(PaymentStatus.FAILED);
            orderRepository.save(order);
            
            inventoryService.releaseStockReservation(order.getOrderItems());
            
            throw new BusinessException("支付处理失败: " + e.getMessage());
        }
    }
    
    /**
     * 取消订单
     */
    public void cancelOrder(Long orderId, String reason) {
        Order order = orderRepository.findById(orderId)
            .orElseThrow(() -> new BusinessException("订单不存在"));
        
        if (!canCancelOrder(order)) {
            throw new BusinessException("订单状态不允许取消");
        }
        
        order.setStatus(OrderStatus.CANCELLED);
        orderRepository.save(order);
        
        // 释放库存
        if (order.getPaymentStatus() == PaymentStatus.PENDING) {
            inventoryService.releaseStockReservation(order.getOrderItems());
        } else if (order.getPaymentStatus() == PaymentStatus.PAID) {
            inventoryService.restoreStock(order.getOrderItems());
            
            // 发起退款
            paymentService.refund(order.getOrderNumber(), order.getFinalAmount(), reason);
        }
        
        // 发送订单取消事件
        publishOrderEvent(orderId, "ORDER_CANCELLED");
    }
    
    /**
     * 发货
     */
    public void shipOrder(Long orderId, ShippingInfoDTO shippingInfo) {
        Order order = orderRepository.findById(orderId)
            .orElseThrow(() -> new BusinessException("订单不存在"));
        
        if (order.getStatus() != OrderStatus.CONFIRMED) {
            throw new BusinessException("订单状态不允许发货");
        }
        
        order.setStatus(OrderStatus.SHIPPED);
        order.setShippingStatus(ShippingStatus.SHIPPED);
        order.setShippingTime(LocalDateTime.now());
        
        orderRepository.save(order);
        
        // 发送发货通知
        sendShippingNotification(order, shippingInfo);
        
        // 发送发货事件
        publishOrderEvent(orderId, "ORDER_SHIPPED");
    }
    
    /**
     * 确认收货
     */
    public void confirmDelivery(Long orderId) {
        Order order = orderRepository.findById(orderId)
            .orElseThrow(() -> new BusinessException("订单不存在"));
        
        if (order.getStatus() != OrderStatus.SHIPPED) {
            throw new BusinessException("订单状态不允许确认收货");
        }
        
        order.setStatus(OrderStatus.DELIVERED);
        order.setShippingStatus(ShippingStatus.DELIVERED);
        order.setDeliveryTime(LocalDateTime.now());
        
        orderRepository.save(order);
        
        // 发送确认收货事件
        publishOrderEvent(orderId, "ORDER_DELIVERED");
    }
    
    /**
     * 查询用户订单
     */
    @Transactional(readOnly = true)
    public Page<OrderDTO> getUserOrders(Long userId, OrderStatus status, Pageable pageable) {
        Page<Order> orderPage;
        
        if (status != null) {
            orderPage = orderRepository.findByUserIdAndStatus(userId, status, pageable);
        } else {
            orderPage = orderRepository.findByUserId(userId, pageable);
        }
        
        return orderPage.map(this::convertToDTO);
    }
    
    /**
     * 获取订单详情
     */
    @Transactional(readOnly = true)
    public OrderDetailDTO getOrderDetail(Long orderId) {
        Order order = orderRepository.findByIdWithDetails(orderId)
            .orElseThrow(() -> new BusinessException("订单不存在"));
        
        return convertToDetailDTO(order);
    }
    
    private List<OrderItemDTO> validateAndCalculateOrderItems(List<OrderItemCreateDTO> items) {
        return items.stream()
            .map(item -> {
                ProductDTO product = productService.getProductById(item.getProductId());
                
                if (product.getStatus() != ProductStatus.ACTIVE) {
                    throw new BusinessException("商品已下架: " + product.getName());
                }
                
                if (product.getStock() < item.getQuantity()) {
                    throw new BusinessException("商品库存不足: " + product.getName());
                }
                
                return OrderItemDTO.builder()
                    .productId(product.getId())
                    .productName(product.getName())
                    .quantity(item.getQuantity())
                    .price(product.getPrice())
                    .build();
            })
            .collect(Collectors.toList());
    }
    
    private BigDecimal calculateDiscount(Long userId, BigDecimal totalAmount) {
        // 计算折扣逻辑
        return BigDecimal.ZERO;
    }
    
    private BigDecimal calculateShippingFee(ShippingAddress address, BigDecimal totalAmount) {
        // 计算运费逻辑
        if (totalAmount.compareTo(new BigDecimal("99")) >= 0) {
            return BigDecimal.ZERO; // 满99免运费
        }
        return new BigDecimal("10"); // 默认运费10元
    }
    
    private boolean canCancelOrder(Order order) {
        return order.getStatus() == OrderStatus.PENDING || 
               order.getStatus() == OrderStatus.CONFIRMED;
    }
    
    private void publishOrderEvent(Long orderId, String eventType) {
        OrderEvent event = OrderEvent.builder()
            .orderId(orderId)
            .eventType(eventType)
            .timestamp(LocalDateTime.now())
            .build();
        
        rabbitTemplate.convertAndSend("order.events", event);
    }
    
    private void scheduleOrderCancellation(Long orderId) {
        // 30分钟后自动取消未支付订单
        OrderCancellationTask task = OrderCancellationTask.builder()
            .orderId(orderId)
            .executeTime(LocalDateTime.now().plusMinutes(30))
            .build();
        
        rabbitTemplate.convertAndSend("order.cancellation.delayed", task);
    }
    
    private void sendOrderConfirmationEmail(Order order) {
        // 发送订单确认邮件
    }
    
    private void sendShippingNotification(Order order, ShippingInfoDTO shippingInfo) {
        // 发送发货通知
    }
    
    private OrderDTO convertToDTO(Order order) {
        return OrderDTO.builder()
            .id(order.getId())
            .orderNumber(order.getOrderNumber())
            .totalAmount(order.getTotalAmount())
            .discountAmount(order.getDiscountAmount())
            .shippingFee(order.getShippingFee())
            .finalAmount(order.getFinalAmount())
            .status(order.getStatus())
            .paymentStatus(order.getPaymentStatus())
            .shippingStatus(order.getShippingStatus())
            .createTime(order.getCreateTime())
            .build();
    }
    
    private OrderDetailDTO convertToDetailDTO(Order order) {
        List<OrderItemDetailDTO> items = order.getOrderItems().stream()
            .map(item -> OrderItemDetailDTO.builder()
                .productId(item.getProduct().getId())
                .productName(item.getProduct().getName())
                .productImage(item.getProduct().getMainImage())
                .quantity(item.getQuantity())
                .price(item.getPrice())
                .totalPrice(item.getTotalPrice())
                .build())
            .collect(Collectors.toList());
        
        return OrderDetailDTO.builder()
            .id(order.getId())
            .orderNumber(order.getOrderNumber())
            .items(items)
            .totalAmount(order.getTotalAmount())
            .discountAmount(order.getDiscountAmount())
            .shippingFee(order.getShippingFee())
            .finalAmount(order.getFinalAmount())
            .status(order.getStatus())
            .paymentStatus(order.getPaymentStatus())
            .shippingStatus(order.getShippingStatus())
            .shippingAddress(order.getShippingAddress())
            .paymentMethod(order.getPaymentMethod())
            .paymentTime(order.getPaymentTime())
            .shippingTime(order.getShippingTime())
            .deliveryTime(order.getDeliveryTime())
            .createTime(order.getCreateTime())
            .build();
    }
}

9.4 API接口设计

用户控制器

@RestController
@RequestMapping("/api/users")
@Api(tags = "用户管理")
@Validated
public class UserController {
    
    @Autowired
    private UserService userService;
    
    @PostMapping("/register")
    @ApiOperation("用户注册")
    public ResponseEntity<ApiResponse<UserDTO>> register(
            @Valid @RequestBody UserRegistrationDTO registrationDTO) {
        
        UserDTO user = userService.register(registrationDTO);
        return ResponseEntity.ok(ApiResponse.success(user, "注册成功"));
    }
    
    @PostMapping("/login")
    @ApiOperation("用户登录")
    public ResponseEntity<ApiResponse<LoginResponseDTO>> login(
            @Valid @RequestBody LoginRequestDTO loginRequest) {
        
        LoginResponseDTO response = userService.login(loginRequest);
        return ResponseEntity.ok(ApiResponse.success(response, "登录成功"));
    }
    
    @GetMapping("/profile")
    @ApiOperation("获取用户信息")
    @PreAuthorize("hasRole('USER')")
    public ResponseEntity<ApiResponse<UserDTO>> getProfile(
            @AuthenticationPrincipal UserPrincipal currentUser) {
        
        UserDTO user = userService.getUserById(currentUser.getId());
        return ResponseEntity.ok(ApiResponse.success(user));
    }
    
    @PutMapping("/profile")
    @ApiOperation("更新用户信息")
    @PreAuthorize("hasRole('USER')")
    public ResponseEntity<ApiResponse<UserDTO>> updateProfile(
            @AuthenticationPrincipal UserPrincipal currentUser,
            @Valid @RequestBody UserUpdateDTO updateDTO) {
        
        UserDTO user = userService.updateUser(currentUser.getId(), updateDTO);
        return ResponseEntity.ok(ApiResponse.success(user, "更新成功"));
    }
    
    @PostMapping("/change-password")
    @ApiOperation("修改密码")
    @PreAuthorize("hasRole('USER')")
    public ResponseEntity<ApiResponse<Void>> changePassword(
            @AuthenticationPrincipal UserPrincipal currentUser,
            @Valid @RequestBody ChangePasswordDTO changePasswordDTO) {
        
        userService.changePassword(currentUser.getId(), changePasswordDTO);
        return ResponseEntity.ok(ApiResponse.success(null, "密码修改成功"));
    }
    
    @GetMapping
    @ApiOperation("分页查询用户")
    @PreAuthorize("hasRole('ADMIN')")
    public ResponseEntity<ApiResponse<Page<UserDTO>>> getUsers(
            @ModelAttribute UserSearchCriteria criteria,
            @PageableDefault(size = 20, sort = "createTime", direction = Sort.Direction.DESC) Pageable pageable) {
        
        Page<UserDTO> users = userService.findUsers(criteria, pageable);
        return ResponseEntity.ok(ApiResponse.success(users));
    }
    
    @PostMapping("/upload-avatar")
    @ApiOperation("上传头像")
    @PreAuthorize("hasRole('USER')")
    public ResponseEntity<ApiResponse<String>> uploadAvatar(
            @AuthenticationPrincipal UserPrincipal currentUser,
            @RequestParam("file") MultipartFile file) {
        
        if (file.isEmpty()) {
            throw new BusinessException("请选择要上传的文件");
        }
        
        // 验证文件类型和大小
        validateImageFile(file);
        
        String avatarUrl = fileStorageService.uploadAvatar(currentUser.getId(), file);
        
        // 更新用户头像
        UserUpdateDTO updateDTO = new UserUpdateDTO();
        updateDTO.setAvatarUrl(avatarUrl);
        userService.updateUser(currentUser.getId(), updateDTO);
        
        return ResponseEntity.ok(ApiResponse.success(avatarUrl, "头像上传成功"));
    }
    
    private void validateImageFile(MultipartFile file) {
        // 检查文件大小(最大2MB)
        if (file.getSize() > 2 * 1024 * 1024) {
            throw new BusinessException("文件大小不能超过2MB");
        }
        
        // 检查文件类型
        String contentType = file.getContentType();
        if (!Arrays.asList("image/jpeg", "image/png", "image/gif").contains(contentType)) {
            throw new BusinessException("只支持JPG、PNG、GIF格式的图片");
        }
    }
}

商品控制器

@RestController
@RequestMapping("/api/products")
@Api(tags = "商品管理")
@Validated
public class ProductController {
    
    @Autowired
    private ProductService productService;
    
    @GetMapping
    @ApiOperation("搜索商品")
    public ResponseEntity<ApiResponse<Page<ProductDTO>>> searchProducts(
            @ModelAttribute ProductSearchCriteria criteria,
            @PageableDefault(size = 20, sort = "createTime", direction = Sort.Direction.DESC) Pageable pageable) {
        
        Page<ProductDTO> products = productService.searchProducts(criteria, pageable);
        return ResponseEntity.ok(ApiResponse.success(products));
    }
    
    @GetMapping("/{id}")
    @ApiOperation("获取商品详情")
    public ResponseEntity<ApiResponse<ProductDetailDTO>> getProductDetail(
            @PathVariable Long id) {
        
        ProductDetailDTO product = productService.getProductDetail(id);
        return ResponseEntity.ok(ApiResponse.success(product));
    }
    
    @PostMapping
    @ApiOperation("创建商品")
    @PreAuthorize("hasRole('ADMIN') or hasRole('MERCHANT')")
    public ResponseEntity<ApiResponse<ProductDTO>> createProduct(
            @Valid @RequestBody ProductCreateDTO createDTO) {
        
        ProductDTO product = productService.createProduct(createDTO);
        return ResponseEntity.status(HttpStatus.CREATED)
            .body(ApiResponse.success(product, "商品创建成功"));
    }
    
    @PutMapping("/{id}")
    @ApiOperation("更新商品")
    @PreAuthorize("hasRole('ADMIN') or hasRole('MERCHANT')")
    public ResponseEntity<ApiResponse<ProductDTO>> updateProduct(
            @PathVariable Long id,
            @Valid @RequestBody ProductUpdateDTO updateDTO) {
        
        ProductDTO product = productService.updateProduct(id, updateDTO);
        return ResponseEntity.ok(ApiResponse.success(product, "商品更新成功"));
    }
    
    @DeleteMapping("/{id}")
    @ApiOperation("删除商品")
    @PreAuthorize("hasRole('ADMIN')")
    public ResponseEntity<ApiResponse<Void>> deleteProduct(@PathVariable Long id) {
        productService.deleteProduct(id);
        return ResponseEntity.ok(ApiResponse.success(null, "商品删除成功"));
    }
    
    @PostMapping("/{id}/images")
    @ApiOperation("上传商品图片")
    @PreAuthorize("hasRole('ADMIN') or hasRole('MERCHANT')")
    public ResponseEntity<ApiResponse<List<String>>> uploadProductImages(
            @PathVariable Long id,
            @RequestParam("files") List<MultipartFile> files) {
        
        if (files.isEmpty()) {
            throw new BusinessException("请选择要上传的图片");
        }
        
        // 验证图片文件
        files.forEach(this::validateImageFile);
        
        List<String> imageUrls = productService.uploadProductImages(id, files);
        return ResponseEntity.ok(ApiResponse.success(imageUrls, "图片上传成功"));
    }
    
    @GetMapping("/categories")
    @ApiOperation("获取商品分类")
    public ResponseEntity<ApiResponse<List<CategoryDTO>>> getCategories() {
        List<CategoryDTO> categories = productService.getAllCategories();
        return ResponseEntity.ok(ApiResponse.success(categories));
    }
    
    @GetMapping("/brands")
    @ApiOperation("获取商品品牌")
    public ResponseEntity<ApiResponse<List<BrandDTO>>> getBrands() {
        List<BrandDTO> brands = productService.getAllBrands();
        return ResponseEntity.ok(ApiResponse.success(brands));
    }
    
    private void validateImageFile(MultipartFile file) {
        // 检查文件大小(最大5MB)
        if (file.getSize() > 5 * 1024 * 1024) {
            throw new BusinessException("图片大小不能超过5MB");
        }
        
        // 检查文件类型
        String contentType = file.getContentType();
        if (!Arrays.asList("image/jpeg", "image/png", "image/gif").contains(contentType)) {
            throw new BusinessException("只支持JPG、PNG、GIF格式的图片");
        }
    }
}

订单控制器

@RestController
@RequestMapping("/api/orders")
@Api(tags = "订单管理")
@Validated
public class OrderController {
    
    @Autowired
    private OrderService orderService;
    
    @PostMapping
    @ApiOperation("创建订单")
    @PreAuthorize("hasRole('USER')")
    public ResponseEntity<ApiResponse<OrderDTO>> createOrder(
            @AuthenticationPrincipal UserPrincipal currentUser,
            @Valid @RequestBody OrderCreateDTO createDTO) {
        
        OrderDTO order = orderService.createOrder(currentUser.getId(), createDTO);
        return ResponseEntity.status(HttpStatus.CREATED)
            .body(ApiResponse.success(order, "订单创建成功"));
    }
    
    @PostMapping("/{id}/pay")
    @ApiOperation("支付订单")
    @PreAuthorize("hasRole('USER')")
    public ResponseEntity<ApiResponse<PaymentResultDTO>> payOrder(
            @PathVariable Long id,
            @Valid @RequestBody PaymentRequestDTO paymentRequest) {
        
        PaymentResultDTO result = orderService.payOrder(id, paymentRequest);
        return ResponseEntity.ok(ApiResponse.success(result));
    }
    
    @PostMapping("/{id}/cancel")
    @ApiOperation("取消订单")
    @PreAuthorize("hasRole('USER')")
    public ResponseEntity<ApiResponse<Void>> cancelOrder(
            @PathVariable Long id,
            @RequestParam(required = false) String reason) {
        
        orderService.cancelOrder(id, reason);
        return ResponseEntity.ok(ApiResponse.success(null, "订单取消成功"));
    }
    
    @PostMapping("/{id}/ship")
    @ApiOperation("发货")
    @PreAuthorize("hasRole('ADMIN') or hasRole('MERCHANT')")
    public ResponseEntity<ApiResponse<Void>> shipOrder(
            @PathVariable Long id,
            @Valid @RequestBody ShippingInfoDTO shippingInfo) {
        
        orderService.shipOrder(id, shippingInfo);
        return ResponseEntity.ok(ApiResponse.success(null, "发货成功"));
    }
    
    @PostMapping("/{id}/confirm")
    @ApiOperation("确认收货")
    @PreAuthorize("hasRole('USER')")
    public ResponseEntity<ApiResponse<Void>> confirmDelivery(@PathVariable Long id) {
        orderService.confirmDelivery(id);
        return ResponseEntity.ok(ApiResponse.success(null, "确认收货成功"));
    }
    
    @GetMapping
    @ApiOperation("查询用户订单")
    @PreAuthorize("hasRole('USER')")
    public ResponseEntity<ApiResponse<Page<OrderDTO>>> getUserOrders(
            @AuthenticationPrincipal UserPrincipal currentUser,
            @RequestParam(required = false) OrderStatus status,
            @PageableDefault(size = 20, sort = "createTime", direction = Sort.Direction.DESC) Pageable pageable) {
        
        Page<OrderDTO> orders = orderService.getUserOrders(currentUser.getId(), status, pageable);
        return ResponseEntity.ok(ApiResponse.success(orders));
    }
    
    @GetMapping("/{id}")
    @ApiOperation("获取订单详情")
    @PreAuthorize("hasRole('USER')")
    public ResponseEntity<ApiResponse<OrderDetailDTO>> getOrderDetail(@PathVariable Long id) {
        OrderDetailDTO order = orderService.getOrderDetail(id);
        return ResponseEntity.ok(ApiResponse.success(order));
    }
    
    @GetMapping("/admin")
    @ApiOperation("管理员查询订单")
    @PreAuthorize("hasRole('ADMIN')")
    public ResponseEntity<ApiResponse<Page<OrderDTO>>> getOrdersForAdmin(
            @ModelAttribute OrderSearchCriteria criteria,
            @PageableDefault(size = 20, sort = "createTime", direction = Sort.Direction.DESC) Pageable pageable) {
        
        Page<OrderDTO> orders = orderService.findOrders(criteria, pageable);
        return ResponseEntity.ok(ApiResponse.success(orders));
    }
}

9.5 配置文件

主配置文件

# application.yml
spring:
  profiles:
    active: dev
  application:
    name: ecommerce-system
  
  # 数据库配置
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    hikari:
      maximum-pool-size: 20
      minimum-idle: 5
      idle-timeout: 300000
      connection-timeout: 20000
      max-lifetime: 1200000
  
  # JPA配置
  jpa:
    hibernate:
      ddl-auto: validate
    show-sql: false
    properties:
      hibernate:
        dialect: org.hibernate.dialect.MySQL8Dialect
        format_sql: true
        use_sql_comments: true
        jdbc:
          batch_size: 20
        order_inserts: true
        order_updates: true
  
  # Redis配置
  redis:
    timeout: 2000ms
    lettuce:
      pool:
        max-active: 8
        max-idle: 8
        min-idle: 0
        max-wait: -1ms
  
  # RabbitMQ配置
  rabbitmq:
    virtual-host: /
    connection-timeout: 15000
    template:
      retry:
        enabled: true
        initial-interval: 1000
        max-attempts: 3
        max-interval: 10000
        multiplier: 1.0
  
  # 文件上传配置
  servlet:
    multipart:
      max-file-size: 10MB
      max-request-size: 50MB
  
  # 缓存配置
  cache:
    type: redis
    redis:
      time-to-live: 3600000
      cache-null-values: false
  
  # Elasticsearch配置
  elasticsearch:
    rest:
      uris: http://localhost:9200
      connection-timeout: 5s
      read-timeout: 30s

# JWT配置
jwt:
  secret: mySecretKey
  expiration: 86400000 # 24小时
  refresh-expiration: 604800000 # 7天

# 文件存储配置
file:
  storage:
    type: minio
    minio:
      endpoint: http://localhost:9000
      bucket: ecommerce
      access-key: minioadmin
      secret-key: minioadmin

# 支付配置
payment:
  alipay:
    app-id: your-app-id
    private-key: your-private-key
    public-key: your-public-key
    gateway-url: https://openapi.alipay.com/gateway.do
  wechat:
    app-id: your-app-id
    mch-id: your-mch-id
    key: your-key
    cert-path: classpath:cert/apiclient_cert.p12

# 监控配置
management:
  endpoints:
    web:
      exposure:
        include: health,info,metrics,prometheus
  endpoint:
    health:
      show-details: always
  metrics:
    export:
      prometheus:
        enabled: true

# 日志配置
logging:
  level:
    com.example.ecommerce: INFO
    org.springframework.security: DEBUG
    org.hibernate.SQL: DEBUG
    org.hibernate.type.descriptor.sql.BasicBinder: TRACE
  pattern:
    console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"
    file: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"
  file:
    name: logs/ecommerce.log
    max-size: 100MB
    max-history: 30

开发环境配置

# application-dev.yml
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/ecommerce_dev?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
    username: root
    password: password
  
  redis:
    host: localhost
    port: 6379
    database: 0
  
  rabbitmq:
    host: localhost
    port: 5672
    username: guest
    password: guest
  
  jpa:
    show-sql: true
    hibernate:
      ddl-auto: update

# 开发环境日志级别
logging:
  level:
    root: INFO
    com.example.ecommerce: DEBUG
    org.springframework.web: DEBUG

# 开发环境文件存储
file:
  storage:
    type: local
    local:
      upload-dir: ./uploads
      base-url: http://localhost:8080/files

生产环境配置

# application-prod.yml
spring:
  datasource:
    url: jdbc:mysql://${DB_HOST:localhost}:${DB_PORT:3306}/${DB_NAME:ecommerce}?useUnicode=true&characterEncoding=utf8&useSSL=true&serverTimezone=Asia/Shanghai
    username: ${DB_USERNAME:ecommerce}
    password: ${DB_PASSWORD:password}
    hikari:
      maximum-pool-size: 50
      minimum-idle: 10
  
  redis:
    host: ${REDIS_HOST:localhost}
    port: ${REDIS_PORT:6379}
    password: ${REDIS_PASSWORD:}
    database: ${REDIS_DATABASE:0}
  
  rabbitmq:
    host: ${RABBITMQ_HOST:localhost}
    port: ${RABBITMQ_PORT:5672}
    username: ${RABBITMQ_USERNAME:guest}
    password: ${RABBITMQ_PASSWORD:guest}
  
  jpa:
    show-sql: false
    hibernate:
      ddl-auto: validate

# 生产环境日志级别
logging:
  level:
    root: WARN
    com.example.ecommerce: INFO
  file:
    name: /var/log/ecommerce/application.log

# 生产环境文件存储
file:
  storage:
    type: minio
    minio:
      endpoint: ${MINIO_ENDPOINT:http://localhost:9000}
      bucket: ${MINIO_BUCKET:ecommerce}
      access-key: ${MINIO_ACCESS_KEY:minioadmin}
      secret-key: ${MINIO_SECRET_KEY:minioadmin}

# JWT配置
jwt:
  secret: ${JWT_SECRET:myProductionSecretKey}
  expiration: ${JWT_EXPIRATION:86400000}

9.6 Docker部署配置

Dockerfile

# 多阶段构建
FROM maven:3.8.4-openjdk-17 AS builder

WORKDIR /app
COPY pom.xml .
RUN mvn dependency:go-offline -B

COPY src ./src
RUN mvn clean package -DskipTests

FROM openjdk:17-jre-slim

WORKDIR /app

# 安装必要的工具
RUN apt-get update && apt-get install -y \
    curl \
    && rm -rf /var/lib/apt/lists/*

# 创建应用用户
RUN groupadd -r appuser && useradd -r -g appuser appuser

# 复制应用文件
COPY --from=builder /app/target/ecommerce-system-*.jar app.jar

# 创建日志目录
RUN mkdir -p /var/log/ecommerce && chown -R appuser:appuser /var/log/ecommerce

# 切换到应用用户
USER appuser

# 健康检查
HEALTHCHECK --interval=30s --timeout=3s --start-period=60s --retries=3 \
  CMD curl -f http://localhost:8080/actuator/health || exit 1

# 暴露端口
EXPOSE 8080

# JVM参数优化
ENV JAVA_OPTS="-Xms512m -Xmx1024m -XX:+UseG1GC -XX:G1HeapRegionSize=16m -XX:+UseStringDeduplication"

# 启动应用
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]

Docker Compose配置

# docker-compose.yml
version: '3.8'

services:
  # 应用服务
  ecommerce-app:
    build: .
    ports:
      - "8080:8080"
    environment:
      - SPRING_PROFILES_ACTIVE=docker
      - DB_HOST=mysql
      - DB_NAME=ecommerce
      - DB_USERNAME=ecommerce
      - DB_PASSWORD=password
      - REDIS_HOST=redis
      - RABBITMQ_HOST=rabbitmq
      - ELASTICSEARCH_URIS=http://elasticsearch:9200
      - MINIO_ENDPOINT=http://minio:9000
    depends_on:
      - mysql
      - redis
      - rabbitmq
      - elasticsearch
      - minio
    volumes:
      - ./logs:/var/log/ecommerce
    networks:
      - ecommerce-network
    restart: unless-stopped
  
  # MySQL数据库
  mysql:
    image: mysql:8.0
    environment:
      - MYSQL_ROOT_PASSWORD=rootpassword
      - MYSQL_DATABASE=ecommerce
      - MYSQL_USER=ecommerce
      - MYSQL_PASSWORD=password
    ports:
      - "3306:3306"
    volumes:
      - mysql_data:/var/lib/mysql
      - ./docker/mysql/init.sql:/docker-entrypoint-initdb.d/init.sql
    networks:
      - ecommerce-network
    restart: unless-stopped
  
  # Redis缓存
  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"
    volumes:
      - redis_data:/data
    networks:
      - ecommerce-network
    restart: unless-stopped
  
  # RabbitMQ消息队列
  rabbitmq:
    image: rabbitmq:3-management
    environment:
      - RABBITMQ_DEFAULT_USER=admin
      - RABBITMQ_DEFAULT_PASS=admin
    ports:
      - "5672:5672"
      - "15672:15672"
    volumes:
      - rabbitmq_data:/var/lib/rabbitmq
    networks:
      - ecommerce-network
    restart: unless-stopped
  
  # Elasticsearch搜索引擎
  elasticsearch:
    image: elasticsearch:8.8.0
    environment:
      - discovery.type=single-node
      - xpack.security.enabled=false
      - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
    ports:
      - "9200:9200"
    volumes:
      - elasticsearch_data:/usr/share/elasticsearch/data
    networks:
      - ecommerce-network
    restart: unless-stopped
  
  # MinIO对象存储
  minio:
    image: minio/minio:latest
    environment:
      - MINIO_ROOT_USER=minioadmin
      - MINIO_ROOT_PASSWORD=minioadmin
    ports:
      - "9000:9000"
      - "9001:9001"
    volumes:
      - minio_data:/data
    command: server /data --console-address ":9001"
    networks:
      - ecommerce-network
    restart: unless-stopped
  
  # Nginx反向代理
  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./docker/nginx/nginx.conf:/etc/nginx/nginx.conf
      - ./docker/nginx/ssl:/etc/nginx/ssl
    depends_on:
      - ecommerce-app
    networks:
      - ecommerce-network
    restart: unless-stopped

volumes:
  mysql_data:
  redis_data:
  rabbitmq_data:
  elasticsearch_data:
  minio_data:

networks:
  ecommerce-network:
    driver: bridge

9.7 项目总结

技术亮点

  1. 微服务架构设计

    • 模块化设计,职责分离
    • 服务间通过消息队列异步通信
    • 支持水平扩展
  2. 高性能优化

    • Redis多级缓存策略
    • 数据库连接池优化
    • Elasticsearch全文搜索
    • 异步处理提升响应速度
  3. 高可用保障

    • 分布式事务处理
    • 熔断降级机制
    • 健康检查和监控
    • 容器化部署
  4. 安全防护

    • JWT身份认证
    • RBAC权限控制
    • 数据加密存储
    • API接口防护

业务特色

  1. 完整的电商流程

    • 用户注册登录
    • 商品浏览搜索
    • 购物车管理
    • 订单处理流程
    • 支付集成
    • 物流跟踪
  2. 智能化功能

    • 商品推荐算法
    • 库存预警机制
    • 价格监控
    • 用户行为分析
  3. 运营支持

    • 数据统计分析
    • 营销活动管理
    • 客户服务系统
    • 财务结算

扩展方向

  1. 技术扩展

    • 引入Spring Cloud微服务治理
    • 集成Kubernetes容器编排
    • 添加分布式链路追踪
    • 实现读写分离
  2. 业务扩展

    • 多租户支持
    • 国际化多语言
    • 移动端APP
    • 第三方平台集成
  3. 性能扩展

    • CDN内容分发
    • 数据库分库分表
    • 消息队列集群
    • 缓存集群

学习收获

通过本项目实战,你将掌握:

  1. Spring Boot核心技术

    • 自动配置原理
    • 依赖注入机制
    • AOP切面编程
    • 事务管理
  2. 企业级开发技能

    • 项目架构设计
    • 代码规范管理
    • 测试驱动开发
    • 持续集成部署
  3. 系统设计能力

    • 高并发处理
    • 分布式系统
    • 缓存策略
    • 安全防护
  4. 运维部署经验

    • Docker容器化
    • 监控告警
    • 性能调优
    • 故障排查

最佳实践建议

  1. 代码质量

    • 遵循SOLID原则
    • 编写单元测试
    • 代码审查机制
    • 文档完善
  2. 性能优化

    • 合理使用缓存
    • 数据库索引优化
    • 异步处理
    • 资源池化
  3. 安全防护

    • 输入验证
    • 权限控制
    • 数据加密
    • 审计日志
  4. 运维监控

    • 健康检查
    • 性能监控
    • 日志收集
    • 告警机制

本项目实战案例展示了Spring Boot在企业级应用开发中的强大能力,通过完整的电商系统实现,帮助开发者深入理解Spring Boot的核心技术和最佳实践,为实际项目开发提供有价值的参考。