Browse Source

Merge remote-tracking branch 'origin/dev' into dev

lyx 3 weeks ago
parent
commit
e15e915a18
36 changed files with 1549 additions and 155 deletions
  1. 6 0
      alien-entity/pom.xml
  2. 17 0
      alien-entity/src/main/java/shop/alien/entity/store/LawyerConsultationOrder.java
  3. 43 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/LawFirmExportResponseVO.java
  4. 46 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/LawFirmListVO.java
  5. 49 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/LawyerExportResponseVO.java
  6. 62 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/LawyerListVO.java
  7. 1 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/OrderRevenueVO.java
  8. 5 3
      alien-entity/src/main/java/shop/alien/mapper/LawFirmMapper.java
  9. 76 7
      alien-entity/src/main/java/shop/alien/mapper/LawyerConsultationOrderMapper.java
  10. 8 0
      alien-entity/src/main/java/shop/alien/mapper/LawyerServiceAreaMapper.java
  11. 2 2
      alien-entity/src/main/resources/mapper/LawFirmMapper.xml
  12. 18 0
      alien-lawyer/pom.xml
  13. 17 0
      alien-lawyer/src/main/java/shop/alien/lawyer/controller/AliController.java
  14. 71 3
      alien-lawyer/src/main/java/shop/alien/lawyer/controller/LawFirmReconciliationController.java
  15. 26 0
      alien-lawyer/src/main/java/shop/alien/lawyer/controller/LawyerClientConsultationOrderController.java
  16. 26 0
      alien-lawyer/src/main/java/shop/alien/lawyer/controller/LawyerConsultationOrderController.java
  17. 1 1
      alien-lawyer/src/main/java/shop/alien/lawyer/controller/LawyerImgController.java
  18. 48 3
      alien-lawyer/src/main/java/shop/alien/lawyer/service/LawFirmReconciliationService.java
  19. 9 0
      alien-lawyer/src/main/java/shop/alien/lawyer/service/LawyerClientConsultationOrderService.java
  20. 21 1
      alien-lawyer/src/main/java/shop/alien/lawyer/service/LawyerConsultationOrderService.java
  21. 78 9
      alien-lawyer/src/main/java/shop/alien/lawyer/service/impl/LawFirmReconciliationServiceImpl.java
  22. 88 45
      alien-lawyer/src/main/java/shop/alien/lawyer/service/impl/LawyerClientConsultationOrderServiceImpl.java
  23. 362 18
      alien-lawyer/src/main/java/shop/alien/lawyer/service/impl/LawyerConsultationOrderServiceImpl.java
  24. 14 1
      alien-lawyer/src/main/java/shop/alien/lawyer/service/impl/LawyerUserServiceImpl.java
  25. 13 0
      alien-lawyer/src/main/java/shop/alien/lawyer/service/impl/LawyerUserViolationServiceImpl.java
  26. 93 11
      alien-lawyer/src/main/java/shop/alien/lawyer/service/impl/OrderExpirationServiceImpl.java
  27. 1 1
      alien-store/src/main/java/shop/alien/store/controller/LawyerImgController.java
  28. 1 1
      alien-store/src/main/java/shop/alien/store/controller/StoreImgController.java
  29. 1 6
      alien-store/src/main/java/shop/alien/store/service/impl/StoreDictServiceImpl.java
  30. 2 1
      alien-store/src/main/java/shop/alien/store/service/impl/StoreOfficialAlbumServiceImpl.java
  31. 1 1
      alien-util/pom.xml
  32. 45 0
      alien-util/src/main/java/shop/alien/util/common/WebTool.java
  33. 261 0
      alien-util/src/main/java/shop/alien/util/excel/EasyExcelUtil.java
  34. 35 38
      alien-util/src/main/java/shop/alien/util/excel/ExcelWriteTest.java
  35. 1 2
      alien-util/src/main/java/shop/alien/util/excel/UserExcel.java
  36. 1 1
      alien-util/src/main/java/shop/alien/util/port/StartCommand.java

+ 6 - 0
alien-entity/pom.xml

@@ -76,6 +76,12 @@
             <version>2.0.53</version>
             <scope>compile</scope>
         </dependency>
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>easyexcel</artifactId>
+            <version>4.0.3</version>
+            <scope>compile</scope>
+        </dependency>
 
     </dependencies>
 

+ 17 - 0
alien-entity/src/main/java/shop/alien/entity/store/LawyerConsultationOrder.java

@@ -146,5 +146,22 @@ public class LawyerConsultationOrder extends Model<LawyerConsultationOrder> {
     @ApiModelProperty(value = "律师接单状态  0接单  1拒绝接单")
     @TableField("accept_orders_status")
     private  Integer acceptOrdersStatus;
+
+    @ApiModelProperty(value = "申请退款状态,0-未申请,1-已申请,2-律师已拒绝,3-律师已同意")
+    @TableField("apply_refund_status")
+    private String applyRefundStatus;
+
+    @ApiModelProperty(value = "申请退款时间")
+    @TableField(value = "apply_refund_time", fill = FieldFill.UPDATE)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date applyRefundTime;
+
+    @ApiModelProperty(value = "申请退款原因")
+    @TableField("apply_refund_reason")
+    private String applyRefundReason;
+
+    @ApiModelProperty(value = "拒绝退款原因")
+    @TableField("reject_refund_reason")
+    private String rejectRefundReason;
 }
 

+ 43 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/LawFirmExportResponseVO.java

@@ -0,0 +1,43 @@
+package shop.alien.entity.store.vo;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * 律所对账导出响应对象
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@ApiModel(value = "LawFirmExportResponseVO对象", description = "律所对账导出响应对象")
+public class LawFirmExportResponseVO implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "导出状态:true-成功,false-失败")
+    private Boolean success;
+
+    @ApiModelProperty(value = "导出消息")
+    private String message;
+
+    @ApiModelProperty(value = "查询到的总数据条数")
+    private Integer totalCount;
+
+    @ApiModelProperty(value = "实际导出的数据条数")
+    private Integer exportCount;
+
+    @ApiModelProperty(value = "文件名")
+    private String fileName;
+
+    @ApiModelProperty(value = "是否导出全部数据:true-全部,false-本页")
+    private Boolean exportAll;
+}
+

+ 46 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/LawFirmListVO.java

@@ -0,0 +1,46 @@
+package shop.alien.entity.store.vo;
+
+import com.alibaba.excel.annotation.ExcelIgnore;
+import com.alibaba.excel.annotation.ExcelProperty;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 律所对账列表视图对象
+ * 用于 /getAllLawFirmList 接口
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Data
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@ApiModel(value = "LawFirmListVO对象", description = "律所对账列表视图对象")
+public class LawFirmListVO implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @ExcelIgnore
+    @ApiModelProperty(value = "律所ID")
+    private Integer firmId;
+
+    @ExcelProperty(value = "律所名称", index = 0)
+    @ApiModelProperty(value = "律所名称")
+    private String firmName;
+
+    @ExcelProperty(value = "总订单数量", index = 1)
+    @ApiModelProperty(value = "总订单数量")
+    private Long totalOrderCount;
+
+    @ExcelProperty(value = "总订单金额(元)", index = 2)
+    @ApiModelProperty(value = "总订单金额(单位:元)")
+    private String totalOrderAmountYuan;
+
+    @ExcelProperty(value = "平台信息服务费(元)", index = 3)
+    @ApiModelProperty(value = "平台信息服务费(单位:元)")
+    private String platformServiceFeeYuan;
+}
+

+ 49 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/LawyerExportResponseVO.java

@@ -0,0 +1,49 @@
+package shop.alien.entity.store.vo;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * 律师对账导出响应对象
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@ApiModel(value = "LawyerExportResponseVO对象", description = "律师对账导出响应对象")
+public class LawyerExportResponseVO implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "导出状态:true-成功,false-失败")
+    private Boolean success;
+
+    @ApiModelProperty(value = "导出消息")
+    private String message;
+
+    @ApiModelProperty(value = "律所ID")
+    private Integer firmId;
+
+    @ApiModelProperty(value = "律所名称")
+    private String firmName;
+
+    @ApiModelProperty(value = "查询到的总数据条数")
+    private Integer totalCount;
+
+    @ApiModelProperty(value = "实际导出的数据条数")
+    private Integer exportCount;
+
+    @ApiModelProperty(value = "文件名")
+    private String fileName;
+
+    @ApiModelProperty(value = "是否导出全部数据:true-全部,false-本页")
+    private Boolean exportAll;
+}
+

+ 62 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/LawyerListVO.java

@@ -0,0 +1,62 @@
+package shop.alien.entity.store.vo;
+
+import com.alibaba.excel.annotation.ExcelIgnore;
+import com.alibaba.excel.annotation.ExcelProperty;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 律师对账列表视图对象
+ * 用于 /getLawyerListWithName 接口
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Data
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@ApiModel(value = "LawyerListVO对象", description = "律师对账列表视图对象")
+public class LawyerListVO implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @ExcelIgnore
+    @ApiModelProperty(value = "律师ID")
+    private Integer lawyerId;
+
+    @ExcelProperty(value = "律所名称", index = 0)
+    @ApiModelProperty(value = "律所名称")
+    private String firmName;
+
+    @ExcelProperty(value = "律师名称", index = 1)
+    @ApiModelProperty(value = "律师名称")
+    private String lawyerName;
+
+    @ExcelProperty(value = "律师执业证号", index = 2)
+    @ApiModelProperty(value = "律师执业证号")
+    private String lawyerCertificateNo;
+
+    @ExcelProperty(value = "总订单数量", index = 3)
+    @ApiModelProperty(value = "总订单数量")
+    private Long totalOrderCount;
+
+    @ExcelProperty(value = "总订单金额(元)", index = 4)
+    @ApiModelProperty(value = "总订单金额(单位:元)")
+    private String totalOrderAmountYuan;
+
+    @ExcelProperty(value = "平台信息服务费(元)", index = 5)
+    @ApiModelProperty(value = "平台信息服务费(单位:元)")
+    private String platformServiceFeeYuan;
+
+    @ExcelIgnore
+    @ApiModelProperty(value = "律师头像")
+    private String headImg;
+
+    @ExcelIgnore
+    @ApiModelProperty(value = "律所ID")
+    private Integer firmId;
+}
+

+ 1 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/OrderRevenueVO.java

@@ -44,3 +44,4 @@ public class OrderRevenueVO implements Serializable {
     private Long completedRevenueStr;
 }
 
+

+ 5 - 3
alien-entity/src/main/java/shop/alien/mapper/LawFirmMapper.java

@@ -6,7 +6,9 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import org.apache.ibatis.annotations.Mapper;
 import org.apache.ibatis.annotations.Param;
 import shop.alien.entity.store.LawFirm;
+import shop.alien.entity.store.vo.LawFirmListVO;
 import shop.alien.entity.store.vo.LawFirmReconciliationVO;
