微服务分层架构技术解析:从 API 到数据访问的全方位探秘
前言
在当今复杂多变的软件开发领域,微服务架构已成为构建大型分布式系统的核心范式。它通过将系统分解为一组小型、独立且高度内聚的服务,实现了模块化开发、独立部署与扩展,从而显著提升了系统的可维护性、可扩展性和灵活性。本系列文章深入剖析微服务架构的各个关键层面,从 API 层的设计原则与最佳实践,到业务逻辑层的服务解耦与协作模式,再到数据访问层的高效数据处理与持久化策略,全方位揭示微服务系统的设计精髓与技术细节。无论是初涉微服务的开发新手,还是渴望深化架构理解的技术专家,都能从本文中获取宝贵的知识与洞见,助力您在微服务架构的实践中游刃有余,构建出高效、稳定、可扩展的软件系统。
api 层
该代码片段中的两个 Lombok 注解在订单状态枚举类中起到以下核心作用:
@RequiredArgsConstructor // 自动生成包含所有 final 字段的构造函数
@Getter // 自动生成所有字段的 getter 方法
具体作用说明:
- @RequiredArgsConstructor
-
-
自动生成包含
type
和name
两个字段的构造函数 -
使枚举实例的初始化更简洁(示例):
WAITING_FOR_PAYMENT(0, "待支付") // 实际调用构造函数
-
- @Getter
-
-
自动生成
getType()
和getName()
方法 -
在业务代码中可直接调用(示例):
OrderOrderStatusEnum status = ...;
Integer type = status.getType(); // 获取状态码
String name = status.getName(); // 获取中文描述
-
设计亮点:
- 通过注解替代样板代码,使枚举类更简洁
- 便于与其他模块交互(如状态码与数据库字段映射)
- 提升代码可维护性(新增状态只需添加枚举项,无需修改方法)
注意事项:
- 枚举构造函数默认是
private
,Lombok 在此场景下会自动处理访问权限 - 枚举字段必须声明为
final
才能配合@RequiredArgsConstructor
使用
public static final Integer[] ARRAYS = Arrays.stream(values()).map(OrderOrderTypeEnum::getType).toArray(Integer[]::new);
这段代码的作用是生成包含所有订单类型枚举值的数组,以下是具体分析:
public static final Integer[] ARRAYS = Arrays.stream(values()) // 获取所有枚举实例
.map(OrderOrderTypeEnum::getType) // 提取 type 字段值
.toArray(Integer[]::new); // 转换为 Integer 数组
三个核心作用:
-
统一枚举值管理
自动收集所有枚举项的type
值(0/1/2/3),避免手动维护数组 -
支持框架接口实现
为ArrayValuable
接口提供数据源,实现以下方法时会直接返回该数组:public Integer[] array() {
return ARRAYS; // 返回 [0,1,2,3]
} -
业务应用场景
// 前端下拉框选项
List<CommonOptionRespVO> options = Arrays.stream(OrderOrderTypeEnum.ARRAYS)
.map(type -> new CommonOptionRespVO(type, getNameByType(type)))
.collect(Collectors.toList());// 数据库查询校验
if (!ArrayUtils.contains(OrderOrderTypeEnum.ARRAYS, queryType)) {
throw exception(ORDER_TYPE_NOT_EXISTS);
}
设计优势:
- 自动同步:新增枚举项时数组自动更新
- 类型安全:避免硬编码数字带来的错误
- 统一出口:所有类型值通过唯一入口获取
注意点:枚举项 Route 的命名建议改为全大写 ROUTE 保持命名规范
apiconstants 详细分析
我将从 API 配置管理的角度解析该类的设计意图和实际应用:
核心作用分析
public class ApiConstants {
// 服务注册与发现标识(需与 application.yml 中 spring.application.name 一致)
public static final String NAME = "ticket-server";
// RPC 接口统一前缀(示例:最终路径为 /rpc/api/resource/xxx)
public static final String PREFIX = RpcConstants.RPC_API_PREFIX + "/resource";
// 接口版本标识(用于灰度发布)
public static final String VERSION = "1.0.0";
}
各常量使用场景
-
NAME 常量
应用配置示例
spring:
application:
name: #{ApiConstants.NAME} # 实际会被替换为 ticket-server
cloud:
nacos:
discovery:
service: ${spring.application.name} -
PREFIX 常量
// 接口路径统一管理示例
@RestController
@RequestMapping(ApiConstants.PREFIX + "/order")
public class TicketOrderController {
@GetMapping("/get") // 完整路径:/rpc/api/resource/order/get
public CommonResult<OrderVO> getOrder() { ... }
} -
VERSION 常量
// Feign 客户端版本控制
@FeignClient(name = ApiConstants.NAME, url = "${yudao.service.url}",
configuration = FeignConfiguration.class,
contextId = "ticketResourceApi")
public interface TicketResourceApi {@GetMapping(value = "/version", headers = "API-VERSION=" + ApiConstants.VERSION) String getVersion();
}
设计优势
- 集中管理:避免配置散落在多处
- 命名规范:统一服务标识和接口路径
- 可维护性:修改服务名只需改动常量值
- 可读性:常量名称自解释(优于直接使用字符串)
扩展建议
如需新增配置项可参考以下模式:
// 新增示例
public static final String CACHE_PREFIX = "ticket:resource";
public static final int MAX_RETRY_TIMES = 3;
DictTypeConstants.java
我将从字典管理的角度解析该接口的设计意图和实际应用:
核心作用分析
public interface DictTypeConstants {
// 订单模块 - 订单类型(对应字典表的数据)
String ORDER_MODULE_ORDER_TYPE = "order_module_order_type";
// ...其他字典类型常量
}
具体应用场景
-
后端数据查询
// 查询订单类型字典数据
List<DictData> orderTypes = dictDataService.getDictData(DictTypeConstants.ORDER_MODULE_ORDER_TYPE); -
前端选项渲染
// 前端通过接口获取字典选项
@GetMapping("/dict-data/list")
public CommonResult<List<DictDataRespVO>> listDictData(@RequestParam String dictType) {
// 例如传入 ORDER_MODULE_PAYMENT_METHOD 获取支付方式选项
} -
数据校验
// 校验支付方式是否合法
if (!DictTypeConstants.ORDER_MODULE_PAYMENT_METHOD.equals(paymentMethod.getDictType())) {
throw exception(PAYMENT_METHOD_INVALID);
}
数据结构示例
字典表数据示例 :
```text
dict_type | dict_value | dict_label
order_module_order_type | 0 | 普通订单
order_module_order_type | 1 | 团购订单
order_module_order_type | 2 | 秒杀订单
### 设计优势
1. **统一管理**:避免字典类型硬编码
2. **强类型约束**:编译时检查替代字符串拼写错误
3. **模块化隔离**:通过模块前缀避免字典类型冲突
4. **可维护性**:新增字典类型只需添加常量
### 扩展建议
如需新增字典类型可参考以下模式:
```java
// 新增退票原因字典类型
String ORDER_MODULE_REFUND_REASON = "order_module_refund_reason";
该设计通过预定义字典类型常量,实现了业务字典的标准化管理,是典型的高可维护性解决方案。
Biz 层
重点看服务解耦那块分层
此处为语雀内容卡片,点击链接查看:https://www.yuque.com/hunshazhao-kxzhf/vnt2dy/xe9tc961usm4cl76
biz层中详细每个分层的分析
1. 框架层(Framework Layer)
- 功能与技术点配置:作为整个系统的基础,需先搭建好 Spring Security、Feign、JWT 等配置,为系统提供安全和通信支持。
-
- Spring Security 配置:定义安全策略,如基于角色的访问控制,配置用户认证和授权方式。
- Feign 客户端配置:设置超时时间、重试机制等,确保服务间远程调用的稳定性和可靠性。
- JWT 配置:实现用户令牌的生成和解析,确保用户身份在无状态分布式环境中安全传递。
- 联动方式实现:通过过滤器或拦截器检查请求中的认证信息,确保请求的合法性和安全性。

