Quellcode durchsuchen

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/UserActionRewardRule.java
#	alien-job/src/main/java/shop/alien/job/second/AiUserViolationJob.java
#	alien-job/src/main/java/shop/alien/job/store/BadReviewAppealJob.java
#	alien-lawyer/src/main/java/shop/alien/lawyer/service/impl/LawyerClientConsultationOrderServiceImpl.java
#	alien-lawyer/src/main/java/shop/alien/lawyer/service/impl/LawyerConsultationOrderServiceImpl.java
#	alien-lawyer/src/main/java/shop/alien/lawyer/service/impl/OrderExpirationServiceImpl.java
#	alien-lawyer/src/main/java/shop/alien/lawyer/util/AliSms.java
#	alien-second/src/main/java/shop/alien/second/service/impl/SecondGoodsServiceImpl.java
#	alien-store-platform/src/main/java/shop/alien/storeplatform/service/impl/StorePlatformLoginServiceImpl.java
#	alien-store-platform/src/main/java/shop/alien/storeplatform/service/impl/StorePlatformRenovationServiceImpl.java
#	alien-store/src/main/java/shop/alien/store/service/impl/UserActionRewardRuleServiceImpl.java
LuTong vor 1 Woche
Ursprung
Commit
1401d9ce5c
49 geänderte Dateien mit 7240 neuen und 211 gelöschten Zeilen
  1. 6 0
      alien-entity/pom.xml
  2. 103 0
      alien-entity/src/main/java/shop/alien/entity/store/DrinkInfo.java
  3. 1 1
      alien-entity/src/main/java/shop/alien/entity/store/StoreImg.java
  4. 6 5
      alien-entity/src/main/java/shop/alien/entity/store/StoreInfo.java
  5. 1 1
      alien-entity/src/main/java/shop/alien/entity/store/StoreMenu.java
  6. 11 0
      alien-entity/src/main/java/shop/alien/entity/store/StoreStaffConfig.java
  7. 79 0
      alien-entity/src/main/java/shop/alien/entity/store/UserActionRewardRule.java
  8. 13 1
      alien-entity/src/main/java/shop/alien/entity/store/dto/StoreInfoDto.java
  9. 66 0
      alien-entity/src/main/java/shop/alien/entity/store/dto/deserializer/StringToListDeserializer.java
  10. 82 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/DrinkInfoVo.java
  11. 3 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/LifeDiscountCouponFriendRuleVo.java
  12. 3 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/LifeDiscountCouponVo.java
  13. 3 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/StoreUserVo.java
  14. 1 1
      alien-entity/src/main/java/shop/alien/entity/storePlatform/StoreOperationalActivity.java
  15. 14 0
      alien-entity/src/main/java/shop/alien/mapper/DrinkInfoMapper.java
  16. 2 0
      alien-entity/src/main/java/shop/alien/mapper/LifeDiscountCouponStoreFriendMapper.java
  17. 9 5
      alien-entity/src/main/java/shop/alien/mapper/LifeFansMapper.java
  18. 1 1
      alien-entity/src/main/java/shop/alien/mapper/StoreMenuMapper.java
  19. 34 0
      alien-entity/src/main/resources/mapper/DrinkInfoMapper.xml
  20. 199 0
      alien-job/src/main/java/shop/alien/job/second/AiUserViolationJob.java
  21. 435 0
      alien-job/src/main/java/shop/alien/job/store/BadReviewAppealJob.java
  22. 1389 0
      alien-lawyer/src/main/java/shop/alien/lawyer/service/impl/LawyerClientConsultationOrderServiceImpl.java
  23. 1787 0
      alien-lawyer/src/main/java/shop/alien/lawyer/service/impl/LawyerConsultationOrderServiceImpl.java
  24. 443 0
      alien-lawyer/src/main/java/shop/alien/lawyer/service/impl/OrderExpirationServiceImpl.java
  25. 114 0
      alien-lawyer/src/main/java/shop/alien/lawyer/util/AliSms.java
  26. 13 4
      alien-second/src/main/java/shop/alien/second/service/impl/SecondGoodsServiceImpl.java
  27. 303 0
      alien-store-platform/src/main/java/shop/alien/storeplatform/service/impl/StorePlatformLoginServiceImpl.java
  28. 113 0
      alien-store-platform/src/main/java/shop/alien/storeplatform/service/impl/StorePlatformRenovationServiceImpl.java
  29. 46 0
      alien-store/src/main/java/shop/alien/store/controller/BathFacilityServiceController.java
  30. 190 0
      alien-store/src/main/java/shop/alien/store/controller/DrinkInfoController.java
  31. 99 0
      alien-store/src/main/java/shop/alien/store/controller/OperationalActivityController.java
  32. 23 6
      alien-store/src/main/java/shop/alien/store/controller/StoreInfoController.java
  33. 1 1
      alien-store/src/main/java/shop/alien/store/controller/StoreMenuController.java
  34. 20 0
      alien-store/src/main/java/shop/alien/store/controller/StoreStaffConfigController.java
  35. 6 0
      alien-store/src/main/java/shop/alien/store/service/BathFacilityServiceService.java
  36. 141 0
      alien-store/src/main/java/shop/alien/store/service/DrinkInfoService.java
  37. 5 0
      alien-store/src/main/java/shop/alien/store/service/LifeUserStoreService.java
  38. 44 0
      alien-store/src/main/java/shop/alien/store/service/OperationalActivityService.java
  39. 80 49
      alien-store/src/main/java/shop/alien/store/service/StoreInfoService.java
  40. 4 2
      alien-store/src/main/java/shop/alien/store/service/StoreOfficialAlbumService.java
  41. 18 0
      alien-store/src/main/java/shop/alien/store/service/StoreStaffConfigService.java
  42. 75 0
      alien-store/src/main/java/shop/alien/store/service/impl/BathFacilityServiceServiceImpl.java
  43. 260 0
      alien-store/src/main/java/shop/alien/store/service/impl/DrinkInfoServiceImpl.java
  44. 1 0
      alien-store/src/main/java/shop/alien/store/service/impl/LifeDiscountCouponServiceImpl.java
  45. 256 0
      alien-store/src/main/java/shop/alien/store/service/impl/OperationalActivityServiceImpl.java
  46. 358 127
      alien-store/src/main/java/shop/alien/store/service/impl/StoreInfoServiceImpl.java
  47. 1 1
      alien-store/src/main/java/shop/alien/store/service/impl/StoreMenuServiceImpl.java
  48. 65 6
      alien-store/src/main/java/shop/alien/store/service/impl/StoreStaffConfigServiceImpl.java
  49. 313 0
      alien-store/src/main/java/shop/alien/store/service/impl/UserActionRewardRuleServiceImpl.java

+ 6 - 0
alien-entity/pom.xml

@@ -77,6 +77,12 @@
             <scope>compile</scope>
         </dependency>
 
+        <!-- Jackson for JSON deserialization -->
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-databind</artifactId>
+        </dependency>
+
     </dependencies>
 
     <build>

+ 103 - 0
alien-entity/src/main/java/shop/alien/entity/store/DrinkInfo.java

@@ -0,0 +1,103 @@
+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.math.BigDecimal;
+import java.util.Date;
+
+/**
+ * 二期-酒水餐食信息
+ *
+ * @author ssk
+ * @since 2025-05-14
+ */
+@Data
+@JsonInclude
+@TableName("drink_info")
+@ApiModel(value = "DrinkInfo对象", description = "酒水餐食信息")
+public class DrinkInfo  {
+
+    @ApiModelProperty(value = "主键")
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    @ApiModelProperty(value = "类型(枚举值:酒水/餐食)")
+    @TableField("type")
+    private String type;
+
+    @ApiModelProperty(value = "名称")
+    @TableField("name")
+    private String name;
+
+    @ApiModelProperty(value = "售价")
+    @TableField("price")
+    private BigDecimal price;
+
+    @ApiModelProperty(value = "成本价")
+    @TableField("cost_price")
+    private BigDecimal costPrice;
+
+    @ApiModelProperty(value = "酒精度")
+    @TableField("alcohol_volume")
+    private BigDecimal alcoholVolume;
+
+    @ApiModelProperty(value = "图片")
+    @TableField("pic_url")
+    private String picUrl;
+
+    @ApiModelProperty(value = "分类")
+    @TableField("category")
+    private String category;
+
+    @ApiModelProperty(value = "口味")
+    @TableField("flavor")
+    private String flavor;
+
+    @ApiModelProperty(value = "描述")
+    @TableField("description")
+    private String description;
+
+    @ApiModelProperty(value = "排序")
+    @TableField("sort")
+    private Integer sort;
+
+    @ApiModelProperty(value = "是否推荐 0:否 1:是")
+    @TableField("is_recommended")
+    private Integer isRecommended;
+
+    @ApiModelProperty(value = "状态 0:下架 1:上架")
+    @TableField("status")
+    private Integer status;
+
+    @ApiModelProperty(value = "门店ID")
+    @TableField("store_id")
+    private Integer storeId;
+
+    @ApiModelProperty(value = "创建人ID")
+    @TableField("create_user_id")
+    private Integer createUserId;
+
+    @ApiModelProperty(value = "创建时间")
+    @TableField(value = "create_time", fill = FieldFill.INSERT)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date createTime;
+
+    @ApiModelProperty(value = "修改人ID")
+    @TableField("update_user_id")
+    private Integer updateUserId;
+
+    @ApiModelProperty(value = "修改时间")
+    @TableField(value = "update_time", fill = FieldFill.UPDATE)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date updateTime;
+
+    @ApiModelProperty(value = "是否删除 0:未删除 1:已删除")
+    @TableField("is_deleted")
+    @TableLogic
+    private Integer isDeleted;
+}

+ 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.食品经营许可证审核后状态, 28:运动器材设施图片, 29:洗浴设施及服务图片 30 酒水")
+    @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.食品经营许可证审核后类型 26.运营活动活动标题图 27.运营活动活动详情图 28.运动设施 29.洗浴设施及服务 30.酒水 31.娱乐经营许可证 32.娱乐经营许可证审核前")
     @TableField("img_type")
     private Integer imgType;
 

+ 6 - 5
alien-entity/src/main/java/shop/alien/entity/store/StoreInfo.java

