周振林 周振林
首页
  • 前端文章

    • HTML
    • CSS
    • Tailwind CSS (opens new window)
    • JavaScript
    • Vue3
    • 其他
  • Spring
  • SpringMVC
  • Mybatis
  • 安装教程
  • 其他教程
  • 基础
  • 虚拟化
  • Docker
  • OpenStack
  • 心情杂货
关于
收藏

周振林

IT界的小学生
首页
  • 前端文章

    • HTML
    • CSS
    • Tailwind CSS (opens new window)
    • JavaScript
    • Vue3
    • 其他
  • Spring
  • SpringMVC
  • Mybatis
  • 安装教程
  • 其他教程
  • 基础
  • 虚拟化
  • Docker
  • OpenStack
  • 心情杂货
关于
收藏
  • Spring

    • 接口设计规范
    • Spring IoC
    • Spring AOP
    • SpringBoot基础
    • SpringBoot 常用注解
    • SpringBoot Conditional注解
    • SpringBoot 组件注入方式
    • SpringBoot 组件Bean生命周期
    • SpringBoot自动配置原理
    • SpringBoot自定义Starter
    • SpringBoot其他功能
    • SpringBoot JdbcTemplate
    • SpringBoot事务
    • SpringBoot文档
    • SpringBoot Starter和BOM区别
    • SpringBoot集成Modbus实现设备
    • 查询优化N+1
    • Response设置响应编码
    • Thymeleaf教程
    • Maven教程
    • Tree工具类,轻松搞定树结构
    • 项目代码组织方式
      • 项目代码组织方式
        • 核心代码实现(按流程拆解)
        • 1. 枚举与异常(统一规则,避免混乱)
        • (1)错误码枚举(ErrorCodeEnum.java)
        • (2)业务异常(BusinessException.java)
        • (3)全局异常处理器(GlobalExceptionHandler.java)
        • 2. DTO(数据传输,隔离实体)
        • 3. 领域模型(充血模型,封装核心规则)
        • 4. 服务层(核心业务逻辑,按领域拆分)
        • (1)库存服务(InventoryService.java)- 专注库存操作
        • (2)优惠券服务(CouponService.java)- 专注优惠券操作
        • (3)订单核心服务(OrderService.java)- 编排流程,不做具体实现
        • 5. 控制层(接收请求,返回响应)
        • 三、核心设计思想
  • SpringMVC

  • Mybatis

  • 安装教程

  • 其他教程

  • 后端
  • Spring
周振林
2025-12-02
目录

项目代码组织方式

# 项目代码组织方式

电商 “订单创建” 为例(包含库存扣减、优惠券核销、支付单生成、消息通知等复杂联动),贴合真实开发场景。

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

# 核心代码实现(按流程拆解)

# 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)业务异常(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

# (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. 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

# 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

# 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)优惠券服务(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

# (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

# 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

# 三、核心设计思想

  1. 分层解耦:
  • 控制层(Controller):只做 “参数校验 + 调用服务 + 返回结果”,不碰业务;
  • 服务层(Service):按领域拆分(库存、优惠券、订单),每个服务专注自己的领域,可复用(如库存服务还能被 “秒杀” 功能调用);
  • 持久层(Mapper):只做数据库 CRUD,不包含任何业务逻辑。
  1. 单一职责:
  • 库存服务只关心 “扣减库存、校验库存”,不关心优惠券怎么用;
  • 优惠券服务只关心 “核销优惠券、计算抵扣金额”,不关心库存够不够;
  • 订单服务只做 “流程编排”,把各个领域服务串起来,不做具体实现。
  1. 领域驱动(DDD):
  • 领域模型(Order)封装核心规则(如订单创建、状态流转),而不是单纯的 POJO;
  • 业务逻辑放在领域模型或领域服务中,而非控制层或持久层,更符合真实业务场景。
  1. 可扩展性:
  • 若后续需要添加 “积分抵扣” 功能,只需新增PointService,在订单服务中添加调用逻辑,无需修改原有代码;
  • 若支付方式新增 “银联”,只需扩展PayTypeEnum和PaymentService,订单流程无需改动。
  1. 可维护性:
  • 异常统一处理,避免到处 try-catch;
  • 枚举统一状态和错误码,避免魔法值(如不用1代表待支付,而是用OrderStatusEnum.PENDING_PAY);
  • 代码按领域拆分,后续排查问题时,能快速定位到具体服务(如库存不足→直接看InventoryService)。
Last Updated: 2025/12/02, 15:43:10
Tree工具类,轻松搞定树结构
SpringMVC路径映射

← Tree工具类,轻松搞定树结构 SpringMVC路径映射→

最近更新
01
查询优化N+1
12-02
02
Mybatis分页插件
12-02
03
Mybatis动态SQL
12-02
更多文章>
Copyright © 2019-2025 鲁ICP备19032096号-1
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式
×