Эх сурвалжийг харах

Merge branch 'refs/heads/sit' into sit-shenzhen

penghao 12 цаг өмнө
parent
commit
056f072d84
100 өөрчлөгдсөн 10947 нэмэгдсэн , 187 устгасан
  1. 29 0
      alien-entity/src/main/java/shop/alien/entity/store/LawyerConsultationOrder.java
  2. 16 0
      alien-entity/src/main/java/shop/alien/entity/store/LawyerUser.java
  3. 4 0
      alien-entity/src/main/java/shop/alien/entity/store/LifeNotice.java
  4. 50 1
      alien-entity/src/main/java/shop/alien/entity/store/LifeUserOrder.java
  5. 4 0
      alien-entity/src/main/java/shop/alien/entity/store/OrderReview.java
  6. 8 0
      alien-entity/src/main/java/shop/alien/entity/store/ReviewComment.java
  7. 72 0
      alien-entity/src/main/java/shop/alien/entity/store/StoreBanner.java
  8. 8 0
      alien-entity/src/main/java/shop/alien/entity/store/StoreComment.java
  9. 1 1
      alien-entity/src/main/java/shop/alien/entity/store/StoreCommentAppeal.java
  10. 17 0
      alien-entity/src/main/java/shop/alien/entity/store/dto/LawyerConsultationOrderDto.java
  11. 40 0
      alien-entity/src/main/java/shop/alien/entity/store/dto/LawyerReplyDto.java
  12. 16 0
      alien-entity/src/main/java/shop/alien/entity/store/dto/LawyerUserDto.java
  13. 6 0
      alien-entity/src/main/java/shop/alien/entity/store/dto/PayStatusRequest.java
  14. 6 0
      alien-entity/src/main/java/shop/alien/entity/store/dto/ReviewReplyDto.java
  15. 37 0
      alien-entity/src/main/java/shop/alien/entity/store/dto/StoreBannerDto.java
  16. 2 2
      alien-entity/src/main/java/shop/alien/entity/store/excelVo/LawFirmExcelVo.java
  17. 64 0
      alien-entity/src/main/java/shop/alien/entity/store/excelVo/LawyerConsultationOrderExcelVo.java
  18. 1 1
      alien-entity/src/main/java/shop/alien/entity/store/vo/LawFirmListVO.java
  19. 1 1
      alien-entity/src/main/java/shop/alien/entity/store/vo/LawFirmPaymentVO.java
  20. 17 1
      alien-entity/src/main/java/shop/alien/entity/store/vo/LawyerConsultationOrderVO.java
  21. 75 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/LawyerDashboardVO.java
  22. 1 1
      alien-entity/src/main/java/shop/alien/entity/store/vo/LawyerListVO.java
  23. 59 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/LawyerOrderStatisticsVO.java
  24. 14 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/LawyerUserVo.java
  25. 189 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/LawyerViolationDetailVO.java
  26. 16 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/LifeUserOrderCommentVo.java
  27. 49 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/LifeUserOrderListWithStatisticsVO.java
  28. 1 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/OrderRevenueVO.java
  29. 4 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/OrderReviewDetailVo.java
  30. 3 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/OrderReviewVo.java
  31. 31 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/PendingReviewVo.java
  32. 6 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/ReviewCommentVo.java
  33. 24 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/StoreBannerVo.java
  34. 117 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/UserOrderVo.java
  35. 190 10
      alien-entity/src/main/java/shop/alien/mapper/LawyerConsultationOrderMapper.java
  36. 5 0
      alien-entity/src/main/java/shop/alien/mapper/LawyerUserMapper.java
  37. 73 0
      alien-entity/src/main/java/shop/alien/mapper/LifeUserOrderMapper.java
  38. 3 1
      alien-entity/src/main/java/shop/alien/mapper/OrderReviewMapper.java
  39. 14 0
      alien-entity/src/main/java/shop/alien/mapper/StoreBannerMapper.java
  40. 2 2
      alien-entity/src/main/java/shop/alien/mapper/StoreCommentAppealMapper.java
  41. 47 0
      alien-entity/src/main/java/shop/alien/mapper/StoreCommentMapper.java
  42. 19 19
      alien-entity/src/main/resources/mapper/LawFirmMapper.xml
  43. 10 3
      alien-entity/src/main/resources/mapper/LawyerConsultationOrderMapper.xml
  44. 24 2
      alien-entity/src/main/resources/mapper/OrderReviewMapper.xml
  45. 93 11
      alien-entity/src/main/resources/mapper/ReviewCommentMapper.xml
  46. 3 2
      alien-entity/src/main/resources/mapper/second/LawyerReconcMapper.xml
  47. 6 6
      alien-gateway/src/main/java/shop/alien/gateway/controller/SystemController.java
  48. 108 0
      alien-gateway/src/main/java/shop/alien/gateway/util/SmsUtil.java
  49. 4 2
      alien-job/src/main/java/shop/alien/job/second/AiUserViolationJob.java
  50. 7 7
      alien-job/src/main/java/shop/alien/job/store/BadReviewAppealJob.java
  51. 12 4
      alien-lawyer/src/main/java/shop/alien/lawyer/controller/LawFirmController.java
  52. 80 0
      alien-lawyer/src/main/java/shop/alien/lawyer/controller/LawyerClientConsultationOrderController.java
  53. 54 5
      alien-lawyer/src/main/java/shop/alien/lawyer/controller/LawyerConsultationOrderController.java
  54. 50 6
      alien-lawyer/src/main/java/shop/alien/lawyer/controller/LawyerNoticeController.java
  55. 96 0
      alien-lawyer/src/main/java/shop/alien/lawyer/controller/LawyerStatisticsController.java
  56. 424 0
      alien-lawyer/src/main/java/shop/alien/lawyer/controller/LawyerStoreCommentController.java
  57. 9 0
      alien-lawyer/src/main/java/shop/alien/lawyer/controller/LawyerUserController.java
  58. 71 0
      alien-lawyer/src/main/java/shop/alien/lawyer/controller/LawyerUserViolationController.java
  59. 23 1
      alien-lawyer/src/main/java/shop/alien/lawyer/controller/OrderReviewController.java
  60. 92 0
      alien-lawyer/src/main/java/shop/alien/lawyer/controller/PaymentController.java
  61. 59 4
      alien-lawyer/src/main/java/shop/alien/lawyer/controller/ReviewCommentController.java
  62. 61 0
      alien-lawyer/src/main/java/shop/alien/lawyer/payment/PaymentStrategy.java
  63. 64 0
      alien-lawyer/src/main/java/shop/alien/lawyer/payment/PaymentStrategyFactory.java
  64. 602 0
      alien-lawyer/src/main/java/shop/alien/lawyer/payment/impl/AlipayPaymentStrategyImpl.java
  65. 1265 0
      alien-lawyer/src/main/java/shop/alien/lawyer/payment/impl/WeChatPaymentStrategyImpl.java
  66. 1 0
      alien-lawyer/src/main/java/shop/alien/lawyer/service/LawFirmReconciliationService.java
  67. 4 1
      alien-lawyer/src/main/java/shop/alien/lawyer/service/LawFirmService.java
  68. 30 0
      alien-lawyer/src/main/java/shop/alien/lawyer/service/LawyerClientConsultationOrderService.java
  69. 13 1
      alien-lawyer/src/main/java/shop/alien/lawyer/service/LawyerNoticeService.java
  70. 31 0
      alien-lawyer/src/main/java/shop/alien/lawyer/service/LawyerStatisticsService.java
  71. 3 0
      alien-lawyer/src/main/java/shop/alien/lawyer/service/LawyerUserService.java
  72. 14 0
      alien-lawyer/src/main/java/shop/alien/lawyer/service/LawyerUserViolationService.java
  73. 15 0
      alien-lawyer/src/main/java/shop/alien/lawyer/service/OrderExpirationService.java
  74. 17 0
      alien-lawyer/src/main/java/shop/alien/lawyer/service/RefundRecordService.java
  75. 12 0
      alien-lawyer/src/main/java/shop/alien/lawyer/service/ReviewCommentService.java
  76. 269 0
      alien-lawyer/src/main/java/shop/alien/lawyer/service/StoreCommentService.java
  77. 10 4
      alien-lawyer/src/main/java/shop/alien/lawyer/service/impl/CommentAppealServiceImpl.java
  78. 3 3
      alien-lawyer/src/main/java/shop/alien/lawyer/service/impl/LawFirmReconciliationServiceImpl.java
  79. 35 37
      alien-lawyer/src/main/java/shop/alien/lawyer/service/impl/LawFirmServiceImpl.java
  80. 464 1
      alien-lawyer/src/main/java/shop/alien/lawyer/service/impl/LawyerClientConsultationOrderServiceImpl.java
  81. 605 24
      alien-lawyer/src/main/java/shop/alien/lawyer/service/impl/LawyerConsultationOrderServiceImpl.java
  82. 120 2
      alien-lawyer/src/main/java/shop/alien/lawyer/service/impl/LawyerNoticeServiceImpl.java
  83. 796 0
      alien-lawyer/src/main/java/shop/alien/lawyer/service/impl/LawyerStatisticsServiceImpl.java
  84. 0 4
      alien-lawyer/src/main/java/shop/alien/lawyer/service/impl/LawyerUserLogInServiceImpl.java
  85. 26 1
      alien-lawyer/src/main/java/shop/alien/lawyer/service/impl/LawyerUserServiceImpl.java
  86. 487 1
      alien-lawyer/src/main/java/shop/alien/lawyer/service/impl/LawyerUserViolationServiceImpl.java
  87. 88 0
      alien-lawyer/src/main/java/shop/alien/lawyer/service/impl/OrderExpirationServiceImpl.java
  88. 13 2
      alien-lawyer/src/main/java/shop/alien/lawyer/service/impl/OrderReviewServiceImpl.java
  89. 22 0
      alien-lawyer/src/main/java/shop/alien/lawyer/service/impl/RefundRecordServiceImpl.java
  90. 200 2
      alien-lawyer/src/main/java/shop/alien/lawyer/service/impl/ReviewCommentServiceImpl.java
  91. 2007 0
      alien-lawyer/src/main/java/shop/alien/lawyer/service/impl/StoreCommentServiceImpl.java
  92. 856 0
      alien-lawyer/src/main/java/shop/alien/lawyer/util/WXPayUtility.java
  93. 1 1
      alien-lawyer/src/main/resources/bootstrap.yml
  94. 7 1
      alien-second/src/main/java/shop/alien/second/service/impl/SecondGoodsAuditServiceImpl.java
  95. 9 2
      alien-second/src/main/java/shop/alien/second/util/AiTaskUtils.java
  96. 4 3
      alien-second/src/main/java/shop/alien/second/util/AiUserViolationUtils.java
  97. 1 1
      alien-store/src/main/java/shop/alien/store/controller/AiAuditController.java
  98. 72 0
      alien-store/src/main/java/shop/alien/store/controller/AiChatController.java
  99. 26 3
      alien-store/src/main/java/shop/alien/store/controller/AliController.java
  100. 128 0
      alien-store/src/main/java/shop/alien/store/controller/CommentAppealController.java

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

@@ -179,5 +179,34 @@ public class LawyerConsultationOrder extends Model<LawyerConsultationOrder> {
     @ApiModelProperty(value = "支付方式 1支付宝 2微信")
     @TableField("pay_type")
     private  String payType;
+
+
+    @ApiModelProperty(value = "分钟收费-钱数")
+    @TableField("charge_minute")
+    private Integer chargeMinute;
+
+    @ApiModelProperty(value = "分钟收费-分钟数")
+    @TableField("minute_num")
+    private Integer minuteNum;
+
+    @ApiModelProperty(value = "次数收费-钱数")
+    @TableField("charge_time")
+    private Integer chargeTime;
+
+    @ApiModelProperty(value = "次数收费-次数")
+    @TableField("time_num")
+    private Integer timeNum;
+
+    @ApiModelProperty(value = "价格")
+    @TableField(exist = false)
+    private Integer price;
+
+    @ApiModelProperty(value = "方式  次 / 分钟")
+    @TableField(exist = false)
+    private String serviceType;
+
+
+
+
 }
 

+ 16 - 0
alien-entity/src/main/java/shop/alien/entity/store/LawyerUser.java

@@ -337,5 +337,21 @@ public class LawyerUser extends Model<LawyerUser> {
     @TableField(exist = false)
     private Integer reviewCount;
 
+    @ApiModelProperty(value = "分钟收费-钱数")
+    @TableField("charge_minute")
+    private Integer chargeMinute;
+
+    @ApiModelProperty(value = "分钟收费-分钟数")
+    @TableField("minute_num")
+    private Integer minuteNum;
+
+    @ApiModelProperty(value = "次数收费-钱数")
+    @TableField("charge_time")
+    private Integer chargeTime;
+
+    @ApiModelProperty(value = "次数收费-次数")
+    @TableField("time_num")
+    private Integer timeNum;
+
 }
 

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

@@ -63,4 +63,8 @@ public class LifeNotice {
     @ApiModelProperty(value = "修改人ID")
     @TableField("updated_user_id")
     private Integer updatedUserId;
+
+    @ApiModelProperty(value = "业务类型0-其他,1-律师模块通知")
+    @TableField("business_type")
+    private Integer businessType;
 }

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

@@ -4,53 +4,102 @@ import com.baomidou.mybatisplus.annotation.*;
 import com.fasterxml.jackson.annotation.JsonFormat;
 import com.fasterxml.jackson.annotation.JsonInclude;
 import io.swagger.annotations.ApiModelProperty;
+import io.swagger.annotations.ApiModel;
 import lombok.Data;
 
 import java.math.BigDecimal;
 import java.util.Date;
 
 /**
- * 用户订单
+ * 用户订单基础实体,对应表:life_user_order。
+ * 仅承载订单字段(不含评论信息),在评论视图中会被扩展为 VO。
+ *
+ * 字段对应:
+ * id                  - 主键
+ * userId              - 用户id
+ * storeId             - 商铺id
+ * quanId              - 优惠券券id(life_discount_coupon_user)
+ * quanCode            - 券编号
+ * orderNo             - 订单编号
+ * status              - 订单状态(0待支付;1已支付/待使用;2已核销;3已过期;4已取消;5已退款;6退款失败;7已完成)
+ * price               - 原价
+ * finalPrice          - 实付价
+ * buyTime             - 购买时间
+ * payTime             - 支付时间
+ * payMethod           - 支付方式
+ * usedTime            - 使用时间
+ * refundTime          - 退款时间
+ * deleteFlag          - 删除标记(0未删,1已删)
+ * createdTime         - 创建时间
+ * createdUserId       - 创建人ID
+ * updatedTime         - 更新时间
+ * updatedUserId       - 修改人ID
+ * sendDiscountCouponFlag - 核销发送优惠券标记(0未发,1已发)
+ * isSync              - 是否同步
+ * couponType          - 订单类型(1代金券;2团购订单)
+ * cancelTime          - 取消时间
+ * cancelReason        - 取消原因
+ * finishTime          - 完成时间
+ * orderStr            - 订单字符串
+ * expertOrderId       - 达人订单id
+ * orderAppraise       - 订单评价标记(0未评价;1已评价)
  */
 @Data
 @JsonInclude
 @TableName("life_user_order")
+@ApiModel(value = "LifeUserOrder对象", description = "用户订单表")
 public class LifeUserOrder {
 
+    @ApiModelProperty(value = "主键")
     @TableId(value = "id", type = IdType.AUTO)
     private String id;
 
+    @ApiModelProperty(value = "用户id")
     private String userId;
 
+    @ApiModelProperty(value = "商铺id")
     private String storeId;
 
+    @ApiModelProperty(value = "优惠券券id(life_discount_coupon_user)")
     private String quanId;
 
+    @ApiModelProperty(value = "券编号")
     private String quanCode;
 
+    @ApiModelProperty(value = "订单编号")
     private String orderNo;
 
+    @ApiModelProperty(value = "订单字符串")
     private String orderStr;
 
+    @ApiModelProperty(value = "订单状态,0待支付;1已支付/待使用;2已核销;3已过期;4已取消;5已退款;6退款失败;7已完成")
     private Integer status;
 
+    @ApiModelProperty(value = "订单价格(原价)")
     private String price;
 
+    @ApiModelProperty(value = "最终价格(付款)")
     private String finalPrice;
 
+    @ApiModelProperty(value = "购买时间")
     private Date buyTime;
 
+    @ApiModelProperty(value = "支付时间")
     private Date payTime;
 
+    @ApiModelProperty(value = "支付方法")
     private String payMethod;
 
+    @ApiModelProperty(value = "使用时间")
     private Date usedTime;
 
+    @ApiModelProperty(value = "退款时间")
     private Date refundTime;
 
     /**
      * 购买类型(订单类型,1,代金券;2,团购订单)
      */
+    @ApiModelProperty(value = "订单类型,1代金券;2团购订单")
     private Integer couponType;
 
 

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

@@ -100,5 +100,9 @@ public class OrderReview {
     @ApiModelProperty(value = "修改人ID")
     @TableField("updated_user_id")
     private Integer updatedUserId;
+
+    @ApiModelProperty(value = "申诉id (如果该评价被申诉 申诉id会有值)")
+    @TableField("appeal_id")
+    private String appealId;
 }
 

+ 8 - 0
alien-entity/src/main/java/shop/alien/entity/store/ReviewComment.java

@@ -38,6 +38,14 @@ public class ReviewComment {
     @TableField("receive_user_id")
     private Integer receiveUserId;
 
+    @ApiModelProperty(value = "发送用户类型:1-普通用户,2-律师")
+    @TableField("send_user_type")
+    private Integer sendUserType;
+
+    @ApiModelProperty(value = "接收用户类型:1-普通用户,2-律师")
+    @TableField("receive_user_type")
+    private Integer receiveUserType;
+
     @ApiModelProperty(value = "评论内容")
     @TableField("comment_content")
     private String commentContent;

+ 72 - 0
alien-entity/src/main/java/shop/alien/entity/store/StoreBanner.java

@@ -0,0 +1,72 @@
+package shop.alien.entity.store;
+
+import com.baomidou.mybatisplus.annotation.*;
+import com.baomidou.mybatisplus.extension.activerecord.Model;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ * Banner表
+ *
+ * @author gpt
+ * @since 2025-12-16
+ */
+@Data
+@JsonInclude
+@TableName("store_banner")
+@ApiModel(value = "StoreBanner对象", description = "Banner表")
+public class StoreBanner extends Model<StoreBanner> {
+
+    @ApiModelProperty(value = "主键")
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    @ApiModelProperty(value = "模块编码")
+    @TableField("module_code")
+    private String moduleCode;
+
+    @ApiModelProperty(value = "模块名称")
+    @TableField("module_name")
+    private String moduleName;
+
+    @ApiModelProperty(value = "状态:1 启用,0 禁用")
+    @TableField("status")
+    private String status;
+
+    @ApiModelProperty(value = "排序")
+    @TableField("sort")
+    private Integer sort;
+
+    @ApiModelProperty(value = "图片")
+    @TableField("img_urls")
+    private String imgUrls;
+
+    @ApiModelProperty(value = "删除标记, 0:未删除, 1:已删除")
+    @TableField("delete_flag")
+    @TableLogic
+    private Integer deleteFlag;
+
+    @ApiModelProperty(value = "创建时间")
+    @TableField(value = "created_time", fill = FieldFill.INSERT)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date createdTime;
+
+    @ApiModelProperty(value = "创建人ID")
+    @TableField("created_user_id")
+    private Integer createdUserId;
+
+    @ApiModelProperty(value = "修改时间")
+    @TableField(value = "updated_time", fill = FieldFill.INSERT_UPDATE)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date updatedTime;
+
+    @ApiModelProperty(value = "修改人ID")
+    @TableField("updated_user_id")
+    private Integer updatedUserId;
+}
+

+ 8 - 0
alien-entity/src/main/java/shop/alien/entity/store/StoreComment.java

@@ -123,4 +123,12 @@ public class StoreComment extends Model<StoreComment> {
     @TableField("service_score")
     private Double serviceScore;
 
+    @ApiModelProperty(value = "订单id")
+    @TableField("order_id")
+    private Integer orderId;
+
+    @ApiModelProperty(value = "律师id")
+    @TableField("lawyer_id")
+    private Integer lawyerId;
+
 }

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

@@ -43,7 +43,7 @@ public class StoreCommentAppeal extends Model<StoreCommentAppeal> {
     @TableField("img_id")
     private String imgId;
 
-    @ApiModelProperty(value = "申诉状态: 0:待处理, 1:已驳回, 2:已同意")
+    @ApiModelProperty(value = "申诉状态: 0:待处理, 1:已驳回, 2:已同意, 3:处理中")
     @TableField("appeal_status")
     private Integer appealStatus;
 

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

@@ -130,5 +130,22 @@ public class LawyerConsultationOrderDto extends Model<LawyerConsultationOrderDto
     @TableField("pay_type")
     private  String payType;
 
+    @ApiModelProperty(value = "分钟收费-钱数")
+    @TableField("charge_minute")
+    private Integer chargeMinute;
+
+    @ApiModelProperty(value = "分钟收费-分钟数")
+    @TableField("minute_num")
+    private Integer minuteNum;
+
+    @ApiModelProperty(value = "次数收费-钱数")
+    @TableField("charge_time")
+    private Integer chargeTime;
+
+
+    @ApiModelProperty(value = "次数收费-次数")
+    @TableField("time_num")
+    private Integer timeNum;
+
 }
 

+ 40 - 0
alien-entity/src/main/java/shop/alien/entity/store/dto/LawyerReplyDto.java

@@ -0,0 +1,40 @@
+package shop.alien.entity.store.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+/**
+ * 律师回复DTO
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Data
+@ApiModel(value = "LawyerReplyDto对象", description = "律师回复DTO")
+public class LawyerReplyDto {
+
+    @ApiModelProperty(value = "回复类型:1-回复评价,2-回复评论", required = true)
+    private Integer replyType;
+
+    @ApiModelProperty(value = "评价ID(replyType=1时必填)")
+    private Integer reviewId;
+
+    @ApiModelProperty(value = "评论ID(replyType=2时必填)")
+    private Integer commentId;
+
+    @ApiModelProperty(value = "被回复用户ID(可选,不填时默认回复评价/评论的创建者)")
+    private Integer replyToUserId;
+
+    @ApiModelProperty(value = "回复内容", required = true)
+    private String replyContent;
+
+    @ApiModelProperty(value = "律师用户ID(必填)", required = true)
+    private Integer lawyerUserId;
+
+    @ApiModelProperty(value = "发送用户类型:1-普通用户,2-律师(必填)", required = true)
+    private Integer sendUserType;
+
+    @ApiModelProperty(value = "接收用户类型:1-普通用户,2-律师(必填)", required = true)
+    private Integer receiveUserType;
+}

+ 16 - 0
alien-entity/src/main/java/shop/alien/entity/store/dto/LawyerUserDto.java

@@ -140,5 +140,21 @@ public class LawyerUserDto implements Serializable {
 
     @ApiModelProperty(value = "验证码")
     private String msgCode;
+
+    @ApiModelProperty(value = "分钟收费-钱数")
+    @TableField("charge_minute")
+    private Integer chargeMinute;
+
+    @ApiModelProperty(value = "分钟收费-分钟数")
+    @TableField("minute_num")
+    private Integer minuteNum;
+
+    @ApiModelProperty(value = "次数收费-钱数")
+    @TableField("charge_time")
+    private Integer chargeTime;
+
+    @ApiModelProperty(value = "次数收费-次数")
+    @TableField("time_num")
+    private Integer timeNum;
 }
 

+ 6 - 0
alien-entity/src/main/java/shop/alien/entity/store/dto/PayStatusRequest.java

@@ -32,5 +32,11 @@ public class PayStatusRequest implements Serializable {
     
     @ApiModelProperty
     private String alipayNo;
+
+    @ApiModelProperty
+    private String payType;
+
+
+
 }
 

+ 6 - 0
alien-entity/src/main/java/shop/alien/entity/store/dto/ReviewReplyDto.java

@@ -25,5 +25,11 @@ public class ReviewReplyDto {
 
     @ApiModelProperty(value = "用户ID")
     private Integer userId;
+
+    @ApiModelProperty(value = "发送用户类型:1-普通用户,2-律师(可选,不填时使用首评的接收用户类型)")
+    private Integer sendUserType;
+
+    @ApiModelProperty(value = "接收用户类型:1-普通用户,2-律师(可选,不填时使用首评的发送用户类型)")
+    private Integer receiveUserType;
 }
 

+ 37 - 0
alien-entity/src/main/java/shop/alien/entity/store/dto/StoreBannerDto.java

@@ -0,0 +1,37 @@
+package shop.alien.entity.store.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * Banner提交DTO
+ *
+ * 用于新增/修改 Banner 的请求载体。
+ */
+@Data
+@ApiModel(value = "StoreBannerDto对象", description = "Banner提交DTO")
+public class StoreBannerDto implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "主键,修改时传递")
+    private Integer id;
+
+    @ApiModelProperty(value = "模块编码")
+    private String moduleCode;
+
+    @ApiModelProperty(value = "模块名称")
+    private String moduleName;
+
+    @ApiModelProperty(value = "状态:1 启用,0 禁用")
+    private String status;
+
+    @ApiModelProperty(value = "排序")
+    private Integer sort;
+
+    @ApiModelProperty(value = "图片URL,多个可用逗号分隔或前端自定义格式")
+    private String imgUrls;
+}
+

+ 2 - 2
alien-entity/src/main/java/shop/alien/entity/store/excelVo/LawFirmExcelVo.java

@@ -65,7 +65,7 @@ public class LawFirmExcelVo {
     @ApiModelProperty(value = "详细地址")
     private String address;
 
-//    @ExcelHeader("联系电话")
+    @ExcelHeader("联系电话")
     @ApiModelProperty(value = "联系电话")
     private String phone;
 
@@ -121,7 +121,7 @@ public class LawFirmExcelVo {
     @ApiModelProperty(value = "律师事务所收款账号(必须是16位或19位,每行只能填写一个账号,不允许逗号分隔)")
     private String paymentAccount;
 
-    @ExcelHeader("平台佣金比例")
+//    @ExcelHeader("平台佣金比例")
     @ApiModelProperty(value = "平台佣金比例(百分比)")
     private String platformCommissionRatio;
 

+ 64 - 0
alien-entity/src/main/java/shop/alien/entity/store/excelVo/LawyerConsultationOrderExcelVo.java

@@ -0,0 +1,64 @@
+package shop.alien.entity.store.excelVo;
+
+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;
+
+/**
+ * 律师咨询订单Excel导出对象
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Data
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@ApiModel(value = "LawyerConsultationOrderExcelVo对象", description = "律师咨询订单Excel导出对象")
+public class LawyerConsultationOrderExcelVo implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @ExcelProperty(value = "律师名称", index = 0)
+    @ApiModelProperty(value = "律师名称")
+    private String lawyerName;
+
+    @ExcelProperty(value = "律师电话", index = 1)
+    @ApiModelProperty(value = "律师电话")
+    private String lawyerPhone;
+
+    @ExcelProperty(value = "订单编号", index = 2)
+    @ApiModelProperty(value = "订单编号")
+    private String orderNumber;
+
+    @ExcelProperty(value = "订单金额", index = 3)
+    @ApiModelProperty(value = "订单金额(单位:元)")
+    private String orderAmount;
+
+    @ExcelProperty(value = "订单状态", index = 4)
+    @ApiModelProperty(value = "订单状态")
+    private String orderStatus;
+
+    @ExcelProperty(value = "支付方式", index = 5)
+    @ApiModelProperty(value = "支付方式")
+    private String payType;
+
+    @ExcelProperty(value = "分钟收费-钱数", index = 6)
+    @ApiModelProperty(value = "分钟收费-钱数(单位:元)")
+    private String chargeMinute;
+
+    @ExcelProperty(value = "分钟收费-分钟数", index = 7)
+    @ApiModelProperty(value = "分钟收费-分钟数")
+    private String minuteNum;
+
+    @ExcelProperty(value = "次数收费-钱数", index = 8)
+    @ApiModelProperty(value = "次数收费-钱数(单位:元)")
+    private String chargeTime;
+
+    @ExcelProperty(value = "次数收费-次数", index = 9)
+    @ApiModelProperty(value = "次数收费-次数")
+    private String timeNum;
+}
+

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

@@ -39,7 +39,7 @@ public class LawFirmListVO implements Serializable {
     @ApiModelProperty(value = "总订单金额(单位:元)")
     private String totalOrderAmountYuan;
 
-    @ExcelProperty(value = "平台信息服务费(元)", index = 3)
+    @ExcelIgnore
     @ApiModelProperty(value = "平台信息服务费(单位:元)")
     private String platformServiceFeeYuan;
 }

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

@@ -76,7 +76,7 @@ public class LawFirmPaymentVO implements Serializable {
     @ApiModelProperty(value = "律所简称")
     private String shortName;
 
-    @ExcelIgnore
+    @ExcelProperty(value = "联系电话", index = 5)
     @ApiModelProperty(value = "联系电话")
     private String phone;
 

+ 17 - 1
alien-entity/src/main/java/shop/alien/entity/store/vo/LawyerConsultationOrderVO.java

@@ -8,7 +8,6 @@ import io.swagger.annotations.ApiModelProperty;
 import lombok.Data;
 
 import java.io.Serializable;
-import java.math.BigDecimal;
 import java.time.LocalDate;
 import java.time.ZoneId;
 import java.time.temporal.ChronoUnit;
@@ -250,6 +249,8 @@ public class LawyerConsultationOrderVO implements Serializable {
     @ApiModelProperty(value = "申诉处理状态 0 审核中 1审核通过 2驳回")
     private String commentStatus;
 
+    @ApiModelProperty(value = "评价状态 1:  没有评价,去评价 2:  有评价了,没有被举报或者举报驳回了,就是查看评价 3:  有评价了,被举报成功了,什么都不显示")
+    private String commonStatus;
 
     /**
      * 获取执业年限(根据执业开始日期自动计算)
@@ -280,5 +281,20 @@ public class LawyerConsultationOrderVO implements Serializable {
     @TableField("pay_type")
     private  String payType;
 
+    @ApiModelProperty(value = "分钟收费-钱数")
+    private Integer chargeMinute;
+
+    @ApiModelProperty(value = "分钟收费-分钟数")
+    private Integer minuteNum;
+
+    @ApiModelProperty(value = "次数收费-钱数")
+    private Integer chargeTime;
+
+    @ApiModelProperty(value = "次数收费-次数")
+    private Integer timeNum;
+
+    @ApiModelProperty(value = "订单金额字符串(拼接后的字段)")
+    private String orderPriceStr;
+
 }
 

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

@@ -0,0 +1,75 @@
+package shop.alien.entity.store.vo;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+
+/**
+ * 律师仪表板数据VO
+ * 包含律师个人信息、订单统计和收益统计
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Data
+@ApiModel(value = "LawyerDashboardVO对象", description = "律师仪表板数据VO")
+public class LawyerDashboardVO implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    // ========== 律师基本信息 ==========
+    @ApiModelProperty(value = "律师ID")
+    private Integer lawyerId;
+
+    @ApiModelProperty(value = "律师姓名")
+    private String name;
+
+    @ApiModelProperty(value = "律师头像")
+    private String avatar;
+
+    @ApiModelProperty(value = "服务领域(多个用逗号分隔)")
+    private String serviceFields;
+
+    // ========== 性能指标 ==========
+    @ApiModelProperty(value = "本月订单量")
+    private Integer monthOrderCount;
+
+    @ApiModelProperty(value = "总订单量")
+    private Integer totalOrderCount;
+
+    @ApiModelProperty(value = "本月收益(元)")
+    private BigDecimal monthRevenue;
+
+    @ApiModelProperty(value = "总收益(元)")
+    private BigDecimal totalRevenue;
+
+    // ========== 订单信息 ==========
+    @ApiModelProperty(value = "进行中订单统计")
+    private OrderStatusInfo inProgressOrder;
+
+    @ApiModelProperty(value = "待接单订单统计")
+    private OrderStatusInfo pendingAcceptOrder;
+
+    @ApiModelProperty(value = "已退款订单统计")
+    private OrderStatusInfo refundedOrder;
+
+    /**
+     * 订单状态信息内部类
+     */
+    @Data
+    @ApiModel(value = "OrderStatusInfo对象", description = "订单状态信息")
+    public static class OrderStatusInfo implements Serializable {
+
+        private static final long serialVersionUID = 1L;
+
+        @ApiModelProperty(value = "订单总数")
+        private Integer orderCount;
+
+        @ApiModelProperty(value = "金额合计(元)")
+        private BigDecimal totalAmount;
+    }
+}
+

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

@@ -47,7 +47,7 @@ public class LawyerListVO implements Serializable {
     @ApiModelProperty(value = "总订单金额(单位:元)")
     private String totalOrderAmountYuan;
 
-    @ExcelProperty(value = "平台信息服务费(元)", index = 5)
+    @ExcelIgnore
     @ApiModelProperty(value = "平台信息服务费(单位:元)")
     private String platformServiceFeeYuan;
 

+ 59 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/LawyerOrderStatisticsVO.java

@@ -0,0 +1,59 @@
+package shop.alien.entity.store.vo;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+
+/**
+ * 律师订单统计数据VO
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Data
+@ApiModel(value = "LawyerOrderStatisticsVO对象", description = "律师订单统计数据VO")
+public class LawyerOrderStatisticsVO implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "本月订单量")
+    private Integer monthOrderCount;
+
+    @ApiModelProperty(value = "本月收益(元)")
+    private BigDecimal monthRevenue;
+
+    @ApiModelProperty(value = "总订单量")
+    private Integer totalOrderCount;
+
+    @ApiModelProperty(value = "总收益(元)")
+    private BigDecimal totalRevenue;
+
+    @ApiModelProperty(value = "进行中订单统计")
+    private OrderStatusStatistics inProgressOrder;
+
+    @ApiModelProperty(value = "待接单订单统计")
+    private OrderStatusStatistics pendingAcceptOrder;
+
+    @ApiModelProperty(value = "已退款订单统计")
+    private OrderStatusStatistics refundedOrder;
+
+    /**
+     * 订单状态统计内部类
+     */
+    @Data
+    @ApiModel(value = "OrderStatusStatistics对象", description = "订单状态统计数据")
+    public static class OrderStatusStatistics implements Serializable {
+
+        private static final long serialVersionUID = 1L;
+
+        @ApiModelProperty(value = "订单总数")
+        private Integer orderCount;
+
+        @ApiModelProperty(value = "金额合计(元)")
+        private BigDecimal totalAmount;
+    }
+}
+

+ 14 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/LawyerUserVo.java

@@ -318,7 +318,21 @@ public class LawyerUserVo implements Serializable {
     @ApiModelProperty(value = "律所地址")
     private String addressNew;
 
+    @ApiModelProperty(value = "分钟收费-钱数")
+    @TableField("charge_minute")
+    private Integer chargeMinute;
 
+    @ApiModelProperty(value = "分钟收费-分钟数")
+    @TableField("minute_num")
+    private Integer minuteNum;
+
+    @ApiModelProperty(value = "次数收费-钱数")
+    @TableField("charge_time")
+    private Integer chargeTime;
+
+    @ApiModelProperty(value = "次数收费-次数")
+    @TableField("time_num")
+    private Integer timeNum;
 
 }
 

+ 189 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/LawyerViolationDetailVO.java

@@ -0,0 +1,189 @@
+package shop.alien.entity.store.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 律师举报详情VO(包含举报信息、订单信息、律师信息)
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Data
+@ApiModel(value = "LawyerViolationDetailVO对象", description = "律师举报详情VO(包含举报信息、订单信息、律师信息)")
+public class LawyerViolationDetailVO implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    // ========== 举报信息 ==========
+    @ApiModelProperty(value = "举报ID")
+    private Integer violationId;
+
+    @ApiModelProperty(value = "订单号")
+    private String orderNumber;
+
+    @ApiModelProperty(value = "举报原因")
+    private String violationReason;
+
+    @ApiModelProperty(value = "其他原因具体内容")
+    private String otherReasonContent;
+
+    @ApiModelProperty(value = "举报凭证图片")
+    private String reportEvidenceImg;
+
+    @ApiModelProperty(value = "处理状态(0:未处理,1:违规,2:未违规)")
+    private String processingStatus;
+
+    @ApiModelProperty(value = "处理时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date processingTime;
+
+    @ApiModelProperty(value = "举报结果")
+    private String reportResult;
+
+    @ApiModelProperty(value = "举报创建时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date violationCreatedTime;
+
+    @ApiModelProperty(value = "举报用户ID")
+    private String reportingUserId;
+
+    @ApiModelProperty(value = "举报用户姓名")
+    private String reportingUserName;
+
+    @ApiModelProperty(value = "被举报用户ID")
+    private String reportedUserId;
+
+    @ApiModelProperty(value = "举报用户类型(1:商户,2:用户,3:律师)")
+    private String reportingUserType;
+
+    // ========== 订单信息 ==========
+    @ApiModelProperty(value = "订单ID")
+    private Integer orderId;
+
+    @ApiModelProperty(value = "客户端用户ID")
+    private Integer clientUserId;
+
+    @ApiModelProperty(value = "问题描述")
+    private String problemDescription;
+
+    @ApiModelProperty(value = "订单金额,单位分")
+    private Integer orderAmount;
+
+    @ApiModelProperty(value = "咨询费用,单位分")
+    private Integer consultationFee;
+
+    @ApiModelProperty(value = "咨询开始时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date startTime;
+
+    @ApiModelProperty(value = "咨询结束时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date endTime;
+
+    @ApiModelProperty(value = "订单状态, 0:待支付,1.待接单 2:进行中, 3:已完成, 4:已取消,5.已退款")
+    private Integer orderStatus;
+
+    @ApiModelProperty(value = "支付状态, 0:未支付, 1:已支付")
+    private Integer paymentStatus;
+
+    @ApiModelProperty(value = "下单时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date orderTime;
+
+    @ApiModelProperty(value = "支付时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date paymentTime;
+
+    @ApiModelProperty(value = "用户评分, 1-5星")
+    private Integer rating;
+
+    @ApiModelProperty(value = "用户评价内容")
+    private String comment;
+
+    // ========== 律师信息 ==========
+    @ApiModelProperty(value = "律师用户ID")
+    private Integer lawyerUserId;
+
+    @ApiModelProperty(value = "律师姓名")
+    private String lawyerName;
+
+    @ApiModelProperty(value = "律师手机号")
+    private String lawyerPhone;
+
+    @ApiModelProperty(value = "律师邮箱")
+    private String lawyerEmail;
+
+    @ApiModelProperty(value = "律师执业证号")
+    private String lawyerCertificateNo;
+
+    @ApiModelProperty(value = "所属律师事务所")
+    private String lawFirm;
+
+    @ApiModelProperty(value = "执业年限")
+    private Integer practiceYears;
+
+    @ApiModelProperty(value = "专业领域,多个用逗号分隔")
+    private String specialtyFields;
+
+    @ApiModelProperty(value = "资质认证状态, 0:未认证, 1:认证中, 2:已认证, 3:认证失败")
+    private Integer certificationStatus;
+
+    @ApiModelProperty(value = "服务评分, 0-5分")
+    private Double serviceScore;
+
+    @ApiModelProperty(value = "服务次数")
+    private Integer serviceCount;
+
+    @ApiModelProperty(value = "咨询收费标准(单位分/小时)")
+    private Integer lawyerConsultationFee;
+
+    @ApiModelProperty(value = "所属省份")
+    private String province;
+
+    @ApiModelProperty(value = "所属城市")
+    private String city;
+
+    @ApiModelProperty(value = "所属区县")
+    private String district;
+
+    @ApiModelProperty(value = "详细地址")
+    private String address;
+
+    @ApiModelProperty(value = "律师头像")
+    private String headImg;
+
+    @ApiModelProperty(value = "律师昵称")
+    private String nickName;
+
+    @ApiModelProperty(value = "个人简介(详细)")
+    private String personalIntroduction;
+
+    @ApiModelProperty(value = "律所名称")
+    private String firmName;
+
+    @ApiModelProperty(value = "支付方式 1支付宝 2微信")
+    private String payType;
+
+    @ApiModelProperty(value = "订单金额字符串(拼接后的字段)")
+    private String orderPriceStr;
+
+    @ApiModelProperty(value = "分钟收费-钱数")
+    private Integer chargeMinute;
+
+    @ApiModelProperty(value = "分钟收费-分钟数")
+    private Integer minuteNum;
+
+    @ApiModelProperty(value = "次数收费-钱数")
+    private Integer chargeTime;
+
+    @ApiModelProperty(value = "次数收费-次数")
+    private Integer timeNum;
+
+}
+

+ 16 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/LifeUserOrderCommentVo.java

@@ -7,6 +7,10 @@ import shop.alien.entity.store.LifeUserOrder;
 import java.util.Date;
 import java.util.List;
 
+/**
+ * 订单 + 评价视图 VO,承载用户的订单信息以及店铺/律师的评价数据。
+ * 在店铺评价场景中追加评分、图片等字段(src=1),在律师评价场景中同样复用(src=2)。
+ */
 @Data
 @JsonInclude
 public class LifeUserOrderCommentVo extends LifeUserOrder {
@@ -25,9 +29,21 @@ public class LifeUserOrderCommentVo extends LifeUserOrder {
 
     private String storeName;
 
+    /** 评价内容(店铺评论 comment_content / 律师评价 review_content) */
+    private String commentContent;
+
     private String score;
 
     private Date commentDate;
 
     private List<String> imgUrls;
+
+    /**
+     * 评论/评价图片或图片ID集合(兼容店铺评价 img_id、律师评价 review_images)
+     */
+    /** 原始图片ID或URL集合(店铺评价为 img_id,律师评价为 review_images JSON) */
+    private String imgId;
+
+    /** 数据来源:1-店铺评价(store_comment),2-律师评价(lawyer_order_review) */
+    private Integer src;
 }

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

@@ -0,0 +1,49 @@
+package shop.alien.entity.store.vo;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 订单列表及统计信息VO
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Data
+@ApiModel(value = "LifeUserOrderListWithStatisticsVO对象", description = "订单列表及统计信息VO")
+public class LifeUserOrderListWithStatisticsVO implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "订单列表")
+    private IPage<LifeUserOrderVo> orderList;
+
+    @ApiModelProperty(value = "待支付订单数量")
+    private Long waitPayCount;
+
+    @ApiModelProperty(value = "已支付/待使用订单数量")
+    private Long waitUseCount;
+
+    @ApiModelProperty(value = "已核销订单数量")
+    private Long usedCount;
+
+    @ApiModelProperty(value = "已过期订单数量")
+    private Long expireCount;
+
+    @ApiModelProperty(value = "已取消订单数量")
+    private Long cancelCount;
+
+    @ApiModelProperty(value = "已退款订单数量")
+    private Long refundCount;
+
+    @ApiModelProperty(value = "退款失败订单数量")
+    private Long refundFailedCount;
+
+    @ApiModelProperty(value = "已完成订单数量")
+    private Long completeCount;
+}
+

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

@@ -45,3 +45,4 @@ public class OrderRevenueVO implements Serializable {
 }
 
 
+

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

@@ -3,6 +3,7 @@ package shop.alien.entity.store.vo;
 import io.swagger.annotations.ApiModel;
 import io.swagger.annotations.ApiModelProperty;
 import lombok.Data;
+import shop.alien.entity.store.LifeUser;
 
 import java.util.List;
 
@@ -30,5 +31,8 @@ public class OrderReviewDetailVo {
 
     @ApiModelProperty(value = "当前用户是否已点赞,0:未点赞,1:已点赞")
     private Integer isLiked;
+
+    @ApiModelProperty(value = "用户详细信息")
+    private LifeUser userDetail;
 }
 

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

@@ -91,5 +91,8 @@ public class OrderReviewVo {
 
     @ApiModelProperty(value = "订单申诉状态")
     private Integer isAppealed;
+
+    @ApiModelProperty(value = "申诉id (如果该评价被申诉 申诉id会有值)")
+    private String appealId;
 }
 

+ 31 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/PendingReviewVo.java

@@ -49,5 +49,36 @@ public class PendingReviewVo {
     @ApiModelProperty(value = "订单完成时间")
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
     private Date completedTime;
+
+    @ApiModelProperty(value = "问题描述")
+    private String problemDescription;
+
+    @ApiModelProperty(value = "订单金额,单位分")
+    private Integer orderAmount;
+
+    @ApiModelProperty(value = "咨询费用,单位分")
+    private Integer consultationFee;
+
+    @ApiModelProperty(value = "咨询开始时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date startTime;
+
+    @ApiModelProperty(value = "咨询结束时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date endTime;
+
+    @ApiModelProperty(value = "下单时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date orderTime;
+
+    @ApiModelProperty(value = "支付时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date paymentTime;
+
+    @ApiModelProperty(value = "订单状态, 0:待支付,1.待接单 2:进行中, 3:已完成, 4:已取消,5.已退款")
+    private Integer orderStatus;
+
+    @ApiModelProperty(value = "支付状态, 0:未支付, 1:已支付")
+    private Integer paymentStatus;
 }
 

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

@@ -39,6 +39,12 @@ public class ReviewCommentVo {
     @ApiModelProperty(value = "接收用户名称")
     private String receiveUserName;
 
+    @ApiModelProperty(value = "发送用户类型:1-普通用户,2-律师")
+    private Integer sendUserType;
+
+    @ApiModelProperty(value = "接收用户类型:1-普通用户,2-律师")
+    private Integer receiveUserType;
+
     @ApiModelProperty(value = "评论内容")
     private String commentContent;
 

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

@@ -0,0 +1,24 @@
+package shop.alien.entity.store.vo;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import shop.alien.entity.store.StoreBanner;
+
+/**
+ * Banner展示VO
+ *
+ * 面向前端展示的 Banner 数据对象。
+ */
+@EqualsAndHashCode(callSuper = true)
+@Data
+@JsonInclude
+@ApiModel(value = "StoreBannerVo对象", description = "Banner展示VO")
+public class StoreBannerVo extends StoreBanner {
+
+    @ApiModelProperty(value = "状态描述")
+    private String statusText;
+}
+

+ 117 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/UserOrderVo.java

@@ -0,0 +1,117 @@
+package shop.alien.entity.store.vo;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 用户订单统一视图对象(包含商户订单和律师订单)
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Data
+@JsonInclude
+@ApiModel(value = "UserOrderVo对象", description = "用户订单统一视图对象")
+public class UserOrderVo implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "订单ID")
+    private String orderId;
+
+    @ApiModelProperty(value = "订单编号")
+    private String orderNumber;
+
+    @ApiModelProperty(value = "订单类型:1-商户订单,2-律师订单")
+    private Integer orderType;
+
+    @ApiModelProperty(value = "用户ID")
+    private String userId;
+
+    @ApiModelProperty(value = "商户/律师ID")
+    private String businessId;
+
+    @ApiModelProperty(value = "商户名称/律师名称")
+    private String businessName;
+
+    @ApiModelProperty(value = "订单金额(单位:分,商户订单)/订单金额(单位:分,律师订单)")
+    private String orderAmount;
+
+    @ApiModelProperty(value = "实付金额(商户订单)")
+    private String finalPrice;
+
+    @ApiModelProperty(value = "订单状态")
+    private Integer orderStatus;
+
+    @ApiModelProperty(value = "订单状态文本")
+    private String orderStatusValue;
+
+    @ApiModelProperty(value = "支付状态(律师订单:0-未支付,1-已支付)")
+    private Integer paymentStatus;
+
+    @ApiModelProperty(value = "下单时间")
+    private Date orderTime;
+
+    @ApiModelProperty(value = "支付时间")
+    private Date payTime;
+
+    @ApiModelProperty(value = "订单创建时间")
+    private Date createdTime;
+
+    @ApiModelProperty(value = "商户订单相关:店铺ID")
+    private String storeId;
+
+    @ApiModelProperty(value = "商户订单相关:商品名称")
+    private String couponName;
+
+    @ApiModelProperty(value = "商户订单相关:商品图片URL")
+    private String imgUrl;
+
+    @ApiModelProperty(value = "商户订单相关:是否已评价(0-未评价,1-已评价)")
+    private Integer orderAppraise;
+
+    @ApiModelProperty(value = "律师订单相关:律师用户ID")
+    private Integer lawyerUserId;
+
+    @ApiModelProperty(value = "律师订单相关:问题描述")
+    private String problemDescription;
+
+    @ApiModelProperty(value = "律师订单相关:咨询费用(单位:分)")
+    private Integer consultationFee;
+
+    @ApiModelProperty(value = "律师订单相关:咨询开始时间")
+    private Date startTime;
+
+    @ApiModelProperty(value = "律师订单相关:咨询结束时间")
+    private Date endTime;
+
+    @ApiModelProperty(value = "律师订单相关:律师头像")
+    private String lawyerHeadImg;
+
+    @ApiModelProperty(value = "律师订单相关:是否已评价")
+    private Boolean hasReview;
+
+    @ApiModelProperty(value = "商户订单评分(总体评分)")
+    private Double score;
+
+    @ApiModelProperty(value = "商户订单其他评分(口味/环境/服务)")
+    private String otherScore;
+
+    @ApiModelProperty(value = "律师订单总体评分(1-5星,支持0.5星)")
+    private Double overallRating;
+
+    @ApiModelProperty(value = "律师订单服务态度评分(1-5星,支持0.5星)")
+    private Double serviceAttitudeRating;
+
+    @ApiModelProperty(value = "律师订单响应时间评分(1-5星,支持0.5星)")
+    private Double responseTimeRating;
+
+    @ApiModelProperty(value = "律师订单专业能力评分(1-5星,支持0.5星)")
+    private Double professionalAbilityRating;
+
+}

+ 190 - 10
alien-entity/src/main/java/shop/alien/mapper/LawyerConsultationOrderMapper.java

@@ -4,16 +4,12 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.core.toolkit.Constants;
-import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import org.apache.ibatis.annotations.*;
 import org.apache.ibatis.annotations.Param;
 import shop.alien.entity.store.LawyerConsultationOrder;
 import shop.alien.entity.store.vo.LawyerConsultationOrderVO;
-
-import java.util.List;
+import shop.alien.entity.store.vo.LawyerViolationDetailVO;
 import shop.alien.entity.store.dto.LawyerConsultationOrderDto;
-import shop.alien.entity.store.vo.LifeCouponVo;
-import shop.alien.entity.store.vo.LifeUserExpertOrderVo;
 
 import java.util.List;
 import java.util.Map;
@@ -99,7 +95,11 @@ public interface LawyerConsultationOrderMapper extends BaseMapper<LawyerConsulta
             "accept_orders_time ," +
             "reason_order_refusal ," +
             "accept_orders_status ," +
-            "pay_type " +
+            "pay_type ," +
+            "charge_minute ,"+
+            "minute_num ,"+
+            "charge_time ,"+
+            "time_num"+
             ")"+
             "VALUES" +
             " (" +
@@ -132,7 +132,11 @@ public interface LawyerConsultationOrderMapper extends BaseMapper<LawyerConsulta
             "#{acceptOrdersTime} ," +
             "#{reasonOrderRefusal} ," +
             "#{acceptOrdersStatus} ," +
-            "#{payType} " +
+            "#{payType} ," +
+            "#{chargeMinute} ," +
+            "#{minuteNum} ," +
+            "#{chargeTime} ," +
+            "#{timeNum} " +
             ")")
             int insertOrder(LawyerConsultationOrderDto order);
 
@@ -316,6 +320,10 @@ public interface LawyerConsultationOrderMapper extends BaseMapper<LawyerConsulta
             "        lco.apply_refund_reason,\n" +
             "        lco.apply_refund_process_time,\n" +
             "        lco.apply_refund_time,\n" +
+            "        lco.charge_minute,\n" +
+            "        lco.minute_num,\n" +
+            "        lco.charge_time,\n" +
+            "        lco.time_num,\n" +
             "        CASE\n" +
             "        WHEN EXISTS (SELECT 1 FROM lawyer_order_review lor WHERE lor.order_id = lco.id) THEN '1'\n" +
             "        ELSE '2'\n" +
@@ -354,7 +362,8 @@ public interface LawyerConsultationOrderMapper extends BaseMapper<LawyerConsulta
             "        luv.processing_time,\n" +
             "        lur.user_image,\n" +
             "        cas.status as commentStatus,\n" +
-            "        luv.report_result\n" +
+            "        luv.report_result ,\n" +
+            "        lco.pay_type\n" +
             "        FROM lawyer_consultation_order lco\n" +
             "        LEFT JOIN lawyer_user lu ON lco.lawyer_user_id = lu.id AND lu.delete_flag = 0\n" +
             "        LEFT JOIN law_firm lf on lf.id = lu.firm_id\n" +
@@ -439,9 +448,23 @@ public interface LawyerConsultationOrderMapper extends BaseMapper<LawyerConsulta
             "        lea.expertise_area_info,\n" +
             "        lur.user_name client_user_name,\n" +
             "        lur.user_phone client_user_phone,\n" +
-            "        lco.order_amount - lco.consultation_fee as lawyerEarnings\n" +
+            "        lco.order_amount - lco.consultation_fee as lawyerEarnings,\n" +
+            "        lco.charge_minute,\n" +
+            "        lco.minute_num,\n" +
+            "        lco.charge_time,\n" +
+            "        lco.time_num\n" +
             "        FROM lawyer_consultation_order lco\n" +
-            "        LEFT JOIN lawyer_user_violation luv ON luv.order_number = lco.order_number and luv.delete_flag = 0\n" +
+            "        LEFT JOIN (\n" +
+            "            SELECT luv1.*\n" +
+            "            FROM lawyer_user_violation luv1\n" +
+            "            INNER JOIN (\n" +
+            "                SELECT order_number, MAX(id) as max_id\n" +
+            "                FROM lawyer_user_violation\n" +
+            "                WHERE delete_flag = 0\n" +
+            "                GROUP BY order_number\n" +
+            "            ) luv2 ON luv1.order_number = luv2.order_number AND luv1.id = luv2.max_id\n" +
+            "            WHERE luv1.delete_flag = 0\n" +
+            "        ) luv ON luv.order_number = lco.order_number\n" +
             "        LEFT JOIN lawyer_user lu ON lco.lawyer_user_id = lu.id AND lu.delete_flag = 0\n" +
             "        LEFT JOIN law_firm lf on lf.id = lu.firm_id\n" +
             "        left join lawyer_expertise_area lea on lea.id = lu.lawyer_expertise_area_id and lea.delete_flag = 0 " +
@@ -514,5 +537,162 @@ public interface LawyerConsultationOrderMapper extends BaseMapper<LawyerConsulta
             @Param("clientUserId") Integer clientUserId,
             @Param("lawyerUserId") Integer lawyerUserId);
 
+    /**
+     * 统计本月订单量和本月收益
+     *
+     * @param lawyerUserId 律师用户ID
+     * @param startTime    本月开始时间
+     * @param endTime      本月结束时间
+     * @return 统计数据Map,包含monthOrderCount和monthRevenue(单位:分)
+     */
+    @Select("SELECT " +
+            "COUNT(*) as monthOrderCount, " +
+            "COALESCE(SUM(order_amount), 0) as monthRevenue " +
+            "FROM lawyer_consultation_order " +
+            "WHERE lawyer_user_id = #{lawyerUserId} " +
+            "AND delete_flag = 0 " +
+            "AND order_status = 3 " +
+            "AND created_time >= #{startTime} " +
+            "AND created_time < #{endTime}")
+    Map<String, Object> getMonthStatistics(
+            @Param("lawyerUserId") Integer lawyerUserId,
+            @Param("startTime") String startTime,
+            @Param("endTime") String endTime);
+
+    /**
+     * 统计总订单量和总收益
+     *
+     * @param lawyerUserId 律师用户ID
+     * @return 统计数据Map,包含totalOrderCount和totalRevenue(单位:分)
+     */
+    @Select("SELECT " +
+            "COUNT(*) as totalOrderCount, " +
+            "COALESCE(SUM(order_amount), 0) as totalRevenue " +
+            "FROM lawyer_consultation_order " +
+            "WHERE lawyer_user_id = #{lawyerUserId} " +
+            "AND order_status = 3 " +
+            "AND delete_flag = 0")
+    Map<String, Object> getTotalStatistics(@Param("lawyerUserId") Integer lawyerUserId);
+
+    /**
+     * 统计进行中订单(订单状态=2)
+     *
+     * @param lawyerUserId 律师用户ID
+     * @return 统计数据Map,包含orderCount和totalAmount(单位:分)
+     */
+    @Select("SELECT " +
+            "COUNT(*) as orderCount, " +
+            "COALESCE(SUM(order_amount), 0) as totalAmount " +
+            "FROM lawyer_consultation_order " +
+            "WHERE lawyer_user_id = #{lawyerUserId} " +
+            "AND delete_flag = 0 " +
+            "AND order_status = 2")
+    Map<String, Object> getInProgressStatistics(@Param("lawyerUserId") Integer lawyerUserId);
+
+    /**
+     * 统计待接单订单(订单状态=1)
+     *
+     * @param lawyerUserId 律师用户ID
+     * @return 统计数据Map,包含orderCount和totalAmount(单位:分)
+     */
+    @Select("SELECT " +
+            "COUNT(*) as orderCount, " +
+            "COALESCE(SUM(order_amount), 0) as totalAmount " +
+            "FROM lawyer_consultation_order " +
+            "WHERE lawyer_user_id = #{lawyerUserId} " +
+            "AND delete_flag = 0 " +
+            "AND order_status = 1")
+    Map<String, Object> getPendingAcceptStatistics(@Param("lawyerUserId") Integer lawyerUserId);
+
+    /**
+     * 统计已退款订单(申请退款状态=3)
+     *
+     * @param lawyerUserId 律师用户ID
+     * @return 统计数据Map,包含orderCount和totalAmount(单位:分)
+     */
+    @Select("SELECT " +
+            "COUNT(*) as orderCount, " +
+            "COALESCE(SUM(order_amount), 0) as totalAmount " +
+            "FROM lawyer_consultation_order " +
+            "WHERE lawyer_user_id = #{lawyerUserId} " +
+            "AND delete_flag = 0 " +
+            "AND order_status = 5")
+    Map<String, Object> getRefundedStatistics(@Param("lawyerUserId") Integer lawyerUserId);
+
+    /**
+     * 根据订单ID查询举报详情(包含举报信息、订单信息、律师信息)
+     *
+     * @param orderId 订单ID
+     * @return 举报详情VO
+     */
+    @Select("SELECT " +
+            "luv.id AS violationId, " +
+            "luv.order_number, " +
+            "luv.violation_reason, " +
+            "luv.other_reason_content, " +
+            "luv.report_evidence_img, " +
+            "luv.processing_status, " +
+            "luv.processing_time, " +
+            "luv.report_result, " +
+            "luv.created_time AS violationCreatedTime, " +
+            "luv.reporting_user_id, " +
+            "luv.reported_user_id, " +
+            "lco.id AS orderId, " +
+            "lco.client_user_id, " +
+            "lco.problem_description, " +
+            "lco.order_amount, " +
+            "lco.consultation_fee, " +
+            "lco.start_time, " +
+            "lco.end_time, " +
+            "lco.order_status, " +
+            "lco.payment_status, " +
+            "lco.order_time, " +
+            "lco.payment_time, " +
+            "lco.rating, " +
+            "lco.comment, " +
+            "lco.lawyer_user_id, " +
+            "lco.pay_type, " +
+            "lco.charge_minute, " +
+            "lco.minute_num, " +
+            "lco.charge_time, " +
+            "lco.time_num, " +
+            "lu.name AS lawyerName, " +
+            "lu.phone AS lawyerPhone, " +
+            "lu.email AS lawyerEmail, " +
+            "lu.lawyer_certificate_no, " +
+            "lu.law_firm, " +
+            "lu.practice_years, " +
+            "lu.specialty_fields, " +
+            "lu.certification_status, " +
+            "lu.service_score, " +
+            "lu.service_count, " +
+            "lu.consultation_fee AS lawyerConsultationFee, " +
+            "lu.province, " +
+            "lu.city, " +
+            "lu.district, " +
+            "lu.address, " +
+            "lu.head_img, " +
+            "lu.nick_name, " +
+            "lu.personal_introduction, " +
+            "luv.reporting_user_type, " +
+            "lf.firm_name " +
+            "FROM lawyer_consultation_order lco " +
+            "LEFT JOIN (" +
+            "    SELECT luv1.* " +
+            "    FROM lawyer_user_violation luv1 " +
+            "    INNER JOIN (" +
+            "        SELECT order_number, MAX(id) as max_id " +
+            "        FROM lawyer_user_violation " +
+            "        WHERE delete_flag = 0 " +
+            "        GROUP BY order_number" +
+            "    ) luv2 ON luv1.order_number = luv2.order_number AND luv1.id = luv2.max_id " +
+            "    WHERE luv1.delete_flag = 0" +
+            ") luv ON luv.order_number = lco.order_number " +
+            "LEFT JOIN lawyer_user lu ON lco.lawyer_user_id = lu.id AND lu.delete_flag = 0 " +
+            "LEFT JOIN law_firm lf ON lf.id = lu.firm_id " +
+            "WHERE lco.id = #{orderId} AND lco.delete_flag = 0 " +
+            "LIMIT 1")
+    LawyerViolationDetailVO getViolationDetailByOrderId(@Param("orderId") Integer orderId);
+
 }
 

+ 5 - 0
alien-entity/src/main/java/shop/alien/mapper/LawyerUserMapper.java

@@ -102,6 +102,11 @@ Integer updateLawyerUser(LawyerUser user);
            "user.personal_introduction, " +
            "user.order_receiving_status, " +
            "user.practice_start_date, " +
+           "user.charge_minute, " +
+           "user.minute_num, " +
+           "user.charge_time, " +
+           "user.time_num, " +
+           "user.certificate_image, " +
            "firm.firm_name AS firmName, " +
            "firmTwo.payment_account AS paymentNum, " +
            "firmTwo.address AS address, " +

+ 73 - 0
alien-entity/src/main/java/shop/alien/mapper/LifeUserOrderMapper.java

@@ -130,6 +130,79 @@ public interface LifeUserOrderMapper extends BaseMapper<LifeUserOrder> {
     IPage<LifeUserOrderVo> queryUserOrderList(IPage<LifeUserOrderVo> brandedPage,@Param(Constants.WRAPPER) QueryWrapper<LifeUserOrderVo> lifeUserOrderQueryWrapper);
 
     /**
+     * 统计各状态订单数量
+     * <p>
+     * 注意:此查询需要与queryUserOrderList使用相同的查询条件,但不需要分页和分组
+     * </p>
+     *
+     * @param queryWrapper 查询条件包装器(需要移除分页和分组条件)
+     * @return 各状态订单数量统计,key为orderStatus(订单状态),value为orderCount(订单数量)
+     */
+    @Select("WITH total_coupon AS (\n" +
+            "    -- 团购\n" +
+            "    SELECT id coupon_id, 2 coupon_type, lgbm.group_name coupon_name, " +
+            "           SUBSTRING_INDEX(image_id, ',', 1) AS image_id, " +
+            "           effective_date_type, effective_date_value \n" +
+            "    FROM life_group_buy_main lgbm \n" +
+            "    UNION ALL\n" +
+            "    -- 代金券\n" +
+            "    SELECT id coupon_id, 1 coupon_type, lc.name coupon_name, " +
+            "           SUBSTRING_INDEX(image_path, ',', 1) AS image_id, " +
+            "           0 effective_date_type, expiration_date effective_date_value \n" +
+            "    FROM life_coupon lc \n" +
+            ")\n" +
+            "SELECT " +
+            "    luo.status AS orderStatus, " +
+            "    COUNT(DISTINCT luo.id) AS orderCount " +
+            "FROM life_user_order luo " +
+            "LEFT JOIN store_info si ON si.id = luo.store_id " +
+            "LEFT JOIN life_user lu ON lu.id = luo.user_id AND lu.delete_flag = 0 " +
+            "LEFT JOIN order_coupon_middle ocm ON ocm.order_id = luo.id " +
+            "LEFT JOIN total_coupon tc ON tc.coupon_id = ocm.coupon_id AND tc.coupon_type = luo.coupon_type " +
+            "LEFT JOIN store_img simg ON simg.id = tc.image_id AND simg.delete_flag = 0 " +
+            "LEFT JOIN store_comment sc ON sc.business_id = luo.id AND sc.delete_flag = 0 AND sc.business_type = 5 " +
+            "LEFT JOIN life_discount_coupon_user ldcu ON ldcu.id = luo.quan_id " +
+            "LEFT JOIN life_discount_coupon ldc ON ldc.id = ldcu.coupon_id " +
+            "${ew.customSqlSegment} " +
+            "GROUP BY luo.status")
+    List<Map<String, Object>> countOrdersByStatus(@Param(Constants.WRAPPER) QueryWrapper<LifeUserOrderVo> queryWrapper);
+
+    /**
+     * 统计全部订单数量
+     * <p>
+     * 统计符合条件的全部订单数量,不区分状态
+     * </p>
+     *
+     * @param queryWrapper 查询条件包装器
+     * @return 全部订单数量
+     */
+    @Select("WITH total_coupon AS (\n" +
+            "    -- 团购\n" +
+            "    SELECT id coupon_id, 2 coupon_type, lgbm.group_name coupon_name, " +
+            "           SUBSTRING_INDEX(image_id, ',', 1) AS image_id, " +
+            "           effective_date_type, effective_date_value \n" +
+            "    FROM life_group_buy_main lgbm \n" +
+            "    UNION ALL\n" +
+            "    -- 代金券\n" +
+            "    SELECT id coupon_id, 1 coupon_type, lc.name coupon_name, " +
+            "           SUBSTRING_INDEX(image_path, ',', 1) AS image_id, " +
+            "           0 effective_date_type, expiration_date effective_date_value \n" +
+            "    FROM life_coupon lc \n" +
+            ")\n" +
+            "SELECT COUNT(DISTINCT luo.id) AS totalOrderCount " +
+            "FROM life_user_order luo " +
+            "LEFT JOIN store_info si ON si.id = luo.store_id " +
+            "LEFT JOIN life_user lu ON lu.id = luo.user_id AND lu.delete_flag = 0 " +
+            "LEFT JOIN order_coupon_middle ocm ON ocm.order_id = luo.id " +
+            "LEFT JOIN total_coupon tc ON tc.coupon_id = ocm.coupon_id AND tc.coupon_type = luo.coupon_type " +
+            "LEFT JOIN store_img simg ON simg.id = tc.image_id AND simg.delete_flag = 0 " +
+            "LEFT JOIN store_comment sc ON sc.business_id = luo.id AND sc.delete_flag = 0 AND sc.business_type = 5 " +
+            "LEFT JOIN life_discount_coupon_user ldcu ON ldcu.id = luo.quan_id " +
+            "LEFT JOIN life_discount_coupon ldc ON ldc.id = ldcu.coupon_id " +
+            "${ew.customSqlSegment}")
+    Long countTotalOrders(@Param(Constants.WRAPPER) QueryWrapper<LifeUserOrderVo> queryWrapper);
+
+    /**
      * 查询过期退款订单
      * @param selectWrapper
      * @return

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

@@ -25,6 +25,7 @@ public interface OrderReviewMapper extends BaseMapper<OrderReview> {
      * @param lawyerUserId 律师用户ID
      * @param userId 评价用户ID
      * @param currentUserId 当前用户ID(用于判断是否已点赞,可为null)
+     * @param filterAppealId 是否只查询申诉ID为空的评价(true:只查询申诉ID为空的评价,false或null:不过滤)
      * @return 分页结果
      */
     IPage<OrderReviewVo> getReviewListWithUser(
@@ -32,7 +33,8 @@ public interface OrderReviewMapper extends BaseMapper<OrderReview> {
             @Param("orderId") Integer orderId,
             @Param("lawyerUserId") Integer lawyerUserId,
             @Param("userId") Integer userId,
-            @Param("currentUserId") Integer currentUserId
+            @Param("currentUserId") Integer currentUserId,
+            @Param("filterAppealId") Boolean filterAppealId
     );
 
     /**

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

@@ -0,0 +1,14 @@
+package shop.alien.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import shop.alien.entity.store.StoreBanner;
+
+/**
+ * Banner表 Mapper 接口
+ *
+ * @author gpt
+ * @since 2025-12-16
+ */
+public interface StoreBannerMapper extends BaseMapper<StoreBanner> {
+}
+

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

@@ -76,13 +76,13 @@ public interface StoreCommentAppealMapper extends BaseMapper<StoreCommentAppeal>
             "LEFT JOIN store_comment sc ON sca.comment_id = sc.id " +
             "LEFT JOIN store_img si ON sca.img_id = si.id " +
             "LEFT JOIN (SELECT sc.id id, si.img_url user_img_url FROM store_comment sc LEFT JOIN store_img si ON sc.img_id = si.id AND sc.delete_flag = 0) sci ON sci.id = sca.comment_id " +
-            "WHERE sca.appeal_status = 0 AND sca.delete_flag = 0")
+            "WHERE sca.appeal_status = 0 and record_id is null AND sca.delete_flag = 0")
     List<Map<String, Object>> getAppealList();
 
 
     @Select("SELECT sca.id, sca.appeal_status, sca.final_result, sca.comment_id, sca.record_id " +
             "FROM store_comment_appeal sca " +
-            "WHERE sca.appeal_status = 0")
+            "WHERE sca.appeal_status = 3")
     List<StoreCommentAppeal> getPendingAppeals();
 
     @Update("UPDATE store_comment_appeal " +

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

@@ -198,4 +198,51 @@ WHERE
             "\tAND s.store_id = #{storeId}\n" +
             "\tLIMIT 1")
     StoreCommentVo getCommentOneInfo(@Param("storeId") int storeId);
+
+    /**
+     * 当前用户的全部评价(店铺订单评价 + 律师订单评价)
+     */
+    @Select("SELECT * FROM ( " +
+            "SELECT " +
+            "luo.id, luo.user_id, luo.store_id, " +
+            "CONVERT(luo.quan_id USING utf8mb4) COLLATE utf8mb4_unicode_ci AS quan_id, " +
+            "CONVERT(luo.quan_code USING utf8mb4) COLLATE utf8mb4_unicode_ci AS quan_code, " +
+            "CONVERT(luo.order_no USING utf8mb4) COLLATE utf8mb4_unicode_ci AS order_no, " +
+            "CONVERT(luo.order_str USING utf8mb4) COLLATE utf8mb4_unicode_ci AS order_str, " +
+            "luo.status, " +
+            "CONVERT(luo.price USING utf8mb4) COLLATE utf8mb4_unicode_ci AS price, " +
+            "CONVERT(luo.final_price USING utf8mb4) COLLATE utf8mb4_unicode_ci AS final_price, " +
+            "luo.buy_time, luo.pay_time, " +
+            "CONVERT(luo.pay_method USING utf8mb4) COLLATE utf8mb4_unicode_ci AS pay_method, " +
+            "luo.used_time, luo.refund_time, luo.coupon_type, luo.cancel_time, " +
+            "CONVERT(luo.cancel_reason USING utf8mb4) COLLATE utf8mb4_unicode_ci AS cancel_reason, " +
+            "luo.finish_time, luo.delete_flag, luo.created_time, luo.created_user_id, luo.updated_time, luo.updated_user_id, luo.send_discount_coupon_flag, luo.expert_order_id, luo.order_appraise, " +
+            "lgbm.image_id AS groupBuyImgId, NULL AS groupBuyImgUrl, " +
+            "CONVERT(lgbm.group_name USING utf8mb4) COLLATE utf8mb4_unicode_ci AS groupBuyName, " +
+            "lgbm.group_type AS groupBuyType, " +
+            "CONVERT(store.business_section USING utf8mb4) COLLATE utf8mb4_unicode_ci AS storeType, " +
+            "CONVERT(store.business_types_name USING utf8mb4) COLLATE utf8mb4_unicode_ci AS businessTypesName, " +
+            "CONVERT(store.store_name USING utf8mb4) COLLATE utf8mb4_unicode_ci AS storeName, " +
+            "CONVERT(sc.comment_content USING utf8mb4) COLLATE utf8mb4_unicode_ci AS commentContent, sc.score, sc.created_time AS commentDate, " +
+            "CONVERT(sc.img_id USING utf8mb4) COLLATE utf8mb4_unicode_ci AS imgId, 1 AS src " +
+            "FROM store_comment sc " +
+            "LEFT JOIN life_user_order luo ON sc.business_id = luo.id " +
+            "LEFT JOIN order_coupon_middle ocm ON luo.id = ocm.order_id " +
+            "LEFT JOIN life_group_buy_main lgbm ON ocm.coupon_id = lgbm.id " +
+            "LEFT JOIN store_info store ON luo.store_id = store.id " +
+            "WHERE sc.business_type = 5 AND sc.delete_flag = 0 AND sc.user_id = #{userId} " +
+            "UNION ALL " +
+            "SELECT " +
+            "CAST(lor.order_id AS CHAR CHARACTER SET utf8mb4) COLLATE utf8mb4_unicode_ci AS id, " +
+            "CAST(lor.user_id AS CHAR CHARACTER SET utf8mb4) COLLATE utf8mb4_unicode_ci AS user_id, " +
+            "NULL AS store_id, NULL AS quan_id, NULL AS quan_code, " +
+            "CONVERT(lor.order_number USING utf8mb4) COLLATE utf8mb4_unicode_ci AS order_no, " +
+            "NULL AS order_str, NULL AS status, NULL AS price, NULL AS final_price, NULL AS buy_time, NULL AS pay_time, NULL AS pay_method, NULL AS used_time, NULL AS refund_time, NULL AS coupon_type, NULL AS cancel_time, NULL AS cancel_reason, NULL AS finish_time, lor.delete_flag, lor.created_time, lor.created_user_id, lor.updated_time, lor.updated_user_id, NULL AS send_discount_coupon_flag, NULL AS expert_order_id, NULL AS order_appraise, " +
+            "NULL AS groupBuyImgId, NULL AS groupBuyImgUrl, NULL AS groupBuyName, NULL AS groupBuyType, NULL AS storeType, NULL AS businessTypesName, NULL AS storeName, " +
+            "lor.review_content AS commentContent, lor.overall_rating AS score, lor.created_time AS commentDate, " +
+            "CONVERT(lor.review_images USING utf8mb4) COLLATE utf8mb4_unicode_ci AS imgId, 2 AS src " +
+            "FROM lawyer_order_review lor " +
+            "WHERE lor.delete_flag = 0 AND lor.user_id = #{userId} " +
+            ") t ORDER BY t.commentDate DESC")
+    IPage<LifeUserOrderCommentVo> getUserAllCommentsPage(IPage<LifeUserOrderCommentVo> page, @Param("userId") Integer userId);
 }

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

@@ -70,30 +70,32 @@
                 <!-- 指定律所ID时,从律所表开始,确保即使没有订单也返回 -->
                 SELECT
                     COUNT(DISTINCT lco.id) as order_count,
-                    COALESCE(SUM(lco.lawyer_earnings), 0) as total_amount,
-                    COALESCE(SUM(lco.consultation_fee), 0) as total_platform_service_fee
-                FROM law_firm lf
-                LEFT JOIN lawyer_consultation_order lco ON lco.place_id = lf.id
-                    AND lco.delete_flag = 0
-                    AND lco.order_status = 3
+                    COALESCE(SUM(lco.order_amount), 0) as total_amount
+                FROM lawyer_consultation_order lco
+                INNER JOIN lawyer_user lu ON lco.lawyer_user_id = lu.id
+                AND lu.delete_flag = 0
+                LEFT JOIN law_firm lf ON lf.id = lu.firm_id
+                    AND lf.delete_flag = 0
                     <if test="startDate != null">
                         AND lco.order_time &gt;= CONCAT(#{startDate}, ' 00:00:00')
                     </if>
                     <if test="endDate != null">
                         AND lco.order_time &lt;= CONCAT(#{endDate}, ' 23:59:59')
                     </if>
-                WHERE lf.delete_flag = 0
+                WHERE lco.delete_flag = 0
+                    AND lco.order_status = 3
                     AND lf.id = #{firmId}
             </when>
             <otherwise>
                 <!-- 未指定律所ID时,从订单表开始 -->
                 SELECT
                     COUNT(DISTINCT lco.id) as order_count,
-                    COALESCE(SUM(lco.lawyer_earnings), 0) as total_amount,
-                    COALESCE(SUM(lco.consultation_fee), 0) as total_platform_service_fee
+                    COALESCE(SUM(lco.order_amount), 0) as total_amount
                 FROM lawyer_consultation_order lco
-                LEFT JOIN law_firm lf ON lco.place_id = lf.id
-                    AND lf.delete_flag = 0
+                INNER JOIN lawyer_user lu ON lco.lawyer_user_id = lu.id
+                AND lu.delete_flag = 0
+                LEFT JOIN law_firm lf ON lf.id = lu.firm_id
+                AND lf.delete_flag = 0
                 WHERE
                 <include refid="Order_Common_Where"/>
                 <if test="firmName != null and firmName != ''">
@@ -130,10 +132,10 @@
             lf.id as firm_id,
             lf.firm_name,
             COUNT(DISTINCT lco.id) as order_count,
-            CAST(ROUND(COALESCE(SUM(lco.lawyer_earnings), 0) / 100.0, 2) AS CHAR) as total_amount_yuan,
-            CAST(ROUND(COALESCE(SUM(lco.consultation_fee), 0) / 100.0, 2) AS CHAR) as commission_fee_yuan
+            CAST(ROUND(COALESCE(SUM(lco.order_amount), 0) / 100.0, 2) AS CHAR) as total_amount_yuan
         FROM law_firm lf
-        LEFT JOIN lawyer_consultation_order lco ON lco.place_id = lf.id
+        LEFT JOIN lawyer_user lu ON lf.id = lu.firm_id
+        LEFT JOIN lawyer_consultation_order lco ON lco.lawyer_user_id = lu.id
             AND lco.delete_flag = 0
             AND lco.order_status = 3
             <if test="startDate != null">
@@ -147,7 +149,7 @@
             AND lf.firm_name LIKE CONCAT('%', #{firmName}, '%')
         </if>
         GROUP BY lf.id, lf.firm_name
-        ORDER BY COALESCE(SUM(lco.lawyer_earnings), 0) DESC
+        ORDER BY COALESCE(SUM(lco.order_amount), 0) DESC
     </select>
 
     <!-- 查询律所下所有律师的对账统计信息(按律师分组) -->
@@ -191,14 +193,12 @@
             lf.id as firm_id,
             lf.firm_name,
             COUNT(DISTINCT lco.id) as order_count,
-            CAST(ROUND(COALESCE(SUM(lco.lawyer_earnings), 0) / 100.0, 2) AS CHAR) as total_amount_yuan,
-            CAST(ROUND(COALESCE(SUM(lco.consultation_fee), 0) / 100.0, 2) AS CHAR) as commission_fee_yuan
+            CAST(ROUND(COALESCE(SUM(lco.order_amount), 0) / 100.0, 2) AS CHAR) as total_amount_yuan
         FROM lawyer_user lu
         INNER JOIN law_firm lf ON lu.firm_id = lf.id
             AND lf.delete_flag = 0
             AND lf.id = #{firmId}
         LEFT JOIN lawyer_consultation_order lco ON lco.lawyer_user_id = lu.id
-            AND lco.place_id = #{firmId}
             AND lco.delete_flag = 0
             AND lco.order_status = 3
             <if test="startDate != null">
@@ -212,7 +212,7 @@
             AND lu.name LIKE CONCAT('%', #{lawyerName}, '%')
         </if>
         GROUP BY lu.id, lu.name, lu.head_img, lu.lawyer_certificate_no, lf.id, lf.firm_name
-        ORDER BY COALESCE(SUM(lco.lawyer_earnings), 0) DESC
+        ORDER BY COALESCE(SUM(lco.order_amount), 0) DESC
     </select>
 
     <!-- 查询律师的已完成订单列表(分页) -->

+ 10 - 3
alien-entity/src/main/resources/mapper/LawyerConsultationOrderMapper.xml

@@ -75,7 +75,7 @@
         <result column="lawyer_phone" property="lawyerPhone" />
         <result column="lawyer_email" property="lawyerEmail" />
         <result column="lawyer_certificate_no" property="lawyerCertificateNo" />
-        <result column="law_firm" property="lawFirm" />
+        <result column="firm_name" property="lawFirm" />
         <result column="practice_years" property="practiceYears" />
         <result column="specialty_fields" property="specialtyFields" />
         <result column="certification_status" property="certificationStatus" />
@@ -115,12 +115,17 @@
             o.rating,
             o.comment,
             o.created_time,
+            o.pay_type,
+            o.charge_minute,
+            o.minute_num,
+            o.charge_time,
+            o.time_num,
             -- 律師信息
             l.name AS lawyer_name,
             l.phone AS lawyer_phone,
             l.email AS lawyer_email,
             l.lawyer_certificate_no,
-            l.law_firm,
+            lf.firm_name AS law_firm,
             l.practice_years,
             l.specialty_fields,
             l.certification_status,
@@ -136,6 +141,7 @@
             l.personal_introduction
         FROM lawyer_consultation_order o
         LEFT JOIN lawyer_user l ON o.lawyer_user_id = l.id AND l.delete_flag = 0
+        LEFT JOIN law_firm lf ON lf.id = l.firm_id AND lf.delete_flag = 0
         WHERE o.delete_flag = 0
         <if test="orderNumber != null and orderNumber != ''">
             AND o.order_number = #{orderNumber}
@@ -189,7 +195,7 @@
         lu.phone AS lawyer_phone,
         lu.email AS lawyer_email,
         lu.lawyer_certificate_no,
-        lu.law_firm,
+        lf.firm_name AS law_firm,
         lu.practice_years,
         lu.specialty_fields,
         lu.certification_status,
@@ -206,6 +212,7 @@
         lea.expertise_area_info
         FROM lawyer_consultation_order lco
         LEFT JOIN lawyer_user lu ON lco.lawyer_user_id = lu.id AND lu.delete_flag = 0
+        LEFT JOIN law_firm lf ON lf.id = lu.firm_id AND lf.delete_flag = 0
         left join lawyer_expertise_area lea on lea.id = lu.lawyer_expertise_area_id and lea.delete_flag = 0
         WHERE lco.delete_flag = 0
         <if test="userId != null and userId !=''">

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

@@ -79,6 +79,9 @@
         <if test="userId != null">
             AND orv.user_id = #{userId}
         </if>
+        <if test="filterAppealId != null and filterAppealId == true">
+            AND (orv.appeal_id IS NULL OR orv.appeal_id = '')
+        </if>
         ORDER BY orv.created_time DESC
     </select>
 
@@ -165,7 +168,8 @@
                 WHEN #{currentUserId} IS NOT NULL AND llr.id IS NOT NULL THEN 1
                 ELSE 0
             END AS is_liked,
-            orv.created_time
+            orv.created_time,
+            orv.appeal_id
         FROM lawyer_order_review orv
         LEFT JOIN life_user lu ON lu.id = orv.user_id AND lu.delete_flag = 0
         LEFT JOIN lawyer_user lu2 ON lu2.id = orv.lawyer_user_id AND lu2.delete_flag = 0
@@ -190,6 +194,15 @@
         <result column="review_content" property="reviewContent" />
         <result column="created_time" property="createdTime" />
         <result column="completed_time" property="completedTime" />
+        <result column="problem_description" property="problemDescription" />
+        <result column="order_amount" property="orderAmount" />
+        <result column="consultation_fee" property="consultationFee" />
+        <result column="start_time" property="startTime" />
+        <result column="end_time" property="endTime" />
+        <result column="order_time" property="orderTime" />
+        <result column="payment_time" property="paymentTime" />
+        <result column="order_status" property="orderStatus" />
+        <result column="payment_status" property="paymentStatus" />
     </resultMap>
 
     <!-- 分页查询待评价列表(查询用户已完成但未评价的订单对应的律师信息) -->
@@ -204,7 +217,16 @@
             NULL AS overall_rating,
             NULL AS review_content,
             lco.end_time AS created_time,
-            lco.end_time AS completed_time
+            lco.end_time AS completed_time,
+            lco.problem_description,
+            lco.order_amount,
+            lco.consultation_fee,
+            lco.start_time,
+            lco.end_time,
+            lco.order_time,
+            lco.payment_time,
+            lco.order_status,
+            lco.payment_status
         FROM lawyer_consultation_order lco
         LEFT JOIN lawyer_user lu ON lu.id = lco.lawyer_user_id AND lu.delete_flag = 0
         LEFT JOIN law_firm lf ON lf.id = lu.firm_id AND lf.delete_flag = 0

+ 93 - 11
alien-entity/src/main/resources/mapper/ReviewCommentMapper.xml

@@ -8,9 +8,13 @@
         <result column="review_id" property="reviewId" />
         <result column="send_user_id" property="sendUserId" />
         <result column="receive_user_id" property="receiveUserId" />
-        <result column="user_name" property="sendUserName" />
+        <!-- send_user_name: 发送用户名称(根据 send_user_id 和 send_user_type 查询) -->
+        <result column="send_user_name" property="sendUserName" />
         <result column="user_avatar" property="userAvatar" />
+        <!-- receive_user_name: 接收用户名称(根据 receive_user_id 和 receive_user_type 查询) -->
         <result column="receive_user_name" property="receiveUserName" />
+        <result column="send_user_type" property="sendUserType" />
+        <result column="receive_user_type" property="receiveUserType" />
         <result column="comment_content" property="commentContent" />
         <result column="like_count" property="likeCount" />
         <result column="reply_count" property="replyCount" />
@@ -21,15 +25,37 @@
     </resultMap>
 
     <!-- 根据评价ID查询评论列表(包含用户信息) -->
+    <!-- 评论列表中的 sendUserName 和 receiveUserName 根据 sendUserType 和 receiveUserType 分别从对应的表中查询 -->
     <select id="getCommentListByReviewId" resultMap="ReviewCommentVoResultMap">
         SELECT
             rc.id,
             rc.review_id,
             rc.send_user_id,
             rc.receive_user_id,
-            lu.user_name AS user_name,
-            lu.user_image AS user_avatar,
-            lu2.user_name AS receive_user_name,
+            -- 发送用户名称:根据 sendUserType 分别查询普通用户表和律师表
+            -- sendUserType = 2(律师):优先从 lawyer_user 表查询,如果没有则从 life_user 表查询
+            -- sendUserType = 1 或 NULL(普通用户):优先从 life_user 表查询,如果没有则从 lawyer_user 表查询
+            CASE
+                WHEN rc.send_user_type = 2 THEN COALESCE(lu_lawyer.name, lu_user.user_name)
+                WHEN rc.send_user_type = 1 OR rc.send_user_type IS NULL THEN COALESCE(lu_user.user_name, lu_lawyer.name)
+                ELSE COALESCE(lu_user.user_name, lu_lawyer.name)
+            END AS send_user_name,
+            -- 发送用户头像:根据 sendUserType 分别查询普通用户表和律师表
+            CASE
+                WHEN rc.send_user_type = 2 THEN COALESCE(lu_lawyer.head_img, lu_user.user_image)
+                WHEN rc.send_user_type = 1 OR rc.send_user_type IS NULL THEN COALESCE(lu_user.user_image, lu_lawyer.head_img)
+                ELSE COALESCE(lu_user.user_image, lu_lawyer.head_img)
+            END AS user_avatar,
+            -- 接收用户名称:根据 receiveUserType 严格从对应的表中查询
+            -- receiveUserType = 2(律师):只从 lawyer_user 表查询
+            -- receiveUserType = 1 或 NULL(普通用户):只从 life_user 表查询
+            CASE
+                WHEN rc.receive_user_type = 2 THEN lu_lawyer2.name
+                WHEN rc.receive_user_type = 1 OR rc.receive_user_type IS NULL THEN lu_user2.user_name
+                ELSE NULL
+            END AS receive_user_name,
+            rc.send_user_type,
+            rc.receive_user_type,
             rc.comment_content,
             rc.like_count,
             rc.reply_count,
@@ -41,8 +67,25 @@
             END AS is_liked,
             rc.created_time
         FROM lawyer_review_comment rc
-        LEFT JOIN life_user lu ON lu.id = rc.send_user_id AND lu.delete_flag = 0
-        LEFT JOIN life_user lu2 ON lu2.id = rc.receive_user_id AND lu2.delete_flag = 0
+        -- 发送用户:普通用户表(先 JOIN,不限制类型)
+        LEFT JOIN life_user lu_user ON lu_user.id = rc.send_user_id 
+            AND lu_user.delete_flag = 0
+        -- 发送用户:律师表(先 JOIN,不限制类型)
+        LEFT JOIN lawyer_user lu_lawyer ON lu_lawyer.id = rc.send_user_id 
+            AND lu_lawyer.delete_flag = 0
+        -- 接收用户:普通用户表(先 JOIN,不限制类型)
+        LEFT JOIN life_user lu_user2 ON lu_user2.id = rc.receive_user_id 
+            AND lu_user2.delete_flag = 0
+        -- 接收用户:律师表(先 JOIN,不限制类型)
+        LEFT JOIN lawyer_user lu_lawyer2 ON lu_lawyer2.id = rc.receive_user_id 
+            AND lu_lawyer2.delete_flag = 0
+        -- 如果 receive_user_id 在用户表中查询不到,尝试从评论表查询(处理 receive_user_id 可能是评论 ID 的情况)
+        LEFT JOIN lawyer_review_comment rc_comment ON rc_comment.id = rc.receive_user_id 
+            AND rc_comment.delete_flag = 0
+        LEFT JOIN life_user lu_user_comment ON lu_user_comment.id = rc_comment.send_user_id 
+            AND lu_user_comment.delete_flag = 0
+        LEFT JOIN lawyer_user lu_lawyer_comment ON lu_lawyer_comment.id = rc_comment.send_user_id 
+            AND lu_lawyer_comment.delete_flag = 0
         LEFT JOIN life_like_record llr ON CONVERT(llr.huifu_id, CHAR) = CONVERT(rc.id, CHAR)
             AND llr.type = '8' 
             AND CONVERT(llr.dianzan_id, CHAR) = CONVERT(#{currentUserId}, CHAR)
@@ -54,15 +97,37 @@
     </select>
 
     <!-- 根据首评ID查询回复列表(包含用户信息) -->
+    <!-- 回复列表中的 sendUserName 和 receiveUserName 根据 sendUserType 和 receiveUserType 分别从对应的表中查询 -->
     <select id="getReplyListByHeadId" resultMap="ReviewCommentVoResultMap">
         SELECT
             rc.id,
             rc.review_id,
             rc.send_user_id,
             rc.receive_user_id,
-            lu.user_name AS user_name,
-            lu.user_image AS user_avatar,
-            lu2.user_name AS receive_user_name,
+            -- 发送用户名称:根据 sendUserType 分别查询普通用户表和律师表
+            -- sendUserType = 2(律师):优先从 lawyer_user 表查询,如果没有则从 life_user 表查询
+            -- sendUserType = 1 或 NULL(普通用户):优先从 life_user 表查询,如果没有则从 lawyer_user 表查询
+            CASE
+                WHEN rc.send_user_type = 2 THEN COALESCE(lu_lawyer.name, lu_user.user_name)
+                WHEN rc.send_user_type = 1 OR rc.send_user_type IS NULL THEN COALESCE(lu_user.user_name, lu_lawyer.name)
+                ELSE COALESCE(lu_user.user_name, lu_lawyer.name)
+            END AS send_user_name,
+            -- 发送用户头像:根据 sendUserType 分别查询普通用户表和律师表
+            CASE
+                WHEN rc.send_user_type = 2 THEN COALESCE(lu_lawyer.head_img, lu_user.user_image)
+                WHEN rc.send_user_type = 1 OR rc.send_user_type IS NULL THEN COALESCE(lu_user.user_image, lu_lawyer.head_img)
+                ELSE COALESCE(lu_user.user_image, lu_lawyer.head_img)
+                END AS user_avatar,
+            -- 接收用户名称:根据 receiveUserType 严格从对应的表中查询
+            -- receiveUserType = 2(律师):只从 lawyer_user 表查询
+            -- receiveUserType = 1 或 NULL(普通用户):只从 life_user 表查询
+            CASE
+                WHEN rc.receive_user_type = 2 THEN lu_lawyer2.name
+                WHEN rc.receive_user_type = 1 OR rc.receive_user_type IS NULL THEN lu_user2.user_name
+                ELSE NULL
+            END AS receive_user_name,
+            rc.send_user_type,
+            rc.receive_user_type,
             rc.comment_content,
             rc.like_count,
             rc.reply_count,
@@ -74,8 +139,25 @@
             END AS is_liked,
             rc.created_time
         FROM lawyer_review_comment rc
-        LEFT JOIN life_user lu ON lu.id = rc.send_user_id AND lu.delete_flag = 0
-        LEFT JOIN life_user lu2 ON lu2.id = rc.receive_user_id AND lu2.delete_flag = 0
+        -- 发送用户:普通用户表(先 JOIN,不限制类型)
+        LEFT JOIN life_user lu_user ON lu_user.id = rc.send_user_id 
+            AND lu_user.delete_flag = 0
+        -- 发送用户:律师表(先 JOIN,不限制类型)
+        LEFT JOIN lawyer_user lu_lawyer ON lu_lawyer.id = rc.send_user_id 
+            AND lu_lawyer.delete_flag = 0
+        -- 接收用户:普通用户表(先 JOIN,不限制类型)
+        LEFT JOIN life_user lu_user2 ON lu_user2.id = rc.receive_user_id 
+            AND lu_user2.delete_flag = 0
+        -- 接收用户:律师表(先 JOIN,不限制类型)
+        LEFT JOIN lawyer_user lu_lawyer2 ON lu_lawyer2.id = rc.receive_user_id 
+            AND lu_lawyer2.delete_flag = 0
+        -- 如果 receive_user_id 在用户表中查询不到,尝试从评论表查询(处理 receive_user_id 可能是评论 ID 的情况)
+        LEFT JOIN lawyer_review_comment rc_comment ON rc_comment.id = rc.receive_user_id 
+            AND rc_comment.delete_flag = 0
+        LEFT JOIN life_user lu_user_comment ON lu_user_comment.id = rc_comment.send_user_id 
+            AND lu_user_comment.delete_flag = 0
+        LEFT JOIN lawyer_user lu_lawyer_comment ON lu_lawyer_comment.id = rc_comment.send_user_id 
+            AND lu_lawyer_comment.delete_flag = 0
         LEFT JOIN life_like_record llr ON CONVERT(llr.huifu_id, CHAR) = CONVERT(rc.id, CHAR)
             AND llr.type = '8' 
             AND CONVERT(llr.dianzan_id, CHAR) = CONVERT(#{currentUserId}, CHAR)

+ 3 - 2
alien-entity/src/main/resources/mapper/second/LawyerReconcMapper.xml

@@ -31,7 +31,7 @@
         <result column="lawyer_phone" property="lawyerPhone" />
         <result column="lawyer_email" property="lawyerEmail" />
         <result column="lawyer_certificate_no" property="lawyerCertificateNo" />
-        <result column="law_firm" property="lawFirm" />
+        <result column="firm_name" property="lawFirm" />
         <result column="practice_years" property="practiceYears" />
         <result column="specialty_fields" property="specialtyFields" />
         <result column="certification_status" property="certificationStatus" />
@@ -149,7 +149,7 @@
             lu.phone AS lawyer_phone,
             lu.email AS lawyer_email,
             lu.lawyer_certificate_no,
-            lu.law_firm,
+            lf.firm_name AS law_firm,
             lu.practice_years,
             lu.specialty_fields,
             lu.certification_status,
@@ -165,6 +165,7 @@
             lu.personal_introduction
         FROM lawyer_consultation_order lco
         LEFT JOIN lawyer_user lu ON lco.lawyer_user_id = lu.id AND lu.delete_flag = 0
+        LEFT JOIN law_firm lf ON lf.id = lu.firm_id AND lf.delete_flag = 0
         WHERE lco.delete_flag = 0
         <if test="orderNumber != null and orderNumber != ''">
             AND lco.order_number LIKE CONCAT('%', #{orderNumber}, '%')

+ 6 - 6
alien-gateway/src/main/java/shop/alien/gateway/controller/SystemController.java

@@ -80,7 +80,7 @@ public class SystemController {
         }
     }
 
-    @ApiOperation("web中台系统用户注册")
+    @ApiOperation("web中台分配律师账号")
     @ApiOperationSupport(order = 3)
     @ApiImplicitParams({
             @ApiImplicitParam(name = "userName", value = "用户名", dataType = "String", paramType = "body", required = true),
@@ -91,7 +91,7 @@ public class SystemController {
     })
     @PostMapping(value = "/register")
     public R<LifeSys> register(@RequestBody SystemRegisterDto registerDto) {
-        log.info("SystemController.register?userName={},roleId={}", registerDto.getUserName(), registerDto.getRoleId());
+        log.info("SystemController.register?userName={},roleId={},phone={}", registerDto.getUserName(), registerDto.getRoleId());
         return systemService.register(registerDto.getUserName(), registerDto.getUserPassword(), registerDto.getRoleId(), registerDto.getStatus(), registerDto.getRemark());
     }
 
@@ -219,7 +219,7 @@ public class SystemController {
                     "            \"icon\": \"Briefcase\",\n" +
                     "            \"title\": \"法律场景\",\n" +
                     "            \"isLink\": \"\",\n" +
-                    "            \"isHide\": false,\n" +
+                    "            \"isHide\": true,\n" +
                     "            \"isFull\": false,\n" +
                     "            \"isAffix\": false,\n" +
                     "            \"isKeepAlive\": false\n" +
@@ -277,7 +277,7 @@ public class SystemController {
                     "        \"icon\": \"UserFilled\",\n" +
                     "        \"title\": \"用户管理\",\n" +
                     "        \"isLink\": \"\",\n" +
-                    "        \"isHide\": false,\n" +
+                    "        \"isHide\": true,\n" +
                     "        \"isFull\": false,\n" +
                     "        \"isAffix\": false,\n" +
                     "        \"isKeepAlive\": false\n" +
@@ -291,7 +291,7 @@ public class SystemController {
                     "        \"icon\": \"UserFilled\",\n" +
                     "        \"title\": \"举报审核\",\n" +
                     "        \"isLink\": \"\",\n" +
-                    "        \"isHide\": false,\n" +
+                    "        \"isHide\": true,\n" +
                     "        \"isFull\": false,\n" +
                     "        \"isAffix\": false,\n" +
                     "        \"isKeepAlive\": false\n" +
@@ -305,7 +305,7 @@ public class SystemController {
                     "        \"icon\": \"UserFilled\",\n" +
                     "        \"title\": \"申诉审核\",\n" +
                     "        \"isLink\": \"\",\n" +
-                    "        \"isHide\": false,\n" +
+                    "        \"isHide\": true,\n" +
                     "        \"isFull\": false,\n" +
                     "        \"isAffix\": false,\n" +
                     "        \"isKeepAlive\": false\n" +

+ 108 - 0
alien-gateway/src/main/java/shop/alien/gateway/util/SmsUtil.java

@@ -0,0 +1,108 @@
+package shop.alien.gateway.util;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+import org.springframework.util.StringUtils;
+
+/**
+ * 短信发送工具类
+ * 
+ * 注意:如果需要实际发送短信,需要在pom.xml中添加阿里云短信SDK依赖:
+ * <dependency>
+ *     <groupId>com.aliyun</groupId>
+ *     <artifactId>dysmsapi20170525</artifactId>
+ *     <version>3.0.0</version>
+ * </dependency>
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Slf4j
+@Component
+public class SmsUtil {
+
+    @Value("${ali.sms.accessKeyId:}")
+    private String accessKeyId;
+
+    @Value("${ali.sms.accessKeySecret:}")
+    private String accessKeySecret;
+
+    @Value("${ali.sms.endPoint:}")
+    private String endPoint;
+
+    @Value("${ali.sms.signName:}")
+    private String signName;
+
+    @Value("${ali.sms.templateCode:}")
+    private String templateCode;
+
+    /**
+     * 发送账号密码短信
+     * 短信内容:您的账号{userName},密码是{password}
+     *
+     * @param phone    手机号
+     * @param userName 账号(用户名)
+     * @param password 密码
+     * @return 是否发送成功
+     */
+    public boolean sendAccountPasswordSms(String phone, String userName, String password) {
+        if (!StringUtils.hasText(phone)) {
+            log.warn("手机号为空,无法发送短信");
+            return false;
+        }
+
+        log.info("SmsUtil.sendAccountPasswordSms?phone={},userName={}", phone, userName);
+
+        try {
+            // 构建短信内容:您的账号{userName},密码是{password}
+            String messageContent = String.format("您的账号%s,密码是%s", userName, password);
+
+            // 如果配置为空,只记录日志(测试环境)
+            if (!StringUtils.hasText(accessKeyId) || !StringUtils.hasText(accessKeySecret)) {
+                log.info("短信配置未设置,仅记录日志。短信内容:{}", messageContent);
+                return true; // 测试环境返回成功
+            }
+
+            // TODO: 如果需要实际发送短信,请取消下面的注释并添加阿里云短信SDK依赖
+            /*
+            Config config = new Config()
+                    .setEndpoint(endPoint)
+                    .setAccessKeyId(accessKeyId)
+                    .setAccessKeySecret(accessKeySecret);
+
+            // 构建发送请求
+            // 注意:这里使用模板方式,如果阿里云有专门的账号密码通知模板,请使用对应的templateCode
+            // 如果没有,可以使用通用模板,将内容作为参数传递
+            SendSmsRequest sendSmsRequest = new SendSmsRequest()
+                    .setSignName(signName)
+                    .setTemplateCode(templateCode)
+                    .setPhoneNumbers(phone)
+                    // 根据实际模板参数调整,这里假设模板参数为 {"account":"账号","password":"密码"}
+                    .setTemplateParam("{\"account\":\"" + userName + "\",\"password\":\"" + password + "\"}");
+
+            RuntimeOptions runtime = new RuntimeOptions();
+            Client client = new Client(config);
+            SendSmsResponse sendSmsResponse = client.sendSmsWithOptions(sendSmsRequest, runtime);
+
+            if ("OK".equals(sendSmsResponse.getBody().getCode())) {
+                log.info("账号密码短信发送成功,phone={}, userName={}", phone, userName);
+                return true;
+            } else {
+                log.error("账号密码短信发送失败,phone={}, userName={}, error={}",
+                        phone, userName, sendSmsResponse.getBody().getMessage());
+                return false;
+            }
+            */
+            
+            // 当前实现:仅记录日志
+            log.info("账号密码短信内容:{},发送至:{}", messageContent, phone);
+            return true;
+            
+        } catch (Exception e) {
+            log.error("发送账号密码短信异常,phone={}, userName={}, error={}", phone, userName, e.getMessage(), e);
+            return false;
+        }
+    }
+}
+

+ 4 - 2
alien-job/src/main/java/shop/alien/job/second/AiUserViolationJob.java

@@ -45,9 +45,11 @@ public class AiUserViolationJob {
     @Value("${third-party-pass-word.base-url}")
     private String passWord;
 
-    private String loginUrl = "http://192.168.2.250:9000/ai/user-auth-core/api/v1/auth/login";
+    @Value("${third-party-login.base-url:http://192.168.2.250:9000/ai/user-auth-core/api/v1/auth/login}")
+    private String loginUrl;
 
-    private String aiUserViolationCheckUrl = "http://192.168.2.250:9000/ai/auto-review/api/v1/user_complaint_record/result";
+    @Value("${third-party-aiUserViolationCheckUrl.base-url:http://192.168.2.250:9000/ai/auto-review/api/v1/user_complaint_record/result}")
+    private String aiUserViolationCheckUrl;
 
     @XxlJob("getAiUserViolationResult")
     public R<String> getAiUserViolationResult() {

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

@@ -56,20 +56,20 @@ public class BadReviewAppealJob {
 //    @Value("${third-party-login.base-url}")
 //    private String loginUrl;
 
-    private String loginUrl = "http://192.168.2.78:9000/ai/user-auth-core/api/v1/auth/login";
-
-
     @Value("${third-party-user-name.base-url}")
     private String userName;
 
     @Value("${third-party-pass-word.base-url}")
     private String passWord;
 
-    private String analyzeUrl = "http://192.168.2.78:9000/ai/auto-review/api/v1/analyze";
-
-    private String resultUrl = "http://192.168.2.78:9000/ai/auto-review";
+    @Value("${third-party-login.base-url}")
+    private String loginUrl;
 
+    @Value("${third-party-analyzeUrl.base-url}")
+    private String analyzeUrl;
 
+    @Value("${third-party-resultUrl.base-url}")
+    private String resultUrl;
 
     /**
      * 差评申述置信度分析接口地址
@@ -299,7 +299,7 @@ public class BadReviewAppealJob {
                 //修改评论状态为"处理中"
                 StoreCommentAppeal sCommentAppeal = new StoreCommentAppeal();
                 sCommentAppeal.setId((Integer) storeCommentAppeal.get("id"));
-                sCommentAppeal.setAppealStatus(0);
+                sCommentAppeal.setAppealStatus(3);
                 sCommentAppeal.setRecordId(recordId);
                 storeCommentAppealMapper.updateById(sCommentAppeal);
 

+ 12 - 4
alien-lawyer/src/main/java/shop/alien/lawyer/controller/LawFirmController.java

@@ -146,12 +146,20 @@ public class LawFirmController {
         return lawFirmService.getLawFirmDetail(id);
     }
 
-    @ApiOperation(value = "导出律所数据到Excel", notes = "导出律所数据到Excel文件,包含收款账号信息")
+    @ApiOperation(value = "导出律所数据到Excel", notes = "导出律所数据到Excel文件,包含收款账号信息。支持按律所ID过滤和分页导出")
     @ApiOperationSupport(order = 8)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "firmId", value = "律所ID(可选,用于过滤特定律所)", dataType = "Integer", paramType = "query"),
+            @ApiImplicitParam(name = "page", value = "页数(可选,默认1,如果pageSize为空则导出全部)", dataType = "int", paramType = "query"),
+            @ApiImplicitParam(name = "size", value = "页容(可选,默认10,如果为空则导出全部数据)", dataType = "int", paramType = "query")
+    })
     @GetMapping("/export")
-    public void exportLawFirm(HttpServletResponse response) throws IOException {
-        log.info("LawFirmController.exportLawFirm");
-        lawFirmService.exportLawFirm(response);
+    public void exportLawFirm(HttpServletResponse response,
+                              @RequestParam(required = false) Integer firmId,
+                              @RequestParam(required = false) Integer page,
+                              @RequestParam(required = false) Integer size) throws IOException {
+        log.info("LawFirmController.exportLawFirm?firmId={},page={},size={}", firmId, page, size);
+        lawFirmService.exportLawFirm(response, firmId, page, size);
     }
 
     @ApiOperation(value = "从Excel导入律所数据", notes = "从Excel文件导入律所数据,支持批量导入。导入规则:1. 按统一社会信用代码(creditCode)分组,相同信用代码的数据会自动合并;2. 相同信用代码的多行数据会合并收款账号;3. 如果统一社会信用代码已存在,将更新现有记录并合并收款账号;4. 收款账号必须全局唯一,已存在的收款账号将跳过添加")

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

@@ -174,5 +174,85 @@ public class LawyerClientConsultationOrderController {
     public R<Boolean> refundApplyProcess(@RequestBody LawyerConsultationOrder lawyerConsultationOrder) {
         return lawyerClientConsultationOrderService.refundApplyProcess(lawyerConsultationOrder);
     }
+
+    /**
+     * 订单开始计时(律师端)
+     * <p>
+     * 订单开始计时:
+     * 1. 当订单为按时收费,订单状态为进行中时,订单开始计时
+     * </p>
+     *
+     * @param id        订单ID
+     * @return 操作结果
+     */
+    @ApiOperation("订单开始计时(律师端)")
+    @ApiOperationSupport(order = 5)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "id", value = "订单ID", dataType = "String", paramType = "query", required = true)
+    })
+    @GetMapping("/orderStartTiming")
+    public R<Boolean> orderStartTiming(@RequestParam(value = "id", required = true) String id) {
+        log.info("订单开始计时,订单ID={}", id);
+
+        // 参数校验
+        if (id == null || id.trim().isEmpty()) {
+            log.warn("订单开始计时失败:订单ID为空");
+            return R.fail("订单ID不能为空");
+        }
+
+        return lawyerClientConsultationOrderService.orderStartTiming(id);
+    }
+
+    /**
+     * 检查订单接单后是否有消息记录(律师端)
+     * <p>
+     * 根据订单ID查询订单信息,获取律师接单时间、用户ID和律师ID,
+     * 然后查询 life_message 表,检查是否存在律师发送给用户的消息记录,
+     * 且消息创建时间大于接单时间。
+     * </p>
+     * <p>
+     * 处理流程:
+     * 1. 参数校验:订单ID不能为空
+     * 2. 查询订单信息:获取接单时间、用户ID、律师ID
+     * 3. 查询律师和用户信息:获取律师手机号和用户手机号
+     * 4. 构建消息查询条件:sender_id=律师phone,receiver_id=用户phone,created_time>接单时间
+     * 5. 查询消息记录:如果存在记录返回true,否则返回false
+     * </p>
+     *
+     * @param id 订单ID,不能为空
+     * @return 统一响应结果,true表示有消息记录,false表示无消息记录
+     * @author system
+     * @since 2025-01-XX
+     */
+    @ApiOperation("检查订单接单后是否有消息记录(律师端)")
+    @ApiOperationSupport(order = 8)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "id", value = "订单ID", dataType = "String", paramType = "query", required = true)
+    })
+    @GetMapping("/checkMessageAfterAccept")
+    public R<Boolean> checkMessageAfterAccept(@RequestParam(value = "id", required = true) String id) {
+        log.info("检查订单接单后是否有消息记录请求开始,订单ID={}", id);
+
+        // 参数校验
+        if (id == null || id.trim().isEmpty()) {
+            log.warn("检查订单接单后是否有消息记录失败:订单ID为空");
+            return R.fail("订单ID不能为空");
+        }
+
+        try {
+            Boolean hasMessage = lawyerClientConsultationOrderService.checkMessageAfterAccept(id);
+            log.info("检查订单接单后是否有消息记录成功,订单ID={},是否有消息={}", id, hasMessage);
+            return R.data(hasMessage);
+        } catch (IllegalArgumentException e) {
+            log.warn("检查订单接单后是否有消息记录参数校验失败,订单ID={},错误信息:{}", id, e.getMessage());
+            return R.fail(e.getMessage());
+        } catch (RuntimeException e) {
+            log.error("检查订单接单后是否有消息记录异常,订单ID={},异常信息:{}", id, e.getMessage(), e);
+            return R.fail("检查消息记录失败:" + e.getMessage());
+        } catch (Exception e) {
+            log.error("检查订单接单后是否有消息记录未知异常,订单ID={},异常信息:{}", id, e.getMessage(), e);
+            return R.fail("检查消息记录失败,请稍后重试");
+        }
+    }
 }
 

+ 54 - 5
alien-lawyer/src/main/java/shop/alien/lawyer/controller/LawyerConsultationOrderController.java

@@ -15,7 +15,6 @@ import shop.alien.lawyer.service.LawyerConsultationOrderService;
 import shop.alien.lawyer.service.OrderExpirationService;
 import shop.alien.util.myBaticsPlus.QueryBuilder;
 
-import java.util.Date;
 import java.util.List;
 import java.util.Map;
 
@@ -222,6 +221,10 @@ public class LawyerConsultationOrderController {
             @ApiImplicitParam(name = "problemDescription", value = "问题描述", dataType = "String", paramType = "query"),
             @ApiImplicitParam(name = "alipayNo", value = "支付宝订单编号", dataType = "String", paramType = "query", required = true),
             @ApiImplicitParam(name = "orderStr", value = "支付宝订单字符串", dataType = "String", paramType = "query", required = true),
+            @ApiImplicitParam(name = "payType", value = "支付方式 1支付宝 2微信", dataType = "String", paramType = "query"),
+            @ApiImplicitParam(name = "chargeMinute", value = "分钟收费-钱数", dataType = "Integer", paramType = "query"),
+            @ApiImplicitParam(name = "minuteNum", value = "分钟收费-分钟数", dataType = "Integer", paramType = "query"),
+            @ApiImplicitParam(name = "chargeTime", value = "次数收费-钱数", dataType = "Integer", paramType = "query"),
 //            @ApiImplicitParam(name = "lawyerName", value = "律师姓名(支持模糊查询,关联查询)", dataType = "String", paramType = "query"),
 //            @ApiImplicitParam(name = "consultationFee", value = "咨询费用,单位分", dataType = "Integer", paramType = "query"),
 //            @ApiImplicitParam(name = "startTime", value = "咨询开始时间", dataType = "Date", paramType = "query"),
@@ -245,7 +248,10 @@ public class LawyerConsultationOrderController {
     @ApiImplicitParams({
             @ApiImplicitParam(name = "orderNumber", value = "订单编号", dataType = "String", paramType = "query", required = true),
             @ApiImplicitParam(name = "orderStatus", value = "订单状态, 0:待支付, 1:待接单, 2:进行中, 3:已完成, 4:已取消", dataType = "Integer", paramType = "query", required = true),
-            @ApiImplicitParam(name = "paymentStatus", value = "支付状态, 0:未支付, 1:已支付", dataType = "Integer", paramType = "query", required = true)
+            @ApiImplicitParam(name = "paymentStatus", value = "支付状态, 0:未支付, 1:已支付", dataType = "Integer", paramType = "query", required = true),
+            @ApiImplicitParam(name = "payType", value = "支付方式 1支付宝 2微信", dataType = "String", paramType = "query"),
+            @ApiImplicitParam(name = "orderStr", value = "支付宝或VX的str", dataType = "String", paramType = "query"),
+            @ApiImplicitParam(name = "alipayNo", value = "支付宝/VX订单编号", dataType = "String", paramType = "query"),
     })
     @PostMapping("/payStatus")
     public R<LawyerConsultationOrderDto> payStatus(@RequestBody PayStatusRequest request) {
@@ -291,15 +297,46 @@ public class LawyerConsultationOrderController {
         return R.data(consultationOrderService.getConsultationOrderDetail(lawyerOrderId));
     }
 
+    /**
+     * 取消律师咨询订单(用户端)
+     * <p>
+     * 支持取消待支付和待接单状态的订单:
+     * - 待支付订单:取消后订单状态变更为已取消,同时取消订单支付超时计时器
+     * - 待接单订单:取消后订单状态变更为已退款
+     * 已取消或已完成的订单不允许再次取消
+     * </p>
+     *
+     * @param id 订单ID
+     * @return 取消结果,true表示成功,false表示失败
+     */
     @ApiOperation("取消律师咨询订单(用户端)")
     @ApiOperationSupport(order = 13)
     @ApiImplicitParams({
-            @ApiImplicitParam(name = "id", value = "订单id", dataType = "String", paramType = "query", required = true)
+            @ApiImplicitParam(name = "id", value = "订单ID", dataType = "String", paramType = "query", required = true)
     })
     @PostMapping("/cancelOrder")
     public R<Boolean> cancelOrder(@RequestParam(value = "id", required = true) String id) {
         log.info("LawyerConsultationOrderController.cancelOrder?id={}", id);
-        return R.data(consultationOrderService.cancelOrder(id));
+
+        // 参数校验
+        if (id == null || id.trim().isEmpty()) {
+            log.warn("取消订单失败:订单ID为空");
+            return R.fail("订单ID不能为空");
+        }
+
+        try {
+            boolean success = consultationOrderService.cancelOrder(id);
+            if (success) {
+                log.info("取消订单成功,订单ID={}", id);
+                return R.data(true);
+            } else {
+                log.warn("取消订单失败,订单ID={}", id);
+                return R.fail("取消订单失败,请检查订单状态");
+            }
+        } catch (Exception e) {
+            log.error("取消订单异常,订单ID={},异常信息:{}", id, e.getMessage(), e);
+            return R.fail("取消订单失败:" + e.getMessage());
+        }
     }
 
     @ApiOperation("查询咨询订单信息(律师端)")
@@ -347,7 +384,19 @@ public class LawyerConsultationOrderController {
     })
     @PostMapping("/checkOrder")
     public R<Map<String, String>> checkOrder(@RequestParam Integer clientUserId, @RequestParam Integer lawyerUserId) {
-        log.info("LawyerConsultationOrderController.checkOrder?clientUserId={},lawyerUserId{}", clientUserId, lawyerUserId);
+        log.info("LawyerConsultationOrderController.checkOrder?clientUserId={},lawyerUserId={}", clientUserId, lawyerUserId);
+        
+        // 参数校验
+        if (clientUserId == null) {
+            log.warn("创建订单前校验失败:客户端用户ID为空");
+            return R.fail("客户端用户ID不能为空");
+        }
+        
+        if (lawyerUserId == null) {
+            log.warn("创建订单前校验失败:律师用户ID为空");
+            return R.fail("律师用户ID不能为空");
+        }
+        
         return consultationOrderService.checkOrder(clientUserId, lawyerUserId);
     }
 

+ 50 - 6
alien-lawyer/src/main/java/shop/alien/lawyer/controller/LawyerNoticeController.java

@@ -92,15 +92,59 @@ public class LawyerNoticeController {
         
         return lawyerNoticeService.hasUnreadNotice(receiverId);
     }
+    /**
+     * 标记通知为已读
+     * <p>
+     * 根据通知ID将指定通知标记为已读状态
+     * </p>
+     * <p>
+     * 处理流程:
+     * 1. 参数校验:通知ID必须大于0且不能为null
+     * 2. 调用Service层方法执行更新操作
+     * 3. 根据更新结果返回相应的响应
+     * </p>
+     *
+     * @param id 通知ID,必须大于0且不能为null
+     * @return 统一响应结果,true表示已读成功,false表示已读失败
+     * @author system
+     * @since 2025-01-XX
+     */
     @GetMapping("/readNoticeById")
     @ApiOperation("通知已读")
     @ApiOperationSupport(order = 4)
-    @ApiImplicitParams({@ApiImplicitParam(name = "id", value = "主键id", dataType = "Integer", paramType = "query")})
-    public R<Boolean> readNoticeById(Integer id) {
-        log.info("LifeNoticeController.readNoticeById?receiverId={}", id);
-        if (lawyerNoticeService.readNoticeById(id) > 0) {
-            return R.success("已读成功");
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "id", value = "通知ID(主键id)", dataType = "Integer", paramType = "query", required = true)
+    })
+    public R<Boolean> readNoticeById(@RequestParam(value = "id", required = true) Integer id) {
+        log.info("标记通知为已读请求开始,id={}", id);
+
+        // 参数校验
+        if (id == null || id <= 0) {
+            log.warn("标记通知为已读请求失败:通知ID为空或无效,id={}", id);
+            return R.fail("通知ID不能为空且必须大于0");
+        }
+
+        try {
+            // 调用Service层方法执行更新
+            int affectedRows = lawyerNoticeService.readNoticeById(id);
+
+            // 根据更新结果返回响应
+            if (affectedRows > 0) {
+                log.info("标记通知为已读请求成功,id={},受影响记录数={}", id, affectedRows);
+                return R.success("已读成功");
+            } else {
+                log.warn("标记通知为已读请求失败,id={},受影响记录数={},可能是通知不存在或已被删除", id, affectedRows);
+                return R.fail("已读失败,通知不存在或已被删除");
+            }
+        } catch (IllegalArgumentException e) {
+            log.warn("标记通知为已读请求参数校验失败,id={},错误信息:{}", id, e.getMessage());
+            return R.fail(e.getMessage());
+        } catch (RuntimeException e) {
+            log.error("标记通知为已读请求异常,id={},异常信息:{}", id, e.getMessage(), e);
+            return R.fail("标记通知为已读失败:" + e.getMessage());
+        } catch (Exception e) {
+            log.error("标记通知为已读请求未知异常,id={},异常信息:{}", id, e.getMessage(), e);
+            return R.fail("标记通知为已读失败,请稍后重试");
         }
-        return R.fail("已读失败");
     }
 }

+ 96 - 0
alien-lawyer/src/main/java/shop/alien/lawyer/controller/LawyerStatisticsController.java

@@ -0,0 +1,96 @@
+package shop.alien.lawyer.controller;
+
+import io.swagger.annotations.*;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.*;
+import shop.alien.entity.result.R;
+import shop.alien.entity.store.vo.LawyerDashboardVO;
+import shop.alien.entity.store.vo.LawyerOrderStatisticsVO;
+import shop.alien.lawyer.service.LawyerStatisticsService;
+
+/**
+ * 律师统计 前端控制器
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Slf4j
+@Api(tags = {"律师平台-统计管理"})
+@ApiSort(15)
+@CrossOrigin
+@RestController
+@RequestMapping("/lawyer/statistics")
+@RequiredArgsConstructor
+public class LawyerStatisticsController {
+
+    private final LawyerStatisticsService lawyerStatisticsService;
+
+    /**
+     * 获取律师订单统计数据
+     * <p>
+     * 获取律师的订单统计数据,包括本月订单量、本月收益、总订单量、总收益,
+     * 以及进行中、待接单、已退款等各状态订单的统计信息
+     * </p>
+     *
+     * @param lawyerUserId 律师用户ID,必须大于0
+     * @return 统一响应结果,包含订单统计数据
+     * @author system
+     * @since 2025-01-XX
+     */
+    @ApiOperation("获取律师订单统计数据")
+    @ApiOperationSupport(order = 1)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "lawyerUserId", value = "律师用户ID", dataType = "Integer", paramType = "query", required = true)
+    })
+    @GetMapping("/getOrderStatistics")
+    public R<LawyerOrderStatisticsVO> getOrderStatistics(
+            @RequestParam(value = "lawyerUserId", required = true) Integer lawyerUserId) {
+        log.info("获取律师订单统计数据请求开始,lawyerUserId={}", lawyerUserId);
+
+        // 参数校验
+        if (!validateLawyerUserId(lawyerUserId)) {
+            log.warn("获取律师订单统计数据失败:律师用户ID为空或无效,lawyerUserId={}", lawyerUserId);
+            return R.fail("律师用户ID不能为空");
+        }
+
+        try {
+            R<LawyerOrderStatisticsVO> result = lawyerStatisticsService.getOrderStatistics(lawyerUserId);
+            log.info("获取律师订单统计数据请求完成,lawyerUserId={}", lawyerUserId);
+            return result;
+        } catch (Exception e) {
+            log.error("获取律师订单统计数据请求异常,lawyerUserId={},异常信息:{}",
+                    lawyerUserId, e.getMessage(), e);
+            return R.fail("获取统计数据失败,请稍后重试");
+        }
+    }
+
+    /**
+     * 校验律师用户ID
+     * <p>
+     * 检查律师用户ID是否为空或无效
+     * </p>
+     *
+     * @param lawyerUserId 律师用户ID
+     * @return true表示有效,false表示无效
+     */
+    private boolean validateLawyerUserId(Integer lawyerUserId) {
+        return lawyerUserId != null && lawyerUserId > 0;
+    }
+
+    @ApiOperation("获取律师仪表板数据(包含个人信息、订单统计和收益统计)")
+    @ApiOperationSupport(order = 2)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "lawyerUserId", value = "律师用户ID", dataType = "Integer", paramType = "query", required = true)
+    })
+    @GetMapping("/getLawyerDashboard")
+    public R<LawyerDashboardVO> getLawyerDashboard(@RequestParam(value = "lawyerUserId", required = true) Integer lawyerUserId) {
+        log.info("LawyerStatisticsController.getLawyerDashboard?lawyerUserId={}", lawyerUserId);
+        if (lawyerUserId == null || lawyerUserId <= 0) {
+            log.warn("获取律师仪表板数据失败:律师用户ID为空或无效");
+            return R.fail("律师用户ID不能为空");
+        }
+        return lawyerStatisticsService.getLawyerDashboard(lawyerUserId);
+    }
+}
+

+ 424 - 0
alien-lawyer/src/main/java/shop/alien/lawyer/controller/LawyerStoreCommentController.java

@@ -0,0 +1,424 @@
+package shop.alien.lawyer.controller;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import io.swagger.annotations.*;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartRequest;
+import shop.alien.entity.result.R;
+import shop.alien.entity.store.StoreComment;
+import shop.alien.entity.store.dto.OrderReviewDto;
+import shop.alien.entity.store.dto.ReviewCommentRequestDto;
+import shop.alien.entity.store.dto.ReviewReplyDto;
+import shop.alien.entity.store.vo.LifeUserOrderCommentVo;
+import shop.alien.entity.store.vo.OrderReviewDetailVo;
+import shop.alien.entity.store.vo.OrderReviewVo;
+import shop.alien.entity.store.vo.ReviewCommentVo;
+import shop.alien.entity.store.vo.StoreCommentCountVo;
+import shop.alien.entity.store.vo.StoreCommentVo;
+import shop.alien.entity.store.vo.StoreCommitPercentVo;
+import shop.alien.lawyer.service.StoreCommentService;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * 评论表 前端控制器
+ *
+ * @author ssk
+ * @since 2025-01-02
+ */
+@Slf4j
+@Api(tags = {"二期-评论/评价"})
+@ApiSort(11)
+@CrossOrigin
+@RestController
+@RequestMapping("/storeComment")
+@RequiredArgsConstructor
+public class LawyerStoreCommentController {
+
+    private final StoreCommentService storeCommentService;
+
+    @ApiOperation("评论/评价列表")
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "pageNum", value = "页数", dataType = "Integer", paramType = "query", required = true),
+            @ApiImplicitParam(name = "pageSize", value = "页容", dataType = "Integer", paramType = "query", required = true),
+            @ApiImplicitParam(name = "businessId", value = "业务id", dataType = "Integer", paramType = "query"),
+            @ApiImplicitParam(name = "businessType", value = "业务类型(1:订单评论, 2:动态社区评论, 3:活动评论, 4:店铺打卡评论, 5:订单评价, 6:订单评论的评论)", dataType = "Integer", paramType = "query"),
+            @ApiImplicitParam(name = "storeId", value = "门店id", dataType = "Integer", paramType = "query"),
+            @ApiImplicitParam(name = "replyStatus", value = "回复状态(0:全部, 1:已回复, 2:未回复)", dataType = "Integer", paramType = "query"),
+            @ApiImplicitParam(name = "commentLevel", value = "评论等级(0:全部, 1:好评, 2:中评, 3:差评)", dataType = "Integer", paramType = "query"),
+            @ApiImplicitParam(name = "days", value = "查询时间, 多少天前", dataType = "Integer", paramType = "query"),
+            @ApiImplicitParam(name = "phoneId", value = "消息标识", dataType = "String", paramType = "query"),
+            @ApiImplicitParam(name = "userType", value = "评论的用户类型(0:商家, 其他:用户)", dataType = "String", paramType = "query"),
+            @ApiImplicitParam(name = "tagId", value = "标签id)", dataType = "Integer", paramType = "query"),
+            @ApiImplicitParam(name = "hasImage", value = "是否有图片(0否/1是)", dataType = "Boolean", paramType = "query")
+    })
+    @GetMapping("/getList")
+    public R<IPage<StoreCommentVo>> getList(Integer pageNum, Integer pageSize, Integer businessId, Integer businessType, Integer storeId, Integer replyStatus, Integer commentLevel, Integer days, String phoneId, Integer userType, Integer tagId,@RequestParam(required = false, defaultValue = "false") Boolean hasImage) {
+        log.info("StoreCommentController.getList?pageNum={}&pageSize={}&businessId={}&businessType={}&storeId={}&replyStatus={}&commentLevel={}&days={}&phoneId={}&userType={}&tagId={}&hasImage={}", pageNum, pageSize, businessId, businessType, storeId, replyStatus, commentLevel, days, phoneId, userType, tagId, hasImage);
+        return R.data(storeCommentService.getList(pageNum, pageSize, businessId, businessType, storeId, replyStatus, commentLevel, days, phoneId, userType, tagId, hasImage));
+    }
+
+    @ApiOperation("获取最新一条评论/评价")
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "businessId", value = "业务id", dataType = "String", paramType = "query"),
+            @ApiImplicitParam(name = "businessType", value = "业务类型(1:订单评论, 2:动态社区评论, 3:活动评论, 4:店铺打卡评论, 5:订单评价)", dataType = "String", paramType = "query"),
+            @ApiImplicitParam(name = "storeId", value = "门店id", dataType = "String", paramType = "query"),
+            @ApiImplicitParam(name = "userType", value = "用户类型(0:商家, 其他:用户)", dataType = "String", paramType = "query")
+    })
+    @GetMapping("/getOne")
+    public R<StoreCommentVo> getOne(Integer businessId, Integer businessType, Integer storeId) {
+        log.info("StoreCommentController.getOne?businessId={}&businessType={}&storeId={}", businessId, businessType, storeId);
+        return R.data(storeCommentService.getOne(businessId, businessType, storeId));
+    }
+
+    @ApiOperation("评论/评价数量和评分")
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "businessId", value = "业务id", dataType = "String", paramType = "query"),
+            @ApiImplicitParam(name = "businessType", value = "业务类型(1:订单评论, 2:动态社区评论, 3:活动评论, 4:店铺打卡评论, 5:订单评价)", dataType = "String", paramType = "query"),
+            @ApiImplicitParam(name = "storeId", value = "门店id", dataType = "String", paramType = "query")
+    })
+    @GetMapping("/getCommitCountAndScore")
+    public R<Map<String, Object>> getCommitCountAndScore(Integer businessId, Integer businessType, Integer storeId, String phoneId, Integer days) {
+        log.info("StoreCommentController.getCommitCountAndScore?businessId={}&businessType={}&storeId={}&phoneId={}&days={}", businessId, businessType, storeId, phoneId, days);
+        return R.data(storeCommentService.getCommitCountAndScore(businessId, businessType, storeId, phoneId, days));
+    }
+
+    @ApiOperation(value = "新增评论(旧)", notes = "0:成功, 1:失败, 2:文本内容异常, 3:图片内容异常")
+    @ApiImplicitParams({@ApiImplicitParam(name = "multipartRequest", value = "文件", dataType = "File", paramType = "query"),
+            @ApiImplicitParam(name = "businessId", value = "业务id", dataType = "Integer", paramType = "query"),
+            @ApiImplicitParam(name = "businessType", value = "业务类型(1:订单评论, 2:动态社区评论, 3:活动评论)", dataType = "Integer", paramType = "query"),
+            @ApiImplicitParam(name = "storeId", value = "门店id", dataType = "Integer", paramType = "query", required = true),
+            @ApiImplicitParam(name = "userId", value = "用户id", dataType = "Integer", paramType = "query", required = true),
+            @ApiImplicitParam(name = "replyId", value = "回复id", dataType = "Integer", paramType = "query"),
+            @ApiImplicitParam(name = "commentContent", value = "评论内容", dataType = "String", paramType = "query")
+    })
+    @PostMapping("/userComment")
+    public R<Integer> userComment(MultipartRequest multipartRequest,
+                                  Integer businessId,
+                                  Integer businessType,
+                                  Integer storeId,
+                                  Integer userId,
+                                  Integer replyId,
+                                  String commentContent) {
+        log.info("StoreCommentController.userComment?businessId={}&businessType={}&storeId={}&userId={}&replyId={}&commentContent={}", businessId, businessType, storeId, userId, replyId, commentContent);
+        Set<String> fileNameSet = multipartRequest.getMultiFileMap().keySet();
+        log.info(String.valueOf(fileNameSet.size()));
+        return R.data(storeCommentService.userComment(multipartRequest, businessId, businessType, storeId, userId, replyId, commentContent));
+    }
+
+    @ApiOperation(value = "新增或修改评论/评价", notes = "0:成功, 1:失败, 2:文本内容异常, 3:图片内容异常")
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "multipartRequest", value = "文件", dataType = "File", paramType = "query"),
+            @ApiImplicitParam(name = "id", value = "主键", dataType = "Integer", paramType = "query"),
+            @ApiImplicitParam(name = "businessId", value = "业务id", dataType = "Integer", paramType = "query"),
+            @ApiImplicitParam(name = "businessType", value = "业务类型(1:订单评论, 2:动态社区评论, 3:活动评论, 4:店铺打卡评论, 5:订单评价)", dataType = "Integer", paramType = "query"),
+            @ApiImplicitParam(name = "storeId", value = "门店id", dataType = "Integer", paramType = "query"),
+            @ApiImplicitParam(name = "userId", value = "用户id", dataType = "Integer", paramType = "query", required = true),
+            @ApiImplicitParam(name = "replyId", value = "回复id", dataType = "Integer", paramType = "query"),
+            @ApiImplicitParam(name = "commentContent", value = "评论内容", dataType = "String", paramType = "query"),
+            @ApiImplicitParam(name = "score", value = "评分", dataType = "Double", paramType = "query"),
+            @ApiImplicitParam(name = "otherScore", value = "其他评分", dataType = "String", paramType = "query"),
+            @ApiImplicitParam(name = "isAnonymous", value = "是否匿名(0:否(默认), 1:是)", dataType = "Integer", paramType = "query"),
+            @ApiImplicitParam(name = "evaluationTags", value = "评价标签", dataType = "String", paramType = "query"),
+            @ApiImplicitParam(name = "phoneId", value = "用户id", dataType = "String", paramType = "query")
+    })
+    @PostMapping("/saveComment")
+    public R<Integer> saveComment(MultipartRequest multipartRequest, Integer id, Integer businessId, Integer businessType, Integer storeId, Integer userId, Integer replyId, String commentContent, Double score, String otherScore, Integer isAnonymous, String evaluationTags, String phoneId) {
+        log.info("StoreCommentController.saveComment?id={}&businessId={}&businessType={}&storeId={}&userId={}&replyId={}&commentContent={}&score={}&otherScore={}&isAnonymous={}&evaluationTags={}&phoneId={}", id, businessId, businessType, storeId, userId, replyId, commentContent, score, otherScore, isAnonymous, evaluationTags, phoneId);
+        Set<String> fileNameSet = multipartRequest.getMultiFileMap().keySet();
+        log.info(String.valueOf(fileNameSet.size()));
+        return R.data(storeCommentService.addComment(multipartRequest, id, businessId, businessType, storeId, userId, replyId, commentContent, score, otherScore, isAnonymous, evaluationTags, phoneId));
+    }
+
+    @ApiOperation(value = "新增或修改评论/评价(new)", notes = "0:成功, 1:失败, 2:文本内容异常, 3:图片内容异常")
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "multipartRequest", value = "文件", dataType = "File", paramType = "query"),
+            @ApiImplicitParam(name = "id", value = "主键", dataType = "Integer", paramType = "query"),
+            @ApiImplicitParam(name = "businessId", value = "业务id", dataType = "Integer", paramType = "query"),
+            @ApiImplicitParam(name = "businessType", value = "业务类型(1:订单评论, 2:动态社区评论, 3:活动评论, 4:店铺打卡评论, 5:订单评价)", dataType = "Integer", paramType = "query"),
+            @ApiImplicitParam(name = "storeId", value = "门店id", dataType = "Integer", paramType = "query"),
+            @ApiImplicitParam(name = "orderId", value = "订单id", dataType = "Integer", paramType = "query"),
+            @ApiImplicitParam(name = "lawyerId", value = "律师id", dataType = "Integer", paramType = "query"),
+            @ApiImplicitParam(name = "userId", value = "用户id", dataType = "Integer", paramType = "query", required = true),
+            @ApiImplicitParam(name = "replyId", value = "回复id", dataType = "Integer", paramType = "query"),
+            @ApiImplicitParam(name = "commentContent", value = "评论内容", dataType = "String", paramType = "query"),
+            @ApiImplicitParam(name = "score", value = "评分", dataType = "Double", paramType = "query"),
+            @ApiImplicitParam(name = "otherScore", value = "其他评分", dataType = "String", paramType = "query"),
+            @ApiImplicitParam(name = "isAnonymous", value = "是否匿名(0:否(默认), 1:是)", dataType = "Integer", paramType = "query"),
+            @ApiImplicitParam(name = "evaluationTags", value = "评价标签", dataType = "String", paramType = "query"),
+            @ApiImplicitParam(name = "phoneId", value = "用户id", dataType = "String", paramType = "query")
+    })
+    @PostMapping("/saveCommentNew")
+    public R<Integer> saveCommentNew(MultipartRequest multipartRequest, Integer id, Integer businessId, Integer businessType, Integer storeId, Integer orderId, Integer lawyerId, Integer userId, Integer replyId, String commentContent, Double score, String otherScore, Integer isAnonymous, String evaluationTags, String phoneId) {
+        log.info("StoreCommentController.saveCommentNew?id={}&businessId={}&businessType={}&storeId={}&orderId={}&lawyerId={}&userId={}&replyId={}&commentContent={}&score={}&otherScore={}&isAnonymous={}&evaluationTags={}&phoneId={}", id, businessId, businessType, storeId, orderId, lawyerId, userId, replyId, commentContent, score, otherScore, isAnonymous, evaluationTags, phoneId);
+        Set<String> fileNameSet = multipartRequest.getMultiFileMap().keySet();
+        log.info(String.valueOf(fileNameSet.size()));
+        return R.data(storeCommentService.addCommentNew(multipartRequest, id, businessId, businessType, storeId, orderId, lawyerId, userId, replyId, commentContent, score, otherScore, isAnonymous, evaluationTags, phoneId));
+    }
+
+    @ApiOperation(value = "回复率, 评价比例")
+    @ApiImplicitParams({@ApiImplicitParam(name = "storeId", value = "门店id", dataType = "Integer", paramType = "query", required = true)})
+    @GetMapping("/getCommitPercent")
+    public R<StoreCommitPercentVo> getCommitPercent(@RequestParam("storeId") Integer storeId) {
+        return R.data(storeCommentService.getCommitPercent(storeId));
+    }
+
+    @ApiOperation(value = "删除评论/评价")
+    @ApiImplicitParams({@ApiImplicitParam(name = "id", value = "评论主键", dataType = "Integer", paramType = "query", required = true)})
+    @GetMapping("/delete")
+    public R<Boolean> delete(Integer id) {
+        if (storeCommentService.removeById(id)) {
+            return R.success("删除成功");
+        }
+        return R.fail("删除失败");
+    }
+
+    @ApiOperation("未评价/已评价列表")
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "pageNum", value = "页数", dataType = "Integer", paramType = "query", required = true),
+            @ApiImplicitParam(name = "pageSize", value = "页容", dataType = "Integer", paramType = "query", required = true),
+            @ApiImplicitParam(name = "type", value = "1:未评价, 2:已评价", dataType = "Integer", paramType = "query", required = true),
+            @ApiImplicitParam(name = "userId", value = "用户id", dataType = "String", paramType = "query", required = true),
+    })
+    @GetMapping("/getCommentOrderPage")
+    public R<IPage<LifeUserOrderCommentVo>> getCommentOrderPage(Integer pageNum, Integer pageSize, Integer type, String userId) {
+        log.info("StoreCommentController.getCommentOrderPage?pageNum={}&pageSize={}&type={}&userId={}", pageNum, pageSize, type, userId);
+        return R.data(storeCommentService.getCommentOrderPage(pageNum, pageSize, type, userId));
+    }
+
+    @ApiOperation("当前用户全部评价(店铺+律师)")
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "pageNum", value = "页数", dataType = "Integer", paramType = "query", required = true),
+            @ApiImplicitParam(name = "pageSize", value = "页容", dataType = "Integer", paramType = "query", required = true),
+            @ApiImplicitParam(name = "type", value = "预留字段,传1或2均可", dataType = "Integer", paramType = "query"),
+            @ApiImplicitParam(name = "userId", value = "用户id", dataType = "Integer", paramType = "query", required = true),
+    })
+    @GetMapping("/getUserAllCommentsPage")
+    public R<IPage<LifeUserOrderCommentVo>> getUserAllCommentsPage(Integer pageNum, Integer pageSize, Integer type, Integer userId) {
+        log.info("StoreCommentController.getUserAllCommentsPage?pageNum={}&pageSize={}&type={}&userId={}", pageNum, pageSize, type, userId);
+        return R.data(storeCommentService.getUserAllCommentsPage(pageNum, pageSize, userId));
+    }
+
+    @ApiOperation("获取店铺评价计数统计")
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "storeId", value = "门店id", dataType = "Integer", paramType = "query", required = true)
+    })
+    @GetMapping("/getAppraiseCount")
+    public R<StoreCommentCountVo> getAppraiseCount(@RequestParam("storeId") Integer storeId) {
+        log.info("StoreCommentController.getAppraiseCount?storeId={}", storeId);
+        return R.data(storeCommentService.getAppraiseCount(storeId));
+    }
+
+    // ==================== 订单评价相关接口(迁移自 OrderReviewController)====================
+
+    @ApiOperation("创建订单评价(只有订单用户才能评价)")
+    @ApiOperationSupport(order = 20)
+    @PostMapping("/orderReview/create")
+    public R<StoreComment> createOrderReview(@RequestBody OrderReviewDto reviewDto) {
+        log.info("StoreCommentController.createOrderReview?reviewDto={}", reviewDto);
+        if (reviewDto.getUserId() == null) {
+            return R.fail("用户未登录");
+        }
+        return storeCommentService.createOrderReview(reviewDto);
+    }
+
+    @ApiOperation("获取评价详情(包含评论和回复)")
+    @ApiOperationSupport(order = 21)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "orderId", value = "评价ID", dataType = "int", paramType = "query", required = true),
+            @ApiImplicitParam(name = "currentUserId", value = "用户ID", dataType = "int", paramType = "query", required = true)
+    })
+    @GetMapping("/orderReview/detail")
+    public R<OrderReviewDetailVo> getOrderReviewDetail(
+            @RequestParam(required = false) Integer orderId,
+            @RequestParam(required = false) Integer currentUserId) {
+        log.info("StoreCommentController.getOrderReviewDetail?reviewId={}, currentUserId={}", orderId, currentUserId);
+        if (orderId == null) {
+            return R.fail("订单id不为空");
+        }
+        return storeCommentService.getOrderReviewDetail(orderId, currentUserId);
+    }
+
+    @ApiOperation("点赞评价")
+    @ApiOperationSupport(order = 22)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "reviewId", value = "评价ID", dataType = "int", paramType = "query", required = true),
+            @ApiImplicitParam(name = "userId", value = "用户ID", dataType = "int", paramType = "query", required = true)
+    })
+    @PostMapping("/orderReview/like")
+    public R<Boolean> likeOrderReview(@RequestBody StoreComment storeComment) {
+        Integer reviewId = storeComment.getId();
+        Integer userId = storeComment.getUserId();
+        log.info("StoreCommentController.likeOrderReview?reviewId={}, userId={}", reviewId, userId);
+        if (userId == null) {
+            return R.fail("用户未登录");
+        }
+        if (reviewId == null) {
+            return R.fail("评价ID不能为空");
+        }
+        return storeCommentService.likeOrderReview(reviewId, userId);
+    }
+
+    @ApiOperation("取消点赞评价")
+    @ApiOperationSupport(order = 23)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "reviewId", value = "评价ID", dataType = "int", paramType = "query", required = true),
+            @ApiImplicitParam(name = "userId", value = "用户ID", dataType = "int", paramType = "query", required = true)
+    })
+    @PostMapping("/orderReview/cancelLike")
+    public R<Boolean> cancelLikeOrderReview(@RequestBody StoreComment storeComment) {
+        Integer reviewId = storeComment.getId();
+        Integer userId = storeComment.getUserId();
+        log.info("StoreCommentController.cancelLikeOrderReview?reviewId={}, userId={}", reviewId, userId);
+        if (userId == null) {
+            return R.fail("用户未登录");
+        }
+        return storeCommentService.cancelLikeOrderReview(reviewId, userId);
+    }
+
+    @ApiOperation("分页查询评价列表")
+    @ApiOperationSupport(order = 24)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "page", value = "页数(默认1)", dataType = "int", paramType = "query"),
+            @ApiImplicitParam(name = "size", value = "页容(默认10)", dataType = "int", paramType = "query"),
+            @ApiImplicitParam(name = "orderId", value = "订单ID", dataType = "int", paramType = "query"),
+            @ApiImplicitParam(name = "lawyerUserId", value = "律师用户ID", dataType = "int", paramType = "query"),
+            @ApiImplicitParam(name = "userId", value = "评价用户ID", dataType = "int", paramType = "query"),
+            @ApiImplicitParam(name = "currentUserId", value = "当前用户ID(用于判断是否已点赞)", dataType = "int", paramType = "query")
+    })
+    @GetMapping("/orderReview/list")
+    public R<IPage<OrderReviewVo>> getOrderReviewList(
+            @RequestParam(defaultValue = "1") int page,
+            @RequestParam(defaultValue = "10") int size,
+            @RequestParam(required = false) Integer orderId,
+            @RequestParam(required = false) Integer lawyerUserId,
+            @RequestParam(required = false) Integer userId,
+            @RequestParam(required = false) Integer currentUserId) {
+        log.info("StoreCommentController.getOrderReviewList?page={}, size={}, orderId={}, lawyerUserId={}, userId={}, currentUserId={}",
+                page, size, orderId, lawyerUserId, userId, currentUserId);
+        return storeCommentService.getOrderReviewList(page, size, orderId, lawyerUserId, userId, currentUserId);
+    }
+
+    @ApiOperation("根据订单ID查询评价")
+    @ApiOperationSupport(order = 25)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "orderId", value = "订单ID", dataType = "int", paramType = "query", required = true),
+            @ApiImplicitParam(name = "currentUserId", value = "当前用户ID(用于判断是否已点赞)", dataType = "int", paramType = "query")
+    })
+    @GetMapping("/orderReview/getByOrderId")
+    public R<OrderReviewVo> getOrderReviewByOrderId(
+            @RequestParam Integer orderId,
+            @RequestParam(required = false) Integer currentUserId) {
+        log.info("StoreCommentController.getOrderReviewByOrderId?orderId={}, currentUserId={}", orderId, currentUserId);
+        return storeCommentService.getOrderReviewByOrderId(orderId, currentUserId);
+    }
+
+    // ==================== 评价评论相关接口(迁移自 ReviewCommentController)====================
+
+    @ApiOperation("创建评论(其他用户对评价的评论)")
+    @ApiOperationSupport(order = 30)
+    @PostMapping("/reviewComment/create")
+    public R<StoreComment> createReviewComment(@RequestBody StoreComment comment) {
+        log.info("StoreCommentController.createReviewComment?comment={}", comment);
+        if (comment.getUserId() == null) {
+            return R.fail("用户未登录");
+        }
+        return storeCommentService.createReviewComment(comment);
+    }
+
+    @ApiOperation("根据评价ID查询评论列表")
+    @ApiOperationSupport(order = 31)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "reviewId", value = "评价ID", dataType = "int", paramType = "query", required = true),
+            @ApiImplicitParam(name = "currentUserId", value = "当前用户ID(用于判断是否已点赞)", dataType = "int", paramType = "query")
+    })
+    @GetMapping("/reviewComment/list")
+    public R<List<ReviewCommentVo>> getReviewCommentListByReviewId(
+            @RequestParam Integer reviewId,
+            @RequestParam(required = false) Integer currentUserId) {
+        log.info("StoreCommentController.getReviewCommentListByReviewId?reviewId={}, currentUserId={}", reviewId, currentUserId);
+        return storeCommentService.getReviewCommentListByReviewId(reviewId, currentUserId);
+    }
+
+    @ApiOperation("点赞评论")
+    @ApiOperationSupport(order = 32)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "commentId", value = "评论ID", dataType = "int", paramType = "query", required = true),
+            @ApiImplicitParam(name = "userId", value = "用户ID", dataType = "int", paramType = "query", required = true)
+    })
+    @PostMapping("/reviewComment/like")
+    public R<Boolean> likeReviewComment(@RequestBody ReviewCommentRequestDto requestDto) {
+        log.info("StoreCommentController.likeReviewComment?commentId={}, userId={}", requestDto.getCommentId(), requestDto.getUserId());
+        if (requestDto.getUserId() == null) {
+            return R.fail("用户未登录");
+        }
+        if (requestDto.getCommentId() == null) {
+            return R.fail("评论ID不能为空");
+        }
+        return storeCommentService.likeReviewComment(requestDto);
+    }
+
+    @ApiOperation("取消点赞评论")
+    @ApiOperationSupport(order = 33)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "commentId", value = "评论ID", dataType = "int", paramType = "query", required = true),
+            @ApiImplicitParam(name = "userId", value = "用户ID", dataType = "int", paramType = "query", required = true)
+    })
+    @PostMapping("/reviewComment/cancelLike")
+    public R<Boolean> cancelLikeReviewComment(@RequestBody ReviewCommentRequestDto requestDto) {
+        log.info("StoreCommentController.cancelLikeReviewComment?commentId={}, userId={}", requestDto.getCommentId(), requestDto.getUserId());
+        if (requestDto.getUserId() == null) {
+            return R.fail("用户未登录");
+        }
+        if (requestDto.getCommentId() == null) {
+            return R.fail("评论ID不能为空");
+        }
+        return storeCommentService.cancelLikeReviewComment(requestDto);
+    }
+
+    @ApiOperation("创建回复(用户对评论的回复)")
+    @ApiOperationSupport(order = 34)
+    @PostMapping("/reviewComment/reply/create")
+    public R<StoreComment> createReviewReply(@RequestBody ReviewReplyDto replyDto) {
+        log.info("StoreCommentController.createReviewReply?replyDto={}", replyDto);
+        if (replyDto.getUserId() == null) {
+            return R.fail("用户未登录");
+        }
+        return storeCommentService.createReviewReply(replyDto);
+    }
+
+    @ApiOperation("根据首评ID查询回复列表")
+    @ApiOperationSupport(order = 35)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "headId", value = "首评ID", dataType = "int", paramType = "query", required = true),
+            @ApiImplicitParam(name = "currentUserId", value = "当前用户ID(用于判断是否已点赞)", dataType = "int", paramType = "query")
+    })
+    @GetMapping("/reviewComment/reply/list")
+    public R<List<ReviewCommentVo>> getReviewReplyListByHeadId(
+            @RequestParam Integer headId,
+            @RequestParam(required = false) Integer currentUserId) {
+        log.info("StoreCommentController.getReviewReplyListByHeadId?headId={}, currentUserId={}", headId, currentUserId);
+        return storeCommentService.getReviewReplyListByHeadId(headId, currentUserId);
+    }
+
+    @ApiOperation("删除回复")
+    @ApiOperationSupport(order = 36)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "replyId", value = "回复ID", dataType = "int", paramType = "query", required = true),
+            @ApiImplicitParam(name = "userId", value = "用户ID", dataType = "int", paramType = "query", required = true)
+    })
+    @PostMapping("/reviewComment/reply/delete")
+    public R<Boolean> deleteReviewReply(
+            @RequestParam Integer replyId,
+            @RequestParam Integer userId) {
+        log.info("StoreCommentController.deleteReviewReply?replyId={}, userId={}", replyId, userId);
+        if (userId == null) {
+            return R.fail("用户未登录");
+        }
+        return storeCommentService.deleteReviewReply(replyId, userId);
+    }
+}

+ 9 - 0
alien-lawyer/src/main/java/shop/alien/lawyer/controller/LawyerUserController.java

@@ -8,6 +8,7 @@ import org.springframework.web.bind.annotation.*;
 import shop.alien.entity.result.R;
 import shop.alien.entity.store.LawyerUser;
 import shop.alien.entity.store.dto.LawyerRecommendedDto;
+import shop.alien.entity.store.dto.LawyerUserDto;
 import shop.alien.entity.store.vo.LawyerUserVo;
 import shop.alien.lawyer.service.LawyerUserService;
 import shop.alien.util.myBaticsPlus.QueryBuilder;
@@ -332,5 +333,13 @@ public class LawyerUserController {
         return lawyerUserService.isRecommended(request.getLawyerId(), request.getIsRecommended());
     }
 
+    @ApiOperation("编辑律师收费")
+    @ApiOperationSupport(order = 19)
+    @PostMapping("/updateLawyerUserCharge")
+    public R<LawyerUserVo> updateLawyerUserCharge(@RequestBody LawyerUserDto lawyerUserDto) {
+        log.info("LawyerUserController.updateLawyerUserCharge?lawyerUserDto={}", lawyerUserDto);
+        return lawyerUserService.updateLawyerUserCharge(lawyerUserDto);
+    }
+
 }
 

+ 71 - 0
alien-lawyer/src/main/java/shop/alien/lawyer/controller/LawyerUserViolationController.java

@@ -11,6 +11,7 @@ import shop.alien.entity.store.LawyerUserViolation;
 import shop.alien.entity.store.StoreDictionary;
 import shop.alien.entity.store.dto.LawyerUserViolationDto;
 import shop.alien.entity.store.vo.LawyerUserViolationVo;
+import shop.alien.entity.store.vo.LawyerViolationDetailVO;
 import shop.alien.lawyer.service.LawyerUserViolationService;
 
 import java.util.List;
@@ -223,5 +224,75 @@ public class LawyerUserViolationController {
         }
     }
 
+    /**
+     * 根据订单ID查询举报详情
+     * <p>
+     * 查询举报详情信息,包括举报的详情信息、订单的详情信息以及订单律师名字相关信息
+     * </p>
+     * <p>
+     * 功能说明:
+     * 1. 通过订单ID查询订单、举报、律师关联信息
+     * 2. 校验订单是否存在以及是否存在举报记录
+     * 3. 填充举报人姓名和订单价格字符串等附加信息
+     * 4. 返回完整的举报详情信息
+     * </p>
+     *
+     * @param orderId 订单ID,必须大于0
+     * @return 统一响应结果,包含举报详情VO对象(包含举报信息、订单信息、律师信息)
+     * @author system
+     * @since 2025-01-XX
+     */
+    @ApiOperation(value = "根据订单ID查询举报详情", notes = "查询举报详情信息,包括举报的详情信息、订单的详情信息以及订单律师名字相关信息")
+    @ApiOperationSupport(order = 9)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "orderId", value = "订单ID", dataType = "Integer", paramType = "query", required = true)
+    })
+    @GetMapping("/getViolationDetailByOrderId")
+    public R<LawyerViolationDetailVO> getViolationDetailByOrderId(
+            @RequestParam(value = "orderId", required = true) Integer orderId) {
+        log.info("根据订单ID查询举报详情请求开始,orderId={}", orderId);
+
+        // 参数校验
+        if (!validateOrderId(orderId)) {
+            log.warn("根据订单ID查询举报详情失败:订单ID为空或无效,orderId={}", orderId);
+            return R.fail("订单ID不能为空");
+        }
+
+        try {
+            LawyerViolationDetailVO detailVO = lawyerUserViolationService.getViolationDetailByOrderId(orderId);
+            if (detailVO == null) {
+                log.warn("根据订单ID查询举报详情:订单不存在或未举报,orderId={}", orderId);
+                return R.fail("订单不存在或未举报");
+            }
+
+            log.info("根据订单ID查询举报详情成功,orderId={}, violationId={}, orderNumber={}",
+                    orderId, detailVO.getViolationId(), detailVO.getOrderNumber());
+            return R.data(detailVO);
+
+        } catch (IllegalArgumentException e) {
+            log.warn("根据订单ID查询举报详情参数校验失败,orderId={},错误信息:{}", orderId, e.getMessage());
+            return R.fail(e.getMessage());
+        } catch (RuntimeException e) {
+            log.warn("根据订单ID查询举报详情业务异常,orderId={},异常信息:{}", orderId, e.getMessage());
+            return R.fail(e.getMessage());
+        } catch (Exception e) {
+            log.error("根据订单ID查询举报详情异常,orderId={},异常信息:{}", orderId, e.getMessage(), e);
+            return R.fail("查询举报详情失败,请稍后重试");
+        }
+    }
+
+    /**
+     * 校验订单ID
+     * <p>
+     * 检查订单ID是否为空或无效
+     * </p>
+     *
+     * @param orderId 订单ID
+     * @return true表示有效,false表示无效
+     */
+    private boolean validateOrderId(Integer orderId) {
+        return orderId != null && orderId > 0;
+    }
+
 }
 

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

@@ -1,5 +1,6 @@
 package shop.alien.lawyer.controller;
 
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import io.swagger.annotations.*;
 import lombok.RequiredArgsConstructor;
@@ -59,6 +60,27 @@ public class OrderReviewController {
         return orderReviewService.getReviewDetail(reviewId, currentUserId);
     }
 
+    @ApiOperation("获取评价")
+    @ApiOperationSupport(order = 4)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "orderId", value = "订单ID", dataType = "int", paramType = "query", required = true),
+            @ApiImplicitParam(name = "currentUserId", value = "用户ID只能删除自己的评价)", dataType = "int", paramType = "query")
+    })
+    @GetMapping("/select/reviewIdByOrderId")
+    public R<OrderReviewDetailVo> reviewIdByOrderId(
+            @RequestParam Integer orderId,
+            @RequestParam(required = false) Integer currentUserId) {
+        log.info("OrderReviewController.getReviewDetail?reviewId={}, currentUserId={}", orderId, currentUserId);
+        QueryWrapper<OrderReview> queryWrapper = new QueryWrapper<>();
+        queryWrapper.eq("order_id", orderId);
+        queryWrapper.eq("delete_flag", 0);
+        OrderReview review = orderReviewService.getOne(queryWrapper);
+        if (review == null) {
+            return R.fail("评价ID不能为空");
+        }
+        return orderReviewService.getReviewDetail(review.getId(), currentUserId);
+    }
+
     @ApiOperation("点赞评价")
     @ApiOperationSupport(order = 8)
     @ApiImplicitParams({
@@ -161,7 +183,7 @@ public class OrderReviewController {
     }
 
 
-    @ApiOperation("分页查询我的评价列表")
+    @ApiOperation("分页查询待评价的订单列表(返回未评论的订单信息以及律师信息)")
     @ApiOperationSupport(order = 7)
     @ApiImplicitParams({
             @ApiImplicitParam(name = "page", value = "页数(默认1)", dataType = "int", paramType = "query"),

+ 92 - 0
alien-lawyer/src/main/java/shop/alien/lawyer/controller/PaymentController.java

@@ -0,0 +1,92 @@
+package shop.alien.lawyer.controller;
+
+
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiImplicitParam;
+import io.swagger.annotations.ApiImplicitParams;
+import io.swagger.annotations.ApiOperation;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.CrossOrigin;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import shop.alien.entity.result.R;
+import shop.alien.lawyer.payment.PaymentStrategyFactory;
+
+
+import java.util.Map;
+
+/**
+ * @author lyx
+ * @version 1.0
+ * @date 2025/11/20 17:34
+ */
+@Slf4j
+@Api(tags = {"2.5-支付接口"})
+@CrossOrigin
+@RestController
+@RequestMapping("/payment")
+@RequiredArgsConstructor
+public class PaymentController {
+
+    private final PaymentStrategyFactory paymentStrategyFactory;
+
+
+    @ApiOperation("创建预支付订单")
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "price", value = "订单金额", required = true, paramType = "query", dataType = "String"),
+            @ApiImplicitParam(name = "subject", value = "订单标题", required = true, paramType = "query", dataType = "String"),
+            @ApiImplicitParam(name = "payType", value = "支付类型(alipay:支付宝, wechatPay:微信支付)", required = true, paramType = "query", dataType = "String")
+    })
+    @RequestMapping("/prePay")
+    public R prePay(String price, String subject, String payType) {
+        log.info("PaymentController:prePay, price: {}, subject: {}, payType: {}", price, subject, payType);
+        try {
+            return paymentStrategyFactory.getStrategy(payType).createPrePayOrder(price, subject);
+        } catch (Exception e) {
+            return R.fail(e.getMessage());
+        }
+    }
+
+    /**
+     * 通知接口 之后可能会用
+     * @param notifyData
+     * @return
+     */
+    @RequestMapping("/notify")
+    public R notify(String notifyData) {
+        return  null;
+    }
+
+    /**
+     * 查询订单状态
+     * @param transactionId 交易订单号(微信支付订单号/商户订单号)
+     * @param payType 支付类型
+     * @param id 订单id
+     * @param id 订单str
+     * @return 订单状态信息
+     */
+    @RequestMapping("/searchOrderByOutTradeNoPath")
+    public R searchOrderByOutTradeNoPath(String transactionId, String payType ,Integer id ,String orderStr) {
+        try {
+            return paymentStrategyFactory.getStrategy(payType).searchOrderByOutTradeNoPath(transactionId,id,orderStr);
+        } catch (Exception e) {
+            return R.fail(e.getMessage());
+        }
+    }
+
+    /**
+     * 退款接口
+     * @param params 退款参数(包含订单号、退款金额等)
+     * @return 退款结果
+     */
+    @RequestMapping("/refunds")
+    public R refunds(@RequestBody Map<String, String> params) {
+        try {
+            return R.data(paymentStrategyFactory.getStrategy(params.get("payType")).handleRefund(params));
+        } catch (Exception e) {
+            return R.fail(e.getMessage());
+        }
+    }
+}

+ 59 - 4
alien-lawyer/src/main/java/shop/alien/lawyer/controller/ReviewCommentController.java

@@ -6,6 +6,7 @@ import lombok.extern.slf4j.Slf4j;
 import org.springframework.web.bind.annotation.*;
 import shop.alien.entity.result.R;
 import shop.alien.entity.store.ReviewComment;
+import shop.alien.entity.store.dto.LawyerReplyDto;
 import shop.alien.entity.store.dto.ReviewCommentRequestDto;
 import shop.alien.entity.store.dto.ReviewReplyDto;
 import shop.alien.entity.store.vo.ReviewCommentVo;
@@ -30,14 +31,38 @@ public class ReviewCommentController {
 
     private final ReviewCommentService reviewCommentService;
 
-    @ApiOperation("创建评论(其他用户对评价的评论)")
+    @ApiOperation("创建评论(用户对评价的评论,支持普通用户和律师)")
     @ApiOperationSupport(order = 1)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "reviewId", value = "评价ID", dataType = "int", paramType = "body", required = true),
+            @ApiImplicitParam(name = "sendUserId", value = "发送用户ID(评论用户ID,如果传入userId则自动映射到sendUserId)", dataType = "int", paramType = "body", required = true),
+            @ApiImplicitParam(name = "userId", value = "用户ID(登录用户ID,会自动映射到sendUserId,如果sendUserId已存在则优先使用sendUserId)", dataType = "int", paramType = "body", required = false),
+            @ApiImplicitParam(name = "receiveUserId", value = "接收用户ID(可选,不填时默认为评价的创建者)", dataType = "int", paramType = "body", required = false),
+            @ApiImplicitParam(name = "sendUserType", value = "发送用户类型:1-普通用户,2-律师(必填)", dataType = "int", paramType = "body", required = true),
+            @ApiImplicitParam(name = "receiveUserType", value = "接收用户类型:1-普通用户,2-律师(必填)", dataType = "int", paramType = "body", required = true),
+            @ApiImplicitParam(name = "commentContent", value = "评论内容", dataType = "String", paramType = "body", required = true)
+    })
     @PostMapping("/create")
     public R<ReviewComment> createComment(@RequestBody ReviewComment comment) {
         log.info("ReviewCommentController.createComment?comment={}", comment);
-        if (comment.getUserId() == null) {
-            return R.fail("用户未登录");
+        
+        // 参数校验:优先使用sendUserId,如果不存在则使用userId
+        if (comment.getSendUserId() == null) {
+            if (comment.getUserId() == null) {
+                return R.fail("用户ID不能为空");
+            }
+            // 将userId映射到sendUserId
+            comment.setSendUserId(comment.getUserId());
         }
+        
+        // 校验用户类型字段:必须传入
+        if (comment.getSendUserType() == null) {
+            return R.fail("发送用户类型不能为空");
+        }
+        if (comment.getReceiveUserType() == null) {
+            return R.fail("接收用户类型不能为空");
+        }
+        
         return reviewCommentService.createComment(comment);
     }
 
@@ -103,12 +128,30 @@ public class ReviewCommentController {
 
     @ApiOperation("创建回复(用户对评论的回复)")
     @ApiOperationSupport(order = 6)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "commentId", value = "首评ID", dataType = "int", paramType = "body", required = true),
+            @ApiImplicitParam(name = "userId", value = "用户ID(发送用户ID)", dataType = "int", paramType = "body", required = true),
+            @ApiImplicitParam(name = "replyToUserId", value = "被回复用户ID(可选,不填时默认为首评的发送用户)", dataType = "int", paramType = "body", required = false),
+            @ApiImplicitParam(name = "sendUserType", value = "发送用户类型:1-普通用户,2-律师(必填)", dataType = "int", paramType = "body", required = true),
+            @ApiImplicitParam(name = "receiveUserType", value = "接收用户类型:1-普通用户,2-律师(必填)", dataType = "int", paramType = "body", required = true),
+            @ApiImplicitParam(name = "replyContent", value = "回复内容", dataType = "String", paramType = "body", required = true)
+    })
     @PostMapping("/reply/create")
     public R<ReviewComment> createReply(@RequestBody ReviewReplyDto replyDto) {
         log.info("ReviewCommentController.createReply?replyDto={}", replyDto);
+        
         if (replyDto.getUserId() == null) {
-            return R.fail("用户未登录");
+            return R.fail("用户ID不能为空");
+        }
+        
+        // 校验用户类型字段:必须传入
+        if (replyDto.getSendUserType() == null) {
+            return R.fail("发送用户类型不能为空");
         }
+        if (replyDto.getReceiveUserType() == null) {
+            return R.fail("接收用户类型不能为空");
+        }
+        
         return reviewCommentService.createReply(replyDto);
     }
 
@@ -155,6 +198,18 @@ public class ReviewCommentController {
         return reviewCommentService.deleteReviewComment(reviewComment);
     }
 
+    @Deprecated
+    @ApiOperation(value = "律师回复(律师对用户的评价或评论的回复)", notes = "【已废弃】请使用 createComment(回复评价)或 createReply(回复评论)接口,通过 sendUserType=2 标识律师身份,系统会自动进行权限校验")
+    @ApiOperationSupport(order = 12)
+    @PostMapping("/lawyer/reply")
+    public R<ReviewComment> lawyerReply(@RequestBody LawyerReplyDto replyDto) {
+        log.warn("【废弃接口】ReviewCommentController.lawyerReply 已废弃,请使用 createComment 或 createReply 接口");
+        log.info("ReviewCommentController.lawyerReply?replyDto={}", replyDto);
+        if (replyDto.getLawyerUserId() == null) {
+            return R.fail("律师用户ID不能为空");
+        }
+        return reviewCommentService.lawyerReply(replyDto);
+    }
 
 }
 

+ 61 - 0
alien-lawyer/src/main/java/shop/alien/lawyer/payment/PaymentStrategy.java

@@ -0,0 +1,61 @@
+package shop.alien.lawyer.payment;
+
+
+import shop.alien.entity.result.R;
+
+import java.util.Map;
+
+/**
+ * 支付策略接口
+ *
+ * @author lyx
+ * @date 2025/11/20
+ */
+public interface PaymentStrategy {
+
+    /** 生成预支付订单
+     *
+     * @param price 订单金额
+     * @param subject 订单标题
+     * @return 预支付订单信息
+     * @throws Exception 生成异常
+     */
+    R createPrePayOrder(String price, String subject) throws Exception;
+
+
+    /**
+     * 处理支付通知
+     *
+     * @param notifyData 支付通知数据
+     * @return 处理结果
+     * @throws Exception 处理异常
+     */
+    R handleNotify(String notifyData) throws Exception;
+
+     /**
+     * 查询订单状态
+     *
+     * @param transactionId 交易订单号
+     * @return 订单状态信息
+     * @throws Exception 查询异常
+     */
+    R searchOrderByOutTradeNoPath(String transactionId,Integer id,String orderStr) throws Exception;
+
+     /**
+     * 处理退款请求
+     *
+     * @param params 退款请求参数
+     * @return 处理结果
+     * @throws Exception 处理异常
+     */
+     String handleRefund(Map<String,String> params) throws Exception;
+
+
+
+    /**
+     * 获取策略类型字符串
+     *
+     * @return 策略类型字符串
+     */
+    String getType();
+}

+ 64 - 0
alien-lawyer/src/main/java/shop/alien/lawyer/payment/PaymentStrategyFactory.java

@@ -0,0 +1,64 @@
+package shop.alien.lawyer.payment;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.PostConstruct;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 支付策略工厂
+ *
+ * @author lyx
+ * @date 2025/11/20
+ */
+@Slf4j
+@Component
+public class PaymentStrategyFactory {
+
+    @Autowired
+    private List<PaymentStrategy> paymentStrategies;
+
+    private final Map<String, PaymentStrategy> strategyMap = new HashMap<>();
+
+        /**
+         * 初始化策略映射
+         */
+        @PostConstruct
+        public void init() {
+            if (paymentStrategies != null && !paymentStrategies.isEmpty()) {
+                for (PaymentStrategy strategy : paymentStrategies) {
+                    strategyMap.put(strategy.getType(), strategy);
+                    log.info("注册支付策略: {} -> {}", strategy.getType(), strategy.getClass().getSimpleName());
+                }
+            }
+        }
+
+    /**
+     * 根据类型获取OCR策略
+     *
+     * @param type OCR类型
+     * @return OCR策略实例
+     * @throws IllegalArgumentException 如果类型不存在
+     */
+    public PaymentStrategy getStrategy(String type) {
+        PaymentStrategy strategy = strategyMap.get(type);
+        if (strategy == null) {
+            throw new IllegalArgumentException("不支持的支付类型: " + type);
+        }
+        return strategy;
+    }
+
+    /**
+     * 检查是否支持指定的OCR类型
+     *
+     * @param type OCR类型
+     * @return 是否支持
+     */
+    public boolean supports(String type) {
+        return strategyMap.containsKey(type);
+    }
+}

+ 602 - 0
alien-lawyer/src/main/java/shop/alien/lawyer/payment/impl/AlipayPaymentStrategyImpl.java

@@ -0,0 +1,602 @@
+package shop.alien.lawyer.payment.impl;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import com.alipay.api.AlipayApiException;
+import com.alipay.api.AlipayClient;
+import com.alipay.api.AlipayConfig;
+import com.alipay.api.DefaultAlipayClient;
+import com.alipay.api.domain.AlipayTradeAppPayModel;
+import com.alipay.api.domain.AlipayTradeRefundModel;
+import com.alipay.api.request.AlipayTradeAppPayRequest;
+import com.alipay.api.request.AlipayTradeQueryRequest;
+import com.alipay.api.request.AlipayTradeRefundRequest;
+import com.alipay.api.response.AlipayTradeAppPayResponse;
+import com.alipay.api.response.AlipayTradeQueryResponse;
+import com.alipay.api.response.AlipayTradeRefundResponse;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.ibatis.jdbc.Null;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+import shop.alien.entity.result.R;
+import shop.alien.entity.store.LawyerConsultationOrder;
+import shop.alien.entity.store.RefundRecord;
+import shop.alien.entity.store.StoreAliPayRefundLog;
+import shop.alien.mapper.LawyerConsultationOrderMapper;
+import shop.alien.lawyer.payment.PaymentStrategy;
+import shop.alien.lawyer.service.RefundRecordService;
+import shop.alien.lawyer.service.StoreAliPayRefundLogService;
+import shop.alien.util.common.UniqueRandomNumGenerator;
+import shop.alien.util.common.UrlEncode;
+import shop.alien.util.common.constant.PaymentEnum;
+import shop.alien.util.system.OSUtil;
+
+import java.math.BigDecimal;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Map;
+
+/**
+ * 支付宝支付策略
+ *
+ * @author lyx
+ * @date 2025/11/20
+ */
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class AlipayPaymentStrategyImpl implements PaymentStrategy {
+
+    /**
+     * 商家端appId
+     */
+    @Value("${payment.aliPay.business.appId}")
+    private String businessAppId;
+
+    /**
+     * 商家端app私钥
+     */
+    @Value("${payment.aliPay.business.appPrivateKey}")
+    private String businessAppPrivateKey;
+
+    /**
+     * 商家端app公钥
+     */
+    @Value("${payment.aliPay.business.appPublicKey}")
+    private String businessAppPublicKey;
+
+    /**
+     * windows应用公钥证书文件路径
+     */
+    @Value("${payment.aliPay.business.win.appCertPath}")
+    private String businessWinAppCertPath;
+
+    /**
+     * windows支付宝公钥证书文件路径
+     */
+    @Value("${payment.aliPay.business.win.alipayPublicCertPath}")
+    private String businessWinAlipayPublicCertPath;
+
+    /**
+     * windows支付宝根证书文件路径
+     */
+    @Value("${payment.aliPay.business.win.alipayRootCertPath}")
+    private String businessWinAlipayRootCertPath;
+
+    /**
+     * linux应用公钥证书文件路径
+     */
+    @Value("${payment.aliPay.business.linux.appCertPath}")
+    private String businessLinuxAppCertPath;
+
+    /**
+     * linux支付宝公钥证书文件路径
+     */
+    @Value("${payment.aliPay.business.linux.alipayPublicCertPath}")
+    private String businessLinuxAlipayPublicCertPath;
+
+    /**
+     * linux支付宝根证书文件路径
+     */
+    @Value("${payment.aliPay.business.linux.alipayRootCertPath}")
+    private String businessLinuxAlipayRootCertPath;
+
+    /**
+     * 支对称加密算法密钥
+     */
+    @Value("${ali.aes.encryptKey}")
+    private String encryptKey;
+    /**
+     * 支付宝网关地址
+     */
+    @Value("${payment.aliPay.host}")
+    private String aliPayApiHost;
+
+    private final StoreAliPayRefundLogService storeAliPayRefundLogService;
+
+    private final RefundRecordService refundRecordService;
+
+    private final LawyerConsultationOrderMapper lawyerConsultationOrderMapper;
+
+
+    /**
+     * 填写阿里配置
+     *
+     * @return AlipayConfig
+     */
+    private AlipayConfig setAlipayConfig(String type) {
+        AlipayConfig alipayConfig = new AlipayConfig();
+        alipayConfig.setServerUrl(aliPayApiHost);
+        alipayConfig.setAppId(businessAppId);
+        alipayConfig.setPrivateKey(businessAppPrivateKey);
+        alipayConfig.setFormat("json");
+        alipayConfig.setCharset("UTF-8");
+        alipayConfig.setSignType("RSA2");
+        if ("windows".equals(OSUtil.getOsName())) {
+            alipayConfig.setAppCertPath(businessWinAppCertPath);
+            alipayConfig.setAlipayPublicCertPath(businessWinAlipayPublicCertPath);
+            alipayConfig.setRootCertPath(businessWinAlipayRootCertPath);
+        } else {
+            alipayConfig.setAppCertPath(businessLinuxAppCertPath);
+            alipayConfig.setAlipayPublicCertPath(businessLinuxAlipayPublicCertPath);
+            alipayConfig.setRootCertPath(businessLinuxAlipayRootCertPath);
+        }
+        if ("aes".equals(type)) {
+            alipayConfig.setEncryptType("AES");
+            alipayConfig.setEncryptKey(encryptKey);
+        }
+        return alipayConfig;
+    }
+
+    @Override
+    public R createPrePayOrder(String price, String subject) throws Exception {
+        log.info("创建支付宝预支付订单,价格:{},描述:{}", price, subject);
+        // 参数验证
+        if (price == null || price.trim().isEmpty()) {
+            return R.fail("价格不能为空");
+        }
+        if (subject == null || subject.trim().isEmpty()) {
+            return R.fail("订单描述不能为空");
+        }
+        try {
+            BigDecimal amount = new BigDecimal(price);
+            if (amount.compareTo(BigDecimal.ZERO) <= 0) {
+                return R.fail("价格必须大于0");
+            }
+        } catch (NumberFormatException e) {
+            return R.fail("价格格式不正确");
+        }
+        // 生成订单号
+        String orderNo = UniqueRandomNumGenerator.generateUniqueCode(19);
+        // 构建预支付请求
+        AlipayTradeAppPayRequest request = buildPrePayRequest(price, subject,orderNo);
+        
+        try {
+            // 调用支付宝接口创建预支付订单
+            JSONObject result = prePayOrderRun(request);
+            result.put("orderNo", orderNo);
+            log.info("支付宝预支付订单创建成功,商家订单号:{}", orderNo);
+            return R.data(result);
+        } catch (AlipayApiException e) {
+            log.error("支付宝预支付失败,错误码:{},错误信息:{}", e.getErrCode(), e.getErrMsg());
+            return R.fail("支付宝预支付失败:" + e.getErrMsg());
+        }
+    }
+
+    @Override
+    public R<Object> handleNotify(String notifyData) throws Exception {
+        log.info("处理支付宝支付通知,通知数据:{}", notifyData);
+        // TODO: 实现支付通知处理逻辑
+        // 1. 验证签名
+        // 2. 解析通知数据
+        // 3. 更新订单状态
+        // 4. 返回处理结果
+        return R.fail("功能尚未实现");
+    }
+
+    @Override
+    public R<Object> searchOrderByOutTradeNoPath(String transactionId,Integer id ,String orderStr) throws Exception {
+        try {
+
+            //根据id把transactionId和orderStr更新到lawyer_consultation_order表中
+
+            LambdaUpdateWrapper<LawyerConsultationOrder> updateWrapper = new LambdaUpdateWrapper<>();
+            updateWrapper.eq(LawyerConsultationOrder::getId, id);
+            updateWrapper.set(LawyerConsultationOrder::getAlipayNo, transactionId);
+            updateWrapper.set(LawyerConsultationOrder::getOrderStr, orderStr);
+
+            int a=lawyerConsultationOrderMapper.update(null, updateWrapper);
+
+            if (a>0){
+                log.info("更新订单成功");
+            }
+
+            AlipayClient alipayClient = new DefaultAlipayClient(setAlipayConfig(null));
+            // 2. 构造查询请求
+            AlipayTradeQueryRequest request = new AlipayTradeQueryRequest();
+
+            // 设置biz_content(JSON格式)
+            JSONObject bizContent = new JSONObject();
+            if (StringUtils.isNotBlank(transactionId)) {
+                bizContent.put("out_trade_no", transactionId);
+            }
+//            if (StringUtils.isNotBlank(tradeNo)) {
+//                bizContent.put("trade_no", tradeNo);
+//            }
+            request.setBizContent(bizContent.toJSONString());
+
+            // 3. 调用接口并获取响应
+            AlipayTradeQueryResponse response = alipayClient.certificateExecute(request);
+            if (response.isSuccess()) {
+                // 4. 解析支付状态
+                String tradeStatus = response.getTradeStatus();
+                log.info("订单查询成功,支付状态:" + tradeStatus);
+
+                // 支付状态说明:
+                // WAIT_BUYER_PAY:等待买家付款
+                // TRADE_CLOSED:交易关闭(超时未支付/已取消)
+                // TRADE_SUCCESS:支付成功(即时到账/普通转账)
+                // TRADE_FINISHED:交易完成(不可退款的场景,如即时到账)
+                if ("TRADE_SUCCESS".equals(tradeStatus)) {
+                    return R.success("支付成功");
+                } else if ("TRADE_CLOSED".equals(tradeStatus)) {
+                    return R.fail("交易已关闭");
+                } else if ("WAIT_BUYER_PAY".equals(tradeStatus)) {
+                    return R.fail("等待买家付款");
+                } else if ("TRADE_FINISHED".equals(tradeStatus)) {
+                    return R.success("交易完成");
+                }
+                return R.success("订单状态:" + tradeStatus);
+            } else {
+                return R.fail("订单查询失败:" + response.getMsg() + "(" + response.getSubMsg() + ")");
+            }
+        } catch (AlipayApiException e) {
+            log.error("支付宝订单查询异常", e);
+            return R.fail("查询异常:" + e.getMessage());
+        }
+
+    }
+
+    @Override
+    public String handleRefund(Map<String, String> params) throws Exception {
+        log.info("处理支付宝退款请求,参数:{}", params);
+        try {
+            AlipayTradeRefundRequest request = buildRefundRequest(params);
+            AlipayTradeRefundResponse response = refundRun(request);
+            String refundReslut = "";
+            JSONObject responseBody = JSONObject.parseObject(response.getBody());
+            JSONObject refundResponse = responseBody.getJSONObject("alipay_trade_refund_response");
+            
+            if (response.isSuccess()) {
+                refundReslut = "调用成功";
+                // 保存退款信息进入到支付宝退款记录表(保留原有逻辑)
+                StoreAliPayRefundLog refundLog = new StoreAliPayRefundLog();
+                // 响应基本信息
+                refundLog.setResponseCode(refundResponse.getString("code"));
+                refundLog.setResponseMsg(refundResponse.getString("msg"));
+                // 买家信息
+                refundLog.setBuyerLogonId(refundResponse.getString("buyer_logon_id"));
+                refundLog.setBuyerOpenId(refundResponse.getString("buyer_open_id"));
+                // 资金变动信息
+                refundLog.setFundChange(refundResponse.getString("fund_change"));
+                // 订单信息
+                refundLog.setOutTradeNo(refundResponse.getString("out_trade_no"));
+                refundLog.setTradeNo(refundResponse.getString("trade_no"));
+                // 退款金额信息
+                refundLog.setRefundFee(refundResponse.getString("refund_fee"));
+                refundLog.setSendBackFee(refundResponse.getString("send_back_fee"));
+                // 退款渠道明细(转换为JSON字符串)
+                if (refundResponse.containsKey("refund_detail_item_list")) {
+                    JSONArray refundDetailList = refundResponse.getJSONArray("refund_detail_item_list");
+                    if (refundDetailList != null) {
+                        refundLog.setRefundDetailItemList(JSON.toJSONString(refundDetailList));
+                    }
+                }
+                // 退款时间(字符串转Date)
+                String gmtRefundPayStr = refundResponse.getString("gmt_refund_pay");
+                if (StringUtils.isNotBlank(gmtRefundPayStr)) {
+                    try {
+                        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+                        refundLog.setGmtRefundPay(sdf.parse(gmtRefundPayStr));
+                    } catch (ParseException e) {
+                        log.warn("解析退款时间失败: {}", gmtRefundPayStr, e);
+                    }
+                }
+                // 退款原因和部分退款编号(从请求参数中获取)
+                refundLog.setRefundReason(params.get("refundReason"));
+                refundLog.setOutRequestNo(params.get("partialRefundCode"));
+                // 证书和签名信息
+                refundLog.setAlipayCertSn(responseBody.getString("alipay_cert_sn"));
+                refundLog.setSign(responseBody.getString("sign"));
+                // 标准字段
+                refundLog.setDeleteFlag(0);
+                refundLog.setCreatedTime(new Date());
+                storeAliPayRefundLogService.save(refundLog);
+                
+                // 保存到通用退款记录表
+                try {
+                    RefundRecord refundRecord = buildRefundRecordFromAlipayResponse(refundResponse, responseBody, params);
+                    if (refundRecord != null) {
+                        // 检查是否已存在,避免重复插入
+                        long count = refundRecordService.lambdaQuery()
+                                .eq(RefundRecord::getOutRefundNo, refundRecord.getOutRefundNo())
+                                .count();
+                        if (count == 0) {
+                            refundRecordService.save(refundRecord);
+                            log.info("支付宝退款记录已保存到RefundRecord表,商户退款单号:{}", refundRecord.getOutRefundNo());
+                        } else {
+                            log.info("支付宝退款记录已存在,跳过插入,商户退款单号:{}", refundRecord.getOutRefundNo());
+                        }
+                    }
+                } catch (Exception e) {
+                    log.error("保存支付宝退款记录到RefundRecord表失败", e);
+                    // 不抛出异常,避免影响原有逻辑
+                }
+            } else {
+                log.warn("AliPayConfig.processRefund ERROR Msg={}", response.getBody());
+                refundReslut = refundResponse.getString("sub_msg");
+                
+                // 保存失败记录到通用退款记录表
+                try {
+                    RefundRecord refundRecord = buildRefundRecordFromAlipayError(refundResponse, responseBody, params);
+                    if (refundRecord != null) {
+                        // 检查是否已存在,避免重复插入
+                        long count = refundRecordService.lambdaQuery()
+                                .eq(RefundRecord::getOutRefundNo, refundRecord.getOutRefundNo())
+                                .count();
+                        if (count == 0) {
+                            refundRecordService.save(refundRecord);
+                            log.info("支付宝退款失败记录已保存到RefundRecord表,商户退款单号:{}", refundRecord.getOutRefundNo());
+                        } else {
+                            log.info("支付宝退款失败记录已存在,跳过插入,商户退款单号:{}", refundRecord.getOutRefundNo());
+                        }
+                    }
+                } catch (Exception e) {
+                    log.error("保存支付宝退款失败记录到RefundRecord表失败", e);
+                }
+            }
+            return refundReslut;
+        } catch (AlipayApiException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public String getType() {
+        return PaymentEnum.ALIPAY.getType();
+    }
+
+
+    /**
+     * 构建预支付请求
+     */
+    private AlipayTradeAppPayRequest buildPrePayRequest(String price, String subject,String outTradeNo) {
+            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmss");
+            AlipayTradeAppPayRequest request = new AlipayTradeAppPayRequest();
+            AlipayTradeAppPayModel model = new AlipayTradeAppPayModel();
+            model.setOutTradeNo(outTradeNo);
+            model.setTotalAmount(price);
+            model.setSubject(subject);
+            model.setPassbackParams(UrlEncode.getUrlEncode(dateFormat.toString()));
+            request.setBizModel(model);
+            return request;
+    }
+
+    /**
+     * 构建退款请求
+     */
+    private AlipayTradeRefundRequest buildRefundRequest(Map<String, String> params) {
+        AlipayTradeRefundRequest request = new AlipayTradeRefundRequest();
+        AlipayTradeRefundModel model = new AlipayTradeRefundModel();
+        model.setOutTradeNo(params.get("outTradeNo"));
+        model.setRefundAmount(params.get("refundAmount"));
+        model.setRefundReason(params.get("refundReason"));
+        if (StringUtils.isNotBlank(params.get("partialRefundCode"))) {
+            model.setOutRequestNo(params.get("partialRefundCode"));
+        }
+        request.setBizModel(model);
+        return request;
+    }
+
+    /**
+     * 执行预支付订单请求
+     */
+    private JSONObject prePayOrderRun(AlipayTradeAppPayRequest request) throws AlipayApiException {
+        JSONObject jsonObject = new JSONObject();
+        AlipayClient alipayClient = new DefaultAlipayClient(setAlipayConfig(null));
+        AlipayTradeAppPayResponse response = alipayClient.sdkExecute(request);
+        String orderStr = "";
+        if (response.isSuccess()) {
+            orderStr = response.getBody();
+        } else {
+            System.out.println("调用失败 response:" + response);
+            orderStr = "调用失败";
+        }
+        jsonObject.put("orderStr", orderStr);
+        return jsonObject;
+    }
+
+
+
+    /**
+     * 执行退款请求
+     */
+    private AlipayTradeRefundResponse refundRun(AlipayTradeRefundRequest request) throws AlipayApiException {
+        AlipayClient alipayClient = new DefaultAlipayClient(setAlipayConfig(null));
+        AlipayTradeRefundResponse response = alipayClient.certificateExecute(request);
+        return response;
+    }
+
+    /**
+     * 从支付宝退款响应构建RefundRecord对象(成功情况)
+     */
+    private RefundRecord buildRefundRecordFromAlipayResponse(JSONObject refundResponse, JSONObject responseBody, Map<String, String> params) {
+        try {
+            RefundRecord record = new RefundRecord();
+            
+            // 基本信息
+            record.setPayType(PaymentEnum.ALIPAY.getType());
+            record.setOutTradeNo(refundResponse.getString("out_trade_no"));
+            record.setTransactionId(refundResponse.getString("trade_no"));
+            // 部分退款编号作为商户退款单号,如果没有则生成一个
+            String outRefundNo = params.get("partialRefundCode");
+            if (StringUtils.isBlank(outRefundNo)) {
+                outRefundNo = UniqueRandomNumGenerator.generateUniqueCode(19);
+            }
+            record.setOutRefundNo(outRefundNo);
+            record.setRefundId(null); // 支付宝没有单独的退款单号
+            record.setRefundStatus("SUCCESS");
+            
+            // 金额信息(支付宝返回的是元,需要转换为分)
+            String refundFeeStr = refundResponse.getString("refund_fee");
+            String sendBackFeeStr = refundResponse.getString("send_back_fee");
+            if (StringUtils.isNotBlank(refundFeeStr)) {
+                record.setRefundAmount(new BigDecimal(refundFeeStr).multiply(new BigDecimal(100)).longValue());
+            }
+            if (StringUtils.isNotBlank(sendBackFeeStr)) {
+                record.setActualRefundAmount(new BigDecimal(sendBackFeeStr).multiply(new BigDecimal(100)).longValue());
+            }
+            // 订单总金额从params获取,如果没有则从退款金额推算
+            String totalAmountStr = params.get("totalAmount");
+            if (StringUtils.isNotBlank(totalAmountStr)) {
+                record.setTotalAmount(new BigDecimal(totalAmountStr).multiply(new BigDecimal(100)).longValue());
+            }
+            record.setCurrency("CNY");
+            
+            // 退款原因
+            record.setRefundReason(params.get("refundReason"));
+            
+            // 退款详情
+            if (refundResponse.containsKey("refund_detail_item_list")) {
+                JSONArray refundDetailList = refundResponse.getJSONArray("refund_detail_item_list");
+                if (refundDetailList != null) {
+                    record.setRefundDetail(JSON.toJSONString(refundDetailList));
+                }
+            }
+            
+            // 用户收到退款账户
+            record.setUserReceivedAccount(refundResponse.getString("buyer_logon_id"));
+            
+            // 退款时间
+            String gmtRefundPayStr = refundResponse.getString("gmt_refund_pay");
+            if (StringUtils.isNotBlank(gmtRefundPayStr)) {
+                try {
+                    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+                    Date refundTime = sdf.parse(gmtRefundPayStr);
+                    record.setRefundSuccessTime(refundTime);
+                    record.setRefundCreateTime(refundTime); // 支付宝没有单独的创建时间
+                } catch (ParseException e) {
+                    log.warn("解析退款时间失败: {}", gmtRefundPayStr, e);
+                }
+            }
+            
+            // 业务信息(从params获取,如果有的话)
+            if (params.containsKey("userId")) {
+                try {
+                    record.setUserId(Integer.parseInt(params.get("userId")));
+                } catch (NumberFormatException e) {
+                    log.warn("解析userId失败: {}", params.get("userId"));
+                }
+            }
+            if (params.containsKey("orderId")) {
+                record.setOrderId(params.get("orderId"));
+            }
+            if (params.containsKey("storeId")) {
+                try {
+                    record.setStoreId(Integer.parseInt(params.get("storeId")));
+                } catch (NumberFormatException e) {
+                    log.warn("解析storeId失败: {}", params.get("storeId"));
+                }
+            }
+            
+            // 响应数据
+            record.setResponseData(responseBody.toJSONString());
+            
+            // 标准字段
+            record.setDeleteFlag(0);
+            record.setCreatedTime(new Date());
+            
+            return record;
+        } catch (Exception e) {
+            log.error("构建RefundRecord对象失败", e);
+            return null;
+        }
+    }
+
+    /**
+     * 从支付宝退款错误响应构建RefundRecord对象(失败情况)
+     */
+    private RefundRecord buildRefundRecordFromAlipayError(JSONObject refundResponse, JSONObject responseBody, Map<String, String> params) {
+        try {
+            RefundRecord record = new RefundRecord();
+            
+            // 基本信息
+            record.setPayType(PaymentEnum.ALIPAY.getType());
+            record.setOutTradeNo(params.get("outTradeNo"));
+            // 部分退款编号作为商户退款单号,如果没有则生成一个
+            String outRefundNo = params.get("partialRefundCode");
+            if (StringUtils.isBlank(outRefundNo)) {
+                outRefundNo = UniqueRandomNumGenerator.generateUniqueCode(19);
+            }
+            record.setOutRefundNo(outRefundNo);
+            record.setRefundStatus("ABNORMAL");
+            
+            // 金额信息
+            String refundAmountStr = params.get("refundAmount");
+            if (StringUtils.isNotBlank(refundAmountStr)) {
+                record.setRefundAmount(new BigDecimal(refundAmountStr).multiply(new BigDecimal(100)).longValue());
+            }
+            String totalAmountStr = params.get("totalAmount");
+            if (StringUtils.isNotBlank(totalAmountStr)) {
+                record.setTotalAmount(new BigDecimal(totalAmountStr).multiply(new BigDecimal(100)).longValue());
+            }
+            record.setCurrency("CNY");
+            
+            // 退款原因
+            record.setRefundReason(params.get("refundReason"));
+            
+            // 错误信息
+            record.setErrorCode(refundResponse.getString("code"));
+            record.setErrorMsg(refundResponse.getString("sub_msg"));
+            
+            // 业务信息
+            if (params.containsKey("userId")) {
+                try {
+                    record.setUserId(Integer.parseInt(params.get("userId")));
+                } catch (NumberFormatException e) {
+                    log.warn("解析userId失败: {}", params.get("userId"));
+                }
+            }
+            if (params.containsKey("orderId")) {
+                record.setOrderId(params.get("orderId"));
+            }
+            if (params.containsKey("storeId")) {
+                try {
+                    record.setStoreId(Integer.parseInt(params.get("storeId")));
+                } catch (NumberFormatException e) {
+                    log.warn("解析storeId失败: {}", params.get("storeId"));
+                }
+            }
+            
+            // 响应数据
+            record.setResponseData(responseBody.toJSONString());
+            
+            // 标准字段
+            record.setDeleteFlag(0);
+            record.setCreatedTime(new Date());
+            record.setRefundCreateTime(new Date());
+            
+            return record;
+        } catch (Exception e) {
+            log.error("构建RefundRecord对象失败", e);
+            return null;
+        }
+    }
+
+}
+

+ 1265 - 0
alien-lawyer/src/main/java/shop/alien/lawyer/payment/impl/WeChatPaymentStrategyImpl.java

@@ -0,0 +1,1265 @@
+package shop.alien.lawyer.payment.impl;
+
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.google.gson.annotations.Expose;
+import com.google.gson.annotations.SerializedName;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import okhttp3.*;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+import shop.alien.entity.result.R;
+import shop.alien.entity.store.LawyerConsultationOrder;
+import shop.alien.entity.store.RefundRecord;
+
+import shop.alien.lawyer.payment.PaymentStrategy;
+import shop.alien.lawyer.service.RefundRecordService;
+import shop.alien.lawyer.util.WXPayUtility;
+import shop.alien.mapper.LawyerConsultationOrderMapper;
+import shop.alien.util.common.UniqueRandomNumGenerator;
+import shop.alien.util.common.constant.PaymentEnum;
+import shop.alien.util.system.OSUtil;
+
+import javax.annotation.PostConstruct;
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.math.BigDecimal;
+import java.nio.charset.StandardCharsets;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.Signature;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.time.OffsetDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.*;
+
+/**
+ * 微信支付策略
+ *
+ * @author lyx
+ * @date 2025/11/20
+ */
+
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class WeChatPaymentStrategyImpl implements PaymentStrategy {
+
+    private final RefundRecordService refundRecordService;
+
+    @Value("${payment.wechatPay.host}")
+    private String wechatPayApiHost;
+
+    @Value("${payment.wechatPay.prePayPath}")
+    private String prePayPath;
+
+    @Value("${payment.wechatPay.searchOrderByTransactionIdPath}")
+    private String searchOrderByTransactionIdPath;
+
+    @Value("${payment.wechatPay.searchOrderByOutTradeNoPath}")
+    private String searchOrderByOutTradeNoPath;
+
+    @Value("${payment.wechatPay.refundPath}")
+    private String refundPath;
+
+    /**
+     * 微信支付应用id
+     */
+    @Value("${payment.wechatPay.business.appId}")
+    private String appId;
+
+    /**
+     * 微信支付商户id
+     */
+    @Value("${payment.wechatPay.business.mchId}")
+    private String mchId;
+
+    /**
+     * 微信支付商户私钥路径
+     */
+    @Value("${payment.wechatPay.business.win.privateKeyPath}")
+    private String privateWinKeyPath;
+
+    /**
+     * 微信支付商户私钥路径(Linux环境)
+     */
+    @Value("${payment.wechatPay.business.linux.privateKeyPath}")
+    private String privateLinuxKeyPath;
+
+    /**
+     * 微信支付公钥路径
+     */
+    @Value("${payment.wechatPay.business.win.wechatPayPublicKeyFilePath}")
+    private String wechatWinPayPublicKeyFilePath;
+
+    /**
+     * 微信支付公钥路径(Linux环境)
+     */
+    @Value("${payment.wechatPay.business.linux.wechatPayPublicKeyFilePath}")
+    private String wechatLinuxPayPublicKeyFilePath;
+
+    /**
+     * 微信支付商户证书序列号
+     */
+    @Value("${payment.wechatPay.business.merchantSerialNumber}")
+    private String merchantSerialNumber;
+
+    /**
+     * 微信支付API V3密钥
+     */
+    @Value("${payment.wechatPay.business.apiV3key}")
+    private String apiV3key;
+
+    /**
+     * 微信支付公钥id
+     */
+    @Value("${payment.wechatPay.business.wechatPayPublicKeyId}")
+    private String wechatPayPublicKeyId;
+
+    @Value("${payment.wechatPay.business.prePayNotifyUrl}")
+    private String prePayNotifyUrl;
+
+    @Value("${payment.wechatPay.business.refundNotifyUrl}")
+    private String refundNotifyUrl;
+
+    private PrivateKey privateKey;
+    private PublicKey wechatPayPublicKey;
+
+    private static String POSTMETHOD = "POST";
+    private static String GETMETHOD = "GET";
+
+
+    private final LawyerConsultationOrderMapper lawyerConsultationOrderMapper;
+
+    @PostConstruct
+    public void setWeChatPaymentConfig() {
+        String privateKeyPath;
+        String wechatPayPublicKeyFilePath;
+        if ("windows".equals(OSUtil.getOsName())) {
+            privateKeyPath = privateWinKeyPath;
+            wechatPayPublicKeyFilePath = wechatWinPayPublicKeyFilePath;
+        } else {
+            privateKeyPath = privateLinuxKeyPath;
+            wechatPayPublicKeyFilePath = wechatLinuxPayPublicKeyFilePath;
+        }
+        this.privateKey = WXPayUtility.loadPrivateKeyFromPath(privateKeyPath);
+        this.wechatPayPublicKey = WXPayUtility.loadPublicKeyFromPath(wechatPayPublicKeyFilePath);
+    }
+
+    @Override
+    public R createPrePayOrder(String price, String subject) throws Exception {
+        log.info("创建微信预支付订单,价格:{},描述:{}", price, subject);
+        // 参数验证
+        if (price == null || price.trim().isEmpty()) {
+            return R.fail("价格不能为空");
+        }
+        if (subject == null || subject.trim().isEmpty()) {
+            return R.fail("订单描述不能为空");
+        }
+        try {
+            BigDecimal amount = new BigDecimal(price);
+            if (amount.compareTo(BigDecimal.ZERO) <= 0) {
+                return R.fail("价格必须大于0");
+            }
+        } catch (NumberFormatException e) {
+            return R.fail("价格格式不正确");
+        }
+
+        CommonPrepayRequest request = new CommonPrepayRequest();
+        request.appid = appId;
+        request.mchid = mchId;
+        request.description = subject;
+        request.outTradeNo = UniqueRandomNumGenerator.generateUniqueCode(19);
+
+//        request.timeExpire = "2018-06-08T10:34:56+08:00"; 超时支付不传默认7天
+//        request.attach = "自定义数据说明";
+        // 目前没用,但是必填
+        request.notifyUrl = prePayNotifyUrl;
+//        request.goodsTag = "WXG";
+//        request.supportFapiao = false;
+        request.amount = new CommonAmountInfo();
+        request.amount.total = new BigDecimal(price).multiply(new BigDecimal(100)).longValue();
+        request.amount.currency = "CNY";
+        /* 【优惠功能】 优惠功能
+        request.detail = new CouponInfo();
+        request.detail.costPrice = 608800L;
+        request.detail.invoiceId = "微信123";
+        request.detail.goodsDetail = new ArrayList<>();
+        {
+            GoodsDetail goodsDetailItem = new GoodsDetail();
+            goodsDetailItem.merchantGoodsId = "1246464644";
+            goodsDetailItem.wechatpayGoodsId = "1001";
+            goodsDetailItem.goodsName = "iPhoneX 256G";
+            goodsDetailItem.quantity = 1L;
+            goodsDetailItem.unitPrice = 528800L;
+            request.detail.goodsDetail.add(goodsDetailItem);
+        };*/
+        /* 【场景信息】 场景信息
+        request.sceneInfo = new CommonSceneInfo();
+        request.sceneInfo.payerClientIp = "14.23.150.211";
+        request.sceneInfo.deviceId = "013467007045764";
+        request.sceneInfo.storeInfo = new StoreInfo();
+        request.sceneInfo.storeInfo.id = "0001";
+        request.sceneInfo.storeInfo.name = "腾讯大厦分店";
+        request.sceneInfo.storeInfo.areaCode = "440305";
+        request.sceneInfo.storeInfo.address = "广东省深圳市南山区科技中一道10000号";*/
+        /* 【结算信息】 结算信息
+        request.settleInfo = new SettleInfo();
+        request.settleInfo.profitSharing = false;*/
+        try {
+            DirectAPIv3AppPrepayResponse response = this.prePayOrderRun(request);
+            log.info("微信预支付订单创建成功,预支付ID:{}", response.prepayId);
+            Map<String,String> result = new HashMap<>();
+            result.put("prepayId", response.prepayId);
+            result.put("appId", appId);
+            result.put("mchId", mchId);
+            result.put("orderNo", request.outTradeNo);
+            // 生成sign
+//            appId
+
+//            随机字符串
+//            prepay_id
+            long timestamp = System.currentTimeMillis() / 1000; // 时间戳
+            String nonce = WXPayUtility.createNonce(32); // 随机字符串
+            String prepayId = response.prepayId;
+            String message = String.format("%s\n%s\n%s\n%s\n", appId, timestamp, nonce, prepayId);
+//            String sign = WXPayUtility.sign(message, sha256withRSA, privateKey);
+            Signature sign = Signature.getInstance("SHA256withRSA");
+            sign.initSign(privateKey);
+            sign.update(message.getBytes(StandardCharsets.UTF_8));
+            result.put("sign", Base64.getEncoder().encodeToString(sign.sign()));
+            result.put("timestamp", String.valueOf(timestamp));
+            result.put("nonce", nonce);
+            return R.data(result);
+        } catch (WXPayUtility.ApiException e) {
+            log.error("微信支付预支付失败,状态码:{},错误信息:{}", e.getErrorCode(), e.getMessage());
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @Override
+    public R handleNotify(String notifyData) throws Exception {
+        /**
+         * 目前没用,先写着
+         */
+        return null;
+    }
+
+    @Override
+    public R searchOrderByOutTradeNoPath(String transactionId ,Integer id,String orderStr) throws Exception {
+        log.info("查询微信支付订单状态,交易订单号:{}", transactionId);
+
+        LambdaUpdateWrapper<LawyerConsultationOrder> updateWrapper = new LambdaUpdateWrapper<>();
+        updateWrapper.eq(LawyerConsultationOrder::getId, id);
+        updateWrapper.set(LawyerConsultationOrder::getAlipayNo, transactionId);
+        updateWrapper.set(LawyerConsultationOrder::getOrderStr, orderStr);
+
+        int a=lawyerConsultationOrderMapper.update(null, updateWrapper);
+
+        if (a>0){
+            log.info("更新订单成功");
+        }
+
+
+
+        QueryByWxTradeNoRequest request = new QueryByWxTradeNoRequest();
+        request.transactionId = transactionId;
+        request.mchid = mchId;
+        try {
+            DirectAPIv3QueryResponse response = searchOrderRun(request);
+
+            if (response.tradeState.equals("NOTPAY")) {
+                log.info("微信支付订单已支付,订单号:{}", response.outTradeNo);
+                return R.fail("NOTPAY");
+            }
+
+
+            return R.data(response);
+        } catch (WXPayUtility.ApiException e) {
+            log.error("查询微信支付订单状态失败,状态码:{},错误信息:{}", e.getErrorCode(), e.getMessage());
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @Override
+    public String handleRefund(Map<String,String> params) throws Exception {
+
+        CreateRequest request = new CreateRequest();
+        // 微信支付订单号和商户订单号必须二选一,不能同时为空,查询的时候使用的是商户订单号
+        //request.transactionId = "1217752501201407033233368018";
+        request.outTradeNo = params.get("outTradeNo");
+        // 退款订单号
+        request.outRefundNo = UniqueRandomNumGenerator.generateUniqueCode(19);
+        // 退款原因
+        request.reason = params.get("reason");
+        // 退款回调通知地址
+        request.notifyUrl = refundNotifyUrl;
+        // 退款资金来源选填
+        //request.fundsAccount = ReqFundsAccount.AVAILABLE;
+        // 金额信息
+        request.amount = new AmountReq();
+        request.amount.refund = new BigDecimal(params.get("refundAmount")).longValue();
+        // 退款出资账户及金额 目前不需要,需要的时候再看
+        /*
+        request.amount.from = new ArrayList<>();
+        {
+            FundsFromItem fromItem = new FundsFromItem();
+            fromItem.account = Account.AVAILABLE;
+            fromItem.amount = 444L;
+            request.amount.from.add(fromItem);
+        };*/
+        // 订单总金额
+        request.amount.total = new BigDecimal(params.get("totalAmount")).longValue();
+        // 退款币种 目前默认人民币 CNY
+        request.amount.currency = "CNY";
+        // 退款商品信息 目前不需要,需要的时候再看
+        /*
+        request.goodsDetail = new ArrayList<>();
+        {
+            GoodsDetail goodsDetailItem = new GoodsDetail();
+            goodsDetailItem.merchantGoodsId = "1217752501201407033233368018";
+            goodsDetailItem.wechatpayGoodsId = "1001";
+            goodsDetailItem.goodsName = "iPhone6s 16G";
+            goodsDetailItem.unitPrice = 528800L;
+            goodsDetailItem.refundAmount = 528800L;
+            goodsDetailItem.refundQuantity = 1L;
+            request.goodsDetail.add(goodsDetailItem);
+        };*/
+        // 记录退款请求信息
+        log.info("开始处理微信支付退款,商户订单号:{},商户退款单号:{},退款金额:{}分,订单总金额:{}分,退款原因:{}",
+                request.outTradeNo, request.outRefundNo, request.amount.refund, request.amount.total, request.reason);
+        String refundResult = "";
+        try {
+            Refund response = refundRun(request);
+            // 退款状态
+            String status = response.status != null ? response.status.name() : "UNKNOWN";
+            if ("SUCCESS".equals(status) || "PROCESSING".equals(status)) {
+                // refund_id 申请退款受理成功时,该笔退款单在微信支付侧生成的唯一标识。
+                String refundId = response.refundId;
+                // 商户申请退款时传的商户系统内部退款单号。
+                String outRefundNo = response.outRefundNo != null ? response.outRefundNo : request.outRefundNo;
+                // 微信支付订单号
+                String transactionId = response.transactionId;
+
+                // 退款金额信息
+                String refundAmount = response.amount != null && response.amount.refund != null
+                        ? String.valueOf(response.amount.refund) : String.valueOf(request.amount.refund);
+                String totalAmount = response.amount != null && response.amount.total != null
+                        ? String.valueOf(response.amount.total) : String.valueOf(request.amount.total);
+                // 退款成功时间
+                String successTime = response.successTime;
+                // 退款创建时间
+                String createTime = response.createTime;
+                // 退款渠道
+                String channel = response.channel != null ? response.channel.name() : "UNKNOWN";
+
+                // 记录退款成功详细信息
+                log.info("微信支付退款成功 - 商户订单号:{},微信支付订单号:{},商户退款单号:{},微信退款单号:{}," +
+                                "退款状态:{},退款金额:{}分,订单总金额:{}分,退款渠道:{},创建时间:{},成功时间:{}",
+                        request.outTradeNo, transactionId, outRefundNo, refundId, status, refundAmount,
+                        totalAmount, channel, createTime, successTime != null ? successTime : "未完成");
+
+                // 保存到通用退款记录表
+                try {
+                    RefundRecord refundRecord = buildRefundRecordFromWeChatResponse(response, request, params);
+                    if (refundRecord != null) {
+                        // 检查是否已存在,避免重复插入
+                        long count = refundRecordService.lambdaQuery()
+                                .eq(RefundRecord::getOutRefundNo, refundRecord.getOutRefundNo())
+                                .count();
+                        if (count == 0) {
+                            refundRecordService.save(refundRecord);
+                            log.info("微信支付退款记录已保存到RefundRecord表,商户退款单号:{}", refundRecord.getOutRefundNo());
+                        } else {
+                            log.info("微信支付退款记录已存在,跳过插入,商户退款单号:{}", refundRecord.getOutRefundNo());
+                        }
+                    }
+                } catch (Exception e) {
+                    log.error("保存微信支付退款记录到RefundRecord表失败", e);
+                    // 不抛出异常,避免影响原有逻辑
+                }
+
+                refundResult = "调用成功";
+                return refundResult;
+            } else {
+                log.error("微信支付退款失败 - 商户订单号:{},商户退款单号:{},退款金额:{}分,订单总金额:{}分," +
+                                "退款状态:{}",
+                        request.outTradeNo, request.outRefundNo, request.amount.refund,
+                        request.amount.total, status);
+                
+                // 保存失败记录到通用退款记录表
+                try {
+                    RefundRecord refundRecord = buildRefundRecordFromWeChatError(response, request, params, status);
+                    if (refundRecord != null) {
+                        // 检查是否已存在,避免重复插入
+                        long count = refundRecordService.lambdaQuery()
+                                .eq(RefundRecord::getOutRefundNo, refundRecord.getOutRefundNo())
+                                .count();
+                        if (count == 0) {
+                            refundRecordService.save(refundRecord);
+                            log.info("微信支付退款失败记录已保存到RefundRecord表,商户退款单号:{}", refundRecord.getOutRefundNo());
+                        } else {
+                            log.info("微信支付退款失败记录已存在,跳过插入,商户退款单号:{}", refundRecord.getOutRefundNo());
+                        }
+                    }
+                } catch (Exception e) {
+                    log.error("保存微信支付退款失败记录到RefundRecord表失败", e);
+                }
+                
+                return "退款失败";
+            }
+        }
+        catch (Exception e) {
+            // 记录其他异常
+            log.error("微信支付退款异常 - 商户订单号:{},商户退款单号:{},退款金额:{}分,订单总金额:{}分," +
+                    "退款原因:{},异常信息:{}",
+                    request.outTradeNo, request.outRefundNo, request.amount.refund, 
+                    request.amount.total, request.reason, e.getMessage(), e);
+            
+            // 保存异常记录到通用退款记录表
+            try {
+                RefundRecord refundRecord = buildRefundRecordFromWeChatException(request, params, e);
+                if (refundRecord != null) {
+                    // 检查是否已存在,避免重复插入
+                    long count = refundRecordService.lambdaQuery()
+                            .eq(RefundRecord::getOutRefundNo, refundRecord.getOutRefundNo())
+                            .count();
+                    if (count == 0) {
+                        refundRecordService.save(refundRecord);
+                        log.info("微信支付退款异常记录已保存到RefundRecord表,商户退款单号:{}", refundRecord.getOutRefundNo());
+                    } else {
+                        log.info("微信支付退款异常记录已存在,跳过插入,商户退款单号:{}", refundRecord.getOutRefundNo());
+                    }
+                }
+            } catch (Exception ex) {
+                log.error("保存微信支付退款异常记录到RefundRecord表失败", ex);
+            }
+            
+            refundResult = "退款处理异常:" + e.getMessage();
+            return refundResult;
+        }
+    }
+
+
+    @Override
+    public String getType() {
+        return PaymentEnum.WECHAT_PAY.getType();
+    }
+
+
+    public DirectAPIv3AppPrepayResponse prePayOrderRun(CommonPrepayRequest request) {
+        String uri = prePayPath;
+        String reqBody = WXPayUtility.toJson(request);
+
+        Request.Builder reqBuilder = new Request.Builder().url(wechatPayApiHost + uri);
+        reqBuilder.addHeader("Accept", "application/json");
+        reqBuilder.addHeader("Wechatpay-Serial", wechatPayPublicKeyId);
+        reqBuilder.addHeader("Authorization", WXPayUtility.buildAuthorization(mchId, merchantSerialNumber, privateKey, POSTMETHOD, uri, reqBody));
+        reqBuilder.addHeader("Content-Type", "application/json");
+        RequestBody requestBody = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), reqBody);
+        reqBuilder.method(POSTMETHOD, requestBody);
+        Request httpRequest = reqBuilder.build();
+
+        // 发送HTTP请求
+        OkHttpClient client = new OkHttpClient.Builder().build();
+        try (Response httpResponse = client.newCall(httpRequest).execute()) {
+            String respBody = WXPayUtility.extractBody(httpResponse);
+            if (httpResponse.code() >= 200 && httpResponse.code() < 300) {
+                // 2XX 成功,验证应答签名
+                WXPayUtility.validateResponse(wechatPayPublicKeyId, wechatPayPublicKey,
+                        httpResponse.headers(), respBody);
+
+                // 从HTTP应答报文构建返回数据
+                return WXPayUtility.fromJson(respBody, DirectAPIv3AppPrepayResponse.class);
+            } else {
+                throw new WXPayUtility.ApiException(httpResponse.code(), respBody, httpResponse.headers());
+            }
+        } catch (IOException e) {
+            throw new UncheckedIOException("Sending request to " + uri + " failed.", e);
+        }
+    }
+
+    public DirectAPIv3QueryResponse searchOrderRun(QueryByWxTradeNoRequest request) {
+        String uri = searchOrderByOutTradeNoPath;
+        uri = uri.replace("{out_trade_no}", WXPayUtility.urlEncode(request.transactionId));
+        Map<String, Object> args = new HashMap<>();
+        args.put("mchid", mchId);
+        String queryString = WXPayUtility.urlEncode(args);
+        if (!queryString.isEmpty()) {
+            uri = uri + "?" + queryString;
+        }
+
+        Request.Builder reqBuilder = new Request.Builder().url(wechatPayApiHost + uri);
+        reqBuilder.addHeader("Accept", "application/json");
+        reqBuilder.addHeader("Wechatpay-Serial", wechatPayPublicKeyId);
+        reqBuilder.addHeader("Authorization", WXPayUtility.buildAuthorization(mchId, merchantSerialNumber, privateKey, GETMETHOD, uri, null));
+        reqBuilder.method(GETMETHOD, null);
+        Request httpRequest = reqBuilder.build();
+
+        // 发送HTTP请求
+        OkHttpClient client = new OkHttpClient.Builder().build();
+        try (Response httpResponse = client.newCall(httpRequest).execute()) {
+            String respBody = WXPayUtility.extractBody(httpResponse);
+            if (httpResponse.code() >= 200 && httpResponse.code() < 300) {
+                // 2XX 成功,验证应答签名
+                WXPayUtility.validateResponse(this.wechatPayPublicKeyId, this.wechatPayPublicKey,
+                        httpResponse.headers(), respBody);
+
+                // 从HTTP应答报文构建返回数据
+                return WXPayUtility.fromJson(respBody, DirectAPIv3QueryResponse.class);
+            } else {
+                throw new WXPayUtility.ApiException(httpResponse.code(), respBody, httpResponse.headers());
+            }
+        } catch (IOException e) {
+            throw new UncheckedIOException("Sending request to " + uri + " failed.", e);
+        }
+    }
+
+    public Refund refundRun(CreateRequest request) {
+        String uri = refundPath;
+        String reqBody = WXPayUtility.toJson(request);
+
+        Request.Builder reqBuilder = new Request.Builder().url(wechatPayApiHost + uri);
+        reqBuilder.addHeader("Accept", "application/json");
+        reqBuilder.addHeader("Wechatpay-Serial", wechatPayPublicKeyId);
+        reqBuilder.addHeader("Authorization", WXPayUtility.buildAuthorization(mchId, merchantSerialNumber, privateKey, POSTMETHOD, uri, reqBody));
+        reqBuilder.addHeader("Content-Type", "application/json");
+        RequestBody requestBody = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), reqBody);
+        reqBuilder.method(POSTMETHOD, requestBody);
+        Request httpRequest = reqBuilder.build();
+
+        // 发送HTTP请求
+        OkHttpClient client = new OkHttpClient.Builder().build();
+        try (Response httpResponse = client.newCall(httpRequest).execute()) {
+            String respBody = WXPayUtility.extractBody(httpResponse);
+            if (httpResponse.code() >= 200 && httpResponse.code() < 300) {
+                // 2XX 成功,验证应答签名
+                WXPayUtility.validateResponse(this.wechatPayPublicKeyId, this.wechatPayPublicKey,
+                        httpResponse.headers(), respBody);
+
+                // 从HTTP应答报文构建返回数据
+                return WXPayUtility.fromJson(respBody, Refund.class);
+            } else {
+                throw new WXPayUtility.ApiException(httpResponse.code(), respBody, httpResponse.headers());
+            }
+        } catch (IOException e) {
+            throw new UncheckedIOException("Sending request to " + uri + " failed.", e);
+        }
+    }
+
+    /*****************************************下面为支付使用参数*****************************************/
+    public static class CommonPrepayRequest {
+        @SerializedName("appid")
+        public String appid;
+
+        @SerializedName("mchid")
+        public String mchid;
+
+        @SerializedName("description")
+        public String description;
+
+        @SerializedName("out_trade_no")
+        public String outTradeNo;
+
+        @SerializedName("time_expire")
+        public String timeExpire;
+
+        @SerializedName("attach")
+        public String attach;
+
+        @SerializedName("notify_url")
+        public String notifyUrl;
+
+        @SerializedName("goods_tag")
+        public String goodsTag;
+
+        @SerializedName("support_fapiao")
+        public Boolean supportFapiao;
+
+        @SerializedName("amount")
+        public CommonAmountInfo amount;
+
+        @SerializedName("detail")
+        public CouponInfo detail;
+
+        @SerializedName("scene_info")
+        public CommonSceneInfo sceneInfo;
+
+        @SerializedName("settle_info")
+        public SettleInfo settleInfo;
+    }
+
+    public static class DirectAPIv3AppPrepayResponse {
+        @SerializedName("prepay_id")
+        public String prepayId;
+    }
+
+    public static class CommonAmountInfo {
+        @SerializedName("total")
+        public Long total;
+
+        @SerializedName("currency")
+        public String currency;
+    }
+
+    public static class CouponInfo {
+        @SerializedName("cost_price")
+        public Long costPrice;
+
+        @SerializedName("invoice_id")
+        public String invoiceId;
+
+        @SerializedName("goods_detail")
+        public List<GoodsDetail> goodsDetail;
+    }
+
+    public static class CommonSceneInfo {
+        @SerializedName("payer_client_ip")
+        public String payerClientIp;
+
+        @SerializedName("device_id")
+        public String deviceId;
+
+        @SerializedName("store_info")
+        public StoreInfo storeInfo;
+    }
+
+    public static class SettleInfo {
+        @SerializedName("profit_sharing")
+        public Boolean profitSharing;
+    }
+
+    public static class StoreInfo {
+        @SerializedName("id")
+        public String id;
+
+        @SerializedName("name")
+        public String name;
+
+        @SerializedName("area_code")
+        public String areaCode;
+
+        @SerializedName("address")
+        public String address;
+    }
+
+    public static class QueryByWxTradeNoRequest {
+        @SerializedName("mchid")
+        @Expose(serialize = false)
+        public String mchid;
+
+        @SerializedName("transaction_id")
+        @Expose(serialize = false)
+        public String transactionId;
+    }
+
+    public static class DirectAPIv3QueryResponse {
+        @SerializedName("appid")
+        public String appid;
+
+        @SerializedName("mchid")
+        public String mchid;
+
+        @SerializedName("out_trade_no")
+        public String outTradeNo;
+
+        @SerializedName("transaction_id")
+        public String transactionId;
+
+        @SerializedName("trade_type")
+        public String tradeType;
+
+        @SerializedName("trade_state")
+        public String tradeState;
+
+        @SerializedName("trade_state_desc")
+        public String tradeStateDesc;
+
+        @SerializedName("bank_type")
+        public String bankType;
+
+        @SerializedName("attach")
+        public String attach;
+
+        @SerializedName("success_time")
+        public String successTime;
+
+        @SerializedName("payer")
+        public CommRespPayerInfo payer;
+
+        @SerializedName("amount")
+        public CommRespAmountInfo amount;
+
+        @SerializedName("scene_info")
+        public CommRespSceneInfo sceneInfo;
+
+        @SerializedName("promotion_detail")
+        public List<PromotionDetail> promotionDetail;
+    }
+
+    public static class CommRespPayerInfo {
+        @SerializedName("openid")
+        public String openid;
+    }
+
+    public static class CommRespAmountInfo {
+        @SerializedName("total")
+        public Long total;
+
+        @SerializedName("payer_total")
+        public Long payerTotal;
+
+        @SerializedName("currency")
+        public String currency;
+
+        @SerializedName("payer_currency")
+        public String payerCurrency;
+    }
+
+    public static class CommRespSceneInfo {
+        @SerializedName("device_id")
+        public String deviceId;
+    }
+
+    public static class PromotionDetail {
+        @SerializedName("coupon_id")
+        public String couponId;
+
+        @SerializedName("name")
+        public String name;
+
+        @SerializedName("scope")
+        public String scope;
+
+        @SerializedName("type")
+        public String type;
+
+        @SerializedName("amount")
+        public Long amount;
+
+        @SerializedName("stock_id")
+        public String stockId;
+
+        @SerializedName("wechatpay_contribute")
+        public Long wechatpayContribute;
+
+        @SerializedName("merchant_contribute")
+        public Long merchantContribute;
+
+        @SerializedName("other_contribute")
+        public Long otherContribute;
+
+        @SerializedName("currency")
+        public String currency;
+
+        @SerializedName("goods_detail")
+        public List<GoodsDetailInPromotion> goodsDetail;
+    }
+
+    public static class GoodsDetailInPromotion {
+        @SerializedName("goods_id")
+        public String goodsId;
+
+        @SerializedName("quantity")
+        public Long quantity;
+
+        @SerializedName("unit_price")
+        public Long unitPrice;
+
+        @SerializedName("discount_amount")
+        public Long discountAmount;
+
+        @SerializedName("goods_remark")
+        public String goodsRemark;
+    }
+
+    public static class CreateRequest {
+        @SerializedName("transaction_id")
+        public String transactionId;
+
+        @SerializedName("out_trade_no")
+        public String outTradeNo;
+
+        @SerializedName("out_refund_no")
+        public String outRefundNo;
+
+        @SerializedName("reason")
+        public String reason;
+
+        @SerializedName("notify_url")
+        public String notifyUrl;
+
+        @SerializedName("funds_account")
+        public ReqFundsAccount fundsAccount;
+
+        @SerializedName("amount")
+        public AmountReq amount;
+
+        @SerializedName("goods_detail")
+        public List<GoodsDetail> goodsDetail;
+
+    }
+
+    public static class Refund {
+        @SerializedName("refund_id")
+        public String refundId;
+
+        @SerializedName("out_refund_no")
+        public String outRefundNo;
+
+        @SerializedName("transaction_id")
+        public String transactionId;
+
+        @SerializedName("out_trade_no")
+        public String outTradeNo;
+
+        @SerializedName("channel")
+        public Channel channel;
+
+        @SerializedName("user_received_account")
+        public String userReceivedAccount;
+
+        @SerializedName("success_time")
+        public String successTime;
+
+        @SerializedName("create_time")
+        public String createTime;
+
+        @SerializedName("status")
+        public Status status;
+
+        @SerializedName("funds_account")
+        public FundsAccount fundsAccount;
+
+        @SerializedName("amount")
+        public Amount amount;
+
+        @SerializedName("promotion_detail")
+        public List<Promotion> promotionDetail;
+
+    }
+
+    public enum ReqFundsAccount {
+        @SerializedName("AVAILABLE")
+        AVAILABLE,
+        @SerializedName("UNSETTLED")
+        UNSETTLED
+    }
+
+    public static class AmountReq {
+        @SerializedName("refund")
+        public Long refund;
+
+        @SerializedName("from")
+        public List<FundsFromItem> from;
+
+        @SerializedName("total")
+        public Long total;
+
+        @SerializedName("currency")
+        public String currency;
+    }
+
+    public static class GoodsDetail {
+        @SerializedName("merchant_goods_id")
+        public String merchantGoodsId;
+
+        @SerializedName("wechatpay_goods_id")
+        public String wechatpayGoodsId;
+
+        @SerializedName("goods_name")
+        public String goodsName;
+
+        @SerializedName("quantity")
+        public Long quantity;
+
+        @SerializedName("unit_price")
+        public Long unitPrice;
+
+        @SerializedName("refund_amount")
+        public Long refundAmount;
+
+        @SerializedName("refund_quantity")
+        public Long refundQuantity;
+    }
+
+    public enum Channel {
+        @SerializedName("ORIGINAL")
+        ORIGINAL,
+        @SerializedName("BALANCE")
+        BALANCE,
+        @SerializedName("OTHER_BALANCE")
+        OTHER_BALANCE,
+        @SerializedName("OTHER_BANKCARD")
+        OTHER_BANKCARD
+    }
+
+    public enum Status {
+        @SerializedName("SUCCESS")
+        SUCCESS,
+        @SerializedName("CLOSED")
+        CLOSED,
+        @SerializedName("PROCESSING")
+        PROCESSING,
+        @SerializedName("ABNORMAL")
+        ABNORMAL
+    }
+
+    public enum FundsAccount {
+        @SerializedName("UNSETTLED")
+        UNSETTLED,
+        @SerializedName("AVAILABLE")
+        AVAILABLE,
+        @SerializedName("UNAVAILABLE")
+        UNAVAILABLE,
+        @SerializedName("OPERATION")
+        OPERATION,
+        @SerializedName("BASIC")
+        BASIC,
+        @SerializedName("ECNY_BASIC")
+        ECNY_BASIC
+    }
+
+    public static class Amount {
+        @SerializedName("total")
+        public Long total;
+
+        @SerializedName("refund")
+        public Long refund;
+
+        @SerializedName("from")
+        public List<FundsFromItem> from;
+
+        @SerializedName("payer_total")
+        public Long payerTotal;
+
+        @SerializedName("payer_refund")
+        public Long payerRefund;
+
+        @SerializedName("settlement_refund")
+        public Long settlementRefund;
+
+        @SerializedName("settlement_total")
+        public Long settlementTotal;
+
+        @SerializedName("discount_refund")
+        public Long discountRefund;
+
+        @SerializedName("currency")
+        public String currency;
+
+        @SerializedName("refund_fee")
+        public Long refundFee;
+    }
+
+    public static class Promotion {
+        @SerializedName("promotion_id")
+        public String promotionId;
+
+        @SerializedName("scope")
+        public PromotionScope scope;
+
+        @SerializedName("type")
+        public PromotionType type;
+
+        @SerializedName("amount")
+        public Long amount;
+
+        @SerializedName("refund_amount")
+        public Long refundAmount;
+
+        @SerializedName("goods_detail")
+        public List<GoodsDetail> goodsDetail;
+    }
+
+    public static class FundsFromItem {
+        @SerializedName("account")
+        public Account account;
+
+        @SerializedName("amount")
+        public Long amount;
+    }
+
+    public enum PromotionScope {
+        @SerializedName("GLOBAL")
+        GLOBAL,
+        @SerializedName("SINGLE")
+        SINGLE
+    }
+
+    public enum PromotionType {
+        @SerializedName("CASH")
+        CASH,
+        @SerializedName("NOCASH")
+        NOCASH
+    }
+
+    public enum Account {
+        @SerializedName("AVAILABLE")
+        AVAILABLE,
+        @SerializedName("UNAVAILABLE")
+        UNAVAILABLE
+    }
+
+    /**
+     * 从微信支付退款响应构建RefundRecord对象(成功情况)
+     */
+    private RefundRecord buildRefundRecordFromWeChatResponse(Refund response, CreateRequest request, Map<String, String> params) {
+        try {
+            RefundRecord record = new RefundRecord();
+            
+            // 基本信息
+            record.setPayType(PaymentEnum.WECHAT_PAY.getType());
+            record.setOutTradeNo(response.outTradeNo != null ? response.outTradeNo : request.outTradeNo);
+            record.setTransactionId(response.transactionId);
+            record.setOutRefundNo(response.outRefundNo != null ? response.outRefundNo : request.outRefundNo);
+            record.setRefundId(response.refundId);
+            record.setRefundStatus(response.status != null ? response.status.name() : "UNKNOWN");
+            
+            // 金额信息(微信返回的是分)
+            if (response.amount != null) {
+                record.setTotalAmount(response.amount.total);
+                record.setRefundAmount(response.amount.refund);
+                record.setActualRefundAmount(response.amount.refund); // 微信退款金额就是实际退款金额
+                if (response.amount.currency != null) {
+                    record.setCurrency(response.amount.currency);
+                } else {
+                    record.setCurrency("CNY");
+                }
+            } else {
+                record.setTotalAmount(request.amount.total);
+                record.setRefundAmount(request.amount.refund);
+                record.setActualRefundAmount(request.amount.refund);
+                record.setCurrency(request.amount.currency != null ? request.amount.currency : "CNY");
+            }
+            
+            // 退款原因
+            record.setRefundReason(request.reason);
+            
+            // 退款渠道
+            if (response.channel != null) {
+                record.setRefundChannel(response.channel.name());
+            }
+            
+            // 退款资金来源
+            if (response.fundsAccount != null) {
+                record.setFundsAccount(response.fundsAccount.name());
+            }
+            
+            // 用户收到退款账户
+            record.setUserReceivedAccount(response.userReceivedAccount);
+            
+            // 退款详情(如果有优惠信息)
+            if (response.promotionDetail != null && !response.promotionDetail.isEmpty()) {
+                record.setRefundDetail(WXPayUtility.toJson(response.promotionDetail));
+            }
+            
+            // 退款时间(微信返回的是ISO 8601格式字符串,需要转换为Date)
+            if (response.createTime != null) {
+                Date createTime = parseWeChatTime(response.createTime);
+                if (createTime != null) {
+                    record.setRefundCreateTime(createTime);
+                }
+            }
+            if (response.successTime != null) {
+                Date successTime = parseWeChatTime(response.successTime);
+                if (successTime != null) {
+                    record.setRefundSuccessTime(successTime);
+                }
+            }
+            
+            // 业务信息(从params获取,如果有的话)
+            if (params != null) {
+                if (params.containsKey("userId")) {
+                    try {
+                        record.setUserId(Integer.parseInt(params.get("userId")));
+                    } catch (NumberFormatException e) {
+                        log.warn("解析userId失败: {}", params.get("userId"));
+                    }
+                }
+                if (params.containsKey("orderId")) {
+                    record.setOrderId(params.get("orderId"));
+                }
+                if (params.containsKey("storeId")) {
+                    try {
+                        record.setStoreId(Integer.parseInt(params.get("storeId")));
+                    } catch (NumberFormatException e) {
+                        log.warn("解析storeId失败: {}", params.get("storeId"));
+                    }
+                }
+            }
+            
+            // 响应数据
+            record.setResponseData(WXPayUtility.toJson(response));
+            
+            // 标准字段
+            record.setDeleteFlag(0);
+            record.setCreatedTime(new Date());
+            
+            return record;
+        } catch (Exception e) {
+            log.error("构建RefundRecord对象失败", e);
+            return null;
+        }
+    }
+
+    /**
+     * 从微信支付退款错误响应构建RefundRecord对象(失败情况)
+     */
+    private RefundRecord buildRefundRecordFromWeChatError(Refund response, CreateRequest request, Map<String, String> params, String status) {
+        try {
+            RefundRecord record = new RefundRecord();
+            
+            // 基本信息
+            record.setPayType(PaymentEnum.WECHAT_PAY.getType());
+            record.setOutTradeNo(response.outTradeNo != null ? response.outTradeNo : request.outTradeNo);
+            record.setTransactionId(response.transactionId);
+            record.setOutRefundNo(response.outRefundNo != null ? response.outRefundNo : request.outRefundNo);
+            record.setRefundId(response.refundId);
+            record.setRefundStatus(status);
+            
+            // 金额信息
+            if (response.amount != null) {
+                record.setTotalAmount(response.amount.total);
+                record.setRefundAmount(response.amount.refund);
+                record.setCurrency(response.amount.currency != null ? response.amount.currency : "CNY");
+            } else {
+                record.setTotalAmount(request.amount.total);
+                record.setRefundAmount(request.amount.refund);
+                record.setCurrency(request.amount.currency != null ? request.amount.currency : "CNY");
+            }
+            
+            // 退款原因
+            record.setRefundReason(request.reason);
+            
+            // 退款时间
+            if (response.createTime != null) {
+                Date createTime = parseWeChatTime(response.createTime);
+                if (createTime != null) {
+                    record.setRefundCreateTime(createTime);
+                }
+            }
+            
+            // 业务信息
+            if (params != null) {
+                if (params.containsKey("userId")) {
+                    try {
+                        record.setUserId(Integer.parseInt(params.get("userId")));
+                    } catch (NumberFormatException e) {
+                        log.warn("解析userId失败: {}", params.get("userId"));
+                    }
+                }
+                if (params.containsKey("orderId")) {
+                    record.setOrderId(params.get("orderId"));
+                }
+                if (params.containsKey("storeId")) {
+                    try {
+                        record.setStoreId(Integer.parseInt(params.get("storeId")));
+                    } catch (NumberFormatException e) {
+                        log.warn("解析storeId失败: {}", params.get("storeId"));
+                    }
+                }
+            }
+            
+            // 响应数据
+            record.setResponseData(WXPayUtility.toJson(response));
+            
+            // 标准字段
+            record.setDeleteFlag(0);
+            record.setCreatedTime(new Date());
+            
+            return record;
+        } catch (Exception e) {
+            log.error("构建RefundRecord对象失败", e);
+            return null;
+        }
+    }
+
+    /**
+     * 从微信支付退款异常构建RefundRecord对象(异常情况)
+     */
+    private RefundRecord buildRefundRecordFromWeChatException(CreateRequest request, Map<String, String> params, Exception e) {
+        try {
+            RefundRecord record = new RefundRecord();
+            
+            // 基本信息
+            record.setPayType(PaymentEnum.WECHAT_PAY.getType());
+            record.setOutTradeNo(request.outTradeNo);
+            record.setOutRefundNo(request.outRefundNo);
+            record.setRefundStatus("ABNORMAL");
+            
+            // 金额信息
+            record.setTotalAmount(request.amount.total);
+            record.setRefundAmount(request.amount.refund);
+            record.setCurrency(request.amount.currency != null ? request.amount.currency : "CNY");
+            
+            // 退款原因
+            record.setRefundReason(request.reason);
+            
+            // 错误信息
+            if (e instanceof WXPayUtility.ApiException) {
+                WXPayUtility.ApiException apiException = (WXPayUtility.ApiException) e;
+                record.setErrorCode(String.valueOf(apiException.getErrorCode()));
+                record.setErrorMsg(apiException.getMessage());
+            } else {
+                record.setErrorMsg(e.getMessage());
+            }
+            
+            // 业务信息
+            if (params != null) {
+                if (params.containsKey("userId")) {
+                    try {
+                        record.setUserId(Integer.parseInt(params.get("userId")));
+                    } catch (NumberFormatException ex) {
+                        log.warn("解析userId失败: {}", params.get("userId"));
+                    }
+                }
+                if (params.containsKey("orderId")) {
+                    record.setOrderId(params.get("orderId"));
+                }
+                if (params.containsKey("storeId")) {
+                    try {
+                        record.setStoreId(Integer.parseInt(params.get("storeId")));
+                    } catch (NumberFormatException ex) {
+                        log.warn("解析storeId失败: {}", params.get("storeId"));
+                    }
+                }
+            }
+            
+            // 标准字段
+            record.setDeleteFlag(0);
+            record.setCreatedTime(new Date());
+            record.setRefundCreateTime(new Date());
+            
+            return record;
+        } catch (Exception ex) {
+            log.error("构建RefundRecord对象失败", ex);
+            return null;
+        }
+    }
+
+    /**
+     * 解析微信支付返回的时间字符串(ISO 8601格式)
+     * 例如:2018-06-08T10:34:56+08:00
+     */
+    private Date parseWeChatTime(String timeStr) {
+        if (timeStr == null || timeStr.trim().isEmpty()) {
+            return null;
+        }
+        try {
+            // 尝试解析ISO 8601格式
+            OffsetDateTime dateTime = OffsetDateTime.parse(timeStr, DateTimeFormatter.ISO_OFFSET_DATE_TIME);
+            return Date.from(dateTime.toInstant());
+        } catch (Exception e) {
+            // 如果ISO 8601解析失败,尝试其他格式
+            try {
+                SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+                return sdf.parse(timeStr);
+            } catch (ParseException ex) {
+                log.warn("解析微信支付时间失败: {}", timeStr, ex);
+                return null;
+            }
+        }
+    }
+}

+ 1 - 0
alien-lawyer/src/main/java/shop/alien/lawyer/service/LawFirmReconciliationService.java

@@ -17,6 +17,7 @@ import java.util.Date;
  */
 public interface LawFirmReconciliationService {
 
+
     /**
      * 获取律所对账总览
      *

+ 4 - 1
alien-lawyer/src/main/java/shop/alien/lawyer/service/LawFirmService.java

@@ -66,9 +66,12 @@ public interface LawFirmService extends IService<LawFirm> {
      * 导出律所数据到Excel
      *
      * @param response HTTP响应
+     * @param firmId 律所ID(可选)
+     * @param pageNum 页码(可选,默认1)
+     * @param pageSize 页容(可选,默认10,如果为null则导出全部)
      * @throws IOException IO异常
      */
-    void exportLawFirm(HttpServletResponse response) throws IOException;
+    void exportLawFirm(HttpServletResponse response, Integer firmId, Integer pageNum, Integer pageSize) throws IOException;
 
     /**
      * 导入律所数据从Excel

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

@@ -58,6 +58,19 @@ public interface LawyerClientConsultationOrderService extends IService<LawyerCon
      */
     R<Boolean> confirmOrder(String id, String actionType);
 
+
+    /**
+     * 订单开始计时(律师端)
+     * <p>
+     * 订单开始计时:
+     * 1. 当订单为按时收费,订单状态为进行中时,订单开始计时
+     * </p>
+     *
+     * @param id        订单ID
+     * @return R<Boolean> 是否成功
+     */
+    R<Boolean> orderStartTiming(String id);
+
     /**
      * 申请退款
      *
@@ -75,5 +88,22 @@ public interface LawyerClientConsultationOrderService extends IService<LawyerCon
      * @return R<Boolean> 是否成功
      */
     R<Boolean> refundApplyProcess(LawyerConsultationOrder lawyerConsultationOrder);
+
+    /**
+     * 检查订单接单后是否有消息记录
+     * <p>
+     * 根据订单ID查询订单信息,获取律师接单时间、用户ID和律师ID,
+     * 然后查询 life_message 表,检查是否存在律师发送给用户的消息记录,
+     * 且消息创建时间大于接单时间。
+     * </p>
+     *
+     * @param id 订单ID,不能为空
+     * @return true表示有消息记录,false表示无消息记录
+     * @throws IllegalArgumentException 当订单ID无效时抛出
+     * @throws RuntimeException        当查询异常时抛出
+     * @author system
+     * @since 2025-01-XX
+     */
+    Boolean checkMessageAfterAccept(String id);
 }
 

+ 13 - 1
alien-lawyer/src/main/java/shop/alien/lawyer/service/LawyerNoticeService.java

@@ -32,7 +32,19 @@ public interface LawyerNoticeService extends IService<LifeNotice> {
      */
     R<Boolean> hasUnreadNotice(String receiverId);
 
+    /**
+     * 根据通知ID标记通知为已读
+     * <p>
+     * 将指定ID的通知标记为已读状态(isRead = 1)
+     * </p>
+     *
+     * @param id 通知ID,必须大于0且不能为null
+     * @return 受影响的记录数,>0表示更新成功,0表示未找到匹配的记录或更新失败
+     * @throws IllegalArgumentException 当通知ID无效时抛出
+     * @throws RuntimeException        当更新操作异常时抛出
+     * @author system
+     * @since 2025-01-XX
+     */
     int readNoticeById(Integer id);
 
-
 }

+ 31 - 0
alien-lawyer/src/main/java/shop/alien/lawyer/service/LawyerStatisticsService.java

@@ -0,0 +1,31 @@
+package shop.alien.lawyer.service;
+
+import shop.alien.entity.result.R;
+import shop.alien.entity.store.vo.LawyerDashboardVO;
+import shop.alien.entity.store.vo.LawyerOrderStatisticsVO;
+
+/**
+ * 律师统计 服务类
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+public interface LawyerStatisticsService {
+
+    /**
+     * 获取律师订单统计数据
+     *
+     * @param lawyerUserId 律师用户ID
+     * @return 订单统计数据
+     */
+    R<LawyerOrderStatisticsVO> getOrderStatistics(Integer lawyerUserId);
+
+    /**
+     * 获取律师仪表板数据(包含个人信息、订单统计和收益统计)
+     *
+     * @param lawyerUserId 律师用户ID
+     * @return 仪表板数据
+     */
+    R<LawyerDashboardVO> getLawyerDashboard(Integer lawyerUserId);
+}
+

+ 3 - 0
alien-lawyer/src/main/java/shop/alien/lawyer/service/LawyerUserService.java

@@ -5,6 +5,7 @@ import com.baomidou.mybatisplus.extension.service.IService;
 import org.springframework.web.bind.annotation.RequestParam;
 import shop.alien.entity.result.R;
 import shop.alien.entity.store.LawyerUser;
+import shop.alien.entity.store.dto.LawyerUserDto;
 import shop.alien.entity.store.vo.LawyerUserVo;
 
 import javax.servlet.http.HttpServletResponse;
@@ -193,5 +194,7 @@ public interface LawyerUserService extends IService<LawyerUser> {
                           String endTime,
                           Integer pageNum,
                           Integer pageSize) throws Exception;
+
+    R<LawyerUserVo> updateLawyerUserCharge(LawyerUserDto lawyerUserDto);
 }
 

+ 14 - 0
alien-lawyer/src/main/java/shop/alien/lawyer/service/LawyerUserViolationService.java

@@ -7,6 +7,7 @@ import shop.alien.entity.store.StoreDictionary;
 import shop.alien.entity.store.UserLoginInfo;
 import shop.alien.entity.store.dto.LawyerUserViolationDto;
 import shop.alien.entity.store.vo.LawyerUserViolationVo;
+import shop.alien.entity.store.vo.LawyerViolationDetailVO;
 
 import java.io.IOException;
 import java.util.List;
@@ -69,5 +70,18 @@ public interface LawyerUserViolationService extends IService<LawyerUserViolation
      * @since 2025-01-XX
      */
     List<StoreDictionary> getViolationReason();
+
+    /**
+     * 根据订单ID查询举报详情
+     * <p>
+     * 查询举报详情信息,包括举报的详情信息、订单的详情信息以及订单律师名字相关信息
+     * </p>
+     *
+     * @param orderId 订单ID,必须大于0
+     * @return 举报详情VO对象,包含举报信息、订单信息、律师信息,如果订单不存在或未举报则返回null
+     * @author system
+     * @since 2025-01-XX
+     */
+    LawyerViolationDetailVO getViolationDetailByOrderId(Integer orderId);
 }
 

+ 15 - 0
alien-lawyer/src/main/java/shop/alien/lawyer/service/OrderExpirationService.java

@@ -22,6 +22,13 @@ public interface OrderExpirationService {
     void handleRefundOrderKey(String expiredKey);
 
     /**
+     * 處理訂單支付超時
+     *
+     * @param orderNum 訂單no
+     */
+    void handleTimeOutCompleteOrder(String orderNum);
+
+    /**
      * 設置訂單支付超時監聽
      * 
      * @param orderNumber 訂單NO
@@ -46,6 +53,14 @@ public interface OrderExpirationService {
     void setOrderRefundTimeout(String orderNumber, long timeoutSeconds);
 
     /**
+     * 设置订单超时完成监听
+     *
+     * @param orderNumber 订单NO
+     * @param timeoutSeconds 超时时间(秒),默認30分鐘
+     */
+    void setOrderCompleteTimeout(String orderNumber, long timeoutSeconds);
+
+    /**
      * 取消訂單支付超時監聽(當訂單已支付時調用)
      * 
      * @param orderNumber 訂單NO

+ 17 - 0
alien-lawyer/src/main/java/shop/alien/lawyer/service/RefundRecordService.java

@@ -0,0 +1,17 @@
+package shop.alien.lawyer.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import shop.alien.entity.store.RefundRecord;
+
+/**
+ * <p>
+ * 退款记录表 服务类
+ * </p>
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+public interface RefundRecordService extends IService<RefundRecord> {
+
+}
+

+ 12 - 0
alien-lawyer/src/main/java/shop/alien/lawyer/service/ReviewCommentService.java

@@ -3,6 +3,7 @@ package shop.alien.lawyer.service;
 import com.baomidou.mybatisplus.extension.service.IService;
 import shop.alien.entity.result.R;
 import shop.alien.entity.store.ReviewComment;
+import shop.alien.entity.store.dto.LawyerReplyDto;
 import shop.alien.entity.store.dto.ReviewCommentRequestDto;
 import shop.alien.entity.store.dto.ReviewReplyDto;
 import shop.alien.entity.store.vo.ReviewCommentVo;
@@ -84,5 +85,16 @@ public interface ReviewCommentService extends IService<ReviewComment> {
      * @return R<Boolean>
      */
     R<Boolean> deleteReviewComment(ReviewComment reviewComment);
+
+    /**
+     * 律师回复(律师对用户的评价或评论的回复)
+     * 
+     * @deprecated 已废弃,请使用 createComment(回复评价)或 createReply(回复评论)接口
+     *             通过 sendUserType=2 标识律师身份,系统会自动进行权限校验
+     * @param replyDto 律师回复DTO
+     * @return R<ReviewComment>
+     */
+    @Deprecated
+    R<ReviewComment> lawyerReply(LawyerReplyDto replyDto);
 }
 

+ 269 - 0
alien-lawyer/src/main/java/shop/alien/lawyer/service/StoreCommentService.java

@@ -0,0 +1,269 @@
+package shop.alien.lawyer.service;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.service.IService;
+import org.springframework.web.multipart.MultipartRequest;
+import shop.alien.entity.result.R;
+import shop.alien.entity.store.StoreComment;
+import shop.alien.entity.store.dto.OrderReviewDto;
+import shop.alien.entity.store.dto.ReviewCommentRequestDto;
+import shop.alien.entity.store.dto.ReviewReplyDto;
+import shop.alien.entity.store.vo.LifeUserOrderCommentVo;
+import shop.alien.entity.store.vo.OrderReviewDetailVo;
+import shop.alien.entity.store.vo.OrderReviewVo;
+import shop.alien.entity.store.vo.ReviewCommentVo;
+import shop.alien.entity.store.vo.StoreCommentCountVo;
+import shop.alien.entity.store.vo.StoreCommentVo;
+import shop.alien.entity.store.vo.StoreCommitPercentVo;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 评论表 服务类
+ *
+ * @author ssk
+ * @since 2025-01-02
+ */
+public interface StoreCommentService extends IService<StoreComment> {
+
+    /**
+     * 评论列表
+     *
+     * @param pageNum      页数
+     * @param pageSize     页容
+     * @param businessId   业务id
+     * @param businessType 业务类型(1:订单评论, 2:动态社区评论, 3:活动评论, 4:店铺打卡评论, 5:订单评价, 6:订单评论的评论)
+     * @param storeId      门店id
+     * @param replyStatus  回复状态(0:全部, 1:已回复, 2:未回复)
+     * @param commentLevel 评论等级(0:全部, 1:好评, 2:中评, 3:差评)
+     * @param days         查询时间, 多少天前
+     * @param phoneId      消息标识
+     * @param userType     评论的用户类型(0:商家, 其他:用户)
+     * @param tagId        标签id
+     * @param hasImage     是否有图片(0否/1是)
+     * @return IPage<StoreComment>
+     */
+    IPage<StoreCommentVo> getList(Integer pageNum, Integer pageSize, Integer businessId, Integer businessType, Integer storeId, Integer replyStatus, Integer commentLevel, Integer days, String phoneId, Integer userType, Integer tagId, Boolean hasImage);
+
+    /**
+     * 获取最新一条评论/评价
+     *
+     * @param businessId   业务id
+     * @param businessType 业务类型(1:订单评论, 2:动态社区评论, 3:活动评论,4:店铺打卡评论)
+     * @param storeId      门店id
+     * @return StoreCommentVo
+     */
+    StoreCommentVo getOne(Integer businessId, Integer businessType, Integer storeId);
+
+    /**
+     * 评论/评价数量和评分
+     *
+     * @param businessId   业务id
+     * @param businessType 业务类型(1:订单评论, 2:动态社区评论, 3:活动评论,4:店铺打卡评论)
+     * @param storeId      门店id
+     * @return Integer
+     */
+    Map<String, Object> getCommitCountAndScore(Integer businessId, Integer businessType, Integer storeId, String phoneId, Integer days);
+
+    /**
+     * 评论
+     *
+     * @param multipartRequest 文件
+     * @param businessId       业务id
+     * @param businessType     业务类型(1:订单评论, 2:动态社区评论, 3:活动评论,4:店铺打卡评论)
+     * @param storeId          门店id
+     * @param userId           用户id
+     * @param replyId          回复id
+     * @param commentContent   评价内容
+     * @return 0:成功, 1:失败, 2:文本内容异常, 3:图片内容异常
+     */
+    Integer userComment(MultipartRequest multipartRequest, Integer businessId, Integer businessType, Integer storeId, Integer userId, Integer replyId, String commentContent);
+
+    /**
+     * 新增或修改评论/评价
+     *
+     * @param multipartRequest 文件
+     * @param id               主键
+     * @param businessId       业务id
+     * @param businessType     业务类型(1:订单评论, 2:动态社区评论, 3:活动评论,4:店铺打卡评论)
+     * @param storeId          门店id
+     * @param userId           用户id
+     * @param replyId          回复id
+     * @param commentContent   评价内容
+     * @param score            评分
+     * @param otherScore       其他评分
+     * @param isAnonymous      是否匿名(0:否(默认), 1:是)
+     * @param evaluationTags   评价标签
+     * @param phoneId          用户id
+     * @return 0:成功, 1:失败, 2:文本内容异常, 3:图片内容异常
+     */
+    Integer addComment(MultipartRequest multipartRequest, Integer id, Integer businessId, Integer businessType, Integer storeId, Integer userId, Integer replyId, String commentContent, Double score, String otherScore, Integer isAnonymous, String evaluationTags, String phoneId);
+
+    /**
+     * 新增或修改评论/评价
+     *
+     * @param multipartRequest 文件
+     * @param id               主键
+     * @param businessId       业务id
+     * @param businessType     业务类型(1:订单评论, 2:动态社区评论, 3:活动评论,4:店铺打卡评论)
+     * @param storeId          门店id
+     * @param userId           用户id
+     * @param replyId          回复id
+     * @param commentContent   评价内容
+     * @param score            评分
+     * @param otherScore       其他评分
+     * @param isAnonymous      是否匿名(0:否(默认), 1:是)
+     * @param evaluationTags   评价标签
+     * @param phoneId          用户id
+     * @return 0:成功, 1:失败, 2:文本内容异常, 3:图片内容异常
+     */
+    Integer addCommentNew(MultipartRequest multipartRequest, Integer id, Integer businessId, Integer businessType, Integer storeId, Integer orderId, Integer lawyerId, Integer userId, Integer replyId, String commentContent, Double score, String otherScore, Integer isAnonymous, String evaluationTags, String phoneId);
+
+    /**
+     * 回复率, 评价比例
+     *
+     * @param storeId 门店id
+     * @return StoreCommitPercentVo
+     */
+    StoreCommitPercentVo getCommitPercent(Integer storeId);
+
+    /**
+     * @param type 1:未评价, 2:已评价
+     */
+    IPage<LifeUserOrderCommentVo> getCommentOrderPage(Integer pageNum, Integer pageSize, Integer type, String userId);
+
+    /**
+     * 查询当前用户全部评价(店铺+律师),格式同 LifeUserOrderCommentVo
+     */
+    IPage<LifeUserOrderCommentVo> getUserAllCommentsPage(Integer pageNum, Integer pageSize, Integer userId);
+
+    /**
+     * 获取店铺评价计数统计
+     *
+     * @param storeId 门店id
+     * @return StoreCommentCountVo
+     */
+    StoreCommentCountVo getAppraiseCount(Integer storeId);
+
+    // ==================== 订单评价相关方法(迁移自 OrderReviewService)====================
+
+    /**
+     * 创建订单评价(只有订单用户才能评价)
+     *
+     * @param reviewDto 评价DTO
+     * @return R<StoreComment>
+     */
+    R<StoreComment> createOrderReview(OrderReviewDto reviewDto);
+
+    /**
+     * 获取评价详情(包含评论和回复)
+     *
+     * @param orderId 评价ID
+     * @param currentUserId 当前用户ID(用于判断是否已点赞,可为null)
+     * @return R<OrderReviewDetailVo>
+     */
+    R<OrderReviewDetailVo> getOrderReviewDetail(Integer orderId, Integer currentUserId);
+
+    /**
+     * 点赞评价
+     *
+     * @param reviewId 评价ID
+     * @param userId 用户ID
+     * @return R<Boolean>
+     */
+    R<Boolean> likeOrderReview(Integer reviewId, Integer userId);
+
+    /**
+     * 取消点赞评价
+     *
+     * @param reviewId 评价ID
+     * @param userId 用户ID
+     * @return R<Boolean>
+     */
+    R<Boolean> cancelLikeOrderReview(Integer reviewId, Integer userId);
+
+    /**
+     * 分页查询评价列表
+     *
+     * @param page 页码
+     * @param size 页大小
+     * @param orderId 订单ID(可选)
+     * @param lawyerUserId 律师用户ID(可选)
+     * @param userId 评价用户ID(可选)
+     * @param currentUserId 当前用户ID(用于判断是否已点赞,可为null)
+     * @return R<IPage<OrderReviewVo>>
+     */
+    R<IPage<OrderReviewVo>> getOrderReviewList(int page, int size, Integer orderId, Integer lawyerUserId, Integer userId, Integer currentUserId);
+
+    /**
+     * 根据订单ID查询评价
+     *
+     * @param orderId 订单ID
+     * @param currentUserId 当前用户ID(用于判断是否已点赞,可为null)
+     * @return R<OrderReviewVo>
+     */
+    R<OrderReviewVo> getOrderReviewByOrderId(Integer orderId, Integer currentUserId);
+
+    // ==================== 评价评论相关方法(迁移自 ReviewCommentService)====================
+
+    /**
+     * 创建评论(其他用户对评价的评论)
+     *
+     * @param comment 评论实体
+     * @return R<StoreComment>
+     */
+    R<StoreComment> createReviewComment(StoreComment comment);
+
+    /**
+     * 根据评价ID查询评论列表
+     *
+     * @param reviewId 评价ID
+     * @param currentUserId 当前用户ID(用于判断是否已点赞,可为null)
+     * @return R<List<ReviewCommentVo>>
+     */
+    R<List<ReviewCommentVo>> getReviewCommentListByReviewId(Integer reviewId, Integer currentUserId);
+
+    /**
+     * 点赞评论
+     *
+     * @param requestDto 请求DTO
+     * @return R<Boolean>
+     */
+    R<Boolean> likeReviewComment(ReviewCommentRequestDto requestDto);
+
+    /**
+     * 取消点赞评论
+     *
+     * @param requestDto 请求DTO
+     * @return R<Boolean>
+     */
+    R<Boolean> cancelLikeReviewComment(ReviewCommentRequestDto requestDto);
+
+    /**
+     * 创建回复(用户对评论的回复)
+     *
+     * @param replyDto 回复DTO
+     * @return R<StoreComment>
+     */
+    R<StoreComment> createReviewReply(ReviewReplyDto replyDto);
+
+    /**
+     * 根据首评ID查询回复列表
+     *
+     * @param headId 首评ID
+     * @param currentUserId 当前用户ID(用于判断是否已点赞,可为null)
+     * @return R<List<ReviewCommentVo>>
+     */
+    R<List<ReviewCommentVo>> getReviewReplyListByHeadId(Integer headId, Integer currentUserId);
+
+    /**
+     * 删除回复
+     *
+     * @param replyId 回复ID
+     * @param userId 用户ID
+     * @return R<Boolean>
+     */
+    R<Boolean> deleteReviewReply(Integer replyId, Integer userId);
+
+}

+ 10 - 4
alien-lawyer/src/main/java/shop/alien/lawyer/service/impl/CommentAppealServiceImpl.java

@@ -71,10 +71,9 @@ public class CommentAppealServiceImpl extends ServiceImpl<CommentAppealMapper, C
             return R.fail("该评论已有申诉记录,无法重复申诉");
         }
 
-        // 如果orderId为空,从OrderReview中获取
-        if (commentAppeal.getOrderId() == null) {
-            OrderReview orderReview = orderReviewMapper.selectById(commentAppeal.getCommentId());
-            if (orderReview != null && orderReview.getOrderId() != null) {
+        OrderReview orderReview = orderReviewMapper.selectById(commentAppeal.getCommentId());
+        if(orderReview != null){
+            if (orderReview.getOrderId() != null) {
                 commentAppeal.setOrderId(orderReview.getOrderId());
             }
         }
@@ -89,6 +88,10 @@ public class CommentAppealServiceImpl extends ServiceImpl<CommentAppealMapper, C
         boolean result = this.save(commentAppeal);
         if (result) {
             log.info("提交申诉成功,id={}", commentAppeal.getId());
+            if(orderReview != null){
+                orderReview.setAppealId(commentAppeal.getId().toString());
+                orderReviewMapper.updateById(orderReview);
+            }
             
             // 更新订单表的申诉状态为2(申诉中)
             if (commentAppeal.getOrderId() != null) {
@@ -215,6 +218,7 @@ public class CommentAppealServiceImpl extends ServiceImpl<CommentAppealMapper, C
             lifeNotice.setIsRead(0);
             lifeNotice.setNoticeType(1);
             lifeNotice.setDeleteFlag(0);
+            lifeNotice.setBusinessType(1);
 
             com.alibaba.fastjson2.JSONObject jsonObject = new com.alibaba.fastjson2.JSONObject();
             jsonObject.put("message", message);
@@ -281,6 +285,7 @@ public class CommentAppealServiceImpl extends ServiceImpl<CommentAppealMapper, C
             lifeNotice.setIsRead(0);
             lifeNotice.setNoticeType(1);
             lifeNotice.setDeleteFlag(0);
+            lifeNotice.setBusinessType(1);
 
             com.alibaba.fastjson2.JSONObject jsonObject = new com.alibaba.fastjson2.JSONObject();
             jsonObject.put("message", message);
@@ -326,6 +331,7 @@ public class CommentAppealServiceImpl extends ServiceImpl<CommentAppealMapper, C
             lifeNotice.setIsRead(0);
             lifeNotice.setNoticeType(1);
             lifeNotice.setDeleteFlag(0);
+            lifeNotice.setBusinessType(1);
 
             com.alibaba.fastjson2.JSONObject jsonObject = new com.alibaba.fastjson2.JSONObject();
             jsonObject.put("message", message);

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

@@ -69,9 +69,9 @@ public class LawFirmReconciliationServiceImpl implements LawFirmReconciliationSe
                 return R.fail("律所不存在或已被删除");
             }
             resultFirmName = lawFirm.getFirmName();
-            if (lawFirm.getPlatformCommissionRatio() == null) {
-                return R.fail("律所[" + resultFirmName + "]的平台佣金比例未设置,无法计算对账信息");
-            }
+//            if (lawFirm.getPlatformCommissionRatio() == null) {
+//                return R.fail("律所[" + resultFirmName + "]的平台佣金比例未设置,无法计算对账信息");
+//            }
         } else if (firmName != null && !firmName.trim().isEmpty()) {
             // 如果只传了 firmName,使用传入的名称
             resultFirmName = firmName;

+ 35 - 37
alien-lawyer/src/main/java/shop/alien/lawyer/service/impl/LawFirmServiceImpl.java

@@ -103,11 +103,32 @@ public class LawFirmServiceImpl extends ServiceImpl<LawFirmMapper, LawFirm> impl
             existingLawFirm = this.getOne(creditCodeWrapper);
         }
         
-        // existingLawFirm 不为空执行更新子表,否则执行新增主子表操作
+        // existingLawFirm 不为空执行更新主表和子表,否则执行新增主子表操作
         if (existingLawFirm != null) {
-            // 更新子表操作:只更新子表数据,不更新主表
+            // 更新主表和子表操作
             Integer firmId = existingLawFirm.getId();
-            log.info("统一社会信用代码[{}]已存在,只更新子表数据,律所ID={}", lawFirm.getCreditCode(), firmId);
+            log.info("统一社会信用代码[{}]已存在,更新主表和子表数据,律所ID={}", lawFirm.getCreditCode(), firmId);
+            
+            // 更新主表数据:将新传入的字段更新到现有记录中
+            lawFirm.setId(firmId);
+            // 保留原有的一些字段(如果新数据没有提供)
+            if (!StringUtils.hasText(lawFirm.getFirmName())) {
+                lawFirm.setFirmName(existingLawFirm.getFirmName());
+            }
+            if (lawFirm.getStatus() == null) {
+                lawFirm.setStatus(existingLawFirm.getStatus());
+            }
+            if (lawFirm.getDeleteFlag() == null) {
+                lawFirm.setDeleteFlag(existingLawFirm.getDeleteFlag());
+            }
+            
+            // 更新主表
+            boolean updateResult = this.updateById(lawFirm);
+            if (!updateResult) {
+                log.error("更新律所主表失败,firmId={}", firmId);
+                return R.fail("更新律所信息失败");
+            }
+            log.info("更新律所主表成功,firmId={}", firmId);
             
             // 保存子表数据
             if (lawFirm.getPaymentList() != null && !lawFirm.getPaymentList().isEmpty()) {
@@ -146,7 +167,9 @@ public class LawFirmServiceImpl extends ServiceImpl<LawFirmMapper, LawFirm> impl
                 }
             }
             
-            return R.data(existingLawFirm, "统一社会信用代码已存在,已更新收款账号列表");
+            // 重新查询更新后的数据返回
+            LawFirm updatedLawFirm = this.getById(firmId);
+            return R.data(updatedLawFirm, "统一社会信用代码已存在,已更新律所信息和收款账号列表");
         } else {
             // 新增主子表操作
             // 保存主表
@@ -502,14 +525,16 @@ public class LawFirmServiceImpl extends ServiceImpl<LawFirmMapper, LawFirm> impl
     }
 
     @Override
-    public void exportLawFirm(HttpServletResponse response) throws IOException {
-        log.info("LawFirmServiceImpl.exportLawFirm");
+    public void exportLawFirm(HttpServletResponse response, Integer firmId, Integer pageNum, Integer pageSize) throws IOException {
+        log.info("LawFirmServiceImpl.exportLawFirm?firmId={},pageNum={},pageSize={}", firmId, pageNum, pageSize);
         try {
-            // 使用 getPaymentPageWithFirm 的方式查询所有数据(不分页)
-            // 设置一个很大的页面大小来获取所有数据
-            Page<LawFirmPaymentVO> page = new Page<>(1, Integer.MAX_VALUE);
+            // 如果 pageSize 为 null,则导出全部数据;否则使用分页
+            int currentPage = (pageNum != null && pageNum > 0) ? pageNum : 1;
+            int size = (pageSize != null && pageSize > 0) ? pageSize : Integer.MAX_VALUE;
+            
+            Page<LawFirmPaymentVO> page = new Page<>(currentPage, size);
             IPage<LawFirmPaymentVO> pageResult = lawFirmPaymentMapper.selectPaymentPageWithFirm(
-                    page, null, null, null, null, null, null, null, null, null
+                    page, firmId, null, null, null, null, null, null, null, null
             );
 
             List<LawFirmPaymentVO> paymentList = pageResult.getRecords();
@@ -763,17 +788,6 @@ public class LawFirmServiceImpl extends ServiceImpl<LawFirmMapper, LawFirm> impl
                     if (!StringUtils.hasText(lawFirm.getDirectorPhone()) && StringUtils.hasText(otherVo.getDirectorPhone())) {
                         lawFirm.setDirectorPhone(otherVo.getDirectorPhone());
                     }
-                    // 补充平台佣金比例(只支持纯数字格式,不支持百分号)
-                    if (lawFirm.getPlatformCommissionRatio() == null && StringUtils.hasText(otherVo.getPlatformCommissionRatio())) {
-                        try {
-                            String ratioStr = otherVo.getPlatformCommissionRatio().trim();
-                            // 解析为整数(必须是纯数字)
-                            Integer ratio = Integer.parseInt(ratioStr);
-                            lawFirm.setPlatformCommissionRatio(ratio);
-                        } catch (Exception e) {
-                            log.warn("解析佣金比例失败:{},必须是纯数字格式", otherVo.getPlatformCommissionRatio(), e);
-                        }
-                    }
                 }
 
                 // 检查统一社会信用代码是否已存在
@@ -797,10 +811,6 @@ public class LawFirmServiceImpl extends ServiceImpl<LawFirmMapper, LawFirm> impl
                     if (lawFirm.getStatus() == null) {
                         lawFirm.setStatus(existingLawFirm.getStatus());
                     }
-                    // 如果新数据没有提供平台佣金比例,保留原有值
-                    if (lawFirm.getPlatformCommissionRatio() == null) {
-                        lawFirm.setPlatformCommissionRatio(existingLawFirm.getPlatformCommissionRatio());
-                    }
                 } else {
                     // 设置默认值(新增时)
                 if (lawFirm.getStatus() == null) {
@@ -1215,18 +1225,6 @@ public class LawFirmServiceImpl extends ServiceImpl<LawFirmMapper, LawFirm> impl
             }
         }
 
-        // 解析佣金比例(只支持纯数字格式,不支持百分号)
-        if (StringUtils.hasText(excelVo.getPlatformCommissionRatio())) {
-            try {
-                String ratioStr = excelVo.getPlatformCommissionRatio().trim();
-                // 解析为整数(必须是纯数字)
-                Integer ratio = Integer.parseInt(ratioStr);
-                lawFirm.setPlatformCommissionRatio(ratio);
-            } catch (Exception e) {
-                log.warn("解析佣金比例失败:{},必须是纯数字格式", excelVo.getPlatformCommissionRatio(), e);
-            }
-        }
-
         return lawFirm;
     }
 

+ 464 - 1
alien-lawyer/src/main/java/shop/alien/lawyer/service/impl/LawyerClientConsultationOrderServiceImpl.java

@@ -57,6 +57,7 @@ public class LawyerClientConsultationOrderServiceImpl extends ServiceImpl<Lawyer
     private final LifeUserMapper lifeUserMapper;
     private final WebSocketProcess webSocketProcess;
     private final LifeMessageMapper lifeMessageMapper;
+    private final LawFirmMapper lawFirmMapper;
 
     /**
      * 系统发送者ID常量
@@ -88,6 +89,26 @@ public class LawyerClientConsultationOrderServiceImpl extends ServiceImpl<Lawyer
      */
     private static final String REFUND_STATUS_AGREED = "3";
 
+    /**
+     * 分钟转秒的转换系数
+     */
+    private static final long MINUTE_TO_SECOND = 60L;
+
+    /**
+     * 律师发送者ID前缀
+     */
+    private static final String LAWYER_SENDER_PREFIX = "lawyer_";
+
+    /**
+     * 用户接收者ID前缀
+     */
+    private static final String USER_RECEIVER_PREFIX = "user_";
+
+    /**
+     * 删除标记:未删除
+     */
+    private static final Integer DELETE_FLAG_NOT_DELETED = 0;
+
     @Value("${order.coefficient}")
     private String coefficient;
     @Value("${order.coefficients}")
@@ -660,7 +681,14 @@ public class LawyerClientConsultationOrderServiceImpl extends ServiceImpl<Lawyer
         orderVO.setLawyerPhone(lawyerUser.getPhone());
         orderVO.setLawyerEmail(lawyerUser.getEmail());
         orderVO.setLawyerCertificateNo(lawyerUser.getLawyerCertificateNo());
-        orderVO.setLawFirm(lawyerUser.getLawFirm());
+        // 通过firm_id查询律所名称
+        if (lawyerUser.getFirmId() != null) {
+            LawFirm lawFirm = lawFirmMapper.selectById(lawyerUser.getFirmId());
+            orderVO.setLawFirm(lawFirm != null && (lawFirm.getDeleteFlag() == null || lawFirm.getDeleteFlag() == 0) 
+                    ? lawFirm.getFirmName() : null);
+        } else {
+            orderVO.setLawFirm(null);
+        }
         orderVO.setPracticeYears(lawyerUser.getPracticeYears());
         orderVO.setSpecialtyFields(lawyerUser.getSpecialtyFields());
         orderVO.setCertificationStatus(lawyerUser.getCertificationStatus());
@@ -794,6 +822,204 @@ public class LawyerClientConsultationOrderServiceImpl extends ServiceImpl<Lawyer
     }
 
     /**
+     * 订单开始计时(律师端)
+     * <p>
+     * 订单开始计时功能说明:
+     * 1. 通过订单ID查询订单信息,验证订单是否存在
+     * 2. 校验订单状态,必须是进行中状态才能开始计时
+     * 3. 判断订单是否按分钟收费(chargeMinute和minuteNum都不为空)
+     * 4. 如果按分钟收费,计算超时时间并调用订单完成超时方法设置超时限制
+     * 5. 超时后系统会自动将订单状态更新为已完成
+     * </p>
+     *
+     * @param id 订单ID,不能为空
+     * @return R<Boolean> 操作结果,true表示成功,false表示失败
+     * @throws IllegalArgumentException 当订单ID为空或无效时抛出
+     * @author system
+     * @since 2025-01-XX
+     */
+    @Override
+    public R<Boolean> orderStartTiming(String id) {
+        log.info("订单开始计时请求开始,订单ID={}", id);
+
+        // 参数校验
+        validateOrderId(id);
+
+        try {
+            // 查询并校验订单
+            LawyerConsultationOrder order = queryAndValidateOrder(id);
+
+            // 校验订单状态
+            validateOrderStatus(order, id);
+
+            // 处理订单计时逻辑
+            return processOrderTiming(order, id);
+
+        } catch (IllegalArgumentException e) {
+            log.warn("订单开始计时参数校验失败,订单ID={},错误信息:{}", id, e.getMessage());
+            return R.fail(e.getMessage());
+        } catch (Exception e) {
+            log.error("订单开始计时异常,订单ID={},异常信息:{}", id, e.getMessage(), e);
+            return R.fail("订单开始计时失败:" + e.getMessage());
+        }
+    }
+
+    /**
+     * 校验订单ID
+     * <p>
+     * 检查订单ID是否为空或无效
+     * </p>
+     *
+     * @param id 订单ID
+     * @throws IllegalArgumentException 当订单ID为空或无效时抛出
+     */
+    private void validateOrderId(String id) {
+        if (StringUtils.isEmpty(id)) {
+            log.warn("订单开始计时参数校验失败:订单ID为空");
+            throw new IllegalArgumentException("订单ID不能为空");
+        }
+
+        // 去除前后空格后再次检查
+        String trimmedId = id.trim();
+        if (trimmedId.isEmpty()) {
+            log.warn("订单开始计时参数校验失败:订单ID为空字符串");
+            throw new IllegalArgumentException("订单ID不能为空");
+        }
+    }
+
+    /**
+     * 查询并校验订单
+     * <p>
+     * 根据订单ID查询订单信息,并校验订单是否存在
+     * </p>
+     *
+     * @param id 订单ID
+     * @return 订单对象
+     * @throws IllegalArgumentException 当订单不存在时抛出
+     */
+    private LawyerConsultationOrder queryAndValidateOrder(String id) {
+        LawyerConsultationOrder order = this.getById(id);
+        if (order == null) {
+            log.warn("订单开始计时失败:订单不存在,订单ID={}", id);
+            throw new IllegalArgumentException("订单不存在");
+        }
+
+        log.debug("订单查询成功,订单ID={}, 订单编号={}, 订单状态={}", 
+                id, order.getOrderNumber(), order.getOrderStatus());
+        return order;
+    }
+
+    /**
+     * 校验订单状态
+     * <p>
+     * 检查订单状态是否为进行中,只有进行中的订单才能开始计时
+     * </p>
+     *
+     * @param order 订单对象
+     * @param id    订单ID(用于日志记录)
+     * @throws IllegalArgumentException 当订单状态不是进行中时抛出
+     */
+    private void validateOrderStatus(LawyerConsultationOrder order, String id) {
+        Integer inProgressStatus = LawyerStatusEnum.INPROGRESS.getStatus();
+        Integer currentOrderStatus = order.getOrderStatus();
+
+        if (!inProgressStatus.equals(currentOrderStatus)) {
+            log.warn("订单开始计时失败:订单状态不是进行中,订单ID={}, 订单编号={}, 当前状态={}, 期望状态={}",
+                    id, order.getOrderNumber(), currentOrderStatus, inProgressStatus);
+            throw new IllegalArgumentException("订单状态不是进行中,无法开始计时");
+        }
+
+        log.debug("订单状态校验通过,订单ID={}, 订单编号={}, 订单状态=进行中", id, order.getOrderNumber());
+    }
+
+    /**
+     * 处理订单计时逻辑
+     * <p>
+     * 判断订单是否按分钟收费,如果是则设置超时限制
+     * </p>
+     *
+     * @param order 订单对象
+     * @param id    订单ID(用于日志记录)
+     * @return 操作结果
+     */
+    private R<Boolean> processOrderTiming(LawyerConsultationOrder order, String id) {
+        // 判断订单是否按分钟收费
+        if (isMinuteBasedCharging(order)) {
+            // 按分钟收费,设置超时限制
+            return setMinuteBasedTimeout(order, id);
+        } else {
+            // 不是按分钟收费,无需设置超时限制
+            log.info("订单不是按分钟收费,无需设置超时限制,订单ID={}, 订单编号={}", 
+                    id, order.getOrderNumber());
+            return R.data(true, "订单不是按分钟收费,无需设置超时限制");
+        }
+    }
+
+    /**
+     * 判断订单是否按分钟收费
+     * <p>
+     * 通过检查chargeMinute和minuteNum字段是否都不为空来判断
+     * </p>
+     *
+     * @param order 订单对象
+     * @return true表示按分钟收费,false表示不是按分钟收费
+     */
+    private boolean isMinuteBasedCharging(LawyerConsultationOrder order) {
+        return order.getChargeMinute() != null && order.getMinuteNum() != null;
+    }
+
+    /**
+     * 设置按分钟收费订单的超时限制
+     * <p>
+     * 计算超时时间(分钟数转换为秒),并调用订单完成超时方法设置超时限制
+     * </p>
+     *
+     * @param order 订单对象
+     * @param id    订单ID(用于日志记录)
+     * @return 操作结果
+     */
+    private R<Boolean> setMinuteBasedTimeout(LawyerConsultationOrder order, String id) {
+        try {
+            // 计算超时时间(分钟数转换为秒)
+            long timeoutSeconds = calculateTimeoutSeconds(order.getMinuteNum());
+
+            // 调用订单完成超时方法设置超时限制
+            orderExpirationService.setOrderCompleteTimeout(order.getOrderNumber(), timeoutSeconds);
+
+            log.info("订单开始计时成功,订单ID={}, 订单编号={}, 超时时间={}秒({}分钟)",
+                    id, order.getOrderNumber(), timeoutSeconds, order.getMinuteNum());
+
+            return R.data(true, "订单开始计时成功");
+
+        } catch (Exception e) {
+            log.error("设置订单超时限制失败,订单ID={}, 订单编号={},异常信息:{}",
+                    id, order.getOrderNumber(), e.getMessage(), e);
+            throw new RuntimeException("设置订单超时限制失败:" + e.getMessage(), e);
+        }
+    }
+
+    /**
+     * 计算超时时间(秒)
+     * <p>
+     * 将分钟数转换为秒数
+     * </p>
+     *
+     * @param minuteNum 分钟数,必须大于0
+     * @return 超时时间(秒)
+     * @throws IllegalArgumentException 当分钟数无效时抛出
+     */
+    private long calculateTimeoutSeconds(Integer minuteNum) {
+        if (minuteNum == null || minuteNum <= 0) {
+            log.warn("计算超时时间失败:分钟数无效,minuteNum={}", minuteNum);
+            throw new IllegalArgumentException("分钟数必须大于0");
+        }
+
+        long timeoutSeconds = minuteNum * MINUTE_TO_SECOND;
+        log.debug("计算超时时间,分钟数={}, 超时时间={}秒", minuteNum, timeoutSeconds);
+        return timeoutSeconds;
+    }
+
+    /**
      * 申请退款
      * <p>
      * 申请退款前会进行以下校验:
@@ -1232,6 +1458,7 @@ public class LawyerClientConsultationOrderServiceImpl extends ServiceImpl<Lawyer
         JSONObject jsonObject = new JSONObject();
         jsonObject.put("message", message);
         lifeNotice.setContext(jsonObject.toJSONString());
+        lifeNotice.setBusinessType(1);
 
         return lifeNotice;
     }
@@ -1382,8 +1609,244 @@ public class LawyerClientConsultationOrderServiceImpl extends ServiceImpl<Lawyer
         JSONObject jsonObject = new JSONObject();
         jsonObject.put("message", message);
         lifeNotice.setContext(jsonObject.toJSONString());
+        lifeNotice.setBusinessType(1);
 
         return lifeNotice;
     }
+
+    /**
+     * 检查订单接单后是否有消息记录
+     * <p>
+     * 根据订单ID查询订单信息,获取律师接单时间、用户ID和律师ID,
+     * 然后查询 life_message 表,检查是否存在律师发送给用户的消息记录,
+     * 且消息创建时间大于接单时间。
+     * </p>
+     * <p>
+     * 处理流程:
+     * 1. 参数校验:订单ID不能为空
+     * 2. 查询订单信息:获取接单时间、用户ID、律师ID
+     * 3. 校验订单信息:订单必须存在,且必须有接单时间
+     * 4. 查询律师信息:获取律师手机号
+     * 5. 查询用户信息:获取用户手机号
+     * 6. 构建消息查询条件:sender_id=律师phone,receiver_id=用户phone,created_time>接单时间
+     * 7. 查询消息记录:如果存在记录返回true,否则返回false
+     * </p>
+     *
+     * @param id 订单ID,不能为空
+     * @return true表示有消息记录,false表示无消息记录
+     * @throws IllegalArgumentException 当订单ID无效或订单不存在时抛出
+     * @throws RuntimeException        当查询异常时抛出
+     * @author system
+     * @since 2025-01-XX
+     */
+    @Override
+    public Boolean checkMessageAfterAccept(String id) {
+        log.info("检查订单接单后是否有消息记录开始,订单ID={}", id);
+
+        // 参数校验
+        validateOrderId(id);
+
+        try {
+            // 查询订单信息
+            LawyerConsultationOrder order = queryOrderById(id);
+
+            // 校验订单信息
+            validateOrderInfo(order, id);
+
+            // 获取接单时间、用户ID、律师ID
+            Date acceptOrdersTime = order.getAcceptOrdersTime();
+            Integer clientUserId = order.getClientUserId();
+            Integer lawyerUserId = order.getLawyerUserId();
+
+            // 查询律师和用户信息,获取手机号
+            String lawyerPhone = queryLawyerPhone(lawyerUserId, id);
+            String userPhone = queryUserPhone(clientUserId, id);
+
+            // 构建消息查询条件并查询
+            boolean hasMessage = queryMessageAfterAccept(lawyerPhone, userPhone, acceptOrdersTime, id);
+
+            log.info("检查订单接单后是否有消息记录完成,订单ID={},是否有消息={}", id, hasMessage);
+            return hasMessage;
+
+        } catch (IllegalArgumentException e) {
+            log.warn("检查订单接单后是否有消息记录参数校验失败,订单ID={},错误信息:{}", id, e.getMessage());
+            throw e;
+        } catch (RuntimeException e) {
+            log.error("检查订单接单后是否有消息记录异常,订单ID={},异常信息:{}", id, e.getMessage(), e);
+            throw new RuntimeException("检查消息记录失败:" + e.getMessage(), e);
+        } catch (Exception e) {
+            log.error("检查订单接单后是否有消息记录未知异常,订单ID={},异常信息:{}", id, e.getMessage(), e);
+            throw new RuntimeException("检查消息记录失败,请稍后重试", e);
+        }
+    }
+
+    /**
+     * 根据订单ID查询订单信息
+     * <p>
+     * 查询订单的详细信息
+     * </p>
+     *
+     * @param id 订单ID
+     * @return 订单信息
+     * @throws RuntimeException 当查询异常时抛出
+     */
+    private LawyerConsultationOrder queryOrderById(String id) {
+        try {
+            LawyerConsultationOrder order = consultationOrderMapper.selectById(id);
+            if (order == null) {
+                log.warn("检查订单接单后是否有消息记录:订单不存在,订单ID={}", id);
+                throw new IllegalArgumentException("订单不存在,订单ID=" + id);
+            }
+            log.debug("查询订单信息成功,订单ID={}", id);
+            return order;
+        } catch (IllegalArgumentException e) {
+            throw e;
+        } catch (Exception e) {
+            log.error("查询订单信息异常,订单ID={},异常信息:{}", id, e.getMessage(), e);
+            throw new RuntimeException("查询订单信息失败:" + e.getMessage(), e);
+        }
+    }
+
+    /**
+     * 校验订单信息
+     * <p>
+     * 检查订单是否包含必要的信息(接单时间、用户ID、律师ID)
+     * </p>
+     *
+     * @param order 订单信息
+     * @param id    订单ID
+     * @throws IllegalArgumentException 当订单信息不完整时抛出
+     */
+    private void validateOrderInfo(LawyerConsultationOrder order, String id) {
+        if (order.getAcceptOrdersTime() == null) {
+            log.warn("检查订单接单后是否有消息记录:订单接单时间为空,订单ID={}", id);
+            throw new IllegalArgumentException("订单接单时间为空,订单ID=" + id);
+        }
+
+        if (order.getClientUserId() == null) {
+            log.warn("检查订单接单后是否有消息记录:订单用户ID为空,订单ID={}", id);
+            throw new IllegalArgumentException("订单用户ID为空,订单ID=" + id);
+        }
+
+        if (order.getLawyerUserId() == null) {
+            log.warn("检查订单接单后是否有消息记录:订单律师ID为空,订单ID={}", id);
+            throw new IllegalArgumentException("订单律师ID为空,订单ID=" + id);
+        }
+    }
+
+    /**
+     * 查询律师手机号
+     * <p>
+     * 根据律师ID查询律师信息,获取手机号
+     * </p>
+     *
+     * @param lawyerUserId 律师ID
+     * @param orderId      订单ID(用于日志)
+     * @return 律师手机号
+     * @throws RuntimeException 当查询异常或律师不存在时抛出
+     */
+    private String queryLawyerPhone(Integer lawyerUserId, String orderId) {
+        try {
+            LawyerUser lawyerUser = lawyerUserMapper.selectById(lawyerUserId);
+            if (lawyerUser == null) {
+                log.warn("检查订单接单后是否有消息记录:律师不存在,律师ID={},订单ID={}", lawyerUserId, orderId);
+                throw new RuntimeException("律师不存在,律师ID=" + lawyerUserId);
+            }
+
+            String phone = lawyerUser.getPhone();
+            if (phone == null || phone.trim().isEmpty()) {
+                log.warn("检查订单接单后是否有消息记录:律师手机号为空,律师ID={},订单ID={}", lawyerUserId, orderId);
+                throw new RuntimeException("律师手机号为空,律师ID=" + lawyerUserId);
+            }
+
+            log.debug("查询律师手机号成功,律师ID={},手机号={}", lawyerUserId, phone);
+            return phone;
+        } catch (RuntimeException e) {
+            throw e;
+        } catch (Exception e) {
+            log.error("查询律师手机号异常,律师ID={},订单ID={},异常信息:{}", lawyerUserId, orderId, e.getMessage(), e);
+            throw new RuntimeException("查询律师手机号失败:" + e.getMessage(), e);
+        }
+    }
+
+    /**
+     * 查询用户手机号
+     * <p>
+     * 根据用户ID查询用户信息,获取手机号
+     * </p>
+     *
+     * @param clientUserId 用户ID
+     * @param orderId      订单ID(用于日志)
+     * @return 用户手机号
+     * @throws RuntimeException 当查询异常或用户不存在时抛出
+     */
+    private String queryUserPhone(Integer clientUserId, String orderId) {
+        try {
+            LifeUser lifeUser = lifeUserMapper.selectById(clientUserId);
+            if (lifeUser == null) {
+                log.warn("检查订单接单后是否有消息记录:用户不存在,用户ID={},订单ID={}", clientUserId, orderId);
+                throw new RuntimeException("用户不存在,用户ID=" + clientUserId);
+            }
+
+            String phone = lifeUser.getUserPhone();
+            if (phone == null || phone.trim().isEmpty()) {
+                log.warn("检查订单接单后是否有消息记录:用户手机号为空,用户ID={},订单ID={}", clientUserId, orderId);
+                throw new RuntimeException("用户手机号为空,用户ID=" + clientUserId);
+            }
+
+            log.debug("查询用户手机号成功,用户ID={},手机号={}", clientUserId, phone);
+            return phone;
+        } catch (RuntimeException e) {
+            throw e;
+        } catch (Exception e) {
+            log.error("查询用户手机号异常,用户ID={},订单ID={},异常信息:{}", clientUserId, orderId, e.getMessage(), e);
+            throw new RuntimeException("查询用户手机号失败:" + e.getMessage(), e);
+        }
+    }
+
+    /**
+     * 查询接单后的消息记录
+     * <p>
+     * 查询 life_message 表,条件:
+     * - sender_id = 律师phone(格式:lawyer_phone)
+     * - receiver_id = 用户phone(格式:user_phone)
+     * - created_time > 接单时间
+     * </p>
+     *
+     * @param lawyerPhone      律师手机号
+     * @param userPhone        用户手机号
+     * @param acceptOrdersTime 接单时间
+     * @param orderId          订单ID(用于日志)
+     * @return true表示有消息记录,false表示无消息记录
+     */
+    private boolean queryMessageAfterAccept(String lawyerPhone, String userPhone, Date acceptOrdersTime, String orderId) {
+        try {
+            // 构建发送者ID和接收者ID
+            String senderId = LAWYER_SENDER_PREFIX + lawyerPhone;
+            String receiverId = USER_RECEIVER_PREFIX + userPhone;
+
+            // 构建查询条件
+            LambdaQueryWrapper<LifeMessage> queryWrapper = new LambdaQueryWrapper<>();
+            queryWrapper.eq(LifeMessage::getSenderId, senderId)
+                    .eq(LifeMessage::getReceiverId, receiverId)
+                    .gt(LifeMessage::getCreatedTime, acceptOrdersTime)
+                    .eq(LifeMessage::getDeleteFlag, DELETE_FLAG_NOT_DELETED)
+                    .last("LIMIT 1");
+
+            // 查询消息记录(只需要判断是否存在,使用 selectOne 并判断是否为 null)
+            LifeMessage message = lifeMessageMapper.selectOne(queryWrapper);
+
+            boolean hasMessage = message != null;
+            log.debug("查询接单后消息记录完成,订单ID={},发送者ID={},接收者ID={},接单时间={},是否有消息={}",
+                    orderId, senderId, receiverId, acceptOrdersTime, hasMessage);
+
+            return hasMessage;
+        } catch (Exception e) {
+            log.error("查询接单后消息记录异常,订单ID={},律师手机号={},用户手机号={},接单时间={},异常信息:{}",
+                    orderId, lawyerPhone, userPhone, acceptOrdersTime, e.getMessage(), e);
+            // 异常时返回 false,避免影响主流程
+            return false;
+        }
+    }
 }
 

+ 605 - 24
alien-lawyer/src/main/java/shop/alien/lawyer/service/impl/LawyerConsultationOrderServiceImpl.java

@@ -24,6 +24,7 @@ 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.payment.PaymentStrategyFactory;
 import shop.alien.lawyer.service.LawyerConsultationOrderService;
 import shop.alien.lawyer.service.LawyerUserService;
 import shop.alien.lawyer.service.OrderExpirationService;
@@ -62,12 +63,54 @@ public class LawyerConsultationOrderServiceImpl extends ServiceImpl<LawyerConsul
     private final WebSocketProcess webSocketProcess;
 
     private final LifeMessageMapper lifeMessageMapper;
+    private final StoreCommentMapper storeCommentMapper;
+    private final LawyerUserViolationMapper lawyerUserViolationMapper;
+    private final CommentAppealMapper commentAppealMapper;
+    private final OrderReviewMapper orderReviewMapper;
+
+
+    private final PaymentStrategyFactory paymentStrategyFactory;
 
     /**
      * 系统发送者ID
      */
     private static final String SYSTEM_SENDER_ID = "system";
 
+    /**
+     * 删除标记:未删除
+     */
+    private static final Integer DELETE_FLAG_NOT_DELETED = 0;
+
+    /**
+     * 金额单位转换:分转元
+     */
+    private static final int FEN_TO_YUAN = 100;
+
+    /**
+     * 默认价格字符串(当所有收费字段都为空时)
+     */
+    private static final String DEFAULT_PRICE_STR = "";
+
+    /**
+     * 默认格式化价格(当价格为null时)
+     */
+    private static final String DEFAULT_FORMATTED_PRICE = "0";
+
+    /**
+     * 价格格式:分钟收费
+     */
+    private static final String PRICE_FORMAT_MINUTE = "¥%s/%d分钟";
+
+    /**
+     * 价格格式:次数收费
+     */
+    private static final String PRICE_FORMAT_TIME = "¥%s/%d次";
+
+    /**
+     * 小数位数:保留2位小数
+     */
+    private static final int DECIMAL_SCALE = 2;
+
     @Value("${order.coefficients}")
     private String coefficients;
 
@@ -163,8 +206,26 @@ public class LawyerConsultationOrderServiceImpl extends ServiceImpl<LawyerConsul
             }
         }
 
+        // 批量查询律所信息
+        Map<Integer, LawFirm> lawFirmMap = new HashMap<>();
+        if (lawyerMap != null && !lawyerMap.isEmpty()) {
+            Set<Integer> firmIds = lawyerMap.values().stream()
+                    .map(LawyerUser::getFirmId)
+                    .filter(Objects::nonNull)
+                    .collect(Collectors.toSet());
+            if (!firmIds.isEmpty()) {
+                List<LawFirm> lawFirms = lawFirmMapper.selectBatchIds(firmIds);
+                if (lawFirms != null) {
+                    lawFirmMap = lawFirms.stream()
+                            .filter(firm -> firm.getDeleteFlag() == null || firm.getDeleteFlag() == 0)
+                            .collect(Collectors.toMap(LawFirm::getId, firm -> firm, (k1, k2) -> k1));
+                }
+            }
+        }
+
         // 轉換為VO並填充律師信息
         final Map<Integer, LawyerUser> finalLawyerMap = lawyerMap;
+        final Map<Integer, LawFirm> finalLawFirmMap = lawFirmMap;
         List<LawyerConsultationOrderVO> voList = records.stream()
                 .map(order -> {
                     LawyerConsultationOrderVO vo = new LawyerConsultationOrderVO();
@@ -178,7 +239,13 @@ public class LawyerConsultationOrderServiceImpl extends ServiceImpl<LawyerConsul
                         vo.setLawyerPhone(lawyer.getPhone());
                         vo.setLawyerEmail(lawyer.getEmail());
                         vo.setLawyerCertificateNo(lawyer.getLawyerCertificateNo());
-                        vo.setLawFirm(lawyer.getLawFirm());
+                        // 通过firm_id查询律所名称
+                        if (lawyer.getFirmId() != null) {
+                            LawFirm lawFirm = finalLawFirmMap.get(lawyer.getFirmId());
+                            vo.setLawFirm(lawFirm != null ? lawFirm.getFirmName() : null);
+                        } else {
+                            vo.setLawFirm(null);
+                        }
                         vo.setPracticeYears(lawyer.getPracticeYears());
                         vo.setSpecialtyFields(lawyer.getSpecialtyFields());
                         vo.setCertificationStatus(lawyer.getCertificationStatus());
@@ -435,6 +502,10 @@ public class LawyerConsultationOrderServiceImpl extends ServiceImpl<LawyerConsul
             return R.fail("订单金额不能为空且必须大于0");
         }
 
+         LawyerUser  lawuer = lawyerUserMapper.selectById(lawyerUserId);
+
+
+
         // 创建订单DTO对象
         LawyerConsultationOrderDto order = new LawyerConsultationOrderDto();
         Date now = new Date();
@@ -450,6 +521,40 @@ public class LawyerConsultationOrderServiceImpl extends ServiceImpl<LawyerConsul
         order.setPlaceId(lawyerConsultationOrder.getPlaceId());
         order.setPayType(lawyerConsultationOrder.getPayType());
 
+        if (lawyerConsultationOrder.getChargeMinute() != null) {
+            order.setChargeMinute(lawyerConsultationOrder.getChargeMinute());
+        }
+        if (lawyerConsultationOrder.getMinuteNum() != null) {
+            order.setMinuteNum(lawyerConsultationOrder.getMinuteNum());
+        }
+        if (lawyerConsultationOrder.getChargeTime() != null) {
+            order.setChargeTime(lawyerConsultationOrder.getChargeTime());
+        }
+        if (lawyerConsultationOrder.getTimeNum() != null) {
+            order.setTimeNum(lawyerConsultationOrder.getTimeNum());
+        }
+        if (lawuer != null) {
+            if (lawuer.getFirmId() != null){
+                order.setPlaceId(lawuer.getFirmId());
+            }
+        }
+
+       String serviceType =lawyerConsultationOrder.getServiceType();
+
+        if (serviceType.contains("次")){
+            //对serviceType进行截取,比如"10次",想要10,则截取字符串"10",并转换为整型10
+            serviceType = serviceType.substring(0, serviceType.indexOf("次"));
+            order.setTimeNum(Integer.valueOf(serviceType));
+
+            order.setChargeTime(lawyerConsultationOrder.getPrice());
+        }
+        if (serviceType.contains("分钟")){
+            serviceType = serviceType.substring(0, serviceType.indexOf("分钟"));
+            order.setMinuteNum(Integer.valueOf(serviceType));
+            order.setChargeMinute(lawyerConsultationOrder.getPrice());
+        }
+
+
         // 设置订单状态
         order.setOrderStatus(0); // 待支付
         order.setPaymentStatus(0); // 未支付
@@ -533,6 +638,15 @@ public class LawyerConsultationOrderServiceImpl extends ServiceImpl<LawyerConsul
         return consultationFee;
     }
 
+    /**
+     * 更新订单支付状态
+     * <p>
+     * 根据支付状态更新订单信息,包括订单状态、支付状态、支付时间等
+     * </p>
+     *
+     * @param request 支付状态更新请求对象,包含订单编号、支付状态、订单状态等信息,不能为空
+     * @return 更新结果,成功返回订单信息,失败返回错误信息
+     */
     @Override
     public R<LawyerConsultationOrderDto> payStatus(PayStatusRequest request) {
         log.info("LawyerConsultationOrderServiceImpl.payStatus?orderNumber={},paymentStatus={},orderStatus={}",
@@ -545,9 +659,12 @@ public class LawyerConsultationOrderServiceImpl extends ServiceImpl<LawyerConsul
         if (null != request.getAlipayNo()){
             order.setAlipayNo(request.getAlipayNo());
         }
+        if (null != request.getPayType()){
+            order.setPayType(request.getPayType());
+        }
         order.setOrderNumber(request.getOrderNumber());
-        order.setPaymentStatus(request.getPaymentStatus());
-        order.setOrderStatus(request.getOrderStatus());
+        order.setPaymentStatus(1);
+        order.setOrderStatus(1);
         order.setUpdatedTime(new Date());
         order.setDeleteFlag(0);
         order.setPaymentTime(new Date());
@@ -611,6 +728,12 @@ public class LawyerConsultationOrderServiceImpl extends ServiceImpl<LawyerConsul
 
             //填充未读消息
             fillUnreadMessage(voPage,userId);
+
+            // 设置订单价格字符串
+            setOrderPriceStrForOrderList(voPage);
+
+            // 设置评价状态
+            setCommonStatusForOrderList(voPage);
         }
 
         // 判断工作日信息
@@ -670,7 +793,7 @@ public class LawyerConsultationOrderServiceImpl extends ServiceImpl<LawyerConsul
         resultMap.put("lawyerCompleteOrderCount", completeCount);
         resultMap.put("lawyerCancelStatusOrderCount", cancelStatusCount);
         resultMap.put("lawyerRefundedStatusOrderCount", refundedStatusCount);
-        resultMap.put("lawyerOrderCount", totalOrderCount);
+        resultMap.put("total", totalOrderCount);
 
         // 设置订单列表
         List<LawyerConsultationOrderVO> orderList = (voPage != null && voPage.getRecords() != null)
@@ -990,7 +1113,7 @@ public class LawyerConsultationOrderServiceImpl extends ServiceImpl<LawyerConsul
     /**
      * 取消律师咨询订单
      * <p>
-     * 取消订单前会进行以下校验和处理
+     * 取消订单的业务逻辑
      * 1. 参数校验:订单ID不能为空
      * 2. 订单存在性校验:订单必须存在
      * 3. 订单状态校验:已取消或已完成的订单不允许再次取消
@@ -1007,7 +1130,7 @@ public class LawyerConsultationOrderServiceImpl extends ServiceImpl<LawyerConsul
     @Transactional(rollbackFor = Exception.class)
     public boolean cancelOrder(String id) {
         log.info("开始取消订单,订单ID={}", id);
-
+        try {
         // 1. 参数校验
         if (!StringUtils.hasText(id)) {
             log.warn("取消订单失败:订单ID为空");
@@ -1021,46 +1144,73 @@ public class LawyerConsultationOrderServiceImpl extends ServiceImpl<LawyerConsul
             return false;
         }
 
+        if (order.getOrderStatus() == 1 ) {
+            Map<String, String> params=new HashMap<>();
+            params.put("payType", "1".equals(order.getPayType()) ? "alipay" : "wechatPay");
+            params.put("outTradeNo",order.getAlipayNo());
+            params.put("totalAmount", String.valueOf(order.getOrderAmount()));
+
+            params.put("refundAmount", String.valueOf(order.getOrderAmount()));
+            if ("1".equals( order.getPayType())){
+                params.put("refundAmount", new BigDecimal(order.getOrderAmount()).divide(new BigDecimal(100)).toString());
+            }
+
+            if ("2".equals( order.getPayType())){
+                params.put("refundReason", "用户取消订单");
+            }
+
+            String refundResult =paymentStrategyFactory.getStrategy(params.get("payType")).handleRefund(params);
+
+            if (!"调用成功".equals(refundResult)){
+                log.warn("取消订单失败:订单取消失败,订单ID={}, 订单编号={}", id, order.getOrderNumber());
+                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)) {
+        // 3.1 检查订单是否已取消(使用 Objects.equals 避免空指针异常)
+        if (Objects.equals(cancelStatus, orderStatus)) {
             log.warn("取消订单失败:订单已取消,订单ID={}, 订单编号={}", id, orderNumber);
             return false;
         }
 
         // 3.2 检查订单是否已完成
-        if (completedStatus.equals(orderStatus)) {
+        if (Objects.equals(completedStatus, orderStatus)) {
             log.warn("取消订单失败:订单已完成,不允许取消,订单ID={}, 订单编号={}", id, orderNumber);
             return false;
         }
 
         // 4. 如果订单是待支付状态,取消Redis中的订单支付超时计时器
-        if (waitPayStatus.equals(orderStatus) && StringUtils.hasText(orderNumber)) {
+        if (Objects.equals(waitPayStatus, orderStatus) && StringUtils.hasText(orderNumber)) {
             try {
                 orderExpirationService.cancelOrderPaymentTimeout(orderNumber);
                 log.info("已取消订单支付超时计时器,订单编号={}", orderNumber);
             } catch (Exception e) {
-                log.error("取消订单支付超时计时器失败,订单编号={}", orderNumber, e);
+                log.error("取消订单支付超时计时器失败,订单编号={},异常信息:{}", orderNumber, e.getMessage(), e);
                 // 继续执行取消订单操作,不因取消计时器失败而中断
             }
         }
 
         // 5. 根据订单状态更新为相应状态
-        Integer targetStatus = null;
-        if (waitAcceptStatus.equals(orderStatus)) {
+        Integer targetStatus;
+        if (Objects.equals(waitAcceptStatus, orderStatus)) {
             // 待接单状态:更新为已退款状态
             targetStatus = refundedStatus;
-        } else if (waitPayStatus.equals(orderStatus)) {
+            log.debug("待接单订单取消,将更新为已退款状态,订单ID={}", id);
+        } else if (Objects.equals(waitPayStatus, orderStatus)) {
             // 待支付状态:更新为已取消状态
             targetStatus = cancelStatus;
+            log.debug("待支付订单取消,将更新为已取消状态,订单ID={}", id);
         } else {
             log.warn("取消订单失败:订单状态不允许取消,订单ID={}, 订单编号={}, 订单状态={}", 
                     id, orderNumber, orderStatus);
@@ -1081,11 +1231,17 @@ public class LawyerConsultationOrderServiceImpl extends ServiceImpl<LawyerConsul
             log.info("取消订单成功,订单ID={}, 订单编号={}, 原状态={}, 新状态={}", 
                     id, orderNumber, orderStatus, targetStatus);
         } else {
-            log.error("取消订单失败:更新数据库失败,订单ID={}, 订单编号={}, 原状态={}", 
-                    id, orderNumber, orderStatus);
+            log.error("取消订单失败:更新数据库失败,订单ID={}, 订单编号={}, 原状态={}, 目标状态={}", 
+                    id, orderNumber, orderStatus, targetStatus);
         }
 
         return success;
+
+        } catch (Exception e) {
+            log.error("取消订单失败:订单ID={}, 错误信息={}", id,  e.getMessage());
+            return false;
+        }
+
     }
 
     /**
@@ -1256,7 +1412,14 @@ public class LawyerConsultationOrderServiceImpl extends ServiceImpl<LawyerConsul
         orderVO.setLawyerPhone(lawyerUser.getPhone());
         orderVO.setLawyerEmail(lawyerUser.getEmail());
         orderVO.setLawyerCertificateNo(lawyerUser.getLawyerCertificateNo());
-        orderVO.setLawFirm(lawyerUser.getLawFirm());
+        // 通过firm_id查询律所名称
+        if (lawyerUser.getFirmId() != null) {
+            LawFirm lawFirm = lawFirmMapper.selectById(lawyerUser.getFirmId());
+            orderVO.setLawFirm(lawFirm != null && (lawFirm.getDeleteFlag() == null || lawFirm.getDeleteFlag() == 0) 
+                    ? lawFirm.getFirmName() : null);
+        } else {
+            orderVO.setLawFirm(null);
+        }
         orderVO.setPracticeYears(lawyerUser.getPracticeYears());
         orderVO.setSpecialtyFields(lawyerUser.getSpecialtyFields());
         orderVO.setCertificationStatus(lawyerUser.getCertificationStatus());
@@ -1273,19 +1436,39 @@ public class LawyerConsultationOrderServiceImpl extends ServiceImpl<LawyerConsul
     }
 
     /**
-     * 查询律师订单列表
+     * 创建订单前的校验
+     * <p>
+     * 检查用户是否已存在咨询该律师的订单(订单状态为待接单或进行中)
+     * </p>
      *
-     * @param clientUserId 用户id
-     * @param lawyerUserId 律师ID
-     * @return 订单列表
+     * @param clientUserId 客户端用户ID,不能为空
+     * @param lawyerUserId 律师用户ID,不能为空
+     * @return 校验结果,如果已存在订单返回失败,否则返回成功
      */
     @Override
     public R<Map<String, String>> checkOrder(Integer clientUserId, Integer lawyerUserId) {
-        log.info("LawyerConsultationOrderController.checkOrder?clientUserId={},lawyerUserId{}", clientUserId, lawyerUserId);
-        List<LawyerConsultationOrder> orderDto = consultationOrderMapper.selectOrder(clientUserId, lawyerUserId);
-        if (CollectionUtils.isNotEmpty(orderDto)) {
+        log.info("LawyerConsultationOrderServiceImpl.checkOrder?clientUserId={},lawyerUserId={}", clientUserId, lawyerUserId);
+        
+        // 参数校验
+        if (clientUserId == null) {
+            log.warn("创建订单前校验失败:客户端用户ID为空");
+            return R.fail("客户端用户ID不能为空");
+        }
+        
+        if (lawyerUserId == null) {
+            log.warn("创建订单前校验失败:律师用户ID为空");
+            return R.fail("律师用户ID不能为空");
+        }
+        
+        // 查询是否存在进行中的订单(订单状态为待接单或进行中)
+        List<LawyerConsultationOrder> existingOrders = consultationOrderMapper.selectOrder(clientUserId, lawyerUserId);
+        
+        if (CollectionUtils.isNotEmpty(existingOrders)) {
+            log.warn("创建订单前校验失败:用户已存在咨询该律师的订单,clientUserId={},lawyerUserId={}", clientUserId, lawyerUserId);
             return R.fail("您已存在咨询该律师的订单");
         }
+        
+        log.info("创建订单前校验成功:可以咨询该律师,clientUserId={},lawyerUserId={}", clientUserId, lawyerUserId);
         return R.success("可以咨询该律师");
     }
 
@@ -1735,6 +1918,7 @@ public class LawyerConsultationOrderServiceImpl extends ServiceImpl<LawyerConsul
             jsonObject.put("message", message);
             lifeNotice.setContext(jsonObject.toJSONString());
             lifeNotice.setNoticeType(1);
+            lifeNotice.setBusinessType(1);
 
             return lifeNotice;
 
@@ -1782,6 +1966,403 @@ public class LawyerConsultationOrderServiceImpl extends ServiceImpl<LawyerConsul
         return webSocketVo;
     }
 
+    /**
+     * 为订单列表设置订单价格字符串
+     * <p>
+     * 遍历订单列表,为每个订单设置orderPriceStr字段
+     * </p>
+     *
+     * @param voPage 订单列表分页对象
+     */
+    private void setOrderPriceStrForOrderList(IPage<LawyerConsultationOrderVO> voPage) {
+        if (voPage == null || CollectionUtils.isEmpty(voPage.getRecords())) {
+            return;
+        }
+
+        for (LawyerConsultationOrderVO orderVO : voPage.getRecords()) {
+            setOrderPriceStr(orderVO);
+        }
+    }
+
+    /**
+     * 设置订单价格字符串
+     * <p>
+     * 根据charge_minute、minute_num、charge_time、time_num字段设置orderPriceStr
+     * 优先级:charge_minute和minute_num > charge_time和time_num
+     * chargeMinute和chargeTime单位都是分,需要转换为元并保留两位小数,去掉尾随的0
+     * </p>
+     * <p>
+     * 处理逻辑:
+     * 1. 优先检查是否按分钟收费(chargeMinute和minuteNum都不为空)
+     * 2. 其次检查是否按次数收费(chargeTime和timeNum都不为空)
+     * 3. 如果都不满足,则设置为空字符串
+     * </p>
+     *
+     * @param orderVO 订单VO对象,不能为null
+     */
+    private void setOrderPriceStr(LawyerConsultationOrderVO orderVO) {
+        if (orderVO == null) {
+            log.warn("设置订单价格字符串失败:订单VO对象为null");
+            return;
+        }
+
+        try {
+            // 优先处理按分钟收费
+            if (isMinuteBasedCharging(orderVO)) {
+                String orderPriceStr = buildMinuteBasedPriceStr(orderVO);
+                orderVO.setOrderPriceStr(orderPriceStr);
+                log.debug("设置订单价格字符串成功(按分钟收费),chargeMinute={}, minuteNum={}, orderPriceStr={}",
+                        orderVO.getChargeMinute(), orderVO.getMinuteNum(), orderPriceStr);
+                return;
+            }
+
+            // 其次处理按次数收费
+            if (isTimeBasedCharging(orderVO)) {
+                String orderPriceStr = buildTimeBasedPriceStr(orderVO);
+                orderVO.setOrderPriceStr(orderPriceStr);
+                log.debug("设置订单价格字符串成功(按次数收费),chargeTime={}, timeNum={}, orderPriceStr={}",
+                        orderVO.getChargeTime(), orderVO.getTimeNum(), orderPriceStr);
+                return;
+            }
+
+            // 当都为空则返回空
+            orderVO.setOrderPriceStr(DEFAULT_PRICE_STR);
+            log.debug("订单价格字段都为空,设置默认空字符串");
+
+        } catch (Exception e) {
+            log.error("设置订单价格字符串异常,异常信息:{}", e.getMessage(), e);
+            // 异常时设置默认值,避免影响主流程
+            orderVO.setOrderPriceStr(DEFAULT_PRICE_STR);
+        }
+    }
+
+    /**
+     * 判断是否按分钟收费
+     * <p>
+     * 通过检查chargeMinute和minuteNum是否都不为空来判断
+     * </p>
+     *
+     * @param orderVO 订单VO对象
+     * @return true表示按分钟收费,false表示不是
+     */
+    private boolean isMinuteBasedCharging(LawyerConsultationOrderVO orderVO) {
+        return orderVO != null
+                && orderVO.getChargeMinute() != null
+                && orderVO.getMinuteNum() != null;
+    }
+
+    /**
+     * 判断是否按次数收费
+     * <p>
+     * 通过检查chargeTime和timeNum是否都不为空来判断
+     * </p>
+     *
+     * @param orderVO 订单VO对象
+     * @return true表示按次数收费,false表示不是
+     */
+    private boolean isTimeBasedCharging(LawyerConsultationOrderVO orderVO) {
+        return orderVO != null
+                && orderVO.getChargeTime() != null
+                && orderVO.getTimeNum() != null;
+    }
+
+    /**
+     * 构建按分钟收费的价格字符串
+     * <p>
+     * 格式:¥{价格}/{分钟数}分钟
+     * 例如:¥1.5/30分钟
+     * </p>
+     *
+     * @param orderVO 订单VO对象
+     * @return 格式化后的价格字符串
+     */
+    private String buildMinuteBasedPriceStr(LawyerConsultationOrderVO orderVO) {
+        // chargeMinute单位是分,需要转换为元并保留两位小数,去掉尾随的0
+        BigDecimal chargeMinuteYuan = convertFenToYuan(orderVO.getChargeMinute());
+        String priceStr = formatPrice(chargeMinuteYuan);
+        return String.format(PRICE_FORMAT_MINUTE, priceStr, orderVO.getMinuteNum());
+    }
+
+    /**
+     * 构建按次数收费的价格字符串
+     * <p>
+     * 格式:¥{价格}/{次数}次
+     * 例如:¥5/3次
+     * </p>
+     *
+     * @param orderVO 订单VO对象
+     * @return 格式化后的价格字符串
+     */
+    private String buildTimeBasedPriceStr(LawyerConsultationOrderVO orderVO) {
+        // chargeTime单位是分,需要转换为元并保留两位小数,去掉尾随的0
+        BigDecimal chargeTimeYuan = convertFenToYuan(orderVO.getChargeTime());
+        String priceStr = formatPrice(chargeTimeYuan);
+        return String.format(PRICE_FORMAT_TIME, priceStr, orderVO.getTimeNum());
+    }
+
+    /**
+     * 将分转换为元
+     * <p>
+     * 分转元,保留指定小数位数,使用四舍五入
+     * </p>
+     *
+     * @param fen 金额(分),单位:分
+     * @return 金额(元),单位:元,保留2位小数
+     */
+    private BigDecimal convertFenToYuan(Integer fen) {
+        if (fen == null) {
+            log.warn("转换分转元失败:金额为null,返回0");
+            return BigDecimal.ZERO;
+        }
+
+        if (fen < 0) {
+            log.warn("转换分转元失败:金额为负数,fen={},返回0", fen);
+            return BigDecimal.ZERO;
+        }
+
+        return BigDecimal.valueOf(fen)
+                .divide(BigDecimal.valueOf(FEN_TO_YUAN), DECIMAL_SCALE, RoundingMode.HALF_UP);
+    }
+
+    /**
+     * 格式化价格,去掉尾随的0
+     * <p>
+     * 将BigDecimal价格格式化为字符串,去掉尾随的0
+     * 例如:1.00 -> 1, 1.50 -> 1.5, 1.25 -> 1.25
+     * </p>
+     *
+     * @param price 价格(BigDecimal),可能为null
+     * @return 格式化后的价格字符串,如果price为null则返回"0"
+     */
+    private String formatPrice(BigDecimal price) {
+        if (price == null) {
+            log.debug("格式化价格:价格为null,返回默认值0");
+            return DEFAULT_FORMATTED_PRICE;
+        }
+
+        try {
+            // 去掉尾随的0
+            String formattedPrice = price.stripTrailingZeros().toPlainString();
+            log.debug("格式化价格成功,原始价格={}, 格式化后={}", price, formattedPrice);
+            return formattedPrice;
+        } catch (Exception e) {
+            log.error("格式化价格异常,price={},异常信息:{}", price, e.getMessage(), e);
+            // 异常时返回默认值
+            return DEFAULT_FORMATTED_PRICE;
+        }
+    }
+
+    /**
+     * 设置订单列表的评价状态(commonStatus)
+     * <p>
+     * 根据订单ID查询 lawyer_order_review 和 comment_appeals 表,设置每个订单的评价状态:
+     * 1. 如果 lawyer_order_review 表中 order_id=订单ID 的记录不存在,则 commonStatus=1
+     * 2. 如果 lawyer_order_review 存在,但 comment_appeals 表中 comment_id=lawyer_order_review.id 的记录不存在,
+     *    或者存在但 processing_status(status) 为 0 或 1,则 commonStatus=2
+     * 3. 其他情况 commonStatus=3
+     * </p>
+     *
+     * @param voPage 订单分页对象
+     */
+    private void setCommonStatusForOrderList(IPage<LawyerConsultationOrderVO> voPage) {
+        if (voPage == null || CollectionUtils.isEmpty(voPage.getRecords())) {
+            log.debug("订单列表为空,跳过设置评价状态");
+            return;
+        }
+
+        // 提取订单ID列表
+        List<Integer> orderIds = voPage.getRecords().stream()
+                .map(LawyerConsultationOrderVO::getId)
+                .filter(Objects::nonNull)
+                .collect(Collectors.toList());
+
+        if (CollectionUtils.isEmpty(orderIds)) {
+            log.debug("订单ID列表为空,跳过设置评价状态");
+            return;
+        }
+
+        // 查询 lawyer_order_review 表,order_id=订单ID
+        Map<Integer, OrderReview> orderIdToReviewMap = queryOrderReviewByOrderIds(orderIds);
+
+        // 提取 lawyer_order_review 的 ID 列表
+        List<Integer> reviewIds = orderIdToReviewMap.values().stream()
+                .filter(Objects::nonNull)
+                .map(OrderReview::getId)
+                .filter(Objects::nonNull)
+                .collect(Collectors.toList());
+
+        // 查询 comment_appeals 表,comment_id=lawyer_order_review.id
+        Map<Integer, CommentAppeal> reviewIdToAppealMap = queryCommentAppealsByReviewIds(reviewIds);
+
+        // 为每个订单设置 commonStatus
+        for (LawyerConsultationOrderVO orderVO : voPage.getRecords()) {
+            if (orderVO == null || orderVO.getId() == null) {
+                continue;
+            }
+
+            String commonStatus = calculateCommonStatus(
+                    orderVO.getId(), orderIdToReviewMap, reviewIdToAppealMap);
+            orderVO.setCommonStatus(commonStatus);
+        }
+
+        log.debug("设置订单评价状态完成,订单数量={}", voPage.getRecords().size());
+    }
+
+    /**
+     * 根据订单ID列表查询 lawyer_order_review 记录
+     * <p>
+     * 查询条件:order_id=订单ID
+     * 如果一个 order_id 存在多条记录,只取最新的一条(按创建时间倒序)
+     * </p>
+     *
+     * @param orderIds 订单ID列表
+     * @return 订单ID到评价记录的映射,key为订单ID,value为评价记录(每个订单ID对应最新的一条评价)
+     */
+    private Map<Integer, OrderReview> queryOrderReviewByOrderIds(List<Integer> orderIds) {
+        if (CollectionUtils.isEmpty(orderIds)) {
+            return Collections.emptyMap();
+        }
+
+        try {
+            LambdaQueryWrapper<OrderReview> queryWrapper = new LambdaQueryWrapper<>();
+            queryWrapper.in(OrderReview::getOrderId, orderIds)
+                    .eq(OrderReview::getDeleteFlag, 0)
+                    .orderByDesc(OrderReview::getCreatedTime);
+
+            List<OrderReview> reviewList = orderReviewMapper.selectList(queryWrapper);
+
+            // 按 orderId 分组,每个 orderId 只保留最新的一条记录
+            // 由于查询已按创建时间倒序排序,同一个 orderId 的第一条记录即为最新的
+            return reviewList.stream()
+                    .filter(Objects::nonNull)
+                    .filter(review -> review.getId() != null && review.getOrderId() != null)
+                    .collect(Collectors.toMap(
+                            OrderReview::getOrderId,
+                            review -> review,
+                            (existing, replacement) -> {
+                                // 由于已按创建时间倒序排序,existing 是先遍历到的记录(即最新的)
+                                // 但为了安全起见,仍然比较创建时间,确保保留最新的记录
+                                if (existing.getCreatedTime() != null && replacement.getCreatedTime() != null) {
+                                    // existing.getCreatedTime() >= replacement.getCreatedTime()(因为已排序)
+                                    return existing.getCreatedTime().after(replacement.getCreatedTime()) 
+                                            || existing.getCreatedTime().equals(replacement.getCreatedTime())
+                                            ? existing 
+                                            : replacement;
+                                } else if (existing.getCreatedTime() != null) {
+                                    // existing 有创建时间,replacement 没有,保留 existing
+                                    return existing;
+                                } else if (replacement.getCreatedTime() != null) {
+                                    // replacement 有创建时间,existing 没有,保留 replacement
+                                    return replacement;
+                                } else {
+                                    // 都没有创建时间,保留已存在的记录(先遍历到的,即排序后的第一条)
+                                    return existing;
+                                }
+                            }
+                    ));
+        } catch (Exception e) {
+            log.error("查询 lawyer_order_review 记录异常,orderIds={},异常信息:{}", orderIds, e.getMessage(), e);
+            return Collections.emptyMap();
+        }
+    }
+
+    /**
+     * 根据评价ID列表查询 comment_appeals 记录
+     * <p>
+     * 查询条件:comment_id=评价ID(lawyer_order_review.id)
+     * </p>
+     *
+     * @param reviewIds 评价ID列表(lawyer_order_review.id)
+     * @return 评价ID到申诉记录的映射,key为评价ID,value为申诉记录
+     */
+    private Map<Integer, CommentAppeal> queryCommentAppealsByReviewIds(List<Integer> reviewIds) {
+        if (CollectionUtils.isEmpty(reviewIds)) {
+            return Collections.emptyMap();
+        }
+
+        try {
+            // 构建查询条件
+            LambdaQueryWrapper<CommentAppeal> queryWrapper = new LambdaQueryWrapper<>();
+            queryWrapper.in(CommentAppeal::getCommentId, reviewIds)
+                    .eq(CommentAppeal::getDeleteFlag, DELETE_FLAG_NOT_DELETED);
+
+            List<CommentAppeal> appealList = commentAppealMapper.selectList(queryWrapper);
+
+            // 构建映射:key为评价ID,value为申诉记录
+            // 如果一个评价有多个申诉记录,只取第一个(通常一个评价应该只有一个申诉记录)
+            Map<Integer, CommentAppeal> resultMap = new HashMap<>();
+
+            for (CommentAppeal appeal : appealList) {
+                if (appeal == null || appeal.getCommentId() == null) {
+                    continue;
+                }
+
+                // 如果已经存在该评价的申诉记录,保留第一个(先查询到的)
+                if (!resultMap.containsKey(appeal.getCommentId())) {
+                    resultMap.put(appeal.getCommentId(), appeal);
+                }
+            }
+
+            log.debug("查询 comment_appeals 记录成功,reviewIds={},查询到记录数={}",
+                    reviewIds, resultMap.size());
+
+            return resultMap;
+        } catch (Exception e) {
+            log.error("查询 comment_appeals 记录异常,reviewIds={},异常信息:{}",
+                    reviewIds, e.getMessage(), e);
+            return Collections.emptyMap();
+        }
+    }
+
+    /**
+     * 计算订单的评价状态
+     * <p>
+     * 根据查询结果计算订单的 commonStatus 值:
+     * 1. 如果 lawyer_order_review 表中没有记录,则 commonStatus=1
+     * 2. 如果 comment_appeals 表中没有记录,或者 processing_status(status) 为 0 或 1,则 commonStatus=2
+     * 3. 其他情况 commonStatus=3
+     * </p>
+     *
+     * @param orderId               订单ID
+     * @param orderIdToReviewMap    订单ID到评价记录的映射
+     * @param reviewIdToAppealMap   评价ID到申诉记录的映射
+     * @return 评价状态字符串("1"、"2"或"3")
+     */
+    private String calculateCommonStatus(Integer orderId,
+                                        Map<Integer, OrderReview> orderIdToReviewMap,
+                                        Map<Integer, CommentAppeal> reviewIdToAppealMap) {
+        // 第一步:查询 lawyer_order_review 表
+        OrderReview review = orderIdToReviewMap.get(orderId);
+
+        // 如果关联不出来记录,则 commonStatus=1
+        if (review == null || review.getId() == null) {
+            log.debug("订单未找到评价记录,设置 commonStatus=1,orderId={}", orderId);
+            return "1";
+        }
+
+        // 第二步:查询 comment_appeals 表
+        CommentAppeal appeal = reviewIdToAppealMap.get(review.getId());
+
+        // 如果关联不出来或者关联出来的 processing_status(status) 为 0 或者 1,则 commonStatus=2
+        if (appeal == null) {
+            log.debug("评价未找到申诉记录,设置 commonStatus=2,orderId={}, reviewId={}",
+                    orderId, review.getId());
+            return "2";
+        }
+
+        Integer status = appeal.getStatus();
+        if (status == null || status == 0 || status == 2) {
+            log.debug("申诉记录处理状态为0或1,设置 commonStatus=2,orderId={}, reviewId={}, status={}",
+                    orderId, review.getId(), status);
+            return "2";
+        }
+
+        // 其他情况 commonStatus=3
+        log.debug("设置 commonStatus=3,orderId={}, reviewId={}, status={}",
+                orderId, review.getId(), status);
+        return "3";
+    }
+
 
 }
 

+ 120 - 2
alien-lawyer/src/main/java/shop/alien/lawyer/service/impl/LawyerNoticeServiceImpl.java

@@ -60,6 +60,11 @@ public class LawyerNoticeServiceImpl extends ServiceImpl<LifeNoticeMapper, LifeN
     private static final Integer IS_READ_UNREAD = 0;
 
     /**
+     * 已读标识:1-已读
+     */
+    private static final Integer IS_READ_READ = 1;
+
+    /**
      * 发送者ID分隔符
      */
     private static final String SENDER_ID_SEPARATOR = "_";
@@ -349,11 +354,124 @@ public class LawyerNoticeServiceImpl extends ServiceImpl<LifeNoticeMapper, LifeN
         return R.data(hasUnread);
     }
 
+    /**
+     * 根据通知ID标记通知为已读
+     * <p>
+     * 处理流程:
+     * 1. 参数校验:通知ID必须大于0且不能为null
+     * 2. 构建更新条件:根据ID匹配通知记录
+     * 3. 执行更新:将isRead字段设置为1(已读)
+     * 4. 返回更新结果:返回受影响的记录数
+     * </p>
+     * <p>
+     * 注意事项:
+     * - 如果通知不存在或ID无效,更新操作将不会影响任何记录,返回0
+     * - 如果通知已经是已读状态,更新操作仍然会执行但不会改变状态
+     * </p>
+     *
+     * @param id 通知ID,必须大于0且不能为null
+     * @return 受影响的记录数,>0表示更新成功,0表示未找到匹配的记录或更新失败
+     * @throws IllegalArgumentException 当通知ID无效时抛出
+     * @throws RuntimeException        当更新操作异常时抛出
+     * @author system
+     * @since 2025-01-XX
+     */
     @Override
     public int readNoticeById(Integer id) {
+        log.info("标记通知为已读开始,id={}", id);
+
+        // 参数校验
+        validateNoticeId(id);
+
+        try {
+            // 构建更新条件
+            LambdaUpdateWrapper<LifeNotice> updateWrapper = buildUpdateWrapper(id);
+
+            // 执行更新操作
+            int affectedRows = executeUpdate(updateWrapper);
+
+            // 记录更新结果
+            logUpdateResult(id, affectedRows);
+
+            return affectedRows;
+        } catch (IllegalArgumentException e) {
+            log.warn("标记通知为已读参数校验失败,id={},错误信息:{}", id, e.getMessage());
+            throw e;
+        } catch (RuntimeException e) {
+            log.error("标记通知为已读异常,id={},异常信息:{}", id, e.getMessage(), e);
+            throw new RuntimeException("标记通知为已读失败:" + e.getMessage(), e);
+        } catch (Exception e) {
+            log.error("标记通知为已读未知异常,id={},异常信息:{}", id, e.getMessage(), e);
+            throw new RuntimeException("标记通知为已读失败,请稍后重试", e);
+        }
+    }
+
+    /**
+     * 校验通知ID
+     * <p>
+     * 检查通知ID是否为空或无效
+     * </p>
+     *
+     * @param id 通知ID
+     * @throws IllegalArgumentException 当通知ID为空或无效时抛出
+     */
+    private void validateNoticeId(Integer id) {
+        if (id == null) {
+            log.warn("标记通知为已读参数校验失败:通知ID为null");
+            throw new IllegalArgumentException("通知ID不能为空");
+        }
+
+        if (id <= 0) {
+            log.warn("标记通知为已读参数校验失败:通知ID无效,id={}", id);
+            throw new IllegalArgumentException("通知ID必须大于0");
+        }
+    }
+
+    /**
+     * 构建更新条件包装器
+     * <p>
+     * 根据通知ID构建更新条件,将isRead字段设置为已读状态
+     * </p>
+     *
+     * @param id 通知ID
+     * @return 更新条件包装器
+     */
+    private LambdaUpdateWrapper<LifeNotice> buildUpdateWrapper(Integer id) {
         LambdaUpdateWrapper<LifeNotice> wrapper = new LambdaUpdateWrapper<>();
         wrapper.eq(LifeNotice::getId, id);
-        wrapper.set(LifeNotice::getIsRead, 1);
-        return lifeNoticeMapper.update(null, wrapper);
+        wrapper.set(LifeNotice::getIsRead, IS_READ_READ);
+
+        log.debug("构建更新条件成功,id={}, isRead={}", id, IS_READ_READ);
+        return wrapper;
+    }
+
+    /**
+     * 执行更新操作
+     * <p>
+     * 调用Mapper执行更新操作,将通知标记为已读
+     * </p>
+     *
+     * @param updateWrapper 更新条件包装器
+     * @return 受影响的记录数
+     */
+    private int executeUpdate(LambdaUpdateWrapper<LifeNotice> updateWrapper) {
+        return lifeNoticeMapper.update(null, updateWrapper);
+    }
+
+    /**
+     * 记录更新结果日志
+     * <p>
+     * 根据更新结果记录相应级别的日志
+     * </p>
+     *
+     * @param id          通知ID
+     * @param affectedRows 受影响的记录数
+     */
+    private void logUpdateResult(Integer id, int affectedRows) {
+        if (affectedRows > 0) {
+            log.info("标记通知为已读成功,id={},受影响记录数={}", id, affectedRows);
+        } else {
+            log.warn("标记通知为已读未找到匹配记录,id={},受影响记录数={},可能是通知不存在或已被删除", id, affectedRows);
+        }
     }
 }

+ 796 - 0
alien-lawyer/src/main/java/shop/alien/lawyer/service/impl/LawyerStatisticsServiceImpl.java

@@ -0,0 +1,796 @@
+package shop.alien.lawyer.service.impl;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import shop.alien.entity.result.R;
+import shop.alien.entity.store.LawyerUser;
+import shop.alien.entity.store.vo.LawyerDashboardVO;
+import shop.alien.entity.store.vo.LawyerOrderStatisticsVO;
+import shop.alien.lawyer.service.LawyerStatisticsService;
+import shop.alien.lawyer.service.LawyerUserService;
+import shop.alien.mapper.LawyerConsultationOrderMapper;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.time.temporal.TemporalAdjusters;
+import java.util.Map;
+
+/**
+ * 律师统计 服务实现类
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class LawyerStatisticsServiceImpl implements LawyerStatisticsService {
+
+    private final LawyerConsultationOrderMapper lawyerConsultationOrderMapper;
+    private final LawyerUserService lawyerUserService;
+
+    /**
+     * 金额单位转换:分转元
+     */
+    private static final int FEN_TO_YUAN = 100;
+
+    /**
+     * 日期时间格式
+     */
+    private static final String DATE_TIME_PATTERN = "yyyy-MM-dd HH:mm:ss";
+
+    /**
+     * 默认小数位数
+     */
+    private static final int DECIMAL_SCALE = 2;
+
+    /**
+     * 默认订单数量(当查询结果为空时)
+     */
+    private static final Integer DEFAULT_ORDER_COUNT = 0;
+
+    /**
+     * 默认收益金额(当查询结果为空时)
+     */
+    private static final BigDecimal DEFAULT_REVENUE = BigDecimal.ZERO;
+
+    /**
+     * Map key:订单数量
+     */
+    private static final String KEY_ORDER_COUNT = "orderCount";
+
+    /**
+     * Map key:总金额
+     */
+    private static final String KEY_TOTAL_AMOUNT = "totalAmount";
+
+    /**
+     * Map key:本月订单量
+     */
+    private static final String KEY_MONTH_ORDER_COUNT = "monthOrderCount";
+
+    /**
+     * Map key:本月收益
+     */
+    private static final String KEY_MONTH_REVENUE = "monthRevenue";
+
+    /**
+     * Map key:总订单量
+     */
+    private static final String KEY_TOTAL_ORDER_COUNT = "totalOrderCount";
+
+    /**
+     * Map key:总收益
+     */
+    private static final String KEY_TOTAL_REVENUE = "totalRevenue";
+
+    /**
+     * 日期格式化器
+     */
+    private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern(DATE_TIME_PATTERN);
+
+    /**
+     * 获取律师订单统计数据
+     * <p>
+     * 统计律师的订单数据,包括:
+     * 1. 本月订单量和本月收益
+     * 2. 总订单量和总收益
+     * 3. 进行中订单统计
+     * 4. 待接单订单统计
+     * 5. 已退款订单统计
+     * </p>
+     * <p>
+     * 处理流程:
+     * 1. 参数校验:律师用户ID必须大于0
+     * 2. 查询本月统计数据(订单量、收益)
+     * 3. 查询总统计数据(订单量、收益)
+     * 4. 查询各状态订单统计数据(进行中、待接单、已退款)
+     * 5. 组装并返回统计数据
+     * </p>
+     *
+     * @param lawyerUserId 律师用户ID,必须大于0
+     * @return 订单统计数据,包含本月数据、总数据和各状态订单统计
+     * @throws IllegalArgumentException 当律师用户ID无效时抛出
+     * @throws RuntimeException        当查询异常时抛出
+     * @author system
+     * @since 2025-01-XX
+     */
+    @Override
+    public R<LawyerOrderStatisticsVO> getOrderStatistics(Integer lawyerUserId) {
+        log.info("获取律师订单统计数据开始,lawyerUserId={}", lawyerUserId);
+
+        // 参数校验
+        validateLawyerUserId(lawyerUserId);
+
+        try {
+            // 创建统计对象
+            LawyerOrderStatisticsVO statistics = createStatisticsVO();
+
+            // 构建统计数据
+            buildStatisticsData(statistics, lawyerUserId);
+
+            // 记录成功日志
+            logStatisticsSuccess(lawyerUserId, statistics);
+
+            return R.data(statistics);
+        } catch (IllegalArgumentException e) {
+            log.warn("获取律师订单统计数据参数校验失败,lawyerUserId={},错误信息:{}", lawyerUserId, e.getMessage());
+            return R.fail(e.getMessage());
+        } catch (RuntimeException e) {
+            log.error("获取律师订单统计数据异常,lawyerUserId={},异常信息:{}", lawyerUserId, e.getMessage(), e);
+            return R.fail("获取统计数据失败:" + e.getMessage());
+        } catch (Exception e) {
+            log.error("获取律师订单统计数据未知异常,lawyerUserId={},异常信息:{}", lawyerUserId, e.getMessage(), e);
+            return R.fail("获取统计数据失败,请稍后重试");
+        }
+    }
+
+    /**
+     * 校验律师用户ID
+     * <p>
+     * 检查律师用户ID是否为空或无效
+     * </p>
+     *
+     * @param lawyerUserId 律师用户ID
+     * @throws IllegalArgumentException 当律师用户ID为空或无效时抛出
+     */
+    private void validateLawyerUserId(Integer lawyerUserId) {
+        if (lawyerUserId == null) {
+            log.warn("获取律师订单统计数据参数校验失败:律师用户ID为null");
+            throw new IllegalArgumentException("律师用户ID不能为空");
+        }
+
+        if (lawyerUserId <= 0) {
+            log.warn("获取律师订单统计数据参数校验失败:律师用户ID无效,lawyerUserId={}", lawyerUserId);
+            throw new IllegalArgumentException("律师用户ID必须大于0");
+        }
+    }
+
+    /**
+     * 创建统计数据VO对象
+     * <p>
+     * 初始化统计数据对象
+     * </p>
+     *
+     * @return 统计数据VO对象
+     */
+    private LawyerOrderStatisticsVO createStatisticsVO() {
+        return new LawyerOrderStatisticsVO();
+    }
+
+    /**
+     * 构建统计数据
+     * <p>
+     * 构建所有统计数据,包括本月数据、总数据和各状态订单统计
+     * </p>
+     *
+     * @param statistics   统计数据VO对象
+     * @param lawyerUserId 律师用户ID
+     */
+    private void buildStatisticsData(LawyerOrderStatisticsVO statistics, Integer lawyerUserId) {
+        // 统计本月数据
+        buildMonthStatistics(statistics, lawyerUserId);
+
+        // 统计总数据
+        buildTotalStatistics(statistics, lawyerUserId);
+
+        // 统计各状态订单数据
+        buildOrderStatusStatistics(statistics, lawyerUserId);
+    }
+
+    /**
+     * 构建各状态订单统计数据
+     * <p>
+     * 统计进行中、待接单、已退款订单的统计信息
+     * </p>
+     *
+     * @param statistics   统计数据VO对象
+     * @param lawyerUserId 律师用户ID
+     */
+    private void buildOrderStatusStatistics(LawyerOrderStatisticsVO statistics, Integer lawyerUserId) {
+        // 统计进行中订单
+        statistics.setInProgressOrder(queryAndBuildOrderStatusStatistics(
+                lawyerUserId, StatisticsType.IN_PROGRESS));
+
+        // 统计待接单订单
+        statistics.setPendingAcceptOrder(queryAndBuildOrderStatusStatistics(
+                lawyerUserId, StatisticsType.PENDING_ACCEPT));
+
+        // 统计已退款订单
+        statistics.setRefundedOrder(queryAndBuildOrderStatusStatistics(
+                lawyerUserId, StatisticsType.REFUNDED));
+    }
+
+    /**
+     * 查询并构建订单状态统计数据
+     * <p>
+     * 根据统计类型查询对应的统计数据并构建返回对象
+     * </p>
+     *
+     * @param lawyerUserId   律师用户ID
+     * @param statisticsType 统计类型
+     * @return 订单状态统计对象
+     */
+    private LawyerOrderStatisticsVO.OrderStatusStatistics queryAndBuildOrderStatusStatistics(
+            Integer lawyerUserId, StatisticsType statisticsType) {
+        try {
+            Map<String, Object> statsMap = queryOrderStatusStatistics(lawyerUserId, statisticsType);
+            return buildOrderStatusStatistics(statsMap);
+        } catch (Exception e) {
+            log.error("查询订单状态统计数据异常,lawyerUserId={}, statisticsType={},异常信息:{}",
+                    lawyerUserId, statisticsType, e.getMessage(), e);
+            // 异常时返回默认值,避免影响主流程
+            return createDefaultOrderStatusStatistics();
+        }
+    }
+
+    /**
+     * 查询订单状态统计数据
+     * <p>
+     * 根据统计类型调用对应的Mapper方法查询统计数据
+     * </p>
+     *
+     * @param lawyerUserId   律师用户ID
+     * @param statisticsType 统计类型
+     * @return 统计数据Map
+     */
+    private Map<String, Object> queryOrderStatusStatistics(Integer lawyerUserId, StatisticsType statisticsType) {
+        switch (statisticsType) {
+            case IN_PROGRESS:
+                return lawyerConsultationOrderMapper.getInProgressStatistics(lawyerUserId);
+            case PENDING_ACCEPT:
+                return lawyerConsultationOrderMapper.getPendingAcceptStatistics(lawyerUserId);
+            case REFUNDED:
+                return lawyerConsultationOrderMapper.getRefundedStatistics(lawyerUserId);
+            default:
+                log.warn("未知的统计类型,statisticsType={}", statisticsType);
+                return null;
+        }
+    }
+
+    /**
+     * 创建默认订单状态统计对象
+     * <p>
+     * 当查询异常或结果为空时返回默认值
+     * </p>
+     *
+     * @return 默认订单状态统计对象
+     */
+    private LawyerOrderStatisticsVO.OrderStatusStatistics createDefaultOrderStatusStatistics() {
+        LawyerOrderStatisticsVO.OrderStatusStatistics orderStatusStatistics =
+                new LawyerOrderStatisticsVO.OrderStatusStatistics();
+        orderStatusStatistics.setOrderCount(DEFAULT_ORDER_COUNT);
+        orderStatusStatistics.setTotalAmount(DEFAULT_REVENUE);
+        return orderStatusStatistics;
+    }
+
+    /**
+     * 记录统计成功日志
+     * <p>
+     * 记录统计数据的关键信息,便于问题排查
+     * </p>
+     *
+     * @param lawyerUserId 律师用户ID
+     * @param statistics   统计数据VO对象
+     */
+    private void logStatisticsSuccess(Integer lawyerUserId, LawyerOrderStatisticsVO statistics) {
+        log.info("获取律师订单统计数据成功,lawyerUserId={}, monthOrderCount={}, monthRevenue={}, "
+                        + "totalOrderCount={}, totalRevenue={}, inProgressCount={}, pendingAcceptCount={}, refundedCount={}",
+                lawyerUserId,
+                statistics.getMonthOrderCount(),
+                statistics.getMonthRevenue(),
+                statistics.getTotalOrderCount(),
+                statistics.getTotalRevenue(),
+                statistics.getInProgressOrder() != null ? statistics.getInProgressOrder().getOrderCount() : 0,
+                statistics.getPendingAcceptOrder() != null ? statistics.getPendingAcceptOrder().getOrderCount() : 0,
+                statistics.getRefundedOrder() != null ? statistics.getRefundedOrder().getOrderCount() : 0);
+    }
+
+    /**
+     * 构建本月统计数据
+     * <p>
+     * 查询并设置本月的订单量和收益数据
+     * </p>
+     *
+     * @param statistics   统计对象
+     * @param lawyerUserId 律师用户ID
+     */
+    private void buildMonthStatistics(LawyerOrderStatisticsVO statistics, Integer lawyerUserId) {
+        try {
+            // 获取当前月份时间范围
+            String[] timeRange = getCurrentMonthTimeRange();
+
+            // 查询本月统计数据
+            Map<String, Object> monthStats = queryMonthStatistics(lawyerUserId, timeRange);
+
+            // 设置统计数据
+            setMonthStatisticsFromMap(statistics, monthStats);
+
+            log.debug("构建本月统计数据成功,lawyerUserId={}, monthOrderCount={}, monthRevenue={}",
+                    lawyerUserId, statistics.getMonthOrderCount(), statistics.getMonthRevenue());
+        } catch (Exception e) {
+            log.error("构建本月统计数据异常,lawyerUserId={},异常信息:{}", lawyerUserId, e.getMessage(), e);
+            // 异常时设置默认值,避免影响主流程
+            setDefaultMonthStatistics(statistics);
+        }
+    }
+
+    /**
+     * 查询本月统计数据
+     * <p>
+     * 调用Mapper方法查询本月的订单量和收益
+     * </p>
+     *
+     * @param lawyerUserId 律师用户ID
+     * @param timeRange    时间范围数组,[0]为开始时间,[1]为结束时间
+     * @return 统计数据Map
+     */
+    private Map<String, Object> queryMonthStatistics(Integer lawyerUserId, String[] timeRange) {
+        return lawyerConsultationOrderMapper.getMonthStatistics(
+                lawyerUserId, timeRange[0], timeRange[1]);
+    }
+
+    /**
+     * 从Map中设置本月统计数据
+     * <p>
+     * 将查询结果Map转换为统计数据并设置到VO对象中
+     * </p>
+     *
+     * @param statistics 统计对象
+     * @param monthStats 本月统计数据Map
+     */
+    private void setMonthStatisticsFromMap(LawyerOrderStatisticsVO statistics, Map<String, Object> monthStats) {
+        if (monthStats != null) {
+            statistics.setMonthOrderCount(getIntegerValue(monthStats.get(KEY_MONTH_ORDER_COUNT)));
+            Long monthRevenueFen = getLongValue(monthStats.get(KEY_MONTH_REVENUE));
+            statistics.setMonthRevenue(convertFenToYuan(monthRevenueFen));
+        } else {
+            setDefaultMonthStatistics(statistics);
+        }
+    }
+
+    /**
+     * 设置默认本月统计数据
+     * <p>
+     * 当查询结果为空时设置默认值
+     * </p>
+     *
+     * @param statistics 统计对象
+     */
+    private void setDefaultMonthStatistics(LawyerOrderStatisticsVO statistics) {
+        statistics.setMonthOrderCount(DEFAULT_ORDER_COUNT);
+        statistics.setMonthRevenue(DEFAULT_REVENUE);
+    }
+
+    /**
+     * 构建总统计数据
+     * <p>
+     * 查询并设置总订单量和总收益数据
+     * </p>
+     *
+     * @param statistics   统计对象
+     * @param lawyerUserId 律师用户ID
+     */
+    private void buildTotalStatistics(LawyerOrderStatisticsVO statistics, Integer lawyerUserId) {
+        try {
+            // 查询总统计数据
+            Map<String, Object> totalStats = queryTotalStatistics(lawyerUserId);
+
+            // 设置统计数据
+            setTotalStatisticsFromMap(statistics, totalStats);
+
+            log.debug("构建总统计数据成功,lawyerUserId={}, totalOrderCount={}, totalRevenue={}",
+                    lawyerUserId, statistics.getTotalOrderCount(), statistics.getTotalRevenue());
+        } catch (Exception e) {
+            log.error("构建总统计数据异常,lawyerUserId={},异常信息:{}", lawyerUserId, e.getMessage(), e);
+            // 异常时设置默认值,避免影响主流程
+            setDefaultTotalStatistics(statistics);
+        }
+    }
+
+    /**
+     * 查询总统计数据
+     * <p>
+     * 调用Mapper方法查询总的订单量和收益
+     * </p>
+     *
+     * @param lawyerUserId 律师用户ID
+     * @return 统计数据Map
+     */
+    private Map<String, Object> queryTotalStatistics(Integer lawyerUserId) {
+        return lawyerConsultationOrderMapper.getTotalStatistics(lawyerUserId);
+    }
+
+    /**
+     * 从Map中设置总统计数据
+     * <p>
+     * 将查询结果Map转换为统计数据并设置到VO对象中
+     * </p>
+     *
+     * @param statistics 统计对象
+     * @param totalStats 总统计数据Map
+     */
+    private void setTotalStatisticsFromMap(LawyerOrderStatisticsVO statistics, Map<String, Object> totalStats) {
+        if (totalStats != null) {
+            statistics.setTotalOrderCount(getIntegerValue(totalStats.get(KEY_TOTAL_ORDER_COUNT)));
+            Long totalRevenueFen = getLongValue(totalStats.get(KEY_TOTAL_REVENUE));
+            statistics.setTotalRevenue(convertFenToYuan(totalRevenueFen));
+        } else {
+            setDefaultTotalStatistics(statistics);
+        }
+    }
+
+    /**
+     * 设置默认总统计数据
+     * <p>
+     * 当查询结果为空时设置默认值
+     * </p>
+     *
+     * @param statistics 统计对象
+     */
+    private void setDefaultTotalStatistics(LawyerOrderStatisticsVO statistics) {
+        statistics.setTotalOrderCount(DEFAULT_ORDER_COUNT);
+        statistics.setTotalRevenue(DEFAULT_REVENUE);
+    }
+
+    /**
+     * 构建订单状态统计数据
+     * <p>
+     * 将查询结果Map转换为订单状态统计对象
+     * </p>
+     *
+     * @param statsMap 统计数据Map,可能为null
+     * @return 订单状态统计对象,如果statsMap为null则返回默认值
+     */
+    private LawyerOrderStatisticsVO.OrderStatusStatistics buildOrderStatusStatistics(
+            Map<String, Object> statsMap) {
+        LawyerOrderStatisticsVO.OrderStatusStatistics orderStatusStatistics =
+                new LawyerOrderStatisticsVO.OrderStatusStatistics();
+
+        if (statsMap != null) {
+            // 设置订单数量
+            Integer orderCount = getIntegerValue(statsMap.get(KEY_ORDER_COUNT));
+            orderStatusStatistics.setOrderCount(orderCount);
+
+            // 设置总金额(分转元)
+            Long amountFen = getLongValue(statsMap.get(KEY_TOTAL_AMOUNT));
+            BigDecimal totalAmount = convertFenToYuan(amountFen);
+            orderStatusStatistics.setTotalAmount(totalAmount);
+
+            log.debug("构建订单状态统计数据成功,orderCount={}, totalAmount={}", orderCount, totalAmount);
+        } else {
+            // 设置默认值
+            orderStatusStatistics.setOrderCount(DEFAULT_ORDER_COUNT);
+            orderStatusStatistics.setTotalAmount(DEFAULT_REVENUE);
+            log.debug("订单状态统计数据为空,设置默认值");
+        }
+
+        return orderStatusStatistics;
+    }
+
+    /**
+     * 获取当前月份的时间范围
+     * <p>
+     * 计算当前月份的第一天00:00:00到下个月第一天的00:00:00的时间范围
+     * </p>
+     *
+     * @return 时间范围数组,[0]为开始时间(本月第一天00:00:00),[1]为结束时间(下月第一天00:00:00)
+     */
+    private String[] getCurrentMonthTimeRange() {
+        try {
+            // 获取当前日期
+            LocalDate now = LocalDate.now();
+
+            // 计算本月第一天
+            LocalDate firstDayOfMonth = now.with(TemporalAdjusters.firstDayOfMonth());
+
+            // 计算下月第一天
+            LocalDate firstDayOfNextMonth = now.with(TemporalAdjusters.firstDayOfNextMonth());
+
+            // 转换为日期时间(开始时间为00:00:00,结束时间为00:00:00)
+            LocalDateTime startDateTime = firstDayOfMonth.atStartOfDay();
+            LocalDateTime endDateTime = firstDayOfNextMonth.atStartOfDay();
+
+            // 格式化为字符串
+            String startTimeStr = startDateTime.format(DATE_TIME_FORMATTER);
+            String endTimeStr = endDateTime.format(DATE_TIME_FORMATTER);
+
+            log.debug("计算当前月份时间范围,startTime={}, endTime={}", startTimeStr, endTimeStr);
+
+            return new String[]{startTimeStr, endTimeStr};
+        } catch (Exception e) {
+            log.error("计算当前月份时间范围异常,异常信息:{}", e.getMessage(), e);
+            throw new RuntimeException("计算月份时间范围失败:" + e.getMessage(), e);
+        }
+    }
+
+    /**
+     * 将分转换为元(保留2位小数)
+     * <p>
+     * 将金额从分转换为元,保留2位小数,使用四舍五入方式
+     * </p>
+     *
+     * @param fen 金额(分),单位:分,可能为null或0
+     * @return 金额(元),单位:元,保留2位小数,如果fen为null或0则返回BigDecimal.ZERO
+     */
+    private BigDecimal convertFenToYuan(Long fen) {
+        if (fen == null) {
+            log.debug("转换分转元:金额为null,返回0");
+            return DEFAULT_REVENUE;
+        }
+
+        if (fen == 0) {
+            log.debug("转换分转元:金额为0,返回0");
+            return DEFAULT_REVENUE;
+        }
+
+        if (fen < 0) {
+            log.warn("转换分转元:金额为负数,fen={},返回0", fen);
+            return DEFAULT_REVENUE;
+        }
+
+        try {
+            BigDecimal yuan = BigDecimal.valueOf(fen)
+                    .divide(BigDecimal.valueOf(FEN_TO_YUAN), DECIMAL_SCALE, RoundingMode.HALF_UP);
+            log.debug("转换分转元成功,fen={}, yuan={}", fen, yuan);
+            return yuan;
+        } catch (Exception e) {
+            log.error("转换分转元异常,fen={},异常信息:{}", fen, e.getMessage(), e);
+            // 异常时返回默认值
+            return DEFAULT_REVENUE;
+        }
+    }
+
+    /**
+     * 从Map中获取Integer值
+     * <p>
+     * 支持Integer、Long、Number类型的转换
+     * 如果value为null或无法转换,则返回默认值0
+     * </p>
+     *
+     * @param value 对象值,可能为Integer、Long或其他Number类型
+     * @return Integer值,如果value为null或无法转换则返回默认值0
+     */
+    private Integer getIntegerValue(Object value) {
+        if (value == null) {
+            log.debug("从Map获取Integer值:值为null,返回默认值0");
+            return DEFAULT_ORDER_COUNT;
+        }
+
+        // 优先处理Integer类型
+        if (value instanceof Integer) {
+            Integer result = (Integer) value;
+            log.debug("从Map获取Integer值成功,value={}, type=Integer", result);
+            return result;
+        }
+
+        // 处理Long类型
+        if (value instanceof Long) {
+            Integer result = ((Long) value).intValue();
+            log.debug("从Map获取Integer值成功,value={}, type=Long, converted={}", value, result);
+            return result;
+        }
+
+        // 处理其他Number类型
+        if (value instanceof Number) {
+            Integer result = ((Number) value).intValue();
+            log.debug("从Map获取Integer值成功,value={}, type={}, converted={}",
+                    value, value.getClass().getSimpleName(), result);
+            return result;
+        }
+
+        // 无法转换的类型
+        log.warn("无法将对象转换为Integer,value={}, type={},返回默认值0",
+                value, value.getClass().getName());
+        return DEFAULT_ORDER_COUNT;
+    }
+
+    /**
+     * 从Map中获取Long值
+     * <p>
+     * 支持Long、Integer、Number类型的转换
+     * 如果value为null或无法转换,则返回默认值0L
+     * </p>
+     *
+     * @param value 对象值,可能为Long、Integer或其他Number类型
+     * @return Long值,如果value为null或无法转换则返回默认值0L
+     */
+    private Long getLongValue(Object value) {
+        if (value == null) {
+            log.debug("从Map获取Long值:值为null,返回默认值0L");
+            return 0L;
+        }
+
+        // 优先处理Long类型
+        if (value instanceof Long) {
+            Long result = (Long) value;
+            log.debug("从Map获取Long值成功,value={}, type=Long", result);
+            return result;
+        }
+
+        // 处理Integer类型
+        if (value instanceof Integer) {
+            Long result = ((Integer) value).longValue();
+            log.debug("从Map获取Long值成功,value={}, type=Integer, converted={}", value, result);
+            return result;
+        }
+
+        // 处理其他Number类型
+        if (value instanceof Number) {
+            Long result = ((Number) value).longValue();
+            log.debug("从Map获取Long值成功,value={}, type={}, converted={}",
+                    value, value.getClass().getSimpleName(), result);
+            return result;
+        }
+
+        // 无法转换的类型
+        log.warn("无法将对象转换为Long,value={}, type={},返回默认值0L",
+                value, value.getClass().getName());
+        return 0L;
+    }
+
+    /**
+     * 统计类型枚举
+     * <p>
+     * 用于区分不同的订单状态统计类型
+     * </p>
+     */
+    private enum StatisticsType {
+        /**
+         * 进行中订单统计
+         */
+        IN_PROGRESS,
+
+        /**
+         * 待接单订单统计
+         */
+        PENDING_ACCEPT,
+
+        /**
+         * 已退款订单统计
+         */
+        REFUNDED
+    }
+
+    @Override
+    public R<LawyerDashboardVO> getLawyerDashboard(Integer lawyerUserId) {
+        log.info("获取律师仪表板数据开始,lawyerUserId={}", lawyerUserId);
+
+        // 参数校验
+        if (lawyerUserId == null || lawyerUserId <= 0) {
+            log.warn("获取律师仪表板数据失败:律师用户ID为空或无效,lawyerUserId={}", lawyerUserId);
+            return R.fail("律师用户ID不能为空");
+        }
+
+        try {
+            LawyerDashboardVO dashboard = new LawyerDashboardVO();
+
+            // 获取律师基本信息
+            LawyerUser lawyer = lawyerUserService.getById(lawyerUserId);
+            if (lawyer == null || lawyer.getDeleteFlag() == 1) {
+                log.warn("获取律师仪表板数据失败:律师不存在,lawyerUserId={}", lawyerUserId);
+                return R.fail("律师不存在");
+            }
+
+            // 设置律师基本信息
+            dashboard.setLawyerId(lawyer.getId());
+            dashboard.setName(lawyer.getName() != null ? lawyer.getName() : "");
+            dashboard.setAvatar(lawyer.getHeadImg() != null ? lawyer.getHeadImg() : "");
+            dashboard.setServiceFields(lawyer.getSpecialtyFields() != null ? lawyer.getSpecialtyFields() : "");
+
+            // 统计本月数据
+            buildMonthStatisticsForDashboard(dashboard, lawyerUserId);
+
+            // 统计总数据
+            buildTotalStatisticsForDashboard(dashboard, lawyerUserId);
+
+            // 统计进行中订单
+            dashboard.setInProgressOrder(buildOrderStatusInfo(
+                    lawyerConsultationOrderMapper.getInProgressStatistics(lawyerUserId)));
+
+            // 统计待接单订单
+            dashboard.setPendingAcceptOrder(buildOrderStatusInfo(
+                    lawyerConsultationOrderMapper.getPendingAcceptStatistics(lawyerUserId)));
+
+            // 统计已退款订单
+            dashboard.setRefundedOrder(buildOrderStatusInfo(
+                    lawyerConsultationOrderMapper.getRefundedStatistics(lawyerUserId)));
+
+            log.info("获取律师仪表板数据成功,lawyerUserId={}, name={}, monthOrderCount={}, monthRevenue={}, "
+                            + "totalOrderCount={}, totalRevenue={}",
+                    lawyerUserId, dashboard.getName(), dashboard.getMonthOrderCount(), dashboard.getMonthRevenue(),
+                    dashboard.getTotalOrderCount(), dashboard.getTotalRevenue());
+
+            return R.data(dashboard);
+        } catch (RuntimeException e) {
+            log.error("获取律师仪表板数据异常,lawyerUserId={}", lawyerUserId, e);
+            return R.fail("获取仪表板数据失败:" + e.getMessage());
+        }
+    }
+
+    /**
+     * 构建本月统计数据(用于仪表板)
+     *
+     * @param dashboard   仪表板对象
+     * @param lawyerUserId 律师用户ID
+     */
+    private void buildMonthStatisticsForDashboard(LawyerDashboardVO dashboard, Integer lawyerUserId) {
+        String[] timeRange = getCurrentMonthTimeRange();
+        Map<String, Object> monthStats = lawyerConsultationOrderMapper.getMonthStatistics(
+                lawyerUserId, timeRange[0], timeRange[1]);
+
+        if (monthStats != null) {
+            dashboard.setMonthOrderCount(getIntegerValue(monthStats.get(KEY_MONTH_ORDER_COUNT)));
+            Long monthRevenueFen = getLongValue(monthStats.get(KEY_MONTH_REVENUE));
+            dashboard.setMonthRevenue(convertFenToYuan(monthRevenueFen));
+        } else {
+            dashboard.setMonthOrderCount(0);
+            dashboard.setMonthRevenue(BigDecimal.ZERO);
+        }
+    }
+
+    /**
+     * 构建总统计数据(用于仪表板)
+     *
+     * @param dashboard   仪表板对象
+     * @param lawyerUserId 律师用户ID
+     */
+    private void buildTotalStatisticsForDashboard(LawyerDashboardVO dashboard, Integer lawyerUserId) {
+        Map<String, Object> totalStats = lawyerConsultationOrderMapper.getTotalStatistics(lawyerUserId);
+
+        if (totalStats != null) {
+            dashboard.setTotalOrderCount(getIntegerValue(totalStats.get(KEY_TOTAL_ORDER_COUNT)));
+            Long totalRevenueFen = getLongValue(totalStats.get(KEY_TOTAL_REVENUE));
+            dashboard.setTotalRevenue(convertFenToYuan(totalRevenueFen));
+        } else {
+            dashboard.setTotalOrderCount(0);
+            dashboard.setTotalRevenue(BigDecimal.ZERO);
+        }
+    }
+
+    /**
+     * 构建订单状态信息(用于仪表板)
+     *
+     * @param statsMap 统计数据Map
+     * @return 订单状态信息对象
+     */
+    private LawyerDashboardVO.OrderStatusInfo buildOrderStatusInfo(Map<String, Object> statsMap) {
+        LawyerDashboardVO.OrderStatusInfo orderStatusInfo = new LawyerDashboardVO.OrderStatusInfo();
+
+        if (statsMap != null) {
+            orderStatusInfo.setOrderCount(getIntegerValue(statsMap.get(KEY_ORDER_COUNT)));
+            Long amountFen = getLongValue(statsMap.get(KEY_TOTAL_AMOUNT));
+            orderStatusInfo.setTotalAmount(convertFenToYuan(amountFen));
+        } else {
+            orderStatusInfo.setOrderCount(0);
+            orderStatusInfo.setTotalAmount(BigDecimal.ZERO);
+        }
+
+        return orderStatusInfo;
+    }
+}
+

+ 0 - 4
alien-lawyer/src/main/java/shop/alien/lawyer/service/impl/LawyerUserLogInServiceImpl.java

@@ -42,9 +42,6 @@ public class LawyerUserLogInServiceImpl extends ServiceImpl<LawyerUserMapper, La
     @Value("${jwt.expiration-time}")
     private String effectiveTime;
 
-    @Value("${order.lawyerFee}")
-    private String lawyerFee;
-
     /**
      * 设定初始化默认密码
      */
@@ -116,7 +113,6 @@ public class LawyerUserLogInServiceImpl extends ServiceImpl<LawyerUserMapper, La
             user.setIsRecommended(0);
             user.setOrderReceivingStatus(1);
             user.setPassType(2);
-            user.setConsultationFee(Integer.parseInt(lawyerFee));
             user.setNickName(user.getName() != null && !user.getName().isEmpty() ?
                     user.getName().charAt(0) + "律师" : "律师");
             lawyerUserMapper.insert(user);

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

@@ -14,6 +14,7 @@ import org.springframework.util.CollectionUtils;
 import org.springframework.util.StringUtils;
 import shop.alien.entity.result.R;
 import shop.alien.entity.store.*;
+import shop.alien.entity.store.dto.LawyerUserDto;
 import shop.alien.entity.store.excelVo.LawyerUserExcelVo;
 import shop.alien.entity.store.vo.LawyerUserVo;
 import shop.alien.lawyer.config.BaseRedisService;
@@ -120,7 +121,15 @@ public class LawyerUserServiceImpl extends ServiceImpl<LawyerUserMapper, LawyerU
                 (lawyer.getAccountBlurb() != null ? lawyer.getAccountBlurb() : "Ta还没有个人业务介绍"));
         result.put("field", lawyer.getSpecialtyFields() != null ? lawyer.getSpecialtyFields() : "");
         result.put("experience", lawyer.getPracticeYears() != null ? lawyer.getPracticeYears() : 0);
-        result.put("lawFirm", lawyer.getLawFirm() != null ? lawyer.getLawFirm() : "");
+        // 通过firm_id查询律所名称
+        String lawFirmName = "";
+        if (lawyer.getFirmId() != null) {
+            LawFirm lawFirm = lawFirmMapper.selectById(lawyer.getFirmId());
+            if (lawFirm != null && (lawFirm.getDeleteFlag() == null || lawFirm.getDeleteFlag() == 0)) {
+                lawFirmName = lawFirm.getFirmName() != null ? lawFirm.getFirmName() : "";
+            }
+        }
+        result.put("lawFirm", lawFirmName);
         result.put("online", lawyer.getIsOnline() != null && lawyer.getIsOnline() == 1);
         result.put("price", lawyer.getConsultationFee() != null ? lawyer.getConsultationFee() / 100 : 0);
 
@@ -681,6 +690,10 @@ public class LawyerUserServiceImpl extends ServiceImpl<LawyerUserMapper, LawyerU
             lawyerUser.setPracticeStartDate(lawyerUserVo.getPracticeStartDate());
             hasUpdate = true;
         }
+        if (lawyerUserVo.getCertificateImage() != null) {
+            lawyerUser.setCertificateImage(lawyerUserVo.getCertificateImage());
+            hasUpdate = true;
+        }
 
 // 只有当有字段需要更新时才执行更新操作
         if (hasUpdate) {
@@ -820,6 +833,18 @@ public class LawyerUserServiceImpl extends ServiceImpl<LawyerUserMapper, LawyerU
         EasyExcelUtil.exportExcel(response, excelVoList, LawyerUserExcelVo.class, "中台律师列表", "中台律师列表");
     }
 
+    @Override
+    public R<LawyerUserVo> updateLawyerUserCharge(LawyerUserDto lawyerUserDto) {
+        LawyerUser lawyerUser = new LawyerUser();
+        lawyerUser.setId(lawyerUserDto.getId());
+        lawyerUser.setChargeMinute(lawyerUserDto.getChargeMinute());
+        lawyerUser.setMinuteNum(lawyerUserDto.getMinuteNum());
+        lawyerUser.setChargeTime(lawyerUserDto.getChargeTime());
+        lawyerUser.setTimeNum(lawyerUserDto.getTimeNum());
+        this.updateById(lawyerUser);
+        return R.success("修改成功");
+    }
+
     /**
      * 将LawyerUserVo转换为LawyerUserExcelVo
      *

+ 487 - 1
alien-lawyer/src/main/java/shop/alien/lawyer/service/impl/LawyerUserViolationServiceImpl.java

@@ -17,6 +17,7 @@ import org.springframework.util.CollectionUtils;
 import shop.alien.entity.store.*;
 import shop.alien.entity.store.dto.LawyerUserViolationDto;
 import shop.alien.entity.store.vo.LawyerUserViolationVo;
+import shop.alien.entity.store.vo.LawyerViolationDetailVO;
 import shop.alien.entity.store.vo.WebSocketVo;
 import shop.alien.mapper.*;
 import shop.alien.lawyer.config.WebSocketProcess;
@@ -381,7 +382,7 @@ public class LawyerUserViolationServiceImpl extends ServiceImpl<LawyerUserViolat
             LifeNotice lifeNotice = new LifeNotice();
             lifeNotice.setSenderId(SYSTEM_SENDER_ID);
             lifeNotice.setBusinessId(lawyerUserViolation.getId());
-            lifeNotice.setTitle("咨询订单举报通知");
+            lifeNotice.setTitle("举报通知");
 
             // 获取举报人接收ID
             String receiverId = getReporterReceiverId(lawyerUserViolation);
@@ -398,6 +399,7 @@ public class LawyerUserViolationServiceImpl extends ServiceImpl<LawyerUserViolat
             jsonObject.put("message", message);
             lifeNotice.setContext(jsonObject.toJSONString());
             lifeNotice.setNoticeType(1);
+            lifeNotice.setBusinessType(1);
 
             return lifeNotice;
 
@@ -604,6 +606,7 @@ public class LawyerUserViolationServiceImpl extends ServiceImpl<LawyerUserViolat
             jsonObject.put("message", message);
             lifeNotice.setContext(jsonObject.toJSONString());
             lifeNotice.setNoticeType(1);
+            lifeNotice.setBusinessType(1);
 
             return lifeNotice;
 
@@ -1312,6 +1315,7 @@ public class LawyerUserViolationServiceImpl extends ServiceImpl<LawyerUserViolat
         JSONObject jsonObject = new JSONObject();
         jsonObject.put("message", message);
         lifeNotice.setContext(jsonObject.toJSONString());
+        lifeNotice.setBusinessType(1);
 
         return lifeNotice;
     }
@@ -1471,5 +1475,487 @@ public class LawyerUserViolationServiceImpl extends ServiceImpl<LawyerUserViolat
             throw new RuntimeException("查询举报原因列表失败:" + e.getMessage(), e);
         }
     }
+
+    /**
+     * 根据订单ID查询举报详情
+     * <p>
+     * 查询举报详情信息,包括举报的详情信息、订单的详情信息以及订单律师名字相关信息
+     * </p>
+     * <p>
+     * 处理流程:
+     * 1. 参数校验:订单ID必须大于0
+     * 2. 查询举报详情:通过Mapper查询订单、举报、律师关联信息
+     * 3. 校验举报信息:检查是否存在举报记录(violationId不为null)
+     * 4. 填充举报人姓名:根据举报人ID查询并设置举报人姓名
+     * 5. 设置订单价格字符串:根据收费方式格式化价格显示
+     * </p>
+     *
+     * @param orderId 订单ID,必须大于0
+     * @return 举报详情VO对象,包含举报信息、订单信息、律师信息,如果订单不存在或未举报则返回null
+     * @throws IllegalArgumentException 当订单ID无效时抛出
+     * @throws RuntimeException        当查询异常时抛出
+     * @author system
+     * @since 2025-01-XX
+     */
+    @Override
+    public LawyerViolationDetailVO getViolationDetailByOrderId(Integer orderId) {
+        log.info("根据订单ID查询举报详情开始,orderId={}", orderId);
+
+        // 参数校验
+        validateOrderIdForViolationDetail(orderId);
+
+        try {
+            // 查询举报详情
+            LawyerViolationDetailVO detailVO = queryViolationDetailByOrderId(orderId);
+            if (detailVO == null) {
+                log.warn("根据订单ID查询举报详情:订单不存在,orderId={}", orderId);
+                return null;
+            }
+
+            // 检查是否存在举报信息(如果violationId为null,说明没有举报记录)
+            if (!hasViolationInfo(detailVO)) {
+                log.warn("根据订单ID查询举报详情:订单存在但没有举报信息,orderId={}", orderId);
+                return null;
+            }
+
+            // 填充举报详情附加信息
+            fillViolationDetailAdditionalInfo(detailVO);
+
+            log.info("根据订单ID查询举报详情成功,orderId={}, violationId={}, orderNumber={}",
+                    orderId, detailVO.getViolationId(), detailVO.getOrderNumber());
+            return detailVO;
+
+        } catch (IllegalArgumentException e) {
+            log.warn("根据订单ID查询举报详情参数校验失败,orderId={},错误信息:{}", orderId, e.getMessage());
+            throw e;
+        } catch (Exception e) {
+            log.error("根据订单ID查询举报详情异常,orderId={},异常信息:{}", orderId, e.getMessage(), e);
+            throw new RuntimeException("查询举报详情失败:" + e.getMessage(), e);
+        }
+    }
+
+    /**
+     * 校验订单ID(用于查询举报详情)
+     * <p>
+     * 检查订单ID是否为空或无效
+     * </p>
+     *
+     * @param orderId 订单ID
+     * @throws IllegalArgumentException 当订单ID为空或无效时抛出
+     */
+    private void validateOrderIdForViolationDetail(Integer orderId) {
+        if (orderId == null) {
+            log.warn("根据订单ID查询举报详情参数校验失败:订单ID为null");
+            throw new IllegalArgumentException("订单ID不能为空");
+        }
+
+        if (orderId <= 0) {
+            log.warn("根据订单ID查询举报详情参数校验失败:订单ID无效,orderId={}", orderId);
+            throw new IllegalArgumentException("订单ID必须大于0");
+        }
+    }
+
+    /**
+     * 查询举报详情
+     * <p>
+     * 通过Mapper查询订单、举报、律师关联信息
+     * </p>
+     *
+     * @param orderId 订单ID
+     * @return 举报详情VO对象,如果订单不存在返回null
+     */
+    private LawyerViolationDetailVO queryViolationDetailByOrderId(Integer orderId) {
+        try {
+            LawyerViolationDetailVO detailVO = consultationOrderMapper.getViolationDetailByOrderId(orderId);
+            if (detailVO != null) {
+                log.debug("查询举报详情成功,orderId={}, orderNumber={}", orderId, detailVO.getOrderNumber());
+            }
+            return detailVO;
+        } catch (Exception e) {
+            log.error("查询举报详情数据库异常,orderId={},异常信息:{}", orderId, e.getMessage(), e);
+            throw new RuntimeException("查询举报详情失败:" + e.getMessage(), e);
+        }
+    }
+
+    /**
+     * 检查是否存在举报信息
+     * <p>
+     * 通过检查violationId是否为null来判断是否存在举报记录
+     * </p>
+     *
+     * @param detailVO 举报详情VO对象
+     * @return true表示存在举报信息,false表示不存在
+     */
+    private boolean hasViolationInfo(LawyerViolationDetailVO detailVO) {
+        return detailVO != null && detailVO.getViolationId() != null;
+    }
+
+    /**
+     * 填充举报详情附加信息
+     * <p>
+     * 填充举报人姓名和订单价格字符串等附加信息
+     * </p>
+     *
+     * @param detailVO 举报详情VO对象
+     */
+    private void fillViolationDetailAdditionalInfo(LawyerViolationDetailVO detailVO) {
+        if (detailVO == null) {
+            return;
+        }
+
+        // 填充举报人姓名
+        fillReportingUserName(detailVO);
+
+        // 设置订单价格字符串
+        setOrderPriceStr(detailVO);
+    }
+
+    /**
+     * 填充举报人姓名
+     * <p>
+     * 根据举报人类型和ID查询并设置举报人姓名
+     * </p>
+     *
+     * @param detailVO 举报详情VO对象
+     */
+    private void fillReportingUserName(LawyerViolationDetailVO detailVO) {
+        if (detailVO == null) {
+            return;
+        }
+
+        String reportingUserId = detailVO.getReportingUserId();
+        String reportingUserType = detailVO.getReportingUserType();
+
+        if (StringUtils.isEmpty(reportingUserId)) {
+            log.debug("举报人ID为空,无需填充举报人姓名");
+            return;
+        }
+
+        if (StringUtils.isEmpty(reportingUserType)) {
+            log.warn("举报人类型为空,无法确定查询表,reportingUserId={}", reportingUserId);
+            return;
+        }
+
+        String reportingUserName = getReportingUserNameByTypeAndId(reportingUserType, reportingUserId);
+        detailVO.setReportingUserName(reportingUserName);
+        log.debug("填充举报人姓名成功,reportingUserType={}, reportingUserId={}, reportingUserName={}",
+                reportingUserType, reportingUserId, reportingUserName);
+    }
+
+    /**
+     * 金额单位转换:分转元
+     */
+    private static final int FEN_TO_YUAN = 100;
+
+    /**
+     * 默认价格字符串(当所有收费字段都为空时)
+     */
+    private static final String DEFAULT_PRICE_STR = "";
+
+    /**
+     * 默认格式化价格(当价格为null时)
+     */
+    private static final String DEFAULT_FORMATTED_PRICE = "0";
+
+    /**
+     * 价格格式:分钟收费
+     */
+    private static final String PRICE_FORMAT_MINUTE = "¥%s/%d分钟";
+
+    /**
+     * 价格格式:次数收费
+     */
+    private static final String PRICE_FORMAT_TIME = "¥%s/%d次";
+
+    /**
+     * 小数位数:保留2位小数
+     */
+    private static final int DECIMAL_SCALE = 2;
+
+    /**
+     * 设置订单价格字符串
+     * <p>
+     * 根据charge_minute、minute_num、charge_time、time_num字段设置orderPriceStr
+     * 优先级:charge_minute和minute_num > charge_time和time_num
+     * chargeMinute和chargeTime单位都是分,需要转换为元并保留两位小数,去掉尾随的0
+     * </p>
+     * <p>
+     * 处理逻辑:
+     * 1. 优先检查是否按分钟收费(chargeMinute和minuteNum都不为空)
+     * 2. 其次检查是否按次数收费(chargeTime和timeNum都不为空)
+     * 3. 如果都不满足,则设置为空字符串
+     * </p>
+     *
+     * @param detailVO 举报详情VO对象,不能为null
+     */
+    private void setOrderPriceStr(LawyerViolationDetailVO detailVO) {
+        if (detailVO == null) {
+            log.warn("设置订单价格字符串失败:举报详情VO对象为null");
+            return;
+        }
+
+        try {
+            // 优先处理按分钟收费
+            if (isMinuteBasedCharging(detailVO)) {
+                String orderPriceStr = buildMinuteBasedPriceStr(detailVO);
+                detailVO.setOrderPriceStr(orderPriceStr);
+                log.debug("设置订单价格字符串成功(按分钟收费),chargeMinute={}, minuteNum={}, orderPriceStr={}",
+                        detailVO.getChargeMinute(), detailVO.getMinuteNum(), orderPriceStr);
+                return;
+            }
+
+            // 其次处理按次数收费
+            if (isTimeBasedCharging(detailVO)) {
+                String orderPriceStr = buildTimeBasedPriceStr(detailVO);
+                detailVO.setOrderPriceStr(orderPriceStr);
+                log.debug("设置订单价格字符串成功(按次数收费),chargeTime={}, timeNum={}, orderPriceStr={}",
+                        detailVO.getChargeTime(), detailVO.getTimeNum(), orderPriceStr);
+                return;
+            }
+
+            // 当都为空则返回空
+            detailVO.setOrderPriceStr(DEFAULT_PRICE_STR);
+            log.debug("订单价格字段都为空,设置默认空字符串");
+
+        } catch (Exception e) {
+            log.error("设置订单价格字符串异常,异常信息:{}", e.getMessage(), e);
+            // 异常时设置默认值,避免影响主流程
+            detailVO.setOrderPriceStr(DEFAULT_PRICE_STR);
+        }
+    }
+
+    /**
+     * 判断是否按分钟收费
+     * <p>
+     * 通过检查chargeMinute和minuteNum是否都不为空来判断
+     * </p>
+     *
+     * @param detailVO 举报详情VO对象
+     * @return true表示按分钟收费,false表示不是
+     */
+    private boolean isMinuteBasedCharging(LawyerViolationDetailVO detailVO) {
+        return detailVO != null
+                && detailVO.getChargeMinute() != null
+                && detailVO.getMinuteNum() != null;
+    }
+
+    /**
+     * 判断是否按次数收费
+     * <p>
+     * 通过检查chargeTime和timeNum是否都不为空来判断
+     * </p>
+     *
+     * @param detailVO 举报详情VO对象
+     * @return true表示按次数收费,false表示不是
+     */
+    private boolean isTimeBasedCharging(LawyerViolationDetailVO detailVO) {
+        return detailVO != null
+                && detailVO.getChargeTime() != null
+                && detailVO.getTimeNum() != null;
+    }
+
+    /**
+     * 构建按分钟收费的价格字符串
+     * <p>
+     * 格式:¥{价格}/{分钟数}分钟
+     * 例如:¥1.5/30分钟
+     * </p>
+     *
+     * @param detailVO 举报详情VO对象
+     * @return 格式化后的价格字符串
+     */
+    private String buildMinuteBasedPriceStr(LawyerViolationDetailVO detailVO) {
+        // chargeMinute单位是分,需要转换为元并保留两位小数,去掉尾随的0
+        BigDecimal chargeMinuteYuan = convertFenToYuan(detailVO.getChargeMinute());
+        String priceStr = formatPrice(chargeMinuteYuan);
+        return String.format(PRICE_FORMAT_MINUTE, priceStr, detailVO.getMinuteNum());
+    }
+
+    /**
+     * 构建按次数收费的价格字符串
+     * <p>
+     * 格式:¥{价格}/{次数}次
+     * 例如:¥5/3次
+     * </p>
+     *
+     * @param detailVO 举报详情VO对象
+     * @return 格式化后的价格字符串
+     */
+    private String buildTimeBasedPriceStr(LawyerViolationDetailVO detailVO) {
+        // chargeTime单位是分,需要转换为元并保留两位小数,去掉尾随的0
+        BigDecimal chargeTimeYuan = convertFenToYuan(detailVO.getChargeTime());
+        String priceStr = formatPrice(chargeTimeYuan);
+        return String.format(PRICE_FORMAT_TIME, priceStr, detailVO.getTimeNum());
+    }
+
+    /**
+     * 将分转换为元
+     * <p>
+     * 分转元,保留指定小数位数,使用四舍五入
+     * </p>
+     *
+     * @param fen 金额(分),单位:分
+     * @return 金额(元),单位:元,保留2位小数
+     */
+    private BigDecimal convertFenToYuan(Integer fen) {
+        if (fen == null) {
+            log.warn("转换分转元失败:金额为null,返回0");
+            return BigDecimal.ZERO;
+        }
+
+        if (fen < 0) {
+            log.warn("转换分转元失败:金额为负数,fen={},返回0", fen);
+            return BigDecimal.ZERO;
+        }
+
+        return BigDecimal.valueOf(fen)
+                .divide(BigDecimal.valueOf(FEN_TO_YUAN), DECIMAL_SCALE, RoundingMode.HALF_UP);
+    }
+
+    /**
+     * 格式化价格,去掉尾随的0
+     * <p>
+     * 将BigDecimal价格格式化为字符串,去掉尾随的0
+     * 例如:1.00 -> 1, 1.50 -> 1.5, 1.25 -> 1.25
+     * </p>
+     *
+     * @param price 价格(BigDecimal),可能为null
+     * @return 格式化后的价格字符串,如果price为null则返回"0"
+     */
+    private String formatPrice(BigDecimal price) {
+        if (price == null) {
+            log.debug("格式化价格:价格为null,返回默认值0");
+            return DEFAULT_FORMATTED_PRICE;
+        }
+
+        try {
+            // 去掉尾随的0
+            String formattedPrice = price.stripTrailingZeros().toPlainString();
+            log.debug("格式化价格成功,原始价格={}, 格式化后={}", price, formattedPrice);
+            return formattedPrice;
+        } catch (Exception e) {
+            log.error("格式化价格异常,price={},异常信息:{}", price, e.getMessage(), e);
+            // 异常时返回默认值
+            return DEFAULT_FORMATTED_PRICE;
+        }
+    }
+
+    /**
+     * 根据用户类型和ID获取举报人姓名
+     * <p>
+     * 根据用户类型直接查询对应的表,提高查询效率:
+     * 1. 律师用户(type='3'):从lawyer_user表查询name字段
+     * 2. 商户用户(type='1'):从store_user表查询nick_name字段
+     * 3. 普通用户(type='2'):从life_user表查询user_name字段
+     * </p>
+     *
+     * @param userType 用户类型,1:商户,2:用户,3:律师
+     * @param userId   用户ID,不能为空
+     * @return 用户姓名,如果用户不存在或查询异常则返回null
+     */
+    private String getReportingUserNameByTypeAndId(String userType, String userId) {
+        if (StringUtils.isEmpty(userType)) {
+            log.warn("获取举报人姓名失败:用户类型为空,userId={}", userId);
+            return null;
+        }
+
+        if (StringUtils.isEmpty(userId)) {
+            log.warn("获取举报人姓名失败:用户ID为空,userType={}", userType);
+            return null;
+        }
+
+        try {
+            // 根据用户类型直接查询对应的表,避免依次查询多张表
+            if (USER_TYPE_LAWYER.equals(userType)) {
+                // 律师用户:从lawyer_user表查询
+                return queryLawyerUserName(userId);
+            } else if (USER_TYPE_STORE.equals(userType)) {
+                // 商户用户:从store_user表查询
+                return queryStoreUserName(userId);
+            } else if (USER_TYPE_LIFE.equals(userType)) {
+                // 普通用户:从life_user表查询
+                return queryLifeUserName(userId);
+            } else {
+                log.warn("获取举报人姓名失败:未知的用户类型,userType={}, userId={}", userType, userId);
+                return null;
+            }
+
+        } catch (Exception e) {
+            log.error("获取举报人姓名异常,userType={}, userId={},异常信息:{}", userType, userId, e.getMessage(), e);
+            return null;
+        }
+    }
+
+    /**
+     * 查询律师用户姓名
+     * <p>
+     * 从lawyer_user表查询律师用户姓名
+     * </p>
+     *
+     * @param userId 用户ID
+     * @return 律师用户姓名,如果用户不存在则返回null
+     */
+    private String queryLawyerUserName(String userId) {
+        try {
+            LawyerUser lawyerUser = lawyerUserMapper.selectById(userId);
+            if (lawyerUser != null && StringUtils.isNotEmpty(lawyerUser.getName())) {
+                log.debug("查询律师用户姓名成功,userId={}, userName={}", userId, lawyerUser.getName());
+                return lawyerUser.getName();
+            }
+            log.debug("查询律师用户姓名:用户不存在或姓名为空,userId={}", userId);
+            return null;
+        } catch (Exception e) {
+            log.error("查询律师用户姓名异常,userId={},异常信息:{}", userId, e.getMessage(), e);
+            return null;
+        }
+    }
+
+    /**
+     * 查询商户用户姓名
+     * <p>
+     * 从store_user表查询商户用户昵称
+     * </p>
+     *
+     * @param userId 用户ID
+     * @return 商户用户昵称,如果用户不存在则返回null
+     */
+    private String queryStoreUserName(String userId) {
+        try {
+            StoreUser storeUser = storeUserMapper.selectById(userId);
+            if (storeUser != null && StringUtils.isNotEmpty(storeUser.getNickName())) {
+                log.debug("查询商户用户姓名成功,userId={}, userName={}", userId, storeUser.getNickName());
+                return storeUser.getNickName();
+            }
+            log.debug("查询商户用户姓名:用户不存在或姓名为空,userId={}", userId);
+            return null;
+        } catch (Exception e) {
+            log.error("查询商户用户姓名异常,userId={},异常信息:{}", userId, e.getMessage(), e);
+            return null;
+        }
+    }
+
+    /**
+     * 查询普通用户姓名
+     * <p>
+     * 从life_user表查询普通用户姓名
+     * </p>
+     *
+     * @param userId 用户ID
+     * @return 普通用户姓名,如果用户不存在则返回null
+     */
+    private String queryLifeUserName(String userId) {
+        try {
+            LifeUser lifeUser = lifeUserMapper.selectById(userId);
+            if (lifeUser != null && StringUtils.isNotEmpty(lifeUser.getUserName())) {
+                log.debug("查询普通用户姓名成功,userId={}, userName={}", userId, lifeUser.getUserName());
+                return lifeUser.getUserName();
+            }
+            log.debug("查询普通用户姓名:用户不存在或姓名为空,userId={}", userId);
+            return null;
+        } catch (Exception e) {
+            log.error("查询普通用户姓名异常,userId={},异常信息:{}", userId, e.getMessage(), e);
+            return null;
+        }
+    }
 }
 

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

@@ -75,6 +75,11 @@ public class OrderExpirationServiceImpl implements OrderExpirationService, Comma
     private static final String ORDER_REFUND_TIMEOUT_PREFIX = "lawyer:order:refund:timeout:";
 
     /**
+     * Redis key前綴:订单超时完成
+     */
+    private static final String ORDER_COMPLETE_TIMEOUT_PREFIX = "lawyer:order:complete:timeout:";
+
+    /**
      * 默認超時時間:30分鐘
      */
     private static final long DEFAULT_TIMEOUT_SECONDS = 30 * 60;
@@ -100,6 +105,10 @@ public class OrderExpirationServiceImpl implements OrderExpirationService, Comma
         // 註冊訂單退款超時處理器
         expirationHandler.registerHandler(ORDER_REFUND_TIMEOUT_PREFIX, this::handleRefundOrderKey);
         log.info("訂單退款超時處理器註冊完成,前綴: {}", ORDER_REFUND_TIMEOUT_PREFIX);
+
+        // 订单超时完成处理器
+        expirationHandler.registerHandler(ORDER_COMPLETE_TIMEOUT_PREFIX, this::handleTimeOutCompleteOrderKey);
+        log.info("订单已经完成,前綴: {}", ORDER_COMPLETE_TIMEOUT_PREFIX);
     }
 
     @Override
@@ -244,6 +253,25 @@ public class OrderExpirationServiceImpl implements OrderExpirationService, Comma
         }
     }
 
+    /**
+     * 处理订单超时完成key
+     *
+     * @param expiredKey 過期的key,格式:lawyer:order:complete:timeout:{orderId}
+     */
+    private void handleTimeOutCompleteOrderKey(String expiredKey) {
+        try {
+            // 從key中提取訂單ID
+            String orderNo = expiredKey.replace(ORDER_COMPLETE_TIMEOUT_PREFIX, "");
+
+            log.info("订单超时已完成,订单no: {}", orderNo);
+
+            // 處理訂單支付超時
+            handleTimeOutCompleteOrder(orderNo);
+        } catch (Exception e) {
+            log.error("處理過期訂單key失敗,key: {}", expiredKey, e);
+        }
+    }
+
     private void refundParam(Map<String,String> paramMap,LawyerConsultationOrder order,String refundReason){
         //支付宝
         if ("1".equals(order.getPayType())) {
@@ -303,6 +331,46 @@ public class OrderExpirationServiceImpl implements OrderExpirationService, Comma
     }
 
     @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void handleTimeOutCompleteOrder(String orderNum) {
+        log.info("开始处理订单完成,订单no: {}", orderNum);
+
+        try {
+            // 查詢訂單
+            LambdaQueryWrapper<LawyerConsultationOrder> queryWrapper = new LambdaQueryWrapper<>();
+            queryWrapper.eq(LawyerConsultationOrder::getOrderNumber, orderNum).last("LIMIT 1");
+            LawyerConsultationOrder order = orderMapper.selectOne(queryWrapper);
+            if (order == null) {
+                log.warn("订单不存在,订单no: {}", orderNum);
+                return;
+            }
+
+            // 只处理进行中的订单
+            if (order.getOrderStatus() != null && order.getOrderStatus() == 2) {
+                log.info("订单为进行中状态,开始完成订单,订单no: {}", orderNum);
+
+                LawyerConsultationOrder lawyerConsultationOrder = new LawyerConsultationOrder();
+                // 更新订单状态为已完成
+                lawyerConsultationOrder.setOrderStatus(3); // 3:已完成
+                lawyerConsultationOrder.setId(order.getId());
+                lawyerConsultationOrder.setUpdatedTime(new java.util.Date());
+
+                int updated = orderMapper.updateById(lawyerConsultationOrder);
+                if (updated > 0) {
+                    log.info("订单已经超时,已经自动完成,订单no: {}", orderNum);
+                } else {
+                    log.error("订单自动完成失败,订单no: {}", orderNum);
+                }
+            } else {
+                log.info("订单不是进行中,无需处理,订单no: {}, 当前状态: {}", orderNum, order.getOrderStatus());
+            }
+        } catch (Exception e) {
+            log.error("处理订单状态完成异常,订单no: {}", orderNum, e);
+            throw e;
+        }
+    }
+
+    @Override
     public void setOrderPaymentTimeout(String orderNumber, long timeoutSeconds) {
         if (orderNumber == null) {
             log.warn("訂單ID為null,無法設置支付超時監聽");
@@ -353,6 +421,23 @@ public class OrderExpirationServiceImpl implements OrderExpirationService, Comma
         log.info("設置訂單退款超時監聽,訂單NO: {}, 超時時間: {}秒, key: {}", orderNumber, timeout, key);
     }
 
+    @Override
+    public void setOrderCompleteTimeout(String orderNumber, long timeoutSeconds) {
+        if (orderNumber == null) {
+            log.warn("订单NO为null,无法设置订单超时完成监听");
+            return;
+        }
+
+        String key = ORDER_COMPLETE_TIMEOUT_PREFIX + orderNumber;
+        long timeout = timeoutSeconds > 0 ? timeoutSeconds : 60 * Long.parseLong(coefficients);
+
+        // 设置Redis key,带过期时间
+        // 当key过期时,会触发RedisKeyExpirationListener
+        redisService.setString(key, String.valueOf(orderNumber), timeout);
+
+        log.info("设置订单超时完成监听,订单: {}, 超时时间: {}秒, key: {}", orderNumber, timeout, key);
+    }
+
     /**
      * 取消訂單支付超時監聽(當訂單已支付時調用)
      * 
@@ -421,6 +506,7 @@ public class OrderExpirationServiceImpl implements OrderExpirationService, Comma
         jsonObject.put("message", message);
         lifeNotice.setContext(jsonObject.toJSONString());
         lifeNotice.setNoticeType(1);
+        lifeNotice.setBusinessType(1);
 
         return lifeNotice;
     }
@@ -436,6 +522,8 @@ public class OrderExpirationServiceImpl implements OrderExpirationService, Comma
         jsonObject.put("message", message);
         lifeNotice.setContext(jsonObject.toJSONString());
         lifeNotice.setNoticeType(1);
+        lifeNotice.setBusinessType(1);
+        lifeNotice.setCreatedTime(new Date());
 
         return lifeNotice;
     }

+ 13 - 2
alien-lawyer/src/main/java/shop/alien/lawyer/service/impl/OrderReviewServiceImpl.java

@@ -25,9 +25,11 @@ import shop.alien.entity.store.vo.PendingReviewVo;
 import shop.alien.lawyer.service.OrderReviewService;
 import shop.alien.lawyer.service.ReviewCommentService;
 import shop.alien.entity.store.LawyerUser;
+import shop.alien.entity.store.LifeUser;
 import shop.alien.mapper.LawyerConsultationOrderMapper;
 import shop.alien.mapper.LawyerUserMapper;
 import shop.alien.mapper.LifeLikeRecordMapper;
+import shop.alien.mapper.LifeUserMapper;
 import shop.alien.mapper.OrderReviewMapper;
 import shop.alien.mapper.ReviewCommentMapper;
 
@@ -53,6 +55,7 @@ public class OrderReviewServiceImpl extends ServiceImpl<OrderReviewMapper, Order
     private final ReviewCommentMapper reviewCommentMapper;
     private final LifeLikeRecordMapper lifeLikeRecordMapper;
     private final LawyerUserMapper lawyerUserMapper;
+    private final LifeUserMapper lifeUserMapper;
 
     @Override
     public R<OrderReview> createReview(OrderReviewDto reviewDto) {
@@ -206,7 +209,7 @@ public class OrderReviewServiceImpl extends ServiceImpl<OrderReviewMapper, Order
                 pageNum, pageSize, orderId, lawyerUserId, userId, currentUserId);
 
         Page<OrderReviewVo> page = new Page<>(pageNum, pageSize);
-        IPage<OrderReviewVo> result = orderReviewMapper.getReviewListWithUser(page, orderId, lawyerUserId, userId, currentUserId);
+        IPage<OrderReviewVo> result = orderReviewMapper.getReviewListWithUser(page, orderId, lawyerUserId, userId, currentUserId, null);
         
         // 处理评价图片JSON字符串转换为列表
         if (result.getRecords() != null) {
@@ -274,6 +277,12 @@ public class OrderReviewServiceImpl extends ServiceImpl<OrderReviewMapper, Order
             totalCommentCount = 0;
         }
         
+        // 查询用户详细信息
+        LifeUser userDetail = null;
+        if (reviewVo.getUserId() != null) {
+            userDetail = lifeUserMapper.selectById(reviewVo.getUserId());
+        }
+
         // 构建返回结果
         OrderReviewDetailVo detailVo = new OrderReviewDetailVo();
         detailVo.setReview(reviewVo);
@@ -282,6 +291,8 @@ public class OrderReviewServiceImpl extends ServiceImpl<OrderReviewMapper, Order
         // 设置评价的点赞数和是否已点赞(从 reviewVo 中获取)
         detailVo.setLikeCount(reviewVo.getLikeCount() != null ? reviewVo.getLikeCount() : 0);
         detailVo.setIsLiked(reviewVo.getIsLiked() != null ? reviewVo.getIsLiked() : 0);
+        // 设置用户详细信息
+        detailVo.setUserDetail(userDetail);
 
         return R.data(detailVo);
     }
@@ -461,7 +472,7 @@ public class OrderReviewServiceImpl extends ServiceImpl<OrderReviewMapper, Order
         }
 
         Page<OrderReviewVo> page = new Page<>(pageNum, pageSize);
-        IPage<OrderReviewVo> result = orderReviewMapper.getReviewListWithUser(page, null, null, userId, currentUserId);
+        IPage<OrderReviewVo> result = orderReviewMapper.getReviewListWithUser(page, null, null, userId, currentUserId, true);
 
         // 处理评价图片:将JSON字符串转换为List<String>
         if (result.getRecords() != null) {

+ 22 - 0
alien-lawyer/src/main/java/shop/alien/lawyer/service/impl/RefundRecordServiceImpl.java

@@ -0,0 +1,22 @@
+package shop.alien.lawyer.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import org.springframework.stereotype.Service;
+import shop.alien.entity.store.RefundRecord;
+import shop.alien.lawyer.service.RefundRecordService;
+import shop.alien.mapper.RefundRecordMapper;
+
+
+/**
+ * <p>
+ * 退款记录表 服务实现类
+ * </p>
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Service
+public class RefundRecordServiceImpl extends ServiceImpl<RefundRecordMapper, RefundRecord> implements RefundRecordService {
+
+}
+

+ 200 - 2
alien-lawyer/src/main/java/shop/alien/lawyer/service/impl/ReviewCommentServiceImpl.java

@@ -12,13 +12,16 @@ import shop.alien.entity.result.R;
 import shop.alien.entity.store.LifeLikeRecord;
 import shop.alien.entity.store.OrderReview;
 import shop.alien.entity.store.ReviewComment;
+import shop.alien.entity.store.dto.LawyerReplyDto;
 import shop.alien.entity.store.dto.ReviewCommentRequestDto;
 import shop.alien.entity.store.dto.ReviewReplyDto;
 import shop.alien.entity.store.vo.ReviewCommentVo;
 import shop.alien.lawyer.service.OrderReviewService;
 import shop.alien.lawyer.service.ReviewCommentService;
+import shop.alien.mapper.LawyerUserMapper;
 import shop.alien.mapper.LifeLikeRecordMapper;
 import shop.alien.mapper.ReviewCommentMapper;
+import shop.alien.entity.store.LawyerUser;
 
 import java.util.Date;
 import java.util.List;
@@ -38,13 +41,34 @@ public class ReviewCommentServiceImpl extends ServiceImpl<ReviewCommentMapper, R
     private final ReviewCommentMapper reviewCommentMapper;
     private final OrderReviewService orderReviewService;
     private final LifeLikeRecordMapper lifeLikeRecordMapper;
+    private final LawyerUserMapper lawyerUserMapper;
 
     public ReviewCommentServiceImpl(ReviewCommentMapper reviewCommentMapper,
                                     @Lazy OrderReviewService orderReviewService,
-                                    LifeLikeRecordMapper lifeLikeRecordMapper) {
+                                    LifeLikeRecordMapper lifeLikeRecordMapper,
+                                    LawyerUserMapper lawyerUserMapper) {
         this.reviewCommentMapper = reviewCommentMapper;
         this.orderReviewService = orderReviewService;
         this.lifeLikeRecordMapper = lifeLikeRecordMapper;
+        this.lawyerUserMapper = lawyerUserMapper;
+    }
+
+    /**
+     * 判断用户类型:1-普通用户,2-律师
+     *
+     * @param userId 用户ID
+     * @return 用户类型:1-普通用户,2-律师
+     */
+    private Integer getUserType(Integer userId) {
+        if (userId == null) {
+            return 1; // 默认为普通用户
+        }
+        // 查询律师表,如果存在且未删除,则为律师
+        LawyerUser lawyerUser = lawyerUserMapper.selectById(userId);
+        if (lawyerUser != null && (lawyerUser.getDeleteFlag() == null || lawyerUser.getDeleteFlag() == 0)) {
+            return 2; // 律师
+        }
+        return 1; // 普通用户
     }
 
     @Override
@@ -71,6 +95,21 @@ public class ReviewCommentServiceImpl extends ServiceImpl<ReviewCommentMapper, R
             return R.fail("评价不存在或已删除");
         }
 
+        // 验证用户类型参数:必须传入
+        if (comment.getSendUserType() == null) {
+            return R.fail("发送用户类型不能为空");
+        }
+        if (comment.getReceiveUserType() == null) {
+            return R.fail("接收用户类型不能为空");
+        }
+
+        // 权限校验:如果是律师(sendUserType=2),只能回复自己订单的评价
+        if (comment.getSendUserType() == 2) {
+            if (!review.getLawyerUserId().equals(comment.getSendUserId())) {
+                return R.fail("只能回复自己的订单评价");
+            }
+        }
+
         // 设置评论属性
         // 接收用户ID为评价的创建者(如果未设置)
         if (comment.getReceiveUserId() == null) {
@@ -252,11 +291,34 @@ public class ReviewCommentServiceImpl extends ServiceImpl<ReviewCommentMapper, R
             return R.fail("只能回复首评");
         }
 
+        // 校验用户类型参数:必须传入
+        if (replyDto.getSendUserType() == null) {
+            return R.fail("发送用户类型不能为空");
+        }
+        if (replyDto.getReceiveUserType() == null) {
+            return R.fail("接收用户类型不能为空");
+        }
+
+        // 权限校验:如果是律师(sendUserType=2),只能回复自己订单评价下的评论
+        if (replyDto.getSendUserType() == 2) {
+            OrderReview review = orderReviewService.getById(headComment.getReviewId());
+            if (review == null || review.getDeleteFlag() == 1) {
+                return R.fail("评价不存在或已删除");
+            }
+            if (!review.getLawyerUserId().equals(userId)) {
+                return R.fail("只能回复自己订单评价下的评论");
+            }
+        }
+
         // 创建回复
         ReviewComment reply = new ReviewComment();
         reply.setReviewId(headComment.getReviewId());
         reply.setSendUserId(userId);
-        reply.setReceiveUserId(replyDto.getReplyToUserId() != null ? replyDto.getReplyToUserId() : headComment.getSendUserId());
+        Integer receiveUserId = replyDto.getReplyToUserId() != null ? replyDto.getReplyToUserId() : headComment.getSendUserId();
+        reply.setReceiveUserId(receiveUserId);
+        // 设置用户类型:Controller层已经校验过,直接使用
+        reply.setSendUserType(replyDto.getSendUserType());
+        reply.setReceiveUserType(replyDto.getReceiveUserType());
         reply.setCommentContent(replyDto.getReplyContent());
         reply.setLikeCount(0);
         reply.setReplyCount(0);
@@ -430,5 +492,141 @@ public class ReviewCommentServiceImpl extends ServiceImpl<ReviewCommentMapper, R
         log.warn("删除评论失败,评论ID={}, userId={}", id, userId);
         return R.fail("删除失败");
     }
+
+    /**
+     * @deprecated 已废弃,请使用 createComment 或 createReply 接口
+     */
+    @Deprecated
+    @Override
+    public R<ReviewComment> lawyerReply(LawyerReplyDto replyDto) {
+        log.warn("【废弃方法】ReviewCommentServiceImpl.lawyerReply 已废弃,请使用 createComment 或 createReply 方法");
+        log.info("ReviewCommentServiceImpl.lawyerReply?replyDto={}", replyDto);
+
+        // 参数校验
+        if (replyDto == null) {
+            return R.fail("回复信息不能为空");
+        }
+        if (replyDto.getReplyType() == null) {
+            return R.fail("回复类型不能为空");
+        }
+        if (replyDto.getLawyerUserId() == null) {
+            return R.fail("律师用户ID不能为空");
+        }
+        if (replyDto.getReplyContent() == null || replyDto.getReplyContent().trim().isEmpty()) {
+            return R.fail("回复内容不能为空");
+        }
+
+        ReviewComment reply = new ReviewComment();
+        Integer receiveUserId = null;
+        Integer reviewId = null;
+
+        // 根据回复类型处理
+        if (replyDto.getReplyType() == 1) {
+            // 回复评价
+            if (replyDto.getReviewId() == null) {
+                return R.fail("评价ID不能为空");
+            }
+
+            // 验证评价是否存在
+            OrderReview review = orderReviewService.getById(replyDto.getReviewId());
+            if (review == null || review.getDeleteFlag() == 1) {
+                return R.fail("评价不存在或已删除");
+            }
+
+            // 验证律师权限:必须是该评价对应的律师
+            if (!review.getLawyerUserId().equals(replyDto.getLawyerUserId())) {
+                return R.fail("只能回复自己的订单评价");
+            }
+
+            reviewId = review.getId();
+            receiveUserId = replyDto.getReplyToUserId() != null ? replyDto.getReplyToUserId() : review.getUserId();
+            reply.setReviewId(reviewId);
+            reply.setHeadType(0); // 0:是首评(对评价的直接回复)
+            reply.setHeadId(null);
+
+        } else if (replyDto.getReplyType() == 2) {
+            // 回复评论
+            if (replyDto.getCommentId() == null) {
+                return R.fail("评论ID不能为空");
+            }
+
+            // 验证评论是否存在
+            ReviewComment comment = this.getById(replyDto.getCommentId());
+            if (comment == null || comment.getDeleteFlag() == 1) {
+                return R.fail("评论不存在或已删除");
+            }
+
+            // 获取评价信息,验证律师权限
+            OrderReview review = orderReviewService.getById(comment.getReviewId());
+            if (review == null || review.getDeleteFlag() == 1) {
+                return R.fail("评价不存在或已删除");
+            }
+
+            // 验证律师权限:必须是该评价对应的律师
+            if (!review.getLawyerUserId().equals(replyDto.getLawyerUserId())) {
+                return R.fail("只能回复自己订单评价下的评论");
+            }
+
+            // 如果评论是首评,则回复首评;如果评论是回复,则需要回复首评
+            Integer headCommentId = comment.getHeadType() == 0 ? comment.getId() : comment.getHeadId();
+            ReviewComment headComment = this.getById(headCommentId);
+            if (headComment == null || headComment.getDeleteFlag() == 1) {
+                return R.fail("首评不存在或已删除");
+            }
+
+            reviewId = comment.getReviewId();
+            receiveUserId = replyDto.getReplyToUserId() != null ? replyDto.getReplyToUserId() : comment.getSendUserId();
+            reply.setReviewId(reviewId);
+            reply.setHeadType(1); // 1:是回复
+            reply.setHeadId(headCommentId); // 指向首评ID
+
+        } else {
+            return R.fail("回复类型错误,只能是1(回复评价)或2(回复评论)");
+        }
+
+        // 创建回复
+        reply.setSendUserId(replyDto.getLawyerUserId());
+        reply.setReceiveUserId(receiveUserId);
+        // 验证用户类型参数:必须传入
+        if (replyDto.getSendUserType() == null) {
+            return R.fail("发送用户类型不能为空");
+        }
+        if (replyDto.getReceiveUserType() == null) {
+            return R.fail("接收用户类型不能为空");
+        }
+        reply.setSendUserType(replyDto.getSendUserType());
+        reply.setReceiveUserType(replyDto.getReceiveUserType());
+        reply.setCommentContent(replyDto.getReplyContent());
+        reply.setLikeCount(0);
+        reply.setReplyCount(0);
+        reply.setCreatedUserId(replyDto.getLawyerUserId());
+        reply.setCreatedTime(new Date());
+
+        boolean success = this.save(reply);
+        if (success) {
+            // 如果是回复评论,更新首评的回复数
+            if (replyDto.getReplyType() == 2 && reply.getHeadId() != null) {
+                ReviewComment headComment = this.getById(reply.getHeadId());
+                if (headComment != null) {
+                    headComment.setReplyCount((headComment.getReplyCount() == null ? 0 : headComment.getReplyCount()) + 1);
+                    this.updateById(headComment);
+                }
+            }
+
+            // 更新评价的评论数
+            OrderReview review = orderReviewService.getById(reviewId);
+            if (review != null) {
+                review.setCommentCount((review.getCommentCount() == null ? 0 : review.getCommentCount()) + 1);
+                orderReviewService.updateById(review);
+                log.info("更新评价评论数成功,评价ID={}, 评论数={}", reviewId, review.getCommentCount());
+            }
+
+            log.info("律师回复成功,回复ID={}, 律师ID={}", reply.getId(), replyDto.getLawyerUserId());
+            return R.data(reply, "回复成功");
+        } else {
+            log.error("律师回复失败");
+            return R.fail("回复失败");
+        }
+    }
 }
 

+ 2007 - 0
alien-lawyer/src/main/java/shop/alien/lawyer/service/impl/StoreCommentServiceImpl.java

@@ -0,0 +1,2007 @@
+package shop.alien.lawyer.service.impl;
+
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson2.JSONObject;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
+import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
+import com.baomidou.mybatisplus.core.toolkit.StringUtils;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.google.common.collect.Lists;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.web.multipart.MultipartFile;
+import org.springframework.web.multipart.MultipartRequest;
+import shop.alien.entity.result.R;
+import shop.alien.entity.store.*;
+import shop.alien.entity.store.dto.OrderReviewDto;
+import shop.alien.entity.store.dto.ReviewCommentRequestDto;
+import shop.alien.entity.store.dto.ReviewReplyDto;
+import shop.alien.entity.store.vo.*;
+import shop.alien.lawyer.config.WebSocketProcess;
+import shop.alien.lawyer.service.StoreCommentService;
+import shop.alien.lawyer.service.StoreImgService;
+import shop.alien.lawyer.util.FileUploadUtil;
+import shop.alien.mapper.*;
+import shop.alien.util.common.DateUtils;
+import shop.alien.util.common.netease.ImageCheckUtil;
+import shop.alien.util.common.netease.TextCheckUtil;
+import shop.alien.util.common.safe.TextModerationResultVO;
+import shop.alien.util.common.safe.TextModerationUtil;
+import shop.alien.util.common.safe.TextReviewServiceEnum;
+
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.text.SimpleDateFormat;
+import java.util.*;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+/**
+ * 评论表 服务实现类
+ *
+ * @author ssk
+ * @since 2025-01-02
+ */
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class StoreCommentServiceImpl extends ServiceImpl<StoreCommentMapper, StoreComment> implements StoreCommentService {
+
+    private final StoreCommentMapper storeCommentMapper;
+
+    private final LifeLikeRecordMapper lifeLikeRecordMapper;
+
+    private final FileUploadUtil fileUploadUtil;
+
+    private final StoreImgMapper storeImgMapper;
+
+    private final LifeUserViolationMapper lifeUserViolationMapper;
+
+    private final StoreUserMapper storeUserMapper;
+
+    private final LifeUserMapper lifeUserMapper;
+
+    private final StoreCommentAppealMapper storeCommentAppealMapper;
+
+    private final StoreInfoMapper storeInfoMapper;
+
+    private final LifeNoticeMapper lifeNoticeMapper;
+
+    private final WebSocketProcess webSocketProcess;
+
+    private final TagsSynonymMapper tagsSynonymMapper;
+
+    private final LifeUserOrderMapper lifeUserOrderMapper;
+
+    private final LawyerConsultationOrderMapper lawyerConsultationOrderMapper;
+    private final LawyerUserMapper lawyerUserMapper;
+    private final LawFirmMapper lawFirmMapper;
+
+    @Autowired
+    private TextModerationUtil textModerationUtil;
+    @Autowired
+    private StoreImgService storeImgService;
+
+    /**
+     * 评论列表
+     *
+     * @param pageNum      页数
+     * @param pageSize     页容
+     * @param businessId   业务id
+     * @param businessType 业务类型(1:订单评论, 2:动态社区评论, 3:活动评论, 4:店铺打卡评论, 5:订单评价, 6:订单评论的评论)
+     * @param storeId      门店id
+     * @param replyStatus  回复状态(0:全部, 1:已回复, 2:未回复)
+     * @param commentLevel 评论等级(0:全部, 1:好评, 2:中评, 3:差评)
+     * @param days         查询时间, 多少天前
+     * @param phoneId      消息标识
+     * @param userType     评论的用户类型(0:商家, 其他:用户)
+     * @param tagId        标签id
+     * @param hasImage     是否有图片(0否/1是)
+     * @return IPage<StoreComment>
+     */
+    @Override
+    public IPage<StoreCommentVo> getList(Integer pageNum, Integer pageSize, Integer businessId, Integer businessType, Integer storeId, Integer replyStatus, Integer commentLevel, Integer days, String phoneId, Integer userType, Integer tagId, Boolean hasImage) {
+        IPage<StoreCommentVo> storeCommentIPage = new Page<>(pageNum, pageSize);
+        QueryWrapper<StoreCommentVo> queryWrapper = new QueryWrapper<>();
+        queryWrapper.eq(null != businessId, "a.business_id", businessId);
+        queryWrapper.eq(null != businessType, "a.business_type", businessType);
+        queryWrapper.eq(null != storeId, "a.store_id", storeId);
+        //好评 a>=4
+        queryWrapper.ge(null != commentLevel && 1 == commentLevel, "a.score", 4.5);
+        //中评 a>=2&& a<4
+        queryWrapper.ge(null != commentLevel && 2 == commentLevel, "a.score", 3).le(null != commentLevel && 2 == commentLevel, "a.score", 4);
+        //差评 a<2
+        queryWrapper.ge(null != commentLevel && 3 == commentLevel, "a.score", 0.5).le(null != commentLevel && 3 == commentLevel, "a.score", 2.5);
+        if (null != days) {
+            Date date = DateUtils.calcDays(new Date(), -days);
+            queryWrapper.ge("a.created_time", DateUtils.formatDate(date, "yyyy-MM-dd"));
+        }
+        //通过当前登录人id 类型 查询举报业务id
+        List<LifeUserViolation> lifeUserViolations = new ArrayList<>();
+        if(businessType==2){
+            String userId;
+            if(phoneId.startsWith("store_")){
+                userId = phoneId.substring("store_".length());
+                StoreUser storeUser = storeUserMapper.selectOne(new LambdaQueryWrapper<StoreUser>().eq(StoreUser::getPhone,userId));
+                if(storeUser!=null){
+                    LambdaQueryWrapper<LifeUserViolation> lambdaQueryWrapper = new LambdaQueryWrapper<>();
+                    lambdaQueryWrapper.eq(LifeUserViolation::getReportingUserId,storeUser.getId());
+                    lambdaQueryWrapper.eq(LifeUserViolation::getReportingUserType,1);
+                    lambdaQueryWrapper.ne(LifeUserViolation::getProcessingStatus,2);
+                    lifeUserViolations = lifeUserViolationMapper.selectList(lambdaQueryWrapper);
+                }
+            }else{
+                userId = phoneId.substring("user_".length());
+                LifeUser lifeUser = lifeUserMapper.selectOne(new LambdaQueryWrapper<LifeUser>().eq(LifeUser::getUserPhone,userId));
+                if(lifeUser!=null){
+                    LambdaQueryWrapper<LifeUserViolation> lambdaQueryWrapper = new LambdaQueryWrapper<>();
+                    lambdaQueryWrapper.eq(LifeUserViolation::getReportingUserId,lifeUser.getId());
+                    lambdaQueryWrapper.eq(LifeUserViolation::getReportingUserType,2);
+                    lambdaQueryWrapper.ne(LifeUserViolation::getProcessingStatus,2);
+                    lifeUserViolations = lifeUserViolationMapper.selectList(lambdaQueryWrapper);
+                }
+            }
+        }
+        List<Integer> businessIds = lifeUserViolations.stream()
+                .filter(Objects::nonNull)
+                .map(LifeUserViolation::getBusinessId)
+                .filter(id -> id != null && !id.toString().isEmpty())
+                .collect(Collectors.toList());
+        if(businessIds!=null && businessIds.size()>0){
+            queryWrapper.notIn("a.id",businessIds);
+        }
+
+        //根据标签id查询表签字表拿到评论ids
+        if(tagId !=null){
+            List<TagsSynonym> tagsSynonymList = tagsSynonymMapper.selectList(new LambdaQueryWrapper<TagsSynonym>().eq(TagsSynonym::getMainTagId,tagId));
+            if(CollectionUtils.isNotEmpty(tagsSynonymList)){
+                List<Integer> commentIdList = tagsSynonymList.stream().filter(synonym -> synonym != null)
+                        .map(TagsSynonym::getCommentId).filter(commentId -> commentId != null).collect(Collectors.toList());
+                queryWrapper.in("a.id",commentIdList);
+            }
+        }
+
+        //先查询父级评论
+        queryWrapper.isNull(businessType != 1, "a.reply_id").eq("a.delete_flag", 0).orderByDesc("a.created_time");
+        IPage<StoreCommentVo> page = storeCommentMapper.getCommentPage(storeCommentIPage, queryWrapper);
+        List<StoreCommentVo> records = page.getRecords();
+        List<StoreCommentVo> storeCommentVoList = new ArrayList<>();
+
+        //评价图片
+        Set<Integer> imgIdList = new HashSet<>();
+        for (int i = 0; i < records.size(); i++) {
+            StoreCommentVo storeCommentVo = new StoreCommentVo();
+            BeanUtils.copyProperties(records.get(i), storeCommentVo);
+            //首评
+            storeCommentVo.setFirstComment(i == 0);
+            //有无图片
+            if (records.get(i).getImgId() != null) {
+                storeCommentVo.setHasImage(true);
+                String[] split = records.get(i).getImgId().split(",");
+                for (String s : split) {
+                    imgIdList.add(Integer.parseInt(s));
+                }
+            } else {
+                storeCommentVo.setHasImage(false);
+            }
+            //低分
+            storeCommentVo.setLowScore(records.get(i).getScore() <= 1);
+
+            QueryWrapper<StoreCommentAppealVo> storeCommentAppealVoQueryWrapper = new QueryWrapper<>();
+            storeCommentAppealVoQueryWrapper.eq("a.comment_id", storeCommentVo.getId()).eq("a.delete_flag", 0).orderByDesc("a.created_time").last("limit 1");
+            StoreCommentAppealVo commentDetail = storeCommentAppealMapper.getCommentDetail(storeCommentAppealVoQueryWrapper);
+            if (ObjectUtils.isNotEmpty(commentDetail)) {
+                storeCommentVo.setAppealFlag(1);
+                storeCommentVo.setAppealStatusStr(commentDetail.getAppealStatusStr());
+                storeCommentVo.setAppealStatus(commentDetail.getAppealStatus());
+            }else {
+                storeCommentVo.setAppealFlag(0);
+            }
+
+            //商家用户
+            if (StringUtils.isNotEmpty(records.get(i).getPhoneId())) {
+                if (records.get(i).getPhoneId().contains("store_")) {
+                    storeCommentVo.setStoreUserFlag(0);
+                    StoreUser storeUser = storeUserMapper.selectOne(new LambdaQueryWrapper<StoreUser>().eq(StoreUser::getStoreId, records.get(i).getStoreId()));
+                    storeCommentVo.setStoreUserName(storeUser.getName());
+                    storeCommentVo.setStoreUserImg(storeUser.getHeadImg());
+                } else {
+                    storeCommentVo.setStoreUserFlag(1);
+                }
+            }
+            StoreInfo storeInfo = storeInfoMapper.selectById(records.get(i).getStoreId());
+            if (storeInfo != null) {
+                storeCommentVo.setStoreName(storeInfo.getStoreName());
+            }
+            storeCommentVoList.add(storeCommentVo);
+        }
+
+        //塞图片
+        if (!imgIdList.isEmpty()) {
+            List<StoreImg> storeImgList = storeImgMapper.selectList(new QueryWrapper<StoreImg>().in("id", imgIdList));
+
+            storeCommentVoList.forEach(storeCommentVo -> {
+                if (null != storeCommentVo.getImgId()) {
+                    storeCommentVo.setImgUrl(new ArrayList<>());
+                    for (StoreImg storeImg : storeImgList) {
+                        for (String s : storeCommentVo.getImgId().split(",")) {
+                            if (storeImg.getId().equals(Integer.parseInt(s))) {
+                                storeCommentVo.getImgUrl().add(storeImg.getImgUrl());
+                            }
+                        }
+                    }
+                }
+            });
+        }
+
+        //父级评论下拼接子评论
+        for (StoreCommentVo storeCommentVo : storeCommentVoList) {
+            storeCommentVo.setCommitCount(0);
+            //父级点赞状态
+            if (StringUtils.isNotEmpty(phoneId)) {
+                LambdaQueryWrapper<LifeLikeRecord> likeRecordQueryWrapper = new LambdaQueryWrapper<>();
+                likeRecordQueryWrapper.eq(LifeLikeRecord::getDianzanId, phoneId).eq(LifeLikeRecord::getHuifuId, storeCommentVo.getId());
+                Integer i = lifeLikeRecordMapper.selectCount(likeRecordQueryWrapper);
+                if (i > 0) {
+                    storeCommentVo.setIsLike(1);
+                } else {
+                    storeCommentVo.setIsLike(0);
+                }
+            }
+            QueryWrapper<StoreCommentVo> childQueryWrapper = new QueryWrapper<>();
+            if(businessIds!=null && businessIds.size()>0){
+                childQueryWrapper.notIn("a.id",businessIds);
+            }
+            childQueryWrapper.eq(null != businessId && 6 == businessId, "a.business_id", businessId).eq("a.delete_flag", 0).eq("a.reply_id", storeCommentVo.getId()).orderByDesc("a.created_time");
+            List<StoreCommentVo> childCommentList = storeCommentMapper.getCommentList(childQueryWrapper);
+            storeCommentVo.setCommitCount(storeCommentVo.getCommitCount() + childCommentList.size());
+            //评价的评论的评论
+            childCommentList.forEach(child -> {
+                QueryWrapper<StoreCommentVo> childQueryCommentWrapper = new QueryWrapper<>();
+                childQueryCommentWrapper
+//                        .eq(null != businessId && 6 == businessId, "a.business_id", businessId)
+                        .eq("a.delete_flag", 0).eq("a.reply_id", child.getId()).orderByDesc("a.created_time");
+                List<StoreCommentVo> childComment2List = storeCommentMapper.getCommentList(childQueryCommentWrapper);
+
+                //商家用户
+                for (StoreCommentVo commentVo : childComment2List) {
+                    if (StringUtils.isNotEmpty(commentVo.getPhoneId())) {
+                        if (commentVo.getPhoneId().contains("store_")) {
+                            commentVo.setStoreUserFlag(0);
+                            StoreUser storeUser = storeUserMapper.selectOne(new LambdaQueryWrapper<StoreUser>().eq(StoreUser::getStoreId, child.getStoreId()));
+                            commentVo.setStoreUserName(storeUser.getName());
+                            commentVo.setStoreUserImg(storeUser.getHeadImg());
+                        } else {
+                            commentVo.setStoreUserFlag(1);
+                        }
+                    }
+                }
+
+                child.setStoreComment(childComment2List);
+                storeCommentVo.setCommitCount(storeCommentVo.getCommitCount() + childComment2List.size());
+
+                //商家用户
+                if (StringUtils.isNotEmpty(child.getPhoneId())) {
+                    if (child.getPhoneId().contains("store_")) {
+                        child.setStoreUserFlag(0);
+                        StoreUser storeUser = storeUserMapper.selectOne(new LambdaQueryWrapper<StoreUser>().eq(StoreUser::getStoreId, child.getStoreId()));
+                        child.setStoreUserName(storeUser.getName());
+                        child.setStoreUserImg(storeUser.getHeadImg());
+                    } else {
+                        child.setStoreUserFlag(1);
+                    }
+                }
+
+            });
+            //筛选商家评论
+            List<StoreCommentVo> storeCommentList = new ArrayList<>();
+            if (null != userType && userType == 0) {
+                List<StoreCommentVo> collect = childCommentList.stream().filter(childComment -> childComment.getPhoneId().contains("store_")).collect(Collectors.toList());
+                if (!collect.isEmpty()) {
+                    storeCommentList.add(collect.get(0));
+                    storeCommentVo.setStoreComment(storeCommentList);
+                }
+            } else {
+                storeCommentVo.setStoreComment(childCommentList);
+            }
+            //子级点赞状态
+            if (StringUtils.isNotEmpty(phoneId)) {
+                childCommentList.forEach(child -> {
+                    LambdaQueryWrapper<LifeLikeRecord> childlikeRecordQueryWrapper = new LambdaQueryWrapper<>();
+                    childlikeRecordQueryWrapper.eq(LifeLikeRecord::getDianzanId, phoneId).eq(LifeLikeRecord::getHuifuId, child.getId());
+                    if (lifeLikeRecordMapper.selectCount(childlikeRecordQueryWrapper) > 0) {
+                        child.setIsLike(1);
+                    } else {
+                        child.setIsLike(0);
+                    }
+                });
+            }
+        }
+        IPage<StoreCommentVo> resultPage = new Page<>();
+        resultPage.setCurrent(page.getCurrent());
+        resultPage.setSize(page.getSize());
+        resultPage.setTotal(page.getTotal());
+        resultPage.setPages(page.getPages());
+        //筛选有无评论
+        List<StoreCommentVo> collect = new ArrayList<>();
+        if (replyStatus != null) {
+            if (1 == replyStatus) {
+                //只查询有回复的评论
+                collect = storeCommentVoList.stream().filter(storeCommentVo -> ObjectUtils.isNotEmpty(storeCommentVo.getStoreComment())).collect(Collectors.toList());
+            } else if (2 == replyStatus) {
+                //只查询无回复的评论
+                collect = storeCommentVoList.stream().filter(storeCommentVo -> ObjectUtils.isEmpty(storeCommentVo.getStoreComment())).collect(Collectors.toList());
+            } else {
+                collect = storeCommentVoList;
+            }
+        }
+//        collect.forEach(item -> {
+//            item.setCommitCount(null != item.getStoreComment() ? item.getStoreComment().size() : 0);
+//        });
+        resultPage.setRecords(collect);
+        return resultPage;
+    }
+
+    /**
+     * 获取最新一条评论/评价
+     *
+     * @param businessId   业务id
+     * @param businessType 业务类型(1:订单评论, 2:动态社区评论, 3:活动评论,4:店铺打卡评论)
+     * @param storeId      门店id
+     * @return StoreCommentVo
+     */
+    @Override
+    public StoreCommentVo getOne(Integer businessId, Integer businessType, Integer storeId) {
+        AtomicReference<Integer> count = new AtomicReference<>(0);
+        QueryWrapper<StoreCommentVo> queryWrapper = new QueryWrapper<>();
+        queryWrapper.eq(null != businessId, "a.business_id", businessId).eq(null != businessType, "a.business_type", businessType).eq(null != storeId, "a.store_id", storeId).eq("a.delete_flag", 0).orderByDesc("a.created_time");
+        List<StoreCommentVo> commentList = storeCommentMapper.getCommentList(queryWrapper);
+        count.updateAndGet(v -> v + commentList.size());
+        //父级评论下拼接子评论
+        for (StoreCommentVo storeCommentVo : commentList) {
+            QueryWrapper<StoreCommentVo> childQueryWrapper = new QueryWrapper<>();
+            childQueryWrapper.eq("a.delete_flag", 0).eq("a.reply_id", storeCommentVo.getId());
+            List<StoreCommentVo> childCommentList = storeCommentMapper.getCommentList(childQueryWrapper);
+            count.updateAndGet(v -> v + childCommentList.size());
+            //评价的评论的评论
+            childCommentList.forEach(child -> {
+                QueryWrapper<StoreCommentVo> childQueryCommentWrapper = new QueryWrapper<>();
+                childQueryCommentWrapper
+//                        .eq(null != businessId && 6 == businessId, "a.business_id", businessId)
+                        .eq("a.delete_flag", 0).eq("a.reply_id", child.getId()).orderByDesc("a.created_time");
+                List<StoreCommentVo> childComment2List = storeCommentMapper.getCommentList(childQueryCommentWrapper);
+                count.updateAndGet(v -> v + childComment2List.size());
+            });
+        }
+        if (!commentList.isEmpty()) {
+            StoreCommentVo storeCommentVo = commentList.get(0);
+            storeCommentVo.setCommitCount(Integer.parseInt(String.valueOf(count)));
+            return storeCommentVo;
+        }
+        return null;
+    }
+
+    /**
+     * 评论/评价数量和评分
+     *
+     * @param businessId   业务id
+     * @param businessType 业务类型(1:订单评论, 2:动态社区评论, 3:活动评论,4:店铺打卡评论)
+     * @param storeId      门店id
+     * @return Integer
+     */
+    @Override
+    public Map<String, Object> getCommitCountAndScore(Integer businessId, Integer businessType, Integer storeId, String phoneId, Integer days) {
+        Map<String, Object> map = new HashMap<>();
+        AtomicReference<Integer> count = new AtomicReference<>(0);
+        QueryWrapper<StoreCommentVo> queryWrapper = new QueryWrapper<>();
+        queryWrapper.eq(null != businessId, "a.business_id", businessId);
+        queryWrapper.eq(null != businessType, "a.business_type", businessType);
+        queryWrapper.eq(null != storeId, "a.store_id", storeId);
+
+        if (null != days) {
+            Date date = DateUtils.calcDays(new Date(), -days);
+            queryWrapper.ge("a.created_time", DateUtils.formatDate(date, "yyyy-MM-dd"));
+        }
+
+        //通过当前登录人id 类型 查询举报业务id
+        List<LifeUserViolation> lifeUserViolations = new ArrayList<>();
+        if(businessType==2){
+            String userId;
+            if(phoneId!=null && !phoneId.equals("") && phoneId.startsWith("store_")){
+                userId = phoneId.substring("store_".length());
+                StoreUser storeUser = storeUserMapper.selectOne(new LambdaQueryWrapper<StoreUser>().eq(StoreUser::getPhone,userId));
+                if(storeUser!=null){
+                    LambdaQueryWrapper<LifeUserViolation> lambdaQueryWrapper = new LambdaQueryWrapper<>();
+                    lambdaQueryWrapper.eq(LifeUserViolation::getReportingUserId,storeUser.getId());
+                    lambdaQueryWrapper.eq(LifeUserViolation::getReportingUserType,1);
+                    lambdaQueryWrapper.ne(LifeUserViolation::getProcessingStatus,2);
+                    lifeUserViolations = lifeUserViolationMapper.selectList(lambdaQueryWrapper);
+                }
+            }else if(phoneId!=null && !phoneId.equals("") && phoneId.startsWith("user_")){
+                userId = phoneId.substring("user_".length());
+                LifeUser lifeUser = lifeUserMapper.selectOne(new LambdaQueryWrapper<LifeUser>().eq(LifeUser::getUserPhone,userId));
+                if(lifeUser!=null){
+                    LambdaQueryWrapper<LifeUserViolation> lambdaQueryWrapper = new LambdaQueryWrapper<>();
+                    lambdaQueryWrapper.eq(LifeUserViolation::getReportingUserId,lifeUser.getId());
+                    lambdaQueryWrapper.eq(LifeUserViolation::getReportingUserType,2);
+                    lambdaQueryWrapper.ne(LifeUserViolation::getProcessingStatus,2);
+                    lifeUserViolations = lifeUserViolationMapper.selectList(lambdaQueryWrapper);
+                }
+            }
+        }
+
+        List<Integer> businessIds = lifeUserViolations.stream()
+                .filter(Objects::nonNull)
+                .map(LifeUserViolation::getBusinessId)
+                .filter(id -> id != null && !id.toString().isEmpty())
+                .collect(Collectors.toList());
+        if(businessIds!=null && businessIds.size()>0){
+            queryWrapper.notIn("a.id",businessIds);
+        }
+
+        //先查询父级评论
+        queryWrapper.isNull(businessType != 1, "a.reply_id").eq("a.delete_flag", 0).orderByDesc("a.created_time");
+        List<StoreCommentVo> storeCommentVoList = storeCommentMapper.getCommentList(queryWrapper);
+        count.updateAndGet(v -> v + storeCommentVoList.size());
+        //评分合计
+        Double sumScore = 0.0;
+        //父级评论下拼接子评论
+        for (StoreCommentVo storeCommentVo : storeCommentVoList) {
+            sumScore += storeCommentVo.getScore();
+            QueryWrapper<StoreCommentVo> childQueryWrapper = new QueryWrapper<>();
+            childQueryWrapper.eq("a.delete_flag", 0).eq("a.reply_id", storeCommentVo.getId());
+            List<StoreCommentVo> childCommentList = storeCommentMapper.getCommentList(childQueryWrapper);
+            count.updateAndGet(v -> v + childCommentList.size());
+            //评价的评论的评论
+            childCommentList.forEach(child -> {
+                QueryWrapper<StoreCommentVo> childQueryCommentWrapper = new QueryWrapper<>();
+                childQueryCommentWrapper
+//                        .eq(null != businessId && 6 == businessId, "a.business_id", businessId)
+                        .eq("a.delete_flag", 0).eq("a.reply_id", child.getId()).orderByDesc("a.created_time");
+                List<StoreCommentVo> childComment2List = storeCommentMapper.getCommentList(childQueryCommentWrapper);
+                count.updateAndGet(v -> v + childComment2List.size());
+            });
+        }
+        map.put("commitCount", count.toString());
+        map.put("img", storeCommentVoList.stream().filter(i -> i.getScore() >= 4.5).map(StoreCommentVo::getUserImage).distinct().limit(6).collect(Collectors.toList()));
+        if (sumScore == 0 || storeCommentVoList.isEmpty()) {
+            map.put("score", "0");
+        } else {
+            BigDecimal score = new BigDecimal(sumScore).divide(new BigDecimal(storeCommentVoList.size()), 2, BigDecimal.ROUND_HALF_UP);
+            map.put("score", score.toString());
+        }
+
+//        Map<String, Object> commentCountAndScore = storeCommentMapper.getCommentCountAndScore(businessId, businessType, storeId);
+//        Object scoreObj = commentCountAndScore.get("score");
+//        double scoreDouble = 0.0;
+//        if (scoreObj != null) {
+//            if (scoreObj instanceof Number) {
+//                scoreDouble = ((Number) scoreObj).doubleValue();
+//            } else if (scoreObj instanceof String) {
+//                scoreDouble = Double.parseDouble((String) scoreObj);
+//            }
+//        }
+//        if (scoreDouble == 0) {
+//            map.put("score", "0");
+//        } else {
+//            BigDecimal scoreBigDecimal = new BigDecimal(String.valueOf(scoreDouble)).divide(new BigDecimal(commentCountAndScore.get("rootCount").toString()), 2, BigDecimal.ROUND_HALF_UP);
+//            map.put("score", scoreBigDecimal.toString());
+//        }
+//        map.put("commitCount", commentCountAndScore.get("count").toString());
+        return map;
+    }
+
+    /**
+     * 评论
+     *
+     * @param multipartRequest 文件
+     * @param businessId       业务id
+     * @param businessType     业务类型(1:订单评论, 2:动态社区评论, 3:活动评论,4:店铺打卡评论)
+     * @param storeId          门店id
+     * @param userId           用户id
+     * @param replyId          回复id
+     * @param commentContent   评价内容
+     * @return 0:成功, 1:失败, 2:文本内容异常, 3:图片内容异常
+     */
+    @Override
+    public Integer userComment(MultipartRequest multipartRequest, Integer businessId, Integer businessType, Integer storeId, Integer userId, Integer replyId, String commentContent) {
+        try {
+            Map<String, String> checkText = TextCheckUtil.check(commentContent);
+            if (null == checkText || checkText.get("result").equals("1")) {
+                return 2;
+            }
+            StoreComment storeComment = new StoreComment();
+            storeComment.setStoreId(storeId);
+            storeComment.setUserId(userId);
+            storeComment.setCommentContent(commentContent);
+            storeComment.setReplyId(replyId);
+            storeComment.setBusinessId(businessId);
+            storeComment.setBusinessType(businessType);
+            List<String> fileNameSet = new ArrayList<>(multipartRequest.getMultiFileMap().keySet());
+            if (!fileNameSet.isEmpty()) {
+                StringBuilder imgId = new StringBuilder();
+                for (int i = 0; i < fileNameSet.size(); i++) {
+                    MultipartFile multipartFile = multipartRequest.getFileMap().get(fileNameSet.get(i));
+                    if (null != multipartFile) {
+                        byte[] fileByte;
+                        try {
+                            fileByte = multipartFile.getBytes();
+                        } catch (IOException e) {
+                            return 1;
+                        }
+                        String base64 = Base64.getEncoder().encodeToString(fileByte);
+                        Map<String, String> checkImage = ImageCheckUtil.check(base64, 2);
+                        if (checkImage != null && checkImage.get("result").equals("1")) {
+                            return 3;
+                        }
+                        StoreImg storeImg = new StoreImg();
+                        storeImg.setStoreId(storeComment.getStoreId());
+                        storeImg.setImgType(8);
+                        storeImg.setImgSort(i + 1);
+                        storeImg.setImgUrl(fileUploadUtil.uploadOneFile(multipartFile));
+                        storeImgMapper.insert(storeImg);
+                        imgId.append(storeImg.getId()).append(",");
+                    }
+                }
+                if (!imgId.toString().isEmpty()) {
+                    storeComment.setImgId(imgId.substring(0, imgId.length() - 1));
+                }
+            }
+            storeComment.setCreatedUserId(storeComment.getUserId());
+            return this.save(storeComment) ? 0 : 1;
+        } catch (Exception e) {
+            log.error("StoreCommentService.userComment ERROR Msg={}", e.getMessage());
+            return 1;
+        }
+
+    }
+
+    /**
+     * 新增或修改评论/评价
+     *
+     * @param multipartRequest 文件
+     * @param id               主键
+     * @param businessId       业务id
+     * @param businessType     业务类型(1:订单评论, 2:动态社区评论, 3:活动评论,4:店铺打卡评论)
+     * @param storeId          门店id
+     * @param userId           用户id
+     * @param replyId          回复id
+     * @param commentContent   评价内容
+     * @param score            评分
+     * @param otherScore       其他评分
+     * @param isAnonymous      是否匿名(0:否(默认), 1:是)
+     * @param evaluationTags   评价标签
+     * @param phoneId          用户id
+     * @return 0:成功, 1:失败, 2:文本内容异常, 3:图片内容异常
+     */
+    @Override
+    public Integer addComment(MultipartRequest multipartRequest, Integer id, Integer businessId, Integer businessType, Integer storeId, Integer userId, Integer replyId, String commentContent, Double score, String otherScore, Integer isAnonymous, String evaluationTags, String phoneId) {
+        try {
+            List<String> servicesList = Lists.newArrayList();
+            servicesList.add(TextReviewServiceEnum.COMMENT_DETECTION_PRO.getService());
+            servicesList.add(TextReviewServiceEnum.LLM_QUERY_MODERATION.getService());
+            TextModerationResultVO textCheckResult = textModerationUtil.invokeFunction(commentContent, servicesList);
+            if ("high".equals(textCheckResult.getRiskLevel())) {
+                return 2;
+            }
+
+            /*Map<String, String> checkText = TextCheckUtil.check(commentContent);
+            if (null == checkText || checkText.get("result").equals("1")) {
+                return 2;
+            }*/
+            StoreComment storeComment = new StoreComment();
+            storeComment.setId(id);
+            storeComment.setStoreId(storeId);
+            storeComment.setUserId(userId);
+            storeComment.setCommentContent(commentContent);
+            storeComment.setReplyId(replyId);
+            storeComment.setBusinessId(businessId);
+            storeComment.setBusinessType(businessType);
+            storeComment.setScore(score);
+
+            if (StringUtils.isNotEmpty(otherScore)) {
+                List<LifeCouponVo> lifeCouponVos = JSONArray.parseArray(otherScore, LifeCouponVo.class);
+                lifeCouponVos.stream().filter(i -> "口味".equals(i.getName())).findFirst().ifPresent(item -> storeComment.setTasteScore(Double.valueOf(item.getRateScore())));
+                lifeCouponVos.stream().filter(i -> "环境".equals(i.getName())).findFirst().ifPresent(item -> storeComment.setEnScore(Double.valueOf(item.getRateScore())));
+                lifeCouponVos.stream().filter(i -> "服务".equals(i.getName())).findFirst().ifPresent(item -> storeComment.setServiceScore(Double.valueOf(item.getRateScore())));
+            }
+            storeComment.setOtherScore(otherScore);
+            storeComment.setIsAnonymous(isAnonymous);
+            storeComment.setEvaluationTags(evaluationTags);
+            storeComment.setPhoneId(phoneId);
+            List<String> fileNameSet = new ArrayList<>(multipartRequest.getMultiFileMap().keySet());
+            if (!fileNameSet.isEmpty() && storeId != null) {
+                StringBuilder imgId = new StringBuilder();
+                for (int i = 0; i < fileNameSet.size(); i++) {
+                    MultipartFile multipartFile = multipartRequest.getFileMap().get(fileNameSet.get(i));
+                   //b
+                    System.out.println(multipartFile.getSize());
+                    //kb
+                    System.out.println(multipartFile.getSize() / 1024);
+                    if (null != multipartFile && multipartFile.getSize() / 1024 > 0) {
+                        byte[] fileByte;
+                        try {
+                            fileByte = multipartFile.getBytes();
+                        } catch (IOException e) {
+                            return 1;
+                        }
+                        String base64 = Base64.getEncoder().encodeToString(fileByte);
+                        Map<String, String> checkImage = ImageCheckUtil.check(base64, 2);
+                        if (checkImage != null && checkImage.get("result").equals("1")) {
+                            return 3;
+                        }
+                        StoreImg storeImg = new StoreImg();
+                        storeImg.setStoreId(storeComment.getStoreId());
+                        storeImg.setImgType(8);
+                        storeImg.setImgSort(i + 1);
+                        storeImg.setImgUrl(fileUploadUtil.uploadOneFile(multipartFile));
+                        storeImgMapper.insert(storeImg);
+                        imgId.append(storeImg.getId()).append(",");
+                    }
+                }
+                if (!imgId.toString().isEmpty()) {
+                    storeComment.setImgId(imgId.substring(0, imgId.length() - 1));
+                }
+            }
+            storeComment.setCreatedUserId(storeComment.getUserId());
+            int i = this.save(storeComment) ? 0 : 1;
+
+            //判断类型如果为5是评价 更新订单表orderAppraise为1 订单已评价
+            if(businessType == 5){
+                LambdaUpdateWrapper<LifeUserOrder> lambdaUpdateWrapper = new LambdaUpdateWrapper<>();
+                lambdaUpdateWrapper.eq(LifeUserOrder :: getId, businessId );
+                lambdaUpdateWrapper.set(LifeUserOrder :: getOrderAppraise, 1);
+                lifeUserOrderMapper.update(null,lambdaUpdateWrapper);
+            }
+            StoreInfoScoreVo storeInfoScoreVo = storeCommentMapper.getCommentCountAndScoreInfo(storeId);
+            double total = storeInfoScoreVo.getTotal();
+            double scoreAvg = (total == 0 ? 0 : storeInfoScoreVo.getScore() / total);
+            double tasteScore = (total == 0 ? 0 : storeInfoScoreVo.getTasteScore() / total);
+            double enScore = (total == 0 ? 0 : storeInfoScoreVo.getEnScore() / total);
+            double serviceScore = (total == 0 ? 0 : storeInfoScoreVo.getServiceScore() / total);
+            StoreInfo storeInfo = new StoreInfo();
+            storeInfo.setId(storeId);
+            storeInfo.setScoreAvg(new BigDecimal(scoreAvg).setScale(2, RoundingMode.HALF_UP).doubleValue());
+            storeInfo.setTasteScore(new BigDecimal(tasteScore).setScale(2, RoundingMode.HALF_UP).doubleValue());
+            storeInfo.setEnScore(new BigDecimal(enScore).setScale(2, RoundingMode.HALF_UP).doubleValue());
+            storeInfo.setServiceScore(new BigDecimal(serviceScore).setScale(2, RoundingMode.HALF_UP).doubleValue());
+            storeInfoMapper.updateById(storeInfo);
+            StoreUser storeUser = storeUserMapper.selectOne(new LambdaQueryWrapper<StoreUser>().eq(StoreUser::getStoreId, storeInfo.getId()).eq(StoreUser::getDeleteFlag, 0));
+
+            // 如果差评,则发送差评提醒
+            if(score != null && score >= 0.5 && score <= 2.5){
+                SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+                String commonDate = simpleDateFormat.format(new Date());
+                LifeNotice lifeMessage = new LifeNotice();
+                lifeMessage.setReceiverId("store_" + storeUser.getPhone());
+                String text = "在"+commonDate+",您的店铺有一条差评记录,您可查看评价内容是否属实,如不属实,可向平台进行申诉。";
+                JSONObject jsonObject = new JSONObject();
+                jsonObject.put("message", text);
+                lifeMessage.setContext(jsonObject.toJSONString());
+                lifeMessage.setTitle("差评通知");
+                lifeMessage.setSenderId("system");
+                lifeMessage.setIsRead(0);
+                lifeMessage.setNoticeType(1);
+                lifeMessage.setBusinessType(1);
+                lifeNoticeMapper.insert(lifeMessage);
+
+                WebSocketVo websocketVo = new WebSocketVo();
+                websocketVo.setSenderId("system");
+                websocketVo.setReceiverId("store_" + storeUser.getPhone());
+                websocketVo.setCategory("notice");
+                websocketVo.setNoticeType("1");
+                websocketVo.setIsRead(0);
+                websocketVo.setText(JSONObject.from(lifeMessage).toJSONString());
+                webSocketProcess.sendMessage("store_" + storeUser.getPhone(), JSONObject.from(websocketVo).toJSONString());
+            }
+            return i;
+        } catch (Exception e) {
+            log.error("StoreCommentService.userComment ERROR Msg={}", e.getMessage());
+            return 1;
+        }
+
+    }
+
+    /**
+     * 新增或修改评论/评价
+     *
+     * @param multipartRequest 文件
+     * @param id               主键
+     * @param businessId       业务id
+     * @param businessType     业务类型(1:订单评论, 2:动态社区评论, 3:活动评论,4:店铺打卡评论)
+     * @param storeId          门店id
+     * @param userId           用户id
+     * @param replyId          回复id
+     * @param commentContent   评价内容
+     * @param score            评分
+     * @param otherScore       其他评分
+     * @param isAnonymous      是否匿名(0:否(默认), 1:是)
+     * @param evaluationTags   评价标签
+     * @param phoneId          用户id
+     * @return 0:成功, 1:失败, 2:文本内容异常, 3:图片内容异常
+     */
+    @Override
+    public Integer addCommentNew(MultipartRequest multipartRequest, Integer id, Integer businessId, Integer businessType, Integer storeId, Integer orderId, Integer lawyerId, Integer userId, Integer replyId, String commentContent, Double score, String otherScore, Integer isAnonymous, String evaluationTags, String phoneId) {
+        try {
+            List<String> servicesList = Lists.newArrayList();
+            servicesList.add(TextReviewServiceEnum.COMMENT_DETECTION_PRO.getService());
+            servicesList.add(TextReviewServiceEnum.LLM_QUERY_MODERATION.getService());
+            TextModerationResultVO textCheckResult = textModerationUtil.invokeFunction(commentContent, servicesList);
+            if ("high".equals(textCheckResult.getRiskLevel())) {
+                return 2;
+            }
+
+            /*Map<String, String> checkText = TextCheckUtil.check(commentContent);
+            if (null == checkText || checkText.get("result").equals("1")) {
+                return 2;
+            }*/
+            StoreComment storeComment = new StoreComment();
+            storeComment.setId(id);
+            storeComment.setStoreId(storeId);
+            storeComment.setUserId(userId);
+            storeComment.setCommentContent(commentContent);
+            storeComment.setReplyId(replyId);
+            storeComment.setBusinessId(businessId);
+            storeComment.setBusinessType(businessType);
+            storeComment.setScore(score);
+            storeComment.setLawyerId(lawyerId);
+            storeComment.setOrderId(orderId);
+
+            if (StringUtils.isNotEmpty(otherScore)) {
+                List<LifeCouponVo> lifeCouponVos = JSONArray.parseArray(otherScore, LifeCouponVo.class);
+                lifeCouponVos.stream().filter(i -> "口味".equals(i.getName())).findFirst().ifPresent(item -> storeComment.setTasteScore(Double.valueOf(item.getRateScore())));
+                lifeCouponVos.stream().filter(i -> "环境".equals(i.getName())).findFirst().ifPresent(item -> storeComment.setEnScore(Double.valueOf(item.getRateScore())));
+                lifeCouponVos.stream().filter(i -> "服务".equals(i.getName())).findFirst().ifPresent(item -> storeComment.setServiceScore(Double.valueOf(item.getRateScore())));
+            }
+            storeComment.setOtherScore(otherScore);
+            storeComment.setIsAnonymous(isAnonymous);
+            storeComment.setEvaluationTags(evaluationTags);
+            storeComment.setPhoneId(phoneId);
+            List<String> fileNameSet = new ArrayList<>(multipartRequest.getMultiFileMap().keySet());
+            if (!fileNameSet.isEmpty() && storeId != null) {
+                StringBuilder imgId = new StringBuilder();
+                for (int i = 0; i < fileNameSet.size(); i++) {
+                    MultipartFile multipartFile = multipartRequest.getFileMap().get(fileNameSet.get(i));
+                    //b
+                    System.out.println(multipartFile.getSize());
+                    //kb
+                    System.out.println(multipartFile.getSize() / 1024);
+                    if (null != multipartFile && multipartFile.getSize() / 1024 > 0) {
+                        byte[] fileByte;
+                        try {
+                            fileByte = multipartFile.getBytes();
+                        } catch (IOException e) {
+                            return 1;
+                        }
+                        String base64 = Base64.getEncoder().encodeToString(fileByte);
+                        Map<String, String> checkImage = ImageCheckUtil.check(base64, 2);
+                        if (checkImage != null && checkImage.get("result").equals("1")) {
+                            return 3;
+                        }
+                        StoreImg storeImg = new StoreImg();
+                        storeImg.setStoreId(storeComment.getStoreId());
+                        storeImg.setImgType(8);
+                        storeImg.setImgSort(i + 1);
+                        storeImg.setImgUrl(fileUploadUtil.uploadOneFile(multipartFile));
+                        storeImgMapper.insert(storeImg);
+                        imgId.append(storeImg.getId()).append(",");
+                    }
+                }
+                if (!imgId.toString().isEmpty()) {
+                    storeComment.setImgId(imgId.substring(0, imgId.length() - 1));
+                }
+            }
+            storeComment.setCreatedUserId(storeComment.getUserId());
+            int i = this.save(storeComment) ? 0 : 1;
+
+            //判断类型如果为5是评价 更新订单表orderAppraise为1 订单已评价
+            if(businessType == 5){
+                LambdaUpdateWrapper<LifeUserOrder> lambdaUpdateWrapper = new LambdaUpdateWrapper<>();
+                lambdaUpdateWrapper.eq(LifeUserOrder :: getId, businessId );
+                lambdaUpdateWrapper.set(LifeUserOrder :: getOrderAppraise, 1);
+                lifeUserOrderMapper.update(null,lambdaUpdateWrapper);
+            }
+            StoreInfoScoreVo storeInfoScoreVo = storeCommentMapper.getCommentCountAndScoreInfo(storeId);
+            double total = storeInfoScoreVo.getTotal();
+            double scoreAvg = (total == 0 ? 0 : storeInfoScoreVo.getScore() / total);
+            double tasteScore = (total == 0 ? 0 : storeInfoScoreVo.getTasteScore() / total);
+            double enScore = (total == 0 ? 0 : storeInfoScoreVo.getEnScore() / total);
+            double serviceScore = (total == 0 ? 0 : storeInfoScoreVo.getServiceScore() / total);
+            StoreInfo storeInfo = new StoreInfo();
+            storeInfo.setId(storeId);
+            storeInfo.setScoreAvg(new BigDecimal(scoreAvg).setScale(2, RoundingMode.HALF_UP).doubleValue());
+            storeInfo.setTasteScore(new BigDecimal(tasteScore).setScale(2, RoundingMode.HALF_UP).doubleValue());
+            storeInfo.setEnScore(new BigDecimal(enScore).setScale(2, RoundingMode.HALF_UP).doubleValue());
+            storeInfo.setServiceScore(new BigDecimal(serviceScore).setScale(2, RoundingMode.HALF_UP).doubleValue());
+            storeInfoMapper.updateById(storeInfo);
+            StoreUser storeUser = storeUserMapper.selectOne(new LambdaQueryWrapper<StoreUser>().eq(StoreUser::getStoreId, storeInfo.getId()).eq(StoreUser::getDeleteFlag, 0));
+
+            // 如果差评,则发送差评提醒
+            if(score != null && score >= 0.5 && score <= 2.5){
+                SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+                String commonDate = simpleDateFormat.format(new Date());
+                LifeNotice lifeMessage = new LifeNotice();
+                lifeMessage.setReceiverId("store_" + storeUser.getPhone());
+                String text = "在"+commonDate+",您的店铺有一条差评记录,您可查看评价内容是否属实,如不属实,可向平台进行申诉。";
+                JSONObject jsonObject = new JSONObject();
+                jsonObject.put("message", text);
+                lifeMessage.setContext(jsonObject.toJSONString());
+                lifeMessage.setTitle("差评通知");
+                lifeMessage.setSenderId("system");
+                lifeMessage.setIsRead(0);
+                lifeMessage.setNoticeType(1);
+                lifeMessage.setBusinessType(1);
+                lifeNoticeMapper.insert(lifeMessage);
+
+                WebSocketVo websocketVo = new WebSocketVo();
+                websocketVo.setSenderId("system");
+                websocketVo.setReceiverId("store_" + storeUser.getPhone());
+                websocketVo.setCategory("notice");
+                websocketVo.setNoticeType("1");
+                websocketVo.setIsRead(0);
+                websocketVo.setText(JSONObject.from(lifeMessage).toJSONString());
+                webSocketProcess.sendMessage("store_" + storeUser.getPhone(), JSONObject.from(websocketVo).toJSONString());
+            }
+            return i;
+        } catch (Exception e) {
+            log.error("StoreCommentService.userComment ERROR Msg={}", e.getMessage());
+            return 1;
+        }
+
+    }
+
+    /**
+     * 回复率, 评价比例
+     *
+     * @param storeId 门店id
+     * @return StoreCommitPercentVo
+     */
+    @Override
+    public StoreCommitPercentVo getCommitPercent(Integer storeId) {
+        StoreCommitPercentVo storeCommit = storeCommentMapper.getCommentByStoreId(storeId);
+        if (storeCommit != null) {
+            if ("0".equals(storeCommit.getAllCommentCount())) {
+                storeCommit.setHighPercent("0%");
+                storeCommit.setMidPercent("0%");
+                storeCommit.setLowPercent("0%");
+            } else {
+                double highPercentDouble = Double.parseDouble(storeCommit.getHighCommentCount()) / Double.parseDouble(storeCommit.getRootCommentCount());
+                BigDecimal highPercent = BigDecimal.valueOf(highPercentDouble).multiply(BigDecimal.valueOf(100)).setScale(2, RoundingMode.HALF_UP);
+                //好评
+                storeCommit.setHighPercent(highPercent + "%");
+                double midPercentDouble = Double.parseDouble(storeCommit.getMidCommentCount()) / Double.parseDouble(storeCommit.getRootCommentCount());
+                BigDecimal midPercent = BigDecimal.valueOf(midPercentDouble).multiply(BigDecimal.valueOf(100)).setScale(2, RoundingMode.HALF_UP);
+                //中评
+                storeCommit.setMidPercent(midPercent + "%");
+                double lowPercentDouble = Double.parseDouble(storeCommit.getLowCommentCount()) / Double.parseDouble(storeCommit.getRootCommentCount());
+                BigDecimal lowPercent = BigDecimal.valueOf(lowPercentDouble).multiply(BigDecimal.valueOf(100)).setScale(2, RoundingMode.HALF_UP);
+                //差评
+                storeCommit.setLowPercent(lowPercent + "%");
+            }
+            if ("0".equals(storeCommit.getRootCommentCount())) {
+                storeCommit.setReplyPercent("0%");
+            } else {
+                double replyPercentDouble = Double.parseDouble(storeCommit.getCommentCount()) / Double.parseDouble(storeCommit.getRootCommentCount());
+                BigDecimal replyPercent = BigDecimal.valueOf(replyPercentDouble).multiply(BigDecimal.valueOf(100)).setScale(2, RoundingMode.HALF_UP);
+                //回复率
+                storeCommit.setReplyPercent(replyPercent + "%");
+            }
+            return storeCommit;
+        }
+        return null;
+    }
+
+    @Override
+    public IPage<LifeUserOrderCommentVo> getCommentOrderPage(Integer pageNum, Integer pageSize, Integer type, String userId) {
+        IPage<LifeUserOrderCommentVo> lifeUserOrderCommentVoIPage = new Page<>(pageNum, pageSize);
+        IPage<LifeUserOrderCommentVo> commentOrderPage;
+        //1.未评价 2.已评价
+        if (type == 1) {
+            commentOrderPage = storeCommentMapper.getCommentOrderWPJPage(lifeUserOrderCommentVoIPage, userId);
+        } else {
+            commentOrderPage = storeCommentMapper.getCommentOrderYPJPage(lifeUserOrderCommentVoIPage, userId);
+        }
+        List<String> collect = commentOrderPage.getRecords().stream().map(LifeUserOrderCommentVo::getGroupBuyImgId).collect(Collectors.toList());
+
+        if (ObjectUtils.isNotEmpty(collect)) {
+            List<StoreImg> storeImgList = storeImgMapper.selectList(new QueryWrapper<StoreImg>().inSql("id", String.join(",", collect)));
+//            commentOrderPage.getRecords().forEach(i ->
+//                    i.setGroupBuyImgUrl(storeImgList.stream().filter(j -> j.getId().toString().equals(i.getGroupBuyImgId()))
+//                            .findFirst().map(StoreImg::getImgUrl).orElse(null)));
+            // 2. 优化分组:id唯一,直接映射为 Map<Integer, StoreImg>(避免List处理)
+            Map<Integer, StoreImg> imgIdToImgMap = storeImgList.stream()
+                    .collect(Collectors.toMap(
+                            StoreImg::getId,  // 键:图片id
+                            Function.identity(),  // 值:图片对象本身
+                            (existing, replacement) -> existing  // 若有重复id(理论上不会),保留第一个
+                    ));
+
+            // 3. 遍历订单评论记录,处理图片URL拼接
+            for (LifeUserOrderCommentVo record : commentOrderPage.getRecords()) {
+                String groupBuyImgId = record.getGroupBuyImgId();
+                // 空指针防护:如果imgId为空,直接设为空字符串
+                if (groupBuyImgId == null || groupBuyImgId.trim().isEmpty()) {
+                    record.setGroupBuyImgUrl("");
+                    continue;
+                }
+
+                // 分割imgId并一次性转换为Integer(避免重复解析)
+                List<Integer> imgIds = Arrays.stream(groupBuyImgId.split(","))
+                        .map(String::trim)  // 处理可能的空格
+                        .filter(idStr -> !idStr.isEmpty())  // 过滤空字符串(如连续逗号导致的)
+                        .map(idStr -> {
+                            try {
+                                return Integer.parseInt(idStr);
+                            } catch (NumberFormatException e) {
+                                // 处理非数字id的异常(如无效id)
+                                return null;
+                            }
+                        })
+                        .filter(Objects::nonNull)  // 过滤转换失败的null
+                        .collect(Collectors.toList());
+
+                // 拼接图片URL(使用StringBuilder高效拼接)
+                StringBuilder imgUrlBuilder = new StringBuilder();
+                for (Integer imgId : imgIds) {
+                    StoreImg storeImg = imgIdToImgMap.get(imgId);
+                    if (storeImg != null && storeImg.getImgUrl() != null) {
+                        if (imgUrlBuilder.length() > 0) {
+                            imgUrlBuilder.append(",");  // 非第一个元素前加逗号
+                        }
+                        imgUrlBuilder.append(storeImg.getImgUrl());
+                    }
+                }
+
+                // 设置最终的图片URL字符串(去掉末尾可能的逗号,这里通过逻辑避免了)
+                record.setGroupBuyImgUrl(imgUrlBuilder.toString());
+            }
+        }
+
+
+        List<LifeUserOrderCommentVo> lifeUserOrderCommentVos =  commentOrderPage.getRecords();
+        if(CollectionUtils.isEmpty(lifeUserOrderCommentVos)){
+            return commentOrderPage;
+        }
+        List<String> orderIds = lifeUserOrderCommentVos.stream().map(LifeUserOrderCommentVo::getId).collect(Collectors.toList());
+        List<StoreComment> storeCommentList = storeCommentMapper.selectList(new QueryWrapper<StoreComment>().inSql("business_id", String.join(",", orderIds)).eq("business_type", 5));
+        if(CollectionUtils.isEmpty(storeCommentList)){
+            return commentOrderPage;
+        }
+        Map<Integer, StoreComment> commentIdToMap = storeCommentList.stream()
+                .collect(Collectors.toMap(
+                        StoreComment::getBusinessId,
+                        Function.identity(),
+                        (existing, replacement) -> existing  // 若有重复id(理论上不会),保留第一个
+                ));
+        if(CollectionUtils.isEmpty(commentIdToMap)){
+            return commentOrderPage;
+        }
+        for(LifeUserOrderCommentVo lifeUserOrderCommentVo : commentOrderPage.getRecords()){
+            Integer orderId = Integer.parseInt(lifeUserOrderCommentVo.getId());
+
+            if(commentIdToMap.containsKey(orderId)){
+                StoreComment storeComment = commentIdToMap.get(orderId);
+                lifeUserOrderCommentVo.setScore(storeComment.getScore().toString());
+                lifeUserOrderCommentVo.setCommentDate(storeComment.getCreatedTime());
+                String imgIds = storeComment.getImgId();
+
+                if(StringUtils.isNotEmpty(imgIds)){
+                    LambdaQueryWrapper<StoreImg> storeImgLambdaQueryWrapper = new LambdaQueryWrapper<>();
+                    List<String> imgIdList = Arrays.stream(imgIds.split(","))
+                            .map(String::trim)
+                            .collect(Collectors.toList());
+                    storeImgLambdaQueryWrapper.in(StoreImg::getId, imgIdList);
+                    List<StoreImg> storeImgList = storeImgMapper.selectList(storeImgLambdaQueryWrapper);
+                    if(CollectionUtils.isNotEmpty(storeImgList)){
+                        List<String> imgUrlList = storeImgList.stream()    // 转换为 Stream
+                                .map(StoreImg::getImgUrl)                  // 映射,提取 name 字段
+                                .collect(Collectors.toList());
+                        lifeUserOrderCommentVo.setImgUrls(imgUrlList);
+                    }
+                }
+            }
+        }
+
+        return commentOrderPage;
+    }
+
+    /**
+     * 当前用户的全部评价(店铺订单评价 + 律师订单评价)
+     */
+    @Override
+    public IPage<LifeUserOrderCommentVo> getUserAllCommentsPage(Integer pageNum, Integer pageSize, Integer userId) {
+        IPage<LifeUserOrderCommentVo> page = new Page<>(pageNum, pageSize);
+        IPage<LifeUserOrderCommentVo> result = storeCommentMapper.getUserAllCommentsPage(page, userId);
+        List<LifeUserOrderCommentVo> records = result.getRecords();
+        if (CollectionUtils.isEmpty(records)) {
+            return result;
+        }
+
+        // 收集需要补全的图片ID
+        Set<Integer> allImgIds = new HashSet<>();
+        records.forEach(r -> {
+            // 团购图片
+            if (StringUtils.isNotEmpty(r.getGroupBuyImgId())) {
+                Arrays.stream(r.getGroupBuyImgId().split(","))
+                        .map(String::trim)
+                        .filter(s -> !s.isEmpty())
+                        .forEach(s -> {
+                            try { allImgIds.add(Integer.parseInt(s)); } catch (NumberFormatException ignored) {}
+                        });
+            }
+            // 店铺评价图片(img_id)
+            if (r.getSrc() != null && r.getSrc() == 1 && StringUtils.isNotEmpty(r.getImgId())) {
+                Arrays.stream(r.getImgId().split(","))
+                        .map(String::trim)
+                        .filter(s -> !s.isEmpty())
+                        .forEach(s -> {
+                            try { allImgIds.add(Integer.parseInt(s)); } catch (NumberFormatException ignored) {}
+                        });
+            }
+        });
+
+        final Map<Integer, StoreImg> imgIdToImgMap = new HashMap<>();
+        if (!allImgIds.isEmpty()) {
+            List<StoreImg> storeImgList = storeImgMapper.selectList(new QueryWrapper<StoreImg>().in("id", allImgIds));
+            imgIdToImgMap.putAll(storeImgList.stream().collect(Collectors.toMap(StoreImg::getId, Function.identity(), (a, b) -> a)));
+        }
+
+        // 补全 groupBuyImgUrl 与评价图片列表
+        for (LifeUserOrderCommentVo r : records) {
+            // 团购图片 URL
+            if (StringUtils.isNotEmpty(r.getGroupBuyImgId())) {
+                List<String> urls = Arrays.stream(r.getGroupBuyImgId().split(","))
+                        .map(String::trim)
+                        .filter(s -> !s.isEmpty())
+                        .map(idStr -> {
+                            try {
+                                StoreImg img = imgIdToImgMap.get(Integer.parseInt(idStr));
+                                return img != null ? img.getImgUrl() : null;
+                            } catch (NumberFormatException e) {
+                                return null;
+                            }
+                        })
+                        .filter(Objects::nonNull)
+                        .collect(Collectors.toList());
+                r.setGroupBuyImgUrl(String.join(",", urls));
+            }
+
+            // 评价图片 URL 列表
+            if (r.getSrc() != null && r.getSrc() == 1) {
+                // 店铺评价:imgId 是逗号分隔的图片ID
+                if (StringUtils.isNotEmpty(r.getImgId())) {
+                    List<String> imgUrls = Arrays.stream(r.getImgId().split(","))
+                            .map(String::trim)
+                            .filter(s -> !s.isEmpty())
+                            .map(idStr -> {
+                                try {
+                                    StoreImg img = imgIdToImgMap.get(Integer.parseInt(idStr));
+                                    return img != null ? img.getImgUrl() : null;
+                                } catch (NumberFormatException e) {
+                                    return null;
+                                }
+                            })
+                            .filter(Objects::nonNull)
+                            .collect(Collectors.toList());
+                    r.setImgUrls(imgUrls);
+                }
+            } else if (r.getSrc() != null && r.getSrc() == 2) {
+                // 律师评价:review_images 是 JSON 数组(URL 列表)
+                if (StringUtils.isNotEmpty(r.getImgId())) {
+                    try {
+                        List<String> imgUrls = JSONArray.parseArray(r.getImgId(), String.class);
+                        r.setImgUrls(imgUrls);
+                    } catch (Exception e) {
+                        log.warn("parse lawyer review images fail, raw={}", r.getImgId());
+                    }
+                }
+            }
+        }
+        return result;
+    }
+
+    /**
+     * 获取店铺评价计数统计
+     *
+     * @param storeId 门店id
+     * @return StoreCommentCountVo
+     */
+    @Override
+    public StoreCommentCountVo getAppraiseCount(Integer storeId) {
+        StoreCommentCountVo countVo = new StoreCommentCountVo();
+
+        // 查询条件:只统计当前门店、根评论(reply_id为null)、未删除的评论
+        LambdaQueryWrapper<StoreComment> totalWrapper = buildBaseStoreCommentWrapper(storeId);
+        countVo.setTotalCount(storeCommentMapper.selectCount(totalWrapper));
+
+        // 2. 有图评论数(img_id不为空且不为空字符串)
+        LambdaQueryWrapper<StoreComment> imageWrapper = buildBaseStoreCommentWrapper(storeId);
+        imageWrapper.isNotNull(StoreComment::getImgId)
+                .apply("img_id != ''");
+        countVo.setImageCount(storeCommentMapper.selectCount(imageWrapper));
+
+        // 3. 好评数(score >= 4.5)
+        LambdaQueryWrapper<StoreComment> goodWrapper = buildBaseStoreCommentWrapper(storeId);
+        goodWrapper.ge(StoreComment::getScore, 4.5);
+        countVo.setGoodCount(storeCommentMapper.selectCount(goodWrapper));
+
+        // 4. 中评数(score >= 3 && score <= 4)
+        LambdaQueryWrapper<StoreComment> midWrapper = buildBaseStoreCommentWrapper(storeId);
+        midWrapper.ge(StoreComment::getScore, 3.0)
+                .le(StoreComment::getScore, 4.0);
+        countVo.setMidCount(storeCommentMapper.selectCount(midWrapper));
+
+        // 5. 差评数(score >= 0.5 && score <= 2.5)
+        LambdaQueryWrapper<StoreComment> badWrapper = buildBaseStoreCommentWrapper(storeId);
+        badWrapper.ge(StoreComment::getScore, 0.5)
+                .le(StoreComment::getScore, 2.5);
+        countVo.setBadCount(storeCommentMapper.selectCount(badWrapper));
+
+        return countVo;
+    }
+
+    /**
+     * 构建当前门店根评论的查询条件
+     */
+    private LambdaQueryWrapper<StoreComment> buildBaseStoreCommentWrapper(Integer storeId) {
+        return new LambdaQueryWrapper<StoreComment>()
+                .eq(StoreComment::getStoreId, storeId)
+                .gt(StoreComment::getScore,0 )
+                .eq(StoreComment::getBusinessType, 5)
+                .eq(StoreComment::getDeleteFlag, 0);
+    }
+
+    // ==================== 订单评价相关方法实现(迁移自 OrderReviewService)====================
+
+    @Override
+    public R<StoreComment> createOrderReview(OrderReviewDto reviewDto) {
+        log.info("StoreCommentServiceImpl.createOrderReview?reviewDto={}", reviewDto);
+
+        // 参数校验
+        if (reviewDto == null) {
+            return R.fail("评价信息不能为空");
+        }
+        if (reviewDto.getOrderId() == null) {
+            return R.fail("订单ID不能为空");
+        }
+        Integer userId = reviewDto.getUserId();
+        if (userId == null) {
+            return R.fail("用户ID不能为空");
+        }
+
+        // 验证订单是否存在且属于该用户
+        LawyerConsultationOrder order = lawyerConsultationOrderMapper.selectById(reviewDto.getOrderId());
+        if (order == null) {
+            return R.fail("订单不存在");
+        }
+        if (!order.getClientUserId().equals(userId)) {
+            return R.fail("只能评价自己的订单");
+        }
+
+        // 检查订单是否已完成
+        if (order.getOrderStatus() == null || order.getOrderStatus() != 3) {
+            return R.fail("只能对已完成的订单进行评价");
+        }
+
+        // 检查是否已经评价过(使用 store_comment 表,business_type=5)
+        LambdaQueryWrapper<StoreComment> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(StoreComment::getOrderId, reviewDto.getOrderId())
+                .eq(StoreComment::getBusinessType, 5)
+                .eq(StoreComment::getDeleteFlag, 0);
+        StoreComment existingReview = this.getOne(queryWrapper);
+        if (existingReview != null) {
+            return R.success("该订单已经评价过了");
+        }
+
+        // 创建评价(使用 store_comment 表)
+        StoreComment review = new StoreComment();
+        review.setOrderId(reviewDto.getOrderId());
+        review.setLawyerId(order.getLawyerUserId());
+        review.setUserId(userId);
+        review.setCommentContent(reviewDto.getReviewContent());
+        review.setScore(reviewDto.getOverallRating());
+        review.setBusinessId(reviewDto.getOrderId()); // 订单ID作为业务ID
+        review.setBusinessType(5); // 订单评价
+        review.setIsAnonymous(reviewDto.getIsAnonymous() != null ? reviewDto.getIsAnonymous() : 0);
+        review.setLikeCount(0);
+
+        
+        // 处理评价图片:将图片URL存入store_img表,并将图片ID存入store_comment的img_id字段
+        if (reviewDto.getReviewImages() != null && !reviewDto.getReviewImages().isEmpty()) {
+            StringBuilder imgIdBuilder = new StringBuilder();
+            Integer orderId = reviewDto.getOrderId();
+            
+            for (int i = 0; i < reviewDto.getReviewImages().size(); i++) {
+                String imgUrl = reviewDto.getReviewImages().get(i);
+                if (StringUtils.isNotEmpty(imgUrl)) {
+                    StoreImg storeImg = new StoreImg();
+                    storeImg.setImgType(8); // 用户评论图片类型
+                    storeImg.setBusinessId(orderId); // 订单ID作为业务ID
+                    storeImg.setImgUrl(imgUrl);
+                    storeImg.setImgSort(i + 1); // 图片排序
+                    storeImg.setCreatedUserId(userId);
+                    storeImg.setCreatedTime(new Date());
+                    storeImg.setDeleteFlag(0);
+                    storeImg.setStoreId(0);
+                    int insertResult = storeImgMapper.insert(storeImg);
+                    if (insertResult > 0 && storeImg.getId() != null) {
+                        imgIdBuilder.append(storeImg.getId()).append(",");
+                        log.info("创建评价图片成功,图片ID={}, 订单ID={}", storeImg.getId(), orderId);
+                    } else {
+                        log.warn("创建评价图片失败,图片URL={}, 订单ID={}", imgUrl, orderId);
+                    }
+                }
+            }
+            
+            // 将图片ID用逗号分隔存入img_id字段
+            if (imgIdBuilder.length() > 0) {
+                String imgIdStr = imgIdBuilder.substring(0, imgIdBuilder.length() - 1);
+                review.setImgId(imgIdStr);
+                log.info("评价图片ID设置成功,imgId={}, 订单ID={}", imgIdStr, orderId);
+            }
+        }
+        
+        review.setCreatedUserId(userId);
+        review.setCreatedTime(new Date());
+
+        boolean success = this.save(review);
+        if (success) {
+            log.info("创建评价成功,评价ID={}", review.getId());
+            // 更新律师评分
+            updateLawyerServiceScore(order.getLawyerUserId());
+            return R.data(review, "提交成功");
+        } else {
+            log.error("创建评价失败");
+            return R.fail("创建评价失败");
+        }
+    }
+
+    /**
+     * 更新律师服务评分和好评/中评/差评数量
+     */
+    private void updateLawyerServiceScore(Integer lawyerUserId) {
+        if (lawyerUserId == null) {
+            log.warn("更新律师评分失败:律师ID为空");
+            return;
+        }
+
+        try {
+            // 从 store_comment 表计算平均评分(business_type=5)
+            LambdaQueryWrapper<StoreComment> queryWrapper = new LambdaQueryWrapper<>();
+            queryWrapper.eq(StoreComment::getLawyerId, lawyerUserId)
+                    .eq(StoreComment::getBusinessType, 5)
+                    .eq(StoreComment::getDeleteFlag, 0)
+                    .isNotNull(StoreComment::getScore);
+            List<StoreComment> reviews = this.list(queryWrapper);
+            
+            Double averageRating = null;
+            if (!reviews.isEmpty()) {
+                double sum = reviews.stream()
+                        .filter(r -> r.getScore() != null)
+                        .mapToDouble(StoreComment::getScore)
+                        .sum();
+                averageRating = sum / reviews.size();
+            }
+
+            Double serviceScore;
+            if (averageRating != null) {
+                serviceScore = Math.floor(averageRating * 10.0) / 10.0;
+                serviceScore = Math.max(0.0, Math.min(5.0, serviceScore));
+            } else {
+                serviceScore = 0.0;
+            }
+
+            // 统计好评、中评、差评数量
+            long goodCount = reviews.stream()
+                    .filter(r -> r.getScore() != null && r.getScore() >= 4.5 && r.getScore() <= 5)
+                    .count();
+            long mediumCount = reviews.stream()
+                    .filter(r -> r.getScore() != null && r.getScore() >= 3.0 && r.getScore() < 4.5)
+                    .count();
+            long badCount = reviews.stream()
+                    .filter(r -> r.getScore() != null && r.getScore() >= 0.5 && r.getScore() < 3.0)
+                    .count();
+
+            // 更新律师评分
+            LambdaUpdateWrapper<LawyerUser> updateWrapper = new LambdaUpdateWrapper<>();
+            updateWrapper.eq(LawyerUser::getId, lawyerUserId);
+            updateWrapper.set(LawyerUser::getServiceScore, serviceScore);
+            updateWrapper.set(LawyerUser::getGoodReviewCount, (int) goodCount);
+            updateWrapper.set(LawyerUser::getMediumReviewCount, (int) mediumCount);
+            updateWrapper.set(LawyerUser::getBadReviewCount, (int) badCount);
+            int result = lawyerUserMapper.update(null, updateWrapper);
+
+            if (result > 0) {
+                log.info("更新律师评分成功,律师ID={}, 平均评分={}, 服务评分={}, 好评数={}, 中评数={}, 差评数={}",
+                        lawyerUserId, averageRating, serviceScore, goodCount, mediumCount, badCount);
+            }
+        } catch (Exception e) {
+            log.error("更新律师评分异常,律师ID={}, 错误信息={}", lawyerUserId, e.getMessage(), e);
+        }
+    }
+
+    @Override
+    public R<OrderReviewDetailVo> getOrderReviewDetail(Integer orderId, Integer currentUserId) {
+        log.info("StoreCommentServiceImpl.getOrderReviewDetail?orderId={}, currentUserId={}", orderId, currentUserId);
+
+        if (orderId == null) {
+            return R.fail("订单ID不能为空");
+        }
+
+        // 根据orderId从store_comment表查询评价详情
+        // 条件:business_id=orderId, store_id为空, business_type=5
+        LambdaQueryWrapper<StoreComment> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(StoreComment::getBusinessId, orderId)
+                .isNull(StoreComment::getStoreId)
+                .eq(StoreComment::getBusinessType, 5)
+                .eq(StoreComment::getDeleteFlag, 0);
+        
+        StoreComment review = this.getOne(queryWrapper);
+        if (review == null) {
+            log.warn("评价不存在,orderId={}", orderId);
+            return R.fail("评价不存在");
+        }
+
+        // 构建 OrderReviewVo
+        OrderReviewVo reviewVo = new OrderReviewVo();
+        reviewVo.setId(review.getId());
+        reviewVo.setOrderId(orderId);
+        reviewVo.setUserId(review.getUserId());
+        reviewVo.setReviewContent(review.getCommentContent());
+        reviewVo.setOverallRating(review.getScore());
+        reviewVo.setIsAnonymous(review.getIsAnonymous());
+        reviewVo.setLikeCount(review.getLikeCount());
+        reviewVo.setCreatedTime(review.getCreatedTime());
+
+        // 查询订单信息获取订单编号
+        LawyerConsultationOrder order = lawyerConsultationOrderMapper.selectById(orderId);
+        if (order != null) {
+            reviewVo.setOrderNumber(order.getOrderNumber());
+            reviewVo.setLawyerUserId(order.getLawyerUserId());
+        }
+
+        // 查询用户信息
+        if (review.getUserId() != null) {
+            LifeUser lifeUser = lifeUserMapper.selectById(review.getUserId());
+            if (lifeUser != null) {
+                reviewVo.setUserName(lifeUser.getUserName());
+                reviewVo.setUserAvatar(lifeUser.getUserImage());
+            }
+        }
+
+        // 查询律师信息
+        Integer lawyerId = review.getLawyerId();
+        if (lawyerId == null && order != null) {
+            lawyerId = order.getLawyerUserId();
+        }
+        if (lawyerId != null) {
+            LawyerUser lawyerUser = lawyerUserMapper.selectById(lawyerId);
+            if (lawyerUser != null) {
+                reviewVo.setLawyerUserId(lawyerId);
+                reviewVo.setLawyerName(lawyerUser.getName());
+                reviewVo.setLawyerAvatar(lawyerUser.getHeadImg());
+                
+                // 查询律所信息
+                if (lawyerUser.getFirmId() != null) {
+                    try {
+                        LawFirm lawFirm = lawFirmMapper.selectById(lawyerUser.getFirmId());
+                        if (lawFirm != null && StringUtils.isNotEmpty(lawFirm.getFirmName())) {
+                            reviewVo.setLawFirmName(lawFirm.getFirmName());
+                        }
+                    } catch (Exception e) {
+                        log.warn("查询律所信息失败,firmId={}", lawyerUser.getFirmId(), e);
+                    }
+                }
+            }
+        }
+
+        // 解析评分信息(从otherScore字段)
+        if (StringUtils.isNotEmpty(review.getOtherScore())) {
+            try {
+                List<com.alibaba.fastjson.JSONObject> scoreList = JSONArray.parseArray(review.getOtherScore(), com.alibaba.fastjson.JSONObject.class);
+                if (scoreList != null && !scoreList.isEmpty()) {
+                    for (com.alibaba.fastjson.JSONObject scoreObj : scoreList) {
+                        if (scoreObj != null) {
+                            String name = scoreObj.getString("name");
+                            String rateScore = scoreObj.getString("rateScore");
+                            if (StringUtils.isNotEmpty(name) && StringUtils.isNotEmpty(rateScore)) {
+                                try {
+                                    Double scoreValue = Double.parseDouble(rateScore);
+                                    if ("服务态度".equals(name)) {
+                                        reviewVo.setServiceAttitudeRating(scoreValue);
+                                    } else if ("响应时间".equals(name)) {
+                                        reviewVo.setResponseTimeRating(scoreValue);
+                                    } else if ("专业能力".equals(name)) {
+                                        reviewVo.setProfessionalAbilityRating(scoreValue);
+                                    }
+                                } catch (NumberFormatException e) {
+                                    log.warn("解析评分失败,name={}, rateScore={}", name, rateScore, e);
+                                }
+                            }
+                        }
+                    }
+                }
+            } catch (Exception e) {
+                log.warn("解析otherScore失败,otherScore={}", review.getOtherScore(), e);
+            }
+        }
+
+        // 查询评价图片(从store_img表,img_type=8, business_id=orderId)
+        List<String> imageUrls = new ArrayList<>();
+        if (StringUtils.isNotEmpty(review.getImgId())) {
+            String[] imgIds = review.getImgId().split(",");
+            if (imgIds.length > 0) {
+                List<Integer> imgIdList = new ArrayList<>();
+                for (String imgIdStr : imgIds) {
+                    if (StringUtils.isNotEmpty(imgIdStr.trim())) {
+                        try {
+                            imgIdList.add(Integer.parseInt(imgIdStr.trim()));
+                        } catch (NumberFormatException e) {
+                            log.warn("图片ID格式错误,imgId={}", imgIdStr, e);
+                        }
+                    }
+                }
+                if (!imgIdList.isEmpty()) {
+                    LambdaQueryWrapper<StoreImg> imgWrapper = new LambdaQueryWrapper<>();
+                    imgWrapper.in(StoreImg::getId, imgIdList)
+                            .eq(StoreImg::getDeleteFlag, 0)
+                            .orderByAsc(StoreImg::getImgSort);
+                    List<StoreImg> storeImgs = storeImgMapper.selectList(imgWrapper);
+                    if (CollectionUtils.isNotEmpty(storeImgs)) {
+                        imageUrls = storeImgs.stream()
+                                .map(StoreImg::getImgUrl)
+                                .filter(url -> StringUtils.isNotEmpty(url))
+                                .collect(java.util.stream.Collectors.toList());
+                    }
+                }
+            }
+        }
+        reviewVo.setReviewImages(imageUrls);
+
+        // 检查当前用户是否已点赞
+        if (currentUserId != null) {
+            LambdaQueryWrapper<LifeLikeRecord> likeWrapper = new LambdaQueryWrapper<>();
+            likeWrapper.eq(LifeLikeRecord::getType, "7")
+                    .eq(LifeLikeRecord::getDianzanId, String.valueOf(currentUserId))
+                    .eq(LifeLikeRecord::getHuifuId, String.valueOf(review.getId()))
+                    .eq(LifeLikeRecord::getDeleteFlag, 0);
+            long likeCount = lifeLikeRecordMapper.selectCount(likeWrapper);
+            reviewVo.setIsLiked(likeCount > 0 ? 1 : 0);
+        } else {
+            reviewVo.setIsLiked(0);
+        }
+
+        // 查询评论列表(business_type=6,business_id=评价ID)
+        Integer reviewId = review.getId();
+        R<List<ReviewCommentVo>> commentsResult = getReviewCommentListByReviewId(reviewId, currentUserId);
+        List<ReviewCommentVo> comments = new ArrayList<>();
+        if (commentsResult.getCode() == 200 && commentsResult.getData() != null) {
+            comments = commentsResult.getData();
+        }
+
+        // 统计评论总数(包括首评和回复)
+        int totalCommentCount = comments.size();
+        for (ReviewCommentVo comment : comments) {
+            if (comment.getReplies() != null) {
+                totalCommentCount += comment.getReplies().size();
+            }
+        }
+
+        // 构建返回结果
+        OrderReviewDetailVo detailVo = new OrderReviewDetailVo();
+        detailVo.setReview(reviewVo);
+        detailVo.setComments(comments);
+        detailVo.setTotalCommentCount(totalCommentCount);
+        detailVo.setLikeCount(reviewVo.getLikeCount() != null ? reviewVo.getLikeCount() : 0);
+        detailVo.setIsLiked(reviewVo.getIsLiked() != null ? reviewVo.getIsLiked() : 0);
+
+        log.info("查询评价详情成功,orderId={}, reviewId={}", orderId, reviewId);
+        return R.data(detailVo);
+    }
+
+    @Override
+    public R<Boolean> likeOrderReview(Integer reviewId, Integer userId) {
+        log.info("StoreCommentServiceImpl.likeOrderReview?reviewId={}, userId={}", reviewId, userId);
+
+        // 验证评价是否存在
+        StoreComment review = this.getById(reviewId);
+        if (review == null || review.getDeleteFlag() == 1 || review.getBusinessType() != 5) {
+            return R.fail("评价不存在或已删除");
+        }
+
+        // 检查是否已点赞(type=7 表示订单评价点赞)
+        LambdaQueryWrapper<LifeLikeRecord> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(LifeLikeRecord::getType, "7")
+                .eq(LifeLikeRecord::getDianzanId, String.valueOf(userId))
+                .eq(LifeLikeRecord::getHuifuId, String.valueOf(reviewId))
+                .eq(LifeLikeRecord::getDeleteFlag, 0);
+        List<LifeLikeRecord> records = lifeLikeRecordMapper.selectList(queryWrapper);
+
+        if (CollectionUtils.isEmpty(records)) {
+            // 插入点赞记录
+            LifeLikeRecord likeRecord = new LifeLikeRecord();
+            likeRecord.setDianzanId(String.valueOf(userId));
+            likeRecord.setHuifuId(String.valueOf(reviewId));
+            likeRecord.setType("7");
+            likeRecord.setCreatedTime(new Date());
+            likeRecord.setCreatedUserId(userId);
+            lifeLikeRecordMapper.insert(likeRecord);
+
+            // 更新评价点赞数
+            LambdaUpdateWrapper<StoreComment> updateWrapper = new LambdaUpdateWrapper<>();
+            updateWrapper.eq(StoreComment::getId, reviewId);
+            updateWrapper.setSql("like_count = like_count + 1");
+            int result = storeCommentMapper.update(null, updateWrapper);
+
+            if (result > 0) {
+                return R.data(true, "点赞成功");
+            } else {
+                return R.fail("点赞失败");
+            }
+        } else {
+            return R.data(true, "已点赞");
+        }
+    }
+
+    @Override
+    public R<Boolean> cancelLikeOrderReview(Integer reviewId, Integer userId) {
+        log.info("StoreCommentServiceImpl.cancelLikeOrderReview?reviewId={}, userId={}", reviewId, userId);
+
+        // 查询点赞记录
+        LambdaQueryWrapper<LifeLikeRecord> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(LifeLikeRecord::getType, "7")
+                .eq(LifeLikeRecord::getDianzanId, String.valueOf(userId))
+                .eq(LifeLikeRecord::getHuifuId, String.valueOf(reviewId))
+                .eq(LifeLikeRecord::getDeleteFlag, 0);
+        List<LifeLikeRecord> records = lifeLikeRecordMapper.selectList(queryWrapper);
+
+        if (!CollectionUtils.isEmpty(records)) {
+            // 删除点赞记录(逻辑删除)
+            for (LifeLikeRecord record : records) {
+                lifeLikeRecordMapper.deleteById(record.getId());
+            }
+
+            // 更新评价点赞数
+            LambdaUpdateWrapper<StoreComment> updateWrapper = new LambdaUpdateWrapper<>();
+            updateWrapper.eq(StoreComment::getId, reviewId);
+            updateWrapper.setSql("like_count = GREATEST(0, like_count - 1)");
+            int result = storeCommentMapper.update(null, updateWrapper);
+
+            if (result > 0) {
+                return R.data(true, "取消点赞成功");
+            } else {
+                return R.fail("取消点赞失败");
+            }
+        } else {
+            return R.data(true, "未点赞");
+        }
+    }
+
+    @Override
+    public R<IPage<OrderReviewVo>> getOrderReviewList(int page, int size, Integer orderId, Integer lawyerUserId, Integer userId, Integer currentUserId) {
+        log.info("StoreCommentServiceImpl.getOrderReviewList?page={}, size={}, orderId={}, lawyerUserId={}, userId={}, currentUserId={}",
+                page, size, orderId, lawyerUserId, userId, currentUserId);
+
+        // 构建查询条件
+        LambdaQueryWrapper<StoreComment> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(StoreComment::getBusinessType, 5)
+                .eq(StoreComment::getDeleteFlag, 0);
+        
+        if (orderId != null) {
+            queryWrapper.eq(StoreComment::getOrderId, orderId);
+        }
+        if (lawyerUserId != null) {
+            queryWrapper.eq(StoreComment::getLawyerId, lawyerUserId);
+        }
+        if (userId != null) {
+            queryWrapper.eq(StoreComment::getUserId, userId);
+        }
+        
+        queryWrapper.orderByDesc(StoreComment::getCreatedTime);
+
+        // 分页查询
+        Page<StoreComment> pageObj = new Page<>(page, size);
+        IPage<StoreComment> commentPage = this.page(pageObj, queryWrapper);
+
+        // 转换为 OrderReviewVo(需要关联用户和律师信息)
+        // TODO: 需要自定义查询或手动构建 OrderReviewVo,关联用户和律师信息
+        Page<OrderReviewVo> resultPage = new Page<>(page, size);
+        resultPage.setTotal(commentPage.getTotal());
+        resultPage.setPages(commentPage.getPages());
+        
+        List<OrderReviewVo> voList = new ArrayList<>();
+        for (StoreComment comment : commentPage.getRecords()) {
+            OrderReviewVo vo = new OrderReviewVo();
+            vo.setId(comment.getId());
+            vo.setOrderId(comment.getOrderId());
+            vo.setUserId(comment.getUserId());
+            vo.setLawyerUserId(comment.getLawyerId());
+            vo.setOverallRating(comment.getScore());
+            vo.setReviewContent(comment.getCommentContent());
+            vo.setIsAnonymous(comment.getIsAnonymous());
+            vo.setLikeCount(comment.getLikeCount());
+            vo.setCreatedTime(comment.getCreatedTime());
+            // TODO: 需要查询并设置用户名称、头像、律师信息等
+            voList.add(vo);
+        }
+        resultPage.setRecords(voList);
+
+        return R.data(resultPage);
+    }
+
+    @Override
+    public R<OrderReviewVo> getOrderReviewByOrderId(Integer orderId, Integer currentUserId) {
+        log.info("StoreCommentServiceImpl.getOrderReviewByOrderId?orderId={}, currentUserId={}", orderId, currentUserId);
+
+        LambdaQueryWrapper<StoreComment> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(StoreComment::getOrderId, orderId)
+                .eq(StoreComment::getBusinessType, 5)
+                .eq(StoreComment::getDeleteFlag, 0)
+                .orderByDesc(StoreComment::getCreatedTime)
+                .last("LIMIT 1");
+        
+        StoreComment comment = this.getOne(queryWrapper);
+        if (comment == null) {
+            return R.fail("评价不存在");
+        }
+
+        // 转换为 OrderReviewVo
+        OrderReviewVo vo = new OrderReviewVo();
+        vo.setId(comment.getId());
+        vo.setOrderId(comment.getOrderId());
+        vo.setUserId(comment.getUserId());
+        vo.setLawyerUserId(comment.getLawyerId());
+        vo.setOverallRating(comment.getScore());
+        vo.setReviewContent(comment.getCommentContent());
+        vo.setIsAnonymous(comment.getIsAnonymous());
+        vo.setLikeCount(comment.getLikeCount());
+        vo.setCreatedTime(comment.getCreatedTime());
+        // TODO: 需要查询并设置用户名称、头像、律师信息等
+
+        return R.data(vo);
+    }
+
+    // ==================== 评价评论相关方法实现(迁移自 ReviewCommentService)====================
+
+    @Override
+    public R<StoreComment> createReviewComment(StoreComment comment) {
+        log.info("StoreCommentServiceImpl.createReviewComment?comment={}", comment);
+
+        // 参数校验
+        if (comment == null) {
+            return R.fail("评论信息不能为空");
+        }
+        if (comment.getBusinessId() == null) {
+            return R.fail("评价ID不能为空");
+        }
+        if (comment.getCommentContent() == null || comment.getCommentContent().trim().isEmpty()) {
+            return R.fail("评论内容不能为空");
+        }
+        Integer userId = comment.getUserId();
+        if (userId == null) {
+            return R.fail("用户ID不能为空");
+        }
+
+        // 验证评价是否存在(business_type=5)
+        StoreComment review = this.getById(comment.getBusinessId());
+        if (review == null || review.getDeleteFlag() == 1 || review.getBusinessType() != 5) {
+            return R.fail("评价不存在或已删除");
+        }
+
+        // 设置评论属性
+        comment.setBusinessType(6); // 订单评价的评论
+        comment.setBusinessId(review.getId()); // 评价ID作为业务ID
+        comment.setOrderId(review.getOrderId()); // 从评价中获取订单ID
+        comment.setLawyerId(review.getLawyerId()); // 从评价中获取律师ID
+        comment.setLikeCount(0);
+        comment.setReplyId(null); // 首评没有reply_id
+        comment.setCreatedUserId(userId);
+        comment.setCreatedTime(new Date());
+
+        boolean success = this.save(comment);
+        if (success) {
+            log.info("创建评论成功,评论ID={}", comment.getId());
+            return R.data(comment, "评论成功");
+        } else {
+            log.error("创建评论失败");
+            return R.fail("创建评论失败");
+        }
+    }
+
+    @Override
+    public R<List<ReviewCommentVo>> getReviewCommentListByReviewId(Integer reviewId, Integer currentUserId) {
+        log.info("StoreCommentServiceImpl.getReviewCommentListByReviewId?reviewId={}, currentUserId={}", reviewId, currentUserId);
+
+        if (reviewId == null) {
+            return R.fail("评价ID不能为空");
+        }
+
+        // 查询评论列表(business_type=6,business_id=reviewId,reply_id为null表示首评)
+        LambdaQueryWrapper<StoreComment> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(StoreComment::getBusinessId, reviewId)
+                .eq(StoreComment::getBusinessType, 6)
+                .isNull(StoreComment::getReplyId) // 首评
+                .eq(StoreComment::getDeleteFlag, 0)
+                .orderByDesc(StoreComment::getCreatedTime);
+        
+        List<StoreComment> comments = this.list(queryWrapper);
+
+        // 转换为 ReviewCommentVo 并加载回复列表
+        List<ReviewCommentVo> voList = new ArrayList<>();
+        for (StoreComment comment : comments) {
+            ReviewCommentVo vo = new ReviewCommentVo();
+            vo.setId(comment.getId());
+            vo.setReviewId(comment.getBusinessId());
+            vo.setSendUserId(comment.getUserId());
+            vo.setCommentContent(comment.getCommentContent());
+            vo.setLikeCount(comment.getLikeCount());
+            vo.setCreatedTime(comment.getCreatedTime());
+            // TODO: 需要查询并设置用户名称、头像等
+            
+            // 检查是否已点赞
+            if (currentUserId != null) {
+                LambdaQueryWrapper<LifeLikeRecord> likeWrapper = new LambdaQueryWrapper<>();
+                likeWrapper.eq(LifeLikeRecord::getType, "8")
+                        .eq(LifeLikeRecord::getDianzanId, String.valueOf(currentUserId))
+                        .eq(LifeLikeRecord::getHuifuId, String.valueOf(comment.getId()))
+                        .eq(LifeLikeRecord::getDeleteFlag, 0);
+                vo.setIsLiked(lifeLikeRecordMapper.selectCount(likeWrapper) > 0 ? 1 : 0);
+            } else {
+                vo.setIsLiked(0);
+            }
+
+            // 查询回复列表
+            R<List<ReviewCommentVo>> repliesResult = getReviewReplyListByHeadId(comment.getId(), currentUserId);
+            if (repliesResult.getCode() == 200) {
+                vo.setReplies(repliesResult.getData());
+            }
+
+            voList.add(vo);
+        }
+
+        return R.data(voList);
+    }
+
+    @Override
+    public R<Boolean> likeReviewComment(ReviewCommentRequestDto requestDto) {
+        log.info("StoreCommentServiceImpl.likeReviewComment?requestDto={}", requestDto);
+
+        Integer commentId = requestDto.getCommentId();
+        Integer userId = requestDto.getUserId();
+
+        if (commentId == null) {
+            return R.fail("评论ID不能为空");
+        }
+        if (userId == null) {
+            return R.fail("用户ID不能为空");
+        }
+
+        // 验证评论是否存在
+        StoreComment comment = this.getById(commentId);
+        if (comment == null || comment.getDeleteFlag() == 1 || comment.getBusinessType() != 6) {
+            return R.fail("评论不存在或已删除");
+        }
+
+        // 检查是否已点赞(type=8 表示评论点赞)
+        LambdaQueryWrapper<LifeLikeRecord> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(LifeLikeRecord::getType, "8")
+                .eq(LifeLikeRecord::getDianzanId, String.valueOf(userId))
+                .eq(LifeLikeRecord::getHuifuId, String.valueOf(commentId))
+                .eq(LifeLikeRecord::getDeleteFlag, 0);
+        List<LifeLikeRecord> records = lifeLikeRecordMapper.selectList(queryWrapper);
+
+        if (CollectionUtils.isEmpty(records)) {
+            // 插入点赞记录
+            LifeLikeRecord likeRecord = new LifeLikeRecord();
+            likeRecord.setDianzanId(String.valueOf(userId));
+            likeRecord.setHuifuId(String.valueOf(commentId));
+            likeRecord.setType("8");
+            likeRecord.setCreatedTime(new Date());
+            likeRecord.setCreatedUserId(userId);
+            lifeLikeRecordMapper.insert(likeRecord);
+
+            // 更新评论点赞数
+            LambdaUpdateWrapper<StoreComment> updateWrapper = new LambdaUpdateWrapper<>();
+            updateWrapper.eq(StoreComment::getId, commentId);
+            updateWrapper.setSql("like_count = like_count + 1");
+            int result = storeCommentMapper.update(null, updateWrapper);
+
+            if (result > 0) {
+                return R.data(true, "点赞成功");
+            } else {
+                return R.fail("点赞失败");
+            }
+        } else {
+            return R.data(true, "已点赞");
+        }
+    }
+
+    @Override
+    public R<Boolean> cancelLikeReviewComment(ReviewCommentRequestDto requestDto) {
+        log.info("StoreCommentServiceImpl.cancelLikeReviewComment?requestDto={}", requestDto);
+
+        Integer commentId = requestDto.getCommentId();
+        Integer userId = requestDto.getUserId();
+        if (commentId == null) {
+            return R.fail("评论ID不能为空");
+        }
+        if (userId == null) {
+            return R.fail("用户ID不能为空");
+        }
+
+        // 查询点赞记录
+        LambdaQueryWrapper<LifeLikeRecord> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(LifeLikeRecord::getType, "8")
+                .eq(LifeLikeRecord::getDianzanId, String.valueOf(userId))
+                .eq(LifeLikeRecord::getHuifuId, String.valueOf(commentId))
+                .eq(LifeLikeRecord::getDeleteFlag, 0);
+        List<LifeLikeRecord> records = lifeLikeRecordMapper.selectList(queryWrapper);
+
+        if (!CollectionUtils.isEmpty(records)) {
+            // 删除点赞记录(逻辑删除)
+            for (LifeLikeRecord record : records) {
+                lifeLikeRecordMapper.deleteById(record.getId());
+            }
+
+            // 更新评论点赞数
+            LambdaUpdateWrapper<StoreComment> updateWrapper = new LambdaUpdateWrapper<>();
+            updateWrapper.eq(StoreComment::getId, commentId);
+            updateWrapper.setSql("like_count = GREATEST(0, like_count - 1)");
+            int result = storeCommentMapper.update(null, updateWrapper);
+
+            if (result > 0) {
+                return R.data(true, "取消点赞成功");
+            } else {
+                return R.fail("取消点赞失败");
+            }
+        } else {
+            return R.data(true, "未点赞");
+        }
+    }
+
+    @Override
+    public R<StoreComment> createReviewReply(ReviewReplyDto replyDto) {
+        log.info("StoreCommentServiceImpl.createReviewReply?replyDto={}", replyDto);
+
+        // 参数校验
+        if (replyDto == null) {
+            return R.fail("回复信息不能为空");
+        }
+        if (replyDto.getCommentId() == null) {
+            return R.fail("评论ID不能为空");
+        }
+        if (replyDto.getReplyContent() == null || replyDto.getReplyContent().trim().isEmpty()) {
+            return R.fail("回复内容不能为空");
+        }
+        Integer userId = replyDto.getUserId();
+        if (userId == null) {
+            return R.fail("用户ID不能为空");
+        }
+
+        // 验证首评是否存在
+        StoreComment headComment = this.getById(replyDto.getCommentId());
+        if (headComment == null || headComment.getDeleteFlag() == 1 || headComment.getBusinessType() != 6) {
+            return R.fail("评论不存在或已删除");
+        }
+        // 验证是否为首评(reply_id为null表示首评)
+        if (headComment.getReplyId() != null) {
+            return R.fail("只能回复首评");
+        }
+
+        // 创建回复
+        StoreComment reply = new StoreComment();
+        reply.setBusinessId(headComment.getBusinessId()); // 评价ID
+        reply.setBusinessType(6); // 订单评价的评论
+        reply.setUserId(userId);
+        reply.setCommentContent(replyDto.getReplyContent());
+        reply.setOrderId(headComment.getOrderId());
+        reply.setLawyerId(headComment.getLawyerId());
+        reply.setReplyId(replyDto.getCommentId()); // 指向首评ID
+        reply.setLikeCount(0);
+        reply.setCreatedUserId(userId);
+        reply.setCreatedTime(new Date());
+
+        boolean success = this.save(reply);
+        if (success) {
+            log.info("创建回复成功,回复ID={}", reply.getId());
+            return R.data(reply, "回复成功");
+        } else {
+            log.error("创建回复失败");
+            return R.fail("创建回复失败");
+        }
+    }
+
+    @Override
+    public R<List<ReviewCommentVo>> getReviewReplyListByHeadId(Integer headId, Integer currentUserId) {
+        log.info("StoreCommentServiceImpl.getReviewReplyListByHeadId?headId={}, currentUserId={}", headId, currentUserId);
+
+        if (headId == null) {
+            return R.fail("首评ID不能为空");
+        }
+
+        // 查询回复列表(business_type=6,reply_id=headId)
+        LambdaQueryWrapper<StoreComment> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(StoreComment::getReplyId, headId)
+                .eq(StoreComment::getBusinessType, 6)
+                .eq(StoreComment::getDeleteFlag, 0)
+                .orderByAsc(StoreComment::getCreatedTime);
+        
+        List<StoreComment> replies = this.list(queryWrapper);
+
+        // 转换为 ReviewCommentVo
+        List<ReviewCommentVo> voList = new ArrayList<>();
+        for (StoreComment reply : replies) {
+            ReviewCommentVo vo = new ReviewCommentVo();
+            vo.setId(reply.getId());
+            vo.setReviewId(reply.getBusinessId());
+            vo.setSendUserId(reply.getUserId());
+            vo.setCommentContent(reply.getCommentContent());
+            vo.setLikeCount(reply.getLikeCount());
+            vo.setCreatedTime(reply.getCreatedTime());
+            // TODO: 需要查询并设置用户名称、头像等
+            
+            // 检查是否已点赞
+            if (currentUserId != null) {
+                LambdaQueryWrapper<LifeLikeRecord> likeWrapper = new LambdaQueryWrapper<>();
+                likeWrapper.eq(LifeLikeRecord::getType, "8")
+                        .eq(LifeLikeRecord::getDianzanId, String.valueOf(currentUserId))
+                        .eq(LifeLikeRecord::getHuifuId, String.valueOf(reply.getId()))
+                        .eq(LifeLikeRecord::getDeleteFlag, 0);
+                vo.setIsLiked(lifeLikeRecordMapper.selectCount(likeWrapper) > 0 ? 1 : 0);
+            } else {
+                vo.setIsLiked(0);
+            }
+
+            voList.add(vo);
+        }
+
+        return R.data(voList);
+    }
+
+    @Override
+    public R<Boolean> deleteReviewReply(Integer replyId, Integer userId) {
+        log.info("StoreCommentServiceImpl.deleteReviewReply?replyId={}, userId={}", replyId, userId);
+
+        if (replyId == null) {
+            return R.fail("回复ID不能为空");
+        }
+        if (userId == null) {
+            return R.fail("用户ID不能为空");
+        }
+
+        // 查询回复
+        StoreComment reply = this.getById(replyId);
+        if (reply == null) {
+            return R.fail("回复不存在");
+        }
+        if (reply.getReplyId() == null) {
+            return R.fail("该记录不是回复");
+        }
+
+        // 验证是否为回复用户
+        if (!reply.getUserId().equals(userId)) {
+            return R.fail("只能删除自己的回复");
+        }
+
+        // 删除回复(逻辑删除)
+        boolean success = this.removeById(reply.getId());
+
+        if (success) {
+            log.info("删除回复成功,回复ID={}", replyId);
+            return R.data(true, "删除成功");
+        } else {
+            log.error("删除回复失败,回复ID={}", replyId);
+            return R.fail("删除回复失败");
+        }
+    }
+}

+ 856 - 0
alien-lawyer/src/main/java/shop/alien/lawyer/util/WXPayUtility.java

@@ -0,0 +1,856 @@
+package shop.alien.lawyer.util;
+
+import com.google.gson.*;
+import com.google.gson.annotations.Expose;
+import com.google.gson.annotations.SerializedName;
+import okhttp3.Headers;
+import okhttp3.Response;
+import okio.BufferedSource;
+import org.bouncycastle.crypto.digests.SM3Digest;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.spec.GCMParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UncheckedIOException;
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.security.*;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.spec.X509EncodedKeySpec;
+import java.time.DateTimeException;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.*;
+import java.util.Map.Entry;
+
+public class WXPayUtility {
+    private static final Gson gson = new GsonBuilder()
+            .disableHtmlEscaping()
+            .addSerializationExclusionStrategy(new ExclusionStrategy() {
+                @Override
+                public boolean shouldSkipField(FieldAttributes fieldAttributes) {
+                    final Expose expose = fieldAttributes.getAnnotation(Expose.class);
+                    return expose != null && !expose.serialize();
+                }
+
+                @Override
+                public boolean shouldSkipClass(Class<?> aClass) {
+                    return false;
+                }
+            })
+            .addDeserializationExclusionStrategy(new ExclusionStrategy() {
+                @Override
+                public boolean shouldSkipField(FieldAttributes fieldAttributes) {
+                    final Expose expose = fieldAttributes.getAnnotation(Expose.class);
+                    return expose != null && !expose.deserialize();
+                }
+
+                @Override
+                public boolean shouldSkipClass(Class<?> aClass) {
+                    return false;
+                }
+            })
+            .create();
+    private static final char[] SYMBOLS =
+            "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray();
+    private static final SecureRandom random = new SecureRandom();
+
+    /**
+     * 将 Object 转换为 JSON 字符串
+     */
+    public static String toJson(Object object) {
+        return gson.toJson(object);
+    }
+
+    /**
+     * 将 JSON 字符串解析为特定类型的实例
+     */
+    public static <T> T fromJson(String json, Class<T> classOfT) throws JsonSyntaxException {
+        return gson.fromJson(json, classOfT);
+    }
+
+    /**
+     * 从公私钥文件路径中读取文件内容
+     *
+     * @param keyPath 文件路径
+     * @return 文件内容
+     */
+    private static String readKeyStringFromPath(String keyPath) {
+        try {
+            return new String(Files.readAllBytes(Paths.get(keyPath)), StandardCharsets.UTF_8);
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    /**
+     * 读取 PKCS#8 格式的私钥字符串并加载为私钥对象
+     *
+     * @param keyString 私钥文件内容,以 -----BEGIN PRIVATE KEY----- 开头
+     * @return PrivateKey 对象
+     */
+    public static PrivateKey loadPrivateKeyFromString(String keyString) {
+        try {
+            keyString = keyString.replace("-----BEGIN PRIVATE KEY-----", "")
+                    .replace("-----END PRIVATE KEY-----", "")
+                    .replaceAll("\\s+", "");
+            return KeyFactory.getInstance("RSA").generatePrivate(
+                    new PKCS8EncodedKeySpec(Base64.getDecoder().decode(keyString)));
+        } catch (NoSuchAlgorithmException e) {
+            throw new UnsupportedOperationException(e);
+        } catch (InvalidKeySpecException e) {
+            throw new IllegalArgumentException(e);
+        }
+    }
+
+    /**
+     * 从 PKCS#8 格式的私钥文件中加载私钥
+     *
+     * @param keyPath 私钥文件路径
+     * @return PrivateKey 对象
+     */
+    public static PrivateKey loadPrivateKeyFromPath(String keyPath) {
+        return loadPrivateKeyFromString(readKeyStringFromPath(keyPath));
+    }
+
+    /**
+     * 读取 PKCS#8 格式的公钥字符串并加载为公钥对象
+     *
+     * @param keyString 公钥文件内容,以 -----BEGIN PUBLIC KEY----- 开头
+     * @return PublicKey 对象
+     */
+    public static PublicKey loadPublicKeyFromString(String keyString) {
+        try {
+            keyString = keyString.replace("-----BEGIN PUBLIC KEY-----", "")
+                    .replace("-----END PUBLIC KEY-----", "")
+                    .replaceAll("\\s+", "");
+            return KeyFactory.getInstance("RSA").generatePublic(
+                    new X509EncodedKeySpec(Base64.getDecoder().decode(keyString)));
+        } catch (NoSuchAlgorithmException e) {
+            throw new UnsupportedOperationException(e);
+        } catch (InvalidKeySpecException e) {
+            throw new IllegalArgumentException(e);
+        }
+    }
+
+    /**
+     * 从 PKCS#8 格式的公钥文件中加载公钥
+     *
+     * @param keyPath 公钥文件路径
+     * @return PublicKey 对象
+     */
+    public static PublicKey loadPublicKeyFromPath(String keyPath) {
+        return loadPublicKeyFromString(readKeyStringFromPath(keyPath));
+    }
+
+    /**
+     * 创建指定长度的随机字符串,字符集为[0-9a-zA-Z],可用于安全相关用途
+     */
+    public static String createNonce(int length) {
+        char[] buf = new char[length];
+        for (int i = 0; i < length; ++i) {
+            buf[i] = SYMBOLS[random.nextInt(SYMBOLS.length)];
+        }
+        return new String(buf);
+    }
+
+    /**
+     * 使用公钥按照 RSA_PKCS1_OAEP_PADDING 算法进行加密
+     *
+     * @param publicKey 加密用公钥对象
+     * @param plaintext 待加密明文
+     * @return 加密后密文
+     */
+    public static String encrypt(PublicKey publicKey, String plaintext) {
+        final String transformation = "RSA/ECB/OAEPWithSHA-1AndMGF1Padding";
+
+        try {
+            Cipher cipher = Cipher.getInstance(transformation);
+            cipher.init(Cipher.ENCRYPT_MODE, publicKey);
+            return Base64.getEncoder().encodeToString(cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8)));
+        } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
+            throw new IllegalArgumentException("The current Java environment does not support " + transformation, e);
+        } catch (InvalidKeyException e) {
+            throw new IllegalArgumentException("RSA encryption using an illegal publicKey", e);
+        } catch (BadPaddingException | IllegalBlockSizeException e) {
+            throw new IllegalArgumentException("Plaintext is too long", e);
+        }
+    }
+
+    public static String aesAeadDecrypt(byte[] key, byte[] associatedData, byte[] nonce,
+                                        byte[] ciphertext) {
+        final String transformation = "AES/GCM/NoPadding";
+        final String algorithm = "AES";
+        final int tagLengthBit = 128;
+
+        try {
+            Cipher cipher = Cipher.getInstance(transformation);
+            cipher.init(
+                    Cipher.DECRYPT_MODE,
+                    new SecretKeySpec(key, algorithm),
+                    new GCMParameterSpec(tagLengthBit, nonce));
+            if (associatedData != null) {
+                cipher.updateAAD(associatedData);
+            }
+            return new String(cipher.doFinal(ciphertext), StandardCharsets.UTF_8);
+        } catch (InvalidKeyException
+                 | InvalidAlgorithmParameterException
+                 | BadPaddingException
+                 | IllegalBlockSizeException
+                 | NoSuchAlgorithmException
+                 | NoSuchPaddingException e) {
+            throw new IllegalArgumentException(String.format("AesAeadDecrypt with %s Failed",
+                    transformation), e);
+        }
+    }
+
+    /**
+     * 使用私钥按照指定算法进行签名
+     *
+     * @param message    待签名串
+     * @param algorithm  签名算法,如 SHA256withRSA
+     * @param privateKey 签名用私钥对象
+     * @return 签名结果
+     */
+    public static String sign(String message, String algorithm, PrivateKey privateKey) {
+        byte[] sign;
+        try {
+            Signature signature = Signature.getInstance(algorithm);
+            signature.initSign(privateKey);
+            signature.update(message.getBytes(StandardCharsets.UTF_8));
+            sign = signature.sign();
+        } catch (NoSuchAlgorithmException e) {
+            throw new UnsupportedOperationException("The current Java environment does not support " + algorithm, e);
+        } catch (InvalidKeyException e) {
+            throw new IllegalArgumentException(algorithm + " signature uses an illegal privateKey.", e);
+        } catch (SignatureException e) {
+            throw new RuntimeException("An error occurred during the sign process.", e);
+        }
+        return Base64.getEncoder().encodeToString(sign);
+    }
+
+    /**
+     * 使用公钥按照特定算法验证签名
+     *
+     * @param message   待签名串
+     * @param signature 待验证的签名内容
+     * @param algorithm 签名算法,如:SHA256withRSA
+     * @param publicKey 验签用公钥对象
+     * @return 签名验证是否通过
+     */
+    public static boolean verify(String message, String signature, String algorithm,
+                                 PublicKey publicKey) {
+        try {
+            Signature sign = Signature.getInstance(algorithm);
+            sign.initVerify(publicKey);
+            sign.update(message.getBytes(StandardCharsets.UTF_8));
+            return sign.verify(Base64.getDecoder().decode(signature));
+        } catch (SignatureException e) {
+            return false;
+        } catch (InvalidKeyException e) {
+            throw new IllegalArgumentException("verify uses an illegal publickey.", e);
+        } catch (NoSuchAlgorithmException e) {
+            throw new UnsupportedOperationException("The current Java environment does not support" + algorithm, e);
+        }
+    }
+
+    /**
+     * 根据微信支付APIv3请求签名规则构造 Authorization 签名
+     *
+     * @param mchid               商户号
+     * @param certificateSerialNo 商户API证书序列号
+     * @param privateKey          商户API证书私钥
+     * @param method              请求接口的HTTP方法,请使用全大写表述,如 GET、POST、PUT、DELETE
+     * @param uri                 请求接口的URL
+     * @param body                请求接口的Body
+     * @return 构造好的微信支付APIv3 Authorization 头
+     */
+    public static String buildAuthorization(String mchid, String certificateSerialNo,
+                                            PrivateKey privateKey,
+                                            String method, String uri, String body) {
+        String nonce = createNonce(32);
+        long timestamp = Instant.now().getEpochSecond();
+
+        String message = String.format("%s\n%s\n%d\n%s\n%s\n", method, uri, timestamp, nonce,
+                body == null ? "" : body);
+
+        String signature = sign(message, "SHA256withRSA", privateKey);
+
+        return String.format(
+                "WECHATPAY2-SHA256-RSA2048 mchid=\"%s\",nonce_str=\"%s\",signature=\"%s\"," +
+                        "timestamp=\"%d\",serial_no=\"%s\"",
+                mchid, nonce, signature, timestamp, certificateSerialNo);
+    }
+
+    /**
+     * 计算输入流的哈希值
+     *
+     * @param inputStream 输入流
+     * @param algorithm   哈希算法名称,如 "SHA-256", "SHA-1"
+     * @return 哈希值的十六进制字符串
+     */
+    private static String calculateHash(InputStream inputStream, String algorithm) {
+        try {
+            MessageDigest digest = MessageDigest.getInstance(algorithm);
+            byte[] buffer = new byte[8192];
+            int bytesRead;
+            while ((bytesRead = inputStream.read(buffer)) != -1) {
+                digest.update(buffer, 0, bytesRead);
+            }
+            byte[] hashBytes = digest.digest();
+            StringBuilder hexString = new StringBuilder();
+            for (byte b : hashBytes) {
+                String hex = Integer.toHexString(0xff & b);
+                if (hex.length() == 1) {
+                    hexString.append('0');
+                }
+                hexString.append(hex);
+            }
+            return hexString.toString();
+        } catch (NoSuchAlgorithmException e) {
+            throw new UnsupportedOperationException(algorithm + " algorithm not available", e);
+        } catch (IOException e) {
+            throw new RuntimeException("Error reading from input stream", e);
+        }
+    }
+
+    /**
+     * 计算输入流的 SHA256 哈希值
+     *
+     * @param inputStream 输入流
+     * @return SHA256 哈希值的十六进制字符串
+     */
+    public static String sha256(InputStream inputStream) {
+        return calculateHash(inputStream, "SHA-256");
+    }
+
+    /**
+     * 计算输入流的 SHA1 哈希值
+     *
+     * @param inputStream 输入流
+     * @return SHA1 哈希值的十六进制字符串
+     */
+    public static String sha1(InputStream inputStream) {
+        return calculateHash(inputStream, "SHA-1");
+    }
+
+    /**
+     * 计算输入流的 SM3 哈希值
+     *
+     * @param inputStream 输入流
+     * @return SM3 哈希值的十六进制字符串
+     */
+    public static String sm3(InputStream inputStream) {
+        // 确保Bouncy Castle Provider已注册
+        if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
+            Security.addProvider(new BouncyCastleProvider());
+        }
+
+        try {
+            SM3Digest digest = new SM3Digest();
+            byte[] buffer = new byte[8192];
+            int bytesRead;
+            while ((bytesRead = inputStream.read(buffer)) != -1) {
+                digest.update(buffer, 0, bytesRead);
+            }
+            byte[] hashBytes = new byte[digest.getDigestSize()];
+            digest.doFinal(hashBytes, 0);
+
+            StringBuilder hexString = new StringBuilder();
+            for (byte b : hashBytes) {
+                String hex = Integer.toHexString(0xff & b);
+                if (hex.length() == 1) {
+                    hexString.append('0');
+                }
+                hexString.append(hex);
+            }
+            return hexString.toString();
+        } catch (IOException e) {
+            throw new RuntimeException("Error reading from input stream", e);
+        }
+    }
+
+    /**
+     * 对参数进行 URL 编码
+     *
+     * @param content 参数内容
+     * @return 编码后的内容
+     */
+    public static String urlEncode(String content) {
+        try {
+            return URLEncoder.encode(content, StandardCharsets.UTF_8.name());
+        } catch (UnsupportedEncodingException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * 对参数Map进行 URL 编码,生成 QueryString
+     *
+     * @param params Query参数Map
+     * @return QueryString
+     */
+    public static String urlEncode(Map<String, Object> params) {
+        if (params == null || params.isEmpty()) {
+            return "";
+        }
+
+        StringBuilder result = new StringBuilder();
+        for (Entry<String, Object> entry : params.entrySet()) {
+            if (entry.getValue() == null) {
+                continue;
+            }
+
+            String key = entry.getKey();
+            Object value = entry.getValue();
+            if (value instanceof List) {
+                List<?> list = (List<?>) entry.getValue();
+                for (Object temp : list) {
+                    appendParam(result, key, temp);
+                }
+            } else {
+                appendParam(result, key, value);
+            }
+        }
+        return result.toString();
+    }
+
+    /**
+     * 将键值对 放入返回结果
+     *
+     * @param result 返回的query string
+     * @param key 属性
+     * @param value 属性值
+     */
+    private static void appendParam(StringBuilder result, String key, Object value) {
+        if (result.length() > 0) {
+            result.append("&");
+        }
+
+        String valueString;
+        // 如果是基本类型、字符串或枚举,直接转换;如果是对象,序列化为JSON
+        if (value instanceof String || value instanceof Number ||
+                value instanceof Boolean || value instanceof Enum) {
+            valueString = value.toString();
+        } else {
+            valueString = toJson(value);
+        }
+
+        result.append(key)
+                .append("=")
+                .append(urlEncode(valueString));
+    }
+
+    /**
+     * 从应答中提取 Body
+     *
+     * @param response HTTP 请求应答对象
+     * @return 应答中的Body内容,Body为空时返回空字符串
+     */
+    public static String extractBody(Response response) {
+        if (response.body() == null) {
+            return "";
+        }
+
+        try {
+            BufferedSource source = response.body().source();
+            return source.readUtf8();
+        } catch (IOException e) {
+            throw new RuntimeException(String.format("An error occurred during reading response body. " +
+                    "Status: %d", response.code()), e);
+        }
+    }
+
+    /**
+     * 根据微信支付APIv3应答验签规则对应答签名进行验证,验证不通过时抛出异常
+     *
+     * @param wechatpayPublicKeyId 微信支付公钥ID
+     * @param wechatpayPublicKey   微信支付公钥对象
+     * @param headers              微信支付应答 Header 列表
+     * @param body                 微信支付应答 Body
+     */
+    public static void validateResponse(String wechatpayPublicKeyId, PublicKey wechatpayPublicKey,
+                                        Headers headers,
+                                        String body) {
+        String timestamp = headers.get("Wechatpay-Timestamp");
+        String requestId = headers.get("Request-ID");
+        try {
+            Instant responseTime = Instant.ofEpochSecond(Long.parseLong(timestamp));
+            // 拒绝过期请求
+            if (Duration.between(responseTime, Instant.now()).abs().toMinutes() >= 5) {
+                throw new IllegalArgumentException(
+                        String.format("Validate response failed, timestamp[%s] is expired, request-id[%s]",
+                                timestamp, requestId));
+            }
+        } catch (DateTimeException | NumberFormatException e) {
+            throw new IllegalArgumentException(
+                    String.format("Validate response failed, timestamp[%s] is invalid, request-id[%s]",
+                            timestamp, requestId));
+        }
+        String serialNumber = headers.get("Wechatpay-Serial");
+        if (!Objects.equals(serialNumber, wechatpayPublicKeyId)) {
+            throw new IllegalArgumentException(
+                    String.format("Validate response failed, Invalid Wechatpay-Serial, Local: %s, Remote: " +
+                            "%s", wechatpayPublicKeyId, serialNumber));
+        }
+
+        String signature = headers.get("Wechatpay-Signature");
+        String message = String.format("%s\n%s\n%s\n", timestamp, headers.get("Wechatpay-Nonce"),
+                body == null ? "" : body);
+
+        boolean success = verify(message, signature, "SHA256withRSA", wechatpayPublicKey);
+        if (!success) {
+            throw new IllegalArgumentException(
+                    String.format("Validate response failed,the WechatPay signature is incorrect.%n"
+                                    + "Request-ID[%s]\tresponseHeader[%s]\tresponseBody[%.1024s]",
+                            headers.get("Request-ID"), headers, body));
+        }
+    }
+
+    /**
+     * 根据微信支付APIv3通知验签规则对通知签名进行验证,验证不通过时抛出异常
+     * @param wechatpayPublicKeyId 微信支付公钥ID
+     * @param wechatpayPublicKey 微信支付公钥对象
+     * @param headers 微信支付通知 Header 列表
+     * @param body 微信支付通知 Body
+     */
+    public static void validateNotification(String wechatpayPublicKeyId,
+                                            PublicKey wechatpayPublicKey, Headers headers,
+                                            String body) {
+        String timestamp = headers.get("Wechatpay-Timestamp");
+        try {
+            Instant responseTime = Instant.ofEpochSecond(Long.parseLong(timestamp));
+            // 拒绝过期请求
+            if (Duration.between(responseTime, Instant.now()).abs().toMinutes() >= 5) {
+                throw new IllegalArgumentException(
+                        String.format("Validate notification failed, timestamp[%s] is expired", timestamp));
+            }
+        } catch (DateTimeException | NumberFormatException e) {
+            throw new IllegalArgumentException(
+                    String.format("Validate notification failed, timestamp[%s] is invalid", timestamp));
+        }
+        String serialNumber = headers.get("Wechatpay-Serial");
+        if (!Objects.equals(serialNumber, wechatpayPublicKeyId)) {
+            throw new IllegalArgumentException(
+                    String.format("Validate notification failed, Invalid Wechatpay-Serial, Local: %s, " +
+                                    "Remote: %s",
+                            wechatpayPublicKeyId,
+                            serialNumber));
+        }
+
+        String signature = headers.get("Wechatpay-Signature");
+        String message = String.format("%s\n%s\n%s\n", timestamp, headers.get("Wechatpay-Nonce"),
+                body == null ? "" : body);
+
+        boolean success = verify(message, signature, "SHA256withRSA", wechatpayPublicKey);
+        if (!success) {
+            throw new IllegalArgumentException(
+                    String.format("Validate notification failed, WechatPay signature is incorrect.\n"
+                                    + "responseHeader[%s]\tresponseBody[%.1024s]",
+                            headers, body));
+        }
+    }
+
+    /**
+     * 对微信支付通知进行签名验证、解析,同时将业务数据解密。验签名失败、解析失败、解密失败时抛出异常
+     * @param apiv3Key 商户的 APIv3 Key
+     * @param wechatpayPublicKeyId 微信支付公钥ID
+     * @param wechatpayPublicKey   微信支付公钥对象
+     * @param headers              微信支付请求 Header 列表
+     * @param body                 微信支付请求 Body
+     * @return 解析后的通知内容,解密后的业务数据可以使用 Notification.getPlaintext() 访问
+     */
+    public static Notification parseNotification(String apiv3Key, String wechatpayPublicKeyId,
+                                                 PublicKey wechatpayPublicKey, Headers headers,
+                                                 String body) {
+        validateNotification(wechatpayPublicKeyId, wechatpayPublicKey, headers, body);
+        Notification notification = gson.fromJson(body, Notification.class);
+        notification.decrypt(apiv3Key);
+        return notification;
+    }
+
+    /**
+     * 微信支付API错误异常,发送HTTP请求成功,但返回状态码不是 2XX 时抛出本异常
+     */
+    public static class ApiException extends RuntimeException {
+        private static final long serialVersionUID = 2261086748874802175L;
+
+        private final int statusCode;
+        private final String body;
+        private final Headers headers;
+        private final String errorCode;
+        private final String errorMessage;
+
+        public ApiException(int statusCode, String body, Headers headers) {
+            super(String.format("微信支付API访问失败,StatusCode: [%s], Body: [%s], Headers: [%s]", statusCode,
+                    body, headers));
+            this.statusCode = statusCode;
+            this.body = body;
+            this.headers = headers;
+
+            if (body != null && !body.isEmpty()) {
+                JsonElement code;
+                JsonElement message;
+
+                try {
+                    JsonObject jsonObject = gson.fromJson(body, JsonObject.class);
+                    code = jsonObject.get("code");
+                    message = jsonObject.get("message");
+                } catch (JsonSyntaxException ignored) {
+                    code = null;
+                    message = null;
+                }
+                this.errorCode = code == null ? null : code.getAsString();
+                this.errorMessage = message == null ? null : message.getAsString();
+            } else {
+                this.errorCode = null;
+                this.errorMessage = null;
+            }
+        }
+
+        /**
+         * 获取 HTTP 应答状态码
+         */
+        public int getStatusCode() {
+            return statusCode;
+        }
+
+        /**
+         * 获取 HTTP 应答包体内容
+         */
+        public String getBody() {
+            return body;
+        }
+
+        /**
+         * 获取 HTTP 应答 Header
+         */
+        public Headers getHeaders() {
+            return headers;
+        }
+
+        /**
+         * 获取 错误码 (错误应答中的 code 字段)
+         */
+        public String getErrorCode() {
+            return errorCode;
+        }
+
+        /**
+         * 获取 错误消息 (错误应答中的 message 字段)
+         */
+        public String getErrorMessage() {
+            return errorMessage;
+        }
+    }
+
+    public static class Notification {
+        @SerializedName("id")
+        private String id;
+        @SerializedName("create_time")
+        private String createTime;
+        @SerializedName("event_type")
+        private String eventType;
+        @SerializedName("resource_type")
+        private String resourceType;
+        @SerializedName("summary")
+        private String summary;
+        @SerializedName("resource")
+        private Resource resource;
+        private String plaintext;
+
+        public String getId() {
+            return id;
+        }
+
+        public String getCreateTime() {
+            return createTime;
+        }
+
+        public String getEventType() {
+            return eventType;
+        }
+
+        public String getResourceType() {
+            return resourceType;
+        }
+
+        public String getSummary() {
+            return summary;
+        }
+
+        public Resource getResource() {
+            return resource;
+        }
+
+        /**
+         * 获取解密后的业务数据(JSON字符串,需要自行解析)
+         */
+        public String getPlaintext() {
+            return plaintext;
+        }
+
+        private void validate() {
+            if (resource == null) {
+                throw new IllegalArgumentException("Missing required field `resource` in notification");
+            }
+            resource.validate();
+        }
+
+        /**
+         * 使用 APIv3Key 对通知中的业务数据解密,解密结果可以通过 getPlainText 访问。
+         * 外部拿到的 Notification 一定是解密过的,因此本方法没有设置为 public
+         * @param apiv3Key 商户APIv3 Key
+         */
+        private void decrypt(String apiv3Key) {
+            validate();
+
+            plaintext = aesAeadDecrypt(
+                    apiv3Key.getBytes(StandardCharsets.UTF_8),
+                    resource.associatedData.getBytes(StandardCharsets.UTF_8),
+                    resource.nonce.getBytes(StandardCharsets.UTF_8),
+                    Base64.getDecoder().decode(resource.ciphertext)
+            );
+        }
+
+        public static class Resource {
+            @SerializedName("algorithm")
+            private String algorithm;
+
+            @SerializedName("ciphertext")
+            private String ciphertext;
+
+            @SerializedName("associated_data")
+            private String associatedData;
+
+            @SerializedName("nonce")
+            private String nonce;
+
+            @SerializedName("original_type")
+            private String originalType;
+
+            public String getAlgorithm() {
+                return algorithm;
+            }
+
+            public String getCiphertext() {
+                return ciphertext;
+            }
+
+            public String getAssociatedData() {
+                return associatedData;
+            }
+
+            public String getNonce() {
+                return nonce;
+            }
+
+            public String getOriginalType() {
+                return originalType;
+            }
+
+            private void validate() {
+                if (algorithm == null || algorithm.isEmpty()) {
+                    throw new IllegalArgumentException("Missing required field `algorithm` in Notification" +
+                            ".Resource");
+                }
+                if (!Objects.equals(algorithm, "AEAD_AES_256_GCM")) {
+                    throw new IllegalArgumentException(String.format("Unsupported `algorithm`[%s] in " +
+                            "Notification.Resource", algorithm));
+                }
+
+                if (ciphertext == null || ciphertext.isEmpty()) {
+                    throw new IllegalArgumentException("Missing required field `ciphertext` in Notification" +
+                            ".Resource");
+                }
+
+                if (associatedData == null || associatedData.isEmpty()) {
+                    throw new IllegalArgumentException("Missing required field `associatedData` in " +
+                            "Notification.Resource");
+                }
+
+                if (nonce == null || nonce.isEmpty()) {
+                    throw new IllegalArgumentException("Missing required field `nonce` in Notification" +
+                            ".Resource");
+                }
+
+                if (originalType == null || originalType.isEmpty()) {
+                    throw new IllegalArgumentException("Missing required field `originalType` in " +
+                            "Notification.Resource");
+                }
+            }
+        }
+    }
+    /**
+     * 根据文件名获取对应的Content-Type
+     * @param fileName 文件名
+     * @return Content-Type字符串
+     */
+    public static String getContentTypeByFileName(String fileName) {
+        if (fileName == null || fileName.isEmpty()) {
+            return "application/octet-stream";
+        }
+
+        // 获取文件扩展名
+        String extension = "";
+        int lastDotIndex = fileName.lastIndexOf('.');
+        if (lastDotIndex > 0 && lastDotIndex < fileName.length() - 1) {
+            extension = fileName.substring(lastDotIndex + 1).toLowerCase();
+        }
+
+        // 常见文件类型映射
+        Map<String, String> contentTypeMap = new HashMap<>();
+        // 图片类型
+        contentTypeMap.put("png", "image/png");
+        contentTypeMap.put("jpg", "image/jpeg");
+        contentTypeMap.put("jpeg", "image/jpeg");
+        contentTypeMap.put("gif", "image/gif");
+        contentTypeMap.put("bmp", "image/bmp");
+        contentTypeMap.put("webp", "image/webp");
+        contentTypeMap.put("svg", "image/svg+xml");
+        contentTypeMap.put("ico", "image/x-icon");
+
+        // 文档类型
+        contentTypeMap.put("pdf", "application/pdf");
+        contentTypeMap.put("doc", "application/msword");
+        contentTypeMap.put("docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document");
+        contentTypeMap.put("xls", "application/vnd.ms-excel");
+        contentTypeMap.put("xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
+        contentTypeMap.put("ppt", "application/vnd.ms-powerpoint");
+        contentTypeMap.put("pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation");
+
+        // 文本类型
+        contentTypeMap.put("txt", "text/plain");
+        contentTypeMap.put("html", "text/html");
+        contentTypeMap.put("css", "text/css");
+        contentTypeMap.put("js", "application/javascript");
+        contentTypeMap.put("json", "application/json");
+        contentTypeMap.put("xml", "application/xml");
+        contentTypeMap.put("csv", "text/csv");
+
+        // 音视频类型
+        contentTypeMap.put("mp3", "audio/mpeg");
+        contentTypeMap.put("wav", "audio/wav");
+        contentTypeMap.put("mp4", "video/mp4");
+        contentTypeMap.put("avi", "video/x-msvideo");
+        contentTypeMap.put("mov", "video/quicktime");
+
+        // 压缩文件类型
+        contentTypeMap.put("zip", "application/zip");
+        contentTypeMap.put("rar", "application/x-rar-compressed");
+        contentTypeMap.put("7z", "application/x-7z-compressed");
+
+
+        return contentTypeMap.getOrDefault(extension, "application/octet-stream");
+    }
+}

+ 1 - 1
alien-lawyer/src/main/resources/bootstrap.yml

@@ -1,3 +1,3 @@
 spring:
   profiles:
-    active: dev
+    active: test

+ 7 - 1
alien-second/src/main/java/shop/alien/second/service/impl/SecondGoodsAuditServiceImpl.java

@@ -66,6 +66,12 @@ public class SecondGoodsAuditServiceImpl implements SecondGoodsAuditService {
     private boolean videoModerationBlockOnFailure;
 
     /**
+     * AI审核结果查询接口地址
+     */
+    @Value("${audit.result-url:http://192.168.2.250:9100/ai/auto-review/api/v1/audit_task/getResult}")
+    private String aiAuditResultUrl;
+
+    /**
      * 视频审核服务,用于处理商品中视频内容的审核
      */
     private final VideoModerationService videoModerationService;
@@ -638,7 +644,7 @@ public class SecondGoodsAuditServiceImpl implements SecondGoodsAuditService {
      * @return success-审核通过, reject-审核拒绝, pending-处理中, fail-处理失败
      */
     private String processAiTaskResult(SecondAiTask task, String accessToken) {
-        String resultUrl = "http://192.168.2.250:9000/ai/auto-review/api/v1/audit_task/getResult?task_id=" + task.getTaskId();
+        String resultUrl = aiAuditResultUrl + "?task_id=" + task.getTaskId();
         try {
             org.springframework.http.HttpHeaders headers = new org.springframework.http.HttpHeaders();
             headers.set("Authorization", "Bearer " + accessToken);

+ 9 - 2
alien-second/src/main/java/shop/alien/second/util/AiTaskUtils.java

@@ -5,6 +5,7 @@ import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.collections4.map.HashedMap;
 import org.springframework.beans.factory.annotation.Value;
+import org.springframework.cloud.context.config.annotation.RefreshScope;
 import org.springframework.http.*;
 import org.springframework.stereotype.Component;
 import org.springframework.util.LinkedMultiValueMap;
@@ -20,12 +21,13 @@ import java.util.Map;
 @Slf4j
 @Component
 @RequiredArgsConstructor
+@RefreshScope
 public class AiTaskUtils {
 
     private final RestTemplate restTemplate;
 
 //    @Value("${third-party-login.base-url:http://192.168.2.250:9000/ai/user-auth-core/api/v1/auth/login}")
-    private String loginUrl = "http://192.168.2.250:9000/ai/user-auth-core/api/v1/auth/login";
+//    private String loginUrl = "http://192.168.2.250:9000/ai/user-auth-core/api/v1/auth/login";
 
     @Value("${third-party-user-name.base-url:UdUser}")
     private String userName;
@@ -33,8 +35,13 @@ public class AiTaskUtils {
     @Value("${third-party-pass-word.base-url:123456}")
     private String passWord;
 
-    private String auditTaskUrl = "http://192.168.2.250:9000/ai/auto-review/api/v1/audit_task/product";
 
+    @Value("${third-party-login.base-url:http://192.168.2.250:9100/ai/user-auth-core/api/v1/auth/login}")
+    private String loginUrl;
+
+    @Value("${audit.auditTaskUrl:http://192.168.2.250:9100/ai/auto-review/api/v1/audit_task/product}")
+    private String auditTaskUrl;
+//    private String auditTaskUrl = "http://192.168.2.250:9000/ai/auto-review/api/v1/audit_task/product";
 
 
     private String auditTaskResultUrl = "http://192.168.2.250:9000/ai/task-core/api/v1/audit_task/getResult";

+ 4 - 3
alien-second/src/main/java/shop/alien/second/util/AiUserViolationUtils.java

@@ -33,8 +33,8 @@ public class AiUserViolationUtils {
     private final LifeUserMapper lifeUserMapper;
     private final LifeMessageMapper lifeMessageMapper;
 
-    //    @Value("${third-party-login.base-url:http://192.168.2.250:9000/ai/user-auth-core/api/v1/auth/login}")
-    private String loginUrl = "http://192.168.2.250:9000/ai/user-auth-core/api/v1/auth/login";
+    @Value("${third-party-login.base-url:http://192.168.2.250:9000/ai/user-auth-core/api/v1/auth/login}")
+    private String loginUrl;
 
     @Value("${third-party-user-name.base-url:UdUser}")
     private String userName;
@@ -42,7 +42,8 @@ public class AiUserViolationUtils {
     @Value("${third-party-pass-word.base-url:123456}")
     private String passWord;
 
-    private String userComplaintRecordUrl = "http://192.168.2.250:9000/ai/auto-review/api/v1/user_complaint_record/submit";
+    @Value("${third-party-userComplaintRecordUrl.base-url:http://192.168.2.250:9000/ai/auto-review/api/v1/user_complaint_record/submit}")
+    private String userComplaintRecordUrl;
 
 
     /**

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

@@ -109,7 +109,7 @@ public class AiAuditController {
 
     private R<JSONObject> getAiAuditContentCheck(String accessToken, String text) {
         JSONObject data = new JSONObject();
-        data.put("is_trade_related", true);
+        data.put("is_compliant", true);
 
         // 初始化请求体Map
         Map<String, Object> requestBody = new HashMap<>();

+ 72 - 0
alien-store/src/main/java/shop/alien/store/controller/AiChatController.java

@@ -0,0 +1,72 @@
+package shop.alien.store.controller;
+
+
+import com.alibaba.fastjson2.JSONObject;
+import io.swagger.annotations.Api;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.util.StringUtils;
+import org.springframework.web.bind.annotation.CrossOrigin;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.client.RestTemplate;
+import shop.alien.store.util.ai.AiAuthTokenUtil;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@Slf4j
+@Api(tags = {"ai聊天"})
+@CrossOrigin
+@RestController
+@RequestMapping("/aiChat")
+@RequiredArgsConstructor
+public class AiChatController {
+
+    private final AiAuthTokenUtil aiAuthTokenUtil;
+
+    private final RestTemplate restTemplate;
+
+    @Value("${third-party-ai-chat.base-url:http://192.168.2.250:9000/ai/life-manager/api/v1/question_classification/classify_and_route}")
+    private String aiChatUrl;
+
+
+    /**
+     * 调用 AI 服务,获取聊天结果
+     *
+     * @return 聊天结果
+     */
+    @RequestMapping("/aiChatResult")
+    public ResponseEntity<String> aiChat(@RequestParam("question") String question){
+        String accessToken = aiAuthTokenUtil.getAccessToken();
+        JSONObject data = new JSONObject();
+        if (!StringUtils.hasText(accessToken)) {
+            data.put("fail","登录失败");
+            return ResponseEntity.badRequest().body(data.toJSONString());
+        }
+
+        // 初始化请求体Map
+        Map<String, Object> requestBody = new HashMap<>();
+        requestBody.put("question", question);
+        HttpHeaders aiHeaders = new HttpHeaders();
+        aiHeaders.setContentType(MediaType.APPLICATION_JSON);
+        aiHeaders.set("Authorization", "Bearer " + accessToken);
+//        aiHeaders.set("Authorization", "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1cHN0b3JlQGFkbWluLmNvbSIsImlkIjo2LCJ0aW1lIjoxNzYyOTI1NDAzLjY1MTY5MjZ9.07lz8Ox2cGC28UCmqcKCt5R6Rfwtgs-Eiu0ttgWRxws");
+
+        HttpEntity<Map<String, Object>> request = new HttpEntity<>(requestBody, aiHeaders);
+
+        try {
+            ResponseEntity<String> stringResponseEntity = restTemplate.postForEntity(aiChatUrl, request, String.class);
+            return stringResponseEntity;
+        } catch (Exception e) {
+            log.error("调用AI文本审核接口 接口异常------", e);
+        }
+        return  ResponseEntity.badRequest().body(null);
+    }
+}

+ 26 - 3
alien-store/src/main/java/shop/alien/store/controller/AliController.java

@@ -108,9 +108,9 @@ public class AliController {
                         .size();
             }
         }
-        if (size > 0) {
-            return R.fail("该身份证已实名认证过");
-        }
+//        if (size > 0) {
+//            return R.fail("该身份证已实名认证过");
+//        }
         if (aliPayConfig.getIdInfo(name, idCard)) {
             return R.success("身份验证成功");
         }
@@ -157,6 +157,29 @@ public class AliController {
         return R.fail("短信发送失败");
     }
 
+
+
+    @ApiOperation("发送律师账号密码短信")
+    @ApiOperationSupport(order = 5)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "phone", value = "手机号", dataType = "String", paramType = "query", required = true),
+            @ApiImplicitParam(name = "userName", value = "用户名", dataType = "String", paramType = "query", required = true),
+            @ApiImplicitParam(name = "password", value = "密码", dataType = "String", paramType = "query", required = true)
+    })
+    @GetMapping("/sendLawyerSms")
+    public R sendLawyerSms(
+            @RequestParam("phone") String phone,
+            @RequestParam("userName") String userName,
+            @RequestParam("password") String password
+    ) {
+        Integer result = aliSmsConfig.sendLawyerSms(phone, userName, password);
+        log.info("AliController.sendLawyerSms?phone={}&userName={}&result={}", phone, userName, result);
+        if (result != null) {
+            return R.data("短信发送成功");
+        }
+        return R.fail("短信发送失败");
+    }
+
     @ApiOperation("校验短信验证码")
     @ApiOperationSupport(order = 4)
     @ApiImplicitParams({

+ 128 - 0
alien-store/src/main/java/shop/alien/store/controller/CommentAppealController.java

@@ -0,0 +1,128 @@
+package shop.alien.store.controller;
+
+import com.alibaba.nacos.client.naming.utils.RandomUtils;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import io.swagger.annotations.*;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.*;
+import shop.alien.entity.result.R;
+import shop.alien.entity.store.CommentAppeal;
+import shop.alien.entity.store.dto.AuditAppealRequestDto;
+import shop.alien.entity.store.vo.CommentAppealVo;
+import shop.alien.store.service.CommentAppealService;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 评论申诉表 前端控制器
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Slf4j
+@Api(tags = {"评论申诉管理"})
+@ApiSort(20)
+@CrossOrigin
+@RestController
+@RequestMapping("/commentAppeal")
+@RequiredArgsConstructor
+public class CommentAppealController {
+
+    private final CommentAppealService commentAppealService;
+
+    @ApiOperation(value = "提交申诉", notes = "用户提交评论申诉")
+    @ApiOperationSupport(order = 1)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "commentAppeal", value = "申诉信息", dataType = "CommentAppeal", paramType = "body", required = true)
+    })
+    @PostMapping("/submit")
+    public R<CommentAppeal> submitAppeal(@RequestBody CommentAppeal commentAppeal) {
+        log.info("CommentAppealController.submitAppeal?commentAppeal={}", commentAppeal);
+        //申诉时间
+        commentAppeal.setAppealTime(new Date());
+        commentAppeal.setAppealNumber(generateOrderNumber());
+        return commentAppealService.submitAppeal(commentAppeal);
+    }
+
+    @ApiOperation(value = "审核申诉", notes = "管理员审核申诉,状态:1-已通过,2-已驳回。审核通过时会删除评价及回复,并发送通知给相关用户")
+    @ApiOperationSupport(order = 2)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "auditAppealRequestDto", value = "审核申诉请求", dataType = "AuditAppealRequestDto", paramType = "body", required = true)
+    })
+    @PostMapping("/audit")
+    public R<Boolean> auditAppeal(@RequestBody AuditAppealRequestDto auditAppealRequestDto) {
+        log.info("CommentAppealController.auditAppeal?auditAppealRequestDto={}", auditAppealRequestDto);
+        return commentAppealService.auditAppeal(auditAppealRequestDto.getId(), auditAppealRequestDto.getStatus(), auditAppealRequestDto.getReviewReasons());
+    }
+
+    @ApiOperation(value = "中台申诉列表", notes = "分页查询申诉列表,支持多条件筛选,包含订单编号、用户信息、律师信息等")
+    @ApiOperationSupport(order = 3)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "pageNum", value = "页数(默认1)", dataType = "int", paramType = "query"),
+            @ApiImplicitParam(name = "pageSize", value = "页容(默认10)", dataType = "int", paramType = "query"),
+            @ApiImplicitParam(name = "status", value = "申诉状态:0-待处理,1-已通过,2-已驳回", dataType = "Integer", paramType = "query"),
+            @ApiImplicitParam(name = "orderNumber", value = "评价单号(模糊查询)", dataType = "String", paramType = "query"),
+            @ApiImplicitParam(name = "userName", value = "用户姓名(模糊查询)", dataType = "String", paramType = "query"),
+            @ApiImplicitParam(name = "userPhone", value = "用户电话(模糊查询)", dataType = "String", paramType = "query"),
+            @ApiImplicitParam(name = "lawyerName", value = "律师姓名(模糊查询)", dataType = "String", paramType = "query"),
+            @ApiImplicitParam(name = "lawyerPhone", value = "律师电话(模糊查询)", dataType = "String", paramType = "query"),
+            @ApiImplicitParam(name = "startTime", value = "开始时间(格式:yyyy-MM-dd HH:mm:ss)", dataType = "String", paramType = "query"),
+            @ApiImplicitParam(name = "endTime", value = "结束时间(格式:yyyy-MM-dd HH:mm:ss)", dataType = "String", paramType = "query")
+    })
+    @GetMapping("/getPage")
+    public R<IPage<CommentAppealVo>> getAppealPage(
+            @RequestParam int pageNum,
+            @RequestParam int pageSize,
+            @RequestParam(required = false) Integer status,
+            @RequestParam(required = false) String orderNumber,
+            @RequestParam(required = false) String userName,
+            @RequestParam(required = false) String userPhone,
+            @RequestParam(required = false) String lawyerName,
+            @RequestParam(required = false) String lawyerPhone,
+            @RequestParam(required = false) String startTime,
+            @RequestParam(required = false) String endTime) {
+        log.info("CommentAppealController.getAppealPage?pageNum={}, pageSize={}, status={}, orderNumber={}, userName={}, userPhone={}, lawyerName={}, lawyerPhone={}, startTime={}, endTime={}",
+                pageNum, pageSize, status, orderNumber, userName, userPhone, lawyerName, lawyerPhone, startTime, endTime);
+        return commentAppealService.getAppealPage(pageNum, pageSize, status, orderNumber, userName, userPhone, lawyerName, lawyerPhone, startTime, endTime);
+    }
+
+    @ApiOperation(value = "获取申诉详情", notes = "根据申诉ID获取申诉详情,包含评价和用户信息")
+    @ApiOperationSupport(order = 4)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "id", value = "申诉ID", dataType = "Integer", paramType = "query", required = true)
+    })
+    @GetMapping("/getDetail")
+    public R<CommentAppealVo> getAppealDetail(@RequestParam("id") Integer id) {
+        log.info("CommentAppealController.getAppealDetail?id={}", id);
+        return commentAppealService.getAppealDetail(id);
+    }
+
+    @ApiOperation(value = "申诉历史列表", notes = "分页查询申诉历史列表,支持按状态和评论ID筛选,包含评价和用户信息")
+    @ApiOperationSupport(order = 5)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "pageNum", value = "页数(默认1)", dataType = "int", paramType = "query"),
+            @ApiImplicitParam(name = "pageSize", value = "页容(默认10)", dataType = "int", paramType = "query"),
+            @ApiImplicitParam(name = "status", value = "申诉状态:0-待处理,1-已通过,2-已驳回", dataType = "Integer", paramType = "query"),
+            @ApiImplicitParam(name = "lawyerUserId", value = "律师用户ID", dataType = "Integer", paramType = "query")
+    })
+    @GetMapping("/getAppealHistory")
+    public R<List<CommentAppealVo>> getAppealHistory(
+            @RequestParam int pageNum,
+            @RequestParam int pageSize,
+            @RequestParam(required = false) Integer status,
+            @RequestParam(required = false) Integer lawyerUserId) {
+        log.info("CommentAppealController.getAppealHistory?pageNum={}, pageSize={}, status={}, lawyerUserId={}",
+                pageNum, pageSize, status, lawyerUserId);
+        List<CommentAppealVo> appealList = commentAppealService.getAppealHistory(pageNum, pageSize, status, lawyerUserId);
+        return R.data(appealList, "查询成功");
+    }
+
+    private String generateOrderNumber() {
+        String dateStr = new SimpleDateFormat("yyyyMMdd").format(new Date());
+        String randomStr = String.format("%05d", RandomUtils.nextInt(100000));
+        return "LAW" + dateStr + randomStr;
+    }
+}

Энэ ялгаанд хэт олон файл өөрчлөгдсөн тул зарим файлыг харуулаагүй болно