微服务分层架构技术解析:从 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>
四、数据流向示意图
相关推荐
lllsure1 小时前
【快速入门】MyBatis
java·后端·mybatis
爱学习的学姐1 小时前
【精品源码】Java宠物领养网站+SpringBoot+VUE+前后端分离
java·spring boot·宠物
字节源流2 小时前
【SpringMVC】常用注解:@SessionAttributes
java·服务器·前端
贫道绝缘子3 小时前
Leetcode-132.Palindrome Partitioning II [C++][Java]
java·c++·算法·leetcode
信徒_3 小时前
java 中判断对象是否可以被回收和 GCROOT
java·开发语言·jvm
多多*4 小时前
浅谈Mysql数据库事务操作 用mybatis操作mysql事务 再在Springboot中使用Spring事务控制mysql事务回滚
java·数据库·windows·github·mybatis
Ttang234 小时前
SpringBoot(4)——SpringBoot自动配置原理
java·开发语言·spring boot·后端·spring·自动配置·原理
苏雨流丰4 小时前
Java中按照不同字段进行排序
java·开发语言
神仙别闹4 小时前
基于Java+MySQL实现的医药销售管理系统
java·开发语言·mysql
小九没绝活5 小时前
设计模式-原型模式
java·设计模式·原型模式