3/25 酒店微服务模块学习整理-日期(7/31输出给前端 校验)《酒店每日售卖管理系统设计与实现》

发布于:2025-03-28 ⋅ 阅读:(115) ⋅ 点赞:(0)

前言

本文详细解析了一个基于 Java 和 Spring 框架的酒店管理系统中的“酒店每日售卖管理”模块的代码实现。该模块涵盖了数据访问层(DAL)、服务层(Service)和控制层(Controller)的完整设计与实现。通过对代码的逐行分析,我们将深入了解其架构设计、业务逻辑处理以及与数据库的交互方式。同时,本文还将探讨如何通过合理的分层架构和设计模式提升系统的可维护性和扩展性

数据 dal 层

我将解析用户提供的 文件结构:

// 包声明(符合项目分层规范)
package cn.iocoder.central.module.hotel.dal.dataobject;

// 导入依赖
import lombok.*; // Lombok 简化代码
import com.baomidou.mybatisplus.annotation.*; // MyBatis-Plus 注解
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; // 基础DO类

// 类注解
@TableName("product_hotel_daily_sales") // 映射数据库表
@KeySequence("product_hotel_daily_sales_seq") // 序列主键(Oracle/PostgreSQL等)
@Data // Lombok 生成getter/setter
@EqualsAndHashCode(callSuper = true) // 包含父类字段
@ToString(callSuper = true) // 包含父类字段
@Builder // 建造者模式
@NoArgsConstructor // 无参构造
@AllArgsConstructor // 全参构造

// 类定义(继承BaseDO包含通用字段)
public class HotelDailySalesDO extends BaseDO {
    // 主键(自增)
    @TableId
    private Long id;
    
    // 业务字段
    private Long sellingRoomId; // 房间ID
    private LocalDateTime saleDate; // 销售日期
    
    // 状态类字段
    private Integer isForbiddenSale; // 禁售状态(需关联枚举)
    private Integer inventoryType; // 库存类型(需关联枚举)
    private Integer overbookingFlag; // 超卖标记(需关联枚举)
    
    // 价格类字段
    private BigDecimal salePrice; // 销售价
    private BigDecimal settlementPrice; // 结算价
    
    // 库存相关
    private Integer reservedInventory; // 保留库存
    private Integer salesCount; // 已售数量
    
    // 时间规则
    private String inventoryConfirmationTime; // 库存确认时间
    private String advanceBookingTime; // 提前预定时间
    
    // 退改规则
    private Integer refundPolicy; // 退改政策(需关联枚举)
    private String latestCancellationTime; // 最晚取消时间
    
    // 扣款类型
    private Integer deductionType; // 扣款方式(需关联枚举)
    
    // 供应商信息
    private String supplierCode; // 供应商编码
}

关键组成部分解析:

  1. ORM 映射
    • @TableName:映射数据库表 product_hotel_daily_sales
    • @KeySequence:用于 Oracle/PostgreSQL 的主键序列(MySQL 可忽略)
  1. 继承结构
    • 继承自 包含:
public abstract class BaseDO {
    private LocalDateTime createTime; // 创建时间
    private LocalDateTime updateTime; // 更新时间
    private String creator; // 创建人
    private String updater; // 更新人
    // ... 其他基础字段
}
  1. 字段类型规范
    • 金额字段使用 BigDecimal
    • 时间字段使用 LocalDateTime
    • 状态字段使用 Integer 配合枚举(需完善TODO)
  1. 需完善部分
// 示例:需要补充枚举类引用
/**
 * 禁售状态
 * 枚举 {@link cn.iocoder.central.module.hotel.enums.HotelForbiddenStatusEnum}
 */
private Integer isForbiddenSale;

关联代码生成器:

根据 模板生成,包含:

#foreach ($column in $columns)
    /**
     * ${column.columnComment}
     #if ("$!column.dictType" != "")
     * 枚举 {@link TODO ${column.dictType}}
     #end
     */
    private ${column.javaType} ${column.javaField};
#end

建议后续完善所有 TODO 注释,关联具体的枚举类以实现状态管理。

我将为您继续解析 <mcfile name="HotelDailySalesDO.java"></mcfile> 的业务字段设计规范:

业务字段分组解析

// ... [省略已解析部分]

// 核心业务字段组
// ==================== 库存控制 ==================== 
/** 库存类型 (0-非保留房 1-保留房) */
private Integer inventoryType;
/** 保留库存量 */
private Integer reservedInventory;
/** 已售数量 */
private Integer salesCount;

// ==================== 销售策略 ====================
/** 销售价 (需处理小数精度) */
private BigDecimal salePrice;
/** 结算价 (财务核算关键字段) */ 
private BigDecimal settlementPrice;

// ==================== 预定规则 ====================
/** 库存确认时间 (格式示例:"14:00") */
private String inventoryConfirmationTime;
/** 提前预定时间 (格式示例:"3天前18:00") */ 
private String advanceBookingTime;

// ==================== 风控策略 ====================
/** 超卖开关 (0-禁止 1-允许) */
private Integer overbookingFlag;
/** 扣款类型 (0-不扣 1-首日 2-全额) */ 
private Integer deductionType;

需要立即完善的枚举关联

// ... [示例修改其中一个枚举关联]
    /**
     * 禁售状态
     * 枚举 {@link cn.iocoder.central.module.hotel.enums.HotelSaleStatusEnum}
     */
    private Integer isForbiddenSale;

代码生成器关联分析

通过 <mcfile name="do.vm"></mcfile> 模板生成的字段包含以下关键处理:

#foreach ($column in $columns)
#if ("$!column.dictType" != "") 
    // 自动生成的枚举关联占位符
    * 枚举 {@link ${column.dictType}}
#end
#end

建议后续步骤:

  1. module-hotel 模块创建 enums
  2. 按字段创建对应枚举类 (示例结构):
@Getter
public enum HotelSaleStatusEnum {
    FORBIDDEN(0, "禁售"),
    ALLOWED(1, "正常销售");

    private final Integer code;
    private final String desc;

    HotelSaleStatusEnum(Integer code, String desc) {
        this.code = code;
        this.desc = desc;
    }
}

hoteldailysalesDO.java

package cn.iocoder.central.module.hotel.dal.dataobject;

import lombok.*;

import java.math.BigDecimal;
import java.time.LocalDateTime;

import com.baomidou.mybatisplus.annotation.*;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;

/**
 * 产品-酒店每日售卖管理 DO
 *
 * @author 陕文旅
 */
@TableName("product_hotel_daily_sales")
@KeySequence("product_hotel_daily_sales_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class HotelDailySalesDO extends BaseDO {

    /**
     * 主键
     */
    @TableId
    private Long id;
    /**
     * 房间id
     */
    private Long sellingRoomId;
    /**
     * 日期
     */
    private LocalDateTime saleDate;
    /**
     * 禁售(0禁售1不禁售)
     *
     * 枚举 {@link TODO is_banned 对应的类}
     */
    private Integer isForbiddenSale;
    /**
     * 销售价
     */
    private BigDecimal salePrice;
    /**
     * 结算价
     */
    private BigDecimal settlementPrice;
    /**
     * 库存类型0非保留房1保留房
     *
     * 枚举 {@link TODO inventory_type 对应的类}
     */
    private Integer inventoryType;
    /**
     * 保留库存
     */
    private Integer reservedInventory;
    /**
     * 销售数量
     */
    private Integer salesCount;
    /**
     * 超卖(0不可1可)
     *
     * 枚举 {@link TODO overbooking_flag 对应的类}
     */
    private Integer overbookingFlag;
    /**
     * 库存确定时间
     */
    private String inventoryConfirmationTime;
    /**
     * 提前预定时间
     */
    private String advanceBookingTime;
    /**
     * 退改规则0免费退改1有条件退改2不可退改
     *
     * 枚举 {@link TODO refund_change_rule 对应的类}
     */
    private Integer refundPolicy;
    /**
     * 最晚无损取消时间
     */
    private String latestCancellationTime;
    /**
     * 扣款类型(0否1首日2全额)
     *
     * 枚举 {@link TODO deduction_type 对应的类}
     */
    private Integer deductionType;
    /**
     * 供应商编码
     */
    private String supplierCode;

}

hoteldailysalesmapper.java

package cn.iocoder.central.module.hotel.dal.mysql;

import cn.iocoder.central.module.hotel.controller.admin.vo.dailysales.HotelDailySalesPageReqVO;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.central.module.hotel.dal.dataobject.HotelDailySalesDO;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import jakarta.validation.constraints.NotNull;
import org.apache.ibatis.annotations.Mapper;

import java.time.LocalDateTime;
import java.util.List;

/**
 * 产品-酒店每日售卖管理 Mapper
 *
 * @author 陕文旅
 */
@Mapper
public interface HotelDailySalesMapper extends BaseMapperX<HotelDailySalesDO> {

    default PageResult<HotelDailySalesDO> selectPage(HotelDailySalesPageReqVO reqVO) {
        return selectPage(reqVO, new LambdaQueryWrapperX<HotelDailySalesDO>()
                .betweenIfPresent(HotelDailySalesDO::getSaleDate, reqVO.getSaleDate())
                .eqIfPresent(HotelDailySalesDO::getIsForbiddenSale, reqVO.getIsForbiddenSale())
                .eqIfPresent(HotelDailySalesDO::getSalePrice, reqVO.getSalePrice())
                .eqIfPresent(HotelDailySalesDO::getSettlementPrice, reqVO.getSettlementPrice())
                .eqIfPresent(HotelDailySalesDO::getInventoryType, reqVO.getInventoryType())
                .eqIfPresent(HotelDailySalesDO::getReservedInventory, reqVO.getReservedInventory())
                .eqIfPresent(HotelDailySalesDO::getOverbookingFlag, reqVO.getOverbookingFlag())
                .eqIfPresent(HotelDailySalesDO::getRefundPolicy, reqVO.getRefundPolicy())
                .eqIfPresent(HotelDailySalesDO::getDeductionType, reqVO.getDeductionType())
                .eqIfPresent(HotelDailySalesDO::getSupplierCode, reqVO.getSupplierCode())
                .betweenIfPresent(HotelDailySalesDO::getCreateTime, reqVO.getCreateTime())
                .orderByDesc(HotelDailySalesDO::getId));
    }

    default List<HotelDailySalesDO> selectlistBySellingId(Long sellingId, LocalDateTime startDate, LocalDateTime endDate) {
        return selectList(new LambdaQueryWrapperX<HotelDailySalesDO>()
                .eq(HotelDailySalesDO::getSellingRoomId, sellingId)
                .betweenIfPresent(HotelDailySalesDO::getSaleDate, startDate, endDate)
                .orderByAsc(HotelDailySalesDO::getSaleDate));
    }

    default void updateIsForbiddenSale(Long id){
        // 构造更新条件
        LambdaUpdateWrapper<HotelDailySalesDO> updateWrapper = new LambdaUpdateWrapper<>();
        updateWrapper.setSql("is_forbidden_sale = CASE WHEN is_forbidden_sale = 0 THEN 1 ELSE 0 END")
                .eq(HotelDailySalesDO::getId, id);

        // 执行更新操作
        this.update(updateWrapper);
    }


    default void updateIsForbiddenSaleByDate(String dateStr, Long sellingRoomId, int isForbiddenSale){
        update(new LambdaUpdateWrapper<HotelDailySalesDO>()
                .set(HotelDailySalesDO::getIsForbiddenSale, isForbiddenSale)
                .eq(HotelDailySalesDO::getSaleDate, dateStr)
                .eq(HotelDailySalesDO::getSellingRoomId, sellingRoomId));
    }

    void updateUseXmlById(HotelDailySalesDO bean);
}

包名和导入部分

package cn.iocoder.central.module.hotel.dal.mysql;

import cn.iocoder.central.module.hotel.controller.admin.vo.dailysales.HotelDailySalesPageReqVO;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.central.module.hotel.dal.dataobject.HotelDailySalesDO;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import jakarta.validation.constraints.NotNull;
import org.apache.ibatis.annotations.Mapper;

import java.time.LocalDateTime;
import java.util.List;
  • 包名cn.iocoder.central.module.hotel.dal.mysql,表示这个Mapper接口属于酒店模块的MySQL数据访问层。
  • 导入的类
    • HotelDailySalesPageReqVO:分页查询请求对象,用于接收前端传来的分页查询参数。
    • PageResult:分页查询结果对象,用于封装分页查询的结果。
    • LambdaQueryWrapperX:扩展了MyBatis-Plus的LambdaQueryWrapper,提供了更灵活的查询条件构造方式。
    • BaseMapperX:自定义的Mapper基础接口,继承了MyBatis-Plus的BaseMapper,可能添加了一些额外的通用方法。
    • HotelDailySalesDO:数据对象,对应数据库中的酒店每日售卖信息表。
    • LambdaUpdateWrapper:用于构造更新操作的条件。
    • jakarta.validation.constraints.NotNull:用于注解参数,表示该参数不能为空。
    • org.apache.ibatis.annotations.Mapper:MyBatis的注解,用于标记这是一个Mapper接口。
    • java.time.LocalDateTime:用于处理日期和时间。
    • java.util.List:用于表示集合。

类注释和注解

/**
 * 产品-酒店每日售卖管理 Mapper
 *
 * @author 陕文旅
 */