分模块解析 ticket 模块的 framework 框架层设计,首先分析安全配置部分:
1. 安全配置模块 (security)
@Configuration("resourceSecurityConfiguration")
public class SecurityConfiguration {
@Bean("resourceAuthorizeRequestsCustomizer")
public AuthorizeRequestsCustomizer authorizeRequestsCustomizer() {
return new AuthorizeRequestsCustomizer() {
@Override
public void customize(registry) {
// Swagger 文档
registry.requestMatchers("/v3/api-docs/**").permitAll()
.requestMatchers("/swagger-ui.html").permitAll()
// SpringBoot Actuator
registry.requestMatchers("/actuator/**").permitAll()
// RPC 接口
registry.requestMatchers(ApiConstants.PREFIX + "/**").authenticated();
}
};
}
}
核心作用:
- 定义模块专属安全策略(与其他模块隔离)
- 开放 Swagger/Actuator 端点无需认证
- 限制
/resource
前缀接口必须认证 - 通过
@Configuration("resourceSecurityConfiguration")
实现多模块安全配置共存
2. RPC 配置模块 (rpc)
@Configuration(proxyBeanMethods = false)
@EnableFeignClients(clients = AdminUserApi.class)
public class RpcConfiguration {
}
设计特点:
- 精准控制 Feign 客户端扫描范围(避免全包扫描)
- 集成系统模块的用户服务 API(AdminUserApi)
proxyBeanMethods = false
提升启动速度
3. 常量配置模块
public class ApiConstants {
public static final String NAME = "ticket-server";
public static final String PREFIX = RpcConstants.RPC_API_PREFIX + "/resource";
}
跨模块应用:
- 服务注册发现(NAME 对应 Nacos 服务名)
- 安全配置(PREFIX 用于接口权限控制)
- Feign 客户端标识(name = ApiConstants.NAME)
4. 数据翻译模块 (easy-trans)
通过 application.yaml 配置实现全局翻译:
easy-trans:
is-enable-global: true # 启用全局翻译(拦截所有 SpringMVC ResponseBody)
业务应用示例 :
public class TicketOrderInfoRespVO {
@Trans(type = TransType.DICTIONARY, key = DictTypeConstants.ORDER_MODULE_ORDER_TYPE)
private Integer orderType; // 自动转换为 "普通订单"/"团购订单"
}
核心价值:
- 自动将数据字典值转换为前端可读标签
- 减少 Controller 层的转换代码
- 支持 Redis 缓存字典数据提升性能
5. 缓存策略模块
Redis 配置示例:
spring:
data:
redis:
host: 127.0.0.1
port: 6379
password: 282312
database: 0
缓存应用模式:
@Service
public class TicketOrderServiceImpl implements TicketOrderService {
// 方法结果缓存
@Cacheable(value = "ticketOrder", key = "#id")
public TicketOrderInfoDO getOrder(Long id) {
return mapper.selectById(id);
}
// 缓存更新
@CacheEvict(value = "ticketOrder", key = "#order.id")
public void updateOrder(TicketOrderInfoDO order) {
mapper.updateById(order);
}
}
6. 监控模块
通过 starter 集成多种监控能力:
<dependency>
<groupId>cn.iocoder.cloud</groupId>
<artifactId>yudao-spring-boot-starter-monitor</artifactId>
</dependency>
监控维度:
-
Spring Boot Actuator 端点(健康检查、metrics)
-
Prometheus 格式指标采集
-
SkyWalking GRPC 日志收集(分布式追踪)
<appender name="GRPC" class="org.apache.skywalking.apm.toolkit.log.logback.v1.x.log.GRPCLogClientAppender"> <encoder> <pattern>${PATTERN_DEFAULT}</pattern> </encoder> </appender>
7. 定时任务模块
配置示例(基于 XXL-JOB):
xxl:
job:
admin:
addresses: http://127.0.0.1:9080/xxl-job-admin
executor:
appname: ticket-server
port: 9998
任务处理器示例:
@JobHandler(name = "orderStatusSyncJob")
@Component
public class OrderStatusSyncJob extends IJobHandler {
@XxlJob("syncOrderStatus")
public ReturnT<String> execute(String param) {
// 同步第三方订单状态
return SUCCESS;
}
}
8. 消息队列模块
配置示例(基于 RocketMQ):
rocketmq:
name-server: 127.0.0.1:9876
producer:
group: ticket-group
消息监听示例:
@RocketMQMessageListener(
topic = "ORDER_TOPIC",
consumerGroup = "ticket-order-group",
selectorExpression = "order_create"
)
@Component
public class OrderCreateConsumer implements RocketMQListener<OrderCreateMessage> {
@Override
public void onMessage(OrderCreateMessage message) {
// 处理订单创建消息
}
}
9. 数据权限模块
实现原理(基于 MyBatis 拦截器):
@Bean
public DataPermissionDatabaseInterceptor dataPermissionDatabaseInterceptor(
@Value("${central.data-permission.enable:true}") Boolean enable) {
return new DataPermissionDatabaseInterceptor(enable);
}
业务应用示例 :
@DataPermission(
deptColumn = "create_dept", // 部门字段
userIdColumn = "create_user" // 用户字段
)
public class TicketOrderInfoPageReqVO extends PageParam {
// 查询条件自动追加数据权限过滤
}
SQL 自动改写效果:
/* 原始 SQL */
SELECT * FROM order_ticket_order_info WHERE cenic_spots_id = ?
/* 改写后 SQL */
SELECT * FROM order_ticket_order_info
WHERE cenic_spots_id = ?
AND create_dept IN (当前用户部门及子部门)
AND create_user = 当前用户ID
10. 工作流模块
流程定义示例(退款审批流程):
<process id="refundAudit" name="退款审批流程">
<startEvent id="start"/>
<userTask id="leaderAudit" name="主管审批"/>
<exclusiveGateway id="exclusiveGw"/>
<sequenceFlow id="flow1" sourceRef="start" targetRef="leaderAudit"/>
<sequenceFlow id="flow2" sourceRef="leaderAudit" targetRef="exclusiveGw"/>
<sequenceFlow id="flow3" sourceRef="exclusiveGw" targetRef="end">
<conditionExpression xsi:type="tFormalExpression">
${refundAmount <= 1000}
</conditionExpression>
</sequenceFlow>
</process>
业务集成代码:
@Transactional(rollbackFor = Exception.class)
public void applyRefund(Long refundId) {
// 启动流程实例
BpmProcessInstanceCreateReqDTO reqDTO = new BpmProcessInstanceCreateReqDTO();
reqDTO.setProcessDefinitionKey("refundAudit");
reqDTO.setBusinessKey(String.valueOf(refundId));
String processInstanceId = bpmProcessInstanceApi.createProcessInstance(reqDTO);
// 更新业务记录状态
refundMapper.updateProcessInstanceIdById(refundId, processInstanceId);
}
2. 数据访问层(Data Access Layer - DAL)
- 功能与技术点实现:负责数据存储与访问,包含 DataObject、MySQL 和 Redis 三部分。
-
- 实体类定义:使用 MyBatis Plus 定义数据库实体类(DataObject),映射数据库表结构。
- Mapper 接口与 XML 文件编写:编写 Mapper 接口和对应的 XML 文件,定义 SQL 语句,实现数据库操作。
- RedisTemplate 配置:配置 RedisTemplate,实现与 Redis 的交互,支持多种数据结构存储查询。
- 事务管理配置:结合 Spring 的事务管理器,确保数据操作一致性。
- 联动方式实现:通过 MyBatis Plus 的 Mapper 接口与 MySQL 数据库交互,执行 SQL 语句完成数据操作。对于 Redis 缓存操作,使用 RedisTemplate 进行数据的存取和过期设置。在数据查询时,优先从 Redis 缓存中获取数据,若缓存不存在则查询 MySQL 数据库,并将查询结果缓存到 Redis 中。

