Bladeren bron

修改bug

jinshihui 6 dagen geleden
bovenliggende
commit
af369d9e81

+ 3 - 1
nightFragrance-admin/src/main/java/com/ylx/web/controller/massage/TJsController.java

@@ -224,10 +224,12 @@ public class TJsController extends BaseController {
                     Map<String, BigDecimal> collect = nearbyTechnicians.stream().collect(Collectors.toMap(TJs::getcPhone, TJs::getDistance));
                     js.setIds(nearbyTechnicians.stream().map(TJs::getcPhone).collect(Collectors.toList()));
                     log.info("缓存技师===========>:{}", JSONUtil.toJsonStr(nearbyTechnicians.stream().map(TJs::getcPhone).collect(Collectors.toList())));
+
                     int size = (int) page.getSize();
                     int pageNum = (int) page.getCurrent();
-                    page.setSize(100);
+
                     page.setCurrent(1);
+                    page.setSize(100);
                     Page<TJs> all = jsService.getAll(page, js);
                     log.info("总条数============>:{},当前页:{}", all.getRecords().size(), pageNum);
                     all.getRecords().forEach(item -> {

+ 2 - 2
nightFragrance-admin/src/main/java/com/ylx/web/controller/massage/TOrderController.java

@@ -43,13 +43,13 @@ public class TOrderController extends BaseController {
 
 
     /**
-     * 添加申请
+     * 下单
      *
      * @param order
      * @return R<TOrder>
      */
     @Log(title = "新增订单", businessType = BusinessType.INSERT)
-    @ApiOperation("添加申请")
+    @ApiOperation("下单")
     @RequestMapping(value = "wx/add", method = RequestMethod.POST)
     public R<TOrder> add(@RequestBody TOrder order) {
         try {

+ 1 - 1
nightFragrance-framework/src/main/java/com/ylx/framework/config/SecurityConfig.java

@@ -115,7 +115,7 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter
                         "/api/lbt/v1/getAll", "/api/js/v1/select", "/api/xiangmu/v1/wx/getAll", "/api/order/v1/getStatus",
                         "/api/xiangmu/v1/getByid", "/api/xiangmu/v1/highlights","/api/js/v1/wx/getByid","/api/js/v1/wx/select","/api/js/v1/wx/add", "/api/recharge/v1/test",
                         "/wx/pay/payNotify","/wx/pay/refundNotify","/weChat/getAccessToken","/weChat/getCode","/weChat/verifyToken","/sq/getAccessToken",
-                        "/area/select","/system/dept/list","/api/xiangmu/v1/wx/recommend").permitAll()
+                        "/area/select","/system/dept/list","/api/xiangmu/v1/wx/recommend","/api/order/v1/wx/add").permitAll()
                 // 静态资源,可匿名访问
                 .antMatchers(HttpMethod.GET, "/", "/*.txt","/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll()
                 .antMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**").permitAll()

+ 12 - 0
nightFragrance-massage/src/main/java/com/ylx/massage/domain/Location.java

@@ -13,15 +13,27 @@ import lombok.Data;
 @ApiModel("位置信息")
 public class Location {
 
+    /**
+     * 经度
+     */
     @ApiModelProperty("经度")
     private Double longitude;
 
+    /**
+     * 纬度
+     */
     @ApiModelProperty("纬度")
     private Double latitude;
 
+     /**
+     * 半径
+     */
     @ApiModelProperty("半径")
     private Double radius;
 
+    /**
+     * 条数
+     */
     @ApiModelProperty("条数")
     private Long limit;
 

+ 3 - 0
nightFragrance-massage/src/main/java/com/ylx/massage/domain/vo/TJsVo.java

@@ -25,6 +25,9 @@ public class TJsVo extends TJs {
     @ApiModelProperty("免车费")
     private String free;
 
+    /**
+     *
+     */
     private List<String> ids;
 
     @ApiModelProperty("推荐技师ID")

+ 30 - 2
nightFragrance-massage/src/main/java/com/ylx/massage/enums/OrderStatusEnum.java

@@ -18,11 +18,32 @@ public enum OrderStatusEnum {
     ALL(100, "全部"),
 
     //进行中(待接单,已接单,已到达,服务中)
+
+    /**
+     * 待接单
+     */
     WAIT_JD(0, "待接单"),
+
+    /**
+     * 已接单
+     */
     RECEIVED_ORDER(1, "已接单"),
+
     DEPART(6, "已出发"),
+
+    /**
+     * 已到达
+     */
     ARRIVED(2, "已到达"),
+
+    /**
+     *
+     */
     SERVICE(3, "服务中"),
+
+    /**
+     * 待评价
+     */
     WAIT_EVALUATE(4, "待评价"),
 
     /**
@@ -34,7 +55,15 @@ public enum OrderStatusEnum {
      * 待付款
      */
     WAIT_PAY(-1, "待付款"),
+
+    /**
+     * 已取消
+     */
     CANCEL(-2, "已取消"),
+
+    /**
+     * 已拒绝
+     */
     REFUSE(-3, "已拒绝");
 
 
@@ -47,7 +76,6 @@ public enum OrderStatusEnum {
     }
 
     public static List<Enumproject> getStatusEnum() {
-
         ArrayList<Enumproject> objects = Lists.newArrayList();
         for (OrderStatusEnum value : OrderStatusEnum.values()) {
             Enumproject enumproject = new Enumproject();
@@ -61,7 +89,7 @@ public enum OrderStatusEnum {
 
     public static String getDescByCode(Integer code) {
         for (OrderStatusEnum value : OrderStatusEnum.values()) {
-            if(value.getCode().equals(code)){
+            if (value.getCode().equals(code)) {
                 return value.getInfo();
             }
         }

+ 135 - 0
nightFragrance-massage/src/main/java/com/ylx/massage/enums/ProjectCategoryEnum.java

@@ -0,0 +1,135 @@
+package com.ylx.massage.enums;
+
+import com.ylx.common.utils.StringUtils;
+import lombok.Getter;
+import org.apache.commons.compress.utils.Lists;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 项目分类枚举
+ * <p>
+ * 定义不同服务项目的分类,用于跨项目互斥控制。
+ * 确保技师在同一时间段内只能服务一个项目,实现服务时间的互斥性。
+ * </p>
+ *
+ * @author ylx
+ * @version 1.0
+ * @since 2024
+ */
+@Getter
+public enum ProjectCategoryEnum {
+
+    /**
+     * 按摩类项目
+     * 包括:全身按摩、足疗、精油开背等按摩服务
+     */
+    MASSAGE(1, "按摩类"),
+
+    /**
+     * 陪聊类项目
+     * 包括:陪聊、陪玩、陪伴等非按摩类服务
+     * 预留功能,用于未来扩展
+     */
+    COMPANION(2, "陪聊类"),
+
+    /**
+     * 其他类项目
+     * 预留分类,用于未来扩展其他类型的服务项目
+     */
+    OTHER(99, "其他类");
+
+    private final Integer code;
+    private final String info;
+
+    ProjectCategoryEnum(Integer code, String info) {
+        this.code = code;
+        this.info = info;
+    }
+
+    /**
+     * 获取所有项目分类枚举列表
+     * <p>
+     * 用于前端展示项目分类选项。
+     * </p>
+     *
+     * @return List<Enumproject> 项目分类枚举列表
+     */
+    public static List<Enumproject> getProjectCategoryEnum() {
+        ArrayList<Enumproject> objects = Lists.newArrayList();
+        for (ProjectCategoryEnum value : ProjectCategoryEnum.values()) {
+            Enumproject enumproject = new Enumproject();
+            enumproject.setInfo(value.getInfo());
+            enumproject.setCode(value.getCode());
+            objects.add(enumproject);
+        }
+        return objects;
+    }
+
+    /**
+     * 根据代码获取项目分类描述
+     * <p>
+     * 用于将数据库中的分类代码转换为可读的描述文本。
+     * </p>
+     *
+     * @param code 项目分类代码
+     * @return String 项目分类描述,如果代码无效则返回空字符串
+     */
+    public static String getDescByCode(Integer code) {
+        if (code == null) {
+            return StringUtils.EMPTY;
+        }
+
+        for (ProjectCategoryEnum value : ProjectCategoryEnum.values()) {
+            if (value.getCode().equals(code)) {
+                return value.getInfo();
+            }
+        }
+        return StringUtils.EMPTY;
+    }
+
+    /**
+     * 根据代码获取项目分类枚举
+     * <p>
+     * 用于获取指定代码对应的枚举对象。
+     * </p>
+     *
+     * @param code 项目分类代码
+     * @return ProjectCategoryEnum 项目分类枚举,如果代码无效则返回null
+     */
+    public static ProjectCategoryEnum getByCode(Integer code) {
+        if (code == null) {
+            return null;
+        }
+
+        for (ProjectCategoryEnum value : ProjectCategoryEnum.values()) {
+            if (value.getCode().equals(code)) {
+                return value;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * 验证项目分类代码是否有效
+     * <p>
+     * 检查给定的分类代码是否在预定义的分类枚举中。
+     * </p>
+     *
+     * @param code 项目分类代码
+     * @return boolean 如果分类代码有效返回true,否则返回false
+     */
+    public static boolean isValidCode(Integer code) {
+        if (code == null) {
+            return false;
+        }
+
+        for (ProjectCategoryEnum value : ProjectCategoryEnum.values()) {
+            if (value.getCode().equals(code)) {
+                return true;
+            }
+        }
+        return false;
+    }
+}

+ 74 - 0
nightFragrance-massage/src/main/java/com/ylx/massage/service/OrderValidationService.java

@@ -0,0 +1,74 @@
+package com.ylx.massage.service;
+
+import com.ylx.massage.domain.TOrder;
+
+/**
+ * 订单校验服务接口
+ * <p>
+ * 提供订单创建和接单时的校验功能,确保技师服务时间的互斥性。
+ * 采用订单状态锁机制,防止技师在同一时间段内被分配多个订单。
+ * </p>
+ *
+ * @author ylx
+ * @version 1.0
+ * @since 2024
+ */
+public interface OrderValidationService {
+
+    /**
+     * 检查技师是否可以接单
+     * <p>
+     * 基于订单状态锁机制,检查技师是否有进行中的订单。
+     * 进行中的订单状态包括:待接单(0)、已接单(1)、已出发(6)、已到达(2)、服务中(3)
+     * </p>
+     * <b>校验规则:</b>
+     * <ul>
+     *   <li>如果技师有进行中的订单,不允许接单</li>
+     *   <li>支持跨项目校验(按摩、陪玩等项目类型)</li>
+     *   <li>升级订单除外(parentNo不为空的订单)</li>
+     * </ul>
+     *
+     * @param technicianId 技师ID
+     * @param newOrder 新订单信息
+     * @return true-可以接单
+     * @throws ServiceException 当技师不能接单时抛出异常,包含详细原因
+     */
+    boolean canAcceptOrder(String technicianId, TOrder newOrder);
+
+    /**
+     * 检查技师是否可以被手动分配订单
+     * <p>
+     * 用于后台管理员手动分配订单的场景。
+     * 规则与接单相同:技师不能有进行中的订单。
+     * </p>
+     *
+     * @param technicianId 技师ID
+     * @return true-可以分配
+     * @throws ServiceException 当技师不能被分配订单时抛出异常
+     */
+    boolean canManualAssign(String technicianId);
+
+    /**
+     * 获取技师的进行中订单数量
+     * <p>
+     * 统计技师当前进行中的订单数量。
+     * 用于前端展示技师状态和负载情况。
+     * </p>
+     *
+     * @param technicianId 技师ID
+     * @return 进行中的订单数量
+     */
+    long getInProgressOrderCount(String technicianId);
+
+    /**
+     * 检查订单是否可以支付
+     * <p>
+     * 防止在订单状态发生变化后仍然可以支付。
+     * </p>
+     *
+     * @param orderNo 订单号
+     * @return true-可以支付
+     * @throws ServiceException 当订单不可支付时抛出异常
+     */
+    boolean canPayOrder(String orderNo);
+}

+ 290 - 0
nightFragrance-massage/src/main/java/com/ylx/massage/service/impl/OrderValidationServiceImpl.java

@@ -0,0 +1,290 @@
+package com.ylx.massage.service.impl;
+
+import cn.hutool.core.util.StrUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.ylx.common.exception.ServiceException;
+import com.ylx.massage.domain.TOrder;
+import com.ylx.massage.enums.OrderStatusEnum;
+import com.ylx.massage.service.OrderValidationService;
+import com.ylx.massage.service.TOrderService;
+import com.ylx.massage.utils.OrderTimeRangeUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Service;
+
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * 订单校验服务实现类
+ * <p>
+ * 实现订单创建和接单时的校验功能,基于订单状态锁机制。
+ * 确保技师服务时间的互斥性,防止冲突订单的产生。
+ * </p>
+ *
+ * @author ylx
+ * @version 1.0
+ * @since 2024
+ */
+@Service
+public class OrderValidationServiceImpl implements OrderValidationService {
+
+    private static final Logger log = LoggerFactory.getLogger(OrderValidationServiceImpl.class);
+
+    /**
+     * 进行中的订单状态列表
+     * 包括:已接单(1)、已到达(2)、服务中(3)
+     */
+    private static final List<Integer> IN_PROGRESS_STATUSES = Arrays.asList(
+        OrderStatusEnum.RECEIVED_ORDER.getCode(), // 1 - 已接单
+        OrderStatusEnum.ARRIVED.getCode(),        // 2 - 已到达
+        OrderStatusEnum.SERVICE.getCode()         // 3 - 服务中
+    );
+
+    private final TOrderService orderService;
+
+    public OrderValidationServiceImpl(TOrderService orderService) {
+        this.orderService = orderService;
+    }
+
+    /**
+     * 检查技师是否可以接单
+     * <p>
+     * 基于订单状态锁机制和时间范围校验,检查技师是否有进行中的订单。
+     * 如果技师有进行中的订单,进一步检查时间范围是否重叠。
+     * </p>
+     * <p>
+     * 时间范围校验逻辑:
+     * 1. 如果现有订单有明确的时间范围(开始时间、结束时间),检查是否与新订单重叠
+     * 2. 如果现有订单时间信息不完整,采用保守策略,拒绝接单
+     * 3. 考虑30分钟缓冲时间,在两个订单之间留出缓冲期
+     * </p>
+     *
+     * @param technicianId 技师ID
+     * @param newOrder 新订单信息
+     * @return boolean true-可以接单
+     * @throws ServiceException 当技师不能接单时抛出异常
+     */
+    @Override
+    public boolean canAcceptOrder(String technicianId, TOrder newOrder) {
+        // 1. 参数校验
+        if (StrUtil.isBlank(technicianId)) {
+            throw new ServiceException("技师ID不能为空");
+        }
+
+        // 2. 升级订单特殊处理(升级订单可以创建)
+        if (OrderTimeRangeUtils.isUpgradeOrder(newOrder)) {
+            log.info("升级订单,跳过互斥校验,订单号:{}", newOrder.getOrderNo());
+            return true;
+        }
+
+        // 3. 查询技师的进行中订单
+        LambdaQueryWrapper<TOrder> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(TOrder::getcJsId, technicianId)
+                .in(TOrder::getnStatus, IN_PROGRESS_STATUSES)
+                .eq(TOrder::getIsDelete, 0);
+
+        List<TOrder> inProgressOrders = orderService.list(queryWrapper);
+
+        // 4. 如果没有进行中的订单,可以接单
+        if (inProgressOrders == null || inProgressOrders.isEmpty()) {
+            log.info("技师 {} 没有进行中的订单,可以接单", technicianId);
+            return true;
+        }
+
+        // 5. 有进行中的订单,进一步检查时间范围
+        log.info("技师 {} 有 {} 个进行中的订单,开始时间范围校验", technicianId, inProgressOrders.size());
+
+        // 估算新订单的开始和结束时间
+        LocalDateTime newOrderStart = OrderTimeRangeUtils.estimateStartTime(newOrder);
+        LocalDateTime newOrderEnd = OrderTimeRangeUtils.estimateEndTime(newOrder);
+
+        // 创建临时订单对象用于时间范围校验
+        TOrder newOrderWithTime = new TOrder();
+        newOrderWithTime.setOrderNo(newOrder.getOrderNo());
+        newOrderWithTime.setcGoods(newOrder.getcGoods());
+        newOrderWithTime.setStartTime(newOrderStart);
+        newOrderWithTime.setEndTime(newOrderEnd);
+
+        // 遍历所有进行中的订单,检查时间范围是否重叠
+        for (TOrder existingOrder : inProgressOrders) {
+            if (OrderTimeRangeUtils.isTimeRangeOverlapping(newOrderWithTime, existingOrder)) {
+                // 时间范围重叠,构建详细错误信息
+                String message = buildTimeConflictMessage(existingOrder, newOrderWithTime);
+                log.warn("技师 {} 无法接单,原因:{}", technicianId, message);
+                throw new ServiceException(message);
+            }
+        }
+
+        // 6. 所有进行中的订单都与新订单时间不冲突,可以接单
+        log.info("技师 {} 所有进行中订单的时间范围与新订单不冲突,可以接单", technicianId);
+        return true;
+    }
+
+    /**
+     * 检查技师是否可以被手动分配订单
+     * <p>
+     * 用于后台管理员手动分配订单的场景。
+     * 与接单校验逻辑相同,技师不能有进行中的订单,且时间范围不能重叠。
+     * </p>
+     *
+     * @param technicianId 技师ID
+     * @return true-可以分配
+     * @throws ServiceException 当技师不能被分配订单时抛出异常
+     */
+    @Override
+    public boolean canManualAssign(String technicianId) {
+        // 1. 参数校验
+        if (StrUtil.isBlank(technicianId)) {
+            throw new ServiceException("技师ID不能为空");
+        }
+
+        // 2. 查询技师的进行中订单
+        LambdaQueryWrapper<TOrder> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(TOrder::getcJsId, technicianId)
+                .in(TOrder::getnStatus, IN_PROGRESS_STATUSES)
+                .eq(TOrder::getIsDelete, 0);
+
+        List<TOrder> inProgressOrders = orderService.list(queryWrapper);
+
+        // 3. 如果没有进行中的订单,可以分配
+        if (inProgressOrders == null || inProgressOrders.isEmpty()) {
+            log.info("技师 {} 没有进行中的订单,可以手动分配订单", technicianId);
+            return true;
+        }
+
+        // 4. 有进行中的订单,检查时间范围
+        log.info("技师 {} 有 {} 个进行中的订单,无法手动分配", technicianId, inProgressOrders.size());
+
+        TOrder currentOrder = inProgressOrders.get(0);
+        String statusDesc = OrderStatusEnum.getDescByCode(currentOrder.getnStatus());
+
+        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
+        StringBuilder message = new StringBuilder();
+        message.append(String.format("该技师当前有%s的订单(订单号:%s),无法手动分配。\n",
+            statusDesc, currentOrder.getOrderNo()));
+
+        // 添加时间信息
+        if (currentOrder.getStartTime() != null) {
+            message.append("开始时间:").append(currentOrder.getStartTime().format(formatter)).append("\n");
+        }
+        if (currentOrder.getEndTime() != null) {
+            message.append("结束时间:").append(currentOrder.getEndTime().format(formatter)).append("\n");
+        }
+
+        message.append("请等待当前订单完成后再试。");
+
+        log.warn("技师 {} 无法手动分配订单,原因:{}", technicianId, message);
+        throw new ServiceException(message.toString());
+    }
+
+    /**
+     * 获取技师的进行中订单数量
+     * <p>
+     * 统计技师当前进行中的订单数量。
+     * 用于前端展示技师状态和负载情况。
+     * </p>
+     *
+     * @param technicianId 技师ID
+     * @return 进行中的订单数量
+     */
+    @Override
+    public long getInProgressOrderCount(String technicianId) {
+        if (StrUtil.isBlank(technicianId)) {
+            return 0;
+        }
+
+        LambdaQueryWrapper<TOrder> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(TOrder::getcJsId, technicianId)
+                .in(TOrder::getnStatus, IN_PROGRESS_STATUSES)
+                .eq(TOrder::getIsDelete, 0);
+
+        return orderService.count(queryWrapper);
+    }
+
+    /**
+     * 检查订单是否可以支付
+     * <p>
+     * 防止在订单状态发生变化后仍然可以支付。
+     * 只有待付款状态的订单才可以支付。
+     * </p>
+     *
+     * @param orderNo 订单号
+     * @return true-可以支付
+     * @throws ServiceException 当订单不可支付时抛出异常
+     */
+    @Override
+    public boolean canPayOrder(String orderNo) {
+        if (StrUtil.isBlank(orderNo)) {
+            throw new ServiceException("订单号不能为空");
+        }
+
+        LambdaQueryWrapper<TOrder> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(TOrder::getOrderNo, orderNo)
+                .eq(TOrder::getIsDelete, 0);
+
+        TOrder order = orderService.getOne(queryWrapper);
+
+        if (order == null) {
+            throw new ServiceException("订单不存在");
+        }
+
+        // 只有待付款状态可以支付
+        if (!OrderStatusEnum.WAIT_PAY.getCode().equals(order.getnStatus())) {
+            String currentStatus = OrderStatusEnum.getDescByCode(order.getnStatus());
+            throw new ServiceException(
+                String.format("订单当前状态为【%s】,无法支付。只有待付款状态的订单才可以支付。", currentStatus)
+            );
+        }
+
+        return true;
+    }
+
+    /**
+     * 构建时间冲突的详细错误信息
+     * <p>
+     * 当检测到时间范围重叠时,生成包含详细时间信息的错误消息。
+     * </p>
+     *
+     * @param existingOrder 现有订单
+     * @param newOrder 新订单
+     * @return String 错误信息
+     */
+    private String buildTimeConflictMessage(TOrder existingOrder, TOrder newOrder) {
+        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
+
+        StringBuilder message = new StringBuilder();
+        message.append("该技师当前有进行中的订单,时间冲突。\n");
+
+        // 现有订单信息
+        message.append("现有订单:").append(existingOrder.getOrderNo()).append("\n");
+        if (existingOrder.getStartTime() != null) {
+            message.append("  开始时间:").append(existingOrder.getStartTime().format(formatter)).append("\n");
+        }
+        if (existingOrder.getEndTime() != null) {
+            message.append("  结束时间:").append(existingOrder.getEndTime().format(formatter)).append("\n");
+        }
+
+        String statusDesc = OrderStatusEnum.getDescByCode(existingOrder.getnStatus());
+        message.append("  订单状态:").append(statusDesc).append("\n");
+
+        // 新订单信息
+        message.append("新订单:").append(newOrder.getOrderNo()).append("\n");
+        if (newOrder.getStartTime() != null) {
+            message.append("  预计开始时间:").append(newOrder.getStartTime().format(formatter)).append("\n");
+        }
+        if (newOrder.getEndTime() != null) {
+            message.append("  预计结束时间:").append(newOrder.getEndTime().format(formatter)).append("\n");
+        }
+
+        // 提示用户
+        Long durationMinutes = OrderTimeRangeUtils.calculateOrderDuration(newOrder);
+        if (durationMinutes != null) {
+            message.append("\n建议:请等待当前订单完成后再试,或选择其他时间段(需间隔至少30分钟)。");
+        }
+
+        return message.toString();
+    }
+}

+ 18 - 3
nightFragrance-massage/src/main/java/com/ylx/massage/service/impl/TOrderServiceImpl.java

@@ -89,6 +89,8 @@ public class TOrderServiceImpl extends ServiceImpl<TOrderMapper, TOrder> impleme
     @Resource
     private RefundVoucherService refundVoucherService;
 
+    @Resource
+    private OrderValidationService orderValidationService;
 
     /**
      * 判断是否免车费
@@ -131,7 +133,7 @@ public class TOrderServiceImpl extends ServiceImpl<TOrderMapper, TOrder> impleme
     @Override
     @Transactional(rollbackFor = Exception.class)
     public TOrder addOrder(TOrder order) {
-
+        // 1. 基础参数校验
         if (StringUtils.isBlank(order.getcJsId())) {
             throw new ServiceException("请选择技师");
         }
@@ -139,6 +141,12 @@ public class TOrderServiceImpl extends ServiceImpl<TOrderMapper, TOrder> impleme
             throw new ServiceException("请选择项目");
         }
 
+        // 2. 【新增】订单状态锁校验 - 检查技师是否可以接单
+        // 确保技师没有进行中的订单,保证服务时间互斥
+        log.info("开始校验技师 {} 是否可以接单,订单号:{}", order.getcJsId(), order.getOrderNo());
+        orderValidationService.canAcceptOrder(order.getcJsId(), order);
+        log.info("技师 {} 接单校验通过,继续创建订单", order.getcJsId());
+
         //优惠卷减免
 //        List<CouponReceiveVo> coupons = couponReceiveService.getByOpenId(order.getcOpenId());
 //        BigDecimal preferential = this.setCoupon(coupons);
@@ -158,9 +166,11 @@ public class TOrderServiceImpl extends ServiceImpl<TOrderMapper, TOrder> impleme
         }
         TJs js = jsService.getById(order.getcJsId());
 
-        //添加位置信息
+        //添加技师位置信息
         locationUtil.geoAdd(LocationUtil.GEO_KEY_USER, js.getcOpenId() + order.getOrderNo(), Double.parseDouble(js.getLongitude().toString()), Double.parseDouble(js.getLatitude().toString()));
+        //添加用户位置信息
         locationUtil.geoAdd(LocationUtil.GEO_KEY_USER, order.getcOpenId() + order.getOrderNo(), Double.parseDouble(address.getLongitude().toString()), Double.parseDouble(address.getLatitude().toString()));
+        //计算距离
         double distance = locationUtil.getDistance(js.getcOpenId() + order.getOrderNo(), order.getcOpenId() + order.getOrderNo());
         locationUtil.remove(LocationUtil.GEO_KEY_USER, js.getcOpenId() + order.getOrderNo(), order.getcOpenId() + order.getOrderNo());
         order.setDistance(new BigDecimal(distance));
@@ -263,7 +273,7 @@ public class TOrderServiceImpl extends ServiceImpl<TOrderMapper, TOrder> impleme
 
         LambdaQueryWrapper<TOrder> wrapper = new LambdaQueryWrapper<>();
         wrapper.eq(TOrder::getcId, order.getcId()).eq(TOrder::getnStatus, OrderStatusEnum.RECEIVED_ORDER.getCode());
-        order.setnStatus(OrderStatusEnum.DEPART.getCode());
+        //order.setnStatus(OrderStatusEnum.DEPART.getCode());
         order.setDepartTime(new Date());
         order.setDepartLatitude(Optional.ofNullable(order.getDepartLatitude()).orElse(BigDecimal.ZERO));
         order.setDepartLongitude(Optional.ofNullable(order.getDepartLongitude()).orElse(BigDecimal.ZERO));
@@ -833,6 +843,11 @@ public class TOrderServiceImpl extends ServiceImpl<TOrderMapper, TOrder> impleme
 
         TOrder orderNew = this.getById(order);
 
+        // 【新增】订单状态锁校验 - 检查技师是否可以接单
+        log.info("开始校验技师 {} 是否可以接单,订单号:{}", orderNew.getcJsId(), orderNew.getOrderNo());
+        orderValidationService.canAcceptOrder(orderNew.getcJsId(), orderNew);
+        log.info("技师 {} 接单校验通过,继续接单流程", orderNew.getcJsId());
+
         // 检查订单对应的技师是否存在
 //        updateJs (orderNew);
         // 更新订单状态

+ 1 - 0
nightFragrance-massage/src/main/java/com/ylx/massage/utils/DateTimeUtils.java

@@ -438,6 +438,7 @@ public final class DateTimeUtils {
      *
      * @param date   指定日期
      * @param amount
+     * @return Date 加减后的日期
      */
     public static Date addMinute(Date date, int amount) {
         return add(date, Calendar.MINUTE, amount);

+ 7 - 0
nightFragrance-massage/src/main/java/com/ylx/massage/utils/LocationUtil.java

@@ -104,6 +104,9 @@ public class LocationUtil {
 
     /**
     * 获取附近的技师以及距离
+     *
+     * @param locationBo 位置信息
+     * @return List<TJs> 附近的技师以及距离
     */
     public List<TJs> dis(Location locationBo) {
 
@@ -139,6 +142,10 @@ public class LocationUtil {
 
     /**
      * 获得两点之间的距离
+     *
+     * @param jsKey 技师key
+     * @param userKey 用户key
+     * @return double 距离
      * */
     public double getDistance(String jsKey,String userKey) {
         // 获取两个城市之间的距离

+ 2 - 1
nightFragrance-massage/src/main/java/com/ylx/massage/utils/MassageUtil.java

@@ -36,7 +36,8 @@ public class MassageUtil {
      * 计算出租车费用
      *
      * @param distance 全程距离(公里)
-     * @return 费用总额(元)
+     * @param deptId 部门id
+     * @return BigDecimal 费用总额(元)
      */
     public BigDecimal calculateTaxiFare(BigDecimal distance, String deptId) {
         BigDecimal BASE_FARE = baseFare; // 起步价

+ 259 - 0
nightFragrance-massage/src/main/java/com/ylx/massage/utils/OrderTimeRangeUtils.java

@@ -0,0 +1,259 @@
+package com.ylx.massage.utils;
+
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import com.ylx.massage.domain.TOrder;
+import com.ylx.massage.enums.OrderStatusEnum;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.time.LocalDateTime;
+
+/**
+ * 订单时间范围校验工具类
+ * <p>
+ * 提供订单时间范围重叠校验的功能,确保技师服务时间的互斥性。
+ * 通过检查订单的时间范围(开始时间到结束时间)是否有重叠来判断是否冲突。
+ * </p>
+ *
+ * @author ylx
+ * @version 1.0
+ * @since 2024
+ */
+public class OrderTimeRangeUtils {
+
+    private static final Logger log = LoggerFactory.getLogger(OrderTimeRangeUtils.class);
+
+    /**
+     * 添加缓冲时间(分钟)
+     * <p>
+     * 在两个订单之间添加缓冲时间,用于处理路途、准备等额外时间。
+     * 例如:订单A 14:00结束,订单B 14:30开始,如果有30分钟缓冲时间,则不会冲突。
+     * </p>
+     */
+    private static final int BUFFER_MINUTES = 30;
+
+    /**
+     * 检查新订单是否与现有订单时间范围重叠
+     * <p>
+     * 时间范围重叠判断逻辑:
+     * 1. 如果现有订单或新订单的时间信息不完整,则认为有冲突(保守策略)
+     * 2. 计算两个订单的时间范围,并判断是否重叠
+     * 3. 考虑缓冲时间,在两个订单之间留出缓冲期
+     * </p>
+     *
+     * @param newOrder 新订单
+     * @param existingOrder 现有订单
+     * @return boolean 如果时间范围重叠返回true,否则返回false
+     */
+    public static boolean isTimeRangeOverlapping(TOrder newOrder, TOrder existingOrder) {
+        // 1. 检查现有订单的时间信息
+        LocalDateTime existingStart = existingOrder.getStartTime();
+        LocalDateTime existingEnd = existingOrder.getEndTime();
+
+        // 2. 检查新订单的时间信息
+        LocalDateTime newStart = newOrder.getStartTime();
+        LocalDateTime newEnd = newOrder.getEndTime();
+
+        // 3. 保守策略:如果任何订单的时间信息不完整,认为有冲突
+        if (existingStart == null || existingEnd == null || newStart == null || newEnd == null) {
+            log.warn("订单时间信息不完整,认为时间冲突。现有订单:{},新订单:{}",
+                existingOrder.getOrderNo(), newOrder.getOrderNo());
+            return true;
+        }
+
+        // 4. 计算新订单的服务时长(分钟)
+        Long newDurationMinutes = calculateOrderDuration(newOrder);
+        if (newDurationMinutes == null || newDurationMinutes <= 0) {
+            log.warn("无法计算新订单的服务时长,订单号:{}", newOrder.getOrderNo());
+            return true;
+        }
+
+        // 5. 重新计算新订单的结束时间(基于当前时间 + 服务时长)
+        // 注意:新订单的startTime和endTime可能是预约时间,需要基于实际情况计算
+        LocalDateTime actualNewStart = newStart;
+        LocalDateTime actualNewEnd = actualNewStart.plusMinutes(newDurationMinutes);
+
+        // 6. 应用缓冲时间
+        // 在现有订单结束后增加缓冲时间,新订单需要在缓冲时间之后才能开始
+        LocalDateTime existingEndWithBuffer = existingEnd.plusMinutes(BUFFER_MINUTES);
+
+        // 7. 判断时间范围是否重叠
+        // 重叠条件:新订单开始时间 < 现有订单结束时间(含缓冲)
+        // 并且:新订单结束时间 > 现有订单开始时间
+        boolean isOverlapping = !actualNewStart.isAfter(existingEndWithBuffer) && !actualNewEnd.isBefore(existingStart);
+
+        if (isOverlapping) {
+            log.info("检测到时间冲突:现有订单 {}({} - {})与新订单 {}({} - {})",
+                existingOrder.getOrderNo(), existingStart, existingEnd,
+                newOrder.getOrderNo(), actualNewStart, actualNewEnd);
+        } else {
+            log.info("无时间冲突:现有订单 {}({} - {})与新订单 {}({} - {})",
+                existingOrder.getOrderNo(), existingStart, existingEnd,
+                newOrder.getOrderNo(), actualNewStart, actualNewEnd);
+        }
+        return isOverlapping;
+    }
+
+    /**
+     * 计算订单的服务时长(分钟)
+     * <p>
+     * 从订单明细(cGoods)中获取所有项目的服务时长,并计算总时长。
+     * </p>
+     *
+     * @param order 订单对象
+     * @return Long 服务时长(分钟),如果无法计算则返回null
+     */
+    public static Long calculateOrderDuration(TOrder order) {
+        if (order == null || order.getcGoods() == null || order.getcGoods().isEmpty()) {
+            log.warn("订单或订单明细为空,无法计算服务时长");
+            return null;
+        }
+
+        try {
+            JSONArray goods = order.getcGoods();
+            long totalMinutes = 0;
+
+            for (int i = 0; i < goods.size(); i++) {
+                JSONObject item = goods.getJSONObject(i);
+                Integer minutes = item.getInteger("nMinute");
+                Integer number = item.getInteger("number");
+
+                if (minutes != null && number != null) {
+                    totalMinutes += (minutes * number);
+                }
+            }
+
+            if (totalMinutes <= 0) {
+                log.warn("订单服务时长计算结果无效:{} 分钟,订单号:{}", totalMinutes, order.getOrderNo());
+                return null;
+            }
+
+            log.debug("订单 {} 服务时长:{} 分钟", order.getOrderNo(), totalMinutes);
+            return totalMinutes;
+        } catch (Exception e) {
+            log.error("计算订单服务时长失败,订单号:{}", order.getOrderNo(), e);
+            return null;
+        }
+    }
+
+    /**
+     * 检查订单是否有有效的时间范围
+     * <p>
+     * 判断订单是否具有有效的开始时间和结束时间。
+     * </p>
+     *
+     * @param order 订单对象
+     * @return boolean 如果订单有有效的时间范围返回true,否则返回false
+     */
+    public static boolean hasValidTimeRange(TOrder order) {
+        if (order == null) {
+            return false;
+        }
+
+        LocalDateTime start = order.getStartTime();
+        LocalDateTime end = order.getEndTime();
+
+        return start != null && end != null && start.isBefore(end);
+    }
+
+    /**
+     * 检查是否需要基于服务时长重新计算结束时间
+     * <p>
+     * 对于待接单状态的订单,结束时间可能未设置,需要基于开始时间和服务时长计算。
+     * </p>
+     *
+     * @param order 订单对象
+     * @return boolean 如果需要重新计算结束时间返回true,否则返回false
+     */
+    public static boolean needsEndTimeCalculation(TOrder order) {
+        if (order == null) {
+            return false;
+        }
+
+        // 待接单状态的订单,可能还没有设置结束时间
+        boolean isWaitOrder = OrderStatusEnum.WAIT_JD.getCode().equals(order.getnStatus());
+
+        // 结束时间为空或早于开始时间
+        boolean invalidEndTime = order.getEndTime() == null
+            || (order.getStartTime() != null && order.getEndTime().isBefore(order.getStartTime()));
+
+        return isWaitOrder && invalidEndTime;
+    }
+
+    /**
+     * 估算订单的开始时间
+     * <p>
+     * 根据订单的当前状态和时间字段,估算订单的开始时间。
+     * - 如果已有startTime,直接使用
+     * - 如果是待接单状态,使用当前时间作为开始时间
+     * - 如果有reachTime,使用到达时间作为开始时间
+     * - 如果有acceptanceTime,使用接单时间+路程时间作为开始时间
+     * </p>
+     *
+     * @param order 订单对象
+     * @return LocalDateTime 估算的开始时间
+     */
+    public static LocalDateTime estimateStartTime(TOrder order) {
+        if (order == null) {
+            return null;
+        }
+        // 使用已设置的startTime
+        if (order.getStartTime() != null) {
+            return order.getStartTime();
+        }
+        return null;
+    }
+
+    /**
+     * 估算订单的结束时间
+     * <p>
+     * 基于开始时间和订单服务时长,估算订单的结束时间。
+     * </p>
+     *
+     * @param order 订单对象
+     * @return LocalDateTime 估算的结束时间
+     */
+    public static LocalDateTime estimateEndTime(TOrder order) {
+        if (order == null) {
+            return null;
+        }
+
+        // 1. 如果已有endTime,直接使用
+        if (order.getEndTime() != null) {
+            return order.getEndTime();
+        }
+
+        // 2. 基于开始时间和服务时长计算
+        LocalDateTime startTime = estimateStartTime(order);
+        if (startTime == null) {
+            return null;
+        }
+
+        Long durationMinutes = calculateOrderDuration(order);
+        log.info("订单 {} 服务时长:{} 分钟", order.getOrderNo(), durationMinutes);
+        if (durationMinutes == null || durationMinutes <= 0) {
+            // 默认60分钟
+            durationMinutes = 60L;
+        }
+        return startTime.plusMinutes(durationMinutes);
+    }
+
+    /**
+     * 检查订单是否是升级订单
+     * <p>
+     * 升级订单(orderType == 2)在原有订单基础上增加服务项目,
+     * 不需要额外的时间互斥校验。
+     * </p>
+     *
+     * @param order 订单对象
+     * @return boolean 如果是升级订单返回true,否则返回false
+     */
+    public static boolean isUpgradeOrder(TOrder order) {
+        return order != null
+            && order.getParentNo() != null
+            && !order.getParentNo().trim().isEmpty()
+            && order.getOrderType() != null
+            && order.getOrderType() == 2;
+    }
+}

+ 2 - 0
nightFragrance-massage/src/main/resources/mapper/massage/TJsMapper.xml

@@ -162,6 +162,8 @@
 <!--            RAND() LIMIT #{ param.suiji }-->
 <!--        </if>-->
     </select>
+
+
     <select id="getBlockGetJs" resultType="com.ylx.massage.domain.vo.HomeBlock">
         SELECT
             DATE_FORMAT( create_time, '%Y-%m' ) AS MONTH,