@Mapper
public interface HotelDailySalesMapper extends BaseMapperX<HotelDailySalesDO> {
  • 类注释:说明了这个Mapper接口的用途,即用于管理酒店每日售卖信息。
  • @Mapper注解:标记这是一个MyBatis的Mapper接口,MyBatis会自动扫描并实现这个接口。
  • extends BaseMapperX<HotelDailySalesDO>:继承了BaseMapperX,这意味着这个Mapper接口继承了BaseMapperX中定义的所有通用方法,如selectByIdinsert等,同时HotelDailySalesDO是这个Mapper操作的数据对象。

方法解析

1. selectPage方法
default PageResult<HotelDailySalesDO> selectPage(HotelDailySalesPageReqVO reqVO) {
    return selectPage(reqVO, new LambdaQueryWrapperX<HotelDailySalesDO>()
            .betweenIfPresent(HotelDailySalesDO::getSaleDate, reqVO.getSaleDate())
            .eqIfPresent(HotelDailySalesDO::getIsForbiddenSale, reqVO.getIsForbiddenSale())
            .eqIfPresent(HotelDailySalesDO::getSalePrice, reqVO.getSalePrice())
            .eqIfPresent(HotelDailySalesDO::getSettlementPrice, reqVO.getSettlementPrice())
            .eqIfPresent(HotelDailySalesDO::getInventoryType, reqVO.getInventoryType())
            .eqIfPresent(HotelDailySalesDO::getReservedInventory, reqVO.getReservedInventory())
            .eqIfPresent(HotelDailySalesDO::getOverbookingFlag, reqVO.getOverbookingFlag())
            .eqIfPresent(HotelDailySalesDO::getRefundPolicy, reqVO.getRefundPolicy())
            .eqIfPresent(HotelDailySalesDO::getDeductionType, reqVO.getDeductionType())
            .eqIfPresent(HotelDailySalesDO::getSupplierCode, reqVO.getSupplierCode())
            .betweenIfPresent(HotelDailySalesDO::getCreateTime, reqVO.getCreateTime())
            .orderByDesc(HotelDailySalesDO::getId));
}
  • 功能:根据分页查询请求对象reqVO的条件,查询酒店每日售卖信息的分页数据。
  • LambdaQueryWrapperX:用于构造查询条件。
    • betweenIfPresent:如果reqVO中对应的字段不为空,则添加between条件。
    • eqIfPresent:如果reqVO中对应的字段不为空,则添加=条件。
    • orderByDesc:按id字段降序排序。
  • 返回值PageResult<HotelDailySalesDO>,分页查询的结果,包含查询到的数据和分页信息。
2. selectlistBySellingId方法
default List<HotelDailySalesDO> selectlistBySellingId(Long sellingId, LocalDateTime startDate, LocalDateTime endDate) {
    return selectList(new LambdaQueryWrapperX<HotelDailySalesDO>()
            .eq(HotelDailySalesDO::getSellingRoomId, sellingId)
            .betweenIfPresent(HotelDailySalesDO::getSaleDate, startDate, endDate)
            .orderByAsc(HotelDailySalesDO::getSaleDate));
}
  • 功能:根据销售房间IDsellingId和日期范围startDateendDate,查询酒店每日售卖信息的列表。
  • LambdaQueryWrapperX
    • eq:添加=条件,筛选出sellingRoomId等于sellingId的记录。
    • betweenIfPresent:如果startDateendDate不为空,则添加between条件,筛选出saleDate在该日期范围内的记录。
    • orderByAsc:按saleDate字段升序排序。
  • 返回值List<HotelDailySalesDO>,查询到的酒店每日售卖信息列表。
3. updateIsForbiddenSale方法
default void updateIsForbiddenSale(Long id){
    // 构造更新条件
    LambdaUpdateWrapper<HotelDailySalesDO> updateWrapper = new LambdaUpdateWrapper<>();
    updateWrapper.setSql("is_forbidden_sale = CASE WHEN is_forbidden_sale = 0 THEN 1 ELSE 0 END")
            .eq(HotelDailySalesDO::getId, id);

    // 执行更新操作
    this.update(updateWrapper);
}
  • 功能:根据IDid,更新酒店每日售卖信息的is_forbidden_sale字段的值,将其取反(0变1,1变0)。
  • LambdaUpdateWrapper
    • setSql:通过SQL语句直接设置更新的字段值,这里使用了CASE WHEN语句实现取反操作。
    • eq:添加=条件,指定要更新的记录的id
  • this.update:调用MyBatis-Plus的update方法执行更新操作。
4. updateIsForbiddenSaleByDate方法
default void updateIsForbiddenSaleByDate(String dateStr, Long sellingRoomId, int isForbiddenSale){
    update(new LambdaUpdateWrapper<HotelDailySalesDO>()
            .set(HotelDailySalesDO::getIsForbiddenSale, isForbiddenSale)
            .eq(HotelDailySalesDO::getSaleDate, dateStr)
            .eq(HotelDailySalesDO::getSellingRoomId, sellingRoomId));
}
  • 功能:根据销售日期dateStr和销售房间IDsellingRoomId,更新酒店每日售卖信息的is_forbidden_sale字段的值为isForbiddenSale
  • LambdaUpdateWrapper
    • set:设置要更新的字段is_forbidden_sale的值为isForbiddenSale
    • eq:添加两个=条件,分别指定saleDatesellingRoomId
  • update:执行更新操作。
5. updateUseXmlById方法
void updateUseXmlById(HotelDailySalesDO bean);
  • 功能:根据传入的HotelDailySalesDO对象bean,更新数据库中的记录。这个方法的具体实现可能在Mapper的XML文件中定义,而不是通过MyBatis-Plus的注解或方法实现。
  • 参数HotelDailySalesDO bean,包含要更新的字段和对应的值。

服务层

HotelDailySalesService.java

package cn.iocoder.central.module.hotel.service;

import cn.iocoder.central.module.hotel.controller.admin.vo.dailysales.Daily.HotelDailyPageReqVO;
import cn.iocoder.central.module.hotel.controller.admin.vo.dailysales.Daily.HotelDailyRespVO;
import cn.iocoder.central.module.hotel.controller.admin.vo.dailysales.HotelDailySalesPageReqVO;
import cn.iocoder.central.module.hotel.controller.admin.vo.dailysales.HotelDailySalesSaveReqVO;
import cn.iocoder.central.module.hotel.controller.admin.vo.dailysales.HotelDailySalesTimeRangeReqVO;
import cn.iocoder.central.module.hotel.controller.admin.vo.dailysales.HotelDailySalesUpdateReqVO;
import jakarta.validation.*;
import cn.iocoder.central.module.hotel.dal.dataobject.HotelDailySalesDO;
import cn.iocoder.yudao.framework.common.pojo.PageResult;

/**
 * 产品-酒店每日售卖管理 Service 接口
 *
 * @author 陕文旅
 */
public interface HotelDailySalesService {

    /**
     * 创建产品-酒店每日售卖管理
     *
     * @param createReqVO 创建信息
     * @return 编号
     */
    boolean createHotelDailySales(@Valid HotelDailySalesSaveReqVO createReqVO);

    /**
     * 更新产品-酒店每日售卖管理
     *
     * @param updateReqVO 更新信息
     */
    void updateHotelDailySales(@Valid HotelDailySalesSaveReqVO updateReqVO);

    /**
     * 删除产品-酒店每日售卖管理
     *
     * @param id 编号
     */
    void deleteHotelDailySales(Long id);

    /**
     * 获得产品-酒店每日售卖管理
     *
     * @param id 编号
     * @return 产品-酒店每日售卖管理
     */
    HotelDailySalesDO getHotelDailySales(Long id);

    /**
     * 获得产品-酒店每日售卖管理分页
     *
     * @param pageReqVO 分页查询
     * @return 产品-酒店每日售卖管理分页
     */
    PageResult<HotelDailySalesDO> getHotelDailySalesPage(HotelDailySalesPageReqVO pageReqVO);

    PageResult<HotelDailyRespVO> getHotelDailyPage(@Valid HotelDailyPageReqVO pageReqVO);

    void updateIsForbiddenSale(Long id);

    Boolean createHotelDailySalesByTimeRange(@Valid HotelDailySalesTimeRangeReqVO createReqVO);

    void updateAreForbiddenSale(@Valid HotelDailySalesUpdateReqVO hotelDailySalesUpdateReqVO);
}

一、模块构成分析

// 包结构:酒店模块服务层
package cn.iocoder.central.module.hotel.service;

// VO(Value Object)层导入:用于参数接收和响应
import cn.iocoder.central.module.hotel.controller.admin.vo.dailysales.*;
// 数据对象导入:数据库实体映射
import cn.iocoder.central.module.hotel.dal.dataobject.HotelDailySalesDO;
// 分页工具类:来自yudao框架
import cn.iocoder.yudao.framework.common.pojo.PageResult;

二、接口方法解析(按功能分类)

public interface HotelDailySalesService {

    // 基础CRUD操作
    boolean createHotelDailySales(@Valid HotelDailySalesSaveReqVO createReqVO);
    void updateHotelDailySales(@Valid HotelDailySalesSaveReqVO updateReqVO);
    void deleteHotelDailySales(Long id);
    
    // 数据查询
    HotelDailySalesDO getHotelDailySales(Long id);
    PageResult<HotelDailySalesDO> getHotelDailySalesPage(HotelDailySalesPageReqVO pageReqVO);
    
    // 特殊业务操作
    void updateIsForbiddenSale(Long id); // 单条记录禁售状态切换
    Boolean createHotelDailySalesByTimeRange(@Valid HotelDailySalesTimeRangeReqVO createReqVO); // 批量创建
    void updateAreForbiddenSale(@Valid HotelDailySalesUpdateReqVO hotelDailySalesUpdateReqVO); // 批量状态更新
    
    // 关联业务查询(Daily子模块)
    PageResult<HotelDailyRespVO> getHotelDailyPage(@Valid HotelDailyPageReqVO pageReqVO);
}

三、文件联动关系图

graph TD
    A[HotelDailySalesService] -->|实现| B[HotelDailySalesServiceImpl]
    A -->|使用| C[HotelDailySalesDO]
    A -->|参数接收| D[HotelDailySalesSaveReqVO]
    A -->|参数接收| E[HotelDailySalesPageReqVO]
    A -->|返回| F[PageResult]
    C -->|映射| G[hotel_daily_sales表]
    B -->|依赖| H[HotelDailySalesMapper]
    H -->|SQL| I[HotelDailySalesMapper.xml]
    D -->|被| J[HotelDailySalesController]使用

四、关键联动说明

  1. 与控制层联动:
  • 控制器会注入本Service
  • 接收前端参数时会使用本接口中的*ReqVO对象
  1. 与持久层联动:
  • 通过操作数据库
  • XML映射文件定义在resources/mapper/hotel/HotelDailySalesMapper.xml
  1. 与领域对象联动:
  • 对应数据库表结构
  • 字段定义与数据库表hotel_daily_sales严格对应

五、方法参数设计特点

// 分页查询参数示例
public PageResult<HotelDailySalesDO> getHotelDailySalesPage(
    HotelDailySalesPageReqVO pageReqVO) { // 专用分页参数对象
    ...
}

// 批量操作参数示例
public Boolean createHotelDailySalesByTimeRange(
    @Valid HotelDailySalesTimeRangeReqVO createReqVO) { // 包含时间范围
    ...
}

所有参数对象都使用VO进行严格校验(通过@Valid注解),保证输入数据的合法性。

HotelDailySalesServiceimpl.java

package cn.iocoder.central.module.hotel.service;

import cn.iocoder.central.module.hotel.controller.admin.vo.dailysales.*;
import cn.iocoder.central.module.hotel.controller.admin.vo.dailysales.Daily.HotelDailyPageReqVO;
import cn.iocoder.central.module.hotel.controller.admin.vo.dailysales.Daily.HotelDailyRespVO;
import cn.iocoder.central.module.hotel.controller.admin.vo.dailysales.Daily.HotelDailySellingList;
import cn.iocoder.central.module.hotel.controller.admin.vo.selling.HotelSellingRespVO;
import cn.iocoder.central.module.hotel.dal.mysql.HotelPhysicalMapper;
import cn.iocoder.central.module.hotel.dal.mysql.HotelSellingMapper;
import cn.iocoder.central.module.hotel.enums.ErrorCodeConstants;
import cn.iocoder.yudao.framework.common.util.date.DateTimeFormatterUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import jakarta.annotation.Resource;
import org.springframework.validation.annotation.Validated;

import cn.iocoder.central.module.hotel.dal.dataobject.HotelDailySalesDO;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;

import cn.iocoder.central.module.hotel.dal.mysql.HotelDailySalesMapper;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;

import static cn.iocoder.central.module.hotel.enums.ErrorCodeConstants.*;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;


/**
 * 产品-酒店每日售卖管理 Service 实现类
 *
 * @author 陕文旅
 */
@Service
@Validated
public class HotelDailySalesServiceImpl implements HotelDailySalesService {

    @Resource
    private HotelDailySalesMapper hotelDailySalesMapper;
    @Autowired
    private HotelSellingMapper hotelSellingMapper;
    @Autowired
    private HotelPhysicalMapper hotelPhysicalMapper;


    @Override
    public boolean createHotelDailySales(HotelDailySalesSaveReqVO createReqVO) {
        //遍历每一个日期
        for (String saleDate : createReqVO.getSaleDate()) {
            //转化日期格式
            saleDate = DateTimeFormatterUtils.formatDateTime(saleDate);
            //查询这个日期是否存在,获取id
            Long id = hotelSellingMapper.selectByDateAndSellId(saleDate,createReqVO.getSellingRoomId());
            //创建传入数据
            HotelDailySalesSaveVO hotelDailySalesSaveVO = BeanUtils.toBean(createReqVO, HotelDailySalesSaveVO.class);
            hotelDailySalesSaveVO.setId(id);
            hotelDailySalesSaveVO.setSaleDate(saleDate);
            if(id == null){
                //插入数据
                hotelDailySalesMapper.insert(BeanUtils.toBean(hotelDailySalesSaveVO, HotelDailySalesDO.class));
            }else{
                //修改数据
                hotelDailySalesMapper.updateUseXmlById(BeanUtils.toBean(hotelDailySalesSaveVO, HotelDailySalesDO.class));
            }
        }
        // 返回
        return true;
    }

    @Override
    public void updateHotelDailySales(HotelDailySalesSaveReqVO updateReqVO) {
        // 校验存在
        validateHotelDailySalesExists(updateReqVO.getId());
        // 更新
        HotelDailySalesDO updateObj = BeanUtils.toBean(updateReqVO, HotelDailySalesDO.class);
        hotelDailySalesMapper.updateById(updateObj);
    }

    @Override
    public void deleteHotelDailySales(Long id) {
        // 校验存在
        validateHotelDailySalesExists(id);
        // 删除
        hotelDailySalesMapper.deleteById(id);
    }

    private void validateHotelDailySalesExists(Long id) {
        if (hotelDailySalesMapper.selectById(id) == null) {
            throw exception(HOTEL_DAILY_SALES_NOT_EXISTS);
        }
    }

    @Override
    public HotelDailySalesDO getHotelDailySales(Long id) {
        return hotelDailySalesMapper.selectById(id);
    }

    @Override
    public PageResult<HotelDailySalesDO> getHotelDailySalesPage(HotelDailySalesPageReqVO pageReqVO) {
        return hotelDailySalesMapper.selectPage(pageReqVO);
    }

    @Override
    public PageResult<HotelDailyRespVO> getHotelDailyPage(HotelDailyPageReqVO pageReqVO) {
        // 查询物理表,获取物理id、物理名称
        List<HotelDailyRespVO> hotelDailyRespVOList = BeanUtils.toBean(
                hotelPhysicalMapper.selectByHotelId(pageReqVO.getHotelId()),HotelDailyRespVO.class);

        // 遍历物理表,获取售卖房型和每日价格
        hotelDailyRespVOList.forEach(hotelDailyRespVO -> {
            //传入物理房型id和查询参数
            hotelDailyRespVO.setSellingList(getHotelDailySellingList(hotelDailyRespVO.getId(),pageReqVO));
        });

        // 获取总条数并构造PageResult
        Long total = (long) hotelDailyRespVOList.size();
        return new PageResult<>(hotelDailyRespVOList, total);
    }

    @Override
    public void updateIsForbiddenSale(Long id) {
        // 校验存在
        validateHotelDailySalesExists(id);
        // 修改数据
        hotelDailySalesMapper.updateIsForbiddenSale(id);
    }

    @Override
    public Boolean createHotelDailySalesByTimeRange(HotelDailySalesTimeRangeReqVO createReqVO) {

        // 定义日期格式化器
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");

        // 解析起始日期和结束日期
        LocalDate startDate = LocalDate.parse(createReqVO.getBeginSaleDate(), formatter);
        LocalDate endDate = LocalDate.parse(createReqVO.getEndSaleDate(), formatter);

        // 遍历日期范围
        for (LocalDate date = startDate; !date.isAfter(endDate); date = date.plusDays(1)) {
            // 获取当前日期的星期值(1-7对应周一到周日)
            int currentDayOfWeek = date.getDayOfWeek().getValue();
            // 检查是否符合星期条件
            if (isDayOfWeekMatch(currentDayOfWeek, createReqVO.getWeek())) {
                //符合
                //转化日期格式
                String formattedDate = date.format(formatter);
                //查询这个日期是否存在,获取id
                Long id = hotelSellingMapper.selectByDateAndSellId(formattedDate,createReqVO.getSellingRoomId());
                //创建传入数据
                HotelDailySalesSaveVO hotelDailySalesSaveVO = BeanUtils.toBean(createReqVO, HotelDailySalesSaveVO.class);
                hotelDailySalesSaveVO.setId(id);
                hotelDailySalesSaveVO.setSaleDate(formattedDate);
                if(id == null){
                    //插入数据
                    hotelDailySalesMapper.insert(BeanUtils.toBean(hotelDailySalesSaveVO, HotelDailySalesDO.class));
                }else{
                    //修改数据
                    hotelDailySalesMapper.updateUseXmlById(BeanUtils.toBean(hotelDailySalesSaveVO, HotelDailySalesDO.class));
                }
            }
        }
        // 返回
        return true;
    }

