微服务分层架构技术解析:从 API 到数据访问的全方位探秘

微服务分层架构技术解析:从 API 到数据访问的全方位探秘

前言

在当今复杂多变的软件开发领域,微服务架构已成为构建大型分布式系统的核心范式。它通过将系统分解为一组小型、独立且高度内聚的服务,实现了模块化开发、独立部署与扩展,从而显著提升了系统的可维护性、可扩展性和灵活性。本系列文章深入剖析微服务架构的各个关键层面,从 API 层的设计原则与最佳实践,到业务逻辑层的服务解耦与协作模式,再到数据访问层的高效数据处理与持久化策略,全方位揭示微服务系统的设计精髓与技术细节。无论是初涉微服务的开发新手,还是渴望深化架构理解的技术专家,都能从本文中获取宝贵的知识与洞见,助力您在微服务架构的实践中游刃有余,构建出高效、稳定、可扩展的软件系统。

api 层

该代码片段中的两个 Lombok 注解在订单状态枚举类中起到以下核心作用:

复制代码
@RequiredArgsConstructor  // 自动生成包含所有 final 字段的构造函数
@Getter                     // 自动生成所有字段的 getter 方法

具体作用说明

  1. @RequiredArgsConstructor
    • 自动生成包含 typename 两个字段的构造函数

    • 使枚举实例的初始化更简洁(示例):

      WAITING_FOR_PAYMENT(0, "待支付") // 实际调用构造函数

  1. @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 数组

三个核心作用

  1. 统一枚举值管理
    自动收集所有枚举项的 type 值(0/1/2/3),避免手动维护数组

  2. 支持框架接口实现
    ArrayValuable 接口提供数据源,实现以下方法时会直接返回该数组:

    public Integer[] array() {
    return ARRAYS; // 返回 [0,1,2,3]
    }

  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";
}

各常量使用场景

  1. NAME 常量

    应用配置示例

    spring:
    application:
    name: #{ApiConstants.NAME} # 实际会被替换为 ticket-server
    cloud:
    nacos:
    discovery:
    service: ${spring.application.name}

  2. PREFIX 常量

    // 接口路径统一管理示例
    @RestController
    @RequestMapping(ApiConstants.PREFIX + "/order")
    public class TicketOrderController {
    @GetMapping("/get") // 完整路径:/rpc/api/resource/order/get
    public CommonResult<OrderVO> getOrder() { ... }
    }

  3. 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();

    }

设计优势

  1. 集中管理:避免配置散落在多处
  2. 命名规范:统一服务标识和接口路径
  3. 可维护性:修改服务名只需改动常量值
  4. 可读性:常量名称自解释(优于直接使用字符串)

扩展建议

如需新增配置项可参考以下模式:

复制代码
// 新增示例
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";
    // ...其他字典类型常量
}

具体应用场景

  1. 后端数据查询

    // 查询订单类型字典数据
    List<DictData> orderTypes = dictDataService.getDictData(DictTypeConstants.ORDER_MODULE_ORDER_TYPE);

  2. 前端选项渲染

    // 前端通过接口获取字典选项
    @GetMapping("/dict-data/list")
    public CommonResult<List<DictDataRespVO>> listDictData(@RequestParam String dictType) {
    // 例如传入 ORDER_MODULE_PAYMENT_METHOD 获取支付方式选项
    }

  3. 数据校验

    // 校验支付方式是否合法
    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>

监控维度

  1. Spring Boot Actuator 端点(健康检查、metrics)

  2. Prometheus 格式指标采集

  3. 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; // ⑪ 业务字段标准命名(景区编码)
关键设计解析
  1. 多数据库支持
    @KeySequence 实现 Oracle/PostgreSQL 等数据库的序列主键,MySQL 自动忽略(通过 application.yamlmybatis-plus.global-config.db-config.id-type 配置自增策略)

  2. 审计字段继承
    继承的 包含:

    public abstract class BaseDO {
    private LocalDateTime createTime; // 创建时间(DB 自动填充)
    private LocalDateTime updateTime; // 更新时间(DB 自动填充)
    private Long creator; // 创建人(通过线程上下文自动填充)
    private Long updater; // 更新人(通过线程上下文自动填充)
    }

  3. Lombok 最佳实践
    callSuper = true 确保审计字段参与对象比较和日志输出,避免以下问题:

    TicketOrderInfoDO order1 = TicketOrderInfoDO.builder().id(1L).build();
    TicketOrderInfoDO order2 = TicketOrderInfoDO.builder().id(1L).build();
    // 没有 callSuper=true 时可能错误判断两个对象相等

  4. 字段设计规范
    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; // 时间类型精确对应
典型代码生成场景
  1. 数据库表逆向工程
    根据 order_ticket_order_info 表结构自动生成字段(含注释)

  2. 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
