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 项目总结
技术亮点
微服务架构设计
- 模块化设计,职责分离
- 服务间通过消息队列异步通信
- 支持水平扩展
高性能优化
- Redis多级缓存策略
- 数据库连接池优化
- Elasticsearch全文搜索
- 异步处理提升响应速度
高可用保障
- 分布式事务处理
- 熔断降级机制
- 健康检查和监控
- 容器化部署
安全防护
- JWT身份认证
- RBAC权限控制
- 数据加密存储
- API接口防护
业务特色
完整的电商流程
- 用户注册登录
- 商品浏览搜索
- 购物车管理
- 订单处理流程
- 支付集成
- 物流跟踪
智能化功能
- 商品推荐算法
- 库存预警机制
- 价格监控
- 用户行为分析
运营支持
- 数据统计分析
- 营销活动管理
- 客户服务系统
- 财务结算
扩展方向
技术扩展
- 引入Spring Cloud微服务治理
- 集成Kubernetes容器编排
- 添加分布式链路追踪
- 实现读写分离
业务扩展
- 多租户支持
- 国际化多语言
- 移动端APP
- 第三方平台集成
性能扩展
- CDN内容分发
- 数据库分库分表
- 消息队列集群
- 缓存集群
学习收获
通过本项目实战,你将掌握:
Spring Boot核心技术
- 自动配置原理
- 依赖注入机制
- AOP切面编程
- 事务管理
企业级开发技能
- 项目架构设计
- 代码规范管理
- 测试驱动开发
- 持续集成部署
系统设计能力
- 高并发处理
- 分布式系统
- 缓存策略
- 安全防护
运维部署经验
- Docker容器化
- 监控告警
- 性能调优
- 故障排查
最佳实践建议
代码质量
- 遵循SOLID原则
- 编写单元测试
- 代码审查机制
- 文档完善
性能优化
- 合理使用缓存
- 数据库索引优化
- 异步处理
- 资源池化
安全防护
- 输入验证
- 权限控制
- 数据加密
- 审计日志
运维监控
- 健康检查
- 性能监控
- 日志收集
- 告警机制
本项目实战案例展示了Spring Boot在企业级应用开发中的强大能力,通过完整的电商系统实现,帮助开发者深入理解Spring Boot的核心技术和最佳实践,为实际项目开发提供有价值的参考。