- 这是一个实际的项目例子,基于SpringBoot框架,按照DDD思想搭建,适合互联网场景下的轻量级应用。
- 保证代码结构清晰,充分运营设计模式
- 既满足功能需要,又不过度开发,而是具备一定的扩展性
java-web/
│── src/
│ ├── main/
│ │ ├── java/
│ │ │ ├── com
│ │ │ │ └── microwind # 组织名
│ │ │ │ │ └── knife # 项目名,若项目较大,可以再建立模块目录
│ │ │ │ │ ├── application/ # 应用层(协调领域逻辑,处理业务用例)
│ │ │ │ │ │ ├── services/ # 服务层,业务逻辑目录
│ │ │ │ │ │ │ ├── order/ # 订单应用服务
│ │ │ │ │ │ │ │ └── OrderService.java # 订单应用服务
│ │ │ │ │ │ │ ├── sign/ # 签名应用服务
│ │ │ │ │ │ │ │ ├── SignService.java # 签名核心服务
│ │ │ │ │ │ │ │ ├── SignValidationService.java # 签名验证服务
│ │ │ │ │ │ │ │ ├── DynamicSaltService.java # 动态盐值服务
│ │ │ │ │ │ │ │ └── strategy/ # 签名策略模式
│ │ │ │ │ │ │ │ ├── secretkey/ # 密钥获取策略
│ │ │ │ │ │ │ │ ├── dynamicsalt/ # 动态盐值策略
│ │ │ │ │ │ │ │ └── interfacesalt/ # 接口盐值策略
│ │ │ │ │ │ │ └── user/ # 用户应用服务
│ │ │ │ │ │ ├── dto/ # 数据传输对象(DTO)
│ │ │ │ │ │ │ ├── order/ # 订单数据交换对象
│ │ │ │ │ │ │ │ └── OrderDTO.java
│ │ │ │ │ │ │ ├── sign/ # 签名数据交换对象
│ │ │ │ │ │ │ │ ├── SignDTO.java # 签名DTO
│ │ │ │ │ │ │ │ ├── DynamicSaltDTO.java # 动态盐值DTO
│ │ │ │ │ │ │ │ └── SignMapper.java # 签名对象映射器
│ │ │ │ │ │ │ └── user/ # 用户数据交换对象
│ │ │ │ │ │ └── config/ # 应用配置
│ │ │ │ │ │ ├── SignConfig.java # 签名配置类
│ │ │ │ │ │ └── ApiAuthConfig.java # API认证配置
│ │ │ │ │ ├── domain/ # 领域层(核心业务逻辑和接口定义)
│ │ │ │ │ │ ├── order/ # 订单聚合(聚合根和业务逻辑)
│ │ │ │ │ │ │ └── Order.java # 订单实体(聚合根),包含核心业务逻辑
│ │ │ │ │ │ ├── sign/ # 签名领域
│ │ │ │ │ │ │ ├── Sign.java # 签名领域对象
│ │ │ │ │ │ │ ├── SignUserAuth.java # 签名用户授权
│ │ │ │ │ │ │ ├── SignUserInfo.java # 签名用户信息
│ │ │ │ │ │ │ ├── DynamicSalt.java # 动态盐值领域对象
│ │ │ │ │ │ │ └── SignDomainService.java # 签名领域服务
│ │ │ │ │ │ ├── user/ # 用户聚合
│ │ │ │ │ │ └── repository/ # 仓库接口
│ │ │ │ │ │ ├── Repository.java # [可选]仓库通用接口
│ │ │ │ │ │ ├── OrderRepository.java # 订单仓储接口
│ │ │ │ │ │ ├── SignRepository.java # 签名仓储接口
│ │ │ │ │ │ └── apiauth/ # API认证仓储接口
│ │ │ │ │ ├── infrastructure/ # 基础设施层(实现领域层定义的接口)
│ │ │ │ │ │ ├── repository/ # 仓储实现
│ │ │ │ │ │ │ ├── OrderRepositoryImpl.java # 订单仓储实现
│ │ │ │ │ │ │ └── SignRepositoryImpl.java # 签名仓储实现
│ │ │ │ │ │ ├── messaging/ # 消息队列实现
│ │ │ │ │ │ └── configuration/ # 基础配置(与外部系统相关)
│ │ │ │ │ │ ├── DatabaseConfig.java # [可选]数据库配置
│ │ │ │ │ │ └── ApiAuthDataSourceConfig.java # API认证数据源配置
│ │ │ │ │ ├── interfaces/ # 接口层(处理外部请求,如HTTP接口)
│ │ │ │ │ │ ├── controllers/ # RESTful API接口
│ │ │ │ │ │ │ ├── order/ # 订单相关控制器
│ │ │ │ │ │ │ │ └── OrderController.java
│ │ │ │ │ │ │ ├── sign/ # 签名相关控制器
│ │ │ │ │ │ │ │ ├── SignController.java # 签名API接口
│ │ │ │ │ │ │ │ └── SignPageController.java # 签名页面接口
│ │ │ │ │ │ │ ├── user/ # 用户相关控制器
│ │ │ │ │ │ │ └── apiauth/ # API认证相关控制器
│ │ │ │ │ │ ├── annotation/ # 注解定义
│ │ │ │ │ │ │ ├── RequireSign.java # 需要签名验证注解
│ │ │ │ │ │ │ ├── IgnoreSignHeader.java # 忽略签名验证注解
│ │ │ │ │ │ │ └── WithParams.java # 签名参数模式枚举
│ │ │ │ │ │ ├── advice/ # 切面增强
│ │ │ │ │ │ │ └── SignHeaderAdvice.java # 签名Header参数绑定
│ │ │ │ │ │ └── vo/ # 视图对象(View Object)
│ │ │ │ │ │ ├── order/ # 订单视图对象
│ │ │ │ │ │ ├── sign/ # 签名视图对象
│ │ │ │ │ │ │ ├── SignRequest.java # 签名请求VO
│ │ │ │ │ │ │ ├── SignResponse.java # 签名响应VO
│ │ │ │ │ │ │ ├── SignHeaderRequest.java # 签名Header请求VO
│ │ │ │ │ │ │ └── DynamicSaltRequest.java # 动态盐值请求VO
│ │ │ │ │ │ └── user/ # 用户视图对象
│ │ │ │ │ ├── middleware/ # 中间件(例如:鉴权、日志、拦截等)
│ │ │ │ │ │ ├── LoggingFilter.java # 日志中间件
│ │ │ │ │ │ ├── SignatureInterceptor.java # 签名验证拦截器
│ │ │ │ │ │ └── CachedBodyFilter.java # 请求体缓存过滤器
│ │ │ │ │ ├── common/ # 通用组件(通用的服务类)
│ │ │ │ │ │ └── ApiResponse.java # 统一响应对象
│ │ │ │ │ ├── config/ # 通用配置(管理服务器和应用信息)
│ │ │ │ │ │ └── WebConfig.java # 服务通用配置
│ │ │ │ │ ├── utils/ # 实用工具
│ │ │ │ │ │ ├── DataUtils.java # 日期工具
│ │ │ │ │ │ └── SignatureUtil.java # 签名工具类
│ │ │ │ │ └── Application.java # 应用启动类
│ │ │ └── resources/
│ │ │ │ └── application.yml # 配置文件
│ │ │ └── webapp/ # [可选]web运行目录,可模拟
│ │ └── test/
│ │ └── java/
│ │ ├── com
│ │ │ └── knife
│ │ │ ├── application/ # 应用层的测试
│ │ │ ├── interfaces/ # 接口层的测试
│ │ │ └── controllers/
│ │ │ └── sign/ # 签名接口测试
│── pom.xml # Maven 配置文件(如果使用 Maven)
│── build.gradle # Gradle 配置文件(如果使用 Gradle)- 领域模型主要分界面、应用、领域和基础这四层,每个项目都包括这四个部分。
- 如果项目属于中小型,则不再区分模块目录,相关文件按照层级放在一起,只是domain按模块区分目录。
- 如果项目较大,则可以按照模块再分别建立目录,每个模块包括这四层。
- 在微服务理念下,单个项目没有必要太大,过大既影响运行速度也影响开发效率,如果项目太大了,不如进行项目拆分。
本项目实现了完整的 API 签名验证机制,用于保护接口安全,防止数据篡改和重放攻击。
核心组件:
- 接口层:
@RequireSign、@IgnoreSignHeader注解,SignController API 接口 - 中间件层:SignatureInterceptor 拦截器,CachedBodyFilter 请求体缓存过滤器
- 应用层:SignService、DynamicSaltService、SignValidationService
- 策略模式:SecretKeyStrategy、DynamicSaltStrategy、InterfaceSaltStrategy
- 领域层:Sign、DynamicSalt、SignUserAuth 领域对象
- 工具类:SignatureUtil 签名工具类(支持 MD5、SHA1、SHA256、SM3)
详细文档:请参考 SIGN.md 了解签名机制的详细说明、配置方式和使用示例。
- service:封装应用业务逻辑,如订单创建、处理。
- dto:数据传输对象(DTO),用于应用层与外部传递数据。
- order:订单聚合(包含聚合根、实体、领域事件等)。
- repository:领域仓储接口,定义持久化操作。
- repository:基础设施层的仓储实现。
- messaging:消息队列或事件流的集成。
- configuration:基础设施配置,如数据库连接、API 密钥等。
- controllers:RESTful API 层,用于处理外部请求。
- routes:路由配置,映射 API 路径和控制器。
创建schema.sql,导入数据库结构。请提前准备好MySQL
CREATE DATABASE order_db;
use order_db;
-- orders表
CREATE TABLE `orders` (
`order_id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '订单ID',
`order_no` VARCHAR(50) NOT NULL COMMENT '订单编号',
`user_id` BIGINT UNSIGNED NOT NULL COMMENT '用户ID',
`order_name` VARCHAR(255) NOT NULL COMMENT '订单名称',
`amount` DECIMAL(10, 2) NOT NULL COMMENT '订单金额',
`status` VARCHAR(50) NOT NULL COMMENT '订单状态',
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`order_id`),
UNIQUE KEY `idx_order_no` (`order_no`),
KEY `idx_user_id` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单表';
-- order_item表
CREATE TABLE `order_item` (
`order_item_id` bigint unsigned NOT NULL AUTO_INCREMENT,
`price` double NOT NULL,
`product` varchar(255) DEFAULT NULL,
`quantity` int NOT NULL,
`order_id` bigint unsigned DEFAULT NULL,
PRIMARY KEY (`order_item_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;# 本例子依赖 JDK17 Tomcat10、Maven3.8、Lomok1.8、Mapstruct1.6等,详见pom.xml
# 相关版本可以降低或升级,需要同步调整依赖包和个别代码写法。
# 安装
$ mvn clean install -U
# 打包
$ mvn clean package -P prod -DskipTests
# 运行应用
$ java -jar target/spring-boot-order-1.0.0.jar
# 直接运行
$ mvn spring-boot:run
Starting server on http://localhost:8080 successfully.
# 访问验证
$ curl -X GET http://localhost:8080/api/orders
$ curl -X GET http://localhost:8080/api/orders/订单ID
# 运行测试
$ mvn testDDD 强调通过聚合(Aggregate)、实体(Entity)和值对象(Value Object)组织业务逻辑。
在 Java 中,使用 class 定义实体和值对象:
// 实体(Entity)
public class Order {
private int id;
private String name;
// getters and setters
}DDD 通常采用 分层架构,通用项目结构如下:
- 领域层(Domain Layer):核心业务逻辑,如
domain目录下的实体和聚合。
@Entity
@Table(name = "orders")
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ElementCollection
@CollectionTable(name = "order_items")
private List<OrderItem> items; // 值对象
// 核心业务方法
public void addItem(Product product, int quantity) {
this.items.add(new OrderItem(product, quantity));
}
}- 应用层(Application Layer):用例(Use Cases)和业务流程编排。
@Service
@Transactional
@RequiredArgsConstructor
public class OrderAppService {
private final OrderRepository orderRepository;
public OrderDTO createOrder(OrderCreateCommand command) {
Order order = new Order();
command.getItems().forEach(item ->
order.addItem(item.getProduct(), item.getQuantity()));
order = orderRepository.save(order);
return OrderDTO.fromDomain(order);
}
}- 基础设施层(Infrastructure Layer):数据库、缓存、外部 API 适配等。
// JPA 仓储实现(依赖 Spring Data JPA)
@Repository
@RequiredArgsConstructor
public class JpaOrderRepository implements OrderRepository {
private final OrderJpaRepository jpaRepository;
private final DomainEventPublisher eventPublisher;
@Override
public Order save(Order order) {
Order savedOrder = jpaRepository.save(order);
order.domainEvents().forEach(eventPublisher::publish);
return savedOrder;
}
}- 接口层(Interface Layer):提供 HTTP、RPC 或 CLI 接口。
// REST 控制器
@RestController
@RequestMapping("/api/orders")
@RequiredArgsConstructor
public class OrderController {
private final OrderAppService orderAppService;
@PostMapping
public ResponseEntity<OrderDTO> createOrder(@Valid @RequestBody OrderRequest request) {
OrderDTO dto = orderAppService.createOrder(request.toCommand());
return ResponseEntity.created(URI.create("/orders/" + dto.getId())).body(dto);
}
}领域层不应直接依赖基础设施,而是通过 接口(Interface) 进行依赖倒置。例如: OrderRepository 是领域层定义的接口,OrderRepositoryImpl 是基础设施层的具体实现。
// 领域层接口
public interface OrderRepository {
Order save(Order order);
Optional<Order> findById(int id);
}// 基础设施层实现
@Repository
public class OrderRepositoryImpl implements OrderRepository {
private final JpaRepository<Order, Integer> jpaRepository;
@Override
public Order save(Order order) {
return jpaRepository.save(order);
}
@Override
public Optional<Order> findById(int id) {
return jpaRepository.findById(id);
}
}聚合根(Aggregate Root)管理整个聚合的生命周期:
例如,Order 是聚合根,OrderItem 是子聚合。Order 聚合控制子聚合 OrderItem 的生命周期并确保业务一致性。
// 订单实体(聚合根)
public class Order {
private int id;
private List<OrderItem> items;
private final String status;
private final LocalDateTime createdAt;
private LocalDateTime updatedAt;
public Order(int id, List<OrderItem> items) {
this.id = id;
this.items = items;
this.status = "PENDING";
this.createdAt = LocalDateTime.now();
}
public void addItem(OrderItem item) {
this.items.add(item);
}
// 其他业务逻辑方法
}订单项是 OrderItem,可以视为子聚合:
public class OrderItem {
private int id;
private String productName;
private int quantity;
private BigDecimal price;
// 构造方法和其他方法
}应用服务封装领域逻辑,避免外部直接操作领域对象:
// 业务流程处理
@Service
public class OrderService {
private final OrderRepository orderRepository;
public OrderService(OrderRepository orderRepository) {
this.orderRepository = orderRepository;
}
public Order createOrder(int orderId, List<OrderItem> items) {
Order order = new Order(orderId, items, "Pending");
return orderRepository.save(order);
}
}使用 领域事件(Domain Events) 进行解耦,在 Java 语言中可通过 Channel 或 Pub/Sub 实现:
public class OrderCreatedEvent {
private final int orderId;
public OrderCreatedEvent(int orderId) {
this.orderId = orderId;
}
// getters
}
@Service
public class EventPublisher {
@Autowired
private ApplicationEventPublisher eventPublisher;
public void publish(OrderCreatedEvent event) {
eventPublisher.publishEvent(event);
}
}DDD 可结合 CQRS(Command Query Responsibility Segregation),在 Java 中可用 命令(Command) 处理变更操作,用 查询(Query) 处理数据读取:
public class CreateOrderCommand {
private final int orderId;
private final List<OrderItem> items;
public CreateOrderCommand(int orderId, List<OrderItem> items) {
this.orderId = orderId;
this.items = items;
}
// getters
}Java DDD 实践强调:
- 使用
class作为领域模型(Entity、Value Object、Aggregate) - 依赖倒置,通过接口定义领域层,不直接依赖基础设施
- 使用应用服务
(Service)封装业务逻辑,避免外部直接操作领域对象 - 事件驱动,通过
ApplicationEventPublisher进行解耦 - 结合
CQRS,实现命令和查询分离,提高可扩展性