+import shop.alien.entity.store.vo.LawyerListVO;
 
 import java.util.Date;
 import java.util.List;
@@ -45,7 +47,7 @@ public interface LawFirmMapper extends BaseMapper<LawFirm> {
      * @param endDate   结束日期
      * @return 律所对账统计列表
      */
-    List<LawFirmReconciliationVO> getAllLawFirmReconciliationStatistics(
+    List<LawFirmListVO> getAllLawFirmReconciliationStatistics(
             @Param("firmName") String firmName,
             @Param("startDate") Date startDate,
             @Param("endDate") Date endDate
@@ -59,7 +61,7 @@ public interface LawFirmMapper extends BaseMapper<LawFirm> {
      * @param endDate   结束日期(可选)
      * @return 律师对账统计列表
      */
-    List<LawFirmReconciliationVO> getLawyerReconciliationStatistics(
+    List<LawyerListVO> getLawyerReconciliationStatistics(
             @Param("firmId") Integer firmId,
             @Param("startDate") Date startDate,
             @Param("endDate") Date endDate
@@ -74,7 +76,7 @@ public interface LawFirmMapper extends BaseMapper<LawFirm> {
      * @param endDate   结束日期(可选)
      * @return 律师对账统计列表
      */
-    List<LawFirmReconciliationVO> getLawyerReconciliationStatisticsWithName(
+    List<LawyerListVO> getLawyerReconciliationStatisticsWithName(
             @Param("firmId") Integer firmId,
             @Param("lawyerName") String lawyerName,
             @Param("startDate") Date startDate,

+ 76 - 7
alien-entity/src/main/java/shop/alien/mapper/LawyerConsultationOrderMapper.java

@@ -95,10 +95,10 @@ public interface LawyerConsultationOrderMapper extends BaseMapper<LawyerConsulta
             "`alipay_no`," +
             "`order_str`," +
             "place_id," +
-            "lawyer_earnings" +
-            "accept_orders_time" +
-            "reason_order_refusal" +
-            "accept_orders_status" +
+            "lawyer_earnings ," +
+            "accept_orders_time ," +
+            "reason_order_refusal ," +
+            "accept_orders_status " +
             ")"+
             "VALUES" +
             " (" +
@@ -127,14 +127,83 @@ public interface LawyerConsultationOrderMapper extends BaseMapper<LawyerConsulta
             " #{alipayNo}," +
             " #{orderStr}," +
             "#{placeId}," +
-            "#{lawyerEarnings}" +
-            "#{acceptOrdersTime}" +
-            "#{reasonOrderRefusal}" +
+            "#{lawyerEarnings} ," +
+            "#{acceptOrdersTime} ," +
+            "#{reasonOrderRefusal} ," +
             "#{acceptOrdersStatus}" +
             ")")
             int insertOrder(LawyerConsultationOrderDto order);
 
 
+//    @Insert("<script>" +
+//            "INSERT INTO `alien`.`lawyer_consultation_order`" +
+//            "<trim prefix=\"(\" suffix=\")\" suffixOverrides=\",\">" +
+//            "<if test='id != null'>`id`,</if>" +
+//            "<if test='orderNumber != null'>`order_number`,</if>" +
+//            "<if test='clientUserId != null'>`client_user_id`,</if>" +
+//            "<if test='lawyerUserId != null'>`lawyer_user_id`,</if>" +
+//            "<if test='problemScenarioId != null'>`problem_scenario_id`,</if>" +
+//            "<if test='problemDescription != null'>`problem_description`,</if>" +
+//            "<if test='orderAmount != null'>`order_amount`,</if>" +
+//            "<if test='consultationFee != null'>`consultation_fee`,</if>" +
+//            "<if test='startTime != null'>`start_time`,</if>" +
+//            "<if test='endTime != null'>`end_time`,</if>" +
+//            "<if test='orderStatus != null'>`order_status`,</if>" +
+//            "<if test='paymentStatus != null'>`payment_status`,</if>" +
+//            "<if test='orderTime != null'>`order_time`,</if>" +
+//            "<if test='paymentTime != null'>`payment_time`,</if>" +
+//            "<if test='validityPeriod != null'>`validity_period`,</if>" +
+//            "<if test='rating != null'>`rating`,</if>" +
+//            "<if test='comment != null'>`comment`,</if>" +
+//            "<if test='deleteFlag != null'>`delete_flag`,</if>" +
+//            "<if test='createdTime != null'>`created_time`,</if>" +
+//            "<if test='createdUserId != null'>`created_user_id`,</if>" +
+//            "<if test='updatedTime != null'>`updated_time`,</if>" +
+//            "<if test='updatedUserId != null'>`updated_user_id`,</if>" +
+//            "<if test='alipayNo != null'>`alipay_no`,</if>" +
+//            "<if test='orderStr != null'>`order_str`,</if>" +
+//            "<if test='placeId != null'>`place_id`,</if>" +
+//            "<if test='lawyerEarnings != null'>`lawyer_earnings`,</if>" +
+//            "<if test='acceptOrdersTime != null'>`accept_orders_time`,</if>" +
+//            "<if test='reasonOrderRefusal != null'>`reason_order_refusal`,</if>" +
+//            "<if test='acceptOrdersStatus != null'>`accept_orders_status`,</if>" +
+//            "</trim>" +
+//            " VALUES " +
+//            "<trim prefix=\"(\" suffix=\")\" suffixOverrides=\",\">" +
+//            "<if test='id != null'>#{id},</if>" +
+//            "<if test='orderNumber != null'>#{orderNumber},</if>" +
+//            "<if test='clientUserId != null'>#{clientUserId},</if>" +
+//            "<if test='lawyerUserId != null'>#{lawyerUserId},</if>" +
+//            "<if test='problemScenarioId != null'>#{problemScenarioId},</if>" +
+//            "<if test='problemDescription != null'>#{problemDescription},</if>" +
+//            "<if test='orderAmount != null'>#{orderAmount},</if>" +
+//            "<if test='consultationFee != null'>#{consultationFee},</if>" +
+//            "<if test='startTime != null'>#{startTime},</if>" +
+//            "<if test='endTime != null'>#{endTime},</if>" +
+//            "<if test='orderStatus != null'>#{orderStatus},</if>" +
+//            "<if test='paymentStatus != null'>#{paymentStatus},</if>" +
+//            "<if test='orderTime != null'>#{orderTime},</if>" +
+//            "<if test='paymentTime != null'>#{paymentTime},</if>" +
+//            "<if test='validityPeriod != null'>#{validityPeriod},</if>" +
+//            "<if test='rating != null'>#{rating},</if>" +
+//            "<if test='comment != null'>#{comment},</if>" +
+//            "<if test='deleteFlag != null'>#{deleteFlag},</if>" +
+//            "<if test='createdTime != null'>#{createdTime},</if>" +
+//            "<if test='createdUserId != null'>#{createdUserId},</if>" +
+//            "<if test='updatedTime != null'>#{updatedTime},</if>" +
+//            "<if test='updatedUserId != null'>#{updatedUserId},</if>" +
+//            "<if test='alipayNo != null'>#{alipayNo},</if>" +
+//            "<if test='orderStr != null'>#{orderStr},</if>" +
+//            "<if test='placeId != null'>#{placeId},</if>" +
+//            "<if test='lawyerEarnings != null'>#{lawyerEarnings},</if>" +
+//            "<if test='acceptOrdersTime != null'>#{acceptOrdersTime},</if>" +
+//            "<if test='reasonOrderRefusal != null'>#{reasonOrderRefusal},</if>" +
+//            "<if test='acceptOrdersStatus != null'>#{acceptOrdersStatus},</if>" +
+//            "</trim>" +
+//            "</script>")
+//    int insertOrder(LawyerConsultationOrderDto order);
+
+
 
 
 

+ 8 - 0
alien-entity/src/main/java/shop/alien/mapper/LawyerServiceAreaMapper.java

@@ -6,6 +6,7 @@ import com.baomidou.mybatisplus.core.toolkit.Constants;
 import org.apache.ibatis.annotations.Mapper;
 import org.apache.ibatis.annotations.Param;
 import org.apache.ibatis.annotations.Select;
+import shop.alien.entity.store.LawyerLegalProblemScenario;
 import shop.alien.entity.store.LawyerServiceArea;
 import shop.alien.entity.store.vo.LifeAppealManageVo;
 import shop.alien.entity.store.vo.LifeCouponVo;
@@ -28,5 +29,12 @@ public interface LawyerServiceAreaMapper extends BaseMapper<LawyerServiceArea> {
             "left join lawyer_legal_problem_scenario llps on llps.id = lsa.problem_scenario_id \n" +
             " ${ew.customSqlSegment}")
     List<Map<String, Object>> getLawyerLegalProblemScenarioList(@Param(Constants.WRAPPER) QueryWrapper<LawyerServiceArea> queryWrapper);
+
+
+
+    @Select("select lsa.lawyer_user_id, llps.id ,llps.name from lawyer_service_area lsa \n" +
+            "left join lawyer_legal_problem_scenario llps on llps.id = lsa.problem_scenario_id \n" +
+            " ${ew.customSqlSegment}")
+    List<LawyerLegalProblemScenario> getProblemScenario(@Param(Constants.WRAPPER) QueryWrapper<LawyerServiceArea> queryWrapper);
 }
 

+ 2 - 2
alien-entity/src/main/resources/mapper/LawFirmMapper.xml

@@ -104,7 +104,7 @@
     </select>
 
     <!-- 查询所有律所的对账统计信息(按律所分组) -->
-    <resultMap id="LawFirmReconciliationStatisticsResultMap" type="shop.alien.entity.store.vo.LawFirmReconciliationVO">
+    <resultMap id="LawFirmReconciliationStatisticsResultMap" type="shop.alien.entity.store.vo.LawFirmListVO">
         <result column="firm_id" property="firmId" />
         <result column="firm_name" property="firmName" />
         <result column="order_count" property="totalOrderCount" />
@@ -113,7 +113,7 @@
     </resultMap>
 
     <!-- 查询律所下所有律师的对账统计信息(按律师分组) -->
-    <resultMap id="LawyerReconciliationStatisticsResultMap" type="shop.alien.entity.store.vo.LawFirmReconciliationVO">
+    <resultMap id="LawyerReconciliationStatisticsResultMap" type="shop.alien.entity.store.vo.LawyerListVO">
         <result column="lawyer_id" property="lawyerId" />
         <result column="lawyer_name" property="lawyerName" />
         <result column="head_img" property="headImg" />

+ 18 - 0
alien-lawyer/pom.xml

@@ -264,6 +264,24 @@
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-webflux</artifactId>
         </dependency>
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>easyexcel</artifactId>
+            <version>4.0.3</version>
+            <scope>compile</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>easyexcel</artifactId>
+            <version>4.0.3</version>
+            <scope>compile</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>easyexcel</artifactId>
+            <version>4.0.3</version>
+            <scope>compile</scope>
+        </dependency>
 
     </dependencies>
 

+ 17 - 0
alien-lawyer/src/main/java/shop/alien/lawyer/controller/AliController.java

@@ -72,4 +72,21 @@ public class AliController {
                                 @RequestParam(value = "partialRefundCode") String partialRefundCode) {
         return aliApi.processRefund(outTradeNo, refundAmount, refundReason, partialRefundCode);
     }
+
+
+    @ApiOperation("律师端or用户退款")
+    @ApiOperationSupport(order = 14)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "outTradeNo", value = "订单号", dataType = "String", paramType = "query", required = true),
+            @ApiImplicitParam(name = "refundAmount", value = "退款金额", dataType = "String", paramType = "query", required = true),
+            @ApiImplicitParam(name = "refundReason", value = "退款原因", dataType = "String", paramType = "query", required = true),
+            @ApiImplicitParam(name = "partialRefundCode", value = "部分退款编码", dataType = "String", paramType = "query", required = false)
+    })
+    @GetMapping("/processLawRefund")
+    public R<String> processLawRefund(@RequestParam(value = "outTradeNo") String outTradeNo,
+                                @RequestParam(value = "refundAmount") String refundAmount,
+                                @RequestParam(value = "refundReason") String refundReason,
+                                @RequestParam(value = "partialRefundCode") String partialRefundCode) {
+        return R.data(aliApi.processRefund(outTradeNo, refundAmount, refundReason, partialRefundCode));
+    }
 }

+ 71 - 3
alien-lawyer/src/main/java/shop/alien/lawyer/controller/LawFirmReconciliationController.java

@@ -7,9 +7,12 @@ import lombok.extern.slf4j.Slf4j;
 import org.springframework.format.annotation.DateTimeFormat;
 import org.springframework.web.bind.annotation.*;
 import shop.alien.entity.result.R;
+import shop.alien.entity.store.vo.LawFirmListVO;
 import shop.alien.entity.store.vo.LawFirmReconciliationVO;
+import shop.alien.entity.store.vo.LawyerListVO;
 import shop.alien.lawyer.service.LawFirmReconciliationService;
 
+import javax.servlet.http.HttpServletResponse;
 import java.util.Date;
 
 /**
@@ -58,7 +61,7 @@ public class LawFirmReconciliationController {
             @ApiImplicitParam(name = "pageSize", value = "页容(默认10)", dataType = "Integer", paramType = "query")
     })
     @GetMapping("/getAllLawFirmList")
-    public R<IPage<LawFirmReconciliationVO>> getAllLawFirmReconciliationList(
+    public R<IPage<LawFirmListVO>> getAllLawFirmReconciliationList(
             @RequestParam(value = "firmName", required = false) String firmName,
             @RequestParam(value = "startDate", required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") Date startDate,
             @RequestParam(value = "endDate", required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") Date endDate,
@@ -79,7 +82,7 @@ public class LawFirmReconciliationController {
             @ApiImplicitParam(name = "pageSize", value = "页容(默认10)", dataType = "Integer", paramType = "query")
     })
     @GetMapping("/getLawyerList")
-    public R<IPage<LawFirmReconciliationVO>> getLawyerReconciliationList(
+    public R<IPage<LawyerListVO>> getLawyerReconciliationList(
             @RequestParam(value = "firmId") Integer firmId,
             @RequestParam(value = "startDate", required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") Date startDate,
             @RequestParam(value = "endDate", required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") Date endDate,
@@ -101,7 +104,7 @@ public class LawFirmReconciliationController {
             @ApiImplicitParam(name = "pageSize", value = "页容(默认10)", dataType = "Integer", paramType = "query")
     })
     @GetMapping("/getLawyerListWithName")
-    public R<IPage<LawFirmReconciliationVO>> getLawyerReconciliationListWithName(
+    public R<IPage<LawyerListVO>> getLawyerReconciliationListWithName(
             @RequestParam(value = "firmId") Integer firmId,
             @RequestParam(value = "lawyerName", required = false) String lawyerName,
             @RequestParam(value = "startDate", required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") Date startDate,
@@ -130,5 +133,70 @@ public class LawFirmReconciliationController {
         return lawFirmReconciliationService.getLawyerOrderList(lawyerId, pageNum, pageSize);
     }
 
+    @ApiOperation("导出所有律所的对账统计列表到Excel(基于/getAllLawFirmList接口数据)")
+    @ApiOperationSupport(order = 6)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "firmName", value = "律所名称(可选,模糊查询)", dataType = "String", paramType = "query", required = false),
+            @ApiImplicitParam(name = "startDate", value = "开始日期(可选,格式:yyyy-MM-dd)", dataType = "String", paramType = "query"),
+            @ApiImplicitParam(name = "endDate", value = "结束日期(可选,格式:yyyy-MM-dd)", dataType = "String", paramType = "query"),
+            @ApiImplicitParam(name = "pageNum", value = "页码(可选,不传或传0则导出全部,传值则导出本页)", dataType = "Integer", paramType = "query"),
+            @ApiImplicitParam(name = "pageSize", value = "页容(可选,与pageNum配合使用)", dataType = "Integer", paramType = "query")
+    })
+    @GetMapping(value = "/exportAllLawFirmList", produces = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
+    public void exportAllLawFirmList(
+            HttpServletResponse response,
+            @RequestParam(value = "firmName", required = false) String firmName,
+            @RequestParam(value = "startDate", required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") Date startDate,
+            @RequestParam(value = "endDate", required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") Date endDate,
+            @RequestParam(value = "pageNum", required = false) Integer pageNum,
+            @RequestParam(value = "pageSize", required = false) Integer pageSize) throws Exception {
+        log.info("LawFirmReconciliationController.exportAllLawFirmList?firmName={},startDate={},endDate={},pageNum={},pageSize={}",
+                firmName, startDate, endDate, pageNum, pageSize);
+        try {
+            lawFirmReconciliationService.exportAllLawFirmList(response, firmName, startDate, endDate, pageNum, pageSize);
+        } catch (Exception e) {
+            log.error("导出律所对账列表异常", e);
+            if (!response.isCommitted()) {
+                response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+                response.setContentType("application/json;charset=UTF-8");
+                response.getWriter().write("{\"code\":500,\"msg\":\"导出失败:" + e.getMessage() + "\"}");
+            }
+            throw e;
+        }
+    }
+
+    @ApiOperation("导出律师对账统计列表到Excel(基于/getLawyerListWithName接口数据)")
+    @ApiOperationSupport(order = 7)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "firmId", value = "律所ID", dataType = "Integer", paramType = "query", required = true),
+            @ApiImplicitParam(name = "lawyerName", value = "律师名称(可选,模糊查询)", dataType = "String", paramType = "query", required = false),
+            @ApiImplicitParam(name = "startDate", value = "开始日期(可选,格式:yyyy-MM-dd)", dataType = "String", paramType = "query"),
+            @ApiImplicitParam(name = "endDate", value = "结束日期(可选,格式:yyyy-MM-dd)", dataType = "String", paramType = "query"),
+            @ApiImplicitParam(name = "pageNum", value = "页码(可选,不传或传0则导出全部,传值则导出本页)", dataType = "Integer", paramType = "query"),
+            @ApiImplicitParam(name = "pageSize", value = "页容(可选,与pageNum配合使用)", dataType = "Integer", paramType = "query")
+    })
+    @GetMapping(value = "/exportLawyerListWithName", produces = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
+    public void exportLawyerListWithName(
+            HttpServletResponse response,
+            @RequestParam(value = "firmId") Integer firmId,
+            @RequestParam(value = "lawyerName", required = false) String lawyerName,
+            @RequestParam(value = "startDate", required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") Date startDate,
+            @RequestParam(value = "endDate", required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") Date endDate,
+            @RequestParam(value = "pageNum", required = false) Integer pageNum,
+            @RequestParam(value = "pageSize", required = false) Integer pageSize) throws Exception {
+        log.info("LawFirmReconciliationController.exportLawyerListWithName?firmId={},lawyerName={},startDate={},endDate={},pageNum={},pageSize={}",
+                firmId, lawyerName, startDate, endDate, pageNum, pageSize);
+        try {
+            lawFirmReconciliationService.exportLawyerListWithName(response, firmId, lawyerName, startDate, endDate, pageNum, pageSize);
+        } catch (Exception e) {
+            log.error("导出律师对账列表异常", e);
+            if (!response.isCommitted()) {
+                response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+                response.setContentType("application/json;charset=UTF-8");
+                response.getWriter().write("{\"code\":500,\"msg\":\"导出失败:" + e.getMessage() + "\"}");
+            }
+            throw e;
+        }
+    }
 }
 

+ 26 - 0
alien-lawyer/src/main/java/shop/alien/lawyer/controller/LawyerClientConsultationOrderController.java

@@ -3,11 +3,14 @@ package shop.alien.lawyer.controller;
 import io.swagger.annotations.*;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.web.bind.annotation.*;
 import shop.alien.entity.result.R;
 import shop.alien.entity.store.LawyerConsultationOrder;
 import shop.alien.entity.store.vo.LawyerConsultationOrderVO;
 import shop.alien.lawyer.service.LawyerClientConsultationOrderService;
+import shop.alien.lawyer.service.LawyerConsultationOrderService;
+import shop.alien.lawyer.service.OrderExpirationService;
 
 import java.util.Map;
 
@@ -124,5 +127,28 @@ public class LawyerClientConsultationOrderController {
         return lawyerClientConsultationOrderService.confirmOrder(id, actionType);
     }
 
+    @ApiOperation("申请退款(律师端)")
+    @ApiOperationSupport(order = 6)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "id", value = "订单ID", dataType = "Integer", paramType = "query", required = true),
+            @ApiImplicitParam(name = "refundReason", value = "退款原因", dataType = "String", paramType = "query", required = true)
+    })
+    @PostMapping("/requestRefund")
+    public R<Boolean> requestRefund(@RequestParam(value = "id", required = true) Integer id,
+                                     @RequestParam(value = "refundReason", required = true) String refundReason) {
+        log.info("申请退款,订单ID={}, 退款原因={}", id, refundReason);
+        if (id == null) {
+            log.warn("申请退款失败:订单ID为空");
+            return R.fail("订单ID不能为空");
+        }
+        if (refundReason == null || refundReason.trim().isEmpty()) {
+            log.warn("申请退款失败:退款原因为空");
+            return R.fail("退款原因不能为空");
+        }
+        return lawyerClientConsultationOrderService.requestRefund(id, refundReason);
+    }
+
+
+
 }
 

+ 26 - 0
alien-lawyer/src/main/java/shop/alien/lawyer/controller/LawyerConsultationOrderController.java

@@ -364,5 +364,31 @@ public class LawyerConsultationOrderController {
         return consultationOrderService.getOrderIncome(lawyerConsultationOrderVO);
     }
 
+    @ApiOperation("用户申请退款")
+    @ApiOperationSupport(order = 17)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "clientUserId", value = "客户端用户ID", dataType = "Integer", paramType = "query", required = true),
+            @ApiImplicitParam(name = "orderId", value = "订单ID", dataType = "Integer", paramType = "query", required = true),
+            @ApiImplicitParam(name = "applyRefundReason", value = "申请退款原因", dataType = "String", paramType = "query", required = true)
+    })
+    @PostMapping("/applyRefund")
+    public R<Boolean> applyRefund(@RequestParam Integer clientUserId, @RequestParam Integer orderId, @RequestParam String applyRefundReason) {
+        log.info("LawyerConsultationOrderController.applyRefund?clientUserId={},orderId{}", clientUserId, orderId);
+        return consultationOrderService.applyRefund(clientUserId, orderId, applyRefundReason);
+    }
+
+    @ApiOperation("用户完成订单")
+    @ApiOperationSupport(order = 18)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "clientUserId", value = "客户端用户ID", dataType = "Integer", paramType = "query", required = true),
+            @ApiImplicitParam(name = "orderId", value = "订单ID", dataType = "Integer", paramType = "query", required = true),
+            @ApiImplicitParam(name = "applyRefundReason", value = "申请退款原因", dataType = "String", paramType = "query", required = true)
+    })
+    @PostMapping("/completeOrder")
+    public R<Boolean> completeOrder(@RequestParam Integer clientUserId, @RequestParam Integer orderId) {
+        log.info("LawyerConsultationOrderController.completeOrder?clientUserId={},orderId{}", clientUserId, orderId);
+        return consultationOrderService.completeOrder(clientUserId, orderId);
+    }
+
 }
 

+ 1 - 1
alien-lawyer/src/main/java/shop/alien/lawyer/controller/LawyerImgController.java

@@ -1,6 +1,6 @@
 package shop.alien.lawyer.controller;
 
-import com.alibaba.excel.util.CollectionUtils;
+import com.alibaba.nacos.common.utils.CollectionUtils;
 import io.swagger.annotations.*;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;

+ 48 - 3
alien-lawyer/src/main/java/shop/alien/lawyer/service/LawFirmReconciliationService.java

@@ -2,8 +2,11 @@ package shop.alien.lawyer.service;
 
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import shop.alien.entity.result.R;
+import shop.alien.entity.store.vo.LawFirmListVO;
 import shop.alien.entity.store.vo.LawFirmReconciliationVO;
+import shop.alien.entity.store.vo.LawyerListVO;
 
+import javax.servlet.http.HttpServletResponse;
 import java.util.Date;
 
 /**
@@ -40,7 +43,7 @@ public interface LawFirmReconciliationService {
      * @param pageSize  页容
      * @return 律所对账统计列表(分页)
      */
-    R<IPage<LawFirmReconciliationVO>> getAllLawFirmReconciliationList(
+    R<IPage<LawFirmListVO>> getAllLawFirmReconciliationList(
             String firmName,
             Date startDate,
             Date endDate,
@@ -58,7 +61,7 @@ public interface LawFirmReconciliationService {
      * @param pageSize  页容
      * @return 律师对账统计列表(分页)
      */
-    R<IPage<LawFirmReconciliationVO>> getLawyerReconciliationList(
+    R<IPage<LawyerListVO>> getLawyerReconciliationList(
             Integer firmId,
             Date startDate,
             Date endDate,
@@ -77,7 +80,7 @@ public interface LawFirmReconciliationService {
      * @param pageSize  页容
      * @return 律师对账统计列表(分页)
      */
-    R<IPage<LawFirmReconciliationVO>> getLawyerReconciliationListWithName(
+    R<IPage<LawyerListVO>> getLawyerReconciliationListWithName(
             Integer firmId,
             String lawyerName,
             Date startDate,
@@ -99,5 +102,47 @@ public interface LawFirmReconciliationService {
             Integer pageNum,
             Integer pageSize
     );
+
+    /**
+     * 导出所有律所的对账统计列表到Excel
+     *
+     * @param response  HTTP响应对象
+     * @param firmName  律所名称(可选,模糊查询)
+     * @param startDate 开始日期(可选)
+     * @param endDate   结束日期(可选)
+     * @param pageNum   页码(可选,不传或传0则导出全部,传值则导出本页)
+     * @param pageSize  页容(可选,与pageNum配合使用)
+     * @throws Exception 导出过程中的异常
+     */
+    void exportAllLawFirmList(
+            HttpServletResponse response,
+            String firmName,
+            Date startDate,
+            Date endDate,
+            Integer pageNum,
+            Integer pageSize
+    ) throws Exception;
+
+    /**
+     * 导出律师对账统计列表到Excel
+     *
+     * @param response   HTTP响应对象
+     * @param firmId     律所ID
+     * @param lawyerName 律师名称(可选,模糊查询)
+     * @param startDate  开始日期(可选)
+     * @param endDate    结束日期(可选)
+     * @param pageNum    页码(可选,不传或传0则导出全部,传值则导出本页)
+     * @param pageSize   页容(可选,与pageNum配合使用)
+     * @throws Exception 导出过程中的异常
+     */
+    void exportLawyerListWithName(
+            HttpServletResponse response,
+            Integer firmId,
+            String lawyerName,
+            Date startDate,
+            Date endDate,
+            Integer pageNum,
+            Integer pageSize
+    ) throws Exception;
 }
 

+ 9 - 0
alien-lawyer/src/main/java/shop/alien/lawyer/service/LawyerClientConsultationOrderService.java

@@ -52,5 +52,14 @@ public interface LawyerClientConsultationOrderService extends IService<LawyerCon
      * @return R<Boolean> 是否成功
      */
     R<Boolean> confirmOrder(Integer id, Integer actionType);
+
+    /**
+     * 申请退款
+     *
+     * @param id 订单ID
+     * @param refundReason 退款原因
+     * @return R<Boolean> 是否成功
+     */
+    R<Boolean> requestRefund(Integer id, String refundReason);
 }
 

+ 21 - 1
alien-lawyer/src/main/java/shop/alien/lawyer/service/LawyerConsultationOrderService.java

@@ -129,9 +129,29 @@ public interface LawyerConsultationOrderService extends IService<LawyerConsultat
    /**
    * 获取律师收入
    *
-   * @param lawyerUserId
+   * @param lawyerConsultationOrderVO
    * @return
    */
    R<Map<String, Object>> getOrderIncome(LawyerConsultationOrderVO lawyerConsultationOrderVO);
+
+
+    /**
+     * 用户端申请退款
+     *
+     * @param clientUserId
+     * @param orderId
+     * @return
+     */
+    R<Boolean> applyRefund(Integer clientUserId, Integer orderId, String applyRefundReason);
+
+
+    /**
+     * 用户端完成订单
+     *
+     * @param clientUserId
+     * @param orderId
+     * @return
+     */
+    R<Boolean> completeOrder(Integer clientUserId, Integer orderId);
 }
 

+ 78 - 9
alien-lawyer/src/main/java/shop/alien/lawyer/service/impl/LawFirmReconciliationServiceImpl.java

@@ -9,12 +9,16 @@ import org.springframework.transaction.annotation.Transactional;
 import shop.alien.entity.result.R;
 import shop.alien.entity.store.LawFirm;
 import shop.alien.entity.store.LawyerUser;
+import shop.alien.entity.store.vo.LawFirmListVO;
 import shop.alien.entity.store.vo.LawFirmReconciliationVO;
+import shop.alien.entity.store.vo.LawyerListVO;
 import shop.alien.lawyer.service.LawFirmReconciliationService;
 import shop.alien.lawyer.service.LawFirmService;
 import shop.alien.lawyer.service.LawyerUserService;
 import shop.alien.mapper.LawFirmMapper;
+import shop.alien.util.excel.EasyExcelUtil;
 
+import javax.servlet.http.HttpServletResponse;
 import java.math.BigDecimal;
 import java.math.RoundingMode;
 import java.util.Collections;
@@ -87,7 +91,7 @@ public class LawFirmReconciliationServiceImpl implements LawFirmReconciliationSe
 
 
     @Override
-    public R<IPage<LawFirmReconciliationVO>> getAllLawFirmReconciliationList(
+    public R<IPage<LawFirmListVO>> getAllLawFirmReconciliationList(
             String firmName,
             Date startDate,
             Date endDate,
@@ -97,10 +101,10 @@ public class LawFirmReconciliationServiceImpl implements LawFirmReconciliationSe
                 firmName, startDate, endDate, pageNum, pageSize);
 
         // 查询所有律所的统计信息(SQL中已直接计算为元)
-        List<LawFirmReconciliationVO> firmList = lawFirmMapper.getAllLawFirmReconciliationStatistics(firmName, startDate, endDate);
+        List<LawFirmListVO> firmList = lawFirmMapper.getAllLawFirmReconciliationStatistics(firmName, startDate, endDate);
 
         // 手动分页
-        Page<LawFirmReconciliationVO> page = new Page<>();
+        Page<LawFirmListVO> page = new Page<>();
         if (pageNum != null && pageSize != null && pageNum > 0 && pageSize > 0) {
             page.setCurrent(pageNum);
             page.setSize(pageSize);
@@ -124,7 +128,7 @@ public class LawFirmReconciliationServiceImpl implements LawFirmReconciliationSe
     }
 
     @Override
-    public R<IPage<LawFirmReconciliationVO>> getLawyerReconciliationList(
+    public R<IPage<LawyerListVO>> getLawyerReconciliationList(
             Integer firmId,
             Date startDate,
             Date endDate,
@@ -143,10 +147,10 @@ public class LawFirmReconciliationServiceImpl implements LawFirmReconciliationSe
         }
 
         // 查询律所下所有律师的统计信息(SQL中已直接计算为元)
-        List<LawFirmReconciliationVO> lawyerList = lawFirmMapper.getLawyerReconciliationStatistics(firmId, startDate, endDate);
+        List<LawyerListVO> lawyerList = lawFirmMapper.getLawyerReconciliationStatistics(firmId, startDate, endDate);
 
         // 手动分页
-        Page<LawFirmReconciliationVO> page = new Page<>();
+        Page<LawyerListVO> page = new Page<>();
         if (pageNum != null && pageSize != null && pageNum > 0 && pageSize > 0) {
             page.setCurrent(pageNum);
             page.setSize(pageSize);
@@ -170,7 +174,7 @@ public class LawFirmReconciliationServiceImpl implements LawFirmReconciliationSe
     }
 
     @Override
-    public R<IPage<LawFirmReconciliationVO>> getLawyerReconciliationListWithName(
+    public R<IPage<LawyerListVO>> getLawyerReconciliationListWithName(
             Integer firmId,
             String lawyerName,
             Date startDate,
@@ -190,10 +194,10 @@ public class LawFirmReconciliationServiceImpl implements LawFirmReconciliationSe
         }
 
         // 查询律所下所有律师的统计信息(SQL中已直接计算为元,支持律师名称模糊查询)
-        List<LawFirmReconciliationVO> lawyerList = lawFirmMapper.getLawyerReconciliationStatisticsWithName(firmId, lawyerName, startDate, endDate);
+        List<LawyerListVO> lawyerList = lawFirmMapper.getLawyerReconciliationStatisticsWithName(firmId, lawyerName, startDate, endDate);
 
         // 手动分页
-        Page<LawFirmReconciliationVO> page = new Page<>();
+        Page<LawyerListVO> page = new Page<>();
         if (pageNum != null && pageSize != null && pageNum > 0 && pageSize > 0) {
             page.setCurrent(pageNum);
             page.setSize(pageSize);
@@ -244,6 +248,71 @@ public class LawFirmReconciliationServiceImpl implements LawFirmReconciliationSe
         return R.data(orderPage, "查询成功");
     }
 
+    @Override
+    public void exportAllLawFirmList(
+            HttpServletResponse response,
+            String firmName,
+            Date startDate,
+            Date endDate,
+            Integer pageNum,
+            Integer pageSize) throws Exception {
+        log.info("LawFirmReconciliationServiceImpl.exportAllLawFirmList?firmName={},startDate={},endDate={},pageNum={},pageSize={}",
+                firmName, startDate, endDate, pageNum, pageSize);
+
+        // 调用查询接口获取数据
+        R<IPage<LawFirmListVO>> result = getAllLawFirmReconciliationList(firmName, startDate, endDate, pageNum, pageSize);
+
+        if (result == null || !result.isSuccess()) {
+            log.warn("获取律所对账列表失败:{}", result != null ? result.getMsg() : "返回结果为空");
+            throw new RuntimeException(result != null ? result.getMsg() : "获取数据失败,请稍后重试");
+        }
+
+        IPage<LawFirmListVO> page = result.getData();
+        if (page == null || page.getRecords() == null || page.getRecords().isEmpty()) {
+            log.warn("律所对账列表导出:无数据可导出");
+            throw new IllegalArgumentException("暂无数据可导出");
+        }
+
+        List<LawFirmListVO> exportList = page.getRecords();
+        log.info("律所对账列表导出:查询到{}条数据,导出{}条数据", page.getTotal(), exportList.size());
+
+        // 使用EasyExcelUtil导出
+        EasyExcelUtil.exportExcel(response, exportList, LawFirmListVO.class, "律所对账列表", "律所对账列表");
+    }
+
+    @Override
+    public void exportLawyerListWithName(
+            HttpServletResponse response,
+            Integer firmId,
+            String lawyerName,
+            Date startDate,
+            Date endDate,
+            Integer pageNum,
+            Integer pageSize) throws Exception {
+        log.info("LawFirmReconciliationServiceImpl.exportLawyerListWithName?firmId={},lawyerName={},startDate={},endDate={},pageNum={},pageSize={}",
+                firmId, lawyerName, startDate, endDate, pageNum, pageSize);
+
+        // 调用查询接口获取数据
+        R<IPage<LawyerListVO>> result = getLawyerReconciliationListWithName(firmId, lawyerName, startDate, endDate, pageNum, pageSize);
+
+        if (result == null || !result.isSuccess()) {
+            log.warn("获取律师对账列表失败:{}", result != null ? result.getMsg() : "返回结果为空");
+            throw new RuntimeException(result != null ? result.getMsg() : "获取数据失败,请稍后重试");
+        }
+
+        IPage<LawyerListVO> page = result.getData();
+        if (page == null || page.getRecords() == null || page.getRecords().isEmpty()) {
+            log.warn("律师对账列表导出:无数据可导出,firmId={}", firmId);
+            throw new IllegalArgumentException("暂无数据可导出");
+        }
+
+        List<LawyerListVO> exportList = page.getRecords();
+        log.info("律师对账列表导出:查询到{}条数据,导出{}条数据,firmId={}", page.getTotal(), exportList.size(), firmId);
+
+        // 使用EasyExcelUtil导出
+        EasyExcelUtil.exportExcel(response, exportList, LawyerListVO.class, "律师对账列表", "律师对账列表");
+    }
+
     /**
      * 将分转换为元(字符串格式)
      *

+ 88 - 45
alien-lawyer/src/main/java/shop/alien/lawyer/service/impl/LawyerClientConsultationOrderServiceImpl.java

@@ -11,6 +11,7 @@ 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;
@@ -57,6 +58,12 @@ public class LawyerClientConsultationOrderServiceImpl extends ServiceImpl<Lawyer
     private final OrderExpirationService orderExpirationService;
     private final AlienStoreFeign alienStoreFeign;
 
+    @Value("${order.coefficient}")
+    private String coefficient;
+    @Value("${order.coefficients}")
+    private String coefficients;
+
+
 
     /**
      * 删除咨询订单
@@ -608,6 +615,10 @@ public class LawyerClientConsultationOrderServiceImpl extends ServiceImpl<Lawyer
             return R.fail("订单不存在");
         }
 
+        //清除redis缓存,避免调用订单超时处理
+        orderExpirationService.cancelOrderAcceptTimeout(order.getOrderNumber());
+
+
         // 验证订单状态是否为待接单(1)
         Integer orderStatus = order.getOrderStatus();
         Integer waitAcceptStatus = 1; // 待接单
@@ -636,7 +647,7 @@ public class LawyerClientConsultationOrderServiceImpl extends ServiceImpl<Lawyer
             }
         } else {
             // 取消:更新订单状态为已取消(4),并进行退款
-            Integer cancelStatus = LawyerStatusEnum.CANCEL.getStatus(); // 4:已取消
+            Integer cancelStatus = LawyerStatusEnum.REFUNDED.getStatus(); // 4:已取消
             order.setOrderStatus(cancelStatus);
             order.setUpdatedTime(new Date());
 
@@ -646,54 +657,86 @@ public class LawyerClientConsultationOrderServiceImpl extends ServiceImpl<Lawyer
                 log.error("律师取消订单失败:数据库操作失败,订单ID={}, 订单编号={}", id, order.getOrderNumber());
                 return R.fail("取消订单失败");
             }
+        }
+        return R.success("接单成功");
+    }
 
-            // 进行退款处理
-            if (order.getPaymentStatus() != null && order.getPaymentStatus() == 1) {
-                // 只有已支付的订单才需要退款
-                String orderNumber = order.getOrderNumber();
-                Integer orderAmount = order.getOrderAmount();
-                
-                if (orderNumber == null || orderNumber.trim().isEmpty()) {
-                    log.warn("律师取消订单失败:订单编号为空,无法退款,订单ID={}", id);
-                    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 (orderAmount == null || orderAmount <= 0) {
-                    log.warn("律师取消订单失败:订单金额无效,无法退款,订单ID={}, 订单金额={}", id, orderAmount);
-                    return R.fail("订单金额无效,无法退款");
-                }
+        // 参数校验
+        if (id == null) {
+            log.warn("申请退款失败:订单ID为空");
+            return R.fail("订单ID不能为空");
+        }
 
-                // 将订单金额从分转换为元(字符串格式,保留两位小数)
-                String refundAmount = String.format("%.2f", orderAmount / 100.0);
-                String refundReason = "律师取消订单";
-                return R.data(true, "取消订单成功,退款已处理");
-
-//                try {
-//                    // 调用退款接口(全退,partialRefundCode 传空字符串)
-//                    String refundResult = alienStoreFeign.processRefund(orderNumber, refundAmount, refundReason, "");
-//
-//                    if ("调用成功".equals(refundResult)) {
-//                        log.info("律师取消订单并退款成功,订单ID={}, 订单编号={}, 退款金额={}元",
-//                                id, orderNumber, refundAmount);
-//                        return R.data(true, "取消订单成功,退款已处理");
-//                    } else {
-//                        log.error("律师取消订单退款失败,订单ID={}, 订单编号={}, 退款结果={}",
-//                                id, orderNumber, refundResult);
-//                        // 退款失败,但订单状态已更新为已取消,返回警告信息
-//                        return R.fail("订单已取消,但退款处理失败:" + refundResult);
-//                    }
-//                } catch (Exception e) {
-//                    log.error("律师取消订单退款异常,订单ID={}, 订单编号={}", id, orderNumber, e);
-//                    // 退款异常,但订单状态已更新为已取消,返回警告信息
-//                    return R.fail("订单已取消,但退款处理异常:" + e.getMessage());
-//                }
-            } else {
-                // 未支付的订单,直接取消即可
-                log.info("律师取消订单成功(未支付订单无需退款),订单ID={}, 订单编号={}",
-                        id, order.getOrderNumber());
-                return R.data(true, "取消订单成功");
-            }
+        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, "退款申请已提交,请等待处理");
     }
 }
 

+ 362 - 18
alien-lawyer/src/main/java/shop/alien/lawyer/service/impl/LawyerConsultationOrderServiceImpl.java

@@ -1,5 +1,6 @@
 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;
@@ -19,10 +20,14 @@ import shop.alien.entity.store.LawFirm;
 import shop.alien.entity.store.LawyerConsultationOrder;
 import shop.alien.entity.store.LawyerServiceArea;
 import shop.alien.entity.store.LawyerUser;
+import shop.alien.entity.store.LifeNotice;
+import shop.alien.entity.store.LifeUser;
 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;
@@ -58,6 +63,14 @@ public class LawyerConsultationOrderServiceImpl extends ServiceImpl<LawyerConsul
     private final OrderExpirationService orderExpirationService;
 //    private final AliApi aliApi;
     private final LawFirmMapper lawFirmMapper;
+    private final LifeNoticeMapper lifeNoticeMapper;
+    private final LifeUserMapper lifeUserMapper;
+    private final WebSocketProcess webSocketProcess;
+
+    /**
+     * 系统发送者ID
+     */
+    private static final String SYSTEM_SENDER_ID = "system";
 
 
     @Override
@@ -397,7 +410,7 @@ public class LawyerConsultationOrderServiceImpl extends ServiceImpl<LawyerConsul
 
     @Override
     public R<LawyerConsultationOrderDto> consultNow(LawyerConsultationOrder lawyerConsultationOrder) {
-        log.info("LawyerConsultationOrderServiceImpl.consultNow?lawyerConsultationOrder={}", lawyerConsultationOrder);
+            log.info("LawyerConsultationOrderServiceImpl.consultNow?lawyerConsultationOrder={}", lawyerConsultationOrder);
 
         // 参数校验
         if (lawyerConsultationOrder == null) {
@@ -910,61 +923,100 @@ public class LawyerConsultationOrderServiceImpl extends ServiceImpl<LawyerConsul
 
     /**
      * 取消律师咨询订单
+     * <p>
+     * 取消订单前会进行以下校验和处理:
+     * 1. 参数校验:订单ID不能为空
+     * 2. 订单存在性校验:订单必须存在
+     * 3. 订单状态校验:已取消或已完成的订单不允许再次取消
+     * 4. 如果订单是待支付状态,会取消Redis中的订单支付超时计时器
+     * 5. 根据订单状态更新为相应状态:
+     *    - 待接单状态:更新为已退款状态
+     *    - 待支付状态:更新为已取消状态
+     * </p>
      *
      * @param id 订单ID
-     * @return 是否取消成功
+     * @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, order.getOrderNumber());
+            log.warn("取消订单失败:订单已取消,订单ID={}, 订单编号={}", id, orderNumber);
             return false;
         }
 
-        // 如果订单已完成,不允许取消
-        Integer completedStatus = LawyerStatusEnum.COMPLETE.getStatus(); // 3:已完成
+        // 3.2 检查订单是否已完成
         if (completedStatus.equals(orderStatus)) {
-            log.warn("取消订单失败:订单已完成,不允许取消,订单ID={}, 订单编号={}", id, order.getOrderNumber());
+            log.warn("取消订单失败:订单已完成,不允许取消,订单ID={}, 订单编号={}", id, orderNumber);
             return false;
         }
 
-        // 如果订单是待支付状态,取消Redis中的订单支付超时计时
-        Integer waitPayStatus = LawyerStatusEnum.WAIT_PAY.getStatus(); // 0:待支付
-        if (waitPayStatus.equals(orderStatus) && order.getOrderNumber() != null) {
-            orderExpirationService.cancelOrderPaymentTimeout(order.getOrderNumber());
-            log.info("已取消订单支付超时计时,订单编号={}", order.getOrderNumber());
+        // 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, cancelStatus)
+                .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, order.getOrderNumber(), orderStatus);
+            log.info("取消订单成功,订单ID={}, 订单编号={}, 原状态={}, 新状态={}", 
+                    id, orderNumber, orderStatus, targetStatus);
         } else {
-            log.error("取消订单失败:更新数据库失败,订单ID={}, 订单编号={}", id, order.getOrderNumber());
+            log.error("取消订单失败:更新数据库失败,订单ID={}, 订单编号={}, 原状态={}", 
+                    id, orderNumber, orderStatus);
         }
 
         return success;
@@ -1297,6 +1349,176 @@ public class LawyerConsultationOrderServiceImpl extends ServiceImpl<LawyerConsul
     }
 
     /**
+     * 用户申请退款
+     * <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(Integer clientUserId, Integer orderId, String applyRefundReason) {
+        log.info("开始申请退款,用户ID={}, 订单ID={}", clientUserId, orderId);
+
+        // 1. 参数校验
+        if (clientUserId == null || clientUserId <= 0) {
+            log.warn("申请退款失败:用户ID为空或无效,clientUserId={}", clientUserId);
+            return R.fail("用户ID不能为空");
+        }
+
+        if (orderId == null || orderId <= 0) {
+            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())) {
+            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) {
+            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(Integer clientUserId, Integer orderId) {
+        log.info("开始完成订单,用户ID={}, 订单ID={}", clientUserId, orderId);
+
+        // 1. 参数校验
+        if (clientUserId == null || clientUserId <= 0) {
+            log.warn("完成订单失败:用户ID为空或无效,clientUserId={}", clientUserId);
+            return R.fail("用户ID不能为空");
+        }
+
+        if (orderId == null || orderId <= 0) {
+            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())) {
+            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("完成订单失败,请稍后重试");
+        }
+    }
+
+    /**
      * 格式化收益金额(分转元)
      *
      * @param revenueInCents 收益金额(单位:分)
@@ -1311,6 +1533,128 @@ public class LawyerConsultationOrderServiceImpl extends ServiceImpl<LawyerConsul
         return revenueInYuan.toString();
     }
 
+    /**
+     * 发送退款申请通知给申请人
+     *
+     * @param order        订单对象
+     * @param clientUserId 客户端用户ID
+     */
+    private void sendRefundApplyNotice(LawyerConsultationOrder order, Integer 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, Integer 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(Integer clientUserId) {
+        if (clientUserId == null || clientUserId <= 0) {
+            log.warn("客户端用户ID为空或无效");
+            return null;
+        }
+
+        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;
+    }
+
 
 }
 

+ 14 - 1
alien-lawyer/src/main/java/shop/alien/lawyer/service/impl/LawyerUserServiceImpl.java

@@ -524,6 +524,16 @@ public class LawyerUserServiceImpl extends ServiceImpl<LawyerUserMapper, LawyerU
         }
 
         LawyerUserVo result=lawyerUserMapper.selectInfo(lawyerId);
+
+        //把result中的数据problemScenarioId,scenarioNames 这两个数据放入到lawyerLegalProblemScenarioList中
+        QueryWrapper<LawyerServiceArea> wrapper = new QueryWrapper<>();
+        wrapper.in("lsa.lawyer_user_id", lawyerId)
+                .eq("lsa.delete_flag", 0)
+                .eq("lsa.status", 1);
+        result.setLawyerLegalProblemScenarioList(lawyerServiceAreaMapper.getProblemScenario(wrapper));
+
+
+
         return R.data(result);
     }
 
@@ -542,13 +552,16 @@ public class LawyerUserServiceImpl extends ServiceImpl<LawyerUserMapper, LawyerU
         BeanUtils.copyProperties(lawyerUserVo, lawyerUser);
 
         Integer result = lawyerUserMapper.updateLawyerUser(lawyerUser);
+
+
+
         if (result <= 0) {
             log.warn("更新律师用户信息失败:更新数据库失败,律师ID={}", lawyerUserVo.getId());
             return R.fail("修改律师信息失败");
         }
 
         //更新法律场景部分
-        if(!lawyerUserVo.getFirstLevelScenarioId().isEmpty()){
+        if(lawyerUserVo.getFirstLevelScenarioId() != null && !lawyerUserVo.getFirstLevelScenarioId().isEmpty()){
             lawyerServiceAreaMapper.delete(new QueryWrapper<LawyerServiceArea>().eq("lawyer_user_id", lawyerUserVo.getId()));
             for (String id : lawyerUserVo.getFirstLevelScenarioId().split(",")) {
                 LawyerServiceArea lawyerServiceArea = new LawyerServiceArea();

+ 13 - 0
alien-lawyer/src/main/java/shop/alien/lawyer/service/impl/LawyerUserViolationServiceImpl.java

@@ -21,6 +21,7 @@ import shop.alien.lawyer.config.WebSocketProcess;
 import shop.alien.lawyer.service.LawyerUserViolationService;
 import shop.alien.lawyer.service.LawyerUserService;
 import shop.alien.util.common.EnumUtil;
+import shop.alien.util.common.constant.LawyerStatusEnum;
 
 import java.io.IOException;
 import java.text.SimpleDateFormat;
@@ -101,6 +102,9 @@ public class LawyerUserViolationServiceImpl extends ServiceImpl<LawyerUserViolat
 
     private final StoreDictionaryMapper storeDictionaryMapper;
 
+    private final LawyerConsultationOrderMapper consultationOrderMapper;
+
+
     /**
      * 用户举报处理
      * <p>
@@ -751,6 +755,15 @@ public class LawyerUserViolationServiceImpl extends ServiceImpl<LawyerUserViolat
             // 更新举报记录状态
             updateViolationStatus(violation, processingStatus, reportResult);
 
+            // 根据举报处理进行订单状态翻转
+            if (PROCESSING_STATUS_APPROVED.equals(processingStatus)) {
+                // 审批通过
+                LambdaUpdateWrapper<LawyerConsultationOrder> lawyerConsultationOrderLambdaUpdateWrapper = new LambdaUpdateWrapper<>();
+                lawyerConsultationOrderLambdaUpdateWrapper.eq(LawyerConsultationOrder::getOrderNumber, violation.getOrderId());
+                lawyerConsultationOrderLambdaUpdateWrapper.set(LawyerConsultationOrder::getOrderStatus, LawyerStatusEnum.REFUNDED.getStatus());
+                consultationOrderMapper.update(null, lawyerConsultationOrderLambdaUpdateWrapper);
+            }
+
             // 构建并发送通知消息
             sendApprovalNotifications(violation, processingStatus, reportResult);
 

+ 93 - 11
alien-lawyer/src/main/java/shop/alien/lawyer/service/impl/OrderExpirationServiceImpl.java

@@ -1,20 +1,30 @@
 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.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.listener.RedisKeyExpirationHandler;
 import shop.alien.lawyer.service.OrderExpirationService;
 import shop.alien.mapper.LawyerConsultationOrderMapper;
+import shop.alien.mapper.LifeNoticeMapper;
+import shop.alien.mapper.LifeUserMapper;
 
 
 import javax.annotation.PostConstruct;
+import java.math.BigDecimal;
+import java.math.RoundingMode;
 
 /**
  * 訂單過期處理服務實現類
@@ -36,6 +46,10 @@ public class OrderExpirationServiceImpl implements OrderExpirationService, Comma
     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;
 
     /**
      * Redis key前綴:訂單支付超時
@@ -58,6 +72,11 @@ public class OrderExpirationServiceImpl implements OrderExpirationService, Comma
     private static final long DEFAULT_TIMEOUT_SECONDS = 30 * 60;
 
     /**
+     * 系统发送者ID
+     */
+    private static final String SYSTEM_SENDER_ID = "system";
+
+    /**
      * 初始化時註冊訂單支付超時處理器
      */
     @PostConstruct
@@ -68,11 +87,11 @@ public class OrderExpirationServiceImpl implements OrderExpirationService, Comma
 
         // 註冊订单待接单超时處理器
         expirationHandler.registerHandler(ORDER_ACCEPT_TIMEOUT_PREFIX, this::handleRefundOrderKey);
-        log.info("訂單支付超時處理器註冊完成,前綴: {}", ORDER_ACCEPT_TIMEOUT_PREFIX);
+        log.info("訂單接单超時處理器註冊完成,前綴: {}", ORDER_ACCEPT_TIMEOUT_PREFIX);
 
         // 註冊訂單退款超時處理器
         expirationHandler.registerHandler(ORDER_REFUND_TIMEOUT_PREFIX, this::handleRefundOrderKey);
-        log.info("訂單支付超時處理器註冊完成,前綴: {}", ORDER_REFUND_TIMEOUT_PREFIX);
+        log.info("訂單退款超時處理器註冊完成,前綴: {}", ORDER_REFUND_TIMEOUT_PREFIX);
     }
 
     @Override
@@ -107,14 +126,45 @@ public class OrderExpirationServiceImpl implements OrderExpirationService, Comma
     private void handleRefundOrderKey(String expiredKey) {
         try {
             // 從key中提取訂單ID
-            String orderNo = expiredKey.replace(ORDER_PAYMENT_TIMEOUT_PREFIX, "");
+            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个工作日原路返还,请注意查收。";
+            }
+            if (expiredKey.contains(ORDER_REFUND_TIMEOUT_PREFIX)) {
+                orderNo = expiredKey.replace(ORDER_REFUND_TIMEOUT_PREFIX, "");
+                refundReason = "申请退款超时后退款";
+                title = "申请退款通知";
+                message = "您的编号为" + orderNo + "的订单,申请退款的信息已提交给律师,等待律师同意。律师同意或48小时未处理,系统会将订单金额原路返还,请注意查收。";
+            }
 
-            log.info("檢測到訂單支付超時,訂單no: {}", orderNo);
+            // 查詢訂單
+            LambdaQueryWrapper<LawyerConsultationOrder> queryWrapper = new LambdaQueryWrapper<>();
+            queryWrapper.eq(LawyerConsultationOrder::getOrderNumber, orderNo).last("LIMIT 1");
+            LawyerConsultationOrder order = orderMapper.selectOne(queryWrapper);
 
-            // 處理訂單支付超時
-            handleOrderPaymentTimeout(orderNo);
+            log.info("檢測到有訂單需要退款,訂單no: {}", orderNo);
+
+            // 處理訂單退款
+            aliController.processRefund(order.getAlipayNo(),  new BigDecimal(order.getOrderAmount()).divide(new BigDecimal(100), 2, RoundingMode.HALF_UP).toString(), refundReason,"");
+
+            log.info("訂單退款成功,訂單no: {}", orderNo);
+
+            //通知
+            LifeNotice lifeNotice = buildLifeNotice(order, title, message);
+            WebSocketVo webSocketVo = buildWebSocketVo(lifeNotice);
+
+            lifeNoticeMapper.insert(lifeNotice);
+            webSocketProcess.sendMessage(lifeNotice.getReceiverId(), JSONObject.from(webSocketVo).toJSONString());
+
+            log.info("系统通知发送成功,訂單no: {}", orderNo);
         } catch (Exception e) {
-            log.error("處理過期訂單key失敗,key: {}", expiredKey, e);
+            log.error("處理有訂單需要退款key失敗,key: {}", expiredKey, e);
         }
     }
 
@@ -189,7 +239,7 @@ public class OrderExpirationServiceImpl implements OrderExpirationService, Comma
         // 當key過期時,會觸發RedisKeyExpirationListener
         redisService.setString(key, String.valueOf(orderNumber), timeout);
 
-        log.info("設置訂單支付超時監聽,訂單NO: {}, 超時時間: {}秒, key: {}", orderNumber, timeout, key);
+        log.info("設置訂單接单超時監聽,訂單NO: {}, 超時時間: {}秒, key: {}", orderNumber, timeout, key);
     }
 
     @Override
@@ -206,7 +256,7 @@ public class OrderExpirationServiceImpl implements OrderExpirationService, Comma
         // 當key過期時,會觸發RedisKeyExpirationListener
         redisService.setString(key, String.valueOf(orderNumber), timeout);
 
-        log.info("設置訂單支付超時監聽,訂單NO: {}, 超時時間: {}秒, key: {}", orderNumber, timeout, key);
+        log.info("設置訂單退款超時監聽,訂單NO: {}, 超時時間: {}秒, key: {}", orderNumber, timeout, key);
     }
 
     /**
@@ -234,7 +284,7 @@ public class OrderExpirationServiceImpl implements OrderExpirationService, Comma
         String key = ORDER_ACCEPT_TIMEOUT_PREFIX + orderNumber;
         redisService.delete(key);
 
-        log.info("取消訂單支付超時監聽,訂單ID: {}, key: {}", orderNumber, key);
+        log.info("取消訂單接单超時監聽,訂單ID: {}, key: {}", orderNumber, key);
     }
 
     @Override
@@ -246,7 +296,39 @@ public class OrderExpirationServiceImpl implements OrderExpirationService, Comma
         String key = ORDER_REFUND_TIMEOUT_PREFIX + orderNumber;
         redisService.delete(key);
 
-        log.info("取消訂單支付超時監聽,訂單ID: {}, key: {}", orderNumber, 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;
     }
 }
 

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

@@ -1,6 +1,6 @@
 package shop.alien.store.controller;
 
-import com.alibaba.excel.util.CollectionUtils;
+import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
 import io.swagger.annotations.*;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;

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

@@ -1,6 +1,6 @@
 package shop.alien.store.controller;
 
-import com.alibaba.excel.util.CollectionUtils;
+import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
 import io.swagger.annotations.*;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;

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

@@ -1,17 +1,12 @@
 package shop.alien.store.service.impl;
 
-import com.alibaba.excel.util.CollectionUtils;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
-import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
-import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import lombok.RequiredArgsConstructor;
 import org.apache.commons.lang.StringUtils;
-import org.springframework.beans.BeanUtils;
 import org.springframework.stereotype.Service;
 import shop.alien.entity.store.StoreDictionary;
-import shop.alien.entity.store.StoreMenu;
-import shop.alien.entity.store.vo.StoreDictionaryVo;
 import shop.alien.entity.store.vo.SystemConfigVo;
 import shop.alien.mapper.StoreDictionaryMapper;
 import shop.alien.store.service.StoreDictService;

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

@@ -1,7 +1,7 @@
 package shop.alien.store.service.impl;
 
-import com.alibaba.excel.util.CollectionUtils;
 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import lombok.RequiredArgsConstructor;
 import org.apache.commons.lang.StringUtils;
@@ -14,6 +14,7 @@ import shop.alien.mapper.StoreImgMapper;
 import shop.alien.mapper.StoreOfficialAlbumMapper;
 import shop.alien.store.service.StoreOfficialAlbumService;
 import shop.alien.store.util.CommonConstant;
+
 import java.util.Collections;
 import java.util.List;
 import java.util.stream.Collectors;

+ 1 - 1
alien-util/pom.xml

@@ -121,7 +121,7 @@
         <dependency>
             <groupId>com.alibaba</groupId>
             <artifactId>easyexcel</artifactId>
-            <version>1.1.2-beta5</version>
+            <version>4.0.3</version>
         </dependency>
         <!-- 阿里云内容安全 -->
         <dependency>

+ 45 - 0
alien-util/src/main/java/shop/alien/util/common/WebTool.java

@@ -0,0 +1,45 @@
+package shop.alien.util.common;
+
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * Web工具类
+ * 用于在非Controller层获取HttpServletRequest和HttpServletResponse
+ *
+ * @author system
+ */
+public class WebTool {
+
+    /**
+     * 获取当前请求的HttpServletRequest
+     *
+     * @return HttpServletRequest
+     * @throws IllegalStateException 如果当前不在Web请求上下文中
+     */
+    public static HttpServletRequest getRequest() {
+        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
+        if (attributes == null) {
+            throw new IllegalStateException("当前不在Web请求上下文中,无法获取HttpServletRequest");
+        }
+        return attributes.getRequest();
+    }
+
+    /**
+     * 获取当前请求的HttpServletResponse
+     *
+     * @return HttpServletResponse
+     * @throws IllegalStateException 如果当前不在Web请求上下文中
+     */
+    public static HttpServletResponse getResponse() {
+        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
+        if (attributes == null) {
+            throw new IllegalStateException("当前不在Web请求上下文中,无法获取HttpServletResponse");
+        }
+        return attributes.getResponse();
+    }
+}
+

+ 261 - 0
alien-util/src/main/java/shop/alien/util/excel/EasyExcelUtil.java

@@ -0,0 +1,261 @@
+package shop.alien.util.excel;
+
+import com.alibaba.excel.EasyExcel;
+import com.alibaba.excel.read.listener.ReadListener;
+import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.multipart.MultipartFile;
+import shop.alien.util.common.WebTool;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+
+/**
+ * EasyExcel 工具类
+ * 封装常用的 Excel 导入导出功能
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Slf4j
+public class EasyExcelUtil {
+
+    /**
+     * 导出 Excel 文件到 HTTP 响应(自动获取响应对象)
+     *
+     * @param dataList  要导出的数据列表
+     * @param clazz     数据类型的 Class 对象
+     * @param fileName  导出的文件名(不带扩展名)
+     * @param sheetName 工作表名称
+     * @param <T>       数据类型
+     * @throws IOException 如果发生 IO 异常
+     */
+    public static <T> void exportExcel(List<T> dataList, Class<T> clazz, String fileName, String sheetName) throws IOException {
+        HttpServletResponse response = WebTool.getResponse();
+        exportExcel(response, dataList, clazz, fileName, sheetName);
+    }
+
+    /**
+     * 导出 Excel 文件到 HTTP 响应
+     *
+     * @param response  HttpServletResponse 对象
+     * @param dataList  要导出的数据列表
+     * @param clazz     数据类型的 Class 对象
+     * @param fileName  导出的文件名(不带扩展名)
+     * @param sheetName 工作表名称
+     * @param <T>       数据类型
+     * @throws IOException 如果发生 IO 异常
+     */
+    public static <T> void exportExcel(HttpServletResponse response, List<T> dataList, Class<T> clazz,
+                                       String fileName, String sheetName) throws IOException {
+        if (dataList == null || dataList.isEmpty()) {
+            log.warn("[EasyExcelUtil] 导出数据为空: {}", fileName);
+            throw new IllegalArgumentException("导出数据为空");
+        }
+
+        // 设置响应头
+        setupResponse(response, fileName);
+
+        // 导出Excel
+        EasyExcel.write(response.getOutputStream(), clazz)
+                .sheet(sheetName != null ? sheetName : "Sheet1")
+                .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()) // 自动列宽
+                .doWrite(dataList);
+
+        log.info("[EasyExcelUtil] 导出成功: {}, 共 {} 条数据", fileName, dataList.size());
+    }
+
+    /**
+     * 导出 Excel 文件到 HTTP 响应(使用默认工作表名称)
+     *
+     * @param response HttpServletResponse 对象
+     * @param dataList 要导出的数据列表
+     * @param clazz    数据类型的 Class 对象
+     * @param fileName 导出的文件名(不带扩展名)
+     * @param <T>      数据类型
+     * @throws IOException 如果发生 IO 异常
+     */
+    public static <T> void exportExcel(HttpServletResponse response, List<T> dataList, Class<T> clazz, String fileName) throws IOException {
+        exportExcel(response, dataList, clazz, fileName, "Sheet1");
+    }
+
+    /**
+     * 导出 Excel 文件到本地文件
+     *
+     * @param filePath  文件路径
+     * @param dataList  要导出的数据列表
+     * @param clazz     数据类型的 Class 对象
+     * @param sheetName 工作表名称
+     * @param <T>       数据类型
+     * @throws IOException 如果发生 IO 异常
+     */
+    public static <T> void exportExcelToFile(String filePath, List<T> dataList, Class<T> clazz, String sheetName) throws IOException {
+        if (dataList == null || dataList.isEmpty()) {
+            log.warn("[EasyExcelUtil] 导出数据为空: {}", filePath);
+            throw new IllegalArgumentException("导出数据为空");
+        }
+
+        EasyExcel.write(filePath, clazz)
+                .sheet(sheetName != null ? sheetName : "Sheet1")
+                .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
+                .doWrite(dataList);
+
+        log.info("[EasyExcelUtil] 导出到文件成功: {}, 共 {} 条数据", filePath, dataList.size());
+    }
+
+    /**
+     * 导出 Excel 文件到本地文件(使用默认工作表名称)
+     *
+     * @param filePath 文件路径
+     * @param dataList 要导出的数据列表
+     * @param clazz    数据类型的 Class 对象
+     * @param <T>      数据类型
+     * @throws IOException 如果发生 IO 异常
+     */
+    public static <T> void exportExcelToFile(String filePath, List<T> dataList, Class<T> clazz) throws IOException {
+        exportExcelToFile(filePath, dataList, clazz, "Sheet1");
+    }
+
+    /**
+     * 导出 Excel 文件到输出流
+     *
+     * @param outputStream 输出流
+     * @param dataList     要导出的数据列表
+     * @param clazz        数据类型的 Class 对象
+     * @param sheetName    工作表名称
+     * @param <T>          数据类型
+     * @throws IOException 如果发生 IO 异常
+     */
+    public static <T> void exportExcelToStream(OutputStream outputStream, List<T> dataList, Class<T> clazz, String sheetName) throws IOException {
+        if (dataList == null || dataList.isEmpty()) {
+            log.warn("[EasyExcelUtil] 导出数据为空");
+            throw new IllegalArgumentException("导出数据为空");
+        }
+
+        EasyExcel.write(outputStream, clazz)
+                .sheet(sheetName != null ? sheetName : "Sheet1")
+                .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
+                .doWrite(dataList);
+
+        log.info("[EasyExcelUtil] 导出到流成功, 共 {} 条数据", dataList.size());
+    }
+
+    /**
+     * 导入 Excel 文件
+     *
+     * @param file         上传的 MultipartFile 文件
+     * @param clazz        数据类型的 Class 对象
+     * @param readListener 读取监听器
+     * @param <T>          数据类型
+     * @throws IOException 如果发生 IO 异常
+     */
+    public static <T> void importExcel(MultipartFile file, Class<T> clazz, ReadListener<T> readListener) throws IOException {
+        if (file == null || file.isEmpty()) {
+            throw new IllegalArgumentException("上传文件为空");
+        }
+
+        log.info("[EasyExcelUtil] 开始导入Excel文件: {}", file.getOriginalFilename());
+
+        EasyExcel.read(file.getInputStream(), clazz, readListener)
+                .sheet()
+                .doRead();
+
+        log.info("[EasyExcelUtil] Excel文件导入完成: {}", file.getOriginalFilename());
+    }
+
+    /**
+     * 导入 Excel 文件(指定Sheet)
+     *
+     * @param file         上传的 MultipartFile 文件
+     * @param clazz        数据类型的 Class 对象
+     * @param readListener 读取监听器
+     * @param sheetNo      Sheet索引(从0开始)
+     * @param <T>          数据类型
+     * @throws IOException 如果发生 IO 异常
+     */
+    public static <T> void importExcel(MultipartFile file, Class<T> clazz, ReadListener<T> readListener, int sheetNo) throws IOException {
+        if (file == null || file.isEmpty()) {
+            throw new IllegalArgumentException("上传文件为空");
+        }
+
+        log.info("[EasyExcelUtil] 开始导入Excel文件: {}, Sheet: {}", file.getOriginalFilename(), sheetNo);
+
+        EasyExcel.read(file.getInputStream(), clazz, readListener)
+                .sheet(sheetNo)
+                .doRead();
+
+        log.info("[EasyExcelUtil] Excel文件导入完成: {}", file.getOriginalFilename());
+    }
+
+    /**
+     * 导入 Excel 文件(指定Sheet名称)
+     *
+     * @param file         上传的 MultipartFile 文件
+     * @param clazz        数据类型的 Class 对象
+     * @param readListener 读取监听器
+     * @param sheetName    Sheet名称
+     * @param <T>          数据类型
+     * @throws IOException 如果发生 IO 异常
+     */
+    public static <T> void importExcel(MultipartFile file, Class<T> clazz, ReadListener<T> readListener, String sheetName) throws IOException {
+        if (file == null || file.isEmpty()) {
+            throw new IllegalArgumentException("上传文件为空");
+        }
+
+        log.info("[EasyExcelUtil] 开始导入Excel文件: {}, Sheet: {}", file.getOriginalFilename(), sheetName);
+
+        EasyExcel.read(file.getInputStream(), clazz, readListener)
+                .sheet(sheetName)
+                .doRead();
+
+        log.info("[EasyExcelUtil] Excel文件导入完成: {}", file.getOriginalFilename());
+    }
+
+    /**
+     * 下载导入模板(只有表头,无数据)
+     *
+     * @param response HttpServletResponse 对象
+     * @param clazz    数据类型的 Class 对象
+     * @param fileName 文件名(不带扩展名)
+     * @param <T>      数据类型
+     * @throws IOException 如果发生 IO 异常
+     */
+    public static <T> void downloadTemplate(HttpServletResponse response, Class<T> clazz, String fileName) throws IOException {
+        setupResponse(response, fileName);
+
+        // 导出空列表,只有表头
+        EasyExcel.write(response.getOutputStream(), clazz)
+                .sheet("Sheet1")
+                .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
+                .doWrite(new java.util.ArrayList<>());
+
+        log.info("[EasyExcelUtil] 模板下载成功: {}", fileName);
+    }
+
+    /**
+     * 设置 HTTP 响应头
+     *
+     * @param response HttpServletResponse 对象
+     * @param fileName 文件名(不带扩展名)
+     * @throws IOException 如果发生 IO 异常
+     */
+    private static void setupResponse(HttpServletResponse response, String fileName) throws IOException {
+        response.reset(); // 重置响应,清除之前可能设置的响应头
+        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
+        response.setCharacterEncoding("utf-8");
+
+        // 使用 RFC 5987 标准格式,更好地支持中文文件名
+        String encodedFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8.toString()).replaceAll("\\+", "%20");
+        response.setHeader("Content-Disposition", "attachment;filename=\"" + encodedFileName + ".xlsx\";filename*=utf-8''" + encodedFileName + ".xlsx");
+
+        // 设置缓存控制
+        response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
+        response.setHeader("Pragma", "no-cache");
+        response.setDateHeader("Expires", 0);
+    }
+}
+

+ 35 - 38
alien-util/src/main/java/shop/alien/util/excel/ExcelWriteTest.java

@@ -1,19 +1,17 @@
 package shop.alien.util.excel;
 
-import com.alibaba.excel.ExcelWriter;
-import com.alibaba.excel.metadata.Sheet;
-import com.alibaba.excel.support.ExcelTypeEnum;
+import com.alibaba.excel.EasyExcel;
 import org.junit.jupiter.api.Test;
 
-import java.io.FileOutputStream;
 import java.io.IOException;
-import java.io.OutputStream;
 import java.util.ArrayList;
 import java.util.List;
 
 /**
+ * EasyExcel 2.2.10 写入测试
+ * 
  * @author ssk
- * @version 1.0
+ * @version 2.0
  * @date 2020/7/21 17:18
  */
 public class ExcelWriteTest {
@@ -25,43 +23,42 @@ public class ExcelWriteTest {
      */
     @Test
     public void writeWithoutHead() throws IOException {
-        try (OutputStream out = new FileOutputStream("d://withoutHead.xlsx");) {
-            ExcelWriter writer = new ExcelWriter(out, ExcelTypeEnum.XLSX, false);
-            Sheet sheet1 = new Sheet(1, 0);
-            sheet1.setSheetName("sheet1");
-            List<List<String>> data = new ArrayList<>();
-            for (int i = 0; i < 100; i++) {
-                List<String> item = new ArrayList<>();
-                item.add("item0" + i);
-                item.add("item1" + i);
-                item.add("item2" + i);
-                data.add(item);
-            }
-            writer.write0(data, sheet1);
-            writer.finish();
+        String fileName = "d://withoutHead.xlsx";
+        List<List<String>> data = new ArrayList<>();
+        for (int i = 0; i < 100; i++) {
+            List<String> item = new ArrayList<>();
+            item.add("item0" + i);
+            item.add("item1" + i);
+            item.add("item2" + i);
+            data.add(item);
         }
+        EasyExcel.write(fileName)
+                .sheet("sheet1")
+                .doWrite(data);
     }
 
+    /**
+     * 带表头写入
+     *
+     * @throws IOException
+     */
     @Test
     public void writeWithHead() throws IOException {
-        try (OutputStream out = new FileOutputStream("D://withHead.xlsx")) {
-            ExcelWriter writer = new ExcelWriter(out, ExcelTypeEnum.XLSX);
-            Sheet sheet1 = new Sheet(1, 0, UserExcel.class);
-            sheet1.setSheetName("sheet1");
-            List<UserExcel> data = new ArrayList<>();
-            for (int i = 0; i < 100; i++) {
-                UserExcel item = new UserExcel();
-                item.name = "name" + i;
-                item.age = "age" + i;
-                item.email = "email" + i;
-                item.address = "address" + i;
-                item.sax = "sax" + i;
-                item.height = "height" + i;
-                item.last = "last" + i;
-                data.add(item);
-            }
-            writer.write(data, sheet1);
-            writer.finish();
+        String fileName = "D://withHead.xlsx";
+        List<UserExcel> data = new ArrayList<>();
+        for (int i = 0; i < 100; i++) {
+            UserExcel item = new UserExcel();
+            item.name = "name" + i;
+            item.age = "age" + i;
+            item.email = "email" + i;
+            item.address = "address" + i;
+            item.sax = "sax" + i;
+            item.height = "height" + i;
+            item.last = "last" + i;
+            data.add(item);
         }
+        EasyExcel.write(fileName, UserExcel.class)
+                .sheet("sheet1")
+                .doWrite(data);
     }
 }

+ 1 - 2
alien-util/src/main/java/shop/alien/util/excel/UserExcel.java

@@ -1,7 +1,6 @@
 package shop.alien.util.excel;
 
 import com.alibaba.excel.annotation.ExcelProperty;
-import com.alibaba.excel.metadata.BaseRowModel;
 import lombok.Data;
 
 /**
@@ -12,7 +11,7 @@ import lombok.Data;
  * @date 2020/7/21 17:13
  */
 @Data
-public class UserExcel extends BaseRowModel {
+public class UserExcel {
 
     @ExcelProperty(value = "姓名", index = 0)
     public String name;

+ 1 - 1
alien-util/src/main/java/shop/alien/util/port/StartCommand.java

@@ -1,7 +1,7 @@
 package shop.alien.util.port;
 
-import com.alibaba.excel.util.StringUtils;
 import lombok.extern.slf4j.Slf4j;
+import org.springframework.util.StringUtils;
 
 /**
  * 设置可用端口