Ver código fonte

Merge branch 'sit' into uat

# Conflicts:
#	alien-entity/src/main/java/shop/alien/entity/store/StoreInfo.java
#	alien-entity/src/main/java/shop/alien/entity/store/dto/StoreInfoDto.java
#	alien-entity/src/main/java/shop/alien/entity/store/vo/LawyerConsultationOrderVO.java
#	alien-entity/src/main/java/shop/alien/entity/store/vo/StoreInfoVo.java
#	alien-lawyer/src/main/java/shop/alien/lawyer/controller/OrderReviewController.java
#	alien-lawyer/src/main/java/shop/alien/lawyer/service/impl/OrderReviewServiceImpl.java
#	alien-store/src/main/java/shop/alien/store/controller/StoreInfoController.java
#	alien-store/src/main/java/shop/alien/store/service/StoreInfoService.java
#	alien-store/src/main/java/shop/alien/store/service/impl/StoreInfoServiceImpl.java
#	alien-store/src/main/java/shop/alien/store/service/impl/StoreOfficialAlbumServiceImpl.java
#	alien-store/src/main/java/shop/alien/store/util/ali/ocr/AbstractOcrStrategy.java
#	alien-store/src/main/java/shop/alien/store/util/ali/ocr/strategy/BusinessLicenseOcrStrategy.java
#	alien-store/src/main/java/shop/alien/store/util/ali/ocr/strategy/FoodManageLicenseOcrStrategy.java
yindp 2 semanas atrás
pai
commit
4c4ff69429
69 arquivos alterados com 8143 adições e 121 exclusões
  1. 86 0
      alien-entity/src/main/java/shop/alien/entity/store/BathFacilityService.java
  2. 86 0
      alien-entity/src/main/java/shop/alien/entity/store/SportsEquipmentFacility.java
  3. 1 1
      alien-entity/src/main/java/shop/alien/entity/store/StoreImg.java
  4. 30 0
      alien-entity/src/main/java/shop/alien/entity/store/StoreInfo.java
  5. 19 3
      alien-entity/src/main/java/shop/alien/entity/store/StoreMenu.java
  6. 75 0
      alien-entity/src/main/java/shop/alien/entity/store/StorePersonnel.java
  7. 10 0
      alien-entity/src/main/java/shop/alien/entity/store/StoreStaffConfig.java
  8. 23 0
      alien-entity/src/main/java/shop/alien/entity/store/dto/StoreInfoDto.java
  9. 4 0
      alien-entity/src/main/java/shop/alien/entity/store/excelVo/StoreInfoExcelVo.java
  10. 39 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/BathFacilityServiceCategoryVo.java
  11. 66 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/BathFacilityServiceVo.java
  12. 24 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/CouponAndEventVo.java
  13. 285 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/LawyerConsultationOrderVO.java
  14. 39 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/SportsEquipmentFacilityCategoryVo.java
  15. 61 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/SportsEquipmentFacilityVo.java
  16. 53 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/StoreAlbumDetailVo.java
  17. 25 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/StoreAlbumNameVo.java
  18. 4 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/StoreCommentVo.java
  19. 33 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/StoreInfoVo.java
  20. 6 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/StoreMainInfoVo.java
  21. 29 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/StoreOfficialAlbumImgVo.java
  22. 34 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/StorePersonnelVo.java
  23. 32 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/StoreThreeLevelStructureVo.java
  24. 17 0
      alien-entity/src/main/java/shop/alien/mapper/BathFacilityServiceMapper.java
  25. 177 0
      alien-entity/src/main/java/shop/alien/mapper/LifeCouponMapper.java
  26. 17 0
      alien-entity/src/main/java/shop/alien/mapper/SportsEquipmentFacilityMapper.java
  27. 13 0
      alien-entity/src/main/java/shop/alien/mapper/StoreCommentMapper.java
  28. 26 0
      alien-entity/src/main/java/shop/alien/mapper/StoreInfoMapper.java
  29. 54 0
      alien-entity/src/main/java/shop/alien/mapper/StoreMenuMapper.java
  30. 44 0
      alien-entity/src/main/java/shop/alien/mapper/StorePersonnelMapper.java
  31. 40 13
      alien-entity/src/main/java/shop/alien/mapper/TagsMainMapper.java
  32. 13 0
      alien-job/src/main/java/shop/alien/job/feign/AlienStoreFeign.java
  33. 30 7
      alien-job/src/main/java/shop/alien/job/store/LifeUserOrderJob.java
  34. 279 0
      alien-lawyer/src/main/java/shop/alien/lawyer/controller/OrderReviewController.java
  35. 694 0
      alien-lawyer/src/main/java/shop/alien/lawyer/service/impl/OrderReviewServiceImpl.java
  36. 168 0
      alien-store/src/main/java/shop/alien/store/controller/BathFacilityServiceController.java
  37. 17 0
      alien-store/src/main/java/shop/alien/store/controller/LifeCouponController.java
  38. 194 0
      alien-store/src/main/java/shop/alien/store/controller/SportsEquipmentFacilityController.java
  39. 448 7
      alien-store/src/main/java/shop/alien/store/controller/StoreInfoController.java
  40. 84 5
      alien-store/src/main/java/shop/alien/store/controller/StoreMenuController.java
  41. 90 4
      alien-store/src/main/java/shop/alien/store/controller/StoreOfficialAlbumController.java
  42. 87 0
      alien-store/src/main/java/shop/alien/store/controller/StorePersonnelController.java
  43. 48 0
      alien-store/src/main/java/shop/alien/store/controller/StoreStaffConfigController.java
  44. 82 0
      alien-store/src/main/java/shop/alien/store/service/BathFacilityServiceService.java
  45. 49 44
      alien-store/src/main/java/shop/alien/store/service/LifeCommentService.java
  46. 7 0
      alien-store/src/main/java/shop/alien/store/service/LifeCouponService.java
  47. 1 1
      alien-store/src/main/java/shop/alien/store/service/LifeUserDynamicsService.java
  48. 86 0
      alien-store/src/main/java/shop/alien/store/service/SportsEquipmentFacilityService.java
  49. 138 4
      alien-store/src/main/java/shop/alien/store/service/StoreInfoService.java
  50. 32 4
      alien-store/src/main/java/shop/alien/store/service/StoreMenuService.java
  51. 25 1
      alien-store/src/main/java/shop/alien/store/service/StoreOfficialAlbumService.java
  52. 58 0
      alien-store/src/main/java/shop/alien/store/service/StorePersonnelService.java
  53. 19 0
      alien-store/src/main/java/shop/alien/store/service/StoreStaffConfigService.java
  54. 258 0
      alien-store/src/main/java/shop/alien/store/service/impl/BathFacilityServiceServiceImpl.java
  55. 5 0
      alien-store/src/main/java/shop/alien/store/service/impl/LifeCouponServiceImpl.java
  56. 4 2
      alien-store/src/main/java/shop/alien/store/service/impl/LifeDiscountCouponServiceImpl.java
  57. 307 0
      alien-store/src/main/java/shop/alien/store/service/impl/SportsEquipmentFacilityServiceImpl.java
  58. 2385 5
      alien-store/src/main/java/shop/alien/store/service/impl/StoreInfoServiceImpl.java
  59. 279 20
      alien-store/src/main/java/shop/alien/store/service/impl/StoreMenuServiceImpl.java
  60. 183 0
      alien-store/src/main/java/shop/alien/store/service/impl/StoreOfficialAlbumServiceImpl.java
  61. 161 0
      alien-store/src/main/java/shop/alien/store/service/impl/StorePersonnelServiceImpl.java
  62. 31 0
      alien-store/src/main/java/shop/alien/store/service/impl/StoreStaffConfigServiceImpl.java
  63. 109 0
      alien-store/src/main/java/shop/alien/store/util/ali/ocr/AbstractOcrStrategy.java
  64. 156 0
      alien-store/src/main/java/shop/alien/store/util/ali/ocr/strategy/BusinessLicenseOcrStrategy.java
  65. 154 0
      alien-store/src/main/java/shop/alien/store/util/ali/ocr/strategy/FoodManageLicenseOcrStrategy.java
  66. 0 0
      logging.path_IS_UNDEFINED/DEBUG.log
  67. 0 0
      logging.path_IS_UNDEFINED/ERROR.log
  68. 6 0
      logging.path_IS_UNDEFINED/INFO.log
  69. 4 0
      logging.path_IS_UNDEFINED/WARN.log

+ 86 - 0
alien-entity/src/main/java/shop/alien/entity/store/BathFacilityService.java

@@ -0,0 +1,86 @@
+package shop.alien.entity.store;
+
+import com.baomidou.mybatisplus.annotation.*;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 洗浴设施及服务表
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Data
+@JsonInclude
+@TableName("bath_facility_service")
+@ApiModel(value = "BathFacilityService对象", description = "洗浴设施及服务表")
+public class BathFacilityService implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "主键")
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    @ApiModelProperty(value = "门店ID")
+    @TableField("store_id")
+    private Integer storeId;
+
+    @ApiModelProperty(value = "设施分类(1:洗浴区, 2:汗蒸区, 3:休闲区, 4:餐饮区)")
+    @TableField("facility_category")
+    private Integer facilityCategory;
+
+    @ApiModelProperty(value = "名称")
+    @TableField("facility_name")
+    private String facilityName;
+
+    @ApiModelProperty(value = "收费标准")
+    @TableField("charging_standard")
+    private String chargingStandard;
+
+    @ApiModelProperty(value = "使用时间类型(0:全天, 1:选择时间)")
+    @TableField("usage_time_type")
+    private Integer usageTimeType;
+
+    @ApiModelProperty(value = "使用开始时间(HH:mm格式)")
+    @TableField("usage_start_time")
+    private String usageStartTime;
+
+    @ApiModelProperty(value = "使用结束时间(HH:mm格式)")
+    @TableField("usage_end_time")
+    private String usageEndTime;
+
+    @ApiModelProperty(value = "是否显示在店铺详情(0:隐藏, 1:显示)")
+    @TableField("display_in_store_detail")
+    private Integer displayInStoreDetail;
+
+    @ApiModelProperty(value = "删除标记, 0:未删除, 1:已删除")
+    @TableField("delete_flag")
+    @TableLogic
+    private Integer deleteFlag;
+
+    @ApiModelProperty(value = "创建时间")
+    @TableField(value = "created_time", fill = FieldFill.INSERT)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date createdTime;
+
+    @ApiModelProperty(value = "创建人ID")
+    @TableField(value = "created_user_id", fill = FieldFill.INSERT)
+    private Integer createdUserId;
+
+    @ApiModelProperty(value = "修改时间")
+    @TableField(value = "updated_time", fill = FieldFill.INSERT_UPDATE)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date updatedTime;
+
+    @ApiModelProperty(value = "修改人ID")
+    @TableField(value = "updated_user_id", fill = FieldFill.INSERT_UPDATE)
+    private Integer updatedUserId;
+}
+

+ 86 - 0
alien-entity/src/main/java/shop/alien/entity/store/SportsEquipmentFacility.java

@@ -0,0 +1,86 @@
+package shop.alien.entity.store;
+
+import com.baomidou.mybatisplus.annotation.*;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 运动器材设施表
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Data
+@JsonInclude
+@TableName("sports_equipment_facility")
+@ApiModel(value = "SportsEquipmentFacility对象", description = "运动器材设施表")
+public class SportsEquipmentFacility implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "主键")
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    @ApiModelProperty(value = "门店ID")
+    @TableField("store_id")
+    private Integer storeId;
+
+    @ApiModelProperty(value = "设施分类(1:有氧区, 2:力量区, 3:单功能机械区)")
+    @TableField("facility_category")
+    private Integer facilityCategory;
+
+    @ApiModelProperty(value = "设施名称")
+    @TableField("facility_name")
+    private String facilityName;
+
+    @ApiModelProperty(value = "数量")
+    @TableField("quantity")
+    private Integer quantity;
+
+    @ApiModelProperty(value = "品牌")
+    @TableField("brand")
+    private String brand;
+
+    @ApiModelProperty(value = "描述")
+    @TableField("description")
+    private String description;
+
+    @ApiModelProperty(value = "是否显示在店铺详情(0:隐藏, 1:显示)")
+    @TableField("display_in_store_detail")
+    private Integer displayInStoreDetail;
+
+    @ApiModelProperty(value = "删除标记, 0:未删除, 1:已删除")
+    @TableField("delete_flag")
+    @TableLogic
+    private Integer deleteFlag;
+
+    @ApiModelProperty(value = "创建时间")
+    @TableField(value = "created_time", fill = FieldFill.INSERT)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date createdTime;
+
+    @ApiModelProperty(value = "创建人ID")
+    @TableField(value = "created_user_id", fill = FieldFill.INSERT)
+    private Integer createdUserId;
+
+    @ApiModelProperty(value = "修改时间")
+    @TableField(value = "updated_time", fill = FieldFill.INSERT_UPDATE)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date updatedTime;
+
+    @ApiModelProperty(value = "修改人ID")
+    @TableField(value = "updated_user_id", fill = FieldFill.INSERT_UPDATE)
+    private Integer updatedUserId;
+
+    @ApiModelProperty(value = "收费类型0-免费,1-收费")
+    @TableField("billing_type")
+    private Integer billingType;
+}
+

+ 1 - 1
alien-entity/src/main/java/shop/alien/entity/store/StoreImg.java

@@ -30,7 +30,7 @@ public class StoreImg extends Model<StoreImg> {
     @TableField("store_id")
     private Integer storeId;
 
-    @ApiModelProperty(value = "图片类型, 0:其他, 1:入口图, 2:相册, 3:菜品, 4:环境, 5:价目表, 6:推荐菜, 7:菜单, 8:用户评论, 9:商家申诉,10:商家头像,11:店铺轮播图,12:联名卡图片,13:动态折扣, 14:套餐图片,15:合同照片,17:打卡广场小人图片 18: 商品发布图片 19:二手商品与用户举报图片,20头图单图模式,21头图多图模式 , 22续签合同,23,二手商品记录图片类型, 24 食品经营许可证审核前状态 25.食品经营许可证审核后状态")
+    @ApiModelProperty(value = "图片类型, 0:其他, 1:入口图, 2:相册, 3:菜品, 4:环境, 5:价目表, 6:推荐菜, 7:菜单, 8:用户评论, 9:商家申诉,10:商家头像,11:店铺轮播图,12:联名卡图片,13:动态折扣, 14:套餐图片,15:合同照片,17:打卡广场小人图片 18: 商品发布图片 19:二手商品与用户举报图片,20头图单图模式,21头图多图模式 , 22续签合同,23,二手商品记录图片类型, 24 食品经营许可证审核前状态 25.食品经营许可证审核后状态, 28:运动器材设施图片, 29:洗浴设施及服务图片 30 酒水")
     @TableField("img_type")
     private Integer imgType;
 

+ 30 - 0
alien-entity/src/main/java/shop/alien/entity/store/StoreInfo.java

@@ -234,4 +234,34 @@ public class StoreInfo {
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
     @TableField("update_renew_contract_time")
     private Date  updateRenewContractTime;
+
+    @ApiModelProperty(value = "分类id(词典表 键为 business_classify)")
+    @TableField("business_classify")
+    private String businessClassify;
+
+    @ApiModelProperty(value = "分类名称")
+    @TableField("business_classify_name")
+    private String businessClassifyName;
+
+    @ApiModelProperty(value = "是否提供餐食 0 否 1 是")
+    @TableField("meal_provided")
+    private Integer mealProvided;
+
+    @ApiModelProperty(value = "娱乐经营许可证状态 字典 foodLicenceStatus")
+    @TableField("entertainment_licence_status")
+    private Integer entertainmentLicenceStatus;
+
+    @ApiModelProperty(value = "娱乐经营许可证失败原因")
+    @TableField("entertainment_licence_reason")
+    private String entertainmentLicenceReason;
+
+    @ApiModelProperty(value = "娱乐经营许可证到期时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    @TableField("entertainment_licence_expiration_time")
+    private Date entertainmentLicenceExpirationTime;
+
+    @ApiModelProperty(value = "变更娱乐经营许可证提交时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    @TableField("update_entertainment_licence_time")
+    private Date updateEntertainmentLicenceTime;
 }

+ 19 - 3
alien-entity/src/main/java/shop/alien/entity/store/StoreMenu.java

@@ -34,15 +34,19 @@ public class StoreMenu {
     @TableField("img_id")
     private Integer imgId;
 
-    @ApiModelProperty(value = "菜品名称")
+    @ApiModelProperty(value = "菜单类型:1-菜单,2-酒水")
+    @TableField("dish_menu_type")
+    private String dishMenuType;
+
+    @ApiModelProperty(value = "名称")
     @TableField("dish_name")
     private String dishName;
 
-    @ApiModelProperty(value = "菜品价格")
+    @ApiModelProperty(value = "价格")
     @TableField("dish_price")
     private BigDecimal dishPrice;
 
-    @ApiModelProperty(value = "菜品类型, 0:菜单, 1:推荐")
+    @ApiModelProperty(value = "是否推荐, 0:非推荐, 1:推荐")
     @TableField("dish_type")
     private Integer dishType;
 
@@ -89,5 +93,17 @@ public class StoreMenu {
     @TableField("description")
     private String description;
 
+    @ApiModelProperty(value = "酒精度")
+    @TableField("alcoholVolume")
+    private String alcoholVolume;
+
+    @ApiModelProperty(value = "分类")
+    @TableField("category")
+    private String category;
+
+    @ApiModelProperty(value = "品味")
+    @TableField("flavor")
+    private String flavor;
+
 
 }

+ 75 - 0
alien-entity/src/main/java/shop/alien/entity/store/StorePersonnel.java

@@ -0,0 +1,75 @@
+package shop.alien.entity.store;
+
+import com.baomidou.mybatisplus.annotation.*;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ * 店铺人员实体类
+ *
+ * @author system
+ * @since 2025-01-15
+ */
+@Data
+@JsonInclude
+@TableName("store_personnel")
+@ApiModel(value = "StorePersonnel对象", description = "店铺人员")
+public class StorePersonnel {
+
+    @ApiModelProperty(value = "主键")
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    @ApiModelProperty(value = "门店id")
+    @TableField("store_id")
+    private Integer storeId;
+
+    @ApiModelProperty(value = "人员姓名")
+    @TableField("personnel_name")
+    private String personnelName;
+
+    @ApiModelProperty(value = "角色标签,1-歌手,2-技师,3-教练,4-美发师/美容师")
+    @TableField("role_tags")
+    private String roleTags;
+
+    @ApiModelProperty(value = "图片id,关联store_img表")
+    @TableField("img_id")
+    private Integer imgId;
+
+    @ApiModelProperty(value = "人员描述")
+    @TableField("description")
+    private String description;
+
+    @ApiModelProperty(value = "排序")
+    @TableField("sort")
+    private Integer sort;
+
+    @ApiModelProperty(value = "删除标记, 0:未删除, 1:已删除")
+    @TableField("delete_flag")
+    @TableLogic
+    private Integer deleteFlag;
+
+    @ApiModelProperty(value = "创建时间")
+    @TableField(value = "created_time", fill = FieldFill.INSERT)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date createdTime;
+
+    @ApiModelProperty(value = "创建人ID")
+    @TableField("created_user_id")
+    private Integer createdUserId;
+
+    @ApiModelProperty(value = "修改时间")
+    @TableField(value = "updated_time", fill = FieldFill.INSERT_UPDATE)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date updatedTime;
+
+    @ApiModelProperty(value = "修改人ID")
+    @TableField("updated_user_id")
+    private Integer updatedUserId;
+}
+

+ 10 - 0
alien-entity/src/main/java/shop/alien/entity/store/StoreStaffConfig.java

@@ -27,33 +27,43 @@ public class StoreStaffConfig extends Model<StoreStaffConfig> {
     @TableId(value = "id", type = IdType.AUTO)
     private Integer id;
 
+    @ApiModelProperty(value = "员工职位")
     @TableField("staff_position")
     private String staffPosition;
 
+    @ApiModelProperty(value = "员工名称/昵称")
     @TableField("name")
     private String name;
 
+    @ApiModelProperty(value = "头像")
     @TableField("staff_image")
     private String staffImage;
 
+    @ApiModelProperty(value = "擅长项目")
     @TableField("proficient_projects")
     private String proficientProjects;
 
+    @ApiModelProperty(value = "标签")
     @TableField("tag")
     private String tag;
 
+    @ApiModelProperty(value = "个人简介")
     @TableField("personal_introduction")
     private String personalIntroduction;
 
+    @ApiModelProperty(value = "人员状态0-待审核 1-审核通过 2-审核拒绝")
     @TableField("status")
     private String status;
 
+    @ApiModelProperty(value = "店铺ID")
     @TableField("store_id")
     private Integer storeId;
 
+    @ApiModelProperty(value = "店铺名称")
     @TableField("store_name")
     private String storeName;
 
+    @ApiModelProperty(value = "拒绝原因")
     @TableField("rejection_reason")
     private String rejectionReason;
 

+ 23 - 0
alien-entity/src/main/java/shop/alien/entity/store/dto/StoreInfoDto.java

@@ -179,4 +179,27 @@ public class StoreInfoDto {
     @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
     @DateTimeFormat(pattern = "yyyy-MM-dd")
     private Date foodLicenceExpirationTime;
+
+    @ApiModelProperty(value = "分类id(词典表 键为 business_classify)(多个ID用逗号拼接)")
+    private List<String> businessClassify;
+
+    @ApiModelProperty(value = "分类名称")
+    private String businessClassifyName;
+
+    @ApiModelProperty(value = "是否提供餐食")
+    private Integer  mealsFlag;
+
+    @ApiModelProperty(value = "娱乐经营许可证图片地址")
+    private String entertainmentLicenceUrl;
+
+    @ApiModelProperty(value = "娱乐经营许可证状态")
+    private Integer entertainmentLicenceStatus;
+
+    @ApiModelProperty(value = "娱乐经营许可证原因")
+    private String entertainmentLicenceReason;
+
+    @ApiModelProperty(value = "娱乐经营许可证到期时间")
+    @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
+    @DateTimeFormat(pattern = "yyyy-MM-dd")
+    private Date entertainmentLicenceExpirationTime;
 }

+ 4 - 0
alien-entity/src/main/java/shop/alien/entity/store/excelVo/StoreInfoExcelVo.java

@@ -44,6 +44,10 @@ public class StoreInfoExcelVo {
     @ApiModelProperty(value = "经营种类")
     private String businessTypesName;
 
+    @ExcelHeader("经营分类")
+    @ApiModelProperty(value = "经营分类")
+    private String businessClassifyName;
+
     @ExcelHeader(value = "详细地址")
     @ApiModelProperty(value = "详细地址")
     private String storeAddress;

+ 39 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/BathFacilityServiceCategoryVo.java

@@ -0,0 +1,39 @@
+package shop.alien.entity.store.vo;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 洗浴设施及服务分类汇总视图对象
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Data
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@ApiModel(value = "BathFacilityServiceCategoryVo对象", description = "洗浴设施及服务分类汇总视图对象")
+public class BathFacilityServiceCategoryVo implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "设施分类(1:洗浴区, 2:汗蒸区, 3:休闲区, 4:餐饮区)")
+    private Integer facilityCategory;
+
+    @ApiModelProperty(value = "设施分类名称")
+    private String facilityCategoryName;
+
+    @ApiModelProperty(value = "该分类下的设备数量")
+    private Integer facilityCount;
+
+    @ApiModelProperty(value = "该分类的代表图片(从该分类下第一个设备的第一张图片获取)")
+    private String categoryImage;
+
+    @ApiModelProperty(value = "该分类下的设备列表(包含设备信息和图片)")
+    private List<BathFacilityServiceVo> facilityList;
+}
+

+ 66 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/BathFacilityServiceVo.java

@@ -0,0 +1,66 @@
+package shop.alien.entity.store.vo;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 洗浴设施及服务视图对象
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Data
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@ApiModel(value = "BathFacilityServiceVo对象", description = "洗浴设施及服务视图对象")
+public class BathFacilityServiceVo implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "主键")
+    private Integer id;
+
+    @ApiModelProperty(value = "门店ID")
+    private Integer storeId;
+
+    @ApiModelProperty(value = "设施分类(1:洗浴区, 2:汗蒸区, 3:休闲区, 4:餐饮区)")
+    private Integer facilityCategory;
+
+    @ApiModelProperty(value = "设施分类名称")
+    private String facilityCategoryName;
+
+    @ApiModelProperty(value = "名称")
+    private String facilityName;
+
+    @ApiModelProperty(value = "收费标准")
+    private String chargingStandard;
+
+    @ApiModelProperty(value = "使用时间类型(0:全天, 1:选择时间)")
+    private Integer usageTimeType;
+
+    @ApiModelProperty(value = "使用时间类型文本")
+    private String usageTimeTypeText;
+
+    @ApiModelProperty(value = "使用开始时间(HH:mm格式)")
+    private String usageStartTime;
+
+    @ApiModelProperty(value = "使用结束时间(HH:mm格式)")
+    private String usageEndTime;
+
+    @ApiModelProperty(value = "使用时间范围(如: 08:00-20:00)")
+    private String usageTimeRange;
+
+    @ApiModelProperty(value = "是否显示在店铺详情(0:隐藏, 1:显示)")
+    private Integer displayInStoreDetail;
+
+    @ApiModelProperty(value = "是否显示在店铺详情文本")
+    private String displayInStoreDetailText;
+
+    @ApiModelProperty(value = "图片列表")
+    private List<String> imageList;
+}
+

+ 24 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/CouponAndEventVo.java

@@ -0,0 +1,24 @@
+package shop.alien.entity.store.vo;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import shop.alien.entity.store.StoreImg;
+
+import java.util.List;
+
+
+@Data
+@JsonInclude
+@NoArgsConstructor
+@ApiModel(value = "代金券和营销活动", description = "代金券和营销活动")
+public class CouponAndEventVo {
+
+    @ApiModelProperty(value = "代金券")
+    private List<LifeCouponVo> couponList;
+
+    @ApiModelProperty(value = "营销活动")
+    private List<StoreImg> marketingList;
+}

+ 285 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/LawyerConsultationOrderVO.java

@@ -0,0 +1,285 @@
+package shop.alien.entity.store.vo;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import shop.alien.entity.store.LawyerLegalProblemScenario;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.time.ZoneId;
+import java.time.temporal.ChronoUnit;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 律师咨询订单视图对象(包含律师信息)
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Data
+@JsonInclude
+@ApiModel(value = "LawyerConsultationOrderVO对象", description = "咨询订单视图对象(包含律师信息)")
+public class LawyerConsultationOrderVO implements Serializable {
+
+    @ApiModelProperty(value = "主键")
+    private Integer id;
+
+    @ApiModelProperty(value = "订单编号")
+    private String orderNumber;
+
+    @ApiModelProperty(value = "客户端用户ID")
+    private Integer clientUserId;
+
+    @ApiModelProperty(value = "律师用户ID")
+    private Integer lawyerUserId;
+
+    @ApiModelProperty(value = "法律问题场景ID")
+    private String problemScenarioId;
+
+    @ApiModelProperty(value = "问题描述")
+    private String problemDescription;
+
+    @ApiModelProperty(value = "订单金额,单位分")
+    private Integer orderAmount;
+
+    @ApiModelProperty(value = "咨询费用(本单收益),单位分")
+    private Integer consultationFee;
+
+    @ApiModelProperty(value = "咨询开始时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date startTime;
+
+    @ApiModelProperty(value = "咨询结束时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date endTime;
+
+    @ApiModelProperty(value = "订单状态, 0:待支付,1.待接单 2:进行中, 3:已完成, 4:已取消,5.已退款")
+    private Integer orderStatus;
+
+    @ApiModelProperty(value = "支付状态, 0:未支付, 1:已支付")
+    private Integer paymentStatus;
+
+    @ApiModelProperty(value = "下单时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date orderTime;
+
+    @ApiModelProperty(value = "支付时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date paymentTime;
+
+    @ApiModelProperty(value = "订单有效期")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date validityPeriod;
+
+    @ApiModelProperty(value = "用户评分, 1-5星")
+    private Integer rating;
+
+    @ApiModelProperty(value = "用户评价内容")
+    private String comment;
+
+    @ApiModelProperty(value = "创建时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date createdTime;
+
+    // ========== 律师信息 ==========
+
+    @ApiModelProperty(value = "律师姓名")
+    private String lawyerName;
+
+    @ApiModelProperty(value = "律师手机号")
+    private String lawyerPhone;
+
+    @ApiModelProperty(value = "律师邮箱")
+    private String lawyerEmail;
+
+    @ApiModelProperty(value = "律师执业证号")
+    private String lawyerCertificateNo;
+
+    @ApiModelProperty(value = "所属律师事务所")
+    private String lawFirm;
+
+    @ApiModelProperty(value = "执业年限")
+    private Integer practiceYears;
+
+    @ApiModelProperty(value = "专业领域,多个用逗号分隔")
+    private String specialtyFields;
+
+    @ApiModelProperty(value = "资质认证状态, 0:未认证, 1:认证中, 2:已认证, 3:认证失败")
+    private Integer certificationStatus;
+
+    @ApiModelProperty(value = "服务评分, 0-5分")
+    private Double serviceScore;
+
+    @ApiModelProperty(value = "服务次数")
+    private Integer serviceCount;
+
+    @ApiModelProperty(value = "咨询收费标准(单位分/小时)")
+    private Integer lawyerConsultationFee;
+
+    @ApiModelProperty(value = "所属省份")
+    private String province;
+
+    @ApiModelProperty(value = "所属城市")
+    private String city;
+
+    @ApiModelProperty(value = "所属区县")
+    private String district;
+
+    @ApiModelProperty(value = "详细地址")
+    private String address;
+
+    @ApiModelProperty(value = "头像")
+    private String headImg;
+
+    @ApiModelProperty(value = "用户头像")
+    private String userImage;
+
+    @ApiModelProperty(value = "昵称")
+    private String nickName;
+
+    @ApiModelProperty(value = "个人简介(详细)")
+    private String personalIntroduction;
+
+    @ApiModelProperty(value = "法律场景列表")
+    private List<String> lawyerLegalProblemScenarioList;
+
+    @ApiModelProperty(value = "倒計時(秒),僅待支付訂單有效,30分鐘有效期")
+    private Long countdownSeconds;
+
+    @ApiModelProperty(value = "领域信息")
+    private String expertiseAreaInfo;
+
+    @ApiModelProperty(value = "客户端用户姓名")
+    private String clientUserName;
+
+    @ApiModelProperty(value = "客户端用户电话")
+    private String clientUserPhone;
+
+    @ApiModelProperty(value = "支付宝订单编号")
+    @TableField("alipay_no")
+    private String alipayNo;
+
+    @ApiModelProperty(value = "支付宝订单字符串")
+    @TableField("order_str")
+    private String orderStr;
+
+    @ApiModelProperty(value = "佣金比例")
+    private String commissionRate;
+
+    @ApiModelProperty(value = "佣金比例(前端使用)")
+    private String commissionRateStr;
+
+    @ApiModelProperty(value = "律师收益(订单金额-平台佣金)")
+    private  Integer lawyerEarnings;
+
+    @ApiModelProperty(value = "开始时间")
+    private String startTimeNew;
+
+    @ApiModelProperty(value = "结束时间")
+    private String endTimeNew;
+
+    @ApiModelProperty(value = "账号简介")
+    private String accountBlurb;
+
+    @ApiModelProperty(value = "律所名称")
+    private String firmName;
+
+    @ApiModelProperty(value = "律师接单时间")
+    @TableField("accept_orders_time")
+    private  Date acceptOrdersTime;
+
+    @ApiModelProperty(value = "拒绝接单原因")
+    @TableField("reason_order_refusal")
+    private  String reasonOrderRefusal;
+
+    @ApiModelProperty(value = "律师接单状态  0接单  1拒绝接单")
+    @TableField("accept_orders_status")
+    private  Integer acceptOrdersStatus;
+
+    @ApiModelProperty(value = "申请退款原因")
+    private  String applyRefundReason;
+
+    @ApiModelProperty(value = "申请退款状态,0-未申请,1-已申请,2-律师已拒绝,3-律师已同意")
+    @TableField("apply_refund_status")
+    private  String applyRefundStatus;
+
+    @ApiModelProperty(value = "是否被举报(1:被举报,2:未被举报)")
+    private  String isViolation;
+
+    @ApiModelProperty(value = "拒绝退款原因")
+    private  String rejectRefundReason;
+
+    @ApiModelProperty(value = "是否可申请退款(1-可申请,2-不可申请)")
+    private  String isCanApplyRefund;
+
+    @ApiModelProperty(value = "是否可申请举报(1-可举报,2-不可举报)")
+    private  String isCanApplyViolation;
+
+    @ApiModelProperty(value = "是否已经评价(1-已评价,2-未评价)")
+    private  String isHasCommon;
+
+    @ApiModelProperty(value = "用户申请退款时间")
+    private  Date applyRefundTime;
+
+    @ApiModelProperty(value = "申请退款处理时间")
+    private  Date applyRefundProcessTime;
+
+    @ApiModelProperty(value = "举报处理状态(0:待处理,1:已通过,2:已驳回)")
+    private  String processingStatus;
+
+    @ApiModelProperty(value = "举报处理时间")
+    private  Date processingTime;
+
+    @ApiModelProperty(value = "举报处理结果")
+    private  String reportResult;
+
+    @ApiModelProperty(value = "律师年限")
+    private  Date practiceStartDate;
+
+    @ApiModelProperty(value = "未读消息数量")
+    private  Long unreadMessage;
+
+    @ApiModelProperty(value = "律师接单状态")
+    private int orderReceivingStatus;
+
+    @ApiModelProperty(value = "申诉处理状态 0 审核中 1审核通过 2驳回")
+    private String commentStatus;
+
+
+    /**
+     * 获取执业年限(根据执业开始日期自动计算)
+     * 返回当前时间减去执业开始时间的年数
+     *
+     * @return 执业年限(年)
+     */
+    public Integer getPracticeYears() {
+        if (practiceStartDate == null) {
+            return null;
+        }
+        try {
+            // 将 Date 转换为 LocalDate
+            LocalDate startDate = practiceStartDate.toInstant()
+                    .atZone(ZoneId.systemDefault())
+                    .toLocalDate();
+            LocalDate currentDate = LocalDate.now();
+
+            // 计算年数差
+            long years = ChronoUnit.YEARS.between(startDate, currentDate);
+            return (int) Math.max(0, years); // 确保不为负数
+        } catch (Exception e) {
+            return null;
+        }
+    }
+
+    @ApiModelProperty(value = "支付方式 1支付宝 2微信")
+    @TableField("pay_type")
+    private  String payType;
+
+}
+

+ 39 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/SportsEquipmentFacilityCategoryVo.java

@@ -0,0 +1,39 @@
+package shop.alien.entity.store.vo;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 运动器材设施分类汇总视图对象
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Data
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@ApiModel(value = "SportsEquipmentFacilityCategoryVo对象", description = "运动器材设施分类汇总视图对象")
+public class SportsEquipmentFacilityCategoryVo implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "设施分类(1:有氧区, 2:力量区, 3:单功能机械区)")
+    private Integer facilityCategory;
+
+    @ApiModelProperty(value = "设施分类名称")
+    private String facilityCategoryName;
+
+    @ApiModelProperty(value = "该分类下的设备数量")
+    private Integer facilityCount;
+
+    @ApiModelProperty(value = "该分类下的图片列表")
+    private List<String> imageList;
+
+    @ApiModelProperty(value = "该分类下的设备列表")
+    private List<SportsEquipmentFacilityVo> facilityList;
+}
+

+ 61 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/SportsEquipmentFacilityVo.java

@@ -0,0 +1,61 @@
+package shop.alien.entity.store.vo;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 运动器材设施视图对象
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Data
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@ApiModel(value = "SportsEquipmentFacilityVo对象", description = "运动器材设施视图对象")
+public class SportsEquipmentFacilityVo implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "主键")
+    private Integer id;
+
+    @ApiModelProperty(value = "门店ID")
+    private Integer storeId;
+
+    @ApiModelProperty(value = "设施分类(1:有氧区, 2:力量区, 3:单功能机械区)")
+    private Integer facilityCategory;
+
+    @ApiModelProperty(value = "设施分类名称")
+    private String facilityCategoryName;
+
+    @ApiModelProperty(value = "设施名称")
+    private String facilityName;
+
+    @ApiModelProperty(value = "数量")
+    private Integer quantity;
+
+    @ApiModelProperty(value = "品牌")
+    private String brand;
+
+    @ApiModelProperty(value = "描述")
+    private String description;
+
+    @ApiModelProperty(value = "是否显示在店铺详情(0:隐藏, 1:显示)")
+    private Integer displayInStoreDetail;
+
+    @ApiModelProperty(value = "是否显示在店铺详情文本")
+    private String displayInStoreDetailText;
+
+    @ApiModelProperty(value = "收费类型0-免费,1-收费")
+    private Integer billingType;
+
+    @ApiModelProperty(value = "图片列表")
+    private List<String> imageList;
+}
+

+ 53 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/StoreAlbumDetailVo.java

@@ -0,0 +1,53 @@
+package shop.alien.entity.store.vo;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * 官方相册详情VO(包含相册名称、图片列表和数量)
+ *
+ * @author zhangchen
+ * @since 2025-01-16
+ */
+@Data
+@JsonInclude
+@ApiModel(value = "StoreAlbumDetailVo", description = "官方相册详情VO")
+public class StoreAlbumDetailVo {
+
+    @ApiModelProperty(value = "相册名称")
+    private String albumName;
+
+    @ApiModelProperty(value = "该相册下的图片数量")
+    private Integer imgCount;
+
+    @ApiModelProperty(value = "该相册下的图片列表")
+    private List<StoreImgInfo> imgList;
+
+    /**
+     * 图片信息
+     */
+    @Data
+    @JsonInclude
+    @ApiModel(value = "StoreImgInfo", description = "图片信息")
+    public static class StoreImgInfo {
+        @ApiModelProperty(value = "图片ID")
+        private Integer id;
+
+        @ApiModelProperty(value = "图片链接")
+        private String imgUrl;
+
+        @ApiModelProperty(value = "图片描述")
+        private String imgDescription;
+
+        @ApiModelProperty(value = "图片排序")
+        private Integer imgSort;
+
+        @ApiModelProperty(value = "图片类型")
+        private Integer imgType;
+    }
+}
+

+ 25 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/StoreAlbumNameVo.java

@@ -0,0 +1,25 @@
+package shop.alien.entity.store.vo;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+/**
+ * 官方相册名称VO
+ *
+ * @author zhangchen
+ * @since 2025-01-16
+ */
+@Data
+@JsonInclude
+@ApiModel(value = "StoreAlbumNameVo", description = "官方相册名称VO")
+public class StoreAlbumNameVo {
+
+    @ApiModelProperty(value = "相册名称")
+    private String albumName;
+
+    @ApiModelProperty(value = "该相册下的图片数量")
+    private Integer imgCount;
+}
+

+ 4 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/StoreCommentVo.java

@@ -4,6 +4,7 @@ import com.fasterxml.jackson.annotation.JsonInclude;
 import io.swagger.annotations.ApiModelProperty;
 import lombok.Data;
 import shop.alien.entity.store.StoreComment;
+import shop.alien.entity.store.StoreImg;
 
 import java.util.List;
 
@@ -65,4 +66,7 @@ public class StoreCommentVo extends StoreComment {
 
     @ApiModelProperty(value = "门店名称")
     private String  storeName;
+
+    @ApiModelProperty(value = "图片地址")
+    private List<StoreImg> imgList;
 }

+ 33 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/StoreInfoVo.java

@@ -1,5 +1,6 @@
 package shop.alien.entity.store.vo;
 
+import com.alibaba.fastjson2.JSONObject;
 import com.fasterxml.jackson.annotation.JsonFormat;
 import com.fasterxml.jackson.annotation.JsonInclude;
 import io.swagger.annotations.ApiModel;
@@ -214,4 +215,36 @@ public class StoreInfoVo extends StoreInfo {
 
     @ApiModelProperty(value = "动态数量")
     private Integer dynamicsNum;
+
+    @ApiModelProperty(value = "推荐列表距离(米)")
+    private String distance3;
+
+    @ApiModelProperty(value = "店铺头图(多图列表)")
+    private List<String> storeAlbumUrlList;
+
+    @ApiModelProperty(value = "分类id(词典表 键为 business_classify)(多个ID用逗号拼接)")
+    private String businessClassify;
+
+    @ApiModelProperty(value = "分类名称")
+    private String businessClassifyName;
+
+    @ApiModelProperty(value = "分类id(词典表 键为 business_classify)(多个ID用逗号拼接)")
+    private List<String> businessClassifyList;
+
+    @ApiModelProperty(value = "分类名称")
+    private List<String> businessClassifyNameList;
+
+    @ApiModelProperty(value = "是否提供餐食")
+    private Integer  mealsFlag;
+
+    @ApiModelProperty(value = "身份证正面")
+    private JSONObject idcardFace;
+    @ApiModelProperty(value = "身份证反面")
+    private JSONObject idcardBack;
+    @ApiModelProperty(value = "经营许可证")
+    private JSONObject jyxkz;
+    @ApiModelProperty(value = "食品经营许可证")
+    private JSONObject foodLicence;
+    @ApiModelProperty(value = "娱乐经营许可证")
+    private JSONObject entertainmentLicence;
 }

+ 6 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/StoreMainInfoVo.java

@@ -44,6 +44,12 @@ public class StoreMainInfoVo extends StoreInfo {
     @ApiModelProperty(value = "菜单")
     List<StoreMenuVo> menuUrl;
 
+    @ApiModelProperty(value = "推荐酒水")
+    List<StoreMenuVo> recommendBeverageUrl;
+
+    @ApiModelProperty(value = "酒水单")
+    List<StoreMenuVo> beverageUrl;
+
     @ApiModelProperty(value = "门店标签")
     StoreLabel storeLabel;
 

+ 29 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/StoreOfficialAlbumImgVo.java

@@ -0,0 +1,29 @@
+package shop.alien.entity.store.vo;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import shop.alien.entity.store.StoreImg;
+
+import java.util.List;
+
+/**
+ * 官方相册图片列表返回VO
+ *
+ * @author zhangchen
+ * @since 2025-01-16
+ */
+@Data
+@JsonInclude
+@ApiModel(value = "StoreOfficialAlbumImgVo", description = "官方相册图片列表返回VO")
+public class StoreOfficialAlbumImgVo {
+
+    @ApiModelProperty(value = "图片列表")
+    private List<StoreImg> imgList;
+
+    @ApiModelProperty(value = "图片总数")
+    private Integer totalCount;
+
+}
+

+ 34 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/StorePersonnelVo.java

@@ -0,0 +1,34 @@
+package shop.alien.entity.store.vo;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import shop.alien.entity.store.StorePersonnel;
+
+import java.util.List;
+
+/**
+ * 店铺人员视图对象
+ *
+ * @author system
+ * @since 2025-01-15
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+@JsonInclude
+public class StorePersonnelVo extends StorePersonnel {
+
+    @ApiModelProperty(value = "图片排序")
+    private Integer imgSort;
+
+    @ApiModelProperty(value = "图片链接")
+    private String imgUrl;
+
+    @ApiModelProperty(value = "图片描述")
+    private String imgDescription;
+
+    @ApiModelProperty(value = "人员排序列表")
+    private List<StorePersonnelVo> sortList;
+}
+

+ 32 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/StoreThreeLevelStructureVo.java

@@ -0,0 +1,32 @@
+package shop.alien.entity.store.vo;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import shop.alien.entity.store.StoreDictionary;
+
+import java.util.List;
+
+/**
+ * 门店三级分类结构VO
+ *
+ * @author ssk
+ * @since 2025-01-XX
+ */
+@Data
+@JsonInclude
+@ApiModel(value = "StoreThreeLevelStructureVo对象", description = "门店三级分类结构")
+public class StoreThreeLevelStructureVo {
+
+    @ApiModelProperty(value = "门店ID")
+    private Integer storeId;
+
+    @ApiModelProperty(value = "门店名称")
+    private String storeName;
+
+    @ApiModelProperty(value = "三级分类树状结构")
+    private List<StoreDictionary> threeLevelStructure;
+
+}
+

+ 17 - 0
alien-entity/src/main/java/shop/alien/mapper/BathFacilityServiceMapper.java

@@ -0,0 +1,17 @@
+package shop.alien.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+import shop.alien.entity.store.BathFacilityService;
+
+/**
+ * 洗浴设施及服务 Mapper 接口
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Mapper
+public interface BathFacilityServiceMapper extends BaseMapper<BathFacilityService> {
+
+}
+

+ 177 - 0
alien-entity/src/main/java/shop/alien/mapper/LifeCouponMapper.java

@@ -80,4 +80,181 @@ public interface LifeCouponMapper extends BaseMapper<LifeCoupon> {
             @Param("buyCount") int buyCount,
             @Param("soldOutStatus") int soldOutStatus
     );
+
+
+    /**
+     * 查询代金券详情(连表查询商铺信息)
+     *
+     * @param id 代金券ID
+     * @return LifeCouponVo
+     */
+    @Select("SELECT " +
+            "lc.id, " +
+            "lc.data_type, " +
+            "lc.buy_use_start_time, " +
+            "lc.buy_use_end_time, " +
+            "lc.use_festival, " +
+            "lc.coupon_code, " +
+            "lc.name, " +
+            "lc.store_id, " +
+            "lc.price, " +
+            "lc.offprice, " +
+            "lc.expiration_date, " +
+            "lc.start_date, " +
+            "lc.end_date, " +
+            "lc.unused_date, " +
+            "lc.single_qty, " +
+            "lc.buy_limit, " +
+            "lc.stock_qty, " +
+            "lc.use_rule, " +
+            "lc.min_use_peoples, " +
+            "lc.max_use_peoples, " +
+            "lc.dietary_preferences, " +
+            "lc.cooking_methods, " +
+            "lc.applicable_rule, " +
+            "lc.status, " +
+            "lc.image_path, " +
+            "lc.store_service, " +
+            "lc.fapiao_info, " +
+            "lc.type, " +
+            "lc.approval_comments, " +
+            "lc.open_price_protection, " +
+            "lc.price_below, " +
+            "lc.coupon_comp, " +
+            "lc.coupon_comp_date, " +
+            "lc.rate_score, " +
+            "lc.discount_tag, " +
+            "lc.discount_tag_name, " +
+            "lc.delete_flag, " +
+            "lc.status_delete_flag, " +
+            "lc.created_time, " +
+            "lc.created_user_id, " +
+            "lc.updated_time, " +
+            "lc.updated_user_id, " +
+            "lc.store_type, " +
+            "lc.sale_time_str_type, " +
+            "lc.sale_time_end_type, " +
+            "lc.purchase_limit_code, " +
+            "lc.original_price, " +
+            "lc.discounted_price, " +
+            "lc.business_types, " +
+            "lc.housing_type, " +
+            "lc.room_type, " +
+            "lc.room_type_code, " +
+            "lc.kitchen_xq, " +
+            "lc.bathroom_xq, " +
+            "lc.living_room, " +
+            "lc.location_of_the_property, " +
+            "lc.total_area, " +
+            "lc.entertainment_facilities_code, " +
+            "lc.entertainment_facilities, " +
+            "lc.convenience_facilities_code, " +
+            "lc.convenience_facilities, " +
+            "lc.bathroom_facilities_code, " +
+            "lc.bathroom_facilities, " +
+            "lc.bath_products_code, " +
+            "lc.bath_products, " +
+            "lc.service_code, " +
+            "lc.service, " +
+            "lc.safety_code, " +
+            "lc.safety, " +
+            "lc.reservation_instructions, " +
+            "lc.in_time, " +
+            "lc.out_time, " +
+            "lc.deposit_flag, " +
+            "lc.deposit_amount, " +
+            "lc.children_flag, " +
+            "lc.children_description, " +
+            "lc.pet_flag, " +
+            "lc.extra_bed_flag, " +
+            "lc.bed_price, " +
+            "lc.invoice_info, " +
+            "lc.invoice_instructions, " +
+            "lc.cancellation_policy_type, " +
+            "lc.cancellation_policy, " +
+            "lc.Cancellation_instructions, " +
+            "lc.insured_price, " +
+            "lc.validity_period_type, " +
+            "lc.box_type_code, " +
+            "lc.box_type, " +
+            "lc.snacks_code, " +
+            "lc.snacks, " +
+            "lc.available_duration, " +
+            "lc.write_off_type, " +
+            "lc.write_off, " +
+            "lc.refund_rules_type, " +
+            "lc.refund_rules, " +
+            "lc.refund_instructions, " +
+            "lc.edit_rules_code, " +
+            "lc.edit_rules, " +
+            "lc.edit_instructions, " +
+            "lc.tangchi_code, " +
+            "lc.tangchi, " +
+            "lc.sweat_sauna_code, " +
+            "lc.sweat_sauna, " +
+            "lc.pictures_and_text, " +
+            "lc.supplement, " +
+            "lc.unavailable_date_type, " +
+            "lc.reservation_rules, " +
+            "lc.applicable_num, " +
+            "lc.other_rules, " +
+            "lc.sales_type, " +
+            "lc.massage_technique_code, " +
+            "lc.massage_technique, " +
+            "lc.massage_area_code, " +
+            "lc.massage_area, " +
+            "lc.massage_tools_code, " +
+            "lc.massage_tools, " +
+            "lc.compress_tools_code, " +
+            "lc.compress_tools, " +
+            "lc.class_format_code, " +
+            "lc.class_format, " +
+            "lc.class_duration_code, " +
+            "lc.class_duration, " +
+            "lc.class_num_code, " +
+            "lc.class_num, " +
+            "lc.applicable_parts_code, " +
+            "lc.applicable_parts, " +
+            "lc.hygiene_code, " +
+            "lc.hygiene, " +
+            "lc.foundation_code, " +
+            "lc.foundation, " +
+            "lc.bathroom_code, " +
+            "lc.bathroom, " +
+            "lc.kitchen_code, " +
+            "lc.kitchen, " +
+            "lc.validity_period_yh_type, " +
+            "lc.validity_period_yh, " +
+            "lc.course_validity_period_code, " +
+            "lc.course_validity_period, " +
+            "lc.uploaded_picList, " +
+            "lc.validity_period, " +
+            "lc.stacking_type, " +
+            "lc.general_type, " +
+            "lc.single_can_use, " +
+            "lc.apply_type, " +
+            "lc.apply_desc, " +
+            "lc.expiration_type, " +
+            "lc.unused_type, " +
+            "lc.house_type, " +
+            "lc.house_type_b, " +
+            "lc.house_type_l, " +
+            "lc.house_type_k, " +
+            "lc.house_type_t, " +
+            "lc.sale_time_str, " +
+            "lc.sale_time_end, " +
+            "lc.unavai_lable_date, " +
+            "lc.room_type_str, " +
+            "lc.time_range, " +
+            "lc.cancellation_policy_str, " +
+            "lc.validity_periodyh_str, " +
+            "si.store_name, " +
+            "si.store_address, " +
+            "si.store_tel AS phone, " +
+            "si.business_status, " +
+            "si.store_status " +
+            "FROM life_coupon lc " +
+            "LEFT JOIN store_info si ON si.id = lc.store_id AND si.delete_flag = 0 " +
+            "WHERE lc.id = #{id} AND lc.delete_flag = 0")
+    LifeCouponVo getNewCouponDetail(@Param("id") String id);
 }

+ 17 - 0
alien-entity/src/main/java/shop/alien/mapper/SportsEquipmentFacilityMapper.java

@@ -0,0 +1,17 @@
+package shop.alien.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+import shop.alien.entity.store.SportsEquipmentFacility;
+
+/**
+ * 运动器材设施 Mapper 接口
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Mapper
+public interface SportsEquipmentFacilityMapper extends BaseMapper<SportsEquipmentFacility> {
+
+}
+

+ 13 - 0
alien-entity/src/main/java/shop/alien/mapper/StoreCommentMapper.java

@@ -185,4 +185,17 @@ WHERE
             "AND luo.user_id = #{userId} " +
             "AND luo.id IN (SELECT business_id FROM store_comment WHERE business_type = 5 AND user_id = #{userId} AND delete_flag = 0)")
     IPage<LifeUserOrderCommentVo> getCommentOrderYPJPage(IPage<LifeUserOrderCommentVo> page, @Param("userId") String userId);
+
+    @Select("SELECT\n" +
+            "\ts.*,\n" +
+            "\tlu.user_name, \n" +
+            "\tlu.user_image \n" +
+            "FROM\n" +
+            "\tstore_comment s\n" +
+            "\tLEFT JOIN life_user lu ON s.user_id = lu.id \n" +
+            "WHERE\n" +
+            "\ts.business_type = 5 \n" +
+            "\tAND s.store_id = #{storeId}\n" +
+            "\tLIMIT 1")
+    StoreCommentVo getCommentOneInfo(@Param("storeId") int storeId);
 }

+ 26 - 0
alien-entity/src/main/java/shop/alien/mapper/StoreInfoMapper.java

@@ -130,4 +130,30 @@ public interface StoreInfoMapper extends BaseMapper<StoreInfo> {
             "from store_info where id = #{storeId}"
             )
     Double getStoreDistance(@Param("position") String position,@Param("storeId") Integer storeId);
+
+
+    /**
+     * 更多推荐(用户端)
+     *
+     * @param queryWrapper 查询条件
+     * @param position 经纬度位置(格式:经度,纬度)
+     * @return List<StoreInfoVo>
+     */
+    @Select("select a.*,b.name store_contact, b.phone store_phone, b.id_card idCard, b.password, c.dict_detail store_status_str, d.dict_detail business_status_str, e.dict_detail store_type_str,  " +
+            "( " +
+            " select ifnull(round(avg(score), 1), 0) " +
+            " from store_evaluation eval " +
+            " where eval.store_id = a.id and eval.delete_flag = 0 " +
+            ") score, " +
+            "ROUND(ST_Distance_Sphere(ST_GeomFromText(CONCAT('POINT(', REPLACE(#{position}, ',', ' '), ')' )), ST_GeomFromText(CONCAT('POINT(', REPLACE(a.store_position, ',', ' '), ')' )))) distance3 " +
+            "from store_info a " +
+            "left join store_user b on a.id = b.store_id and a.delete_flag = 0 and b.delete_flag = 0 " +
+            "left join store_dictionary c on a.store_status = c.dict_id and c.type_name = 'storeState' and c.delete_flag = 0 " +
+            "left join store_dictionary d on a.business_status = d.dict_id and d.type_name = 'businessStatus' and d.delete_flag = 0 " +
+            "left join store_dictionary e on e.type_name = 'storeType' and e.dict_id = a.store_type and e.delete_flag = 0 " +
+            "${ew.customSqlSegment} " +
+            "and a.store_position is not null and a.store_position != '' " +
+            "and ROUND(ST_Distance_Sphere(ST_GeomFromText(CONCAT('POINT(', REPLACE(#{position}, ',', ' '), ')' )), ST_GeomFromText(CONCAT('POINT(', REPLACE(a.store_position, ',', ' '), ')' ))) / 1000, 2) <= 1 " +
+            "order by distance3 asc limit 20")
+    List<StoreInfoVo> getMoreRecommendedStores(@Param(Constants.WRAPPER) QueryWrapper<StoreInfoVo> queryWrapper, @Param("position") String position);
 }

+ 54 - 0
alien-entity/src/main/java/shop/alien/mapper/StoreMenuMapper.java

@@ -28,6 +28,21 @@ public interface StoreMenuMapper extends BaseMapper<StoreMenu> {
     //"图片类型, 0:其他, 1:入口图, 2:相册, 3:菜品, 4:环境, 5:价目表, 6:推荐菜, 7:菜单, 8:用户评论, 9:商家申诉, 10:商家头像, 11:店铺轮播图"
     @Select("select a.*, b.img_url, b.img_sort, b.img_description from store_menu a " +
             "left join store_img b on a.img_id = b.id where a.delete_flag = 0 and b.delete_flag = 0 " +
+            "and a.store_id = #{storeId} " +
+            "and (if(#{dishType} is null, 1 = 1, a.dish_type = #{dishType})) " +
+            "and (if(#{dishMenuType} is null, 1 = 1, a.dish_menu_type = #{dishMenuType}))")
+    List<StoreMenuVo> getStoreMenuList(@Param("storeId") Integer storeId, @Param("dishType") Integer dishType, @Param("dishMenuType") Integer dishMenuType);
+
+    /**
+     * 获取菜单
+     *
+     * @param storeId  门店id
+     * @param dishType 菜品类型, 0:菜单, 1:推荐
+     * @return List<StoreMenuVo>
+     */
+    //"图片类型, 0:其他, 1:入口图, 2:相册, 3:菜品, 4:环境, 5:价目表, 6:推荐菜, 7:菜单, 8:用户评论, 9:商家申诉, 10:商家头像, 11:店铺轮播图"
+    @Select("select a.*, b.img_url, b.img_sort, b.img_description from store_menu a " +
+            "left join store_img b on a.img_id = b.id where a.delete_flag = 0 and b.delete_flag = 0 " +
             "and a.store_id = #{storeId} AND (if(#{dishType} is null, 1 = 1, a.dish_type = #{dishType}))")
     List<StoreMenuVo> getStoreMenuList(@Param("storeId") Integer storeId, @Param("dishType") Integer dishType);
 
@@ -35,4 +50,43 @@ public interface StoreMenuMapper extends BaseMapper<StoreMenu> {
             "left join store_img b on a.img_id = b.id where a.delete_flag = 0 and b.delete_flag = 0 " +
             "and a.id = #{id}")
     StoreMenuVo getMenuInfo(@Param("id") Integer id);
+
+    /**
+     * 获取菜单(客户端)
+     * <p>
+     * 根据门店ID查询菜单列表,支持按菜品类型和菜单类型筛选。
+     * 查询未删除的菜单及其关联的图片信息。
+     * </p>
+     *
+     * @param storeId      门店ID,必填
+     * @param dishType     菜品类型,可选,0:非推荐, 1:推荐。当为null时,不限制菜品类型
+     * @param dishMenuType 菜单类型,可选,1-菜单,2-酒水。当为null时,不限制菜单类型
+     * @return 菜单列表,包含菜单信息和关联的图片信息
+     */
+    @Select("<script>" +
+            "SELECT " +
+            "    a.*, " +
+            "    b.img_url, " +
+            "    b.img_sort, " +
+            "    b.img_description " +
+            "FROM store_menu a " +
+            "LEFT JOIN store_img b ON a.img_id = b.id " +
+            "WHERE a.delete_flag = 0 " +
+            "    AND (b.delete_flag = 0 OR b.delete_flag IS NULL) " +
+            "    AND a.store_id = #{storeId} " +
+            "<if test='dishType != null'>" +
+            "    AND a.dish_type = #{dishType} " +
+            "</if>" +
+            "<if test='dishMenuType != null'>" +
+            "    AND a.dish_menu_type = #{dishMenuType} " +
+            "</if>" +
+            "</script>")
+    List<StoreMenuVo> getClientMenuByStoreId(@Param("storeId") Integer storeId,
+                                             @Param("dishType") Integer dishType,
+                                             @Param("dishMenuType") Integer dishMenuType);
+
+    @Select("select a.*, b.img_url, b.img_sort, b.img_description from store_menu a " +
+            "left join store_img b on a.img_id = b.id where a.delete_flag = 0 and b.delete_flag = 0 " +
+            "and a.id = #{id}")
+    StoreMenuVo getClientMenuInfoById(@Param("id") Integer id);
 }

+ 44 - 0
alien-entity/src/main/java/shop/alien/mapper/StorePersonnelMapper.java

@@ -0,0 +1,44 @@
+package shop.alien.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+import shop.alien.entity.store.StorePersonnel;
+import shop.alien.entity.store.vo.StorePersonnelVo;
+
+import java.util.List;
+
+/**
+ * 店铺人员 Mapper 接口
+ *
+ * @author system
+ * @since 2025-01-15
+ */
+@Mapper
+public interface StorePersonnelMapper extends BaseMapper<StorePersonnel> {
+
+    /**
+     * 获取店铺人员列表
+     *
+     * @param storeId 门店id
+     * @return List<StorePersonnelVo>
+     */
+    @Select("select a.*, b.img_url, b.img_sort, b.img_description from store_personnel a " +
+            "left join store_img b on a.img_id = b.id where a.delete_flag = 0 and b.delete_flag = 0 " +
+            "and a.store_id = #{storeId} " +
+            "order by a.sort asc")
+    List<StorePersonnelVo> getStorePersonnelList(@Param("storeId") Integer storeId);
+
+    /**
+     * 获取人员详情
+     *
+     * @param id 人员id
+     * @return StorePersonnelVo
+     */
+    @Select("select a.*, b.img_url, b.img_sort, b.img_description from store_personnel a " +
+            "left join store_img b on a.img_id = b.id where a.delete_flag = 0 and b.delete_flag = 0 " +
+            "and a.id = #{id}")
+    StorePersonnelVo getPersonnelInfo(@Param("id") Integer id);
+}
+

+ 40 - 13
alien-entity/src/main/java/shop/alien/mapper/TagsMainMapper.java

@@ -5,6 +5,7 @@ import org.apache.ibatis.annotations.Insert;
 import org.apache.ibatis.annotations.Mapper;
 import org.apache.ibatis.annotations.Select;
 import shop.alien.entity.store.TagsMain;
+import shop.alien.entity.store.vo.StoreCommentVo;
 import shop.alien.entity.store.vo.TagsMainVo;
 
 import java.util.List;
@@ -40,21 +41,47 @@ public interface TagsMainMapper extends BaseMapper<TagsMain> {
     })
     int insertBatchTagsMain(List<TagsMain> tagsMainList);
 
+
+    @Select("SELECT\n" +
+            "  main.id,\n" +
+            "  main.tag_name,\n" +
+            "  main.store_id,\n" +
+            "  main.tag_type,\n" +
+            "  IFNULL(GROUP_CONCAT(DISTINCT synonym.comment_id ORDER BY synonym.comment_id SEPARATOR ','), '') AS evaluate_ids,\n" +
+            "  IFNULL(COUNT(DISTINCT synonym.comment_id), 0) AS evaluateNumber\n" +
+            "FROM\n" +
+            "  tags_main main\n" +
+            "LEFT JOIN tags_synonym synonym \n" +
+            "  ON main.id = synonym.main_tag_id\n" +
+            "  AND synonym.delete_flag = 0  \n" +
+            "WHERE\n" +
+            "  main.store_id = #{storeId}\n" +
+            "  AND main.delete_flag = 0\n" +
+            "GROUP BY\n" +
+            "  main.id, main.tag_name, main.store_id, main.tag_type  \n" +
+            "ORDER BY\n" +
+            "  main.id;")
+    List<TagsMainVo> getStoreEvaluateTags(Integer storeId);
+
+
     @Select("SELECT\n" +
-            "    main.id,\n" +
-            "    main.tag_name,\n" +
-            "    main.store_id,\n" +
-            "    main.tag_type,\n" +
-            "    COUNT(DISTINCT synonym.comment_id) AS evaluateNumber\n" +
+            "  ts.comment_id,\n" +
+            "  MIN(ts.id) AS tag_id,  \n" +
+            "  ts.main_tag_id,\n" +
+            "  GROUP_CONCAT(DISTINCT ts.synonym_tag SEPARATOR ',') AS synonym_tags,  \n" +
+            "  MIN(ts.delete_flag) AS tag_delete_flag,\n" +
+            "  MAX(ts.created_time) AS tag_created_time,\n" +
+            "  sc.*\n" +
             "FROM\n" +
-            "    tags_main main\n" +
-            "    LEFT JOIN tags_synonym synonym \n" +
-            "        ON main.id = synonym.main_tag_id\n" +
-            "        AND synonym.delete_flag = 0  \n" +
+            "  tags_synonym ts\n" +
+            "LEFT JOIN store_comment sc \n" +
+            "  ON ts.comment_id = sc.id\n" +
             "WHERE\n" +
-            "    main.store_id = #{storeId} \n" +
-            "    AND main.delete_flag = 0\n" +
+            "  ts.main_tag_id = #{mainId}  \n" +
             "GROUP BY\n" +
-            "    main.id")
-    List<TagsMainVo> getStoreEvaluateTags(int storeId);
+            "  ts.comment_id, ts.main_tag_id,  \n" +
+            "  sc.id  \n" +
+            "ORDER BY\n" +
+            "  sc.created_time DESC; ")
+    List<StoreCommentVo> getstoreCommentList(Integer mainId);
 }

+ 13 - 0
alien-job/src/main/java/shop/alien/job/feign/AlienStoreFeign.java

@@ -3,7 +3,12 @@ package shop.alien.job.feign;
 import com.alibaba.fastjson.JSONObject;
 import org.springframework.cloud.openfeign.FeignClient;
 import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RequestParam;
+import shop.alien.entity.result.R;
+
+import java.util.Map;
 
 @FeignClient(url = "${feign.alienStore.url}", name = "alien-store")
 public interface AlienStoreFeign {
@@ -56,4 +61,12 @@ public interface AlienStoreFeign {
                                    @RequestParam(value = "refundReason") String refundReason,
                                    @RequestParam(value = "partialRefundCode") String partialRefundCode);
 
+    /**
+     * 退款接口
+     * @param params 退款参数(包含订单号、退款金额等)
+     * @return 退款结果
+     */
+    @RequestMapping("payment/refunds")
+    public R refunds(@RequestBody Map<String, String> params);
+
 }

+ 30 - 7
alien-job/src/main/java/shop/alien/job/store/LifeUserOrderJob.java

@@ -18,6 +18,7 @@ import shop.alien.util.common.UniqueRandomNumGenerator;
 import shop.alien.util.common.constant.CouponTypeEnum;
 import shop.alien.util.common.constant.DiscountCouponEnum;
 import shop.alien.util.common.constant.OrderStatusEnum;
+import shop.alien.util.common.constant.PaymentEnum;
 
 import java.math.BigDecimal;
 import java.text.ParseException;
@@ -212,15 +213,37 @@ public class LifeUserOrderJob {
                         // 统计退款金额
                         BigDecimal totalRefundAmount = refundList.stream().map(x -> x.getPrice()).reduce(BigDecimal.ZERO, BigDecimal::add);
                         // 判断本次是否是全退 执行退款
-                        String refundResult = null;
-                        if (refundList.size() == orderCouponMiddleList.size()) {
-                            // 全退
-                            refundResult = alienStoreFeign.processRefund(order.getOrderNo(), totalRefundAmount.toString(), "过期自动退","");
+                        String refundResult = "";
+                        String partialRefundCode = "";
+                        if (refundList.size() != orderCouponMiddleList.size()) {
+                            partialRefundCode = UniqueRandomNumGenerator.generateUniqueCode(12);
+                        }
+                        String payMethod = order.getPayMethod();
+                        String payType = "";
+                        Map<String, String> params = new HashMap<>();
+                        if("支付宝支付".equals(payMethod)) {
+                            payType = PaymentEnum.ALIPAY.getType();
+                            params.put("payType", payType);
+                            params.put("outTradeNo", order.getOrderNo());
+                            params.put("refundAmount", totalRefundAmount.toString());
+                            params.put("refundReason", "过期自动退");
+                            params.put("partialRefundCode",partialRefundCode);
                         } else {
-                            // TODO 本次部分退款
-                            String partialRefundCode = UniqueRandomNumGenerator.generateUniqueCode(12);
-                            refundResult = alienStoreFeign.processRefund(order.getOrderNo(), totalRefundAmount.toString(), "过期自动退",partialRefundCode);
+                            payType = PaymentEnum.WECHAT_PAY.getType();
+                            params.put("payType", payType);
+                            params.put("outTradeNo", order.getOrderNo());
+                            params.put("reason", "过期自动退");
+                            params.put("refundAmount", String.valueOf(totalRefundAmount.multiply(new BigDecimal(100)).longValue()));
+                            params.put("totalAmount", String.valueOf(new BigDecimal(order.getFinalPrice()).multiply(new BigDecimal(100)).longValue()));
+                        }
+                        // 调用支付策略
+                        try {
+                            refundResult = alienStoreFeign.refunds(params).getData().toString();
+                        } catch (Exception e) {
+                            log.error(String.format("LifeUserOrderJob requestRefund error, payType: %s, params: %s, error: %s", payType, params, e.getMessage()));
                         }
+
+
                         if ("调用成功".equals(refundResult)) {
                             //  1.更新中间表状态
                             int updateNum = orderCouponMiddleMapper.update(null, new LambdaUpdateWrapper<OrderCouponMiddle>()

+ 279 - 0
alien-lawyer/src/main/java/shop/alien/lawyer/controller/OrderReviewController.java

@@ -0,0 +1,279 @@
+package shop.alien.lawyer.controller;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import io.swagger.annotations.*;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.*;
+import shop.alien.entity.result.R;
+import shop.alien.entity.store.OrderReview;
+import shop.alien.entity.store.dto.OrderReviewDto;
+import shop.alien.entity.store.vo.LawyerReviewStatisticsVo;
+import shop.alien.entity.store.vo.OrderReviewDetailVo;
+import shop.alien.entity.store.vo.OrderReviewVo;
+import shop.alien.entity.store.vo.PendingReviewVo;
+import shop.alien.lawyer.service.OrderReviewService;
+
+/**
+ * 订单评价 前端控制器
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Slf4j
+@Api(tags = {"律师平台-订单评价"})
+@ApiSort(15)
+@CrossOrigin
+@RestController
+@RequestMapping("/lawyer/orderReview")
+@RequiredArgsConstructor
+public class OrderReviewController {
+
+    private final OrderReviewService orderReviewService;
+
+    @ApiOperation("创建订单评价(只有订单用户才能评价)")
+    @ApiOperationSupport(order = 1)
+    @PostMapping("/create")
+    public R<OrderReview> createReview(@RequestBody OrderReviewDto reviewDto) {
+        log.info("OrderReviewController.createReview?reviewDto={}, ", reviewDto);
+        if (reviewDto.getUserId() == null) {
+            return R.fail("用户未登录");
+        }
+        return orderReviewService.createReview(reviewDto);
+    }
+
+    @ApiOperation("获取评价详情(包含评论和回复)")
+    @ApiOperationSupport(order = 3)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "reviewId", value = "评价ID", dataType = "int", paramType = "query", required = true),
+            @ApiImplicitParam(name = "currentUserId", value = "当前用户ID(用于判断是否已点赞)", dataType = "int", paramType = "query")
+    })
+    @GetMapping("/detail/reviewId")
+    public R<OrderReviewDetailVo> getReviewDetail(
+            @RequestParam Integer reviewId,
+            @RequestParam(required = false) Integer currentUserId) {
+        log.info("OrderReviewController.getReviewDetail?reviewId={}, currentUserId={}", reviewId, currentUserId);
+        if (reviewId == null) {
+            return R.fail("评价ID不能为空");
+        }
+        return orderReviewService.getReviewDetail(reviewId, currentUserId);
+    }
+
+    @ApiOperation("点赞评价")
+    @ApiOperationSupport(order = 8)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "reviewId", value = "评价ID", dataType = "int", paramType = "query", required = true),
+            @ApiImplicitParam(name = "userId", value = "用户ID", dataType = "int", paramType = "query", required = true)
+    })
+    @PostMapping("/like")
+    public R<Boolean> likeReview(@RequestBody OrderReview orderReview) {
+        Integer reviewId = orderReview.getId();
+        Integer userId = orderReview.getUserId();
+        log.info("OrderReviewController.likeReview?reviewId={}, userId={}", reviewId, userId);
+        if (userId == null) {
+            return R.fail("用户未登录");
+        }
+        if (reviewId == null) {
+            return R.fail("评价ID不能为空");
+        }
+        return orderReviewService.likeReview(reviewId, userId);
+    }
+
+    @ApiOperation("取消点赞评价")
+    @ApiOperationSupport(order = 9)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "reviewId", value = "评价ID", dataType = "int", paramType = "query", required = true),
+            @ApiImplicitParam(name = "userId", value = "用户ID", dataType = "int", paramType = "query", required = true)
+    })
+    @PostMapping("/cancelLike")
+    public R<Boolean> cancelLikeReview(@RequestBody OrderReview orderReview) {
+        Integer reviewId = orderReview.getId();
+        Integer userId = orderReview.getUserId();
+        log.info("OrderReviewController.cancelLikeReview?reviewId={}, userId={}", reviewId, userId);
+        if (userId == null) {
+            return R.fail("用户未登录");
+        }
+        return orderReviewService.cancelLikeReview(reviewId, userId);
+    }
+
+    @ApiOperation("分页查询评价列表")
+    @ApiOperationSupport(order = 2)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "page", value = "页数(默认1)", dataType = "int", paramType = "query"),
+            @ApiImplicitParam(name = "size", value = "页容(默认10)", dataType = "int", paramType = "query"),
+            @ApiImplicitParam(name = "orderId", value = "订单ID", dataType = "int", paramType = "query"),
+            @ApiImplicitParam(name = "lawyerUserId", value = "律师用户ID", dataType = "int", paramType = "query"),
+            @ApiImplicitParam(name = "userId", value = "评价用户ID", dataType = "int", paramType = "query"),
+            @ApiImplicitParam(name = "currentUserId", value = "当前用户ID(用于判断是否已点赞)", dataType = "int", paramType = "query")
+    })
+    @GetMapping("/list")
+    public R<IPage<OrderReviewVo>> getReviewList(
+            @RequestParam(defaultValue = "1") int page,
+            @RequestParam(defaultValue = "10") int size,
+            @RequestParam(required = false) Integer orderId,
+            @RequestParam(required = false) Integer lawyerUserId,
+            @RequestParam(required = false) Integer userId,
+            @RequestParam(required = false) Integer currentUserId) {
+        log.info("OrderReviewController.getReviewList?page={}, size={}, orderId={}, lawyerUserId={}, userId={}, currentUserId={}",
+                page, size, orderId, lawyerUserId, userId, currentUserId);
+        return orderReviewService.getReviewList(page, size, orderId, lawyerUserId, userId, currentUserId);
+    }
+
+    @ApiOperation("用户删除评价(只能删除自己的评价,删除评价时,会级联删除该评价下的所有评论和回复)")
+    @ApiOperationSupport(order = 4)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "reviewId", value = "评价ID", dataType = "int", paramType = "query", required = true),
+            @ApiImplicitParam(name = "userId", value = "用户ID(必填,只能删除自己的评价)", dataType = "int", paramType = "query", required = true)
+    })
+    @PostMapping("/delete/reviewId")
+    public R<Boolean> deleteReview(@RequestParam Integer reviewId,
+                                   @RequestParam Integer userId) {
+        log.info("OrderReviewController.deleteReview?reviewId={}, userId={}", reviewId, userId);
+        if (userId == null) {
+            return R.fail("用户ID不能为空");
+        }
+        return orderReviewService.deleteReview(reviewId, userId);
+    }
+
+    @ApiOperation("管理员删除评价(可以删除任何评价,删除评价时,会级联删除该评价下的所有评论和回复)")
+    @ApiOperationSupport(order = 5)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "reviewId", value = "评价ID", dataType = "int", paramType = "query", required = true)
+    })
+    @PostMapping("/admin/delete/reviewId")
+    public R<Boolean> deleteReviewByAdmin(@RequestParam Integer reviewId) {
+        log.info("OrderReviewController.deleteReviewByAdmin?reviewId={}", reviewId);
+        return orderReviewService.deleteReviewByAdmin(reviewId);
+    }
+
+    @ApiOperation("根据订单ID查询评价")
+    @ApiOperationSupport(order = 6)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "orderId", value = "订单ID", dataType = "int", paramType = "query", required = true),
+            @ApiImplicitParam(name = "currentUserId", value = "当前用户ID(用于判断是否已点赞)", dataType = "int", paramType = "query")
+    })
+    @GetMapping("/order/orderId")
+    public R<OrderReviewVo> getReviewByOrderId(
+            @RequestParam Integer orderId,
+            @RequestParam(required = false) Integer currentUserId) {
+        log.info("OrderReviewController.getReviewByOrderId?orderId={}, currentUserId={}", orderId, currentUserId);
+        return orderReviewService.getReviewByOrderId(orderId, currentUserId);
+    }
+
+
+    @ApiOperation("分页查询我的评价列表")
+    @ApiOperationSupport(order = 7)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "page", value = "页数(默认1)", dataType = "int", paramType = "query"),
+            @ApiImplicitParam(name = "size", value = "页容(默认10)", dataType = "int", paramType = "query"),
+            @ApiImplicitParam(name = "userId", value = "用户ID", dataType = "int", paramType = "query", required = true)
+    })
+    @GetMapping("/pending/list")
+    public R<IPage<PendingReviewVo>> getPendingReviewList(
+            @RequestParam(defaultValue = "1") int page,
+            @RequestParam(defaultValue = "10") int size,
+            @RequestParam Integer userId) {
+        log.info("OrderReviewController.getPendingReviewList?page={}, size={}, userId={}", page, size, userId);
+        if (userId == null) {
+            return R.fail("用户未登录");
+        }
+        return orderReviewService.getPendingReviewList(page, size, userId);
+    }
+
+    @ApiOperation("分页查询我的评价列表(查询当前用户的所有评价)")
+    @ApiOperationSupport(order = 8)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "page", value = "页数(默认1)", dataType = "int", paramType = "query"),
+            @ApiImplicitParam(name = "size", value = "页容(默认10)", dataType = "int", paramType = "query"),
+            @ApiImplicitParam(name = "userId", value = "用户ID", dataType = "int", paramType = "query", required = true),
+            @ApiImplicitParam(name = "currentUserId", value = "当前用户ID(用于判断是否已点赞)", dataType = "int", paramType = "query")
+    })
+    @GetMapping("/my/list")
+    public R<IPage<OrderReviewVo>> getMyReviewList(
+            @RequestParam(defaultValue = "1") int page,
+            @RequestParam(defaultValue = "10") int size,
+            @RequestParam Integer userId,
+            @RequestParam(required = false) Integer currentUserId) {
+        log.info("OrderReviewController.getMyReviewList?page={}, size={}, userId={}, currentUserId={}", page, size, userId, currentUserId);
+        if (userId == null) {
+            return R.fail("用户未登录");
+        }
+        return orderReviewService.getMyReviewList(page, size, userId, currentUserId);
+    }
+
+    @ApiOperation("获取律师评价统计数据(全部数量、好评数量、中评数量、差评数量、有图数量)")
+    @ApiOperationSupport(order = 10)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "lawyerUserId", value = "律师用户ID", dataType = "int", paramType = "query", required = true)
+    })
+    @GetMapping("/statistics")
+    public R<LawyerReviewStatisticsVo> getLawyerReviewStatistics(@RequestParam Integer lawyerUserId) {
+        log.info("OrderReviewController.getLawyerReviewStatistics?lawyerUserId={}", lawyerUserId);
+        if (lawyerUserId == null) {
+            return R.fail("律师ID不能为空");
+        }
+        return orderReviewService.getLawyerReviewStatistics(lawyerUserId);
+    }
+
+    @ApiOperation("根据律师ID分页查询评价列表(查询指定律师的所有评价)")
+    @ApiOperationSupport(order = 11)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "page", value = "页数(默认1)", dataType = "int", paramType = "query"),
+            @ApiImplicitParam(name = "size", value = "页容(默认10)", dataType = "int", paramType = "query"),
+            @ApiImplicitParam(name = "lawyerUserId", value = "律师用户ID", dataType = "int", paramType = "query", required = true),
+            @ApiImplicitParam(name = "currentUserId", value = "当前用户ID(用于判断是否已点赞)", dataType = "int", paramType = "query")
+    })
+    @GetMapping("/list/byLawyer")
+    public R<IPage<OrderReviewVo>> getReviewListByLawyer(
+            @RequestParam(defaultValue = "1") int page,
+            @RequestParam(defaultValue = "10") int size,
+            @RequestParam Integer lawyerUserId,
+            @RequestParam(required = false) Integer currentUserId) {
+        log.info("OrderReviewController.getReviewListByLawyer?page={}, size={}, lawyerUserId={}, currentUserId={}",
+                page, size, lawyerUserId, currentUserId);
+        if (lawyerUserId == null) {
+            return R.fail("律师ID不能为空");
+        }
+        return orderReviewService.getReviewList(page, size, null, lawyerUserId, null, currentUserId);
+    }
+
+    @ApiOperation("根据律师ID和类型分页查询评价列表(不包含评论)")
+    @ApiOperationSupport(order = 12)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "page", value = "页数(默认1)", dataType = "int", paramType = "query"),
+            @ApiImplicitParam(name = "size", value = "页容(默认10)", dataType = "int", paramType = "query"),
+            @ApiImplicitParam(name = "lawyerUserId", value = "律师用户ID", dataType = "int", paramType = "query", required = true),
+            @ApiImplicitParam(name = "type", value = "查询分类(1:好评,2:中评,3:差评,4:有图,为空时返回全部)", dataType = "int", paramType = "query", required = false),
+            @ApiImplicitParam(name = "currentUserId", value = "当前用户ID(用于判断是否已点赞)", dataType = "int", paramType = "query")
+    })
+    @GetMapping("/list/byLawyerAndType")
+    public R<IPage<OrderReviewVo>> getReviewListByLawyerAndType(
+            @RequestParam(defaultValue = "1") int page,
+            @RequestParam(defaultValue = "10") int size,
+            @RequestParam Integer lawyerUserId,
+            @RequestParam(required = false) Integer type,
+            @RequestParam(required = false) Integer currentUserId) {
+        log.info("OrderReviewController.getReviewListByLawyerAndType?page={}, size={}, lawyerUserId={}, type={}, currentUserId={}",
+                page, size, lawyerUserId, type, currentUserId);
+        if (lawyerUserId == null) {
+            return R.fail("律师ID不能为空");
+        }
+        return orderReviewService.getReviewListByLawyerAndType(page, size, lawyerUserId, type, currentUserId);
+    }
+
+    @ApiOperation("根据订单ID查询订单关联的评价")
+    @ApiOperationSupport(order = 13)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "orderId", value = "订单ID", dataType = "int", paramType = "query", required = true)
+    })
+    @GetMapping("/getByOrderId")
+    public R<OrderReviewVo> queryReviewByOrderId(
+            @RequestParam Integer orderId) {
+        log.info("OrderReviewController.queryReviewByOrderId?orderId={}", orderId);
+        if (orderId == null) {
+            return R.fail("订单ID不能为空");
+        }
+        return orderReviewService.getOrderEvaluation(orderId);
+    }
+}
+

+ 694 - 0
alien-lawyer/src/main/java/shop/alien/lawyer/service/impl/OrderReviewServiceImpl.java

@@ -0,0 +1,694 @@
+package shop.alien.lawyer.service.impl;
+
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONArray;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.CollectionUtils;
+import shop.alien.entity.result.R;
+import shop.alien.entity.store.LawyerConsultationOrder;
+import shop.alien.entity.store.LifeLikeRecord;
+import shop.alien.entity.store.OrderReview;
+import shop.alien.entity.store.ReviewComment;
+import shop.alien.entity.store.dto.OrderReviewDto;
+import shop.alien.entity.store.vo.LawyerReviewStatisticsVo;
+import shop.alien.entity.store.vo.OrderReviewDetailVo;
+import shop.alien.entity.store.vo.OrderReviewVo;
+import shop.alien.entity.store.vo.PendingReviewVo;
+import shop.alien.lawyer.service.OrderReviewService;
+import shop.alien.lawyer.service.ReviewCommentService;
+import shop.alien.entity.store.LawyerUser;
+import shop.alien.mapper.LawyerConsultationOrderMapper;
+import shop.alien.mapper.LawyerUserMapper;
+import shop.alien.mapper.LifeLikeRecordMapper;
+import shop.alien.mapper.OrderReviewMapper;
+import shop.alien.mapper.ReviewCommentMapper;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 订单评价 服务实现类
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Slf4j
+@Transactional
+@Service
+@RequiredArgsConstructor
+public class OrderReviewServiceImpl extends ServiceImpl<OrderReviewMapper, OrderReview> implements OrderReviewService {
+
+    private final OrderReviewMapper orderReviewMapper;
+    private final LawyerConsultationOrderMapper lawyerConsultationOrderMapper;
+    private final ReviewCommentService reviewCommentService;
+    private final ReviewCommentMapper reviewCommentMapper;
+    private final LifeLikeRecordMapper lifeLikeRecordMapper;
+    private final LawyerUserMapper lawyerUserMapper;
+
+    @Override
+    public R<OrderReview> createReview(OrderReviewDto reviewDto) {
+        log.info("OrderReviewServiceImpl.createReview?reviewDto={}", reviewDto);
+
+
+        // 参数校验
+        if (reviewDto == null) {
+            return R.fail("评价信息不能为空");
+        }
+        if (reviewDto.getOrderId() == null) {
+            return R.fail("订单ID不能为空");
+        }
+        Integer userId = reviewDto.getUserId();
+        if (userId == null) {
+            return R.fail("用户ID不能为空");
+        }
+
+        // 验证订单是否存在且属于该用户
+        LawyerConsultationOrder order = lawyerConsultationOrderMapper.selectById(reviewDto.getOrderId());
+        if (order == null) {
+            return R.fail("订单不存在");
+        }
+        if (!order.getClientUserId().equals(userId)) {
+            return R.fail("只能评价自己的订单");
+        }
+
+        // 检查订单是否已完成
+        if (order.getOrderStatus() == null || order.getOrderStatus() != 3) {
+            return R.fail("只能对已完成的订单进行评价");
+        }
+
+        // 检查是否已经评价过
+        LambdaQueryWrapper<OrderReview> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(OrderReview::getOrderId, reviewDto.getOrderId())
+                .eq(OrderReview::getDeleteFlag, 0);
+        OrderReview existingReview = this.getOne(queryWrapper);
+        if (existingReview != null) {
+            return R.success("该订单已经评价过了");
+        }
+
+        // 创建评价
+        OrderReview review = new OrderReview();
+        review.setOrderId(reviewDto.getOrderId());
+        review.setOrderNumber(reviewDto.getOrderNumber());
+        review.setUserId(userId);
+        review.setLawyerUserId(order.getLawyerUserId());
+        review.setOverallRating(reviewDto.getOverallRating());
+        review.setServiceAttitudeRating(reviewDto.getServiceAttitudeRating());
+        review.setResponseTimeRating(reviewDto.getResponseTimeRating());
+        review.setProfessionalAbilityRating(reviewDto.getProfessionalAbilityRating());
+        review.setReviewContent(reviewDto.getReviewContent());
+        
+        // 处理评价图片
+//        if (reviewDto.getReviewImages() != null && !reviewDto.getReviewImages().isEmpty()) {
+//            review.setReviewImages(JSON.toJSONString(reviewDto.getReviewImages()));
+//        }
+
+        if (reviewDto.getReviewImages() != null && !reviewDto.getReviewImages().isEmpty()) {
+            JSONArray jsonArray = new  JSONArray();
+            jsonArray.addAll(reviewDto.getReviewImages());
+            review.setReviewImages(jsonArray.toString());
+        }
+        
+        review.setIsAnonymous(reviewDto.getIsAnonymous() != null ? reviewDto.getIsAnonymous() : 0);
+        review.setLikeCount(0);
+        review.setCommentCount(0);
+        review.setCreatedUserId(userId);
+        review.setCreatedTime(new Date());
+
+        boolean success = this.save(review);
+        if (success) {
+            log.info("创建评价成功,评价ID={}", review.getId());
+            // 更新律师评分
+            updateLawyerServiceScore(review.getLawyerUserId());
+            return R.data(review, "提交成功");
+        } else {
+            log.error("创建评价失败");
+            return R.fail("创建评价失败");
+        }
+    }
+
+    /**
+     * 更新律师服务评分和好评/中评/差评数量
+     * 计算公式:律师评分 = overallRating平均值 (转换为0-5分)
+     * 评分规则:0-2.5分=差评,3-4分=中评,4.5-5分=好评
+     *
+     * @param lawyerUserId 律师用户ID
+     */
+    private void updateLawyerServiceScore(Integer lawyerUserId) {
+        if (lawyerUserId == null) {
+            log.warn("更新律师评分失败:律师ID为空");
+            return;
+        }
+
+        try {
+            // 计算平均评分(1-5星)
+            Double averageRating = orderReviewMapper.getAverageRatingByLawyerUserId(lawyerUserId);
+            
+            Double serviceScore;
+            if (averageRating != null) {
+                // 转换为0-5分,保留一位小数(直接截取,不四舍五入)
+                serviceScore = Math.floor(averageRating * 10.0) / 10.0;
+                // 确保在0-5范围内
+                serviceScore = Math.max(0.0, Math.min(5.0, serviceScore));
+            } else {
+                // 如果没有评价,设置为0.0
+                serviceScore = 0.0;
+                log.info("律师暂无评价,将评分设置为0.0,律师ID={}", lawyerUserId);
+            }
+
+            // 统计好评、中评、差评数量
+            Integer goodReviewCount = orderReviewMapper.getGoodReviewCountByLawyerUserId(lawyerUserId);
+            Integer mediumReviewCount = orderReviewMapper.getMediumReviewCountByLawyerUserId(lawyerUserId);
+            Integer badReviewCount = orderReviewMapper.getBadReviewCountByLawyerUserId(lawyerUserId);
+
+            // 处理空值
+            if (goodReviewCount == null) {
+                goodReviewCount = 0;
+            }
+            if (mediumReviewCount == null) {
+                mediumReviewCount = 0;
+            }
+            if (badReviewCount == null) {
+                badReviewCount = 0;
+            }
+
+            // 更新律师评分和好评/中评/差评数量
+            LambdaUpdateWrapper<LawyerUser> updateWrapper = new LambdaUpdateWrapper<>();
+            updateWrapper.eq(LawyerUser::getId, lawyerUserId);
+            updateWrapper.set(LawyerUser::getServiceScore, serviceScore);
+            updateWrapper.set(LawyerUser::getGoodReviewCount, goodReviewCount);
+            updateWrapper.set(LawyerUser::getMediumReviewCount, mediumReviewCount);
+            updateWrapper.set(LawyerUser::getBadReviewCount, badReviewCount);
+            int result = lawyerUserMapper.update(null, updateWrapper);
+
+            if (result > 0) {
+                log.info("更新律师评分成功,律师ID={}, 平均评分={}, 服务评分={}, 好评数={}, 中评数={}, 差评数={}",
+                        lawyerUserId, averageRating, serviceScore, goodReviewCount, mediumReviewCount, badReviewCount);
+            } else {
+                log.warn("更新律师评分失败,律师ID={}", lawyerUserId);
+            }
+        } catch (Exception e) {
+            log.error("更新律师评分异常,律师ID={}, 错误信息={}", lawyerUserId, e.getMessage(), e);
+        }
+    }
+
+    @Override
+    public R<IPage<OrderReviewVo>> getReviewList(int pageNum, int pageSize, Integer orderId, Integer lawyerUserId, Integer userId, Integer currentUserId) {
+        log.info("OrderReviewServiceImpl.getReviewList?pageNum={}, pageSize={}, orderId={}, lawyerUserId={}, userId={}, currentUserId={}",
+                pageNum, pageSize, orderId, lawyerUserId, userId, currentUserId);
+
+        Page<OrderReviewVo> page = new Page<>(pageNum, pageSize);
+        IPage<OrderReviewVo> result = orderReviewMapper.getReviewListWithUser(page, orderId, lawyerUserId, userId, currentUserId);
+        
+        // 处理评价图片JSON字符串转换为列表
+        if (result.getRecords() != null) {
+            for (OrderReviewVo vo : result.getRecords()) {
+                // 处理评价图片:从JSON字符串解析为List
+                if (vo.getReviewImagesJson() != null && !vo.getReviewImagesJson().trim().isEmpty()) {
+                    try {
+                        List<String> images = JSON.parseArray(vo.getReviewImagesJson(), String.class);
+                        vo.setReviewImages(images != null ? images : new ArrayList<>());
+                    } catch (Exception e) {
+                        log.warn("解析评价图片失败:{}", e.getMessage());
+                        vo.setReviewImages(new ArrayList<>());
+                    }
+                } else {
+                    vo.setReviewImages(new ArrayList<>());
+                }
+            }
+        }
+
+        return R.data(result);
+    }
+
+    @Override
+    public R<OrderReviewDetailVo> getReviewDetail(Integer reviewId, Integer currentUserId) {
+        log.info("OrderReviewServiceImpl.getReviewDetail?reviewId={}, currentUserId={}", reviewId, currentUserId);
+
+        if (reviewId == null) {
+            return R.fail("评价ID不能为空");
+        }
+
+        // 查询评价详情
+        OrderReviewVo reviewVo = orderReviewMapper.getReviewDetailById(reviewId, currentUserId);
+        if (reviewVo == null) {
+            return R.fail("评价不存在");
+        }
+        log.debug("查询评价详情,reviewId={}, currentUserId={}, isLiked={}", reviewId, currentUserId, reviewVo.getIsLiked());
+
+        // 处理评价图片:从SQL查询结果中解析JSON字符串
+        if (reviewVo.getReviewImagesJson() != null && !reviewVo.getReviewImagesJson().trim().isEmpty()) {
+            try {
+                List<String> images = JSON.parseArray(reviewVo.getReviewImagesJson(), String.class);
+                reviewVo.setReviewImages(images != null ? images : new ArrayList<>());
+            } catch (Exception e) {
+                log.warn("解析评价图片失败:{}", e.getMessage());
+                reviewVo.setReviewImages(new ArrayList<>());
+            }
+        } else {
+            reviewVo.setReviewImages(new ArrayList<>());
+        }
+
+        // 查询评论列表
+        List<shop.alien.entity.store.vo.ReviewCommentVo> comments = reviewCommentMapper.getCommentListByReviewId(reviewId, currentUserId);
+        
+        // 为每个评论查询回复列表
+        if (comments != null && !comments.isEmpty()) {
+            for (shop.alien.entity.store.vo.ReviewCommentVo comment : comments) {
+                List<shop.alien.entity.store.vo.ReviewCommentVo> replies = reviewCommentMapper.getReplyListByHeadId(comment.getId(), currentUserId);
+                comment.setReplies(replies);
+            }
+        }
+        
+        // 查询总评论数(包括首评论和子评论)
+        Integer totalCommentCount = reviewCommentMapper.getTotalCommentCountByReviewId(reviewId);
+        if (totalCommentCount == null) {
+            totalCommentCount = 0;
+        }
+        
+        // 构建返回结果
+        OrderReviewDetailVo detailVo = new OrderReviewDetailVo();
+        detailVo.setReview(reviewVo);
+        detailVo.setComments(comments != null ? comments : new ArrayList<>());
+        detailVo.setTotalCommentCount(totalCommentCount);
+        // 设置评价的点赞数和是否已点赞(从 reviewVo 中获取)
+        detailVo.setLikeCount(reviewVo.getLikeCount() != null ? reviewVo.getLikeCount() : 0);
+        detailVo.setIsLiked(reviewVo.getIsLiked() != null ? reviewVo.getIsLiked() : 0);
+
+        return R.data(detailVo);
+    }
+
+    @Override
+    public R<Boolean> deleteReview(Integer reviewId, Integer userId) {
+        log.info("OrderReviewServiceImpl.deleteReview?reviewId={}, userId={}", reviewId, userId);
+
+        if (reviewId == null) {
+            return R.fail("评价ID不能为空");
+        }
+
+        if (userId == null) {
+            return R.fail("用户ID不能为空");
+        }
+
+        // 查询评价
+        OrderReview review = this.getById(reviewId);
+        if (review == null) {
+            return R.fail("评价不存在");
+        }
+
+        // 验证是否为评价用户(只能删除自己的评价)
+        if (!review.getUserId().equals(userId)) {
+            return R.fail("只能删除自己的评价");
+        }
+
+        return deleteReviewInternal(reviewId, userId, review);
+    }
+
+    @Override
+    public R<Boolean> deleteReviewByAdmin(Integer reviewId) {
+        log.info("OrderReviewServiceImpl.deleteReviewByAdmin?reviewId={}", reviewId);
+
+        if (reviewId == null) {
+            return R.fail("评价ID不能为空");
+        }
+
+
+        // 查询评价
+        OrderReview review = this.getById(reviewId);
+        if (review == null) {
+            return R.fail("评价不存在");
+        }
+
+        return deleteReviewInternal(reviewId, null, review);
+    }
+
+    /**
+     * 内部删除评价方法(删除评价时,会级联删除该评价下的所有评论和回复)
+     *
+     * @param reviewId 评价ID
+     * @param userId 用户ID(可为null,管理员删除时为空)
+     * @param review 评价对象
+     * @return R<Boolean>
+     */
+    private R<Boolean> deleteReviewInternal(Integer reviewId, Integer userId, OrderReview review) {
+
+        // 查询该评价下的所有评论
+        LambdaQueryWrapper<ReviewComment> commentWrapper = new LambdaQueryWrapper<>();
+        commentWrapper.eq(ReviewComment::getReviewId, reviewId)
+                .eq(ReviewComment::getDeleteFlag, 0);
+        List<ReviewComment> comments = reviewCommentService.list(commentWrapper);
+
+        // 删除评价(逻辑删除)
+        int num = orderReviewMapper.deleteById(reviewId);
+
+        if (num > 0) {
+            // 级联删除该评价下的所有评论和回复
+            for (ReviewComment comment : comments) {
+                // 删除评论下的所有回复(逻辑删除)
+                LambdaUpdateWrapper<ReviewComment> replyUpdateWrapper = new LambdaUpdateWrapper<>();
+                replyUpdateWrapper.eq(ReviewComment::getHeadId, comment.getId())
+                        .eq(ReviewComment::getHeadType, 1)
+                        .eq(ReviewComment::getDeleteFlag, 0);
+                replyUpdateWrapper.set(ReviewComment::getDeleteFlag, 1);
+                if (userId != null) {
+                    replyUpdateWrapper.set(ReviewComment::getUpdatedUserId, userId);
+                }
+                replyUpdateWrapper.set(ReviewComment::getUpdatedTime, new Date());
+                reviewCommentService.remove(replyUpdateWrapper);
+                
+                // 删除评论
+                comment.setDeleteFlag(1);
+                if (userId != null) {
+                    comment.setUpdatedUserId(userId);
+                }
+                comment.setUpdatedTime(new Date());
+                reviewCommentService.removeById(comment.getId());
+            }
+            
+            // 更新律师评分
+            updateLawyerServiceScore(review.getLawyerUserId());
+
+            // 管理员删除时,更新订单的 is_appealed 字段为 1
+            if (userId == null && review.getOrderId() != null) {
+                try {
+                    LambdaUpdateWrapper<LawyerConsultationOrder> orderUpdateWrapper = new LambdaUpdateWrapper<>();
+                    orderUpdateWrapper.eq(LawyerConsultationOrder::getId, review.getOrderId())
+                            .set(LawyerConsultationOrder::getIsAppealed, 1);
+                    int updateResult = lawyerConsultationOrderMapper.update(null, orderUpdateWrapper);
+                    if (updateResult > 0) {
+                        log.info("管理员删除评价时,已更新订单的申诉状态,评价ID={}, 订单ID={}", reviewId, review.getOrderId());
+                    } else {
+                        log.warn("管理员删除评价时,更新订单申诉状态失败,评价ID={}, 订单ID={}", reviewId, review.getOrderId());
+                    }
+                } catch (Exception e) {
+                    log.error("管理员删除评价时,更新订单申诉状态异常,评价ID={}, 订单ID={}, 错误信息={}", 
+                            reviewId, review.getOrderId(), e.getMessage(), e);
+                    // 更新订单申诉状态失败不影响删除评价的操作
+                }
+            }
+
+            if (userId != null) {
+                log.info("用户删除评价成功,评价ID={}, 操作人ID={}", reviewId, userId);
+            } else {
+                log.info("管理员删除评价成功,评价ID={}", reviewId);
+            }
+            return R.data(true, "删除成功");
+        } else {
+            log.error("删除评价失败,评价ID={}", reviewId);
+            return R.fail("删除评价失败");
+        }
+    }
+
+    @Override
+    public R<OrderReviewVo> getReviewByOrderId(Integer orderId, Integer currentUserId) {
+        log.info("OrderReviewServiceImpl.getReviewByOrderId?orderId={}, currentUserId={}", orderId, currentUserId);
+
+        if (orderId == null) {
+            return R.fail("订单ID不能为空");
+        }
+
+        OrderReviewVo reviewVo = orderReviewMapper.getReviewByOrderId(orderId, currentUserId);
+        if (reviewVo == null) {
+            return R.fail("该订单暂无评价");
+        }
+
+        // 处理评价图片
+        OrderReview review = this.getOne(new LambdaQueryWrapper<OrderReview>()
+                .eq(OrderReview::getOrderId, orderId)
+                .eq(OrderReview::getDeleteFlag, 0));
+        if (review != null && review.getReviewImages() != null) {
+            try {
+                List<String> images = JSON.parseArray(review.getReviewImages(), String.class);
+                reviewVo.setReviewImages(images);
+            } catch (Exception e) {
+                log.warn("解析评价图片失败:{}", e.getMessage());
+            }
+        }
+
+        return R.data(reviewVo);
+    }
+
+    @Override
+    public R<IPage<PendingReviewVo>> getPendingReviewList(int pageNum, int pageSize, Integer userId) {
+        log.info("OrderReviewServiceImpl.getPendingReviewList?pageNum={}, pageSize={}, userId={}",
+                pageNum, pageSize, userId);
+
+        if (userId == null) {
+            return R.fail("用户ID不能为空");
+        }
+
+        Page<PendingReviewVo> page = new Page<>(pageNum, pageSize);
+        IPage<PendingReviewVo> result = orderReviewMapper.getPendingReviewList(page, userId);
+
+        return R.data(result);
+    }
+
+    @Override
+    public R<IPage<OrderReviewVo>> getMyReviewList(int pageNum, int pageSize, Integer userId, Integer currentUserId) {
+        log.info("OrderReviewServiceImpl.getMyReviewList?pageNum={}, pageSize={}, userId={}, currentUserId={}",
+                pageNum, pageSize, userId, currentUserId);
+
+        if (userId == null) {
+            return R.fail("用户ID不能为空");
+        }
+
+        Page<OrderReviewVo> page = new Page<>(pageNum, pageSize);
+        IPage<OrderReviewVo> result = orderReviewMapper.getReviewListWithUser(page, null, null, userId, currentUserId);
+
+        // 处理评价图片:将JSON字符串转换为List<String>
+        if (result.getRecords() != null) {
+            for (OrderReviewVo vo : result.getRecords()) {
+                // 从数据库查询原始的reviewImages字符串
+                if (vo.getId() != null) {
+                    OrderReview review = this.getById(vo.getId());
+                    if (review != null && review.getReviewImages() != null && !review.getReviewImages().trim().isEmpty()) {
+                        try {
+                            // 解析JSON数组字符串为List<String>
+                            List<String> imagesList = JSON.parseArray(review.getReviewImages(), String.class);
+                            vo.setReviewImages(imagesList != null ? imagesList : new ArrayList<>());
+                        } catch (Exception e) {
+                            log.warn("解析评价图片失败,reviewId={}, error={}", vo.getId(), e.getMessage());
+                            vo.setReviewImages(new ArrayList<>());
+                        }
+                    } else {
+                        // 如果没有图片,设置为空列表
+                        vo.setReviewImages(new ArrayList<>());
+                    }
+                } else {
+                    // 如果ID为空,设置为空列表
+                    vo.setReviewImages(new ArrayList<>());
+                }
+            }
+        }
+
+        return R.data(result);
+    }
+
+    @Override
+    public R<Boolean> likeReview(Integer reviewId, Integer userId) {
+        log.info("OrderReviewServiceImpl.likeReview?reviewId={}, userId={}", reviewId, userId);
+
+        if (reviewId == null) {
+            return R.fail("评价ID不能为空");
+        }
+        if (userId == null) {
+            return R.fail("用户ID不能为空");
+        }
+
+        // 验证评价是否存在
+        OrderReview review = this.getById(reviewId);
+        if (review == null || review.getDeleteFlag() == 1) {
+            return R.fail("评价不存在或已删除");
+        }
+
+        // 检查是否已点赞
+        LambdaQueryWrapper<LifeLikeRecord> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(LifeLikeRecord::getType, "7")
+                .eq(LifeLikeRecord::getDianzanId, String.valueOf(userId))
+                .eq(LifeLikeRecord::getHuifuId, String.valueOf(reviewId))
+                .eq(LifeLikeRecord::getDeleteFlag, 0);
+        List<LifeLikeRecord> records = lifeLikeRecordMapper.selectList(queryWrapper);
+
+        if (CollectionUtils.isEmpty(records)) {
+            // 插入点赞记录
+            LifeLikeRecord likeRecord = new LifeLikeRecord();
+            likeRecord.setDianzanId(String.valueOf(userId));
+            likeRecord.setHuifuId(String.valueOf(reviewId));
+            likeRecord.setType("7");
+            likeRecord.setCreatedTime(new Date());
+            likeRecord.setCreatedUserId(userId);
+            lifeLikeRecordMapper.insert(likeRecord);
+
+            // 更新评价点赞数
+            LambdaUpdateWrapper<OrderReview> updateWrapper = new LambdaUpdateWrapper<>();
+            updateWrapper.eq(OrderReview::getId, reviewId);
+            updateWrapper.setSql("like_count = like_count + 1");
+            int result = orderReviewMapper.update(null, updateWrapper);
+
+            if (result > 0) {
+                log.info("点赞评价成功,评价ID={}", reviewId);
+                return R.data(true, "点赞成功");
+            } else {
+                return R.fail("点赞失败");
+            }
+        } else {
+            return R.data(true, "已点赞");
+        }
+    }
+
+    @Override
+    public R<Boolean> cancelLikeReview(Integer reviewId, Integer userId) {
+        log.info("OrderReviewServiceImpl.cancelLikeReview?reviewId={}, userId={}", reviewId, userId);
+
+        if (reviewId == null) {
+            return R.fail("评价ID不能为空");
+        }
+        if (userId == null) {
+            return R.fail("用户ID不能为空");
+        }
+
+        // 查询点赞记录
+        LambdaQueryWrapper<LifeLikeRecord> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(LifeLikeRecord::getType, "7")
+                .eq(LifeLikeRecord::getDianzanId, String.valueOf(userId))
+                .eq(LifeLikeRecord::getHuifuId, String.valueOf(reviewId))
+                .eq(LifeLikeRecord::getDeleteFlag, 0);
+        List<LifeLikeRecord> records = lifeLikeRecordMapper.selectList(queryWrapper);
+
+        if (!CollectionUtils.isEmpty(records)) {
+            // 删除点赞记录(逻辑删除)
+            for (LifeLikeRecord record : records) {
+                int updateResult = lifeLikeRecordMapper.deleteById(record.getId());
+                log.info("逻辑删除点赞记录,recordId={}, updateResult={}", record.getId(), updateResult);
+            }
+
+            // 更新评价点赞数(移除 gt 条件,确保即使为 0 也能正确更新)
+            LambdaUpdateWrapper<OrderReview> updateWrapper = new LambdaUpdateWrapper<>();
+            updateWrapper.eq(OrderReview::getId, reviewId);
+            updateWrapper.setSql("like_count = GREATEST(0, like_count - 1)");
+            int result = orderReviewMapper.update(null, updateWrapper);
+
+            if (result > 0) {
+                log.info("取消点赞评价成功,评价ID={}", reviewId);
+                return R.data(true, "取消点赞成功");
+            } else {
+                return R.fail("取消点赞失败");
+            }
+        } else {
+            return R.data(true, "未点赞");
+        }
+    }
+
+    @Override
+    public R<IPage<OrderReviewVo>> getReviewListByLawyerAndType(int pageNum, int pageSize, Integer lawyerUserId, Integer type, Integer currentUserId) {
+        log.info("OrderReviewServiceImpl.getReviewListByLawyerAndType?pageNum={}, pageSize={}, lawyerUserId={}, type={}, currentUserId={}",
+                pageNum, pageSize, lawyerUserId, type, currentUserId);
+
+        if (lawyerUserId == null) {
+            return R.fail("律师ID不能为空");
+        }
+
+        Page<OrderReviewVo> page = new Page<>(pageNum, pageSize);
+        IPage<OrderReviewVo> result = orderReviewMapper.getReviewListByLawyerAndType(page, lawyerUserId, type, currentUserId);
+
+        // 处理评价图片JSON字符串转换为列表
+        if (result.getRecords() != null) {
+            for (OrderReviewVo vo : result.getRecords()) {
+                // 处理评价图片:从JSON字符串解析为List
+                if (vo.getReviewImagesJson() != null && !vo.getReviewImagesJson().trim().isEmpty()) {
+                    try {
+                        List<String> images = JSON.parseArray(vo.getReviewImagesJson(), String.class);
+                        vo.setReviewImages(images != null ? images : new ArrayList<>());
+                    } catch (Exception e) {
+                        log.warn("解析评价图片失败:{}", e.getMessage());
+                        vo.setReviewImages(new ArrayList<>());
+                    }
+                } else {
+                    vo.setReviewImages(new ArrayList<>());
+                }
+            }
+        }
+
+        return R.data(result);
+    }
+
+    @Override
+    public R<LawyerReviewStatisticsVo> getLawyerReviewStatistics(Integer lawyerUserId) {
+        log.info("OrderReviewServiceImpl.getLawyerReviewStatistics?lawyerUserId={}", lawyerUserId);
+
+        if (lawyerUserId == null) {
+            return R.fail("律师ID不能为空");
+        }
+
+        try {
+            LawyerReviewStatisticsVo statistics = new LawyerReviewStatisticsVo();
+
+            // 统计全部评价数量
+            Integer totalCount = orderReviewMapper.getTotalReviewCountByLawyerUserId(lawyerUserId);
+            statistics.setTotalCount(totalCount != null ? totalCount : 0);
+
+            // 统计好评数量
+            Integer goodCount = orderReviewMapper.getGoodReviewCountByLawyerUserId(lawyerUserId);
+            statistics.setGoodCount(goodCount != null ? goodCount : 0);
+
+            // 统计中评数量
+            Integer mediumCount = orderReviewMapper.getMediumReviewCountByLawyerUserId(lawyerUserId);
+            statistics.setMediumCount(mediumCount != null ? mediumCount : 0);
+
+            // 统计差评数量
+            Integer badCount = orderReviewMapper.getBadReviewCountByLawyerUserId(lawyerUserId);
+            statistics.setBadCount(badCount != null ? badCount : 0);
+
+            // 统计有图评价数量
+            Integer imageCount = orderReviewMapper.getImageReviewCountByLawyerUserId(lawyerUserId);
+            statistics.setImageCount(imageCount != null ? imageCount : 0);
+
+            log.info("获取律师评价统计数据成功,律师ID={}, 全部={}, 好评={}, 中评={}, 差评={}, 有图={}",
+                    lawyerUserId, statistics.getTotalCount(), statistics.getGoodCount(),
+                    statistics.getMediumCount(), statistics.getBadCount(), statistics.getImageCount());
+
+            return R.data(statistics);
+        } catch (Exception e) {
+            log.error("获取律师评价统计数据异常,律师ID={}, 错误信息={}", lawyerUserId, e.getMessage(), e);
+            return R.fail("获取统计数据失败");
+        }
+    }
+
+    @Override
+    public R<OrderReviewVo> getOrderEvaluation(Integer orderId) {
+        log.info("OrderReviewServiceImpl.getOrderEvaluation?orderId={}", orderId);
+
+        if (orderId == null) {
+            return R.fail("订单ID不能为空");
+        }
+
+        OrderReviewVo reviewVo = orderReviewMapper.getOrderEvaluation(orderId);
+        if (reviewVo == null) {
+            return R.fail("该订单暂无评价");
+        }
+
+        // 处理评价图片:从JSON字符串解析为List
+        if (reviewVo.getReviewImagesJson() != null && !reviewVo.getReviewImagesJson().trim().isEmpty()) {
+            try {
+                List<String> images = JSON.parseArray(reviewVo.getReviewImagesJson(), String.class);
+                reviewVo.setReviewImages(images != null ? images : new ArrayList<>());
+            } catch (Exception e) {
+                log.warn("解析评价图片失败:{}", e.getMessage());
+                reviewVo.setReviewImages(new ArrayList<>());
+            }
+        } else {
+            reviewVo.setReviewImages(new ArrayList<>());
+        }
+
+        return R.data(reviewVo);
+    }
+}
+

+ 168 - 0
alien-store/src/main/java/shop/alien/store/controller/BathFacilityServiceController.java

@@ -0,0 +1,168 @@
+package shop.alien.store.controller;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import io.swagger.annotations.*;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.*;
+import shop.alien.entity.result.R;
+import shop.alien.entity.store.BathFacilityService;
+import shop.alien.entity.store.vo.BathFacilityServiceCategoryVo;
+import shop.alien.entity.store.vo.BathFacilityServiceVo;
+import shop.alien.store.service.BathFacilityServiceService;
+
+import java.util.List;
+
+/**
+ * 洗浴设施及服务Controller
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Slf4j
+@Api(tags = {"洗浴设施及服务管理"})
+@ApiSort(1)
+@CrossOrigin
+@RestController
+@RequestMapping("/bath/facility/service")
+@RequiredArgsConstructor
+public class BathFacilityServiceController {
+
+    private final BathFacilityServiceService facilityServiceService;
+
+    @ApiOperation("分页查询洗浴设施及服务列表(用户端)")
+    @ApiOperationSupport(order = 1)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "pageNum", value = "页码", dataType = "int", paramType = "query", required = true, defaultValue = "1"),
+            @ApiImplicitParam(name = "pageSize", value = "页大小", dataType = "int", paramType = "query", required = true, defaultValue = "10"),
+            @ApiImplicitParam(name = "storeId", value = "门店ID", dataType = "int", paramType = "query", required = true),
+            @ApiImplicitParam(name = "facilityCategory", value = "设施分类(1:洗浴区, 2:汗蒸区, 3:休闲区, 4:餐饮区)", dataType = "int", paramType = "query")
+    })
+    @GetMapping("/page")
+    public R<IPage<BathFacilityServiceVo>> getPageList(
+            @RequestParam(defaultValue = "1") Integer pageNum,
+            @RequestParam(defaultValue = "10") Integer pageSize,
+            @RequestParam Integer storeId,
+            @RequestParam(required = false) Integer facilityCategory) {
+        log.info("BathFacilityServiceController.getPageList?pageNum={},pageSize={},storeId={},facilityCategory={}",
+                pageNum, pageSize, storeId, facilityCategory);
+        return R.data(facilityServiceService.getPageList(pageNum, pageSize, storeId, facilityCategory));
+    }
+
+    @ApiOperation("查询洗浴设施及服务列表")
+    @ApiOperationSupport(order = 2)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "storeId", value = "门店ID", dataType = "int", paramType = "query", required = true),
+            @ApiImplicitParam(name = "facilityCategory", value = "设施分类(1:洗浴区, 2:汗蒸区, 3:休闲区, 4:餐饮区)", dataType = "int", paramType = "query")
+    })
+    @GetMapping("/list")
+    public R<List<BathFacilityServiceVo>> getList(
+            @RequestParam Integer storeId,
+            @RequestParam(required = false) Integer facilityCategory) {
+        log.info("BathFacilityServiceController.getList?storeId={},facilityCategory={}", storeId, facilityCategory);
+        return R.data(facilityServiceService.getList(storeId, facilityCategory));
+    }
+
+    @ApiOperation("根据ID查询洗浴设施及服务详情(用户端)")
+    @ApiOperationSupport(order = 3)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "id", value = "主键ID", dataType = "int", paramType = "query", required = true)
+    })
+    @GetMapping("/detail")
+    public R<BathFacilityServiceVo> getDetail(@RequestParam Integer id) {
+        log.info("BathFacilityServiceController.getDetail?id={}", id);
+        BathFacilityServiceVo vo = facilityServiceService.getDetail(id);
+        if (vo == null) {
+            return R.fail("数据不存在");
+        }
+        return R.data(vo);
+    }
+
+    @ApiOperation("新增洗浴设施及服务")
+    @ApiOperationSupport(order = 4)
+    @PostMapping("/save")
+    public R<Boolean> saveFacilityService(@RequestBody BathFacilityServiceVo vo) {
+        log.info("BathFacilityServiceController.saveFacilityService?vo={}", vo);
+        try {
+            BathFacilityService facilityService = new BathFacilityService();
+            facilityService.setStoreId(vo.getStoreId());
+            facilityService.setFacilityCategory(vo.getFacilityCategory());
+            facilityService.setFacilityName(vo.getFacilityName());
+            facilityService.setChargingStandard(vo.getChargingStandard());
+            facilityService.setUsageTimeType(vo.getUsageTimeType());
+            facilityService.setUsageStartTime(vo.getUsageStartTime());
+            facilityService.setUsageEndTime(vo.getUsageEndTime());
+            facilityService.setDisplayInStoreDetail(vo.getDisplayInStoreDetail());
+            boolean result = facilityServiceService.saveFacilityService(facilityService, vo.getImageList());
+            if (result) {
+                return R.success("新增成功");
+            }
+            return R.fail("新增失败");
+        } catch (Exception e) {
+            log.error("BathFacilityServiceController.saveFacilityService异常", e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @ApiOperation("修改洗浴设施及服务")
+    @ApiOperationSupport(order = 5)
+    @PostMapping("/update")
+    public R<Boolean> updateFacilityService(@RequestBody BathFacilityServiceVo vo) {
+        log.info("BathFacilityServiceController.updateFacilityService?vo={}", vo);
+        try {
+            if (vo.getId() == null) {
+                return R.fail("主键ID不能为空");
+            }
+            BathFacilityService facilityService = new BathFacilityService();
+            facilityService.setId(vo.getId());
+            facilityService.setStoreId(vo.getStoreId());
+            facilityService.setFacilityCategory(vo.getFacilityCategory());
+            facilityService.setFacilityName(vo.getFacilityName());
+            facilityService.setChargingStandard(vo.getChargingStandard());
+            facilityService.setUsageTimeType(vo.getUsageTimeType());
+            facilityService.setUsageStartTime(vo.getUsageStartTime());
+            facilityService.setUsageEndTime(vo.getUsageEndTime());
+            facilityService.setDisplayInStoreDetail(vo.getDisplayInStoreDetail());
+            boolean result = facilityServiceService.updateFacilityService(facilityService, vo.getImageList());
+            if (result) {
+                return R.success("修改成功");
+            }
+            return R.fail("修改失败");
+        } catch (Exception e) {
+            log.error("BathFacilityServiceController.updateFacilityService异常", e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @ApiOperation("删除洗浴设施及服务")
+    @ApiOperationSupport(order = 6)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "id", value = "主键ID", dataType = "int", paramType = "query", required = true)
+    })
+    @PostMapping("/delete")
+    public R<Boolean> deleteFacilityService(@RequestParam Integer id) {
+        log.info("BathFacilityServiceController.deleteFacilityService?id={}", id);
+        try {
+            boolean result = facilityServiceService.deleteFacilityService(id);
+            if (result) {
+                return R.success("删除成功");
+            }
+            return R.fail("删除失败");
+        } catch (Exception e) {
+            log.error("BathFacilityServiceController.deleteFacilityService异常", e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @ApiOperation("查询指定店铺按分类汇总的设备信息(包含设备数量、设备列表和图片)(用户端)")
+    @ApiOperationSupport(order = 7)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "storeId", value = "门店ID", dataType = "int", paramType = "query", required = true)
+    })
+    @GetMapping("/categorySummary")
+    public R<List<BathFacilityServiceCategoryVo>> getCategorySummary(@RequestParam Integer storeId) {
+        log.info("BathFacilityServiceController.getCategorySummary?storeId={}", storeId);
+        return R.data(facilityServiceService.getCategorySummary(storeId));
+    }
+}
+

+ 17 - 0
alien-store/src/main/java/shop/alien/store/controller/LifeCouponController.java

@@ -10,6 +10,7 @@ import shop.alien.entity.result.R;
 import shop.alien.entity.store.EssentialHolidayComparison;
 import shop.alien.entity.store.LifeCoupon;
 import shop.alien.entity.store.vo.LifeCouponStatusVo;
+import shop.alien.entity.store.vo.LifeCouponVo;
 import shop.alien.store.service.LifeCouponService;
 
 import java.util.Map;
@@ -78,6 +79,22 @@ public class LifeCouponController {
         return R.data(lifeCouponService.getOne(objectLambdaQueryWrapper));
     }
 
+    @ApiOperation("代金劵详情")
+    @GetMapping("/getNewCouponDetail")
+    private R<LifeCouponVo> getNewCouponDetail(@RequestParam("id") String id) {
+        log.info("LifeCouponController.getNewCouponDetail?id={}", id);
+        try {
+            LifeCouponVo lifeCouponVo = lifeCouponService.getNewCouponDetail(id);
+            if (lifeCouponVo == null) {
+                return R.fail("代金券不存在或已删除");
+            }
+            return R.data(lifeCouponVo);
+        } catch (Exception e) {
+            log.error("LifeCouponController.getNewCouponDetail ERROR Msg={}", e.getMessage(), e);
+            return R.fail("查询失败");
+        }
+    }
+
     @ApiOperation("旧 核销订单")
     @ApiImplicitParams({@ApiImplicitParam(name = "storeId", value = "门店id", dataType = "Integer", paramType = "query", required = true), @ApiImplicitParam(name = "quanCode", value = "券码", dataType = "Integer", paramType = "query", required = true)})
     @GetMapping("/verify")

+ 194 - 0
alien-store/src/main/java/shop/alien/store/controller/SportsEquipmentFacilityController.java

@@ -0,0 +1,194 @@
+package shop.alien.store.controller;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import io.swagger.annotations.*;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.*;
+import shop.alien.entity.result.R;
+import shop.alien.entity.store.SportsEquipmentFacility;
+import shop.alien.entity.store.vo.SportsEquipmentFacilityCategoryVo;
+import shop.alien.entity.store.vo.SportsEquipmentFacilityVo;
+import shop.alien.store.service.SportsEquipmentFacilityService;
+
+import java.util.List;
+
+/**
+ * 运动器材设施Controller
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Slf4j
+@Api(tags = {"运动器材设施管理"})
+@ApiSort(1)
+@CrossOrigin
+@RestController
+@RequestMapping("/sports/equipment/facility")
+@RequiredArgsConstructor
+public class SportsEquipmentFacilityController {
+
+    private final SportsEquipmentFacilityService facilityService;
+
+    @ApiOperation("分页查询运动器材设施列表")
+    @ApiOperationSupport(order = 1)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "pageNum", value = "页码", dataType = "int", paramType = "query", required = true, defaultValue = "1"),
+            @ApiImplicitParam(name = "pageSize", value = "页大小", dataType = "int", paramType = "query", required = true, defaultValue = "10"),
+            @ApiImplicitParam(name = "storeId", value = "门店ID", dataType = "int", paramType = "query", required = true),
+            @ApiImplicitParam(name = "facilityCategory", value = "设施分类(1:有氧区, 2:力量区, 3:单功能机械区)", dataType = "int", paramType = "query")
+    })
+    @GetMapping("/page")
+    public R<IPage<SportsEquipmentFacilityVo>> getPageList(
+            @RequestParam(defaultValue = "1") Integer pageNum,
+            @RequestParam(defaultValue = "10") Integer pageSize,
+            @RequestParam Integer storeId,
+            @RequestParam(required = false) Integer facilityCategory) {
+        log.info("SportsEquipmentFacilityController.getPageList?pageNum={},pageSize={},storeId={},facilityCategory={}",
+                pageNum, pageSize, storeId, facilityCategory);
+        return R.data(facilityService.getPageList(pageNum, pageSize, storeId, facilityCategory));
+    }
+
+    @ApiOperation("查询运动器材设施列表")
+    @ApiOperationSupport(order = 2)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "storeId", value = "门店ID", dataType = "int", paramType = "query", required = true),
+            @ApiImplicitParam(name = "facilityCategory", value = "设施分类(1:有氧区, 2:力量区, 3:单功能机械区)", dataType = "int", paramType = "query")
+    })
+    @GetMapping("/list")
+    public R<List<SportsEquipmentFacilityVo>> getList(
+            @RequestParam Integer storeId,
+            @RequestParam(required = false) Integer facilityCategory) {
+        log.info("SportsEquipmentFacilityController.getList?storeId={},facilityCategory={}", storeId, facilityCategory);
+        return R.data(facilityService.getList(storeId, facilityCategory));
+    }
+
+    @ApiOperation("根据ID查询运动器材设施详情(用户端)")
+    @ApiOperationSupport(order = 3)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "id", value = "主键ID", dataType = "int", paramType = "query", required = true)
+    })
+    @GetMapping("/detail")
+    public R<SportsEquipmentFacilityVo> getDetail(@RequestParam Integer id) {
+        log.info("SportsEquipmentFacilityController.getDetail?id={}", id);
+        SportsEquipmentFacilityVo vo = facilityService.getDetail(id);
+        if (vo == null) {
+            return R.fail("数据不存在");
+        }
+        return R.data(vo);
+    }
+
+    @ApiOperation("新增运动器材设施")
+    @ApiOperationSupport(order = 4)
+    @PostMapping("/save")
+    public R<Boolean> saveFacility(@RequestBody SportsEquipmentFacilityVo vo) {
+        log.info("SportsEquipmentFacilityController.saveFacility?vo={}", vo);
+        try {
+            SportsEquipmentFacility facility = new SportsEquipmentFacility();
+            facility.setStoreId(vo.getStoreId());
+            facility.setFacilityCategory(vo.getFacilityCategory());
+            facility.setFacilityName(vo.getFacilityName());
+            facility.setQuantity(vo.getQuantity());
+            facility.setBrand(vo.getBrand());
+            facility.setDescription(vo.getDescription());
+            facility.setDisplayInStoreDetail(vo.getDisplayInStoreDetail());
+            boolean result = facilityService.saveFacility(facility, vo.getImageList());
+            if (result) {
+                return R.success("新增成功");
+            }
+            return R.fail("新增失败");
+        } catch (Exception e) {
+            log.error("SportsEquipmentFacilityController.saveFacility异常", e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @ApiOperation("修改运动器材设施")
+    @ApiOperationSupport(order = 5)
+    @PostMapping("/update")
+    public R<Boolean> updateFacility(@RequestBody SportsEquipmentFacilityVo vo) {
+        log.info("SportsEquipmentFacilityController.updateFacility?vo={}", vo);
+        try {
+            if (vo.getId() == null) {
+                return R.fail("主键ID不能为空");
+            }
+            SportsEquipmentFacility facility = new SportsEquipmentFacility();
+            facility.setId(vo.getId());
+            facility.setStoreId(vo.getStoreId());
+            facility.setFacilityCategory(vo.getFacilityCategory());
+            facility.setFacilityName(vo.getFacilityName());
+            facility.setQuantity(vo.getQuantity());
+            facility.setBrand(vo.getBrand());
+            facility.setDescription(vo.getDescription());
+            facility.setDisplayInStoreDetail(vo.getDisplayInStoreDetail());
+            boolean result = facilityService.updateFacility(facility, vo.getImageList());
+            if (result) {
+                return R.success("修改成功");
+            }
+            return R.fail("修改失败");
+        } catch (Exception e) {
+            log.error("SportsEquipmentFacilityController.updateFacility异常", e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @ApiOperation("删除运动器材设施")
+    @ApiOperationSupport(order = 6)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "id", value = "主键ID", dataType = "int", paramType = "query", required = true)
+    })
+    @PostMapping("/delete")
+    public R<Boolean> deleteFacility(@RequestParam Integer id) {
+        log.info("SportsEquipmentFacilityController.deleteFacility?id={}", id);
+        try {
+            boolean result = facilityService.deleteFacility(id);
+            if (result) {
+                return R.success("删除成功");
+            }
+            return R.fail("删除失败");
+        } catch (Exception e) {
+            log.error("SportsEquipmentFacilityController.deleteFacility异常", e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @ApiOperation("查询指定店铺按分类汇总的设备信息(包含设备数量、图片列表和设备列表)(用户端)")
+    @ApiOperationSupport(order = 7)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "storeId", value = "门店ID", dataType = "int", paramType = "query", required = true)
+    })
+    @GetMapping("/categorySummary")
+    public R<List<SportsEquipmentFacilityCategoryVo>> getCategorySummary(@RequestParam Integer storeId) {
+        log.info("SportsEquipmentFacilityController.getCategorySummary?storeId={}", storeId);
+        return R.data(facilityService.getCategorySummary(storeId));
+    }
+
+
+
+    @ApiOperation("根据ID查询运动器材设施详情(商户端)")
+    @ApiOperationSupport(order = 8)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "id", value = "主键ID", dataType = "int", paramType = "query", required = true)
+    })
+    @GetMapping("/storeDetail")
+    public R<SportsEquipmentFacilityVo> getStoreDetail(@RequestParam Integer id) {
+        log.info("SportsEquipmentFacilityController.getStoreDetail?id={}", id);
+        SportsEquipmentFacilityVo vo = facilityService.getStoreDetail(id);
+        if (vo == null) {
+            return R.fail("数据不存在");
+        }
+        return R.data(vo);
+    }
+
+    @ApiOperation("查询指定店铺按分类汇总的设备信息(包含设备数量、图片列表和设备列表)(商户端)")
+    @ApiOperationSupport(order = 9)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "storeId", value = "门店ID", dataType = "int", paramType = "query", required = true)
+    })
+    @GetMapping("/storeCategorySummary")
+    public R<List<SportsEquipmentFacilityCategoryVo>> getstoreCategorySummary(@RequestParam Integer storeId) {
+        log.info("SportsEquipmentFacilityController.getstoreCategorySummary?storeId={}", storeId);
+        return R.data(facilityService.getstoreCategorySummary(storeId));
+    }
+}
+

+ 448 - 7
alien-store/src/main/java/shop/alien/store/controller/StoreInfoController.java

@@ -1,17 +1,23 @@
 package shop.alien.store.controller;
 
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import io.swagger.annotations.*;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.collections4.CollectionUtils;
 import org.springframework.http.ResponseEntity;
 import org.springframework.web.bind.annotation.*;
 import org.springframework.web.multipart.MultipartRequest;
 import shop.alien.entity.result.R;
+import shop.alien.entity.second.SecondGoodsCategory;
 import shop.alien.entity.store.*;
 import shop.alien.entity.store.dto.StoreInfoDto;
 import shop.alien.entity.store.vo.*;
+import shop.alien.entity.storePlatform.StoreLicenseHistory;
+import shop.alien.mapper.*;
+import shop.alien.mapper.storePlantform.StoreLicenseHistoryMapper;
 import shop.alien.mapper.StoreCommentSummaryInterestMapper;
 import shop.alien.mapper.TagsMainMapper;
 import shop.alien.mapper.WebAuditMapper;
@@ -19,9 +25,7 @@ import shop.alien.store.config.BaseRedisService;
 import shop.alien.store.service.StoreInfoService;
 
 import java.io.IOException;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
+import java.util.*;
 
 /**
  * 二期-门店信息Controller
@@ -48,6 +52,13 @@ public class StoreInfoController {
 
     private final StoreCommentSummaryInterestMapper storeCommentSummaryInterestMapper;
 
+    /** 商户证照历史记录数据访问对象 */
+    private final StoreLicenseHistoryMapper licenseHistoryMapper;
+
+    private final StoreImgMapper storeImgMapper;
+
+    private final StoreCommentMapper storeCommentMapper;
+
     @ApiOperation("获取所有门店")
     @ApiOperationSupport(order = 1)
     @GetMapping("/getAll")
@@ -218,6 +229,68 @@ public class StoreInfoController {
     }
 
     /**
+     * 新web-分页查询店铺信息
+     *
+     * @param pageNum      页码
+     * @param pageSize     页容
+     * @param storeName    门店名称
+     * @param storeContact 联系人
+     * @param storePhone   门店电话
+     * @param storeType    门店类型
+     * @return IPage<StoreInfoVo>
+     */
+    @ApiOperation("新中台web-分页查询店铺信息")
+    @ApiOperationSupport(order = 20)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "pageNum", value = "页数", dataType = "int", paramType = "query", required = true),
+            @ApiImplicitParam(name = "pageSize", value = "页容", dataType = "int", paramType = "query", required = true),
+            @ApiImplicitParam(name = "storeName", value = "门店名称", dataType = "String", paramType = "query"),
+            @ApiImplicitParam(name = "storeContact", value = "门店联系人", dataType = "String", paramType = "query"),
+            @ApiImplicitParam(name = "storePhone", value = "门店电话", dataType = "String", paramType = "query"),
+            @ApiImplicitParam(name = "storeType", value = "门店类型", dataType = "String", paramType = "query"),
+            @ApiImplicitParam(name = "expiredState", value = "过期状态", dataType = "String", paramType = "query"),
+            @ApiImplicitParam(name = "businessSection", value = "经营板块", dataType = "String", paramType = "query"),
+            @ApiImplicitParam(name = "storeApplicationStatus", value = "门店审核状态", dataType = "String", paramType = "query"),
+            @ApiImplicitParam(name = "jingdu", value = "jingdu", dataType = "String", paramType = "query"),
+            @ApiImplicitParam(name = "weidu", value = "weidu", dataType = "String", paramType = "query"),
+            @ApiImplicitParam(name = "renewContractStatus", value = "门店续签合同状态", dataType = "String", paramType = "query"),
+            @ApiImplicitParam(name = "foodLicenceStatus", value = "门店经营许可证状态", dataType = "String", paramType = "query"),
+            @ApiImplicitParam(name = "foodLicenceWhetherExpiredStatus", value = "门店经营许可证是否过期状态", dataType = "String", paramType = "query")
+    })
+    @GetMapping("/getNewStorePage")
+    public R<IPage<StoreInfoVo>> getNewStorePage(
+            @RequestParam(defaultValue = "1") int pageNum,
+            @RequestParam(defaultValue = "10") int pageSize,
+            @RequestParam(required = false) String storeName,
+            @RequestParam(required = false) String storeContact,
+            @RequestParam(required = false) String id,
+            @RequestParam(required = false) String storePhone,
+            @RequestParam(required = false) String storeType,
+            @RequestParam(required = false) String expiredState,
+            @RequestParam(required = false) String storeApplicationStatus,
+            @RequestParam(required = false) String businessSection,
+            @RequestParam(required = false) String storeStatus,
+            @RequestParam(required = false) String jingdu,
+            @RequestParam(required = false) String weidu,
+            @RequestParam(required = false) String renewContractStatus,
+            @RequestParam(required = false) String foodLicenceStatus,
+            @RequestParam(required = false) String foodLicenceWhetherExpiredStatus)
+    {
+        log.info("StoreInfoController.getStoresPage?pageNum={}," +
+                        "pageSize={},storeName={},storeContact={}," +
+                        "id={},storePhone={},storeType={},expiredState={}," +
+                        "storeApplicationStatus={},storeStatus={}," +
+                        "businessSection={},jingdu={},weidu={}," +
+                        "renewContractStatus={},foodLicenceStatus={}" +
+                        ",foodLicenceWhetherExpiredStatus={}",
+                pageNum, pageSize, storeName, storeContact,
+                id, storePhone, storeType, expiredState, storeApplicationStatus,
+                storeStatus, businessSection, jingdu, weidu, renewContractStatus,
+                foodLicenceStatus, foodLicenceWhetherExpiredStatus);
+        return R.data(storeInfoService.getNewStorePage(pageNum, pageSize, storeName, storeContact, id, storePhone, storeType, expiredState, storeApplicationStatus, storeStatus, businessSection, jingdu, weidu, renewContractStatus, foodLicenceStatus, foodLicenceWhetherExpiredStatus));
+    }
+
+    /**
      * web-重置门店密码
      *
      * @param storeId 门店id
@@ -615,6 +688,16 @@ public class StoreInfoController {
                 storeInfo.setFoodLicenceStatus(storeInfoDto.getFoodLicenceStatus());
                 boolean flag = storeInfoService.updateById(storeInfo);
                 if (flag) {
+                    // 审核拒绝时修改提交记录
+                    LambdaUpdateWrapper<StoreLicenseHistory> wrapper = new LambdaUpdateWrapper<>();
+                    wrapper.eq(StoreLicenseHistory::getStoreId, storeInfo.getId());
+                    wrapper.eq(StoreLicenseHistory::getLicenseStatus, 2);
+                    wrapper.eq(StoreLicenseHistory::getLicenseExecuteStatus, 2);
+                    wrapper.set(StoreLicenseHistory::getLicenseExecuteStatus, 3);
+                    wrapper.set(StoreLicenseHistory::getReasonRefusal, storeInfoDto.getFoodLicenceReason());
+                    wrapper.set(StoreLicenseHistory::getDeleteFlag, 1);
+                    licenseHistoryMapper.update(null, wrapper);
+
                     //待审核状态变为已审核
                     WebAudit webAudit = webAuditMapper.selectOne(new LambdaQueryWrapper<WebAudit>().eq(WebAudit::getStoreInfoId, storeInfo.getId()).eq(WebAudit::getDeleteFlag, 0).eq(WebAudit::getType, "7"));
                     if (webAudit != null) {
@@ -649,13 +732,44 @@ public class StoreInfoController {
 
     @ApiOperation(value = "AI服务-门店评价标签")
     @GetMapping("/getStoreEvaluateTags")
-    @ApiImplicitParams({@ApiImplicitParam(name = "storeId", value = "门店id", dataType = "int", paramType = "query")})
-    public R<List<TagsMainVo>> getStoreEvaluateTags(int storeId) {
-        log.info("StoreInfoController.getStoreEvaluateTags?storeId={}", storeId);
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "storeId", value = "门店id", dataType = "int", paramType = "query", required = true)
+    })
+    public R<Map<String,Object>> getStoreEvaluateTags(@RequestParam("storeId") int storeId) {
+        log.info("StoreInfoController.getStoreEvaluateTags?storeId={}, tagType={}", storeId);
+        Map<String,Object> map = new HashMap<>();
         List<TagsMainVo> voList = tagsMainMapper.getStoreEvaluateTags(storeId);
-        return R.data(voList);
+        if(voList !=null && voList.size()>0){
+            StoreCommentVo storeComment = storeCommentMapper.getCommentOneInfo(storeId);
+            if(storeComment!=null){
+                if(storeComment.getImgId()!=null){
+                    List<String> imgIds = Arrays.asList(storeComment.getImgId().split(","));
+                    List<StoreImg> storeImgs = storeImgMapper.selectList(new LambdaQueryWrapper<StoreImg>().in(StoreImg::getId, imgIds));
+                    storeComment.setImgList(storeImgs);
+                }
+                LambdaQueryWrapper<StoreComment> lambdaQueryWrapper1 = new LambdaQueryWrapper<>();
+                lambdaQueryWrapper1.eq(StoreComment :: getStoreId, storeId);
+                lambdaQueryWrapper1.eq(StoreComment :: getBusinessType, 5);
+                lambdaQueryWrapper1.eq(StoreComment :: getDeleteFlag, 0);
+                Integer commitCount = storeCommentMapper.selectCount(lambdaQueryWrapper1);
+                map.put("commitCount",commitCount);
+            }
+            map.put("tag",voList);
+            map.put("evaluate",storeComment);
+        }
+        return R.data(map);
     }
 
+    @ApiOperation(value = "AI服务-门店评价标签下评价")
+    @GetMapping("/getStoreEvaluateTagsList")
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "mainId", value = "标签id", dataType = "int", paramType = "query", required = true)
+    })
+    public R<List<StoreCommentVo>> getStoreEvaluateTagsList(@RequestParam("mainId") int mainId) {
+        log.info("StoreInfoController.getStoreEvaluateTagsList?mainId={}", mainId);
+        List<StoreCommentVo> list = tagsMainMapper.getstoreCommentList(mainId);
+        return R.data(list);
+    }
     @ApiOperation(value = "AI服务-门店趣味信息")
     @GetMapping("/getStoreInterestInfo")
     @ApiImplicitParams({
@@ -667,4 +781,331 @@ public class StoreInfoController {
         return R.data(list);
     }
 
+    @ApiOperation(value = "AI服务-店铺审核")
+    @ApiOperationSupport(order = 15)
+    @PostMapping("/aiApproveStoreInfo")
+    public R<Boolean> aiApproveStoreInfo(@RequestBody AiApproveStoreInfo aiApproveStoreInfo) {
+        log.info("StoreInfoController.aiApproveStoreInfo");
+        try {
+            storeInfoService.aiApproveStoreInfo(aiApproveStoreInfo);
+            return R.success("店铺审核完成");
+        } catch (Exception e) {
+            log.error("AI服务-店铺审核异常", e);
+            return R.fail("店铺审核失败:" + e.getMessage());
+        }
+    }
+
+    @ApiOperation(value = "更多推荐(用户端)")
+    @GetMapping("/getMoreRecommendedStores")
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "lon", value = "经度", dataType = "double", paramType = "query", required = true),
+            @ApiImplicitParam(name = "lat", value = "纬度", dataType = "double", paramType = "query", required = true),
+            @ApiImplicitParam(name = "businessSection", value = "经营板块", dataType = "String", paramType = "query", required = true),
+            @ApiImplicitParam(name = "businessTypes", value = "经营分类", dataType = "String", paramType = "query"),
+            @ApiImplicitParam(name = "businessClassify", value = "分类", dataType = "String", paramType = "query")
+    })
+    public R<List<StoreInfoVo>> getMoreRecommendedStores(Double lon , Double lat, String businessSection, String businessTypes, String businessClassify) {
+        log.info("StoreInfoController.getMoreRecommendedStores?lon={},lat={},businessSection={},businessTypes={},businessClassify={}", lon,lat, businessSection, businessTypes, businessClassify);
+        return R.data(storeInfoService.getMoreRecommendedStores(lon,lat, businessSection, businessTypes, businessClassify));
+
+    }
+
+    @ApiOperation(value = "AI服务-门头识别")
+    @ApiOperationSupport(order = 16)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "storeUserId", value = "门店用户id", dataType = "String", paramType = "query", required = true),
+            @ApiImplicitParam(name = "imageUrl", value = "图片URL", dataType = "String", paramType = "query", required = true)
+    })
+    @GetMapping("/getStoreOcrData")
+    public R<Map<String, Object>> getStoreOcrData(@RequestParam("storeUserId") String storeUserId,
+                                                  @RequestParam("imageUrl") String imageUrl) {
+        log.info("StoreInfoController.getStoreOcrData?storeId={},imageUrl={}", storeUserId, imageUrl);
+        if (storeUserId == null || storeUserId.trim().isEmpty() || imageUrl == null || imageUrl.trim().isEmpty()) {
+            return R.fail("门店ID与图片URL不能为空");
+        }
+        Map<String, Object> ocrData = null;
+        try {
+            ocrData = storeInfoService.getStoreOcrData(storeUserId, imageUrl);
+        } catch (Exception e) {
+            return R.fail("未查询到OCR识别数据");
+        }
+        return R.data(ocrData);
+    }
+
+    @ApiOperation(value = "获取店铺详情(用户端)")
+    @ApiOperationSupport(order = 17)
+    @GetMapping("/getClientStoreDetail")
+    @ResponseBody
+    public R getClientStoreDetail(@RequestParam("id") String id,
+                                  @RequestParam(value = "userId", required = false) String userId,
+                                  @RequestParam(value = "jingdu", required = false) String jingdu,
+                                  @RequestParam(value = "weidu", required = false) String weidu) {
+        log.info("StoreInfoController.getClientStoreDetail?id={},userId={},jingdu={},weidu={}", id, jingdu, weidu);
+        StoreInfoVo storeDetail = storeInfoService.getClientStoreDetail(id, userId, jingdu, weidu);
+        return R.data(storeDetail);
+    }
+
+
+    /**
+     * 查询四种类型店铺(酒吧、ktv、洗浴汗蒸、按摩足浴)并按距离筛选
+     *
+     * @param lon         经度
+     * @param lat         纬度
+     * @param distance    距离范围(单位:公里)
+     * @param sortType    排序模式(1:智能排序,2:好评优先,3:距离优先)
+     * @param businessType 店铺类型(KTV=3、洗浴汗蒸=4、按摩足浴=5,酒吧需要查询字典表),可选,如果指定则只查询该类型
+     * @param pageNum     页码
+     * @param pageSize    页容
+     * @return R<IPage<StoreInfoVo>> 分页的门店信息列表
+     */
+    @ApiOperation("查询四种类型店铺(酒吧、ktv、洗浴汗蒸、按摩足浴)并按距离筛选")
+    @ApiOperationSupport(order = 16)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "lon", value = "经度", dataType = "Double", paramType = "query", required = true),
+            @ApiImplicitParam(name = "lat", value = "纬度", dataType = "Double", paramType = "query", required = true),
+            @ApiImplicitParam(name = "distance", value = "距离范围(单位:公里)", dataType = "Double", paramType = "query"),
+            @ApiImplicitParam(name = "sortType", value = "排序模式(1:智能排序,2:好评优先,3:距离优先)", dataType = "int", paramType = "query"),
+            @ApiImplicitParam(name = "businessType", value = "店铺类型(KTV=3、洗浴汗蒸=4、按摩足浴=5,丽人美发=6,运动健身=7,酒吧=11)", dataType = "int", paramType = "query"),
+            @ApiImplicitParam(name = "categoryId", value = "字典表id,根据此id查询经营板块、经营种类、分类并匹配店铺", dataType = "Integer", paramType = "query"),
+            @ApiImplicitParam(name = "pageNum", value = "页码", dataType = "int", paramType = "query", required = true),
+            @ApiImplicitParam(name = "pageSize", value = "页容", dataType = "int", paramType = "query", required = true)
+    })
+    @GetMapping("/getSpecialTypeStoresByDistance")
+    public R<IPage<StoreInfoVo>> getSpecialTypeStoresByDistance(
+            @RequestParam("lon") Double lon,
+            @RequestParam("lat") Double lat,
+            @RequestParam(value = "distance", required = false) Double distance,
+            @RequestParam(value = "sortType", required = false, defaultValue = "1") Integer sortType,
+            @RequestParam(value = "businessType", required = false) Integer businessType,
+            @RequestParam(value = "categoryId", required = false) Integer categoryId,
+            @RequestParam(defaultValue = "1") int pageNum,
+            @RequestParam(defaultValue = "10") int pageSize) {
+        log.info("StoreInfoController.getSpecialTypeStoresByDistance?lon={},lat={},distance={},sortType={},businessType={},categoryId={},pageNum={},pageSize={}",
+                lon, lat, distance, sortType, businessType, categoryId, pageNum, pageSize);
+
+        try {
+            // 参数校验:经度范围 [-180, 180],纬度范围 [-90, 90]
+            if (lon == null || lat == null) {
+                return R.fail("经纬度参数不能为空");
+            }
+
+            // 智能检测:如果参数可能传反了(lat超出范围但lon在范围内),给出提示
+            if (Math.abs(lat) > 90 && Math.abs(lon) <= 90) {
+                return R.fail(String.format("参数可能传反了!当前值: lon=%s, lat=%s。经度范围: [-180, 180],纬度范围: [-90, 90]。请检查参数顺序。", lon, lat));
+            }
+
+            if (lon < -180 || lon > 180) {
+                return R.fail("经度参数超出有效范围 [-180, 180],当前值: " + lon);
+            }
+            if (lat < -90 || lat > 90) {
+                return R.fail("纬度参数超出有效范围 [-90, 90],当前值: " + lat);
+            }
+
+            // 调用服务层获取筛选结果
+            IPage<StoreInfoVo> result = storeInfoService.getSpecialTypeStoresByDistance(lon, lat, distance, sortType, businessType, categoryId, pageNum, pageSize);
+
+            // 记录响应日志
+            log.info("四种类型店铺距离筛选响应 - 总记录数: {}, 当前页: {}, 页大小: {}",
+                    result.getTotal(), result.getCurrent(), result.getSize());
+
+            return R.data(result);
+        } catch (IllegalArgumentException e) {
+            log.warn("四种类型店铺距离筛选参数错误: {}", e.getMessage());
+            return R.fail(e.getMessage());
+        } catch (Exception e) {
+            log.error("四种类型店铺距离筛选异常", e);
+            return R.fail("筛选失败,请稍后重试");
+        }
+    }
+
+
+
+    /**
+     * 获取休闲娱乐分类数据(主分类和子分类)
+     * 根据图片需求,返回层级结构的分类数据
+     *
+     * @return R<List<StoreDictionaryVo>> 分类列表,包含主分类和子分类
+     */
+    @ApiOperation("获取休闲娱乐分类数据(主分类和子分类)")
+    @ApiOperationSupport(order = 18)
+    @GetMapping("/getLeisureEntertainmentCategories")
+    public R<List<StoreDictionaryVo>> getLeisureEntertainmentCategories() {
+        log.info("StoreInfoController.getLeisureEntertainmentCategories");
+        try {
+            List<StoreDictionaryVo> result = storeInfoService.getLeisureEntertainmentCategories();
+            return R.data(result);
+        } catch (Exception e) {
+            log.error("获取休闲娱乐分类数据异常", e);
+            return R.fail("获取分类数据失败,请稍后重试");
+        }
+    }
+
+    /**
+     * 获取休闲娱乐分类数据(主分类和子分类)
+     * 根据图片需求,返回层级结构的分类数据
+     *getAllBusinessSection
+     * @return R<List<StoreDictionary>> 分类列表,包含主分类和子分类
+     */
+
+
+    @ApiOperation("获取所有经营板块以及子类")
+    @ApiOperationSupport(order = 18)
+    @GetMapping("/getAllBusinessSection")
+    public R<List<StoreDictionary>> queryBusinessSectionTree(
+            @ApiParam(value = "一级分类dictId,可选,如果传入则只返回该一级分类下的二级和三级分类", required = false)
+            @RequestParam(required = false) String businessSection) {
+        log.info("platformBusinessSection.queryBusinessSectionTree, businessSection={}", businessSection);
+
+        try {
+            List<StoreDictionary> result = storeInfoService.getAllBusinessSection(businessSection);
+            return R.data(result);
+        }catch (Exception e){
+            log.error("获取休闲娱乐分类数据异常", e);
+            return R.fail("获取分类数据失败,请稍后重试");
+        }
+
+    }
+
+
+
+    /**
+     * 你可能还喜欢(推荐店铺)
+     * 根据一级分类、二级分类、三级分类进行店铺筛选
+     * 筛选顺序:先三级,然后二级,最后一级
+     *
+     * @param businessSection 一级分类(经营板块)
+     * @param businessTypes 二级分类(经营种类)
+     * @param businessClassify 三级分类(分类)
+     * @param lon 经度
+     * @param lat 纬度
+     * @return R<List<StoreInfoVo>> 店铺信息列表
+     */
+    @ApiOperation("你可能还喜欢(推荐店铺)")
+    @ApiOperationSupport(order = 19)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "businessSection", value = "一级分类(经营板块)", dataType = "String", paramType = "query"),
+            @ApiImplicitParam(name = "businessTypes", value = "二级分类(经营种类)", dataType = "String", paramType = "query"),
+            @ApiImplicitParam(name = "businessClassify", value = "三级分类(分类)", dataType = "String", paramType = "query"),
+            @ApiImplicitParam(name = "lon", value = "经度", dataType = "Double", paramType = "query"),
+            @ApiImplicitParam(name = "lat", value = "纬度", dataType = "Double", paramType = "query")
+    })
+    @GetMapping("/getRecommendedStores")
+    public R<List<StoreInfoVo>> getRecommendedStores(
+            @RequestParam(value = "businessSection", required = true) String businessSection,
+            @RequestParam(value = "businessTypes", required = false) String businessTypes,
+            @RequestParam(value = "businessClassify", required = false) String businessClassify,
+            @RequestParam(value = "lon", required = false) Double lon,
+            @RequestParam(value = "lat", required = false) Double lat) {
+        log.info("StoreInfoController.getRecommendedStores?businessSection={},businessTypes={},businessClassify={},lon={},lat={}",
+                businessSection, businessTypes, businessClassify, lon, lat);
+        try {
+            List<StoreInfoVo> result = storeInfoService.getRecommendedStores(businessSection, businessTypes, businessClassify, lon, lat);
+            return R.data(result);
+        } catch (Exception e) {
+            log.error("获取推荐店铺异常", e);
+            return R.fail("获取推荐店铺失败,请稍后重试");
+        }
+    }
+
+
+
+    @ApiOperation(value = "获取banner图详情")
+    @ApiOperationSupport(order = 7)
+    @GetMapping("/getBannerUrlInfo")
+    @ResponseBody
+    public R getBannerUrlInfo(@RequestParam("storeId") String storeId, @RequestParam("businessId") Integer businessId) {
+        log.info("StoreInfoController.getBannerUrlInfo?storeId={},businessId{}", storeId, businessId);
+
+        try {
+
+            return R.data(storeInfoService.getBannerUrlInfo(storeId, businessId));
+
+        } catch (Exception e) {
+
+            log.error("StoreInfoController.getBannerUrlInfo ERROR Msg={}", e.getMessage(), e);
+
+            return R.fail("查询失败");
+        }
+
+    }
+
+
+    /**
+     * 查询两种类型店铺(丽人美发、运动健身)并按距离筛选
+     *
+     * @param lon         经度
+     * @param lat         纬度
+     * @param distance    距离范围(单位:公里)
+     * @param sortType    排序模式(1:智能排序,2:好评优先,3:距离优先)
+     * @param businessType 店铺类型(KTV=3、洗浴汗蒸=4、按摩足浴=5,丽人美发=6,运动健身=7),可选,如果指定则只查询该类型
+     * @param pageNum     页码
+     * @param pageSize    页容
+     * @return R<IPage<StoreInfoVo>> 分页的门店信息列表
+     */
+    @ApiOperation("查询四种类型店铺(丽人美发、运动健身)并按距离筛选")
+    @ApiOperationSupport(order = 16)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "lon", value = "经度", dataType = "Double", paramType = "query", required = true),
+            @ApiImplicitParam(name = "lat", value = "纬度", dataType = "Double", paramType = "query", required = true),
+            @ApiImplicitParam(name = "distance", value = "距离范围(单位:公里)", dataType = "Double", paramType = "query"),
+            @ApiImplicitParam(name = "sortType", value = "排序模式(1:智能排序,2:好评优先,3:距离优先)", dataType = "int", paramType = "query"),
+            @ApiImplicitParam(name = "businessType", value = "店铺类型(KTV=3、洗浴汗蒸=4、按摩足浴=5,丽人美发=6,运动健身=7,酒吧=11)", dataType = "int", paramType = "query"),
+            @ApiImplicitParam(name = "categoryId", value = "字典表id,根据此id查询经营板块、经营种类、分类并匹配店铺", dataType = "Integer", paramType = "query"),
+            @ApiImplicitParam(name = "pageNum", value = "页码", dataType = "int", paramType = "query", required = true),
+            @ApiImplicitParam(name = "pageSize", value = "页容", dataType = "int", paramType = "query", required = true)
+    })
+    @GetMapping("/getLifeServicesByDistance")
+    public R<IPage<StoreInfoVo>> getLifeServicesByDistance(
+            @RequestParam("lon") Double lon,
+            @RequestParam("lat") Double lat,
+            @RequestParam(value = "distance", required = false) Double distance,
+            @RequestParam(value = "sortType", required = false, defaultValue = "1") Integer sortType,
+            @RequestParam(value = "businessType", required = false) Integer businessType,
+            @RequestParam(value = "categoryId", required = false) Integer categoryId,
+            @RequestParam(defaultValue = "1") int pageNum,
+            @RequestParam(defaultValue = "10") int pageSize) {
+        log.info("StoreInfoController.getLifeServicesByDistance?lon={},lat={},distance={},sortType={},businessType={},categoryId={},pageNum={},pageSize={}",
+                lon, lat, distance, sortType, businessType, categoryId, pageNum, pageSize);
+
+        try {
+            // 参数校验:经度范围 [-180, 180],纬度范围 [-90, 90]
+            if (lon == null || lat == null) {
+                return R.fail("经纬度参数不能为空");
+            }
+
+            // 智能检测:如果参数可能传反了(lat超出范围但lon在范围内),给出提示
+            if (Math.abs(lat) > 90 && Math.abs(lon) <= 90) {
+                return R.fail(String.format("参数可能传反了!当前值: lon=%s, lat=%s。经度范围: [-180, 180],纬度范围: [-90, 90]。请检查参数顺序。", lon, lat));
+            }
+
+            if (lon < -180 || lon > 180) {
+                return R.fail("经度参数超出有效范围 [-180, 180],当前值: " + lon);
+            }
+            if (lat < -90 || lat > 90) {
+                return R.fail("纬度参数超出有效范围 [-90, 90],当前值: " + lat);
+            }
+
+            // 调用服务层获取筛选结果
+            IPage<StoreInfoVo> result = storeInfoService.getLifeServicesByDistance(lon, lat, distance, sortType, businessType, categoryId, pageNum, pageSize);
+
+            // 记录响应日志
+            log.info("四种类型店铺距离筛选响应 - 总记录数: {}, 当前页: {}, 页大小: {}",
+                    result.getTotal(), result.getCurrent(), result.getSize());
+
+            return R.data(result);
+        } catch (IllegalArgumentException e) {
+            log.warn("四种类型店铺距离筛选参数错误: {}", e.getMessage());
+            return R.fail(e.getMessage());
+        } catch (Exception e) {
+            log.error("四种类型店铺距离筛选异常", e);
+            return R.fail("筛选失败,请稍后重试");
+        }
+    }
+
+
+
+
+
+
+
 }

+ 84 - 5
alien-store/src/main/java/shop/alien/store/controller/StoreMenuController.java

@@ -10,6 +10,7 @@ import shop.alien.entity.store.vo.StoreMenuVo;
 import shop.alien.store.service.StoreMenuService;
 
 import java.util.List;
+import java.util.Map;
 
 /**
  * 二期-门店菜单Controller
@@ -32,13 +33,19 @@ public class StoreMenuController {
     @ApiOperationSupport(order = 1)
     @ApiImplicitParams({
             @ApiImplicitParam(name = "storeId", value = "门店id", dataType = "Integer", paramType = "query", required = true),
-            @ApiImplicitParam(name = "dishType", value = "菜品类型, 0:菜单, 1:推荐", dataType = "Integer", paramType = "query"),
-            @ApiImplicitParam(name = "phoneId", value = "消息标识", dataType = "Integer", paramType = "query")
+            @ApiImplicitParam(name = "dishType", value = "菜品类型, 0:非推荐, 1:推荐", dataType = "Integer", paramType = "query"),
+            @ApiImplicitParam(name = "phoneId", value = "用户手机号", dataType = "String", paramType = "query"),
+            @ApiImplicitParam(name = "dishMenuType", value = "菜单类型:1-菜单,2-酒水", dataType = "String", paramType = "query")
     })
     @GetMapping("/getMenuByStoreId")
-    public R<List<StoreMenuVo>> getMenuByStoreId(Integer storeId, Integer dishType, String phoneId) {
-        log.info("StoreRecommendController.getByStoreId?id={}&dishType={}&phoneId={}", storeId, dishType, phoneId);
-        return R.data(storeMenuService.getStoreMenu(storeId, dishType, phoneId));
+    public R<List<StoreMenuVo>> getMenuByStoreId(
+            @RequestParam(value = "storeId", required = true) Integer storeId,
+            @RequestParam(value = "dishType", required = false) Integer dishType,
+            @RequestParam(value = "phoneId", required = false) String phoneId,
+            @RequestParam(value = "dishMenuType", required = false) Integer dishMenuType) {
+        log.info("StoreMenuController.getMenuByStoreId?storeId={}&dishType={}&phoneId={}&dishMenuType={}",
+                storeId, dishType, phoneId, dishMenuType);
+        return R.data(storeMenuService.getStoreMenu(storeId, dishType, phoneId, dishMenuType));
     }
 
     @ApiOperation("新增或修改门店菜单")
@@ -116,4 +123,76 @@ public class StoreMenuController {
         log.info("StoreRecommendController.getMenuLikeStatus?userId={}&menuId={}", userId, menuId);
         return R.data(storeMenuService.getMenuLikeStatus(userId, menuId));
     }
+
+
+    /**
+     * 获取菜单(用户端)
+     * <p>
+     * 根据门店ID获取菜单列表,支持按菜品类型、菜单类型筛选,并可查询用户点赞状态
+     * 返回菜单列表以及按四种组合统计的数量:
+     * 1. menu_all: dish_menu_type=1(菜单),dish_type=0(全部)
+     * 2. menu_recommend: dish_menu_type=1(菜单),dish_type=1(推荐)
+     * 3. drink_all: dish_menu_type=2(酒水),dish_type=0(全部)
+     * 4. drink_recommend: dish_menu_type=2(酒水),dish_type=1(推荐)
+     * </p>
+     *
+     * @param storeId      门店ID,必填,必须大于0
+     * @param dishType     菜品类型,可选,0:非推荐, 1:推荐
+     * @param phoneId      用户手机号,可选,用于查询点赞状态
+     * @param dishMenuType 菜单类型,可选,1-菜单,2-酒水
+     * @return 统一返回结果,包含菜单列表和统计信息(list:菜单列表,count:按四种组合统计的数量)
+     * @throws IllegalArgumentException 当门店ID为空或小于等于0时抛出
+     */
+    @ApiOperation("获取菜单(用户端)")
+    @ApiOperationSupport(order = 9)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "storeId", value = "门店id", dataType = "Integer", paramType = "query", required = true),
+            @ApiImplicitParam(name = "dishType", value = "菜品类型, 0:(推荐+非推荐), 1:推荐", dataType = "Integer", paramType = "query"),
+            @ApiImplicitParam(name = "phoneId", value = "用户手机号", dataType = "String", paramType = "query"),
+            @ApiImplicitParam(name = "dishMenuType", value = "菜单类型:1-菜单,2-酒水", dataType = "Integer", paramType = "query")
+    })
+    @GetMapping("/getClientMenuByStoreId")
+    public R<Map<String, Object>> getClientMenuByStoreId(
+            @RequestParam(value = "storeId", required = true) Integer storeId,
+            @RequestParam(value = "dishType", required = false) Integer dishType,
+            @RequestParam(value = "phoneId", required = true) String phoneId,
+            @RequestParam(value = "dishMenuType", required = false) Integer dishMenuType) {
+        // 记录请求入参
+        log.info("获取用户端菜单,入参:storeId={}, dishType={}, phoneId={}, dishMenuType={}",
+                storeId, dishType, phoneId, dishMenuType);
+
+        // 参数校验
+        if (storeId == null || storeId <= 0) {
+            log.warn("获取用户端菜单失败,门店ID无效:storeId={}", storeId);
+            return R.fail("门店ID不能为空且必须大于0");
+        }
+
+        try {
+            // 调用服务层获取菜单列表和统计信息
+            Map<String, Object> result = storeMenuService.getClientMenuByStoreId(
+                    storeId, dishType, phoneId, dishMenuType);
+
+            // 记录返回结果
+            @SuppressWarnings("unchecked")
+            List<StoreMenuVo> menuList = (List<StoreMenuVo>) result.get("list");
+            log.info("获取用户端菜单成功,门店ID:{},返回菜单数量:{},统计信息:{}",
+                    storeId, menuList != null ? menuList.size() : 0, result.get("count"));
+
+            return R.data(result);
+        } catch (Exception e) {
+            log.error("获取用户端菜单异常,门店ID:{},异常信息:{}", storeId, e.getMessage(), e);
+            return R.fail("获取菜单失败:" + e.getMessage());
+        }
+    }
+
+    @ApiOperation("获取门店菜单详情(用户端)")
+    @ApiOperationSupport(order = 4)
+    @ApiImplicitParams({@ApiImplicitParam(name = "id", value = "菜单ID", dataType = "Integer", paramType = "query"),
+            @ApiImplicitParam(name = "phoneId", value = "用户手机号", dataType = "String", paramType = "query")})
+    @GetMapping("/getClientMenuInfoById")
+    public R<StoreMenuVo> getClientMenuInfoById(@RequestParam(value = "id", required = true) Integer id,
+                                                @RequestParam(value = "phoneId", required = true) String phoneId) {
+        log.info("StoreRecommendController.getClientMenuInfoById?id={},phoneId={}", id, phoneId);
+        return R.data(storeMenuService.getClientMenuInfoById(id, phoneId));
+    }
 }

+ 90 - 4
alien-store/src/main/java/shop/alien/store/controller/StoreOfficialAlbumController.java

@@ -1,9 +1,6 @@
 package shop.alien.store.controller;
 
-import io.swagger.annotations.Api;
-import io.swagger.annotations.ApiOperation;
-import io.swagger.annotations.ApiOperationSupport;
-import io.swagger.annotations.ApiSort;
+import io.swagger.annotations.*;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.web.bind.annotation.*;
@@ -11,6 +8,8 @@ import shop.alien.entity.result.R;
 import shop.alien.entity.store.StoreOfficialAlbum;
 import shop.alien.entity.store.vo.StoreOfficialAlbumVo;
 import shop.alien.store.service.StoreOfficialAlbumService;
+import shop.alien.entity.store.vo.StoreAlbumNameVo;
+import shop.alien.entity.store.vo.StoreOfficialAlbumImgVo;
 
 import java.util.List;
 
@@ -59,4 +58,91 @@ public class StoreOfficialAlbumController {
         }
         return R.fail("失败");
     }
+    /**
+     * 获取官方相册图片列表(客户端)
+     * <p>
+     * 根据门店ID和相册名称查询官方相册中的图片列表
+     * 查询条件:imgType = 2(官方相册),通过 business_id 关联到 store_official_album 表,按 albumName 筛选
+     * </p>
+     *
+     * @param storeId   门店ID,必填,必须大于0
+     * @param albumName 相册名称,可选。例如:酒水、餐食、环境、全部等。当为null或空字符串时,查询全部
+     * @return 统一返回结果,包含图片列表和总数
+     * @throws IllegalArgumentException 当门店ID为空或小于等于0时抛出
+     */
+    @ApiOperation("获取官方相册图片列表(客户端)")
+    @ApiOperationSupport(order = 4)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "storeId", value = "门店ID", dataType = "Integer", paramType = "query", required = true),
+            @ApiImplicitParam(name = "albumName", value = "相册名称,例如:酒水、餐食、环境、全部等。不传或传空字符串时查询全部", dataType = "String", paramType = "query")
+    })
+    @GetMapping("/getOfficialAlbumImgList")
+    public R<StoreOfficialAlbumImgVo> getOfficialAlbumImgList(
+            @RequestParam(value = "storeId", required = true) Integer storeId,
+            @RequestParam(value = "albumName", required = false) String albumName) {
+        // 记录请求入参
+        log.info("获取官方相册图片列表,入参:storeId={}, albumName={}", storeId, albumName);
+
+        // 参数校验
+        if (storeId == null || storeId <= 0) {
+            log.warn("获取官方相册图片列表失败,门店ID无效:storeId={}", storeId);
+            return R.fail("门店ID不能为空且必须大于0");
+        }
+
+        try {
+            // 调用服务层获取图片列表
+            StoreOfficialAlbumImgVo result = storeOfficialAlbumService.getOfficialAlbumImgList(storeId, albumName);
+
+            // 记录返回结果
+            log.info("获取官方相册图片列表成功,门店ID:{},相册名称:{},返回图片数量:{}",
+                    storeId, albumName, result.getTotalCount());
+
+            return R.data(result);
+        } catch (Exception e) {
+            log.error("获取官方相册图片列表异常,门店ID:{},异常信息:{}", storeId, e.getMessage(), e);
+            return R.fail("获取图片列表失败:" + e.getMessage());
+        }
+    }
+
+    /**
+     * 获取官方相册名称列表(客户端)
+     * <p>
+     * 根据门店ID查询所有可用的相册名称列表,用于前端展示筛选选项
+     * 返回每个相册名称及其对应的图片数量
+     * </p>
+     *
+     * @param storeId 门店ID,必填,必须大于0
+     * @return 统一返回结果,包含相册名称列表和每个相册的图片数量
+     * @throws IllegalArgumentException 当门店ID为空或小于等于0时抛出
+     */
+    @ApiOperation("获取官方相册名称列表(客户端)")
+    @ApiOperationSupport(order = 5)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "storeId", value = "门店ID", dataType = "Integer", paramType = "query", required = true)
+    })
+    @GetMapping("/getAlbumNameList")
+    public R<List<StoreAlbumNameVo>> getAlbumNameList(
+            @RequestParam(value = "storeId", required = true) Integer storeId) {
+        // 记录请求入参
+        log.info("获取官方相册名称列表,入参:storeId={}", storeId);
+
+        // 参数校验
+        if (storeId == null || storeId <= 0) {
+            log.warn("获取官方相册名称列表失败,门店ID无效:storeId={}", storeId);
+            return R.fail("门店ID不能为空且必须大于0");
+        }
+
+        try {
+            // 调用服务层获取相册名称列表
+            List<StoreAlbumNameVo> result = storeOfficialAlbumService.getAlbumNameList(storeId);
+
+            // 记录返回结果
+            log.info("获取官方相册名称列表成功,门店ID:{},返回相册数量:{}", storeId, result.size());
+
+            return R.data(result);
+        } catch (Exception e) {
+            log.error("获取官方相册名称列表异常,门店ID:{},异常信息:{}", storeId, e.getMessage(), e);
+            return R.fail("获取相册名称列表失败:" + e.getMessage());
+        }
+    }
 }

+ 87 - 0
alien-store/src/main/java/shop/alien/store/controller/StorePersonnelController.java

@@ -0,0 +1,87 @@
+package shop.alien.store.controller;
+
+import io.swagger.annotations.*;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.*;
+import shop.alien.entity.result.R;
+import shop.alien.entity.store.StorePersonnel;
+import shop.alien.entity.store.vo.StorePersonnelVo;
+import shop.alien.store.service.StorePersonnelService;
+
+import java.util.List;
+
+/**
+ * 店铺人员Controller
+ *
+ * @author system
+ * @since 2025-01-15
+ */
+@Slf4j
+@Api(tags = {"店铺人员"})
+@ApiSort(5)
+@CrossOrigin
+@RestController
+@RequestMapping("/personnel")
+@RequiredArgsConstructor
+public class StorePersonnelController {
+
+    private final StorePersonnelService storePersonnelService;
+
+    @ApiOperation("获取店铺人员列表")
+    @ApiOperationSupport(order = 1)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "storeId", value = "门店id", dataType = "Integer", paramType = "query", required = true)
+    })
+    @GetMapping("/getPersonnelList")
+    public R<List<StorePersonnelVo>> getPersonnelList(
+            @RequestParam(value = "storeId", required = true) Integer storeId) {
+        log.info("StorePersonnelController.getPersonnelList?storeId={}", storeId);
+        return R.data(storePersonnelService.getStorePersonnelList(storeId));
+    }
+
+    @ApiOperation("获取人员详情")
+    @ApiOperationSupport(order = 2)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "id", value = "人员id", dataType = "Integer", paramType = "query", required = true)
+    })
+    @GetMapping("/getPersonnelInfo")
+    public R<StorePersonnelVo> getPersonnelInfo(
+            @RequestParam(value = "id", required = true) Integer id) {
+        log.info("StorePersonnelController.getPersonnelInfo?id={}", id);
+        return R.data(storePersonnelService.getPersonnelInfo(id));
+    }
+
+    @ApiOperation("新增或修改店铺人员")
+    @ApiOperationSupport(order = 3)
+    @PostMapping("/saveOrUpdate")
+    public R<String> saveOrUpdate(@RequestBody StorePersonnelVo storePersonnelVo) {
+        log.info("StorePersonnelController.saveOrUpdate?storePersonnelVo={}", storePersonnelVo);
+        return storePersonnelService.saveOrUpdatePersonnel(storePersonnelVo);
+    }
+
+    @ApiOperation("删除店铺人员")
+    @ApiOperationSupport(order = 4)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "ids", value = "人员id列表", dataType = "List<Integer>", paramType = "query", required = true)
+    })
+    @GetMapping("/delete")
+    public R<String> delete(@RequestParam(value = "ids") List<Integer> ids) {
+        log.info("StorePersonnelController.delete?ids={}", ids);
+        return storePersonnelService.deletePersonnel(ids);
+    }
+
+    @ApiOperation("保存人员排序")
+    @ApiOperationSupport(order = 5)
+    @PostMapping("/savePersonnelSort")
+    public R<Boolean> savePersonnelSort(@RequestBody List<StorePersonnel> storePersonnelList) {
+        log.info("StorePersonnelController.savePersonnelSort?storePersonnelList={}", storePersonnelList);
+        Boolean flag = storePersonnelService.savePersonnelSort(storePersonnelList);
+        if (flag) {
+            return R.success("已更新排序");
+        } else {
+            return R.fail("排序失败");
+        }
+    }
+}
+

+ 48 - 0
alien-store/src/main/java/shop/alien/store/controller/StoreStaffConfigController.java

@@ -78,4 +78,52 @@ public class StoreStaffConfigController {
         return R.data(s);
     }
 
+    /**
+     * 员工列表查询接口(用户端)
+     *
+     * @param page    分页页数
+     * @param size    分页条数
+     * @param storeId 店铺ID
+     * @param status  员工状态
+     * @return 员工列表
+     */
+    @ApiOperation("员工列表查询(用户端)")
+    @ApiOperationSupport(order = 5)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "page", value = "分页页数", dataType = "Integer", paramType = "query", required = false),
+            @ApiImplicitParam(name = "size", value = "分页条数", dataType = "Integer", paramType = "query", required = false),
+            @ApiImplicitParam(name = "storeId", value = "店铺ID", dataType = "Integer", paramType = "query", required = true),
+            @ApiImplicitParam(name = "status", value = "员工状态(0-待审核 1-审核通过 2-审核拒绝)", dataType = "String", paramType = "query", required = false)
+    })
+    @GetMapping("/queryStaffList")
+    public R<IPage<StoreStaffConfig>> queryStaffList(
+            @RequestParam(value = "page", defaultValue = "1") Integer page,
+            @RequestParam(value = "size", defaultValue = "100") Integer size,
+            @RequestParam(value = "storeId", required = true) Integer storeId,
+            @RequestParam(value = "status", required = false) String status) {
+        log.info("StoreStaffConfigController.queryStaffList?page={}&size={}&storeId={}&status={}", page, size, storeId, status);
+        IPage<StoreStaffConfig> result = storeStaffConfigService.queryStaffList(page, size, storeId, status);
+        return R.data(result);
+    }
+
+    /**
+     * 员工详情查询接口(用户端)
+     *
+     * @param id 员工主键id
+     * @return 员工详情
+     */
+    @ApiOperation("员工详情查询(用户端)")
+    @ApiOperationSupport(order = 6)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "id", value = "员工主键id", dataType = "Integer", paramType = "query", required = true)
+    })
+    @GetMapping("/queryStaffDetail")
+    public R<StoreStaffConfig> queryStaffDetail(@RequestParam(value = "id", required = true) Integer id) {
+        log.info("StoreStaffConfigController.queryStaffDetail?id={}", id);
+        StoreStaffConfig result = storeStaffConfigService.queryStaffDetail(id);
+        if (result == null) {
+            return R.fail("员工不存在");
+        }
+        return R.data(result);
+    }
 }

+ 82 - 0
alien-store/src/main/java/shop/alien/store/service/BathFacilityServiceService.java

@@ -0,0 +1,82 @@
+package shop.alien.store.service;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.service.IService;
+import shop.alien.entity.store.BathFacilityService;
+import shop.alien.entity.store.vo.BathFacilityServiceCategoryVo;
+import shop.alien.entity.store.vo.BathFacilityServiceVo;
+
+import java.util.List;
+
+/**
+ * 洗浴设施及服务服务接口
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+public interface BathFacilityServiceService extends IService<BathFacilityService> {
+
+    /**
+     * 分页查询洗浴设施及服务列表
+     *
+     * @param pageNum          页码
+     * @param pageSize         页大小
+     * @param storeId          门店ID
+     * @param facilityCategory 设施分类
+     * @return IPage<BathFacilityServiceVo>
+     */
+    IPage<BathFacilityServiceVo> getPageList(Integer pageNum, Integer pageSize, Integer storeId, Integer facilityCategory);
+
+    /**
+     * 查询洗浴设施及服务列表
+     *
+     * @param storeId          门店ID
+     * @param facilityCategory 设施分类
+     * @return List<BathFacilityServiceVo>
+     */
+    List<BathFacilityServiceVo> getList(Integer storeId, Integer facilityCategory);
+
+    /**
+     * 根据ID查询详情
+     *
+     * @param id 主键ID
+     * @return BathFacilityServiceVo
+     */
+    BathFacilityServiceVo getDetail(Integer id);
+
+    /**
+     * 新增洗浴设施及服务
+     *
+     * @param facilityService 设施服务信息
+     * @param imageList       图片列表
+     * @return boolean
+     */
+    boolean saveFacilityService(BathFacilityService facilityService, List<String> imageList);
+
+    /**
+     * 修改洗浴设施及服务
+     *
+     * @param facilityService 设施服务信息
+     * @param imageList       图片列表
+     * @return boolean
+     */
+    boolean updateFacilityService(BathFacilityService facilityService, List<String> imageList);
+
+    /**
+     * 删除洗浴设施及服务
+     *
+     * @param id 主键ID
+     * @return boolean
+     */
+    boolean deleteFacilityService(Integer id);
+
+    /**
+     * 查询指定店铺按分类汇总的设备信息
+     * 包含每个分类的设备数量和设备列表(包括设备信息和图片)
+     *
+     * @param storeId 门店ID
+     * @return List<BathFacilityServiceCategoryVo>
+     */
+    List<BathFacilityServiceCategoryVo> getCategorySummary(Integer storeId);
+}
+

+ 49 - 44
alien-store/src/main/java/shop/alien/store/service/LifeCommentService.java

@@ -54,6 +54,7 @@ public class LifeCommentService {
         updateWrapper.eq(LifeLikeRecord::getType, type);
         updateWrapper.eq(LifeLikeRecord::getDianzanId, userId);
         updateWrapper.eq(LifeLikeRecord::getHuifuId, huifuId);
+        updateWrapper.eq(LifeLikeRecord::getDeleteFlag, 0);
         List<LifeLikeRecord> record = lifeLikeRecordMapper.selectList(updateWrapper);
         if (CollectionUtils.isEmpty(record)) {
             LifeLikeRecord lifeLikeRecord = new LifeLikeRecord();
@@ -62,39 +63,40 @@ public class LifeCommentService {
             lifeLikeRecord.setDianzanId(userId);
             lifeLikeRecord.setType(type);
             lifeLikeRecordMapper.insert(lifeLikeRecord);
-        }
-        if ("1".equals(type)) {
-            LambdaUpdateWrapper<StoreComment> lambdaUpdateWrapper = new LambdaUpdateWrapper<>();
-            lambdaUpdateWrapper.eq(StoreComment::getId, huifuId);
-            lambdaUpdateWrapper.setSql("like_count = like_count + 1");
-            return storeCommentMapper.update(null, lambdaUpdateWrapper);
-        } else if ("2".equals(type)) {
-            LambdaUpdateWrapper<LifeUserDynamics> lambdaUpdateWrapper = new LambdaUpdateWrapper<>();
-            lambdaUpdateWrapper.eq(LifeUserDynamics::getId, huifuId);
-            lambdaUpdateWrapper.setSql("dianzan_count = dianzan_count + 1");
-            int num = lifeUserDynamicsMapper.update(null, lambdaUpdateWrapper);
-            if (num > 0) insertNotice(userId, huifuId, type);
-            return num;
-        } else if ("3".equals(type)) {
-            LambdaUpdateWrapper<LifeActivity> lambdaUpdateWrapper = new LambdaUpdateWrapper<>();
-            lambdaUpdateWrapper.eq(LifeActivity::getId, huifuId);
-            lambdaUpdateWrapper.setSql("dianzan_count = dianzan_count + 1");
-            return lifeActivityMapper.update(null, lambdaUpdateWrapper);
-        } else if ("4".equals(type)) {
-            LambdaUpdateWrapper<StoreMenu> lambdaUpdateWrapper = new LambdaUpdateWrapper<>();
-            lambdaUpdateWrapper.eq(StoreMenu::getId, huifuId);
-            lambdaUpdateWrapper.setSql("like_count = like_count + 1");
-            return storeRecommendMapper.update(null, lambdaUpdateWrapper);
-        } else if ("5".equals(type)) {
-            LambdaUpdateWrapper<StoreClockIn> lambdaUpdateWrapper = new LambdaUpdateWrapper<>();
-            lambdaUpdateWrapper.eq(StoreClockIn::getId, huifuId);
-            lambdaUpdateWrapper.setSql("like_count = like_count + 1");
-            return storeClockInMapper.update(null, lambdaUpdateWrapper);
-        } else if ("6".equals(type)) {
-            LambdaUpdateWrapper<SecondGoods> lambdaUpdateWrapper = new LambdaUpdateWrapper<>();
-            lambdaUpdateWrapper.eq(SecondGoods::getId, huifuId);
-            lambdaUpdateWrapper.setSql("like_count = like_count + 1");
-            return secondGoodsMapper.update(null, lambdaUpdateWrapper);
+
+            if ("1".equals(type)) {
+                LambdaUpdateWrapper<StoreComment> lambdaUpdateWrapper = new LambdaUpdateWrapper<>();
+                lambdaUpdateWrapper.eq(StoreComment::getId, huifuId);
+                lambdaUpdateWrapper.setSql("like_count = like_count + 1");
+                return storeCommentMapper.update(null, lambdaUpdateWrapper);
+            } else if ("2".equals(type)) {
+                LambdaUpdateWrapper<LifeUserDynamics> lambdaUpdateWrapper = new LambdaUpdateWrapper<>();
+                lambdaUpdateWrapper.eq(LifeUserDynamics::getId, huifuId);
+                lambdaUpdateWrapper.setSql("dianzan_count = dianzan_count + 1");
+                int num = lifeUserDynamicsMapper.update(null, lambdaUpdateWrapper);
+                if (num > 0) insertNotice(userId, huifuId, type);
+                return num;
+            } else if ("3".equals(type)) {
+                LambdaUpdateWrapper<LifeActivity> lambdaUpdateWrapper = new LambdaUpdateWrapper<>();
+                lambdaUpdateWrapper.eq(LifeActivity::getId, huifuId);
+                lambdaUpdateWrapper.setSql("dianzan_count = dianzan_count + 1");
+                return lifeActivityMapper.update(null, lambdaUpdateWrapper);
+            } else if ("4".equals(type)) {
+                LambdaUpdateWrapper<StoreMenu> lambdaUpdateWrapper = new LambdaUpdateWrapper<>();
+                lambdaUpdateWrapper.eq(StoreMenu::getId, huifuId);
+                lambdaUpdateWrapper.setSql("like_count = like_count + 1");
+                return storeRecommendMapper.update(null, lambdaUpdateWrapper);
+            } else if ("5".equals(type)) {
+                LambdaUpdateWrapper<StoreClockIn> lambdaUpdateWrapper = new LambdaUpdateWrapper<>();
+                lambdaUpdateWrapper.eq(StoreClockIn::getId, huifuId);
+                lambdaUpdateWrapper.setSql("like_count = like_count + 1");
+                return storeClockInMapper.update(null, lambdaUpdateWrapper);
+            } else if ("6".equals(type)) {
+                LambdaUpdateWrapper<SecondGoods> lambdaUpdateWrapper = new LambdaUpdateWrapper<>();
+                lambdaUpdateWrapper.eq(SecondGoods::getId, huifuId);
+                lambdaUpdateWrapper.setSql("like_count = like_count + 1");
+                return secondGoodsMapper.update(null, lambdaUpdateWrapper);
+            }
         }
         return 0;
     }
@@ -117,17 +119,19 @@ public class LifeCommentService {
     }
 
     public int cancelLike(String userId, String huifuId, String type) {
-        LambdaUpdateWrapper<LifeLikeRecord> updateWrapper = new LambdaUpdateWrapper<>();
-        updateWrapper.eq(LifeLikeRecord::getDianzanId, userId);
-        updateWrapper.eq(LifeLikeRecord::getHuifuId, huifuId);
-        List<LifeLikeRecord> record = lifeLikeRecordMapper.selectList(updateWrapper);
+        LambdaQueryWrapper<LifeLikeRecord> lifeLikeRecordLambdaQueryWrapper = new LambdaQueryWrapper<>();
+        lifeLikeRecordLambdaQueryWrapper.eq(LifeLikeRecord::getDianzanId, userId);
+        lifeLikeRecordLambdaQueryWrapper.eq(LifeLikeRecord::getHuifuId, huifuId);
+        lifeLikeRecordLambdaQueryWrapper.eq(LifeLikeRecord::getType, type);
+        lifeLikeRecordLambdaQueryWrapper.eq(LifeLikeRecord::getDeleteFlag, 0);
+        List<LifeLikeRecord> record = lifeLikeRecordMapper.selectList(lifeLikeRecordLambdaQueryWrapper);
         if (!CollectionUtils.isEmpty(record)) {
-            LambdaUpdateWrapper<LifeLikeRecord> lambdaUpdateWrapper = new LambdaUpdateWrapper<>();
-            lambdaUpdateWrapper.eq(LifeLikeRecord::getHuifuId, huifuId);
-            lambdaUpdateWrapper.eq(LifeLikeRecord::getDianzanId, userId);
-            lambdaUpdateWrapper.eq(LifeLikeRecord::getType, type);
-            lifeLikeRecordMapper.delete(lambdaUpdateWrapper);
-        }
+            LambdaUpdateWrapper<LifeLikeRecord> lifeLikeRecordLambdaUpdateWrapper = new LambdaUpdateWrapper<>();
+            lifeLikeRecordLambdaUpdateWrapper.eq(LifeLikeRecord::getHuifuId, huifuId);
+            lifeLikeRecordLambdaUpdateWrapper.eq(LifeLikeRecord::getDianzanId, userId);
+            lifeLikeRecordLambdaUpdateWrapper.eq(LifeLikeRecord::getType, type);
+            lifeLikeRecordMapper.delete(lifeLikeRecordLambdaUpdateWrapper);
+
         if ("1".equals(type)) {
             LambdaUpdateWrapper<StoreComment> lambdaUpdateWrapper = new LambdaUpdateWrapper<>();
             lambdaUpdateWrapper.eq(StoreComment::getId, huifuId).gt(StoreComment::getLikeCount, 0);
@@ -159,8 +163,9 @@ public class LifeCommentService {
             lambdaUpdateWrapper.setSql("like_count = like_count - 1");
             return secondGoodsMapper.update(null, lambdaUpdateWrapper);
         }
-        return 0;
     }
+        return 0;
+}
 
     public int addOrUpdateStore(LifeComment store) {
         if (StringUtils.isEmpty(store.getId())) {

+ 7 - 0
alien-store/src/main/java/shop/alien/store/service/LifeCouponService.java

@@ -56,4 +56,11 @@ public interface LifeCouponService extends IService<LifeCoupon> {
      * @return
      */
     R<String> orderVerify(String orderCode);
+
+    /**
+     * 获取代金券详情(包含商铺信息)
+     * @param id 代金券ID
+     * @return LifeCouponVo
+     */
+    shop.alien.entity.store.vo.LifeCouponVo getNewCouponDetail(String id);
 }

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

@@ -250,7 +250,7 @@ public class LifeUserDynamicsService extends ServiceImpl<LifeUserDynamicsMapper,
             // 查询拉黑的对象
             LambdaQueryWrapper<LifeBlacklist> myLifeBlacklistWrapper = new LambdaQueryWrapper<>();
 
-            if (null != myLifeUser.getId()) {
+            if (null != myLifeUser && null != myLifeUser.getId()) {
                 myLifeBlacklistWrapper.eq(LifeBlacklist::getBlockerType, "2");
                 myLifeBlacklistWrapper.eq(LifeBlacklist::getBlockerId, myLifeUser.getId());
             } else {

+ 86 - 0
alien-store/src/main/java/shop/alien/store/service/SportsEquipmentFacilityService.java

@@ -0,0 +1,86 @@
+package shop.alien.store.service;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.service.IService;
+import shop.alien.entity.store.SportsEquipmentFacility;
+import shop.alien.entity.store.vo.SportsEquipmentFacilityCategoryVo;
+import shop.alien.entity.store.vo.SportsEquipmentFacilityVo;
+
+import java.util.List;
+
+/**
+ * 运动器材设施服务接口
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+public interface SportsEquipmentFacilityService extends IService<SportsEquipmentFacility> {
+
+    /**
+     * 分页查询运动器材设施列表
+     *
+     * @param pageNum          页码
+     * @param pageSize         页大小
+     * @param storeId           门店ID
+     * @param facilityCategory 设施分类
+     * @return IPage<SportsEquipmentFacilityVo>
+     */
+    IPage<SportsEquipmentFacilityVo> getPageList(Integer pageNum, Integer pageSize, Integer storeId, Integer facilityCategory);
+
+    /**
+     * 查询运动器材设施列表
+     *
+     * @param storeId           门店ID
+     * @param facilityCategory 设施分类
+     * @return List<SportsEquipmentFacilityVo>
+     */
+    List<SportsEquipmentFacilityVo> getList(Integer storeId, Integer facilityCategory);
+
+    /**
+     * 根据ID查询详情
+     *
+     * @param id 主键ID
+     * @return SportsEquipmentFacilityVo
+     */
+    SportsEquipmentFacilityVo getDetail(Integer id);
+
+    /**
+     * 新增运动器材设施
+     *
+     * @param facility 设施信息
+     * @param imageList 图片列表
+     * @return boolean
+     */
+    boolean saveFacility(SportsEquipmentFacility facility, List<String> imageList);
+
+    /**
+     * 修改运动器材设施
+     *
+     * @param facility 设施信息
+     * @param imageList 图片列表
+     * @return boolean
+     */
+    boolean updateFacility(SportsEquipmentFacility facility, List<String> imageList);
+
+    /**
+     * 删除运动器材设施
+     *
+     * @param id 主键ID
+     * @return boolean
+     */
+    boolean deleteFacility(Integer id);
+
+    /**
+     * 查询指定店铺按分类汇总的设备信息
+     * 包含每个分类的设备数量、图片列表和设备列表
+     *
+     * @param storeId 门店ID
+     * @return List<SportsEquipmentFacilityCategoryVo>
+     */
+    List<SportsEquipmentFacilityCategoryVo> getCategorySummary(Integer storeId);
+
+    SportsEquipmentFacilityVo getStoreDetail(Integer id);
+
+    List<SportsEquipmentFacilityCategoryVo> getstoreCategorySummary(Integer storeId);
+}
+

+ 138 - 4
alien-store/src/main/java/shop/alien/store/service/StoreInfoService.java

@@ -4,10 +4,7 @@ import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.service.IService;
 import org.springframework.http.ResponseEntity;
 import org.springframework.web.multipart.MultipartRequest;
-import shop.alien.entity.store.StoreBusinessInfo;
-import shop.alien.entity.store.StoreImg;
-import shop.alien.entity.store.StoreInfo;
-import shop.alien.entity.store.StoreInfoDraft;
+import shop.alien.entity.store.*;
 import shop.alien.entity.store.dto.StoreInfoDto;
 import shop.alien.entity.store.vo.*;
 
@@ -65,6 +62,19 @@ public interface StoreInfoService extends IService<StoreInfo> {
     IPage<StoreInfoVo> getStorePage(int page, int size, String storeName, String storeContact, String id, String storePhone, String storeType, String expiredState, String storeApplicationStatus, String storeStatus, String businessSection, String jingdu, String weidu, String renewContractStatus, String foodLicenceStatus, String foodLicenceWhetherExpiredStatus);
 
     /**
+     * web-分页查询店铺信息
+     *
+     * @param page         页码
+     * @param size         页容
+     * @param storeName    门店名称
+     * @param storeContact 联系人
+     * @param storePhone   门店电话
+     * @param storeType    门店类型
+     * @return IPage<StoreInfoVo>
+     */
+    IPage<StoreInfoVo> getNewStorePage(int page, int size, String storeName, String storeContact, String id, String storePhone, String storeType, String expiredState, String storeApplicationStatus, String storeStatus, String businessSection, String jingdu, String weidu, String renewContractStatus, String foodLicenceStatus, String foodLicenceWhetherExpiredStatus);
+
+    /**
      * web-重置门店密码
      *
      * @param storeId 门店id
@@ -279,4 +289,128 @@ public interface StoreInfoService extends IService<StoreInfo> {
      *
      */
     int foodLicenceType(int id);
+
+    /**
+     * 根据门店及图片地址查询最新OCR识别数据
+     *
+     * @param storeUserId  门店ID
+     * @param imageUrl 图片URL
+     * @return OCR识别记录
+     */
+    Map<String, Object> getStoreOcrData(String storeUserId, String imageUrl);
+
+    void aiApproveStoreInfo(AiApproveStoreInfo aiApproveStoreInfo);
+
+
+    /**
+     * 查询四种类型店铺(酒吧、ktv、洗浴汗蒸、按摩足浴)并按距离筛选
+     *
+     * @param lon         经度
+     * @param lat         纬度
+     * @param distance    距离范围(单位:公里)
+     * @param sortType    排序模式(1:智能排序,2:好评优先,3:距离优先)
+     * @param businessType 店铺类型(KTV=3、洗浴汗蒸=4、按摩足浴=5,酒吧需要查询字典表),可选
+     * @param categoryId 字典表id,根据此id查询经营板块、经营种类、分类并匹配店铺,可选
+     * @param pageNum     页码
+     * @param pageSize    页容
+     * @return IPage<StoreInfoVo> 分页的门店信息列表
+     */
+    IPage<StoreInfoVo> getSpecialTypeStoresByDistance(Double lon, Double lat, Double distance, Integer sortType, Integer businessType, Integer categoryId, int pageNum, int pageSize);
+
+
+
+    /**
+     * 获取休闲娱乐分类数据(主分类和子分类)
+     * 返回层级结构的分类数据,包含主分类和对应的子分类
+     *
+     * @return List<StoreDictionaryVo> 分类列表,包含主分类和子分类
+     */
+    List<StoreDictionaryVo> getLeisureEntertainmentCategories();
+
+
+    List<StoreDictionary> getAllBusinessSection();
+
+    /**
+     * 根据一级分类获取该分类下的二级和三级分类
+     *
+     * @param businessSection 一级分类的dictId(可选,如果为空则返回所有分类)
+     * @return List<StoreDictionary> 分类树形结构
+     */
+    List<StoreDictionary> getAllBusinessSection(String businessSection);
+
+
+    /**
+     * 你可能还喜欢(推荐店铺)
+     * 根据一级分类、二级分类、三级分类进行店铺筛选
+     * 筛选顺序:先三级,然后二级,最后一级
+     *
+     * @param businessSection 一级分类(经营板块)
+     * @param businessTypes 二级分类(经营种类)
+     * @param businessClassify 三级分类(分类)
+     * @param lon 经度
+     * @param lat 纬度
+     * @return List<StoreInfoVo> 店铺信息列表
+     */
+    List<StoreInfoVo> getRecommendedStores(String businessSection, String businessTypes, String businessClassify, Double lon, Double lat);
+
+
+
+
+
+    /**
+     * 查询两种类型店铺(丽人美发、运动健身)并按距离筛选
+     *
+     * @param lon         经度
+     * @param lat         纬度
+     * @param distance    距离范围(单位:公里)
+     * @param sortType    排序模式(1:智能排序,2:好评优先,3:距离优先)
+     * @param businessType 店铺类型(KTV=3、洗浴汗蒸=4、按摩足浴=5,丽人美发=6,运动健身=7),可选,如果指定则只查询该类型
+     * @param pageNum     页码
+     * @param pageSize    页容
+     * @return R<IPage<StoreInfoVo>> 分页的门店信息列表
+     */
+    IPage<StoreInfoVo> getLifeServicesByDistance(Double lon, Double lat, Double distance, Integer sortType, Integer businessType, Integer categoryId, int pageNum, int pageSize);
+
+    /**
+     * 获取活动banner图
+     */
+    List<StoreImg> getBannerUrl (String storeId);
+    /**
+     * 获取活动详情banner图
+     */
+
+    List<StoreImg> getBannerUrlInfo(String storeId, Integer businessId);
+
+    /**
+     * web-分页查询店铺信息
+     *
+
+     * @return IPage<StoreInfoVo>
+     */
+    List<StoreInfoVo> getMoreRecommendedStores(Double lon , Double lat, String businessSection, String businessTypes, String businessClassify);
+
+
+    /**
+     * web端查询门店明细
+     */
+    StoreInfoVo getClientStoreDetail(String storeId, String userId, String jingdu, String weidu);
+
+    /**
+     * 获取门店代金券
+     */
+    List<LifeCouponVo> getStoreCouponList(String storeId);
+
+    /**
+     * 中台web端查询门店明细
+     */
+    StoreInfoVo getNewStoreDetail(String storeId);
+
+    /**
+     * 新中台web端修改门店及门店用户
+     *
+     * @return ResponseEntity
+     */
+    StoreInfoVo editNewStoreInfo(StoreInfoDto storeInfoDto);
+
+
 }

+ 32 - 4
alien-store/src/main/java/shop/alien/store/service/StoreMenuService.java

@@ -19,12 +19,14 @@ public interface StoreMenuService extends IService<StoreMenu> {
     /**
      * 获取门店菜单
      *
-     * @param storeId  门店id
-     * @param dishType 菜品类型, 0:菜单, 1:推荐
-     * @param phoneId  消息标识
+     * @param storeId      门店id
+     * @param dishType     菜品类型, 0:非推荐, 1:推荐
+     * @param phoneId      用户手机号
+     * @param dishMenuType 菜单类型:1-菜单,2-酒水
      * @return list
      */
-    List<StoreMenuVo> getStoreMenu(Integer storeId, Integer dishType, String phoneId);
+    List<StoreMenuVo> getStoreMenu(Integer storeId, Integer dishType, String phoneId, Integer dishMenuType);
+
 
     /**
      * 获取菜品详情
@@ -76,5 +78,31 @@ public interface StoreMenuService extends IService<StoreMenu> {
      * @return boolean
      */
     boolean getMenuLikeStatus(String userId, Integer menuId);
+
+
+    /**
+     * 获取门店菜单(客户端)
+     * <p>
+     * 根据门店ID查询菜单列表,支持按菜品类型和菜单类型筛选。
+     * 当查询推荐菜且提供用户手机号时,会批量查询并设置用户的点赞状态。
+     * 最终结果按排序字段升序排列。
+     * </p>
+     *
+     * @param storeId      门店ID,必填,必须大于0
+     * @param dishType     菜品类型,可选,0:非推荐, 1:推荐。当为0时,查询条件中不包含菜品类型限制
+     * @param phoneId      用户手机号,可选,用于查询用户对推荐菜的点赞状态
+     * @param dishMenuType 菜单类型,可选,1-菜单,2-酒水
+     * @return Map包含菜单列表和统计信息:list(菜单列表)、count(按四种组合统计:menu_all、menu_recommend、drink_all、drink_recommend)
+     * @throws IllegalArgumentException 当门店ID为空或小于等于0时抛出
+     */
+    java.util.Map<String, Object> getClientMenuByStoreId(Integer storeId, Integer dishType, String phoneId, Integer dishMenuType);
+
+    /**
+     * 获取菜品详情
+     *
+     * @param id 菜品id
+     * @return StoreMenuVo
+     */
+    StoreMenuVo getClientMenuInfoById(Integer id, String phoneId);
 }
 

+ 25 - 1
alien-store/src/main/java/shop/alien/store/service/StoreOfficialAlbumService.java

@@ -13,4 +13,28 @@ public interface StoreOfficialAlbumService extends IService<StoreOfficialAlbum>
     StoreOfficialAlbum createOrUpdateOfficialAlbum(StoreOfficialAlbum storeOfficialAlbum);
     List<StoreOfficialAlbumVo> getOfficialAlbumList(String storeId);
     int deleteOfficialAlbum(List<StoreOfficialAlbum> storeOfficialAlbumList);
-}
+
+    /**
+     * 获取官方相册图片列表(客户端)
+     * <p>
+     * 根据门店ID和相册名称查询官方相册中的图片列表
+     * 查询条件:imgType = 2(官方相册),通过 business_id 关联到 store_official_album 表,按 albumName 筛选
+     * </p>
+     *
+     * @param storeId   门店ID,必填
+     * @param albumName 相册名称,可选。例如:酒水、餐食、环境、全部等。当为null或空字符串时,查询全部
+     * @return 图片列表和总数
+     */
+    shop.alien.entity.store.vo.StoreOfficialAlbumImgVo getOfficialAlbumImgList(Integer storeId, String albumName);
+
+    /**
+     * 获取官方相册名称列表(客户端)
+     * <p>
+     * 根据门店ID查询所有可用的相册名称列表,用于前端展示筛选选项
+     * </p>
+     *
+     * @param storeId 门店ID,必填
+     * @return 相册名称列表,包含相册名称和图片数量
+     */
+    List<shop.alien.entity.store.vo.StoreAlbumNameVo> getAlbumNameList(Integer storeId);
+}

+ 58 - 0
alien-store/src/main/java/shop/alien/store/service/StorePersonnelService.java

@@ -0,0 +1,58 @@
+package shop.alien.store.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import shop.alien.entity.result.R;
+import shop.alien.entity.store.StorePersonnel;
+import shop.alien.entity.store.vo.StorePersonnelVo;
+
+import java.util.List;
+
+/**
+ * 店铺人员服务类
+ *
+ * @author system
+ * @since 2025-01-15
+ */
+public interface StorePersonnelService extends IService<StorePersonnel> {
+
+    /**
+     * 获取店铺人员列表
+     *
+     * @param storeId 门店id
+     * @return List<StorePersonnelVo>
+     */
+    List<StorePersonnelVo> getStorePersonnelList(Integer storeId);
+
+    /**
+     * 获取人员详情
+     *
+     * @param id 人员id
+     * @return StorePersonnelVo
+     */
+    StorePersonnelVo getPersonnelInfo(Integer id);
+
+    /**
+     * 新增或修改店铺人员
+     *
+     * @param storePersonnelVo 人员信息
+     * @return R<String>
+     */
+    R<String> saveOrUpdatePersonnel(StorePersonnelVo storePersonnelVo);
+
+    /**
+     * 删除店铺人员
+     *
+     * @param ids 人员id列表
+     * @return R<String>
+     */
+    R<String> deletePersonnel(List<Integer> ids);
+
+    /**
+     * 保存人员排序
+     *
+     * @param storePersonnelList 人员列表
+     * @return Boolean
+     */
+    Boolean savePersonnelSort(List<StorePersonnel> storePersonnelList);
+}
+

+ 19 - 0
alien-store/src/main/java/shop/alien/store/service/StoreStaffConfigService.java

@@ -17,4 +17,23 @@ public interface StoreStaffConfigService {
     StoreStaffConfig getStaffConfigDeatail(Integer id);
 
     String staffConfigExport(String status) throws IOException;
+
+    /**
+     * 员工列表查询
+     *
+     * @param page    分页页数
+     * @param size    分页条数
+     * @param storeId 店铺ID
+     * @param status  员工状态
+     * @return 员工列表
+     */
+    IPage<StoreStaffConfig> queryStaffList(Integer page, Integer size, Integer storeId, String status);
+
+    /**
+     * 员工详情查询
+     *
+     * @param id 员工主键id
+     * @return 员工详情
+     */
+    StoreStaffConfig queryStaffDetail(Integer id);
 }

+ 258 - 0
alien-store/src/main/java/shop/alien/store/service/impl/BathFacilityServiceServiceImpl.java

@@ -0,0 +1,258 @@
+package shop.alien.store.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.BeanUtils;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.CollectionUtils;
+import org.springframework.util.StringUtils;
+import shop.alien.entity.store.BathFacilityService;
+import shop.alien.entity.store.StoreImg;
+import shop.alien.entity.store.vo.BathFacilityServiceCategoryVo;
+import shop.alien.entity.store.vo.BathFacilityServiceVo;
+import shop.alien.mapper.BathFacilityServiceMapper;
+import shop.alien.mapper.StoreImgMapper;
+import shop.alien.store.service.BathFacilityServiceService;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * 洗浴设施及服务服务实现类
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Slf4j
+@Service
+@RequiredArgsConstructor
+@Transactional(rollbackFor = Exception.class)
+public class BathFacilityServiceServiceImpl extends ServiceImpl<BathFacilityServiceMapper, BathFacilityService>
+        implements BathFacilityServiceService {
+
+    private final BathFacilityServiceMapper facilityServiceMapper;
+    private final StoreImgMapper storeImgMapper;
+
+    /**
+     * 设施分类名称映射
+     */
+    private static final String[] FACILITY_CATEGORY_NAMES = {"", "洗浴区", "汗蒸区", "休闲区", "餐饮区"};
+
+    /**
+     * 洗浴设施及服务图片类型
+     */
+    private static final Integer IMG_TYPE_BATH_FACILITY = 29;
+
+    @Override
+    public IPage<BathFacilityServiceVo> getPageList(Integer pageNum, Integer pageSize, Integer storeId, Integer facilityCategory) {
+        Page<BathFacilityService> page = new Page<>(pageNum, pageSize);
+        LambdaQueryWrapper<BathFacilityService> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(BathFacilityService::getStoreId, storeId);
+        if (facilityCategory != null) {
+            queryWrapper.eq(BathFacilityService::getFacilityCategory, facilityCategory);
+        }
+        queryWrapper.orderByDesc(BathFacilityService::getCreatedTime);
+        IPage<BathFacilityService> facilityServicePage = facilityServiceMapper.selectPage(page, queryWrapper);
+        return facilityServicePage.convert(this::convertToVo);
+    }
+
+    @Override
+    public List<BathFacilityServiceVo> getList(Integer storeId, Integer facilityCategory) {
+        LambdaQueryWrapper<BathFacilityService> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(BathFacilityService::getStoreId, storeId);
+        if (facilityCategory != null) {
+            queryWrapper.eq(BathFacilityService::getFacilityCategory, facilityCategory);
+        }
+        queryWrapper.orderByDesc(BathFacilityService::getCreatedTime);
+        List<BathFacilityService> facilityServiceList = facilityServiceMapper.selectList(queryWrapper);
+        return facilityServiceList.stream().map(this::convertToVo).collect(Collectors.toList());
+    }
+
+    @Override
+    public BathFacilityServiceVo getDetail(Integer id) {
+        BathFacilityService facilityService = facilityServiceMapper.selectById(id);
+        if (facilityService == null) {
+            return null;
+        }
+        return convertToVo(facilityService);
+    }
+
+    @Override
+    public boolean saveFacilityService(BathFacilityService facilityService, List<String> imageList) {
+        // 校验使用时间
+        validateUsageTime(facilityService);
+        // 保存设施服务信息
+        boolean result = this.save(facilityService);
+        if (result && !CollectionUtils.isEmpty(imageList)) {
+            // 保存图片
+            saveImages(facilityService.getId(), facilityService.getStoreId(), imageList);
+        }
+        return result;
+    }
+
+    @Override
+    public boolean updateFacilityService(BathFacilityService facilityService, List<String> imageList) {
+        // 校验使用时间
+        validateUsageTime(facilityService);
+        // 更新设施服务信息
+        boolean result = this.updateById(facilityService);
+        if (result && !CollectionUtils.isEmpty(imageList)) {
+            // 删除旧图片
+            LambdaQueryWrapper<StoreImg> deleteWrapper = new LambdaQueryWrapper<>();
+            deleteWrapper.eq(StoreImg::getBusinessId, facilityService.getId())
+                    .eq(StoreImg::getImgType, IMG_TYPE_BATH_FACILITY);
+            storeImgMapper.delete(deleteWrapper);
+            // 保存新图片
+            saveImages(facilityService.getId(), facilityService.getStoreId(), imageList);
+        }
+        return result;
+    }
+
+    @Override
+    public boolean deleteFacilityService(Integer id) {
+        // 删除图片
+        LambdaQueryWrapper<StoreImg> deleteWrapper = new LambdaQueryWrapper<>();
+        deleteWrapper.eq(StoreImg::getBusinessId, id)
+                .eq(StoreImg::getImgType, IMG_TYPE_BATH_FACILITY);
+        storeImgMapper.delete(deleteWrapper);
+        // 删除设施服务
+        return this.removeById(id);
+    }
+
+    /**
+     * 校验使用时间
+     */
+    private void validateUsageTime(BathFacilityService facilityService) {
+        if (facilityService.getUsageTimeType() == null) {
+            throw new RuntimeException("使用时间类型不能为空");
+        }
+        // 如果选择时间,需要填写开始时间和结束时间
+        if (facilityService.getUsageTimeType() == 1) {
+            if (!StringUtils.hasText(facilityService.getUsageStartTime())
+                    || !StringUtils.hasText(facilityService.getUsageEndTime())) {
+                throw new RuntimeException("选择时间时,开始时间和结束时间不能为空");
+            }
+            // 校验时间格式
+            if (!facilityService.getUsageStartTime().matches("^([0-1]?[0-9]|2[0-3]):[0-5][0-9]$")
+                    || !facilityService.getUsageEndTime().matches("^([0-1]?[0-9]|2[0-3]):[0-5][0-9]$")) {
+                throw new RuntimeException("时间格式错误,请使用HH:mm格式");
+            }
+        }
+    }
+
+    /**
+     * 保存图片
+     */
+    private void saveImages(Integer facilityServiceId, Integer storeId, List<String> imageList) {
+        for (int i = 0; i < imageList.size(); i++) {
+            StoreImg storeImg = new StoreImg();
+            storeImg.setStoreId(storeId);
+            storeImg.setImgType(IMG_TYPE_BATH_FACILITY);
+            storeImg.setBusinessId(facilityServiceId);
+            storeImg.setImgUrl(imageList.get(i));
+            storeImg.setImgSort(i + 1);
+            storeImg.setImgDescription("洗浴设施及服务图片");
+            storeImgMapper.insert(storeImg);
+        }
+    }
+
+    /**
+     * 转换为VO对象
+     */
+    private BathFacilityServiceVo convertToVo(BathFacilityService facilityService) {
+        BathFacilityServiceVo vo = new BathFacilityServiceVo();
+        BeanUtils.copyProperties(facilityService, vo);
+        // 设置分类名称
+        if (facilityService.getFacilityCategory() != null && facilityService.getFacilityCategory() > 0
+                && facilityService.getFacilityCategory() < FACILITY_CATEGORY_NAMES.length) {
+            vo.setFacilityCategoryName(FACILITY_CATEGORY_NAMES[facilityService.getFacilityCategory()]);
+        }
+        // 设置使用时间类型文本
+        if (facilityService.getUsageTimeType() != null) {
+            vo.setUsageTimeTypeText(facilityService.getUsageTimeType() == 0 ? "全天" : "选择时间");
+            // 设置使用时间范围
+            if (facilityService.getUsageTimeType() == 1
+                    && StringUtils.hasText(facilityService.getUsageStartTime())
+                    && StringUtils.hasText(facilityService.getUsageEndTime())) {
+                vo.setUsageTimeRange(facilityService.getUsageStartTime() + "-" + facilityService.getUsageEndTime());
+            }
+        }
+        // 设置显示状态文本
+        if (facilityService.getDisplayInStoreDetail() != null) {
+            vo.setDisplayInStoreDetailText(facilityService.getDisplayInStoreDetail() == 1 ? "显示" : "隐藏");
+        }
+        // 查询图片列表
+        LambdaQueryWrapper<StoreImg> imageWrapper = new LambdaQueryWrapper<>();
+        imageWrapper.eq(StoreImg::getBusinessId, facilityService.getId())
+                .eq(StoreImg::getImgType, IMG_TYPE_BATH_FACILITY);
+        imageWrapper.orderByAsc(StoreImg::getImgSort);
+        List<StoreImg> imageList = storeImgMapper.selectList(imageWrapper);
+        if (!CollectionUtils.isEmpty(imageList)) {
+            vo.setImageList(imageList.stream().map(StoreImg::getImgUrl)
+                    .collect(Collectors.toList()));
+        } else {
+            vo.setImageList(new ArrayList<>());
+        }
+        return vo;
+    }
+
+    @Override
+    public List<BathFacilityServiceCategoryVo> getCategorySummary(Integer storeId) {
+        List<BathFacilityServiceCategoryVo> result = new ArrayList<>();
+        
+        // 遍历所有分类(1:洗浴区, 2:汗蒸区, 3:休闲区, 4:餐饮区)
+        for (int category = 1; category < FACILITY_CATEGORY_NAMES.length; category++) {
+            BathFacilityServiceCategoryVo categoryVo = new BathFacilityServiceCategoryVo();
+            categoryVo.setFacilityCategory(category);
+            categoryVo.setFacilityCategoryName(FACILITY_CATEGORY_NAMES[category]);
+            
+            // 查询该分类下的设备列表(不分页)
+            LambdaQueryWrapper<BathFacilityService> facilityWrapper = new LambdaQueryWrapper<>();
+            facilityWrapper.eq(BathFacilityService::getStoreId, storeId)
+                    .eq(BathFacilityService::getFacilityCategory, category);
+            facilityWrapper.orderByDesc(BathFacilityService::getCreatedTime);
+            List<BathFacilityService> facilityServiceList = facilityServiceMapper.selectList(facilityWrapper);
+            
+            // 设置设备数量
+            categoryVo.setFacilityCount(facilityServiceList != null ? facilityServiceList.size() : 0);
+            
+            // 查询该分类的代表图片(从第一个设备的第一张图片获取)
+            if (!CollectionUtils.isEmpty(facilityServiceList)) {
+                BathFacilityService firstFacility = facilityServiceList.get(0);
+                // 查询该设备的第一张图片
+                LambdaQueryWrapper<StoreImg> imageWrapper = new LambdaQueryWrapper<>();
+                imageWrapper.eq(StoreImg::getStoreId, storeId)
+                        .eq(StoreImg::getBusinessId, firstFacility.getId())
+                        .eq(StoreImg::getImgType, IMG_TYPE_BATH_FACILITY);
+                imageWrapper.orderByAsc(StoreImg::getImgSort);
+                imageWrapper.last("LIMIT 1");
+                StoreImg firstImage = storeImgMapper.selectOne(imageWrapper);
+                if (firstImage != null) {
+                    categoryVo.setCategoryImage(firstImage.getImgUrl());
+                }
+            }
+            
+            // 转换为VO列表(包含设备信息和图片)
+            List<BathFacilityServiceVo> facilityVoList = new ArrayList<>();
+            if (!CollectionUtils.isEmpty(facilityServiceList)) {
+                for (BathFacilityService facilityService : facilityServiceList) {
+                    // 使用现有的convertToVo方法,它会自动查询图片
+                    BathFacilityServiceVo vo = convertToVo(facilityService);
+                    facilityVoList.add(vo);
+                }
+            }
+            categoryVo.setFacilityList(facilityVoList);
+            
+            result.add(categoryVo);
+        }
+        
+        return result;
+    }
+}
+

+ 5 - 0
alien-store/src/main/java/shop/alien/store/service/impl/LifeCouponServiceImpl.java

@@ -689,4 +689,9 @@ public class LifeCouponServiceImpl extends ServiceImpl<LifeCouponMapper, LifeCou
         return R.success("效验通过");
     }
 
+    @Override
+    public shop.alien.entity.store.vo.LifeCouponVo getNewCouponDetail(String id) {
+        return lifeCouponMapper.getNewCouponDetail(id);
+    }
+
 }

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

@@ -347,8 +347,10 @@ public class LifeDiscountCouponServiceImpl extends ServiceImpl<LifeDiscountCoupo
         try {
             LocalDateTime now = LocalDateTime.now();
             //根据店铺id查询该店铺的优惠券,状态是开启领取的券
-            List<LifeDiscountCoupon> lifeDiscountCoupons = lifeDiscountCouponMapper.selectList(new LambdaQueryWrapper<LifeDiscountCoupon>().eq(LifeDiscountCoupon::getStoreId, storeId).eq(LifeDiscountCoupon::getGetStatus, "1") //还有库存
-                    .ge(LifeDiscountCoupon::getEndGetDate, LocalDate.now()).orderByDesc(LifeDiscountCoupon::getCreatedTime));
+            List<LifeDiscountCoupon> lifeDiscountCoupons = lifeDiscountCouponMapper.selectList(new LambdaQueryWrapper<LifeDiscountCoupon>()
+                    .eq(LifeDiscountCoupon::getStoreId, storeId).eq(LifeDiscountCoupon::getGetStatus, "1") //还有库存
+                    .ge(LifeDiscountCoupon::getEndGetDate, LocalDate.now())
+                    .orderByDesc(LifeDiscountCoupon::getAttentionCanReceived));
             //根据优惠券列表查询该优惠券是否领取过
             for (LifeDiscountCoupon lifeDiscountCoupon : lifeDiscountCoupons) {
                 LifeDiscountCouponVo lifeDiscountCouponVo = new LifeDiscountCouponVo();

+ 307 - 0
alien-store/src/main/java/shop/alien/store/service/impl/SportsEquipmentFacilityServiceImpl.java

@@ -0,0 +1,307 @@
+package shop.alien.store.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.BeanUtils;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.CollectionUtils;
+import shop.alien.entity.store.SportsEquipmentFacility;
+import shop.alien.entity.store.StoreImg;
+import shop.alien.entity.store.vo.SportsEquipmentFacilityCategoryVo;
+import shop.alien.entity.store.vo.SportsEquipmentFacilityVo;
+import shop.alien.mapper.SportsEquipmentFacilityMapper;
+import shop.alien.mapper.StoreImgMapper;
+import shop.alien.store.service.SportsEquipmentFacilityService;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * 运动器材设施服务实现类
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Slf4j
+@Service
+@RequiredArgsConstructor
+@Transactional(rollbackFor = Exception.class)
+public class SportsEquipmentFacilityServiceImpl extends ServiceImpl<SportsEquipmentFacilityMapper, SportsEquipmentFacility>
+        implements SportsEquipmentFacilityService {
+
+    private final SportsEquipmentFacilityMapper facilityMapper;
+    private final StoreImgMapper storeImgMapper;
+
+    /**
+     * 运动器材设施图片类型
+     */
+    private static final Integer IMG_TYPE_SPORTS_EQUIPMENT = 28;
+
+    /**
+     * 设施分类名称映射
+     */
+    private static final String[] FACILITY_CATEGORY_NAMES = {"", "有氧区", "力量区", "单功能机械区"};
+
+    @Override
+    public IPage<SportsEquipmentFacilityVo> getPageList(Integer pageNum, Integer pageSize, Integer storeId, Integer facilityCategory) {
+        Page<SportsEquipmentFacility> page = new Page<>(pageNum, pageSize);
+        LambdaQueryWrapper<SportsEquipmentFacility> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(SportsEquipmentFacility::getStoreId, storeId);
+        if (facilityCategory != null) {
+            queryWrapper.eq(SportsEquipmentFacility::getFacilityCategory, facilityCategory);
+        }
+        queryWrapper.orderByDesc(SportsEquipmentFacility::getCreatedTime);
+        IPage<SportsEquipmentFacility> facilityPage = facilityMapper.selectPage(page, queryWrapper);
+        return facilityPage.convert(this::convertToVo);
+    }
+
+    @Override
+    public List<SportsEquipmentFacilityVo> getList(Integer storeId, Integer facilityCategory) {
+        LambdaQueryWrapper<SportsEquipmentFacility> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(SportsEquipmentFacility::getStoreId, storeId);
+        if (facilityCategory != null) {
+            queryWrapper.eq(SportsEquipmentFacility::getFacilityCategory, facilityCategory);
+        }
+        queryWrapper.orderByDesc(SportsEquipmentFacility::getCreatedTime);
+        List<SportsEquipmentFacility> facilityList = facilityMapper.selectList(queryWrapper);
+        return facilityList.stream().map(this::convertToVo).collect(Collectors.toList());
+    }
+
+    @Override
+    public SportsEquipmentFacilityVo getDetail(Integer id) {
+        SportsEquipmentFacility facility = facilityMapper.selectById(id);
+        if (facility == null) {
+            return null;
+        }
+        return convertToVo(facility);
+    }
+
+    @Override
+    public SportsEquipmentFacilityVo getStoreDetail(Integer id) {
+        SportsEquipmentFacility facility = facilityMapper.selectById(id);
+        if (facility == null) {
+            return null;
+        }
+        return convertToVo(facility);
+    }
+
+    @Override
+    public boolean saveFacility(SportsEquipmentFacility facility, List<String> imageList) {
+        // 校验图片数量(最多20张)
+        if (!CollectionUtils.isEmpty(imageList) && imageList.size() > 20) {
+            throw new RuntimeException("实景图片最多上传20张");
+        }
+        // 保存设施信息
+        boolean result = this.save(facility);
+        if (result && !CollectionUtils.isEmpty(imageList) && facility.getFacilityCategory() != null) {
+            // 删除该分类下的旧图片(如果存在)
+            LambdaQueryWrapper<StoreImg> deleteWrapper = new LambdaQueryWrapper<>();
+            deleteWrapper.eq(StoreImg::getStoreId, facility.getStoreId())
+                    .eq(StoreImg::getBusinessId, facility.getFacilityCategory())
+                    .eq(StoreImg::getImgType, IMG_TYPE_SPORTS_EQUIPMENT);
+            storeImgMapper.delete(deleteWrapper);
+            // 保存新图片,business_id存储facility_category
+            saveImages(facility.getFacilityCategory(), facility.getStoreId(), imageList);
+        }
+        return result;
+    }
+
+    @Override
+    public boolean updateFacility(SportsEquipmentFacility facility, List<String> imageList) {
+        // 校验图片数量(最多20张)
+        if (!CollectionUtils.isEmpty(imageList) && imageList.size() > 20) {
+            throw new RuntimeException("实景图片最多上传20张");
+        }
+        // 更新设施信息
+        boolean result = this.updateById(facility);
+        if (result && !CollectionUtils.isEmpty(imageList) && facility.getFacilityCategory() != null) {
+            // 删除该分类下的旧图片
+            LambdaQueryWrapper<StoreImg> deleteWrapper = new LambdaQueryWrapper<>();
+            deleteWrapper.eq(StoreImg::getStoreId, facility.getStoreId())
+                    .eq(StoreImg::getBusinessId, facility.getFacilityCategory())
+                    .eq(StoreImg::getImgType, IMG_TYPE_SPORTS_EQUIPMENT);
+            storeImgMapper.delete(deleteWrapper);
+            // 保存新图片,business_id存储facility_category
+            saveImages(facility.getFacilityCategory(), facility.getStoreId(), imageList);
+        }
+        return result;
+    }
+
+    @Override
+    public boolean deleteFacility(Integer id) {
+        // 注意:图片和设施没有关系,只和facility_category有关系,所以删除设施时不删除图片
+        // 删除设施
+        return this.removeById(id);
+    }
+
+    @Override
+    public List<SportsEquipmentFacilityCategoryVo> getCategorySummary(Integer storeId) {
+        List<SportsEquipmentFacilityCategoryVo> result = new ArrayList<>();
+        
+        // 遍历所有分类(1:有氧区, 2:力量区, 3:单功能机械区)
+        for (int category = 1; category < FACILITY_CATEGORY_NAMES.length; category++) {
+            SportsEquipmentFacilityCategoryVo categoryVo = new SportsEquipmentFacilityCategoryVo();
+            categoryVo.setFacilityCategory(category);
+            categoryVo.setFacilityCategoryName(FACILITY_CATEGORY_NAMES[category]);
+            
+            // 查询该分类下的设备列表(不分页)
+            LambdaQueryWrapper<SportsEquipmentFacility> facilityWrapper = new LambdaQueryWrapper<>();
+            facilityWrapper.eq(SportsEquipmentFacility::getStoreId, storeId)
+                    .eq(SportsEquipmentFacility::getFacilityCategory, category);
+            facilityWrapper.orderByDesc(SportsEquipmentFacility::getCreatedTime);
+            List<SportsEquipmentFacility> facilityList = facilityMapper.selectList(facilityWrapper);
+            
+            // 设置设备数量
+            categoryVo.setFacilityCount(facilityList != null ? facilityList.size() : 0);
+            
+            // 转换为VO列表(不包含图片,避免重复查询)
+            List<SportsEquipmentFacilityVo> facilityVoList = new ArrayList<>();
+            if (!CollectionUtils.isEmpty(facilityList)) {
+                for (SportsEquipmentFacility facility : facilityList) {
+                    SportsEquipmentFacilityVo vo = new SportsEquipmentFacilityVo();
+                    BeanUtils.copyProperties(facility, vo);
+                    // 设置分类名称
+                    vo.setFacilityCategoryName(FACILITY_CATEGORY_NAMES[category]);
+                    // 设置显示状态文本
+                    if (facility.getDisplayInStoreDetail() != null) {
+                        vo.setDisplayInStoreDetailText(facility.getDisplayInStoreDetail() == 1 ? "显示" : "隐藏");
+                    }
+                    facilityVoList.add(vo);
+                }
+            }
+            categoryVo.setFacilityList(facilityVoList);
+            
+            // 查询该分类下的图片列表
+            LambdaQueryWrapper<StoreImg> imageWrapper = new LambdaQueryWrapper<>();
+            imageWrapper.eq(StoreImg::getStoreId, storeId)
+                    .eq(StoreImg::getBusinessId, category)
+                    .eq(StoreImg::getImgType, IMG_TYPE_SPORTS_EQUIPMENT);
+            imageWrapper.orderByAsc(StoreImg::getImgSort);
+            List<StoreImg> imageList = storeImgMapper.selectList(imageWrapper);
+            if (!CollectionUtils.isEmpty(imageList)) {
+                categoryVo.setImageList(imageList.stream().map(StoreImg::getImgUrl)
+                        .collect(Collectors.toList()));
+            } else {
+                categoryVo.setImageList(new ArrayList<>());
+            }
+            
+            result.add(categoryVo);
+        }
+        
+        return result;
+    }
+
+    /**
+     * 保存图片
+     * 
+     * @param facilityCategory 设施分类
+     * @param storeId 门店ID
+     * @param imageList 图片列表
+     */
+    private void saveImages(Integer facilityCategory, Integer storeId, List<String> imageList) {
+        for (int i = 0; i < imageList.size(); i++) {
+            StoreImg storeImg = new StoreImg();
+            storeImg.setStoreId(storeId);
+            storeImg.setImgType(IMG_TYPE_SPORTS_EQUIPMENT);
+            storeImg.setBusinessId(facilityCategory); // business_id存储facility_category
+            storeImg.setImgUrl(imageList.get(i));
+            storeImg.setImgSort(i + 1);
+            storeImg.setImgDescription("运动器材设施实景图片");
+            storeImgMapper.insert(storeImg);
+        }
+    }
+
+    /**
+     * 转换为VO对象
+     */
+    private SportsEquipmentFacilityVo convertToVo(SportsEquipmentFacility facility) {
+        SportsEquipmentFacilityVo vo = new SportsEquipmentFacilityVo();
+        BeanUtils.copyProperties(facility, vo);
+        // 设置分类名称
+        if (facility.getFacilityCategory() != null && facility.getFacilityCategory() > 0
+                && facility.getFacilityCategory() < FACILITY_CATEGORY_NAMES.length) {
+            vo.setFacilityCategoryName(FACILITY_CATEGORY_NAMES[facility.getFacilityCategory()]);
+        }
+        // 设置显示状态文本
+        if (facility.getDisplayInStoreDetail() != null) {
+            vo.setDisplayInStoreDetailText(facility.getDisplayInStoreDetail() == 1 ? "显示" : "隐藏");
+        }
+        // 查询图片列表,根据store_id和facility_category查询
+        LambdaQueryWrapper<StoreImg> imageWrapper = new LambdaQueryWrapper<>();
+        imageWrapper.eq(StoreImg::getStoreId, facility.getStoreId())
+                .eq(StoreImg::getBusinessId, facility.getFacilityCategory())
+                .eq(StoreImg::getImgType, IMG_TYPE_SPORTS_EQUIPMENT);
+        imageWrapper.orderByAsc(StoreImg::getImgSort);
+        List<StoreImg> imageList = storeImgMapper.selectList(imageWrapper);
+        if (!CollectionUtils.isEmpty(imageList)) {
+            vo.setImageList(imageList.stream().map(StoreImg::getImgUrl)
+                    .collect(Collectors.toList()));
+        }
+        return vo;
+    }
+
+
+    @Override
+    public List<SportsEquipmentFacilityCategoryVo> getstoreCategorySummary(Integer storeId) {
+        List<SportsEquipmentFacilityCategoryVo> result = new ArrayList<>();
+
+        // 遍历所有分类(1:有氧区, 2:力量区, 3:单功能机械区)
+        for (int category = 1; category < FACILITY_CATEGORY_NAMES.length; category++) {
+            SportsEquipmentFacilityCategoryVo categoryVo = new SportsEquipmentFacilityCategoryVo();
+            categoryVo.setFacilityCategory(category);
+            categoryVo.setFacilityCategoryName(FACILITY_CATEGORY_NAMES[category]);
+
+            // 查询该分类下的设备列表(不分页)
+            LambdaQueryWrapper<SportsEquipmentFacility> facilityWrapper = new LambdaQueryWrapper<>();
+            facilityWrapper.eq(SportsEquipmentFacility::getStoreId, storeId)
+                    .eq(SportsEquipmentFacility::getFacilityCategory, category);
+            facilityWrapper.orderByDesc(SportsEquipmentFacility::getCreatedTime);
+            List<SportsEquipmentFacility> facilityList = facilityMapper.selectList(facilityWrapper);
+
+            // 设置设备数量
+            categoryVo.setFacilityCount(facilityList != null ? facilityList.size() : 0);
+
+            // 转换为VO列表(不包含图片,避免重复查询)
+            List<SportsEquipmentFacilityVo> facilityVoList = new ArrayList<>();
+            if (!CollectionUtils.isEmpty(facilityList)) {
+                for (SportsEquipmentFacility facility : facilityList) {
+                    SportsEquipmentFacilityVo vo = new SportsEquipmentFacilityVo();
+                    BeanUtils.copyProperties(facility, vo);
+                    // 设置分类名称
+                    vo.setFacilityCategoryName(FACILITY_CATEGORY_NAMES[category]);
+                    // 设置显示状态文本
+                    if (facility.getDisplayInStoreDetail() != null) {
+                        vo.setDisplayInStoreDetailText(facility.getDisplayInStoreDetail() == 1 ? "显示" : "隐藏");
+                    }
+                    facilityVoList.add(vo);
+                }
+            }
+            categoryVo.setFacilityList(facilityVoList);
+
+            // 查询该分类下的图片列表
+            LambdaQueryWrapper<StoreImg> imageWrapper = new LambdaQueryWrapper<>();
+            imageWrapper.eq(StoreImg::getStoreId, storeId)
+                    .eq(StoreImg::getBusinessId, category)
+                    .eq(StoreImg::getImgType, IMG_TYPE_SPORTS_EQUIPMENT);
+            imageWrapper.orderByAsc(StoreImg::getImgSort);
+            List<StoreImg> imageList = storeImgMapper.selectList(imageWrapper);
+            if (!CollectionUtils.isEmpty(imageList)) {
+                categoryVo.setImageList(imageList.stream().map(StoreImg::getImgUrl)
+                        .collect(Collectors.toList()));
+            } else {
+                categoryVo.setImageList(new ArrayList<>());
+            }
+
+            result.add(categoryVo);
+        }
+
+        return result;
+    }
+}
+

+ 2385 - 5
alien-store/src/main/java/shop/alien/store/service/impl/StoreInfoServiceImpl.java

@@ -11,15 +11,16 @@ import com.baomidou.mybatisplus.core.toolkit.StringUtils;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.BeanUtils;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.data.geo.Point;
-import org.springframework.http.HttpHeaders;
-import org.springframework.http.HttpStatus;
-import org.springframework.http.ResponseEntity;
+import org.springframework.http.*;
+import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.util.CollectionUtils;
+import org.springframework.web.client.RestTemplate;
 import org.springframework.web.multipart.MultipartFile;
 import org.springframework.web.multipart.MultipartRequest;
 import shop.alien.entity.store.*;
@@ -30,7 +31,9 @@ import shop.alien.entity.store.excelVo.StoreInfoExpiredRecordsExcelVo;
 import shop.alien.entity.store.excelVo.util.ExcelExporter;
 import shop.alien.entity.store.excelVo.util.ExcelGenerator;
 import shop.alien.entity.store.vo.*;
+import shop.alien.entity.storePlatform.StoreLicenseHistory;
 import shop.alien.mapper.*;
+import shop.alien.mapper.storePlantform.StoreLicenseHistoryMapper;
 import shop.alien.store.config.BaseRedisService;
 import shop.alien.store.config.GaoDeMapUtil;
 import shop.alien.store.config.WebSocketProcess;
@@ -38,10 +41,12 @@ import shop.alien.store.service.*;
 import shop.alien.store.util.CommonConstant;
 import shop.alien.store.util.FileUploadUtil;
 import shop.alien.store.util.GroupConstant;
+import shop.alien.store.util.ai.AiAuthTokenUtil;
 import shop.alien.util.ali.AliOSSUtil;
 import shop.alien.util.common.DistanceUtil;
 import shop.alien.util.common.constant.CouponStatusEnum;
 import shop.alien.util.common.constant.CouponTypeEnum;
+import shop.alien.util.common.constant.OcrTypeEnum;
 import shop.alien.util.common.constant.OrderStatusEnum;
 
 import javax.annotation.Resource;
@@ -67,9 +72,12 @@ import java.util.stream.Collectors;
  * @since 2024-12-05
  */
 @Service
+@Slf4j
 @RequiredArgsConstructor
 @Transactional
 public class StoreInfoServiceImpl extends ServiceImpl<StoreInfoMapper, StoreInfo> implements StoreInfoService {
+    private static final int DEFAULT_DISTANCE_METER = 1000;
+
 
     private final String DEFAULT_PASSWORD = "123456";
 
@@ -135,6 +143,18 @@ public class StoreInfoServiceImpl extends ServiceImpl<StoreInfoMapper, StoreInfo
 
     private final LifeBlacklistMapper lifeBlacklistMapper;
 
+    private final OcrImageUploadMapper ocrImageUploadMapper;
+
+    private final AiAuthTokenUtil aiAuthTokenUtil;
+
+    private final RestTemplate restTemplate;
+
+    private final StoreImgService storeImgService;
+
+
+    /** 商户证照历史记录数据访问对象 */
+    private final StoreLicenseHistoryMapper licenseHistoryMapper;
+
     @Resource
     private StoreIncomeDetailsRecordService storeIncomeDetailsRecordService;
 
@@ -145,6 +165,12 @@ public class StoreInfoServiceImpl extends ServiceImpl<StoreInfoMapper, StoreInfo
     @Value("${spring.web.resources.excel-generate-path}")
     private String excelGeneratePath;
 
+    @Value("${third-party-identification.base-url}")
+    private String identificationPath;
+
+    @Value("${third-party-applications.base-url}")
+    private String applicationsPath;
+
     /**
      * 懒得查, 留着导出Excel
      */
@@ -191,9 +217,16 @@ public class StoreInfoServiceImpl extends ServiceImpl<StoreInfoMapper, StoreInfo
         List<StoreImg> albumUrlList = storeImgMapper.selectList(albumUrlQueryWrapper);
         storeMainInfoVo.setAlbumUrl(albumUrlList.stream().sorted(Comparator.comparing(StoreImg::getImgSort)).map(StoreImg::getImgUrl).collect(Collectors.toList()));
         //推荐菜
-        storeMainInfoVo.setRecommendUrl(storeMenuMapper.getStoreMenuList(id, 1).stream().sorted(Comparator.comparing(StoreMenuVo::getImgSort)).collect(Collectors.toList()));
+        storeMainInfoVo.setRecommendUrl(storeMenuMapper.getStoreMenuList(id, 1,1).stream().sorted(Comparator.comparing(StoreMenuVo::getImgSort)).collect(Collectors.toList()));
         //菜单
-        storeMainInfoVo.setMenuUrl(storeMenuMapper.getStoreMenuList(id, 0).stream().sorted(Comparator.comparing(StoreMenuVo::getImgSort)).collect(Collectors.toList()));
+        storeMainInfoVo.setMenuUrl(storeMenuMapper.getStoreMenuList(id, 0,1).stream().sorted(Comparator.comparing(StoreMenuVo::getImgSort)).collect(Collectors.toList()));
+
+        // todo 需要根据门店类型来判断
+        // 推荐酒水
+        storeMainInfoVo.setRecommendBeverageUrl(storeMenuMapper.getStoreMenuList(id, 1,1).stream().sorted(Comparator.comparing(StoreMenuVo::getImgSort)).collect(Collectors.toList()));
+        //酒单
+        storeMainInfoVo.setBeverageUrl(storeMenuMapper.getStoreMenuList(id, 0,1).stream().sorted(Comparator.comparing(StoreMenuVo::getImgSort)).collect(Collectors.toList()));
+
         //门店标签
         storeMainInfoVo.setStoreLabel(storeLabelMapper.selectOne(new LambdaQueryWrapper<StoreLabel>().eq(StoreLabel::getStoreId, id)));
         //营业时间
@@ -476,6 +509,180 @@ public class StoreInfoServiceImpl extends ServiceImpl<StoreInfoMapper, StoreInfo
         return storeInfoVoPage;
     }
 
+    @Override
+    public IPage<StoreInfoVo> getNewStorePage(int page, int size, String storeName, String storeContact, String id, String storePhone, String storeType, String expiredState, String storeApplicationStatus, String storeStatus, String businessSection, String jingdu, String weidu, String renewContractStatus, String foodLicenceStatus, String foodLicenceWhetherExpiredStatus) {
+        checkAndUpdateExpiredRecords();
+        IPage<StoreInfoVo> iPage = new Page<>(page, size);
+        QueryWrapper<StoreInfoVo> queryWrapper = new QueryWrapper<>();
+        queryWrapper.like(storeName != null && !storeName.isEmpty(), "a.store_name", storeName).like(storeContact != null && !storeContact.isEmpty(), "b.name", storeContact).like(storePhone != null && !storePhone.isEmpty(), "b.phone", storePhone).like(storeType != null && !storeType.isEmpty(), "a.store_type", storeType).eq(StringUtils.isNotEmpty(storeApplicationStatus), "a.store_application_status", storeApplicationStatus).eq(StringUtils.isNotEmpty(businessSection), "a.business_section", businessSection).eq(StringUtils.isNotEmpty(storeStatus), "a.store_status", storeStatus).eq(StringUtils.isNotEmpty(renewContractStatus), "a.renew_contract_status", renewContractStatus).eq(StringUtils.isNotEmpty(foodLicenceStatus), "a.food_licence_status", foodLicenceStatus).like(StringUtils.isNotEmpty(id), "a.id", id).eq("a.delete_flag", 0).eq("b.delete_flag", 0).orderByDesc("a.created_time");
+        //如果查询未过期
+        // 获取当前时刻
+        Date currentDate = new Date();
+        // 获取当前日期和时间
+        Calendar calendar = Calendar.getInstance();
+        // 将时间设置为 0 点
+        calendar.set(Calendar.HOUR_OF_DAY, 0);
+        calendar.set(Calendar.MINUTE, 0);
+        calendar.set(Calendar.SECOND, 0);
+        calendar.set(Calendar.MILLISECOND, 0);
+        // 加上 30 天
+        calendar.add(Calendar.DAY_OF_MONTH, 30);
+        // 获取 Date 对象
+        Date thirtyDaysAgo = calendar.getTime();
+        if ("0".equals(expiredState)) {
+            queryWrapper.ge("a.expiration_time", thirtyDaysAgo);
+        } else if ("1".equals(expiredState)) {
+            queryWrapper.lt("a.expiration_time", thirtyDaysAgo);
+            queryWrapper.ge("a.expiration_time", currentDate);
+        } else if ("2".equals(expiredState)) {
+            queryWrapper.lt("a.expiration_time", currentDate);
+        }
+        // 获取当前时间
+        LocalDate nowDay = LocalDate.now();
+        //经营许可证到期状态筛选 0 查询食品经营许可证已到期
+        if (CommonConstant.FOOD_LICENCE_EXPIRE_STATUS.equals(foodLicenceWhetherExpiredStatus)) {
+            queryWrapper.lt("a.food_licence_expiration_time", nowDay);
+        } else if (CommonConstant.FOOD_LICENCE_ABOUT_TO_EXPIRE_STATUS.equals(foodLicenceWhetherExpiredStatus)) {//经营许可证筛选状态 1 查询食品经营许可证即将到期 距离到期时间30天内
+            queryWrapper.lt("a.food_licence_expiration_time", nowDay.plusDays(31))
+                    .ge("a.food_licence_expiration_time", nowDay);
+        } else if (CommonConstant.FOOD_LICENCE_NOT_EXPIRED_STATUS.equals(foodLicenceWhetherExpiredStatus)) {//经营许可证筛选状态 2 查询食品经营许可证未到期 距离到期时间大于30天以上
+            queryWrapper.gt("a.food_licence_expiration_time", nowDay.plusDays(31));
+        }
+        //查出所有店铺
+        IPage<StoreInfoVo> storeInfoVoPage = storeInfoMapper.getNewStoreInfoVoPage(iPage, queryWrapper);
+        List<StoreInfoVo> records = storeInfoVoPage.getRecords();
+        if (!records.isEmpty()) {
+            // 提前查询所有需要的字典数据
+            List<StoreInfoVo> collect = records.stream().filter(record -> StringUtils.isNotEmpty(record.getStoreType())).collect(Collectors.toList());
+            Set<String> allTypes = collect.stream().map(StoreInfoVo::getStoreType).flatMap(type -> Arrays.stream(type.split(","))).collect(Collectors.toSet());
+
+            List<StoreDictionary> storeDictionaries = storeDictionaryMapper.selectList(new LambdaQueryWrapper<StoreDictionary>().eq(StoreDictionary::getTypeName, "storeType").isNull(StoreDictionary::getParentId).in(!allTypes.isEmpty(), StoreDictionary::getDictId, allTypes));
+            Map<String, String> typeMap = storeDictionaries.stream().collect(Collectors.toMap(StoreDictionary::getDictId, StoreDictionary::getDictDetail));
+
+            Map<String, List<LifeCoupon>> quanListByStoreId = new HashMap<>();
+            Map<Object, List<Map<String, Object>>> avgScoreMap = new HashMap<>();
+            Map<Object, List<Map<String, Object>>> avgPriceMap = new HashMap<>();
+            Map<Integer, List<StoreComment>> commentMap = new HashMap<>();
+            // 如果获取美食列表进行以下前置操作
+            if (StringUtils.isNotEmpty(businessSection) && businessSection.equals("1")) {
+                List<Integer> storeIds = records.stream().map(StoreInfoVo::getId).collect(Collectors.toList());
+                LambdaUpdateWrapper<LifeCoupon> quanWrapper = new LambdaUpdateWrapper<>();
+                quanWrapper.in(LifeCoupon::getStoreId, storeIds).eq(LifeCoupon::getStatus, 1).orderByDesc(LifeCoupon::getCreatedTime);
+                List<LifeCoupon> quanList = lifeCouponMapper.selectList(quanWrapper);
+                quanListByStoreId = quanList.stream().collect(Collectors.groupingBy(LifeCoupon::getStoreId));
+                // 获取全部店铺的评分与平均花销
+                // TODO sotre_comment 没有花销 怎么算平均花销
+                avgScoreMap = storeEvaluationMapper.allStoreAvgScore().stream().collect(Collectors.groupingBy(o -> o.get("store_id")));
+                commentMap = storeCommentMapper.selectList(new QueryWrapper<StoreComment>().eq("business_type", "5").eq("delete_flag", 0)).stream().collect(Collectors.groupingBy(StoreComment::getStoreId));
+                avgPriceMap = lifeUserOrderMapper.allStoreAvgPrice().stream().collect(Collectors.groupingBy(o -> o.get("store_id")));
+            }
+            for (StoreInfoVo record : records) {
+                //处理类型
+                if (StringUtils.isNotEmpty(record.getStoreType())) {
+                    String[] types = record.getStoreType().split(",");
+                    List<String> typeDetails = Arrays.stream(types).map(typeMap::get).filter(Objects::nonNull).collect(Collectors.toList());
+                    record.setStoreTypeStr(String.join(",", typeDetails));
+                    record.setStoreTypeList(Arrays.asList(types));
+                }
+                //写经纬度
+                String[] split = record.getStorePosition().split(",");
+                record.setStorePositionLongitude(split[0]);
+                record.setStorePositionLatitude(split[1]);
+                //处理一下到期状态
+                Date expirationTime = record.getExpirationTime();
+                if (expirationTime != null) {
+                    // 获取当前时间
+                    Calendar now = Calendar.getInstance();
+                    Date nowCurrentDate = now.getTime();
+                    // 计算 30 天后的时间
+                    now.add(Calendar.DAY_OF_YEAR, 30);
+                    Date thirtyDaysLater = now.getTime();
+                    // 比较两个日期
+                    if (expirationTime.after(currentDate)) {
+                        record.setExpiredState("0");
+                        if (expirationTime != null && (expirationTime.after(nowCurrentDate) || expirationTime.equals(nowCurrentDate)) && expirationTime.before(thirtyDaysLater)) {
+                            record.setExpiredState("1");
+                        }
+                    } else {
+                        record.setExpiredState("2");
+                    }
+
+                    // 获取当前时间
+                    LocalDate nowLocal = LocalDate.now();
+                    // 将 expirationTime 转换为 LocalDate
+                    LocalDate expDate = expirationTime.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
+                    // 计算距离到期的天数
+                    long daysToExpire = ChronoUnit.DAYS.between(nowLocal, expDate);
+                    record.setDaysToExpire(daysToExpire);
+                }
+                // 处理经营许可证到期时间回显状态 根据经营许可证筛选状态筛选
+                Date foodDate = record.getFoodLicenceExpirationTime();
+                if (foodDate != null) {
+                    // 获取当前时间
+                    LocalDate nowLocal = LocalDate.now();
+                    LocalDate localDate = foodDate.toInstant()  // Date -> Instant(UTC时间戳)
+                            .atZone(ZoneId.systemDefault())  // Instant -> ZonedDateTime(系统默认时区)
+                            .toLocalDate();
+                    long remainingDays = ChronoUnit.DAYS.between(nowLocal, localDate);
+                    if (remainingDays == -1) {
+                        record.setFoodLicenceRemainingDays("0天");
+                        record.setFoodLicenceExpireStatus("已到期");
+                    } else if (remainingDays >= 0 && remainingDays <= 30) {
+                        record.setFoodLicenceRemainingDays(remainingDays + "天");
+                        record.setFoodLicenceExpireStatus("即将到期");
+                    } else if (remainingDays > 30) {
+                        record.setFoodLicenceRemainingDays(remainingDays + "天");
+                        record.setFoodLicenceExpireStatus("未到期");
+                    }
+                }
+
+
+                // 处理状态
+                if (record.getLogoutFlag() == 1) {
+                    record.setStoreStatusStr("已注销");
+                    record.setStoreStatus(2);
+                }
+                // 根据八大类不同进行个性化操作
+                if (StringUtils.isNotEmpty(businessSection)) {
+                    switch (businessSection) {
+                        case "1":
+                            record.setAvgScore("0");
+                            record.setAvgPrice("0");
+                            record.setTotalNum("0");
+
+                            if ((jingdu != null && !jingdu.isEmpty()) && (weidu != null && !weidu.isEmpty())) {
+                                double storeJing = Double.parseDouble(split[0]);
+                                double storeWei = Double.parseDouble(split[1]);
+                                double storeDistance = DistanceUtil.haversineCalculateDistance(Double.parseDouble(jingdu), Double.parseDouble(weidu), storeJing, storeWei);
+                                record.setDistance(storeDistance);
+                            } else {
+                                record.setDistance(0);
+                            }
+
+                            // 设置店铺得分,设置店铺人均消费,设置总评论数
+                            if (avgScoreMap.containsKey(String.valueOf(record.getId()))) {
+                                record.setAvgScore(String.valueOf(avgScoreMap.get(String.valueOf(record.getId())).get(0).get("avg_score")));
+                                record.setAvgPrice(String.valueOf(avgPriceMap.get(String.valueOf(record.getId())).get(0).get("avg_price")));
+                            }
+                            // 设置店铺得分,设置店铺人均消费,设置总评论数
+                            if (commentMap.containsKey(record.getId())) {
+                                record.setTotalNum(String.valueOf(commentMap.get(record.getId()).size()));
+                            }
+
+                            // 设置店铺优惠券列表
+                            if (!quanListByStoreId.isEmpty() && quanListByStoreId.containsKey(String.valueOf(record.getId()))) {
+                                record.setQuanList(quanListByStoreId.get(String.valueOf(record.getId())));
+                            }
+                            break;
+                    }
+                }
+            }
+        }
+
+        toExcel = storeInfoVoPage.getRecords();
+        return storeInfoVoPage;
+    }
+
     /**
      * 重置门店密码
      *
@@ -667,6 +874,16 @@ public class StoreInfoServiceImpl extends ServiceImpl<StoreInfoMapper, StoreInfo
                 storeImg.setImgUrl(storeInfoDto.getFoodLicenceUrl());
                 storeImgMapper.insert(storeImg);
         }
+        //存入店铺娱乐经营许可证图片
+        if (StringUtils.isNotEmpty(storeInfoDto.getEntertainmentLicenceUrl())) {
+            StoreImg storeImg = new StoreImg();
+            storeImg.setStoreId(storeInfo.getId());
+            storeImg.setImgType(26);
+            storeImg.setImgSort(0);
+            storeImg.setImgDescription("娱乐经营许可证审核通过图片");
+            storeImg.setImgUrl(storeInfoDto.getEntertainmentLicenceUrl());
+            storeImgMapper.insert(storeImg);
+        }
 
         //初始化标签数据
         LambdaQueryWrapper<TagStoreRelation> tagStoreRelationLambdaQueryWrapper = new LambdaQueryWrapper<>();
@@ -1297,6 +1514,14 @@ public class StoreInfoServiceImpl extends ServiceImpl<StoreInfoMapper, StoreInfo
         if (!storeDictionaries.isEmpty()) {
             result.setBusinessStatusStr(storeDictionaries.get(0).getDictDetail());
         }
+        // TODO 之后修改********** 正常OcrType由前端传存储ocr表要加新字段。传参要由前端传。
+        // 查询并设置各类证件OCR信息
+        result.setJyxkz(convertOcrResultToJson(storeUser.getId(), "BUSINESS_LICENSE", "娱乐", true));
+        result.setIdcardFace(convertOcrResultToJson(storeUser.getId(), "ID_CARD", "face", true));
+        result.setIdcardBack(convertOcrResultToJson(storeUser.getId(), "ID_CARD", "back", true));
+        result.setFoodLicence(convertOcrResultToJson(storeUser.getId(), "FOOD_MANAGE_LICENSE", null, true));
+        result.setEntertainmentLicence(convertOcrResultToJson(storeUser.getId(), "BUSINESS_LICENSE", "营业执照", false));
+
         return result;
     }
 
@@ -2196,6 +2421,16 @@ public class StoreInfoServiceImpl extends ServiceImpl<StoreInfoMapper, StoreInfo
             storeImg.setImgType(24);
             storeImg.setImgDescription("经营许可证审核通过前图片");
             storeImgMapper.insert(storeImg);
+
+            // 经营许可证历史表插入
+            StoreLicenseHistory licenseHistory = new StoreLicenseHistory();
+            licenseHistory.setStoreId(storeImg.getStoreId());
+            licenseHistory.setLicenseStatus(2);
+            licenseHistory.setLicenseExecuteStatus(2);
+            licenseHistory.setImgUrl(storeImg.getImgUrl());
+            licenseHistory.setDeleteFlag(0);
+            licenseHistoryMapper.insert(licenseHistory);
+
             //更新店铺
             StoreInfo storeInfo = new StoreInfo();
             storeInfo.setFoodLicenceStatus(2);
@@ -2284,6 +2519,26 @@ public class StoreInfoServiceImpl extends ServiceImpl<StoreInfoMapper, StoreInfo
         LambdaUpdateWrapper<StoreImg> imgLambdaUpdateWrapper = new LambdaUpdateWrapper();
         imgLambdaUpdateWrapper.in(StoreImg::getId, imgList).set(StoreImg::getImgType, 25).set(StoreImg::getImgDescription,"经营许可证审核通过图片");
         int num = storeImgMapper.update(null, imgLambdaUpdateWrapper);
+
+
+        // 将原来的食品经营许可证历史表数据删除
+        LambdaUpdateWrapper<StoreLicenseHistory> wrapper = new LambdaUpdateWrapper<>();
+        wrapper.eq(StoreLicenseHistory::getStoreId, id);
+        wrapper.eq(StoreLicenseHistory::getLicenseStatus, 2);
+        wrapper.eq(StoreLicenseHistory::getLicenseExecuteStatus, 1);
+        wrapper.eq(StoreLicenseHistory::getDeleteFlag, 0);
+        wrapper.set(StoreLicenseHistory::getDeleteFlag, 1);
+        licenseHistoryMapper.update(null, wrapper);
+
+        // 将新的食品经营许可证历史表数据变为审核通过
+        LambdaUpdateWrapper<StoreLicenseHistory> wrapper1 = new LambdaUpdateWrapper<>();
+        wrapper1.eq(StoreLicenseHistory::getStoreId, id);
+        wrapper1.eq(StoreLicenseHistory::getLicenseStatus, 2);
+        wrapper1.eq(StoreLicenseHistory::getLicenseExecuteStatus, 2);
+        wrapper1.eq(StoreLicenseHistory::getDeleteFlag, 0);
+        wrapper1.set(StoreLicenseHistory::getLicenseExecuteStatus, 1);
+        licenseHistoryMapper.update(null, wrapper1);
+
         return num;
     }
 
@@ -2321,4 +2576,2129 @@ public class StoreInfoServiceImpl extends ServiceImpl<StoreInfoMapper, StoreInfo
         }
         return result;
     }
+
+    public void updateLicenseHistoryInfo(Integer storeId, Integer foodLicenceStatus) {
+        if (foodLicenceStatus == 3) {
+            // 审核拒绝时修改提交记录
+            LambdaUpdateWrapper<StoreLicenseHistory> wrapper = new LambdaUpdateWrapper<>();
+            wrapper.eq(StoreLicenseHistory::getStoreId, storeId);
+            wrapper.eq(StoreLicenseHistory::getLicenseStatus, 1);
+            wrapper.eq(StoreLicenseHistory::getLicenseExecuteStatus, 2);
+            wrapper.set(StoreLicenseHistory::getLicenseExecuteStatus, 3);
+            wrapper.set(StoreLicenseHistory::getReasonRefusal, storeId);
+            wrapper.set(StoreLicenseHistory::getDeleteFlag, 1);
+            licenseHistoryMapper.update(null, wrapper);
+        } else {
+            // 将原来的食品经营许可证历史表数据删除
+            LambdaUpdateWrapper<StoreLicenseHistory> wrapper = new LambdaUpdateWrapper<>();
+            wrapper.eq(StoreLicenseHistory::getStoreId, storeId);
+            wrapper.eq(StoreLicenseHistory::getLicenseStatus, 1);
+            wrapper.eq(StoreLicenseHistory::getLicenseExecuteStatus, 1);
+            wrapper.eq(StoreLicenseHistory::getDeleteFlag, 0);
+            wrapper.set(StoreLicenseHistory::getDeleteFlag, 1);
+            licenseHistoryMapper.update(null, wrapper);
+
+            // 将新的食品经营许可证历史表数据变为审核通过
+            LambdaUpdateWrapper<StoreLicenseHistory> wrapper1 = new LambdaUpdateWrapper<>();
+            wrapper1.eq(StoreLicenseHistory::getStoreId, storeId);
+            wrapper1.eq(StoreLicenseHistory::getLicenseStatus, 1);
+            wrapper1.eq(StoreLicenseHistory::getLicenseExecuteStatus, 2);
+            wrapper1.eq(StoreLicenseHistory::getDeleteFlag, 0);
+            wrapper1.set(StoreLicenseHistory::getLicenseExecuteStatus, 1);
+            licenseHistoryMapper.update(null, wrapper1);
+        }
+    }
+
+    @Override
+    public void aiApproveStoreInfo(AiApproveStoreInfo aiApproveStoreInfo) {
+        HttpHeaders aiHeaders = new HttpHeaders();
+        aiHeaders.setContentType(MediaType.APPLICATION_JSON);
+        aiHeaders.set("Authorization", "Bearer " + aiAuthTokenUtil.getAccessToken());
+
+        HttpEntity<AiApproveStoreInfo> request = new HttpEntity<>(aiApproveStoreInfo, aiHeaders);
+        ResponseEntity<String> response = null;
+        try {
+            response = restTemplate.postForEntity(applicationsPath, request, String.class);
+            if (response.getStatusCodeValue() != 200) {
+                throw new RuntimeException("AI门店审核接口调用失败 http状态:" + response.getStatusCode());
+            }
+            if (StringUtils.isNotEmpty(response.getBody())) {
+                JSONObject jsonObject = JSONObject.parseObject(response.getBody());
+                if (jsonObject.getInteger("code") == 200) {
+                    JSONObject data = jsonObject.getJSONObject("data");
+                    List<StoreInfo> storeInfos = storeInfoMapper.selectList(new LambdaQueryWrapper<StoreInfo>()
+                            .eq(StoreInfo::getCreatedUserId, aiApproveStoreInfo.getUserId()).eq(StoreInfo::getStoreApplicationStatus, 0).eq(StoreInfo::getDeleteFlag, 0));
+                    for (StoreInfo storeInfo : storeInfos) {
+                        if ("approved".equals(data.getString("status"))) {
+                            approveStoreInfo(storeInfo.getId().toString(),1, "审核通过");
+                        } else if ("rejected".equals(data.getString("status"))) {
+                            approveStoreInfo(storeInfo.getId().toString(),2, data.getString("audit_summary"));
+                        } else {
+                            System.out.println("未知状态");
+                        }
+                    }
+                } else {
+                    throw new RuntimeException("AI门店审核接口调用失败 code:" + jsonObject.getInteger("code"));
+                }
+            }
+        } catch (Exception e){
+            throw new RuntimeException("调用门店审核接口异常", e);
+        }
+    }
+
+    @Override
+    public Map<String, Object> getStoreOcrData(String storeUserId, String imageUrl) {
+        Map<String, Object> map = new HashMap<>();
+        LambdaQueryWrapper<OcrImageUpload> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(OcrImageUpload::getStoreUserId, storeUserId)
+                .eq(OcrImageUpload::getOcrType, OcrTypeEnum.BUSINESS_LICENSE.getCode());
+        OcrImageUpload ocrImageUploads = ocrImageUploadMapper.selectOne(queryWrapper);
+        if(ocrImageUploads== null){
+            throw new RuntimeException("未找到OCI识别数据!");
+        }
+        String accessToken = aiAuthTokenUtil.getAccessToken();
+
+        HttpHeaders aiHeaders = new HttpHeaders();
+        aiHeaders.setContentType(MediaType.APPLICATION_JSON);
+        aiHeaders.set("Authorization", "Bearer " + accessToken);
+        Map<String, Object> jsonBody = new HashMap<>();
+        List<String> imageUrls = new ArrayList<>();
+        imageUrls.add(imageUrl);
+        jsonBody.put("image_urls", imageUrls);
+        String ocrResult = ocrImageUploads.getOcrResult();
+        JSONObject jsonObject = JSONObject.parseObject(ocrResult);
+        jsonBody.put("merchant_name", jsonObject.get("companyName"));
+
+        HttpEntity<Map<String, Object>> request = new HttpEntity<>(jsonBody, aiHeaders);
+        ResponseEntity<String> response = null;
+        HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
+        factory.setConnectTimeout(5000);
+        factory.setReadTimeout(60000);
+        restTemplate.setRequestFactory(factory);
+        try {
+            response = restTemplate.postForEntity(identificationPath, request, String.class);
+            if (response.getStatusCodeValue() != 200) {
+                throw new RuntimeException("AI内容审核接口调用失败 http状态:" + response.getStatusCode());
+            }
+
+        } catch (Exception e) {
+            throw new RuntimeException("AI接口调用失败");
+        }
+        com.alibaba.fastjson2.JSONObject responseNode = com.alibaba.fastjson2.JSONObject.parseObject(response.getBody());
+        if (responseNode == null) {
+            throw new RuntimeException("AI接口调用失败,响应内容为空");
+        } else {
+            Integer code = responseNode.getInteger("code");
+            if (code == 200) {
+                Map<String, Object> dataMap = (Map<String, Object>) responseNode.get("data");
+                if (Objects.nonNull(dataMap)) {
+                    // 获取match_results(JSON中是数组,反序列化为List<Map>)
+                    List<Map<String, Object>> matchResults = (List<Map<String, Object>>) dataMap.get("match_results");
+                    map.put("overall_match", dataMap.get("overall_match"));
+                    if (Objects.nonNull(matchResults) && !matchResults.isEmpty()) {
+                        map.put("match_reason", matchResults.get(0).get("match_reason"));
+                    }
+                }
+            } else {
+                throw new RuntimeException("AI接口调用失败,错误码: " + code);
+            }
+        }
+        return map;
+    }
+
+    /**
+     * 获取活动banner图
+     */
+
+    public List<StoreImg> getBannerUrl(String storeId){
+        LambdaQueryWrapper<StoreImg> queryWrapper = new LambdaQueryWrapper<StoreImg>()
+                .eq(StoreImg::getStoreId, Integer.parseInt(storeId))
+                .eq(StoreImg::getImgType, 26)
+                .eq(StoreImg::getDeleteFlag, 0);
+        return storeImgMapper.selectList(queryWrapper);
+    }
+
+
+
+    /**
+     * 获取活动详情banner图
+     */
+
+    public List<StoreImg> getBannerUrlInfo(String storeId, Integer businessId){
+        LambdaQueryWrapper<StoreImg> queryWrapper = new LambdaQueryWrapper<StoreImg>()
+                .eq(StoreImg::getStoreId, Integer.parseInt(storeId))
+                .eq(StoreImg::getImgType, 27)
+                .eq(StoreImg::getDeleteFlag, 0)
+                .eq(StoreImg::getBusinessId, businessId);
+        return storeImgMapper.selectList(queryWrapper);
+    }
+
+
+
+
+    @Override
+    public IPage<StoreInfoVo> getLifeServicesByDistance(Double lon, Double lat, Double distance, Integer sortType, Integer businessType, Integer categoryId, int pageNum, int pageSize) {
+        // 参数校验
+        if (lon == null || lat == null) {
+            throw new IllegalArgumentException("经纬度参数不能为空");
+        }
+
+        // 智能检测:如果参数可能传反了(lat超出范围但lon在范围内),给出提示
+        if (Math.abs(lat) > 90 && Math.abs(lon) <= 90) {
+            throw new IllegalArgumentException(
+                    String.format("参数可能传反了!当前值: lon=%s, lat=%s。经度范围: [-180, 180],纬度范围: [-90, 90]。请检查参数顺序。", lon, lat)
+            );
+        }
+
+        // 校验经纬度范围:经度 [-180, 180],纬度 [-90, 90]
+        if (lon < -180 || lon > 180) {
+            throw new IllegalArgumentException("经度参数超出有效范围 [-180, 180],当前值: " + lon);
+        }
+        if (lat < -90 || lat > 90) {
+            throw new IllegalArgumentException("纬度参数超出有效范围 [-90, 90],当前值: " + lat);
+        }
+        final int finalSortType = (sortType == null || sortType < 1 || sortType > 3) ? 1 : sortType; // 默认智能排序
+        if (pageNum <= 0) {
+            pageNum = 1;
+        }
+        if (pageSize <= 0) {
+            pageSize = 10;
+        }
+
+        // 构建查询条件
+        QueryWrapper<StoreInfoVo> queryWrapper = new QueryWrapper<>();
+
+        // 如果传入了dictId,根据字典id查询经营板块、经营种类、分类并匹配店铺
+        if (categoryId != null) {
+            // 根据categoryId查询字典表记录
+            StoreDictionary dict = storeDictionaryMapper.selectById(categoryId);
+            if (dict == null || dict.getDeleteFlag() == 1) {
+                throw new IllegalArgumentException("字典id不存在或已删除: " + categoryId);
+            }
+
+            String typeName = dict.getTypeName();
+            String dictIdStr =dict.getDictId();
+
+            if ("business_section".equals(typeName)) {
+                // 如果是经营板块,直接匹配business_section
+                try {
+                    Integer sectionId = Integer.parseInt(dictIdStr);
+                    queryWrapper.eq("a.business_section", sectionId);
+                } catch (NumberFormatException e) {
+                    throw new IllegalArgumentException("经营板块dictId格式错误: " + dictIdStr);
+                }
+            } else if ("business_type".equals(typeName)) {
+                // 如果是经营种类,需要:
+                // 1. 向上查找父级(经营板块)
+                // 2. 匹配business_section和business_types
+                StoreDictionary parentDict = storeDictionaryMapper.selectById(dict.getParentId());
+                if (parentDict == null || !"business_section".equals(parentDict.getTypeName())) {
+                    throw new IllegalArgumentException("经营种类的父级不是经营板块,categoryId: " + categoryId);
+                }
+
+                try {
+                    Integer sectionId = Integer.parseInt(parentDict.getDictId());
+                    queryWrapper.eq("a.business_section", sectionId);
+                    // 使用FIND_IN_SET匹配business_types字段(逗号分隔)
+                    queryWrapper.apply("FIND_IN_SET({0}, a.business_types) > 0", dictIdStr);
+                } catch (NumberFormatException e) {
+                    throw new IllegalArgumentException("经营板块或经营种类dictId格式错误");
+                }
+            } else if ("business_classify".equals(typeName)) {
+                // 如果是分类,需要:
+                // 1. 向上查找父级(经营种类)
+                // 2. 向上查找祖父级(经营板块)
+                // 3. 匹配business_section、business_types和business_classify
+                StoreDictionary parentDict = storeDictionaryMapper.selectById(dict.getParentId());
+                if (parentDict == null || !"business_type".equals(parentDict.getTypeName())) {
+                    throw new IllegalArgumentException("分类的父级不是经营种类,categoryId: " + categoryId);
+                }
+
+                StoreDictionary grandParentDict = storeDictionaryMapper.selectById(parentDict.getParentId());
+                if (grandParentDict == null || !"business_section".equals(grandParentDict.getTypeName())) {
+                    throw new IllegalArgumentException("经营种类的父级不是经营板块,categoryId: " + categoryId);
+                }
+
+                try {
+                    Integer sectionId = Integer.parseInt(grandParentDict.getDictId());
+                    queryWrapper.eq("a.business_section", sectionId);
+                    // 使用FIND_IN_SET匹配business_types字段
+                    queryWrapper.apply("FIND_IN_SET({0}, a.business_types) > 0", parentDict.getDictId());
+                    // 使用FIND_IN_SET匹配business_classify字段
+                    queryWrapper.apply("FIND_IN_SET({0}, a.business_classify) > 0", dictIdStr);
+                } catch (NumberFormatException e) {
+                    throw new IllegalArgumentException("经营板块、经营种类或分类dictId格式错误");
+                }
+            } else {
+                throw new IllegalArgumentException("不支持的字典类型: " + typeName + ", categoryId: " + categoryId);
+            }
+        } else if (businessType != null) {
+            // 如果指定了businessType,则根据传入的数值进行筛选
+            // 直接使用传入的数值作为business_section进行筛选
+            // KTV=3、洗浴汗蒸=4、按摩足浴=5,酒吧的数值未知(由调用方传入)
+            queryWrapper.eq("a.business_section", businessType);
+        } else {
+            // 如果没有指定businessType,则查询所有两种类型的店铺
+            // 需要查询字典表获取所有四种类型的dictId
+            List<String> storeTypeNames = Arrays.asList("丽人美发","运动健身");
+            List<StoreDictionary> storeDictionaries = storeDictionaryMapper.selectList(
+                    new LambdaQueryWrapper<StoreDictionary>()
+                            .eq(StoreDictionary::getTypeName, "business_section")
+                            .in(StoreDictionary::getDictDetail, storeTypeNames)
+                            .eq(StoreDictionary::getDeleteFlag, 0)
+            );
+
+            List<Integer> businessSectionIds = storeDictionaries.stream()
+                    .filter(d -> StringUtils.isNotEmpty(d.getDictId()))
+                    .map(StoreDictionary::getDictId)
+                    .map(dictIdStr -> {
+                        try {
+                            return Integer.parseInt(dictIdStr);
+                        } catch (NumberFormatException e) {
+                            return null;
+                        }
+                    })
+                    .filter(Objects::nonNull)
+                    .collect(Collectors.toList());
+
+            // 使用business_section字段进行筛选
+            queryWrapper.in("a.business_section", businessSectionIds);
+        }
+
+        // 过滤已删除的门店
+        queryWrapper.eq("a.delete_flag", 0);
+        // 过滤注销的门店
+        queryWrapper.eq("a.logout_flag", 0);
+        // 过滤禁用的门店
+        queryWrapper.ne("a.store_status", 0);
+        // 过滤永久关门的店铺
+        queryWrapper.ne("a.business_status", 99);
+        // 过滤过期的经营许可证
+        queryWrapper.ge("a.entertainment_licence_expiration_time", new Date());
+
+        // 距离优先模式:只显示10公里内且3.5星以上的店铺
+        final Double finalDistance;
+        if (finalSortType == 3) {
+            queryWrapper.ge("a.score_avg", 3.5);
+            if (distance == null || distance <= 0) {
+                finalDistance = 10.0; // 默认10公里
+            } else if (distance > 10) {
+                finalDistance = 10.0; // 距离优先最多10公里
+            } else {
+                finalDistance = distance;
+            }
+        } else {
+            finalDistance = distance;
+        }
+
+        // 先按距离排序获取所有数据(用于计算距离)
+        queryWrapper.orderByAsc("dist");
+
+        // 创建分页对象(先获取更多数据用于排序计算)
+        IPage<StoreInfoVo> page = new Page<>(1, 1000); // 先获取足够多的数据
+        IPage<StoreInfoVo> storeInfoIPage = storeInfoMapper.getPageForDistance(page, lon + "," + lat, queryWrapper);
+        List<StoreInfoVo> storeInfoVoList = storeInfoIPage.getRecords();
+
+        // 如果指定了距离范围,进行距离筛选
+        if (finalDistance != null && finalDistance > 0) {
+            storeInfoVoList = storeInfoVoList.stream()
+                    .filter(store -> {
+                        String distStr = store.getDist();
+                        if (distStr == null || distStr.isEmpty()) {
+                            return false;
+                        }
+                        try {
+                            double storeDistance = Double.parseDouble(distStr);
+                            return storeDistance <= finalDistance;
+                        } catch (NumberFormatException e) {
+                            return false;
+                        }
+                    })
+                    .collect(Collectors.toList());
+        }
+
+        // 计算时间范围
+        LocalDateTime now = LocalDateTime.now();
+        LocalDateTime sevenDaysAgo = now.minusDays(7);
+        LocalDateTime thirtyDaysAgo = now.minusDays(30);
+        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+        String sevenDaysAgoStr = sevenDaysAgo.format(formatter);
+        String thirtyDaysAgoStr = thirtyDaysAgo.format(formatter);
+
+        // 提取所有门店ID
+        List<Integer> storeIds = storeInfoVoList.stream().map(StoreInfoVo::getId).collect(Collectors.toList());
+
+        if (CollectionUtils.isEmpty(storeIds)) {
+            // 如果没有符合条件的店铺,返回空结果
+            IPage<StoreInfoVo> emptyPage = new Page<>(pageNum, pageSize);
+            emptyPage.setRecords(new ArrayList<>());
+            emptyPage.setTotal(0);
+            return emptyPage;
+        }
+
+        // 查询近7天销量(订单数)
+        Map<Integer, Long> sales7DaysMap = new HashMap<>();
+        if (finalSortType == 1) { // 智能排序需要销量数据
+            LambdaQueryWrapper<LifeUserOrder> orderWrapper = new LambdaQueryWrapper<>();
+            orderWrapper.in(LifeUserOrder::getStoreId, storeIds)
+                    .and(w -> w.and(w1 -> w1.eq(LifeUserOrder::getStatus, 7)
+                                    .ge(LifeUserOrder::getFinishTime, sevenDaysAgoStr))
+                            .or(w2 -> w2.eq(LifeUserOrder::getStatus, 1)
+                                    .ge(LifeUserOrder::getPayTime, sevenDaysAgoStr)))
+                    .eq(LifeUserOrder::getDeleteFlag, 0);
+            List<LifeUserOrder> orders7Days = lifeUserOrderMapper.selectList(orderWrapper);
+            Map<Integer, Long> tempSalesMap = orders7Days.stream()
+                    .filter(order -> order.getStoreId() != null)
+                    .collect(Collectors.groupingBy(
+                            order -> Integer.parseInt(order.getStoreId()),
+                            Collectors.counting()));
+            sales7DaysMap.putAll(tempSalesMap);
+        }
+
+        // 查询近30天好评数(score >= 4.5)
+        Map<Integer, Long> goodComment30DaysMap = new HashMap<>();
+        // 查询近7天评论数
+        Map<Integer, Long> comment7DaysMap = new HashMap<>();
+        // 查询总评论数
+        Map<Integer, Long> totalCommentMap = new HashMap<>();
+
+        if (finalSortType == 2) { // 好评优先需要评论数据
+            // 近30天好评数
+            LambdaQueryWrapper<StoreComment> goodCommentWrapper = new LambdaQueryWrapper<>();
+            goodCommentWrapper.in(StoreComment::getStoreId, storeIds)
+                    .eq(StoreComment::getBusinessType, 5)
+                    .eq(StoreComment::getDeleteFlag, 0)
+                    .isNull(StoreComment::getReplyId)
+                    .ge(StoreComment::getScore, 4.5)
+                    .ge(StoreComment::getCreatedTime, thirtyDaysAgoStr);
+            List<StoreComment> goodComments30Days = storeCommentMapper.selectList(goodCommentWrapper);
+            Map<Integer, Long> tempGoodCommentMap = goodComments30Days.stream()
+                    .collect(Collectors.groupingBy(StoreComment::getStoreId, Collectors.counting()));
+            goodComment30DaysMap.putAll(tempGoodCommentMap);
+
+            // 近7天评论数
+            LambdaQueryWrapper<StoreComment> comment7DaysWrapper = new LambdaQueryWrapper<>();
+            comment7DaysWrapper.in(StoreComment::getStoreId, storeIds)
+                    .eq(StoreComment::getBusinessType, 5)
+                    .eq(StoreComment::getDeleteFlag, 0)
+                    .isNull(StoreComment::getReplyId)
+                    .ge(StoreComment::getCreatedTime, sevenDaysAgoStr);
+            List<StoreComment> comments7Days = storeCommentMapper.selectList(comment7DaysWrapper);
+            Map<Integer, Long> tempComment7DaysMap = comments7Days.stream()
+                    .collect(Collectors.groupingBy(StoreComment::getStoreId, Collectors.counting()));
+            comment7DaysMap.putAll(tempComment7DaysMap);
+
+            // 总评论数
+            LambdaQueryWrapper<StoreComment> totalCommentWrapper = new LambdaQueryWrapper<>();
+            totalCommentWrapper.in(StoreComment::getStoreId, storeIds)
+                    .eq(StoreComment::getBusinessType, 5)
+                    .eq(StoreComment::getDeleteFlag, 0)
+                    .isNull(StoreComment::getReplyId);
+            List<StoreComment> totalComments = storeCommentMapper.selectList(totalCommentWrapper);
+            Map<Integer, Long> tempTotalCommentMap = totalComments.stream()
+                    .collect(Collectors.groupingBy(StoreComment::getStoreId, Collectors.counting()));
+            totalCommentMap.putAll(tempTotalCommentMap);
+        }
+
+        // 获取全部店铺的评分
+        Map<String, List<Map<String, Object>>> avgScoreMap = storeEvaluationMapper.allStoreAvgScore().stream()
+                .collect(Collectors.groupingBy(o -> o.get("store_id").toString()));
+        Map<String, List<Map<String, Object>>> avgPriceMap = lifeUserOrderMapper.allStoreAvgPrice().stream()
+                .collect(Collectors.groupingBy(o -> o.get("store_id").toString()));
+
+        // 计算综合得分并排序
+        List<StoreInfoVo> sortedList = storeInfoVoList.stream()
+                .map(store -> {
+                    // 获取基础评分(score_avg × 2,标准化为0-10分)
+                    Double scoreAvg = store.getScoreAvg() != null ? store.getScoreAvg() : 0.0;
+                    double baseScore = Math.min(scoreAvg * 2, 10.0); // 基础评分,最高10分
+
+                    // 获取距离
+                    double storeDistance = 999999;
+                    try {
+                        String distStr = store.getDist();
+                        if (distStr != null && !distStr.isEmpty()) {
+                            storeDistance = Double.parseDouble(distStr);
+                        }
+                    } catch (NumberFormatException e) {
+                        // 忽略
+                    }
+
+                    double finalScore = 0.0;
+
+                    if (finalSortType == 1) {
+                        // 智能排序:综合评分×50% + 近7天销量×30% + 距离得分×20%
+                        // 综合评分(基础评分)
+                        double scorePart = baseScore * 0.5;
+
+                        // 近7天销量(需要标准化,假设最大销量为100)
+                        long sales7Days = sales7DaysMap.getOrDefault(store.getId(), 0L);
+                        double salesScore = Math.min(sales7Days / 100.0 * 10, 10.0); // 标准化到0-10
+                        double salesPart = salesScore * 0.3;
+
+                        // 距离得分(距离越近得分越高,10公里内计算)
+                        double distanceScore = storeDistance <= 10 ? (10 - storeDistance) / 10.0 * 10 : 0;
+                        double distancePart = distanceScore * 0.2;
+
+                        finalScore = scorePart + salesPart + distancePart;
+                    } else if (finalSortType == 2) {
+                        // 好评优先:综合评分×50% + 近30天好评数×35% + 近7天新评占比×15%
+                        double scorePart = baseScore * 0.5;
+
+                        // 近30天好评数(需要标准化,假设最大好评数为50)
+                        long goodComment30Days = goodComment30DaysMap.getOrDefault(store.getId(), 0L);
+                        double goodCommentScore = Math.min(goodComment30Days / 50.0 * 10, 10.0);
+                        double goodCommentPart = goodCommentScore * 0.35;
+
+                        // 近7天新评占比
+                        long comment7Days = comment7DaysMap.getOrDefault(store.getId(), 0L);
+                        long totalComment = totalCommentMap.getOrDefault(store.getId(), 1L); // 避免除0
+                        double newCommentRatio = (double) comment7Days / totalComment;
+                        double newCommentPart = newCommentRatio * 10 * 0.15; // 占比转换为0-10分
+
+                        finalScore = scorePart + goodCommentPart + newCommentPart;
+                    } else if (finalSortType == 3) {
+                        // 距离优先:距离得分 = (10 - 实际距离) × 80% + 基础评分 × 20%(10公里内计算)
+                        if (storeDistance <= 10) {
+                            double distanceScore = (10 - storeDistance) / 10.0 * 10; // 标准化到0-10
+                            double distancePart = distanceScore * 0.8;
+                            double scorePart = baseScore * 0.2;
+                            finalScore = distancePart + scorePart;
+                        } else {
+                            finalScore = -1; // 超出范围,不展示
+                        }
+                    }
+
+                    // 设置综合得分(用于排序)
+                    store.setDistance(storeDistance);
+                    // 使用反射或扩展字段存储finalScore,这里我们使用一个临时字段
+                    // 由于StoreInfoVo没有finalScore字段,我们使用distance字段临时存储,排序后再恢复
+                    return new Object[] { store, finalScore };
+                })
+                .filter(item -> {
+                    // 距离优先模式:过滤掉超出范围的
+                    if (finalSortType == 3) {
+                        return ((Double) item[1]) >= 0;
+                    }
+                    return true;
+                })
+                .sorted((a, b) -> Double.compare((Double) b[1], (Double) a[1])) // 按得分降序
+                .map(item -> (StoreInfoVo) item[0])
+                .collect(Collectors.toList());
+
+        // 分页处理
+        long total = sortedList.size();
+        int start = (pageNum - 1) * pageSize;
+        int end = Math.min(start + pageSize, sortedList.size());
+        List<StoreInfoVo> pagedList;
+        if (start < sortedList.size()) {
+            pagedList = sortedList.subList(start, end);
+        } else {
+            pagedList = new ArrayList<>();
+        }
+
+        // 查询每个店铺的最新代金券(只显示一个,最新创建的)
+        Map<Integer, LifeCoupon> latestCouponMap = new HashMap<>();
+        if (!CollectionUtils.isEmpty(pagedList)) {
+            List<Integer> pagedStoreIds = pagedList.stream().map(StoreInfoVo::getId).collect(Collectors.toList());
+            // 查询所有店铺的代金券,按创建时间降序
+            LambdaQueryWrapper<LifeCoupon> couponWrapper = new LambdaQueryWrapper<>();
+            couponWrapper.in(LifeCoupon::getStoreId, pagedStoreIds.stream().map(String::valueOf).collect(Collectors.toList()))
+                    .eq(LifeCoupon::getStatus, CouponStatusEnum.ONGOING.getCode())
+                    .eq(LifeCoupon::getType, 1) // 代金券类型
+                    .eq(LifeCoupon::getDeleteFlag, 0)
+                    .orderByDesc(LifeCoupon::getCreatedTime);
+            List<LifeCoupon> allCoupons = lifeCouponMapper.selectList(couponWrapper);
+
+            // 为每个店铺只保留最新创建的一个代金券
+            for (LifeCoupon coupon : allCoupons) {
+                if (coupon.getStoreId() != null) {
+                    try {
+                        Integer storeId = Integer.parseInt(coupon.getStoreId());
+                        // 如果该店铺还没有代金券,或者当前代金券更新,则更新
+                        if (!latestCouponMap.containsKey(storeId)) {
+                            latestCouponMap.put(storeId, coupon);
+                        }
+                    } catch (NumberFormatException e) {
+                        // 忽略无效的storeId
+                    }
+                }
+            }
+        }
+
+        // 设置评分、平均消费和代金券
+        for (StoreInfoVo store : pagedList) {
+            if (avgScoreMap.containsKey(String.valueOf(store.getId()))) {
+                store.setAvgScore(new BigDecimal(avgScoreMap.get(String.valueOf(store.getId())).get(0).get("avg_score").toString())
+                        .setScale(1, RoundingMode.HALF_UP).toString());
+                store.setTotalNum(avgScoreMap.get(String.valueOf(store.getId())).get(0).get("total_num").toString());
+            } else {
+                store.setAvgScore("0");
+                store.setTotalNum("0");
+            }
+
+            if (avgPriceMap.containsKey(String.valueOf(store.getId()))) {
+                store.setAvgPrice(avgPriceMap.get(String.valueOf(store.getId())).get(0).get("avg_price").toString());
+            } else {
+                store.setAvgPrice("0");
+            }
+
+            // 设置最新代金券(只显示一个)
+            if (latestCouponMap.containsKey(store.getId())) {
+                LifeCoupon latestCoupon = latestCouponMap.get(store.getId());
+                LifeCouponVo couponVo = new LifeCouponVo();
+                BeanUtils.copyProperties(latestCoupon, couponVo);
+                // 只设置一个代金券到列表中
+                List<LifeCouponVo> couponList = new ArrayList<>();
+                couponList.add(couponVo);
+                store.setCouponList(couponList);
+            } else {
+                // 如果没有代金券,设置为空列表
+                store.setCouponList(new ArrayList<>());
+            }
+        }
+
+        // 创建分页对象
+        IPage<StoreInfoVo> resultPage = new Page<>(pageNum, pageSize);
+        resultPage.setRecords(pagedList);
+        resultPage.setTotal(total);
+        resultPage.setCurrent(pageNum);
+        resultPage.setSize(pageSize);
+        resultPage.setPages((total + pageSize - 1) / pageSize);
+
+        return resultPage;
+    }
+
+    /**
+     * 查询OCR图片上传记录并转换为JSONObject
+     *
+     * @param storeUserId 店铺用户ID
+     * @param ocrType     OCR类型
+     * @param likeKeyword 模糊查询关键词,可为null
+     * @param includeImageUrl 是否包含imageUrl字段
+     * @return JSONObject,如果查询结果为空则返回空的JSONObject
+     */
+    private com.alibaba.fastjson2.JSONObject convertOcrResultToJson(Integer storeUserId, String ocrType, String likeKeyword, boolean includeImageUrl) {
+        LambdaQueryWrapper<OcrImageUpload> wrapper = new LambdaQueryWrapper<OcrImageUpload>()
+                .eq(OcrImageUpload::getStoreUserId, storeUserId)
+                .eq(OcrImageUpload::getOcrType, ocrType)
+                .orderByDesc(OcrImageUpload::getCreateTime)
+                .last("limit 1");
+
+        if (StringUtils.isNotEmpty(likeKeyword)) {
+            wrapper.like(OcrImageUpload::getOcrResult, likeKeyword);
+        }
+
+        OcrImageUpload ocrImageUpload = ocrImageUploadMapper.selectOne(wrapper);
+
+        if (ocrImageUpload == null || StringUtils.isEmpty(ocrImageUpload.getOcrResult())) {
+            return new com.alibaba.fastjson2.JSONObject();
+        }
+
+        com.alibaba.fastjson2.JSONObject jsonObject = com.alibaba.fastjson2.JSONObject.parseObject(ocrImageUpload.getOcrResult());
+        if (includeImageUrl && StringUtils.isNotEmpty(ocrImageUpload.getImageUrl())) {
+            jsonObject.put("imageUrl", ocrImageUpload.getImageUrl());
+        }
+
+        return jsonObject;
+    }
+
+
+
+    /**
+     * 你可能还喜欢(推荐店铺)
+     * 根据一级分类、二级分类、三级分类进行店铺筛选
+     * 筛选顺序:先三级,然后二级,最后一级
+     *
+     * @param businessSection 一级分类(经营板块)
+     * @param businessTypes 二级分类(经营种类)
+     * @param businessClassify 三级分类(分类)
+     * @param lon 经度
+     * @param lat 纬度
+     * @return List<StoreInfoVo> 店铺信息列表
+     */
+    @Override
+    public List<StoreInfoVo> getRecommendedStores(String businessSection, String businessTypes, String businessClassify, Double lon, Double lat) {
+        log.info("StoreInfoServiceImpl.getRecommendedStores?businessSection={},businessTypes={},businessClassify={},lon={},lat={}",
+                businessSection, businessTypes, businessClassify, lon, lat);
+
+        QueryWrapper<StoreInfoVo> queryWrapper = new QueryWrapper<>();
+        // 基础条件:未删除、已启用
+        queryWrapper.eq("a.delete_flag", 0)
+                .eq("b.delete_flag", 0)
+                .ne("a.business_status", 99) // 过滤永久关门的店铺
+                .ne("a.store_status", 0); // 过滤禁用的店铺
+
+        // 按照三级->二级->一级的顺序筛选
+
+        // 1. 先按三级分类(business_classify)筛选
+        if (StringUtils.isNotEmpty(businessClassify)) {
+            // 解析businessClassify参数(格式:1,2,3)
+            String[] classifyArray = businessClassify.split(",");
+            // 使用FIND_IN_SET函数检查数据库字段是否包含参数中的任何一个值
+            queryWrapper.and(wrapper -> {
+                for (int i = 0; i < classifyArray.length; i++) {
+                    String classify = classifyArray[i].trim();
+                    if (StringUtils.isNotEmpty(classify)) {
+                        if (i == 0) {
+                            wrapper.apply("FIND_IN_SET({0}, a.business_classify) > 0", classify);
+                        } else {
+                            wrapper.or().apply("FIND_IN_SET({0}, a.business_classify) > 0", classify);
+                        }
+                    }
+                }
+            });
+        }
+
+        // 2. 然后按二级分类(business_types)筛选
+        if (StringUtils.isNotEmpty(businessTypes)) {
+            // business_types可能是逗号分隔的字符串,使用FIND_IN_SET处理
+            String[] typesArray = businessTypes.split(",");
+            queryWrapper.and(wrapper -> {
+                for (int i = 0; i < typesArray.length; i++) {
+                    String type = typesArray[i].trim();
+                    if (StringUtils.isNotEmpty(type)) {
+                        if (i == 0) {
+                            wrapper.apply("FIND_IN_SET({0}, a.business_types) > 0", type);
+                        } else {
+                            wrapper.or().apply("FIND_IN_SET({0}, a.business_types) > 0", type);
+                        }
+                    }
+                }
+            });
+        }
+
+        // 3. 最后按一级分类(business_section)筛选
+        if (StringUtils.isNotEmpty(businessSection)) {
+            try {
+                Integer sectionId = Integer.parseInt(businessSection.trim());
+                queryWrapper.eq("a.business_section", sectionId);
+            } catch (NumberFormatException e) {
+                log.warn("一级分类参数格式错误: {}", businessSection);
+            }
+        }
+
+        // 查询店铺列表
+        List<StoreInfoVo> storeInfoVoList;
+        if (lon != null && lat != null) {
+            // 如果提供了经纬度,使用带距离计算的查询方法
+            String position = lon + "," + lat;
+            queryWrapper.isNotNull("a.store_position")
+                    .ne("a.store_position", "");
+            // 按距离排序
+            queryWrapper.orderByAsc("dist");
+            storeInfoVoList = storeInfoMapper.getStoreInfoVoListNew(queryWrapper, position);
+        } else {
+            // 如果没有提供经纬度,使用普通查询方法
+            queryWrapper.orderByDesc("a.created_time");
+            storeInfoVoList = storeInfoMapper.getStoreInfoVoList(queryWrapper);
+        }
+
+        if (CollectionUtils.isEmpty(storeInfoVoList)) {
+            return Collections.emptyList();
+        }
+
+        // 处理店铺信息,设置评分等
+        // 提前查询所有需要的字典数据
+        List<StoreInfoVo> collect = storeInfoVoList.stream()
+                .filter(record -> StringUtils.isNotEmpty(record.getStoreType()))
+                .collect(Collectors.toList());
+        Set<String> allTypes = collect.stream()
+                .map(StoreInfoVo::getStoreType)
+                .flatMap(type -> Arrays.stream(type.split(",")))
+                .collect(Collectors.toSet());
+
+        List<StoreDictionary> storeDictionaries = storeDictionaryMapper.selectList(
+                new LambdaQueryWrapper<StoreDictionary>()
+                        .eq(StoreDictionary::getTypeName, "storeType")
+                        .isNull(StoreDictionary::getParentId)
+                        .in(!allTypes.isEmpty(), StoreDictionary::getDictId, allTypes));
+        Map<String, String> typeMap = storeDictionaries.stream()
+                .collect(Collectors.toMap(StoreDictionary::getDictId, StoreDictionary::getDictDetail));
+
+        // 计算平均分和评价
+        Map<String, List<Map<String, Object>>> avgScoreMap = new HashMap<>();
+        Map<Integer, List<StoreComment>> commentMap = new HashMap<>();
+
+        avgScoreMap = storeEvaluationMapper.allStoreAvgScore().stream()
+                .collect(Collectors.groupingBy(o -> o.get("store_id").toString()));
+        commentMap = storeCommentMapper.selectList(
+                        new QueryWrapper<StoreComment>()
+                                .eq("business_type", "5")
+                                .eq("delete_flag", 0))
+                .stream()
+                .collect(Collectors.groupingBy(StoreComment::getStoreId));
+
+        for (StoreInfoVo record : storeInfoVoList) {
+            // 处理类型
+            if (StringUtils.isNotEmpty(record.getStoreType())) {
+                String[] types = record.getStoreType().split(",");
+                List<String> typeDetails = Arrays.stream(types)
+                        .map(typeMap::get)
+                        .filter(Objects::nonNull)
+                        .collect(Collectors.toList());
+                record.setStoreTypeStr(String.join(",", typeDetails));
+                record.setStoreTypeList(Arrays.asList(types));
+            }
+
+            // 处理经纬度
+            if (StringUtils.isNotEmpty(record.getStorePosition())) {
+                String[] split = record.getStorePosition().split(",");
+                if (split.length >= 2) {
+                    record.setStorePositionLongitude(split[0]);
+                    record.setStorePositionLatitude(split[1]);
+                }
+            }
+
+            // 如果提供了经纬度,处理距离信息
+            if (lon != null && lat != null && StringUtils.isNotEmpty(record.getStorePosition())) {
+                try {
+                    // 手动计算距离(单位:公里)
+                    String[] positionArray = record.getStorePosition().split(",");
+                    if (positionArray.length >= 2) {
+                        double storeLon = Double.parseDouble(positionArray[0].trim());
+                        double storeLat = Double.parseDouble(positionArray[1].trim());
+                        // 计算距离(单位:公里)
+                        double distanceKm = DistanceUtil.haversineCalculateDistance(lon, lat, storeLon, storeLat);
+                        record.setDistance(distanceKm);
+                    }
+                } catch (Exception e) {
+                    log.warn("计算店铺距离失败,storeId={},error={}", record.getId(), e.getMessage());
+                }
+            }
+
+            // 处理到期状态
+            Date expirationTime = record.getExpirationTime();
+            if (expirationTime != null) {
+                Date currentDate = new Date();
+                Calendar now = Calendar.getInstance();
+                Date nowCurrentDate = now.getTime();
+                now.add(Calendar.DAY_OF_YEAR, 30);
+                Date thirtyDaysLater = now.getTime();
+
+                if (expirationTime.after(currentDate)) {
+                    record.setExpiredState("0");
+                    if ((expirationTime.after(nowCurrentDate) || expirationTime.equals(nowCurrentDate))
+                            && expirationTime.before(thirtyDaysLater)) {
+                        record.setExpiredState("1");
+                    }
+                } else {
+                    record.setExpiredState("2");
+                }
+
+                LocalDate nowLocal = LocalDate.now();
+                LocalDate expDate = expirationTime.toInstant()
+                        .atZone(ZoneId.systemDefault())
+                        .toLocalDate();
+                long daysToExpire = ChronoUnit.DAYS.between(nowLocal, expDate);
+                record.setDaysToExpire(daysToExpire);
+            }
+
+            // 设置店铺得分和总评论数
+            if (avgScoreMap.containsKey(String.valueOf(record.getId()))) {
+                record.setAvgScore(String.valueOf(
+                        avgScoreMap.get(String.valueOf(record.getId())).get(0).get("avg_score")));
+            }
+            if (commentMap.containsKey(record.getId())) {
+                record.setTotalNum(String.valueOf(commentMap.get(record.getId()).size()));
+            }
+        }
+
+        return storeInfoVoList;
+    }
+
+
+
+    /**
+     * 构建树形结构(优化版)
+     *
+     * @param flatList 扁平列表
+     * @return 树形结构列表
+     */
+    private List<StoreDictionary> buildTreeOptimized(List<StoreDictionary> flatList) {
+        if (flatList == null || flatList.isEmpty()) {
+            return new ArrayList<>();
+        }
+
+        // 创建三个存储结构
+        Map<Integer, StoreDictionary> nodeMap = new HashMap<>();  // ID到节点的映射
+        Map<Integer, List<StoreDictionary>> parentChildMap = new HashMap<>();  // 父ID到子节点列表的映射
+        List<StoreDictionary> result = new ArrayList<>();  // 结果列表
+
+        // 填充nodeMap和parentChildMap
+        for (StoreDictionary entity : flatList) {
+            Integer id = entity.getId();
+            Integer parentId = entity.getParentId();
+
+            // 存入节点映射
+            nodeMap.put(id, entity);
+
+            // 初始化子节点列表
+            entity.setStoreDictionaryList(new ArrayList<>());
+
+            // 如果是根节点(parentId为null或0),直接添加到结果
+            if (parentId == null || parentId == 0) {
+                result.add(entity);
+            } else {
+                // 否则,记录父子关系
+                parentChildMap.computeIfAbsent(parentId, k -> new ArrayList<>()).add(entity);
+            }
+        }
+
+        // 建立父子关系
+        for (StoreDictionary entity : flatList) {
+            Integer id = entity.getId();
+            if (parentChildMap.containsKey(id)) {
+                entity.getStoreDictionaryList().addAll(parentChildMap.get(id));
+            }
+        }
+
+        return result;
+    }
+
+    @Override
+    public List<StoreDictionaryVo> getLeisureEntertainmentCategories() {
+        // 定义四种主分类名称
+        List<String> mainCategoryNames = Arrays.asList("酒吧", "KTV", "洗浴汗蒸", "按摩足疗");
+
+        // 查询所有主分类(business_section类型)
+        List<StoreDictionary> allMainCategories = storeDictionaryMapper.selectList(
+                new LambdaQueryWrapper<StoreDictionary>()
+                        .eq(StoreDictionary::getTypeName, "business_section")
+                        .in(StoreDictionary::getDictDetail, mainCategoryNames)
+                        .eq(StoreDictionary::getDeleteFlag, 0)
+        );
+
+        // 构建主分类列表,包含"全部"选项
+        List<StoreDictionaryVo> result = new ArrayList<>();
+
+        // 添加"全部"选项
+        StoreDictionaryVo allCategory = new StoreDictionaryVo();
+        allCategory.setId(0);
+        allCategory.setDictId("0");
+        allCategory.setDictDetail("全部");
+        allCategory.setTypeName("business_section");
+        allCategory.setSubDataList(new ArrayList<>());
+        result.add(allCategory);
+
+        // 查询所有子分类(根据parent_id关联)
+        Map<Integer, List<StoreDictionary>> subCategoryMap = new HashMap<>();
+        if (!CollectionUtils.isEmpty(allMainCategories)) {
+            List<Integer> mainCategoryIds = allMainCategories.stream()
+                    .map(StoreDictionary::getId)
+                    .collect(Collectors.toList());
+
+            // 查询所有子分类
+            List<StoreDictionary> allSubCategories = storeDictionaryMapper.selectList(
+                    new LambdaQueryWrapper<StoreDictionary>()
+                            .in(StoreDictionary::getParentId, mainCategoryIds)
+                            .eq(StoreDictionary::getDeleteFlag, 0)
+                            .in(StoreDictionary::getTypeName, "business_section","business_type","business_classify")
+            );
+
+            // 按parentId分组
+            subCategoryMap = allSubCategories.stream()
+                    .collect(Collectors.groupingBy(StoreDictionary::getParentId));
+        }
+
+        // 构建主分类及其子分类
+        for (StoreDictionary mainCategory : allMainCategories) {
+            StoreDictionaryVo mainVo = new StoreDictionaryVo();
+            BeanUtils.copyProperties(mainCategory, mainVo);
+
+            // 获取该主分类下的子分类
+            List<StoreDictionary> subCategories = subCategoryMap.getOrDefault(mainCategory.getId(), new ArrayList<>());
+            List<StoreDictionaryVo> subVoList = new ArrayList<>();
+
+            for (StoreDictionary subCategory : subCategories) {
+                StoreDictionaryVo subVo = new StoreDictionaryVo();
+                BeanUtils.copyProperties(subCategory, subVo);
+                subVoList.add(subVo);
+            }
+
+            mainVo.setSubDataList(subVoList);
+            result.add(mainVo);
+        }
+
+        return result;
+    }
+
+
+    @Override
+    public IPage<StoreInfoVo> getSpecialTypeStoresByDistance(Double lon, Double lat, Double distance, Integer sortType, Integer businessType, Integer categoryId, int pageNum, int pageSize) {
+        // 参数校验
+        if (lon == null || lat == null) {
+            throw new IllegalArgumentException("经纬度参数不能为空");
+        }
+
+        // 智能检测:如果参数可能传反了(lat超出范围但lon在范围内),给出提示
+        if (Math.abs(lat) > 90 && Math.abs(lon) <= 90) {
+            throw new IllegalArgumentException(
+                    String.format("参数可能传反了!当前值: lon=%s, lat=%s。经度范围: [-180, 180],纬度范围: [-90, 90]。请检查参数顺序。", lon, lat)
+            );
+        }
+
+        // 校验经纬度范围:经度 [-180, 180],纬度 [-90, 90]
+        if (lon < -180 || lon > 180) {
+            throw new IllegalArgumentException("经度参数超出有效范围 [-180, 180],当前值: " + lon);
+        }
+        if (lat < -90 || lat > 90) {
+            throw new IllegalArgumentException("纬度参数超出有效范围 [-90, 90],当前值: " + lat);
+        }
+        final int finalSortType = (sortType == null || sortType < 1 || sortType > 3) ? 1 : sortType; // 默认智能排序
+        if (pageNum <= 0) {
+            pageNum = 1;
+        }
+        if (pageSize <= 0) {
+            pageSize = 10;
+        }
+
+        // 构建查询条件
+        QueryWrapper<StoreInfoVo> queryWrapper = new QueryWrapper<>();
+
+        // 如果传入了dictId,根据字典id查询经营板块、经营种类、分类并匹配店铺
+        if (categoryId != null) {
+            // 根据categoryId查询字典表记录
+            StoreDictionary dict = storeDictionaryMapper.selectById(categoryId);
+            if (dict == null || dict.getDeleteFlag() == 1) {
+                throw new IllegalArgumentException("字典id不存在或已删除: " + categoryId);
+            }
+
+            String typeName = dict.getTypeName();
+            String dictIdStr =dict.getDictId();
+
+            if ("business_section".equals(typeName)) {
+                // 如果是经营板块,直接匹配business_section
+                try {
+                    Integer sectionId = Integer.parseInt(dictIdStr);
+                    queryWrapper.eq("a.business_section", sectionId);
+                } catch (NumberFormatException e) {
+                    throw new IllegalArgumentException("经营板块dictId格式错误: " + dictIdStr);
+                }
+            } else if ("business_type".equals(typeName)) {
+                // 如果是经营种类,需要:
+                // 1. 向上查找父级(经营板块)
+                // 2. 匹配business_section和business_types
+                StoreDictionary parentDict = storeDictionaryMapper.selectById(dict.getParentId());
+                if (parentDict == null || !"business_section".equals(parentDict.getTypeName())) {
+                    throw new IllegalArgumentException("经营种类的父级不是经营板块,categoryId: " + categoryId);
+                }
+
+                try {
+                    Integer sectionId = Integer.parseInt(parentDict.getDictId());
+                    queryWrapper.eq("a.business_section", sectionId);
+                    // 使用FIND_IN_SET匹配business_types字段(逗号分隔)
+                    queryWrapper.apply("FIND_IN_SET({0}, a.business_types) > 0", dictIdStr);
+                } catch (NumberFormatException e) {
+                    throw new IllegalArgumentException("经营板块或经营种类dictId格式错误");
+                }
+            } else if ("business_classify".equals(typeName)) {
+                // 如果是分类,需要:
+                // 1. 向上查找父级(经营种类)
+                // 2. 向上查找祖父级(经营板块)
+                // 3. 匹配business_section、business_types和business_classify
+                StoreDictionary parentDict = storeDictionaryMapper.selectById(dict.getParentId());
+                if (parentDict == null || !"business_type".equals(parentDict.getTypeName())) {
+                    throw new IllegalArgumentException("分类的父级不是经营种类,categoryId: " + categoryId);
+                }
+
+                StoreDictionary grandParentDict = storeDictionaryMapper.selectById(parentDict.getParentId());
+                if (grandParentDict == null || !"business_section".equals(grandParentDict.getTypeName())) {
+                    throw new IllegalArgumentException("经营种类的父级不是经营板块,categoryId: " + categoryId);
+                }
+
+                try {
+                    Integer sectionId = Integer.parseInt(grandParentDict.getDictId());
+                    queryWrapper.eq("a.business_section", sectionId);
+                    // 使用FIND_IN_SET匹配business_types字段
+                    queryWrapper.apply("FIND_IN_SET({0}, a.business_types) > 0", parentDict.getDictId());
+                    // 使用FIND_IN_SET匹配business_classify字段
+                    queryWrapper.apply("FIND_IN_SET({0}, a.business_classify) > 0", dictIdStr);
+                } catch (NumberFormatException e) {
+                    throw new IllegalArgumentException("经营板块、经营种类或分类dictId格式错误");
+                }
+            } else {
+                throw new IllegalArgumentException("不支持的字典类型: " + typeName + ", categoryId: " + categoryId);
+            }
+        } else if (businessType != null) {
+            // 如果指定了businessType,则根据传入的数值进行筛选
+            // 直接使用传入的数值作为business_section进行筛选
+            // KTV=3、洗浴汗蒸=4、按摩足浴=5,酒吧的数值未知(由调用方传入)
+            queryWrapper.eq("a.business_section", businessType);
+        } else {
+            // 如果没有指定businessType,则查询所有四种类型的店铺
+            // 需要查询字典表获取所有四种类型的dictId
+            List<String> storeTypeNames = Arrays.asList("酒吧", "KTV", "洗浴汗蒸", "按摩足疗");
+            List<StoreDictionary> storeDictionaries = storeDictionaryMapper.selectList(
+                    new LambdaQueryWrapper<StoreDictionary>()
+                            .eq(StoreDictionary::getTypeName, "business_section")
+                            .in(StoreDictionary::getDictDetail, storeTypeNames)
+                            .eq(StoreDictionary::getDeleteFlag, 0)
+            );
+
+            List<Integer> businessSectionIds = storeDictionaries.stream()
+                    .filter(d -> StringUtils.isNotEmpty(d.getDictId()))
+                    .map(StoreDictionary::getDictId)
+                    .map(dictIdStr -> {
+                        try {
+                            return Integer.parseInt(dictIdStr);
+                        } catch (NumberFormatException e) {
+                            return null;
+                        }
+                    })
+                    .filter(Objects::nonNull)
+                    .collect(Collectors.toList());
+
+            // 使用business_section字段进行筛选
+            queryWrapper.in("a.business_section", businessSectionIds);
+        }
+
+        // 过滤已删除的门店
+        queryWrapper.eq("a.delete_flag", 0);
+        // 过滤注销的门店
+        queryWrapper.eq("a.logout_flag", 0);
+        // 过滤禁用的门店
+        queryWrapper.ne("a.store_status", 0);
+        // 过滤永久关门的店铺
+        queryWrapper.ne("a.business_status", 99);
+        // 过滤过期的经营许可证
+        queryWrapper.ge("a.entertainment_licence_expiration_time", new Date());
+
+        // 距离优先模式:只显示10公里内且3.5星以上的店铺
+        final Double finalDistance;
+        if (finalSortType == 3) {
+            queryWrapper.ge("a.score_avg", 3.5);
+            if (distance == null || distance <= 0) {
+                finalDistance = 10.0; // 默认10公里
+            } else if (distance > 10) {
+                finalDistance = 10.0; // 距离优先最多10公里
+            } else {
+                finalDistance = distance;
+            }
+        } else {
+            finalDistance = distance;
+        }
+
+        // 先按距离排序获取所有数据(用于计算距离)
+        queryWrapper.orderByAsc("dist");
+
+        // 创建分页对象(先获取更多数据用于排序计算)
+        IPage<StoreInfoVo> page = new Page<>(1, 1000); // 先获取足够多的数据
+        IPage<StoreInfoVo> storeInfoIPage = storeInfoMapper.getPageForDistance(page, lon + "," + lat, queryWrapper);
+        List<StoreInfoVo> storeInfoVoList = storeInfoIPage.getRecords();
+
+        // 如果指定了距离范围,进行距离筛选
+        if (finalDistance != null && finalDistance > 0) {
+            storeInfoVoList = storeInfoVoList.stream()
+                    .filter(store -> {
+                        String distStr = store.getDist();
+                        if (distStr == null || distStr.isEmpty()) {
+                            return false;
+                        }
+                        try {
+                            double storeDistance = Double.parseDouble(distStr);
+                            return storeDistance <= finalDistance;
+                        } catch (NumberFormatException e) {
+                            return false;
+                        }
+                    })
+                    .collect(Collectors.toList());
+        }
+
+        // 计算时间范围
+        LocalDateTime now = LocalDateTime.now();
+        LocalDateTime sevenDaysAgo = now.minusDays(7);
+        LocalDateTime thirtyDaysAgo = now.minusDays(30);
+        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+        String sevenDaysAgoStr = sevenDaysAgo.format(formatter);
+        String thirtyDaysAgoStr = thirtyDaysAgo.format(formatter);
+
+        // 提取所有门店ID
+        List<Integer> storeIds = storeInfoVoList.stream().map(StoreInfoVo::getId).collect(Collectors.toList());
+
+        if (CollectionUtils.isEmpty(storeIds)) {
+            // 如果没有符合条件的店铺,返回空结果
+            IPage<StoreInfoVo> emptyPage = new Page<>(pageNum, pageSize);
+            emptyPage.setRecords(new ArrayList<>());
+            emptyPage.setTotal(0);
+            return emptyPage;
+        }
+
+        // 查询近7天销量(订单数)
+        Map<Integer, Long> sales7DaysMap = new HashMap<>();
+        if (finalSortType == 1) { // 智能排序需要销量数据
+            LambdaQueryWrapper<LifeUserOrder> orderWrapper = new LambdaQueryWrapper<>();
+            orderWrapper.in(LifeUserOrder::getStoreId, storeIds)
+                    .and(w -> w.and(w1 -> w1.eq(LifeUserOrder::getStatus, 7)
+                                    .ge(LifeUserOrder::getFinishTime, sevenDaysAgoStr))
+                            .or(w2 -> w2.eq(LifeUserOrder::getStatus, 1)
+                                    .ge(LifeUserOrder::getPayTime, sevenDaysAgoStr)))
+                    .eq(LifeUserOrder::getDeleteFlag, 0);
+            List<LifeUserOrder> orders7Days = lifeUserOrderMapper.selectList(orderWrapper);
+            Map<Integer, Long> tempSalesMap = orders7Days.stream()
+                    .filter(order -> order.getStoreId() != null)
+                    .collect(Collectors.groupingBy(
+                            order -> Integer.parseInt(order.getStoreId()),
+                            Collectors.counting()));
+            sales7DaysMap.putAll(tempSalesMap);
+        }
+
+        // 查询近30天好评数(score >= 4.5)
+        Map<Integer, Long> goodComment30DaysMap = new HashMap<>();
+        // 查询近7天评论数
+        Map<Integer, Long> comment7DaysMap = new HashMap<>();
+        // 查询总评论数
+        Map<Integer, Long> totalCommentMap = new HashMap<>();
+
+        if (finalSortType == 2) { // 好评优先需要评论数据
+            // 近30天好评数
+            LambdaQueryWrapper<StoreComment> goodCommentWrapper = new LambdaQueryWrapper<>();
+            goodCommentWrapper.in(StoreComment::getStoreId, storeIds)
+                    .eq(StoreComment::getBusinessType, 5)
+                    .eq(StoreComment::getDeleteFlag, 0)
+                    .isNull(StoreComment::getReplyId)
+                    .ge(StoreComment::getScore, 4.5)
+                    .ge(StoreComment::getCreatedTime, thirtyDaysAgoStr);
+            List<StoreComment> goodComments30Days = storeCommentMapper.selectList(goodCommentWrapper);
+            Map<Integer, Long> tempGoodCommentMap = goodComments30Days.stream()
+                    .collect(Collectors.groupingBy(StoreComment::getStoreId, Collectors.counting()));
+            goodComment30DaysMap.putAll(tempGoodCommentMap);
+
+            // 近7天评论数
+            LambdaQueryWrapper<StoreComment> comment7DaysWrapper = new LambdaQueryWrapper<>();
+            comment7DaysWrapper.in(StoreComment::getStoreId, storeIds)
+                    .eq(StoreComment::getBusinessType, 5)
+                    .eq(StoreComment::getDeleteFlag, 0)
+                    .isNull(StoreComment::getReplyId)
+                    .ge(StoreComment::getCreatedTime, sevenDaysAgoStr);
+            List<StoreComment> comments7Days = storeCommentMapper.selectList(comment7DaysWrapper);
+            Map<Integer, Long> tempComment7DaysMap = comments7Days.stream()
+                    .collect(Collectors.groupingBy(StoreComment::getStoreId, Collectors.counting()));
+            comment7DaysMap.putAll(tempComment7DaysMap);
+
+            // 总评论数
+            LambdaQueryWrapper<StoreComment> totalCommentWrapper = new LambdaQueryWrapper<>();
+            totalCommentWrapper.in(StoreComment::getStoreId, storeIds)
+                    .eq(StoreComment::getBusinessType, 5)
+                    .eq(StoreComment::getDeleteFlag, 0)
+                    .isNull(StoreComment::getReplyId);
+            List<StoreComment> totalComments = storeCommentMapper.selectList(totalCommentWrapper);
+            Map<Integer, Long> tempTotalCommentMap = totalComments.stream()
+                    .collect(Collectors.groupingBy(StoreComment::getStoreId, Collectors.counting()));
+            totalCommentMap.putAll(tempTotalCommentMap);
+        }
+
+        // 获取全部店铺的评分
+        Map<String, List<Map<String, Object>>> avgScoreMap = storeEvaluationMapper.allStoreAvgScore().stream()
+                .collect(Collectors.groupingBy(o -> o.get("store_id").toString()));
+        Map<String, List<Map<String, Object>>> avgPriceMap = lifeUserOrderMapper.allStoreAvgPrice().stream()
+                .collect(Collectors.groupingBy(o -> o.get("store_id").toString()));
+
+        // 计算综合得分并排序
+        List<StoreInfoVo> sortedList = storeInfoVoList.stream()
+                .map(store -> {
+                    // 获取基础评分(score_avg × 2,标准化为0-10分)
+                    Double scoreAvg = store.getScoreAvg() != null ? store.getScoreAvg() : 0.0;
+                    double baseScore = Math.min(scoreAvg * 2, 10.0); // 基础评分,最高10分
+
+                    // 获取距离
+                    double storeDistance = 999999;
+                    try {
+                        String distStr = store.getDist();
+                        if (distStr != null && !distStr.isEmpty()) {
+                            storeDistance = Double.parseDouble(distStr);
+                        }
+                    } catch (NumberFormatException e) {
+                        // 忽略
+                    }
+
+                    double finalScore = 0.0;
+
+                    if (finalSortType == 1) {
+                        // 智能排序:综合评分×50% + 近7天销量×30% + 距离得分×20%
+                        // 综合评分(基础评分)
+                        double scorePart = baseScore * 0.5;
+
+                        // 近7天销量(需要标准化,假设最大销量为100)
+                        long sales7Days = sales7DaysMap.getOrDefault(store.getId(), 0L);
+                        double salesScore = Math.min(sales7Days / 100.0 * 10, 10.0); // 标准化到0-10
+                        double salesPart = salesScore * 0.3;
+
+                        // 距离得分(距离越近得分越高,10公里内计算)
+                        double distanceScore = storeDistance <= 10 ? (10 - storeDistance) / 10.0 * 10 : 0;
+                        double distancePart = distanceScore * 0.2;
+
+                        finalScore = scorePart + salesPart + distancePart;
+                    } else if (finalSortType == 2) {
+                        // 好评优先:综合评分×50% + 近30天好评数×35% + 近7天新评占比×15%
+                        double scorePart = baseScore * 0.5;
+
+                        // 近30天好评数(需要标准化,假设最大好评数为50)
+                        long goodComment30Days = goodComment30DaysMap.getOrDefault(store.getId(), 0L);
+                        double goodCommentScore = Math.min(goodComment30Days / 50.0 * 10, 10.0);
+                        double goodCommentPart = goodCommentScore * 0.35;
+
+                        // 近7天新评占比
+                        long comment7Days = comment7DaysMap.getOrDefault(store.getId(), 0L);
+                        long totalComment = totalCommentMap.getOrDefault(store.getId(), 1L); // 避免除0
+                        double newCommentRatio = (double) comment7Days / totalComment;
+                        double newCommentPart = newCommentRatio * 10 * 0.15; // 占比转换为0-10分
+
+                        finalScore = scorePart + goodCommentPart + newCommentPart;
+                    } else if (finalSortType == 3) {
+                        // 距离优先:距离得分 = (10 - 实际距离) × 80% + 基础评分 × 20%(10公里内计算)
+                        if (storeDistance <= 10) {
+                            double distanceScore = (10 - storeDistance) / 10.0 * 10; // 标准化到0-10
+                            double distancePart = distanceScore * 0.8;
+                            double scorePart = baseScore * 0.2;
+                            finalScore = distancePart + scorePart;
+                        } else {
+                            finalScore = -1; // 超出范围,不展示
+                        }
+                    }
+
+                    // 设置综合得分(用于排序)
+                    store.setDistance(storeDistance);
+                    // 使用反射或扩展字段存储finalScore,这里我们使用一个临时字段
+                    // 由于StoreInfoVo没有finalScore字段,我们使用distance字段临时存储,排序后再恢复
+                    return new Object[] { store, finalScore };
+                })
+                .filter(item -> {
+                    // 距离优先模式:过滤掉超出范围的
+                    if (finalSortType == 3) {
+                        return ((Double) item[1]) >= 0;
+                    }
+                    return true;
+                })
+                .sorted((a, b) -> Double.compare((Double) b[1], (Double) a[1])) // 按得分降序
+                .map(item -> (StoreInfoVo) item[0])
+                .collect(Collectors.toList());
+
+        // 分页处理
+        long total = sortedList.size();
+        int start = (pageNum - 1) * pageSize;
+        int end = Math.min(start + pageSize, sortedList.size());
+        List<StoreInfoVo> pagedList;
+        if (start < sortedList.size()) {
+            pagedList = sortedList.subList(start, end);
+        } else {
+            pagedList = new ArrayList<>();
+        }
+
+        // 查询每个店铺的最新代金券(只显示一个,最新创建的)
+        Map<Integer, LifeCoupon> latestCouponMap = new HashMap<>();
+        if (!CollectionUtils.isEmpty(pagedList)) {
+            List<Integer> pagedStoreIds = pagedList.stream().map(StoreInfoVo::getId).collect(Collectors.toList());
+            // 查询所有店铺的代金券,按创建时间降序
+            LambdaQueryWrapper<LifeCoupon> couponWrapper = new LambdaQueryWrapper<>();
+            couponWrapper.in(LifeCoupon::getStoreId, pagedStoreIds.stream().map(String::valueOf).collect(Collectors.toList()))
+                    .eq(LifeCoupon::getStatus, CouponStatusEnum.ONGOING.getCode())
+                    .eq(LifeCoupon::getType, 1) // 代金券类型
+                    .eq(LifeCoupon::getDeleteFlag, 0)
+                    .orderByDesc(LifeCoupon::getCreatedTime);
+            List<LifeCoupon> allCoupons = lifeCouponMapper.selectList(couponWrapper);
+
+            // 为每个店铺只保留最新创建的一个代金券
+            for (LifeCoupon coupon : allCoupons) {
+                if (coupon.getStoreId() != null) {
+                    try {
+                        Integer storeId = Integer.parseInt(coupon.getStoreId());
+                        // 如果该店铺还没有代金券,或者当前代金券更新,则更新
+                        if (!latestCouponMap.containsKey(storeId)) {
+                            latestCouponMap.put(storeId, coupon);
+                        }
+                    } catch (NumberFormatException e) {
+                        // 忽略无效的storeId
+                    }
+                }
+            }
+        }
+
+        // 设置评分、平均消费和代金券
+        for (StoreInfoVo store : pagedList) {
+            if (avgScoreMap.containsKey(String.valueOf(store.getId()))) {
+                store.setAvgScore(new BigDecimal(avgScoreMap.get(String.valueOf(store.getId())).get(0).get("avg_score").toString())
+                        .setScale(1, RoundingMode.HALF_UP).toString());
+                store.setTotalNum(avgScoreMap.get(String.valueOf(store.getId())).get(0).get("total_num").toString());
+            } else {
+                store.setAvgScore("0");
+                store.setTotalNum("0");
+            }
+
+            if (avgPriceMap.containsKey(String.valueOf(store.getId()))) {
+                store.setAvgPrice(avgPriceMap.get(String.valueOf(store.getId())).get(0).get("avg_price").toString());
+            } else {
+                store.setAvgPrice("0");
+            }
+
+            // 设置最新代金券(只显示一个)
+            if (latestCouponMap.containsKey(store.getId())) {
+                LifeCoupon latestCoupon = latestCouponMap.get(store.getId());
+                LifeCouponVo couponVo = new LifeCouponVo();
+                BeanUtils.copyProperties(latestCoupon, couponVo);
+                // 只设置一个代金券到列表中
+                List<LifeCouponVo> couponList = new ArrayList<>();
+                couponList.add(couponVo);
+                store.setCouponList(couponList);
+            } else {
+                // 如果没有代金券,设置为空列表
+                store.setCouponList(new ArrayList<>());
+            }
+        }
+
+        // 创建分页对象
+        IPage<StoreInfoVo> resultPage = new Page<>(pageNum, pageSize);
+        resultPage.setRecords(pagedList);
+        resultPage.setTotal(total);
+        resultPage.setCurrent(pageNum);
+        resultPage.setSize(pageSize);
+        resultPage.setPages((total + pageSize - 1) / pageSize);
+
+        return resultPage;
+    }
+
+    @Override
+    public List<StoreDictionary> getAllBusinessSection() {
+        // 查询所有经营种类数据
+        LambdaQueryWrapper<StoreDictionary> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.in(StoreDictionary::getTypeName, "business_section","business_type","business_classify");
+        queryWrapper.eq(StoreDictionary::getDeleteFlag, 0);
+        queryWrapper.orderByAsc(StoreDictionary::getSortId);
+        List<StoreDictionary> storeDictionaryList = storeDictionaryMapper.selectList(queryWrapper);
+
+        // 构建三级树形结构
+        return buildTreeOptimized(storeDictionaryList);
+    }
+
+    @Override
+    public List<StoreDictionary> getAllBusinessSection(String businessSection) {
+        // 如果没有传入一级分类参数,返回所有分类
+        if (businessSection == null || businessSection.trim().isEmpty()) {
+            return getAllBusinessSection();
+        }
+
+        // 1. 根据dictId查询一级分类
+        StoreDictionary firstLevelDict = storeDictionaryMapper.selectOne(
+                new LambdaQueryWrapper<StoreDictionary>()
+                        .eq(StoreDictionary::getDictId, businessSection.trim())
+                        .eq(StoreDictionary::getTypeName, "business_section")
+                        .eq(StoreDictionary::getDeleteFlag, 0)
+        );
+
+        if (firstLevelDict == null) {
+            log.warn("未找到一级分类,businessSection={}", businessSection);
+            return new ArrayList<>();
+        }
+
+        // 2. 查询该一级分类下的所有二级分类(business_type)
+        List<StoreDictionary> secondLevelDicts = storeDictionaryMapper.selectList(
+                new LambdaQueryWrapper<StoreDictionary>()
+                        .eq(StoreDictionary::getTypeName, "business_type")
+                        .eq(StoreDictionary::getParentId, firstLevelDict.getId())
+                        .eq(StoreDictionary::getDeleteFlag, 0)
+                        .orderByAsc(StoreDictionary::getSortId)
+        );
+
+        if (secondLevelDicts.isEmpty()) {
+            // 如果没有二级分类,只返回一级分类
+            firstLevelDict.setStoreDictionaryList(new ArrayList<>());
+            return Collections.singletonList(firstLevelDict);
+        }
+
+        // 3. 获取所有二级分类的ID
+        List<Integer> secondLevelIds = secondLevelDicts.stream()
+                .map(StoreDictionary::getId)
+                .collect(Collectors.toList());
+
+        // 4. 查询这些二级分类下的所有三级分类(business_classify)
+        List<StoreDictionary> thirdLevelDicts = storeDictionaryMapper.selectList(
+                new LambdaQueryWrapper<StoreDictionary>()
+                        .eq(StoreDictionary::getTypeName, "business_classify")
+                        .in(StoreDictionary::getParentId, secondLevelIds)
+                        .eq(StoreDictionary::getDeleteFlag, 0)
+                        .orderByAsc(StoreDictionary::getSortId)
+        );
+
+        // 5. 构建树形结构
+        // 将一级分类、二级分类、三级分类合并
+        List<StoreDictionary> allDicts = new ArrayList<>();
+        allDicts.add(firstLevelDict);
+        allDicts.addAll(secondLevelDicts);
+        allDicts.addAll(thirdLevelDicts);
+
+        // 构建树形结构
+        return buildTreeOptimized(allDicts);
+    }
+
+
+    /**
+     * web-分页查询店铺信息
+     *
+
+     * @return IPage<StoreInfoVo>
+     */
+    @Override
+    public List<StoreInfoVo> getMoreRecommendedStores(Double lon , Double lat, String businessSection, String businessTypes, String businessClassify) {
+        // 参数校验
+        if (lon == null || lat == null) {
+            log.warn("获取更多推荐店铺失败,经纬度为空");
+            return Collections.emptyList();
+        }
+
+        QueryWrapper<StoreInfoVo> queryWrapper = new QueryWrapper<>();
+        queryWrapper.eq("a.delete_flag", 0).eq("b.delete_flag", 0);
+        //如果查询未过期
+        // 获取当前时刻
+        Date currentDate = new Date();
+        // 获取当前日期和时间
+        Calendar calendar = Calendar.getInstance();
+        // 将时间设置为 0 点
+        calendar.set(Calendar.HOUR_OF_DAY, 0);
+        calendar.set(Calendar.MINUTE, 0);
+        calendar.set(Calendar.SECOND, 0);
+        calendar.set(Calendar.MILLISECOND, 0);
+        // 加上 30 天
+        calendar.add(Calendar.DAY_OF_MONTH, 30);
+        // 如果 expiration_time 为空则不做过期判断;如果不为空则要求大于当前时间
+        queryWrapper.and(w -> w.isNull("a.expiration_time")
+                .or()
+                .gt("a.expiration_time", currentDate));
+
+        // 如果 food_licence_expiration_time 为空则不做过期判断;如果不为空则要求大于当前时间
+        queryWrapper.and(w -> w.isNull("a.food_licence_expiration_time")
+                .or()
+                .gt("a.food_licence_expiration_time", currentDate));
+
+        // 构建一级分类
+        if(StringUtils.isNotEmpty(businessSection)){
+            queryWrapper.eq("a.business_section", businessSection);
+            // 构建二级分类
+            if(StringUtils.isNotEmpty(businessTypes)){
+                queryWrapper.eq("a.business_types", businessTypes);
+                // 构建三级分类
+                if(StringUtils.isNotEmpty(businessClassify)){
+                    // 解析businessClassify参数(格式:1,2,3)
+                    String[] classifyArray = businessClassify.split(",");
+                    // 使用FIND_IN_SET函数检查数据库字段是否包含参数中的任何一个值
+                    queryWrapper.and(wrapper -> {
+                        for (int i = 0; i < classifyArray.length; i++) {
+                            String classify = classifyArray[i].trim();
+                            if (StringUtils.isNotEmpty(classify)) {
+                                if (i == 0) {
+                                    wrapper.apply("FIND_IN_SET({0}, a.business_classify) > 0", classify);
+                                } else {
+                                    wrapper.or().apply("FIND_IN_SET({0}, a.business_classify) > 0", classify);
+                                }
+                            }
+                        }
+                    });
+                }
+            }
+        }
+
+        // 构建position参数(格式:经度,纬度)
+        String position = lon + "," + lat;
+        List<StoreInfoVo> storeInfoVoList = storeInfoMapper.getMoreRecommendedStores(queryWrapper, position);
+        if (CollectionUtils.isEmpty(storeInfoVoList)) {
+            return Collections.emptyList();
+        }
+        // 提前查询所有需要的字典数据
+        List<StoreInfoVo> collect = storeInfoVoList.stream().filter(record -> StringUtils.isNotEmpty(record.getStoreType())).collect(Collectors.toList());
+        Set<String> allTypes = collect.stream().map(StoreInfoVo::getStoreType).flatMap(type -> Arrays.stream(type.split(","))).collect(Collectors.toSet());
+
+        List<StoreDictionary> storeDictionaries = storeDictionaryMapper.selectList(new LambdaQueryWrapper<StoreDictionary>().eq(StoreDictionary::getTypeName, "storeType").isNull(StoreDictionary::getParentId).in(!allTypes.isEmpty(), StoreDictionary::getDictId, allTypes));
+        Map<String, String> typeMap = storeDictionaries.stream().collect(Collectors.toMap(StoreDictionary::getDictId, StoreDictionary::getDictDetail));
+
+
+        // 计算平均分和评价
+        Map<String, List<Map<String, Object>>> avgScoreMap = new HashMap<>();
+        Map<Integer, List<StoreComment>> commentMap = new HashMap<>();
+
+        // 注意:需要将store_id转换为String类型,与后续containsKey判断保持一致
+        avgScoreMap = storeEvaluationMapper.allStoreAvgScore().stream().collect(Collectors.groupingBy(o -> o.get("store_id").toString()));
+        commentMap = storeCommentMapper.selectList(new QueryWrapper<StoreComment>().eq("business_type", "5").eq("delete_flag", 0)).stream().collect(Collectors.groupingBy(StoreComment::getStoreId));
+
+
+        // 查询入口头图
+        List<Integer> storeIds = storeInfoVoList.stream()
+                .map(StoreInfoVo::getId)  // 假设 StoreImg 有 getStoreUrl() 方法
+                .collect(Collectors.toList());
+
+        List<StoreImg> storeImgList = storeImgMapper.selectList(new QueryWrapper<StoreImg>().in("store_id", storeIds).eq("img_type", 1));
+        Map<Integer, List<StoreImg>>  storeCollect = storeImgList.stream()
+                .collect(Collectors.groupingBy(StoreImg::getStoreId));
+
+
+        for (StoreInfoVo record : storeInfoVoList) {
+            //处理类型
+            if (StringUtils.isNotEmpty(record.getStoreType())) {
+                String[] types = record.getStoreType().split(",");
+                List<String> typeDetails = Arrays.stream(types).map(typeMap::get).filter(Objects::nonNull).collect(Collectors.toList());
+                record.setStoreTypeStr(String.join(",", typeDetails));
+                record.setStoreTypeList(Arrays.asList(types));
+            }
+
+
+            // 加入头图
+            if(!CollectionUtils.isEmpty(storeCollect) && storeCollect.containsKey(record.getId())){
+                List<StoreImg> storeImgs = storeCollect.get(record.getId());
+                if(!CollectionUtils.isEmpty(storeImgs)){
+                    record.setEntranceImage(storeImgs.get(0).getImgUrl());
+                }
+            }
+
+            //写经纬度
+            String[] split = record.getStorePosition().split(",");
+            record.setStorePositionLongitude(split[0]);
+            record.setStorePositionLatitude(split[1]);
+            // 格式化距离,移除无意义的小数位
+            if (!StringUtils.isEmpty(record.getDistance3())) {
+                try {
+                    BigDecimal distanceValue = new BigDecimal(record.getDistance3());
+                    record.setDistance3(distanceValue.stripTrailingZeros().toPlainString());
+                } catch (NumberFormatException ex) {
+                    log.warn("店铺距离格式化失败, storeId: {}, distance3: {}", record.getId(), record.getDistance3(), ex);
+                }
+            }
+            //处理一下到期状态
+            Date expirationTime = record.getExpirationTime();
+            if (expirationTime != null) {
+                // 获取当前时间
+                Calendar now = Calendar.getInstance();
+                Date nowCurrentDate = now.getTime();
+                // 计算 30 天后的时间
+                now.add(Calendar.DAY_OF_YEAR, 30);
+                Date thirtyDaysLater = now.getTime();
+                // 比较两个日期
+                if (expirationTime.after(currentDate)) {
+                    record.setExpiredState("0");
+                    if ((expirationTime.after(nowCurrentDate) || expirationTime.equals(nowCurrentDate)) && expirationTime.before(thirtyDaysLater)) {
+                        record.setExpiredState("1");
+                    }
+                } else {
+                    record.setExpiredState("2");
+                }
+
+                // 获取当前时间
+                LocalDate nowLocal = LocalDate.now();
+                // 将 expirationTime 转换为 LocalDate
+                LocalDate expDate = expirationTime.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
+                // 计算距离到期的天数
+                long daysToExpire = ChronoUnit.DAYS.between(nowLocal, expDate);
+                record.setDaysToExpire(daysToExpire);
+            }
+
+            // 设置店铺得分,设置店铺人均消费,设置总评论数
+            if (avgScoreMap.containsKey(String.valueOf(record.getId()))) {
+                record.setAvgScore(String.valueOf(avgScoreMap.get(String.valueOf(record.getId())).get(0).get("avg_score")));
+            }
+            // 设置店铺得分,设置店铺人均消费,设置总评论数
+            if (commentMap.containsKey(record.getId())) {
+                record.setTotalNum(String.valueOf(commentMap.get(record.getId()).size()));
+            }
+
+        }
+
+        // SQL已经实现了距离过滤和排序,直接返回结果
+        return storeInfoVoList;
+    }
+
+
+    @Override
+    public StoreInfoVo getClientStoreDetail(String storeId, String userId, String jingdu, String weidu) {
+        StoreInfoVo result = new StoreInfoVo();
+        StoreInfo storeInfo = storeInfoMapper.selectById(storeId);
+        BeanUtils.copyProperties(storeInfo, result);
+        //将经营板块和种类拆分成集合
+        String businessTypes = storeInfo.getBusinessTypes();
+        if (StringUtils.isNotEmpty(businessTypes)) {
+            String[] split = businessTypes.split(",");
+            List<String> list = Arrays.asList(split);
+            result.setBusinessTypesList(list);
+        }
+        //存入用户账户
+        StoreUser storeUser = storeUserMapper.selectOne(new LambdaQueryWrapper<StoreUser>().eq(StoreUser::getStoreId, storeId));
+        if (storeUser != null) {
+            result.setUserAccount(storeUser.getId().toString());
+            result.setStorePhone(storeUser.getPhone());
+            result.setStoreUserName(storeUser.getName());
+            result.setIdCard(storeUser.getIdCard());
+        }
+//        //存入执照图片地址
+//        List<StoreImg> storeImgs = storeImgMapper.selectList(new LambdaQueryWrapper<StoreImg>().eq(StoreImg::getStoreId, storeId).eq(StoreImg::getImgType, 14));
+//        List<String> storeImgPaths = new ArrayList<>();
+//        for (StoreImg storeImg : storeImgs) {
+//            storeImgPaths.add(storeImg.getImgUrl());
+//        }
+//        result.setBusinessLicenseAddress(storeImgPaths);
+//        //存入合同图片地址
+//        List<StoreImg> storeContractImageImgs = storeImgMapper.selectList(new LambdaQueryWrapper<StoreImg>().eq(StoreImg::getStoreId, storeId).eq(StoreImg::getImgType, 15));
+//        List<String> storeContractImagePathImgs = new ArrayList<>();
+//        for (StoreImg storeImg : storeContractImageImgs) {
+//            storeContractImagePathImgs.add(storeImg.getImgUrl());
+//        }
+//        result.setContractImageList(storeContractImagePathImgs);
+//        //存入续签合同地址
+//        List<StoreImg> renewContractImgs = storeImgMapper.selectList(new LambdaQueryWrapper<StoreImg>().eq(StoreImg::getStoreId, storeId).eq(StoreImg::getImgType, 22));
+//        List<String> renewContractImagePathImgs = new ArrayList<>();
+//        for (StoreImg storeImg : renewContractImgs) {
+//            renewContractImagePathImgs.add(storeImg.getImgUrl());
+//        }
+//        result.setRenewContractImageList(renewContractImagePathImgs);
+//        //存入经营许可证通过地址
+//        List<StoreImg> foodLicenceImgs = storeImgMapper.selectList(new LambdaQueryWrapper<StoreImg>().eq(StoreImg::getStoreId, storeId).eq(StoreImg::getImgType, 25));
+//        List<String> foodLicenceImgsPathImgs = new ArrayList<>();
+//        for (StoreImg storeImg : foodLicenceImgs) {
+//            foodLicenceImgsPathImgs.add(storeImg.getImgUrl());
+//        }
+//        result.setFoodLicenceImageList(foodLicenceImgsPathImgs);
+//        //存入经营许可证未通过地址
+//        List<StoreImg> notPassFoodLicenceImgs = storeImgMapper.selectList(new LambdaQueryWrapper<StoreImg>().eq(StoreImg::getStoreId, storeId).eq(StoreImg::getImgType, 24));
+//        List<String> notPassFoodLicenceList = new ArrayList<>();
+//        for (StoreImg storeImg : notPassFoodLicenceImgs) {
+//            notPassFoodLicenceList.add(storeImg.getImgUrl());
+//        }
+//        result.setNotPassFoodLicenceImageList(notPassFoodLicenceList);
+        // 存放商家入口图
+        List<StoreImg> storeEntranceImageImgs = storeImgMapper.selectList(new LambdaQueryWrapper<StoreImg>().eq(StoreImg::getStoreId, storeId).eq(StoreImg::getImgType, 1));
+        if (!storeEntranceImageImgs.isEmpty()) {
+            result.setEntranceImage(storeEntranceImageImgs.get(0).getImgUrl());
+        } else {
+            result.setEntranceImage("null");
+        }
+        // 存放商家头像
+        List<StoreImg> storeImgs1 = storeImgMapper.selectList(new LambdaQueryWrapper<StoreImg>().eq(StoreImg::getStoreId, storeId).eq(StoreImg::getImgType, 10));
+        if (!storeImgs1.isEmpty()) {
+            result.setImgUrl(storeImgs1.get(0).getImgUrl());
+        } else {
+            result.setImgUrl("null");
+        }
+
+        // 获取店铺相册
+        List<StoreImg> storeAlbumList = new ArrayList<>();
+        if(storeInfo.getImgMode() != null && storeInfo.getImgMode() == 0){
+            storeAlbumList =  storeImgService.getStoreImg(Integer.parseInt(storeId), 20);
+        } else {
+            storeAlbumList =  storeImgService.getStoreImg(Integer.parseInt(storeId), 21);
+        }
+
+        if(!CollectionUtils.isEmpty(storeAlbumList)){
+            List<String> storeAlbumUrlList = storeAlbumList.stream().map(StoreImg::getImgUrl)  // 假设 StoreImg 有 getStoreUrl() 方法
+                    .filter(url -> url != null && !url.trim().isEmpty())  // 过滤空值
+                    .collect(Collectors.toList());
+            result.setStoreAlbumUrlList(storeAlbumUrlList);
+        } else {
+            result.setStoreAlbumUrlList(new ArrayList<>());
+        }
+
+        // 设置经纬度
+        result.setStorePositionLongitude(result.getStorePosition().split(",")[0]);
+        result.setStorePositionLatitude(result.getStorePosition().split(",")[1]);
+        // 设置距离
+        if ((jingdu != null && !jingdu.isEmpty()) && (weidu != null && !weidu.isEmpty())) {
+            /*double storeJing = Double.parseDouble(result.getStorePosition().split(",")[0]);
+            double storeWei = Double.parseDouble(result.getStorePosition().split(",")[1]);
+            double storeDistance = DistanceUtil.haversineCalculateDistance(Double.parseDouble(jingdu), Double.parseDouble(weidu), storeJing, storeWei);*/
+
+            Double distance = storeInfoMapper.getStoreDistance(jingdu + "," + weidu,result.getId());
+
+            result.setDistance(distance);
+        }
+        // 计算店铺到最近地铁站的距离
+        JSONObject nearbySubway = gaoDeMapUtil.getNearbySubway(result.getStorePosition().split(",")[0], result.getStorePosition().split(",")[1]);
+        // 地铁名
+        String subWayName = nearbySubway.getString("name");
+        result.setSubwayName(subWayName);
+        // 地铁站经纬度
+        String subWayJing = nearbySubway.getString("location") == null ? null : nearbySubway.getString("location").split(",")[0];
+        String subWayWei = nearbySubway.getString("location") == null ? null : nearbySubway.getString("location").split(",")[1];
+        if ((subWayJing != null && !subWayJing.isEmpty()) && (subWayWei != null && !subWayWei.isEmpty())) {
+            double storeJing = Double.parseDouble(result.getStorePosition().split(",")[0]);
+            double storeWei = Double.parseDouble(result.getStorePosition().split(",")[1]);
+            double storeDistance2 = DistanceUtil.haversineCalculateDistance(Double.parseDouble(subWayJing), Double.parseDouble(subWayWei), storeJing, storeWei);
+            result.setDistance2(storeDistance2);
+        } else {
+            result.setDistance2(0);
+        }
+        // 当前登录用户是否收藏
+        LambdaUpdateWrapper<LifeCollect> shouCangWrapper = new LambdaUpdateWrapper<>();
+        shouCangWrapper.eq(LifeCollect::getUserId, userId).eq(LifeCollect::getStoreId, storeId);
+        List<LifeCollect> shouCangList = lifeCollectMapper.selectList(shouCangWrapper);
+        if (null == shouCangList || shouCangList.isEmpty()) {
+            result.setCollection(0);
+        } else {
+            result.setCollection(1);
+        }
+
+        // 该用户的打卡记录
+        LambdaQueryWrapper<StoreClockIn> clockInWrapper = new LambdaQueryWrapper<>();
+        clockInWrapper.eq(StoreClockIn::getUserId, userId);
+        List<StoreClockIn> clockInList = storeClockInMapper.selectList(clockInWrapper);
+
+        List<StoreClockIn> clockStoreList = clockInList.stream().filter(item -> item.getStoreId() == Integer.parseInt(storeId)).collect(Collectors.toList());
+        // 该用户是否在该店铺打过卡
+        if (!clockStoreList.isEmpty()) {
+            result.setClockInStore(1);
+        } else {
+            result.setClockInStore(0);
+        }
+
+        // 今天在该店铺是否打过卡
+        int today = (int) clockStoreList.stream().filter(item -> item.getCreatedTime().toInstant().atZone(ZoneId.systemDefault()).toLocalDate().equals(LocalDate.now())).count();
+        if (today > 0) {
+            result.setClockInStoreToday(1);
+        } else {
+            result.setClockInStoreToday(0);
+        }
+
+
+        Map<String, Object> commitCountAndScore = storeCommentService.getCommitCountAndScore(null, 5, Integer.parseInt(storeId), null, null);
+        result.setScore(Double.parseDouble(commitCountAndScore.get("score").toString()));
+        result.setCommitCount(commitCountAndScore.get("commitCount").toString());
+
+
+        // 在该店铺的打卡次数
+        result.setClockInStoreNum(clockStoreList.size());
+
+        // 该用户打卡的所有店铺次数(一个店铺只算一次)
+        int clockInNum = (int) clockInList.stream().map(StoreClockIn::getStoreId).distinct().count();
+        result.setClockInNum(clockInNum);
+
+//        // 获取店铺动态列表
+//        QueryWrapper<LifeUserDynamics> dynamicsWrapper = new QueryWrapper<>();
+//        dynamicsWrapper.eq("phone_id", "store_" + result.getStorePhone()).orderByDesc("lud.created_time");
+//        dynamicsWrapper.eq("lud.delete_flag", 0);
+//
+//        LambdaQueryWrapper<LifeBlacklist> lambdaQueryWrapper1 = new LambdaQueryWrapper<>();
+//        lambdaQueryWrapper1.eq(LifeBlacklist :: getBlockerId, userId);
+//        lambdaQueryWrapper1.eq(LifeBlacklist :: getBlockedPhoneId, "store_" + result.getStorePhone());
+//        LifeBlacklist blacklist = lifeBlacklistMapper.selectOne(lambdaQueryWrapper1);
+//        List<LifeUserDynamicsVo> storeDynamicslist = new ArrayList<>();
+
+//        //判断没有拉黑当前门店账户 查出门店动态
+//        if(blacklist == null){
+//            storeDynamicslist = lifeUserDynamicsMapper.getStoreDynamicslist(userId, "store_" + result.getStorePhone());
+//        }
+//
+//        List<String> followList = new ArrayList<>();
+//        List<String> fansList = new ArrayList<>();
+
+//        if (StringUtils.isNotEmpty(userId)) {
+//            LifeUser lifeUser = lifeUserMapper.selectById(userId);
+//            if (lifeUser != null && StringUtils.isNotEmpty(lifeUser.getUserPhone())) {
+//                // 查询我的关注信息,构建关注者ID列表
+//                LambdaQueryWrapper<LifeFans> lifeFansWrapper = new LambdaQueryWrapper<>();
+//                lifeFansWrapper.eq(LifeFans::getFansId, "user_" + result.getStorePhone());
+//                List<LifeFans> lifeFansList = lifeFansMapper.selectList(lifeFansWrapper);
+//                if (!CollectionUtils.isEmpty(lifeFansList)) {
+//                    followList = lifeFansList.stream().map(LifeFans::getFollowedId).collect(Collectors.toList());
+//                }
+//
+//                // 查询我的粉丝信息,构建粉丝ID列表
+//                lifeFansWrapper = new LambdaQueryWrapper<>();
+//                lifeFansWrapper.eq(LifeFans::getFollowedId, "user_" + result.getStorePhone());
+//                lifeFansList = lifeFansMapper.selectList(lifeFansWrapper);
+//                if (!CollectionUtils.isEmpty(lifeFansList)) {
+//                    fansList = lifeFansList.stream().map(LifeFans::getFansId).collect(Collectors.toList());
+//                }
+//            }
+//        }
+
+//        for (LifeUserDynamicsVo vo : storeDynamicslist) {
+//            if (followList.contains(vo.getPhoneId())) {
+//                vo.setIsFollowThis("1");
+//            } else {
+//                vo.setIsFollowThis("0");
+//            }
+//            if (fansList.contains(vo.getPhoneId())) {
+//                vo.setIsFollowMe("1");
+//            } else {
+//                vo.setIsFollowMe("0");
+//            }
+//        }
+
+//        // 返回动态最新的5条
+//        List<LifeUserDynamicsVo> storeDynamicslist2 = storeDynamicslist.stream()
+//                .limit(5).collect(Collectors.toList());
+//        result.setDynamicsList(storeDynamicslist2);
+//        //设置动态条数
+//        Integer dynamicsNum = storeDynamicslist2.size();
+//        result.setDynamicsNum(dynamicsNum);
+//
+//        // 获取店铺动态总数
+//        result.setTotalDynamicsNum(storeDynamicslist.size());
+
+        //营业时间
+        List<StoreBusinessInfo> storeBusinessInfos = storeBusinessInfoMapper.selectList(new LambdaQueryWrapper<StoreBusinessInfo>().eq(StoreBusinessInfo::getStoreId, storeId).eq(StoreBusinessInfo::getDeleteFlag, 0));
+        if (ObjectUtils.isNotEmpty(storeBusinessInfos)) {
+            result.setStoreBusinessInfo(storeBusinessInfos.get(0));
+            result.setStoreBusinessInfos(storeBusinessInfos);
+            StoreBusinessInfo storeBusinessInfo = result.getStoreBusinessInfos().stream().filter(item -> item.getBusinessType() == 1).findFirst().orElse(null);
+            if (ObjectUtils.isNotEmpty(storeBusinessInfo)) {
+
+                Calendar calendar = Calendar.getInstance(); // 获取Calendar实例
+                int dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK); // 获取星期几,注意Calendar中的DAY_OF_WEEK是从1(代表星期天)开始的
+                String[] days = {"7", "1", "2", "3", "4", "5", "6"};
+                String day = days[dayOfWeek - 1];
+                if (storeBusinessInfo.getBusinessDate().contains(day)) {
+                    if (StringUtils.isNotEmpty(storeBusinessInfo.getStartTime()) && StringUtils.isNotEmpty(storeBusinessInfo.getEndTime())) {
+                        LocalTime now = LocalTime.now();
+                        List<String> startList = Arrays.asList(storeBusinessInfo.getStartTime().split(":"));
+                        List<String> endList = Arrays.asList(storeBusinessInfo.getEndTime().split(":"));
+                        LocalTime start = LocalTime.of(Integer.parseInt(startList.get(0)), Integer.parseInt(startList.get(1)));
+                        LocalTime end = LocalTime.of(Integer.parseInt(endList.get(0)), Integer.parseInt(startList.get(1)));
+                        if (now.isAfter(start) && now.isBefore(end)) {
+                            result.setYyFlag(1);
+                        } else {
+                            result.setYyFlag(0);
+                        }
+                    }
+                } else {
+                    result.setYyFlag(0);
+                }
+            }
+        }
+        LambdaQueryWrapper<StoreDictionary> storeDictionaryLambdaQueryWrapper = new LambdaQueryWrapper<>();
+        storeDictionaryLambdaQueryWrapper.eq(StoreDictionary::getTypeName, "businessStatus")
+                .eq(StringUtils.isNotEmpty(result.getBusinessStatus().toString()), StoreDictionary::getDictId, result.getBusinessStatus());
+        List<StoreDictionary> storeDictionaries = storeDictionaryMapper.selectList(storeDictionaryLambdaQueryWrapper);
+        if (!storeDictionaries.isEmpty()) {
+            result.setBusinessStatusStr(storeDictionaries.get(0).getDictDetail());
+        }
+        return result;
+    }
+
+    @Override
+    public List<LifeCouponVo> getStoreCouponList(String storeId) {
+        // 获取店铺代金券列表
+        LambdaUpdateWrapper<LifeCoupon> quanWrapper = new LambdaUpdateWrapper<>();
+        quanWrapper.eq(LifeCoupon::getStoreId, storeId).eq(LifeCoupon::getStatus, CouponStatusEnum.ONGOING.getCode()).eq(LifeCoupon::getType, 1);
+        List<LifeCoupon> quanList = lifeCouponMapper.selectList(quanWrapper);
+        List<LifeCouponVo> quanVoList = new ArrayList<>();
+        List<String> collect = quanList.stream().map(LifeCoupon::getId).collect(Collectors.toList());
+        // 设置已售数量
+        // 定义需要的订单状态集合
+        Set<Integer> excludeStatuses = new HashSet<>(Arrays.asList(
+                OrderStatusEnum.WAIT_PAY.getStatus(),
+                OrderStatusEnum.WAIT_USE.getStatus(),
+                OrderStatusEnum.USED.getStatus()
+        ));
+        if (!collect.isEmpty()) {
+            List<LifeUserOrderVo> quanCount = lifeUserOrderMapper.getQuanCount(new QueryWrapper<LifeUserOrderVo>()
+                    .eq("luo.store_id", storeId)
+                    .eq("luo.coupon_type", CouponTypeEnum.COUPON.getCode())
+                    .eq("luo.delete_flag", 0)
+                    .in("ocm.status", excludeStatuses)
+                    .groupBy("ocm.coupon_id"));
+            quanList.forEach(a -> {
+                LifeCouponVo lifeCouponVo = new LifeCouponVo();
+                BeanUtils.copyProperties(a, lifeCouponVo);
+                quanCount.forEach(item -> {
+                    if (a.getId().equals(item.getCouponId().toString())) {
+                        lifeCouponVo.setCount(item.getCount());
+                    }
+                });
+                quanVoList.add(lifeCouponVo);
+            });
+        }
+        return quanVoList;
+    }
+
+    @Override
+    public StoreInfoVo getNewStoreDetail(String storeId) {
+
+        StoreInfoVo result = new StoreInfoVo();
+        StoreInfo storeInfo = storeInfoMapper.selectById(storeId);
+        BeanUtils.copyProperties(storeInfo, result);
+        //将经营板块和种类拆分成集合
+        String businessTypes = storeInfo.getBusinessTypes();
+        if (StringUtils.isNotEmpty(businessTypes)) {
+            String[] split = businessTypes.split(",");
+            List<String> list = Arrays.asList(split);
+            result.setBusinessTypesList(list);
+        }
+        //存入用户账户
+        StoreUser storeUser = storeUserMapper.selectOne(new LambdaQueryWrapper<StoreUser>().eq(StoreUser::getStoreId, storeId));
+        if (storeUser != null) {
+            result.setUserAccount(storeUser.getId().toString());
+            result.setStorePhone(storeUser.getPhone());
+            result.setStoreUserName(storeUser.getName());
+            result.setIdCard(storeUser.getIdCard());
+        }
+        //存入执照图片地址
+        List<StoreImg> storeImgs = storeImgMapper.selectList(new LambdaQueryWrapper<StoreImg>().eq(StoreImg::getStoreId, storeId).eq(StoreImg::getImgType, 14));
+        List<String> storeImgPaths = new ArrayList<>();
+        for (StoreImg storeImg : storeImgs) {
+            storeImgPaths.add(storeImg.getImgUrl());
+        }
+        result.setBusinessLicenseAddress(storeImgPaths);
+        //存入合同图片地址
+        List<StoreImg> storeContractImageImgs = storeImgMapper.selectList(new LambdaQueryWrapper<StoreImg>().eq(StoreImg::getStoreId, storeId).eq(StoreImg::getImgType, 15));
+        List<String> storeContractImagePathImgs = new ArrayList<>();
+        for (StoreImg storeImg : storeContractImageImgs) {
+            storeContractImagePathImgs.add(storeImg.getImgUrl());
+        }
+        result.setContractImageList(storeContractImagePathImgs);
+        //存入续签合同地址
+        List<StoreImg> renewContractImgs = storeImgMapper.selectList(new LambdaQueryWrapper<StoreImg>().eq(StoreImg::getStoreId, storeId).eq(StoreImg::getImgType, 22));
+        List<String> renewContractImagePathImgs = new ArrayList<>();
+        for (StoreImg storeImg : renewContractImgs) {
+            renewContractImagePathImgs.add(storeImg.getImgUrl());
+        }
+        result.setRenewContractImageList(renewContractImagePathImgs);
+        //存入经营许可证通过地址
+        List<StoreImg> foodLicenceImgs = storeImgMapper.selectList(new LambdaQueryWrapper<StoreImg>().eq(StoreImg::getStoreId, storeId).eq(StoreImg::getImgType, 25));
+        List<String> foodLicenceImgsPathImgs = new ArrayList<>();
+        for (StoreImg storeImg : foodLicenceImgs) {
+            foodLicenceImgsPathImgs.add(storeImg.getImgUrl());
+        }
+        result.setFoodLicenceImageList(foodLicenceImgsPathImgs);
+        //存入经营许可证未通过地址
+        List<StoreImg> notPassFoodLicenceImgs = storeImgMapper.selectList(new LambdaQueryWrapper<StoreImg>().eq(StoreImg::getStoreId, storeId).eq(StoreImg::getImgType, 24));
+        List<String> notPassFoodLicenceList = new ArrayList<>();
+        for (StoreImg storeImg : notPassFoodLicenceImgs) {
+            notPassFoodLicenceList.add(storeImg.getImgUrl());
+        }
+        result.setNotPassFoodLicenceImageList(notPassFoodLicenceList);
+        // 存放商家入口图
+        List<StoreImg> storeEntranceImageImgs = storeImgMapper.selectList(new LambdaQueryWrapper<StoreImg>().eq(StoreImg::getStoreId, storeId).eq(StoreImg::getImgType, 1));
+        if (!storeEntranceImageImgs.isEmpty()) {
+            result.setEntranceImage(storeEntranceImageImgs.get(0).getImgUrl());
+        } else {
+            result.setEntranceImage("null");
+        }
+        // 存放商家头像
+        List<StoreImg> storeImgs1 = storeImgMapper.selectList(new LambdaQueryWrapper<StoreImg>().eq(StoreImg::getStoreId, storeId).eq(StoreImg::getImgType, 10));
+        if (!storeImgs1.isEmpty()) {
+            result.setImgUrl(storeImgs1.get(0).getImgUrl());
+        } else {
+            result.setImgUrl("null");
+        }
+
+
+        // 店铺平均分
+        /*Map<Object, List<Map<String, Object>>> avgScoreMap = storeEvaluationMapper.allStoreAvgScore().stream().collect(Collectors.groupingBy(o -> o.get("store_id")));
+        if (avgScoreMap.containsKey(String.valueOf(result.getId()))) {
+            result.setScore(Double.parseDouble(avgScoreMap.get(String.valueOf(result.getId())).get(0).get("avg_score").toString()));
+        }*/
+
+//        Map<String, Object> commitCountAndScore = storeCommentService.getCommitCountAndScore(null, 5, Integer.parseInt(storeId), null, null);
+//        result.setScore(Double.parseDouble(commitCountAndScore.get("score").toString()));
+//        result.setCommitCount(commitCountAndScore.get("commitCount").toString());
+//
+
+
+        //营业时间
+        List<StoreBusinessInfo> storeBusinessInfos = storeBusinessInfoMapper.selectList(new LambdaQueryWrapper<StoreBusinessInfo>().eq(StoreBusinessInfo::getStoreId, storeId).eq(StoreBusinessInfo::getDeleteFlag, 0));
+        if (ObjectUtils.isNotEmpty(storeBusinessInfos)) {
+            result.setStoreBusinessInfo(storeBusinessInfos.get(0));
+            result.setStoreBusinessInfos(storeBusinessInfos);
+            //StoreBusinessInfo storeBusinessInfo = result.getStoreBusinessInfo();
+            StoreBusinessInfo storeBusinessInfo = result.getStoreBusinessInfos().stream().filter(item -> item.getBusinessType() == 1).findFirst().orElse(null);
+            if (ObjectUtils.isNotEmpty(storeBusinessInfo)) {
+
+                Calendar calendar = Calendar.getInstance(); // 获取Calendar实例
+                int dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK); // 获取星期几,注意Calendar中的DAY_OF_WEEK是从1(代表星期天)开始的
+                String[] days = {"7", "1", "2", "3", "4", "5", "6"};
+                String day = days[dayOfWeek - 1];
+                if (storeBusinessInfo.getBusinessDate().contains(day)) {
+                    if (StringUtils.isNotEmpty(storeBusinessInfo.getStartTime()) && StringUtils.isNotEmpty(storeBusinessInfo.getEndTime())) {
+                        LocalTime now = LocalTime.now();
+                        List<String> startList = Arrays.asList(storeBusinessInfo.getStartTime().split(":"));
+                        List<String> endList = Arrays.asList(storeBusinessInfo.getEndTime().split(":"));
+                        LocalTime start = LocalTime.of(Integer.parseInt(startList.get(0)), Integer.parseInt(startList.get(1)));
+                        LocalTime end = LocalTime.of(Integer.parseInt(endList.get(0)), Integer.parseInt(startList.get(1)));
+                        if (now.isAfter(start) && now.isBefore(end)) {
+                            result.setYyFlag(1);
+                        } else {
+                            result.setYyFlag(0);
+                        }
+                    }
+                } else {
+                    result.setYyFlag(0);
+                }
+            }
+        }
+        LambdaQueryWrapper<StoreDictionary> storeDictionaryLambdaQueryWrapper = new LambdaQueryWrapper<>();
+        storeDictionaryLambdaQueryWrapper.eq(StoreDictionary::getTypeName, "businessStatus")
+                .eq(StringUtils.isNotEmpty(result.getBusinessStatus().toString()), StoreDictionary::getDictId, result.getBusinessStatus());
+        List<StoreDictionary> storeDictionaries = storeDictionaryMapper.selectList(storeDictionaryLambdaQueryWrapper);
+        if (!storeDictionaries.isEmpty()) {
+            result.setBusinessStatusStr(storeDictionaries.get(0).getDictDetail());
+        }
+        // TODO 之后修改********** 正常OcrType由前端传存储ocr表要加新字段。传参要由前端传。
+        // 查询并设置各类证件OCR信息
+        result.setJyxkz(convertOcrResultToJson(storeUser.getId(), "BUSINESS_LICENSE", "娱乐", true));
+        result.setIdcardFace(convertOcrResultToJson(storeUser.getId(), "ID_CARD", "face", true));
+        result.setIdcardBack(convertOcrResultToJson(storeUser.getId(), "ID_CARD", "back", true));
+        result.setFoodLicence(convertOcrResultToJson(storeUser.getId(), "FOOD_MANAGE_LICENSE", null, true));
+        result.setEntertainmentLicence(convertOcrResultToJson(storeUser.getId(), "BUSINESS_LICENSE", "营业执照", false));
+        return result;
+    }
+
+    @Override
+    public StoreInfoVo editNewStoreInfo(StoreInfoDto storeInfoDto) {
+
+        //获取经营板块id
+        Integer businessSection = storeInfoDto.getBusinessSection();
+        //查询经营板块名称
+        StoreDictionary businessSectionName = storeDictionaryMapper.selectOne(new LambdaQueryWrapper<StoreDictionary>().eq(StoreDictionary::getDictId, businessSection).eq(StoreDictionary::getTypeName, "business_section"));
+        //查询经营种类
+        List<String> businessTypes = storeInfoDto.getBusinessTypes();
+        List<String> businessTypeNames = new ArrayList<>();
+        //获取经营种类名称
+        for (String businessType : businessTypes) {
+            StoreDictionary storeDictionary = storeDictionaryMapper.selectOne(new LambdaQueryWrapper<StoreDictionary>().eq(StoreDictionary::getDictId, businessType).eq(StoreDictionary::getParentId, businessSectionName.getId()));
+            businessTypeNames.add(storeDictionary.getDictDetail());
+        }
+
+        StoreInfoVo result = new StoreInfoVo();
+        StoreInfo storeInfo = new StoreInfo();
+        BeanUtils.copyProperties(storeInfoDto, storeInfo);
+//        List<String> storeTypeList = storeInfoDto.getStoreTypeList();
+//        String storeType = String.join(",", storeTypeList);
+        //存入营运类型
+//        storeInfo.setStoreType(storeType);
+
+        //板块及类型
+        storeInfo.setBusinessSection(businessSection);
+        storeInfo.setBusinessSectionName(businessSectionName.getDictDetail());
+        storeInfo.setBusinessTypes(String.join(",", businessTypes));
+        storeInfo.setBusinessTypesName(String.join(",", businessTypeNames));
+
+        //处理分类信息
+        List<String> businessClassify = storeInfoDto.getBusinessClassify();
+        if (!CollectionUtils.isEmpty(businessClassify)) {
+            List<String> businessClassifyNames = new ArrayList<>();
+            //批量查询分类名称
+            List<StoreDictionary> classifyDicts = storeDictionaryMapper.selectList(
+                    new LambdaQueryWrapper<StoreDictionary>()
+                            .in(StoreDictionary::getDictId, businessClassify)
+                            .eq(StoreDictionary::getTypeName, "business_classify")
+                            .eq(StoreDictionary::getDeleteFlag, 0)
+            );
+            //转为Map方便快速获取
+            Map<String, StoreDictionary> classifyDictMap = classifyDicts.stream()
+                    .collect(Collectors.toMap(
+                            dict -> dict.getDictId().toString(),
+                            Function.identity(),
+                            (existing, replacement) -> existing
+                    ));
+            //提取分类名称
+            for (String classifyId : businessClassify) {
+                StoreDictionary dict = classifyDictMap.get(classifyId);
+                if (Objects.nonNull(dict)) {
+                    businessClassifyNames.add(dict.getDictDetail());
+                } else {
+                    log.warn("无效的分类id:" + classifyId);
+                }
+            }
+            storeInfo.setBusinessClassify(String.join(",", businessClassify));
+            storeInfo.setBusinessClassifyName(String.join(",", businessClassifyNames));
+        }
+
+        storeInfoMapper.updateById(storeInfo);
+        return result;
+    }
+
 }

+ 279 - 20
alien-store/src/main/java/shop/alien/store/service/impl/StoreMenuServiceImpl.java

@@ -8,6 +8,7 @@ import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
 import com.baomidou.mybatisplus.core.toolkit.StringUtils;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.BeanUtils;
 import org.springframework.stereotype.Service;
 import shop.alien.entity.result.R;
@@ -22,9 +23,12 @@ import shop.alien.mapper.StoreMenuMapper;
 import shop.alien.store.service.StoreImgService;
 import shop.alien.store.service.StoreMenuService;
 
+import java.util.Collections;
 import java.util.Comparator;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.stream.Collectors;
 
 /**
@@ -33,6 +37,7 @@ import java.util.stream.Collectors;
  * @author ssk
  * @since 2024-12-05
  */
+@Slf4j
 @Service
 @RequiredArgsConstructor
 public class StoreMenuServiceImpl extends ServiceImpl<StoreMenuMapper, StoreMenu> implements StoreMenuService {
@@ -46,35 +51,74 @@ public class StoreMenuServiceImpl extends ServiceImpl<StoreMenuMapper, StoreMenu
     private final LifeGroupBuyThaliMapper lifeGroupBuyThaliMapper;
 
     /**
+     * 菜品类型:非推荐
+     */
+    private static final Integer DISH_TYPE_NON_RECOMMEND = 0;
+
+    /**
+     * 菜品类型:推荐
+     */
+    private static final Integer DISH_TYPE_RECOMMEND = 1;
+
+    /**
+     * 点赞状态:未点赞
+     */
+    private static final Integer LIKE_STATUS_NO = 0;
+
+    /**
+     * 点赞状态:已点赞
+     */
+    private static final Integer LIKE_STATUS_YES = 1;
+
+    /**
      * 获取门店菜单
      *
-     * @param storeId  门店id
-     * @param dishType 菜品类型, 0:菜单, 1:推荐
-     * @param phoneId  消息标识
+     * @param storeId      门店id
+     * @param dishType     菜品类型, 0:非推荐, 1:推荐
+     * @param phoneId      消息标识
+     * @param dishMenuType 菜单类型:1-菜单,2-酒水
      * @return list
      */
     @Override
-    public List<StoreMenuVo> getStoreMenu(Integer storeId, Integer dishType, String phoneId) {
+    public List<StoreMenuVo> getStoreMenu(Integer storeId, Integer dishType, String phoneId, Integer dishMenuType) {
+        // 查询菜单列表
+        Integer queryDishType = (dishType != null && dishType == 0) ? null : dishType;
+        List<StoreMenuVo> menuList = storeMenuMapper.getStoreMenuList(storeId, queryDishType, dishMenuType);
 
-        if (dishType == 0) {
-            List<StoreMenuVo> collect = storeMenuMapper.getStoreMenuList(storeId, null);
-            return collect.stream().sorted(Comparator.comparing(StoreMenuVo::getSort)).collect(Collectors.toList());
-        } else {
-            List<StoreMenuVo> collect = storeMenuMapper.getStoreMenuList(storeId, dishType);
-            collect.forEach(item -> {
-                if (StringUtils.isNotEmpty(phoneId)) {
-                    LambdaQueryWrapper<LifeLikeRecord> query = new LambdaQueryWrapper<>();
-                    query.eq(LifeLikeRecord::getDianzanId, phoneId).eq(LifeLikeRecord::getHuifuId, item.getId());
-                    Integer i = lifeLikeRecordMapper.selectCount(query);
-                    if (i > 0) {
-                        item.setIsLike(1);
-                    } else {
-                        item.setIsLike(0);
-                    }
+        // 如果是推荐菜且有用户标识,批量查询点赞状态
+        if (dishType != null && dishType == 1 && StringUtils.isNotEmpty(phoneId)
+                && CollectionUtils.isNotEmpty(menuList)) {
+            // 将菜单ID转换为String集合,用于查询点赞记录
+            Set<String> menuIdStrSet = menuList.stream()
+                    .map(item -> String.valueOf(item.getId()))
+                    .collect(Collectors.toSet());
+
+            // 批量查询点赞记录
+            LambdaQueryWrapper<LifeLikeRecord> likeQuery = new LambdaQueryWrapper<>();
+            likeQuery.eq(LifeLikeRecord::getDianzanId, phoneId)
+                    .in(LifeLikeRecord::getHuifuId, menuIdStrSet);
+            List<LifeLikeRecord> likeRecordList = lifeLikeRecordMapper.selectList(likeQuery);
+
+            // 构建已点赞的菜单ID集合
+            Set<String> likedMenuIdSet = likeRecordList.stream()
+                    .map(LifeLikeRecord::getHuifuId)
+                    .collect(Collectors.toSet());
+
+            // 设置点赞状态
+            menuList.forEach(item -> {
+                String menuIdStr = String.valueOf(item.getId());
+                if (likedMenuIdSet.contains(menuIdStr)) {
+                    item.setIsLike(1);
+                } else {
+                    item.setIsLike(0);
                 }
             });
-            return collect.stream().sorted(Comparator.comparing(StoreMenuVo::getSort)).collect(Collectors.toList());
         }
+
+        // 按排序字段排序
+        return menuList.stream()
+                .sorted(Comparator.comparing(StoreMenuVo::getSort))
+                .collect(Collectors.toList());
     }
 
     /**
@@ -280,4 +324,219 @@ public class StoreMenuServiceImpl extends ServiceImpl<StoreMenuMapper, StoreMenu
     public boolean getMenuLikeStatus(String userId, Integer menuId) {
         return lifeLikeRecordMapper.selectCount(new QueryWrapper<LifeLikeRecord>().eq("dianzan_id", userId).eq("huifu_id", menuId).eq("delete_flag", 0)) > 0;
     }
+
+
+    /**
+     * 获取门店菜单(客户端)
+     * <p>
+     * 根据门店ID查询菜单列表,支持按菜品类型和菜单类型筛选。
+     * 当查询推荐菜且提供用户手机号时,会批量查询并设置用户的点赞状态。
+     * 最终结果按排序字段升序排列。
+     * </p>
+     *
+     * @param storeId      门店ID,必填,必须大于0
+     * @param dishType     菜品类型,可选,0:全部, 1:推荐
+     * @param phoneId      用户手机号,可选,用于查询用户对推荐菜的点赞状态
+     * @param dishMenuType 菜单类型,可选,1-菜单,2-酒水
+     * @return 菜单列表,按排序字段升序排列,如果查询结果为空则返回空列表
+     */
+    @Override
+    public Map<String, Object> getClientMenuByStoreId(Integer storeId, Integer dishType, String phoneId, Integer dishMenuType) {
+        log.info("开始获取用户端菜单,门店ID:{},菜品类型:{},用户手机号:{},菜单类型:{}",
+                storeId, dishType, phoneId, dishMenuType);
+
+        // 参数校验
+        if (storeId == null || storeId <= 0) {
+            log.warn("获取用户端菜单失败,门店ID无效:{}", storeId);
+            throw new IllegalArgumentException("门店ID不能为空且必须大于0");
+        }
+
+        // 先查询该门店的所有菜单数据(不应用筛选条件),用于统计
+        List<StoreMenuVo> allMenuList = storeMenuMapper.getClientMenuByStoreId(storeId, null, null);
+
+        // 按照六种组合进行分组统计(基于所有菜单数据,不受筛选条件影响):
+        // 1. dish_menu_type = 1(菜单),dish_type = 0(全部)
+        // 2. dish_menu_type = 1(菜单),dish_type = 1(推荐)
+        // 3. dish_menu_type = 2(酒水),dish_type = 0(全部)
+        // 4. dish_menu_type = 2(酒水),dish_type = 1(推荐)
+        // 5. dish_menu_type = 1和2(全部),dish_type = 0(全部)
+        // 6. dish_menu_type = 1和2(全部),dish_type = 1(推荐)
+        Map<String, Integer> countMap = new HashMap<>();
+
+        if (CollectionUtils.isNotEmpty(allMenuList)) {
+            // 统计:dish_menu_type = 1(菜单),dish_type = 1(推荐)
+            long menuRecommendCount = allMenuList.stream()
+                    .filter(item -> "1".equals(item.getDishMenuType())
+                            && DISH_TYPE_RECOMMEND.equals(item.getDishType()))
+                    .count();
+            countMap.put("menu_recommend", (int) menuRecommendCount);
+
+            // 统计:dish_menu_type = 1(菜单),dish_type = 0(全部)
+            long menuAllCount = allMenuList.stream()
+                    .filter(item -> "1".equals(item.getDishMenuType())
+                            && (item.getDishType() == null || DISH_TYPE_NON_RECOMMEND.equals(item.getDishType())))
+                    .count();
+            countMap.put("menu_all", (int) menuAllCount + (int) menuRecommendCount);
+
+            // 统计:dish_menu_type = 2(酒水),dish_type = 1(推荐)
+            long drinkRecommendCount = allMenuList.stream()
+                    .filter(item -> "2".equals(item.getDishMenuType())
+                            && DISH_TYPE_RECOMMEND.equals(item.getDishType()))
+                    .count();
+            countMap.put("drink_recommend", (int) drinkRecommendCount);
+
+            // 统计:dish_menu_type = 2(酒水),dish_type = 0(全部)
+            long drinkAllCount = allMenuList.stream()
+                    .filter(item -> "2".equals(item.getDishMenuType())
+                            && (item.getDishType() == null || DISH_TYPE_NON_RECOMMEND.equals(item.getDishType())))
+                    .count();
+            countMap.put("drink_all", (int) drinkAllCount + (int) drinkRecommendCount);
+
+            // 新增统计:dist_type 为全部,dish_menu_type 为 1 和 2(菜单和酒水都包含)
+            int allMenuAllCount = countMap.get("menu_all") + countMap.get("drink_all");
+            countMap.put("all_menu_all", allMenuAllCount);
+
+            // 新增统计:dist_type 为 1(推荐),dish_menu_type 为 1 和 2(菜单和酒水都包含)
+            int allMenuRecommendCount = countMap.get("menu_recommend") + countMap.get("drink_recommend");
+            countMap.put("all_menu_recommend", allMenuRecommendCount);
+        } else {
+            // 如果没有数据,返回0
+            countMap.put("menu_all", 0);
+            countMap.put("menu_recommend", 0);
+            countMap.put("drink_all", 0);
+            countMap.put("drink_recommend", 0);
+            countMap.put("all_menu_all", 0);
+            countMap.put("all_menu_recommend", 0);
+        }
+
+        // 处理菜品类型参数:当dishType为0时,转换为null以查询所有类型
+        Integer queryDishType = (dishType != null && DISH_TYPE_NON_RECOMMEND.equals(dishType)) ? null : dishType;
+
+        // 根据筛选条件查询菜单列表(用于返回给前端)
+        List<StoreMenuVo> menuList = storeMenuMapper.getClientMenuByStoreId(storeId, queryDishType, dishMenuType);
+
+        // 如果查询结果为空,返回空列表但保留统计信息
+        if (CollectionUtils.isEmpty(menuList)) {
+            log.info("获取用户端菜单完成,门店ID:{},未查询到符合条件的菜单数据", storeId);
+            Map<String, Object> result = new HashMap<>();
+            result.put("list", Collections.emptyList());
+            result.put("count", countMap);
+            return result;
+        }
+
+        log.info("获取用户端菜单,门店ID:{},查询到符合条件的菜单数量:{}", storeId, menuList.size());
+
+        // 如果是推荐菜且有用户标识,批量查询并设置点赞状态
+        if (DISH_TYPE_RECOMMEND.equals(dishType) && StringUtils.isNotEmpty(phoneId)) {
+            setLikeStatusForMenuList(menuList, phoneId);
+        } else {
+            // 非推荐菜或未提供用户手机号时,设置默认未点赞状态
+            menuList.forEach(item -> item.setIsLike(LIKE_STATUS_NO));
+        }
+
+        // 按排序字段升序排序
+        List<StoreMenuVo> sortedMenuList = menuList.stream()
+                .sorted(Comparator.comparing(StoreMenuVo::getSort, Comparator.nullsLast(Integer::compareTo)))
+                .collect(Collectors.toList());
+
+        // 构建返回结果
+        Map<String, Object> result = new HashMap<>();
+        result.put("list", sortedMenuList);
+        result.put("count", countMap);
+
+        log.info("获取用户端菜单完成,门店ID:{},返回菜单数量:{},统计信息:菜单-全部={},菜单-推荐={},酒水-全部={},酒水-推荐={},全部-全部={},全部-推荐={}",
+                storeId, sortedMenuList.size(),
+                countMap.get("menu_all"), countMap.get("menu_recommend"),
+                countMap.get("drink_all"), countMap.get("drink_recommend"),
+                countMap.get("all_menu_all"), countMap.get("all_menu_recommend"));
+
+        return result;
+    }
+
+    /**
+     * 批量设置菜单的点赞状态
+     * <p>
+     * 根据用户手机号和菜单ID列表,批量查询点赞记录,并设置每个菜单的点赞状态
+     * </p>
+     *
+     * @param menuList 菜单列表
+     * @param phoneId  用户手机号
+     */
+    private void setLikeStatusForMenuList(List<StoreMenuVo> menuList, String phoneId) {
+        if (CollectionUtils.isEmpty(menuList) || StringUtils.isEmpty(phoneId)) {
+            return;
+        }
+
+        log.debug("开始批量查询点赞状态,用户手机号:{},菜单数量:{}", phoneId, menuList.size());
+
+        // 将菜单ID转换为String集合,用于查询点赞记录
+        Set<String> menuIdStrSet = menuList.stream()
+                .map(item -> String.valueOf(item.getId()))
+                .filter(id -> id != null && !"null".equals(id))
+                .collect(Collectors.toSet());
+
+        if (menuIdStrSet.isEmpty()) {
+            log.warn("菜单ID集合为空,无法查询点赞状态");
+            menuList.forEach(item -> item.setIsLike(LIKE_STATUS_NO));
+            return;
+        }
+
+        // 批量查询点赞记录
+        LambdaQueryWrapper<LifeLikeRecord> likeQuery = new LambdaQueryWrapper<>();
+        likeQuery.eq(LifeLikeRecord::getDianzanId, phoneId)
+                .eq(LifeLikeRecord::getDeleteFlag, 0)
+                .in(LifeLikeRecord::getHuifuId, menuIdStrSet);
+        List<LifeLikeRecord> likeRecordList = lifeLikeRecordMapper.selectList(likeQuery);
+
+        // 构建已点赞的菜单ID集合
+        Set<String> likedMenuIdSet = likeRecordList.stream()
+                .map(LifeLikeRecord::getHuifuId)
+                .filter(id -> id != null)
+                .collect(Collectors.toSet());
+
+        log.debug("查询到点赞记录数量:{},已点赞菜单数量:{}",
+                likeRecordList.size(), likedMenuIdSet.size());
+
+        // 设置点赞状态
+        menuList.forEach(item -> {
+            if (item.getId() == null) {
+                item.setIsLike(LIKE_STATUS_NO);
+                return;
+            }
+
+            String menuIdStr = String.valueOf(item.getId());
+            if (likedMenuIdSet.contains(menuIdStr)) {
+                item.setIsLike(LIKE_STATUS_YES);
+            } else {
+                item.setIsLike(LIKE_STATUS_NO);
+            }
+        });
+    }
+
+    /**
+     * 获取菜品详情
+     *
+     * @param id 菜品id
+     * @return StoreMenuVo
+     */
+    @Override
+    public StoreMenuVo getClientMenuInfoById(Integer id, String phoneId) {
+        StoreMenuVo storeMenuVo = storeMenuMapper.getClientMenuInfoById(id);
+        if(StringUtils.isNotEmpty(phoneId)){
+            // 批量查询点赞记录
+            LambdaQueryWrapper<LifeLikeRecord> likeQuery = new LambdaQueryWrapper<>();
+            likeQuery.eq(LifeLikeRecord::getDianzanId, phoneId)
+                    .eq(LifeLikeRecord::getDeleteFlag, 0)
+                    .eq(LifeLikeRecord::getHuifuId, storeMenuVo.getId());
+            int likeCount = lifeLikeRecordMapper.selectCount(likeQuery);
+            if(likeCount > 0){
+                storeMenuVo.setIsLike(1);
+            }
+        } else {
+            storeMenuVo.setLikeCount(0);
+            storeMenuVo.setIsLike(0);
+        }
+
+        return storeMenuVo;
+    }
 }

+ 183 - 0
alien-store/src/main/java/shop/alien/store/service/impl/StoreOfficialAlbumServiceImpl.java

@@ -1,14 +1,20 @@
 package shop.alien.store.service.impl;
 
 import com.alibaba.excel.util.CollectionUtils;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang.StringUtils;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import shop.alien.entity.store.StoreImg;
 import shop.alien.entity.store.StoreOfficialAlbum;
+import shop.alien.entity.store.vo.StoreAlbumDetailVo;
+import shop.alien.entity.store.vo.StoreAlbumNameVo;
+import shop.alien.entity.store.vo.StoreOfficialAlbumImgVo;
 import shop.alien.entity.store.vo.StoreOfficialAlbumVo;
 import shop.alien.mapper.StoreImgMapper;
 import shop.alien.mapper.StoreOfficialAlbumMapper;
@@ -16,8 +22,10 @@ import shop.alien.store.service.StoreOfficialAlbumService;
 import shop.alien.store.util.CommonConstant;
 import java.util.Collections;
 import java.util.List;
+import java.util.Map;
 import java.util.stream.Collectors;
 
+@Slf4j
 @Transactional
 @Service
 @RequiredArgsConstructor
@@ -97,4 +105,179 @@ public class StoreOfficialAlbumServiceImpl extends ServiceImpl<StoreOfficialAlbu
         storeImgMapper.delete(wrapper);
         return CommonConstant.ERROR_CODE_VALID_PARAMS;
     }
+
+
+    /**
+     * 获取官方相册图片列表(客户端)
+     * <p>
+     * 根据门店ID和相册名称查询官方相册中的图片列表
+     * 查询条件:imgType = 2(官方相册),通过 business_id 关联到 store_official_album 表,按 albumName 筛选
+     * </p>
+     *
+     * @param storeId   门店ID,必填
+     * @param albumName 相册名称,可选。例如:酒水、餐食、环境、全部等。当为null或空字符串时,查询全部
+     * @return 图片列表和总数
+     */
+    @Override
+    public StoreOfficialAlbumImgVo getOfficialAlbumImgList(Integer storeId, String albumName) {
+        log.info("开始获取官方相册图片列表,门店ID:{},相册名称:{}", storeId, albumName);
+
+        // 参数校验
+        if (storeId == null || storeId <= 0) {
+            log.warn("获取官方相册图片列表失败,门店ID无效:{}", storeId);
+            throw new IllegalArgumentException("门店ID不能为空且必须大于0");
+        }
+
+        // 先查询符合条件的官方相册ID列表
+        LambdaQueryWrapper<StoreOfficialAlbum> albumQueryWrapper = new LambdaQueryWrapper<>();
+        albumQueryWrapper.eq(StoreOfficialAlbum::getStoreId, storeId)
+                .eq(StoreOfficialAlbum::getDeleteFlag, CommonConstant.DELETE_FLAG_UNDELETE);
+
+        // 如果指定了相册名称,添加筛选条件
+        if (StringUtils.isNotBlank(albumName)) {
+            albumQueryWrapper.eq(StoreOfficialAlbum::getAlbumName, albumName);
+        }
+
+        List<StoreOfficialAlbum> albumList = storeOfficialAlbumMapper.selectList(albumQueryWrapper);
+
+        // 如果相册列表为空,直接返回空结果
+        if (CollectionUtils.isEmpty(albumList)) {
+            log.info("获取官方相册图片列表,门店ID:{},未查询到符合条件的相册", storeId);
+            StoreOfficialAlbumImgVo result = new StoreOfficialAlbumImgVo();
+            result.setImgList(Collections.emptyList());
+            result.setTotalCount(0);
+            return result;
+        }
+
+        // 提取相册ID列表
+        List<Integer> albumIds = albumList.stream()
+                .map(StoreOfficialAlbum::getId)
+                .collect(Collectors.toList());
+
+        log.debug("查询到符合条件的相册数量:{},相册ID列表:{}", albumList.size(), albumIds);
+
+        // 查询这些相册下的所有图片(imgType = 2 表示官方相册)
+        LambdaQueryWrapper<StoreImg> imgQueryWrapper = new LambdaQueryWrapper<>();
+        imgQueryWrapper.eq(StoreImg::getStoreId, storeId)
+                .eq(StoreImg::getImgType, CommonConstant.STORE_IMG_ALBUM) // imgType = 2 表示官方相册
+                .in(StoreImg::getBusinessId, albumIds) // business_id 关联到相册ID
+                .eq(StoreImg::getDeleteFlag, CommonConstant.DELETE_FLAG_UNDELETE)
+                .orderByAsc(StoreImg::getImgSort);
+
+        List<StoreImg> imgList = storeImgMapper.selectList(imgQueryWrapper);
+
+        // 构建返回结果
+        StoreOfficialAlbumImgVo result = new StoreOfficialAlbumImgVo();
+        result.setImgList(imgList);
+        result.setTotalCount(imgList.size());
+
+        log.info("获取官方相册图片列表完成,门店ID:{},相册名称:{},返回图片数量:{}",
+                storeId, albumName, result.getTotalCount());
+
+        return result;
+    }
+
+    /**
+     * 获取官方相册名称列表(客户端)
+     * <p>
+     * 根据门店ID查询所有可用的相册名称列表,用于前端展示筛选选项
+     * 返回每个相册名称及其对应的图片数量
+     * </p>
+     *
+     * @param storeId 门店ID,必填
+     * @return 相册名称列表,包含相册名称和图片数量
+     */
+    @Override
+    public List<StoreAlbumNameVo> getAlbumNameList(Integer storeId) {
+        log.info("开始获取官方相册名称列表,门店ID:{}", storeId);
+
+        // 参数校验
+        if (storeId == null || storeId <= 0) {
+            log.warn("获取官方相册名称列表失败,门店ID无效:{}", storeId);
+            throw new IllegalArgumentException("门店ID不能为空且必须大于0");
+        }
+
+        // 查询该门店下所有未删除的官方相册
+        LambdaQueryWrapper<StoreOfficialAlbum> albumQueryWrapper = new LambdaQueryWrapper<>();
+        albumQueryWrapper.eq(StoreOfficialAlbum::getStoreId, storeId)
+                .eq(StoreOfficialAlbum::getDeleteFlag, CommonConstant.DELETE_FLAG_UNDELETE)
+                .isNotNull(StoreOfficialAlbum::getAlbumName)
+                .ne(StoreOfficialAlbum::getAlbumName, "");
+
+        List<StoreOfficialAlbum> albumList = storeOfficialAlbumMapper.selectList(albumQueryWrapper);
+
+        // 如果相册列表为空,直接返回空列表
+        if (CollectionUtils.isEmpty(albumList)) {
+            log.info("获取官方相册名称列表,门店ID:{},未查询到相册", storeId);
+            return Collections.emptyList();
+        }
+
+        // 提取相册ID列表
+        List<Integer> albumIds = albumList.stream()
+                .map(StoreOfficialAlbum::getId)
+                .collect(Collectors.toList());
+
+        // 查询这些相册下的所有图片(imgType = 2 表示官方相册)
+        LambdaQueryWrapper<StoreImg> imgQueryWrapper = new LambdaQueryWrapper<>();
+        imgQueryWrapper.eq(StoreImg::getStoreId, storeId)
+                .eq(StoreImg::getImgType, CommonConstant.STORE_IMG_ALBUM) // imgType = 2 表示官方相册
+                .in(StoreImg::getBusinessId, albumIds) // business_id 关联到相册ID
+                .eq(StoreImg::getDeleteFlag, CommonConstant.DELETE_FLAG_UNDELETE);
+
+        List<StoreImg> imgList = storeImgMapper.selectList(imgQueryWrapper);
+
+        // 构建相册ID到相册名称的映射
+        Map<Integer, String> albumIdToNameMap = albumList.stream()
+                .collect(Collectors.toMap(
+                        StoreOfficialAlbum::getId,
+                        StoreOfficialAlbum::getAlbumName,
+                        (existing, replacement) -> existing // 如果有重复的ID,保留第一个
+                ));
+
+        // 按相册名称分组统计图片数量
+        Map<String, Long> albumNameCountMap = imgList.stream()
+                .filter(img -> img.getBusinessId() != null && albumIdToNameMap.containsKey(img.getBusinessId()))
+                .collect(Collectors.groupingBy(
+                        img -> albumIdToNameMap.get(img.getBusinessId()),
+                        Collectors.counting()
+                ));
+
+        // 转换为返回VO列表
+        List<StoreAlbumNameVo> result = albumNameCountMap.entrySet().stream()
+                .map(entry -> {
+                    StoreAlbumNameVo vo = new StoreAlbumNameVo();
+                    vo.setAlbumName(entry.getKey());
+                    vo.setImgCount(entry.getValue().intValue());
+                    return vo;
+                })
+                .sorted((a, b) -> {
+                    // 按图片数量降序排序,如果数量相同则按名称排序
+                    int countCompare = Integer.compare(b.getImgCount(), a.getImgCount());
+                    if (countCompare != 0) {
+                        return countCompare;
+                    }
+                    return a.getAlbumName().compareTo(b.getAlbumName());
+                })
+                .collect(Collectors.toList());
+
+        log.info("获取官方相册名称列表完成,门店ID:{},返回相册数量:{}", storeId, result.size());
+
+        return result;
+    }
+
+    /**
+     * 将StoreImg转换为StoreImgInfo
+     *
+     * @param img 图片实体
+     * @return 图片信息VO
+     */
+    private StoreAlbumDetailVo.StoreImgInfo convertToImgInfo(StoreImg img) {
+        StoreAlbumDetailVo.StoreImgInfo info = new StoreAlbumDetailVo.StoreImgInfo();
+        info.setId(img.getId());
+        info.setImgUrl(img.getImgUrl());
+        info.setImgDescription(img.getImgDescription());
+        info.setImgSort(img.getImgSort());
+        info.setImgType(img.getImgType());
+        return info;
+    }
 }

+ 161 - 0
alien-store/src/main/java/shop/alien/store/service/impl/StorePersonnelServiceImpl.java

@@ -0,0 +1,161 @@
+package shop.alien.store.service.impl;
+
+import cn.hutool.core.collection.CollectionUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.StringUtils;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.BeanUtils;
+import org.springframework.stereotype.Service;
+import shop.alien.entity.result.R;
+import shop.alien.entity.store.StoreImg;
+import shop.alien.entity.store.StorePersonnel;
+import shop.alien.entity.store.vo.StorePersonnelVo;
+import shop.alien.mapper.StorePersonnelMapper;
+import shop.alien.store.service.StoreImgService;
+import shop.alien.store.service.StorePersonnelService;
+
+import java.util.Comparator;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * 店铺人员服务实现类
+ *
+ * @author system
+ * @since 2025-01-15
+ */
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class StorePersonnelServiceImpl extends ServiceImpl<StorePersonnelMapper, StorePersonnel> implements StorePersonnelService {
+
+    private final StorePersonnelMapper storePersonnelMapper;
+
+    private final StoreImgService storeImgService;
+
+    /**
+     * 获取店铺人员列表
+     *
+     * @param storeId 门店id
+     * @return List<StorePersonnelVo>
+     */
+    @Override
+    public List<StorePersonnelVo> getStorePersonnelList(Integer storeId) {
+        List<StorePersonnelVo> personnelList = storePersonnelMapper.getStorePersonnelList(storeId);
+        // 按排序字段排序
+        return personnelList.stream()
+                .sorted(Comparator.comparing(StorePersonnelVo::getSort, Comparator.nullsLast(Integer::compareTo)))
+                .collect(Collectors.toList());
+    }
+
+    /**
+     * 获取人员详情
+     *
+     * @param id 人员id
+     * @return StorePersonnelVo
+     */
+    @Override
+    public StorePersonnelVo getPersonnelInfo(Integer id) {
+        return storePersonnelMapper.getPersonnelInfo(id);
+    }
+
+    /**
+     * 新增或修改店铺人员
+     *
+     * @param storePersonnelVo 人员信息
+     * @return R<String>
+     */
+    @Override
+    public R<String> saveOrUpdatePersonnel(StorePersonnelVo storePersonnelVo) {
+        boolean flag = false;
+        LambdaQueryWrapper<StorePersonnel> queryWrapper = new LambdaQueryWrapper<>();
+        Integer imgId = 0;
+
+        // 处理图片信息
+        if (storePersonnelVo.getImgId() == null || storePersonnelVo.getImgId() == 0) {
+            if (StringUtils.isNotEmpty(storePersonnelVo.getImgUrl())) {
+                StoreImg storeImg = new StoreImg();
+                storeImg.setStoreId(storePersonnelVo.getStoreId());
+                // 图片类型:12-人员头像(根据实际业务调整)
+                storeImg.setImgType(12);
+                storeImg.setImgUrl(storePersonnelVo.getImgUrl());
+                storeImg.setImgDescription(storePersonnelVo.getPersonnelName());
+                storeImgService.saveOrUpdate(storeImg);
+                imgId = storeImg.getId();
+            }
+        } else {
+            imgId = storePersonnelVo.getImgId();
+        }
+
+        // 封装StorePersonnel参数
+        StorePersonnel storePersonnel = new StorePersonnel();
+        BeanUtils.copyProperties(storePersonnelVo, storePersonnel);
+        storePersonnel.setImgId(imgId);
+
+        // 修改人员
+        if (storePersonnel.getId() != null) {
+            flag = this.updateById(storePersonnel);
+            if (!flag) {
+                log.error("人员修改失败");
+                return R.fail("人员修改失败");
+            }
+            return R.success("人员修改成功");
+        } else {
+            // 新增人员
+            // 校验人员参数
+            if (StringUtils.isEmpty(storePersonnel.getPersonnelName())) {
+                return R.fail("请输入人员姓名");
+            }
+
+            // 计算排序值
+            queryWrapper.eq(StorePersonnel::getStoreId, storePersonnel.getStoreId());
+            List<StorePersonnel> personnelList = this.list(queryWrapper);
+            if (CollectionUtil.isNotEmpty(personnelList)) {
+                Integer maxSort = personnelList.stream()
+                        .map(StorePersonnel::getSort)
+                        .filter(sort -> sort != null)
+                        .max(Integer::compareTo)
+                        .orElse(0);
+                storePersonnel.setSort(maxSort + 1);
+            } else {
+                storePersonnel.setSort(1);
+            }
+
+            // 保存人员
+            flag = this.save(storePersonnel);
+            if (!flag) {
+                return R.fail("人员新增失败");
+            }
+        }
+        return R.success("新增人员成功");
+    }
+
+    /**
+     * 删除店铺人员
+     *
+     * @param ids 人员id列表
+     * @return R<String>
+     */
+    @Override
+    public R<String> deletePersonnel(List<Integer> ids) {
+        boolean flag = this.removeByIds(ids);
+        if (!flag) {
+            return R.fail("删除失败");
+        }
+        return R.success("删除成功");
+    }
+
+    /**
+     * 保存人员排序
+     *
+     * @param storePersonnelList 人员列表
+     * @return Boolean
+     */
+    @Override
+    public Boolean savePersonnelSort(List<StorePersonnel> storePersonnelList) {
+        return this.updateBatchById(storePersonnelList);
+    }
+}
+

+ 31 - 0
alien-store/src/main/java/shop/alien/store/service/impl/StoreStaffConfigServiceImpl.java

@@ -110,4 +110,35 @@ public class StoreStaffConfigServiceImpl implements StoreStaffConfigService {
         return aliOSSUtil.uploadFile(new File(filePath), "excel/" + fileName + ".xlsx");
     }
 
+
+    @Override
+    public IPage<StoreStaffConfig> queryStaffList(Integer page, Integer size, Integer storeId, String status) {
+        IPage<StoreStaffConfig> storePage = new Page<>(page, size);
+        QueryWrapper<StoreStaffConfig> queryWrapper = new QueryWrapper<>();
+        // 按照店铺ID查询
+        if (storeId != null && storeId > 0) {
+            queryWrapper.eq("store_id", storeId);
+        }
+        // 如果状态不为空,则进行精确匹配查询
+        if (StringUtils.isNotEmpty(status)) {
+            queryWrapper.eq("status", status);
+        }
+        // 只查询未删除的记录
+        queryWrapper.eq("delete_flag", 0);
+        queryWrapper.orderByDesc("created_time");
+        return storeStaffConfigMapper.selectPage(storePage, queryWrapper);
+    }
+
+    @Override
+    public StoreStaffConfig queryStaffDetail(Integer id) {
+        if (id == null || id <= 0) {
+            return null;
+        }
+        QueryWrapper<StoreStaffConfig> queryWrapper = new QueryWrapper<>();
+        queryWrapper.eq("id", id);
+        queryWrapper.eq("delete_flag", 0);
+        return storeStaffConfigMapper.selectOne(queryWrapper);
+
+
+    }
 }

+ 109 - 0
alien-store/src/main/java/shop/alien/store/util/ali/ocr/AbstractOcrStrategy.java

@@ -0,0 +1,109 @@
+package shop.alien.store.util.ali.ocr;
+
+import com.aliyun.ocr_api20210707.Client;
+import com.aliyun.tea.TeaException;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import shop.alien.entity.store.OcrImageUpload;
+import shop.alien.store.service.OcrImageUploadService;
+
+/**
+ * OCR策略抽象基类
+ * 提供公共的客户端创建和异常处理逻辑
+ *
+ * @author lyx
+ * @date 2025/11/18
+ */
+@Slf4j
+public abstract class AbstractOcrStrategy implements OcrStrategy {
+    
+    @Value("${ali.ocr.accessKeyId:}")
+    protected String accessKeyId;
+    
+    @Value("${ali.ocr.accessKeySecret:}")
+    protected String accessKeySecret;
+    
+    @Value("${ali.ocr.endpoint:ocr-api.cn-hangzhou.aliyuncs.com}")
+    protected String endpoint;
+
+    @Autowired
+    protected OcrImageUploadService ocrImageUploadService;
+
+    /**
+     * 创建OCR客户端
+     * 
+     * @return OCR客户端
+     * @throws Exception 创建客户端异常
+     */
+    protected Client createOcrClient() throws Exception {
+        // 工程代码建议使用更安全的无 AK 方式,凭据配置方式请参见:https://help.aliyun.com/document_detail/378657.html。
+        com.aliyun.credentials.Client credential = new com.aliyun.credentials.Client();
+        com.aliyun.teaopenapi.models.Config config = new com.aliyun.teaopenapi.models.Config()
+                .setCredential(credential);
+        
+        // 如果配置了AccessKey,则使用配置的值,否则使用默认值(仅用于测试)
+        if (org.apache.commons.lang3.StringUtils.isNotBlank(accessKeyId)) {
+            config.setAccessKeyId(accessKeyId);
+        } else {
+            // 默认值,仅用于测试,生产环境应该从配置文件读取
+            config.setAccessKeyId("LTAI5tLAUTQg7R1xaKvxAYJu");
+        }
+        
+        if (org.apache.commons.lang3.StringUtils.isNotBlank(accessKeySecret)) {
+            config.setAccessKeySecret(accessKeySecret);
+        } else {
+            // 默认值,仅用于测试,生产环境应该从配置文件读取
+            config.setAccessKeySecret("ayVk34nK9vQZ2bs5vDCYftQCEXXN3B");
+        }
+        
+        // Endpoint 请参考 https://api.aliyun.com/product/ocr-api
+        config.endpoint = endpoint;
+        
+        return new Client(config);
+    }
+    
+    /**
+     * 处理OCR识别异常
+     * 
+     * @param error 异常对象
+     * @param errorMessage 错误消息
+     * @throws Exception 重新抛出的异常
+     */
+    protected void handleOcrException(TeaException error, String errorMessage) throws Exception {
+        log.error("OCR识别失败: {}", errorMessage, error);
+        // 错误 message
+        log.error("错误信息: {}", error.getMessage());
+        // 诊断地址
+        if (error.getData() != null && error.getData().get("Recommend") != null) {
+            log.error("诊断地址: {}", error.getData().get("Recommend"));
+        }
+        com.aliyun.teautil.Common.assertAsString(error.message);
+        throw new Exception("OCR识别失败: " + errorMessage, error);
+    }
+    
+    /**
+     * 处理通用异常
+     * 
+     * @param error 异常对象
+     * @param errorMessage 错误消息
+     * @throws Exception 重新抛出的异常
+     */
+    protected void handleOcrException(Exception error, String errorMessage) throws Exception {
+        if (error instanceof TeaException) {
+            handleOcrException((TeaException) error, errorMessage);
+        } else {
+            TeaException teaException = new TeaException(error.getMessage(), error);
+            handleOcrException(teaException, errorMessage);
+        }
+    }
+
+    public void saveOcrImage(OcrImageUpload ocrImageUpload) {
+        // 保存OCR识别结果到数据库
+        boolean save = ocrImageUploadService.save(ocrImageUpload);
+        if (!save) {
+            log.error("保存OCR识别结果失败: {}", ocrImageUpload);
+        }
+    }
+}
+

+ 156 - 0
alien-store/src/main/java/shop/alien/store/util/ali/ocr/strategy/BusinessLicenseOcrStrategy.java

@@ -0,0 +1,156 @@
+package shop.alien.store.util.ali.ocr.strategy;
+
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import com.aliyun.ocr_api20210707.Client;
+import com.aliyun.ocr_api20210707.models.*;
+import com.aliyun.tea.TeaException;
+import com.aliyun.teautil.models.RuntimeOptions;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+import shop.alien.entity.result.R;
+import shop.alien.entity.store.OcrImageUpload;
+import shop.alien.entity.store.StoreImg;
+import shop.alien.store.service.StoreImgService;
+import shop.alien.store.util.ali.ocr.AbstractOcrStrategy;
+import shop.alien.util.common.constant.OcrTypeEnum;
+
+import java.util.Map;
+
+/**
+ * 营业执照OCR识别策略
+ *
+ * @author lyx
+ * @date 2025/11/18
+ */
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class BusinessLicenseOcrStrategy extends AbstractOcrStrategy {
+
+    private final StoreImgService storeImgService;
+
+    @Override
+    public R recognize(String imageId) throws Exception {
+
+        // 从数据库中获取图片URL
+        StoreImg storeImage = storeImgService.getById(imageId);
+        String imageUrl = storeImage.getImgUrl();
+        return recognizeUrl(imageUrl);
+    }
+
+    @Override
+    public R recognizeUrl(String imageUrl) throws Exception {
+        Client client = createOcrClient();
+        RecognizeBusinessLicenseRequest request = new RecognizeBusinessLicenseRequest()
+                .setUrl(imageUrl);
+
+        try {
+            RecognizeBusinessLicenseResponse response = client.recognizeBusinessLicenseWithOptions(
+                    request, new RuntimeOptions());
+            RecognizeBusinessLicenseResponseBody body = response.getBody();
+
+            if (body == null || body.getData() == null) {
+                throw new Exception("OCR识别返回结果为空");
+            }
+
+            JSONObject jsonObject = JSONObject.parseObject(body.getData());
+            log.info("营业执照OCR识别成功: {}", jsonObject.getJSONObject("data"));
+            return R.data(jsonObject.getJSONObject("data"));
+
+        } catch (TeaException error) {
+            handleOcrException(error, "营业执照识别失败");
+            return R.fail("营业执照识别失败");
+        } catch (Exception error) {
+            handleOcrException(error, "营业执照识别异常");
+            return R.fail("营业执照识别异常");
+        }
+    }
+
+    @Override
+    public R recognizeParams(Map<String, String> params) throws Exception {
+        String[] imageUrls = params.get("imageUrls").split(",");
+        JSONArray resultArray = new JSONArray();
+        for (String imageUrl : imageUrls) {
+            Client client = createOcrClient();
+            RecognizeBusinessLicenseRequest request = new RecognizeBusinessLicenseRequest()
+                    .setUrl(imageUrl); // 默认识别正面,可根据需要修改为 "back" 识别反面
+            try {
+                RecognizeBusinessLicenseResponse response = client.recognizeBusinessLicenseWithOptions(
+                        request, new RuntimeOptions());
+                RecognizeBusinessLicenseResponseBody body = response.getBody();
+
+                if (body == null || body.getData() == null) {
+                    throw new Exception("OCR识别返回结果为空");
+                }
+                JSONObject jsonObject = JSONObject.parseObject(body.getData());
+                log.info("营业执照OCR识别成功: {}", jsonObject.getJSONObject("data"));
+                // 保存OCR图片上传记录
+                OcrImageUpload ocrImageUpload = new OcrImageUpload();
+                ocrImageUpload.setUserId(params.get("userId"));
+                ocrImageUpload.setStoreId(params.get("storeId"));
+                ocrImageUpload.setStoreUserId(params.get("storeUserId"));
+                ocrImageUpload.setImageUrl(imageUrl);
+                ocrImageUpload.setOcrResult(jsonObject.getJSONObject("data").toJSONString());
+                ocrImageUpload.setOcrType(OcrTypeEnum.BUSINESS_LICENSE.getCode());
+                super.saveOcrImage(ocrImageUpload);
+                resultArray.add(jsonObject.getJSONObject("data"));
+            } catch (TeaException error) {
+                handleOcrException(error, "营业执照识别失败");
+                return R.fail("营业执照识别失败");
+            } catch (Exception error) {
+                handleOcrException(error, "营业执照识别异常");
+                return R.fail("营业执照识别异常");
+            }
+        }
+        return R.data(resultArray);
+    }
+
+    @Override
+    public R recognizeByBase64(String imageBase64) throws Exception {
+        Client client = createOcrClient();
+        // 将Base64字符串转换为字节数组,然后创建InputStream
+        byte[] imageBytes = java.util.Base64.getDecoder().decode(imageBase64);
+        java.io.InputStream imageInputStream = new java.io.ByteArrayInputStream(imageBytes);
+
+        RecognizeBusinessLicenseRequest request = new RecognizeBusinessLicenseRequest()
+                .setBody(imageInputStream);
+
+        try {
+            RecognizeBusinessLicenseResponse response = client.recognizeBusinessLicenseWithOptions(
+                    request, new RuntimeOptions());
+            RecognizeBusinessLicenseResponseBody body = response.getBody();
+
+            if (body == null || body.getData() == null) {
+                throw new Exception("OCR识别返回结果为空");
+            }
+
+            JSONObject jsonObject = JSONObject.parseObject(body.getData());
+            log.info("营业执照OCR识别成功: {}", jsonObject.getJSONObject("data"));
+            return R.data(jsonObject.getJSONObject("data"));
+
+        } catch (TeaException error) {
+            handleOcrException(error, "营业执照识别失败");
+            return R.fail("营业执照识别失败");
+        } catch (Exception error) {
+            handleOcrException(error, "营业执照识别异常");
+            return R.fail("营业执照识别异常");
+        } finally {
+            // 关闭流
+            if (imageInputStream != null) {
+                try {
+                    imageInputStream.close();
+                } catch (java.io.IOException e) {
+                    log.warn("关闭输入流失败", e);
+                }
+            }
+        }
+    }
+
+    @Override
+    public String getType() {
+        return OcrTypeEnum.BUSINESS_LICENSE.getCode();
+    }
+}
+

+ 154 - 0
alien-store/src/main/java/shop/alien/store/util/ali/ocr/strategy/FoodManageLicenseOcrStrategy.java

@@ -0,0 +1,154 @@
+package shop.alien.store.util.ali.ocr.strategy;
+
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import com.aliyun.ocr_api20210707.Client;
+import com.aliyun.ocr_api20210707.models.*;
+import com.aliyun.tea.TeaException;
+import com.aliyun.teautil.models.RuntimeOptions;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+import shop.alien.entity.result.R;
+import shop.alien.entity.store.OcrImageUpload;
+import shop.alien.entity.store.StoreImg;
+import shop.alien.store.service.StoreImgService;
+import shop.alien.store.util.ali.ocr.AbstractOcrStrategy;
+import shop.alien.util.common.constant.OcrTypeEnum;
+
+import java.util.Map;
+
+/**
+ * 食品经营许可证OCR识别策略
+ *
+ * @author lyx
+ * @date 2025/11/18
+ */
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class FoodManageLicenseOcrStrategy extends AbstractOcrStrategy {
+
+    private final StoreImgService storeImgService;
+
+    @Override
+    public R recognize(String imageId) throws Exception {
+
+        // 从数据库中获取图片URL
+        StoreImg storeImage = storeImgService.getById(imageId);
+        String imageUrl = storeImage.getImgUrl();
+        return recognizeUrl(imageUrl);
+    }
+
+    @Override
+    public R recognizeUrl(String imageUrl) throws Exception {
+        Client client = createOcrClient();
+        RecognizeFoodManageLicenseRequest request = new RecognizeFoodManageLicenseRequest()
+                .setUrl(imageUrl);
+
+        try {
+            RecognizeFoodManageLicenseResponse response = client.recognizeFoodManageLicenseWithOptions(
+                    request, new RuntimeOptions());
+            RecognizeFoodManageLicenseResponseBody body = response.getBody();
+
+            if (body == null || body.getData() == null) {
+                throw new Exception("OCR识别返回结果为空");
+            }
+
+            JSONObject jsonObject = JSONObject.parseObject(body.getData());
+            log.info("食品经营许可证OCR识别成功: {}", jsonObject.getJSONObject("data"));
+            return R.data(jsonObject.getJSONObject("data"));
+
+        } catch (TeaException error) {
+            handleOcrException(error, "食品经营许可证识别失败");
+            return R.fail("食品经营许可证识别失败");
+        } catch (Exception error) {
+            handleOcrException(error, "食品经营许可证识别异常");
+            return R.fail("食品经营许可证识别异常");
+        }
+    }
+
+    @Override
+    public R recognizeParams(Map<String, String> params) throws Exception {
+        String[] imageUrls = params.get("imageUrls").split(",");
+        JSONArray resultArray = new JSONArray();
+        for (String imageUrl : imageUrls) {
+            Client client = createOcrClient();
+            RecognizeFoodManageLicenseRequest request = new RecognizeFoodManageLicenseRequest()
+                    .setUrl(imageUrl); // 默认识别正面,可根据需要修改为 "back" 识别反面
+            try {
+                RecognizeFoodManageLicenseResponse response = client.recognizeFoodManageLicenseWithOptions(
+                        request, new RuntimeOptions());
+                RecognizeFoodManageLicenseResponseBody body = response.getBody();
+
+                if (body == null || body.getData() == null) {
+                    throw new Exception("OCR识别返回结果为空");
+                }
+                JSONObject jsonObject = JSONObject.parseObject(body.getData());
+                log.info("食品经营许可证OCR识别成功: {}", jsonObject.getJSONObject("data"));
+                // 保存OCR图片上传记录
+                OcrImageUpload ocrImageUpload = new OcrImageUpload();
+                ocrImageUpload.setUserId(params.get("userId"));
+                ocrImageUpload.setStoreId(params.get("storeId"));
+                ocrImageUpload.setStoreUserId(params.get("storeUserId"));
+                ocrImageUpload.setImageUrl(imageUrl);
+                ocrImageUpload.setOcrResult(jsonObject.getJSONObject("data").toJSONString());
+                ocrImageUpload.setOcrType(OcrTypeEnum.FOOD_MANAGE_LICENSE.getCode());
+                super.saveOcrImage(ocrImageUpload);
+                resultArray.add(jsonObject.getJSONObject("data"));
+            } catch (TeaException error) {
+                handleOcrException(error, "食品经营许可证识别失败");
+                return R.fail("食品经营许可证识别失败");
+            } catch (Exception error) {
+                handleOcrException(error, "食品经营许可证识别异常");
+                return R.fail("食品经营许可证识别异常");
+            }
+        }
+        return R.data(resultArray);
+    }
+
+    @Override
+    public R recognizeByBase64(String imageBase64) throws Exception {
+        Client client = createOcrClient();
+        // 将Base64字符串转换为字节数组,然后创建InputStream
+        byte[] imageBytes = java.util.Base64.getDecoder().decode(imageBase64);
+        java.io.InputStream imageInputStream = new java.io.ByteArrayInputStream(imageBytes);
+
+        RecognizeFoodManageLicenseRequest request = new RecognizeFoodManageLicenseRequest()
+                .setBody(imageInputStream);
+
+        try {
+            RecognizeFoodManageLicenseResponse response = client.recognizeFoodManageLicenseWithOptions(
+                    request, new RuntimeOptions());
+            RecognizeFoodManageLicenseResponseBody body = response.getBody();
+
+            if (body == null || body.getData() == null) {
+                throw new Exception("OCR识别返回结果为空");
+            }
+
+            JSONObject jsonObject = JSONObject.parseObject(body.getData());
+            log.info("食品经营许可证OCR识别成功: {}", jsonObject.getJSONObject("data"));
+            return R.data(jsonObject.getJSONObject("data"));
+        } catch (TeaException error) {
+            handleOcrException(error, "食品经营许可证识别失败");
+            return R.fail("食品经营许可证识别失败");
+        } catch (Exception error) {
+            handleOcrException(error, "食品经营许可证识别异常");
+            return R.fail("食品经营许可证识别异常");
+        } finally {
+            // 关闭流
+            if (imageInputStream != null) {
+                try {
+                    imageInputStream.close();
+                } catch (java.io.IOException e) {
+                    log.warn("关闭输入流失败", e);
+                }
+            }
+        }
+    }
+
+    @Override
+    public String getType() {
+        return OcrTypeEnum.FOOD_MANAGE_LICENSE.getCode();
+    }
+}

+ 0 - 0
logging.path_IS_UNDEFINED/DEBUG.log


+ 0 - 0
logging.path_IS_UNDEFINED/ERROR.log


+ 6 - 0
logging.path_IS_UNDEFINED/INFO.log

@@ -0,0 +1,6 @@
+[11/28 08:38:36.388][main      ][INFO ][c.a.n.c.c.i.LocalConfigInfoProcessor    :  212]:[       <clinit>] || LOCAL_SNAPSHOT_PATH:C:\Users\Windows\nacos\config
+[11/28 08:38:36.484][main      ][INFO ][c.a.n.c.c.u.JvmUtil                     :   49]:[       <clinit>] || isMultiInstance:false
+[11/28 08:38:36.541][main      ][INFO ][b.c.PropertySourceBootstrapConfiguration:  109]:[     initialize] || Located property source: [BootstrapPropertySource {name='bootstrapProperties-alien-lawyer-dev.yml,DEFAULT_GROUP'}, BootstrapPropertySource {name='bootstrapProperties-alien-lawyer.yml,DEFAULT_GROUP'}, BootstrapPropertySource {name='bootstrapProperties-alien-lawyer,DEFAULT_GROUP'}]
+[11/28 11:37:14.297][main      ][INFO ][c.a.n.c.c.i.LocalConfigInfoProcessor    :  212]:[       <clinit>] || LOCAL_SNAPSHOT_PATH:C:\Users\Windows\nacos\config
+[11/28 11:37:14.326][main      ][INFO ][c.a.n.c.c.u.JvmUtil                     :   49]:[       <clinit>] || isMultiInstance:false
+[11/28 11:37:14.361][main      ][INFO ][b.c.PropertySourceBootstrapConfiguration:  109]:[     initialize] || Located property source: [BootstrapPropertySource {name='bootstrapProperties-alien-lawyer-dev.yml,DEFAULT_GROUP'}, BootstrapPropertySource {name='bootstrapProperties-alien-lawyer.yml,DEFAULT_GROUP'}, BootstrapPropertySource {name='bootstrapProperties-alien-lawyer,DEFAULT_GROUP'}]

+ 4 - 0
logging.path_IS_UNDEFINED/WARN.log

@@ -0,0 +1,4 @@
+[11/28 08:38:36.529][main      ][WARN ][c.a.c.n.c.NacosPropertySourceBuilder    :   87]:[  loadNacosData] || Ignore the empty nacos configuration and get it based on dataId[alien-lawyer.yml] & group[DEFAULT_GROUP]
+[11/28 08:38:36.538][main      ][WARN ][c.a.c.n.c.NacosPropertySourceBuilder    :   87]:[  loadNacosData] || Ignore the empty nacos configuration and get it based on dataId[alien-lawyer-dev.yml] & group[DEFAULT_GROUP]
+[11/28 11:37:14.355][main      ][WARN ][c.a.c.n.c.NacosPropertySourceBuilder    :   87]:[  loadNacosData] || Ignore the empty nacos configuration and get it based on dataId[alien-lawyer.yml] & group[DEFAULT_GROUP]
+[11/28 11:37:14.359][main      ][WARN ][c.a.c.n.c.NacosPropertySourceBuilder    :   87]:[  loadNacosData] || Ignore the empty nacos configuration and get it based on dataId[alien-lawyer-dev.yml] & group[DEFAULT_GROUP]