我将从数据访问层的角度,解析 ticket 模块的 DAL 结构设计:
核心结构概览
central-module-ticket-biz/src/main/java/cn/iocoder/central/module/ticket/dal/
├─ dataobject/ # 持久化对象
│ ├─ ticketinfo/ # 门票核心业务DO
│ │ └─ TicketOrderInfoDO.java
├─ mysql/ # MyBatis Mapper 接口
│ ├─ ticketinfo/
│ │ └─ TicketOrderInfoMapper.java
└─ redis/ # Redis 缓存操作(当前为占位设计)
核心组件解析
1. 数据对象(DataObject)
@TableName("order_ticket_order_info")
public class TicketOrderInfoDO extends BaseDO {
@TableId
private Long id; // 主键
private String cenicSpotsId; // 景区编号
private String ticketName; // 门票名称
// ...其他 40+ 业务字段
}
作用:
- 数据库表结构映射(@TableName 指定表名)
- 继承 BaseDO 获得创建时间、更新时间等公共字段
- 使用 MyBatis-Plus 注解实现 ORM 映射
XML 格式的代码符号引用为您分析 的关键设计:
@TableName("order_ticket_order_info") // ① 表名映射
@KeySequence("order_ticket_order_info_seq") // ② 多数据库主键策略
@Data // ③ Lombok 数据对象简化
@EqualsAndHashCode(callSuper = true) // ④ 包含父类字段的相等性判断
@ToString(callSuper = true) // ⑤ 包含父类字段的字符串表示
@Builder // ⑥ 建造者模式支持
@NoArgsConstructor // ⑦ MyBatis 反射需要无参构造
@AllArgsConstructor // ⑧ 全字段构造
public class TicketOrderInfoDO extends BaseDO { // ⑨ 继承审计字段
@TableId // ⑩ 主键标识
private Long id;
private String cenicSpotsId; // ⑪ 业务字段标准命名(景区编码)
关键设计解析
-
多数据库支持
@KeySequence
实现 Oracle/PostgreSQL 等数据库的序列主键,MySQL 自动忽略(通过application.yaml
的mybatis-plus.global-config.db-config.id-type
配置自增策略) -
审计字段继承
继承的 包含:public abstract class BaseDO {
private LocalDateTime createTime; // 创建时间(DB 自动填充)
private LocalDateTime updateTime; // 更新时间(DB 自动填充)
private Long creator; // 创建人(通过线程上下文自动填充)
private Long updater; // 更新人(通过线程上下文自动填充)
} -
Lombok 最佳实践
callSuper = true
确保审计字段参与对象比较和日志输出,避免以下问题:TicketOrderInfoDO order1 = TicketOrderInfoDO.builder().id(1L).build();
TicketOrderInfoDO order2 = TicketOrderInfoDO.builder().id(1L).build();
// 没有 callSuper=true 时可能错误判断两个对象相等 -
字段设计规范
cenicSpotsId
字段采用「业务语义 + ID 类型」命名法:
-
cenic
: Centralized Entertainment & Information Center 缩写(项目专属业务术语)Spots
: 景区业务域标识Id
: 明确标识为业务编码(非自增主键)
@Builder
注解核心作用
这是 Lombok 的建造者模式注解,自动生成如下代码结构:
public class TicketOrderInfoDO {
public static TicketOrderInfoDO.TicketOrderInfoDOBuilder builder() {
return new TicketOrderInfoDO.TicketOrderInfoDOBuilder();
}
public static class TicketOrderInfoDOBuilder {
private Long id;
private String cenicSpotsId;
// ... 其他字段
TicketOrderInfoDOBuilder() {}
public TicketOrderInfoDO.TicketOrderInfoDOBuilder id(Long id) {
this.id = id;
return this;
}
public TicketOrderInfoDO.TicketOrderInfoDOBuilder cenicSpotsId(String cenicSpotsId) {
this.cenicSpotsId = cenicSpotsId;
return this;
}
// ... 其他字段的链式方法
public TicketOrderInfoDO build() {
return new TicketOrderInfoDO(id, cenicSpotsId, ...);
}
}
}
实际应用场景
// 创建复杂对象时避免过长的构造函数参数
TicketOrderInfoDO order = TicketOrderInfoDO.builder()
.id(1L)
.cenicSpotsId("CS_001")
.ticketName("迪士尼成人票")
.price(new BigDecimal("599.00"))
.buyTime(LocalDateTime.now())
.build();
设计注意事项
当同时使用 @NoArgsConstructor
和 @Builder
时,需要显式声明无参构造器:
@NoArgsConstructor(access = AccessLevel.PRIVATE) // 强制私有无参构造
@AllArgsConstructor
public class TicketOrderInfoDO extends BaseDO {
// ... 字段
}
该模式特别适用于需要创建包含 40+ 字段的复杂数据对象场景,可显著提升代码可读性。
代码生成器特征分析
该实体类具有典型的代码生成器特征,主要体现在以下方面:
代码生成器特征分析
// 特征 1 - 标准模板结构
@TableName("order_ticket_order_info") // ① 表名映射
@KeySequence("order_ticket_order_info_seq") // ② 多数据库主键策略
@Data // ③ 自动化 Lombok 配置
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class TicketOrderInfoDO extends BaseDO { ... }
// 特征 2 - 字段与表结构强对应
@TableId
private Long id; // 主键(完全对应表结构)
private String cenicSpotsId; // 字段名与数据库列名 cenic_spots_id 自动转换
private LocalDateTime buyTime; // 时间类型精确对应
典型代码生成场景
-
数据库表逆向工程
根据order_ticket_order_info
表结构自动生成字段(含注释) -
MyBatis-Plus 代码生成器配置
常见生成配置示例:FastAutoGenerator.create(dataSourceConfig)
.globalConfig(builder -> builder.outputDir("c:\project\centralservice"))
.packageConfig(builder -> builder.parent("cn.iocoder.central.module.ticket"))
.strategyConfig(builder ->
builder.entityBuilder()
.enableLombok()
.enableChainModel()
.addSuperEntityColumns("create_time", "update_time") // 继承 BaseDO
);
生成后的人工干预点
// 干预 1 - 补充业务语义注释
/**
* 远端订单号(对接第三方系统)
*/
private String remoteOrderNumber;
// 干预 2 - 增加特殊字段类型
@JsonSerialize(using = ToStringSerializer.class) // 防止 Long 精度丢失
private Long distributorId;
// 干预 3 - 自定义校验注解
@NotNull(message = "采购单价不能为空")
private BigDecimal purchasePrice;
2. Mapper 数据接口
public interface TicketOrderInfoMapper extends BaseMapperX<TicketOrderInfoDO> {
default PageResult<TicketOrderInfoDO> selectPage(TicketOrderInfoPageReqVO reqVO) {
return selectPage(reqVO, new LambdaQueryWrapperX<TicketOrderInfoDO>()
.eqIfPresent(TicketOrderInfoDO::getCenicSpotsId, reqVO.getCenicSpotsId())
.likeIfPresent(TicketOrderInfoDO::getCenicSpotsName, reqVO.getCenicSpotsName()));
}
}
作用:
- 继承 BaseMapperX 获得通用 CRUD 方法
- 使用 MyBatis-Plus 动态查询条件构建器
- 分页查询自动处理(与前端传参对接)
3. SQL 映射文件
<select id="selectById" resultType="TicketOrderInfoDO">
SELECT * FROM order_ticket_order_info
WHERE id = #{id}
</select>
<insert id="insert" parameterType="TicketOrderInfoDO" useGeneratedKeys="true" keyProperty="id">
INSERT INTO order_ticket_order_info (cenic_spots_id, ticket_name, ...)
VALUES (#{cenicSpotsId}, #{ticketName}, ...)
</insert>
作用:
- 复杂 SQL 的 XML 实现
- 结果集自动映射到 DO 对象
- 主键自增配置(useGeneratedKeys)
数据流转流程
Controller(VO)
↓ 转换
Service(DTO)
↓ 转换
Mapper(DO) ↔ XML
↓
DB
设计亮点
- 模块化分层
-
- 按业务域划分 ticketinfo 子包
- 不同业务模块的 DO/Mapper 物理隔离
-
继承复用
public class BaseDO {
private LocalDateTime createTime;
private LocalDateTime updateTime;
private Long creator;
private Long updater;
} -
动态查询
// 自动处理空值条件
.eqIfPresent(TicketOrderInfoDO::getTicketId, reqVO.getTicketId())
扩展建议
对于复杂查询场景,可增加 @Select
注解实现动态 SQL:
@SelectProvider(type = TicketOrderSqlBuilder.class, method = "buildQuery")
List<TicketOrderInfoDO> selectByComplexCondition(TicketOrderQuery query);
代码详细注释
// 包路径声明(领域驱动设计中的基础设施层)
package cn.iocoder.central.module.ticket.dal.mysql.ticketinfo;
// ... 导入部分省略 ...
/**
* 门票订单信息 Mapper(数据访问层核心组件)
*
* 功能定位:
* 1. 实现 TicketOrderInfoDO 与 order_ticket_order_info 表的 CRUD 操作
* 2. 处理复杂查询条件构建和分页逻辑
* 3. 对接 MyBatis-Plus 增强功能
*
* @author 陕文旅
*/
@Mapper // MyBatis 组件标识,由 Spring 管理 Bean 生命周期
public interface TicketOrderInfoMapper extends BaseMapperX<TicketOrderInfoDO> {
/**
* 动态分页查询(Java 链式 API 实现)
*
* @param reqVO 分页查询请求参数 VO
* @return 分页结果(包含 total/list/pageNo/pageSize)
*
* 实现特点:
* 1. 使用 LambdaQueryWrapperX 实现类型安全的条件构建
* 2. eqIfPresent/likeIfPresent 实现 null 值自动过滤
* 3. 支持 40+ 字段的精确匹配、模糊查询、时间范围过滤
* 4. 默认按 ID 倒序排序
*/
default PageResult<TicketOrderInfoDO> selectPage(TicketOrderInfoPageReqVO reqVO) {
return selectPage(reqVO, new LambdaQueryWrapperX<TicketOrderInfoDO>()
// 景区相关条件(示例)
.eqIfPresent(TicketOrderInfoDO::getCenicSpotsId, reqVO.getCenicSpotsId())
.likeIfPresent(TicketOrderInfoDO::getCenicSpotsName, reqVO.getCenicSpotsName())
// ... 中间字段条件省略 ...
// 时间范围条件(示例)
.betweenIfPresent(TicketOrderInfoDO::getCreateTime, reqVO.getCreateTime())
// 排序规则
.orderByDesc(TicketOrderInfoDO::getId));
}
/**
* XML 映射方式的分页查询(复杂 SQL 场景)
*
* @param page MyBatis-Plus 分页对象
* @param reqVO 查询参数
* @return 分页结果(响应 VO 对象)
*
* 适用场景:
* 1. 多表联合查询
* 2. 复杂统计计算
* 3. 数据库特性函数使用
*/
IPage<TicketOrderInfoRespVO> selectPageUseXML(IPage<TicketOrderInfoRespVO> page, @Param("reqVO") TicketOrderInfoPageReqVO reqVO);
/**
* 导出数据查询接口
*
* @param pageReqVO 查询参数(复用分页 VO)
* @return 导出数据列表(Excel 专用 VO)
*
* 设计要点:
* 1. 复用分页查询条件确保数据一致性
* 2. 返回结构适配 Excel 导出格式
*/
List<TicketImportVo> getOrderTicketOrderInfoList(@Param("reqVO") TicketOrderInfoPageReqVO pageReqVO);
}
MyBatis-Plus 实现角度分析 TicketOrderInfoMapper.java 的核心设计:
架构分层
@Mapper // ① MyBatis 组件标识
public interface TicketOrderInfoMapper
extends BaseMapperX<TicketOrderInfoDO> { // ② 继承增强基类
// ③ 默认方法实现动态查询
default PageResult<TicketOrderInfoDO> selectPage(TicketOrderInfoPageReqVO reqVO) { ... }
// ④ XML 映射的复杂查询
IPage<TicketOrderInfoRespVO> selectPageUseXML(IPage<TicketOrderInfoRespVO> page, @Param("reqVO") TicketOrderInfoPageReqVO reqVO);
}
核心机制解析
-
动态条件构建
通过 LambdaQueryWrapperX 实现 40+ 字段的动态条件匹配:.eqIfPresent(TicketOrderInfoDO::getCenicSpotsId, reqVO.getCenicSpotsId())
等效于:
<if test="reqVO.cenicSpotsId != null">
cenic_spots_id = #{reqVO.cenicSpotsId}
</if>
-
分页结果处理
PageResult<T>
封装结构:{
"total": 100,
"list": [...],
"pageNo": 1,
"pageSize": 10
}
与前端分页参数自动对接
-
XML 映射模式
<select id="selectPageUseXML" resultType="TicketOrderInfoRespVO"> SELECT o.*, s.supplier_contact AS supplier_contact FROM order_ticket_order_info o LEFT JOIN ticket_supplier s ON o.supplier_id = s.id <where> <if test="reqVO.supplierName != null"> o.supplier_name LIKE CONCAT('%', #{reqVO.supplierName}, '%') </if> </where> </select>
复杂联表查询示例:
扩展应用场景
// 场景 1 - 导出数据对接
public void exportTicketOrders(TicketOrderInfoPageReqVO reqVO) {
List<TicketImportVo> list = ticketOrderInfoMapper.getOrderTicketOrderInfoList(reqVO);
excelUtil.export(list, "门票订单数据");
}
// 场景 2 - 复杂统计查询
@Select("SELECT COUNT(*) FROM order_ticket_order_info WHERE price > #{minPrice}")
int countByMinPrice(@Param("minPrice") BigDecimal minPrice);
设计优化建议
问题 :动态条件方法过长(40+ 字段)
解决方案:使用元编程自动生成条件
public abstract class BaseMapperX<T> extends BaseMapper<T> {
public PageResult<T> selectPageAuto(PageParam reqVO, Function<LambdaQueryWrapperX<T>, LambdaQueryWrapperX<T>> predicate) {
LambdaQueryWrapperX<T> wrapper = new LambdaQueryWrapperX<>();
// 通过反射自动匹配 reqVO 与 DO 字段
ReflectUtil.getFields(reqVO.getClass()).forEach(field -> {
Object value = ReflectUtil.getFieldValue(reqVO, field);
if (value != null) {
wrapper.eq(getColumn(field), value);
}
});
return selectPage(reqVO, predicate.apply(wrapper));
}
}
3. 服务层(Service Layer)
- 功能与技术点实现:作为业务逻辑核心,处理票务信息的增删改查。
-
- 服务接口与实现类编写:定义服务接口,编写实现类,处理业务规则。
- 分布式事务处理:涉及多数据源或服务的事务操作时,使用 Seata 等框架确保事务一致性。
- 服务发现与调用配置:通过 Nacos 发现服务,用 Feign 或 Dubbo 进行远程调用。
- 联动方式实现:通过依赖注入调用 DAL 层的 DAO 方法进行数据操作。当涉及多个服务的协作时,通过 Feign 客户端调用其他服务的 API,Nacos 实现服务发现和负载均衡。对于需要缓存的数据,服务层会先调用 Redis 相关操作从缓存中获取数据,若缓存失效则再调用 MySQL 相关操作从数据库中查询数据并更新缓存。

我将从 MyBatis-Plus 集成和业务逻辑处理两个维度,详细分析 <mcsymbol name="TicketOrderInfoServiceImpl" filename="TicketOrderInfoServiceImpl.java" path="c:\project\centralservice\central-module-ticket\central-module-ticket-biz\src\main\java\cn\iocoder\central\module\ticket\service\ticketinfo\TicketOrderInfoServiceImpl.java" startline="37" type="class"></mcsymbol>
的实现:
服务层核心架构
@Service
@Validated
@Slf4j
public class TicketOrderInfoServiceImpl implements TicketOrderInfoService {
@Resource
private TicketOrderInfoMapper ticketOrderInfoMapper;
// 核心方法实现...
}
分层设计分析
|------------|------------------------------|-----------------|
| 层级 | 组件 | 职责说明 |
| Controller | TicketOrderInfoController
| 处理 HTTP 请求/响应 |
| Service | TicketOrderInfoServiceImpl
| 业务逻辑编排/事务控制 |
| Mapper | TicketOrderInfoMapper
| 数据库访问/动态 SQL 生成 |
| DO | TicketOrderInfoDO
| 数据库实体映射 |
| VO | TicketOrderInfoPageReqVO
| 前端请求参数封装 |
核心方法实现分析
1. 分页查询服务
public PageResult<TicketOrderInfoDO> selectPage(TicketOrderInfoPageReqVO reqVO) {
return ticketOrderInfoMapper.selectPage(reqVO);
}
// 实际调用链路:
Controller → Service → Mapper → MyBatis-Plus 分页插件 → 数据库
2. 数据创建逻辑
public Long createTicketOrderInfo(TicketOrderInfoSaveReqVO createReqVO) {
// 数据转换 + 持久化操作
TicketOrderInfoDO ticketOrderInfo = BeanUtils.toBean(createReqVO, TicketOrderInfoDO.class);
ticketOrderInfoMapper.insert(ticketOrderInfo);
return ticketOrderInfo.getId();
}
-
复杂业务场景示例(门票订单核销)
@Transactional(rollbackFor = Exception.class)
public void verifyTicketOrder(Long id, String operator) {
// 1. 校验订单状态
TicketOrderInfoDO order = ticketOrderInfoMapper.selectById(id);
if (order.getVerifiedCount() >= order.getTicketCount()) {
throw new ServiceException("订单已全部核销");
}// 2. 更新核销数量 ticketOrderInfoMapper.update(null, new LambdaUpdateWrapper<TicketOrderInfoDO>() .setSql("verified_count = verified_count + 1") .eq(TicketOrderInfoDO::getId, id)); // 3. 记录核销日志(需要扩展日志服务) ticketVerifyLogService.logVerify(id, operator);
}
性能优化建议
问题 :全字段更新效率低
解决方案:动态字段更新
public void updateTicketOrderInfo(TicketOrderInfoSaveReqVO updateReqVO) {
// 仅更新非空字段
TicketOrderInfoDO updateObj = BeanUtils.toBean(updateReqVO, TicketOrderInfoDO.class);
ticketOrderInfoMapper.updateById(updateObj);
// 替换 MyBatis-Plus 默认的 updateById 实现为:
ticketOrderInfoMapper.update(
new LambdaUpdateWrapper<TicketOrderInfoDO>()
.set(updateReqVO.getCenicSpotsId() != null, TicketOrderInfoDO::getCenicSpotsId, updateReqVO.getCenicSpotsId())
// 逐个设置非空字段...
.eq(TicketOrderInfoDO::getId, updateReqVO.getId())
);
}
事务管理策略
spring:
transaction:
default-timeout: 30 # 事务默认超时时间30秒
rollback-on-commit-failure: true # 提交失败时回滚
扩展功能建议
分布式锁集成:
public void processOrder(Long id) {
Lock lock = lockTemplate.getLock("order_process:" + id);
try {
if (lock.tryLock()) {
// 处理订单核心逻辑...
}
} finally {
lock.unlock();
}
}
缓存集成示例:
@Cacheable(value = "ticketOrder", key = "#id", unless = "#result == null")
public TicketOrderInfoDO getTicketOrderInfo(Long id) {
return ticketOrderInfoMapper.selectById(id);
}
需要根据具体业务场景选择适合的优化策略。当前服务层实现已具备基础 CRUD 能力,后续可结合具体业务需求增加风控校验、异步处理等高级功能。
TicketOrderInfoServiceImpl.java 实现类
package cn.iocoder.central.module.ticket.service.ticketinfo;
import cn.hutool.core.collection.CollectionUtil;
import cn.iocoder.central.module.ticket.controller.admin.vo.TicketOrderInfoPageReqVO;
import cn.iocoder.central.module.ticket.controller.admin.vo.TicketOrderInfoSaveReqVO;
import cn.iocoder.central.module.ticket.controller.admin.vo.excel.TicketImportRespVo;
import cn.iocoder.central.module.ticket.controller.admin.vo.excel.TicketImportVo;
import cn.iocoder.central.module.ticket.dal.dataobject.ticketinfo.TicketOrderInfoDO;
import cn.iocoder.central.module.ticket.dal.mysql.ticketinfo.TicketOrderInfoMapper;
import cn.iocoder.yudao.framework.common.exception.ErrorCode;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.date.DateTimeFormatterUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import java.util.ArrayList;
import java.util.List;
import static cn.iocoder.central.module.ticket.enums.ErrorCodeConstants.TICKET_IMPORT_LIST_IS_EMPTY;
import static cn.iocoder.central.module.ticket.enums.ErrorCodeConstants.TICKET_ORDER_INFO_NOT_EXISTS;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
/**
* 门票订单信息 Service 实现类
*
* @author 陕文旅
*/
@Service
@Validated
@Slf4j
public class TicketOrderInfoServiceImpl implements TicketOrderInfoService {
@Resource
private TicketOrderInfoMapper ticketOrderInfoMapper;
/**
* 创建门票订单信息
*
* @param createReqVO 创建请求对象,包含门票订单的详细信息
* @return 创建的门票订单的主键ID
*/
@Override
public Long createTicketOrderInfo(TicketOrderInfoSaveReqVO createReqVO) {
// 将请求对象转换为数据访问对象
TicketOrderInfoDO ticketOrderInfo = BeanUtils.toBean(createReqVO, TicketOrderInfoDO.class);
// 插入到数据库
ticketOrderInfoMapper.insert(ticketOrderInfo);
// 返回生成的主键ID
return ticketOrderInfo.getId();
}
/**
* 更新门票订单信息
*
* @param updateReqVO 更新请求对象,包含门票订单的详细信息
*/
@Override
public void updateTicketOrderInfo(TicketOrderInfoSaveReqVO updateReqVO) {
// 校验门票订单是否存在
validateTicketOrderInfoExists(updateReqVO.getId());
// 将请求对象转换为数据访问对象
TicketOrderInfoDO updateObj = BeanUtils.toBean(updateReqVO, TicketOrderInfoDO.class);
// 执行更新操作
ticketOrderInfoMapper.updateById(updateObj);
}
/**
* 删除门票订单信息
*
* @param id 门票订单的主键ID
*/
@Override
public void deleteTicketOrderInfo(Long id) {
// 校验门票订单是否存在
validateTicketOrderInfoExists(id);
// 执行删除操作
ticketOrderInfoMapper.deleteById(id);
}
/**
* 校验门票订单是否存在,不存在则抛出异常
*
* @param id 门票订单的主键ID
*/
private void validateTicketOrderInfoExists(Long id) {
if (ticketOrderInfoMapper.selectById(id) == null) {
// 如果不存在,抛出异常
throw exception((ErrorCode) TICKET_ORDER_INFO_NOT_EXISTS);
}
}
/**
* 获取单个门票订单信息
*
* @param id 门票订单的主键ID
* @return 门票订单信息对象
*/
@Override
public TicketOrderInfoDO getTicketOrderInfo(Long id) {
return ticketOrderInfoMapper.selectById(id);
}
/**
* 分页获取门票订单信息列表
*
* @param pageReqVO 分页请求对象,包含分页参数和查询条件
* @return 分页结果对象,包含门票订单信息列表和分页信息
*/
@Override
public PageResult<TicketOrderInfoDO> getTicketOrderInfoPage(TicketOrderInfoPageReqVO pageReqVO) {
return ticketOrderInfoMapper.selectPage(pageReqVO);
}
/**
* 导入门票订单信息列表
*
* @param list 待导入的门票订单信息列表
* @param updateSupport 是否支持更新已存在的记录
* @return 导入结果对象,包含新增、更新和失败的订单编号列表
*/
@Override
@Transactional
public TicketImportRespVo importTicketList(List<TicketImportVo> list, Boolean updateSupport) {
// 参数校验:列表是否为空
if (CollectionUtil.isEmpty(list)) {
throw exception(TICKET_IMPORT_LIST_IS_EMPTY);
}
// 初始化导入结果对象
TicketImportRespVo respVO = TicketImportRespVo.builder()
.addOrderCodes(new ArrayList<>())
.updateOrderCodes(new ArrayList<>())
.failureOrderCodes(new ArrayList<>())
.build();
log.info("导入的数量:{}, 导入数据:{}", list.size(), list);
log.info("\n================================== 导入开始 ============================");
long startImportTime = System.currentTimeMillis();
// 遍历导入列表,逐个处理
list.forEach(item -> {
log.info("开始导入:{}", item);
// 处理时间字段,格式化为标准日期时间格式
item.setBuyTime(DateTimeFormatterUtils.formatDateTime(item.getBuyTime()));
item.setExpectedTime(DateTimeFormatterUtils.formatDateTime(item.getExpectedTime()));
item.setStartDate(DateTimeFormatterUtils.formatDateTime(item.getStartDate()));
item.setEndDate(DateTimeFormatterUtils.formatDateTime(item.getEndDate()));
item.setCompletionTime(DateTimeFormatterUtils.formatDateTime(item.getCompletionTime()));
item.setCancelTime(DateTimeFormatterUtils.formatDateTime(item.getCancelTime()));
// 转换为数据访问对象
TicketOrderInfoDO TicketOrderInfoDO = BeanUtils.toBean(item, TicketOrderInfoDO.class);
// 插入数据库
ticketOrderInfoMapper.insert(TicketOrderInfoDO);
log.info("新增的ID:{}", TicketOrderInfoDO.getId());
});
long endImportTime = System.currentTimeMillis();
log.info("\n================================== 导入结束 ============================");
log.info("一共用了:{}", endImportTime - startImportTime);
return respVO;
}
}
TicketOrderInfoService.java 接口
package cn.iocoder.central.module.ticket.service.ticketinfo;
import cn.iocoder.central.module.ticket.controller.admin.vo.TicketOrderInfoPageReqVO;
import cn.iocoder.central.module.ticket.controller.admin.vo.TicketOrderInfoSaveReqVO;
import cn.iocoder.central.module.ticket.controller.admin.vo.excel.TicketImportRespVo;
import cn.iocoder.central.module.ticket.controller.admin.vo.excel.TicketImportVo;
import cn.iocoder.central.module.ticket.dal.dataobject.ticketinfo.TicketOrderInfoDO;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import jakarta.validation.Valid;
import java.util.List;
/**
* 门票订单信息 Service 接口
*
* @author 陕文旅
*/
public interface TicketOrderInfoService {
/**
* 创建门票订单信息
*
* @param createReqVO 创建信息,包含门票订单的详细数据
* @return 生成的门票订单编号
*/
Long createTicketOrderInfo(@Valid TicketOrderInfoSaveReqVO createReqVO);
/**
* 更新门票订单信息
*
* @param updateReqVO 更新信息,包含门票订单的详细数据和主键ID
*/
void updateTicketOrderInfo(@Valid TicketOrderInfoSaveReqVO updateReqVO);
/**
* 删除门票订单信息
*
* @param id 门票订单的主键ID
*/
void deleteTicketOrderInfo(Long id);
/**
* 获取单个门票订单信息
*
* @param id 门票订单的主键ID
* @return 门票订单信息对象
*/
TicketOrderInfoDO getTicketOrderInfo(Long id);
/**
* 分页获取门票订单信息列表
*
* @param pageReqVO 分页请求对象,包含分页参数和查询条件
* @return 分页结果对象,包含门票订单信息列表和分页信息
*/
PageResult<TicketOrderInfoDO> getTicketOrderInfoPage(TicketOrderInfoPageReqVO pageReqVO);
/**
* 批量导入门票订单信息
*
* @param list 待导入的门票订单信息列表
* @param updateSupport 是否支持更新已存在的记录
* @return 导入结果对象,包含新增、更新和失败的订单编号列表
*/
TicketImportRespVo importTicketList(List<TicketImportVo> list, Boolean updateSupport);
}
4. 控制层(Controller Layer)
- 功能与技术点实现:处理客户端的 HTTP 请求,尤其针对管理员操作和 Excel 文件相关视图对象(VO)。
-
- 控制器类与方法编写:使用 Spring MVC 处理 HTTP 请求响应,定义请求映射和参数绑定。
- Sentinel 配置:在方法级配置流量控制和熔断降级规则,防止接口过载。
- 统一响应格式设置:使用全局异常处理和响应封装,确保接口返回格式一致。
- 联动方式实现:通过依赖注入(@Autowired)调用服务层的业务逻辑方法。例如,当处理一个门票订单信息的查询请求时,控制器方法会调用服务层的查询业务逻辑方法,获取数据后将其转换为 Excel 格式返回给客户端。

主要功能模块分析
-
数据导入功能 (
importTicketList
)@Transactional
public TicketImportRespVo importTicketList(List<TicketImportVo> list, Boolean updateSupport)
- 实现Excel数据批量导入
- 核心流程:
-
- 参数校验 → 数据准备 → 时间格式处理 → 数据转换 → 持久化存储
- 支持事务管理(@Transactional)
-
关键日志:
log.info("导入的数量:{}, 导入数据:{}", list.size(), list);
log.info("\n================================== 导入开始 ============================");
-
基础CRUD操作
// 创建(带ID返回)
Long createTicketOrderInfo(TicketOrderInfoSaveReqVO createReqVO)// 更新(带存在性校验)
void updateTicketOrderInfo(TicketOrderInfoSaveReqVO updateReqVO)// 删除(带存在性校验)
void deleteTicketOrderInfo(Long id)// 分页查询
PageResult<TicketOrderInfoDO> getTicketOrderInfoPage(TicketOrderInfoPageReqVO pageReqVO) -
校验机制
private void validateTicketOrderInfoExists(Long id) {
if (ticketOrderInfoMapper.selectById(id) == null) {
throw exception((ErrorCode) TICKET_ORDER_INFO_NOT_EXISTS);
}
}
关联组件
- 数据访问层:
-
- 依赖 实现数据库操作
- 使用 MyBatis-Plus 的
BaseMapperX
基础功能
- 数据传输对象:
-
- 入参:
- 出参:
TicketOrderInfoController.java
// 包名,表示该控制器位于 cn.iocoder.central.module.ticket.controller.admin 包下
package cn.iocoder.central.module.ticket.controller.admin;
// 导入所需的各类依赖包和自定义类,包括 VO、DO、Service、工具类等
import cn.iocoder.central.module.ticket.controller.admin.vo.TicketOrderInfoPageReqVO;
import cn.iocoder.central.module.ticket.controller.admin.vo.TicketOrderInfoRespVO;
import cn.iocoder.central.module.ticket.controller.admin.vo.TicketOrderInfoSaveReqVO;
import cn.iocoder.central.module.ticket.controller.admin.vo.excel.TicketImportRespVo;
import cn.iocoder.central.module.ticket.controller.admin.vo.excel.TicketImportVo;
import cn.iocoder.central.module.ticket.dal.dataobject.ticketinfo.TicketOrderInfoDO;
import cn.iocoder.central.module.ticket.service.ticketinfo.TicketOrderInfoService;
import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.util.List;
// 静态导入 CommonResult 的 success 方法和 ApiAccessLog 的枚举值 EXPORT
import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
// 使用 Swagger 的 Tag 注解为该控制器添加标签,名称为 "管理后台 - 门票订单信息"
@Tag(name = "管理后台 - 门票订单信息")
// 使用 Spring 的 RestController 注解,表明该类是一个 RESTful 风格的控制器
@RestController
// 使用 RequestMapping 注解,设置该控制器的请求路径前缀为 "/ticket/order/ticket-order-info"
@RequestMapping("/ticket/order/ticket-order-info")
// 使用 Validated 注解,开启参数校验功能
@Validated
public class TicketOrderInfoController {
// 使用 Resource 注解,按名称自动注入 TicketOrderInfoService 类型的 Bean
@Resource
private TicketOrderInfoService ticketOrderInfoService;
/**
* 创建门票订单信息
*
* @param createReqVO 创建门票订单信息的请求参数对象
* @return 返回包含创建成功后的订单 ID 的 CommonResult 对象
*/
@PostMapping("/create")
@Operation(summary = "创建门票订单信息")
@PreAuthorize("@ss.hasPermission('order:ticket-order-info:create')")
public CommonResult<Long> createTicketOrderInfo(@Valid @RequestBody TicketOrderInfoSaveReqVO createReqVO) {
return success(ticketOrderInfoService.createTicketOrderInfo(createReqVO));
}
/**
* 更新门票订单信息
*
* @param updateReqVO 更新门票订单信息的请求参数对象
* @return 返回表示更新是否成功的 CommonResult 对象
*/
@PutMapping("/update")
@Operation(summary = "更新门票订单信息")
@PreAuthorize("@ss.hasPermission('order:ticket-order-info:update')")
public CommonResult<Boolean> updateTicketOrderInfo(@Valid @RequestBody TicketOrderInfoSaveReqVO updateReqVO) {
ticketOrderInfoService.updateTicketOrderInfo(updateReqVO);
return success(true);
}
/**
* 删除门票订单信息
*
* @param id 要删除的订单信息的 ID
* @return 返回表示删除是否成功的 CommonResult 对象
*/
@DeleteMapping("/delete")
@Operation(summary = "删除门票订单信息")
@Parameter(name = "id", description = "编号", required = true)
@PreAuthorize("@ss.hasPermission('order:ticket-order-info:delete')")
public CommonResult<Boolean> deleteTicketOrderInfo(@RequestParam("id") Long id) {
ticketOrderInfoService.deleteTicketOrderInfo(id);
return success(true);
}
/**
* 根据 ID 获取门票订单信息
*
* @param id 要获取的订单信息的 ID
* @return 返回包含订单信息的 CommonResult 对象
*/
@GetMapping("/get")
@Operation(summary = "获得门票订单信息")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('order:ticket-order-info:query')")
public CommonResult<TicketOrderInfoRespVO> getTicketOrderInfo(@RequestParam("id") Long id) {
TicketOrderInfoDO ticketOrderInfo = ticketOrderInfoService.getTicketOrderInfo(id);
return success(BeanUtils.toBean(ticketOrderInfo, TicketOrderInfoRespVO.class));
}
/**
* 获取门票订单信息分页列表
*
* @param pageReqVO 分页查询的请求参数对象
* @return 返回包含分页结果的 CommonResult 对象
*/
@GetMapping("/page")
@Operation(summary = "获得门票订单信息分页")
@PreAuthorize("@ss.hasPermission('order:ticket-order-info:query')")
public CommonResult<PageResult<TicketOrderInfoRespVO>> getTicketOrderInfoPage(@Valid TicketOrderInfoPageReqVO pageReqVO) {
PageResult<TicketOrderInfoDO> pageResult = ticketOrderInfoService.getTicketOrderInfoPage(pageReqVO);
return success(BeanUtils.toBean(pageResult, TicketOrderInfoRespVO.class));
}
/**
* 导出门票订单信息的 Excel 文件
*
* @param pageReqVO 分页查询的请求参数对象,用于指定要导出的数据范围
* @param response HTTP 响应对象,用于将生成的 Excel 文件返回给客户端
* @throws IOException 如果在导出过程中发生 I/O 错误,则抛出该异常
*/
@GetMapping("/export-excel")
@Operation(summary = "导出门票订单信息 Excel")
@PreAuthorize("@ss.hasPermission('order:ticket-order-info:export')")
@ApiAccessLog(operateType = EXPORT)
public void exportTicketOrderInfoExcel(@Valid TicketOrderInfoPageReqVO pageReqVO,
HttpServletResponse response) throws IOException {
// 设置每页显示条数为 -1,表示获取所有数据
pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
// 获取分页结果中的数据列表
List<TicketOrderInfoDO> list = ticketOrderInfoService.getTicketOrderInfoPage(pageReqVO).getList();
// 使用 ExcelUtils 工具类将数据写入 Excel 文件并返回给客户端
ExcelUtils.write(response, "门票订单信息.xls", "数据", TicketOrderInfoRespVO.class,
BeanUtils.toBean(list, TicketOrderInfoRespVO.class));
}
/**
* 导入门票订单信息的 Excel 文件
*
* @param file 上传的 Excel 文件
* @param updateSupport 是否支持更新,默认为 false
* @return 返回包含导入结果的 CommonResult 对象
* @throws Exception 如果在导入过程中发生异常,则抛出该异常
*/
@PostMapping("/import")
@Operation(summary = "导入门票")
@Parameters({
@Parameter(name = "file", description = "Excel 文件", required = true),
@Parameter(name = "updateSupport", description = "是否支持更新,默认为 false", example = "true")
})
@PreAuthorize("@ss.hasPermission('order:child-ticket-order-info:import')")
public CommonResult<TicketImportRespVo> importExcel(@RequestParam("file") MultipartFile file,
@RequestParam(value = "updateSupport", required = false, defaultValue = "false") Boolean updateSupport) throws Exception {
// 使用 ExcelUtils 工具类读取上传的 Excel 文件,转换为 TicketImportVo 对象列表
List<TicketImportVo> list = ExcelUtils.read(file, TicketImportVo.class);
// 调用服务层方法处理导入的数据,并返回结果
return success(ticketOrderInfoService.importTicketList(list, updateSupport));
}
/**
* 获取门票导入的 Excel 模板文件
*
* @param response HTTP 响应对象,用于将生成的模板文件返回给客户端
* @throws IOException 如果在生成或返回模板文件过程中发生 I/O 错误,则抛出该异常
*/
@GetMapping("/get-ticket-import-template")
@Operation(summary = "获取门票导入模板")
public void TicketImportTemplate(HttpServletResponse response) throws IOException {
// 使用 ExcelUtils 工具类生成模板文件并返回给客户端
ExcelUtils.write(response, "门票导入模板.xlsx", "门票列表", TicketImportVo.class, null);
}
}
以下是 <mcfile name="TicketOrderInfoController.java" path="c:\project\centralservice\central-module-ticket\central-module-ticket-biz\src\main\java\cn\iocoder\central\module\ticket\controller\admin\TicketOrderInfoController.java"></mcfile>
的模块解析:
1. 类声明部分
@Tag(name = "管理后台 - 门票订单信息")
@RestController
@RequestMapping("/ticket/order/ticket-order-info")
@Validated
public class TicketOrderInfoController {
@Tag
:OpenAPI 文档分组标识(Swagger 文档分类)@RestController
:声明为 RESTful 控制器(自动处理 JSON 序列化)@RequestMapping
:定义基础请求路径为/ticket/order/ticket-order-info
@Validated
:启用 Spring 参数校验机制
2. 服务注入
@Resource
private TicketOrderInfoService ticketOrderInfoService;
- 通过
@Resource
注入<mcsymbol name="TicketOrderInfoService" filename="TicketOrderInfoService.java" path="c:\project\centralservice\central-module-ticket\central-module-ticket-biz\src\main\java\cn\iocoder\central\module\ticket\service\ticketinfo\TicketOrderInfoService.java" startline="19" type="interface"></mcsymbol>
服务接口
3. 核心接口解析
3.1 创建接口
@PostMapping("/create")
@Operation(summary = "创建门票订单信息")
@PreAuthorize("@ss.hasPermission('order:ticket-order-info:create')")
public CommonResult<Long> createTicketOrderInfo(@Valid @RequestBody TicketOrderInfoSaveReqVO createReqVO) {
return success(ticketOrderInfoService.createTicketOrderInfo(createReqVO));
}
@PostMapping
:处理 HTTP POST 请求@PreAuthorize
:权限校验(需要order:ticket-order-info:create
权限)@Valid
:触发<mcsymbol name="TicketOrderInfoSaveReqVO" filename="TicketOrderInfoSaveReqVO.java" path="c:\project\centralservice\central-module-ticket\central-module-ticket-biz\src\main\java\cn\iocoder\central\module\ticket\controller\admin\vo\TicketOrderInfoSaveReqVO.java" startline="11" type="class"></mcsymbol>
的参数校验
3.2 分页查询接口
@GetMapping("/page")
@Operation(summary = "获得门票订单信息分页")
public CommonResult<PageResult<TicketOrderInfoRespVO>> getTicketOrderInfoPage(...) {
PageResult<TicketOrderInfoDO> pageResult = ticketOrderInfoService.getTicketOrderInfoPage(pageReqVO);
return success(BeanUtils.toBean(pageResult, TicketOrderInfoRespVO.class));
}
- 数据转换 :使用
BeanUtils.toBean
将 DO 对象转换为<mcsymbol name="TicketOrderInfoRespVO" filename="TicketOrderInfoRespVO.java" path="c:\project\centralservice\central-module-ticket\central-module-ticket-biz\src\main\java\cn\iocoder\central\module\ticket\controller\admin\vo\TicketOrderInfoRespVO.java" startline="12" type="class"></mcsymbol>
响应对象 - 分页机制 :继承自
<mcsymbol name="PageParam" filename="PageParam.java" path="c:\project\centralservice\common\yudao-spring-boot-starter-web\src\main\java\cn\iocoder\yudao\framework\common\pojo\PageParam.java" startline="14" type="class"></mcsymbol>
的分页基类
3.3 导入/导出功能
// 导出 Excel
@GetMapping("/export-excel")
public void exportTicketOrderInfoExcel(...) throws IOException {
ExcelUtils.write(response, "门票订单信息.xls", "数据", TicketOrderInfoRespVO.class, ...);
}
// 导入 Excel
@PostMapping("/import")
public CommonResult<TicketImportRespVo> importExcel(...) throws Exception {
List<TicketImportVo> list = ExcelUtils.read(file, TicketImportVo.class);
return success(ticketOrderInfoService.importTicketList(list, updateSupport));
}
- Excel 工具 :使用
<mcsymbol name="ExcelUtils" filename="ExcelUtils.java" path="c:\project\centralservice\common\yudao-spring-boot-starter-excel\src\main\java\cn\iocoder\yudao\framework\excel\core\util\ExcelUtils.java" startline="24" type="class"></mcsymbol>
处理表格数据 - 文件操作 :
MultipartFile
处理上传文件,HttpServletResponse
处理下载响应
4. 安全控制
@PreAuthorize("@ss.hasPermission('order:ticket-order-info:create')")
- 权限模型:使用 Spring Security 表达式控制访问权限
- 权限标识 :格式为
模块:功能:操作
(如order:ticket-order-info:create
)
5. 日志记录
@ApiAccessLog(operateType = EXPORT)
- 操作日志 :通过
<mcsymbol name="ApiAccessLog" filename="ApiAccessLog.java" path="c:\project\centralservice\common\yudao-spring-boot-starter-api-log\src\main\java\cn\iocoder\yudao\framework\apilog\core\annotation\ApiAccessLog.java" startline="14" type="annotation"></mcsymbol>
记录导出操作日志
6. 异常处理机制
public CommonResult<TicketImportRespVo> importExcel(...) throws Exception {
// 隐式依赖全局异常处理器(GlobalExceptionHandler)
}
- 统一异常处理 :通过
CommonResult
统一包装响应结果 - 错误传递 :Service 层抛出的异常会通过 Spring 的
@ControllerAdvice
拦截处理
架构特点
- 分层清晰:严格遵循 Controller -> Service -> Mapper 调用链
- DTO 隔离 :使用 VO 对象进行参数传递(
...ReqVO
/...RespVO
) - 组件复用 :继承 common 模块中的基础组件(如
PageParam
、ExcelUtils
) - 文档集成:通过 Swagger 注解实现 API 文档自动生成
5. 映射层(Mapper Layer)
- 功能与技术点实现:存放 MyBatis 的映射文件,定义 SQL 语句与 Java 方法的映射关系,实现数据库操作结果与 Java 对象的相互转换。
-
- MyBatis Mapper:通过 XML 文件或注解定义 SQL 映射,支持动态 SQL 和结果集映射。
- 联动方式实现:映射层的 Mapper 接口与 DAL 层的 DAO 类进行交互。DAO 类通过注入 Mapper 接口来执行 SQL 语句,完成数据的持久化操作或数据查询。MyBatis 会根据映射文件或注解中的配置,将 Java 对象转换为数据库操作所需的参数,或者将数据库查询结果映射为 Java 对象返回给 DAL 层。
通过以上分层架构设计,各层之间通过依赖注入、接口调用、配置文件等方式实现松耦合的联动,共同构建了一个高可用、安全、可扩展且性能优化的门票微服务系统。

一、核心 Mapper 文件解析
1. TicketOrderInfoMapper.xml
<!-- 动态查询条件 -->
<select id="selectPageUseXML" resultMap="OrderTicketOrderInfoResultMap">
<include refid="selectOrderTicketOrderInfo"/>
<if test="reqVO.orderCode != null and reqVO.orderCode != ''">
AND order_code LIKE CONCAT('%', #{reqVO.orderCode}, '%')
</if>
<if test="reqVO.cenicSpotsName != null and reqVO.cenicSpotsName != ''">
AND cenic_spots_name LIKE CONCAT('%', #{reqVO.cenicSpotsName}, '%')
</if>
</select>
<!-- 结果映射 -->
<resultMap id="OrderTicketOrderInfoResultMap" type="TicketOrderInfoRespVO">
<id property="id" column="id"/>
<result property="cenicSpotsName" column="cenic_spots_name"/>
<result property="ticketName" column="ticket_name"/>
...
</resultMap>
核心功能:
- 动态条件拼接 :通过
<if>
标签实现条件过滤 - 结果集映射:定义数据库字段到 VO 对象的映射关系
- 分页查询支持 :与
<mcsymbol name="TicketOrderInfoMapper.selectPage"></mcsymbol>
接口方法配合实现分页
2. ChildTicketOrderInfoMapper.xml
<resultMap id="ChildTicketOrderInfoResultMap" type="ChildTicketOrderInfoRespVO">
<id property="orderId" column="order_id"/>
<result property="productName" column="product_name"/>
<result property="ticketCount" column="ticket_count"/>
...
</resultMap>
功能特点:
- 处理子订单的复杂字段映射
- 包含金额计算字段(actual_amount 等)
- 关联父订单信息的字段映射
二、Mapper 接口分析
public interface TicketOrderInfoMapper extends BaseMapperX<TicketOrderInfoDO> {
default PageResult<TicketOrderInfoDO> selectPage(TicketOrderInfoPageReqVO reqVO) {
return selectPage(reqVO, new LambdaQueryWrapperX<TicketOrderInfoDO>()
.eqIfPresent(TicketOrderInfoDO::getCenicSpotsId, reqVO.getCenicSpotsId())
.likeIfPresent(TicketOrderInfoDO::getCenicSpotsName, reqVO.getCenicSpotsName())
...);
}
}
实现机制:
- 继承链 :
BaseMapperX
→BaseMapper
(MyBatis-Plus) - 动态条件构建:使用 LambdaQueryWrapperX 的链式调用
- 方法命名规范:
-
eqIfPresent
:相等条件(参数非空时生效)likeIfPresent
:模糊查询条件betweenIfPresent
:范围查询条件
三、架构特点分析
1. 双模式查询支持
|-------------|----------|---------------------------------------------|
| 模式 | 适用场景 | 示例文件 |
| XML 动态 SQL | 复杂多表关联查询 | TicketOrderInfoMapper.xml 中的多条件查询 |
| Java 链式 API | 简单单表查询 | TicketOrderInfoMapper.java 中的 selectPage 方法 |
2. 性能优化点
<!-- 索引优化示例 -->
<if test="reqVO.orderCode != null and reqVO.orderCode != ''">
AND order_code LIKE CONCAT('%', #{reqVO.orderCode}, '%')
</if>
- 对
order_code
字段建议添加普通索引 - 对
cenic_spots_name
建议添加全文索引
3. 安全机制
<!-- SQL 注入防护 -->
<select id="selectPageUseXML" resultMap="OrderTicketOrderInfoResultMap">
SELECT * FROM ticket_order_info
WHERE 1=1
<if test="reqVO.orderCode != null">
AND order_code = #{reqVO.orderCode} <!-- 使用预编译语句 -->
</if>
</select>
四、数据流向示意图
