Parcourir la source

商家端 取消预订 增加短信 通知

qinxuyang il y a 1 mois
Parent
commit
62b2f68651

+ 54 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/StoreBookingTableVo.java

@@ -0,0 +1,54 @@
+package shop.alien.entity.store.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ * 预订服务桌号VO(包含分类名称)
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Data
+@ApiModel(value = "StoreBookingTableVo", description = "预订服务桌号VO")
+public class StoreBookingTableVo {
+
+    @ApiModelProperty(value = "主键ID")
+    private Integer id;
+
+    @ApiModelProperty(value = "门店ID")
+    private Integer storeId;
+
+    @ApiModelProperty(value = "分类ID(关联store_booking_category表)")
+    private Integer categoryId;
+
+    @ApiModelProperty(value = "分类名称")
+    private String categoryName;
+
+    @ApiModelProperty(value = "桌号")
+    private String tableNumber;
+
+    @ApiModelProperty(value = "座位数")
+    private Integer seatingCapacity;
+
+    @ApiModelProperty(value = "删除标记, 0:未删除, 1:已删除")
+    private Integer deleteFlag;
+
+    @ApiModelProperty(value = "创建时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date createdTime;
+
+    @ApiModelProperty(value = "创建人ID")
+    private Integer createdUserId;
+
+    @ApiModelProperty(value = "修改时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date updatedTime;
+
+    @ApiModelProperty(value = "修改人ID")
+    private Integer updatedUserId;
+}

+ 3 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/StoreReservationListVo.java

@@ -76,6 +76,9 @@ public class StoreReservationListVo {
     @ApiModelProperty(value = "订金金额(元)")
     private BigDecimal depositAmount;
 
+    @ApiModelProperty(value = "退款金额(元)")
+    private BigDecimal refundAmount;
+
     @ApiModelProperty(value = "订单状态 0:待支付 1:待使用 2:已完成 3:已过期 4:已取消 5:已关闭 6:退款中 7:已退款 8:商家预订")
     private Integer orderStatus;
 

+ 8 - 3
alien-entity/src/main/resources/mapper/StoreReservationMapper.xml

@@ -100,9 +100,14 @@
         <if test="dateTo != null">
             AND ur.reservation_date &lt;= #{dateTo}
         </if>
-        <if test="orderStatus != null">
-            AND uro.order_status = #{orderStatus}
-        </if>
+        <choose>
+            <when test="orderStatus != null">
+                AND uro.order_status = #{orderStatus}
+            </when>
+            <otherwise>
+                AND uro.order_status IN (1, 2, 7)
+            </otherwise>
+        </choose>
         GROUP BY
             ur.id
         ORDER BY

+ 106 - 120
alien-store/src/main/java/shop/alien/store/controller/AiSearchController.java

@@ -21,12 +21,17 @@ import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
 import org.springframework.web.client.RestTemplate;
 import shop.alien.entity.result.R;
-import shop.alien.entity.store.*;
+import shop.alien.entity.store.CommonRating;
+import shop.alien.entity.store.LifeBlacklist;
+import shop.alien.entity.store.StoreImg;
+import shop.alien.entity.store.StoreUser;
 import shop.alien.entity.store.vo.StoreBannerVo;
-import shop.alien.entity.store.vo.StoreBusinessInfoVo;
 import shop.alien.entity.store.vo.StoreBusinessStatusVo;
 import shop.alien.entity.store.vo.StoreInfoVo;
-import shop.alien.mapper.*;
+import shop.alien.mapper.CommonRatingMapper;
+import shop.alien.mapper.LifeBlacklistMapper;
+import shop.alien.mapper.StoreImgMapper;
+import shop.alien.mapper.StoreUserMapper;
 import shop.alien.store.annotation.TrackEvent;
 import shop.alien.store.service.CommonRatingService;
 import shop.alien.store.service.StoreBannerService;
@@ -53,7 +58,6 @@ public class AiSearchController {
 
     private final StoreImgMapper storeImgMapper;
     private final StoreUserMapper storeUserMapper;
-    private final LifeUserMapper lifeUserMapper;
 
     @Value("${third-party-ai-search.exact.base-url:http://124.93.18.180:7870/api/v1/search}")
     private String aiSearchExactUrl;
@@ -71,88 +75,80 @@ public class AiSearchController {
     private final LifeBlacklistMapper lifeBlacklistMapper;
     private final AiAuthTokenUtil aiAuthTokenUtil;
 
-    @TrackEvent(
-            eventType = "SEARCH",
-            eventCategory = "TRAFFIC",
-            storeId = "",
-            targetType = "STORE"
-    )
-    @RequestMapping("/search")
-    public R search(@RequestBody Map<String,String> map) {
-
-
-        // 初始化请求体Map
-        Map<String, Object> requestBody = new HashMap<>();
-        requestBody.put("query", map.get("storeName"));
-        requestBody.put("page_size", map.get("pageSize"));
-        requestBody.put("user_lat", map.get("lat"));
-        requestBody.put("user_lng", map.get("lon"));
-        requestBody.put("category", map.get("category"));
-        requestBody.put("page", map.get("pageNum"));
-        requestBody.put("sort_by", map.get("sortBy"));
-        if("distance".equals(map.get("sortBy"))){
-            requestBody.put("sort_order", "asc");
-        } else {
-            requestBody.put("sort_order", "desc");
-        }
-        LifeUser lifeUser = lifeUserMapper.selectById(map.get("userId"));
-        if(lifeUser!=null){
-            requestBody.put("fans_id","user_"+lifeUser.getUserPhone());
-        }
-        HttpHeaders aiHeaders = new HttpHeaders();
-        String accessToken = aiAuthTokenUtil.getAccessToken();
-        aiHeaders.setContentType(MediaType.APPLICATION_JSON);
-        aiHeaders.set("Authorization", "Bearer " + accessToken);
-//        aiHeaders.set("Authorization", "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1cHN0b3JlQGFkbWluLmNvbSIsImlkIjo2LCJ0aW1lIjoxNzYyOTI1NDAzLjY1MTY5MjZ9.07lz8Ox2cGC28UCmqcKCt5R6Rfwtgs-Eiu0ttgWRxws");
-
-        HttpEntity<Map<String, Object>> request = new HttpEntity<>(requestBody, aiHeaders);
-        try {
-            log.info("调用AI检索店铺列表最上面根据店铺名查询  接口入参------{}", requestBody);
-            ResponseEntity<String> stringResponseEntity = restTemplate.postForEntity(aiSearchExactUrl, request, String.class);
-            String body = stringResponseEntity.getBody();
-            log.info("调用AI检索店铺列表最上面根据店铺名查询  接口返回------{}", body);
-            JSONObject jsonObject = JSONObject.parseObject(body);
-            JSONObject jsonObject1 = new JSONObject();
-            // 生活服务类别:转换为StoreInfoVo,确保返回的字段名按照StoreInfoVo定义
-            // 模糊搜索:从related_results和matched_results字段获取数据
-            List<StoreInfoVo> relatedResult = convertToStoreInfoList(jsonObject.getJSONArray("related_results"),map.get("userId"));
-            List<StoreInfoVo> matchedResult = convertToStoreInfoList(jsonObject.getJSONArray("matched_results"),map.get("userId"));
-
-            // 并发处理图片和营业时间,提升性能
-            CompletableFuture<Void> relatedImageFuture = CompletableFuture.runAsync(() -> fillStoreImages(relatedResult, 1));
-            CompletableFuture<Void> matchedImageFuture = CompletableFuture.runAsync(() -> fillStoreImages(matchedResult, 1));
-            CompletableFuture<Void> relatedBusinessHoursFuture = CompletableFuture.runAsync(() -> fillBusinessHours(relatedResult));
-            CompletableFuture<Void> matchedBusinessHoursFuture = CompletableFuture.runAsync(() -> fillBusinessHours(matchedResult));
-            
-            // 等待所有任务完成
-            CompletableFuture.allOf(relatedImageFuture, matchedImageFuture, relatedBusinessHoursFuture, matchedBusinessHoursFuture).join();
-
-            // 合并两个列表
-//            List<StoreInfoVo> relatedResults = new ArrayList<>();
-//            List<StoreInfoVo> matchedResults = new ArrayList<>();
-//            matchedResults.addAll(matchedResult);
-//            relatedResults.addAll(relatedResult);
-            jsonObject1.put("matchedRecords", matchedResult);
-            jsonObject1.put("relatedRecords", relatedResult);
-            // 根据matchedResult中的business_section_name去插入到storeBanners中
-            List<String> sectionNames = matchedResult.stream()
-                    .map(StoreInfoVo::getBusinessSectionName)
-                    .filter(StringUtils::isNotBlank)
-                    .distinct()
-                    .collect(Collectors.toList());
-            List<StoreBannerVo> storeBanners = storeBannerService.getBannerByAISearch(sectionNames);
-            jsonObject1.put("storeBanners", storeBanners);
-
-
-            jsonObject1.put("total", jsonObject.get("total"));
-            jsonObject1.put("size", map.get("pageSize"));
-            log.info("调用AI搜索店铺列表最上面根据店铺名查询 后端处理后数据接口返回------{}", jsonObject1);
-            return R.data(jsonObject1);
-        } catch (Exception e) {
-            log.error("调用AI搜索接口 接口异常------", e);
-        }
-        return  R.fail("请求失败");
-    }
+//    @TrackEvent(
+//            eventType = "SEARCH",
+//            eventCategory = "TRAFFIC",
+//            storeId = "",
+//            targetType = "STORE"
+//    )
+//    @RequestMapping("/search")
+//    public R search(@RequestBody Map<String,String> map) {
+//
+//
+//        // 初始化请求体Map
+//        Map<String, Object> requestBody = new HashMap<>();
+//        requestBody.put("query", map.get("storeName"));
+//        requestBody.put("page_size", map.get("pageSize"));
+//        requestBody.put("user_lat", map.get("lat"));
+//        requestBody.put("user_lng", map.get("lon"));
+//        requestBody.put("category", map.get("category"));
+//        requestBody.put("page", map.get("pageNum"));
+//        requestBody.put("sort_by", map.get("sortBy"));
+//        requestBody.put("sort_order", "desc");
+//        HttpHeaders aiHeaders = new HttpHeaders();
+//        String accessToken = aiAuthTokenUtil.getAccessToken();
+//        aiHeaders.setContentType(MediaType.APPLICATION_JSON);
+//        aiHeaders.set("Authorization", "Bearer " + accessToken);
+////        aiHeaders.set("Authorization", "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1cHN0b3JlQGFkbWluLmNvbSIsImlkIjo2LCJ0aW1lIjoxNzYyOTI1NDAzLjY1MTY5MjZ9.07lz8Ox2cGC28UCmqcKCt5R6Rfwtgs-Eiu0ttgWRxws");
+//
+//        HttpEntity<Map<String, Object>> request = new HttpEntity<>(requestBody, aiHeaders);
+//        try {
+//            log.info("调用AI检索店铺列表最上面根据店铺名查询  接口入参------{}", requestBody);
+//            ResponseEntity<String> stringResponseEntity = restTemplate.postForEntity(aiSearchExactUrl, request, String.class);
+//            String body = stringResponseEntity.getBody();
+//            log.info("调用AI检索店铺列表最上面根据店铺名查询  接口返回------{}", body);
+//            JSONObject jsonObject = JSONObject.parseObject(body);
+//            JSONObject jsonObject1 = new JSONObject();
+//            // 生活服务类别:转换为StoreInfoVo,确保返回的字段名按照StoreInfoVo定义
+//            // 模糊搜索:从related_results和matched_results字段获取数据
+//            List<StoreInfoVo> relatedResult = convertToStoreInfoList(jsonObject.getJSONArray("related_results"),map.get("userId"));
+//            List<StoreInfoVo> matchedResult = convertToStoreInfoList(jsonObject.getJSONArray("matched_results"),map.get("userId"));
+//
+//            // 并发处理图片和营业时间,提升性能
+//            CompletableFuture<Void> relatedImageFuture = CompletableFuture.runAsync(() -> fillStoreImages(relatedResult, 1));
+//            CompletableFuture<Void> matchedImageFuture = CompletableFuture.runAsync(() -> fillStoreImages(matchedResult, 1));
+//            CompletableFuture<Void> relatedBusinessHoursFuture = CompletableFuture.runAsync(() -> fillBusinessHours(relatedResult));
+//            CompletableFuture<Void> matchedBusinessHoursFuture = CompletableFuture.runAsync(() -> fillBusinessHours(matchedResult));
+//
+//            // 等待所有任务完成
+//            CompletableFuture.allOf(relatedImageFuture, matchedImageFuture, relatedBusinessHoursFuture, matchedBusinessHoursFuture).join();
+//
+//            // 合并两个列表
+////            List<StoreInfoVo> relatedResults = new ArrayList<>();
+////            List<StoreInfoVo> matchedResults = new ArrayList<>();
+////            matchedResults.addAll(matchedResult);
+////            relatedResults.addAll(relatedResult);
+//            jsonObject1.put("matchedRecords", matchedResult);
+//            jsonObject1.put("relatedRecords", relatedResult);
+//            // 根据matchedResult中的business_section_name去插入到storeBanners中
+//            List<String> sectionNames = matchedResult.stream()
+//                    .map(StoreInfoVo::getBusinessSectionName)
+//                    .filter(StringUtils::isNotBlank)
+//                    .distinct()
+//                    .collect(Collectors.toList());
+//            List<StoreBannerVo> storeBanners = storeBannerService.getBannerByAISearch(sectionNames);
+//            jsonObject1.put("storeBanners", storeBanners);
+//
+//
+//            jsonObject1.put("total", jsonObject.get("total"));
+//            jsonObject1.put("size", map.get("pageSize"));
+//            log.info("调用AI搜索店铺列表最上面根据店铺名查询 后端处理后数据接口返回------{}", jsonObject1);
+//            return R.data(jsonObject1);
+//        } catch (Exception e) {
+//            log.error("调用AI搜索接口 接口异常------", e);
+//        }
+//        return  R.fail("请求失败");
+//    }
 
     @RequestMapping("/fuzzySearch")
     public R fuzzySearch(@RequestBody Map<String,String> map) {
@@ -165,11 +161,7 @@ public class AiSearchController {
         requestBody.put("category", map.get("category"));
         requestBody.put("page", map.get("pageNum"));
         requestBody.put("sort_by", map.get("sortBy"));
-        if("distance".equals(map.get("sortBy"))){
-            requestBody.put("sort_order", "asc");
-        } else {
-            requestBody.put("sort_order", "desc");
-        }
+        requestBody.put("sort_order", "desc");
         HttpHeaders aiHeaders = new HttpHeaders();
         String accessToken = aiAuthTokenUtil.getAccessToken();
         aiHeaders.setContentType(MediaType.APPLICATION_JSON);
@@ -390,7 +382,7 @@ public class AiSearchController {
 
     /**
      * 批量填充营业时间到StoreInfoVo列表中
-     * 通过调用storeInfoService.getStoreInfoBusinessHours方法获取营业时间(包含节假日信息)
+     * 通过调用storeInfoService.getStoreBusinessStatus方法获取营业时间
      *
      * @param result StoreInfoVo列表
      */
@@ -399,38 +391,32 @@ public class AiSearchController {
             return;
         }
 
-        // 遍历result集合,为每个门店调用getStoreInfoBusinessHours方法
+        // 遍历result集合,为每个门店调用getStoreBusinessStatus方法
         for (StoreInfoVo storeInfo : result) {
             if (storeInfo.getId() != null) {
                 try {
-                    // 调用getStoreInfoBusinessHours方法获取营业时间(包含节假日信息)
-                    List<StoreBusinessInfoVo> storeBusinessInfoVos = storeInfoService.getStoreInfoBusinessHours(storeInfo.getId());
-                    if (storeBusinessInfoVos != null && !storeBusinessInfoVos.isEmpty()) {
-                        // 设置包含节假日信息的营业时间列表
-                        storeInfo.setStoreBusinessInfoVos(storeBusinessInfoVos);
-                        // 转换为 List<StoreBusinessInfo> 用于兼容原有字段(StoreBusinessInfoVo 继承自 StoreBusinessInfo)
-                        List<StoreBusinessInfo> storeBusinessInfos = new ArrayList<>(storeBusinessInfoVos);
-                        storeInfo.setStoreBusinessInfos(storeBusinessInfos);
-                        // 设置第一个营业时间信息到 storeBusinessInfo 字段
-                        if (!storeBusinessInfos.isEmpty()) {
-                            storeInfo.setStoreBusinessInfo(storeBusinessInfos.get(0));
-                        }
-                        // 同时设置到openTime字段(格式化为字符串列表)
-                        List<String> openTimeList = storeBusinessInfoVos.stream()
-                                .map(info -> {
-                                    if (info.getBusinessDate() != null && info.getStartTime() != null && info.getEndTime() != null) {
-                                        return info.getBusinessDate() + " " + info.getStartTime() + "-" + info.getEndTime();
-                                    }
-                                    return null;
-                                })
-                                .filter(time -> time != null)
-                                .collect(Collectors.toList());
-                        storeInfo.setOpenTime(openTimeList);
-                    }
-                    // 获取营业状态(通过getStoreBusinessStatus方法)
+                    // 调用getStoreBusinessStatus方法获取营业时间
                     StoreBusinessStatusVo businessStatus = storeInfoService.getStoreBusinessStatus(String.valueOf(storeInfo.getId()));
-                    if (businessStatus != null && businessStatus.getYyFlag() != null) {
-                        storeInfo.setYyFlag(businessStatus.getYyFlag());
+                    if (businessStatus != null) {
+                        // 设置营业时间信息到StoreInfoVo中
+                        if (businessStatus.getStoreBusinessInfos() != null && !businessStatus.getStoreBusinessInfos().isEmpty()) {
+                            storeInfo.setStoreBusinessInfos(businessStatus.getStoreBusinessInfos());
+                            // 同时设置到openTime字段(格式化为字符串列表)
+                            List<String> openTimeList = businessStatus.getStoreBusinessInfos().stream()
+                                    .map(info -> {
+                                        if (info.getBusinessDate() != null && info.getStartTime() != null && info.getEndTime() != null) {
+                                            return info.getBusinessDate() + " " + info.getStartTime() + "-" + info.getEndTime();
+                                        }
+                                        return null;
+                                    })
+                                    .filter(time -> time != null)
+                                    .collect(Collectors.toList());
+                            storeInfo.setOpenTime(openTimeList);
+                        }
+                        // 设置营业状态
+                        if (businessStatus.getYyFlag() != null) {
+                            storeInfo.setYyFlag(businessStatus.getYyFlag());
+                        }
                     }
                 } catch (Exception e) {
                     log.error("获取门店营业时间失败,storeId: {}", storeInfo.getId(), e);

+ 9 - 3
alien-store/src/main/java/shop/alien/store/controller/StoreBookingSettingsController.java

@@ -71,9 +71,6 @@ public class StoreBookingSettingsController {
         if (dto.getBookingDateDisplayDays() == null || dto.getBookingDateDisplayDays() <= 0) {
             return R.fail("预订日期显示天数必须大于0");
         }
-        if (dto.getBookingTimeType() == null) {
-            return R.fail("预订时间类型不能为空");
-        }
         if (dto.getMaxCapacityPerSlot() == null || dto.getMaxCapacityPerSlot() <= 0) {
             return R.fail("单时段最大容纳人数必须大于0");
         }
@@ -142,6 +139,15 @@ public class StoreBookingSettingsController {
             }
         }
         
+        // 编辑校验:如果是编辑操作,检查正常营业时间的id是否有值
+        // 注意:特殊营业时间列表中的项可以部分有id(编辑)部分没有id(新增)
+        if (dto.getId() != null) {
+            // 检查正常营业时间:编辑模式下,如果提供了正常营业时间,必须提供id
+            if (dto.getNormalBusinessHours() != null && dto.getNormalBusinessHours().getId() == null) {
+                return R.fail("编辑模式下,正常营业时间必须提供id");
+            }
+        }
+        
         try {
             // 使用新的方法保存设置和营业时间
             boolean result = storeBookingSettingsService.saveSettingsWithBusinessHours(dto);

+ 3 - 5
alien-store/src/main/java/shop/alien/store/controller/StoreBookingTableController.java

@@ -6,11 +6,10 @@ import lombok.extern.slf4j.Slf4j;
 import org.springframework.util.StringUtils;
 import org.springframework.web.bind.annotation.*;
 import shop.alien.entity.result.R;
-import shop.alien.entity.store.StoreBookingCategory;
 import shop.alien.entity.store.StoreBookingTable;
 import shop.alien.entity.store.dto.StoreBookingTableBatchDTO;
 import shop.alien.entity.store.dto.StoreBookingTableDTO;
-import shop.alien.store.service.StoreBookingCategoryService;
+import shop.alien.entity.store.vo.StoreBookingTableVo;
 import shop.alien.store.service.StoreBookingTableService;
 
 import java.util.List;
@@ -31,7 +30,6 @@ import java.util.List;
 public class StoreBookingTableController {
 
     private final StoreBookingTableService storeBookingTableService;
-    private final StoreBookingCategoryService storeBookingCategoryService;
 
     @ApiOperationSupport(order = 1)
     @ApiOperation("查询预订服务桌号列表")
@@ -40,7 +38,7 @@ public class StoreBookingTableController {
             @ApiImplicitParam(name = "categoryId", value = "分类ID(可选,不传则查询全部)", dataType = "Integer", paramType = "query", required = false)
     })
     @GetMapping("/list")
-    public R<List<StoreBookingTable>> getTableList(
+    public R<List<StoreBookingTableVo>> getTableList(
             @RequestParam Integer storeId,
             @RequestParam(required = false) Integer categoryId) {
         log.info("StoreBookingTableController.getTableList?storeId={}, categoryId={}", storeId, categoryId);
@@ -50,7 +48,7 @@ public class StoreBookingTableController {
         }
         
         try {
-            List<StoreBookingTable> list = storeBookingTableService.getTableList(storeId, categoryId);
+            List<StoreBookingTableVo> list = storeBookingTableService.getTableListWithCategoryName(storeId, categoryId);
             return R.data(list);
         } catch (Exception e) {
             log.error("查询预订服务桌号列表失败", e);

+ 8 - 5
alien-store/src/main/java/shop/alien/store/controller/StoreReservationController.java

@@ -36,7 +36,7 @@ public class StoreReservationController {
             @ApiImplicitParam(name = "status", value = "预约状态(可选,0:待确认 1:已确认 2:已到店 3:已取消 4:未到店超时)", dataType = "Integer", paramType = "query", required = false),
             @ApiImplicitParam(name = "dateFrom", value = "预约日期起 yyyy-MM-dd", dataType = "String", paramType = "query", required = false),
             @ApiImplicitParam(name = "dateTo", value = "预约日期止 yyyy-MM-dd", dataType = "String", paramType = "query", required = false),
-            @ApiImplicitParam(name = "orderStatus", value = "订单状态(可选,0:待支付 1:待使用 2:已完成 3:已过期 4:已取消 5:已关闭 6:退款中 7:已退款 8:商家预订)", dataType = "Integer", paramType = "query", required = false)
+            @ApiImplicitParam(name = "orderStatus", value = "订单状态(可选,0:待支付 1:待使用 2:已完成 3:已过期 4:已取消 5:已关闭 6:退款中 7:已退款 8:商家预订;不传则默认查询:待使用、已完成、已退款三种状态)", dataType = "Integer", paramType = "query", required = false)
     })
     @GetMapping("/list")
     public R<List<StoreReservationListVo>> getReservationList(
@@ -64,18 +64,21 @@ public class StoreReservationController {
     @ApiOperationSupport(order = 2)
     @ApiOperation("商家端取消预约")
     @ApiImplicitParams({
-            @ApiImplicitParam(name = "reservationId", value = "预约ID", dataType = "Integer", paramType = "query", required = true)
+            @ApiImplicitParam(name = "reservationId", value = "预约ID", dataType = "Integer", paramType = "query", required = true),
+            @ApiImplicitParam(name = "cancelReason", value = "取消原因(限30字)", dataType = "String", paramType = "query", required = false)
     })
     @PostMapping("/cancel")
-    public R<String> cancelReservation(@RequestParam Integer reservationId) {
-        log.info("StoreReservationController.cancelReservation?reservationId={}", reservationId);
+    public R<String> cancelReservation(
+            @RequestParam Integer reservationId,
+            @RequestParam(required = false) String cancelReason) {
+        log.info("StoreReservationController.cancelReservation?reservationId={}&cancelReason={}", reservationId, cancelReason);
 
         if (reservationId == null) {
             return R.fail("预约ID不能为空");
         }
 
         try {
-            boolean result = storeReservationService.cancelReservationByStore(reservationId);
+            boolean result = storeReservationService.cancelReservationByStore(reservationId, cancelReason);
             if (result) {
                 return R.success("取消预约成功");
             } else {

+ 10 - 0
alien-store/src/main/java/shop/alien/store/service/StoreBookingTableService.java

@@ -2,6 +2,7 @@ package shop.alien.store.service;
 
 import com.baomidou.mybatisplus.extension.service.IService;
 import shop.alien.entity.store.StoreBookingTable;
+import shop.alien.entity.store.vo.StoreBookingTableVo;
 
 import java.util.List;
 
@@ -23,6 +24,15 @@ public interface StoreBookingTableService extends IService<StoreBookingTable> {
     List<StoreBookingTable> getTableList(Integer storeId, Integer categoryId);
 
     /**
+     * 查询预订服务桌号列表(包含分类名称)
+     *
+     * @param storeId   门店ID
+     * @param categoryId 分类ID(可选,null表示查询全部)
+     * @return List<StoreBookingTableVo>
+     */
+    List<StoreBookingTableVo> getTableListWithCategoryName(Integer storeId, Integer categoryId);
+
+    /**
      * 新增预订服务桌号
      *
      * @param table 桌号对象

+ 2 - 1
alien-store/src/main/java/shop/alien/store/service/StoreReservationService.java

@@ -30,9 +30,10 @@ public interface StoreReservationService {
      * 商家端取消预约
      *
      * @param reservationId 预约ID
+     * @param cancelReason 取消原因(可选,限30字)
      * @return 是否成功
      */
-    boolean cancelReservationByStore(Integer reservationId);
+    boolean cancelReservationByStore(Integer reservationId, String cancelReason);
 
     /**
      * 商家端删除预订信息

+ 69 - 18
alien-store/src/main/java/shop/alien/store/service/impl/StoreBookingBusinessHoursServiceImpl.java

@@ -17,6 +17,7 @@ import shop.alien.util.common.JwtUtil;
 import java.util.Collections;
 import java.util.List;
 import java.util.regex.Pattern;
+import java.util.stream.Collectors;
 
 /**
  * 预订服务营业时间表 服务实现类
@@ -162,32 +163,82 @@ public class StoreBookingBusinessHoursServiceImpl extends ServiceImpl<StoreBooki
     public boolean batchSaveBusinessHours(Integer settingsId, List<StoreBookingBusinessHours> businessHoursList) {
         log.info("StoreBookingBusinessHoursServiceImpl.batchSaveBusinessHours?settingsId={}, size={}", settingsId, businessHoursList != null ? businessHoursList.size() : 0);
         
-        if (settingsId == null) {
-            log.warn("批量保存营业时间失败:设置ID不能为空");
-            throw new RuntimeException("设置ID不能为空");
-        }
-        if (businessHoursList == null || businessHoursList.isEmpty()) {
-            log.warn("批量保存营业时间失败:营业时间列表不能为空");
-            throw new RuntimeException("营业时间列表不能为空");
-        }
+//        if (settingsId == null) {
+//            log.warn("批量保存营业时间失败:设置ID不能为空");
+//            throw new RuntimeException("设置ID不能为空");
+//        }
+//        if (businessHoursList == null || businessHoursList.isEmpty()) {
+//            log.warn("批量保存营业时间失败:营业时间列表不能为空");
+//            throw new RuntimeException("营业时间列表不能为空");
+//        }
         
         // 从JWT获取当前登录用户ID
         Integer userId = getCurrentUserId();
         
-        // 先删除该设置下的所有营业时间(逻辑删除)
-        LambdaUpdateWrapper<StoreBookingBusinessHours> deleteWrapper = new LambdaUpdateWrapper<>();
-        deleteWrapper.eq(StoreBookingBusinessHours::getSettingsId, settingsId);
-        deleteWrapper.set(StoreBookingBusinessHours::getDeleteFlag, 1);
-        if (userId != null) {
-            deleteWrapper.set(StoreBookingBusinessHours::getUpdatedUserId, userId);
+        // 处理空列表情况
+        if (businessHoursList == null || businessHoursList.isEmpty()) {
+            // 如果列表为空,删除该设置下的所有营业时间(逻辑删除)
+            LambdaUpdateWrapper<StoreBookingBusinessHours> deleteWrapper = new LambdaUpdateWrapper<>();
+            deleteWrapper.eq(StoreBookingBusinessHours::getSettingsId, settingsId);
+            deleteWrapper.set(StoreBookingBusinessHours::getDeleteFlag, 1);
+            if (userId != null) {
+                deleteWrapper.set(StoreBookingBusinessHours::getUpdatedUserId, userId);
+            }
+            this.update(deleteWrapper);
+            return true;
+        }
+        
+        // 1. 收集所有传入的id(有id的项,用于编辑)
+        List<Integer> incomingIds = businessHoursList.stream()
+                .map(StoreBookingBusinessHours::getId)
+                .filter(id -> id != null)
+                .collect(Collectors.toList());
+        
+        // 2. 查询现有的营业时间
+        List<StoreBookingBusinessHours> existingHours = getListBySettingsId(settingsId);
+        List<Integer> existingIds = existingHours.stream()
+                .map(StoreBookingBusinessHours::getId)
+                .filter(id -> id != null)
+                .collect(Collectors.toList());
+        
+        // 3. 删除不在传入列表中的记录(逻辑删除)
+        // 如果传入列表中有id,则删除那些不在传入列表中的记录
+        if (!incomingIds.isEmpty()) {
+            List<Integer> idsToDelete = existingIds.stream()
+                    .filter(id -> !incomingIds.contains(id))
+                    .collect(Collectors.toList());
+            
+            if (!idsToDelete.isEmpty()) {
+                LambdaUpdateWrapper<StoreBookingBusinessHours> deleteWrapper = new LambdaUpdateWrapper<>();
+                deleteWrapper.in(StoreBookingBusinessHours::getId, idsToDelete);
+                deleteWrapper.set(StoreBookingBusinessHours::getDeleteFlag, 1);
+                if (userId != null) {
+                    deleteWrapper.set(StoreBookingBusinessHours::getUpdatedUserId, userId);
+                }
+                this.update(deleteWrapper);
+            }
+        } else {
+            // 如果传入列表中没有id,说明全部是新增,先删除该设置下的所有营业时间(逻辑删除)
+            LambdaUpdateWrapper<StoreBookingBusinessHours> deleteWrapper = new LambdaUpdateWrapper<>();
+            deleteWrapper.eq(StoreBookingBusinessHours::getSettingsId, settingsId);
+            deleteWrapper.set(StoreBookingBusinessHours::getDeleteFlag, 1);
+            if (userId != null) {
+                deleteWrapper.set(StoreBookingBusinessHours::getUpdatedUserId, userId);
+            }
+            this.update(deleteWrapper);
         }
-        this.update(deleteWrapper);
         
-        // 批量保存新的营业时间
+        // 4. 遍历传入的列表,有id的更新,没有id的新增
         for (StoreBookingBusinessHours businessHours : businessHoursList) {
             businessHours.setSettingsId(settingsId);
-            businessHours.setId(null); // 确保是新增
-            businessHours.setCreatedUserId(userId);
+            if (businessHours.getId() != null) {
+                // 有id,走更新逻辑
+                businessHours.setCreatedUserId(null); // 更新时不需要设置创建人
+            } else {
+                // 没有id,走新增逻辑
+                businessHours.setId(null); // 确保是新增
+                businessHours.setCreatedUserId(userId);
+            }
             // 验证并保存
             saveOrUpdateBusinessHours(businessHours);
         }

+ 4 - 4
alien-store/src/main/java/shop/alien/store/service/impl/StoreBookingSettingsServiceImpl.java

@@ -81,10 +81,10 @@ public class StoreBookingSettingsServiceImpl extends ServiceImpl<StoreBookingSet
             log.warn("保存预订服务信息设置失败:预订日期显示天数必须大于0");
             throw new RuntimeException("预订日期显示天数必须大于0");
         }
-        if (settings.getBookingTimeType() == null) {
-            log.warn("保存预订服务信息设置失败:预订时间类型不能为空");
-            throw new RuntimeException("预订时间类型不能为空");
-        }
+//        if (settings.getBookingTimeType() == null) {
+//            log.warn("保存预订服务信息设置失败:预订时间类型不能为空");
+//            throw new RuntimeException("预订时间类型不能为空");
+//        }
         if (settings.getMaxCapacityPerSlot() == null || settings.getMaxCapacityPerSlot() <= 0) {
             log.warn("保存预订服务信息设置失败:单时段最大容纳人数必须大于0");
             throw new RuntimeException("单时段最大容纳人数必须大于0");

+ 46 - 2
alien-store/src/main/java/shop/alien/store/service/impl/StoreBookingTableServiceImpl.java

@@ -4,18 +4,23 @@ import com.alibaba.fastjson.JSONObject;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
-import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
+import org.springframework.context.annotation.Lazy;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.util.StringUtils;
+import org.springframework.beans.BeanUtils;
+import shop.alien.entity.store.StoreBookingCategory;
 import shop.alien.entity.store.StoreBookingTable;
+import shop.alien.entity.store.vo.StoreBookingTableVo;
 import shop.alien.mapper.StoreBookingTableMapper;
+import shop.alien.store.service.StoreBookingCategoryService;
 import shop.alien.store.service.StoreBookingTableService;
 import shop.alien.util.common.JwtUtil;
 
 import java.util.Comparator;
 import java.util.List;
+import java.util.Map;
 import java.util.stream.Collectors;
 
 /**
@@ -27,9 +32,14 @@ import java.util.stream.Collectors;
 @Slf4j
 @Service
 @Transactional
-@RequiredArgsConstructor
 public class StoreBookingTableServiceImpl extends ServiceImpl<StoreBookingTableMapper, StoreBookingTable> implements StoreBookingTableService {
 
+    private final StoreBookingCategoryService storeBookingCategoryService;
+
+    public StoreBookingTableServiceImpl(@Lazy StoreBookingCategoryService storeBookingCategoryService) {
+        this.storeBookingCategoryService = storeBookingCategoryService;
+    }
+
     @Override
     public List<StoreBookingTable> getTableList(Integer storeId, Integer categoryId) {
         log.info("StoreBookingTableServiceImpl.getTableList?storeId={}, categoryId={}", storeId, categoryId);
@@ -52,6 +62,40 @@ public class StoreBookingTableServiceImpl extends ServiceImpl<StoreBookingTableM
         
         return list;
     }
+
+    @Override
+    public List<StoreBookingTableVo> getTableListWithCategoryName(Integer storeId, Integer categoryId) {
+        log.info("StoreBookingTableServiceImpl.getTableListWithCategoryName?storeId={}, categoryId={}", storeId, categoryId);
+        
+        // 先查询桌号列表
+        List<StoreBookingTable> tableList = getTableList(storeId, categoryId);
+        
+        // 查询所有分类信息,构建categoryId -> categoryName的映射
+        List<StoreBookingCategory> categoryList = storeBookingCategoryService.list(
+                new LambdaQueryWrapper<StoreBookingCategory>()
+                        .eq(StoreBookingCategory::getStoreId, storeId)
+        );
+        Map<Integer, String> categoryMap = categoryList.stream()
+                .collect(Collectors.toMap(
+                        StoreBookingCategory::getId,
+                        StoreBookingCategory::getCategoryName,
+                        (v1, v2) -> v1 // 如果有重复key,保留第一个
+                ));
+        
+        // 转换为VO并设置分类名称
+        return tableList.stream()
+                .filter(table -> table != null)
+                .map(table -> {
+                    StoreBookingTableVo vo = new StoreBookingTableVo();
+                    BeanUtils.copyProperties(table, vo);
+                    // 设置分类名称
+                    if (table.getCategoryId() != null) {
+                        vo.setCategoryName(categoryMap.get(table.getCategoryId()));
+                    }
+                    return vo;
+                })
+                .collect(Collectors.toList());
+    }
     
     /**
      * 解析桌号用于排序

+ 165 - 2
alien-store/src/main/java/shop/alien/store/service/impl/StoreReservationServiceImpl.java

@@ -6,15 +6,25 @@ import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
+import shop.alien.entity.store.StoreBookingTable;
+import shop.alien.entity.store.StoreInfo;
 import shop.alien.entity.store.UserReservation;
+import shop.alien.entity.store.UserReservationTable;
 import shop.alien.entity.store.UserReservationOrder;
 import shop.alien.entity.store.vo.StoreReservationListVo;
 import shop.alien.mapper.StoreReservationMapper;
+import shop.alien.mapper.UserReservationTableMapper;
 import shop.alien.store.config.BaseRedisService;
 import shop.alien.store.listener.RedisKeyExpirationHandler;
+import shop.alien.store.service.StoreBookingTableService;
+import shop.alien.store.service.StoreInfoService;
 import shop.alien.store.service.StoreReservationService;
 import shop.alien.store.service.UserReservationOrderService;
+import shop.alien.store.util.ali.AliSms;
+import com.alibaba.fastjson.JSONObject;
 import com.fasterxml.jackson.databind.ObjectMapper;
+import shop.alien.entity.store.LifeNotice;
+import shop.alien.mapper.LifeNoticeMapper;
 import javax.annotation.PostConstruct;
 
 import java.text.ParseException;
@@ -22,6 +32,7 @@ import java.text.SimpleDateFormat;
 import java.util.Calendar;
 import java.util.Date;
 import java.util.List;
+import java.util.stream.Collectors;
 
 /**
  * 商家端预约管理 服务实现类
@@ -39,6 +50,11 @@ public class StoreReservationServiceImpl extends ServiceImpl<StoreReservationMap
     private final BaseRedisService baseRedisService;
     private final RedisKeyExpirationHandler expirationHandler;
     private final ObjectMapper objectMapper = new ObjectMapper();
+    private final AliSms aliSms;
+    private final StoreInfoService storeInfoService;
+    private final StoreBookingTableService storeBookingTableService;
+    private final UserReservationTableMapper userReservationTableMapper;
+    private final LifeNoticeMapper lifeNoticeMapper;
 
     /** 预约状态:已取消 */
     private static final int STATUS_CANCELLED = 3;
@@ -68,8 +84,8 @@ public class StoreReservationServiceImpl extends ServiceImpl<StoreReservationMap
     }
 
     @Override
-    public boolean cancelReservationByStore(Integer reservationId) {
-        log.info("StoreReservationServiceImpl.cancelReservationByStore?reservationId={}", reservationId);
+    public boolean cancelReservationByStore(Integer reservationId, String cancelReason) {
+        log.info("StoreReservationServiceImpl.cancelReservationByStore?reservationId={}&cancelReason={}", reservationId, cancelReason);
 
         if (reservationId == null) {
             throw new RuntimeException("预约ID不能为空");
@@ -86,6 +102,15 @@ public class StoreReservationServiceImpl extends ServiceImpl<StoreReservationMap
             throw new RuntimeException("预约已取消,不能重复取消");
         }
 
+        // 校验取消原因(如果提供,限30字)
+        if (cancelReason != null && cancelReason.length() > 30) {
+            throw new RuntimeException("取消原因不能超过30字");
+        }
+        // 如果没有提供取消原因,使用默认值
+        if (cancelReason == null || cancelReason.trim().isEmpty()) {
+            cancelReason = "商家取消";
+        }
+
         // 查询关联的订单信息
         LambdaQueryWrapper<UserReservationOrder> orderWrapper = new LambdaQueryWrapper<>();
         orderWrapper.eq(UserReservationOrder::getReservationId, reservationId);
@@ -99,6 +124,9 @@ public class StoreReservationServiceImpl extends ServiceImpl<StoreReservationMap
                 throw new RuntimeException("更新预约状态失败");
             }
             log.info("商家端取消预约成功(无订单),reservationId={}", reservationId);
+            
+            // 发送短信通知
+            sendCancelReservationSms(reservation, cancelReason);
             return true;
         }
 
@@ -123,6 +151,9 @@ public class StoreReservationServiceImpl extends ServiceImpl<StoreReservationMap
             }
 
             log.info("商家端取消预约成功(免费订单),reservationId={}, orderId={}", reservationId, order.getId());
+            
+            // 发送短信通知
+            sendCancelReservationSms(reservation, cancelReason);
             return true;
         } else if (orderCostType == 1) {
             // 付费订单:功能预留(暂不更新状态,等待后续实现退款逻辑)
@@ -132,6 +163,138 @@ public class StoreReservationServiceImpl extends ServiceImpl<StoreReservationMap
         }
     }
 
+    /**
+     * 发送商家取消预约短信通知
+     */
+    private void sendCancelReservationSms(UserReservation reservation, String cancelReason) {
+        try {
+            // 获取用户手机号
+            String phone = reservation.getReservationUserPhone();
+            if (phone == null || phone.trim().isEmpty()) {
+                log.warn("预约人电话为空,无法发送短信通知,reservationId={}", reservation.getId());
+                return;
+            }
+
+            // 查询店铺信息
+            StoreInfo storeInfo = storeInfoService.getById(reservation.getStoreId());
+            String storeName = storeInfo != null ? storeInfo.getStoreName() : "未知店铺";
+            if (storeName == null || storeName.trim().isEmpty()) {
+                storeName = "未知店铺";
+            }
+
+            // 查询桌号信息
+            LambdaQueryWrapper<UserReservationTable> tableWrapper = new LambdaQueryWrapper<>();
+            tableWrapper.eq(UserReservationTable::getReservationId, reservation.getId())
+                    .eq(UserReservationTable::getDeleteFlag, 0)
+                    .orderByAsc(UserReservationTable::getSort);
+            List<UserReservationTable> reservationTables = userReservationTableMapper.selectList(tableWrapper);
+            
+            String tableNumber = "未知桌号";
+            if (reservationTables != null && !reservationTables.isEmpty()) {
+                // 获取所有桌号,用逗号分隔
+                List<String> tableNumbers = reservationTables.stream()
+                        .map(rt -> {
+                            StoreBookingTable table = storeBookingTableService.getById(rt.getTableId());
+                            return table != null && table.getTableNumber() != null ? table.getTableNumber() : null;
+                        })
+                        .filter(tn -> tn != null && !tn.trim().isEmpty())
+                        .collect(Collectors.toList());
+                
+                if (!tableNumbers.isEmpty()) {
+                    tableNumber = String.join(",", tableNumbers);
+                }
+            }
+
+            // 格式化预约时间:日期 + 时间(格式:2026-01-01 14:00)
+            String dateTime = formatReservationDateTime(reservation);
+            
+            // 构建通知内容(和短信内容一样)
+            // 短信模板:您在${dateTime},预订了${storeName},的${number}桌位,已被商家取消,取消原因:${info},请您重新预订
+            String noticeMessage = String.format("您在%s,预订了%s,的%s桌位,已被商家取消,取消原因:%s,请您重新预订", 
+                    dateTime, storeName, tableNumber, cancelReason);
+            
+            // 调用发送短信接口
+            Integer smsResult = aliSms.sendCancelReservationSms(phone, dateTime, storeName, tableNumber, cancelReason);
+            if (smsResult != null && smsResult == 1) {
+                log.info("商家取消预约短信发送成功,reservationId={}, phone={}", reservation.getId(), phone);
+            } else {
+                log.warn("商家取消预约短信发送失败,reservationId={}, phone={}", reservation.getId(), phone);
+            }
+            
+            // 发送通知(和短信内容一样)
+            sendCancelReservationNotice(reservation, phone, noticeMessage);
+        } catch (Exception e) {
+            // 短信和通知发送失败不影响取消预约流程,只记录日志
+            log.error("发送商家取消预约短信和通知异常,reservationId={}", reservation.getId(), e);
+        }
+    }
+
+    /**
+     * 发送商家取消预约通知
+     */
+    private void sendCancelReservationNotice(UserReservation reservation, String phone, String noticeMessage) {
+        try {
+            // 构建receiverId:用户端使用 "user_" + 手机号
+            String receiverId = "user_" + phone;
+            
+            // 构建通知内容JSON
+            JSONObject contextJson = new JSONObject();
+            contextJson.put("message", noticeMessage);
+            contextJson.put("reservationId", reservation.getId());
+            contextJson.put("reservationNo", reservation.getReservationNo());
+            
+            // 创建通知记录
+            LifeNotice lifeNotice = new LifeNotice();
+            lifeNotice.setSenderId("system");
+            lifeNotice.setReceiverId(receiverId);
+            lifeNotice.setBusinessId(reservation.getId());
+            lifeNotice.setTitle("订单取消提醒");
+            lifeNotice.setContext(contextJson.toJSONString());
+            lifeNotice.setNoticeType(2); // 2-订单提醒
+            lifeNotice.setIsRead(0);
+            
+            // 保存通知到数据库
+            lifeNoticeMapper.insert(lifeNotice);
+            
+            log.info("商家取消预约通知发送成功,reservationId={}, receiverId={}", reservation.getId(), receiverId);
+        } catch (Exception e) {
+            // 通知发送失败不影响流程,只记录日志
+            log.error("发送商家取消预约通知异常,reservationId={}, phone={}", reservation.getId(), phone, e);
+        }
+    }
+
+    /**
+     * 格式化预约时间:日期 + 时间(格式:2026-01-01 14:00)
+     */
+    private String formatReservationDateTime(UserReservation reservation) {
+        try {
+            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
+            
+            String dateStr = "";
+            if (reservation.getReservationDate() != null) {
+                dateStr = dateFormat.format(reservation.getReservationDate());
+            }
+            
+            String timeStr = "";
+            if (reservation.getStartTime() != null && !reservation.getStartTime().trim().isEmpty()) {
+                timeStr = reservation.getStartTime();
+            }
+            
+            if (dateStr.isEmpty() && timeStr.isEmpty()) {
+                return "未知时间";
+            } else if (dateStr.isEmpty()) {
+                return timeStr;
+            } else if (timeStr.isEmpty()) {
+                return dateStr;
+            } else {
+                return dateStr + " " + timeStr;
+            }
+        } catch (Exception e) {
+            log.error("格式化预约时间失败,reservationId={}", reservation.getId(), e);
+            return "未知时间";
+        }
+    }
+
     @Override
     public boolean deleteReservationByStore(Integer reservationId) {
         log.info("StoreReservationServiceImpl.deleteReservationByStore?reservationId={}", reservationId);

+ 114 - 0
alien-store/src/main/java/shop/alien/store/util/ali/AliSms.java

@@ -60,6 +60,10 @@ public class AliSms {
     @Value("${ali.sms.templateAccountCode:}")
     private String templateAccountCode;
 
+    @Value("${ali.sms.templatereFundCode:}")
+    private String templatereFundCode;
+
+
 
 
     /**
@@ -347,4 +351,114 @@ public class AliSms {
         }
     }
 
+    /**
+     * 发送商家预约取消短信
+     * 模板内容:您在${dateTime},预订了${storeName},的${number}桌位,已被商家取消,取消原因:${info},请您重新预订
+     * 模板CODE:SMS_501820954
+     * 
+     * @param phone        用户手机号
+     * @param dateTime     预约时间(格式:2026-01-01 14:00)
+     * @param storeName    店铺名称
+     * @param tableNumber  桌号名称(如:A01)
+     * @param cancelReason 取消原因
+     * @return 1-发送成功, null-发送失败
+     */
+    public Integer sendCancelReservationSms(String phone, String dateTime, String storeName, String tableNumber, String cancelReason) {
+        log.info("AliSms.sendCancelReservationSms?phone={}&dateTime={}&storeName={}&tableNumber={}&cancelReason={}", 
+                phone, dateTime, storeName, tableNumber, cancelReason);
+        try {
+            // 参数校验
+            if (phone == null || phone.trim().isEmpty()) {
+                log.warn("发送商家预约取消短信失败:手机号不能为空");
+                return null;
+            }
+            if (dateTime == null || dateTime.trim().isEmpty()) {
+                log.warn("发送商家预约取消短信失败:预约时间不能为空");
+                return null;
+            }
+            if (storeName == null || storeName.trim().isEmpty()) {
+                log.warn("发送商家预约取消短信失败:店铺名称不能为空");
+                return null;
+            }
+            if (tableNumber == null || tableNumber.trim().isEmpty()) {
+                log.warn("发送商家预约取消短信失败:桌号名称不能为空");
+                return null;
+            }
+            if (cancelReason == null || cancelReason.trim().isEmpty()) {
+                log.warn("发送商家预约取消短信失败:取消原因不能为空");
+                return null;
+            }
+
+            Config config = new Config()
+                    .setEndpoint(endPoint)
+                    .setAccessKeyId(accessKeyId)
+                    .setAccessKeySecret(accessKeySecret);
+
+            // 构建模板参数JSON,必须符合阿里云短信模板参数格式
+            // 模板内容:您在${dateTime},预订了${storeName},的${number}桌位,已被商家取消,取消原因:${info},请您重新预订
+            // 变量:dateTime(时间)、storeName(店铺名称)、number(桌号名称)、info(取消原因)
+            String templateParam = String.format("{\"dateTime\":\"%s\",\"storeName\":\"%s\",\"number\":\"%s\",\"content\":\"%s\"}",
+                    escapeJsonString(dateTime), 
+                    escapeJsonString(storeName),
+                    escapeJsonString(tableNumber),
+                    escapeJsonString(cancelReason));
+
+            // 使用商家预约取消短信模板代码
+            String cancelTemplateCode = templatereFundCode;
+            if (cancelTemplateCode == null || cancelTemplateCode.trim().isEmpty()) {
+                cancelTemplateCode = "SMS_501820954"; // 默认模板代码
+            }
+
+            log.info("AliSms.sendCancelReservationSms 准备发送短信,phone={}, dateTime={}, storeName={}, tableNumber={}, cancelReason={}, templateCode={}, templateParam={}", 
+                    phone, dateTime, storeName, tableNumber, cancelReason, cancelTemplateCode, templateParam);
+
+            // 构建发送请求
+            SendSmsRequest sendSmsRequest = new SendSmsRequest()
+                    // 设置签名
+                    .setSignName(signName)
+                    // 设置模板代码(商家预约取消短信模板)
+                    .setTemplateCode(cancelTemplateCode)
+                    // 设置手机号
+                    .setPhoneNumbers(phone)
+                    // 设置模板参数(必须是有效的JSON格式)
+                    .setTemplateParam(templateParam);
+            
+            // 运行时选择,可以设置不同的属性来配置运行时环境的参数。
+            RuntimeOptions runtime = new RuntimeOptions();
+            Client client = new Client(config);
+            // 调用阿里云短信API
+            SendSmsResponse sendSmsResponse = client.sendSmsWithOptions(sendSmsRequest, runtime);
+            
+            String responseCode = sendSmsResponse.getBody().getCode();
+            String responseMessage = sendSmsResponse.getBody().getMessage();
+            String bizId = sendSmsResponse.getBody().getBizId(); // 业务ID,用于查询发送状态
+            
+            // 记录完整的响应信息
+            log.info("AliSms.sendCancelReservationSms API响应详情,phone={}, responseCode={}, responseMessage={}, bizId={}", 
+                    phone, responseCode, responseMessage, bizId);
+            
+            if (!"OK".equals(responseCode)) {
+                log.error("AliSms.sendCancelReservationSms 短信发送失败,phone={}, dateTime={}, storeName={}, tableNumber={}, cancelReason={}, templateCode={}, templateParam={}, responseCode={}, responseMessage={}, bizId={}", 
+                        phone, dateTime, storeName, tableNumber, cancelReason, cancelTemplateCode, templateParam, responseCode, responseMessage, bizId);
+                return null;
+            }
+            
+            // 即使返回OK,也可能存在其他问题,记录详细信息便于排查
+            log.info("AliSms.sendCancelReservationSms 短信发送成功(API返回OK),phone={}, dateTime={}, storeName={}, tableNumber={}, cancelReason={}, bizId={}, templateCode={}, templateParam={}", 
+                    phone, dateTime, storeName, tableNumber, cancelReason, bizId, cancelTemplateCode, templateParam);
+            
+            // 如果bizId为空,可能是异常情况
+            if (bizId == null || bizId.trim().isEmpty()) {
+                log.warn("AliSms.sendCancelReservationSms 警告:API返回OK但bizId为空,可能存在问题,phone={}, responseMessage={}", 
+                        phone, responseMessage);
+            }
+            
+            return 1;
+        } catch (Exception e) {
+            log.error("AliSms.sendCancelReservationSms ERROR phone={}, dateTime={}, storeName={}, tableNumber={}, cancelReason={}, Msg={}", 
+                    phone, dateTime, storeName, tableNumber, cancelReason, e.getMessage(), e);
+            return null;
+        }
+    }
+
 }