    @Override
    public void updateAreForbiddenSale(HotelDailySalesUpdateReqVO updateReqVO) {
        // 定义日期格式化器
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
        // 遍历售卖房型ID
        for (Long sellingRoomId : updateReqVO.getSellingRoomId()) {
            if (updateReqVO.getTimeType() == 0) {
                // timeType为0,表示按时间段处理
                if (updateReqVO.getSaleDate() == null || updateReqVO.getSaleDate().length != 2) {
                    //要求日期数组有且只有两个,起始和结束时间
                    throw exception(ErrorCodeConstants.SALE_DATE_RANGE_INVALID);
                }
                // 解析起始日期和结束日期
                LocalDate startDate = LocalDate.parse(updateReqVO.getSaleDate()[0], formatter);
                LocalDate endDate = LocalDate.parse(updateReqVO.getSaleDate()[1], formatter);
                // 遍历日期范围
                for (LocalDate date = startDate; !date.isAfter(endDate); date = date.plusDays(1)) {
                    // 检查当前日期是否符合星期条件
                    if (isDayOfWeekMatch(date.getDayOfWeek().getValue(), updateReqVO.getWeek())) {
                        //转化日期格式
                        String dateStr = date.format(formatter);
                        //修改
                        hotelDailySalesMapper.updateIsForbiddenSaleByDate(dateStr,sellingRoomId,updateReqVO.getIsForbiddenSale());
                    }
                }
            } else if (updateReqVO.getTimeType() == 1) {
                // timeType为1,表示按具体日期列表处理
                for (String dateStr : updateReqVO.getSaleDate()) {
                    // 修改
                    hotelDailySalesMapper.updateIsForbiddenSaleByDate(dateStr,sellingRoomId,updateReqVO.getIsForbiddenSale());
                }
            } else {
                // 其他情况,抛出异常
                throw exception(TIME_TYPE_IS_ERROR);
            }
        }

    }
    /**
     * 判断当前日期的星期值是否符合星期条件
     *
     * @param currentDayOfWeek 当前日期的星期值(1-7)
     * @param week             星期条件数组
     * @return 是否匹配
     */
    private boolean isDayOfWeekMatch(int currentDayOfWeek, int[] week) {
        // 如果星期条件包含8,则表示全部星期都匹配
        for (int day : week) {
            if (day == currentDayOfWeek) {
                return true;
            }
        }
        return false;
    }


    /**
     * 根据产品ID和页面请求参数获取酒店每日销售列表
     * @param PId 产品ID,用于查询特定产品的每日销售情况
     * @param pageReqVO 包含分页和过滤条件的请求对象,用于定制查询结果
     * @return 返回一个包含酒店每日销售信息的列表
     */
    private List<HotelDailySellingList> getHotelDailySellingList(Long PId,HotelDailyPageReqVO pageReqVO){
        //创建返回对象列表
        List<HotelDailySellingList> hotelDailySellingLists = new ArrayList<>();

        //获取售卖房型
        List<HotelSellingRespVO> hotelSellingRespVO = BeanUtils.toBean(
                hotelSellingMapper.selectByPId(PId,pageReqVO.getRoomTypeStatus()),HotelSellingRespVO.class);

        //遍历售卖房型
        hotelSellingRespVO.forEach(hsr -> {
            //创建返回对象并补充参数
            HotelDailySellingList hotelDailySellingList= new HotelDailySellingList();
            hotelDailySellingList.setSelling(hsr);
            //调用方法,传入售卖房型id和查询参数,获取每日价格
            hotelDailySellingList.setDailySales(getHotelDailySalesRespVO(hsr.getId(),pageReqVO));
            //加入返回对象列表
            hotelDailySellingLists.add(hotelDailySellingList);
        });

        return hotelDailySellingLists;
    }

    /**
     * 获取酒店每日销售响应VO列表
     * @param SellingId 酒店销售ID
     * @param pageReqVO 酒店每日页面请求VO
     * @return 酒店每日销售响应VO列表
     */
    private List<HotelDailySalesRespVO> getHotelDailySalesRespVO(Long SellingId,HotelDailyPageReqVO pageReqVO){
        //定义限制日期
        int limit;
        //判断显示的状态
        if(pageReqVO.getDisplayStatus() == 0){
            //为按星期,只要七天
            limit = 6;
        } else if (pageReqVO.getDisplayStatus() == 1) {
            //为月份,只要一个月
            limit = 34;
        }else{
            //显示状态错误
            throw exception(Display_Status_NOT_EXISTS);
        }

        // 获取起始日期
        String date = DateTimeFormatterUtils.formatDateTime(pageReqVO.getDate());
        LocalDateTime startDate = LocalDateTime.parse(date, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
        // 获取结束日期
        LocalDateTime endDate = startDate.plusDays(limit);

        //获取数据
        List<HotelDailySalesRespVO> hdsr = BeanUtils.toBean(
                hotelDailySalesMapper.selectlistBySellingId(SellingId,startDate,endDate),HotelDailySalesRespVO.class);
        //定义标识,temp指向当前list的值,len为list长度
        int temp=0;
        int len=hdsr.size();

        //创建结果list
        List<HotelDailySalesRespVO> resultList = new ArrayList<>();

        // 日期增长循环
        for (LocalDateTime currentDate = startDate; !currentDate.isAfter(endDate); currentDate = currentDate.plusDays(1)) {
            //temp小于长度,并且当前日期等于list中的日期,则加入结果list,temp加一
            if(temp<len&&currentDate.isEqual(hdsr.get(temp).getSaleDate())){
                resultList.add(hdsr.get(temp));
                temp++;
            }else{
                // 否则,加入null
                HotelDailySalesRespVO hotelDailySalesRespVO=new HotelDailySalesRespVO();
                hotelDailySalesRespVO.setSaleDate(currentDate);
                resultList.add(hotelDailySalesRespVO);
            }
        }

        return resultList;
    }

}

一、实现类结构解析

// 服务层实现注解
@Service
@Validated
public class HotelDailySalesServiceImpl implements HotelDailySalesService {

    // 持久层依赖注入
    @Resource
    private HotelDailySalesMapper hotelDailySalesMapper;
    @Autowired
    private HotelSellingMapper hotelSellingMapper;  // 关联房型售卖Mapper
    @Autowired
    private HotelPhysicalMapper hotelPhysicalMapper; // 关联物理房型Mapper

    // 核心方法实现...
}

二、核心方法实现解析

  1. 批量创建逻辑(时空维度处理)
@Override
public Boolean createHotelDailySalesByTimeRange(HotelDailySalesTimeRangeReqVO createReqVO) {
    // 日期范围解析(支持跨月处理)
    LocalDate startDate = LocalDate.parse(createReqVO.getBeginSaleDate(), formatter);
    LocalDate endDate = LocalDate.parse(createReqVO.getEndSaleDate(), formatter);

    // 周循环算法(支持跨周日期生成)
    for (LocalDate date = startDate; !date.isAfter(endDate); date = date.plusDays(1)) {
        // 星期匹配算法(支持多选周几)
        if (isDayOfWeekMatch(date.getDayOfWeek().getValue(), createReqVO.getWeek())) {
            // 智能写入策略(存在则更新,不存在则插入)
            Long id = hotelSellingMapper.selectByDateAndSellId(...);
            if(id == null){
                hotelDailySalesMapper.insert(...);
            }else{
                hotelDailySalesMapper.updateUseXmlById(...);
            }
        }
    }
}
  1. 复合分页查询架构
@Override
public PageResult<HotelDailyRespVO> getHotelDailyPage(HotelDailyPageReqVO pageReqVO) {
    // 三级联查架构
    // 1. 物理房型基础查询
    List<HotelDailyRespVO> respList = BeanUtils.toBean(
        hotelPhysicalMapper.selectByHotelId(...), HotelDailyRespVO.class);

    // 2. 嵌套房型售卖数据
    respList.forEach(resp -> {
        resp.setSellingList(getHotelDailySellingList(...));
    });

    // 3. 动态日期填充策略
    private List<HotelDailySalesRespVO> getHotelDailySalesRespVO(...) {
        // 智能日期填充(补全缺失日期空数据)
        for (currentDate = startDate; !currentDate.isAfter(endDate); ...) {
            if(日期存在数据) add数据 else add空对象
        }
    }
}

三、服务联动图谱

graph TD
    A[ServiceImpl] -->|依赖| B[HotelDailySalesMapper]
    A -->|调用| C[HotelSellingMapper]
    A -->|关联| D[HotelPhysicalMapper]
    B -->|执行| E[HotelDailySalesMapper.xml]
    C -->|查询| F[hotel_selling表]
    D -->|映射| G[hotel_physical表]
    A -->|消费| H[HotelDailySalesSaveReqVO]
    A -->|返回| I[HotelDailyRespVO]
    J[HotelDailySalesController] -->|调用| A
    K[ErrorCodeConstants] -->|异常码| A
    L[BeanUtils] -->|对象转换| A

四、关键联动说明

  1. 与Mapper层协作:
  • 使用执行基础CRUD
  • 特殊操作通过XML映射实现,如:
<update id="updateIsForbiddenSaleByDate">
    UPDATE hotel_daily_sales 
    SET is_forbidden_sale = #{isForbidden} 
    WHERE selling_room_id = #{sellId} AND sale_date = #{date}
</update>
  1. 跨模块协作:
  • 通过获取房型售卖数据
  • 使用查询物理房型基础信息
  1. 工具类协作:
  • 实现VO/DO转换
  • 处理日期格式化
  1. 异常处理体系:
// 统一异常码引用
throw exception(HOTEL_DAILY_SALES_NOT_EXISTS); 
// 对应<mcsymbol name="ErrorCodeConstants" filename="ErrorCodeConstants.java" path="c:\project\centralservice\central-module-hotel/central-module-hotel-biz/src/main/java/cn/iocoder/central/module/hotel/enums/ErrorCodeConstants.java" startline="1" type="class"></mcsymbol>中的定义

控制层

package cn.iocoder.central.module.hotel.controller.admin;

import cn.iocoder.central.module.hotel.controller.admin.vo.dailysales.*;
import cn.iocoder.central.module.hotel.controller.admin.vo.dailysales.Daily.HotelDailyPageReqVO;
import cn.iocoder.central.module.hotel.controller.admin.vo.dailysales.Daily.HotelDailyRespVO;
import org.springframework.web.bind.annotation.*;
import jakarta.annotation.Resource;
import org.springframework.validation.annotation.Validated;
import org.springframework.security.access.prepost.PreAuthorize;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Operation;

import jakarta.validation.*;
import jakarta.servlet.http.*;
import java.util.*;
import java.io.IOException;

import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;

import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;

import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.*;

import cn.iocoder.central.module.hotel.dal.dataobject.HotelDailySalesDO;
import cn.iocoder.central.module.hotel.service.HotelDailySalesService;

@Tag(name = "管理后台 - 产品-酒店每日售卖管理")
@RestController
@RequestMapping("/hotel/product/hotel-daily-sales")
@Validated
public class HotelDailySalesController {

    @Resource
    private HotelDailySalesService hotelDailySalesService;

    @PostMapping("/createByDate")
    @Operation(summary = "根据日期创建或修改多个天数价格-酒店每日售卖管理")
    @PreAuthorize("@ss.hasPermission('product:hotel-daily-sales:create')")
    public CommonResult<Boolean> createHotelDailySalesByDate(@Valid @RequestBody HotelDailySalesSaveReqVO createReqVO) {
        return success(hotelDailySalesService.createHotelDailySales(createReqVO));
    }

    @PostMapping("/createByTimeRange")
    @Operation(summary = "根据时段创建或修改多个天数价格-酒店每日售卖管理")
    @PreAuthorize("@ss.hasPermission('product:hotel-daily-sales:create')")
    public CommonResult<Boolean> createHotelDailySales(@Valid @RequestBody HotelDailySalesTimeRangeReqVO createReqVO) {
        return success(hotelDailySalesService.createHotelDailySalesByTimeRange(createReqVO));
    }

    @PutMapping("/update")
    @Operation(summary = "更新产品-酒店每日售卖管理")
    @PreAuthorize("@ss.hasPermission('product:hotel-daily-sales:update')")
    public CommonResult<Boolean> updateHotelDailySales(@Valid @RequestBody HotelDailySalesSaveReqVO updateReqVO) {
        hotelDailySalesService.updateHotelDailySales(updateReqVO);
        return success(true);
    }

    @DeleteMapping("/delete")
    @Operation(summary = "删除产品-酒店每日售卖管理")
    @Parameter(name = "id", description = "编号", required = true)
    @PreAuthorize("@ss.hasPermission('product:hotel-daily-sales:delete')")
    public CommonResult<Boolean> deleteHotelDailySales(@RequestParam("id") Long id) {
        hotelDailySalesService.deleteHotelDailySales(id);
        return success(true);
    }

    @GetMapping("/get")
    @Operation(summary = "获得产品-酒店每日售卖管理")
    @Parameter(name = "id", description = "编号", required = true, example = "1024")
    @PreAuthorize("@ss.hasPermission('product:hotel-daily-sales:query')")
    public CommonResult<HotelDailySalesRespVO> getHotelDailySales(@RequestParam("id") Long id) {
        HotelDailySalesDO hotelDailySales = hotelDailySalesService.getHotelDailySales(id);
        return success(BeanUtils.toBean(hotelDailySales, HotelDailySalesRespVO.class));
    }

    @GetMapping("/page")
    @Operation(summary = "获得产品-酒店每日售卖管理分页")
    @PreAuthorize("@ss.hasPermission('product:hotel-daily-sales:query')")
    public CommonResult<PageResult<HotelDailySalesRespVO>> getHotelDailySalesPage(@Valid HotelDailySalesPageReqVO pageReqVO) {
        PageResult<HotelDailySalesDO> pageResult = hotelDailySalesService.getHotelDailySalesPage(pageReqVO);
        return success(BeanUtils.toBean(pageResult, HotelDailySalesRespVO.class));
    }

    @GetMapping("/daily")
    @Operation(summary = "获得产品每日价格-酒店日历显示分页")
    @PreAuthorize("@ss.hasPermission('product:hotel-daily-sales:query')")
    public CommonResult<PageResult<HotelDailyRespVO>> getHotelDailySalesDaily(@Valid HotelDailyPageReqVO pageReqVO) {

        return success(hotelDailySalesService.getHotelDailyPage(pageReqVO));
    }

    @PutMapping("/isForbiddenSale")
    @Operation(summary = "更改某日价格启用状态-酒店每日售卖管理")
    @PreAuthorize("@ss.hasPermission('product:hotel-daily-sales:update')")
    public CommonResult<Boolean> isForbiddenSale(@RequestParam("id") Long id) {
        hotelDailySalesService.updateIsForbiddenSale(id);
        return success(true);
    }

    @PutMapping("/areSalesForbidden")
    @Operation(summary = "批量更改多日价格启用状态-酒店每日售卖管理")
    @PreAuthorize("@ss.hasPermission('product:hotel-daily-sales:update')")
    public CommonResult<Boolean> areSalesForbidden(@Valid @RequestBody HotelDailySalesUpdateReqVO updateReqVO) {
        hotelDailySalesService.updateAreForbiddenSale(updateReqVO);
        return success(true);
    }

}

我会从模块结构、功能实现、接口规范三个方面详细讲解该控制器的核心设计:

一、基础结构(MVC 控制器标准配置)

@Tag(name = "管理后台 - 产品-酒店每日售卖管理")  // OpenAPI 分组
@RestController  // RESTful 控制器
@RequestMapping("/hotel/product/hotel-daily-sales")  // 基础路径
@Validated  // 启用参数校验
public class HotelDailySalesController {
    @Resource  // 依赖注入
    private HotelDailySalesService hotelDailySalesService;
}
  • 分层架构:符合 MVC 模式,作为控制层接收 HTTP 请求
  • 接口文档:通过 Swagger 注解生成 API 文档
  • 安全控制:使用 @PreAuthorize 实现权限校验
  • 请求处理:统一异常处理(基于 Spring 的全局异常处理机制)

二、核心功能方法(CRUD 拓展)

1. 数据创建
@PostMapping("/createByDate")
@Operation(summary = "根据日期创建或修改多个天数价格")
public CommonResult<Boolean> createHotelDailySalesByDate(@Valid @RequestBody HotelDailySalesSaveReqVO createReqVO) {
    return success(hotelDailySalesService.createHotelDailySales(createReqVO));
}
  • 功能特点:支持批量日期价格创建/更新
  • 参数规范@Valid 实现请求体自动校验
  • 权限控制product:hotel-daily-sales:create 权限码校验
2. 数据查询
@GetMapping("/daily")
@Operation(summary = "获得产品每日价格-酒店日历显示分页")
public CommonResult<PageResult<HotelDailyRespVO>> getHotelDailySalesDaily(@Valid HotelDailyPageReqVO pageReqVO) {
    return success(hotelDailySalesService.getHotelDailyPage(pageReqVO));
}
  • 分页实现:基于 PageParam 的分页参数封装
  • 数据转换:使用 BeanUtils.toBean 实现 DO 到 VO 的转换
  • 查询优化:专门为日历显示定制的分页接口
3. 状态管理
@PutMapping("/areSalesForbidden")
@Operation(summary = "批量更改多日价格启用状态")
public CommonResult<Boolean> areSalesForbidden(@Valid @RequestBody HotelDailySalesUpdateReqVO updateReqVO) {
    hotelDailySalesService.updateAreForbiddenSale(updateReqVO);
    return success(true);
}
  • 批量操作:支持多日销售状态批量修改
  • 幂等设计:使用 PUT 方法确保操作幂等性

三、工程规范实现