设计亮点
  1. 模块化分层
    • 按业务域划分 ticketinfo 子包
    • 不同业务模块的 DO/Mapper 物理隔离
  1. 继承复用

    public class BaseDO {
    private LocalDateTime createTime;
    private LocalDateTime updateTime;
    private Long creator;
    private Long updater;
    }

  2. 动态查询

    // 自动处理空值条件
    .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);
}
核心机制解析
  1. 动态条件构建
    通过 LambdaQueryWrapperX 实现 40+ 字段的动态条件匹配:

    .eqIfPresent(TicketOrderInfoDO::getCenicSpotsId, reqVO.getCenicSpotsId())

等效于:

复制代码
<if test="reqVO.cenicSpotsId != null">
    cenic_spots_id = #{reqVO.cenicSpotsId}
</if>
  1. 分页结果处理
    PageResult<T> 封装结构:

    {
    "total": 100,
    "list": [...],
    "pageNo": 1,
    "pageSize": 10
    }

与前端分页参数自动对接

  1. 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();
}
  1. 复杂业务场景示例(门票订单核销)

    @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 格式返回给客户端。
主要功能模块分析
  1. 数据导入功能 (importTicketList)

    @Transactional
    public TicketImportRespVo importTicketList(List<TicketImportVo> list, Boolean updateSupport)

  • 实现Excel数据批量导入
  • 核心流程:
    • 参数校验 → 数据准备 → 时间格式处理 → 数据转换 → 持久化存储
    • 支持事务管理(@Transactional)
  • 关键日志:

    log.info("导入的数量:{}, 导入数据:{}", list.size(), list);
    log.info("\n================================== 导入开始 ============================");

  1. 基础CRUD操作

    // 创建(带ID返回)
    Long createTicketOrderInfo(TicketOrderInfoSaveReqVO createReqVO)

    // 更新(带存在性校验)
    void updateTicketOrderInfo(TicketOrderInfoSaveReqVO updateReqVO)

    // 删除(带存在性校验)
    void deleteTicketOrderInfo(Long id)

    // 分页查询
    PageResult<TicketOrderInfoDO> getTicketOrderInfoPage(TicketOrderInfoPageReqVO pageReqVO)

  2. 校验机制

    private void validateTicketOrderInfoExists(Long id) {
    if (ticketOrderInfoMapper.selectById(id) == null) {
    throw exception((ErrorCode) TICKET_ORDER_INFO_NOT_EXISTS);
    }
    }

关联组件
  1. 数据访问层
    • 依赖 实现数据库操作
    • 使用 MyBatis-Plus 的 BaseMapperX 基础功能
  1. 数据传输对象
    • 入参:
    • 出参:
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 拦截处理
架构特点
  1. 分层清晰:严格遵循 Controller -> Service -> Mapper 调用链
  2. DTO 隔离 :使用 VO 对象进行参数传递(...ReqVO/...RespVO
  3. 组件复用 :继承 common 模块中的基础组件(如 PageParamExcelUtils
  4. 文档集成:通过 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>
核心功能:
  1. 动态条件拼接 :通过 <if> 标签实现条件过滤
  2. 结果集映射:定义数据库字段到 VO 对象的映射关系
  3. 分页查询支持 :与 <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())
            ...);
    }
}
实现机制:
  1. 继承链BaseMapperXBaseMapper(MyBatis-Plus)
  2. 动态条件构建:使用 LambdaQueryWrapperX 的链式调用
  3. 方法命名规范
    • 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>
四、数据流向示意图
相关推荐
知兀10 分钟前
【MybatisPlus】后端用枚举类,数据库用tinyint,存在枚举类型转换
java
StockTV12 分钟前
印度股票实时数据 NSE和BSE的实时行情、K 线及指数数据
java·开发语言·spring boot·python
User_芊芊君子15 分钟前
【OpenAI 把 AI 玩明白了】:自主推理 + 动态知识图谱,这 4 个技术突破要颠覆行业
java·人工智能·知识图谱
c++之路1 小时前
C++20概述
java·开发语言·c++20
Championship.23.241 小时前
Linux Top 命令族深度解析与实战指南
java·linux·服务器·top·linux调试
橘子海全栈攻城狮1 小时前
【最新源码】养老院系统管理A013
java·spring boot·后端·web安全·微信小程序
逻辑驱动的ken1 小时前
Java高频面试考点18
java·开发语言·数据库·算法·面试·职场和发展·哈希算法
冷雨夜中漫步2 小时前
Claude Code源码分析——Claude Code Agent Loop 详细设计文档
java·开发语言·人工智能·ai
直奔標竿2 小时前
Java开发者AI转型第二十六课!Spring AI 个人知识库实战(五)——联网搜索增强实战
java·开发语言·人工智能·spring boot·后端·spring
one_love_zfl3 小时前
java面试-微服务组件篇
java·微服务·面试