@@ -86,6 +86,7 @@ public class StoreInfo {
 
     @ApiModelProperty(value = "到期时间")
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    @TableField("expiration_time")
     private Date expirationTime;
 
     @ApiModelProperty(value = "门店坐标")
@@ -235,7 +236,7 @@ public class StoreInfo {
     @TableField("update_renew_contract_time")
     private Date  updateRenewContractTime;
 
-    @ApiModelProperty(value = "分类id(词典表 键为 business_classify)")
+    @ApiModelProperty(value = "分类id(词典表 键为 business_classify)(多个ID用逗号拼接)")
     @TableField("business_classify")
     private String businessClassify;
 
@@ -243,11 +244,11 @@ public class StoreInfo {
     @TableField("business_classify_name")
     private String businessClassifyName;
 
-    @ApiModelProperty(value = "是否提供餐食 0 否 1 是")
-    @TableField("meal_provided")
-    private Integer mealProvided;
+    @ApiModelProperty(value = "是否提供餐食")
+    @TableField("meals_flag")
+    private Integer  mealsFlag;
 
-    @ApiModelProperty(value = "娱乐经营许可证状态 字典 foodLicenceStatus")
+    @ApiModelProperty(value = "娱乐经营许可证状态")
     @TableField("entertainment_licence_status")
     private Integer entertainmentLicenceStatus;
 

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

@@ -94,7 +94,7 @@ public class StoreMenu {
     private String description;
 
     @ApiModelProperty(value = "酒精度")
-    @TableField("alcoholVolume")
+    @TableField("alcohol_volume")
     private String alcoholVolume;
 
     @ApiModelProperty(value = "分类")

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

@@ -7,6 +7,7 @@ import com.fasterxml.jackson.annotation.JsonInclude;
 import io.swagger.annotations.ApiModel;
 import io.swagger.annotations.ApiModelProperty;
 import lombok.Data;
+import lombok.EqualsAndHashCode;
 
 import java.util.Date;
 
@@ -16,6 +17,7 @@ import java.util.Date;
  * @Description: 员工管理
  */
 @Data
+@EqualsAndHashCode(callSuper = false)
 @JsonInclude
 @TableName("store_staff_config")
 @ApiModel(value = "StoreStaffConfig对象", description = "员工管理")
@@ -89,4 +91,13 @@ public class StoreStaffConfig extends Model<StoreStaffConfig> {
     @ApiModelProperty(value = "修改人ID")
     @TableField("updated_user_id")
     private Integer updatedUserId;
+
+    @ApiModelProperty(value = "置顶状态, 0:未置顶, 1:已置顶")
+    @TableField("top_status")
+    private Integer topStatus;
+
+    @ApiModelProperty(value = "置顶时间")
+    @TableField("top_time")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date topTime;
 }

+ 79 - 0
alien-entity/src/main/java/shop/alien/entity/store/UserActionRewardRule.java

@@ -0,0 +1,79 @@
+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-XX
+ */
+@Data
+@JsonInclude
+@TableName("user_action_reward_rule")
+@ApiModel(value = "UserActionRewardRule", description = "用户行为奖励规则配置表(3级树形结构)")
+public class UserActionRewardRule {
+
+    @ApiModelProperty(value = "主键ID")
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    @ApiModelProperty(value = "父节点ID,0表示根节点(第一级:角色)")
+    @TableField("parent_id")
+    private Integer parentId;
+
+    @ApiModelProperty(value = "层级:1-角色,2-行为,3-奖励")
+    @TableField("level")
+    private Integer level;
+
+    @ApiModelProperty(value = "名称:根据level区分,1-角色名,2-行为名,3-奖励名")
+    @TableField("name")
+    private String name;
+
+    @ApiModelProperty(value = "奖励数量:如1优惠券、2红包(仅第三级使用)")
+    @TableField("reward_amount")
+    private Integer rewardAmount;
+
+    @ApiModelProperty(value = "状态:0-禁用,1-启用")
+    @TableField("status")
+    private Integer status;
+
+    @ApiModelProperty(value = "排序:数值越大越靠前")
+    @TableField("sort_order")
+    private Integer sortOrder;
+
+    @ApiModelProperty(value = "备注")
+    @TableField("remark")
+    private String remark;
+
+    @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;
+
+    @ApiModelProperty(value = "删除标记, 0:未删除, 1:已删除")
+    @TableField("delete_flag")
+    @TableLogic
+    private Integer deleteFlag;
+}
+

+ 13 - 1
alien-entity/src/main/java/shop/alien/entity/store/dto/StoreInfoDto.java

@@ -3,11 +3,13 @@ package shop.alien.entity.store.dto;
 import com.baomidou.mybatisplus.annotation.TableField;
 import com.fasterxml.jackson.annotation.JsonFormat;
 import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
 import io.swagger.annotations.ApiModel;
 import io.swagger.annotations.ApiModelProperty;
 import lombok.Data;
 import org.springframework.format.annotation.DateTimeFormat;
 import shop.alien.entity.store.StoreBusinessInfo;
+import shop.alien.entity.store.dto.deserializer.StringToListDeserializer;
 
 import java.util.Date;
 import java.util.List;
@@ -112,12 +114,14 @@ public class StoreInfoDto {
     private String businessTypeValue;
 
     @ApiModelProperty(value = "经营种类集合")
+    @JsonDeserialize(using = StringToListDeserializer.class)
     private List<String> businessTypesList;
 
     @ApiModelProperty(value = "经营板块id(词典表 键为 business_section)")
     private Integer businessSection;
 
     @ApiModelProperty(value = "经营种类ids")
+    @JsonDeserialize(using = StringToListDeserializer.class)
     private List<String> businessTypes;
 
     @ApiModelProperty(value = "经营种类名称s")
@@ -127,9 +131,11 @@ public class StoreInfoDto {
     private String userAccount;
 
     @ApiModelProperty(value = "营业执照图片地址")
+    @JsonDeserialize(using = StringToListDeserializer.class)
     private List<String> businessLicenseAddress;
 
     @ApiModelProperty(value = "合同图片地址")
+    @JsonDeserialize(using = StringToListDeserializer.class)
     private List<String> contractImageList;
 
     @ApiModelProperty(value = "经营许可证图片地址")
@@ -181,13 +187,14 @@ public class StoreInfoDto {
     private Date foodLicenceExpirationTime;
 
     @ApiModelProperty(value = "分类id(词典表 键为 business_classify)(多个ID用逗号拼接)")
+    @JsonDeserialize(using = StringToListDeserializer.class)
     private List<String> businessClassify;
 
     @ApiModelProperty(value = "分类名称")
     private String businessClassifyName;
 
     @ApiModelProperty(value = "是否提供餐食")
-    private Integer  mealsFlag;
+    private Integer mealsFlag;
 
     @ApiModelProperty(value = "娱乐经营许可证图片地址")
     private String entertainmentLicenceUrl;
@@ -205,4 +212,9 @@ public class StoreInfoDto {
     @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
     @DateTimeFormat(pattern = "yyyy-MM-dd")
     private Date entertainmentLicenceExpirationTime;
+
+//    @ApiModelProperty(value = "分类id(词典表 键为 business_classify)(多个ID用逗号拼接)")
+//    @JsonDeserialize(using = StringToListDeserializer.class)
+//    private List<String> businessClassify;
+
 }

+ 66 - 0
alien-entity/src/main/java/shop/alien/entity/store/dto/deserializer/StringToListDeserializer.java

@@ -0,0 +1,66 @@
+package shop.alien.entity.store.dto.deserializer;
+
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.JsonDeserializer;
+import com.fasterxml.jackson.databind.JsonNode;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 自定义反序列化器:将字符串或数组转换为List<String>
+ * 支持以下格式:
+ * - 字符串:"0" 或 "1,2,3"
+ * - 数组:["1", "2", "3"]
+ * - null:返回空列表
+ */
+public class StringToListDeserializer extends JsonDeserializer<List<String>> {
+
+    @Override
+    public List<String> deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
+        JsonNode node = p.getCodec().readTree(p);
+        
+        if (node == null || node.isNull()) {
+            return new ArrayList<>();
+        }
+        
+        List<String> result = new ArrayList<>();
+        
+        // 如果是数组
+        if (node.isArray()) {
+            for (JsonNode item : node) {
+                if (item != null && !item.isNull()) {
+                    result.add(item.asText());
+                }
+            }
+        } 
+        // 如果是字符串
+        else if (node.isTextual()) {
+            String value = node.asText();
+            if (value != null && !value.trim().isEmpty()) {
+                // 如果是逗号分隔的字符串,则分割
+                if (value.contains(",")) {
+                    String[] parts = value.split(",");
+                    for (String part : parts) {
+                        String trimmed = part.trim();
+                        if (!trimmed.isEmpty()) {
+                            result.add(trimmed);
+                        }
+                    }
+                } else {
+                    // 单个值
+                    result.add(value.trim());
+                }
+            }
+        }
+        // 如果是数字,转换为字符串
+        else if (node.isNumber()) {
+            result.add(node.asText());
+        }
+        
+        return result;
+    }
+}
+

+ 82 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/DrinkInfoVo.java

@@ -0,0 +1,82 @@
+package shop.alien.entity.store.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.util.Date;
+
+/**
+ * 酒水餐食信息表 VO
+ *
+ * @author ssk
+ * @since 2025-06-13
+ */
+@Data
+@ApiModel(value = "DrinkInfoVo", description = "酒水餐食信息表视图对象")
+public class DrinkInfoVo {
+
+    @ApiModelProperty(value = "主键ID")
+    private Integer id;
+
+    @ApiModelProperty(value = "类型(枚举值:酒水/餐食)")
+    private String type;
+
+    @ApiModelProperty(value = "商品名称")
+    private String name;
+
+    @ApiModelProperty(value = "商品价格")
+    private BigDecimal price;
+
+    @ApiModelProperty(value = "商品成本价")
+    private BigDecimal costPrice;
+
+    @ApiModelProperty(value = "商品分类")
+    private String category;
+
+    @ApiModelProperty(value = "酒精度数")
+    private BigDecimal alcoholVolume;
+
+    @ApiModelProperty(value = "商品图片")
+    private String picUrl;
+
+    @ApiModelProperty(value = "商品描述")
+    private String description;
+
+    @ApiModelProperty(value = "商品口味")
+    private String flavor;
+
+    @ApiModelProperty(value = "门店ID")
+    private Integer storeId;
+
+    @ApiModelProperty(value = "门店名称")
+    private String storeName;
+
+    @ApiModelProperty(value = "是否推荐(0:否, 1:是)")
+    private Integer isRecommended;
+
+    @ApiModelProperty(value = "状态(0:下架, 1:上架)")
+    private Integer status;
+
+    @ApiModelProperty(value = "排序值")
+    private Integer sort;
+
+    @ApiModelProperty(value = "是否删除(0:否, 1:是)")
+    private Integer isDeleted;
+
+    @ApiModelProperty(value = "创建人ID")
+    private Integer createUserId;
+
+    @ApiModelProperty(value = "创建时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date createTime;
+
+    @ApiModelProperty(value = "更新人ID")
+    private Integer updateUserId;
+
+    @ApiModelProperty(value = "更新时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date updateTime;
+}

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

@@ -39,6 +39,9 @@ public class LifeDiscountCouponFriendRuleVo extends LifeDiscountCouponFriendRule
     @ApiModelProperty(value = "优惠券数量")
     private Integer couponNum;
 
+    @ApiModelProperty(value = "优惠券id")
+    private Integer couponId;
+
     @ApiModelProperty(value = "面值")
     private BigDecimal nominalValue;
 

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

@@ -115,6 +115,9 @@ public class LifeDiscountCouponVo {
     @ApiModelProperty(value = "经营板块id(词典表 键为 business_section)")
     private Integer businessSection;
 
+    @ApiModelProperty(value = "经营板块名称")
+    private String businessSectionName;
+
     @ApiModelProperty(value = "自定义领取规则数量")
     private Integer claimRuleCustomizeValue;
 

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

@@ -39,4 +39,7 @@ public class StoreUserVo extends StoreUser {
 
     @ApiModelProperty(value = "手机号")
     private String phone;
+
+    @ApiModelProperty(value = "是否提供餐食")
+    private Integer mealsFlag;
 }

+ 1 - 1
alien-entity/src/main/java/shop/alien/entity/storePlatform/StoreOperationalActivity.java

@@ -67,7 +67,7 @@ public class StoreOperationalActivity {
     @TableField("coupon_quantity")
     private Integer couponQuantity;
 
-    @ApiModelProperty(value = "状态:0-禁用, 1-启用")
+    @ApiModelProperty(value = "状态:1-待审核, 2-未开始, 3-审核拒绝, 4-已售罄, 5-进行中, 6-已下架, 7-已结束")
     @TableField("status")
     private Integer status;
 

+ 14 - 0
alien-entity/src/main/java/shop/alien/mapper/DrinkInfoMapper.java

@@ -0,0 +1,14 @@
+package shop.alien.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import shop.alien.entity.store.DrinkInfo;
+
+/**
+ * 酒水餐食信息表 Mapper 接口
+ *
+ * @author ssk
+ * @since 2025-06-13
+ */
+public interface DrinkInfoMapper extends BaseMapper<DrinkInfo> {
+
+}

+ 2 - 0
alien-entity/src/main/java/shop/alien/mapper/LifeDiscountCouponStoreFriendMapper.java

@@ -38,6 +38,7 @@ public interface LifeDiscountCouponStoreFriendMapper extends BaseMapper<LifeDisc
     @Select("select ldcsf.created_time endDate,ldc.nominal_value nominalValue,ldc.minimum_spending_amount minimumSpendingAmount,img.img_url imgUrl,\n" +
             "si.store_name storeName,\n" +
             "ldc.name couponName,\n" +
+            "ldc.id couponId,\n" +
             "ldcsf.single_qty couponNum\n" +
             "from  life_discount_coupon_store_friend ldcsf\n" +
             "left join life_discount_coupon ldc\n" +
@@ -53,6 +54,7 @@ public interface LifeDiscountCouponStoreFriendMapper extends BaseMapper<LifeDisc
     @Select("select ldcsf.created_time endDate,ldc.nominal_value nominalValue,ldc.minimum_spending_amount minimumSpendingAmount,img.img_url imgUrl,\n" +
             "si.store_name storeName,\n" +
             "ldc.name couponName,\n" +
+            "ldc.id couponId,\n" +
             "ldcsf.single_qty couponNum\n" +
             "from  life_discount_coupon_store_friend ldcsf\n" +
             "left join life_discount_coupon ldc\n" +

+ 9 - 5
alien-entity/src/main/java/shop/alien/mapper/LifeFansMapper.java

@@ -15,7 +15,8 @@ import java.util.List;
 @Mapper
 public interface LifeFansMapper extends BaseMapper<LifeFans> {
 
-    @Select("select foll.*,lb.id blackListid, if(isnull(fans.id), 0, 1) isFollowMe, 1 as isFollowThis,  " +
+    @Select("select MAX(foll.id) id, MAX(foll.name) name, MAX(foll.image) image, foll.phoneId, MAX(foll.blurb) blurb, MAX(foll.blockedType) blockedType, MAX(foll.blockedId) blockedId, MAX(foll.username) username, MAX(foll.accountBlurb) accountBlurb, " +
+            "  MAX(lb.id) blackListid, MAX(if(isnull(fans.id), 0, 1)) isFollowMe, 1 as isFollowThis,  " +
             "  (select count(1) from life_fans where fans_id= foll.phoneId and delete_flag =0) followNum, " +
             "  (select count(1) from life_fans where followed_id= foll.phoneId and delete_flag =0) fansNum from ( " +
             "    with follow as (   " +
@@ -63,7 +64,8 @@ public interface LifeFansMapper extends BaseMapper<LifeFans> {
             "left join life_fans fans on fans.fans_id = foll.phoneId and fans.followed_id = #{fansId} and fans.delete_flag = 0 ")
     List<LifeFansVo> getMyFollowedAll(@Param("fansId") String fansId);
 
-    @Select("select foll.*,lb.id blackListid, if(isnull(fans.id), 0, 1) isFollowThis, 1 as isFollowMe, " +
+    @Select("select MAX(foll.id) id, MAX(foll.name) name, MAX(foll.image) image, foll.phoneId, MAX(foll.blurb) blurb, MAX(foll.blockedType) blockedType, MAX(foll.blockedId) blockedId, " +
+            "  MAX(lb.id) blackListid, MAX(if(isnull(fans.id), 0, 1)) isFollowThis, 1 as isFollowMe, " +
             "    (select count(1) from life_fans fans2 where fans2.followed_id = foll.phoneId and fans2.delete_flag = 0) fansNum, " +
             "    (select count(1) from life_fans fans3 where fans3.fans_id = foll.phoneId and fans3.delete_flag = 0) followNum " +
             "from ( " +
@@ -169,7 +171,8 @@ public interface LifeFansMapper extends BaseMapper<LifeFans> {
             "${ew.customSqlSegment}")
     IPage<LifeFansVo> getMyStoreFans(IPage<LifeFansVo> iPage, @Param("fansId") String fansId, @Param(Constants.WRAPPER) QueryWrapper<LifeFansVo> wrapper);
 
-    @Select("select foll.*, if(isnull(fans.id), 0, 1) isFollowThis, 1 as isFollowMe, " +
+    @Select("select MAX(foll.id) id, MAX(foll.name) name, MAX(foll.image) image, foll.phoneId, MAX(foll.blurb) blurb, " +
+            "  MAX(if(isnull(fans.id), 0, 1)) isFollowThis, 1 as isFollowMe, " +
             "       (select count(1) from life_fans fans2 where fans2.followed_id = foll.phoneId and fans2.delete_flag = 0) fansNum, " +
             "       (select count(1) from life_fans fans3 where fans3.fans_id = foll.phoneId and fans3.delete_flag = 0) followNum " +
             " from ( " +
@@ -187,7 +190,8 @@ public interface LifeFansMapper extends BaseMapper<LifeFans> {
             "${ew.customSqlSegment} ")
     IPage<LifeFansVo> getMyUserFans(IPage<LifeFansVo> iPage, @Param("fansId") String fansId, @Param(Constants.WRAPPER) QueryWrapper<LifeFansVo> wrapper);
 
-    @Select("select foll.*, lb.id blackListid, 1 as isFollowThis, 1 as isFollowMe, " +
+    @Select("select MAX(foll.id) id, MAX(foll.name) name, MAX(foll.image) image, foll.phoneId, MAX(foll.blurb) blurb, MAX(foll.blockedType) blockedType, MAX(foll.blockedId) blockedId, MAX(foll.username) username, MAX(foll.accountBlurb) accountBlurb, " +
+            "  MAX(lb.id) blackListid, 1 as isFollowThis, 1 as isFollowMe, " +
             "       (select count(1) from life_fans fans2 where fans2.followed_id = foll.phoneId and fans2.delete_flag = 0) fansNum, " +
             "       (select count(1) from life_fans fans3 where fans3.fans_id = foll.phoneId and fans3.delete_flag = 0) followNum " +
             "from ( " +
@@ -213,7 +217,7 @@ public interface LifeFansMapper extends BaseMapper<LifeFans> {
             "${ew.customSqlSegment} ")
     IPage<LifeFansVo> getMutualAttention(IPage<LifeFansVo> iPage, @Param("fansId") String fansId, @Param("blockerType") String blockerType, @Param("blockerId") String blockerId, @Param(Constants.WRAPPER) QueryWrapper<LifeFansVo> wrapper);
 
-    @Select("select foll.*, 1 as isFollowThis, 1 as isFollowMe, " +
+    @Select("select MAX(foll.id) id, MAX(foll.name) name, MAX(foll.image) image, foll.phoneId, MAX(foll.blurb) blurb, 1 as isFollowThis, 1 as isFollowMe, " +
             "       (select count(1) from life_fans fans2 where fans2.followed_id = foll.phoneId and fans2.delete_flag = 0) fansNum, " +
             "       (select count(1) from life_fans fans3 where fans3.fans_id = foll.phoneId and fans3.delete_flag = 0) followNum " +
             "from ( " +

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

@@ -31,7 +31,7 @@ public interface StoreMenuMapper extends BaseMapper<StoreMenu> {
             "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);
+    List<StoreMenuVo> getStoreMenuInfoList(@Param("storeId") Integer storeId, @Param("dishType") Integer dishType, @Param("dishMenuType") Integer dishMenuType);
 
     /**
      * 获取菜单

+ 34 - 0
alien-entity/src/main/resources/mapper/DrinkInfoMapper.xml

@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="shop.alien.mapper.DrinkInfoMapper">
+
+    <!-- 通用查询映射结果 -->
+    <resultMap id="BaseResultMap" type="shop.alien.entity.store.DrinkInfo">
+        <id column="id" property="id" />
+        <result column="type" property="type" />
+        <result column="name" property="name" />
+        <result column="price" property="price" />
+        <result column="cost_price" property="costPrice" />
+        <result column="category" property="category" />
+        <result column="alcohol_volume" property="alcoholVolume" />
+        <result column="pic_url" property="picUrl" />
+        <result column="description" property="description" />
+        <result column="flavor" property="flavor" />
+        <result column="store_id" property="storeId" />
+        <result column="is_recommended" property="isRecommended" />
+        <result column="status" property="status" />
+        <result column="sort" property="sort" />
+        <result column="is_deleted" property="isDeleted" />
+        <result column="create_user_id" property="createUserId" />
+        <result column="create_time" property="createTime" />
+        <result column="update_user_id" property="updateUserId" />
+        <result column="update_time" property="updateTime" />
+    </resultMap>
+
+    <!-- 通用查询结果列 -->
+    <sql id="Base_Column_List">
+        id, type, name, price, cost_price, category, alcohol_volume, pic_url, description, flavor, store_id, 
+        is_recommended, status, sort, is_deleted, create_user_id, create_time, update_user_id, update_time
+    </sql>
+
+</mapper>

+ 199 - 0
alien-job/src/main/java/shop/alien/job/second/AiUserViolationJob.java

@@ -0,0 +1,199 @@
+package shop.alien.job.second;
+
+import com.alibaba.fastjson2.JSONObject;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.xxl.job.core.handler.annotation.XxlJob;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.http.client.ClientHttpRequestInterceptor;
+import org.springframework.stereotype.Component;
+import org.springframework.util.LinkedMultiValueMap;
+import org.springframework.util.MultiValueMap;
+import org.springframework.util.StringUtils;
+import org.springframework.web.client.RestTemplate;
+import shop.alien.entity.result.R;
+import shop.alien.entity.second.SecondGoods;
+import shop.alien.entity.store.*;
+import shop.alien.mapper.LifeNoticeMapper;
+import shop.alien.mapper.LifeUserMapper;
+import shop.alien.mapper.LifeUserViolationMapper;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class AiUserViolationJob {
+
+    private final RestTemplate restTemplate;
+    private final LifeUserViolationMapper lifeUserViolationMapper;
+
+    private final LifeUserMapper lifeUserMapper;
+
+    private final LifeNoticeMapper lifeNoticeMapper;
+
+    @Value("${third-party-user-name.base-url}")
+    private String userName;
+
+    @Value("${third-party-pass-word.base-url}")
+    private String passWord;
+
+    private String loginUrl = "http://192.168.2.250:9000/ai/user-auth-core/api/v1/auth/login";
+
+    private String aiUserViolationCheckUrl = "http://192.168.2.250:9000/ai/auto-review/api/v1/user_complaint_record/result";
+
+    @XxlJob("getAiUserViolationResult")
+    public R<String> getAiUserViolationResult() {
+        String accessToken = fetchAiServiceToken();
+        if (!StringUtils.hasText(accessToken)) {
+            return R.fail("调用AI举报用户辅助系统 登录接口失败");
+        }
+        return getAiUserViolationCheck(accessToken);
+    }
+
+    private String fetchAiServiceToken() {
+        log.info("登录Ai服务获取token...{}", loginUrl);
+        MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
+        formData.add("username", userName);
+        formData.add("password", passWord);
+
+        HttpHeaders headers = new HttpHeaders();
+        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
+        HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<>(formData, headers);
+
+        try {
+            ResponseEntity<String> response = restTemplate.postForEntity(loginUrl, requestEntity, String.class);
+            if (response != null && response.getStatusCodeValue() == 200 && response.getBody() != null) {
+                JSONObject jsonObject = JSONObject.parseObject(response.getBody());
+                JSONObject dataJson = jsonObject.getJSONObject("data");
+                return dataJson != null ? dataJson.getString("access_token") : null;
+            }
+            log.error("请求差评申诉辅助系统 登录接口失败 http状态:{}", response != null ? response.getStatusCode() : null);
+        } catch (Exception e) {
+            log.error("调用差评申诉辅助系统登录接口异常", e);
+        }
+        return null;
+    }
+
+    private R<String> getAiUserViolationCheck(String accessToken) {
+
+
+        HttpHeaders analyzeHeaders = new HttpHeaders();
+        analyzeHeaders.setContentType(MediaType.APPLICATION_JSON);
+        analyzeHeaders.set("Authorization", "Bearer " + accessToken);
+        // 查询所有状态为处理中的申诉
+        List<LifeUserViolation> pendingTasks = lifeUserViolationMapper.selectList(
+                new QueryWrapper<LifeUserViolation>()
+                        .eq("processing_status", "0")
+                        .isNotNull("ai_task_id")
+        );
+
+        // 循环调用查询结果接口
+        for (LifeUserViolation task : pendingTasks) {
+            String completedUrl = buildCompletedUrl(task.getAiTaskId());
+
+            ResponseEntity<String> analyzeResp;
+
+            RestTemplate restTemplateWithAuth = new RestTemplate();
+            List<ClientHttpRequestInterceptor> interceptors = new ArrayList<>();
+            interceptors.add((request, body, execution) -> {
+                request.getHeaders().set("Authorization", "Bearer " + accessToken);
+                return execution.execute(request, body);
+            });
+            restTemplateWithAuth.setInterceptors(interceptors);
+
+            ResponseEntity<String> response = null;
+
+            try {
+                analyzeResp = restTemplateWithAuth.getForEntity(completedUrl, String.class);
+
+                if (analyzeResp != null && analyzeResp.getStatusCodeValue() == 200) {
+                    String analyzeBody = analyzeResp.getBody();
+                    log.info("AI举报用户审核提交成功, 返回: {}", analyzeBody);
+
+                    JSONObject analyzeJson = JSONObject.parseObject(analyzeBody);
+                    JSONObject dataJsonObj = analyzeJson.getJSONObject("data");
+
+                    if (dataJsonObj == null) {
+                        log.error("AI举报用户审核返回数据为空");
+                        R.fail("AI举报用户审核返回数据为空");
+                        continue;
+                    }
+
+                    // 获取task_id用于后续查询
+                    String taskId = dataJsonObj.getString("task_id");
+                    if (taskId == null) {
+                        log.error("AI举报用户审核返回task_id为空");
+                        R.fail("AI举报用户审核返回task_id为空");
+                        continue;
+                    }
+
+                    LifeUserViolation aiTask = new LifeUserViolation();
+                    aiTask.setAiTaskId(taskId);
+                    if (dataJsonObj.getString("status").equals("pending")) {
+                        R.fail("审核未结束");
+                        continue;
+                    }
+
+                    if (dataJsonObj.getString("status").equals("completed")) {
+                        if (dataJsonObj.getString("is_valid").equals("true")) {
+                            aiTask.setProcessingStatus("1");
+                            aiTask.setReportResult(dataJsonObj.getString("decision_reason"));
+                        } else {
+                            aiTask.setProcessingStatus("0");
+                            aiTask.setReportResult(dataJsonObj.getString("decision_reason"));
+                        }
+                    }
+
+                    // 发送通知
+                    LifeNotice lifeMessage = new LifeNotice();
+                    LifeUser lifeUser = lifeUserMapper.selectById(task.getReportingUserId());
+                    lifeMessage.setReceiverId("user_" + lifeUser.getUserPhone());
+                    String text = "您的举报用户结果为" + (aiTask.getProcessingStatus().equals("1") ? "违规" : "未违规");
+                    lifeMessage.setContext(text);
+                    lifeMessage.setSenderId("system");
+                    lifeMessage.setIsRead(0);
+                    lifeMessage.setNoticeType(1);
+                    lifeNoticeMapper.insert(lifeMessage);
+
+                    QueryWrapper<LifeUserViolation> queryWrapper = new QueryWrapper<>();
+                    queryWrapper.eq("ai_task_id", taskId);
+                    LifeUserViolation lifeUserViolation = lifeUserViolationMapper.selectOne(queryWrapper);
+                    if (lifeUserViolation == null) {
+                        log.error("AI举报用户不存在");
+                        R.fail("AI举报用户不存在");
+                        continue;
+                    }
+                    lifeUserViolationMapper.update(aiTask, queryWrapper);
+
+                } else {
+                    if (analyzeResp != null) {
+                        log.error("调用AI举报用户审核接口失败, http状态: {}", analyzeResp.getStatusCode());
+                        R.fail("调用AI举报用户审核接口失败, http状态: " + analyzeResp.getStatusCode());
+                    }
+                }
+            } catch (Exception e) {
+                log.error("调用差评申述查询结果接口异常", e);
+            }
+        }
+        return R.success("调用AI举报用户审核结果接口完成");
+    }
+
+    private String buildCompletedUrl(String recordId) {
+        String baseUrl = aiUserViolationCheckUrl;
+        if (!StringUtils.hasText(baseUrl)) {
+            throw new IllegalStateException("ai举报用户接口地址未配置");
+        }
+        if (baseUrl.endsWith("/")) {
+            baseUrl = baseUrl.substring(0, baseUrl.length() - 1);
+        }
+        // 构建新的URL格式: /api/v1/audit_task/getResult?task_id={recordId}
+        return baseUrl + "?task_id=" + recordId;
+    }
+}

+ 435 - 0
alien-job/src/main/java/shop/alien/job/store/BadReviewAppealJob.java

@@ -0,0 +1,435 @@
+package shop.alien.job.store;
+
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONObject;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.xxl.job.core.context.XxlJobHelper;
+import com.xxl.job.core.handler.annotation.XxlJob;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.collections4.map.HashedMap;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.http.client.ClientHttpRequestInterceptor;
+import org.springframework.stereotype.Component;
+import org.springframework.util.LinkedMultiValueMap;
+import org.springframework.util.MultiValueMap;
+import org.springframework.util.StringUtils;
+import org.springframework.web.client.RestTemplate;
+import shop.alien.entity.store.*;
+import shop.alien.entity.result.R;
+import shop.alien.mapper.LifeNoticeMapper;
+import shop.alien.mapper.StoreCommentAppealMapper;
+import shop.alien.mapper.StoreCommentMapper;
+import shop.alien.mapper.StoreUserMapper;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 调用AI差评申诉辅助系统服务类
+ *
+ * @author fcw
+ * @date 2025/11/20 14:21
+ */
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class BadReviewAppealJob {
+
+    private final RestTemplate restTemplate;
+
+    private final StoreCommentAppealMapper storeCommentAppealMapper;
+
+    private final StoreCommentMapper storeCommentMapper;
+    private final StoreUserMapper storeUserMapper;
+    private final LifeNoticeMapper lifeNoticeMapper;
+
+//    @Value("${third-party-login.base-url}")
+//    private String loginUrl;
+
+    private String loginUrl = "http://192.168.2.78:9000/ai/user-auth-core/api/v1/auth/login";
+
+
+    @Value("${third-party-user-name.base-url}")
+    private String userName;
+
+    @Value("${third-party-pass-word.base-url}")
+    private String passWord;
+
+    private String analyzeUrl = "http://192.168.2.78:9000/ai/auto-review/api/v1/analyze";
+
+    private String resultUrl = "http://192.168.2.78:9000/ai/auto-review";
+
+
+
+    /**
+     * 差评申述置信度分析接口地址
+     * 例如: http://192.168.2.250:9004/api/v1/analyze 或通过网关: http://192.168.2.250:9000/ai/auto_review/api/v1/analyze
+     */
+//    @Value("${bad-review-analyze.base-url}")
+//    private String analyzeUrl;
+
+    /**
+     * 调用AI服务获取Token
+     */
+    @XxlJob("getBadReviewAppealJob")
+    public R<String> getBadReviewAppealJob() {
+        String accessToken = fetchAiServiceToken();
+        if (!StringUtils.hasText(accessToken)) {
+            return R.fail("调用差评申诉辅助系统登录任务执行失败 返回异常");
+        }
+        return invokeAnalyzeTask(accessToken);
+    }
+
+
+    @XxlJob("getAppealCompletedResult")
+    public R<String> getAppealCompletedResult() {
+        String accessToken = fetchAiServiceToken();
+        if (!StringUtils.hasText(accessToken)) {
+            return R.fail("调用差评申诉辅助系统 登录接口失败");
+        }
+        return getNegativeReviewAppealCompletedResult(accessToken);
+    }
+
+    private R<String> getNegativeReviewAppealCompletedResult(String accessToken) {
+
+
+        HttpHeaders analyzeHeaders = new HttpHeaders();
+        analyzeHeaders.setContentType(MediaType.APPLICATION_JSON);
+        analyzeHeaders.set("Authorization", "Bearer " + accessToken);
+        // 查询所有状态为处理中的申诉
+        List<StoreCommentAppeal> pendingAppeals = storeCommentAppealMapper.getPendingAppeals();
+        // 循环调用查询结果接口
+        for (StoreCommentAppeal appeal : pendingAppeals) {
+            String completedUrl = buildCompletedUrl(appeal.getRecordId());
+
+            ResponseEntity<String> analyzeResp;
+
+            RestTemplate restTemplateWithAuth = new RestTemplate();
+            List<ClientHttpRequestInterceptor> interceptors = new ArrayList<>();
+            interceptors.add((request, body, execution) -> {
+                request.getHeaders().set("Authorization", "Bearer " + accessToken);
+                return execution.execute(request, body);
+            });
+            restTemplateWithAuth.setInterceptors(interceptors);
+
+            ResponseEntity<String> response = null;
+
+            try {
+                analyzeResp = restTemplateWithAuth.getForEntity(completedUrl, String.class);
+
+                if (analyzeResp != null && analyzeResp.getStatusCodeValue() == 200) {
+                    String analyzeBody = analyzeResp.getBody();
+                    log.info("差评申述置信度分析提交成功, 返回: {}", analyzeBody);
+
+                    JSONObject analyzeJson = JSONObject.parseObject(analyzeBody);
+//                    JSONObject details = analyzeJson.getJSONObject("details");
+//                    String analysisStatus = details.get("analysis_status").toString();
+//                    if (analysisStatus.equals("pending")) {
+//                        log.error("差评申述置信度分析尚未完成");
+//                        R.fail("差评申述置信度分析尚未完成");
+//                        continue;
+//                    }
+                    JSONObject dataJsonObj = analyzeJson.getJSONObject("data");
+
+                    if (dataJsonObj == null) {
+                        log.error("差评申述置信度分析返回数据为空");
+                        R.fail("差评申述置信度分析返回数据为空");
+                        continue;
+                    }
+
+                    // 获取record_id用于后续查询
+                    Integer recordId = dataJsonObj.getInteger("id");
+                    if (recordId == null) {
+                        log.error("差评申述置信度分析返回record_id为空");
+                        R.fail("差评申述置信度分析返回record_id为空");
+                        continue;
+                    }
+
+                    StoreCommentAppeal sCommentAppeal = new StoreCommentAppeal();
+                    sCommentAppeal.setRecordId(appeal.getRecordId());
+                    // 判断得分大小
+                    if (dataJsonObj.getDouble("user_confidence") > dataJsonObj.getDouble("merchant_confidence")){
+                        sCommentAppeal.setAppealStatus(1);
+                        sCommentAppeal.setFinalResult("已驳回");
+                    } else {
+                        sCommentAppeal.setAppealStatus(2);
+                        sCommentAppeal.setFinalResult("已同意");
+                        //假删除评论
+                        StoreComment storeComment = new StoreComment();
+                        storeComment.setId(appeal.getCommentId());
+                        storeComment.setDeleteFlag(1);
+                        storeCommentMapper.updateById(storeComment);
+                    }
+                    sCommentAppeal.setAppealAiApproval(dataJsonObj.toJSONString());
+
+                    sCommentAppeal.setRecordId(recordId);
+                    storeCommentAppealMapper.updateByRecordId(appeal.getRecordId(),
+                            sCommentAppeal.getAppealStatus(),
+                            sCommentAppeal.getFinalResult());
+
+                    // 发送通知
+                    LifeNotice lifeMessage = new LifeNotice();
+                    StoreUser storeUser = storeUserMapper.selectOne(new QueryWrapper<StoreUser>().eq("store_id", appeal.getStoreId()));
+                    lifeMessage.setReceiverId("store_" + storeUser.getPhone());
+                    String text = "您的评论申诉结果为" + sCommentAppeal.getFinalResult();
+                    lifeMessage.setContext(text);
+                    lifeMessage.setSenderId("system");
+                    lifeMessage.setIsRead(0);
+                    lifeMessage.setNoticeType(1);
+                    lifeNoticeMapper.insert(lifeMessage);
+
+                } else {
+                    if (analyzeResp != null) {
+                        log.error("调用差评申述置信度分析接口失败, http状态: {}", analyzeResp.getStatusCode());
+                        R.fail("调用差评申述置信度分析接口失败, http状态: " + analyzeResp.getStatusCode());
+                    }
+                }
+            } catch (Exception e) {
+                log.error("调用差评申述查询结果接口异常", e);
+            }
+        }
+        return R.success("调用差评申述置信度分析结果接口完成");
+    }
+
+
+
+
+    private R<String> invokeAnalyzeTask(String token) {
+        log.info("开始调用差评申述置信度分析接口, url: {}", analyzeUrl);
+
+        HttpHeaders analyzeHeaders = new HttpHeaders();
+        analyzeHeaders.setContentType(MediaType.APPLICATION_JSON);
+        analyzeHeaders.set("Authorization", "Bearer " + token);
+
+        // 查询一段时间内的差评申述
+        // 循环插入
+        List<Map<String, Object>> appealList = storeCommentAppealMapper.getAppealList();
+
+        if (appealList == null || appealList.isEmpty()) {
+            log.info("没有需要处理的差评申述");
+            return R.success("没有需要处理的差评申述");
+        }
+
+        for (Map<String, Object> storeCommentAppeal : appealList) {
+            Map<String, Object> analyzeRequest = new HashedMap<>();
+
+            // 商家申诉材料
+            analyzeRequest.put("merchant_material",
+                    storeCommentAppeal.get("appeal_reason") == null ? "" : storeCommentAppeal.get("appeal_reason").toString());
+
+            // 用户差评内容
+            analyzeRequest.put("user_material",
+                    storeCommentAppeal.get("comment_content") == null ? "" : storeCommentAppeal.get("comment_content").toString());
+
+            // 商家图片:支持多张,转成 Base64 数组
+            List<String> merchantImages = new ArrayList<>();
+            String imgUrls = storeCommentAppeal.get("img_url") == null ? "" : storeCommentAppeal.get("img_url").toString();
+            if (StringUtils.hasText(imgUrls)) {
+                // 假设 img_url 是多个图片用逗号分隔的字符串
+                for (String imageUrl : imgUrls.split(",")) {
+                    String base64 = convertImageToBase64(imageUrl.trim());
+                    if (StringUtils.hasText(base64)) {
+                        merchantImages.add(base64);
+                    }
+                }
+            }
+            analyzeRequest.put("merchant_images", merchantImages);
+
+            // 用户图片:同理
+            List<String> userImages = new ArrayList<>();
+            String userImgUrls = storeCommentAppeal.get("user_img_url") == null ? "" : storeCommentAppeal.get("user_img_url").toString();
+            if (StringUtils.hasText(userImgUrls)) {
+                for (String imageUrl : userImgUrls.split(",")) {
+                    String base64 = convertImageToBase64(imageUrl.trim());
+                    if (StringUtils.hasText(base64)) {
+                        userImages.add(base64);
+                    }
+                }
+            }
+            analyzeRequest.put("user_images", userImages);
+
+            // 其他字段
+            analyzeRequest.put("case_id", storeCommentAppeal.get("comment_id") == null ? "" : storeCommentAppeal.get("comment_id").toString());
+            analyzeRequest.put("order_id", storeCommentAppeal.get("business_id") == null ? "" : storeCommentAppeal.get("business_id").toString());
+
+            HttpEntity<Map<String, Object>> analyzeEntity = new HttpEntity<>(analyzeRequest, analyzeHeaders);
+
+            ResponseEntity<String> analyzeResp = null;
+            try {
+                analyzeResp = restTemplate.postForEntity(analyzeUrl, analyzeEntity, String.class);
+            } catch (org.springframework.web.client.HttpServerErrorException.ServiceUnavailable e) {
+                log.error("调用差评申述置信度分析接口返回503 Service Unavailable错误: {}", e.getResponseBodyAsString());
+                continue;
+            } catch (Exception e) {
+                log.error("调用差评申述置信度分析接口异常", e);
+                continue;
+            }
+
+            if (analyzeResp != null && analyzeResp.getStatusCodeValue() == 200) {
+                String analyzeBody = analyzeResp.getBody();
+                log.info("差评申述置信度分析提交成功, 返回: {}", analyzeBody);
+
+                JSONObject analyzeJson = JSONObject.parseObject(analyzeBody);
+                JSONObject dataJsonObj = analyzeJson.getJSONObject("data");
+
+                if (dataJsonObj == null) {
+                    log.error("差评申述置信度分析返回数据为空");
+                    R.fail("差评申述置信度分析返回数据为空");
+                    continue;
+                }
+
+                // 获取record_id用于后续查询
+                Integer recordId = dataJsonObj.getInteger("record_id");
+                if (recordId == null) {
+                    log.error("差评申述置信度分析返回record_id为空");
+                    R.fail("差评申述置信度分析返回record_id为空");
+                    continue;
+                }
+
+                //修改评论状态为"处理中"
+                StoreCommentAppeal sCommentAppeal = new StoreCommentAppeal();
+                sCommentAppeal.setId((Integer) storeCommentAppeal.get("id"));
+                sCommentAppeal.setAppealStatus(0);
+                sCommentAppeal.setRecordId(recordId);
+                storeCommentAppealMapper.updateById(sCommentAppeal);
+
+            } else {
+                if (analyzeResp != null) {
+                    log.error("调用差评申述置信度分析接口失败, http状态: {}", analyzeResp.getStatusCode());
+                    R.fail("调用差评申述置信度分析接口失败, http状态: " + analyzeResp.getStatusCode());
+                }
+            }
+        }
+        return R.success("调用差评申述置信度分析接口完成");
+    }
+
+    /**
+     * 将图片URL转换为Base64编码
+     *
+     * @param imageUrl 图片URL
+     * @return Base64编码字符串
+     */
+    private String convertImageToBase64(String imageUrl) {
+        // 1. 检查是否为空
+        if (!StringUtils.hasText(imageUrl)) {
+            log.warn("图片URL为空");
+            return "";
+        }
+
+        // 2. 对URL进行编码处理(解决特殊字符问题)
+        String encodedUrl = encodeImageUrl(imageUrl);
+
+        // 3. 验证URL格式
+        if (!isValidUrl(encodedUrl)) {
+            log.warn("无效的图片URL: {}", imageUrl);
+            return "";
+        }
+
+        try {
+            // 4. 下载图片并转换Base64
+            byte[] imageBytes = restTemplate.getForObject(encodedUrl, byte[].class);
+            if (imageBytes != null) {
+                return java.util.Base64.getEncoder().encodeToString(imageBytes);
+            }
+        } catch (org.springframework.web.client.HttpClientErrorException.NotFound e) {
+            log.warn("图片不存在 (404), URL: {}", imageUrl);
+            return "";
+        } catch (Exception e) {
+            log.error("图片转换为Base64失败, URL: {}", imageUrl, e);
+            return "";
+        }
+        return "";
+    }
+
+    // 新增方法:对URL中的特殊字符进行编码
+    private String encodeImageUrl(String imageUrl) {
+        try {
+            URI uri = new URI(imageUrl);
+            String scheme = uri.getScheme();
+            String host = uri.getHost();
+            String path = uri.getPath();
+            String query = uri.getQuery();
+
+            // 对路径中的特殊字符进行编码
+            String encodedPath = java.net.URLEncoder.encode(path, "UTF-8")
+                    .replace("%2F", "/")  // 保留斜杠
+                    .replace("+", "%20"); // 空格编码
+
+            StringBuilder encodedUrl = new StringBuilder();
+            encodedUrl.append(scheme).append("://").append(host).append(encodedPath);
+
+            if (query != null) {
+                encodedUrl.append("?").append(query);
+            }
+
+            return encodedUrl.toString();
+        } catch (Exception e) {
+            log.warn("URL编码失败,使用原始URL: {}", imageUrl);
+            return imageUrl;
+        }
+    }
+
+
+
+    // 辅助方法:验证URL格式合法性
+    private boolean isValidUrl(String url) {
+        try {
+            // 尝试构造URI,验证格式
+            new URI(url);
+            // 进一步检查是否以http/https开头(可选,根据业务需求)
+            return url.startsWith("http://") || url.startsWith("https://");
+        } catch (URISyntaxException e) {
+            return false;
+        }
+    }
+
+    private String fetchAiServiceToken() {
+        log.info("登录Ai服务获取token...{}", loginUrl);
+        MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
+        formData.add("username", userName);
+        formData.add("password", passWord);
+
+        HttpHeaders headers = new HttpHeaders();
+        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
+        HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<>(formData, headers);
+
+        try {
+            ResponseEntity<String> response = restTemplate.postForEntity(loginUrl, requestEntity, String.class);
+            if (response != null && response.getStatusCodeValue() == 200 && response.getBody() != null) {
+                JSONObject jsonObject = JSONObject.parseObject(response.getBody());
+                JSONObject dataJson = jsonObject.getJSONObject("data");
+                return dataJson != null ? dataJson.getString("access_token") : null;
+            }
+            log.error("请求差评申诉辅助系统 登录接口失败 http状态:{}", response != null ? response.getStatusCode() : null);
+        } catch (Exception e) {
+            log.error("调用差评申诉辅助系统登录接口异常", e);
+        }
+        return null;
+    }
+
+    private String buildCompletedUrl(Integer recordId) {
+        String baseUrl = resultUrl;
+        if (!StringUtils.hasText(baseUrl)) {
+            throw new IllegalStateException("差评申述分析接口地址未配置");
+        }
+        if (baseUrl.endsWith("/")) {
+            baseUrl = baseUrl.substring(0, baseUrl.length() - 1);
+        }
+        String completedBase = baseUrl.replace("/api/v1/analyze", "");
+        if (!completedBase.endsWith("/")) {
+            completedBase = completedBase + "/";
+        }
+        return completedBase + "api/v1/records/" + recordId + "/completed";
+    }
+
+}

+ 1389 - 0
alien-lawyer/src/main/java/shop/alien/lawyer/service/impl/LawyerClientConsultationOrderServiceImpl.java

@@ -0,0 +1,1389 @@
+package shop.alien.lawyer.service.impl;
+
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONObject;
+import com.alibaba.nacos.common.utils.CollectionUtils;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+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.beans.BeanUtils;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.StringUtils;
+import shop.alien.entity.result.R;
+import shop.alien.entity.store.*;
+import shop.alien.entity.store.vo.LawyerConsultationOrderVO;
+import shop.alien.entity.store.vo.WebSocketVo;
+import shop.alien.lawyer.config.WebSocketProcess;
+import shop.alien.lawyer.feign.AlienStoreFeign;
+import shop.alien.lawyer.service.LawyerClientConsultationOrderService;
+import shop.alien.lawyer.service.LawyerUserService;
+import shop.alien.lawyer.service.OrderExpirationService;
+import shop.alien.mapper.*;
+import shop.alien.util.common.constant.LawyerStatusEnum;
+import shop.alien.util.common.constant.OrderActionType;
+
+import java.text.SimpleDateFormat;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * 咨询订单 服务实现类
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Slf4j
+@Transactional
+@Service
+@RequiredArgsConstructor
+public class LawyerClientConsultationOrderServiceImpl extends ServiceImpl<LawyerConsultationOrderMapper, LawyerConsultationOrder> implements LawyerClientConsultationOrderService {
+
+    private final LawyerConsultationOrderMapper consultationOrderMapper;
+    private final LawyerUserService lawyerUserService;
+    private final LawyerServiceAreaMapper lawyerServiceAreaMapper;
+    private final LawyerUserMapper lawyerUserMapper;
+    private final OrderExpirationService orderExpirationService;
+    private final AlienStoreFeign alienStoreFeign;
+    private final LifeNoticeMapper lifeNoticeMapper;
+    private final LifeUserMapper lifeUserMapper;
+    private final WebSocketProcess webSocketProcess;
+    private final LifeMessageMapper lifeMessageMapper;
+
+    /**
+     * 系统发送者ID常量
+     */
+    private static final String SYSTEM_SENDER_ID = "system";
+
+    /**
+     * 同意退款动作标识
+     */
+    private static final String ACTION_AGREE = "1";
+
+    /**
+     * 拒绝退款动作标识
+     */
+    private static final String ACTION_REJECT = "2";
+
+    /**
+     * 已申请退款状态
+     */
+    private static final String REFUND_STATUS_APPLIED = "1";
+
+    /**
+     * 律师已拒绝退款状态
+     */
+    private static final String REFUND_STATUS_REJECTED = "2";
+
+    /**
+     * 律师已同意退款状态
+     */
+    private static final String REFUND_STATUS_AGREED = "3";
+
+    @Value("${order.coefficient}")
+    private String coefficient;
+    @Value("${order.coefficients}")
+    private String coefficients;
+
+
+
+    /**
+     * 删除咨询订单
+     * <p>
+     * 删除前会进行以下校验:
+     * 1. 参数校验:订单ID不能为空
+     * 2. 订单存在性校验:订单必须存在
+     * 3. 订单状态校验:进行中的订单不允许删除
+     * 4. 如果订单是待支付状态,会取消Redis中的支付超时计时器
+     * </p>
+     *
+     * @param id 订单ID
+     * @return 删除结果
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public R<Boolean> deleteConsultationOrder(Integer id) {
+        log.info("开始删除咨询订单,订单ID={}", id);
+
+        // 参数校验
+        if (id == null) {
+            log.warn("删除咨询订单失败:订单ID为空");
+            return R.fail("订单ID不能为空");
+        }
+
+        // 查询订单信息
+        LawyerConsultationOrder order = consultationOrderMapper.selectById(id);
+        if (order == null) {
+            log.warn("删除咨询订单失败:订单不存在,订单ID={}", id);
+            return R.fail("订单不存在");
+        }
+
+        // 检查订单状态:进行中的订单不允许删除
+        Integer orderStatus = order.getOrderStatus();
+        Integer inProgressStatus = LawyerStatusEnum.INPROGRESS.getStatus(); // 2:进行中
+        if (inProgressStatus.equals(orderStatus)) {
+            log.warn("删除咨询订单失败:订单进行中,不允许删除,订单ID={}, 订单编号={}",
+                    id, order.getOrderNumber());
+            return R.fail("订单进行中,不允许删除");
+        }
+
+        // 如果订单是待支付状态,取消Redis中的订单支付超时计时
+        Integer waitPayStatus = LawyerStatusEnum.WAIT_PAY.getStatus(); // 0:待支付
+        if (waitPayStatus.equals(orderStatus) && order.getOrderNumber() != null) {
+            try {
+                orderExpirationService.cancelOrderPaymentTimeout(order.getOrderNumber());
+                log.info("已取消订单支付超时计时,订单编号={}", order.getOrderNumber());
+            } catch (Exception e) {
+                log.error("取消订单支付超时计时失败,订单编号={}", order.getOrderNumber(), e);
+                // 继续执行删除操作,不因取消计时器失败而中断
+            }
+        }
+
+        // 执行删除操作
+        boolean result = this.removeById(id);
+        if (result) {
+            log.info("删除咨询订单成功,订单ID={}, 订单编号={}", id, order.getOrderNumber());
+            return R.data(true, "删除成功");
+        } else {
+            log.error("删除咨询订单失败:数据库操作失败,订单ID={}, 订单编号={}", id, order.getOrderNumber());
+            return R.fail("删除失败");
+        }
+    }
+
+    /**
+     * 填充律师服务领域信息到订单VO列表
+     *
+     * @param voPage 订单VO分页对象
+     */
+    private void fillLawyerServiceArea(IPage<LawyerConsultationOrderVO> voPage) {
+        List<LawyerConsultationOrderVO> orderList = voPage.getRecords();
+        if (CollectionUtils.isEmpty(orderList)) {
+            return;
+        }
+
+        // 提取所有律师ID
+        List<Integer> lawyerIdList = orderList.stream()
+                .map(LawyerConsultationOrderVO::getLawyerUserId)
+                .filter(Objects::nonNull)
+                .distinct()
+                .collect(Collectors.toList());
+
+        if (CollectionUtils.isEmpty(lawyerIdList)) {
+            return;
+        }
+
+        // 批量查询律师问题场景
+        Map<String, List<String>> serviceAreaMap = getLawyerServiceArea(lawyerIdList);
+        if (serviceAreaMap.isEmpty()) {
+            return;
+        }
+
+        // 填充问题场景
+        orderList.forEach(order -> {
+            Integer lawyerUserId = order.getLawyerUserId();
+            if (lawyerUserId != null) {
+                String lawyerUserIdStr = String.valueOf(lawyerUserId);
+                List<String> serviceAreaList = serviceAreaMap.get(lawyerUserIdStr);
+                if (CollectionUtils.isNotEmpty(serviceAreaList)) {
+                    order.setLawyerLegalProblemScenarioList(serviceAreaList);
+                }
+            }
+        });
+    }
+
+    /**
+     * 批量查询律师问题场景
+     *
+     * @param lawyerIdList 律师ID列表
+     * @return 律师问题场景Map,key为律师ID(String),value为问题场景名称列表
+     */
+    private Map<String, List<String>> getLawyerServiceArea(List<Integer> lawyerIdList) {
+        Map<String, List<String>> serviceAreaMap = new HashMap<>();
+        if (CollectionUtils.isEmpty(lawyerIdList)) {
+            return serviceAreaMap;
+        }
+
+        QueryWrapper<LawyerServiceArea> wrapper = new QueryWrapper<>();
+        wrapper.in("lsa.lawyer_user_id", lawyerIdList)
+                .eq("lsa.delete_flag", 0)
+                .eq("lsa.status", 1);
+
+        List<Map<String, Object>> serviceAreaDataList = lawyerServiceAreaMapper.getLawyerLegalProblemScenarioList(wrapper);
+        if (CollectionUtils.isEmpty(serviceAreaDataList)) {
+            return serviceAreaMap;
+        }
+
+        for (Map<String, Object> row : serviceAreaDataList) {
+            Object lawyerUserIdObj = row.get("lawyer_user_id");
+            if (lawyerUserIdObj == null) {
+                continue;
+            }
+
+            String lawyerUserId = String.valueOf(lawyerUserIdObj);
+            String scenarioName = (String) row.get("name");
+            String scenarioNameValue = StringUtils.hasText(scenarioName) ? scenarioName : "";
+
+            serviceAreaMap.computeIfAbsent(lawyerUserId, k -> new ArrayList<>())
+                    .add(scenarioNameValue);
+        }
+
+        return serviceAreaMap;
+    }
+
+    /**
+     * 获取咨询订单详情
+     * <p>
+     * 查询订单基本信息、律师信息和律师问题场景信息
+     * </p>
+     *
+     * @param lawyerOrderId 订单ID
+     * @return 咨询订单详情VO,如果订单不存在或参数为空则返回空对象
+     */
+    @Override
+    public LawyerConsultationOrderVO getConsultationOrderDetail(String lawyerOrderId) {
+        log.info("获取咨询订单详情,订单ID={}", lawyerOrderId);
+
+        // 参数校验
+        if (!StringUtils.hasText(lawyerOrderId)) {
+            log.warn("获取咨询订单详情失败:订单ID为空");
+            return new LawyerConsultationOrderVO();
+        }
+
+        // 查询订单信息
+        LawyerConsultationOrder order = consultationOrderMapper.selectById(lawyerOrderId);
+        if (order == null) {
+            log.warn("获取咨询订单详情失败:订单不存在,订单ID={}", lawyerOrderId);
+            return new LawyerConsultationOrderVO();
+        }
+
+        // 复制订单基本信息
+        LawyerConsultationOrderVO orderVO = new LawyerConsultationOrderVO();
+        BeanUtils.copyProperties(order, orderVO);
+
+        // 填充律师相关信息
+        fillLawyerRelatedInfo(orderVO, order.getLawyerUserId());
+
+        return orderVO;
+    }
+
+    /**
+     * 填充律师相关信息到订单VO
+     * <p>
+     * 包括律师基本信息和律师问题场景列表
+     * </p>
+     *
+     * @param orderVO    订单VO对象
+     * @param lawyerUserId 律师用户ID
+     */
+    private void fillLawyerRelatedInfo(LawyerConsultationOrderVO orderVO, Integer lawyerUserId) {
+        if (lawyerUserId == null) {
+            return;
+        }
+
+        // 查询并填充律师基本信息
+        LawyerUser lawyerUser = lawyerUserMapper.selectById(lawyerUserId);
+        if (lawyerUser != null) {
+            fillLawyerInfo(orderVO, lawyerUser);
+        }
+
+        // 查询并填充律师问题场景
+        fillLawyerServiceAreaForOrder(orderVO, lawyerUserId);
+    }
+
+    /**
+     * 填充律师问题场景信息到订单VO
+     *
+     * @param orderVO     订单VO对象
+     * @param lawyerUserId 律师用户ID
+     */
+    private void fillLawyerServiceAreaForOrder(LawyerConsultationOrderVO orderVO, Integer lawyerUserId) {
+        List<Integer> lawyerIdList = Collections.singletonList(lawyerUserId);
+        Map<String, List<String>> serviceAreaMap = getLawyerServiceArea(lawyerIdList);
+
+        if (serviceAreaMap.isEmpty()) {
+            return;
+        }
+
+        String lawyerUserIdStr = String.valueOf(lawyerUserId);
+        List<String> serviceAreaList = serviceAreaMap.get(lawyerUserIdStr);
+        if (CollectionUtils.isNotEmpty(serviceAreaList)) {
+            orderVO.setLawyerLegalProblemScenarioList(serviceAreaList);
+        }
+    }
+
+    /**
+     * 查询咨询订单信息(律师端)
+     *
+     * @param pageNum        页码
+     * @param pageSize       页容量
+     * @param startDate      开始时间
+     * @param endDate        结束时间
+     * @param clientUserName 客户端用户名
+     * @param orderStatus    订单状态
+     * @param lawyerId       律师ID
+     * @return 订单信息Map,包含订单列表、总数、进行中数量、已完成数量
+     */
+    @Override
+    public Map<String, Object> getLawyerConsultationOrderInfo(int pageNum, int pageSize, String startDate,
+            String endDate, String clientUserName, String orderStatus, String lawyerId) {
+        log.info("查询咨询订单信息(律师端)- pageNum={}, pageSize={}, startDate={}, endDate={}, "
+                + "clientUserName={}, orderStatus={}, lawyerId={}",
+                pageNum, pageSize, startDate, endDate, clientUserName, orderStatus, lawyerId);
+
+        // 参数校验:律师ID不能为空
+        if (!StringUtils.hasText(lawyerId)) {
+            log.warn("查询咨询订单信息失败:律师ID为空");
+            return buildEmptyResultMap(pageNum, pageSize);
+        }
+
+        // 创建分页对象
+        Page<LawyerConsultationOrderVO> page = new Page<>(pageNum, pageSize);
+
+        // 构建查询条件
+        QueryWrapper<LawyerConsultationOrderVO> queryWrapper = buildOrderQueryWrapper(
+                lawyerId, orderStatus, clientUserName, startDate, endDate);
+        QueryWrapper<LawyerConsultationOrderVO> statisticsWrapper = buildStatisticsQueryWrapper(
+                lawyerId, clientUserName,startDate, endDate);
+
+        // 查询订单列表
+        IPage<LawyerConsultationOrderVO> voPage = consultationOrderMapper.getLawyerConsultationOrderInfo(
+                page, queryWrapper);
+
+        // 填充律师问题场景信息
+        if (voPage != null) {
+            fillLawyerServiceArea(voPage);
+            // 为待支付订单计算倒计时(30分钟有效期)
+            calculateCountdownForPendingOrders(voPage);
+            //填充未读消息
+            fillUnreadMessage(voPage,lawyerId);
+        }
+
+        // 获取统计信息
+        statisticsWrapper.groupBy("lco.order_status");
+        List<Map<String, Object>> statisticsInfo = consultationOrderMapper.getLawyerStatisticsInfo(
+                statisticsWrapper);
+
+        // 构建状态统计Map并计算各状态订单数量
+        Map<Integer, Integer> statusCountMap = buildStatusCountMap(statisticsInfo);
+        Map<String, Integer> orderStatistics = calculateOrderStatistics(statusCountMap);
+
+        // 构建返回结果
+        return buildResultMap(voPage, orderStatistics);
+    }
+
+    /**
+     * 填充未读消息数量
+     *
+     * @param voPage 订单VO分页对象
+     */
+    private void fillUnreadMessage(IPage<LawyerConsultationOrderVO> voPage,String lawyerId) {
+        List<LawyerConsultationOrderVO> orderList = voPage.getRecords();
+        if (CollectionUtils.isEmpty(orderList)) {
+            return;
+        }
+
+        // 提取所有律师ID,并加上lawyer_前缀
+        List<String> lawyerIdList = orderList.stream()
+                .map(LawyerConsultationOrderVO::getClientUserPhone)
+                .filter(Objects::nonNull)
+                .map(phone -> "user_" + phone)
+                .distinct()
+                .collect(Collectors.toList());
+
+        if (CollectionUtils.isEmpty(lawyerIdList)) {
+            return;
+        }
+
+        LawyerUser lawyerUser = lawyerUserMapper.selectById(lawyerId);
+        String phone = lawyerUser.getPhone();
+
+        LambdaQueryWrapper<LifeMessage> lifeMessageLambdaQueryWrapper = new LambdaQueryWrapper<>();
+        lifeMessageLambdaQueryWrapper.in(LifeMessage::getSenderId, lawyerIdList);
+        lifeMessageLambdaQueryWrapper.eq(LifeMessage::getDeleteFlag, 0);
+        lifeMessageLambdaQueryWrapper.eq(LifeMessage::getIsRead, 0);
+        lifeMessageLambdaQueryWrapper.eq(LifeMessage::getReceiverId, "lawyer_" + phone);
+        List<LifeMessage> lifeMessageList = lifeMessageMapper.selectList(lifeMessageLambdaQueryWrapper);
+
+
+        // 按照senderId进行分组,返回senderId和数量的map
+        Map<String, Long> senderIdCountMap = new HashMap<>();
+        if(CollectionUtils.isNotEmpty(lifeMessageList)){
+            senderIdCountMap = lifeMessageList.stream()
+                    .collect(Collectors.groupingBy(LifeMessage::getSenderId, Collectors.counting()));
+        }
+
+        for(LawyerConsultationOrderVO lawyerConsultationOrderVO : voPage.getRecords()){
+            String lawyerPhone = "user_" + lawyerConsultationOrderVO.getClientUserPhone();
+            if(!senderIdCountMap.isEmpty() && senderIdCountMap.containsKey(lawyerPhone) && lawyerConsultationOrderVO.getOrderStatus() == 2){
+                long messageCount = senderIdCountMap.get(lawyerPhone);
+                lawyerConsultationOrderVO.setUnreadMessage(messageCount);
+            } else {
+                lawyerConsultationOrderVO.setUnreadMessage(0L);
+            }
+        }
+    }
+
+    /**
+     * 构建空结果Map
+     *
+     * @param pageNum  页码
+     * @param pageSize 页容量
+     * @return 空结果Map
+     */
+    private Map<String, Object> buildEmptyResultMap(int pageNum, int pageSize) {
+        Map<String, Object> resultMap = new HashMap<>();
+        Page<LawyerConsultationOrderVO> emptyPage = new Page<>(pageNum, pageSize);
+        emptyPage.setRecords(Collections.emptyList());
+        emptyPage.setTotal(0);
+        resultMap.put("lawyerOrderCount", 0L);
+        resultMap.put("lawyerInProgressOrderCount", 0);
+        resultMap.put("lawyerCompleteOrderCount", 0);
+        resultMap.put("lawyerWaitAcceptStatusOrderCount", 0);
+        resultMap.put("lawyerRefundedStatusOrderCount", 0);
+        resultMap.put("lawyerConsultationOrderList", Collections.emptyList());
+        return resultMap;
+    }
+
+    /**
+     * 计算订单统计信息
+     *
+     * @param statusCountMap 状态统计Map
+     * @return 订单统计信息Map
+     */
+    private Map<String, Integer> calculateOrderStatistics(Map<Integer, Integer> statusCountMap) {
+        Map<String, Integer> statistics = new HashMap<>();
+        Integer waitAcceptStatus = LawyerStatusEnum.WAIT_ACCEPT.getStatus();
+        Integer inProgressStatus = LawyerStatusEnum.INPROGRESS.getStatus();
+        Integer completeStatus = LawyerStatusEnum.COMPLETE.getStatus();
+        Integer refundedStatus = LawyerStatusEnum.REFUNDED.getStatus();
+        Integer cancelStatus = LawyerStatusEnum.CANCEL.getStatus();
+
+        int waitAcceptCount = statusCountMap.getOrDefault(waitAcceptStatus, 0);
+        int inProgressCount = statusCountMap.getOrDefault(inProgressStatus, 0);
+        int completeCount = statusCountMap.getOrDefault(completeStatus, 0);
+        int refundedCount = statusCountMap.getOrDefault(refundedStatus, 0);
+        int cancelCount = statusCountMap.getOrDefault(cancelStatus, 0);
+
+        statistics.put("waitAcceptCount", waitAcceptCount);
+        statistics.put("inProgressCount", inProgressCount);
+        statistics.put("completeCount", completeCount);
+        statistics.put("refundedCount", refundedCount);
+        statistics.put("cancelCount", cancelCount);
+        statistics.put("totalCount", waitAcceptCount + inProgressCount + completeCount + refundedCount + cancelCount);
+
+        return statistics;
+    }
+
+    /**
+     * 构建返回结果Map
+     *
+     * @param voPage         订单分页对象
+     * @param orderStatistics 订单统计信息
+     * @return 结果Map
+     */
+    private Map<String, Object> buildResultMap(IPage<LawyerConsultationOrderVO> voPage,
+            Map<String, Integer> orderStatistics) {
+        Map<String, Object> resultMap = new HashMap<>();
+
+        // 设置统计信息
+        resultMap.put("lawyerInProgressOrderCount", orderStatistics.get("inProgressCount"));
+        resultMap.put("lawyerCompleteOrderCount", orderStatistics.get("completeCount"));
+        resultMap.put("lawyerWaitAcceptStatusOrderCount", orderStatistics.get("waitAcceptCount"));
+        resultMap.put("lawyerRefundedStatusOrderCount", orderStatistics.get("refundedCount"));
+        resultMap.put("lawyerOrderCount", (long) orderStatistics.get("totalCount"));
+
+        // 设置订单列表
+        List<LawyerConsultationOrderVO> orderList = (voPage != null && voPage.getRecords() != null)
+                ? voPage.getRecords() : Collections.emptyList();
+        resultMap.put("lawyerConsultationOrderList", orderList);
+
+        return resultMap;
+    }
+
+    /**
+     * 构建订单查询条件
+     *
+     * @param lawyerId       律师ID
+     * @param orderStatus    订单状态
+     * @param clientUserName 客户端用户名
+     * @param startDate      开始时间
+     * @param endDate        结束时间
+     * @return 查询条件包装器
+     */
+    private QueryWrapper<LawyerConsultationOrderVO> buildOrderQueryWrapper(String lawyerId, String orderStatus,
+            String clientUserName, String startDate, String endDate) {
+        QueryWrapper<LawyerConsultationOrderVO> queryWrapper = new QueryWrapper<>();
+
+        // 订单状态条件
+        if (StringUtils.hasText(orderStatus)) {
+            queryWrapper.in("lco.order_status", Collections.singletonList(orderStatus));
+        } else {
+            // 默认查询非待支付和已取消的订单
+            Integer waitPayStatus = LawyerStatusEnum.WAIT_PAY.getStatus();
+            Integer cancelStatus = LawyerStatusEnum.CANCEL.getStatus();
+            queryWrapper.notIn("lco.order_status",
+                    Arrays.asList( String.valueOf(waitPayStatus)));
+        }
+
+        // 律师ID条件
+        queryWrapper.eq("lco.lawyer_user_id", lawyerId);
+
+        // 客户端用户名条件
+        if (StringUtils.hasText(clientUserName)) {
+            queryWrapper.like("lur.user_name", clientUserName);
+        }
+
+        // 删除标记条件
+        queryWrapper.eq("lco.delete_flag", 0);
+
+        // 时间范围条件
+        if (StringUtils.hasText(startDate)) {
+            queryWrapper.ge("lco.payment_time", startDate);
+        }
+
+        if (StringUtils.hasText(endDate)) {
+            queryWrapper.le("lco.payment_time", endDate);
+        }
+
+        // 排序条件
+        queryWrapper.orderByDesc("lco.created_time");
+
+        return queryWrapper;
+    }
+
+    /**
+     * 构建统计查询条件
+     *
+     * @param lawyerId       律师ID
+     * @param clientUserName 客户端用户名
+     * @return 统计查询条件包装器
+     */
+    private QueryWrapper<LawyerConsultationOrderVO> buildStatisticsQueryWrapper(String lawyerId,
+            String clientUserName, String startDate, String endDate) {
+        QueryWrapper<LawyerConsultationOrderVO> queryWrapper = new QueryWrapper<>();
+        queryWrapper.eq("lco.lawyer_user_id", lawyerId);
+        queryWrapper.eq("lco.delete_flag", 0);
+
+        // 时间范围条件
+        if (StringUtils.hasText(startDate)) {
+            queryWrapper.ge("lco.payment_time", startDate);
+        }
+
+        if (StringUtils.hasText(endDate)) {
+            queryWrapper.le("lco.payment_time", endDate);
+        }
+
+        if (StringUtils.hasText(clientUserName)) {
+            queryWrapper.like("lur.user_name", clientUserName);
+        }
+
+        return queryWrapper;
+    }
+
+    /**
+     * 构建状态统计Map
+     *
+     * @param statisticsInfo 统计信息列表
+     * @return 状态统计Map,key为订单状态,value为订单数量
+     */
+    private Map<Integer, Integer> buildStatusCountMap(List<Map<String, Object>> statisticsInfo) {
+        Map<Integer, Integer> statusCountMap = new HashMap<>();
+        if (CollectionUtils.isEmpty(statisticsInfo)) {
+            return statusCountMap;
+        }
+
+        for (Map<String, Object> map : statisticsInfo) {
+            Object statusObj = map.get("order_status");
+            Object countObj = map.get("order_count");
+
+            if (statusObj == null || countObj == null) {
+                continue;
+            }
+
+            Integer status = (Integer) statusObj;
+            // COUNT(*) 返回类型可能是Long
+            Long countLong = (Long) countObj;
+            statusCountMap.put(status, countLong.intValue());
+        }
+
+        return statusCountMap;
+    }
+
+    /**
+     * 为待支付订单计算倒计时(30分钟有效期)
+     *
+     * @param voPage 订单分页对象
+     */
+    private void calculateCountdownForPendingOrders(IPage<LawyerConsultationOrderVO> voPage) {
+        if (voPage == null || voPage.getRecords() == null) {
+            return;
+        }
+
+        Date now = new Date();
+        long validitySeconds = 30 * 60; // 30分钟 = 1800秒
+
+        for (LawyerConsultationOrderVO vo : voPage.getRecords()) {
+            // 仅对待支付订单(orderStatus=0)计算倒计时
+            if (vo.getOrderStatus() != null && vo.getOrderStatus() == 0) {
+                Date orderTime = vo.getOrderTime();
+                if (orderTime != null) {
+                    long elapsedSeconds = (now.getTime() - orderTime.getTime()) / 1000;
+                    long remainingSeconds = validitySeconds - elapsedSeconds;
+                    // 如果已过期,设置为0
+                    vo.setCountdownSeconds(Math.max(0, remainingSeconds));
+                } else {
+                    vo.setCountdownSeconds(0L);
+                }
+            } else {
+                // 非待支付订单,倒计时为null
+                vo.setCountdownSeconds(null);
+            }
+        }
+    }
+
+    /**
+     * 填充律师信息到订单VO
+     *
+     * @param orderVO    订单VO对象
+     * @param lawyerUser 律师用户对象
+     */
+    private void fillLawyerInfo(LawyerConsultationOrderVO orderVO, LawyerUser lawyerUser) {
+        orderVO.setLawyerName(lawyerUser.getName());
+        orderVO.setLawyerPhone(lawyerUser.getPhone());
+        orderVO.setLawyerEmail(lawyerUser.getEmail());
+        orderVO.setLawyerCertificateNo(lawyerUser.getLawyerCertificateNo());
+        orderVO.setLawFirm(lawyerUser.getLawFirm());
+        orderVO.setPracticeYears(lawyerUser.getPracticeYears());
+        orderVO.setSpecialtyFields(lawyerUser.getSpecialtyFields());
+        orderVO.setCertificationStatus(lawyerUser.getCertificationStatus());
+        orderVO.setServiceCount(lawyerUser.getServiceCount());
+        orderVO.setServiceScore(lawyerUser.getServiceScore());
+        orderVO.setLawyerConsultationFee(lawyerUser.getConsultationFee());
+        orderVO.setProvince(lawyerUser.getProvince());
+        orderVO.setCity(lawyerUser.getCity());
+        orderVO.setDistrict(lawyerUser.getDistrict());
+        orderVO.setAddress(lawyerUser.getAddress());
+        orderVO.setHeadImg(lawyerUser.getHeadImg());
+        orderVO.setNickName(lawyerUser.getNickName());
+        orderVO.setPersonalIntroduction(lawyerUser.getPersonalIntroduction());
+    }
+
+    /**
+     * 律师确认订单
+     * <p>
+     * 支持两种操作类型:
+     * 1. 接单:将订单状态从待接单更新为进行中
+     * 2. 取消:将订单状态更新为已取消
+     * </p>
+     *
+     * @param id        订单ID
+     * @param actionType 操作类型:1-接单,2-取消
+     * @return 确认结果
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public R<Boolean> confirmOrder(String id, String actionType) {
+        log.info("开始律师确认订单,订单ID={}, 操作类型={}", id, actionType);
+
+        // 参数校验
+        if (id == null || id.trim().isEmpty()) {
+            log.warn("律师确认订单失败:订单ID为空");
+            return R.fail("订单ID不能为空");
+        }
+
+        if (actionType == null || actionType.trim().isEmpty()) {
+            log.warn("律师确认订单失败:操作类型为空");
+            return R.fail("操作类型不能为空");
+        }
+
+        // 操作类型格式校验
+        String trimmedActionType = actionType.trim();
+        OrderActionType actionTypeEnum = OrderActionType.getByCode(trimmedActionType);
+        if (actionTypeEnum == null) {
+            log.warn("律师确认订单失败:操作类型无效,actionType={}", actionType);
+            return R.fail("操作类型无效,1-接单,2-取消");
+        }
+
+        // 将订单ID转换为Integer
+        Integer orderId;
+        try {
+            orderId = Integer.parseInt(id.trim());
+        } catch (NumberFormatException e) {
+            log.warn("律师确认订单失败:订单ID格式错误,id={}", id, e);
+            return R.fail("订单ID格式错误");
+        }
+
+        // 查询订单信息
+        LawyerConsultationOrder order = consultationOrderMapper.selectById(orderId);
+        if (order == null) {
+            log.warn("律师确认订单失败:订单不存在,订单ID={}", orderId);
+            return R.fail("订单不存在");
+        }
+
+        // 清除redis缓存,避免调用订单超时处理
+        orderExpirationService.cancelOrderAcceptTimeout(order.getOrderNumber());
+
+        // 验证订单状态是否为待接单
+        Integer orderStatus = order.getOrderStatus();
+        Integer waitAcceptStatus = LawyerStatusEnum.WAIT_ACCEPT.getStatus();
+        if (!waitAcceptStatus.equals(orderStatus)) {
+            log.warn("律师确认订单失败:订单状态不是待接单,订单ID={}, 订单编号={}, 当前状态={}",
+                    orderId, order.getOrderNumber(), orderStatus);
+            return R.fail("订单状态不是待接单,无法确认");
+        }
+
+        // 根据操作类型处理
+        if (actionTypeEnum == OrderActionType.ACCEPT) {
+            // 接单:更新订单状态为进行中
+            Integer inProgressStatus = LawyerStatusEnum.INPROGRESS.getStatus();
+            Date acceptTime = new Date();
+            order.setOrderStatus(inProgressStatus);
+            order.setAcceptOrdersTime(acceptTime);
+            order.setUpdatedTime(acceptTime);
+            LocalDateTime now = LocalDateTime.now();
+            LocalDateTime validityDateTime = now.plusDays(3)
+                    .withHour(0)
+                    .withMinute(0)
+                    .withSecond(0)
+                    .withNano(0);
+            order.setValidityPeriod(Date.from(validityDateTime.atZone(ZoneId.systemDefault()).toInstant()));
+
+            // 执行更新操作
+            boolean result = this.updateById(order);
+            if (result) {
+                log.info("律师接单成功,订单ID={}, 订单编号={}, 状态从{}变更为{}",
+                        orderId, order.getOrderNumber(), waitAcceptStatus, inProgressStatus);
+                
+                // 发送接单通知
+                sendAcceptOrderNotice(order, acceptTime);
+                
+                return R.data(true, "接单成功");
+            } else {
+                log.error("律师接单失败:数据库操作失败,订单ID={}, 订单编号={}", orderId, order.getOrderNumber());
+                return R.fail("接单失败");
+            }
+        } else {
+            // 取消:更新订单状态为已取消
+            Integer cancelStatus = LawyerStatusEnum.REFUNDED.getStatus();
+            order.setOrderStatus(cancelStatus);
+            order.setUpdatedTime(new Date());
+
+            // 执行更新操作
+            boolean result = this.updateById(order);
+            if (result) {
+                log.info("律师取消订单成功,订单ID={}, 订单编号={}, 状态从{}变更为{}",
+                        orderId, order.getOrderNumber(), waitAcceptStatus, cancelStatus);
+                
+                // 发送拒绝接单通知
+                sendRejectOrderNotice(order);
+                
+                return R.data(true, "取消订单成功");
+            } else {
+                log.error("律师取消订单失败:数据库操作失败,订单ID={}, 订单编号={}", orderId, order.getOrderNumber());
+                return R.fail("取消订单失败");
+            }
+        }
+    }
+
+    /**
+     * 申请退款
+     * <p>
+     * 申请退款前会进行以下校验:
+     * 1. 参数校验:订单ID不能为空,退款原因不能为空
+     * 2. 订单存在性校验:订单必须存在
+     * 3. 订单支付状态校验:只有已支付的订单才能申请退款
+     * 4. 订单状态校验:待支付、已取消的订单不允许申请退款
+     * </p>
+     *
+     * @param id 订单ID
+     * @param refundReason 退款原因
+     * @return 申请退款结果
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public R<Boolean> requestRefund(Integer id, String refundReason) {
+        log.info("开始申请退款,订单ID={}, 退款原因={}", id, refundReason);
+
+        // 参数校验
+        if (id == null) {
+            log.warn("申请退款失败:订单ID为空");
+            return R.fail("订单ID不能为空");
+        }
+
+        if (!StringUtils.hasText(refundReason)) {
+            log.warn("申请退款失败:退款原因为空,订单ID={}", id);
+            return R.fail("退款原因不能为空");
+        }
+
+        // 查询订单信息
+        LawyerConsultationOrder order = consultationOrderMapper.selectById(id);
+        if (order == null) {
+            log.warn("申请退款失败:订单不存在,订单ID={}", id);
+            return R.fail("订单不存在");
+        }
+
+        // 检查订单支付状态:只有已支付的订单才能申请退款
+        Integer paymentStatus = order.getPaymentStatus();
+        Integer paidStatus = 1; // 1:已支付
+        if (paymentStatus == 0 || !paidStatus.equals(paymentStatus)) {
+            log.warn("申请退款失败:订单未支付,无法申请退款,订单ID={}, 订单编号={}, 支付状态={}",
+                    id, order.getOrderNumber(), paymentStatus);
+            return R.fail("订单未支付,无法申请退款");
+        }
+
+        // 检查订单状态:待支付、已取消的订单不允许申请退款
+        Integer orderStatus = order.getOrderStatus();
+        Integer waitPayStatus = LawyerStatusEnum.WAIT_PAY.getStatus(); // 0:待支付
+        Integer cancelStatus = LawyerStatusEnum.CANCEL.getStatus(); // 4:已取消
+
+        if (waitPayStatus.equals(orderStatus)) {
+            log.warn("申请退款失败:订单待支付,无法申请退款,订单ID={}, 订单编号={}", id, order.getOrderNumber());
+            return R.fail("订单待支付,无法申请退款");
+        }
+
+        if (cancelStatus.equals(orderStatus)) {
+            log.warn("申请退款失败:订单已取消,无法申请退款,订单ID={}, 订单编号={}", id, order.getOrderNumber());
+            return R.fail("订单已取消,无法申请退款");
+        }
+
+        // 检查订单是否已经申请过退款(这里可以根据业务需求添加退款状态字段来判断)
+        // 目前暂时允许重复申请,实际业务中可能需要添加退款状态字段来防止重复申请
+
+        // 更新订单状态为退款中或已退款(根据业务需求,这里暂时不更新订单状态,只记录退款申请)
+        // 如果需要更新订单状态,可以添加退款状态字段,例如:refundStatus (0:未退款, 1:退款中, 2:已退款, 3:退款失败)
+        
+        // 记录退款申请信息(这里可以根据业务需求创建退款记录表,或者更新订单表的退款相关字段)
+        log.info("申请退款成功,订单ID={}, 订单编号={}, 订单金额={}分, 退款原因={}",
+                id, order.getOrderNumber(), order.getOrderAmount(), refundReason);
+
+        // 这里可以调用实际的退款接口处理退款
+        // 例如:调用支付宝退款接口、更新退款状态等
+        // 目前先返回成功,实际业务中需要根据退款接口的返回结果来判断
+
+        return R.data(true, "退款申请已提交,请等待处理");
+    }
+
+    /**
+     * 退款申请处理(律师端)
+     * <p>
+     * 处理退款申请前会进行以下校验:
+     * 1. 参数校验:律师ID、订单ID、处理动作不能为空
+     * 2. 订单存在性校验:订单必须存在
+     * 3. 订单归属校验:订单必须属于该律师
+     * 4. 退款状态校验:订单必须处于已申请退款状态
+     * 5. 处理动作校验:处理动作必须为同意(1)或拒绝(2)
+     * 6. 拒绝原因校验:拒绝时必须提供拒绝原因
+     * </p>
+     * <p>
+     * 处理逻辑:
+     * - 同意退款:将订单状态设置为已退款(5),申请退款状态设置为已同意(3),并发送同意通知
+     * - 拒绝退款:将申请退款状态设置为已拒绝(2),更新拒绝原因,并发送拒绝通知
+     * </p>
+     *
+     * @param lawyerConsultationOrder 订单对象
+     * @return 处理结果
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public R<Boolean> refundApplyProcess(LawyerConsultationOrder lawyerConsultationOrder) {
+        Integer lawyerId = lawyerConsultationOrder.getLawyerUserId();
+        Integer orderId = lawyerConsultationOrder.getId();
+        String processAction = lawyerConsultationOrder.getProcessAction();
+        String rejectRefundReason = lawyerConsultationOrder.getRejectRefundReason();
+        
+        log.info("开始处理退款申请,律师ID={}, 订单ID={}, 处理动作={}", lawyerId, orderId, processAction);
+
+        // 1. 参数校验
+        R<Boolean> paramValidateResult = validateRefundProcessParams(lawyerId, orderId, processAction, rejectRefundReason);
+        if (paramValidateResult != null) {
+            return paramValidateResult;
+        }
+
+        // 2. 查询订单信息
+        LawyerConsultationOrder order = consultationOrderMapper.selectById(orderId);
+        if (Objects.isNull(order)) {
+            log.warn("处理退款申请失败:订单不存在,订单ID={}", orderId);
+            return R.fail("订单不存在");
+        }
+
+        // 3. 业务校验
+        R<Boolean> businessValidateResult = validateRefundProcessBusiness(lawyerId, order);
+        if (businessValidateResult != null) {
+            return businessValidateResult;
+        }
+
+        // 4. 取消退款超时计时器
+        try {
+            orderExpirationService.cancelOrderRefundTimeout(order.getOrderNumber());
+        } catch (Exception e) {
+            log.error("取消退款超时计时器失败,订单编号={}", order.getOrderNumber(), e);
+            // 继续执行,不因取消计时器失败而中断
+        }
+
+        // 5. 根据处理动作进行相应处理
+        if (ACTION_AGREE.equals(processAction)) {
+            return processAgreeRefund(order);
+        } else {
+            return processRejectRefund(order, rejectRefundReason);
+        }
+    }
+
+    /**
+     * 校验退款处理参数
+     *
+     * @param lawyerId          律师ID
+     * @param orderId           订单ID
+     * @param processAction     处理动作
+     * @param rejectRefundReason 拒绝原因
+     * @return 校验失败时返回错误结果,校验通过返回null
+     */
+    private R<Boolean> validateRefundProcessParams(Integer lawyerId, Integer orderId, 
+                                                     String processAction, String rejectRefundReason) {
+        // 律师ID校验
+        if (Objects.isNull(lawyerId) || lawyerId <= 0) {
+            log.warn("处理退款申请失败:律师ID为空或无效,lawyerId={}", lawyerId);
+            return R.fail("律师ID不能为空");
+        }
+
+        // 订单ID校验
+        if (Objects.isNull(orderId) || orderId <= 0) {
+            log.warn("处理退款申请失败:订单ID为空或无效,orderId={}", orderId);
+            return R.fail("订单ID不能为空");
+        }
+
+        // 处理动作校验
+        if (!StringUtils.hasText(processAction)) {
+            log.warn("处理退款申请失败:处理动作为空,订单ID={}", orderId);
+            return R.fail("处理动作不能为空");
+        }
+
+        // 处理动作值校验
+        if (!ACTION_AGREE.equals(processAction) && !ACTION_REJECT.equals(processAction)) {
+            log.warn("处理退款申请失败:处理动作无效,订单ID={}, 处理动作={}", orderId, processAction);
+            return R.fail("处理动作无效,1-同意,2-拒绝");
+        }
+
+        // 拒绝原因校验:拒绝时必须提供拒绝原因
+        if (ACTION_REJECT.equals(processAction) && !StringUtils.hasText(rejectRefundReason)) {
+            log.warn("处理退款申请失败:拒绝退款时必须提供拒绝原因,订单ID={}", orderId);
+            return R.fail("拒绝退款时必须提供拒绝原因");
+        }
+
+        return null;
+    }
+
+    /**
+     * 校验退款处理业务规则
+     *
+     * @param lawyerId 律师ID
+     * @param order    订单对象
+     * @return 校验失败时返回错误结果,校验通过返回null
+     */
+    private R<Boolean> validateRefundProcessBusiness(Integer lawyerId, LawyerConsultationOrder order) {
+        Integer orderId = order.getId();
+        
+        // 订单归属校验:订单必须属于该律师
+        Integer orderLawyerId = order.getLawyerUserId();
+        if (Objects.isNull(orderLawyerId) || !lawyerId.equals(orderLawyerId)) {
+            log.warn("处理退款申请失败:订单不属于该律师,订单ID={}, 订单律师ID={}, 请求律师ID={}", 
+                    orderId, orderLawyerId, lawyerId);
+            return R.fail("订单不属于该律师,无法处理退款申请");
+        }
+
+        // 退款状态校验:订单必须处于已申请退款状态
+        String applyRefundStatus = order.getApplyRefundStatus();
+        if (!REFUND_STATUS_APPLIED.equals(applyRefundStatus)) {
+            log.warn("处理退款申请失败:订单未申请退款或已处理,订单ID={}, 订单编号={}, 退款状态={}", 
+                    orderId, order.getOrderNumber(), applyRefundStatus);
+            return R.fail("订单未申请退款或已处理,无法重复处理");
+        }
+
+        return null;
+    }
+
+    /**
+     * 处理同意退款
+     * <p>
+     * 1. 更新订单状态为已退款(5)
+     * 2. 更新申请退款状态为已同意(3)
+     * 3. 发送同意退款通知给申请人
+     * </p>
+     *
+     * @param order 订单对象
+     * @return 处理结果
+     */
+    private R<Boolean> processAgreeRefund(LawyerConsultationOrder order) {
+        log.info("开始处理同意退款,订单ID={}, 订单编号={}", order.getId(), order.getOrderNumber());
+
+        try {
+            // 1. 更新订单状态和退款状态
+            Date processTime = new Date();
+            LambdaUpdateWrapper<LawyerConsultationOrder> updateWrapper = new LambdaUpdateWrapper<>();
+            updateWrapper.eq(LawyerConsultationOrder::getId, order.getId())
+                    .set(LawyerConsultationOrder::getOrderStatus, LawyerStatusEnum.REFUNDED.getStatus())
+                    .set(LawyerConsultationOrder::getApplyRefundStatus, REFUND_STATUS_AGREED)
+                    .set(LawyerConsultationOrder::getApplyRefundProcessTime, processTime)
+                    .set(LawyerConsultationOrder::getApplyRefundTime, processTime)
+                    .set(LawyerConsultationOrder::getUpdatedTime, processTime);
+
+            int updateCount = consultationOrderMapper.update(null, updateWrapper);
+            if (updateCount <= 0) {
+                log.error("处理同意退款失败:更新订单状态失败,订单ID={}, 订单编号={}", 
+                        order.getId(), order.getOrderNumber());
+                return R.fail("处理同意退款失败,请稍后重试");
+            }
+
+            // 2. 发送同意退款通知给申请人
+            sendRefundAgreeNotice(order);
+
+            log.info("处理同意退款成功,订单ID={}, 订单编号={}", order.getId(), order.getOrderNumber());
+            return R.data(true, "已同意退款申请");
+        } catch (Exception e) {
+            log.error("处理同意退款异常,订单ID={}, 订单编号={}, 异常信息={}", 
+                    order.getId(), order.getOrderNumber(), e.getMessage(), e);
+            return R.fail("处理同意退款失败,请稍后重试");
+        }
+    }
+
+    /**
+     * 处理拒绝退款
+     * <p>
+     * 1. 更新申请退款状态为已拒绝(2)
+     * 2. 更新拒绝退款原因
+     * 3. 发送拒绝退款通知给申请人
+     * </p>
+     *
+     * @param order              订单对象
+     * @param rejectRefundReason 拒绝原因
+     * @return 处理结果
+     */
+    private R<Boolean> processRejectRefund(LawyerConsultationOrder order, String rejectRefundReason) {
+        log.info("开始处理拒绝退款,订单ID={}, 订单编号={}, 拒绝原因={}", 
+                order.getId(), order.getOrderNumber(), rejectRefundReason);
+
+        try {
+            // 1. 更新订单退款状态和拒绝原因
+            Date processTime = new Date();
+            LambdaUpdateWrapper<LawyerConsultationOrder> updateWrapper = new LambdaUpdateWrapper<>();
+            updateWrapper.eq(LawyerConsultationOrder::getId, order.getId())
+                    .set(LawyerConsultationOrder::getApplyRefundStatus, REFUND_STATUS_REJECTED)
+                    .set(LawyerConsultationOrder::getRejectRefundReason, rejectRefundReason)
+                    .set(LawyerConsultationOrder::getApplyRefundProcessTime, processTime)
+                    .set(LawyerConsultationOrder::getUpdatedTime, processTime);
+
+            int updateCount = consultationOrderMapper.update(null, updateWrapper);
+            if (updateCount <= 0) {
+                log.error("处理拒绝退款失败:更新订单状态失败,订单ID={}, 订单编号={}", 
+                        order.getId(), order.getOrderNumber());
+                return R.fail("处理拒绝退款失败,请稍后重试");
+            }
+
+            // 2. 发送拒绝退款通知给申请人
+            sendRefundRejectNotice(order, rejectRefundReason);
+
+            log.info("处理拒绝退款成功,订单ID={}, 订单编号={}", order.getId(), order.getOrderNumber());
+            return R.data(true, "已拒绝退款申请");
+        } catch (Exception e) {
+            log.error("处理拒绝退款异常,订单ID={}, 订单编号={}, 异常信息={}", 
+                    order.getId(), order.getOrderNumber(), e.getMessage(), e);
+            return R.fail("处理拒绝退款失败,请稍后重试");
+        }
+    }
+
+    /**
+     * 发送同意退款通知给申请人
+     *
+     * @param order 订单对象
+     */
+    private void sendRefundAgreeNotice(LawyerConsultationOrder order) {
+        try {
+            Integer clientUserId = order.getClientUserId();
+            if (clientUserId == null) {
+                log.warn("发送同意退款通知失败:客户端用户ID为空,订单ID={}", order.getId());
+                return;
+            }
+
+            // 获取申请人接收ID
+            String receiverId = getClientReceiverId(clientUserId);
+            if (!StringUtils.hasText(receiverId)) {
+                log.warn("发送同意退款通知失败:获取申请人接收ID失败,订单ID={}, 用户ID={}", 
+                        order.getId(), clientUserId);
+                return;
+            }
+
+            // 构建通知消息
+            String orderNumber = order.getOrderNumber() != null ? order.getOrderNumber() : "";
+            String message = String.format("您的编号为%s的订单,律师已同意您的退款申请,订单金额将在1-3个工作日原路返还,请注意查收。", 
+                    orderNumber);
+
+            // 创建并保存通知
+            LifeNotice lifeNotice = createRefundNotice(order.getId(), receiverId, "退款申请处理通知", message);
+            int noticeResult = lifeNoticeMapper.insert(lifeNotice);
+            if (noticeResult <= 0) {
+                log.warn("发送同意退款通知失败:保存通知失败,订单ID={}", order.getId());
+                return;
+            }
+
+            // 发送WebSocket消息
+            sendWebSocketMessage(receiverId, lifeNotice);
+
+            log.info("发送同意退款通知成功,接收人ID={}, 订单编号={}", receiverId, orderNumber);
+        } catch (Exception e) {
+            log.error("发送同意退款通知异常,订单ID={}, 异常信息={}", order.getId(), e.getMessage(), e);
+        }
+    }
+
+    /**
+     * 发送拒绝退款通知给申请人
+     *
+     * @param order              订单对象
+     * @param rejectRefundReason 拒绝原因
+     */
+    private void sendRefundRejectNotice(LawyerConsultationOrder order, String rejectRefundReason) {
+        try {
+            Integer clientUserId = order.getClientUserId();
+            if (clientUserId == null) {
+                log.warn("发送拒绝退款通知失败:客户端用户ID为空,订单ID={}", order.getId());
+                return;
+            }
+
+            // 获取申请人接收ID
+            String receiverId = getClientReceiverId(clientUserId);
+            if (!StringUtils.hasText(receiverId)) {
+                log.warn("发送拒绝退款通知失败:获取申请人接收ID失败,订单ID={}, 用户ID={}", 
+                        order.getId(), clientUserId);
+                return;
+            }
+
+            // 构建通知消息
+            String orderNumber = order.getOrderNumber() != null ? order.getOrderNumber() : "";
+            String rejectReason = StringUtils.hasText(rejectRefundReason) ? rejectRefundReason : "无";
+            String message = String.format("您的编号为%s的订单,律师已拒绝您的退款申请。拒绝原因:%s。如您仍有疑问,可通过举报的方式提交平台审核", 
+                    orderNumber, rejectReason);
+
+            // 创建并保存通知
+            LifeNotice lifeNotice = createRefundNotice(order.getId(), receiverId, "拒绝退款通知", message);
+            int noticeResult = lifeNoticeMapper.insert(lifeNotice);
+            if (noticeResult <= 0) {
+                log.warn("发送拒绝退款通知失败:保存通知失败,订单ID={}", order.getId());
+                return;
+            }
+
+            // 发送WebSocket消息
+            sendWebSocketMessage(receiverId, lifeNotice);
+
+            log.info("发送拒绝退款通知成功,接收人ID={}, 订单编号={}", receiverId, orderNumber);
+        } catch (Exception e) {
+            log.error("发送拒绝退款通知异常,订单ID={}, 异常信息={}", order.getId(), e.getMessage(), e);
+        }
+    }
+
+    /**
+     * 获取客户端用户接收ID
+     *
+     * @param clientUserId 客户端用户ID
+     * @return 接收人ID,格式:user_ + 手机号
+     */
+    private String getClientReceiverId(Integer clientUserId) {
+        if (clientUserId == null || clientUserId <= 0) {
+            log.warn("获取客户端用户接收ID失败:用户ID为空或无效");
+            return null;
+        }
+
+        try {
+            LifeUser lifeUser = lifeUserMapper.selectById(clientUserId);
+            if (lifeUser != null && StringUtils.hasText(lifeUser.getUserPhone())) {
+                return "user_" + lifeUser.getUserPhone();
+            }
+        } catch (Exception e) {
+            log.error("获取客户端用户手机号异常,用户ID={}, 异常信息={}", clientUserId, e.getMessage(), e);
+        }
+
+        log.warn("获取客户端用户手机号失败,用户ID={}", clientUserId);
+        return null;
+    }
+
+    /**
+     * 创建退款通知对象
+     *
+     * @param businessId 业务ID(订单ID)
+     * @param receiverId 接收人ID
+     * @param title      通知标题
+     * @param message    通知消息
+     * @return 通知对象
+     */
+    private LifeNotice createRefundNotice(Integer businessId, String receiverId, String title, String message) {
+        LifeNotice lifeNotice = new LifeNotice();
+        lifeNotice.setSenderId(SYSTEM_SENDER_ID);
+        lifeNotice.setBusinessId(businessId);
+        lifeNotice.setReceiverId(receiverId);
+        lifeNotice.setTitle(title);
+        lifeNotice.setNoticeType(1);
+        lifeNotice.setIsRead(0);
+
+        JSONObject jsonObject = new JSONObject();
+        jsonObject.put("message", message);
+        lifeNotice.setContext(jsonObject.toJSONString());
+
+        return lifeNotice;
+    }
+
+    /**
+     * 发送WebSocket消息
+     *
+     * @param receiverId 接收人ID
+     * @param lifeNotice 通知对象
+     */
+    private void sendWebSocketMessage(String receiverId, LifeNotice lifeNotice) {
+        try {
+            WebSocketVo webSocketVo = new WebSocketVo();
+            webSocketVo.setSenderId(SYSTEM_SENDER_ID);
+            webSocketVo.setReceiverId(receiverId);
+            webSocketVo.setCategory("notice");
+            webSocketVo.setNoticeType("1");
+            webSocketVo.setIsRead(0);
+            webSocketVo.setText(JSON.toJSONString(lifeNotice));
+            webSocketProcess.sendMessage(receiverId, JSON.toJSONString(webSocketVo));
+        } catch (Exception e) {
+            log.error("发送WebSocket消息异常,接收人ID={}, 异常信息={}", receiverId, e.getMessage(), e);
+        }
+    }
+
+    /**
+     * 发送接单通知给客户端用户
+     *
+     * @param order      订单对象
+     * @param acceptTime 接单时间
+     */
+    private void sendAcceptOrderNotice(LawyerConsultationOrder order, Date acceptTime) {
+        try {
+            Integer clientUserId = order.getClientUserId();
+            if (clientUserId == null) {
+                log.warn("发送接单通知失败:客户端用户ID为空,订单ID={}", order.getId());
+                return;
+            }
+
+            // 获取客户端用户接收ID
+            String receiverId = getClientReceiverId(clientUserId);
+            if (!StringUtils.hasText(receiverId)) {
+                log.warn("发送接单通知失败:获取客户端用户接收ID失败,订单ID={}, 用户ID={}", 
+                        order.getId(), clientUserId);
+                return;
+            }
+
+            // 格式化日期
+            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+            String acceptTimeStr = dateFormat.format(acceptTime);
+            String validityPeriodStr = "";
+            if (order.getValidityPeriod() != null) {
+                validityPeriodStr = dateFormat.format(order.getValidityPeriod());
+            } else {
+                // 如果有效期为空,使用默认提示
+                validityPeriodStr = "待定";
+            }
+
+            // 构建通知消息
+            String orderNumber = order.getOrderNumber() != null ? order.getOrderNumber() : "";
+            String message = String.format("您的编号为%s的订单已于%s被律师接单,有效期至%s。请在有效期内与律师沟通。", 
+                    orderNumber, acceptTimeStr, validityPeriodStr);
+
+            // 创建并保存通知
+            LifeNotice lifeNotice = createOrderNotice(order.getId(), receiverId, "接单通知", message);
+            int noticeResult = lifeNoticeMapper.insert(lifeNotice);
+            if (noticeResult <= 0) {
+                log.warn("发送接单通知失败:保存通知失败,订单ID={}", order.getId());
+                return;
+            }
+
+            // 发送WebSocket消息
+            sendWebSocketMessage(receiverId, lifeNotice);
+
+            log.info("发送接单通知成功,接收人ID={}, 订单编号={}", receiverId, orderNumber);
+        } catch (Exception e) {
+            log.error("发送接单通知异常,订单ID={}, 异常信息={}", order.getId(), e.getMessage(), e);
+        }
+    }
+
+    /**
+     * 发送拒绝接单通知给客户端用户
+     *
+     * @param order 订单对象
+     */
+    private void sendRejectOrderNotice(LawyerConsultationOrder order) {
+        try {
+            Integer clientUserId = order.getClientUserId();
+            if (clientUserId == null) {
+                log.warn("发送拒绝接单通知失败:客户端用户ID为空,订单ID={}", order.getId());
+                return;
+            }
+
+            // 获取客户端用户接收ID
+            String receiverId = getClientReceiverId(clientUserId);
+            if (!StringUtils.hasText(receiverId)) {
+                log.warn("发送拒绝接单通知失败:获取客户端用户接收ID失败,订单ID={}, 用户ID={}", 
+                        order.getId(), clientUserId);
+                return;
+            }
+
+            // 获取拒绝原因
+            String rejectReason = order.getReasonOrderRefusal();
+            if (!StringUtils.hasText(rejectReason)) {
+                rejectReason = "无";
+            }
+
+            // 构建通知消息
+            String orderNumber = order.getOrderNumber() != null ? order.getOrderNumber() : "";
+            String message = String.format("您的编号为%s的订单已被拒绝,拒绝原因:%s。订单金额将在1-3个工作日原路返还,请注意查收。", 
+                    orderNumber, rejectReason);
+
+            // 创建并保存通知
+            LifeNotice lifeNotice = createOrderNotice(order.getId(), receiverId, "拒绝接单通知", message);
+            int noticeResult = lifeNoticeMapper.insert(lifeNotice);
+            if (noticeResult <= 0) {
+                log.warn("发送拒绝接单通知失败:保存通知失败,订单ID={}", order.getId());
+                return;
+            }
+
+            // 发送WebSocket消息
+            sendWebSocketMessage(receiverId, lifeNotice);
+
+            log.info("发送拒绝接单通知成功,接收人ID={}, 订单编号={}", receiverId, orderNumber);
+        } catch (Exception e) {
+            log.error("发送拒绝接单通知异常,订单ID={}, 异常信息={}", order.getId(), e.getMessage(), e);
+        }
+    }
+
+    /**
+     * 创建订单通知对象
+     *
+     * @param businessId 业务ID(订单ID)
+     * @param receiverId 接收人ID
+     * @param title      通知标题
+     * @param message    通知消息
+     * @return 通知对象
+     */
+    private LifeNotice createOrderNotice(Integer businessId, String receiverId, String title, String message) {
+        LifeNotice lifeNotice = new LifeNotice();
+        lifeNotice.setSenderId(SYSTEM_SENDER_ID);
+        lifeNotice.setBusinessId(businessId);
+        lifeNotice.setReceiverId(receiverId);
+        lifeNotice.setTitle(title);
+        lifeNotice.setNoticeType(1);
+        lifeNotice.setIsRead(0);
+
+        JSONObject jsonObject = new JSONObject();
+        jsonObject.put("message", message);
+        lifeNotice.setContext(jsonObject.toJSONString());
+
+        return lifeNotice;
+    }
+}
+

+ 1787 - 0
alien-lawyer/src/main/java/shop/alien/lawyer/service/impl/LawyerConsultationOrderServiceImpl.java

@@ -0,0 +1,1787 @@
+package shop.alien.lawyer.service.impl;
+
+import com.alibaba.fastjson2.JSONObject;
+import com.alibaba.nacos.common.utils.CollectionUtils;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+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.apache.commons.lang.math.RandomUtils;
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.StringUtils;
+import shop.alien.entity.result.R;
+import shop.alien.entity.store.*;
+import shop.alien.entity.store.dto.LawyerConsultationOrderDto;
+import shop.alien.entity.store.dto.PayStatusRequest;
+import shop.alien.entity.store.vo.LawyerConsultationOrderVO;
+import shop.alien.entity.store.vo.OrderRevenueVO;
+import shop.alien.entity.store.vo.WebSocketVo;
+import shop.alien.lawyer.config.WebSocketProcess;
+import shop.alien.lawyer.service.LawyerConsultationOrderService;
+import shop.alien.lawyer.service.LawyerUserService;
+import shop.alien.lawyer.service.OrderExpirationService;
+import shop.alien.lawyer.util.DateUtils;
+import shop.alien.mapper.*;
+import shop.alien.util.common.constant.LawyerStatusEnum;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.text.SimpleDateFormat;
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * 咨询订单 服务实现类
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Slf4j
+@Transactional
+@Service
+@RequiredArgsConstructor
+public class LawyerConsultationOrderServiceImpl extends ServiceImpl<LawyerConsultationOrderMapper, LawyerConsultationOrder> implements LawyerConsultationOrderService {
+
+    private final LawyerConsultationOrderMapper consultationOrderMapper;
+    private final LawyerUserService lawyerUserService;
+    private final LawyerServiceAreaMapper lawyerServiceAreaMapper;
+    private final LawyerUserMapper lawyerUserMapper;
+    private final LawyerExpertiseAreaMapper lawyerExpertiseAreaMapper;
+    private final OrderExpirationService orderExpirationService;
+//    private final AliApi aliApi;
+    private final LawFirmMapper lawFirmMapper;
+    private final LifeNoticeMapper lifeNoticeMapper;
+    private final LifeUserMapper lifeUserMapper;
+    private final WebSocketProcess webSocketProcess;
+
+    private final LifeMessageMapper lifeMessageMapper;
+
+    /**
+     * 系统发送者ID
+     */
+    private static final String SYSTEM_SENDER_ID = "system";
+
+    @Value("${order.coefficients}")
+    private String coefficients;
+
+    @Override
+    public R<IPage<LawyerConsultationOrderVO>> getConsultationOrderList(int pageNum, int pageSize, String orderNumber,
+                                                                        Integer clientUserId, Integer lawyerUserId, String lawyerName, Integer orderStatus) {
+        log.info("LawyerConsultationOrderServiceImpl.getConsultationOrderList?pageNum={},pageSize={},orderNumber={},clientUserId={},lawyerUserId={},lawyerName={},orderStatus={}",
+                pageNum, pageSize, orderNumber, clientUserId, lawyerUserId, lawyerName, orderStatus);
+
+        // 創建分頁對象
+        Page<LawyerConsultationOrderVO> page = new Page<>(pageNum, pageSize);
+
+        // 如果按律師姓名搜索,先查詢匹配的律師ID列表
+        List<Integer> lawyerUserIds = null;
+        if (StringUtils.hasText(lawyerName)) {
+            LambdaQueryWrapper<LawyerUser> lawyerQueryWrapper = new LambdaQueryWrapper<>();
+            lawyerQueryWrapper.eq(LawyerUser::getDeleteFlag, 0);
+            lawyerQueryWrapper.like(LawyerUser::getName, lawyerName);
+            List<LawyerUser> lawyerUsers = lawyerUserService.list(lawyerQueryWrapper);
+            if (lawyerUsers != null && !lawyerUsers.isEmpty()) {
+                lawyerUserIds = lawyerUsers.stream()
+                        .map(LawyerUser::getId)
+                        .collect(Collectors.toList());
+            } else {
+                // 如果沒有找到匹配的律師,返回空結果
+                Page<LawyerConsultationOrderVO> emptyPage = new Page<>(pageNum, pageSize);
+                emptyPage.setRecords(Collections.emptyList());
+                emptyPage.setTotal(0);
+                return R.data(emptyPage);
+            }
+        }
+
+        // 使用JOIN查詢,直接聯查律師信息
+        IPage<LawyerConsultationOrderVO> voPage = consultationOrderMapper.getConsultationOrderListWithLawyer(
+                page, orderNumber, clientUserId, lawyerUserId, lawyerName, orderStatus, lawyerUserIds);
+
+        // 為待支付訂單計算倒計時(30分鐘有效期)
+        if (voPage != null && voPage.getRecords() != null) {
+            Date now = new Date();
+            long validitySeconds = 30 * 60; // 30分鐘 = 1800秒
+
+            for (LawyerConsultationOrderVO vo : voPage.getRecords()) {
+                // 僅對待支付訂單(orderStatus=0)計算倒計時
+                if (vo.getOrderStatus() != null && vo.getOrderStatus() == 0) {
+                    Date orderTime = vo.getOrderTime();
+                    if (orderTime != null) {
+                        long elapsedSeconds = (now.getTime() - orderTime.getTime()) / 1000;
+                        long remainingSeconds = validitySeconds - elapsedSeconds;
+                        // 如果已過期,設置為0
+                        vo.setCountdownSeconds(Math.max(0, remainingSeconds));
+                    } else {
+                        vo.setCountdownSeconds(0L);
+                    }
+                } else {
+                    // 非待支付訂單,倒計時為null
+                    vo.setCountdownSeconds(null);
+                }
+            }
+        }
+
+        return R.data(voPage);
+    }
+
+    /**
+     * 將訂單分頁結果轉換為VO並填充律師信息
+     */
+    private IPage<LawyerConsultationOrderVO> convertToVO(IPage<LawyerConsultationOrder> pageResult) {
+        // 創建VO分頁對象
+        Page<LawyerConsultationOrderVO> voPage = new Page<>(pageResult.getCurrent(), pageResult.getSize(), pageResult.getTotal());
+
+        List<LawyerConsultationOrder> records = pageResult.getRecords();
+        if (records == null || records.isEmpty()) {
+            voPage.setRecords(Collections.emptyList());
+            return voPage;
+        }
+
+        // 收集所有律師ID
+        Set<Integer> lawyerUserIds = records.stream()
+                .map(LawyerConsultationOrder::getLawyerUserId)
+                .filter(id -> id != null)
+                .collect(Collectors.toSet());
+
+        // 批量查詢律師信息
+        Map<Integer, LawyerUser> lawyerMap = new HashMap<>();
+        if (!lawyerUserIds.isEmpty()) {
+            LambdaQueryWrapper<LawyerUser> lawyerWrapper = new LambdaQueryWrapper<>();
+            lawyerWrapper.in(LawyerUser::getId, lawyerUserIds);
+            lawyerWrapper.eq(LawyerUser::getDeleteFlag, 0);
+            List<LawyerUser> lawyerUsers = lawyerUserService.list(lawyerWrapper);
+            if (lawyerUsers != null && !lawyerUsers.isEmpty()) {
+                lawyerMap = lawyerUsers.stream()
+                        .collect(Collectors.toMap(LawyerUser::getId, lawyer -> lawyer, (k1, k2) -> k1));
+            }
+        }
+
+        // 轉換為VO並填充律師信息
+        final Map<Integer, LawyerUser> finalLawyerMap = lawyerMap;
+        List<LawyerConsultationOrderVO> voList = records.stream()
+                .map(order -> {
+                    LawyerConsultationOrderVO vo = new LawyerConsultationOrderVO();
+                    // 複製訂單基本信息
+                    BeanUtils.copyProperties(order, vo);
+
+                    // 填充律師信息
+                    LawyerUser lawyer = finalLawyerMap.get(order.getLawyerUserId());
+                    if (lawyer != null) {
+                        vo.setLawyerName(lawyer.getName());
+                        vo.setLawyerPhone(lawyer.getPhone());
+                        vo.setLawyerEmail(lawyer.getEmail());
+                        vo.setLawyerCertificateNo(lawyer.getLawyerCertificateNo());
+                        vo.setLawFirm(lawyer.getLawFirm());
+                        vo.setPracticeYears(lawyer.getPracticeYears());
+                        vo.setSpecialtyFields(lawyer.getSpecialtyFields());
+                        vo.setCertificationStatus(lawyer.getCertificationStatus());
+                        vo.setServiceScore(lawyer.getServiceScore());
+                        vo.setServiceCount(lawyer.getServiceCount());
+                        vo.setLawyerConsultationFee(lawyer.getConsultationFee());
+                        vo.setProvince(lawyer.getProvince());
+                        vo.setCity(lawyer.getCity());
+                        vo.setDistrict(lawyer.getDistrict());
+                        vo.setAddress(lawyer.getAddress());
+                        vo.setHeadImg(lawyer.getHeadImg());
+                        vo.setNickName(lawyer.getNickName());
+                        vo.setPersonalIntroduction(lawyer.getPersonalIntroduction());
+                    }
+
+                    return vo;
+                })
+                .collect(Collectors.toList());
+
+        voPage.setRecords(voList);
+        return voPage;
+    }
+
+    @Override
+    public R<LawyerConsultationOrder> addConsultationOrder(LawyerConsultationOrder consultationOrder) {
+        log.info("LawyerConsultationOrderServiceImpl.addConsultationOrder?consultationOrder={}", consultationOrder);
+        boolean result = this.save(consultationOrder);
+        if (result) {
+            return R.data(consultationOrder);
+        }
+        return R.fail("新增失败");
+    }
+
+    /**
+     * 编辑咨询订单
+     * <p>
+     * 编辑前会进行以下校验:
+     * 1. 参数校验:订单ID不能为空
+     * 2. 订单存在性校验:订单必须存在
+     * 3. 订单状态校验:已完成的订单不允许修改关键信息
+     * </p>
+     *
+     * @param consultationOrder 咨询订单
+     * @return 编辑结果
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public R<LawyerConsultationOrder> editConsultationOrder(LawyerConsultationOrder consultationOrder) {
+        Integer orderId = consultationOrder.getId();
+        log.info("开始编辑咨询订单,订单ID={}", orderId);
+
+        // 参数校验:订单ID不能为空
+        if (orderId == null) {
+            log.warn("编辑咨询订单失败:订单ID为空");
+            return R.fail("订单ID不能为空");
+        }
+
+        // 查询订单信息,验证订单是否存在
+        LawyerConsultationOrder existingOrder = consultationOrderMapper.selectById(orderId);
+        if (existingOrder == null) {
+            log.warn("编辑咨询订单失败:订单不存在,订单ID={}", orderId);
+            return R.fail("订单不存在");
+        }
+
+        // 记录修改前的订单状态
+        Integer oldOrderStatus = existingOrder.getOrderStatus();
+        Integer oldPaymentStatus = existingOrder.getPaymentStatus();
+        Integer newOrderStatus = consultationOrder.getOrderStatus();
+        Integer newPaymentStatus = consultationOrder.getPaymentStatus();
+
+        log.info("编辑咨询订单,订单ID={}, 订单编号={}, 原订单状态={}, 新订单状态={}, 原支付状态={}, 新支付状态={}",
+                orderId, existingOrder.getOrderNumber(), oldOrderStatus, newOrderStatus,
+                oldPaymentStatus, newPaymentStatus);
+
+        // 订单状态校验:已完成的订单不允许修改关键信息(订单状态、支付状态、订单金额等)
+        Integer completedStatus = LawyerStatusEnum.COMPLETE.getStatus(); // 3:已完成
+        if (completedStatus.equals(oldOrderStatus)) {
+            // 如果订单已完成,只允许修改评价相关字段(rating, comment)
+            if (newOrderStatus != null && !newOrderStatus.equals(oldOrderStatus)) {
+                log.warn("编辑咨询订单失败:订单已完成,不允许修改订单状态,订单ID={}, 订单编号={}",
+                        orderId, existingOrder.getOrderNumber());
+                return R.fail("订单已完成,不允许修改订单状态");
+            }
+            if (newPaymentStatus != null && !newPaymentStatus.equals(oldPaymentStatus)) {
+                log.warn("编辑咨询订单失败:订单已完成,不允许修改支付状态,订单ID={}, 订单编号={}",
+                        orderId, existingOrder.getOrderNumber());
+                return R.fail("订单已完成,不允许修改支付状态");
+            }
+            if (consultationOrder.getOrderAmount() != null &&
+                    !consultationOrder.getOrderAmount().equals(existingOrder.getOrderAmount())) {
+                log.warn("编辑咨询订单失败:订单已完成,不允许修改订单金额,订单ID={}, 订单编号={}",
+                        orderId, existingOrder.getOrderNumber());
+                return R.fail("订单已完成,不允许修改订单金额");
+            }
+        }
+
+        // 已取消的订单不允许修改为其他状态(除非是管理员操作,这里暂不限制)
+        Integer cancelStatus = LawyerStatusEnum.CANCEL.getStatus(); // 4:已取消
+        if (cancelStatus.equals(oldOrderStatus) && newOrderStatus != null &&
+                !cancelStatus.equals(newOrderStatus)) {
+            log.warn("编辑咨询订单警告:订单已取消,尝试修改订单状态,订单ID={}, 订单编号={}, 原状态={}, 新状态={}",
+                    orderId, existingOrder.getOrderNumber(), oldOrderStatus, newOrderStatus);
+            // 这里可以根据业务需求决定是否允许,暂时允许修改
+        }
+
+        // 如果订单状态从待支付变为已支付,需要更新支付时间
+        Integer waitPayStatus = LawyerStatusEnum.WAIT_PAY.getStatus(); // 0:待支付
+        if (waitPayStatus.equals(oldOrderStatus) && newOrderStatus != null &&
+                !waitPayStatus.equals(newOrderStatus) && newPaymentStatus != null && newPaymentStatus == 1) {
+            if (consultationOrder.getPaymentTime() == null) {
+                consultationOrder.setPaymentTime(new Date());
+                log.info("订单状态从待支付变为已支付,自动设置支付时间,订单ID={}, 订单编号={}",
+                        orderId, existingOrder.getOrderNumber());
+            }
+        }
+
+        // 设置更新时间
+        consultationOrder.setUpdatedTime(new Date());
+
+        // 执行更新操作
+        boolean result = this.updateById(consultationOrder);
+        if (result) {
+            log.info("编辑咨询订单成功,订单ID={}, 订单编号={}, 原订单状态={}, 新订单状态={}",
+                    orderId, existingOrder.getOrderNumber(), oldOrderStatus, newOrderStatus);
+            // 重新查询更新后的订单信息
+            LawyerConsultationOrder updatedOrder = consultationOrderMapper.selectById(orderId);
+            return R.data(updatedOrder != null ? updatedOrder : consultationOrder);
+        } else {
+            log.error("编辑咨询订单失败:数据库更新失败,订单ID={}, 订单编号={}",
+                    orderId, existingOrder.getOrderNumber());
+            return R.fail("修改失败");
+        }
+    }
+
+    /**
+     * 删除咨询订单
+     * <p>
+     * 删除前会进行以下校验:
+     * 1. 参数校验:订单ID不能为空
+     * 2. 订单存在性校验:订单必须存在
+     * 3. 订单状态校验:进行中的订单不允许删除
+     * 4. 如果订单是待支付状态,会取消Redis中的支付超时计时器
+     * </p>
+     *
+     * @param id 订单ID
+     * @return 删除结果
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public R<Boolean> deleteConsultationOrder(Integer id) {
+        log.info("开始删除咨询订单,订单ID={}", id);
+
+        // 参数校验
+        if (id == null) {
+            log.warn("删除咨询订单失败:订单ID为空");
+            return R.fail("订单ID不能为空");
+        }
+
+        // 查询订单信息
+        LawyerConsultationOrder order = consultationOrderMapper.selectById(id);
+        if (order == null) {
+            log.warn("删除咨询订单失败:订单不存在,订单ID={}", id);
+            return R.fail("订单不存在");
+        }
+
+        // 检查订单状态:进行中的订单不允许删除
+        Integer orderStatus = order.getOrderStatus();
+        Integer inProgressStatus = LawyerStatusEnum.INPROGRESS.getStatus(); // 2:进行中
+        if (inProgressStatus.equals(orderStatus)) {
+            log.warn("删除咨询订单失败:订单进行中,不允许删除,订单ID={}, 订单编号={}",
+                    id, order.getOrderNumber());
+            return R.fail("订单进行中,不允许删除");
+        }
+
+        // 如果订单是待支付状态,取消Redis中的订单支付超时计时
+        Integer waitPayStatus = LawyerStatusEnum.WAIT_PAY.getStatus(); // 0:待支付
+        if (waitPayStatus.equals(orderStatus) && order.getOrderNumber() != null) {
+            try {
+                orderExpirationService.cancelOrderPaymentTimeout(order.getOrderNumber());
+                log.info("已取消订单支付超时计时,订单编号={}", order.getOrderNumber());
+            } catch (Exception e) {
+                log.error("取消订单支付超时计时失败,订单编号={}", order.getOrderNumber(), e);
+                // 继续执行删除操作,不因取消计时器失败而中断
+            }
+        }
+
+        // 执行删除操作
+        boolean result = this.removeById(id);
+        if (result) {
+            log.info("删除咨询订单成功,订单ID={}, 订单编号={}", id, order.getOrderNumber());
+            return R.data(true, "删除成功");
+        } else {
+            log.error("删除咨询订单失败:数据库操作失败,订单ID={}, 订单编号={}", id, order.getOrderNumber());
+            return R.fail("删除失败");
+        }
+    }
+
+    /*@Override
+    public R<Map<String, Object>> startConsultation(Integer lawyerId, String question, String sessionId, Integer clientUserId, Integer problemScenarioId) {
+        log.info("LawyerConsultationOrderServiceImpl.startConsultation?lawyerId={},question={},sessionId={},clientUserId={},problemScenarioId={}",
+                lawyerId, question, sessionId, clientUserId, problemScenarioId);
+
+        // 创建咨询订单
+        LawyerConsultationOrder order = new LawyerConsultationOrder();
+        order.setLawyerUserId(lawyerId);
+        order.setClientUserId(clientUserId);
+        order.setProblemScenarioId(problemScenarioId);
+        order.setProblemDescription(question);
+        order.setOrderStatus(0);  // 待支付
+        order.setPaymentStatus(0);  // 未支付
+
+        boolean saved = this.save(order);
+        if (!saved) {
+            return R.fail("创建咨询订单失败");
+        }
+
+        Map<String, Object> result = new HashMap<>();
+        result.put("consultationId", order.getId());
+        result.put("lawyerId", lawyerId);
+        result.put("status", "pending");  // pending(待响应), active(进行中), closed(已结束)
+        result.put("createTime", order.getCreatedTime() != null ?
+                new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").format(order.getCreatedTime()) : "");
+
+        return R.data(result);
+    }*/
+
+
+    @Override
+    public R<LawyerConsultationOrderDto> consultNow(LawyerConsultationOrder lawyerConsultationOrder) {
+            log.info("LawyerConsultationOrderServiceImpl.consultNow?lawyerConsultationOrder={}", lawyerConsultationOrder);
+
+        // 参数校验
+        if (lawyerConsultationOrder == null) {
+            log.warn("创建咨询订单失败:订单信息为空");
+            return R.fail("订单信息不能为空");
+        }
+
+        Integer clientUserId = lawyerConsultationOrder.getClientUserId();
+        Integer lawyerUserId = lawyerConsultationOrder.getLawyerUserId();
+        Integer orderAmount = lawyerConsultationOrder.getOrderAmount();
+
+        if (clientUserId == null || clientUserId <= 0) {
+            log.warn("创建咨询订单失败:客户端用户ID无效,clientUserId={}", clientUserId);
+            return R.fail("客户端用户ID不能为空");
+        }
+
+        if (lawyerUserId == null || lawyerUserId <= 0) {
+            log.warn("创建咨询订单失败:律师用户ID无效,lawyerUserId={}", lawyerUserId);
+            return R.fail("律师用户ID不能为空");
+        }
+
+        if (orderAmount == null || orderAmount <= 0) {
+            log.warn("创建咨询订单失败:订单金额无效,orderAmount={}", orderAmount);
+            return R.fail("订单金额不能为空且必须大于0");
+        }
+
+        // 创建订单DTO对象
+        LawyerConsultationOrderDto order = new LawyerConsultationOrderDto();
+        Date now = new Date();
+
+        // 设置订单基本信息
+        order.setClientUserId(clientUserId);
+        order.setLawyerUserId(lawyerUserId);
+        order.setProblemScenarioId(lawyerConsultationOrder.getProblemScenarioId());
+        order.setProblemDescription(lawyerConsultationOrder.getProblemDescription());
+        order.setOrderAmount(orderAmount);
+        order.setAlipayNo(lawyerConsultationOrder.getAlipayNo());
+        order.setOrderStr(lawyerConsultationOrder.getOrderStr());
+        order.setPlaceId(lawyerConsultationOrder.getPlaceId());
+        order.setPayType(lawyerConsultationOrder.getPayType());
+
+        // 设置订单状态
+        order.setOrderStatus(0); // 待支付
+        order.setPaymentStatus(0); // 未支付
+        order.setOrderTime(now);
+        order.setCreatedTime(now);
+        order.setUpdatedTime(now);
+        order.setDeleteFlag(0);
+
+        // 生成订单编号:LAW + 年月日(8位数字)+ 随机5位数字
+        String orderNumber = generateOrderNumber();
+        order.setOrderNumber(orderNumber);
+        log.info("生成订单编号:orderNumber={}", orderNumber);
+
+        // 计算本单收益(平台收益)
+        Integer consultationFee = calculateConsultationFee(lawyerUserId, orderAmount);
+        order.setConsultationFee(consultationFee);
+        log.info("计算咨询费用:lawyerUserId={}, orderAmount={}, consultationFee={}",
+                lawyerUserId, orderAmount, consultationFee);
+
+        // 计算律师收益(订单金额-平台收益)
+        Integer lawyerEarnings =  orderAmount - consultationFee;
+        order.setLawyerEarnings(lawyerEarnings);
+        // 插入订单
+        int insertCount = consultationOrderMapper.insertOrder(order);
+        if (insertCount > 0) {
+            log.info("创建咨询订单成功:orderNumber={}, orderId={}", orderNumber, order.getId());
+            return R.data(order);
+        } else {
+            log.error("创建咨询订单失败:数据库插入失败,orderNumber={}", orderNumber);
+            return R.fail("创建订单失败");
+        }
+    }
+
+    /**
+     * 生成订单编号
+     * <p>
+     * 格式:LAW + 年月日(8位数字)+ 随机5位数字
+     * 示例:LAW2025011512345
+     * </p>
+     *
+     * @return 订单编号
+     */
+    private String generateOrderNumber() {
+        String dateStr = new SimpleDateFormat("yyyyMMdd").format(new Date());
+        String randomStr = String.format("%05d", RandomUtils.nextInt(100000));
+        return "LAW" + dateStr + randomStr;
+    }
+
+    /**
+     * 计算咨询费用(本单收益)
+     * <p>
+     * 根据律所的平台佣金比例计算咨询费用
+     * 如果律师没有关联律所或律所没有设置佣金比例,则使用默认佣金比例3%
+     * </p>
+     *
+     * @param lawyerUserId 律师用户ID
+     * @param orderAmount  订单金额(单位:分)
+     * @return 咨询费用(单位:分)
+     */
+    private Integer calculateConsultationFee(Integer lawyerUserId, Integer orderAmount) {
+        // 默认佣金比例:3%
+        float defaultCommissionRate = 3.0F;
+        float commissionRate = defaultCommissionRate;
+
+        // 查询律师信息
+        LawyerUser lawyerUser = lawyerUserMapper.selectById(lawyerUserId);
+        if (lawyerUser != null) {
+            // 获取律所信息
+            Integer firmId = lawyerUser.getFirmId();
+            if (firmId != null && firmId > 0) {
+                LawFirm lawyerFirm = lawFirmMapper.selectById(firmId);
+                if (lawyerFirm != null && lawyerFirm.getPlatformCommissionRatio() != null
+                        && lawyerFirm.getPlatformCommissionRatio().floatValue() > 0) {
+                    commissionRate = lawyerFirm.getPlatformCommissionRatio().floatValue();
+                }
+            }
+        }
+
+        // 计算咨询费用:订单金额 * 佣金比例 / 100,四舍五入
+        int consultationFee = Math.round(orderAmount * commissionRate / 100);
+        return consultationFee;
+    }
+
+    @Override
+    public R<LawyerConsultationOrderDto> payStatus(PayStatusRequest request) {
+        log.info("LawyerConsultationOrderServiceImpl.payStatus?orderNumber={},paymentStatus={},orderStatus={}",
+                request.getOrderNumber(), request.getPaymentStatus(), request.getOrderStatus());
+
+        LawyerConsultationOrderDto order = new LawyerConsultationOrderDto();
+        if (null != request.getOrderStr()){
+            order.setOrderStr(request.getOrderStr());
+        }
+        if (null != request.getAlipayNo()){
+            order.setAlipayNo(request.getAlipayNo());
+        }
+        order.setOrderNumber(request.getOrderNumber());
+        order.setPaymentStatus(request.getPaymentStatus());
+        order.setOrderStatus(request.getOrderStatus());
+        order.setUpdatedTime(new Date());
+        order.setDeleteFlag(0);
+        order.setPaymentTime(new Date());
+        order.setStartTime(new Date());
+//        LocalDateTime now = LocalDateTime.now();
+//        LocalDateTime validityDateTime = now.plusDays(7)
+//                .withHour(0)
+//                .withMinute(0)
+//                .withSecond(0)
+//                .withNano(0);
+//        order.setValidityPeriod(Date.from(validityDateTime.atZone(ZoneId.systemDefault()).toInstant()));
+//        boolean result = this.updateById(order);
+        Integer result = consultationOrderMapper.updateOrder(order);
+
+        if (result > 0) {
+            return R.data(order);
+        }
+        return R.fail("失败");
+    }
+
+    /**
+     * 根据用户ID查询订单列表(包含律师信息)
+     *
+     * @param pageNum     页码
+     * @param pageSize    页容量
+     * @param userId      用户ID
+     * @param orderStatus 订单状态
+     * @param lawyerName  律师姓名(支持模糊查询)
+     * @return 分页订单列表
+     */
+    @Override
+    public Map<String, Object> getConsultationOrderListById(int pageNum, int pageSize,
+                                                             String userId, String orderStatus,
+                                                             String lawyerName) {
+        log.info("查询咨询订单信息(律师端)- pageNum={}, pageSize={}, userId={}, orderStatus={}, lawyerName={}",
+                pageNum, pageSize, userId, orderStatus, lawyerName);
+
+        // 参数校验:用户ID不能为空
+        if (!StringUtils.hasText(userId)) {
+            log.warn("查询咨询订单信息失败:用户ID为空");
+            return buildEmptyResultMap(pageNum, pageSize);
+        }
+
+        // 创建分页对象
+        Page<LawyerConsultationOrderVO> page = new Page<>(pageNum, pageSize);
+
+        // 构建查询条件
+        QueryWrapper<LawyerConsultationOrderVO> queryWrapper = buildOrderQueryWrapper(
+                lawyerName, orderStatus, userId);
+        QueryWrapper<LawyerConsultationOrderVO> statisticsWrapper = buildStatisticsQueryWrapper(
+                lawyerName, userId);
+
+        // 查询订单列表
+        IPage<LawyerConsultationOrderVO> voPage = consultationOrderMapper.getLawyerConsultationOrderList(
+                page, queryWrapper);
+
+        if (voPage != null) {
+            // 填充法律场景信息和倒计时
+            fillLegalSceneArea(voPage);
+            calculateCountdownForPendingOrders(voPage);
+
+            //填充未读消息
+            fillUnreadMessage(voPage,userId);
+        }
+
+        // 判断工作日信息
+        calculateWorkDayInfo(voPage);
+
+
+        // 获取统计信息
+        statisticsWrapper.groupBy("lco.order_status");
+        List<Map<String, Object>> statisticsInfo = consultationOrderMapper.getLawyerStatisticsInfo(
+                statisticsWrapper);
+
+        // 构建状态统计Map并填充返回结果
+        Map<Integer, Integer> statusCountMap = buildStatusCountMap(statisticsInfo);
+        Map<String, Object> resultMap = buildOrderStatisticsResult(statusCountMap, voPage);
+
+        return resultMap;
+    }
+
+    /**
+     * 构建订单统计结果Map
+     *
+     * @param statusCountMap 状态统计Map
+     * @param voPage        订单分页对象
+     * @return 订单统计结果Map
+     */
+    private Map<String, Object> buildOrderStatisticsResult(Map<Integer, Integer> statusCountMap,
+                                                            IPage<LawyerConsultationOrderVO> voPage) {
+        Map<String, Object> resultMap = new HashMap<>(16);
+
+        // 获取各状态订单数量
+        Integer waitPayStatus = LawyerStatusEnum.WAIT_PAY.getStatus();
+        int waitPayStatusCount = statusCountMap.getOrDefault(waitPayStatus, 0);
+
+        Integer waitAcceptStatus = LawyerStatusEnum.WAIT_ACCEPT.getStatus();
+        int waitAcceptStatusCount = statusCountMap.getOrDefault(waitAcceptStatus, 0);
+
+        Integer inProgressStatus = LawyerStatusEnum.INPROGRESS.getStatus();
+        int inProgressCount = statusCountMap.getOrDefault(inProgressStatus, 0);
+
+        Integer completeStatus = LawyerStatusEnum.COMPLETE.getStatus();
+        int completeCount = statusCountMap.getOrDefault(completeStatus, 0);
+
+        Integer cancelStatus = LawyerStatusEnum.CANCEL.getStatus();
+        int cancelStatusCount = statusCountMap.getOrDefault(cancelStatus, 0);
+
+        Integer refundedStatus = LawyerStatusEnum.REFUNDED.getStatus();
+        int refundedStatusCount = statusCountMap.getOrDefault(refundedStatus, 0);
+
+        // 计算订单总数
+        long totalOrderCount = (long) (waitPayStatusCount + waitAcceptStatusCount + inProgressCount
+                + completeCount + cancelStatusCount + refundedStatusCount);
+
+        // 填充返回结果
+        resultMap.put("lawyerWaitPayStatusOrderCount", waitPayStatusCount);
+        resultMap.put("lawyerWaitAcceptStatusOrderCount", waitAcceptStatusCount);
+        resultMap.put("lawyerInProgressOrderCount", inProgressCount);
+        resultMap.put("lawyerCompleteOrderCount", completeCount);
+        resultMap.put("lawyerCancelStatusOrderCount", cancelStatusCount);
+        resultMap.put("lawyerRefundedStatusOrderCount", refundedStatusCount);
+        resultMap.put("lawyerOrderCount", totalOrderCount);
+
+        // 设置订单列表
+        List<LawyerConsultationOrderVO> orderList = (voPage != null && voPage.getRecords() != null)
+                ? voPage.getRecords() : Collections.emptyList();
+        resultMap.put("lawyerConsultationOrderList", orderList);
+
+        return resultMap;
+    }
+
+    /**
+     * 构建状态统计Map
+     *
+     * @param statisticsInfo 统计信息列表
+     * @return 状态统计Map,key为订单状态,value为订单数量
+     */
+    private Map<Integer, Integer> buildStatusCountMap(List<Map<String, Object>> statisticsInfo) {
+        Map<Integer, Integer> statusCountMap = new HashMap<>(16);
+        if (CollectionUtils.isEmpty(statisticsInfo)) {
+            return statusCountMap;
+        }
+
+        for (Map<String, Object> map : statisticsInfo) {
+            Object statusObj = map.get("order_status");
+            Object countObj = map.get("order_count");
+
+            if (statusObj == null || countObj == null) {
+                continue;
+            }
+
+            Integer status = (Integer) statusObj;
+            // COUNT(*) 返回类型可能是Long
+            Long countLong = (Long) countObj;
+            statusCountMap.put(status, countLong.intValue());
+        }
+
+        return statusCountMap;
+    }
+
+    /**
+     * 构建统计查询条件
+     *
+     * @param lawyerName 律师名称
+     * @param userId     客户端用户ID
+     * @return 统计查询条件包装器
+     */
+    private QueryWrapper<LawyerConsultationOrderVO> buildStatisticsQueryWrapper(String lawyerName,
+                                                                                String userId) {
+        QueryWrapper<LawyerConsultationOrderVO> queryWrapper = new QueryWrapper<>();
+        if (StringUtils.hasText(lawyerName)) {
+            queryWrapper.like("lu.name", lawyerName);
+        }
+        queryWrapper.eq("lco.client_user_id", userId);
+        queryWrapper.eq("lco.delete_flag", 0);
+        return queryWrapper;
+    }
+
+
+    /**
+     * 构建空结果Map
+     *
+     * @param pageNum  页码
+     * @param pageSize 页容量
+     * @return 空结果Map
+     */
+    private Map<String, Object> buildEmptyResultMap(int pageNum, int pageSize) {
+        Map<String, Object> resultMap = new HashMap<>(16);
+        Page<LawyerConsultationOrderVO> emptyPage = new Page<>(pageNum, pageSize);
+        emptyPage.setRecords(Collections.emptyList());
+        emptyPage.setTotal(0);
+        resultMap.put("lawyerWaitPayStatusOrderCount", 0);
+        resultMap.put("lawyerWaitAcceptStatusOrderCount", 0);
+        resultMap.put("lawyerInProgressOrderCount", 0);
+        resultMap.put("lawyerCompleteOrderCount", 0);
+        resultMap.put("lawyerCancelStatusOrderCount", 0);
+        resultMap.put("lawyerRefundedStatusOrderCount", 0);
+        resultMap.put("lawyerOrderCount", 0L);
+        resultMap.put("lawyerConsultationOrderList", Collections.emptyList());
+        return resultMap;
+    }
+
+    /**
+     * 构建订单查询条件
+     *
+     * @param lawyerName 律师名称
+     * @param orderStatus 订单状态
+     * @param userId      客户端用户ID
+     * @return 查询条件包装器
+     */
+    private QueryWrapper<LawyerConsultationOrderVO> buildOrderQueryWrapper(String lawyerName, String orderStatus,
+                                                                           String userId) {
+        QueryWrapper<LawyerConsultationOrderVO> queryWrapper = new QueryWrapper<>();
+
+        // 订单状态条件
+        if (StringUtils.hasText(orderStatus)) {
+            queryWrapper.in("lco.order_status", Collections.singletonList(orderStatus));
+        }
+
+        // 律师名称条件
+        if (StringUtils.hasText(lawyerName)) {
+            queryWrapper.like("lu.name", lawyerName);
+        }
+
+        // 客户端用户ID条件
+        queryWrapper.eq("lco.client_user_id", userId);
+
+        // 删除标记条件
+        queryWrapper.eq("lco.delete_flag", 0);
+
+        // 排序条件:按创建时间倒序
+        queryWrapper.orderByDesc("lco.created_time");
+        return queryWrapper;
+    }
+
+    /**
+     * 根据律师姓名查询律师ID列表
+     *
+     * @param lawyerName 律师姓名
+     * @return 律师ID列表
+     */
+    private List<Integer> queryLawyerIdsByName(String lawyerName) {
+        LambdaQueryWrapper<LawyerUser> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(LawyerUser::getDeleteFlag, 0)
+                .like(LawyerUser::getName, lawyerName);
+        List<LawyerUser> lawyerUsers = lawyerUserService.list(queryWrapper);
+
+        if (CollectionUtils.isEmpty(lawyerUsers)) {
+            return Collections.emptyList();
+        }
+
+        return lawyerUsers.stream()
+                .map(LawyerUser::getId)
+                .filter(Objects::nonNull)
+                .collect(Collectors.toList());
+    }
+
+    /**
+     * 填充律师法律场景到订单VO列表
+     *
+     * @param voPage 订单VO分页对象
+     */
+    private void fillLegalSceneArea(IPage<LawyerConsultationOrderVO> voPage) {
+        List<LawyerConsultationOrderVO> orderList = voPage.getRecords();
+        if (CollectionUtils.isEmpty(orderList)) {
+            return;
+        }
+
+        // 提取所有律师ID
+        List<Integer> lawyerIdList = orderList.stream()
+                .map(LawyerConsultationOrderVO::getLawyerUserId)
+                .filter(Objects::nonNull)
+                .distinct()
+                .collect(Collectors.toList());
+
+        if (CollectionUtils.isEmpty(lawyerIdList)) {
+            return;
+        }
+
+        // 批量查询法律场景
+        Map<String, List<String>> serviceAreaMap = getLawyerServiceArea(lawyerIdList);
+        if (serviceAreaMap.isEmpty()) {
+            return;
+        }
+
+        // 填充问题场景
+        orderList.forEach(order -> {
+            Integer lawyerUserId = order.getLawyerUserId();
+            if (lawyerUserId != null) {
+                String lawyerUserIdStr = String.valueOf(lawyerUserId);
+                List<String> serviceAreaList = serviceAreaMap.get(lawyerUserIdStr);
+                if (CollectionUtils.isNotEmpty(serviceAreaList)) {
+                    order.setLawyerLegalProblemScenarioList(serviceAreaList);
+                }
+            }
+        });
+    }
+
+
+
+    /**
+     * 填充未读消息数量
+     *
+     * @param voPage 订单VO分页对象
+     */
+    private void fillUnreadMessage(IPage<LawyerConsultationOrderVO> voPage,String userId) {
+        List<LawyerConsultationOrderVO> orderList = voPage.getRecords();
+        if (CollectionUtils.isEmpty(orderList)) {
+            return;
+        }
+
+        // 提取所有律师ID,并加上lawyer_前缀
+        List<String> lawyerIdList = orderList.stream()
+                .map(LawyerConsultationOrderVO::getLawyerPhone)
+                .filter(Objects::nonNull)
+                .map(phone -> "lawyer_" + phone)
+                .distinct()
+                .collect(Collectors.toList());
+
+        if (CollectionUtils.isEmpty(lawyerIdList)) {
+            return;
+        }
+
+        LifeUser lifeUser = lifeUserMapper.selectById(userId);
+        String phone = lifeUser.getUserPhone();
+
+        LambdaQueryWrapper<LifeMessage> lifeMessageLambdaQueryWrapper = new LambdaQueryWrapper<>();
+        lifeMessageLambdaQueryWrapper.in(LifeMessage::getSenderId, lawyerIdList);
+        lifeMessageLambdaQueryWrapper.eq(LifeMessage::getDeleteFlag, 0);
+        lifeMessageLambdaQueryWrapper.eq(LifeMessage::getIsRead, 0);
+        lifeMessageLambdaQueryWrapper.eq(LifeMessage::getReceiverId, "user_" + phone);
+        List<LifeMessage> lifeMessageList = lifeMessageMapper.selectList(lifeMessageLambdaQueryWrapper);
+
+        // 按照senderId进行分组,返回senderId和数量的map
+        Map<String, Long> senderIdCountMap = new HashMap<>();
+        if(CollectionUtils.isNotEmpty(lifeMessageList)){
+            senderIdCountMap = lifeMessageList.stream()
+                    .collect(Collectors.groupingBy(LifeMessage::getSenderId, Collectors.counting()));
+        }
+
+        for(LawyerConsultationOrderVO lawyerConsultationOrderVO : voPage.getRecords()){
+            String lawyerPhone = "lawyer_" + lawyerConsultationOrderVO.getLawyerPhone();
+            if(!senderIdCountMap.isEmpty() && senderIdCountMap.containsKey(lawyerPhone) && lawyerConsultationOrderVO.getOrderStatus() == 2){
+                long messageCount = senderIdCountMap.get(lawyerPhone);
+                lawyerConsultationOrderVO.setUnreadMessage(messageCount);
+            } else {
+                lawyerConsultationOrderVO.setUnreadMessage(0L);
+            }
+        }
+    }
+
+
+    /**
+     * 批量查询律师问题场景
+     *
+     * @param lawyerIdList 律师ID列表
+     * @return 律师问题场景Map,key为律师ID(String),value为问题场景名称列表
+     */
+    private Map<String, List<String>> getLawyerServiceArea(List<Integer> lawyerIdList) {
+        Map<String, List<String>> serviceAreaMap = new HashMap<>();
+        if (CollectionUtils.isEmpty(lawyerIdList)) {
+            return serviceAreaMap;
+        }
+
+        QueryWrapper<LawyerServiceArea> wrapper = new QueryWrapper<>();
+        wrapper.in("lsa.lawyer_user_id", lawyerIdList)
+                .eq("lsa.delete_flag", 0)
+                .eq("lsa.status", 1);
+
+        List<Map<String, Object>> serviceAreaDataList = lawyerServiceAreaMapper.getLawyerLegalProblemScenarioList(wrapper);
+        if (CollectionUtils.isEmpty(serviceAreaDataList)) {
+            return serviceAreaMap;
+        }
+
+        for (Map<String, Object> row : serviceAreaDataList) {
+            Object lawyerUserIdObj = row.get("lawyer_user_id");
+            if (lawyerUserIdObj == null) {
+                continue;
+            }
+
+            String lawyerUserId = String.valueOf(lawyerUserIdObj);
+            String scenarioName = (String) row.get("name");
+            String scenarioNameValue = StringUtils.hasText(scenarioName) ? scenarioName : "";
+
+            serviceAreaMap.computeIfAbsent(lawyerUserId, k -> new ArrayList<>())
+                    .add(scenarioNameValue);
+        }
+
+        return serviceAreaMap;
+    }
+
+    /**
+     * 获取咨询订单详情
+     *
+     * @param lawyerOrderId 订单ID
+     * @return 咨询订单详情VO
+     */
+    @Override
+    public LawyerConsultationOrderVO getConsultationOrderDetail(String lawyerOrderId) {
+        log.info("LawyerConsultationOrderServiceImpl.getConsultationOrderDetail?lawyerOrderId={}", lawyerOrderId);
+
+        LawyerConsultationOrderVO orderVO = new LawyerConsultationOrderVO();
+
+        // 参数校验
+        if (!StringUtils.hasText(lawyerOrderId)) {
+            return orderVO;
+        }
+
+        // 查询订单信息
+        LawyerConsultationOrder order = consultationOrderMapper.selectById(lawyerOrderId);
+        if (order == null) {
+            return orderVO;
+        }
+
+        // 复制订单基本信息
+        BeanUtils.copyProperties(order, orderVO);
+
+        // 查询并填充律师信息
+        Integer lawyerUserId = order.getLawyerUserId();
+        if (lawyerUserId != null) {
+            LawyerUser lawyerUser = lawyerUserMapper.selectById(lawyerUserId);
+            if (lawyerUser != null) {
+                fillLawyerInfo(orderVO, lawyerUser);
+            }
+
+
+            // 查询律师问题场景
+            List<Integer> lawyerIdList = Collections.singletonList(lawyerUserId);
+            Map<String, List<String>> serviceAreaMap = getLawyerServiceArea(lawyerIdList);
+            String lawyerUserIdStr = String.valueOf(lawyerUserId);
+            if (!serviceAreaMap.isEmpty() && serviceAreaMap.containsKey(lawyerUserIdStr)) {
+                orderVO.setLawyerLegalProblemScenarioList(serviceAreaMap.get(lawyerUserIdStr));
+            }
+        }
+
+        return orderVO;
+    }
+
+    /**
+     * 取消律师咨询订单
+     * <p>
+     * 取消订单前会进行以下校验和处理:
+     * 1. 参数校验:订单ID不能为空
+     * 2. 订单存在性校验:订单必须存在
+     * 3. 订单状态校验:已取消或已完成的订单不允许再次取消
+     * 4. 如果订单是待支付状态,会取消Redis中的订单支付超时计时器
+     * 5. 根据订单状态更新为相应状态:
+     *    - 待接单状态:更新为已退款状态
+     *    - 待支付状态:更新为已取消状态
+     * </p>
+     *
+     * @param id 订单ID
+     * @return 是否取消成功,true表示成功,false表示失败
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public boolean cancelOrder(String id) {
+        log.info("开始取消订单,订单ID={}", id);
+
+        // 1. 参数校验
+        if (!StringUtils.hasText(id)) {
+            log.warn("取消订单失败:订单ID为空");
+            return false;
+        }
+
+        // 2. 查询订单信息
+        LawyerConsultationOrder order = consultationOrderMapper.selectById(id);
+        if (order == null) {
+            log.warn("取消订单失败:订单不存在,订单ID={}", id);
+            return false;
+        }
+
+        // 3. 订单状态校验
+        Integer orderStatus = order.getOrderStatus();
+        String orderNumber = order.getOrderNumber();
+        Integer cancelStatus = LawyerStatusEnum.CANCEL.getStatus();
+        Integer completedStatus = LawyerStatusEnum.COMPLETE.getStatus();
+        Integer waitAcceptStatus = LawyerStatusEnum.WAIT_ACCEPT.getStatus();
+        Integer waitPayStatus = LawyerStatusEnum.WAIT_PAY.getStatus();
+        Integer refundedStatus = LawyerStatusEnum.REFUNDED.getStatus();
+
+        // 3.1 检查订单是否已取消
+        if (cancelStatus.equals(orderStatus)) {
+            log.warn("取消订单失败:订单已取消,订单ID={}, 订单编号={}", id, orderNumber);
+            return false;
+        }
+
+        // 3.2 检查订单是否已完成
+        if (completedStatus.equals(orderStatus)) {
+            log.warn("取消订单失败:订单已完成,不允许取消,订单ID={}, 订单编号={}", id, orderNumber);
+            return false;
+        }
+
+        // 4. 如果订单是待支付状态,取消Redis中的订单支付超时计时器
+        if (waitPayStatus.equals(orderStatus) && StringUtils.hasText(orderNumber)) {
+            try {
+                orderExpirationService.cancelOrderPaymentTimeout(orderNumber);
+                log.info("已取消订单支付超时计时器,订单编号={}", orderNumber);
+            } catch (Exception e) {
+                log.error("取消订单支付超时计时器失败,订单编号={}", orderNumber, e);
+                // 继续执行取消订单操作,不因取消计时器失败而中断
+            }
+        }
+
+        // 5. 根据订单状态更新为相应状态
+        Integer targetStatus = null;
+        if (waitAcceptStatus.equals(orderStatus)) {
+            // 待接单状态:更新为已退款状态
+            targetStatus = refundedStatus;
+        } else if (waitPayStatus.equals(orderStatus)) {
+            // 待支付状态:更新为已取消状态
+            targetStatus = cancelStatus;
+        } else {
+            log.warn("取消订单失败:订单状态不允许取消,订单ID={}, 订单编号={}, 订单状态={}", 
+                    id, orderNumber, orderStatus);
+            return false;
+        }
+
+        // 6. 更新订单状态
+        LambdaUpdateWrapper<LawyerConsultationOrder> updateWrapper = new LambdaUpdateWrapper<>();
+        updateWrapper.eq(LawyerConsultationOrder::getId, id)
+                .set(LawyerConsultationOrder::getOrderStatus, targetStatus)
+                .set(LawyerConsultationOrder::getUpdatedTime, new Date());
+
+        int updateCount = consultationOrderMapper.update(null, updateWrapper);
+        boolean success = updateCount > 0;
+
+        // 7. 记录操作结果
+        if (success) {
+            log.info("取消订单成功,订单ID={}, 订单编号={}, 原状态={}, 新状态={}", 
+                    id, orderNumber, orderStatus, targetStatus);
+        } else {
+            log.error("取消订单失败:更新数据库失败,订单ID={}, 订单编号={}, 原状态={}", 
+                    id, orderNumber, orderStatus);
+        }
+
+        return success;
+    }
+
+    /**
+     * 查询咨询订单信息(律师端)
+     *
+     * @param pageNum   页码
+     * @param pageSize  页容
+     * @param startDate 开始时间
+     * @param endDate   结束时间
+     * @param lawyerId  律师ID
+     * @return 订单信息Map,包含订单列表、总数、进行中数量、已完成数量
+     */
+    @Override
+    public Map<String, Object> getLawyerConsultationOrderInfo(int pageNum, int pageSize, String startDate, String endDate, String clientUserName, String orderStatus, String lawyerId) {
+        log.info("LawyerConsultationOrderServiceImpl.getLawyerConsultationOrderInfo?pageNum={},pageSize={},startDate={},endDate={},clientUserName={},lawyerId={}",
+                pageNum, pageSize, startDate, endDate, clientUserName, lawyerId);
+
+        Map<String, Object> resultMap = new HashMap<>();
+
+        // 参数校验:律师ID不能为空
+        if (!StringUtils.hasText(lawyerId)) {
+            log.warn("查询咨询订单信息失败:律师ID为空");
+            Page<LawyerConsultationOrderVO> emptyPage = new Page<>(pageNum, pageSize);
+            emptyPage.setRecords(Collections.emptyList());
+            emptyPage.setTotal(0);
+            resultMap.put("lawyerOrderCount", 0L);
+            resultMap.put("lawyerInProgressOrderCount", 0);
+            resultMap.put("lawyerCompleteOrderCount", 0);
+            resultMap.put("lawyerConsultationOrderList", Collections.emptyList());
+            return resultMap;
+        }
+
+        // 创建分页对象
+        Page<LawyerConsultationOrderVO> page = new Page<>(pageNum, pageSize);
+
+        // 构建查询条件:查询进行中(2)和已完成(3)状态的订单
+        QueryWrapper<LawyerConsultationOrderVO> queryWrapper = new QueryWrapper<>();
+        QueryWrapper<LawyerConsultationOrderVO> queryStatisticsWrapper = new QueryWrapper<>();
+
+        if (StringUtils.hasText(orderStatus)) {
+            queryWrapper.in("lco.order_status", Collections.singletonList(orderStatus));
+        } else {
+            queryWrapper.in("lco.order_status", Arrays.asList("2", "3"));
+        }
+        queryWrapper.eq("lco.lawyer_user_id", lawyerId);
+        queryStatisticsWrapper.eq("lco.lawyer_user_id", lawyerId);
+        if (StringUtils.hasText(clientUserName)) {
+            queryWrapper.like("lur.user_name", clientUserName);
+            queryStatisticsWrapper.like("lur.user_name", clientUserName);
+        }
+        queryWrapper.eq("lco.delete_flag", 0);
+        queryStatisticsWrapper.eq("lco.delete_flag", 0);
+        queryWrapper.orderByDesc("lco.created_time");
+        // 时间范围查询:如果开始时间和结束时间都存在,则进行范围查询
+        if (StringUtils.hasText(startDate) && StringUtils.hasText(endDate)) {
+            queryWrapper.between("lco.payment_time", startDate + " 00:00:00", endDate + " 23:59:59");
+        }
+
+        // 查询订单列表
+        IPage<LawyerConsultationOrderVO> voPage = consultationOrderMapper.getLawyerConsultationOrderList(page, queryWrapper);
+
+        // 填充律师问题场景信息
+        if (voPage != null) {
+            fillLegalSceneArea(voPage);
+            // 为待支付订单计算倒计时(30分钟有效期)
+            calculateCountdownForPendingOrders(voPage);
+        }
+
+        //获取统计信息
+        queryStatisticsWrapper.groupBy("lco.order_status");
+        List<Map<String, Object>> statisticsInfo = consultationOrderMapper.getLawyerStatisticsInfo(queryStatisticsWrapper);
+
+        Map<Integer, Integer> statusCountMap = new HashMap<>();
+        for (Map<String, Object> map : statisticsInfo) {
+            Integer status = (Integer) map.get("order_status");
+            Long countLong = (Long) map.get("order_count"); // COUNT(*) 返回类型可能是Long
+            statusCountMap.put(status, countLong.intValue());
+        }
+
+
+        // 统计进行中订单数量
+        int inProgressCount = statusCountMap.getOrDefault(2, 0);
+        resultMap.put("lawyerInProgressOrderCount", inProgressCount);
+
+        // 统计已完成订单数量
+        int completeCount = statusCountMap.getOrDefault(3, 0);
+        resultMap.put("lawyerCompleteOrderCount", completeCount);
+
+        // 统计订单总数
+        resultMap.put("lawyerOrderCount", inProgressCount + completeCount);
+
+        // 设置订单列表
+        List<LawyerConsultationOrderVO> orderList = voPage != null && voPage.getRecords() != null
+                ? voPage.getRecords() : Collections.emptyList();
+        resultMap.put("lawyerConsultationOrderList", orderList);
+
+        return resultMap;
+    }
+
+
+    /**
+     * 为待支付订单计算倒计时(30分钟有效期)
+     *
+     * @param voPage 订单分页对象
+     */
+    private void calculateCountdownForPendingOrders(IPage<LawyerConsultationOrderVO> voPage) {
+        if (voPage == null || voPage.getRecords() == null) {
+            return;
+        }
+
+        Date now = new Date();
+        long validitySeconds = 30 * 60; // 30分钟 = 1800秒
+
+        for (LawyerConsultationOrderVO vo : voPage.getRecords()) {
+            // 仅对待支付订单(orderStatus=0)计算倒计时
+            if (vo.getOrderStatus() != null && vo.getOrderStatus() == 0) {
+                Date orderTime = vo.getOrderTime();
+                if (orderTime != null) {
+                    long elapsedSeconds = (now.getTime() - orderTime.getTime()) / 1000;
+                    long remainingSeconds = validitySeconds - elapsedSeconds;
+                    // 如果已过期,设置为0
+                    vo.setCountdownSeconds(Math.max(0, remainingSeconds));
+                } else {
+                    vo.setCountdownSeconds(0L);
+                }
+            } else {
+                // 非待支付订单,倒计时为null
+                vo.setCountdownSeconds(null);
+            }
+        }
+    }
+
+    /**
+     * 根据工作日判断是否可以申请退款/举报
+     *
+     * @param voPage 订单分页对象
+     */
+    private void calculateWorkDayInfo(IPage<LawyerConsultationOrderVO> voPage) {
+        if (voPage == null || voPage.getRecords() == null) {
+            return;
+        }
+        boolean isCanApplyRefund = DateUtils.getLastThreeWorkdaysRangeInfo(2);
+        boolean isCanApplyViolation = DateUtils.getLastThreeWorkdaysRangeInfo(3);
+
+        for (LawyerConsultationOrderVO vo : voPage.getRecords()) {
+            if(isCanApplyRefund){
+                vo.setIsCanApplyRefund("2");
+            } else {
+                vo.setIsCanApplyRefund("1");
+            }
+
+            if(isCanApplyViolation){
+                vo.setIsCanApplyViolation("2");
+            } else {
+                vo.setIsCanApplyViolation("1");
+            }
+        }
+    }
+
+    /**
+     * 填充律师信息到订单VO
+     *
+     * @param orderVO    订单VO对象
+     * @param lawyerUser 律师用户对象
+     */
+    private void fillLawyerInfo(LawyerConsultationOrderVO orderVO, LawyerUser lawyerUser) {
+        orderVO.setLawyerName(lawyerUser.getName());
+        orderVO.setLawyerPhone(lawyerUser.getPhone());
+        orderVO.setLawyerEmail(lawyerUser.getEmail());
+        orderVO.setLawyerCertificateNo(lawyerUser.getLawyerCertificateNo());
+        orderVO.setLawFirm(lawyerUser.getLawFirm());
+        orderVO.setPracticeYears(lawyerUser.getPracticeYears());
+        orderVO.setSpecialtyFields(lawyerUser.getSpecialtyFields());
+        orderVO.setCertificationStatus(lawyerUser.getCertificationStatus());
+        orderVO.setServiceCount(lawyerUser.getServiceCount());
+        orderVO.setServiceScore(lawyerUser.getServiceScore());
+        orderVO.setLawyerConsultationFee(lawyerUser.getConsultationFee());
+        orderVO.setProvince(lawyerUser.getProvince());
+        orderVO.setCity(lawyerUser.getCity());
+        orderVO.setDistrict(lawyerUser.getDistrict());
+        orderVO.setAddress(lawyerUser.getAddress());
+        orderVO.setHeadImg(lawyerUser.getHeadImg());
+        orderVO.setNickName(lawyerUser.getNickName());
+        orderVO.setPersonalIntroduction(lawyerUser.getPersonalIntroduction());
+    }
+
+    /**
+     * 查询律师订单列表
+     *
+     * @param clientUserId 用户id
+     * @param lawyerUserId 律师ID
+     * @return 订单列表
+     */
+    @Override
+    public R<Map<String, String>> checkOrder(Integer clientUserId, Integer lawyerUserId) {
+        log.info("LawyerConsultationOrderController.checkOrder?clientUserId={},lawyerUserId{}", clientUserId, lawyerUserId);
+        List<LawyerConsultationOrder> orderDto = consultationOrderMapper.selectOrder(clientUserId, lawyerUserId);
+        if (CollectionUtils.isNotEmpty(orderDto)) {
+            return R.fail("您已存在咨询该律师的订单");
+        }
+        return R.success("可以咨询该律师");
+    }
+
+    /**
+     * 获取律师订单收益统计
+     * <p>
+     * 统计三个维度的收益数据:
+     * 1. 全部收益:所有已支付订单的订单总数和收益合计
+     * 2. 进行中:订单状态=2(进行中)且已支付的订单总数和收益合计
+     * 3. 已完成:订单状态=3(已完成)且已支付的订单总数和收益合计
+     * </p>
+     *
+     * @param lawyerConsultationOrderVO 律师用户ID
+     * @param lawyerConsultationOrderVO 开始时间(可选,用于筛选支付时间范围)
+     * @param lawyerConsultationOrderVO 结束时间(可选,用于筛选支付时间范围)
+     * @return 订单收益统计信息
+     */
+    @Override
+    public R<Map<String, Object>> getOrderIncome(LawyerConsultationOrderVO lawyerConsultationOrderVO) {
+        log.info("LawyerConsultationOrderController.getOrderIncome?lawyerConsultationOrderVO{}", lawyerConsultationOrderVO);
+
+        // 参数校验
+        if (lawyerConsultationOrderVO.getLawyerUserId() == null) {
+            log.warn("查询订单收益失败:律师用户ID为空");
+            return R.fail("律师用户ID不能为空");
+        }
+
+        Map<String, Object> resultMap = new HashMap<>();
+        OrderRevenueVO revenueVO = new OrderRevenueVO();
+
+        try {
+            // 构建查询条件:查询已支付的订单(paymentStatus = 1)
+            LambdaQueryWrapper<LawyerConsultationOrder> allRevenueWrapper = new LambdaQueryWrapper<>();
+            allRevenueWrapper.eq(LawyerConsultationOrder::getLawyerUserId, lawyerConsultationOrderVO.getLawyerUserId())
+                    .eq(LawyerConsultationOrder::getPaymentStatus, 1) // 已支付
+                    .in(LawyerConsultationOrder::getOrderStatus, 2,3) // 订单状态=3(已完成)2(进行中)
+                    .eq(LawyerConsultationOrder::getDeleteFlag, 0);
+            
+            // 添加时间段筛选条件(根据支付时间)
+            if (lawyerConsultationOrderVO.getStartTimeNew() != null) {
+                allRevenueWrapper.ge(LawyerConsultationOrder::getPaymentTime, lawyerConsultationOrderVO.getStartTimeNew());
+            }
+            if (lawyerConsultationOrderVO.getEndTimeNew()  != null) {
+                allRevenueWrapper.le(LawyerConsultationOrder::getPaymentTime, lawyerConsultationOrderVO.getEndTimeNew() );
+            }
+
+            // 查询全部已支付订单的统计
+            List<LawyerConsultationOrder> allPaidOrders = consultationOrderMapper.selectList(allRevenueWrapper);
+            long allOrderCount = allPaidOrders.size();
+            long allRevenue = allPaidOrders.stream()
+                    .filter(order -> order.getOrderAmount() != null)
+                    .mapToLong(LawyerConsultationOrder::getOrderAmount)
+                    .sum();
+
+            long consultationFee = allPaidOrders.stream()
+                    .filter(order -> order.getConsultationFee() != null)
+                    .mapToLong(LawyerConsultationOrder::getConsultationFee)
+                    .sum();
+
+            revenueVO.setAllOrderCount(allOrderCount);
+            revenueVO.setAllRevenue(allRevenue-consultationFee);
+
+
+            // 查询进行中订单的统计(orderStatus = 2 且已支付)
+            LambdaQueryWrapper<LawyerConsultationOrder> inProgressWrapper = new LambdaQueryWrapper<>();
+            inProgressWrapper.eq(LawyerConsultationOrder::getLawyerUserId, lawyerConsultationOrderVO.getLawyerUserId())
+                    .eq(LawyerConsultationOrder::getOrderStatus, LawyerStatusEnum.INPROGRESS.getStatus()) // 2:进行中
+                    .eq(LawyerConsultationOrder::getPaymentStatus, 1) // 已支付
+                    .eq(LawyerConsultationOrder::getDeleteFlag, 0);
+            
+            // 添加时间段筛选条件(根据支付时间)
+            if (lawyerConsultationOrderVO.getStartTimeNew() != null) {
+                inProgressWrapper.ge(LawyerConsultationOrder::getPaymentTime, lawyerConsultationOrderVO.getStartTimeNew());
+            }
+            if (lawyerConsultationOrderVO.getEndTimeNew()  != null) {
+                inProgressWrapper.le(LawyerConsultationOrder::getPaymentTime, lawyerConsultationOrderVO.getEndTimeNew() );
+            }
+
+            List<LawyerConsultationOrder> inProgressOrders = consultationOrderMapper.selectList(inProgressWrapper);
+            long inProgressOrderCount = inProgressOrders.size();
+
+            long inProgressRevenue = inProgressOrders.stream()
+                    .filter(order -> order.getOrderAmount() != null)
+                    .mapToLong(LawyerConsultationOrder::getOrderAmount)
+                    .sum();
+
+            long inProgressFee = inProgressOrders.stream()
+                    .filter(order -> order.getConsultationFee() != null)
+                    .mapToLong(LawyerConsultationOrder::getConsultationFee)
+                    .sum();
+
+            revenueVO.setInProgressOrderCount(inProgressOrderCount);
+            revenueVO.setInProgressRevenue(inProgressRevenue-inProgressFee);
+
+
+            // 查询已完成订单的统计(orderStatus = 3 且已支付)
+            LambdaQueryWrapper<LawyerConsultationOrder> completedWrapper = new LambdaQueryWrapper<>();
+            completedWrapper.eq(LawyerConsultationOrder::getLawyerUserId, lawyerConsultationOrderVO.getLawyerUserId())
+                    .eq(LawyerConsultationOrder::getOrderStatus, LawyerStatusEnum.COMPLETE.getStatus()) // 3:已完成
+                    .eq(LawyerConsultationOrder::getPaymentStatus, 1) // 已支付
+                    .eq(LawyerConsultationOrder::getDeleteFlag, 0);
+            
+            // 添加时间段筛选条件(根据支付时间)
+            if (lawyerConsultationOrderVO.getStartTimeNew() != null) {
+                completedWrapper.ge(LawyerConsultationOrder::getPaymentTime, lawyerConsultationOrderVO.getStartTimeNew());
+            }
+            if (lawyerConsultationOrderVO.getEndTimeNew()  != null) {
+                completedWrapper.le(LawyerConsultationOrder::getPaymentTime, lawyerConsultationOrderVO.getEndTimeNew() );
+            }
+
+            List<LawyerConsultationOrder> completedOrders = consultationOrderMapper.selectList(completedWrapper);
+            long completedOrderCount = completedOrders.size();
+            long completedRevenue = completedOrders.stream()
+                    .filter(order -> order.getOrderAmount() != null)
+                    .mapToLong(LawyerConsultationOrder::getOrderAmount)
+                    .sum();
+
+            long completedFee = completedOrders.stream()
+                    .filter(order -> order.getConsultationFee() != null)
+                    .mapToLong(LawyerConsultationOrder::getConsultationFee)
+                    .sum();
+
+            revenueVO.setCompletedOrderCount(completedOrderCount);
+            revenueVO.setCompletedRevenue(completedRevenue-completedFee);
+
+            // 构建返回结果
+            Map<String, Object> allRevenueMap = new HashMap<>();
+            allRevenueMap.put("name", "全部收益");
+            allRevenueMap.put("orderCount", revenueVO.getAllOrderCount());
+            allRevenueMap.put("totalRevenue", revenueVO.getAllRevenue());
+            resultMap.put("allRevenue", allRevenueMap);
+
+            Map<String, Object> inProgressMap = new HashMap<>();
+            inProgressMap.put("name", "进行中");
+            inProgressMap.put("orderCount", revenueVO.getInProgressOrderCount());
+            inProgressMap.put("totalRevenue", revenueVO.getInProgressRevenue());
+            resultMap.put("inProgress", inProgressMap);
+
+            Map<String, Object> completedMap = new HashMap<>();
+            completedMap.put("name", "已完成");
+            completedMap.put("orderCount", revenueVO.getCompletedOrderCount());
+            completedMap.put("totalRevenue", revenueVO.getCompletedRevenue());
+            resultMap.put("completed", completedMap);
+
+            log.info("查询订单收益成功,律师ID={}, 全部订单数={}, 全部收益={}, 进行中订单数={}, 进行中收益={}, 已完成订单数={}, 已完成收益={}",
+                    lawyerConsultationOrderVO.getLawyerUserId(), allOrderCount, allRevenue, inProgressOrderCount, inProgressRevenue,
+                    completedOrderCount, completedRevenue);
+
+            return R.data(resultMap);
+
+        } catch (Exception e) {
+            log.error("查询订单收益失败,律师ID={}", lawyerConsultationOrderVO.getLawyerUserId(), e);
+            return R.fail("查询订单收益失败:" + e.getMessage());
+        }
+    }
+
+    /**
+     * 用户申请退款
+     * <p>
+     * 申请退款前会进行以下校验和处理:
+     * 1. 参数校验:用户ID和订单ID不能为空
+     * 2. 订单存在性校验:订单必须存在
+     * 3. 订单归属校验:订单必须属于该用户
+     * 4. 订单状态校验:只有进行中(2)和已完成(3)状态的订单可以申请退款
+     * 5. 退款状态校验:已申请退款的订单不允许重复申请
+     * 6. 更新订单的申请退款状态为已申请(1),并更新退款申请时间
+     * </p>
+     *
+     * @param clientUserId 客户端用户ID
+     * @param orderId      订单ID
+     * @return 申请退款结果
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public R<Boolean> applyRefund(String clientUserId, String orderId, String applyRefundReason) {
+        log.info("开始申请退款,用户ID={}, 订单ID={}", clientUserId, orderId);
+
+        // 1. 参数校验
+        if (!StringUtils.hasText(clientUserId)) {
+            log.warn("申请退款失败:用户ID为空或无效,clientUserId={}", clientUserId);
+            return R.fail("用户ID不能为空");
+        }
+
+        if (!StringUtils.hasText(orderId)) {
+            log.warn("申请退款失败:订单ID为空或无效,orderId={}", orderId);
+            return R.fail("订单ID不能为空");
+        }
+
+        // 2. 查询订单信息
+        LawyerConsultationOrder order = consultationOrderMapper.selectById(orderId);
+        if (order == null) {
+            log.warn("申请退款失败:订单不存在,订单ID={}", orderId);
+            return R.fail("订单不存在");
+        }
+
+        // 3. 订单归属校验:订单必须属于该用户
+        if (!clientUserId.equals(order.getClientUserId().toString())) {
+            log.warn("申请退款失败:订单不属于该用户,订单ID={}, 订单用户ID={}, 请求用户ID={}",
+                    orderId, order.getClientUserId(), clientUserId);
+            return R.fail("订单不属于该用户,无法申请退款");
+        }
+
+        // 4. 订单状态校验:只有进行中(2)和已完成(3)状态的订单可以申请退款
+        Integer orderStatus = order.getOrderStatus();
+        Integer inProgressStatus = LawyerStatusEnum.INPROGRESS.getStatus(); // 2:进行中
+        Integer completeStatus = LawyerStatusEnum.COMPLETE.getStatus(); // 3:已完成
+
+        if (!inProgressStatus.equals(orderStatus) && !completeStatus.equals(orderStatus)) {
+            log.warn("申请退款失败:订单状态不允许申请退款,订单ID={}, 订单编号={}, 订单状态={}",
+                    orderId, order.getOrderNumber(), orderStatus);
+            return R.fail("只有进行中或已完成的订单可以申请退款");
+        }
+
+        // 5. 退款状态校验:已申请退款的订单不允许重复申请
+        String applyRefundStatus = order.getApplyRefundStatus();
+        String appliedStatus = "1"; // 1:已申请
+        String lawyerAgreedStatus = "3"; // 3:律师已同意
+        if (appliedStatus.equals(applyRefundStatus) || lawyerAgreedStatus.equals(applyRefundStatus)) {
+            log.warn("申请退款失败:订单已申请退款,不允许重复申请,订单ID={}, 订单编号={}, 退款状态={}",
+                    orderId, order.getOrderNumber(), applyRefundStatus);
+            return R.fail("订单已申请退款,不允许重复申请");
+        }
+
+        // 6. 更新订单的申请退款状态为已申请(1),并更新退款申请时间
+        LambdaUpdateWrapper<LawyerConsultationOrder> updateWrapper = new LambdaUpdateWrapper<>();
+        updateWrapper.eq(LawyerConsultationOrder::getId, orderId)
+                .set(LawyerConsultationOrder::getApplyRefundStatus, appliedStatus)
+                .set(LawyerConsultationOrder::getApplyRefundTime, new Date())
+                .set(LawyerConsultationOrder::getUpdatedTime, new Date()).set(LawyerConsultationOrder::getApplyRefundReason, applyRefundReason);
+
+        int updateCount = consultationOrderMapper.update(null, updateWrapper);
+        boolean success = updateCount > 0;
+
+        // 7. 记录操作结果并发送通知
+        if (success) {
+            //设置订单超时退款时间
+            orderExpirationService.setOrderRefundTimeout(order.getOrderNumber(), 60 * Long.parseLong(coefficients));
+
+            log.info("申请退款成功,订单ID={}, 订单编号={}, 订单状态={}, 退款状态=已申请",
+                    orderId, order.getOrderNumber(), orderStatus);
+            
+            // 发送退款申请通知给申请人
+            sendRefundApplyNotice(order, clientUserId);
+            
+            return R.data(true, "退款申请已提交,请等待律师处理");
+        } else {
+            log.error("申请退款失败:更新数据库失败,订单ID={}, 订单编号={}", orderId, order.getOrderNumber());
+            return R.fail("申请退款失败,请稍后重试");
+        }
+    }
+
+    /**
+     * 用户端完成订单
+     * <p>
+     * 完成订单前会进行以下校验和处理:
+     * 1. 参数校验:用户ID和订单ID不能为空
+     * 2. 订单存在性校验:订单必须存在
+     * 3. 订单归属校验:订单必须属于该用户
+     * 4. 订单状态校验:只有进行中(2)状态的订单可以完成
+     * 5. 更新订单状态为已完成(3),并更新完成时间
+     * </p>
+     *
+     * @param clientUserId 客户端用户ID
+     * @param orderId      订单ID
+     * @return 完成订单结果
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public R<Boolean> completeOrder(String clientUserId, String orderId) {
+        log.info("开始完成订单,用户ID={}, 订单ID={}", clientUserId, orderId);
+
+        // 1. 参数校验
+        if (StringUtils.isEmpty(clientUserId)) {
+            log.warn("完成订单失败:用户ID为空或无效,clientUserId={}", clientUserId);
+            return R.fail("用户ID不能为空");
+        }
+
+        if (StringUtils.isEmpty(orderId)) {
+            log.warn("完成订单失败:订单ID为空或无效,orderId={}", orderId);
+            return R.fail("订单ID不能为空");
+        }
+
+        // 2. 查询订单信息
+        LawyerConsultationOrder order = consultationOrderMapper.selectById(orderId);
+        if (order == null) {
+            log.warn("完成订单失败:订单不存在,订单ID={}", orderId);
+            return R.fail("订单不存在");
+        }
+
+        // 3. 订单归属校验:订单必须属于该用户
+        if (!clientUserId.equals(order.getClientUserId().toString())) {
+            log.warn("完成订单失败:订单不属于该用户,订单ID={}, 订单用户ID={}, 请求用户ID={}",
+                    orderId, order.getClientUserId(), clientUserId);
+            return R.fail("订单不属于该用户,无法完成订单");
+        }
+
+        // 4. 订单状态校验:只有进行中(2)状态的订单可以完成
+        Integer orderStatus = order.getOrderStatus();
+        Integer inProgressStatus = LawyerStatusEnum.INPROGRESS.getStatus(); // 2:进行中
+        Integer completeStatus = LawyerStatusEnum.COMPLETE.getStatus(); // 3:已完成
+
+        if (!inProgressStatus.equals(orderStatus)) {
+            log.warn("完成订单失败:订单状态不允许完成,订单ID={}, 订单编号={}, 订单状态={}",
+                    orderId, order.getOrderNumber(), orderStatus);
+            return R.fail("只有进行中的订单可以完成");
+        }
+
+        // 5. 更新订单状态为已完成(3),并更新咨询结束时间和更新时间
+        LambdaUpdateWrapper<LawyerConsultationOrder> updateWrapper = new LambdaUpdateWrapper<>();
+        Date now = new Date();
+        updateWrapper.eq(LawyerConsultationOrder::getId, orderId)
+                .set(LawyerConsultationOrder::getOrderStatus, completeStatus)
+                .set(LawyerConsultationOrder::getEndTime, now)
+                .set(LawyerConsultationOrder::getUpdatedTime, now);
+
+        int updateCount = consultationOrderMapper.update(null, updateWrapper);
+        boolean success = updateCount > 0;
+
+        // 6. 记录操作结果
+        if (success) {
+            log.info("完成订单成功,订单ID={}, 订单编号={}, 原状态={}, 新状态=已完成",
+                    orderId, order.getOrderNumber(), orderStatus);
+            return R.data(true, "订单已完成");
+        } else {
+            log.error("完成订单失败:更新数据库失败,订单ID={}, 订单编号={}", orderId, order.getOrderNumber());
+            return R.fail("完成订单失败,请稍后重试");
+        }
+    }
+
+    /**
+     * 获取申请退款订单详情
+     * <p>
+     * 根据订单ID查询订单信息,用于退款申请详情展示
+     * </p>
+     *
+     * @param orderId 订单ID,不能为空
+     * @return 订单信息,如果订单ID为空或订单不存在则返回null
+     */
+    @Override
+    public LawyerConsultationOrder getApplyRefundDetail(String orderId) {
+        log.info("开始查询申请退款订单详情,订单ID={}", orderId);
+
+        // 参数校验:订单ID不能为空
+        if (!StringUtils.hasText(orderId)) {
+            log.warn("查询申请退款订单详情失败:订单ID为空");
+            return null;
+        }
+
+        try {
+            // 查询订单信息
+            LawyerConsultationOrder order = consultationOrderMapper.selectById(orderId);
+            if (order == null) {
+                log.warn("查询申请退款订单详情失败:订单不存在,订单ID={}", orderId);
+                return null;
+            }
+
+            log.info("查询申请退款订单详情成功,订单ID={}, 订单编号={}", orderId, order.getOrderNumber());
+            return order;
+        } catch (Exception e) {
+            log.error("查询申请退款订单详情异常,订单ID={}, 异常信息={}", orderId, e.getMessage(), e);
+            return null;
+        }
+    }
+
+    /**
+     * 格式化收益金额(分转元)
+     *
+     * @param revenueInCents 收益金额(单位:分)
+     * @return 格式化后的金额字符串(单位:元)
+     */
+    private String formatRevenue(long revenueInCents) {
+        if (revenueInCents == 0) {
+            return "0";
+        }
+        BigDecimal revenue = new BigDecimal(revenueInCents);
+        BigDecimal revenueInYuan = revenue.divide(new BigDecimal(100), 2, RoundingMode.HALF_UP);
+        return revenueInYuan.toString();
+    }
+
+    /**
+     * 发送退款申请通知给申请人
+     *
+     * @param order        订单对象
+     * @param clientUserId 客户端用户ID
+     */
+    private void sendRefundApplyNotice(LawyerConsultationOrder order, String clientUserId) {
+        try {
+            LifeNotice lifeNotice = createRefundApplyNotice(order, clientUserId);
+            if (lifeNotice == null) {
+                log.warn("生成退款申请通知失败,订单ID={}, 用户ID={}", order.getId(), clientUserId);
+                return;
+            }
+
+            int noticeResult = lifeNoticeMapper.insert(lifeNotice);
+            if (noticeResult <= 0) {
+                log.warn("保存退款申请通知失败,订单ID={}, 用户ID={}", order.getId(), clientUserId);
+                return;
+            }
+
+            // 发送WebSocket消息
+            WebSocketVo webSocketVo = buildWebSocketVo(lifeNotice);
+            webSocketProcess.sendMessage(lifeNotice.getReceiverId(),
+                    JSONObject.from(webSocketVo).toJSONString());
+
+            log.info("退款申请通知发送成功,接收人ID={}, 订单编号={}", lifeNotice.getReceiverId(), order.getOrderNumber());
+
+        } catch (Exception e) {
+            log.error("发送退款申请通知异常,订单ID={}, 用户ID={}, 异常信息={}",
+                    order.getId(), clientUserId, e.getMessage(), e);
+        }
+    }
+
+    /**
+     * 创建退款申请通知对象
+     *
+     * @param order        订单对象
+     * @param clientUserId 客户端用户ID
+     * @return 通知对象,如果生成失败返回null
+     */
+    private LifeNotice createRefundApplyNotice(LawyerConsultationOrder order, String clientUserId) {
+        if (order == null || clientUserId == null) {
+            log.warn("创建退款申请通知失败,订单或用户ID为空");
+            return null;
+        }
+
+        try {
+            LifeNotice lifeNotice = new LifeNotice();
+            lifeNotice.setSenderId(SYSTEM_SENDER_ID);
+            lifeNotice.setBusinessId(order.getId());
+            lifeNotice.setTitle("申请退款通知");
+
+            // 获取申请人接收ID
+            String receiverId = getClientReceiverId(clientUserId);
+            if (org.apache.commons.lang3.StringUtils.isEmpty(receiverId)) {
+                log.warn("获取申请人接收ID失败,用户ID={}", clientUserId);
+                return null;
+            }
+            lifeNotice.setReceiverId(receiverId);
+
+            // 构建通知消息内容
+            String orderNumber = order.getOrderNumber();
+            String message = String.format("您的编号为%s的订单,申请退款的信息已提交给律师,等待律师同意。律师同意或48小时未处理,系统会将订单金额原路返还,请注意查收。", 
+                    orderNumber != null ? orderNumber : "");
+
+            JSONObject jsonObject = new JSONObject();
+            jsonObject.put("title", "申请退款通知");
+            jsonObject.put("message", message);
+            lifeNotice.setContext(jsonObject.toJSONString());
+            lifeNotice.setNoticeType(1);
+
+            return lifeNotice;
+
+        } catch (Exception e) {
+            log.error("创建退款申请通知异常,订单ID={}, 用户ID={}, 异常信息={}",
+                    order.getId(), clientUserId, e.getMessage(), e);
+            return null;
+        }
+    }
+
+    /**
+     * 获取客户端用户接收ID
+     *
+     * @param clientUserId 客户端用户ID
+     * @return 接收人ID,格式:user_ + 手机号
+     */
+    private String getClientReceiverId(String clientUserId) {
+        try {
+            LifeUser lifeUser = lifeUserMapper.selectById(clientUserId);
+            if (lifeUser != null && org.apache.commons.lang3.StringUtils.isNotEmpty(lifeUser.getUserPhone())) {
+                return "user_" + lifeUser.getUserPhone();
+            }
+        } catch (Exception e) {
+            log.error("获取客户端用户手机号异常,用户ID={}, 异常信息={}", clientUserId, e.getMessage(), e);
+        }
+
+        log.warn("获取客户端用户手机号失败,用户ID={}", clientUserId);
+        return null;
+    }
+
+    /**
+     * 构建WebSocket消息对象
+     *
+     * @param lifeNotice 通知对象
+     * @return WebSocketVo对象
+     */
+    private WebSocketVo buildWebSocketVo(LifeNotice lifeNotice) {
+        WebSocketVo webSocketVo = new WebSocketVo();
+        webSocketVo.setSenderId(SYSTEM_SENDER_ID);
+        webSocketVo.setReceiverId(lifeNotice.getReceiverId());
+        webSocketVo.setCategory("notice");
+        webSocketVo.setNoticeType("1");
+        webSocketVo.setIsRead(0);
+        webSocketVo.setText(JSONObject.from(lifeNotice).toJSONString());
+        return webSocketVo;
+    }
+
+
+}
+

+ 443 - 0
alien-lawyer/src/main/java/shop/alien/lawyer/service/impl/OrderExpirationServiceImpl.java

@@ -0,0 +1,443 @@
+package shop.alien.lawyer.service.impl;
+
+import com.alibaba.fastjson2.JSONObject;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.CommandLineRunner;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import shop.alien.entity.result.R;
+import shop.alien.entity.store.LawyerConsultationOrder;
+import shop.alien.entity.store.LifeNotice;
+import shop.alien.entity.store.vo.WebSocketVo;
+import shop.alien.lawyer.config.BaseRedisService;
+import shop.alien.lawyer.config.WebSocketProcess;
+import shop.alien.lawyer.controller.AliController;
+import shop.alien.lawyer.feign.AlienStoreFeign;
+import shop.alien.lawyer.listener.RedisKeyExpirationHandler;
+import shop.alien.lawyer.service.OrderExpirationService;
+import shop.alien.mapper.LawyerConsultationOrderMapper;
+import shop.alien.mapper.LifeNoticeMapper;
+import shop.alien.mapper.LifeUserMapper;
+import shop.alien.util.common.DateUtils;
+import shop.alien.util.common.constant.PaymentEnum;
+
+
+import javax.annotation.PostConstruct;
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 訂單過期處理服務實現類
+ * 
+ * @author system
+ * @since 2025-01-XX
+ */
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class OrderExpirationServiceImpl implements OrderExpirationService, CommandLineRunner {
+
+    @Value("${order.coefficient}")
+    private String coefficient;
+
+    @Value("${order.coefficients}")
+    private String coefficients;
+
+    private final BaseRedisService redisService;
+    private final RedisKeyExpirationHandler expirationHandler;
+    private final LawyerConsultationOrderMapper orderMapper;
+    private final AliController aliController;
+    private final LifeNoticeMapper lifeNoticeMapper;
+    private final LifeUserMapper lifeUserMapper;
+    private final WebSocketProcess webSocketProcess;
+    private final AlienStoreFeign alienStoreFeign;
+
+    /**
+     * Redis key前綴:訂單支付超時
+     */
+    private static final String ORDER_PAYMENT_TIMEOUT_PREFIX = "lawyer:order:payment:timeout:";
+
+    /**
+     * Redis key前綴:訂單接单超時
+     */
+    private static final String ORDER_ACCEPT_TIMEOUT_PREFIX = "lawyer:order:accept:timeout:";
+
+    /**
+     * Redis key前綴:訂單退款超時
+     */
+    private static final String ORDER_REFUND_TIMEOUT_PREFIX = "lawyer:order:refund:timeout:";
+
+    /**
+     * 默認超時時間:30分鐘
+     */
+    private static final long DEFAULT_TIMEOUT_SECONDS = 30 * 60;
+
+    /**
+     * 系统发送者ID
+     */
+    private static final String SYSTEM_SENDER_ID = "system";
+
+    /**
+     * 初始化時註冊訂單支付超時處理器
+     */
+    @PostConstruct
+    public void init() {
+        // 註冊訂單支付超時處理器
+        expirationHandler.registerHandler(ORDER_PAYMENT_TIMEOUT_PREFIX, this::handleExpiredOrderKey);
+        log.info("訂單支付超時處理器註冊完成,前綴: {}", ORDER_PAYMENT_TIMEOUT_PREFIX);
+
+        // 註冊订单待接单超时處理器
+        expirationHandler.registerHandler(ORDER_ACCEPT_TIMEOUT_PREFIX, this::handleRefundOrderKey);
+        log.info("訂單接单超時處理器註冊完成,前綴: {}", ORDER_ACCEPT_TIMEOUT_PREFIX);
+
+        // 註冊訂單退款超時處理器
+        expirationHandler.registerHandler(ORDER_REFUND_TIMEOUT_PREFIX, this::handleRefundOrderKey);
+        log.info("訂單退款超時處理器註冊完成,前綴: {}", ORDER_REFUND_TIMEOUT_PREFIX);
+    }
+
+    @Override
+    public void run(String... args) {
+        log.info("OrderExpirationService 初始化完成");
+    }
+
+    /**
+     * 處理過期的訂單key
+     * 
+     * @param expiredKey 過期的key,格式:order:payment:timeout:{orderId}
+     */
+    private void handleExpiredOrderKey(String expiredKey) {
+        try {
+            // 從key中提取訂單ID
+            String orderNo = expiredKey.replace(ORDER_PAYMENT_TIMEOUT_PREFIX, "");
+            
+            log.info("檢測到訂單支付超時,訂單no: {}", orderNo);
+            
+            // 處理訂單支付超時
+            handleOrderPaymentTimeout(orderNo);
+        } catch (Exception e) {
+            log.error("處理過期訂單key失敗,key: {}", expiredKey, e);
+        }
+    }
+
+    /**
+     * 處理過期的訂單key
+     *
+     * @param expiredKey 過期的key,格式:order:payment:timeout:{orderId}
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void handleRefundOrderKey(String expiredKey) {
+        try {
+            // 從key中提取訂單ID
+            String orderNo = "";
+            String refundReason = "";
+            String title = "";
+            String message = "";
+            //律师未接单退款
+            if (expiredKey.contains(ORDER_ACCEPT_TIMEOUT_PREFIX)) {
+                orderNo = expiredKey.replace(ORDER_ACCEPT_TIMEOUT_PREFIX, "");
+                refundReason = "律师未接单超時退款";
+                title = "未接单通知";
+                message = "您的编号为" + orderNo + "的订单已到48小时未被接单,订单金额将在1-3个工作日原路返还,请注意查收。";
+
+                // 查詢訂單
+                LambdaQueryWrapper<LawyerConsultationOrder> queryWrapper = new LambdaQueryWrapper<>();
+                queryWrapper.eq(LawyerConsultationOrder::getOrderNumber, orderNo).last("LIMIT 1");
+                LawyerConsultationOrder order = orderMapper.selectOne(queryWrapper);
+
+                log.info("檢測到有訂單需要退款,訂單no: {}", orderNo);
+
+                //通知
+                LifeNotice lifeNotice = buildUserLifeNotice(order, title, message);
+                WebSocketVo webSocketVo = buildWebSocketVo(lifeNotice);
+
+                lifeNoticeMapper.insert(lifeNotice);
+                webSocketProcess.sendMessage(lifeNotice.getReceiverId(), JSONObject.from(webSocketVo).toJSONString());
+
+                log.info("系统通知发送成功,訂單no: {}", orderNo);
+
+                // 處理訂單退款
+                //aliController.processRefund(order.getAlipayNo(),  new BigDecimal(order.getOrderAmount()).divide(new BigDecimal(100), 2, RoundingMode.HALF_UP).toString(), refundReason,"");
+
+                // 微信/支付宝退款
+                Map<String, String> paramMap = new HashMap<>();
+                refundParam(paramMap, order, refundReason);
+                R refunds = alienStoreFeign.paymentRefunds(paramMap);
+
+                if (refunds.getCode() == 200) {
+                    log.info("訂單退款成功,訂單no: {}", orderNo);
+
+                    //退款到账通知
+                    LifeNotice lifeNotice2 = buildUserLifeNotice(order, "退款到账通知", "您的编号为" + orderNo + "的订单,订单金额已原路返还至您的支付渠道,请查收。");
+                    WebSocketVo webSocketVo2 = buildWebSocketVo(lifeNotice2);
+                    lifeNotice2.setCreatedTime(DateUtils.calcMinute(new Date(), 2));
+
+                    lifeNoticeMapper.insert(lifeNotice2);
+                    webSocketProcess.sendMessage(lifeNotice2.getReceiverId(), JSONObject.from(webSocketVo2).toJSONString());
+
+                    log.info("系统通知发送成功,訂單no: {}", orderNo);
+
+                    LawyerConsultationOrder update = new LawyerConsultationOrder();
+                    update.setId(order.getId());
+                    update.setOrderStatus(5);
+                    update.setApplyRefundTime(new Date());
+                    orderMapper.updateById( update);
+                }
+            }
+            //申请退款 律师没处理
+            if (expiredKey.contains(ORDER_REFUND_TIMEOUT_PREFIX)) {
+                orderNo = expiredKey.replace(ORDER_REFUND_TIMEOUT_PREFIX, "");
+                refundReason = "申请退款超时后退款";
+
+                // 查詢訂單
+                LambdaQueryWrapper<LawyerConsultationOrder> queryWrapper = new LambdaQueryWrapper<>();
+                queryWrapper.eq(LawyerConsultationOrder::getOrderNumber, orderNo).last("LIMIT 1");
+                LawyerConsultationOrder order = orderMapper.selectOne(queryWrapper);
+
+                log.info("檢測到有訂單需要退款,訂單no: {}", orderNo);
+
+                //同意退款通知
+                LifeNotice lifeNotice = buildUserLifeNotice(order, "同意退款通知", "您的编号为" + orderNo + "的订单,律师已同意您的退款申请,订单金额将在1-3个工作日原路返还,请注意查收。");
+                WebSocketVo webSocketVo = buildWebSocketVo(lifeNotice);
+
+                lifeNoticeMapper.insert(lifeNotice);
+                webSocketProcess.sendMessage(lifeNotice.getReceiverId(), JSONObject.from(webSocketVo).toJSONString());
+
+                // 處理訂單退款
+                //String refundResult = aliController.processRefund(order.getAlipayNo(), new BigDecimal(order.getOrderAmount()).divide(new BigDecimal(100), 2, RoundingMode.HALF_UP).toString(), refundReason, "");
+
+                // 微信/支付宝退款
+                Map<String, String> paramMap = new HashMap<>();
+                refundParam(paramMap, order, refundReason);
+                R refunds = alienStoreFeign.paymentRefunds(paramMap);
+
+                if (refunds.getCode() == 200) {
+                    log.info("訂單退款成功,訂單no: {}", orderNo);
+
+                    //退款到账通知
+                    LifeNotice lifeNotice2 = buildUserLifeNotice(order, "退款到账通知", "您的编号为" + orderNo + "的订单,订单金额已原路返还至您的支付渠道,请查收。");
+                    WebSocketVo webSocketVo2 = buildWebSocketVo(lifeNotice2);
+                    lifeNotice2.setCreatedTime(DateUtils.calcMinute(lifeNotice2.getCreatedTime(), 2));
+
+                    lifeNoticeMapper.insert(lifeNotice2);
+                    webSocketProcess.sendMessage(lifeNotice2.getReceiverId(), JSONObject.from(webSocketVo2).toJSONString());
+
+                    log.info("系统通知发送成功,訂單no: {}", orderNo);
+
+                    LawyerConsultationOrder update = new LawyerConsultationOrder();
+                    update.setId(order.getId());
+                    update.setOrderStatus(5);
+                    update.setApplyRefundStatus("4");
+                    update.setApplyRefundTime(new Date());
+                    orderMapper.updateById(update);
+                }
+            }
+        } catch (Exception e) {
+            log.error("處理有訂單需要退款key失敗,key: {}", expiredKey, e);
+        }
+    }
+
+    private void refundParam(Map<String,String> paramMap,LawyerConsultationOrder order,String refundReason){
+        //支付宝
+        if ("1".equals(order.getPayType())) {
+            paramMap.put("payType", PaymentEnum.ALIPAY.getType());
+            paramMap.put("outTradeNo", order.getAlipayNo());
+            paramMap.put("refundReason", refundReason);
+            paramMap.put("refundAmount", new BigDecimal(order.getOrderAmount()).divide(new BigDecimal(100), 2, RoundingMode.HALF_UP).toString());
+        }
+        //微信
+        if ("2".equals(order.getPayType())) {
+            paramMap.put("payType", PaymentEnum.WECHAT_PAY.getType());
+            paramMap.put("outTradeNo", order.getAlipayNo());
+            paramMap.put("reason", refundReason);
+            paramMap.put("refundAmount", order.getOrderAmount().toString());
+            paramMap.put("totalAmount", order.getOrderAmount().toString());
+        }
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void handleOrderPaymentTimeout(String orderNum) {
+        log.info("開始處理訂單支付超時,訂單no: {}", orderNum);
+        
+        try {
+            // 查詢訂單
+            LambdaQueryWrapper<LawyerConsultationOrder> queryWrapper = new LambdaQueryWrapper<>();
+            queryWrapper.eq(LawyerConsultationOrder::getOrderNumber, orderNum).last("LIMIT 1");
+            LawyerConsultationOrder order = orderMapper.selectOne(queryWrapper);
+            if (order == null) {
+                log.warn("訂單不存在,訂單no: {}", orderNum);
+                return;
+            }
+
+            // 只處理待支付狀態的訂單
+            if (order.getOrderStatus() != null && order.getOrderStatus() == 0) {
+                log.info("訂單處於待支付狀態,開始取消訂單,訂單no: {}", orderNum);
+
+                LawyerConsultationOrder lawyerConsultationOrder = new LawyerConsultationOrder();
+                // 更新訂單狀態為已取消
+                lawyerConsultationOrder.setOrderStatus(4); // 4:已取消
+                lawyerConsultationOrder.setId(order.getId());
+                lawyerConsultationOrder.setUpdatedTime(new java.util.Date());
+                
+                int updated = orderMapper.updateById(lawyerConsultationOrder);
+                if (updated > 0) {
+                    log.info("訂單支付超時,已自動取消訂單,訂單no: {}", orderNum);
+                } else {
+                    log.error("取消訂單失敗,訂單no: {}", orderNum);
+                }
+            } else {
+                log.info("訂單狀態不是待支付,無需處理,訂單no: {}, 當前狀態: {}", orderNum, order.getOrderStatus());
+            }
+        } catch (Exception e) {
+            log.error("處理訂單支付超時失敗,訂單no: {}", orderNum, e);
+            throw e;
+        }
+    }
+
+    @Override
+    public void setOrderPaymentTimeout(String orderNumber, long timeoutSeconds) {
+        if (orderNumber == null) {
+            log.warn("訂單ID為null,無法設置支付超時監聽");
+            return;
+        }
+        
+        String key = ORDER_PAYMENT_TIMEOUT_PREFIX + orderNumber;
+        long timeout = timeoutSeconds > 0 ? timeoutSeconds : 60 * Long.parseLong(coefficient);
+        
+        // 設置Redis key,帶過期時間
+        // 當key過期時,會觸發RedisKeyExpirationListener
+        redisService.setString(key, String.valueOf(orderNumber), timeout);
+        
+        log.info("設置訂單支付超時監聽,訂單NO: {}, 超時時間: {}秒, key: {}", orderNumber, timeout, key);
+    }
+
+    @Override
+    public void setOrderAcceptTimeout(String orderNumber, long timeoutSeconds) {
+        if (orderNumber == null) {
+            log.warn("訂單ID為null,無法設置支付超時監聽");
+            return;
+        }
+
+        String key = ORDER_ACCEPT_TIMEOUT_PREFIX + orderNumber;
+        long timeout = timeoutSeconds > 0 ? timeoutSeconds : 60 * Long.parseLong(coefficients);
+
+        // 設置Redis key,帶過期時間
+        // 當key過期時,會觸發RedisKeyExpirationListener
+        redisService.setString(key, String.valueOf(orderNumber), timeout);
+
+        log.info("設置訂單接单超時監聽,訂單NO: {}, 超時時間: {}秒, key: {}", orderNumber, timeout, key);
+    }
+
+    @Override
+    public void setOrderRefundTimeout(String orderNumber, long timeoutSeconds) {
+        if (orderNumber == null) {
+            log.warn("訂單ID為null,無法設置支付超時監聽");
+            return;
+        }
+
+        String key = ORDER_REFUND_TIMEOUT_PREFIX + orderNumber;
+        long timeout = timeoutSeconds > 0 ? timeoutSeconds : 60 * Long.parseLong(coefficients);
+
+        // 設置Redis key,帶過期時間
+        // 當key過期時,會觸發RedisKeyExpirationListener
+        redisService.setString(key, String.valueOf(orderNumber), timeout);
+
+        log.info("設置訂單退款超時監聽,訂單NO: {}, 超時時間: {}秒, key: {}", orderNumber, timeout, key);
+    }
+
+    /**
+     * 取消訂單支付超時監聽(當訂單已支付時調用)
+     * 
+     * @param orderNumber 訂單NO
+     */
+    public void cancelOrderPaymentTimeout(String orderNumber) {
+        if (orderNumber == null) {
+            return;
+        }
+        
+        String key = ORDER_PAYMENT_TIMEOUT_PREFIX + orderNumber;
+        redisService.delete(key);
+        
+        log.info("取消訂單支付超時監聽,訂單ID: {}, key: {}", orderNumber, key);
+    }
+
+    @Override
+    public void cancelOrderAcceptTimeout(String orderNumber) {
+        if (orderNumber == null) {
+            return;
+        }
+
+        String key = ORDER_ACCEPT_TIMEOUT_PREFIX + orderNumber;
+        redisService.delete(key);
+
+        log.info("取消訂單接单超時監聽,訂單ID: {}, key: {}", orderNumber, key);
+    }
+
+    @Override
+    public void cancelOrderRefundTimeout(String orderNumber) {
+        if (orderNumber == null) {
+            return;
+        }
+
+        String key = ORDER_REFUND_TIMEOUT_PREFIX + orderNumber;
+        redisService.delete(key);
+
+        log.info("取消訂單退款超時監聽,訂單ID: {}, key: {}", orderNumber, key);
+    }
+
+    /**
+     * 构建WebSocket消息对象
+     *
+     * @param lifeNotice 通知对象
+     * @return WebSocketVo对象
+     */
+    private WebSocketVo buildWebSocketVo(LifeNotice lifeNotice) {
+        WebSocketVo webSocketVo = new WebSocketVo();
+        webSocketVo.setSenderId(SYSTEM_SENDER_ID);
+        webSocketVo.setReceiverId(lifeNotice.getReceiverId());
+        webSocketVo.setCategory("notice");
+        webSocketVo.setNoticeType("1");
+        webSocketVo.setIsRead(0);
+        webSocketVo.setText(JSONObject.from(lifeNotice).toJSONString());
+        return webSocketVo;
+    }
+
+    private LifeNotice buildLifeNotice(LawyerConsultationOrder lawyerConsultationOrder,String title,String message){
+        LifeNotice lifeNotice = new LifeNotice();
+        lifeNotice.setSenderId(SYSTEM_SENDER_ID);
+        lifeNotice.setBusinessId(lawyerConsultationOrder.getId());
+        lifeNotice.setTitle(title);
+        lifeNotice.setReceiverId("lawyer_" + lifeUserMapper.selectById(lawyerConsultationOrder.getClientUserId()).getUserPhone());
+
+        JSONObject jsonObject = new JSONObject();
+        jsonObject.put("message", message);
+        lifeNotice.setContext(jsonObject.toJSONString());
+        lifeNotice.setNoticeType(1);
+
+        return lifeNotice;
+    }
+
+    private LifeNotice buildUserLifeNotice(LawyerConsultationOrder lawyerConsultationOrder,String title,String message){
+        LifeNotice lifeNotice = new LifeNotice();
+        lifeNotice.setSenderId(SYSTEM_SENDER_ID);
+        lifeNotice.setBusinessId(lawyerConsultationOrder.getId());
+        lifeNotice.setTitle(title);
+        lifeNotice.setReceiverId("user_" + lifeUserMapper.selectById(lawyerConsultationOrder.getClientUserId()).getUserPhone());
+
+        JSONObject jsonObject = new JSONObject();
+        jsonObject.put("message", message);
+        lifeNotice.setContext(jsonObject.toJSONString());
+        lifeNotice.setNoticeType(1);
+
+        return lifeNotice;
+    }
+}
+

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

@@ -0,0 +1,114 @@
+package shop.alien.lawyer.util;
+
+import com.aliyun.dysmsapi20170525.Client;
+import com.aliyun.dysmsapi20170525.models.SendSmsRequest;
+import com.aliyun.dysmsapi20170525.models.SendSmsResponse;
+import com.aliyun.teaopenapi.models.Config;
+import com.aliyun.teautil.models.RuntimeOptions;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+import shop.alien.entity.store.StoreVerificationCode;
+import shop.alien.mapper.StoreVerificationCodeMapper;
+import shop.alien.util.common.RandomCreateUtil;
+import shop.alien.lawyer.config.BaseRedisService;
+
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 阿里云验证码配置
+ *
+ * @author ssk
+ * @version 1.0
+ * @date 2024/12/12 10:17
+ */
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class AliSms {
+    private final BaseRedisService baseRedisService;
+
+    private final StoreVerificationCodeMapper storeVerificationCodeMapper;
+
+    @Value("${ali.sms.accessKeyId}")
+    private String accessKeyId;
+
+    @Value("${ali.sms.accessKeySecret}")
+    private String accessKeySecret;
+
+    @Value("${ali.sms.endPoint}")
+    private String endPoint;
+
+    @Value("${ali.sms.signName}")
+    private String signName;
+
+    @Value("${ali.sms.templateCode}")
+    private String templateCode;
+
+    /**
+     * 发送验证码
+     *
+     * @param phone 手机号
+     * @return 验证码
+     */
+    public Integer sendSms(String phone) {
+        log.info("AliSmsConfig.sendSms?phone={}", phone);
+        try {
+            // -----------------测试用手机号--------------------------------------------------------------------------------------------
+            List<String> phoneList = Arrays.asList("19999990001", "19999990002", "19999990003", "19999990004", "19999990005", "19999990006", "19999990007", "19999990008", "19999990009", "19999990010",
+                    "16666660001", "16666660002", "16666660003", "16666660004", "16666660005", "16666660006", "16666660007", "16666660008", "16666660009", "16666660010");
+            if (phoneList.contains(phone)) {
+                // 验证码发送成功,将验证码保存到redis中 设置60秒过期
+                baseRedisService.setString("verification_lawyer_"+phone,"123456",Long.valueOf(300));
+                return 123456;
+            }
+            // -----------------测试用手机号--------------------------------------------------------------------------------------------
+
+            Config config = new Config()
+                    .setEndpoint(endPoint)
+                    .setAccessKeyId(accessKeyId)
+                    .setAccessKeySecret(accessKeySecret);
+            Integer code = RandomCreateUtil.getRandomNum(100000, 999999);
+
+            // 构建发送请求
+            SendSmsRequest sendSmsRequest = new SendSmsRequest()
+                    // 设置签名
+                    .setSignName(signName)
+                    // 设置模板
+                    .setTemplateCode(templateCode)
+                    // 设置手机号为参数传入的值
+                    .setPhoneNumbers(phone)
+                    // 设置模板参数为传入的验证码
+                    .setTemplateParam("{\"code\":\"" + code + "\"}");
+            // 运行时选择,可以设置不同的属性来配置运行时环境的参数。
+            RuntimeOptions runtime = new RuntimeOptions();
+            Client client = new Client(config);
+            // 复制代码运行请自行打印 API 的返回值
+            SendSmsResponse sendSmsResponse = client.sendSmsWithOptions(sendSmsRequest, runtime);
+            if (!"OK".equals(sendSmsResponse.getBody().getCode())) {
+                return null;
+            }
+            // 验证码发送成功,将验证码保存到redis中 设置60秒过期
+            baseRedisService.setString("verification_lawyer_"+phone,code.toString(),Long.valueOf(300));
+            // 永久存储验证码
+            StoreVerificationCode entity = new StoreVerificationCode();
+            entity.setAppType(2);
+            entity.setBusinessType(7);
+            entity.setPhone(phone);
+            entity.setCode(String.valueOf(code));
+            Date now = new Date();
+            entity.setCreatedTime(now);
+            entity.setUpdatedTime(now);
+            entity.setDeleteFlag(0);
+            storeVerificationCodeMapper.insert(entity);
+            return code;
+        } catch (Exception e) {
+            log.error("AliSmsConfig.sendSms ERROR Msg={}", e.getMessage());
+            return null;
+        }
+    }
+
+}

+ 13 - 4
alien-second/src/main/java/shop/alien/second/service/impl/SecondGoodsServiceImpl.java

@@ -30,10 +30,7 @@ import shop.alien.entity.store.vo.WebSocketVo;
 import shop.alien.mapper.*;
 import shop.alien.mapper.second.*;
 import shop.alien.second.feign.AlienStoreFeign;
-import shop.alien.second.service.PlatformSecondTradeService;
-import shop.alien.second.service.RiskControlService;
-import shop.alien.second.service.SecondGoodsService;
-import shop.alien.second.service.VideoModerationService;
+import shop.alien.second.service.*;
 import shop.alien.util.common.Constants;
 import shop.alien.util.common.VideoUtils;
 import shop.alien.util.common.safe.*;
@@ -166,6 +163,8 @@ public class SecondGoodsServiceImpl extends ServiceImpl<SecondGoodsMapper, Secon
      */
     private final RiskControlService riskControlService;
 
+    private final SecondGoodsAuditService secondGoodsAuditService;
+
     /**
      * 获取商品操作记录详情(管理后台使用)
      * @param recordId 商品操作记录ID
@@ -795,6 +794,8 @@ public class SecondGoodsServiceImpl extends ServiceImpl<SecondGoodsMapper, Secon
         }
         // 审核通过后上架商品
         approveAndListGoods(goods);
+        // 开始第二轮审核
+//        boolean b = secondGoodsAuditService.performSecondRoundReview(goods, goodsDTO);
 
         // 检查用户是否在24小时内发布同类商品超过阈值
         if (!checkUserPublishSameCategoryLimit(goods)) {
@@ -1515,11 +1516,19 @@ public class SecondGoodsServiceImpl extends ServiceImpl<SecondGoodsMapper, Secon
     @Override
     public IPage<SecondGoodsVo> getCollectGoodsPage(IPage<SecondGoodsVo> page, int userId) {
         LifeUser lifeUser = lifeUserMapper.selectById(userId);
+        // 获取商品屏蔽列表
+        List<SecondGoods> shieldedGoodsList = getShieldedGoodsList(userId);
+        // 提取屏蔽商品ID
+        List<Integer> shieldedGoodsIds = shieldedGoodsList.stream()
+                .map(SecondGoods::getId)
+                .collect(Collectors.toList());
+
         QueryWrapper<SecondGoodsVo> queryWrapper = new QueryWrapper<>();
         queryWrapper
                 // 可以查看已删除的商品数据
 //                .eq("sg.delete_flag", Constants.DeleteFlag.NOT_DELETED)
                 .eq("lc.delete_flag", Constants.DeleteFlag.NOT_DELETED)
+                .notIn(CollectionUtil.isNotEmpty(shieldedGoodsIds), "sg.id", shieldedGoodsIds)
                 .eq("lc.user_id", "user_"+lifeUser.getUserPhone())
                 .orderByDesc("lc.created_time");
         return secondGoodsMapper.getCollectGoodsPage(page, queryWrapper);

+ 303 - 0
alien-store-platform/src/main/java/shop/alien/storeplatform/service/impl/StorePlatformLoginServiceImpl.java

@@ -0,0 +1,303 @@
+package shop.alien.storeplatform.service.impl;
+
+import cn.hutool.captcha.CaptchaUtil;
+import cn.hutool.captcha.LineCaptcha;
+import com.alibaba.fastjson.JSONObject;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.cloud.context.config.annotation.RefreshScope;
+import org.springframework.stereotype.Service;
+import org.springframework.util.CollectionUtils;
+import shop.alien.config.redis.BaseRedisService;
+import shop.alien.entity.result.R;
+import shop.alien.entity.store.StoreInfo;
+import shop.alien.entity.store.StoreUser;
+import shop.alien.entity.store.vo.StoreUserVo;
+import shop.alien.mapper.StoreInfoMapper;
+import shop.alien.mapper.StoreUserMapper;
+import shop.alien.storeplatform.service.StorePlatformLoginervice;
+import shop.alien.util.common.DateUtils;
+import shop.alien.util.common.JwtUtil;
+
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+import java.io.IOException;
+import java.util.*;
+
+@Slf4j
+@Service
+@RequiredArgsConstructor
+@RefreshScope
+public class StorePlatformLoginServiceImpl extends ServiceImpl<StoreUserMapper, StoreUser> implements StorePlatformLoginervice {
+
+    @Value("${jwt.expiration-time}")
+    private String effectiveTime;
+
+    @Value("${captcha.register.timeOut}")
+    private Long timeOut;
+
+    private final StoreUserMapper storeUserMapper;
+    private final StoreInfoMapper storeInfoMapper;
+    private final BaseRedisService baseRedisService;
+
+    @Override
+    public R<Boolean> register(String phone, String password) {
+        try {
+            LambdaQueryWrapper<StoreUser> queryWrapper = new LambdaQueryWrapper<>();
+            queryWrapper.eq(StoreUser::getPhone, phone)
+                    .orderByDesc(StoreUser::getCreatedTime).last("LIMIT 1");
+            StoreUser storeUser = storeUserMapper.selectOne(queryWrapper);
+
+            List<Integer> noRegister = Arrays.asList(0, 1);
+
+            if (Objects.isNull((storeUser))) {
+                // 没有数据
+                saveNewUser(phone, password);
+                return R.success("操作成功");
+
+            } else if (noRegister.contains(storeUser.getStatus())) {
+                return R.fail("手机号已经注册过");
+            }
+
+            int daysSinceDeletion = DateUtils.subtraction(storeUser.getCreatedTime(), new Date());
+            if (daysSinceDeletion < 30) return R.fail(String.format("请于%d天后重新申请注册", 30 - daysSinceDeletion));
+            saveNewUser(phone, password);
+            return R.success("操作成功");
+        } catch (Exception e) {
+            log.error("StorePlatformLoginServiceImpl.register(): Error Msg={}", e.getMessage());
+            throw new RuntimeException(e.getMessage());
+        }
+    }
+
+    private void saveNewUser(String phone, String password) {
+        StoreUser registerStoreUser = new StoreUser();
+        registerStoreUser.setPhone(phone);
+        registerStoreUser.setPassword(password);
+        registerStoreUser.setMoney(0);
+        registerStoreUser.setDeleteFlag(0);
+        registerStoreUser.setStatus(0);
+        registerStoreUser.setPassType(2);
+        registerStoreUser.setCreatedTime(new Date());
+        registerStoreUser.setLogoutFlag(0);
+        registerStoreUser.setNickName(phone);
+        storeUserMapper.insert(registerStoreUser);
+    }
+
+    @Override
+    public boolean checkRegister(String phone) {
+        try {
+            LambdaQueryWrapper<StoreUser> queryWrapper = new LambdaQueryWrapper<>();
+            queryWrapper.eq(StoreUser::getPhone, phone);
+            queryWrapper.in(StoreUser::getStatus, 0, 1);
+            List<StoreUser> storeUsers = storeUserMapper.selectList(queryWrapper);
+            return CollectionUtils.isEmpty(storeUsers) ? Boolean.TRUE : Boolean.FALSE;
+        } catch (Exception e) {
+            log.error("StorePlatformLoginServiceImpl.checkRegister(): Error Msg={}", e.getMessage());
+            throw new RuntimeException(e.getMessage());
+        }
+    }
+
+    @Override
+    public R<StoreUserVo> createToKen(StoreUser storeUser) {
+        try {
+            int effectiveTimeInt = Integer.parseInt(effectiveTime.substring(0, effectiveTime.length() - 1));
+            String effectiveTimeUnit = effectiveTime.substring(effectiveTime.length() - 1);
+            long effectiveTimeIntLong = 0L;
+            switch (effectiveTimeUnit) {
+                case "s": {
+                    effectiveTimeIntLong = effectiveTimeInt * 1000L;
+                    break;
+                }
+                case "m": {
+                    effectiveTimeIntLong = effectiveTimeInt * 60L * 1000L;
+                    break;
+                }
+                case "h": {
+                    effectiveTimeIntLong = effectiveTimeInt * 60L * 60L * 1000L;
+                    break;
+                }
+                case "d": {
+                    effectiveTimeIntLong = effectiveTimeInt * 24L * 60L * 60L * 1000L;
+                    break;
+                }
+            }
+
+            StoreUserVo storeUserVo = new StoreUserVo();
+            BeanUtils.copyProperties(storeUser, storeUserVo);
+            Map<String, String> tokenMap = new HashMap<>();
+            tokenMap.put("phone", storeUser.getPhone());
+            tokenMap.put("userName", storeUser.getName());
+            tokenMap.put("userId", storeUser.getId().toString());
+            tokenMap.put("userType", "storePlatform");
+            storeUserVo.setToken(JwtUtil.createJWT("storePlatform_" + storeUser.getPhone(), storeUser.getName(), JSONObject.toJSONString(tokenMap), effectiveTimeIntLong));
+            baseRedisService.setString("storePlatform_" + storeUser.getPhone(), storeUserVo.getToken());
+            StoreInfo storeInfo = storeInfoMapper.selectById(storeUser.getStoreId());
+            if (storeInfo != null) {
+                storeUserVo.setBusinessSection(storeInfo.getBusinessSection());
+                storeUserVo.setBusinessTypesName(storeInfo.getBusinessTypesName());
+                storeUserVo.setMealsFlag(storeInfo.getMealsFlag());
+            }
+            return R.data(storeUserVo);
+        } catch (Exception e) {
+            log.error("StorePlatformLoginServiceImpl.createToKen(): Error Msg={}", e.getMessage());
+            throw new RuntimeException(e.getMessage());
+        }
+    }
+
+    @Override
+    public R<String> forgetOrModifyPassword(String phone, String newPhone, String oldPassword, String newPassword, String confirmNewPassword, String verificationCode, Integer type) {
+        boolean flag = false;
+        try {
+            //类型为0 忘记密码
+            if (type == 0) {
+                return forgetPassword(phone, newPassword);
+            }
+            //修改密码
+            else if (type == 1) {
+                //效验新密码规则
+                passwordVerification(phone, oldPassword, newPassword, confirmNewPassword);
+                LambdaUpdateWrapper<StoreUser> updateWrapper = new LambdaUpdateWrapper<>();
+                updateWrapper.eq(StoreUser::getPhone, phone);
+                updateWrapper.set(StoreUser::getPassword, newPassword);
+                flag = this.update(updateWrapper);
+
+                //修改成功后同步删除redis缓存token
+                if (flag) {
+                    String token = "store_platform_" + phone;
+                    String toKenStr = baseRedisService.getString(token);
+                    if (toKenStr != null) {
+                        baseRedisService.delete(token);
+                    }
+                    return R.success("密码修改成功");
+                } else {
+                    log.error("密码修改失败");
+                    return R.fail("密码修改失败");
+                }
+            }
+            //更换绑定手机号
+            else if (type == 2) {
+                return ChangeBoundPhone(phone, newPhone);
+            }
+            return R.success("密码修改成功");
+        } catch (Exception e) {
+            log.info("StorePlatformLoginServiceImpl.forgetOrModifyPassword(): Error Msg={}", e.getMessage());
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @Override
+    public void generateCaptcha(HttpServletResponse response, HttpSession session) throws IOException {
+        try {
+            LineCaptcha captcha = CaptchaUtil.createLineCaptcha(200, 100, 4, 100);
+            session.setAttribute("captcha", captcha.getCode());
+            response.setContentType("image/png");
+            response.setHeader("Pragma", "No-cache");
+            captcha.write(response.getOutputStream());
+        } catch (IOException e) {
+            log.error("StorePlatformLoginServiceImpl.generateCaptcha(): Error Msg={}", e.getMessage());
+            throw new RuntimeException(e.getMessage());
+        }
+    }
+
+    @Override
+    public JSONObject getExpirationTime(Integer storeId) throws Exception {
+        try {
+            JSONObject jsonObject = new JSONObject();
+            jsonObject.put("expirationTime", 1);
+            jsonObject.put("foodLicenceExpirationTime", 1);
+            jsonObject.put("entertainmentLicenceExpirationTime", 1);
+
+            StoreInfo storeInfo = storeInfoMapper.selectById(storeId);
+            if (storeInfo != null) {
+                if (storeInfo.getExpirationTime() == null || storeInfo.getExpirationTime().compareTo(new Date()) < 0) {
+                    jsonObject.put("expirationTime", 0);
+                }
+                if (storeInfo.getFoodLicenceExpirationTime() == null || storeInfo.getFoodLicenceExpirationTime().compareTo(new Date()) < 0) {
+                    jsonObject.put("foodLicenceExpirationTime", 0);
+                }
+                if (storeInfo.getEntertainmentLicenceExpirationTime() != null && storeInfo.getEntertainmentLicenceExpirationTime().compareTo(new Date()) < 0) {
+                    jsonObject.put("entertainmentLicenceExpirationTime", 0);
+                }
+            }
+            return jsonObject;
+        } catch (Exception e) {
+            log.error("StorePlatformLoginServiceImpl.getExpirationTime(): Error Msg={}", e.getMessage());
+            throw new RuntimeException(e.getMessage());
+        }
+    }
+
+    private void passwordVerification(String phone, String password, String newPassword, String confirmNewPassword) {
+        LambdaUpdateWrapper<StoreUser> wrapperFans = new LambdaUpdateWrapper<>();
+        wrapperFans.eq(StoreUser::getPhone, phone);
+        StoreUser storeUser = this.getOne(wrapperFans);
+        if (!newPassword.equals(confirmNewPassword)) {
+            log.info("两次新密码输入不一致 请重新输入");
+            throw new RuntimeException("两次新密码输入不一致 请重新输入");
+        }
+        if (storeUser == null || storeUser.equals("")) {
+            log.info("该手机号没有注册过账户");
+            throw new RuntimeException("该手机号没有注册过账户");
+        } else {
+            wrapperFans.eq(StoreUser::getPassword, password);
+            StoreUser storeUserPw = this.getOne(wrapperFans);
+            if (storeUserPw == null || storeUserPw.getPassword().equals("")) {
+                log.info("原密码错误");
+                throw new RuntimeException("原密码错误");
+            }
+        }
+    }
+
+    private R<String> forgetPassword(String phone, String newPassword) {
+        boolean flag = false;
+        LambdaUpdateWrapper<StoreUser> updateWrapper = new LambdaUpdateWrapper<>();
+        updateWrapper.eq(StoreUser::getPhone, phone);
+        updateWrapper.set(StoreUser::getPassword, newPassword);
+        flag = this.update(updateWrapper);
+        if (flag) {
+            log.info("密码修改成功");
+            String token = "store_platform_" + phone;
+            String tokenStr = baseRedisService.getString(token);
+            if (tokenStr != null) {
+                baseRedisService.delete(token);
+            }
+        }
+        if (!flag) {
+            log.error("密码修改失败");
+            throw new RuntimeException("密码修改失败");
+        }
+        return R.success("密码修改成功");
+    }
+
+    private R<String> ChangeBoundPhone(String phone, String newPhone) {
+        boolean flag = false;
+        //获取新手机号验证码
+        LambdaUpdateWrapper<StoreUser> newUpdateWrapper = new LambdaUpdateWrapper<>();
+        newUpdateWrapper.eq(StoreUser::getPhone, newPhone);
+        StoreUser newStoreUser = this.getOne(newUpdateWrapper);
+        if (newStoreUser != null) {
+            if (newStoreUser.getPhone().equals(newPhone)) {
+                throw new RuntimeException("该手机号已注册过商户");
+            }
+        } else {
+            LambdaUpdateWrapper<StoreUser> updateWrapper = new LambdaUpdateWrapper<>();
+            updateWrapper.eq(StoreUser::getPhone, phone);
+            StoreUser storeUser = this.getOne(updateWrapper);
+            storeUser.setPhone(newPhone);
+            flag = this.updateById(storeUser);
+            if (flag) {
+                String token = "store_platform_" + phone;
+                String tokenStr = baseRedisService.getString(token);
+                if (tokenStr != null) {
+                    baseRedisService.delete(token);
+                }
+            }
+        }
+        return R.success("新手机号绑定成功");
+    }
+
+}

+ 113 - 0
alien-store-platform/src/main/java/shop/alien/storeplatform/service/impl/StorePlatformRenovationServiceImpl.java

@@ -0,0 +1,113 @@
+package shop.alien.storeplatform.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.BeanUtils;
+import org.springframework.cloud.context.config.annotation.RefreshScope;
+import org.springframework.stereotype.Service;
+import shop.alien.entity.store.*;
+import shop.alien.entity.store.vo.StoreDictionaryVo;
+import shop.alien.entity.store.vo.StoreMainInfoVo;
+import shop.alien.mapper.*;
+import shop.alien.storeplatform.service.StorePlatformRenovationService;
+
+import java.text.SimpleDateFormat;
+import java.time.Duration;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+@Slf4j
+@Service
+@RequiredArgsConstructor
+@RefreshScope
+public class StorePlatformRenovationServiceImpl extends ServiceImpl<StoreInfoMapper, StoreInfo> implements StorePlatformRenovationService {
+
+    private final StoreInfoMapper storeInfoMapper;
+    private final StoreLabelMapper storeLabelMapper;
+    private final StoreBusinessInfoMapper storeBusinessInfoMapper;
+    private final StoreImgMapper storeImgMapper;
+    private final StoreDictionaryMapper storeDictionaryMapper;
+
+    @Override
+    public List<StoreDictionaryVo> getBusinessSection() {
+        List<StoreDictionary> businessSection = storeDictionaryMapper.selectList(new LambdaQueryWrapper<StoreDictionary>().eq(StoreDictionary::getTypeName, "business_section").ne(StoreDictionary::getDictId, 0));
+        List<StoreDictionaryVo> voList = new ArrayList<>();
+        for (StoreDictionary storeDictionary : businessSection) {
+            StoreDictionaryVo vo = new StoreDictionaryVo();
+            BeanUtils.copyProperties(storeDictionary, vo);
+            voList.add(vo);
+        }
+        return voList;
+    }
+
+    @Override
+    public List<StoreDictionaryVo> getBusinessSectionTypes(String parentId) {
+        StoreDictionary businessSection = storeDictionaryMapper.selectOne(new LambdaQueryWrapper<StoreDictionary>().eq(StoreDictionary::getTypeName, "business_section").eq(StoreDictionary::getDictId, parentId));
+        List<StoreDictionary> storeDictionaries = storeDictionaryMapper.selectList(new LambdaQueryWrapper<StoreDictionary>().eq(StoreDictionary::getParentId, businessSection.getId()));
+        List<StoreDictionaryVo> voList = new ArrayList<>();
+        for (StoreDictionary storeDictionary : storeDictionaries) {
+            StoreDictionaryVo vo = new StoreDictionaryVo();
+            BeanUtils.copyProperties(storeDictionary, vo);
+            voList.add(vo);
+        }
+        return voList;
+    }
+
+    @Override
+    public StoreMainInfoVo getDecorationDetail(Integer id) {
+        StoreInfo storeInfo = storeInfoMapper.selectById(id);
+        StoreMainInfoVo storeMainInfoVo = storeInfoMapper.getStoreInfo(id);
+        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
+        if (storeMainInfoVo.getExpirationTime() != null) {
+            String expirationDate = sdf.format(storeMainInfoVo.getExpirationTime());
+            storeMainInfoVo.setExpirationDate(expirationDate);
+        }
+        //审核通过给前台反显未提交
+        if (storeMainInfoVo.getRenewContractStatus() == 1) {
+            storeMainInfoVo.setRenewContractStatus(0);
+        }
+        if (storeMainInfoVo.getStoreStatus() == -1) {
+            LocalDateTime localDateTime = storeMainInfoVo.getLogoutTime().toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
+            LocalDateTime future = localDateTime.plusDays(7);
+            LocalDateTime now = LocalDateTime.now();
+            Duration duration = Duration.between(now, future);
+            long correct = duration.toMillis();
+            storeMainInfoVo.setCountdown(correct);
+        }
+        //存入门店地址
+        storeMainInfoVo.setStoreAddress(storeInfo.getStoreAddress());
+        //经营种类
+        if (storeInfo.getBusinessTypes() != null) {
+            String[] strings = storeInfo.getBusinessTypes().split(",");
+            storeMainInfoVo.setBusinessTypesList(Arrays.stream(strings).collect(Collectors.toList()));
+        }
+        // 分类
+        if (storeInfo.getBusinessClassify() != null) {
+            String[] strings = storeInfo.getBusinessClassify().split(",");
+            storeMainInfoVo.setBusinessClassifyList(Arrays.stream(strings).collect(Collectors.toList()));
+        }
+        //门店标签
+        storeMainInfoVo.setStoreLabel(storeLabelMapper.selectOne(new LambdaQueryWrapper<StoreLabel>().eq(StoreLabel::getStoreId, id)));
+        //营业时间
+        List<StoreBusinessInfo> storeBusinessInfoList = storeBusinessInfoMapper.selectList(new LambdaQueryWrapper<StoreBusinessInfo>().eq(StoreBusinessInfo::getStoreId, id));
+        storeMainInfoVo.setStoreBusinessInfo(storeBusinessInfoList);
+        //营业执照
+        storeMainInfoVo.setBusinessLicenseList(storeImgMapper.selectList(new LambdaQueryWrapper<StoreImg>().eq(StoreImg::getImgType, 14).eq(StoreImg::getStoreId, id)));
+        //合同照片
+        storeMainInfoVo.setContractList(storeImgMapper.selectList(new LambdaQueryWrapper<StoreImg>().eq(StoreImg::getImgType, 15).eq(StoreImg::getStoreId, id)));
+        //经营许可证图片照片
+        storeMainInfoVo.setFoodLicenceList(storeImgMapper.selectList(new LambdaQueryWrapper<StoreImg>().eq(StoreImg::getImgType, 25).eq(StoreImg::getStoreId, id)));
+        //是否连锁
+        String isChain = (storeInfo.getIsChain() == 0) ? "否" : "是";
+        storeMainInfoVo.setIsChainStr(isChain);
+        storeMainInfoVo.setFoodLicenceExpirationTime(storeInfo.getFoodLicenceExpirationTime());
+        return storeMainInfoVo;
+    }
+
+}

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

@@ -164,5 +164,51 @@ public class BathFacilityServiceController {
         log.info("BathFacilityServiceController.getCategorySummary?storeId={}", storeId);
         return R.data(facilityServiceService.getCategorySummary(storeId));
     }
+
+
+    @ApiOperation("分页查询洗浴设施及服务列表(商户端)")
+    @ApiOperationSupport(order = 8)
+    @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("/storePage")
+    public R<IPage<BathFacilityServiceVo>> getStorePageList(
+            @RequestParam(defaultValue = "1") Integer pageNum,
+            @RequestParam(defaultValue = "10") Integer pageSize,
+            @RequestParam Integer storeId,
+            @RequestParam(required = false) Integer facilityCategory) {
+        log.info("BathFacilityServiceController.getStorePageList?pageNum={},pageSize={},storeId={},facilityCategory={}",
+                pageNum, pageSize, storeId, facilityCategory);
+        return R.data(facilityServiceService.getStorePageList(pageNum, pageSize, storeId, facilityCategory));
+    }
+
+    @ApiOperation("根据ID查询洗浴设施及服务详情(商户端)")
+    @ApiOperationSupport(order = 9)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "id", value = "主键ID", dataType = "int", paramType = "query", required = true)
+    })
+    @GetMapping("/storeDetail")
+    public R<BathFacilityServiceVo> getStoreDetail(@RequestParam Integer id) {
+        log.info("BathFacilityServiceController.getStoreDetail?id={}", id);
+        BathFacilityServiceVo vo = facilityServiceService.getStoreDetail(id);
+        if (vo == null) {
+            return R.fail("数据不存在");
+        }
+        return R.data(vo);
+    }
+
+    @ApiOperation("查询指定店铺按分类汇总的设备信息(包含设备数量、设备列表和图片)(商户端)")
+    @ApiOperationSupport(order = 7)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "storeId", value = "门店ID", dataType = "int", paramType = "query", required = true)
+    })
+    @GetMapping("/storeCategorySummary")
+    public R<List<BathFacilityServiceCategoryVo>> getStoreCategorySummary(@RequestParam Integer storeId) {
+        log.info("BathFacilityServiceController.getStoreCategorySummary?storeId={}", storeId);
+        return R.data(facilityServiceService.getStoreCategorySummary(storeId));
+    }
 }
 

+ 190 - 0
alien-store/src/main/java/shop/alien/store/controller/DrinkInfoController.java

@@ -0,0 +1,190 @@
+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.DrinkInfo;
+import shop.alien.entity.store.vo.DrinkInfoVo;
+import shop.alien.store.service.DrinkInfoService;
+
+import java.util.List;
+
+/**
+ * 酒水餐食信息表Controller
+ *
+ * @author ssk
+ * @since 2025-06-13
+ */
+@Slf4j
+@Api(tags = {"酒水餐食管理"})
+@ApiSort(1)
+@CrossOrigin
+@RestController
+@RequestMapping("/store/drink")
+@RequiredArgsConstructor
+public class DrinkInfoController {
+
+    private final DrinkInfoService drinkInfoService;
+
+    @ApiOperation("分页查询酒水餐食列表")
+    @ApiOperationSupport(order = 1)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "page", value = "页码", dataType = "int", paramType = "query", required = true),
+            @ApiImplicitParam(name = "size", value = "页容", dataType = "int", paramType = "query", required = true),
+            @ApiImplicitParam(name = "storeId", value = "门店ID", dataType = "Integer", paramType = "query"),
+            @ApiImplicitParam(name = "type", value = "类型(枚举值:酒水/餐食)", dataType = "String", paramType = "query"),
+            @ApiImplicitParam(name = "category", value = "分类", dataType = "String", paramType = "query"),
+            @ApiImplicitParam(name = "name", value = "名称关键词", dataType = "String", paramType = "query"),
+            @ApiImplicitParam(name = "createUserId", value = "创建人ID(只查询自己创建的)", dataType = "Integer", paramType = "query")
+    })
+    @GetMapping("/page")
+    public R<IPage<DrinkInfoVo>> getDrinkInfoPage(
+            @RequestParam("page") int page,
+            @RequestParam("size") int size,
+            @RequestParam(value = "storeId", required = false) Integer storeId,
+            @RequestParam(value = "type", required = false) String type,
+            @RequestParam(value = "category", required = false) String category,
+            @RequestParam(value = "name", required = false) String name,
+            @RequestParam(value = "createUserId", required = false) Integer createUserId) {
+        log.info("DrinkInfoController.getDrinkInfoPage?page={}, size={}, storeId={}, type={}, category={}, name={}, createUserId={}", 
+                page, size, storeId, type, category, name, createUserId);
+        IPage<DrinkInfoVo> drinkInfoPage = drinkInfoService.getDrinkInfoPage(page, size, storeId, type, category, name, createUserId);
+        return R.data(drinkInfoPage);
+    }
+
+    @ApiOperation("根据ID查询酒水餐食详情")
+    @ApiOperationSupport(order = 2)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "id", value = "主键ID", dataType = "Integer", paramType = "path", required = true)
+    })
+    @GetMapping("/{id}")
+    public R<DrinkInfoVo> getDrinkInfoById(@PathVariable("id") Integer id) {
+        log.info("DrinkInfoController.getDrinkInfoById?id={}", id);
+        DrinkInfoVo drinkInfoVo = drinkInfoService.getDrinkInfoById(id);
+        if (drinkInfoVo == null) {
+            return R.fail("未找到该酒水餐食信息");
+        }
+        return R.data(drinkInfoVo);
+    }
+
+    @ApiOperation("新增酒水餐食")
+    @ApiOperationSupport(order = 3)
+    @PostMapping
+    public R<String> saveDrinkInfo(@RequestBody DrinkInfo drinkInfo) {
+        log.info("DrinkInfoController.saveDrinkInfo?drinkInfo={}", drinkInfo);
+        boolean result = drinkInfoService.saveDrinkInfo(drinkInfo);
+        if (result) {
+            return R.success("新增成功");
+        }
+        return R.fail("新增失败");
+    }
+
+    @ApiOperation("修改酒水餐食")
+    @ApiOperationSupport(order = 4)
+    @PutMapping
+    public R<String> updateDrinkInfo(@RequestBody DrinkInfo drinkInfo) {
+        log.info("DrinkInfoController.updateDrinkInfo?drinkInfo={}", drinkInfo);
+        boolean result = drinkInfoService.updateDrinkInfo(drinkInfo);
+        if (result) {
+            return R.success("修改成功");
+        }
+        return R.fail("修改失败");
+    }
+
+    @ApiOperation("删除酒水餐食")
+    @ApiOperationSupport(order = 5)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "id", value = "主键ID", dataType = "Integer", paramType = "path", required = true)
+    })
+    @DeleteMapping("/{id}")
+    public R<String> deleteDrinkInfo(@PathVariable("id") Integer id) {
+        log.info("DrinkInfoController.deleteDrinkInfo?id={}", id);
+        boolean result = drinkInfoService.deleteDrinkInfo(id);
+        if (result) {
+            return R.success("删除成功");
+        }
+        return R.fail("删除失败");
+    }
+
+    @ApiOperation("设置酒水餐食推荐")
+    @ApiOperationSupport(order = 7)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "id", value = "主键ID", dataType = "Integer", paramType = "path", required = true),
+            @ApiImplicitParam(name = "isRecommended", value = "是否推荐(0:否, 1:是)", dataType = "Integer", paramType = "query", required = true)
+    })
+    @PutMapping("/{id}/recommended")
+    public R<String> updateIsRecommended(
+            @PathVariable("id") Integer id,
+            @RequestParam("isRecommended") Integer isRecommended) {
+        log.info("DrinkInfoController.updateIsRecommended?id={}, isRecommended={}", id, isRecommended);
+        boolean result = drinkInfoService.updateIsRecommended(id, isRecommended);
+        if (result) {
+            return R.success("操作成功");
+        }
+        return R.fail("操作失败");
+    }
+
+    @ApiOperation("更新酒水餐食排序")
+    @ApiOperationSupport(order = 8)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "id", value = "主键ID", dataType = "Integer", paramType = "path", required = true),
+            @ApiImplicitParam(name = "sort", value = "排序值", dataType = "Integer", paramType = "query", required = true)
+    })
+    @PutMapping("/{id}/sort")
+    public R<String> updateSort(
+            @PathVariable("id") Integer id,
+            @RequestParam("sort") Integer sort) {
+        log.info("DrinkInfoController.updateSort?id={}, sort={}", id, sort);
+        boolean result = drinkInfoService.updateSort(id, sort);
+        if (result) {
+            return R.success("操作成功");
+        }
+        return R.fail("操作失败");
+    }
+
+    @ApiOperation("根据门店ID查询所有酒水餐食")
+    @ApiOperationSupport(order = 9)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "storeId", value = "门店ID", dataType = "Integer", paramType = "query", required = true),
+            @ApiImplicitParam(name = "type", value = "类型(枚举值:酒水/餐食)", dataType = "String", paramType = "query")
+    })
+    @GetMapping("/listByStoreId")
+    public R<List<DrinkInfoVo>> getDrinkInfoListByStoreId(
+            @RequestParam("storeId") Integer storeId,
+            @RequestParam(value = "type", required = false) String type) {
+        log.info("DrinkInfoController.getDrinkInfoListByStoreId?storeId={}, type={}", storeId, type);
+        List<DrinkInfoVo> drinkInfoList = drinkInfoService.getDrinkInfoListByStoreId(storeId, type);
+        return R.data(drinkInfoList);
+    }
+
+    @ApiOperation("查询推荐酒水餐食列表")
+    @ApiOperationSupport(order = 10)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "storeId", value = "门店ID", dataType = "Integer", paramType = "query"),
+            @ApiImplicitParam(name = "createUserId", value = "创建人ID(只查询自己创建的)", dataType = "Integer", paramType = "query")
+    })
+    @GetMapping("/recommended")
+    public R<List<DrinkInfoVo>> getRecommendedList(
+            @RequestParam(value = "storeId", required = false) Integer storeId,
+            @RequestParam(value = "createUserId", required = false) Integer createUserId) {
+        log.info("DrinkInfoController.getRecommendedList?storeId={}, createUserId={}", storeId, createUserId);
+        List<DrinkInfoVo> drinkInfoList = drinkInfoService.getRecommendedList(storeId, createUserId);
+        return R.data(drinkInfoList);
+    }
+
+    @ApiOperation("批量更新酒水餐食排序")
+    @ApiOperationSupport(order = 11)
+    @PostMapping("/batchSort")
+    public R<String> batchUpdateSort(@RequestBody List<DrinkInfo> drinkInfoList) {
+        log.info("DrinkInfoController.batchUpdateSort?size={}", drinkInfoList.size());
+        boolean result = drinkInfoService.batchUpdateSort(drinkInfoList);
+        if (result) {
+            return R.success("批量更新排序成功");
+        }
+        return R.fail("批量更新排序失败");
+    }
+
+}

+ 99 - 0
alien-store/src/main/java/shop/alien/store/controller/OperationalActivityController.java

@@ -0,0 +1,99 @@
+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.storePlatform.vo.StoreOperationalActivityVO;
+import shop.alien.store.service.OperationalActivityService;
+
+/**
+ * 运营活动管理控制器
+ *
+ * @author system
+ * @since 2025-11-26
+ */
+@Slf4j
+@Api(tags = {"商家端-运营活动管理"})
+@ApiSort(10)
+@CrossOrigin
+@RestController
+@RequestMapping("/operationalActivity")
+@RequiredArgsConstructor
+public class OperationalActivityController {
+
+    private final OperationalActivityService activityService;
+
+    @ApiOperation("根据活动ID获取详情")
+    @ApiOperationSupport(order = 4)
+    @ApiImplicitParam(name = "id", value = "活动ID", dataTypeClass = Integer.class, paramType = "path", required = true)
+    @GetMapping("/detail/{id}")
+    public R<StoreOperationalActivityVO> getActivityDetailById(@PathVariable("id") Integer id) {
+        log.info("OperationalActivityController.getActivityDetailById: id={}", id);
+        try {
+            StoreOperationalActivityVO result = activityService.getActivityDetailById(id);
+            return R.data(result);
+        } catch (IllegalArgumentException e) {
+            return R.fail(e.getMessage());
+        } catch (Exception e) {
+            log.error("OperationalActivityController.getActivityDetailById ERROR: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @ApiOperation("分页查看运营活动列表")
+    @ApiOperationSupport(order = 5)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "storeId", value = "商户ID", dataTypeClass = Integer.class, paramType = "query"),
+            @ApiImplicitParam(name = "storeName", value = "商户名称(模糊)", dataTypeClass = String.class, paramType = "query"),
+            @ApiImplicitParam(name = "pageNum", value = "当前页", dataTypeClass = Integer.class, paramType = "query"),
+            @ApiImplicitParam(name = "pageSize", value = "每页数量", dataTypeClass = Integer.class, paramType = "query")
+    })
+    @GetMapping("/detail")
+    public R<IPage<StoreOperationalActivityVO>> pageActivityDetail(
+            @RequestParam(value = "storeId", required = false) Integer storeId,
+            @RequestParam(value = "storeName", required = false) String storeName,
+            @RequestParam(value = "pageNum", required = false) Integer pageNum,
+            @RequestParam(value = "pageSize", required = false) Integer pageSize) {
+        log.info("OperationalActivityController.pageActivityDetail storeId={}, storeName={}, pageNum={}, pageSize={}", storeId, storeName, pageNum, pageSize);
+        try {
+            IPage<StoreOperationalActivityVO> result = activityService.pageActivityDetail(storeId, storeName, pageNum, pageSize);
+            return R.data(result);
+        } catch (IllegalArgumentException e) {
+            return R.fail(e.getMessage());
+        } catch (Exception e) {
+            log.error("OperationalActivityController.pageActivityDetail ERROR: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @ApiOperation("运营活动审核处理")
+    @ApiOperationSupport(order = 6)
+    @PostMapping("/audit")
+    public R<String> auditActivity(
+            @RequestParam("id") Integer id,
+            @RequestParam("status") Integer status,
+            @RequestParam(value = "approvalComments", required = false) String approvalComments) {
+        log.info("OperationalActivityController.auditActivity: id={}, status={}, approvalComments={}", id, status, approvalComments);
+        if (id == null) {
+            return R.fail("活动ID不能为空");
+        }
+        if (status == null) {
+            return R.fail("审核状态不能为空");
+        }
+        try {
+            int result = activityService.auditActivity(id, status, approvalComments);
+            if (result > 0) {
+                return R.success("审核处理成功");
+            }
+            return R.fail("审核处理失败");
+        } catch (Exception e) {
+            log.error("OperationalActivityController.auditActivity ERROR: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+}
+

+ 23 - 6
alien-store/src/main/java/shop/alien/store/controller/StoreInfoController.java

@@ -104,6 +104,7 @@ public class StoreInfoController {
         return R.fail("失败");
     }
 
+
     @ApiOperation("新增门店草稿")
     @ApiOperationSupport(order = 3)
     @PostMapping("/saveStoreInfoDraft")
@@ -1013,7 +1014,28 @@ public class StoreInfoController {
             return R.fail("获取推荐店铺失败,请稍后重试");
         }
     }
-
+    /**
+     * 根据business_classify字段获取字典表数据实现UI图中的功能
+     * RESTful风格:GET /store/info/business-classifies
+     * 返回扁平化的分类列表,用于多选分类功能
+     *
+     * @param parentId 父分类ID(可选),如果提供则只返回该父分类下的子分类
+     * @return R<List<StoreDictionaryVo>> 分类列表(扁平化,仅子分类)
+     */
+    @ApiOperation("获取店铺分类信息)")
+    @ApiOperationSupport(order = 19)
+    @GetMapping("/business-classifies")
+    public R<List<StoreDictionaryVo>> getBusinessClassifyData(
+            @RequestParam(value = "parentId", required = false) Integer parentId) {
+        log.info("StoreInfoController.getBusinessClassifyData?parentId={}", parentId);
+        try {
+            List<StoreDictionaryVo> result = storeInfoService.getBusinessClassifyData(parentId);
+            return R.data(result);
+        } catch (Exception e) {
+            log.error("获取business_classify分类数据异常", e);
+            return R.fail("获取分类数据失败,请稍后重试");
+        }
+    }
 
 
     @ApiOperation(value = "获取banner图详情")
@@ -1110,9 +1132,4 @@ public class StoreInfoController {
     }
 
 
-
-
-
-
-
 }

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

@@ -149,7 +149,7 @@ public class StoreMenuController {
             @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")
+            @ApiImplicitParam(name = "dishMenuType", value = "菜单类型:1-菜,2-酒,不传就是菜+酒(页面叫酒", dataType = "Integer", paramType = "query")
     })
     @GetMapping("/getClientMenuByStoreId")
     public R<Map<String, Object>> getClientMenuByStoreId(

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

@@ -78,6 +78,26 @@ public class StoreStaffConfigController {
         return R.data(s);
     }
 
+    @ApiOperation("删除员工")
+    @ApiOperationSupport(order = 4)
+    @ApiImplicitParams({@ApiImplicitParam(name = "id", value = "主键id", dataType = "Integer", paramType = "query", required = true)})
+    @GetMapping("/deleteStaffConfig")
+    public R<Integer> deleteStaffConfig(Integer id) {
+        log.info("StoreStaffConfigController.deleteStaffConfig?id={}", id);
+        return R.data(storeStaffConfigService.deleteStaffConfig(id));
+    }
+
+    @ApiOperation("置顶员工")
+    @ApiOperationSupport(order = 5)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "id", value = "主键id", dataType = "Integer", paramType = "query", required = true),
+            @ApiImplicitParam(name = "topStatus", value = "置顶状态 0-取消置顶, 1-置顶", dataType = "Integer", paramType = "query", required = true)
+    })
+    @GetMapping("/setTopStatus")
+    public R<Integer> setTopStatus(Integer id, Integer topStatus) {
+        log.info("StoreStaffConfigController.setTopStatus?id={},topStatus={}", id, topStatus);
+        return R.data(storeStaffConfigService.setTopStatus(id, topStatus));
+    }
     /**
      * 员工列表查询接口(用户端)
      *

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

@@ -78,5 +78,11 @@ public interface BathFacilityServiceService extends IService<BathFacilityService
      * @return List<BathFacilityServiceCategoryVo>
      */
     List<BathFacilityServiceCategoryVo> getCategorySummary(Integer storeId);
+
+    IPage<BathFacilityServiceVo> getStorePageList(Integer pageNum, Integer pageSize, Integer storeId, Integer facilityCategory);
+
+    BathFacilityServiceVo getStoreDetail(Integer id);
+
+    List<BathFacilityServiceCategoryVo> getStoreCategorySummary(Integer storeId);
 }
 

+ 141 - 0
alien-store/src/main/java/shop/alien/store/service/DrinkInfoService.java

@@ -0,0 +1,141 @@
+package shop.alien.store.service;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.service.IService;
+import shop.alien.entity.store.DrinkInfo;
+import shop.alien.entity.store.vo.DrinkInfoVo;
+
+import java.util.List;
+
+/**
+ * 酒水餐食信息表 服务类
+ *
+ * @author ssk
+ * @since 2025-06-13
+ */
+public interface DrinkInfoService extends IService<DrinkInfo> {
+
+    /**
+     * 分页查询酒水餐食列表
+     *
+     * @param page         页码
+     * @param size         页容
+     * @param storeId      门店ID
+     * @param type         类型(枚举值:酒水/餐食)
+     * @param category     分类
+     * @param name         名称关键词
+     * @param createUserId 创建人ID(只查询指定用户创建的)
+     * @return IPage<DrinkInfoVo>
+     */
+    IPage<DrinkInfoVo> getDrinkInfoPage(int page, int size, Integer storeId, String type, String category, String name, Integer createUserId);
+
+    /**
+     * 根据ID查询酒水餐食详情
+     *
+     * @param id 主键ID
+     * @return DrinkInfoVo
+     */
+    DrinkInfoVo getDrinkInfoById(Integer id);
+
+    /**
+     * 新增酒水餐食
+     *
+     * @param drinkInfo 酒水餐食信息
+     * @return boolean
+     */
+    boolean saveDrinkInfo(DrinkInfo drinkInfo);
+
+    /**
+     * 修改酒水餐食
+     *
+     * @param drinkInfo 酒水餐食信息
+     * @return boolean
+     */
+    boolean updateDrinkInfo(DrinkInfo drinkInfo);
+
+    /**
+     * 删除酒水餐食(软删除)
+     *
+     * @param id 主键ID
+     * @return boolean
+     */
+    boolean deleteDrinkInfo(Integer id);
+
+    /**
+     * 上下架酒水餐食
+     *
+     * @param id     主键ID
+     * @param status 状态(0:下架, 1:上架)
+     * @return boolean
+     */
+    boolean updateStatus(Integer id, Integer status);
+
+    /**
+     * 设置酒水餐食推荐
+     *
+     * @param id            主键ID
+     * @param isRecommended 是否推荐(0:否, 1:是)
+     * @return boolean
+     */
+    boolean updateIsRecommended(Integer id, Integer isRecommended);
+
+    /**
+     * 更新酒水餐食排序
+     *
+     * @param id   主键ID
+     * @param sort 排序值
+     * @return boolean
+     */
+    boolean updateSort(Integer id, Integer sort);
+
+    /**
+     * 根据门店ID查询所有酒水餐食
+     *
+     * @param storeId 门店ID
+     * @param type    类型(枚举值:酒水/餐食)
+     * @return List<DrinkInfoVo>
+     */
+    List<DrinkInfoVo> getDrinkInfoListByStoreId(Integer storeId, String type);
+
+    /**
+     * 查询推荐酒水餐食列表
+     *
+     * @param storeId      门店ID
+     * @param createUserId 创建人ID(只查询指定用户创建的)
+     * @return List<DrinkInfoVo>
+     */
+    List<DrinkInfoVo> getRecommendedList(Integer storeId, Integer createUserId);
+
+    /**
+     * 批量更新酒水餐食排序
+     *
+     * @param drinkInfoList 酒水餐食列表(包含id和sort字段)
+     * @return boolean
+     */
+    boolean batchUpdateSort(List<DrinkInfo> drinkInfoList);
+
+    /**
+     * 获取酒水餐食类型统计
+     *
+     * @param storeId 门店ID
+     * @return Map<String, Long> 类型统计信息
+     */
+    java.util.Map<String, Long> getStatistics(Integer storeId);
+
+    /**
+     * 批量删除酒水餐食
+     *
+     * @param ids 主键ID列表
+     * @return boolean
+     */
+    boolean batchDelete(List<Integer> ids);
+
+    /**
+     * 批量上下架
+     *
+     * @param ids    主键ID列表
+     * @param status 状态(0:下架, 1:上架)
+     * @return boolean
+     */
+    boolean batchUpdateStatus(List<Integer> ids, Integer status);
+}

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

@@ -133,7 +133,12 @@ public class LifeUserStoreService {
                 storeMap.put("businessStatusStr", store.getBusinessStatusStr());
 
                 //474bug
+                storeMap.put("businessSection", store.getBusinessSection());
+                storeMap.put("businessSectionName", store.getBusinessSectionName());
+                storeMap.put("businessTypes", store.getBusinessTypes());
                 storeMap.put("businessTypesName", store.getBusinessTypesName());
+                storeMap.put("businessClassify", store.getBusinessClassify());
+                storeMap.put("businessClassifyName", store.getBusinessClassifyName());
                 storeMap.put("storeBusinessInfo", storeBusinessInfo);
                 // 添加门店ID
                 storeMap.put("storeId", store.getId());

+ 44 - 0
alien-store/src/main/java/shop/alien/store/service/OperationalActivityService.java

@@ -0,0 +1,44 @@
+package shop.alien.store.service;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import shop.alien.entity.storePlatform.vo.StoreOperationalActivityVO;
+
+/**
+ * 运营活动服务接口
+ *
+ * @author system
+ * @since 2025-11-26
+ */
+public interface OperationalActivityService {
+
+    /**
+     * 审核运营活动
+     *
+     * @param id               活动ID
+     * @param status           审核状态(1通过/0驳回等)
+     * @param approvalComments 驳回原因
+     * @return 更新结果
+     */
+    int auditActivity(Integer id, Integer status, String approvalComments);
+
+    /**
+     * 根据门店条件分页查询运营活动
+     *
+     * @param storeId   门店ID
+     * @param storeName 门店名称(模糊)
+     * @param pageNum   页码
+     * @param pageSize  每页数量
+     * @return 活动分页结果
+     */
+    IPage<StoreOperationalActivityVO> pageActivityDetail(Integer storeId, String storeName, Integer pageNum, Integer pageSize);
+
+    /**
+     * 根据活动ID获取活动详情
+     *
+     * @param id 活动ID
+     * @return 活动详情
+     */
+    StoreOperationalActivityVO getActivityDetailById(Integer id);
+
+}
+

+ 80 - 49
alien-store/src/main/java/shop/alien/store/service/StoreInfoService.java

@@ -138,6 +138,13 @@ public interface StoreInfoService extends IService<StoreInfo> {
     StoreInfoVo editStoreInfo(StoreInfoDto storeInfoDto);
 
     /**
+     * 新中台web端修改门店及门店用户
+     *
+     * @return ResponseEntity
+     */
+    StoreInfoVo editNewStoreInfo(StoreInfoDto storeInfoDto);
+
+    /**
      * web端删除门店及门店用户
      *
      * @return ResponseEntity
@@ -177,6 +184,11 @@ public interface StoreInfoService extends IService<StoreInfo> {
     StoreInfoVo getStoreDetail(String storeId, String userId, String jingdu, String weidu);
 
     /**
+     * 中台web端查询门店明细
+     */
+    StoreInfoVo getNewStoreDetail(String storeId);
+
+    /**
      * web端审批结果
      */
     void approveStoreInfo(String storeId, Integer approvalStatus, String reason);
@@ -291,6 +303,52 @@ public interface StoreInfoService extends IService<StoreInfo> {
     int foodLicenceType(int id);
 
     /**
+     * 店铺娱乐经营许可证
+     * @param storeImg
+     * @return
+     */
+    int uploadEntertainmentLicence(StoreImg storeImg);
+
+    /**
+     * 获取店铺娱乐经营许可证状态以及店铺状态为审核中合同图片list
+     * @param id
+     * @return
+     */
+    Map<String,Object> getStoreEntertainmentLicenceStatus(int id);
+
+    /**
+     * 娱乐经营许可证审核通过后 图片类型变为审核通过后 img_type26
+     *
+     */
+    int entertainmentLicenceType(int id);
+
+    /**
+     * 获取门店代金券
+     */
+    List<LifeCouponVo> getStoreCouponList(String storeId);
+
+
+    /**
+     * 获取活动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);
+
+
+    /**
      * 根据门店及图片地址查询最新OCR识别数据
      *
      * @param imageUrl 图片URL
@@ -298,9 +356,6 @@ public interface StoreInfoService extends IService<StoreInfo> {
      */
     Map<String, Object> getStoreOcrData(String imageUrl, String merchantName);
 
-    void aiApproveStoreInfo(AiApproveStoreInfo aiApproveStoreInfo);
-
-
     /**
      * 查询四种类型店铺(酒吧、ktv、洗浴汗蒸、按摩足浴)并按距离筛选
      *
@@ -309,14 +364,17 @@ public interface StoreInfoService extends IService<StoreInfo> {
      * @param distance    距离范围(单位:公里)
      * @param sortType    排序模式(1:智能排序,2:好评优先,3:距离优先)
      * @param businessType 店铺类型(KTV=3、洗浴汗蒸=4、按摩足浴=5,酒吧需要查询字典表),可选
-     * @param categoryId 字典表id,根据此id查询经营板块、经营种类、分类并匹配店铺,可选
+     * @param categoryId  分类ID(二级或三级分类的dictId,从getAllBusinessSection接口获取),可选
      * @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);
 
-
+    /**
+     * web端查询门店明细
+     */
+    StoreInfoVo getClientStoreDetail(String storeId, String userId, String jingdu, String weidu);
 
     /**
      * 获取休闲娱乐分类数据(主分类和子分类)
@@ -337,6 +395,14 @@ public interface StoreInfoService extends IService<StoreInfo> {
      */
     List<StoreDictionary> getAllBusinessSection(String businessSection);
 
+    /**
+     * 获取门店下三级分类结构
+     * 根据门店ID查询该门店的经营板块、经营种类和分类的三级结构
+     *
+     * @param storeId 门店ID
+     * @return StoreThreeLevelStructureVo 包含门店信息和三级分类树形结构
+     */
+    StoreThreeLevelStructureVo getStoreThreeLevelStructure(Integer storeId);
 
     /**
      * 你可能还喜欢(推荐店铺)
@@ -352,8 +418,16 @@ public interface StoreInfoService extends IService<StoreInfo> {
      */
     List<StoreInfoVo> getRecommendedStores(String businessSection, String businessTypes, String businessClassify, Double lon, Double lat);
 
+    /**
+     * 获取三级分类数据
+     * 根据二级分类ID获取该分类下的三级分类
+     *
+     * @param parentId 二级分类ID
+     * @return List<StoreDictionaryVo> 三级分类列表
+     */
+    List<StoreDictionaryVo> getBusinessClassifyData(Integer parentId);
 
-
+    void aiApproveStoreInfo(AiApproveStoreInfo aiApproveStoreInfo);
 
 
     /**
@@ -369,47 +443,4 @@ public interface StoreInfoService extends IService<StoreInfo> {
      * @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);
-
-
 }

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

@@ -2,6 +2,8 @@ package shop.alien.store.service;
 
 import com.baomidou.mybatisplus.extension.service.IService;
 import shop.alien.entity.store.StoreOfficialAlbum;
+import shop.alien.entity.store.vo.StoreAlbumNameVo;
+import shop.alien.entity.store.vo.StoreOfficialAlbumImgVo;
 import shop.alien.entity.store.vo.StoreOfficialAlbumVo;
 
 import java.util.List;
@@ -25,7 +27,7 @@ public interface StoreOfficialAlbumService extends IService<StoreOfficialAlbum>
      * @param albumName 相册名称,可选。例如:酒水、餐食、环境、全部等。当为null或空字符串时,查询全部
      * @return 图片列表和总数
      */
-    shop.alien.entity.store.vo.StoreOfficialAlbumImgVo getOfficialAlbumImgList(Integer storeId, String albumName);
+    StoreOfficialAlbumImgVo getOfficialAlbumImgList(Integer storeId, String albumName);
 
     /**
      * 获取官方相册名称列表(客户端)
@@ -36,5 +38,5 @@ public interface StoreOfficialAlbumService extends IService<StoreOfficialAlbum>
      * @param storeId 门店ID,必填
      * @return 相册名称列表,包含相册名称和图片数量
      */
-    List<shop.alien.entity.store.vo.StoreAlbumNameVo> getAlbumNameList(Integer storeId);
+    List<StoreAlbumNameVo> getAlbumNameList(Integer storeId);
 }

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

@@ -19,6 +19,24 @@ public interface StoreStaffConfigService {
     String staffConfigExport(String status) throws IOException;
 
     /**
+     * 删除员工
+     *
+     * @param id
+     * @return
+     */
+    Integer deleteStaffConfig(Integer id);
+
+    /**
+     * 置顶员工
+     *
+     * @param id 员工ID
+     * @param topStatus 置顶状态 0-未置顶, 1-置顶
+     * @return
+     */
+    Integer setTopStatus(Integer id, Integer topStatus);
+
+
+    /**
      * 员工列表查询
      *
      * @param page    分页页数

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

@@ -254,5 +254,80 @@ public class BathFacilityServiceServiceImpl extends ServiceImpl<BathFacilityServ
         
         return result;
     }
+
+    @Override
+    public IPage<BathFacilityServiceVo> getStorePageList(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 BathFacilityServiceVo getStoreDetail(Integer id) {
+        BathFacilityService facilityService = facilityServiceMapper.selectById(id);
+        if (facilityService == null) {
+            return null;
+        }
+        return convertToVo(facilityService);
+    }
+
+    @Override
+    public List<BathFacilityServiceCategoryVo> getStoreCategorySummary(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;
+    }
 }
 

+ 260 - 0
alien-store/src/main/java/shop/alien/store/service/impl/DrinkInfoServiceImpl.java

@@ -0,0 +1,260 @@
+package shop.alien.store.service.impl;
+
+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.beans.BeanUtils;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import shop.alien.entity.store.DrinkInfo;
+import shop.alien.entity.store.vo.DrinkInfoVo;
+import shop.alien.mapper.DrinkInfoMapper;
+import shop.alien.store.service.DrinkInfoService;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * 酒水餐食信息表 服务实现类
+ *
+ * @author ssk
+ * @since 2025-06-13
+ */
+@Slf4j
+@Service
+@RequiredArgsConstructor
+@Transactional
+public class DrinkInfoServiceImpl extends ServiceImpl<DrinkInfoMapper, DrinkInfo> implements DrinkInfoService {
+
+    private final DrinkInfoMapper drinkInfoMapper;
+
+    @Override
+    public IPage<DrinkInfoVo> getDrinkInfoPage(int page, int size, Integer storeId, String type, String category, String name, Integer createUserId) {
+        LambdaQueryWrapper<DrinkInfo> queryWrapper = new LambdaQueryWrapper<>();
+        // 软删除条件
+        queryWrapper.eq(DrinkInfo::getIsDeleted, 0);
+        // 筛选条件
+        if (createUserId != null) {
+            queryWrapper.eq(DrinkInfo::getCreateUserId, createUserId);
+        }
+        if (storeId != null) {
+            queryWrapper.eq(DrinkInfo::getStoreId, storeId);
+        }
+        if (type != null && !type.isEmpty()) {
+            queryWrapper.eq(DrinkInfo::getType, type);
+        }
+        if (category != null && !category.isEmpty()) {
+            queryWrapper.eq(DrinkInfo::getCategory, category);
+        }
+        if (name != null && !name.isEmpty()) {
+            queryWrapper.like(DrinkInfo::getName, name);
+        }
+        // 按排序和创建时间倒序
+        queryWrapper.orderByAsc(DrinkInfo::getSort);
+        queryWrapper.orderByDesc(DrinkInfo::getCreateTime);
+
+        IPage<DrinkInfo> drinkInfoPage = drinkInfoMapper.selectPage(new Page<>(page, size), queryWrapper);
+        // 转换为Vo
+        IPage<DrinkInfoVo> drinkInfoVoPage = new Page<>(page, size);
+        drinkInfoVoPage.setTotal(drinkInfoPage.getTotal());
+        List<DrinkInfoVo> drinkInfoVoList = drinkInfoPage.getRecords().stream()
+                .map(this::convertToVo)
+                .collect(Collectors.toList());
+        drinkInfoVoPage.setRecords(drinkInfoVoList);
+
+        return drinkInfoVoPage;
+    }
+
+    @Override
+    public DrinkInfoVo getDrinkInfoById(Integer id) {
+        DrinkInfo drinkInfo = drinkInfoMapper.selectById(id);
+        if (drinkInfo == null || drinkInfo.getIsDeleted() == 1) {
+            return null;
+        }
+        return convertToVo(drinkInfo);
+    }
+
+    @Override
+    public boolean saveDrinkInfo(DrinkInfo drinkInfo) {
+        // 设置默认值
+        drinkInfo.setIsDeleted(0);
+        drinkInfo.setStatus(1); // 默认上架
+        drinkInfo.setSort(0); // 默认排序
+        return this.save(drinkInfo);
+    }
+
+    @Override
+    public boolean updateDrinkInfo(DrinkInfo drinkInfo) {
+        return this.updateById(drinkInfo);
+    }
+
+    @Override
+    public boolean deleteDrinkInfo(Integer id) {
+        LambdaUpdateWrapper<DrinkInfo> updateWrapper = new LambdaUpdateWrapper<>();
+        updateWrapper.eq(DrinkInfo::getId, id)
+                .set(DrinkInfo::getIsDeleted, 1);
+        return this.update(updateWrapper);
+    }
+
+    @Override
+    public boolean updateStatus(Integer id, Integer status) {
+        LambdaUpdateWrapper<DrinkInfo> updateWrapper = new LambdaUpdateWrapper<>();
+        updateWrapper.eq(DrinkInfo::getId, id)
+                .eq(DrinkInfo::getIsDeleted, 0)
+                .set(DrinkInfo::getStatus, status);
+        return this.update(updateWrapper);
+    }
+
+    @Override
+    public boolean updateIsRecommended(Integer id, Integer isRecommended) {
+        LambdaUpdateWrapper<DrinkInfo> updateWrapper = new LambdaUpdateWrapper<>();
+        updateWrapper.eq(DrinkInfo::getId, id)
+                .eq(DrinkInfo::getIsDeleted, 0)
+                .set(DrinkInfo::getIsRecommended, isRecommended);
+        return this.update(updateWrapper);
+    }
+
+    @Override
+    public boolean updateSort(Integer id, Integer sort) {
+        LambdaUpdateWrapper<DrinkInfo> updateWrapper = new LambdaUpdateWrapper<>();
+        updateWrapper.eq(DrinkInfo::getId, id)
+                .eq(DrinkInfo::getIsDeleted, 0)
+                .set(DrinkInfo::getSort, sort);
+        return this.update(updateWrapper);
+    }
+
+    @Override
+    public List<DrinkInfoVo> getDrinkInfoListByStoreId(Integer storeId, String type) {
+        LambdaQueryWrapper<DrinkInfo> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(DrinkInfo::getStoreId, storeId)
+                .eq(DrinkInfo::getIsDeleted, 0)
+                .eq(DrinkInfo::getStatus, 1); // 只查询上架的
+        if (type != null && !type.isEmpty()) {
+            queryWrapper.eq(DrinkInfo::getType, type);
+        }
+        queryWrapper.orderByAsc(DrinkInfo::getSort);
+        queryWrapper.orderByDesc(DrinkInfo::getCreateTime);
+
+        List<DrinkInfo> drinkInfoList = drinkInfoMapper.selectList(queryWrapper);
+        return drinkInfoList.stream()
+                .map(this::convertToVo)
+                .collect(Collectors.toList());
+    }
+
+    @Override
+    public List<DrinkInfoVo> getRecommendedList(Integer storeId, Integer createUserId) {
+        LambdaQueryWrapper<DrinkInfo> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(DrinkInfo::getIsRecommended, 1)
+                .eq(DrinkInfo::getIsDeleted, 0)
+                .eq(DrinkInfo::getStatus, 1); // 只查询上架的推荐商品
+        
+        // 只查询指定用户创建的商品
+        if (createUserId != null) {
+            queryWrapper.eq(DrinkInfo::getCreateUserId, createUserId);
+        }
+        
+        if (storeId != null) {
+            // 查询指定门店的商品,或者store_id为null的公共商品
+            queryWrapper.and(wrapper -> wrapper.eq(DrinkInfo::getStoreId, storeId));
+        }
+        queryWrapper.orderByAsc(DrinkInfo::getSort);
+        queryWrapper.orderByDesc(DrinkInfo::getCreateTime);
+
+        List<DrinkInfo> drinkInfoList = drinkInfoMapper.selectList(queryWrapper);
+        return drinkInfoList.stream()
+                .map(this::convertToVo)
+                .collect(Collectors.toList());
+    }
+
+    @Override
+    public boolean batchUpdateSort(List<DrinkInfo> drinkInfoList) {
+        if (drinkInfoList == null || drinkInfoList.isEmpty()) {
+            return false;
+        }
+        try {
+            for (DrinkInfo drinkInfo : drinkInfoList) {
+                if (drinkInfo.getId() != null && drinkInfo.getSort() != null) {
+                    LambdaUpdateWrapper<DrinkInfo> updateWrapper = new LambdaUpdateWrapper<>();
+                    updateWrapper.eq(DrinkInfo::getId, drinkInfo.getId())
+                            .eq(DrinkInfo::getIsDeleted, 0)
+                            .set(DrinkInfo::getSort, drinkInfo.getSort());
+                    this.update(updateWrapper);
+                }
+            }
+            return true;
+        } catch (Exception e) {
+            log.error("批量更新排序失败", e);
+            return false;
+        }
+    }
+
+    @Override
+    public java.util.Map<String, Long> getStatistics(Integer storeId) {
+        LambdaQueryWrapper<DrinkInfo> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(DrinkInfo::getIsDeleted, 0)
+                .eq(DrinkInfo::getStatus, 1); // 只统计上架的
+        if (storeId != null) {
+            queryWrapper.eq(DrinkInfo::getStoreId, storeId);
+        }
+
+        List<DrinkInfo> allList = drinkInfoMapper.selectList(queryWrapper);
+        
+        java.util.Map<String, Long> statistics = new java.util.HashMap<>();
+        statistics.put("total", (long) allList.size());
+        statistics.put("drink", allList.stream().filter(d -> "酒水".equals(d.getType())).count());
+        statistics.put("food", allList.stream().filter(d -> "餐食".equals(d.getType())).count());
+        statistics.put("recommended", allList.stream().filter(d -> d.getIsRecommended() == 1).count());
+        
+        return statistics;
+    }
+
+    @Override
+    public boolean batchDelete(List<Integer> ids) {
+        if (ids == null || ids.isEmpty()) {
+            return false;
+        }
+        try {
+            for (Integer id : ids) {
+                deleteDrinkInfo(id);
+            }
+            return true;
+        } catch (Exception e) {
+            log.error("批量删除失败", e);
+            return false;
+        }
+    }
+
+    @Override
+    public boolean batchUpdateStatus(List<Integer> ids, Integer status) {
+        if (ids == null || ids.isEmpty()) {
+            return false;
+        }
+        try {
+            for (Integer id : ids) {
+                updateStatus(id, status);
+            }
+            return true;
+        } catch (Exception e) {
+            log.error("批量更新状态失败", e);
+            return false;
+        }
+    }
+
+    /**
+     * 将DrinkInfo转换为DrinkInfoVo
+     *
+     * @param drinkInfo DrinkInfo对象
+     * @return DrinkInfoVo对象
+     */
+    private DrinkInfoVo convertToVo(DrinkInfo drinkInfo) {
+        DrinkInfoVo drinkInfoVo = new DrinkInfoVo();
+        BeanUtils.copyProperties(drinkInfo, drinkInfoVo);
+        // 确保图片字段正确映射
+        drinkInfoVo.setPicUrl(drinkInfo.getPicUrl());
+        return drinkInfoVo;
+    }
+}

+ 1 - 0
alien-store/src/main/java/shop/alien/store/service/impl/LifeDiscountCouponServiceImpl.java

@@ -686,6 +686,7 @@ public class LifeDiscountCouponServiceImpl extends ServiceImpl<LifeDiscountCoupo
             LifeDiscountCouponVo lifeDiscountCouponVo = new LifeDiscountCouponVo();
             if (null != storeInfoOne) {
                 lifeDiscountCouponVo.setBusinessSection(storeInfoOne.getBusinessSection());
+                lifeDiscountCouponVo.setBusinessSectionName(storeInfoOne.getBusinessSectionName());
             }
             lifeDiscountCouponVo.setCouponId(lifeDiscountCouponOne.getId());
             BeanUtils.copyProperties(lifeDiscountCouponOne, lifeDiscountCouponVo);

+ 256 - 0
alien-store/src/main/java/shop/alien/store/service/impl/OperationalActivityServiceImpl.java

@@ -0,0 +1,256 @@
+package shop.alien.store.service.impl;
+
+import com.alibaba.excel.util.StringUtils;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.BeanUtils;
+import org.springframework.stereotype.Service;
+import shop.alien.entity.store.LifeDiscountCoupon;
+import shop.alien.entity.store.StoreImg;
+import shop.alien.entity.store.StoreInfo;
+import shop.alien.entity.storePlatform.StoreOperationalActivity;
+import shop.alien.entity.storePlatform.vo.StoreOperationalActivityVO;
+import shop.alien.mapper.LifeDiscountCouponMapper;
+import shop.alien.mapper.StoreImgMapper;
+import shop.alien.mapper.StoreInfoMapper;
+import shop.alien.mapper.storePlantform.StoreOperationalActivityMapper;
+import shop.alien.store.service.OperationalActivityService;
+
+import java.util.*;
+
+/**
+ * 运营活动服务实现类
+ * <p>
+ * 提供运营活动的增删改查功能,包括:
+ * 1. 活动的创建、更新、删除
+ * 2. 活动列表查询、分页查询
+ * 3. 活动状态管理
+ * </p>
+ *
+ * @author system
+ * @since 2025-11-26
+ */
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class OperationalActivityServiceImpl implements OperationalActivityService {
+
+    private final StoreOperationalActivityMapper activityMapper;
+
+    private final StoreImgMapper imgMapper;
+
+    private final LifeDiscountCouponMapper lifeDiscountCouponMapper;
+
+    private final StoreInfoMapper storeInfoMapper;
+
+    @Override
+    public int auditActivity(Integer id, Integer status, String approvalComments) {
+        log.info("OperationalActivityServiceImpl.auditActivity: id={}, status={}, approvalComments={}", id, status, approvalComments);
+
+        if (id == null) {
+            throw new IllegalArgumentException("活动ID不能为空");
+        }
+        if (status == null) {
+            throw new IllegalArgumentException("审核状态不能为空");
+        }
+
+        if (!Objects.equals(status, 1) && StringUtils.isBlank(approvalComments)) {
+            throw new IllegalArgumentException("驳回原因不能为空");
+        }
+
+        StoreOperationalActivity update = new StoreOperationalActivity();
+        update.setId(id);
+        if (Objects.equals(status, 1)) {
+            StoreOperationalActivity activity = activityMapper.selectById(id);
+            if (activity == null) {
+                throw new IllegalArgumentException("活动不存在");
+            }
+            update.setStatus(resolveApprovedStatus(activity));
+            update.setApprovalComments(null);
+        } else {
+            update.setStatus(3);
+            update.setApprovalComments(approvalComments);
+        }
+
+        return activityMapper.updateById(update);
+    }
+
+    @Override
+    public IPage<StoreOperationalActivityVO> pageActivityDetail(Integer storeId, String storeName, Integer pageNum, Integer pageSize) {
+        log.info("OperationalActivityServiceImpl.pageActivityDetail: storeId={}, storeName={}, pageNum={}, pageSize={}", storeId, storeName, pageNum, pageSize);
+
+        if (storeId == null && StringUtils.isBlank(storeName)) {
+            throw new IllegalArgumentException("请至少提供商户ID或商户名称");
+        }
+
+        int current = (pageNum == null || pageNum <= 0) ? 1 : pageNum;
+        int size = (pageSize == null || pageSize <= 0) ? 10 : pageSize;
+
+        // 收集门店ID
+        LinkedHashSet<Integer> storeIds = new LinkedHashSet<>();
+        if (storeId != null) {
+            storeIds.add(storeId);
+        }
+        if (StringUtils.isNotBlank(storeName)) {
+            LambdaQueryWrapper<StoreInfo> storeWrapper = new LambdaQueryWrapper<>();
+            storeWrapper.like(StoreInfo::getStoreName, storeName)
+                    .eq(StoreInfo::getDeleteFlag, 0)
+                    .select(StoreInfo::getId, StoreInfo::getStoreName);
+            List<StoreInfo> storeList = storeInfoMapper.selectList(storeWrapper);
+            for (StoreInfo info : storeList) {
+                if (info.getId() != null) {
+                    storeIds.add(info.getId());
+                }
+            }
+        }
+
+        if (storeIds.isEmpty()) {
+            return new Page<>(current, size, 0);
+        }
+
+        LambdaQueryWrapper<StoreOperationalActivity> wrapper = new LambdaQueryWrapper<>();
+        wrapper.in(StoreOperationalActivity::getStoreId, storeIds);
+        wrapper.eq(StoreOperationalActivity::getDeleteFlag, 0);
+        wrapper.orderByDesc(StoreOperationalActivity::getCreatedTime);
+
+        IPage<StoreOperationalActivity> entityPage = activityMapper.selectPage(new Page<>(current, size), wrapper);
+
+        List<StoreOperationalActivityVO> voRecords = new ArrayList<>(entityPage.getRecords().size());
+        Map<Integer, String> storeCache = new LinkedHashMap<>();
+
+        for (StoreOperationalActivity activity : entityPage.getRecords()) {
+            StoreOperationalActivityVO vo = new StoreOperationalActivityVO();
+            BeanUtils.copyProperties(activity, vo);
+            vo.setStatusName(resolveStatusName(activity.getStatus()));
+
+            if (activity.getCouponId() != null) {
+                LifeDiscountCoupon coupon = lifeDiscountCouponMapper.selectById(activity.getCouponId());
+                if (coupon != null) {
+                    vo.setCouponName(coupon.getName());
+                }
+            }
+
+            attachStoreInfo(vo, storeCache);
+            fillActivityImages(vo);
+            voRecords.add(vo);
+        }
+
+        Page<StoreOperationalActivityVO> voPage = new Page<>(entityPage.getCurrent(), entityPage.getSize(), entityPage.getTotal());
+        voPage.setRecords(voRecords);
+        return voPage;
+    }
+
+    @Override
+    public StoreOperationalActivityVO getActivityDetailById(Integer id) {
+        log.info("OperationalActivityServiceImpl.getActivityDetailById: id={}", id);
+
+        if (id == null) {
+            throw new IllegalArgumentException("活动ID不能为空");
+        }
+
+        StoreOperationalActivity activity = activityMapper.selectById(id);
+        if (activity == null) {
+            return null;
+        }
+
+        StoreOperationalActivityVO vo = new StoreOperationalActivityVO();
+        BeanUtils.copyProperties(activity, vo);
+        vo.setStatusName(resolveStatusName(activity.getStatus()));
+
+        if (activity.getCouponId() != null) {
+            LifeDiscountCoupon coupon = lifeDiscountCouponMapper.selectById(activity.getCouponId());
+            if (coupon != null) {
+                vo.setCouponName(coupon.getName());
+            }
+        }
+
+        attachStoreInfo(vo, new LinkedHashMap<>());
+        fillActivityImages(vo);
+        return vo;
+    }
+
+    private int resolveApprovedStatus(StoreOperationalActivity activity) {
+        Date now = new Date();
+        if (activity.getStartTime() == null || activity.getEndTime() == null) {
+            return 2;
+        }
+        if (now.before(activity.getStartTime())) {
+            return 2;
+        }
+        if (!now.after(activity.getEndTime())) {
+            return 5;
+        }
+        return 7;
+    }
+
+    private String resolveStatusName(Integer status) {
+        if (status == null) {
+            return null;
+        }
+        switch (status) {
+            case 1:
+                return "待审核";
+            case 2:
+                return "未开始";
+            case 3:
+                return "审核拒绝";
+            case 4:
+                return "已售罄";
+            case 5:
+                return "进行中";
+            case 6:
+                return "已下架";
+            case 7:
+                return "已结束";
+            default:
+                return null;
+        }
+    }
+
+    private void attachStoreInfo(StoreOperationalActivityVO vo, Map<Integer, String> cache) {
+        if (vo == null || vo.getStoreId() == null) {
+            return;
+        }
+        Integer sid = vo.getStoreId();
+        if (cache != null && cache.containsKey(sid)) {
+            vo.setStoreName(cache.get(sid));
+            return;
+        }
+        StoreInfo storeInfo = storeInfoMapper.selectById(sid);
+        if (storeInfo != null) {
+            vo.setStoreName(storeInfo.getStoreName());
+            if (cache != null) {
+                cache.put(sid, storeInfo.getStoreName());
+            }
+        }
+    }
+
+    private void fillActivityImages(StoreOperationalActivityVO vo) {
+        if (vo == null || vo.getStoreId() == null || vo.getId() == null) {
+            return;
+        }
+        StoreImg titleImg = imgMapper.selectOne(new LambdaQueryWrapper<StoreImg>()
+                .eq(StoreImg::getStoreId, vo.getStoreId())
+                .eq(StoreImg::getBusinessId, vo.getId())
+                .eq(StoreImg::getImgType, 26)
+                .eq(StoreImg::getDeleteFlag, 0));
+        if (titleImg != null) {
+            vo.setActivityTitleImg(titleImg);
+            vo.setActivityTitleImgUrl(titleImg.getImgUrl());
+        }
+
+        StoreImg detailImg = imgMapper.selectOne(new LambdaQueryWrapper<StoreImg>()
+                .eq(StoreImg::getStoreId, vo.getStoreId())
+                .eq(StoreImg::getBusinessId, vo.getId())
+                .eq(StoreImg::getImgType, 27)
+                .eq(StoreImg::getDeleteFlag, 0));
+        if (detailImg != null) {
+            vo.setActivityDetailImg(detailImg);
+            vo.setActivityDetailImgUrl(detailImg.getImgUrl());
+        }
+    }
+}
+

Datei-Diff unterdrückt, da er zu groß ist
+ 358 - 127
alien-store/src/main/java/shop/alien/store/service/impl/StoreInfoServiceImpl.java


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

@@ -83,7 +83,7 @@ public class StoreMenuServiceImpl extends ServiceImpl<StoreMenuMapper, StoreMenu
     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);
+        List<StoreMenuVo> menuList = storeMenuMapper.getStoreMenuInfoList(storeId, queryDishType, dishMenuType);
 
         // 如果是推荐菜且有用户标识,批量查询点赞状态
         if (dishType != null && dishType == 1 && StringUtils.isNotEmpty(phoneId)

+ 65 - 6
alien-store/src/main/java/shop/alien/store/service/impl/StoreStaffConfigServiceImpl.java

@@ -1,6 +1,7 @@
 package shop.alien.store.service.impl;
 
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.core.toolkit.StringUtils;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
@@ -51,18 +52,54 @@ public class StoreStaffConfigServiceImpl implements StoreStaffConfigService {
         IPage<StoreStaffConfig> storePage = new Page<>(page, size);
         QueryWrapper<StoreStaffConfig> queryWrapper = new QueryWrapper<>();
         queryWrapper.like(null != status && !status.isEmpty(), "status", status);
-        queryWrapper.orderByDesc("created_time");
+        // 排序规则:先按置顶状态降序(置顶的在前),再按置顶时间降序,最后按创建时间降序
+        queryWrapper.orderByDesc("top_status", "top_time", "created_time");
         return storeStaffConfigMapper.selectPage(storePage, queryWrapper);
     }
 
     @Override
     public int addOrUpdateStaffConfig(StoreStaffConfig storeStaffConfig) {
-        if (StringUtils.isEmpty(storeStaffConfig.getId().toString())) {
+        // 判断是新增还是更新:id为null或0或负数表示新增
+        // 注意:新增时不应该传入id,如果传入了非0的id,需要先查询是否存在
+        Integer id = storeStaffConfig.getId();
+        
+        if (id == null || id == 0) {
+            // 新增操作:id为null或0
             Date nowDate = new Date(System.currentTimeMillis());
             storeStaffConfig.setCreatedTime(nowDate);
+            // 设置删除标记为0(未删除)- 必须设置,否则可能插入失败
+            if (storeStaffConfig.getDeleteFlag() == null) {
+                storeStaffConfig.setDeleteFlag(0);
+            }
+            // 如果状态为空,设置默认状态为待审核(0)
+            if (StringUtils.isEmpty(storeStaffConfig.getStatus())) {
+                storeStaffConfig.setStatus("0");
+            }
+            // 新增时,确保id为null,让数据库自动生成
+            storeStaffConfig.setId(null);
             return storeStaffConfigMapper.insert(storeStaffConfig);
         } else {
-            return storeStaffConfigMapper.updateById(storeStaffConfig);
+            // 更新操作:id不为null且不为0
+            // 先查询记录是否存在,如果不存在则转为新增
+            StoreStaffConfig existing = storeStaffConfigMapper.selectById(id);
+            if (existing == null) {
+                // 记录不存在,转为新增操作
+                storeStaffConfig.setId(null); // 重置id,让数据库自动生成
+                Date nowDate = new Date(System.currentTimeMillis());
+                storeStaffConfig.setCreatedTime(nowDate);
+                // 设置删除标记为0(未删除)
+                if (storeStaffConfig.getDeleteFlag() == null) {
+                    storeStaffConfig.setDeleteFlag(0);
+                }
+                // 如果状态为空,设置默认状态为待审核(0)
+                if (StringUtils.isEmpty(storeStaffConfig.getStatus())) {
+                    storeStaffConfig.setStatus("0");
+                }
+                return storeStaffConfigMapper.insert(storeStaffConfig);
+            } else {
+                // 记录存在,执行更新
+                return storeStaffConfigMapper.updateById(storeStaffConfig);
+            }
         }
     }
 
@@ -110,6 +147,28 @@ public class StoreStaffConfigServiceImpl implements StoreStaffConfigService {
         return aliOSSUtil.uploadFile(new File(filePath), "excel/" + fileName + ".xlsx");
     }
 
+    @Override
+    public Integer deleteStaffConfig(Integer id) {
+        // 使用 MyBatis-Plus 的逻辑删除,会自动将 deleteFlag 设置为 1
+        // 因为实体类使用了 @TableLogic 注解
+        return storeStaffConfigMapper.deleteById(id);
+    }
+
+    @Override
+    public Integer setTopStatus(Integer id, Integer topStatus) {
+        if (id == null || topStatus == null) {
+            return 0;
+        }
+        LambdaUpdateWrapper<StoreStaffConfig> lambdaUpdateWrapper = new LambdaUpdateWrapper<>();
+        lambdaUpdateWrapper.eq(StoreStaffConfig::getId, id);
+        lambdaUpdateWrapper.set(StoreStaffConfig::getTopStatus, topStatus);
+        if (topStatus == 0) {
+            lambdaUpdateWrapper.set(StoreStaffConfig::getTopTime, null);
+        } else {
+            lambdaUpdateWrapper.set(StoreStaffConfig::getTopTime, new Date());
+        }
+        return storeStaffConfigMapper.update(null, lambdaUpdateWrapper);
+    }
 
     @Override
     public IPage<StoreStaffConfig> queryStaffList(Integer page, Integer size, Integer storeId, String status) {
@@ -120,9 +179,9 @@ public class StoreStaffConfigServiceImpl implements StoreStaffConfigService {
             queryWrapper.eq("store_id", storeId);
         }
         // 如果状态不为空,则进行精确匹配查询
-        if (StringUtils.isNotEmpty(status)) {
-            queryWrapper.eq("status", status);
-        }
+//        if (StringUtils.isNotEmpty(status)) {
+//            queryWrapper.eq("status", status);
+//        }
         // 只查询未删除的记录
         queryWrapper.eq("delete_flag", 0);
         queryWrapper.orderByDesc("created_time");

+ 313 - 0
alien-store/src/main/java/shop/alien/store/service/impl/UserActionRewardRuleServiceImpl.java

@@ -0,0 +1,313 @@
+package shop.alien.store.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+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.stereotype.Service;
+import shop.alien.entity.result.R;
+import shop.alien.entity.store.UserActionRewardRule;
+import shop.alien.entity.store.vo.RewardRuleTreeNodeVo;
+import shop.alien.mapper.UserActionRewardRuleMapper;
+import shop.alien.store.service.UserActionRewardRuleService;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * 用户行为奖励规则配置表 服务实现类
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class UserActionRewardRuleServiceImpl extends ServiceImpl<UserActionRewardRuleMapper, UserActionRewardRule> implements UserActionRewardRuleService {
+
+    private final UserActionRewardRuleMapper userActionRewardRuleMapper;
+
+
+    @Override
+    public List<RewardRuleTreeNodeVo> getRewardRuleList(int page, int size, String role, String action, String reward, Integer status) {
+        // 查询所有数据(不分页,因为需要构建完整树形结构)
+        LambdaQueryWrapper<UserActionRewardRule> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(UserActionRewardRule::getDeleteFlag, 0);
+        
+        // 如果指定了status,需要过滤
+        if (status != null) {
+            queryWrapper.eq(UserActionRewardRule::getStatus, status);
+        }
+        
+        queryWrapper.orderByDesc(UserActionRewardRule::getSortOrder);
+        queryWrapper.orderByAsc(UserActionRewardRule::getId);
+        
+        List<UserActionRewardRule> allRecords = userActionRewardRuleMapper.selectList(queryWrapper);
+        
+        // 如果有查询条件,先过滤出匹配的节点ID集合
+        Set<Integer> matchedNodeIds = new HashSet<>();
+        if (StringUtils.isNotEmpty(role) || StringUtils.isNotEmpty(action) || StringUtils.isNotEmpty(reward)) {
+            for (UserActionRewardRule record : allRecords) {
+                boolean matched = false;
+                if (StringUtils.isNotEmpty(role) && record.getLevel() == 1 && record.getName().contains(role)) {
+                    matched = true;
+                }
+                if (StringUtils.isNotEmpty(action) && record.getLevel() == 2 && record.getName().contains(action)) {
+                    matched = true;
+                }
+                if (StringUtils.isNotEmpty(reward) && record.getLevel() == 3 && record.getName().contains(reward)) {
+                    matched = true;
+                }
+                if (matched) {
+                    // 收集该节点及其所有父节点和子节点
+                    collectRelatedNodeIds(record.getId(), allRecords, matchedNodeIds);
+                }
+            }
+        }
+        
+        // 如果有匹配条件,只保留匹配的节点
+        if (!matchedNodeIds.isEmpty()) {
+            allRecords = allRecords.stream()
+                .filter(record -> matchedNodeIds.contains(record.getId()))
+                .collect(Collectors.toList());
+        }
+        
+        // 构建树形结构:使用parent_id关联
+        Map<Integer, List<UserActionRewardRule>> childrenMap = new LinkedHashMap<>();
+        
+        // 先收集所有节点,按parent_id分组
+        for (UserActionRewardRule record : allRecords) {
+            Integer parentId = record.getParentId() == null ? 0 : record.getParentId();
+            childrenMap.computeIfAbsent(parentId, k -> new ArrayList<>()).add(record);
+        }
+        
+        // 递归构建树形结构
+        List<RewardRuleTreeNodeVo> result = new ArrayList<>();
+        List<UserActionRewardRule> rootNodes = childrenMap.getOrDefault(0, new ArrayList<>());
+        for (UserActionRewardRule rootNode : rootNodes) {
+            if (rootNode.getLevel() == 1) {
+                RewardRuleTreeNodeVo roleNode = buildTreeNode(rootNode, childrenMap);
+                result.add(roleNode);
+            }
+        }
+        
+        // 对结果进行分页处理(只对第一级节点分页)
+        if (result.size() > 0) {
+            int start = (page - 1) * size;
+            int end = Math.min(start + size, result.size());
+            if (start < result.size()) {
+                return result.subList(start, end);
+            }
+        }
+        
+        return result;
+    }
+    
+    /**
+     * 收集相关节点ID(包括父节点和子节点)
+     */
+    private void collectRelatedNodeIds(Integer nodeId, List<UserActionRewardRule> allRecords, Set<Integer> nodeIds) {
+        if (nodeIds.contains(nodeId)) {
+            return;
+        }
+        nodeIds.add(nodeId);
+        
+        // 查找当前节点
+        UserActionRewardRule currentNode = allRecords.stream()
+            .filter(r -> r.getId().equals(nodeId))
+            .findFirst()
+            .orElse(null);
+        
+        if (currentNode == null) {
+            return;
+        }
+        
+        // 向上查找父节点
+        if (currentNode.getParentId() != null && currentNode.getParentId() != 0) {
+            collectRelatedNodeIds(currentNode.getParentId(), allRecords, nodeIds);
+        }
+        
+        // 向下查找子节点
+        for (UserActionRewardRule record : allRecords) {
+            if (nodeId.equals(record.getParentId())) {
+                collectRelatedNodeIds(record.getId(), allRecords, nodeIds);
+            }
+        }
+    }
+    
+    /**
+     * 递归构建树形节点
+     */
+    private RewardRuleTreeNodeVo buildTreeNode(UserActionRewardRule node, Map<Integer, List<UserActionRewardRule>> childrenMap) {
+        RewardRuleTreeNodeVo treeNode = new RewardRuleTreeNodeVo();
+        treeNode.setValue(node.getName());
+        treeNode.setLabel(node.getName());
+        // disabled逻辑:status=1表示启用,所以disabled应该是status!=1
+        treeNode.setDisabled(node.getStatus() == null || node.getStatus() != 1);
+        
+        // 获取子节点
+        List<UserActionRewardRule> children = childrenMap.getOrDefault(node.getId(), new ArrayList<>());
+        if (!children.isEmpty()) {
+            List<RewardRuleTreeNodeVo> childNodes = new ArrayList<>();
+            // 按sort_order降序排序,然后按id升序排序
+            children.sort((a, b) -> {
+                int sortCompare = Integer.compare(
+                    b.getSortOrder() == null ? 0 : b.getSortOrder(),
+                    a.getSortOrder() == null ? 0 : a.getSortOrder()
+                );
+                if (sortCompare != 0) {
+                    return sortCompare;
+                }
+                return Integer.compare(a.getId(), b.getId());
+            });
+            
+            for (UserActionRewardRule child : children) {
+                RewardRuleTreeNodeVo childNode = buildTreeNode(child, childrenMap);
+                childNodes.add(childNode);
+            }
+            treeNode.setChildren(childNodes);
+        }
+        
+        return treeNode;
+    }
+
+    @Override
+    public UserActionRewardRule getRewardRuleById(Integer id) {
+        if (id == null) {
+            return null;
+        }
+        LambdaQueryWrapper<UserActionRewardRule> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(UserActionRewardRule::getId, id);
+        queryWrapper.eq(UserActionRewardRule::getDeleteFlag, 0);
+        return userActionRewardRuleMapper.selectOne(queryWrapper);
+    }
+
+    @Override
+    public R<String> addOrUpdateRewardRule(UserActionRewardRule rewardRule) {
+        if (rewardRule == null) {
+            return R.fail("奖励规则信息不能为空");
+        }
+
+        // 校验必填字段
+        if (StringUtils.isEmpty(rewardRule.getName())) {
+            return R.fail("名称不能为空");
+        }
+        if (rewardRule.getLevel() == null || rewardRule.getLevel() < 1 || rewardRule.getLevel() > 3) {
+            return R.fail("层级必须为1(角色)、2(行为)或3(奖励)");
+        }
+        if (rewardRule.getLevel() > 1 && rewardRule.getParentId() == null) {
+            return R.fail("非根节点必须指定父节点ID");
+        }
+
+        try {
+            if (rewardRule.getId() == null) {
+                // 新增
+                if (rewardRule.getParentId() == null) {
+                    rewardRule.setParentId(0); // 根节点parent_id为0
+                }
+                if (rewardRule.getStatus() == null) {
+                    rewardRule.setStatus(1); // 默认启用
+                }
+                if (rewardRule.getSortOrder() == null) {
+                    rewardRule.setSortOrder(0); // 默认排序为0
+                }
+                int result = userActionRewardRuleMapper.insert(rewardRule);
+                if (result > 0) {
+                    return R.data("新增成功");
+                } else {
+                    return R.fail("新增失败");
+                }
+            } else {
+                // 更新
+                int result = userActionRewardRuleMapper.updateById(rewardRule);
+                if (result > 0) {
+                    return R.data("更新成功");
+                } else {
+                    return R.fail("更新失败");
+                }
+            }
+        } catch (Exception e) {
+            log.error("保存奖励规则失败", e);
+            return R.fail("保存失败:" + e.getMessage());
+        }
+    }
+
+    @Override
+    public R<String> deleteRewardRuleById(Integer id) {
+        if (id == null) {
+            return R.fail("ID不能为空");
+        }
+
+        try {
+            // 查找所有需要删除的节点ID(包括子节点)
+            Set<Integer> nodeIdsToDelete = new HashSet<>();
+            collectChildNodeIds(id, nodeIdsToDelete);
+            
+            if (nodeIdsToDelete.isEmpty()) {
+                return R.fail("删除失败,记录不存在或已删除");
+            }
+            
+            // 批量逻辑删除
+            LambdaUpdateWrapper<UserActionRewardRule> updateWrapper = new LambdaUpdateWrapper<>();
+            updateWrapper.in(UserActionRewardRule::getId, nodeIdsToDelete)
+                    .set(UserActionRewardRule::getDeleteFlag, 1);
+            int result = userActionRewardRuleMapper.update(null, updateWrapper);
+            if (result > 0) {
+                return R.data("删除成功,共删除 " + result + " 条记录(含子节点)");
+            } else {
+                return R.fail("删除失败,记录不存在或已删除");
+            }
+        } catch (Exception e) {
+            log.error("删除奖励规则失败", e);
+            return R.fail("删除失败:" + e.getMessage());
+        }
+    }
+    
+    /**
+     * 递归收集所有子节点ID
+     */
+    private void collectChildNodeIds(Integer parentId, Set<Integer> nodeIds) {
+        if (nodeIds.contains(parentId)) {
+            return;
+        }
+        nodeIds.add(parentId);
+        
+        // 查找所有子节点
+        LambdaQueryWrapper<UserActionRewardRule> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(UserActionRewardRule::getParentId, parentId)
+                .eq(UserActionRewardRule::getDeleteFlag, 0);
+        List<UserActionRewardRule> children = userActionRewardRuleMapper.selectList(queryWrapper);
+        
+        for (UserActionRewardRule child : children) {
+            collectChildNodeIds(child.getId(), nodeIds);
+        }
+    }
+
+    @Override
+    public R<String> updateRewardRuleStatus(Integer id, Integer status) {
+        if (id == null) {
+            return R.fail("ID不能为空");
+        }
+        if (status == null || (status != 0 && status != 1)) {
+            return R.fail("状态值无效,只能为0(禁用)或1(启用)");
+        }
+
+        try {
+            LambdaUpdateWrapper<UserActionRewardRule> updateWrapper = new LambdaUpdateWrapper<>();
+            updateWrapper.eq(UserActionRewardRule::getId, id)
+                    .set(UserActionRewardRule::getStatus, status);
+            int result = userActionRewardRuleMapper.update(null, updateWrapper);
+            if (result > 0) {
+                return R.data(status == 1 ? "启用成功" : "禁用成功");
+            } else {
+                return R.fail("更新失败,记录不存在或已删除");
+            }
+        } catch (Exception e) {
+            log.error("更新奖励规则状态失败", e);
+            return R.fail("更新失败:" + e.getMessage());
+        }
+    }
+}
+

Einige Dateien werden nicht angezeigt, da zu viele Dateien in diesem Diff geändert wurden.