项目代码组织方式
# 项目代码组织方式
电商 “订单创建” 为例(包含库存扣减、优惠券核销、支付单生成、消息通知等复杂联动),贴合真实开发场景。
com.example.ecommerce
├── controller # 控制层:接收请求、返回响应(不写业务逻辑)
│ └── OrderController.java
├── dto # 数据传输对象:入参/出参封装(隔离实体)
│ ├── request # 入参DTO
│ │ └── CreateOrderRequest.java
│ └── response # 出参DTO
│ └── OrderResponse.java
├── service # 服务层:核心业务逻辑(按领域拆分)
│ ├── OrderService.java # 订单核心服务(编排流程)
│ ├── InventoryService.java # 库存领域服务(专注库存操作)
│ ├── CouponService.java # 优惠券领域服务(专注优惠券操作)
│ ├── PaymentService.java # 支付领域服务(专注支付单生成)
│ └── NoticeService.java # 通知领域服务(专注消息推送)
├── domain # 领域模型:核心实体 + 领域规则(充血模型)
│ ├── Order.java # 订单实体(含订单状态流转规则)
│ ├── OrderItem.java # 订单项实体
│ ├── Inventory.java # 库存实体
│ └── Coupon.java # 优惠券实体
├── mapper # 持久层:数据库操作(仅CRUD,无业务)
│ ├── OrderMapper.java
│ ├── InventoryMapper.java
│ └── CouponMapper.java
├── exception # 异常处理:统一异常 + 业务异常
│ ├── BusinessException.java # 业务异常(如库存不足)
│ └── GlobalExceptionHandler.java # 全局异常拦截
├── enums # 枚举:统一状态/错误码(避免魔法值)
│ ├── OrderStatusEnum.java # 订单状态(待支付/已支付/已取消)
│ ├── CouponStatusEnum.java # 优惠券状态(未使用/已核销/已过期)
│ └── ErrorCodeEnum.java # 错误码(如INVENTORY_INSUFFICIENT)
└── util # 工具类:通用工具(如ID生成、日期处理)
└── IdGenerator.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# 核心代码实现(按流程拆解)
# 1. 枚举与异常(统一规则,避免混乱)
# (1)错误码枚举(ErrorCodeEnum.java)
public enum ErrorCodeEnum {
PARAM_ERROR(1001, "参数校验失败"),
INVENTORY_INSUFFICIENT(2001, "商品库存不足"),
COUPON_INVALID(3001, "优惠券无效或已过期"),
COUPON_NOT_OWNED(3002, "用户未持有该优惠券"),
ORDER_CREATE_FAIL(4001, "订单创建失败");
private final int code;
private final String message;
// 构造器 + getter
}
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
# (2)业务异常(BusinessException.java)
public class BusinessException extends RuntimeException {
private final int code;
private final String message;
// 构造器(接收ErrorCodeEnum)
public BusinessException(ErrorCodeEnum errorCode) {
this.code = errorCode.getCode();
this.message = errorCode.getMessage();
}
// getter
}
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
# (3)全局异常处理器(GlobalExceptionHandler.java)
@RestControllerAdvice
public class GlobalExceptionHandler {
// 拦截业务异常
@ExceptionHandler(BusinessException.class)
public Result<?> handleBusinessException(BusinessException e) {
return Result.fail(e.getCode(), e.getMessage());
}
// 拦截系统异常
@ExceptionHandler(Exception.class)
public Result<?> handleSystemException(Exception e) {
log.error("系统异常:", e);
return Result.fail(5000, "系统繁忙,请稍后重试");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 2. DTO(数据传输,隔离实体)
(1)创建订单入参 DTO(CreateOrderRequest.java)
@Data
@Validated // 参数校验注解
public class CreateOrderRequest {
@NotNull(message = "用户ID不能为空")
private Long userId;
@NotEmpty(message = "订单项不能为空")
private List<OrderItemRequest> orderItems; // 订单项列表(商品ID+数量)
private Long couponId; // 优惠券ID(可选)
@NotNull(message = "支付方式不能为空")
@EnumValid(targetEnum = PayTypeEnum.class, message = "支付方式无效")
private Integer payType; // 支付方式(1-微信,2-支付宝)
}
// 订单项入参子DTO
@Data
public class OrderItemRequest {
@NotNull(message = "商品ID不能为空")
private Long productId;
@NotNull(message = "购买数量不能为空")
@Min(value = 1, message = "购买数量不能小于1")
private Integer quantity;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 3. 领域模型(充血模型,封装核心规则)
订单实体(Order.java)
@Data
public class Order {
private Long id;
private String orderNo; // 订单编号(全局唯一)
private Long userId;
private BigDecimal totalAmount; // 总金额(商品总价)
private BigDecimal discountAmount; // 优惠金额(优惠券抵扣)
private BigDecimal payAmount; // 实付金额(total - discount)
private Integer orderStatus; // 订单状态(参考OrderStatusEnum)
private Integer payType;
private Date createTime;
private List<OrderItem> orderItems; // 订单项列表
// 领域方法:创建订单(封装订单初始化规则)
public static Order create(Long userId, List<OrderItem> orderItems, BigDecimal discountAmount, Integer payType) {
Order order = new Order();
order.setOrderNo(IdGenerator.generateOrderNo()); // 生成唯一订单号
order.setUserId(userId);
order.setOrderItems(orderItems);
// 计算总金额(遍历订单项求和)
BigDecimal totalAmount = orderItems.stream()
.map(item -> item.getProductPrice().multiply(new BigDecimal(item.getQuantity())))
.reduce(BigDecimal.ZERO, BigDecimal::add);
order.setTotalAmount(totalAmount);
order.setDiscountAmount(discountAmount);
order.setPayAmount(totalAmount.subtract(discountAmount));
order.setOrderStatus(OrderStatusEnum.PENDING_PAY.getCode()); // 初始状态:待支付
order.setPayType(payType);
order.setCreateTime(new Date());
return order;
}
// 领域方法:订单状态流转(封装状态变更规则,避免外部乱改)
public void updateStatus(Integer targetStatus) {
// 校验状态流转合法性(如待支付→已支付,不能直接待支付→已取消)
if (!OrderStatusEnum.isValidTransition(this.orderStatus, targetStatus)) {
throw new BusinessException(ErrorCodeEnum.ORDER_STATUS_ERROR);
}
this.orderStatus = targetStatus;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# 4. 服务层(核心业务逻辑,按领域拆分)
# (1)库存服务(InventoryService.java)- 专注库存操作
@Service
@Transactional(rollbackFor = Exception.class)
public class InventoryService {
@Autowired
private InventoryMapper inventoryMapper;
/**
* 扣减库存(支持批量扣减,原子操作)
*/
public void deductInventory(List<OrderItemRequest> orderItems) {
for (OrderItemRequest item : orderItems) {
Long productId = item.getProductId();
Integer quantity = item.getQuantity();
// 1. 查询商品库存(加行锁,避免并发问题)
Inventory inventory = inventoryMapper.selectByProductIdForUpdate(productId);
if (inventory == null) {
throw new BusinessException(ErrorCodeEnum.PRODUCT_NOT_EXIST);
}
// 2. 校验库存是否充足
if (inventory.getStock() < quantity) {
throw new BusinessException(ErrorCodeEnum.INVENTORY_INSUFFICIENT);
}
// 3. 扣减库存
inventory.setStock(inventory.getStock() - quantity);
inventoryMapper.updateById(inventory);
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# (2)优惠券服务(CouponService.java)- 专注优惠券操作
@Service
@Transactional(rollbackFor = Exception.class)
public class CouponService {
@Autowired
private CouponMapper couponMapper;
/**
* 核销优惠券(校验+状态更新)
* @return 优惠券抵扣金额
*/
public BigDecimal useCoupon(Long userId, Long couponId, BigDecimal totalAmount) {
if (couponId == null) {
return BigDecimal.ZERO; // 无优惠券,抵扣金额为0
}
// 1. 查询优惠券(用户ID+优惠券ID,确保用户持有)
Coupon coupon = couponMapper.selectByUserIdAndCouponId(userId, couponId);
if (coupon == null) {
throw new BusinessException(ErrorCodeEnum.COUPON_NOT_OWNED);
}
// 2. 校验优惠券状态(未使用+未过期)
if (!CouponStatusEnum.UNUSED.getCode().equals(coupon.getStatus())) {
throw new BusinessException(ErrorCodeEnum.COUPON_INVALID);
}
if (coupon.getExpireTime().before(new Date())) {
throw new BusinessException(ErrorCodeEnum.COUPON_INVALID);
}
// 3. 校验订单金额是否满足优惠券使用条件(如满100减20)
if (totalAmount.compareTo(coupon.getMinAmount()) < 0) {
throw new BusinessException(ErrorCodeEnum.COUPON_MIN_AMOUNT_NOT_MEET);
}
// 4. 核销优惠券(更新状态为已使用)
coupon.setStatus(CouponStatusEnum.USED.getCode());
coupon.setUseTime(new Date());
couponMapper.updateById(coupon);
// 5. 返回抵扣金额(不超过订单总金额)
return coupon.getDiscountAmount().min(totalAmount);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# (3)订单核心服务(OrderService.java)- 编排流程,不做具体实现
@Service
@Transactional(rollbackFor = Exception.class)
public class OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private InventoryService inventoryService;
@Autowired
private CouponService couponService;
@Autowired
private PaymentService paymentService;
@Autowired
private NoticeService noticeService;
/**
* 创建订单(核心流程编排)
* 流程:参数校验 → 扣减库存 → 核销优惠券 → 创建订单 → 生成支付单 → 发送通知
*/
public OrderResponse createOrder(CreateOrderRequest request) {
Long userId = request.getUserId();
List<OrderItemRequest> orderItems = request.getOrderItems();
Long couponId = request.getCouponId();
Integer payType = request.getPayType();
try {
// 1. 扣减库存(库存不足直接抛异常,事务回滚)
inventoryService.deductInventory(orderItems);
// 2. 构建订单项(转换为领域模型)
List<OrderItem> orderItemList = convertToOrderItems(orderItems);
// 3. 计算订单总金额(用于优惠券核销)
BigDecimal totalAmount = calculateTotalAmount(orderItemList);
// 4. 核销优惠券(获取抵扣金额)
BigDecimal discountAmount = couponService.useCoupon(userId, couponId, totalAmount);
// 5. 创建订单(调用领域模型的create方法,封装初始化规则)
Order order = Order.create(userId, orderItemList, discountAmount, payType);
// 6. 持久化订单(订单主表+订单项子表)
orderMapper.insert(order);
orderMapper.insertOrderItems(orderItemList, order.getId()); // 批量插入订单项
// 7. 生成支付单(调用支付服务,返回支付链接)
String payUrl = paymentService.createPayment(order);
// 8. 发送订单创建通知(异步,不阻塞主流程)
noticeService.sendOrderCreateNoticeAsync(order.getOrderNo(), userId);
// 9. 转换为出参DTO返回
return convertToOrderResponse(order, payUrl);
} catch (BusinessException e) {
// 业务异常:无需额外处理,全局异常处理器会返回错误信息
throw e;
} catch (Exception e) {
// 系统异常:记录日志,抛出统一错误
log.error("创建订单失败:userId={}, request={}", userId, JSON.toJSONString(request), e);
throw new BusinessException(ErrorCodeEnum.ORDER_CREATE_FAIL);
}
}
// 辅助方法:入参订单项 → 领域订单项(封装商品信息,如商品名称、价格)
private List<OrderItem> convertToOrderItems(List<OrderItemRequest> requestItems) {
// 实际开发中会查询商品表获取商品名称、价格,这里简化
return requestItems.stream().map(item -> {
OrderItem orderItem = new OrderItem();
orderItem.setProductId(item.getProductId());
orderItem.setProductName("商品" + item.getProductId()); // 模拟商品名称
orderItem.setProductPrice(new BigDecimal(100)); // 模拟商品单价
orderItem.setQuantity(item.getQuantity());
orderItem.setSubAmount(orderItem.getProductPrice().multiply(new BigDecimal(item.getQuantity())));
return orderItem;
}).collect(Collectors.toList());
}
// 辅助方法:计算订单总金额(遍历订单项求和)
private BigDecimal calculateTotalAmount(List<OrderItem> orderItems) {
return orderItems.stream()
.map(OrderItem::getSubAmount)
.reduce(BigDecimal.ZERO, BigDecimal::add);
}
// 辅助方法:领域订单 → 出参DTO
private OrderResponse convertToOrderResponse(Order order, String payUrl) {
OrderResponse response = new OrderResponse();
response.setOrderId(order.getId());
response.setOrderNo(order.getOrderNo());
response.setTotalAmount(order.getTotalAmount());
response.setPayAmount(order.getPayAmount());
response.setOrderStatus(order.getOrderStatus());
response.setPayUrl(payUrl);
return response;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
# 5. 控制层(接收请求,返回响应)
@RestController
@RequestMapping("/orders")
public class OrderController {
@Autowired
private OrderService orderService;
@PostMapping
public Result<OrderResponse> createOrder(@Valid @RequestBody CreateOrderRequest request) {
OrderResponse orderResponse = orderService.createOrder(request);
return Result.success(orderResponse);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
# 三、核心设计思想
- 分层解耦:
- 控制层(Controller):只做 “参数校验 + 调用服务 + 返回结果”,不碰业务;
- 服务层(Service):按领域拆分(库存、优惠券、订单),每个服务专注自己的领域,可复用(如库存服务还能被 “秒杀” 功能调用);
- 持久层(Mapper):只做数据库 CRUD,不包含任何业务逻辑。
- 单一职责:
- 库存服务只关心 “扣减库存、校验库存”,不关心优惠券怎么用;
- 优惠券服务只关心 “核销优惠券、计算抵扣金额”,不关心库存够不够;
- 订单服务只做 “流程编排”,把各个领域服务串起来,不做具体实现。
- 领域驱动(DDD):
- 领域模型(Order)封装核心规则(如订单创建、状态流转),而不是单纯的 POJO;
- 业务逻辑放在领域模型或领域服务中,而非控制层或持久层,更符合真实业务场景。
- 可扩展性:
- 若后续需要添加 “积分抵扣” 功能,只需新增PointService,在订单服务中添加调用逻辑,无需修改原有代码;
- 若支付方式新增 “银联”,只需扩展PayTypeEnum和PaymentService,订单流程无需改动。
- 可维护性:
- 异常统一处理,避免到处 try-catch;
- 枚举统一状态和错误码,避免魔法值(如不用1代表待支付,而是用OrderStatusEnum.PENDING_PAY);
- 代码按领域拆分,后续排查问题时,能快速定位到具体服务(如库存不足→直接看InventoryService)。
Last Updated: 2025/12/02, 15:43:10