  1. 统一响应格式
    • 所有方法返回 CommonResult<T> 统一响应对象
    • 使用 success() 静态方法构造成功响应
  1. 参数校验体系
@Valid HotelDailyPageReqVO pageReqVO
    • 基于 JSR-380 标准的校验规则
    • 校验失败自动返回错误信息(通过全局异常处理)
  1. 日志追踪
@ApiAccessLog(operateType = EXPORT)
    • 通过自定义注解实现操作日志记录
    • 支持不同操作类型的日志分类
  1. 安全控制矩阵
@PreAuthorize("@ss.hasPermission('product:hotel-daily-sales:delete')")
    • 权限表达式动态校验
    • 权限码按功能模块划分(create/update/delete/query)

四、扩展性设计

  1. VO 对象分层
    • 请求 VO:XXXReqVO 后缀(参数接收)
    • 响应 VO:XXXRespVO 后缀(数据返回)
    • 独立 Daily 子包管理日历相关 VO
  1. 多维度接口
    • 按日期操作 vs 按时段操作
    • 单条操作 vs 批量操作
    • 基础数据操作 vs 状态管理操作

该控制器完整实现了酒店每日价格管理的核心业务场景,通过合理的分层设计和规范实现,保证了系统的可维护性和扩展性。建议重点关注权限控制矩阵的维护和 VO 对象的校验规则定义。

我将从接口规范角度补充讲解删除功能的实现细节,并指出代码中值得注意的设计模式:

五、删除功能规范实现(以删除接口为例)

@DeleteMapping("/delete")  // ▶1 HTTP 方法匹配
@Operation(summary = "删除产品-酒店每日售卖管理")  // ▶2 OpenAPI 描述
@Parameter(name = "id", description = "编号", required = true)  // ▶3 参数说明
@PreAuthorize("@ss.hasPermission('product:hotel-daily-sales:delete')")  // ▶4 权限校验
public CommonResult<Boolean> deleteHotelDailySales(@RequestParam("id") Long id) {  // ▶5 参数绑定
    hotelDailySalesService.deleteHotelDailySales(id);  // ▶6 服务调用
    return success(true);  // ▶7 统一响应
}
规范实现要点:
  1. RESTful 语义化()
    • 严格遵循 HTTP DELETE 方法语义
    • URL 路径 /delete 可优化为 REST 风格 /delete/{id}
  1. 权限控制隔离()
    • 独立 delete 权限标识符
    • 权限表达式与业务逻辑解耦
  1. 参数处理规范
@RequestParam("id") Long id  // ▶ 明确请求参数类型
    • 强制要求参数传递(required = true
    • 类型安全校验前置(自动转换失败返回400错误)
设计模式亮点:
  1. 命令模式应用
hotelDailySalesService.deleteHotelDailySales(id);
    • 控制器仅负责请求派发
    • 具体删除逻辑委托给 Service 层
  1. 空响应优化
return success(true);  // 返回操作状态而非数据
    • 符合 RESTful 对 DELETE 操作的响应建议
    • 避免返回敏感业务数据
  1. 防御性编程
// Service 层应包含如下校验:
public void deleteHotelDailySales(Long id) {
    // 1. 存在性校验
    // 2. 状态校验(如是否可删除)
    // 3. 关联数据校验
}
    • 业务校验下沉至 Service 层
    • 控制器保持"笨"(thin controller)原则

mapper

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.iocoder.central.module.hotel.dal.mysql.HotelDailySalesMapper">

    <!--
        一般情况下,尽可能使用 Mapper 进行 CRUD 增删改查即可。
        无法满足的场景,例如说多表关联查询,才使用 XML 编写 SQL。
        代码生成器暂时只生成 Mapper XML 文件本身,更多推荐 MybatisX 快速开发插件来生成查询。
        文档可见:https://www.iocoder.cn/MyBatis/x-plugins/
     -->

    <update id="updateUseXmlById" parameterType="cn.iocoder.central.module.hotel.dal.dataobject.HotelDailySalesDO">
            UPDATE product_hotel_daily_sales
            <set>
                <!-- 禁售 -->
                <if test="isForbiddenSale != null">
                    is_forbidden_sale = #{isForbiddenSale},
                </if>
                <!-- 销售价 -->
                <if test="salePrice != null">
                    sale_price = #{salePrice},
                </if>
                <!-- 结算价 -->
                <if test="settlementPrice != null">
                    settlement_price = #{settlementPrice},
                </if>
                <!-- 库存类型 -->
                <if test="inventoryType != null">
                    inventory_type = #{inventoryType},
                </if>
                <!-- 保留库存 -->
                <if test="reservedInventory != null">
                    reserved_inventory = #{reservedInventory},
                </if>
                <!-- 销售数量 -->
                <if test="salesCount != null">
                    sales_count = #{salesCount},
                </if>
                <!-- 超卖 -->
                <if test="overbookingFlag != null">
                    overbooking_flag = #{overbookingFlag},
                </if>
                <!-- 库存确定时间 -->
                <if test="inventoryConfirmationTime != null">
                    inventory_confirmation_time = #{inventoryConfirmationTime},
                </if>
                <!-- 提前预定时间 -->
                <if test="advanceBookingTime != null">
                    advance_booking_time = #{advanceBookingTime},
                </if>
                <!-- 退改规则 -->
                <if test="refundPolicy != null">
                    refund_policy = #{refundPolicy},
                </if>
                <!-- 最晚无损取消时间 -->
                <if test="latestCancellationTime != null">
                    latest_cancellation_time = #{latestCancellationTime},
                </if>
                <!-- 扣款类型 -->
                <if test="deductionType != null">
                    deduction_type = #{deductionType},
                </if>
                <!-- 供应商编码 -->
                <if test="supplierCode != null">
                    supplier_code = #{supplierCode},
                </if>
                <!-- 自动去除最后一个逗号 -->
            </set>
            WHERE id = #{id}
    </update>
</mapper>

根据文件内容,这是一个 MyBatis 的 Mapper XML 文件,主要用于定义动态 SQL 更新逻辑。当前的 updateUseXmlById 操作实现了按字段非空条件更新酒店每日销售数据,具有以下特点:

  1. 使用 <set> 标签自动处理末尾逗号
  2. 包含 12 个可空条件更新的业务字段
  3. 通过 id 作为唯一更新条件
  4. 参数类型指向 HotelDailySalesDO 数据对象

根据日期 根据时段的设计通过代码片段可以看到这是两个创建酒店每日销售数据的接口实现,分别处理两种不同的业务场景。以下是对这两个接口的详细分析:

数据层

package cn.iocoder.central.module.hotel.dal.mysql;

import cn.iocoder.central.module.hotel.controller.admin.vo.dailysales.HotelDailySalesPageReqVO;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.central.module.hotel.dal.dataobject.HotelDailySalesDO;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import jakarta.validation.constraints.NotNull;
import org.apache.ibatis.annotations.Mapper;

import java.time.LocalDateTime;
import java.util.List;

/**
 * 产品-酒店每日售卖管理 Mapper
 *
 * @author 陕文旅
 */
@Mapper
public interface HotelDailySalesMapper extends BaseMapperX<HotelDailySalesDO> {

    default PageResult<HotelDailySalesDO> selectPage(HotelDailySalesPageReqVO reqVO) {
        return selectPage(reqVO, new LambdaQueryWrapperX<HotelDailySalesDO>()
                .betweenIfPresent(HotelDailySalesDO::getSaleDate, reqVO.getSaleDate())
                .eqIfPresent(HotelDailySalesDO::getIsForbiddenSale, reqVO.getIsForbiddenSale())
                .eqIfPresent(HotelDailySalesDO::getSalePrice, reqVO.getSalePrice())
                .eqIfPresent(HotelDailySalesDO::getSettlementPrice, reqVO.getSettlementPrice())
                .eqIfPresent(HotelDailySalesDO::getInventoryType, reqVO.getInventoryType())
                .eqIfPresent(HotelDailySalesDO::getReservedInventory, reqVO.getReservedInventory())
                .eqIfPresent(HotelDailySalesDO::getOverbookingFlag, reqVO.getOverbookingFlag())
                .eqIfPresent(HotelDailySalesDO::getRefundPolicy, reqVO.getRefundPolicy())
                .eqIfPresent(HotelDailySalesDO::getDeductionType, reqVO.getDeductionType())
                .eqIfPresent(HotelDailySalesDO::getSupplierCode, reqVO.getSupplierCode())
                .betweenIfPresent(HotelDailySalesDO::getCreateTime, reqVO.getCreateTime())
                .orderByDesc(HotelDailySalesDO::getId));
    }

    default List<HotelDailySalesDO> selectlistBySellingId(Long sellingId, LocalDateTime startDate, LocalDateTime endDate) {
        return selectList(new LambdaQueryWrapperX<HotelDailySalesDO>()
                .eq(HotelDailySalesDO::getSellingRoomId, sellingId)
                .betweenIfPresent(HotelDailySalesDO::getSaleDate, startDate, endDate)
                .orderByAsc(HotelDailySalesDO::getSaleDate));
    }

    default void updateIsForbiddenSale(Long id){
        // 构造更新条件
        LambdaUpdateWrapper<HotelDailySalesDO> updateWrapper = new LambdaUpdateWrapper<>();
        updateWrapper.setSql("is_forbidden_sale = CASE WHEN is_forbidden_sale = 0 THEN 1 ELSE 0 END")
                .eq(HotelDailySalesDO::getId, id);

        // 执行更新操作
        this.update(updateWrapper);
    }


    default void updateIsForbiddenSaleByDate(String dateStr, Long sellingRoomId, int isForbiddenSale){
        update(new LambdaUpdateWrapper<HotelDailySalesDO>()
                .set(HotelDailySalesDO::getIsForbiddenSale, isForbiddenSale)
                .eq(HotelDailySalesDO::getSaleDate, dateStr)
                .eq(HotelDailySalesDO::getSellingRoomId, sellingRoomId));
    }

    void updateUseXmlById(HotelDailySalesDO bean);
}

以下是对这段代码的逐行注释讲解:

package cn.iocoder.central.module.hotel.dal.mysql;
  • 定义包名,将该类放在指定的包结构下,便于项目的组织和管理。
import cn.iocoder.central.module.hotel.controller.admin.vo.dailysales.HotelDailySalesPageReqVO;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.central.module.hotel.dal.dataobject.HotelDailySalesDO;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import jakarta.validation.constraints.NotNull;
import org.apache.ibatis.annotations.Mapper;

import java.time.LocalDateTime;
import java.util.List;
  • 导入所需的类和包,包括自定义的VO(值对象)、分页结果对象、MyBatis-Plus相关的查询构造器、基础Mapper接口、数据对象DO、更新条件构造器、验证注解、MyBatis的Mapper注解以及Java的时间和集合类。
/**
 * 产品-酒店每日售卖管理 Mapper
 *
 * @author 陕文旅
 */
@Mapper
public interface HotelDailySalesMapper extends BaseMapperX<HotelDailySalesDO> {
  • 定义一个接口HotelDailySalesMapper,使用@Mapper注解标记为MyBatis的Mapper接口,表示这是一个数据访问层的接口,用于与数据库进行交互。
  • 继承BaseMapperX<HotelDailySalesDO>,继承了MyBatis-Plus提供的基础Mapper功能,可以使用其默认的增删改查等方法。
    default PageResult<HotelDailySalesDO> selectPage(HotelDailySalesPageReqVO reqVO) {
        return selectPage(reqVO, new LambdaQueryWrapperX<HotelDailySalesDO>()
                .betweenIfPresent(HotelDailySalesDO::getSaleDate, reqVO.getSaleDate())
                .eqIfPresent(HotelDailySalesDO::getIsForbiddenSale, reqVO.getIsForbiddenSale())
                .eqIfPresent(HotelDailySalesDO::getSalePrice, reqVO.getSalePrice())
                .eqIfPresent(HotelDailySalesDO::getSettlementPrice, reqVO.getSettlementPrice())
                .eqIfPresent(HotelDailySalesDO::getInventoryType, reqVO.getInventoryType())
                .eqIfPresent(HotelDailySalesDO::getReservedInventory, reqVO.getReservedInventory())
                .eqIfPresent(HotelDailySalesDO::getOverbookingFlag, reqVO.getOverbookingFlag())
                .eqIfPresent(HotelDailySalesDO::getRefundPolicy, reqVO.getRefundPolicy())
                .eqIfPresent(HotelDailySalesDO::getDeductionType, reqVO.getDeductionType())
                .eqIfPresent(HotelDailySalesDO::getSupplierCode, reqVO.getSupplierCode())
                .betweenIfPresent(HotelDailySalesDO::getCreateTime, reqVO.getCreateTime())
                .orderByDesc(HotelDailySalesDO::getId));
    }
  • 定义一个默认方法selectPage,用于分页查询酒店每日售卖信息。
  • 接收一个HotelDailySalesPageReqVO类型的参数reqVO,这是前端传来的分页查询请求对象,包含了查询条件。
  • 使用selectPage方法进行分页查询,第二个参数是一个LambdaQueryWrapperX查询构造器,用于构建动态查询条件。
    • betweenIfPresent:如果请求中的saleDate(售卖日期)存在,则构建sale_date字段在该日期范围内的查询条件。
    • 多个eqIfPresent:如果请求中对应的字段存在,则构建等于该字段值的查询条件,涉及是否禁止售卖、售卖价格、结算价格、库存类型等多个字段。
    • 最后一个betweenIfPresent:如果请求中的createTime(创建时间)存在,则构建create_time字段在该时间范围内的查询条件。
    • orderByDesc:按id字段降序排列,确保最新的记录排在前面。
    default List<HotelDailySalesDO> selectlistBySellingId(Long sellingId, LocalDateTime startDate, LocalDateTime endDate) {
        return selectList(new LambdaQueryWrapperX<HotelDailySalesDO>()
                .eq(HotelDailySalesDO::getSellingRoomId, sellingId)
                .betweenIfPresent(HotelDailySalesDO::getSaleDate, startDate, endDate)
                .orderByAsc(HotelDailySalesDO::getSaleDate));
    }
  • 定义一个默认方法selectlistBySellingId,用于根据售卖ID和日期范围查询酒店每日售卖信息列表。
  • 接收三个参数:sellingId(售卖房间ID)、startDate(开始日期)、endDate(结束日期)。
  • 使用selectList方法查询符合条件的记录列表,参数是一个LambdaQueryWrapperX查询构造器。
    • eq:构建等于售卖房间ID的查询条件。
    • betweenIfPresent:如果startDateendDate都存在,则构建sale_date字段在该日期范围内的查询条件。
    • orderByAsc:按sale_date字段升序排列,使结果按日期顺序展示。
    default void updateIsForbiddenSale(Long id){
        // 构造更新条件
        LambdaUpdateWrapper<HotelDailySalesDO> updateWrapper = new LambdaUpdateWrapper<>();
        updateWrapper.setSql("is_forbidden_sale = CASE WHEN is_forbidden_sale = 0 THEN 1 ELSE 0 END")
                .eq(HotelDailySalesDO::getId, id);

        // 执行更新操作
        this.update(updateWrapper);
    }
  • 定义一个默认方法updateIsForbiddenSale,用于更新指定ID的酒店每日售卖记录的is_forbidden_sale(是否禁止售卖)字段,实现禁止售卖状态的切换。
  • 接收一个id参数,表示要更新的记录的主键ID。
  • 构造一个LambdaUpdateWrapper更新条件构造器:
    • setSql:设置更新的SQL片段,这里是根据当前is_forbidden_sale的值进行切换,如果为0则变为1,否则变为0。
    • eq:构建等于ID的更新条件,确保只更新指定ID的记录。
  • 调用this.update(updateWrapper)执行更新操作。
    default void updateIsForbiddenSaleByDate(String dateStr, Long sellingRoomId, int isForbiddenSale){
        update(new LambdaUpdateWrapper<HotelDailySalesDO>()
                .set(HotelDailySalesDO::getIsForbiddenSale, isForbiddenSale)
                .eq(HotelDailySalesDO::getSaleDate, dateStr)
                .eq(HotelDailySalesDO::getSellingRoomId, sellingRoomId));
    }
  • 定义一个默认方法updateIsForbiddenSaleByDate,用于根据售卖日期、售卖房间ID和是否禁止售卖来更新记录。
  • 接收三个参数:dateStr(售卖日期字符串)、sellingRoomId(售卖房间ID)、isForbiddenSale(是否禁止售卖的值)。
  • 使用update方法进行更新,参数是一个LambdaUpdateWrapper更新条件构造器:
    • set:设置is_forbidden_sale字段为指定的isForbiddenSale值。
    • 两个eq:分别构建等于售卖日期和售卖房间ID的更新条件,确保只更新符合条件的记录。
    void updateUseXmlById(HotelDailySalesDO bean);
  • 定义一个抽象方法updateUseXmlById,用于根据ID更新酒店每日售卖记录,具体实现可能在XML映射文件中定义。
  • 接收一个HotelDailySalesDO类型的参数bean,表示要更新的数据对象。

服务层

HotelDailySalesServiceImpl

package cn.iocoder.central.module.hotel.service;

import cn.iocoder.central.module.hotel.controller.admin.vo.dailysales.*;
import cn.iocoder.central.module.hotel.controller.admin.vo.dailysales.Daily.HotelDailyPageReqVO;
import cn.iocoder.central.module.hotel.controller.admin.vo.dailysales.Daily.HotelDailyRespVO;
import cn.iocoder.central.module.hotel.controller.admin.vo.dailysales.Daily.HotelDailySellingList;
import cn.iocoder.central.module.hotel.controller.admin.vo.selling.HotelSellingRespVO;
import cn.iocoder.central.module.hotel.dal.mysql.HotelPhysicalMapper;
import cn.iocoder.central.module.hotel.dal.mysql.HotelSellingMapper;
import cn.iocoder.central.module.hotel.enums.ErrorCodeConstants;
import cn.iocoder.yudao.framework.common.util.date.DateTimeFormatterUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import jakarta.annotation.Resource;
import org.springframework.validation.annotation.Validated;

import cn.iocoder.central.module.hotel.dal.dataobject.HotelDailySalesDO;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;

import cn.iocoder.central.module.hotel.dal.mysql.HotelDailySalesMapper;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;

import static cn.iocoder.central.module.hotel.enums.ErrorCodeConstants.*;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;


/**
 * 产品-酒店每日售卖管理 Service 实现类
 *
 * @author 陕文旅
 */
@Service
@Validated
public class HotelDailySalesServiceImpl implements HotelDailySalesService {

    @Resource
    private HotelDailySalesMapper hotelDailySalesMapper;
    @Autowired
    private HotelSellingMapper hotelSellingMapper;
    @Autowired
    private HotelPhysicalMapper hotelPhysicalMapper;


    @Override
    public boolean createHotelDailySales(HotelDailySalesSaveReqVO createReqVO) {
        //遍历每一个日期
        for (String saleDate : createReqVO.getSaleDate()) {
            //转化日期格式
            saleDate = DateTimeFormatterUtils.formatDateTime(saleDate);
            //查询这个日期是否存在,获取id
            Long id = hotelSellingMapper.selectByDateAndSellId(saleDate,createReqVO.getSellingRoomId());
            //创建传入数据
            HotelDailySalesSaveVO hotelDailySalesSaveVO = BeanUtils.toBean(createReqVO, HotelDailySalesSaveVO.class);
            hotelDailySalesSaveVO.setId(id);
            hotelDailySalesSaveVO.setSaleDate(saleDate);
            if(id == null){
                //插入数据
                hotelDailySalesMapper.insert(BeanUtils.toBean(hotelDailySalesSaveVO, HotelDailySalesDO.class));
            }else{
                //修改数据
                hotelDailySalesMapper.updateUseXmlById(BeanUtils.toBean(hotelDailySalesSaveVO, HotelDailySalesDO.class));
            }
        }
        // 返回
        return true;
    }

    @Override
    public void updateHotelDailySales(HotelDailySalesSaveReqVO updateReqVO) {
        // 校验存在
        validateHotelDailySalesExists(updateReqVO.getId());
        // 更新
        HotelDailySalesDO updateObj = BeanUtils.toBean(updateReqVO, HotelDailySalesDO.class);
        hotelDailySalesMapper.updateById(updateObj);
    }

    @Override
    public void deleteHotelDailySales(Long id) {
        // 校验存在
        validateHotelDailySalesExists(id);
        // 删除
        hotelDailySalesMapper.deleteById(id);
    }

    private void validateHotelDailySalesExists(Long id) {
        if (hotelDailySalesMapper.selectById(id) == null) {
            throw exception(HOTEL_DAILY_SALES_NOT_EXISTS);
        }
    }

    @Override
    public HotelDailySalesDO getHotelDailySales(Long id) {
        return hotelDailySalesMapper.selectById(id);
    }

    @Override
    public PageResult<HotelDailySalesDO> getHotelDailySalesPage(HotelDailySalesPageReqVO pageReqVO) {
        return hotelDailySalesMapper.selectPage(pageReqVO);
    }

    @Override
    public PageResult<HotelDailyRespVO> getHotelDailyPage(HotelDailyPageReqVO pageReqVO) {
        // 查询物理表,获取物理id、物理名称
        List<HotelDailyRespVO> hotelDailyRespVOList = BeanUtils.toBean(
                hotelPhysicalMapper.selectByHotelId(pageReqVO.getHotelId()),HotelDailyRespVO.class);

        // 遍历物理表,获取售卖房型和每日价格
        hotelDailyRespVOList.forEach(hotelDailyRespVO -> {
            //传入物理房型id和查询参数
            hotelDailyRespVO.setSellingList(getHotelDailySellingList(hotelDailyRespVO.getId(),pageReqVO));
        });

        // 获取总条数并构造PageResult
        Long total = (long) hotelDailyRespVOList.size();
        return new PageResult<>(hotelDailyRespVOList, total);
    }

    @Override
    public void updateIsForbiddenSale(Long id) {
        // 校验存在
        validateHotelDailySalesExists(id);
        // 修改数据
        hotelDailySalesMapper.updateIsForbiddenSale(id);
    }

    @Override
    public Boolean createHotelDailySalesByTimeRange(HotelDailySalesTimeRangeReqVO createReqVO) {

        // 定义日期格式化器
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");

        // 解析起始日期和结束日期
        LocalDate startDate = LocalDate.parse(createReqVO.getBeginSaleDate(), formatter);
        LocalDate endDate = LocalDate.parse(createReqVO.getEndSaleDate(), formatter);

        // 遍历日期范围
        for (LocalDate date = startDate; !date.isAfter(endDate); date = date.plusDays(1)) {
            // 获取当前日期的星期值(1-7对应周一到周日)
            int currentDayOfWeek = date.getDayOfWeek().getValue();
            // 检查是否符合星期条件
            if (isDayOfWeekMatch(currentDayOfWeek, createReqVO.getWeek())) {
                //符合
                //转化日期格式
                String formattedDate = date.format(formatter);
                //查询这个日期是否存在,获取id
                Long id = hotelSellingMapper.selectByDateAndSellId(formattedDate,createReqVO.getSellingRoomId());
                //创建传入数据
                HotelDailySalesSaveVO hotelDailySalesSaveVO = BeanUtils.toBean(createReqVO, HotelDailySalesSaveVO.class);
                hotelDailySalesSaveVO.setId(id);
                hotelDailySalesSaveVO.setSaleDate(formattedDate);
                if(id == null){
                    //插入数据
                    hotelDailySalesMapper.insert(BeanUtils.toBean(hotelDailySalesSaveVO, HotelDailySalesDO.class));
                }else{
                    //修改数据
                    hotelDailySalesMapper.updateUseXmlById(BeanUtils.toBean(hotelDailySalesSaveVO, HotelDailySalesDO.class));
                }
            }
        }
        // 返回
        return true;
    }

    @Override
    public void updateAreForbiddenSale(HotelDailySalesUpdateReqVO updateReqVO) {
        // 定义日期格式化器
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
        // 遍历售卖房型ID
        for (Long sellingRoomId : updateReqVO.getSellingRoomId()) {
            if (updateReqVO.getTimeType() == 0) {
                // timeType为0,表示按时间段处理
                if (updateReqVO.getSaleDate() == null || updateReqVO.getSaleDate().length != 2) {
                    //要求日期数组有且只有两个,起始和结束时间
                    throw exception(ErrorCodeConstants.SALE_DATE_RANGE_INVALID);
                }
                // 解析起始日期和结束日期
                LocalDate startDate = LocalDate.parse(updateReqVO.getSaleDate()[0], formatter);
                LocalDate endDate = LocalDate.parse(updateReqVO.getSaleDate()[1], formatter);
                // 遍历日期范围
                for (LocalDate date = startDate; !date.isAfter(endDate); date = date.plusDays(1)) {
                    // 检查当前日期是否符合星期条件
                    if (isDayOfWeekMatch(date.getDayOfWeek().getValue(), updateReqVO.getWeek())) {
                        //转化日期格式
                        String dateStr = date.format(formatter);
                        //修改
                        hotelDailySalesMapper.updateIsForbiddenSaleByDate(dateStr,sellingRoomId,updateReqVO.getIsForbiddenSale());
                    }
                }
            } else if (updateReqVO.getTimeType() == 1) {
                // timeType为1,表示按具体日期列表处理
                for (String dateStr : updateReqVO.getSaleDate()) {
                    // 修改
                    hotelDailySalesMapper.updateIsForbiddenSaleByDate(dateStr,sellingRoomId,updateReqVO.getIsForbiddenSale());
                }
            } else {
                // 其他情况,抛出异常
                throw exception(TIME_TYPE_IS_ERROR);
            }
        }

    }
    /**
     * 判断当前日期的星期值是否符合星期条件
     *
     * @param currentDayOfWeek 当前日期的星期值(1-7)
     * @param week             星期条件数组
     * @return 是否匹配
     */
    private boolean isDayOfWeekMatch(int currentDayOfWeek, int[] week) {
        // 如果星期条件包含8,则表示全部星期都匹配
        for (int day : week) {
            if (day == currentDayOfWeek) {
                return true;
            }
        }
        return false;
    }


    /**
     * 根据产品ID和页面请求参数获取酒店每日销售列表
     * @param PId 产品ID,用于查询特定产品的每日销售情况
     * @param pageReqVO 包含分页和过滤条件的请求对象,用于定制查询结果
     * @return 返回一个包含酒店每日销售信息的列表
     */
    private List<HotelDailySellingList> getHotelDailySellingList(Long PId,HotelDailyPageReqVO pageReqVO){
        //创建返回对象列表
        List<HotelDailySellingList> hotelDailySellingLists = new ArrayList<>();

        //获取售卖房型
        List<HotelSellingRespVO> hotelSellingRespVO = BeanUtils.toBean(
                hotelSellingMapper.selectByPId(PId,pageReqVO.getRoomTypeStatus()),HotelSellingRespVO.class);

        //遍历售卖房型
        hotelSellingRespVO.forEach(hsr -> {
            //创建返回对象并补充参数
            HotelDailySellingList hotelDailySellingList= new HotelDailySellingList();
            hotelDailySellingList.setSelling(hsr);
            //调用方法,传入售卖房型id和查询参数,获取每日价格
            hotelDailySellingList.setDailySales(getHotelDailySalesRespVO(hsr.getId(),pageReqVO));
            //加入返回对象列表
            hotelDailySellingLists.add(hotelDailySellingList);
        });

        return hotelDailySellingLists;
    }

    /**
     * 获取酒店每日销售响应VO列表
     * @param SellingId 酒店销售ID
     * @param pageReqVO 酒店每日页面请求VO
     * @return 酒店每日销售响应VO列表
     */
    private List<HotelDailySalesRespVO> getHotelDailySalesRespVO(Long SellingId,HotelDailyPageReqVO pageReqVO){
        //定义限制日期
        int limit;
        //判断显示的状态
        if(pageReqVO.getDisplayStatus() == 0){
            //为按星期,只要七天
            limit = 6;
        } else if (pageReqVO.getDisplayStatus() == 1) {
            //为月份,只要一个月
            limit = 34;
        }else{
            //显示状态错误
            throw exception(Display_Status_NOT_EXISTS);
        }

        // 获取起始日期
        String date = DateTimeFormatterUtils.formatDateTime(pageReqVO.getDate());
        LocalDateTime startDate = LocalDateTime.parse(date, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
        // 获取结束日期
        LocalDateTime endDate = startDate.plusDays(limit);

        //获取数据
        List<HotelDailySalesRespVO> hdsr = BeanUtils.toBean(
                hotelDailySalesMapper.selectlistBySellingId(SellingId,startDate,endDate),HotelDailySalesRespVO.class);
        //定义标识,temp指向当前list的值,len为list长度
        int temp=0;
        int len=hdsr.size();

        //创建结果list
        List<HotelDailySalesRespVO> resultList = new ArrayList<>();

        // 日期增长循环
        for (LocalDateTime currentDate = startDate; !currentDate.isAfter(endDate); currentDate = currentDate.plusDays(1)) {
            //temp小于长度,并且当前日期等于list中的日期,则加入结果list,temp加一
            if(temp<len&&currentDate.isEqual(hdsr.get(temp).getSaleDate())){
                resultList.add(hdsr.get(temp));
                temp++;
            }else{
                // 否则,加入null
                HotelDailySalesRespVO hotelDailySalesRespVO=new HotelDailySalesRespVO();
                hotelDailySalesRespVO.setSaleDate(currentDate);
                resultList.add(hotelDailySalesRespVO);
            }
        }

        return resultList;
    }

}

以下是对这段代码的逐行注释讲解:

package cn.iocoder.central.module.hotel.service;
  • 定义包名,将该类放在指定的包结构下,便于项目的组织和管理。
import cn.iocoder.central.module.hotel.controller.admin.vo.dailysales.*;
import cn.iocoder.central.module.hotel.controller.admin.vo.dailysales.Daily.HotelDailyPageReqVO;
import cn.iocoder.central.module.hotel.controller.admin.vo.dailysales.Daily.HotelDailyRespVO;
import cn.iocoder.central.module.hotel.controller.admin.vo.dailysales.Daily.HotelDailySellingList;
import cn.iocoder.central.module.hotel.controller.admin.vo.selling.HotelSellingRespVO;
import cn.iocoder.central.module.hotel.dal.mysql.HotelPhysicalMapper;
import cn.iocoder.central.module.hotel.dal.mysql.HotelSellingMapper;
import cn.iocoder.central.module.hotel.enums.ErrorCodeConstants;
import cn.iocoder.yudao.framework.common.util.date.DateTimeFormatterUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import jakarta.annotation.Resource;
import org.springframework.validation.annotation.Validated;

import cn.iocoder.central.module.hotel.dal.dataobject.HotelDailySalesDO;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;

import cn.iocoder.central.module.hotel.dal.mysql.HotelDailySalesMapper;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;

import static cn.iocoder.central.module.hotel.enums.ErrorCodeConstants.*;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
  • 导入所需的类和包,包括自定义的VO(值对象)、分页结果对象、MyBatis-Plus相关的Mapper接口、错误码常量、日期工具类、Spring的注解、效验注解、数据对象DO、工具类以及Java的时间和集合类。
/**
 * 产品-酒店每日售卖管理 Service 实现类
 *
 * @author 陕文旅
 */
@Service
@Validated
public class HotelDailySalesServiceImpl implements HotelDailySalesService {
  • 定义一个类HotelDailySalesServiceImpl,使用@Service注解标记为Spring的Service组件,表示这是一个业务逻辑层的实现类。
  • 使用@Validated注解启用方法参数的JSR303校验。
  • 该类实现了HotelDailySalesService接口,提供了酒店每日售卖管理的具体业务实现。
    @Resource
    private HotelDailySalesMapper hotelDailySalesMapper;
    @Autowired
    private HotelSellingMapper hotelSellingMapper;
    @Autowired
    private HotelPhysicalMapper hotelPhysicalMapper;
  • 注入所需的Mapper接口,通过Spring的依赖注入机制获取数据访问层的对象,便于进行数据库操作。
    • hotelDailySalesMapper:用于酒店每日售卖数据的访问。
    • hotelSellingMapper:用于酒店售卖房型数据的访问。
    • hotelPhysicalMapper:用于酒店物理房型数据的访问。
    @Override
    public boolean createHotelDailySales(HotelDailySalesSaveReqVO createReqVO) {
        //遍历每一个日期
        for (String saleDate : createReqVO.getSaleDate()) {
            //转化日期格式
            saleDate = DateTimeFormatterUtils.formatDateTime(saleDate);
            //查询这个日期是否存在,获取id
            Long id = hotelSellingMapper.selectByDateAndSellId(saleDate,createReqVO.getSellingRoomId());
            //创建传入数据
            HotelDailySalesSaveVO hotelDailySalesSaveVO = BeanUtils.toBean(createReqVO, HotelDailySalesSaveVO.class);
            hotelDailySalesSaveVO.setId(id);
            hotelDailySalesSaveVO.setSaleDate(saleDate);
            if(id == null){
                //插入数据
                hotelDailySalesMapper.insert(BeanUtils.toBean(hotelDailySalesSaveVO, HotelDailySalesDO.class));
            }else{
                //修改数据
                hotelDailySalesMapper.updateUseXmlById(BeanUtils.toBean(hotelDailySalesSaveVO, HotelDailySalesDO.class));
            }
        }
        // 返回
        return true;
    }
  • 实现createHotelDailySales方法,用于创建酒店每日售卖信息。
  • 遍历请求对象中的每个售卖日期:
    • 使用DateTimeFormatterUtils.formatDateTime方法格式化日期字符串。
    • 调用hotelSellingMapper.selectByDateAndSellId方法查询该日期和售卖房型ID是否已存在记录,获取其ID。
    • 使用BeanUtils.toBean方法将请求对象转换为目标对象HotelDailySalesSaveVO
    • 设置转换后的对象的ID和售卖日期。
    • 如果ID为null,表示该日期的记录不存在,调用hotelDailySalesMapper.insert方法插入新记录。
    • 否则,调用hotelDailySalesMapper.updateUseXmlById方法更新现有记录。
  • 最后返回true,表示操作成功。
    @Override
    public void updateHotelDailySales(HotelDailySalesSaveReqVO updateReqVO) {
        // 校验存在
        validateHotelDailySalesExists(updateReqVO.getId());
        // 更新
        HotelDailySalesDO updateObj = BeanUtils.toBean(updateReqVO, HotelDailySalesDO.class);
        hotelDailySalesMapper.updateById(updateObj);
    }
  • 实现updateHotelDailySales方法,用于更新酒店每日售卖信息。
  • 首先调用validateHotelDailySalesExists方法校验要更新的记录是否存在,不存在则抛出异常。
  • 使用BeanUtils.toBean方法将请求对象转换为数据对象HotelDailySalesDO
  • 调用hotelDailySalesMapper.updateById方法根据ID更新记录。
    @Override
    public void deleteHotelDailySales(Long id) {
        // 校验存在
        validateHotelDailySalesExists(id);
        // 删除
        hotelDailySalesMapper.deleteById(id);
    }
  • 实现deleteHotelDailySales方法,用于删除指定ID的酒店每日售卖记录。
  • 首先校验记录是否存在。
  • 调用hotelDailySalesMapper.deleteById方法删除记录。
    private void validateHotelDailySalesExists(Long id) {
        if (hotelDailySalesMapper.selectById(id) == null) {
            throw exception(HOTEL_DAILY_SALES_NOT_EXISTS);
        }
    }
  • 定义私有方法validateHotelDailySalesExists,用于校验指定ID的酒店每日售卖记录是否存在。
  • 调用hotelDailySalesMapper.selectById方法查询记录,如果为null,表示记录不存在,抛出自定义异常。
    @Override
    public HotelDailySalesDO getHotelDailySales(Long id) {
        return hotelDailySalesMapper.selectById(id);
    }
  • 实现getHotelDailySales方法,用于根据ID获取酒店每日售卖记录。
  • 直接调用Mapper的selectById方法查询并返回结果。
    @Override
    public PageResult<HotelDailySalesDO> getHotelDailySalesPage(HotelDailySalesPageReqVO pageReqVO) {
        return hotelDailySalesMapper.selectPage(pageReqVO);
    }
  • 实现getHotelDailySalesPage方法,用于分页查询酒店每日售卖记录。
  • 调用Mapper的selectPage方法,传入分页请求对象,返回分页结果。
    @Override
    public PageResult<HotelDailyRespVO> getHotelDailyPage(HotelDailyPageReqVO pageReqVO) {
        // 查询物理表,获取物理id、物理名称
        List<HotelDailyRespVO> hotelDailyRespVOList = BeanUtils.toBean(
                hotelPhysicalMapper.selectByHotelId(pageReqVO.getHotelId()),HotelDailyRespVO.class);

        // 遍历物理表,获取售卖房型和每日价格
        hotelDailyRespVOList.forEach(hotelDailyRespVO -> {
            //传入物理房型id和查询参数
            hotelDailyRespVO.setSellingList(getHotelDailySellingList(hotelDailyRespVO.getId(),pageReqVO));
        });

        // 获取总条数并构造PageResult
        Long total = (long) hotelDailyRespVOList.size();
        return new PageResult<>(hotelDailyRespVOList, total);
    }
  • 实现getHotelDailyPage方法,用于获取酒店每日页面数据,包括物理房型、售卖房型和每日价格等信息。
  • 调用hotelPhysicalMapper.selectByHotelId方法查询物理房型数据,并转换为HotelDailyRespVO列表。
  • 遍历物理房型列表,调用getHotelDailySellingList方法获取每个物理房型的售卖房型和每日价格信息,并设置到响应对象中。
  • 构造并返回分页结果。
    @Override
    public void updateIsForbiddenSale(Long id) {
        // 校验存在
        validateHotelDailySalesExists(id);
        // 修改数据
        hotelDailySalesMapper.updateIsForbiddenSale(id);
    }
  • 实现updateIsForbiddenSale方法,用于更新指定ID的酒店每日售卖记录的禁止售卖状态。
  • 校验记录是否存在。
  • 调用Mapper的updateIsForbiddenSale方法更新状态。
    @Override
    public Boolean createHotelDailySalesByTimeRange(HotelDailySalesTimeRangeReqVO createReqVO) {

        // 定义日期格式化器
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");

        // 解析起始日期和结束日期
        LocalDate startDate = LocalDate.parse(createReqVO.getBeginSaleDate(), formatter);
        LocalDate endDate = LocalDate.parse(createReqVO.getEndSaleDate(), formatter);

        // 遍历日期范围
        for (LocalDate date = startDate; !date.isAfter(endDate); date = date.plusDays(1)) {
            // 获取当前日期的星期值(1-7对应周一到周日)
            int currentDayOfWeek = date.getDayOfWeek().getValue();
            // 检查是否符合星期条件
            if (isDayOfWeekMatch(currentDayOfWeek, createReqVO.getWeek())) {
                //符合
                //转化日期格式
                String formattedDate = date.format(formatter);
                //查询这个日期是否存在,获取id
                Long id = hotelSellingMapper.selectByDateAndSellId(formattedDate,createReqVO.getSellingRoomId());
                //创建传入数据
                HotelDailySalesSaveVO hotelDailySalesSaveVO = BeanUtils.toBean(createReqVO, HotelDailySalesSaveVO.class);
                hotelDailySalesSaveVO.setId(id);
                hotelDailySalesSaveVO.setSaleDate(formattedDate);
                if(id == null){
                    //插入数据
                    hotelDailySalesMapper.insert(BeanUtils.toBean(hotelDailySalesSaveVO, HotelDailySalesDO.class));
                }else{
                    //修改数据
                    hotelDailySalesMapper.updateUseXmlById(BeanUtils.toBean(hotelDailySalesSaveVO, HotelDailySalesDO.class));
                }
            }
        }
        // 返回
        return true;
    }
  • 实现createHotelDailySalesByTimeRange方法,用于根据时间段批量创建或更新酒店每日售卖信息。
  • 定义日期格式化器,解析请求中的起始和结束日期。
  • 遍历从起始日期到结束日期的每一天:
    • 获取当前日期的星期值。
    • 调用isDayOfWeekMatch方法检查是否符合请求中的星期条件。
    • 如果符合,格式化日期,查询该日期和售卖房型ID是否已存在记录。
    • 转换请求对象为保存对象,设置ID和售卖日期。
    • 如果ID为null,插入新记录;否则更新现有记录。
  • 最后返回true表示操作成功。
    @Override
    public void updateAreForbiddenSale(HotelDailySalesUpdateReqVO updateReqVO) {
        // 定义日期格式化器
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
        // 遍历售卖房型ID
        for (Long sellingRoomId : updateReqVO.getSellingRoomId()) {
            if (updateReqVO.getTimeType() == 0) {
                // timeType为0,表示按时间段处理
                if (updateReqVO.getSaleDate() == null || updateReqVO.getSaleDate().length != 2) {
                    //要求日期数组有且只有两个,起始和结束时间
                    throw exception(ErrorCodeConstants.SALE_DATE_RANGE_INVALID);
                }
                // 解析起始日期和结束日期
                LocalDate startDate = LocalDate.parse(updateReqVO.getSaleDate()[0], formatter);
                LocalDate endDate = LocalDate.parse(updateReqVO.getSaleDate()[1], formatter);
                // 遍历日期范围
                for (LocalDate date = startDate; !date.isAfter(endDate); date = date.plusDays(1)) {
                    // 检查当前日期是否符合星期条件
                    if (isDayOfWeekMatch(date.getDayOfWeek().getValue(), updateReqVO.getWeek())) {
                        //转化日期格式
                        String dateStr = date.format(formatter);
                        //修改
                        hotelDailySalesMapper.updateIsForbiddenSaleByDate(dateStr,sellingRoomId,updateReqVO.getIsForbiddenSale());
                    }
                }
            } else if (updateReqVO.getTimeType() == 1) {
                // timeType为1,表示按具体日期列表处理
                for (String dateStr : updateReqVO.getSaleDate()) {
                    // 修改
                    hotelDailySalesMapper.updateIsForbiddenSaleByDate(dateStr,sellingRoomId,updateReqVO.getIsForbiddenSale());
                }
            } else {
                // 其他情况,抛出异常
                throw exception(TIME_TYPE_IS_ERROR);
            }
        }

    }
  • 实现updateAreForbiddenSale方法,用于批量更新酒店每日售卖记录的禁止售卖状态。
  • 定义日期格式化器。
  • 遍历请求中的售卖房型ID列表:
    • 根据timeType的值判断处理方式:
      • timeType为0:按时间段处理,要求请求中的saleDate是一个包含两个元素的数组,分别表示起始和结束日期。解析这两个日期,遍历日期范围内的每一天,检查是否符合星期条件,如果符合则更新该日期的记录。
      • timeType为1:按具体日期列表处理,遍历请求中的每个日期,直接更新对应日期的记录。
      • 其他值:抛出异常表示时间类型错误。
  • 调用Mapper的updateIsForbiddenSaleByDate方法进行更新操作。
    /**
     * 判断当前日期的星期值是否符合星期条件
     *
     * @param currentDayOfWeek 当前日期的星期值(1-7)
     * @param week             星期条件数组
     * @return 是否匹配
     */
    private boolean isDayOfWeekMatch(int currentDayOfWeek, int[] week) {
        // 如果星期条件包含8,则表示全部星期都匹配
        for (int day : week) {
            if (day == currentDayOfWeek) {
                return true;
            }
        }
        return false;
    }
  • 定义私有方法isDayOfWeekMatch,用于判断当前日期的星期值是否符合给定的星期条件数组。
  • 遍历星期条件数组,如果找到与当前星期值相等的元素,则返回true,表示匹配;否则返回false
    /**
     * 根据产品ID和页面请求参数获取酒店每日销售列表
     * @param PId 产品ID,用于查询特定产品的每日销售情况
     * @param pageReqVO 包含分页和过滤条件的请求对象,用于定制查询结果
     * @return 返回一个包含酒店每日销售信息的列表
     */
    private List<HotelDailySellingList> getHotelDailySellingList(Long PId,HotelDailyPageReqVO pageReqVO){
        //创建返回对象列表
        List<HotelDailySellingList> hotelDailySellingLists = new ArrayList<>();

        //获取售卖房型
        List<HotelSellingRespVO> hotelSellingRespVO = BeanUtils.toBean(
                hotelSellingMapper.selectByPId(PId,pageReqVO.getRoomTypeStatus()),HotelSellingRespVO.class);

        //遍历售卖房型
        hotelSellingRespVO.forEach(hsr -> {
            //创建返回对象并补充参数
            HotelDailySellingList hotelDailySellingList= new HotelDailySellingList();
            hotelDailySellingList.setSelling(hsr);
            //调用方法,传入售卖房型id和查询参数,获取每日价格
            hotelDailySellingList.setDailySales(getHotelDailySalesRespVO(hsr.getId(),pageReqVO));
            //加入返回对象列表
            hotelDailySellingLists.add(hotelDailySellingList);
        });

        return hotelDailySellingLists;
    }
  • 定义私有方法getHotelDailySellingList,用于根据产品ID和页面请求参数获取酒店每日销售列表,包括售卖房型和每日价格信息。
  • 创建返回对象列表。
  • 调用hotelSellingMapper.selectByPId方法查询售卖房型数据,并转换为HotelSellingRespVO列表。
  • 遍历售卖房型列表,为每个售卖房型创建HotelDailySellingList对象,设置售卖房型信息,并调用getHotelDailySalesRespVO方法获取每日价格信息,最后将对象添加到返回列表中。
    /**
     * 获取酒店每日销售响应VO列表
     * @param SellingId 酒店销售ID
     * @param pageReqVO 酒店每日页面请求VO
     * @return 酒店每日销售响应VO列表
     */
    private List<HotelDailySalesRespVO> getHotelDailySalesRespVO(Long SellingId,HotelDailyPageReqVO pageReqVO){
        //定义限制日期
        int limit;
        //判断显示的状态
        if(pageReqVO.getDisplayStatus() == 0){
            //为按星期,只要七天
            limit = 6;
        } else if (pageReqVO.getDisplayStatus() == 1) {
            //为月份,只要一个月
            limit = 34;
        }else{
            //显示状态错误
            throw exception(Display_Status_NOT_EXISTS);
        }

        // 获取起始日期
        String date = DateTimeFormatterUtils.formatDateTime(pageReqVO.getDate());
        LocalDateTime startDate = LocalDateTime.parse(date, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
        // 获取结束日期
        LocalDateTime endDate = startDate.plusDays(limit);

        //获取数据
        List<HotelDailySalesRespVO> hdsr = BeanUtils.toBean(
                hotelDailySalesMapper.selectlistBySellingId(SellingId,startDate,endDate),HotelDailySalesRespVO.class);
        //定义标识,temp指向当前list的值,len为list长度
        int temp=0;
        int len=hdsr.size();

        //创建结果list
        List<HotelDailySalesRespVO> resultList = new ArrayList<>();

        // 日期增长循环
        for (LocalDateTime currentDate = startDate; !currentDate.isAfter(endDate); currentDate = currentDate.plusDays(1)) {
            //temp小于长度,并且当前日期等于list中的日期,则加入结果list,temp加一
            if(temp<len&&currentDate.isEqual(hdsr.get(temp).getSaleDate())){
                resultList.add(hdsr.get(temp));
                temp++;
            }else{
                // 否则,加入null
                HotelDailySalesRespVO hotelDailySalesRespVO=new HotelDailySalesRespVO();
                hotelDailySalesRespVO.setSaleDate(currentDate);
                resultList.add(hotelDailySalesRespVO);
            }
        }

        return resultList;
    }
  • 定义私有方法getHotelDailySalesRespVO,用于获取酒店每日销售响应VO列表,根据售卖房型ID和页面请求参数查询每日价格信息。
  • 根据请求中的显示状态确定日期范围限制:
    • 显示状态为0:按星期显示,限制为7天(包括起始日期)。
    • 显示状态为1:按月显示,限制为一个月(35天)。
    • 其他值:抛出异常表示显示状态不存在。
  • 获取起始日期和结束日期。
  • 调用hotelDailySalesMapper.selectlistBySellingId方法查询指定售卖房型ID和日期范围内的每日价格数据,并转换为HotelDailySalesRespVO列表。
  • 遍历从起始日期到结束日期的每一天,检查查询结果中是否有对应日期的数据,如果有则添加到结果列表,否则创建一个空的对象并设置售卖日期后添加到结果列表。
  • 最终返回结果列表,确保返回的日期范围连续且完整。

以上是对代码的逐行注释讲解,帮助理解每个方法的功能和实现细节,以及它们在业务逻辑层中的作用。

控制层

1. 按单个日期创建接口

@PostMapping("/createByDate")
@Operation(summary = "根据日期创建或修改多个天数价格-酒店每日售卖管理")
@PreAuthorize("@ss.hasPermission('product:hotel-daily-sales:create')")
public CommonResult<Boolean> createHotelDailySalesByDate(
    @Valid @RequestBody HotelDailySalesSaveReqVO createReqVO) {
    return success(hotelDailySalesService.createHotelDailySales(createReqVO));
}

核心要素解析

  • 路由定义POST /hotel/product/hotel-daily-sales/createByDate
  • 权限控制: 校验用户权限
  • 请求体验证:使用@Valid注解触发JSR-380校验规则
  • 业务实现:调用中的createHotelDailySales方法

2. 按时段创建接口

@PostMapping("/createByTimeRange")
@Operation(summary = "根据时段创建或修改多个天数价格-酒店每日售卖管理")
@PreAuthorize("@ss.hasPermission('product:hotel-daily-sales:create')")
public CommonResult<Boolean> createHotelDailySales(
    @Valid @RequestBody HotelDailySalesTimeRangeReqVO createReqVO) {
    return success(hotelDailySalesService.createHotelDailySalesByTimeRange(createReqVO));
}

差异点分析

  • 路由路径POST /hotel/product/hotel-daily-sales/createByTimeRange
  • 请求参数:使用作为入参
  • 业务方法:调用服务层的createHotelDailySalesByTimeRange方法

架构设计亮点:

  1. 职责分离:通过不同的VO对象(SaveReqVOTimeRangeReqVO)明确区分两种创建场景的输入参数
  2. RESTful设计:严格遵循POST方法用于资源创建
  3. 响应标准化:使用统一返回格式
  4. 防御式编程:通过@Valid和权限校验实现双重准入控制

HotelDailySalesServiceImpl.java

package cn.iocoder.central.module.hotel.service;

import cn.iocoder.central.module.hotel.controller.admin.vo.dailysales.*;
import cn.iocoder.central.module.hotel.controller.admin.vo.dailysales.Daily.HotelDailyPageReqVO;
import cn.iocoder.central.module.hotel.controller.admin.vo.dailysales.Daily.HotelDailyRespVO;
import cn.iocoder.central.module.hotel.controller.admin.vo.dailysales.Daily.HotelDailySellingList;
import cn.iocoder.central.module.hotel.controller.admin.vo.selling.HotelSellingRespVO;
import cn.iocoder.central.module.hotel.dal.mysql.HotelPhysicalMapper;
import cn.iocoder.central.module.hotel.dal.mysql.HotelSellingMapper;
import cn.iocoder.central.module.hotel.enums.ErrorCodeConstants;
import cn.iocoder.yudao.framework.common.util.date.DateTimeFormatterUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import jakarta.annotation.Resource;
import org.springframework.validation.annotation.Validated;
import cn.iocoder.central.module.hotel.dal.dataobject.HotelDailySalesDO;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.central.module.hotel.dal.mysql.HotelDailySalesMapper;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import static cn.iocoder.central.module.hotel.enums.ErrorCodeConstants.*;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;

/**
 * 产品-酒店每日售卖管理 Service 实现类
 *
 * @author 陕文旅
 */
@Service
@Validated
public class HotelDailySalesServiceImpl implements HotelDailySalesService {

    @Resource
    private HotelDailySalesMapper hotelDailySalesMapper;

    @Autowired
    private HotelSellingMapper hotelSellingMapper;

    @Autowired
    private HotelPhysicalMapper hotelPhysicalMapper;

    /**
     * 创建酒店每日售卖记录
     * 遍历每个销售日期,检查是否存在记录,不存在则插入,存在则更新。
     */
    @Override
    public boolean createHotelDailySales(HotelDailySalesSaveReqVO createReqVO) {
        // 遍历每一个销售日期
        for (String saleDate : createReqVO.getSaleDate()) {
            // 转换日期格式为统一格式
            saleDate = DateTimeFormatterUtils.formatDateTime(saleDate);
            // 查询数据库中是否已存在该日期的记录,获取其 ID
            Long id = hotelSellingMapper.selectByDateAndSellId(saleDate, createReqVO.getSellingRoomId());
            // 创建传入数据对象
            HotelDailySalesSaveVO hotelDailySalesSaveVO = BeanUtils.toBean(createReqVO, HotelDailySalesSaveVO.class);
            hotelDailySalesSaveVO.setId(id); // 设置 ID
            hotelDailySalesSaveVO.setSaleDate(saleDate); // 设置销售日期
            if (id == null) {
                // 如果 ID 为空,表示记录不存在,则插入新记录
                hotelDailySalesMapper.insert(BeanUtils.toBean(hotelDailySalesSaveVO, HotelDailySalesDO.class));
            } else {
                // 如果 ID 不为空,表示记录已存在,则更新记录
                hotelDailySalesMapper.updateUseXmlById(BeanUtils.toBean(hotelDailySalesSaveVO, HotelDailySalesDO.class));
            }
        }
        // 返回 true 表示操作成功
        return true;
    }

    /**
     * 更新酒店每日售卖记录
     * 校验记录是否存在,然后更新记录。
     */
    @Override
    public void updateHotelDailySales(HotelDailySalesSaveReqVO updateReqVO) {
        // 校验指定 ID 的记录是否存在
        validateHotelDailySalesExists(updateReqVO.getId());
        // 将 VO 对象转换为 DO 对象
        HotelDailySalesDO updateObj = BeanUtils.toBean(updateReqVO, HotelDailySalesDO.class);
        // 更新记录
        hotelDailySalesMapper.updateById(updateObj);
    }

    /**
     * 删除酒店每日售卖记录
     * 校验记录是否存在,然后删除记录。
     */
    @Override
    public void deleteHotelDailySales(Long id) {
        // 校验指定 ID 的记录是否存在
        validateHotelDailySalesExists(id);
        // 删除记录
        hotelDailySalesMapper.deleteById(id);
    }

    /**
     * 校验酒店每日售卖记录是否存在
     * 如果记录不存在,抛出异常。
     */
    private void validateHotelDailySalesExists(Long id) {
        if (hotelDailySalesMapper.selectById(id) == null) {
            throw exception(HOTEL_DAILY_SALES_NOT_EXISTS);
        }
    }

    /**
     * 查询单条酒店每日售卖记录
     */
    @Override
    public HotelDailySalesDO getHotelDailySales(Long id) {
        return hotelDailySalesMapper.selectById(id);
    }

    /**
     * 分页查询酒店每日售卖记录
     */
    @Override
    public PageResult<HotelDailySalesDO> getHotelDailySalesPage(HotelDailySalesPageReqVO pageReqVO) {
        return hotelDailySalesMapper.selectPage(pageReqVO);
    }

    /**
     * 获取酒店每日售卖分页结果
     * 查询物理房型信息并构造分页结果。
     */
    @Override
    public PageResult<HotelDailyRespVO> getHotelDailyPage(HotelDailyPageReqVO pageReqVO) {
        // 查询物理表,获取物理 ID 和物理名称
        List<HotelDailyRespVO> hotelDailyRespVOList = BeanUtils.toBean(
                hotelPhysicalMapper.selectByHotelId(pageReqVO.getHotelId()), HotelDailyRespVO.class);
        // 遍历物理表,获取售卖房型和每日价格
        hotelDailyRespVOList.forEach(hotelDailyRespVO -> {
            // 传入物理房型 ID 和查询参数
            hotelDailyRespVO.setSellingList(getHotelDailySellingList(hotelDailyRespVO.getId(), pageReqVO));
        });
        // 获取总条数并构造 PageResult
        Long total = (long) hotelDailyRespVOList.size();
        return new PageResult<>(hotelDailyRespVOList, total);
    }

    /**
     * 更新禁售状态
     */
    @Override
    public void updateIsForbiddenSale(Long id) {
        // 校验记录是否存在
        validateHotelDailySalesExists(id);
        // 修改禁售状态
        hotelDailySalesMapper.updateIsForbiddenSale(id);
    }

    /**
     * 按时间范围创建酒店每日售卖记录
     * 遍历日期范围,检查是否符合星期条件,然后插入或更新记录。
     */
    @Override
    public Boolean createHotelDailySalesByTimeRange(HotelDailySalesTimeRangeReqVO createReqVO) {
        // 定义日期格式化器
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
        // 解析起始日期和结束日期
        LocalDate startDate = LocalDate.parse(createReqVO.getBeginSaleDate(), formatter);
        LocalDate endDate = LocalDate.parse(createReqVO.getEndSaleDate(), formatter);
        // 遍历日期范围
        for (LocalDate date = startDate; !date.isAfter(endDate); date = date.plusDays(1)) {
            // 获取当前日期的星期值(1-7 对应周一到周日)
            int currentDayOfWeek = date.getDayOfWeek().getValue();
            // 检查是否符合星期条件
            if (isDayOfWeekMatch(currentDayOfWeek, createReqVO.getWeek())) {
                // 符合
                // 转化日期格式
                String formattedDate = date.format(formatter);
                // 查询这个日期是否存在,获取 ID
                Long id = hotelSellingMapper.selectByDateAndSellId(formattedDate, createReqVO.getSellingRoomId());
                // 创建传入数据
                HotelDailySalesSaveVO hotelDailySalesSaveVO = BeanUtils.toBean(createReqVO, HotelDailySalesSaveVO.class);
                hotelDailySalesSaveVO.setId(id);
                hotelDailySalesSaveVO.setSaleDate(formattedDate);
                if (id == null) {
                    // 插入数据
                    hotelDailySalesMapper.insert(BeanUtils.toBean(hotelDailySalesSaveVO, HotelDailySalesDO.class));
                } else {
                    // 修改数据
                    hotelDailySalesMapper.updateUseXmlById(BeanUtils.toBean(hotelDailySalesSaveVO, HotelDailySalesDO.class));
                }
            }
        }
        // 返回 true 表示操作成功
        return true;
    }

    /**
     * 批量更新禁售状态
     * 支持按时间段或具体日期列表处理。
     */
    @Override
    public void updateAreForbiddenSale(HotelDailySalesUpdateReqVO updateReqVO) {
        // 定义日期格式化器
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
        // 遍历售卖房型 ID
        for (Long sellingRoomId : updateReqVO.getSellingRoomId()) {
            if (updateReqVO.getTimeType() == 0) {
                // timeType 为 0,表示按时间段处理
                if (updateReqVO.getSaleDate() == null || updateReqVO.getSaleDate().length != 2) {
                    // 要求日期数组有且只有两个,起始和结束时间
                    throw exception(ErrorCodeConstants.SALE_DATE_RANGE_INVALID);
                }
                // 解析起始日期和结束日期
                LocalDate startDate = LocalDate.parse(updateReqVO.getSaleDate()[0], formatter);
                LocalDate endDate = LocalDate.parse(updateReqVO.getSaleDate()[1], formatter);
                // 遍历日期范围
                for (LocalDate date = startDate; !date.isAfter(endDate); date = date.plusDays(1)) {
                    // 检查当前日期是否符合星期条件
                    if (isDayOfWeekMatch(date.getDayOfWeek().getValue(), updateReqVO.getWeek())) {
                        // 转化日期格式
                        String dateStr = date.format(formatter);
                        // 修改禁售状态
                        hotelDailySalesMapper.updateIsForbiddenSaleByDate(dateStr, sellingRoomId, updateReqVO.getIsForbiddenSale());
                    }
                }
            } else if (updateReqVO.getTimeType() == 1) {
                // timeType 为 1,表示按具体日期列表处理
                for (String dateStr : updateReqVO.getSaleDate()) {
                    // 修改禁售状态
                    hotelDailySalesMapper.updateIsForbiddenSaleByDate(dateStr, sellingRoomId, updateReqVO.getIsForbiddenSale());
                }
            } else {
                // 其他情况,抛出异常
                throw exception(TIME_TYPE_IS_ERROR);
            }
        }
    }

    /**
     * 判断当前日期的星期值是否符合星期条件
     * 如果星期条件包含 8,则表示全部星期都匹配。
     */
    private boolean isDayOfWeekMatch(int currentDayOfWeek, int[] week) {
        for (int day : week) {
            if (day == currentDayOfWeek) {
                return true;
            }
        }
        return false;
    }

    /**
     * 根据产品 ID 和页面请求参数获取酒店每日销售列表
     * 查询售卖房型及其每日价格信息。
     */
    private List<HotelDailySellingList> getHotelDailySellingList(Long PId, HotelDailyPageReqVO pageReqVO) {
        // 创建返回对象列表
        List<HotelDailySellingList> hotelDailySellingLists = new ArrayList<>();
        // 获取售卖房型
        List<HotelSellingRespVO> hotelSellingRespVO = BeanUtils.toBean(
                hotelSellingMapper.selectByPId(PId, pageReqVO.getRoomTypeStatus()), HotelSellingRespVO.class);
        // 遍历售卖房型
        hotelSellingRespVO.forEach(hsr -> {
            // 创建返回对象并补充参数
            HotelDailySellingList hotelDailySellingList = new HotelDailySellingList();
            hotelDailySellingList.setSelling(hsr);
            // 调用方法,传入售卖房型 ID 和查询参数,获取每日价格
            hotelDailySellingList.setDailySales(getHotelDailySalesRespVO(hsr.getId(), pageReqVO));
            // 加入返回对象列表
            hotelDailySellingLists.add(hotelDailySellingList);
        });
        return hotelDailySellingLists;
    }

    /**
     * 获取酒店每日销售响应 VO 列表
     * 根据显示状态(按周或按月)构造日期范围,并填充缺失的日期。
     */
    private List<HotelDailySalesRespVO> getHotelDailySalesRespVO(Long SellingId, HotelDailyPageReqVO pageReqVO) {
        // 定义限制日期
        int limit;
        // 判断显示的状态
        if (pageReqVO.getDisplayStatus() == 0) {
            // 为按星期,只要七天
            limit = 6;
        } else if (pageReqVO.getDisplayStatus() == 1) {
            // 为月份,只要一个月
            limit = 34;
        } else {
            // 显示状态错误
            throw exception(Display_Status_NOT_EXISTS);
        }
        // 获取起始日期
        String date = DateTimeFormatterUtils.formatDateTime(pageReqVO.getDate());
        LocalDateTime startDate = LocalDateTime.parse(date, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
        // 获取结束日期
        LocalDateTime endDate = startDate.plusDays(limit);
        // 获取数据
        List<HotelDailySalesRespVO> hdsr = BeanUtils.toBean(
                hotelDailySalesMapper.selectlistBySellingId(SellingId, startDate, endDate), HotelDailySalesRespVO.class);
        // 定义标识,temp 指向当前 list 的值,len 为 list 长度
        int temp = 0;
        int len = hdsr.size();
        // 创建结果 list
        List<HotelDailySalesRespVO> resultList = new ArrayList<>();
        // 日期增长循环
        for (LocalDateTime currentDate = startDate; !currentDate.isAfter(endDate); currentDate = currentDate.plusDays(1)) {
            // temp 小于长度,并且当前日期等于 list 中的日期,则加入结果 list,temp 加一
            if (temp < len && currentDate.isEqual(hdsr.get(temp).getSaleDate())) {
                resultList.add(hdsr.get(temp));
                temp++;
            } else {
                // 否则,加入 null
                HotelDailySalesRespVO hotelDailySalesRespVO = new HotelDailySalesRespVO();
                hotelDailySalesRespVO.setSaleDate(currentDate);
                resultList.add(hotelDailySalesRespVO);
            }
        }
        return resultList;
    }
}

好的,我们将逐行逐句详细解析 HotelDailySalesServiceImpl 类的代码。这个类实现了 HotelDailySalesService 接口,并提供了对酒店每日售卖管理的相关服务实现。以下是逐行解析:


1. 包声明和导入

package cn.iocoder.central.module.hotel.service;
  • 声明了该类所在的包路径。
import cn.iocoder.central.module.hotel.controller.admin.vo.dailysales.*;
import cn.iocoder.central.module.hotel.controller.admin.vo.selling.HotelSellingRespVO;
import cn.iocoder.central.module.hotel.dal.mysql.HotelPhysicalMapper;
import cn.iocoder.central.module.hotel.dal.mysql.HotelSellingMapper;
import cn.iocoder.central.module.hotel.enums.ErrorCodeConstants;
import cn.iocoder.yudao.framework.common.util.date.DateTimeFormatterUtils;
  • 导入了所需的类和工具方法:
    • vo 包中的视图对象(VO)用于数据传输。
    • 数据访问层(DAL)的 Mapper 接口:HotelPhysicalMapper, HotelSellingMapper, HotelDailySalesMapper
    • 枚举类 ErrorCodeConstants 定义了错误码。
    • 工具类 DateTimeFormatterUtils 提供日期格式化功能。

2. 类声明

@Service
@Validated
public class HotelDailySalesServiceImpl implements HotelDailySalesService {
  • @Service 注解表明这是一个 Spring 的服务层组件。
  • @Validated 注解表示启用方法级别的参数校验。
  • 实现了 HotelDailySalesService 接口。

3. 成员变量

@Resource
private HotelDailySalesMapper hotelDailySalesMapper;

@Autowired
private HotelSellingMapper hotelSellingMapper;

@Autowired
private HotelPhysicalMapper hotelPhysicalMapper;
  • 使用 @Resource@Autowired 注解注入了三个 Mapper 对象:
    • hotelDailySalesMapper:操作每日售卖数据。
    • hotelSellingMapper:操作售卖房型数据。
    • hotelPhysicalMapper:操作物理房型数据。

4. 方法:createHotelDailySales

@Override
public boolean createHotelDailySales(HotelDailySalesSaveReqVO createReqVO) {
    for (String saleDate : createReqVO.getSaleDate()) {
        saleDate = DateTimeFormatterUtils.formatDateTime(saleDate);
        Long id = hotelSellingMapper.selectByDateAndSellId(saleDate, createReqVO.getSellingRoomId());
        HotelDailySalesSaveVO hotelDailySalesSaveVO = BeanUtils.toBean(createReqVO, HotelDailySalesSaveVO.class);
        hotelDailySalesSaveVO.setId(id);
        hotelDailySalesSaveVO.setSaleDate(saleDate);
        if (id == null) {
            hotelDailySalesMapper.insert(BeanUtils.toBean(hotelDailySalesSaveVO, HotelDailySalesDO.class));
        } else {
            hotelDailySalesMapper.updateUseXmlById(BeanUtils.toBean(hotelDailySalesSaveVO, HotelDailySalesDO.class));
        }
    }
    return true;
}
  • 遍历 createReqVO 中的每个销售日期。
  • 转换日期格式并检查该日期是否已存在。
  • 如果不存在,则插入新记录;如果存在,则更新记录。
  • 返回 true 表示操作成功。

5. 方法:updateHotelDailySales

@Override
public void updateHotelDailySales(HotelDailySalesSaveReqVO updateReqVO) {
    validateHotelDailySalesExists(updateReqVO.getId());
    HotelDailySalesDO updateObj = BeanUtils.toBean(updateReqVO, HotelDailySalesDO.class);
    hotelDailySalesMapper.updateById(updateObj);
}
  • 校验指定 ID 的记录是否存在。
  • updateReqVO 转换为 HotelDailySalesDO 并更新数据库。

6. 方法:deleteHotelDailySales

@Override
public void deleteHotelDailySales(Long id) {
    validateHotelDailySalesExists(id);
    hotelDailySalesMapper.deleteById(id);
}
  • 校验指定 ID 的记录是否存在。
  • 删除对应的记录。

7. 方法:validateHotelDailySalesExists

private void validateHotelDailySalesExists(Long id) {
    if (hotelDailySalesMapper.selectById(id) == null) {
        throw exception(HOTEL_DAILY_SALES_NOT_EXISTS);
    }
}
  • 校验指定 ID 的记录是否存在。
  • 如果不存在,则抛出异常。

8. 方法:getHotelDailySales

@Override
public HotelDailySalesDO getHotelDailySales(Long id) {
    return hotelDailySalesMapper.selectById(id);
}
  • 查询指定 ID 的每日售卖记录。

9. 方法:getHotelDailySalesPage

@Override
public PageResult<HotelDailySalesDO> getHotelDailySalesPage(HotelDailySalesPageReqVO pageReqVO) {
    return hotelDailySalesMapper.selectPage(pageReqVO);
}
  • 分页查询每日售卖记录。

10. 方法:getHotelDailyPage

@Override
public PageResult<HotelDailyRespVO> getHotelDailyPage(HotelDailyPageReqVO pageReqVO) {
    List<HotelDailyRespVO> hotelDailyRespVOList = BeanUtils.toBean(
            hotelPhysicalMapper.selectByHotelId(pageReqVO.getHotelId()), HotelDailyRespVO.class);
    hotelDailyRespVOList.forEach(hotelDailyRespVO -> {
        hotelDailyRespVO.setSellingList(getHotelDailySellingList(hotelDailyRespVO.getId(), pageReqVO));
    });
    Long total = (long) hotelDailyRespVOList.size();
    return new PageResult<>(hotelDailyRespVOList, total);
}
  • 查询物理房型信息并构造分页结果。
  • 遍历物理房型,获取每个房型的售卖信息和每日价格。
  • 返回分页结果。

11. 方法:updateIsForbiddenSale

@Override
public void updateIsForbiddenSale(Long id) {
    validateHotelDailySalesExists(id);
    hotelDailySalesMapper.updateIsForbiddenSale(id);
}
  • 校验记录是否存在后,更新其禁售状态。

12. 方法:createHotelDailySalesByTimeRange

@Override
public Boolean createHotelDailySalesByTimeRange(HotelDailySalesTimeRangeReqVO createReqVO) {
    DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
    LocalDate startDate = LocalDate.parse(createReqVO.getBeginSaleDate(), formatter);
    LocalDate endDate = LocalDate.parse(createReqVO.getEndSaleDate(), formatter);
    for (LocalDate date = startDate; !date.isAfter(endDate); date = date.plusDays(1)) {
        int currentDayOfWeek = date.getDayOfWeek().getValue();
        if (isDayOfWeekMatch(currentDayOfWeek, createReqVO.getWeek())) {
            String formattedDate = date.format(formatter);
            Long id = hotelSellingMapper.selectByDateAndSellId(formattedDate, createReqVO.getSellingRoomId());
            HotelDailySalesSaveVO hotelDailySalesSaveVO = BeanUtils.toBean(createReqVO, HotelDailySalesSaveVO.class);
            hotelDailySalesSaveVO.setId(id);
            hotelDailySalesSaveVO.setSaleDate(formattedDate);
            if (id == null) {
                hotelDailySalesMapper.insert(BeanUtils.toBean(hotelDailySalesSaveVO, HotelDailySalesDO.class));
            } else {
                hotelDailySalesMapper.updateUseXmlById(BeanUtils.toBean(hotelDailySalesSaveVO, HotelDailySalesDO.class));
            }
        }
    }
    return true;
}
  • 根据时间范围创建每日售卖记录。
  • 遍历日期范围,检查是否符合星期条件。
  • 插入或更新记录。

14. 方法:updateAreForbiddenSale

@Override
public void updateAreForbiddenSale(HotelDailySalesUpdateReqVO updateReqVO) {
    // 定义日期格式化器
    DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
    // 遍历售卖房型ID
    for (Long sellingRoomId : updateReqVO.getSellingRoomId()) {
        if (updateReqVO.getTimeType() == 0) {
            // timeType为0,表示按时间段处理
            if (updateReqVO.getSaleDate() == null || updateReqVO.getSaleDate().length != 2) {
                // 要求日期数组有且只有两个,起始和结束时间
                throw exception(ErrorCodeConstants.SALE_DATE_RANGE_INVALID);
            }
            // 解析起始日期和结束日期
            LocalDate startDate = LocalDate.parse(updateReqVO.getSaleDate()[0], formatter);
            LocalDate endDate = LocalDate.parse(updateReqVO.getSaleDate()[1], formatter);
            // 遍历日期范围
            for (LocalDate date = startDate; !date.isAfter(endDate); date = date.plusDays(1)) {
                // 检查当前日期是否符合星期条件
                if (isDayOfWeekMatch(date.getDayOfWeek().getValue(), updateReqVO.getWeek())) {
                    // 转化日期格式
                    String dateStr = date.format(formatter);
                    // 修改
                    hotelDailySalesMapper.updateIsForbiddenSaleByDate(dateStr, sellingRoomId, updateReqVO.getIsForbiddenSale());
                }
            }
        } else if (updateReqVO.getTimeType() == 1) {
            // timeType为1,表示按具体日期列表处理
            for (String dateStr : updateReqVO.getSaleDate()) {
                // 修改
                hotelDailySalesMapper.updateIsForbiddenSaleByDate(dateStr, sellingRoomId, updateReqVO.getIsForbiddenSale());
            }
        } else {
            // 其他情况,抛出异常
            throw exception(TIME_TYPE_IS_ERROR);
        }
    }
}
  • 根据 timeType 的值决定是按时间段还是按具体日期列表处理。
  • 如果是时间段(timeType == 0),需要校验 saleDate 是否包含起始和结束日期。
  • 如果是具体日期列表(timeType == 1),直接遍历日期列表进行更新。
  • 调用 hotelDailySalesMapper.updateIsForbiddenSaleByDate 更新禁售状态。

15. 方法:isDayOfWeekMatch

private boolean isDayOfWeekMatch(int currentDayOfWeek, int[] week) {
    // 如果星期条件包含8,则表示全部星期都匹配
    for (int day : week) {
        if (day == currentDayOfWeek) {
            return true;
        }
    }
    return false;
}
  • 判断当前日期的星期值是否符合给定的星期条件。
  • 如果 week 数组中包含当前星期值,则返回 true,否则返回 false

16. 方法:getHotelDailySellingList

private List<HotelDailySellingList> getHotelDailySellingList(Long PId, HotelDailyPageReqVO pageReqVO) {
    // 创建返回对象列表
    List<HotelDailySellingList> hotelDailySellingLists = new ArrayList<>();
    // 获取售卖房型
    List<HotelSellingRespVO> hotelSellingRespVO = BeanUtils.toBean(
            hotelSellingMapper.selectByPId(PId, pageReqVO.getRoomTypeStatus()), HotelSellingRespVO.class);
    // 遍历售卖房型
    hotelSellingRespVO.forEach(hsr -> {
        // 创建返回对象并补充参数
        HotelDailySellingList hotelDailySellingList = new HotelDailySellingList();
        hotelDailySellingList.setSelling(hsr);
        // 调用方法,传入售卖房型id和查询参数,获取每日价格
        hotelDailySellingList.setDailySales(getHotelDailySalesRespVO(hsr.getId(), pageReqVO));
        // 加入返回对象列表
        hotelDailySellingLists.add(hotelDailySellingList);
    });
    return hotelDailySellingLists;
}
  • 根据产品 ID 和请求参数获取售卖房型及其每日销售信息。
  • 遍历每个售卖房型,调用 getHotelDailySalesRespVO 获取其每日价格信息,并构造返回结果。

17. 方法:getHotelDailySalesRespVO

private List<HotelDailySalesRespVO> getHotelDailySalesRespVO(Long SellingId, HotelDailyPageReqVO pageReqVO) {
    // 定义限制日期
    int limit;
    // 判断显示的状态
    if (pageReqVO.getDisplayStatus() == 0) {
        // 为按星期,只要七天
        limit = 6;
    } else if (pageReqVO.getDisplayStatus() == 1) {
        // 为月份,只要一个月
        limit = 34;
    } else {
        // 显示状态错误
        throw exception(Display_Status_NOT_EXISTS);
    }
    // 获取起始日期
    String date = DateTimeFormatterUtils.formatDateTime(pageReqVO.getDate());
    LocalDateTime startDate = LocalDateTime.parse(date, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
    // 获取结束日期
    LocalDateTime endDate = startDate.plusDays(limit);
    // 获取数据
    List<HotelDailySalesRespVO> hdsr = BeanUtils.toBean(
            hotelDailySalesMapper.selectlistBySellingId(SellingId, startDate, endDate), HotelDailySalesRespVO.class);
    // 定义标识,temp指向当前list的值,len为list长度
    int temp = 0;
    int len = hdsr.size();
    // 创建结果list
    List<HotelDailySalesRespVO> resultList = new ArrayList<>();
    // 日期增长循环
    for (LocalDateTime currentDate = startDate; !currentDate.isAfter(endDate); currentDate = currentDate.plusDays(1)) {
        // temp小于长度,并且当前日期等于list中的日期,则加入结果list,temp加一
        if (temp < len && currentDate.isEqual(hdsr.get(temp).getSaleDate())) {
            resultList.add(hdsr.get(temp));
            temp++;
        } else {
            // 否则,加入null
            HotelDailySalesRespVO hotelDailySalesRespVO = new HotelDailySalesRespVO();
            hotelDailySalesRespVO.setSaleDate(currentDate);
            resultList.add(hotelDailySalesRespVO);
        }
    }
    return resultList;
}
  • 根据售卖房型 ID 和请求参数获取每日销售响应 VO 列表。
  • 根据 displayStatus 决定日期范围(一周或一个月)。
  • 遍历日期范围,将数据库中的数据与日期对齐,缺失的日期填充为 null

总结

通过对 HotelDailySalesServiceImpl 类的逐行解析,我们可以看到该类主要实现了以下功能:

  1. CRUD 操作:包括创建、更新、删除和查询每日售卖记录。
  2. 批量操作:支持按日期范围批量创建或更新售卖记录。
  3. 分页查询:支持分页查询物理房型及其售卖信息。
  4. 禁售状态管理:支持按时间段或具体日期列表更新禁售状态。
  5. 日期对齐逻辑:在查询每日销售信息时,确保日期连续性,缺失的日期填充为 null


网站公告

今日签到

点亮在社区的每一天
去签到