Procházet zdrojové kódy

lawyer服务中台功能迁移 store服务中

qxy před 1 týdnem
rodič
revize
aa72ba4cee
24 změnil soubory, kde provedl 5236 přidání a 43 odebrání
  1. 128 0
      alien-store/src/main/java/shop/alien/store/controller/CommentAppealController.java
  2. 200 0
      alien-store/src/main/java/shop/alien/store/controller/LawFirmReconciliationController.java
  3. 25 10
      alien-store/src/main/java/shop/alien/store/controller/LawyerLegalProblemScenarioController.java
  4. 62 0
      alien-store/src/main/java/shop/alien/store/controller/LawyerUserController.java
  5. 224 0
      alien-store/src/main/java/shop/alien/store/controller/LawyerUserViolationController.java
  6. 17 23
      alien-store/src/main/java/shop/alien/store/controller/StoreLawFirmController.java
  7. 73 0
      alien-store/src/main/java/shop/alien/store/service/CommentAppealService.java
  8. 147 0
      alien-store/src/main/java/shop/alien/store/service/LawFirmReconciliationService.java
  9. 17 0
      alien-store/src/main/java/shop/alien/store/service/LawyerLegalProblemScenarioService.java
  10. 37 0
      alien-store/src/main/java/shop/alien/store/service/LawyerUserService.java
  11. 71 0
      alien-store/src/main/java/shop/alien/store/service/LawyerUserViolationService.java
  12. 144 0
      alien-store/src/main/java/shop/alien/store/service/OrderReviewService.java
  13. 87 0
      alien-store/src/main/java/shop/alien/store/service/ReviewCommentService.java
  14. 13 0
      alien-store/src/main/java/shop/alien/store/service/StoreLawFirmPaymentService.java
  15. 60 0
      alien-store/src/main/java/shop/alien/store/service/StoreLawFirmService.java
  16. 562 0
      alien-store/src/main/java/shop/alien/store/service/impl/CommentAppealServiceImpl.java
  17. 329 0
      alien-store/src/main/java/shop/alien/store/service/impl/LawFirmReconciliationServiceImpl.java
  18. 152 10
      alien-store/src/main/java/shop/alien/store/service/impl/LawyerLegalProblemScenarioServiceImpl.java
  19. 158 0
      alien-store/src/main/java/shop/alien/store/service/impl/LawyerUserServiceImpl.java
  20. 1473 0
      alien-store/src/main/java/shop/alien/store/service/impl/LawyerUserViolationServiceImpl.java
  21. 685 0
      alien-store/src/main/java/shop/alien/store/service/impl/OrderReviewServiceImpl.java
  22. 435 0
      alien-store/src/main/java/shop/alien/store/service/impl/ReviewCommentServiceImpl.java
  23. 27 0
      alien-store/src/main/java/shop/alien/store/service/impl/StoreLawFirmPaymentServiceImpl.java
  24. 110 0
      alien-store/src/main/java/shop/alien/store/service/impl/StoreLawFirmServiceImpl.java

+ 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;
+    }
+}

+ 200 - 0
alien-store/src/main/java/shop/alien/store/controller/LawFirmReconciliationController.java

@@ -0,0 +1,200 @@
+package shop.alien.store.controller;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import io.swagger.annotations.*;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.format.annotation.DateTimeFormat;
+import org.springframework.web.bind.annotation.*;
+import shop.alien.entity.result.R;
+import shop.alien.entity.store.vo.LawFirmListVO;
+import shop.alien.entity.store.vo.LawFirmReconciliationVO;
+import shop.alien.entity.store.vo.LawyerListVO;
+import shop.alien.store.service.LawFirmReconciliationService;
+
+import javax.servlet.http.HttpServletResponse;
+import java.util.Date;
+
+/**
+ * 律所对账结算控制器
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Slf4j
+@Api(tags = {"律师平台-律所对账结算"})
+@ApiSort(21)
+@CrossOrigin
+@RestController
+@RequestMapping("/lawyer/firm/reconciliation")
+@RequiredArgsConstructor
+public class LawFirmReconciliationController {
+    private final LawFirmReconciliationService lawFirmReconciliationService;
+
+    @ApiOperation("获取所有律所的对账")
+    @ApiOperationSupport(order = 1)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "firmId", value = "律所ID(可选,不传则查询所有律所)", dataType = "Integer", paramType = "query", required = false),
+            @ApiImplicitParam(name = "firmName", value = "律所名称(可选,模糊查询)", dataType = "String", paramType = "query", required = false),
+            @ApiImplicitParam(name = "startDate", value = "开始日期(可选,格式:yyyy-MM-dd)", dataType = "String", paramType = "query"),
+            @ApiImplicitParam(name = "endDate", value = "结束日期(可选,格式:yyyy-MM-dd)", dataType = "String", paramType = "query")
+    })
+    @GetMapping("/getOverview")
+    public R<LawFirmReconciliationVO> getLawFirmReconciliation(
+            @RequestParam(value = "firmId", required = false) Integer firmId,
+            @RequestParam(value = "firmName", required = false) String firmName,
+            @RequestParam(value = "startDate", required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") Date startDate,
+            @RequestParam(value = "endDate", required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") Date endDate) {
+        log.info("LawFirmReconciliationController.getLawFirmReconciliation?firmId={},firmName={},startDate={},endDate={}",
+                firmId, firmName, startDate, endDate);
+        return lawFirmReconciliationService.getLawFirmReconciliation(firmId, firmName, startDate, endDate);
+    }
+
+    @ApiOperation("获取所有律所的对账统计列表(分页)")
+    @ApiOperationSupport(order = 2)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "firmName", value = "律所名称(可选,模糊查询)", dataType = "String", paramType = "query", required = false),
+            @ApiImplicitParam(name = "startDate", value = "开始日期(可选,格式:yyyy-MM-dd)", dataType = "String", paramType = "query"),
+            @ApiImplicitParam(name = "endDate", value = "结束日期(可选,格式:yyyy-MM-dd)", dataType = "String", paramType = "query"),
+            @ApiImplicitParam(name = "pageNum", value = "页码(默认1)", dataType = "Integer", paramType = "query"),
+            @ApiImplicitParam(name = "pageSize", value = "页容(默认10)", dataType = "Integer", paramType = "query")
+    })
+    @GetMapping("/getAllLawFirmList")
+    public R<IPage<LawFirmListVO>> getAllLawFirmReconciliationList(
+            @RequestParam(value = "firmName", required = false) String firmName,
+            @RequestParam(value = "startDate", required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") Date startDate,
+            @RequestParam(value = "endDate", required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") Date endDate,
+            @RequestParam(value = "pageNum", defaultValue = "1") Integer pageNum,
+            @RequestParam(value = "pageSize", defaultValue = "10") Integer pageSize) {
+        log.info("LawFirmReconciliationController.getAllLawFirmReconciliationList?firmName={},startDate={},endDate={},pageNum={},pageSize={}",
+                firmName, startDate, endDate, pageNum, pageSize);
+        return lawFirmReconciliationService.getAllLawFirmReconciliationList(firmName, startDate, endDate, pageNum, pageSize);
+    }
+
+    @ApiOperation("获取律所下所有律师的对账统计列表(分页)")
+    @ApiOperationSupport(order = 3)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "firmId", value = "律所ID", dataType = "Integer", paramType = "query", required = true),
+            @ApiImplicitParam(name = "startDate", value = "开始日期(可选,格式:yyyy-MM-dd)", dataType = "String", paramType = "query"),
+            @ApiImplicitParam(name = "endDate", value = "结束日期(可选,格式:yyyy-MM-dd)", dataType = "String", paramType = "query"),
+            @ApiImplicitParam(name = "pageNum", value = "页码(默认1)", dataType = "Integer", paramType = "query"),
+            @ApiImplicitParam(name = "pageSize", value = "页容(默认10)", dataType = "Integer", paramType = "query")
+    })
+    @GetMapping("/getLawyerList")
+    public R<IPage<LawyerListVO>> getLawyerReconciliationList(
+            @RequestParam(value = "firmId") Integer firmId,
+            @RequestParam(value = "startDate", required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") Date startDate,
+            @RequestParam(value = "endDate", required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") Date endDate,
+            @RequestParam(value = "pageNum", defaultValue = "1") Integer pageNum,
+            @RequestParam(value = "pageSize", defaultValue = "10") Integer pageSize) {
+        log.info("LawFirmReconciliationController.getLawyerReconciliationList?firmId={},startDate={},endDate={},pageNum={},pageSize={}",
+                firmId, startDate, endDate, pageNum, pageSize);
+        return lawFirmReconciliationService.getLawyerReconciliationList(firmId, startDate, endDate, pageNum, pageSize);
+    }
+
+    @ApiOperation("获取律所下所有律师的对账统计列表(分页,支持律师名称模糊查询)")
+    @ApiOperationSupport(order = 4)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "firmId", value = "律所ID", dataType = "Integer", paramType = "query", required = true),
+            @ApiImplicitParam(name = "lawyerName", value = "律师名称(可选,模糊查询)", dataType = "String", paramType = "query", required = false),
+            @ApiImplicitParam(name = "startDate", value = "开始日期(可选,格式:yyyy-MM-dd)", dataType = "String", paramType = "query"),
+            @ApiImplicitParam(name = "endDate", value = "结束日期(可选,格式:yyyy-MM-dd)", dataType = "String", paramType = "query"),
+            @ApiImplicitParam(name = "pageNum", value = "页码(默认1)", dataType = "Integer", paramType = "query"),
+            @ApiImplicitParam(name = "pageSize", value = "页容(默认10)", dataType = "Integer", paramType = "query")
+    })
+    @GetMapping("/getLawyerListWithName")
+    public R<IPage<LawyerListVO>> getLawyerReconciliationListWithName(
+            @RequestParam(value = "firmId") Integer firmId,
+            @RequestParam(value = "lawyerName", required = false) String lawyerName,
+            @RequestParam(value = "startDate", required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") Date startDate,
+            @RequestParam(value = "endDate", required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") Date endDate,
+            @RequestParam(value = "pageNum", defaultValue = "1") Integer pageNum,
+            @RequestParam(value = "pageSize", defaultValue = "10") Integer pageSize) {
+        log.info("LawFirmReconciliationController.getLawyerReconciliationListWithName?firmId={},lawyerName={},startDate={},endDate={},pageNum={},pageSize={}",
+                firmId, lawyerName, startDate, endDate, pageNum, pageSize);
+        return lawFirmReconciliationService.getLawyerReconciliationListWithName(firmId, lawyerName, startDate, endDate, pageNum, pageSize);
+    }
+
+    @ApiOperation("获取律师的已完成订单列表(分页)")
+    @ApiOperationSupport(order = 5)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "lawyerId", value = "律师ID", dataType = "Integer", paramType = "query", required = true),
+            @ApiImplicitParam(name = "pageNum", value = "页码(默认1)", dataType = "Integer", paramType = "query"),
+            @ApiImplicitParam(name = "pageSize", value = "页容(默认10)", dataType = "Integer", paramType = "query")
+    })
+    @GetMapping("/getLawyerOrderList")
+    public R<IPage<LawFirmReconciliationVO>> getLawyerOrderList(
+            @RequestParam(value = "lawyerId") Integer lawyerId,
+            @RequestParam(value = "pageNum", defaultValue = "1") Integer pageNum,
+            @RequestParam(value = "pageSize", defaultValue = "10") Integer pageSize) {
+        log.info("LawFirmReconciliationController.getLawyerOrderList?lawyerId={},pageNum={},pageSize={}",
+                lawyerId, pageNum, pageSize);
+        return lawFirmReconciliationService.getLawyerOrderList(lawyerId, pageNum, pageSize);
+    }
+
+    @ApiOperation("导出所有律所的对账统计列表到Excel(基于/getAllLawFirmList接口数据)")
+    @ApiOperationSupport(order = 6)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "firmName", value = "律所名称(可选,模糊查询)", dataType = "String", paramType = "query", required = false),
+            @ApiImplicitParam(name = "startDate", value = "开始日期(可选,格式:yyyy-MM-dd)", dataType = "String", paramType = "query"),
+            @ApiImplicitParam(name = "endDate", value = "结束日期(可选,格式:yyyy-MM-dd)", dataType = "String", paramType = "query"),
+            @ApiImplicitParam(name = "pageNum", value = "页码(可选,不传或传0则导出全部,传值则导出本页)", dataType = "Integer", paramType = "query"),
+            @ApiImplicitParam(name = "pageSize", value = "页容(可选,与pageNum配合使用)", dataType = "Integer", paramType = "query")
+    })
+    @GetMapping(value = "/exportAllLawFirmList", produces = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
+    public void exportAllLawFirmList(
+            HttpServletResponse response,
+            @RequestParam(value = "firmName", required = false) String firmName,
+            @RequestParam(value = "startDate", required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") Date startDate,
+            @RequestParam(value = "endDate", required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") Date endDate,
+            @RequestParam(value = "pageNum", required = false) Integer pageNum,
+            @RequestParam(value = "pageSize", required = false) Integer pageSize) throws Exception {
+        log.info("LawFirmReconciliationController.exportAllLawFirmList?firmName={},startDate={},endDate={},pageNum={},pageSize={}",
+                firmName, startDate, endDate, pageNum, pageSize);
+        try {
+            lawFirmReconciliationService.exportAllLawFirmList(response, firmName, startDate, endDate, pageNum, pageSize);
+        } catch (Exception e) {
+            log.error("导出律所对账列表异常", e);
+            if (!response.isCommitted()) {
+                response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+                response.setContentType("application/json;charset=UTF-8");
+                response.getWriter().write("{\"code\":500,\"msg\":\"导出失败:" + e.getMessage() + "\"}");
+            }
+            throw e;
+        }
+    }
+
+    @ApiOperation("导出律师对账统计列表到Excel(基于/getLawyerListWithName接口数据)")
+    @ApiOperationSupport(order = 7)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "firmId", value = "律所ID", dataType = "Integer", paramType = "query", required = true),
+            @ApiImplicitParam(name = "lawyerName", value = "律师名称(可选,模糊查询)", dataType = "String", paramType = "query", required = false),
+            @ApiImplicitParam(name = "startDate", value = "开始日期(可选,格式:yyyy-MM-dd)", dataType = "String", paramType = "query"),
+            @ApiImplicitParam(name = "endDate", value = "结束日期(可选,格式:yyyy-MM-dd)", dataType = "String", paramType = "query"),
+            @ApiImplicitParam(name = "pageNum", value = "页码(可选,不传或传0则导出全部,传值则导出本页)", dataType = "Integer", paramType = "query"),
+            @ApiImplicitParam(name = "pageSize", value = "页容(可选,与pageNum配合使用)", dataType = "Integer", paramType = "query")
+    })
+    @GetMapping(value = "/exportLawyerListWithName", produces = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
+    public void exportLawyerListWithName(
+            HttpServletResponse response,
+            @RequestParam(value = "firmId") Integer firmId,
+            @RequestParam(value = "lawyerName", required = false) String lawyerName,
+            @RequestParam(value = "startDate", required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") Date startDate,
+            @RequestParam(value = "endDate", required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") Date endDate,
+            @RequestParam(value = "pageNum", required = false) Integer pageNum,
+            @RequestParam(value = "pageSize", required = false) Integer pageSize) throws Exception {
+        log.info("LawFirmReconciliationController.exportLawyerListWithName?firmId={},lawyerName={},startDate={},endDate={},pageNum={},pageSize={}",
+                firmId, lawyerName, startDate, endDate, pageNum, pageSize);
+        try {
+            lawFirmReconciliationService.exportLawyerListWithName(response, firmId, lawyerName, startDate, endDate, pageNum, pageSize);
+        } catch (Exception e) {
+            log.error("导出律师对账列表异常", e);
+            if (!response.isCommitted()) {
+                response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+                response.setContentType("application/json;charset=UTF-8");
+                response.getWriter().write("{\"code\":500,\"msg\":\"导出失败:" + e.getMessage() + "\"}");
+            }
+            throw e;
+        }
+    }
+}

+ 25 - 10
alien-store/src/main/java/shop/alien/store/controller/LawyerLegalProblemScenarioController.java

@@ -1,4 +1,4 @@
-package shop.alien.store.controller;
+package shop.alien.lawyer.controller;
 
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import io.swagger.annotations.*;
@@ -6,8 +6,8 @@ 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.LawyerImg;
 import shop.alien.entity.store.LawyerLegalProblemScenario;
+import shop.alien.entity.store.dto.LawyerLegalProblemScenarioStatusDto;
 import shop.alien.mapper.LawyerImgMapper;
 import shop.alien.store.service.LawyerLegalProblemScenarioService;
 import shop.alien.util.myBaticsPlus.QueryBuilder;
@@ -90,11 +90,7 @@ public class LawyerLegalProblemScenarioController {
                 .likeFields("name")  // 指定 name 字段使用模糊查询,其他字段使用等值查询
                 .build()
                 .list(lawyerLegalProblemScenarioService);
-        for (LawyerLegalProblemScenario item : list){
-            Integer id=item.getId();
-            LawyerImg a=lawyerImgMapper.getLawyerImgById(id);
-            item.setImgUrl(a.getImgUrl());
-        }
+
         return R.data(list);
     }
 
@@ -115,9 +111,9 @@ public class LawyerLegalProblemScenarioController {
     })
     @GetMapping("/getPage")
     public R<IPage<LawyerLegalProblemScenario>> getPage(@ModelAttribute LawyerLegalProblemScenario lawyerLegalProblemScenario,
-                                                         @RequestParam(defaultValue = "1") int page,
-                                                         @RequestParam(defaultValue = "10") int size) {
-        log.info("LawyerLegalProblemScenarioController.getPage?lawyerLegalProblemScenario={},page={},size={}", 
+                                                        @RequestParam(defaultValue = "1") int page,
+                                                        @RequestParam(defaultValue = "10") int size) {
+        log.info("LawyerLegalProblemScenarioController.getPage?lawyerLegalProblemScenario={},page={},size={}",
                 lawyerLegalProblemScenario, page, size);
         // 参数校验(确保大于0)
         int pageNum = page > 0 ? page : 1;
@@ -131,5 +127,24 @@ public class LawyerLegalProblemScenarioController {
         return R.data(pageResult);
     }
 
+    @ApiOperation("根据一级场景主键获取分类树(包含一级场景及其下所有二级和三级分类)")
+    @ApiOperationSupport(order = 7)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "id", value = "一级场景主键", dataType = "Integer", paramType = "query", required = true)
+    })
+    @GetMapping("/getCategoryTree")
+    public R<LawyerLegalProblemScenario> getCategoryTree(@RequestParam Integer id) {
+        log.info("LawyerLegalProblemScenarioController.getCategoryTree?id={}", id);
+        return lawyerLegalProblemScenarioService.getCategoryTreeByFirstLevelId(id);
+    }
+
+    @ApiOperation("设置法律场景启用/禁用状态")
+    @ApiOperationSupport(order = 8)
+    @PostMapping("/setStatus")
+    public R<Boolean> setStatus(@RequestBody LawyerLegalProblemScenarioStatusDto request) {
+        log.info("LawyerLegalProblemScenarioController.setStatus?request={}", request);
+        return lawyerLegalProblemScenarioService.setStatus(request.getId(), request.getStatus());
+    }
+
 }
 

+ 62 - 0
alien-store/src/main/java/shop/alien/store/controller/LawyerUserController.java

@@ -7,9 +7,11 @@ import lombok.extern.slf4j.Slf4j;
 import org.springframework.web.bind.annotation.*;
 import shop.alien.entity.result.R;
 import shop.alien.entity.store.LawyerUser;
+import shop.alien.entity.store.vo.LawyerUserVo;
 import shop.alien.store.service.LawyerUserService;
 import shop.alien.util.myBaticsPlus.QueryBuilder;
 
+import javax.servlet.http.HttpServletResponse;
 import java.util.List;
 import java.util.Map;
 
@@ -204,5 +206,65 @@ public class LawyerUserController {
         return lawyerUserService.getAiRecommendList(page, size, categoryId);
     }
 
+    @ApiOperation("中台律师列表")
+    @ApiOperationSupport(order = 17)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "name", value = "姓名(支持模糊查询)", dataType = "String", paramType = "query", required = false),
+            @ApiImplicitParam(name = "phone", value = "手机号(支持模糊查询)", dataType = "String", paramType = "query", required = false),
+            @ApiImplicitParam(name = "firmId", value = "律所ID", dataType = "Integer", paramType = "query", required = false),
+            @ApiImplicitParam(name = "startTime", value = "开始日期(用于查询执业开始日期practice_start_date,格式:yyyy-MM-dd)", dataType = "String", paramType = "query", required = false),
+            @ApiImplicitParam(name = "endTime", value = "结束日期(用于查询执业开始日期practice_start_date,格式:yyyy-MM-dd)", dataType = "String", paramType = "query", required = false),
+            @ApiImplicitParam(name = "page", value = "页码(默认1)", dataType = "int", paramType = "query"),
+            @ApiImplicitParam(name = "size", value = "每页数量(默认10)", dataType = "int", paramType = "query")
+    })
+    @GetMapping("/getLawyerList")
+    public R<IPage<LawyerUserVo>> getLawyerList(@RequestParam(required = false) String name,
+                                                @RequestParam(required = false) String phone,
+                                                @RequestParam(required = false) Integer firmId,
+                                                @RequestParam(required = false) String startTime,
+                                                @RequestParam(required = false) String endTime,
+                                                @RequestParam int page,
+                                                @RequestParam int size) {
+        log.info("LawyerUserController.getLawyerList?name={},phone={},firmId={},startTime={},endTime={},page={},size={}",
+                name, phone, firmId, startTime, endTime, page, size);
+        return lawyerUserService.getLawyerList(name, phone, firmId, startTime, endTime, page, size);
+    }
+
+    @ApiOperation("导出中台律师列表")
+    @ApiOperationSupport(order = 19)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "name", value = "姓名(支持模糊查询)", dataType = "String", paramType = "query", required = false),
+            @ApiImplicitParam(name = "phone", value = "手机号(支持模糊查询)", dataType = "String", paramType = "query", required = false),
+            @ApiImplicitParam(name = "firmId", value = "律所ID", dataType = "Integer", paramType = "query", required = false),
+            @ApiImplicitParam(name = "startTime", value = "开始日期(用于查询执业开始日期practice_start_date,格式:yyyy-MM-dd)", dataType = "String", paramType = "query", required = false),
+            @ApiImplicitParam(name = "endTime", value = "结束日期(用于查询执业开始日期practice_start_date,格式:yyyy-MM-dd)", dataType = "String", paramType = "query", required = false),
+            @ApiImplicitParam(name = "pageNum", value = "页码(可选,不传或传0则导出全部,传值则导出本页)", dataType = "Integer", paramType = "query"),
+            @ApiImplicitParam(name = "pageSize", value = "页容(可选,与pageNum配合使用)", dataType = "Integer", paramType = "query")
+    })
+    @GetMapping(value = "/exportLawyerList", produces = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
+    public void exportLawyerList(
+            HttpServletResponse response,
+            @RequestParam(required = false) String name,
+            @RequestParam(required = false) String phone,
+            @RequestParam(required = false) Integer firmId,
+            @RequestParam(required = false) String startTime,
+            @RequestParam(required = false) String endTime,
+            @RequestParam(required = false) Integer pageNum,
+            @RequestParam(required = false) Integer pageSize) throws Exception {
+        log.info("LawyerUserController.exportLawyerList?name={},phone={},firmId={},startTime={},endTime={},pageNum={},pageSize={}",
+                name, phone, firmId, startTime, endTime, pageNum, pageSize);
+        try {
+            lawyerUserService.exportLawyerList(response, name, phone, firmId, startTime, endTime, pageNum, pageSize);
+        } catch (Exception e) {
+            log.error("导出律师列表异常", e);
+            if (!response.isCommitted()) {
+                response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+                response.setContentType("application/json;charset=UTF-8");
+                response.getWriter().write("{\"code\":500,\"msg\":\"导出失败:" + e.getMessage() + "\"}");
+            }
+            throw e;
+        }
+    }
+
 }
 

+ 224 - 0
alien-store/src/main/java/shop/alien/store/controller/LawyerUserViolationController.java

@@ -0,0 +1,224 @@
+package shop.alien.store.controller;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import io.swagger.annotations.*;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.web.bind.annotation.*;
+import shop.alien.entity.result.R;
+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.store.service.LawyerUserViolationService;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * <p>
+ * 律师用户举报 前端控制器
+ * </p>
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Api(tags = {"律师订单用户举报"})
+@Slf4j
+@CrossOrigin
+@RestController
+@RequestMapping("/lawyer/user-violation")
+@RequiredArgsConstructor
+public class LawyerUserViolationController {
+
+    private final LawyerUserViolationService lawyerUserViolationService;
+
+    /**
+     * 用户举报接口
+     * <p>
+     * 用于律师用户提交举报信息,包括被举报用户信息、举报类型、举报内容等
+     * </p>
+     *
+     * @param lawyerUserViolation 举报信息对象,包含以下必填字段:
+     *                            <ul>
+     *                            <li>reportedUserId: 被举报用户ID</li>
+     *                            <li>reportingUserId: 举报用户ID</li>
+     *                            <li>violationType: 违规类型(1-12)</li>
+     *                            <li>reportContextType: 举报内容分类</li>
+     *                            </ul>
+     * @return 统一响应结果,成功返回"举报成功",失败返回"举报失败"
+     * @author system
+     * @since 2025-01-XX
+     */
+    @ApiOperation(value = "用户举报", notes = "提交用户举报信息,系统将自动发送通知消息")
+    @ApiOperationSupport(order = 1)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "reportedUserId", value = "被举报用户ID", required = true, dataType = "String", paramType = "body"),
+            @ApiImplicitParam(name = "reportingUserId", value = "举报用户ID", required = true, dataType = "String", paramType = "body"),
+            @ApiImplicitParam(name = "violationType", value = "违规类型1-服务态度差,2-专业能力差,3-响应时间超过24小时,4-其他原因", required = true, dataType = "String", paramType = "body"),
+            @ApiImplicitParam(name = "reportContextType", value = "举报内容分类", required = true, dataType = "String", paramType = "body"),
+            @ApiImplicitParam(name = "otherReasonContent", value = "其他原因具体内容(当violationType为12时必填)", required = false, dataType = "String", paramType = "body"),
+            @ApiImplicitParam(name = "reportEvidenceImg", value = "举报凭证图片", required = false, dataType = "String", paramType = "body")
+    })
+    @PostMapping("/userReporting")
+    public R<String> userReporting(@RequestBody LawyerUserViolation lawyerUserViolation) {
+        log.info("用户举报请求,被举报用户ID:{},举报用户ID:{}",
+                lawyerUserViolation.getReportedUserId(),
+                lawyerUserViolation.getReportingUserId());
+
+        try {
+            int result = lawyerUserViolationService.userReporting(lawyerUserViolation);
+            if (result > 0) {
+                log.info("用户举报成功,被举报用户ID:{},举报用户ID:{}",
+                        lawyerUserViolation.getReportedUserId(),
+                        lawyerUserViolation.getReportingUserId());
+                return R.success("举报成功");
+            } else {
+                log.warn("用户举报失败,被举报用户ID:{},举报用户ID:{}",
+                        lawyerUserViolation.getReportedUserId(),
+                        lawyerUserViolation.getReportingUserId());
+                return R.fail("举报失败");
+            }
+        } catch (RuntimeException e) {
+            // 业务异常,返回具体的错误信息
+            String errorMessage = e.getMessage();
+            log.warn("用户举报业务异常,被举报用户ID:{},举报用户ID:{},异常信息:{}",
+                    lawyerUserViolation.getReportedUserId(),
+                    lawyerUserViolation.getReportingUserId(),
+                    errorMessage);
+            return R.fail(StringUtils.isNotEmpty(errorMessage) ? errorMessage : "举报失败");
+        } catch (Exception e) {
+            log.error("用户举报异常,被举报用户ID:{},举报用户ID:{},异常信息:{}",
+                    lawyerUserViolation.getReportedUserId(),
+                    lawyerUserViolation.getReportingUserId(),
+                    e.getMessage(), e);
+            return R.fail("举报处理异常,请稍后重试");
+        }
+    }
+
+    @ApiOperation("举报结果")
+    @ApiOperationSupport(order = 2)
+    @GetMapping("/reportListByUserId")
+    public R<Map<String, Object>> reportListByUserId(@RequestParam(value = "userId") String userId) {
+        return R.data(lawyerUserViolationService.reportListByUserId(userId));
+    }
+
+    /**
+     * 根据ID查询举报详情
+     * <p>
+     * 根据举报ID查询举报详细信息,包括被举报人信息、举报原因、举报凭证等
+     * </p>
+     *
+     * @param id 举报记录ID
+     * @return 统一响应结果,包含举报详情VO对象
+     * @author system
+     * @since 2025-01-XX
+     */
+    @ApiOperation(value = "举报详细信息", notes = "根据举报ID查询举报详细信息")
+    @ApiOperationSupport(order = 3)
+    @GetMapping("/reportListById")
+    public R<LawyerUserViolationVo> reportListById(@RequestParam(value = "id") String id) {
+        log.info("查询举报详情请求,ID:{}", id);
+        try {
+            LawyerUserViolationVo violationVo = lawyerUserViolationService.reportListById(id);
+            if (violationVo == null) {
+                log.warn("举报记录不存在,ID:{}", id);
+                return R.fail("举报记录不存在");
+            }
+            return R.data(violationVo);
+        } catch (RuntimeException 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("查询举报详情失败,请稍后重试");
+        }
+    }
+
+    @ApiOperation("举报分页")
+    @ApiOperationSupport(order = 4)
+    @GetMapping("/page")
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "pageNum", value = "页数", dataType = "int", paramType = "query"),
+            @ApiImplicitParam(name = "pageSize", value = "大小", dataType = "int", paramType = "query"),
+            @ApiImplicitParam(name = "orderNumber", value = "订单号", dataType = "String", paramType = "query"),
+            @ApiImplicitParam(name = "phone", value = "用户电话", dataType = "String", paramType = "query"),
+            @ApiImplicitParam(name = "reportedUserName", value = "律师名称", dataType = "String", paramType = "query"),
+            @ApiImplicitParam(name = "processingStatus", value = "状态(0:待处理,1:已通过,2:已驳回)", dataType = "String", paramType = "query"),
+            @ApiImplicitParam(name = "reportedPhone", value = "律师电话", dataType = "String", paramType = "query"),
+            @ApiImplicitParam(name = "violationReason", value = "举报原因", dataType = "String", paramType = "query"),
+            @ApiImplicitParam(name = "startTime", value = "开始时间", dataType = "String", paramType = "query"),
+            @ApiImplicitParam(name = "endTime", value = "结束时间", dataType = "String", paramType = "query")
+    })
+    public R<IPage<LawyerUserViolationDto>> getViolationPage(
+            @RequestParam(defaultValue = "1") int pageNum,
+            @RequestParam(defaultValue = "10") int pageSize,
+            @RequestParam(required = false) String orderNumber,
+            @RequestParam(required = false) String phone,
+            @RequestParam(required = false) String reportedUserName,
+            @RequestParam(required = false) String processingStatus,
+            @RequestParam(required = false) String reportedPhone,
+            @RequestParam(required = false) String violationReason,
+            @RequestParam(required = false) String startTime,
+            @RequestParam(required = false) String endTime
+
+    ) {
+        log.info("LawyerUserViolationController.getViolationPage?pageNum={},pageSize={},orderId={},processingStatus={},reportedUserName={},violationReason={}", pageNum, pageSize, orderNumber, processingStatus, reportedUserName, violationReason);
+        return R.data(lawyerUserViolationService.getViolationPage(pageNum, pageSize, orderNumber, phone, processingStatus, reportedUserName, reportedPhone, violationReason, startTime, endTime));
+    }
+
+    @ApiOperation(value = "举报审核")
+    @ApiOperationSupport(order = 5)
+    @GetMapping("/approve")
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "id", value = "id", dataType = "String", paramType = "query"),
+            @ApiImplicitParam(name = "processingStatus", value = "审批状态(1:审批成功,2:审批失败)", dataType = "String", paramType = "query"),
+            @ApiImplicitParam(name = "reportResult", value = "处理结果", dataType = "String", paramType = "query")
+    })
+    @ResponseBody
+    public R<String> approveStoreInfo(@RequestParam("id") int id, @RequestParam("processingStatus") String processingStatus, @RequestParam("reportResult") String reportResult) {
+        log.info("LawyerUserViolationController.approveStoreInfo?id={}&processingStatus={}", id, processingStatus);
+        lawyerUserViolationService.approve(id, processingStatus, reportResult);
+        return R.success("审批完成");
+    }
+
+    @ApiOperation("查看详情")
+    @ApiOperationSupport(order = 6)
+    @GetMapping("/byId")
+    public LawyerUserViolationDto byId(@RequestParam(defaultValue = "10") int id) {
+        return lawyerUserViolationService.byId(id);
+    }
+
+    @ApiOperation("查看通知详情")
+    @ApiOperationSupport(order = 7)
+    @GetMapping("/byIdNotice")
+    public LawyerUserViolationDto byIdNotice(@RequestParam(defaultValue = "10") int id) {
+        return lawyerUserViolationService.byIdNotice(id);
+    }
+
+    /**
+     * 获取举报原因列表
+     * <p>
+     * 用于获取所有可用的律师违规举报原因选项,供前端下拉选择使用
+     * </p>
+     *
+     * @return 统一响应结果,包含举报原因字典列表
+     * @author system
+     * @since 2025-01-XX
+     */
+    @ApiOperation(value = "获取举报原因", notes = "获取所有可用的律师违规举报原因选项列表")
+    @ApiOperationSupport(order = 8)
+    @GetMapping("/getViolationReason")
+    public R<List<StoreDictionary>> getViolationReason() {
+        log.info("获取举报原因列表请求");
+        try {
+            List<StoreDictionary> violationReasonList = lawyerUserViolationService.getViolationReason();
+            log.info("获取举报原因列表成功,共{}条记录", violationReasonList.size());
+            return R.data(violationReasonList);
+        } catch (Exception e) {
+            log.error("获取举报原因列表异常,异常信息:{}", e.getMessage(), e);
+            return R.fail("获取举报原因列表失败,请稍后重试");
+        }
+    }
+}

+ 17 - 23
alien-store/src/main/java/shop/alien/store/controller/StoreLawFirmController.java

@@ -11,6 +11,8 @@ import shop.alien.entity.result.R;
 import shop.alien.entity.store.LawFirm;
 import shop.alien.entity.store.vo.LawFirmPaymentVO;
 import shop.alien.store.feign.LawyerServiceFeign;
+import shop.alien.store.service.StoreLawFirmService;
+import shop.alien.util.myBaticsPlus.QueryBuilder;
 
 import javax.servlet.http.HttpServletResponse;
 import java.io.IOException;
@@ -28,12 +30,14 @@ import java.util.List;
 @ApiSort(20)
 @CrossOrigin
 @RestController
-@RequestMapping("/storelawyer/firm")
+@RequestMapping("/lawyer/firm")
 @RequiredArgsConstructor
 public class StoreLawFirmController {
 
     private final LawyerServiceFeign lawyerServiceFeign;
 
+    private final StoreLawFirmService storeLawFirmService;
+
     @ApiOperation(value = "新增律所", notes = "新增律所信息,支持同时添加收款账号列表。如果统一社会信用代码(creditCode)已存在,将自动执行更新操作。收款账号(payment_account)必须唯一,不能重复添加。")
     @ApiOperationSupport(order = 1)
     @ApiImplicitParams({
@@ -95,17 +99,7 @@ public class StoreLawFirmController {
     @GetMapping("/getList")
     public R<List<LawFirm>> getList(@ModelAttribute LawFirm lawFirm) {
         log.info("StoreLawFirmController.getList?lawFirm={}", lawFirm);
-        return lawyerServiceFeign.getList(
-                lawFirm.getId(),
-                lawFirm.getFirmName(),
-                lawFirm.getCreditCode(),
-                lawFirm.getStatus(),
-                lawFirm.getCertificationStatus(),
-                null, // province
-                null, // city
-                null, // createdTime_Start
-                null  // createdTime_End
-        );
+        return null;
     }
 
     @ApiOperation(value = "通用分页查询", notes = "分页查询律所列表,包含收款账号列表(paymentList)")
@@ -127,16 +121,16 @@ public class StoreLawFirmController {
         log.info("StoreLawFirmController.getPage?lawFirm={},page={},size={}", lawFirm, page, size);
         int pageNum = page > 0 ? page : 1;
         int pageSize = size > 0 ? size : 10;
-        return lawyerServiceFeign.getPage(
-                pageNum,
-                pageSize,
-                lawFirm.getId(),
-                lawFirm.getFirmName(),
-                lawFirm.getCreditCode(),
-                lawFirm.getStatus(),
-                null, // createdTime_Start
-                null  // createdTime_End
-        );
+        IPage<LawFirm> pageResult = QueryBuilder.of(lawFirm)
+                .likeFields("firmName", "creditCode")  // 律所名称和统一社会信用代码支持模糊查询
+                .page(pageNum, pageSize)
+                .build()
+                .page(storeLawFirmService);
+        // 批量填充收款账号列表
+        if (pageResult != null && pageResult.getRecords() != null && !pageResult.getRecords().isEmpty()) {
+            storeLawFirmService.fillPaymentList(pageResult.getRecords());
+        }
+        return R.data(pageResult);
     }
 
     @ApiOperation(value = "获取律所详情", notes = "获取律所详细信息,包含收款账号列表(paymentList)")
@@ -221,6 +215,6 @@ public class StoreLawFirmController {
                 page, size, firmId, paymentAccount, firmName, creditCode, status, certificationStatus, directorName, createdTimeStart, createdTimeEnd);
         int pageNum = page > 0 ? page : 1;
         int pageSize = size > 0 ? size : 10;
-        return lawyerServiceFeign.getPaymentPageWithFirm(pageNum, pageSize, firmId, paymentAccount, firmName, creditCode, status, certificationStatus, directorName, createdTimeStart, createdTimeEnd);
+        return storeLawFirmService.getPaymentPageWithFirm(pageNum, pageSize, firmId, paymentAccount, firmName, creditCode, status, certificationStatus, directorName, createdTimeStart, createdTimeEnd);
     }
 }

+ 73 - 0
alien-store/src/main/java/shop/alien/store/service/CommentAppealService.java

@@ -0,0 +1,73 @@
+package shop.alien.store.service;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.service.IService;
+import shop.alien.entity.result.R;
+import shop.alien.entity.store.CommentAppeal;
+import shop.alien.entity.store.vo.CommentAppealVo;
+
+import java.util.List;
+/**
+ * 评论申诉表 服务类
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+public interface CommentAppealService extends IService<CommentAppeal> {
+
+    /**
+     * 提交申诉
+     *
+     * @param commentAppeal 申诉信息
+     * @return 操作结果
+     */
+    R<CommentAppeal> submitAppeal(CommentAppeal commentAppeal);
+
+    /**
+     * 审核申诉
+     *
+     * @param id            申诉ID
+     * @param status        审核状态 1:已通过, 2:已驳回
+     * @param reviewReasons 审核原因(可选)
+     * @return 操作结果
+     */
+    R<Boolean> auditAppeal(Integer id, Integer status, String reviewReasons);
+
+    /**
+     * 分页查询申诉列表(中台使用,支持多条件筛选)
+     *
+     * @param pageNum     页数
+     * @param pageSize    页容
+     * @param status      申诉状态 0:待处理, 1:已通过, 2:已驳回
+     * @param orderNumber 评价单号(可选,模糊查询)
+     * @param userName    用户姓名(可选,模糊查询)
+     * @param userPhone   用户电话(可选)
+     * @param lawyerName  律师姓名(可选)
+     * @param lawyerPhone 律师电话(可选)
+     * @param startTime   开始时间(可选,格式:yyyy-MM-dd HH:mm:ss)
+     * @param endTime     结束时间(可选,格式:yyyy-MM-dd HH:mm:ss)
+     * @return 分页结果
+     */
+    R<IPage<CommentAppealVo>> getAppealPage(Integer pageNum, Integer pageSize, Integer status,
+                                            String orderNumber, String userName, String userPhone, String lawyerName, String lawyerPhone,
+                                            String startTime, String endTime);
+
+    /**
+     * 获取申诉详情
+     *
+     * @param id 申诉ID
+     * @return 申诉详情
+     */
+    R<CommentAppealVo> getAppealDetail(Integer id);
+
+    /**
+     * 分页申诉历史列表
+     *
+     * @param pageNum  页数
+     * @param pageSize 页容
+     * @param status   申诉状态 0:待处理, 1:已通过, 2:已驳回
+     * @param lawyerUserId 评论ID(可选)
+     * @return 分页结果
+     */
+    List<CommentAppealVo> getAppealHistory(Integer pageNum, Integer pageSize, Integer status, Integer lawyerUserId);
+}

+ 147 - 0
alien-store/src/main/java/shop/alien/store/service/LawFirmReconciliationService.java

@@ -0,0 +1,147 @@
+package shop.alien.store.service;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import shop.alien.entity.result.R;
+import shop.alien.entity.store.vo.LawFirmListVO;
+import shop.alien.entity.store.vo.LawFirmReconciliationVO;
+import shop.alien.entity.store.vo.LawyerListVO;
+
+import javax.servlet.http.HttpServletResponse;
+import java.util.Date;
+
+/**
+ * 律所对账结算服务类
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+public interface LawFirmReconciliationService {
+
+    /**
+     * 获取律所对账总览
+     *
+     * @param firmId   律所ID(可选,不传则查询所有律所)
+     * @param firmName 律所名称(可选,模糊查询)
+     * @param startDate 开始日期(可选)
+     * @param endDate   结束日期(可选)
+     * @return 律所对账总览(包含总订单数量、总订单金额、总平台信息服务费)
+     */
+    R<LawFirmReconciliationVO> getLawFirmReconciliation(
+            Integer firmId,
+            String firmName,
+            Date startDate,
+            Date endDate
+    );
+
+    /**
+     * 获取所有律所的对账统计列表(分页)
+     *
+     * @param firmName 律所名称(可选,模糊查询)
+     * @param startDate 开始日期(可选)
+     * @param endDate   结束日期(可选)
+     * @param pageNum   页码
+     * @param pageSize  页容
+     * @return 律所对账统计列表(分页)
+     */
+    R<IPage<LawFirmListVO>> getAllLawFirmReconciliationList(
+            String firmName,
+            Date startDate,
+            Date endDate,
+            Integer pageNum,
+            Integer pageSize
+    );
+
+    /**
+     * 获取律所下所有律师的对账统计列表(分页)
+     *
+     * @param firmId   律所ID
+     * @param startDate 开始日期(可选)
+     * @param endDate   结束日期(可选)
+     * @param pageNum   页码
+     * @param pageSize  页容
+     * @return 律师对账统计列表(分页)
+     */
+    R<IPage<LawyerListVO>> getLawyerReconciliationList(
+            Integer firmId,
+            Date startDate,
+            Date endDate,
+            Integer pageNum,
+            Integer pageSize
+    );
+
+    /**
+     * 获取律所下所有律师的对账统计列表(分页,支持律师名称模糊查询)
+     *
+     * @param firmId    律所ID
+     * @param lawyerName 律师名称(可选,模糊查询)
+     * @param startDate 开始日期(可选)
+     * @param endDate   结束日期(可选)
+     * @param pageNum   页码
+     * @param pageSize  页容
+     * @return 律师对账统计列表(分页)
+     */
+    R<IPage<LawyerListVO>> getLawyerReconciliationListWithName(
+            Integer firmId,
+            String lawyerName,
+            Date startDate,
+            Date endDate,
+            Integer pageNum,
+            Integer pageSize
+    );
+
+    /**
+     * 获取律师的已完成订单列表(分页)
+     *
+     * @param lawyerId 律师ID
+     * @param pageNum   页码
+     * @param pageSize  页容
+     * @return 律师订单列表(分页)
+     */
+    R<IPage<LawFirmReconciliationVO>> getLawyerOrderList(
+            Integer lawyerId,
+            Integer pageNum,
+            Integer pageSize
+    );
+
+    /**
+     * 导出所有律所的对账统计列表到Excel
+     *
+     * @param response  HTTP响应对象
+     * @param firmName  律所名称(可选,模糊查询)
+     * @param startDate 开始日期(可选)
+     * @param endDate   结束日期(可选)
+     * @param pageNum   页码(可选,不传或传0则导出全部,传值则导出本页)
+     * @param pageSize  页容(可选,与pageNum配合使用)
+     * @throws Exception 导出过程中的异常
+     */
+    void exportAllLawFirmList(
+            HttpServletResponse response,
+            String firmName,
+            Date startDate,
+            Date endDate,
+            Integer pageNum,
+            Integer pageSize
+    ) throws Exception;
+
+    /**
+     * 导出律师对账统计列表到Excel
+     *
+     * @param response   HTTP响应对象
+     * @param firmId     律所ID
+     * @param lawyerName 律师名称(可选,模糊查询)
+     * @param startDate  开始日期(可选)
+     * @param endDate    结束日期(可选)
+     * @param pageNum    页码(可选,不传或传0则导出全部,传值则导出本页)
+     * @param pageSize   页容(可选,与pageNum配合使用)
+     * @throws Exception 导出过程中的异常
+     */
+    void exportLawyerListWithName(
+            HttpServletResponse response,
+            Integer firmId,
+            String lawyerName,
+            Date startDate,
+            Date endDate,
+            Integer pageNum,
+            Integer pageSize
+    ) throws Exception;
+}

+ 17 - 0
alien-store/src/main/java/shop/alien/store/service/LawyerLegalProblemScenarioService.java

@@ -38,5 +38,22 @@ public interface LawyerLegalProblemScenarioService extends IService<LawyerLegalP
      * @return R<Boolean>
      */
     R<Boolean> deleteLawyerLegalProblemScenario(Integer id);
+
+    /**
+     * 根据一级场景主键获取其下所有分类(包括二级和三级分类的树形结构)
+     *
+     * @param id 一级场景主键
+     * @return R<LawyerLegalProblemScenario> 返回一级场景及其完整的树形结构(一级场景的children包含所有二级分类,每个二级分类的children包含其下的三级分类)
+     */
+    R<LawyerLegalProblemScenario> getCategoryTreeByFirstLevelId(Integer id);
+
+    /**
+     * 设置法律场景启用/禁用状态
+     *
+     * @param id 场景ID
+     * @param status 状态,0:禁用,1:启用
+     * @return R<Boolean> 操作结果
+     */
+    R<Boolean> setStatus(Integer id, Integer status);
 }
 

+ 37 - 0
alien-store/src/main/java/shop/alien/store/service/LawyerUserService.java

@@ -4,7 +4,9 @@ import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.service.IService;
 import shop.alien.entity.result.R;
 import shop.alien.entity.store.LawyerUser;
+import shop.alien.entity.store.vo.LawyerUserVo;
 
+import javax.servlet.http.HttpServletResponse;
 import java.util.List;
 import java.util.Map;
 
@@ -107,5 +109,40 @@ public interface LawyerUserService extends IService<LawyerUser> {
      * @return R<IPage<LawyerUser>>
      */
     R<IPage<LawyerUser>> getAiRecommendList(int page, int size, Integer categoryId);
+
+    /**
+     * 获取中台律师列表
+     *
+     * @return R<LawyerUser> 律师信息
+     */
+    R<IPage<LawyerUserVo>> getLawyerList(String name,
+                                         String phone,
+                                         Integer firmId,
+                                         String startTime,
+                                         String endTime,
+                                         int page,
+                                         int pageSize);
+
+    /**
+     * 导出中台律师列表
+     *
+     * @param response HttpServletResponse对象
+     * @param name 姓名(支持模糊查询)
+     * @param phone 手机号(支持模糊查询)
+     * @param firmId 律所ID
+     * @param startTime 开始日期
+     * @param endTime 结束日期
+     * @param pageNum 页码(可选,不传或传0则导出全部)
+     * @param pageSize 页容(可选,与pageNum配合使用)
+     * @throws Exception 导出异常
+     */
+    void exportLawyerList(HttpServletResponse response,
+                          String name,
+                          String phone,
+                          Integer firmId,
+                          String startTime,
+                          String endTime,
+                          Integer pageNum,
+                          Integer pageSize) throws Exception;
 }
 

+ 71 - 0
alien-store/src/main/java/shop/alien/store/service/LawyerUserViolationService.java

@@ -0,0 +1,71 @@
+package shop.alien.store.service;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.service.IService;
+import shop.alien.entity.store.LawyerUserViolation;
+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 java.io.IOException;
+import java.util.List;
+import java.util.Map;
+/**
+ * <p>
+ * 律师用户举报 服务类
+ * </p>
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+public interface LawyerUserViolationService extends IService<LawyerUserViolation> {
+
+    /**
+     * 用户举报处理
+     *
+     * @param lawyerUserViolation 举报信息对象
+     * @return 插入成功的记录数,失败返回0
+     */
+    int userReporting(LawyerUserViolation lawyerUserViolation);
+
+    Map<String, Object> reportListByUserId(String userId);
+
+    /**
+     * 根据ID查询举报详情
+     * <p>
+     * 根据举报ID查询举报详细信息,包括被举报人信息、举报原因、举报凭证等
+     * </p>
+     *
+     * @param id 举报记录ID,不能为空
+     * @return 举报详情VO对象,如果记录不存在返回null
+     * @throws RuntimeException 当参数无效或查询异常时抛出
+     * @author system
+     * @since 2025-01-XX
+     */
+    LawyerUserViolationVo reportListById(String id);
+
+    IPage<LawyerUserViolationDto> getViolationPage(int pageNum, int pageSize, String orderNumber, String phone, String processingStatus, String reportedUserName, String reportedPhone, String violationReason, String startTime, String endTime);
+
+    void approve(int id, String processingStatus, String reportResult);
+
+    LawyerUserViolationDto byId(Integer id);
+
+    LawyerUserViolationDto byIdNotice(Integer id);
+
+    String exportExcel(String nickName, String phone, String processingStatus) throws IOException;
+
+    String level(UserLoginInfo userLoginInfo);
+
+    /**
+     * 获取举报原因列表
+     * <p>
+     * 查询所有未删除的律师违规举报原因字典数据
+     * </p>
+     *
+     * @return 举报原因字典列表,如果查询失败或没有数据则返回空列表
+     * @author system
+     * @since 2025-01-XX
+     */
+    List<StoreDictionary> getViolationReason();
+}

+ 144 - 0
alien-store/src/main/java/shop/alien/store/service/OrderReviewService.java

@@ -0,0 +1,144 @@
+package shop.alien.store.service;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.service.IService;
+import shop.alien.entity.result.R;
+import shop.alien.entity.store.OrderReview;
+import shop.alien.entity.store.dto.OrderReviewDto;
+import shop.alien.entity.store.vo.OrderReviewDetailVo;
+import shop.alien.entity.store.vo.OrderReviewVo;
+import shop.alien.entity.store.vo.PendingReviewVo;
+
+/**
+ * 订单评价 服务类
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+public interface OrderReviewService extends IService<OrderReview> {
+
+    /**
+     * 创建订单评价(只有订单用户才能评价)
+     *
+     * @param reviewDto 评价DTO
+     *
+     * @return R<OrderReview>
+     */
+    R<OrderReview> createReview(OrderReviewDto reviewDto);
+
+    /**
+     * 获取评价详情(包含评论和回复)
+     *
+     * @param reviewId 评价ID
+     * @param currentUserId 当前用户ID(用于判断是否已点赞,可为null)
+     * @return R<OrderReviewDetailVo>
+     */
+    R<OrderReviewDetailVo> getReviewDetail(Integer reviewId, Integer currentUserId);
+
+    /**
+     * 点赞评价
+     *
+     * @param reviewId 评价ID
+     * @param userId 用户ID
+     * @return R<Boolean>
+     */
+    R<Boolean> likeReview(Integer reviewId, Integer userId);
+
+    /**
+     * 取消点赞评价
+     *
+     * @param reviewId 评价ID
+     * @param userId 用户ID
+     * @return R<Boolean>
+     */
+    R<Boolean> cancelLikeReview(Integer reviewId, Integer userId);
+
+    /**
+     * 分页查询评价列表
+     *
+     * @param pageNum 页码
+     * @param pageSize 页大小
+     * @param orderId 订单ID(可选)
+     * @param lawyerUserId 律师用户ID(可选)
+     * @param userId 评价用户ID(可选)
+     * @param currentUserId 当前用户ID(用于判断是否已点赞,可为null)
+     * @return R<IPage<OrderReviewVo>>
+     */
+    R<IPage<OrderReviewVo>> getReviewList(int pageNum, int pageSize, Integer orderId, Integer lawyerUserId, Integer userId, Integer currentUserId);
+
+
+    /**
+     * 用户删除评价(只能删除自己的评价,删除评价时,会级联删除该评价下的所有评论和回复)
+     *
+     * @param reviewId 评价ID
+     * @param userId 用户ID(必填,只能删除自己的评价)
+     * @return R<Boolean>
+     */
+    R<Boolean> deleteReview(Integer reviewId, Integer userId);
+
+    /**
+     * 管理员删除评价(可以删除任何评价,删除评价时,会级联删除该评价下的所有评论和回复)
+     *
+     * @param reviewId 评价ID
+     * @return R<Boolean>
+     */
+    R<Boolean> deleteReviewByAdmin(Integer reviewId);
+
+    /**
+     * 根据订单ID查询评价
+     *
+     * @param orderId 订单ID
+     * @param currentUserId 当前用户ID(用于判断是否已点赞,可为null)
+     * @return R<OrderReviewVo>
+     */
+    R<OrderReviewVo> getReviewByOrderId(Integer orderId, Integer currentUserId);
+
+    /**
+     * 分页查询待评价列表(查询用户已完成但未评价的订单对应的律师信息)
+     *
+     * @param pageNum 页码
+     * @param pageSize 页大小
+     * @param userId 用户ID
+     * @return R<IPage<PendingReviewVo>>
+     */
+    R<IPage<PendingReviewVo>> getPendingReviewList(int pageNum, int pageSize, Integer userId);
+
+    /**
+     * 分页查询我的评价列表(查询当前用户的所有评价)
+     *
+     * @param pageNum 页码
+     * @param pageSize 页大小
+     * @param userId 用户ID
+     * @param currentUserId 当前用户ID(用于判断是否已点赞,可为null)
+     * @return R<IPage<OrderReviewVo>>
+     */
+    R<IPage<OrderReviewVo>> getMyReviewList(int pageNum, int pageSize, Integer userId, Integer currentUserId);
+
+    /**
+     * 根据律师ID和类型分页查询评价列表(不包含评论)
+     *
+     * @param pageNum 页码
+     * @param pageSize 页大小
+     * @param lawyerUserId 律师用户ID
+     * @param type 查询分类(1:好评,2:中评,3:差评,4:有图,为空时返回全部)
+     * @param currentUserId 当前用户ID(用于判断是否已点赞,可为null)
+     * @return R<IPage<OrderReviewVo>>
+     */
+    R<IPage<OrderReviewVo>> getReviewListByLawyerAndType(int pageNum, int pageSize, Integer lawyerUserId, Integer type, Integer currentUserId);
+
+    /**
+     * 获取律师评价统计数据
+     *
+     * @param lawyerUserId 律师用户ID
+     * @return R<LawyerReviewStatisticsVo>
+     */
+    R<shop.alien.entity.store.vo.LawyerReviewStatisticsVo> getLawyerReviewStatistics(Integer lawyerUserId);
+
+    /**
+     * 根据订单ID查询关联评价
+     *
+     * @param orderId 订单ID
+     * @return R<OrderReviewVo>
+     */
+    R<OrderReviewVo> getOrderEvaluation(Integer orderId);
+}

+ 87 - 0
alien-store/src/main/java/shop/alien/store/service/ReviewCommentService.java

@@ -0,0 +1,87 @@
+package shop.alien.store.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.ReviewCommentRequestDto;
+import shop.alien.entity.store.dto.ReviewReplyDto;
+import shop.alien.entity.store.vo.ReviewCommentVo;
+
+import java.util.List;
+
+/**
+ * 评价评论 服务类
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+public interface ReviewCommentService extends IService<ReviewComment> {
+
+    /**
+     * 创建评论(其他用户对评价的评论)
+     *
+     * @param comment 评论实体
+     * @return R<ReviewComment>
+     */
+    R<ReviewComment> createComment(ReviewComment comment);
+
+    /**
+     * 根据评价ID查询评论列表
+     *
+     * @param reviewId 评价ID
+     * @param currentUserId 当前用户ID(用于判断是否已点赞,可为null)
+     * @return R<List<ReviewCommentVo>>
+     */
+    R<List<ReviewCommentVo>> getCommentListByReviewId(Integer reviewId, Integer currentUserId);
+
+    /**
+     * 点赞评论
+     *
+     * @param requestDto 请求DTO
+     * @return R<Boolean>
+     */
+    R<Boolean> likeComment(ReviewCommentRequestDto requestDto);
+
+    /**
+     * 取消点赞评论
+     *
+     * @param requestDto 请求DTO
+     * @return R<Boolean>
+     */
+    R<Boolean> cancelLikeComment(ReviewCommentRequestDto requestDto);
+
+    /**
+     * 创建回复(用户对评论的回复)
+     *
+     * @param replyDto 回复DTO
+     * @return R<ReviewComment>
+     */
+    R<ReviewComment> createReply(ReviewReplyDto replyDto);
+
+    /**
+     * 根据首评ID查询回复列表
+     *
+     * @param headId 首评ID
+     * @param currentUserId 当前用户ID(用于判断是否已点赞,可为null)
+     * @return R<List<ReviewCommentVo>>
+     */
+    R<List<ReviewCommentVo>> getReplyListByHeadId(Integer headId, Integer currentUserId);
+
+    /**
+     * 删除回复
+     *
+     * @param replyId 回复ID
+     * @param userId 用户ID
+     * @return R<Boolean>
+     */
+    R<Boolean> deleteReply(Integer replyId, Integer userId);
+
+    /**
+     * 删除评论(根据ID,物理删除)
+     * userId有值时只能删除自己发布的评论,userId为空时允许删除任何评论(管理员删除)
+     *
+     * @param reviewComment 评论对象(包含id和userId,userId可选)
+     * @return R<Boolean>
+     */
+    R<Boolean> deleteReviewComment(ReviewComment reviewComment);
+}

+ 13 - 0
alien-store/src/main/java/shop/alien/store/service/StoreLawFirmPaymentService.java

@@ -0,0 +1,13 @@
+package shop.alien.store.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import shop.alien.entity.store.LawFirmPayment;
+
+/**
+ * 律所子表 服务类
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+public interface StoreLawFirmPaymentService extends IService<LawFirmPayment> {
+}

+ 60 - 0
alien-store/src/main/java/shop/alien/store/service/StoreLawFirmService.java

@@ -0,0 +1,60 @@
+package shop.alien.store.service;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.service.IService;
+import shop.alien.entity.result.R;
+import shop.alien.entity.store.LawFirm;
+import shop.alien.entity.store.vo.LawFirmPaymentVO;
+
+import java.util.List;
+
+/**
+ * 律所表 服务类
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+public interface StoreLawFirmService extends IService<LawFirm> {
+
+    /**
+     * 分页查询律所列表
+     *
+     * @param pageNum  页码
+     * @param pageSize 页容
+     * @param firmName 律所名称
+     * @param status   状态
+     * @return IPage<LawFirm>
+     */
+    R<IPage<LawFirm>> getLawFirmList(int pageNum, int pageSize, String firmName, Integer status);
+
+
+    /**
+     * 批量填充律所的收款账号列表
+     *
+     * @param lawFirmList 律所列表
+     */
+    void fillPaymentList(List<LawFirm> lawFirmList);
+
+    /**
+     * 分页查询律所子表关联律所表
+     *
+     * @param pageNum 页码
+     * @param pageSize 页容
+     * @param firmId 律所ID
+     * @param paymentAccount 收款账号(模糊查询)
+     * @param firmName 律所名称(模糊查询)
+     * @param creditCode 统一社会信用代码
+     * @param status 律所状态
+     * @param certificationStatus 认证状态
+     * @param directorName 负责人姓名(模糊查询)
+     * @param createdTimeStart 创建时间开始
+     * @param createdTimeEnd 创建时间结束
+     * @return IPage<LawFirmPaymentVO>
+     */
+    R<IPage<LawFirmPaymentVO>> getPaymentPageWithFirm(
+            int pageNum, int pageSize,
+            Integer firmId, String paymentAccount, String firmName, String creditCode,
+            Integer status, Integer certificationStatus, String directorName,
+            String createdTimeStart, String createdTimeEnd
+    );
+}

+ 562 - 0
alien-store/src/main/java/shop/alien/store/service/impl/CommentAppealServiceImpl.java

@@ -0,0 +1,562 @@
+package shop.alien.store.service.impl;
+
+import com.alibaba.fastjson2.JSON;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.StringUtils;
+import shop.alien.entity.result.R;
+import shop.alien.entity.store.*;
+import shop.alien.entity.store.vo.CommentAppealVo;
+import shop.alien.entity.store.vo.WebSocketVo;
+import shop.alien.mapper.*;
+import shop.alien.store.config.WebSocketProcess;
+import shop.alien.store.service.CommentAppealService;
+import shop.alien.store.service.OrderReviewService;
+import shop.alien.store.service.ReviewCommentService;
+import shop.alien.util.common.JwtUtil;
+import shop.alien.util.common.ListToPage;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 评论申诉表 服务实现类
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Slf4j
+@Transactional
+@Service
+@RequiredArgsConstructor
+public class CommentAppealServiceImpl extends ServiceImpl<CommentAppealMapper, CommentAppeal> implements CommentAppealService {
+    private final OrderReviewService orderReviewService;
+    private final ReviewCommentService reviewCommentService;
+    private final WebSocketProcess webSocketProcess;
+    private final LifeNoticeMapper lifeNoticeMapper;
+    private final OrderReviewMapper orderReviewMapper;
+    private final LifeUserMapper lifeUserMapper;
+    private final LawyerUserMapper lawyerUserMapper;
+    private final LawyerConsultationOrderMapper lawyerConsultationOrderMapper;
+
+
+    @Override
+    public R<CommentAppeal> submitAppeal(CommentAppeal commentAppeal) {
+        log.info("CommentAppealServiceImpl.submitAppeal?commentAppeal={}", commentAppeal);
+
+        // 校验必填字段
+        if (commentAppeal.getCommentId() == null) {
+            return R.fail("评论ID不能为空");
+        }
+        if (!StringUtils.hasText(commentAppeal.getAppealReason())) {
+            return R.fail("申诉理由不能为空");
+        }
+
+        // 检查该评论是否已有待处理或已通过的申诉
+        LambdaQueryWrapper<CommentAppeal> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(CommentAppeal::getCommentId, commentAppeal.getCommentId());
+        wrapper.eq(CommentAppeal::getDeleteFlag, 0);
+        wrapper.in(CommentAppeal::getStatus, 0, 1); // 待处理或已通过
+        CommentAppeal existingAppeal = this.getOne(wrapper);
+        if (existingAppeal != null) {
+            return R.fail("该评论已有申诉记录,无法重复申诉");
+        }
+
+        // 如果orderId为空,从OrderReview中获取
+        if (commentAppeal.getOrderId() == null) {
+            OrderReview orderReview = orderReviewMapper.selectById(commentAppeal.getCommentId());
+            if (orderReview != null && orderReview.getOrderId() != null) {
+                commentAppeal.setOrderId(orderReview.getOrderId());
+            }
+        }
+
+        // 设置默认值
+        commentAppeal.setStatus(0); // 待处理
+        if (commentAppeal.getDeleteFlag() == null) {
+            commentAppeal.setDeleteFlag(0);
+        }
+
+        // 保存申诉
+        boolean result = this.save(commentAppeal);
+        if (result) {
+            log.info("提交申诉成功,id={}", commentAppeal.getId());
+
+            // 更新订单表的申诉状态为2(申诉中)
+            if (commentAppeal.getOrderId() != null) {
+                LambdaUpdateWrapper<LawyerConsultationOrder> lambdaUpdateWrapper = new LambdaUpdateWrapper<>();
+                lambdaUpdateWrapper.eq(LawyerConsultationOrder::getId, commentAppeal.getOrderId())
+                        .set(LawyerConsultationOrder::getIsAppealed, 2);
+                lawyerConsultationOrderMapper.update(null, lambdaUpdateWrapper);
+                log.info("更新订单申诉状态成功,orderId={}, isAppealed=2", commentAppeal.getOrderId());
+            }
+
+            // 发送通知给评价用户,告知其评价被申诉了
+            sendSubmitAppealNotification(commentAppeal);
+
+            return R.data(commentAppeal, "申诉提交成功");
+        } else {
+            return R.fail("申诉提交失败");
+        }
+    }
+
+    @Override
+    public R<Boolean> auditAppeal(Integer id, Integer status, String reviewReasons) {
+        log.info("CommentAppealServiceImpl.auditAppeal?id={}, status={}, reviewReasons={}", id, status, reviewReasons);
+
+        // 校验参数
+        if (id == null) {
+            return R.fail("申诉ID不能为空");
+        }
+
+        if (status == null || (status != 1 && status != 2)) {
+            return R.fail("审核状态不正确,必须是1(已通过)或2(已驳回)");
+        }
+
+        // 从 token 获取当前登录用户ID(审核人)
+        Integer auditUserId = null;
+        try {
+            com.alibaba.fastjson.JSONObject userInfo = JwtUtil.getCurrentUserInfo();
+            if (userInfo != null && userInfo.getString("userId") != null) {
+                auditUserId = userInfo.getInteger("userId");
+            }
+        } catch (Exception e) {
+            log.warn("获取当前用户信息失败,将使用默认值", e);
+        }
+
+        // 查询申诉记录
+        CommentAppeal appeal = this.getById(id);
+        if (appeal == null || appeal.getDeleteFlag() == 1) {
+            return R.fail("申诉记录不存在或已被删除");
+        }
+
+        // 检查状态
+        if (appeal.getStatus() != 0) {
+            return R.fail("该申诉已处理,无法重复审核");
+        }
+
+        // 查询评价信息
+        OrderReview orderReview = orderReviewMapper.selectById(appeal.getCommentId());
+        if (orderReview == null) {
+            return R.fail("评价记录不存在");
+        }
+
+        // 更新状态
+        CommentAppeal updateAppeal = new CommentAppeal();
+        updateAppeal.setId(id);
+        updateAppeal.setStatus(status);
+        updateAppeal.setAppealReviewTime(new Date());
+        if (auditUserId != null) {
+            updateAppeal.setUpdatedUserId(auditUserId);
+        }
+        // 设置审核原因
+        if (StringUtils.hasText(reviewReasons)) {
+            updateAppeal.setReviewReasons(reviewReasons);
+        }
+
+        boolean result = this.updateById(updateAppeal);
+        if (result) {
+            String statusText = status == 1 ? "已通过" : "已驳回";
+            log.info("审核申诉成功,id={}, status={}, auditUserId={}", id, statusText, auditUserId);
+
+            // 如果申诉通过,删除评价及该评价下的所有评论和回复
+            if (status == 1) {
+//                deleteReviewAndRelatedData(appeal.getCommentId());
+                orderReviewService.deleteReviewByAdmin(appeal.getCommentId());
+            }
+
+            // 发送通知
+            sendAuditNotification(appeal, orderReview, status, reviewReasons);
+
+            return R.success("申诉" + statusText);
+        } else {
+            return R.fail("审核失败");
+        }
+    }
+
+    /**
+     * 发送提交申诉通知(给评价用户)
+     *
+     * @param appeal 申诉记录
+     */
+    private void sendSubmitAppealNotification(CommentAppeal appeal) {
+        try {
+            // 查询律师用户信息
+            LambdaQueryWrapper<LawyerUser> lawyerUser = new LambdaQueryWrapper<>();
+            lawyerUser.eq(LawyerUser::getId, appeal.getLawyerUserId());
+            LawyerUser lifeUser = lawyerUserMapper.selectOne(lawyerUser);
+            if (lifeUser == null) {
+                log.warn("评价用户不存在,userId={}", lifeUser.getId());
+                return;
+            }
+            LambdaQueryWrapper<OrderReview> orderReviewLambdaQueryWrapper = new LambdaQueryWrapper<>();
+            orderReviewLambdaQueryWrapper.eq(OrderReview::getId, appeal.getCommentId());
+            OrderReview orderReview = orderReviewMapper.selectOne(orderReviewLambdaQueryWrapper);
+            String orderNumber = "";
+            if (orderReview != null) {
+                orderNumber = orderReview.getOrderNumber();
+            }
+            String receiverId = "user_" + lifeUser.getPhone();
+            String message = String.format("您提交的差评申诉信息,订单编号为" + orderNumber + ",已提交至平台审核,1-3个工作日会发送您审核结果,请注意查收。");
+
+            LifeNotice lifeNotice = new LifeNotice();
+            lifeNotice.setReceiverId(receiverId);
+            lifeNotice.setBusinessId(appeal.getId());
+            lifeNotice.setTitle("申诉通知");
+            lifeNotice.setSenderId("system");
+            lifeNotice.setIsRead(0);
+            lifeNotice.setNoticeType(1);
+            lifeNotice.setDeleteFlag(0);
+
+            com.alibaba.fastjson2.JSONObject jsonObject = new com.alibaba.fastjson2.JSONObject();
+            jsonObject.put("message", message);
+//            jsonObject.put("orderNumber", orderReview.getOrderNumber());
+//            jsonObject.put("appealId", appeal.getId());
+//            jsonObject.put("status", "pending"); // 待审核状态
+            lifeNotice.setContext(jsonObject.toJSONString());
+
+            lifeNoticeMapper.insert(lifeNotice);
+
+            // 发送 WebSocket 消息
+            sendWebSocketNotification(receiverId, lifeNotice);
+            log.info("提交申诉通知发送成功,receiverId={}, appealId={}", receiverId, appeal.getId());
+        } catch (Exception e) {
+            log.error("发送提交申诉通知失败,appealId={}, error={}", appeal.getId(), e.getMessage(), e);
+        }
+    }
+
+    /**
+     * 发送审核通知
+     *
+     * @param appeal      申诉记录
+     * @param orderReview 评价记录
+     * @param status      审核状态 1:已通过, 2:已驳回
+     */
+    private void sendAuditNotification(CommentAppeal appeal, OrderReview orderReview, Integer status, String reviewReasons) {
+        try {
+            if (status == 1) {
+                // 审核通过:给评价用户发通知(评价被删除)
+                sendNotificationToReviewUser(orderReview, appeal);
+                // 审核通过:给律师发通知(申诉成功)
+                sendNotificationToLawyer(orderReview, appeal, true, null);
+            } else {
+                // 审核驳回:给律师发通知(申诉失败)
+                sendNotificationToLawyer(orderReview, appeal, false, reviewReasons);
+            }
+        } catch (Exception e) {
+            log.error("发送审核通知失败,appealId={}, status={}, error={}", appeal.getId(), status, e.getMessage(), e);
+        }
+    }
+
+    /**
+     * 给评价用户发送通知(评价被删除)
+     *
+     * @param orderReview 评价记录
+     * @param appeal      申诉记录
+     */
+    private void sendNotificationToReviewUser(OrderReview orderReview, CommentAppeal appeal) {
+        try {
+            LifeUser lifeUser = lifeUserMapper.selectById(orderReview.getUserId());
+            if (lifeUser == null) {
+                log.warn("评价用户不存在,userId={}", orderReview.getUserId());
+                return;
+            }
+
+            String receiverId = "user_" + lifeUser.getUserPhone();
+            String message = String.format("您对订单编号为" + orderReview.getOrderNumber() + "的评价,律师已进行申诉,经核实,您的评价不实,平台已删除此条评价及回复。");
+
+            LifeNotice lifeNotice = new LifeNotice();
+            lifeNotice.setReceiverId(receiverId);
+            lifeNotice.setBusinessId(appeal.getId());
+            lifeNotice.setTitle("评价被申诉成功通知");
+            lifeNotice.setSenderId("system");
+            lifeNotice.setIsRead(0);
+            lifeNotice.setNoticeType(1);
+            lifeNotice.setDeleteFlag(0);
+
+            com.alibaba.fastjson2.JSONObject jsonObject = new com.alibaba.fastjson2.JSONObject();
+            jsonObject.put("message", message);
+            lifeNotice.setContext(jsonObject.toJSONString());
+
+            lifeNoticeMapper.insert(lifeNotice);
+
+            // 发送 WebSocket 消息
+            sendWebSocketNotification(receiverId, lifeNotice);
+            log.info("评价用户通知发送成功,receiverId={}", receiverId);
+        } catch (Exception e) {
+            log.error("给评价用户发送通知失败,userId={}, error={}", orderReview.getUserId(), e.getMessage(), e);
+        }
+    }
+
+    /**
+     * 给律师发送通知(申诉成功/失败)
+     *
+     * @param orderReview 评价记录
+     * @param appeal      申诉记录
+     * @param isSuccess   是否成功
+     */
+    private void sendNotificationToLawyer(OrderReview orderReview, CommentAppeal appeal, boolean isSuccess, String reviewReasons) {
+        try {
+            LawyerUser lawyerUser = lawyerUserMapper.selectById(orderReview.getLawyerUserId());
+            if (lawyerUser == null) {
+                log.warn("律师用户不存在,lawyerUserId={}", orderReview.getLawyerUserId());
+                return;
+            }
+
+            // 律师的接收ID格式可能是 "lawyer_" + phone 或其他格式,需要根据实际情况调整
+            String receiverId = "lawyer_" + lawyerUser.getPhone();
+            String title = isSuccess ? "申诉成功通知" : "申诉失败通知";
+            String message = isSuccess
+                    ? String.format("您的编号" + orderReview.getOrderNumber() + "的订单,提交的差评申诉信息,经核实,评价内容不实,平台已删除此条评价。")
+                    : String.format("您的编号" + orderReview.getOrderNumber() + "的订单,提交的差评申诉信息,经核实,评价内容属实。失败原因:" + reviewReasons + "。");
+
+            LifeNotice lifeNotice = new LifeNotice();
+            lifeNotice.setReceiverId(receiverId);
+            lifeNotice.setBusinessId(appeal.getId());
+            lifeNotice.setTitle(title);
+            lifeNotice.setSenderId("system");
+            lifeNotice.setIsRead(0);
+            lifeNotice.setNoticeType(1);
+            lifeNotice.setDeleteFlag(0);
+
+            com.alibaba.fastjson2.JSONObject jsonObject = new com.alibaba.fastjson2.JSONObject();
+            jsonObject.put("message", message);
+            lifeNotice.setContext(jsonObject.toJSONString());
+
+            lifeNoticeMapper.insert(lifeNotice);
+
+            // 发送 WebSocket 消息
+            sendWebSocketNotification(receiverId, lifeNotice);
+            log.info("律师通知发送成功,receiverId={}, isSuccess={}", receiverId, isSuccess);
+        } catch (Exception e) {
+            log.error("给律师发送通知失败,lawyerUserId={}, error={}", orderReview.getLawyerUserId(), e.getMessage(), e);
+        }
+    }
+
+    /**
+     * 发送 WebSocket 通知
+     *
+     * @param receiverId 接收人ID
+     * @param lifeNotice 通知内容
+     */
+    private void sendWebSocketNotification(String receiverId, LifeNotice lifeNotice) {
+        try {
+            WebSocketVo webSocketVo = new WebSocketVo();
+            webSocketVo.setSenderId("system");
+            webSocketVo.setReceiverId(receiverId);
+            webSocketVo.setCategory("notice");
+            webSocketVo.setNoticeType("1");
+            webSocketVo.setIsRead(0);
+            webSocketVo.setText(com.alibaba.fastjson2.JSONObject.from(lifeNotice).toJSONString());
+
+            webSocketProcess.sendMessage(receiverId, com.alibaba.fastjson2.JSONObject.from(webSocketVo).toJSONString());
+        } catch (Exception e) {
+            log.error("发送WebSocket通知失败,receiverId={}, error={}", receiverId, e.getMessage(), e);
+        }
+    }
+
+    @Override
+    public R<IPage<CommentAppealVo>> getAppealPage(Integer pageNum, Integer pageSize, Integer status,
+                                                   String orderNumber, String userName, String userPhone, String lawyerName, String lawyerPhone,
+                                                   String startTime, String endTime) {
+        log.info("CommentAppealServiceImpl.getAppealPage?pageNum={}, pageSize={}, status={}, orderNumber={}, userName={}, userPhone={}, lawyerName={}, lawyerPhone={}, startTime={}, endTime={}",
+                pageNum, pageSize, status, orderNumber, userName, userPhone, lawyerName, lawyerPhone, startTime, endTime);
+
+        Page<CommentAppealVo> page = new Page<>(pageNum, pageSize);
+        IPage<CommentAppealVo> pageResult = baseMapper.getAppealPageWithFilters(page, status, orderNumber, userName, userPhone, lawyerName, lawyerPhone, startTime, endTime);
+
+        // 处理数据转换
+        for (CommentAppealVo vo : pageResult.getRecords()) {
+            processAppealVo(vo);
+        }
+
+        return R.data(pageResult);
+    }
+
+    @Override
+    public R<CommentAppealVo> getAppealDetail(Integer id) {
+        log.info("CommentAppealServiceImpl.getAppealDetail?id={}", id);
+
+        if (id == null) {
+            return R.fail("申诉ID不能为空");
+        }
+
+        // 查询申诉详情(包含评价和用户信息)
+        CommentAppealVo vo = baseMapper.getAppealDetailById(id);
+        if (vo == null) {
+            return R.fail("申诉记录不存在或已被删除");
+        }
+
+        // 处理数据转换(复用历史列表的处理逻辑)
+        processAppealVo(vo);
+
+        return R.data(vo, "查询成功");
+    }
+
+    /**
+     * 处理申诉VO数据转换(状态字符串、图片列表等)
+     *
+     * @param vo 申诉VO对象
+     */
+    private void processAppealVo(CommentAppealVo vo) {
+        // 设置状态字符串
+        if (vo.getStatus() != null) {
+            switch (vo.getStatus()) {
+                case 0:
+                    vo.setStatusStr("审核中");
+                    break;
+                case 1:
+                    vo.setStatusStr("已通过");
+                    vo.setAppealResult("申诉成功");
+                    break;
+                case 2:
+                    vo.setStatusStr("被驳回");
+                    vo.setAppealResult("申诉失败");
+                    break;
+                default:
+                    vo.setStatusStr("未知");
+            }
+        }
+
+        // 处理申诉图片列表
+        if (StringUtils.hasText(vo.getImgUrl())) {
+            try {
+                // 如果是 JSON 数组格式,解析为列表
+                if (vo.getImgUrl().trim().startsWith("[")) {
+                    List<String> imgList = JSON.parseArray(vo.getImgUrl(), String.class);
+                    vo.setImgList(imgList != null ? imgList : new ArrayList<>());
+                } else {
+                    // 如果是单个 URL 或逗号分隔的字符串
+                    List<String> imgList = new ArrayList<>();
+                    String[] urls = vo.getImgUrl().split(",");
+                    for (String url : urls) {
+                        if (StringUtils.hasText(url.trim())) {
+                            imgList.add(url.trim());
+                        }
+                    }
+                    vo.setImgList(imgList);
+                }
+            } catch (Exception e) {
+                log.warn("解析申诉图片失败,imgUrl={}", vo.getImgUrl(), e);
+                List<String> imgList = new ArrayList<>();
+                imgList.add(vo.getImgUrl());
+                vo.setImgList(imgList);
+            }
+        } else {
+            vo.setImgList(new ArrayList<>());
+        }
+
+        // 处理评价图片列表
+        if (StringUtils.hasText(vo.getReviewImages())) {
+            try {
+                // 如果是 JSON 数组格式,解析为列表
+                if (vo.getReviewImages().trim().startsWith("[")) {
+                    List<String> reviewImgList = JSON.parseArray(vo.getReviewImages(), String.class);
+                    vo.setReviewImgList(reviewImgList != null ? reviewImgList : new ArrayList<>());
+                } else {
+                    // 如果是单个 URL 或逗号分隔的字符串
+                    List<String> reviewImgList = new ArrayList<>();
+                    String[] urls = vo.getReviewImages().split(",");
+                    for (String url : urls) {
+                        if (StringUtils.hasText(url.trim())) {
+                            reviewImgList.add(url.trim());
+                        }
+                    }
+                    vo.setReviewImgList(reviewImgList);
+                }
+            } catch (Exception e) {
+                log.warn("解析评价图片失败,reviewImages={}", vo.getReviewImages(), e);
+                vo.setReviewImgList(new ArrayList<>());
+            }
+        } else {
+            vo.setReviewImgList(new ArrayList<>());
+        }
+    }
+
+    /**
+     * 删除评价及该评价下的所有评论和回复
+     *
+     * @param reviewId 评价ID
+     */
+    private void deleteReviewAndRelatedData(Integer reviewId) {
+        log.info("删除评价及关联数据,reviewId={}, userId={}", reviewId);
+
+        if (reviewId == null) {
+            log.warn("评价ID为空,无法删除");
+            return;
+        }
+
+        try {
+            // 1. 查询该评价下的所有评论
+            LambdaQueryWrapper<ReviewComment> commentWrapper = new LambdaQueryWrapper<>();
+            commentWrapper.eq(ReviewComment::getReviewId, reviewId)
+                    .eq(ReviewComment::getDeleteFlag, 0);
+            List<ReviewComment> comments = reviewCommentService.list(commentWrapper);
+
+            // 2. 删除每个评论下的所有回复
+            for (ReviewComment comment : comments) {
+                // 删除评论下的所有回复(逻辑删除)
+                LambdaUpdateWrapper<ReviewComment> replyUpdateWrapper = new LambdaUpdateWrapper<>();
+                replyUpdateWrapper.eq(ReviewComment::getHeadId, comment.getId())
+                        .eq(ReviewComment::getHeadType, 1)
+                        .eq(ReviewComment::getDeleteFlag, 0);
+                replyUpdateWrapper.set(ReviewComment::getDeleteFlag, 1);
+                replyUpdateWrapper.set(ReviewComment::getUpdatedTime, new Date());
+                reviewCommentService.update(replyUpdateWrapper);
+                log.info("删除评论下的回复,commentId={}, 回复数已删除", comment.getId());
+            }
+
+            // 3. 删除所有评论(逻辑删除)
+            for (ReviewComment comment : comments) {
+                comment.setDeleteFlag(1);
+                comment.setUpdatedTime(new Date());
+                reviewCommentService.updateById(comment);
+            }
+            log.info("删除评价下的评论,reviewId={}, 评论数={}", reviewId, comments.size());
+
+            // 4. 删除评价本身(逻辑删除)
+            OrderReview review = orderReviewService.getById(reviewId);
+            if (review != null && review.getDeleteFlag() == 0) {
+                review.setDeleteFlag(1);
+                review.setUpdatedTime(new Date());
+                orderReviewService.updateById(review);
+                log.info("删除评价成功,reviewId={}", reviewId);
+            } else {
+                log.warn("评价不存在或已删除,reviewId={}", reviewId);
+            }
+        } catch (Exception e) {
+            log.error("删除评价及关联数据失败,reviewId={}, error={}", reviewId, e.getMessage(), e);
+            throw new RuntimeException("删除评价及关联数据失败:" + e.getMessage(), e);
+        }
+    }
+
+    @Override
+    public List<CommentAppealVo> getAppealHistory(Integer pageNum, Integer pageSize, Integer status, Integer lawyerUserId) {
+        log.info("CommentAppealServiceImpl.getAppealHistory?pageNum={}, pageSize={}, status={}, lawyerUserId={}",
+                pageNum, pageSize, status, lawyerUserId);
+        List<CommentAppealVo> appealList = new ArrayList<>();
+        //status 3 查全部
+        if (status == 3) {
+            appealList = baseMapper.getAppealHistoryList(null, lawyerUserId);
+        } else {
+            // 查询申诉历史列表
+            appealList = baseMapper.getAppealHistoryList(status, lawyerUserId);
+        }
+
+        // 处理数据转换(图片列表等)
+        for (CommentAppealVo vo : appealList) {
+            processAppealVo(vo);
+        }
+
+        ListToPage.setPage(appealList, pageNum, pageSize);
+
+        return appealList;
+    }
+}

+ 329 - 0
alien-store/src/main/java/shop/alien/store/service/impl/LawFirmReconciliationServiceImpl.java

@@ -0,0 +1,329 @@
+package shop.alien.store.service.impl;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import shop.alien.entity.result.R;
+import shop.alien.entity.store.LawFirm;
+import shop.alien.entity.store.LawyerUser;
+import shop.alien.entity.store.vo.LawFirmListVO;
+import shop.alien.entity.store.vo.LawFirmReconciliationVO;
+import shop.alien.entity.store.vo.LawyerListVO;
+import shop.alien.mapper.LawFirmMapper;
+import shop.alien.store.service.LawFirmReconciliationService;
+import shop.alien.store.service.LawyerUserService;
+import shop.alien.store.service.StoreLawFirmService;
+import shop.alien.util.excel.EasyExcelUtil;
+
+import javax.servlet.http.HttpServletResponse;
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 律所对账结算服务实现类
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Slf4j
+@Transactional
+@Service
+@RequiredArgsConstructor
+public class LawFirmReconciliationServiceImpl implements LawFirmReconciliationService {
+
+    private final LawFirmMapper lawFirmMapper;
+    private final StoreLawFirmService lawFirmService;
+    private final LawyerUserService lawyerUserService;
+
+    @Override
+    public R<LawFirmReconciliationVO> getLawFirmReconciliation(
+            Integer firmId,
+            String firmName,
+            Date startDate,
+            Date endDate) {
+        log.info("LawFirmReconciliationServiceImpl.getLawFirmReconciliation?firmId={},firmName={},startDate={},endDate={}",
+                firmId, firmName, startDate, endDate);
+
+        // 查询统计信息
+        java.util.Map<String, Object> statistics = lawFirmMapper.getLawFirmReconciliationStatistics(firmId, firmName, startDate, endDate);
+        long totalOrderCount = statistics != null && statistics.get("order_count") != null
+                ? ((Number) statistics.get("order_count")).longValue() : 0L;
+        long totalOrderAmount = statistics != null && statistics.get("total_amount") != null
+                ? ((Number) statistics.get("total_amount")).longValue() : 0L;
+        long platformServiceFee = statistics != null && statistics.get("total_platform_service_fee") != null
+                ? ((Number) statistics.get("total_platform_service_fee")).longValue() : 0L;
+
+        // 获取律所名称
+        String resultFirmName = "所有律所";
+
+        // 如果 firmId 有值,查询当前律所信息并验证佣金比例
+        if (firmId != null) {
+            LawFirm lawFirm = lawFirmService.getById(firmId);
+            if (lawFirm == null || (lawFirm.getDeleteFlag() != null && lawFirm.getDeleteFlag() == 1)) {
+                return R.fail("律所不存在或已被删除");
+            }
+            resultFirmName = lawFirm.getFirmName();
+            if (lawFirm.getPlatformCommissionRatio() == null) {
+                return R.fail("律所[" + resultFirmName + "]的平台佣金比例未设置,无法计算对账信息");
+            }
+        } else if (firmName != null && !firmName.trim().isEmpty()) {
+            // 如果只传了 firmName,使用传入的名称
+            resultFirmName = firmName;
+        }
+
+        // 构建返回对象
+        LawFirmReconciliationVO vo = new LawFirmReconciliationVO();
+        vo.setFirmId(firmId);
+        vo.setFirmName(resultFirmName);
+        vo.setTotalOrderCount(totalOrderCount);
+        vo.setTotalOrderAmountYuan(convertFenToYuan(totalOrderAmount));
+        vo.setPlatformServiceFeeYuan(convertFenToYuan(platformServiceFee));
+
+        return R.data(vo, "查询成功");
+    }
+
+
+
+    @Override
+    public R<IPage<LawFirmListVO>> getAllLawFirmReconciliationList(
+            String firmName,
+            Date startDate,
+            Date endDate,
+            Integer pageNum,
+            Integer pageSize) {
+        log.info("LawFirmReconciliationServiceImpl.getAllLawFirmReconciliationList?firmName={},startDate={},endDate={},pageNum={},pageSize={}",
+                firmName, startDate, endDate, pageNum, pageSize);
+
+        // 查询所有律所的统计信息(SQL中已直接计算为元)
+        List<LawFirmListVO> firmList = lawFirmMapper.getAllLawFirmReconciliationStatistics(firmName, startDate, endDate);
+
+        // 手动分页
+        Page<LawFirmListVO> page = new Page<>();
+        if (pageNum != null && pageSize != null && pageNum > 0 && pageSize > 0) {
+            page.setCurrent(pageNum);
+            page.setSize(pageSize);
+            page.setTotal(firmList.size());
+
+            int start = (pageNum - 1) * pageSize;
+            int end = Math.min(start + pageSize, firmList.size());
+            if (start < firmList.size()) {
+                page.setRecords(firmList.subList(start, end));
+            } else {
+                page.setRecords(Collections.emptyList());
+            }
+        } else {
+            page.setCurrent(1);
+            page.setSize(firmList.size());
+            page.setTotal(firmList.size());
+            page.setRecords(firmList);
+        }
+
+        return R.data(page, "查询成功");
+    }
+
+    @Override
+    public R<IPage<LawyerListVO>> getLawyerReconciliationList(
+            Integer firmId,
+            Date startDate,
+            Date endDate,
+            Integer pageNum,
+            Integer pageSize) {
+        log.info("LawFirmReconciliationServiceImpl.getLawyerReconciliationList?firmId={},startDate={},endDate={},pageNum={},pageSize={}",
+                firmId, startDate, endDate, pageNum, pageSize);
+
+        // 验证律所是否存在
+        if (firmId == null) {
+            return R.fail("律所ID不能为空");
+        }
+        LawFirm lawFirm = lawFirmService.getById(firmId);
+        if (lawFirm == null || (lawFirm.getDeleteFlag() != null && lawFirm.getDeleteFlag() == 1)) {
+            return R.fail("律所不存在或已被删除");
+        }
+
+        // 查询律所下所有律师的统计信息(SQL中已直接计算为元)
+        List<LawyerListVO> lawyerList = lawFirmMapper.getLawyerReconciliationStatistics(firmId, startDate, endDate);
+
+        // 手动分页
+        Page<LawyerListVO> page = new Page<>();
+        if (pageNum != null && pageSize != null && pageNum > 0 && pageSize > 0) {
+            page.setCurrent(pageNum);
+            page.setSize(pageSize);
+            page.setTotal(lawyerList.size());
+
+            int start = (pageNum - 1) * pageSize;
+            int end = Math.min(start + pageSize, lawyerList.size());
+            if (start < lawyerList.size()) {
+                page.setRecords(lawyerList.subList(start, end));
+            } else {
+                page.setRecords(Collections.emptyList());
+            }
+        } else {
+            page.setCurrent(1);
+            page.setSize(lawyerList.size());
+            page.setTotal(lawyerList.size());
+            page.setRecords(lawyerList);
+        }
+
+        return R.data(page, "查询成功");
+    }
+
+    @Override
+    public R<IPage<LawyerListVO>> getLawyerReconciliationListWithName(
+            Integer firmId,
+            String lawyerName,
+            Date startDate,
+            Date endDate,
+            Integer pageNum,
+            Integer pageSize) {
+        log.info("LawFirmReconciliationServiceImpl.getLawyerReconciliationListWithName?firmId={},lawyerName={},startDate={},endDate={},pageNum={},pageSize={}",
+                firmId, lawyerName, startDate, endDate, pageNum, pageSize);
+
+        // 验证律所是否存在
+        if (firmId == null) {
+            return R.fail("律所ID不能为空");
+        }
+        LawFirm lawFirm = lawFirmService.getById(firmId);
+        if (lawFirm == null || (lawFirm.getDeleteFlag() != null && lawFirm.getDeleteFlag() == 1)) {
+            return R.fail("律所不存在或已被删除");
+        }
+
+        // 查询律所下所有律师的统计信息(SQL中已直接计算为元,支持律师名称模糊查询)
+        List<LawyerListVO> lawyerList = lawFirmMapper.getLawyerReconciliationStatisticsWithName(firmId, lawyerName, startDate, endDate);
+
+        // 手动分页
+        Page<LawyerListVO> page = new Page<>();
+        if (pageNum != null && pageSize != null && pageNum > 0 && pageSize > 0) {
+            page.setCurrent(pageNum);
+            page.setSize(pageSize);
+            page.setTotal(lawyerList.size());
+
+            int start = (pageNum - 1) * pageSize;
+            int end = Math.min(start + pageSize, lawyerList.size());
+            if (start < lawyerList.size()) {
+                page.setRecords(lawyerList.subList(start, end));
+            } else {
+                page.setRecords(Collections.emptyList());
+            }
+        } else {
+            page.setCurrent(1);
+            page.setSize(lawyerList.size());
+            page.setTotal(lawyerList.size());
+            page.setRecords(lawyerList);
+        }
+
+        return R.data(page, "查询成功");
+    }
+
+    @Override
+    public R<IPage<LawFirmReconciliationVO>> getLawyerOrderList(
+            Integer lawyerId,
+            Integer pageNum,
+            Integer pageSize) {
+        log.info("LawFirmReconciliationServiceImpl.getLawyerOrderList?lawyerId={},pageNum={},pageSize={}",
+                lawyerId, pageNum, pageSize);
+
+        // 验证律师是否存在
+        if (lawyerId == null) {
+            return R.fail("律师ID不能为空");
+        }
+        LawyerUser lawyerUser = lawyerUserService.getById(lawyerId);
+        if (lawyerUser == null || (lawyerUser.getDeleteFlag() != null && lawyerUser.getDeleteFlag() == 1)) {
+            return R.fail("律师不存在或已被删除");
+        }
+
+        // 创建分页对象
+        int pageNumValue = pageNum != null && pageNum > 0 ? pageNum : 1;
+        int pageSizeValue = pageSize != null && pageSize > 0 ? pageSize : 10;
+        Page<LawFirmReconciliationVO> page = new Page<>(pageNumValue, pageSizeValue);
+
+        // 查询律师的已完成订单列表
+        IPage<LawFirmReconciliationVO> orderPage = lawFirmMapper.getLawyerOrderList(page, lawyerId);
+
+        return R.data(orderPage, "查询成功");
+    }
+
+    @Override
+    public void exportAllLawFirmList(
+            HttpServletResponse response,
+            String firmName,
+            Date startDate,
+            Date endDate,
+            Integer pageNum,
+            Integer pageSize) throws Exception {
+        log.info("LawFirmReconciliationServiceImpl.exportAllLawFirmList?firmName={},startDate={},endDate={},pageNum={},pageSize={}",
+                firmName, startDate, endDate, pageNum, pageSize);
+
+        // 调用查询接口获取数据
+        R<IPage<LawFirmListVO>> result = getAllLawFirmReconciliationList(firmName, startDate, endDate, pageNum, pageSize);
+
+        if (result == null || !result.isSuccess()) {
+            log.warn("获取律所对账列表失败:{}", result != null ? result.getMsg() : "返回结果为空");
+            throw new RuntimeException(result != null ? result.getMsg() : "获取数据失败,请稍后重试");
+        }
+
+        IPage<LawFirmListVO> page = result.getData();
+        if (page == null || page.getRecords() == null || page.getRecords().isEmpty()) {
+            log.warn("律所对账列表导出:无数据可导出");
+            throw new IllegalArgumentException("暂无数据可导出");
+        }
+
+        List<LawFirmListVO> exportList = page.getRecords();
+        log.info("律所对账列表导出:查询到{}条数据,导出{}条数据", page.getTotal(), exportList.size());
+
+        // 使用EasyExcelUtil导出
+        EasyExcelUtil.exportExcel(response, exportList, LawFirmListVO.class, "律所对账列表", "律所对账列表");
+    }
+
+    @Override
+    public void exportLawyerListWithName(
+            HttpServletResponse response,
+            Integer firmId,
+            String lawyerName,
+            Date startDate,
+            Date endDate,
+            Integer pageNum,
+            Integer pageSize) throws Exception {
+        log.info("LawFirmReconciliationServiceImpl.exportLawyerListWithName?firmId={},lawyerName={},startDate={},endDate={},pageNum={},pageSize={}",
+                firmId, lawyerName, startDate, endDate, pageNum, pageSize);
+
+        // 调用查询接口获取数据
+        R<IPage<LawyerListVO>> result = getLawyerReconciliationListWithName(firmId, lawyerName, startDate, endDate, pageNum, pageSize);
+
+        if (result == null || !result.isSuccess()) {
+            log.warn("获取律师对账列表失败:{}", result != null ? result.getMsg() : "返回结果为空");
+            throw new RuntimeException(result != null ? result.getMsg() : "获取数据失败,请稍后重试");
+        }
+
+        IPage<LawyerListVO> page = result.getData();
+        if (page == null || page.getRecords() == null || page.getRecords().isEmpty()) {
+            log.warn("律师对账列表导出:无数据可导出,firmId={}", firmId);
+            throw new IllegalArgumentException("暂无数据可导出");
+        }
+
+        List<LawyerListVO> exportList = page.getRecords();
+        log.info("律师对账列表导出:查询到{}条数据,导出{}条数据,firmId={}", page.getTotal(), exportList.size(), firmId);
+
+        // 使用EasyExcelUtil导出
+        EasyExcelUtil.exportExcel(response, exportList, LawyerListVO.class, "律师对账列表", "律师对账列表");
+    }
+
+    /**
+     * 将分转换为元(字符串格式)
+     *
+     * @param fen 分
+     * @return 元(字符串格式,保留2位小数)
+     */
+    private String convertFenToYuan(Long fen) {
+        if (fen == null || fen == 0) {
+            return "0.00";
+        }
+        BigDecimal yuan = BigDecimal.valueOf(fen).divide(new BigDecimal(100), 2, RoundingMode.HALF_UP);
+        return yuan.toString();
+    }
+}

+ 152 - 10
alien-store/src/main/java/shop/alien/store/service/impl/LawyerLegalProblemScenarioServiceImpl.java

@@ -1,16 +1,23 @@
 package shop.alien.store.service.impl;
 
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.CollectionUtils;
 import shop.alien.entity.result.R;
 import shop.alien.entity.store.LawyerLegalProblemScenario;
 import shop.alien.mapper.LawyerLegalProblemScenarioMapper;
 import shop.alien.store.service.LawyerLegalProblemScenarioService;
 
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
 /**
  * 法律问题场景 服务实现类
  *
@@ -24,21 +31,20 @@ import shop.alien.store.service.LawyerLegalProblemScenarioService;
 public class LawyerLegalProblemScenarioServiceImpl extends ServiceImpl<LawyerLegalProblemScenarioMapper, LawyerLegalProblemScenario> implements LawyerLegalProblemScenarioService {
 
     @Override
-    public R<LawyerLegalProblemScenario> addLawyerLegalProblemScenario(LawyerLegalProblemScenario LawyerLegalProblemScenario) {
-        log.info("LawyerLegalProblemScenarioServiceImpl.addLawyerLegalProblemScenario?LawyerLegalProblemScenario={}", LawyerLegalProblemScenario);
-        boolean result = this.save(LawyerLegalProblemScenario);
+    public R<LawyerLegalProblemScenario> addLawyerLegalProblemScenario(LawyerLegalProblemScenario lawyerLegalProblemScenario) {
+        boolean result = this.save(lawyerLegalProblemScenario);
         if (result) {
-            return R.data(LawyerLegalProblemScenario);
+            return R.data(lawyerLegalProblemScenario);
         }
         return R.fail("新增失败");
     }
 
     @Override
-    public R<LawyerLegalProblemScenario> editLawyerLegalProblemScenario(LawyerLegalProblemScenario LawyerLegalProblemScenario) {
-        log.info("LawyerLegalProblemScenarioServiceImpl.editLawyerLegalProblemScenario?LawyerLegalProblemScenario={}", LawyerLegalProblemScenario);
-        boolean result = this.updateById(LawyerLegalProblemScenario);
+    public R<LawyerLegalProblemScenario> editLawyerLegalProblemScenario(LawyerLegalProblemScenario lawyerLegalProblemScenario) {
+        log.info("LawyerLegalProblemScenarioServiceImpl.editLawyerLegalProblemScenario?LawyerLegalProblemScenario={}", lawyerLegalProblemScenario);
+        boolean result = this.updateById(lawyerLegalProblemScenario);
         if (result) {
-            return R.data(LawyerLegalProblemScenario);
+            return R.data(lawyerLegalProblemScenario);
         }
         return R.fail("修改失败");
     }
@@ -46,12 +52,148 @@ public class LawyerLegalProblemScenarioServiceImpl extends ServiceImpl<LawyerLeg
     @Override
     public R<Boolean> deleteLawyerLegalProblemScenario(Integer id) {
         log.info("LawyerLegalProblemScenarioServiceImpl.deleteLawyerLegalProblemScenario?id={}", id);
-        boolean result = this.removeById(id);
+
+        // 参数校验
+        if (id == null) {
+            log.warn("删除法律问题场景失败:ID为空");
+            return R.fail("场景ID不能为空");
+        }
+
+        // 查询要删除的场景
+        LawyerLegalProblemScenario scenario = this.getById(id);
+        if (scenario == null) {
+            log.warn("删除法律问题场景失败:场景不存在,id={}", id);
+            return R.fail("场景不存在");
+        }
+
+        // 递归删除所有子场景
+        List<Integer> allIdsToDelete = new ArrayList<>();
+        allIdsToDelete.add(id);
+        collectChildIds(id, allIdsToDelete);
+
+        log.info("准备删除场景及其子场景,共{}个,ids={}", allIdsToDelete.size(), allIdsToDelete);
+
+        // 批量删除所有场景(包括当前场景和所有子场景)
+        boolean result = this.removeByIds(allIdsToDelete);
         if (result) {
-            return R.success("删除成功");
+            log.info("删除法律问题场景成功,共删除{}个场景", allIdsToDelete.size());
+            return R.success("删除成功,共删除" + allIdsToDelete.size() + "个场景");
         }
         return R.fail("删除失败");
     }
 
+    /**
+     * 递归收集所有子场景的ID
+     *
+     * @param parentId 父场景ID
+     * @param idsList  用于收集所有子场景ID的列表
+     */
+    private void collectChildIds(Integer parentId, List<Integer> idsList) {
+        // 查询所有直接子场景(parentId = 当前场景ID)
+        QueryWrapper<LawyerLegalProblemScenario> queryWrapper = new QueryWrapper<>();
+        queryWrapper.eq("parent_id", parentId)
+                .eq("delete_flag", 0);
+        List<LawyerLegalProblemScenario> children = this.list(queryWrapper);
+
+        if (!CollectionUtils.isEmpty(children)) {
+            for (LawyerLegalProblemScenario child : children) {
+                Integer childId = child.getId();
+                idsList.add(childId);
+                // 递归查找子场景的子场景
+                collectChildIds(childId, idsList);
+            }
+        }
+    }
+
+    @Override
+    public R<LawyerLegalProblemScenario> getCategoryTreeByFirstLevelId(Integer id) {
+        log.info("LawyerLegalProblemScenarioServiceImpl.getCategoryTreeByFirstLevelId?id={}", id);
+
+        // 验证一级场景是否存在
+        LawyerLegalProblemScenario firstLevel = this.getById(id);
+        if (firstLevel == null) {
+            return R.fail("一级场景不存在");
+        }
+        if (firstLevel.getLevel() == null || firstLevel.getLevel() != 1) {
+            return R.fail("该主键不是一级场景");
+        }
+
+        // 查询所有二级分类(父ID为一级场景ID,level=2)
+        QueryWrapper<LawyerLegalProblemScenario> secondLevelQuery = new QueryWrapper<>();
+        secondLevelQuery.eq("parent_id", id)
+                .eq("level", 2)
+                .eq("delete_flag", 0)
+                .orderByAsc("sort_order", "id");
+        List<LawyerLegalProblemScenario> secondLevelList = this.list(secondLevelQuery);
+
+        // 如果二级分类为空,直接返回一级场景(children为空列表)
+        if (CollectionUtils.isEmpty(secondLevelList)) {
+            firstLevel.setChildren(new ArrayList<>());
+            return R.data(firstLevel);
+        }
+
+        // 获取所有二级分类的ID列表
+        List<Integer> secondLevelIds = secondLevelList.stream()
+                .map(LawyerLegalProblemScenario::getId)
+                .collect(Collectors.toList());
+
+        // 查询所有三级分类(父ID在二级分类ID列表中,level=3)
+        QueryWrapper<LawyerLegalProblemScenario> thirdLevelQuery = new QueryWrapper<>();
+        thirdLevelQuery.in("parent_id", secondLevelIds)
+                .eq("level", 3)
+                .eq("delete_flag", 0)
+                .orderByAsc("sort_order", "id");
+        List<LawyerLegalProblemScenario> thirdLevelList = this.list(thirdLevelQuery);
+
+        // 将三级分类按父ID分组
+        Map<Integer, List<LawyerLegalProblemScenario>> thirdLevelMap = thirdLevelList.stream()
+                .collect(Collectors.groupingBy(LawyerLegalProblemScenario::getParentId));
+
+        // 为每个二级分类设置其下的三级分类列表
+        for (LawyerLegalProblemScenario secondLevel : secondLevelList) {
+            List<LawyerLegalProblemScenario> children = thirdLevelMap.get(secondLevel.getId());
+            secondLevel.setChildren(children != null ? children : new ArrayList<>());
+        }
+
+        // 将二级分类列表设置到一级场景的children中
+        firstLevel.setChildren(secondLevelList);
+
+        return R.data(firstLevel);
+    }
+
+    @Override
+    public R<Boolean> setStatus(Integer id, Integer status) {
+        log.info("LawyerLegalProblemScenarioServiceImpl.setStatus?id={},status={}", id, status);
+
+        // 参数校验
+        if (id == null) {
+            log.warn("设置法律场景状态失败:场景ID为空");
+            return R.fail("场景ID不能为空");
+        }
+
+        if (status == null || (status != 0 && status != 1)) {
+            log.warn("设置法律场景状态失败:状态参数无效,status={}", status);
+            return R.fail("状态参数无效,必须为0(禁用)或1(启用)");
+        }
+
+        // 查询场景信息
+        LawyerLegalProblemScenario scenario = this.getById(id);
+        if (scenario == null) {
+            log.warn("设置法律场景状态失败:场景不存在,场景ID={}", id);
+            return R.fail("场景不存在");
+        }
+
+        // 更新状态
+        scenario.setStatus(status);
+        boolean result = this.updateById(scenario);
+        if (result) {
+            log.info("设置法律场景状态成功:场景ID={},状态={}", id, status);
+            return R.success("设置成功");
+        } else {
+            log.warn("设置法律场景状态失败:更新数据库失败,场景ID={},状态={}", id, status);
+            return R.fail("设置失败");
+        }
+    }
+
 }
 

+ 158 - 0
alien-store/src/main/java/shop/alien/store/service/impl/LawyerUserServiceImpl.java

@@ -16,12 +16,18 @@ import shop.alien.entity.store.LawyerLegalProblemScenario;
 import shop.alien.entity.store.LawyerServiceArea;
 import shop.alien.entity.store.LawyerUser;
 import shop.alien.entity.store.LawyerUserSearchHistory;
+import shop.alien.entity.store.excelVo.LawyerUserExcelVo;
+import shop.alien.entity.store.vo.LawyerUserVo;
 import shop.alien.mapper.LawyerUserMapper;
 import shop.alien.store.service.LawyerLegalProblemScenarioService;
 import shop.alien.store.service.LawyerServiceAreaService;
 import shop.alien.store.service.LawyerUserSearchHistoryService;
 import shop.alien.store.service.LawyerUserService;
+import shop.alien.util.common.ListToPage;
+import shop.alien.util.excel.EasyExcelUtil;
 
+import javax.servlet.http.HttpServletResponse;
+import java.text.SimpleDateFormat;
 import java.util.*;
 import java.util.stream.Collectors;
 
@@ -408,5 +414,157 @@ public class LawyerUserServiceImpl extends ServiceImpl<LawyerUserMapper, LawyerU
 
         return R.data(pageResult);
     }
+
+    @Override
+    public R<IPage<LawyerUserVo>> getLawyerList(String name,
+                                                String phone,
+                                                Integer firmId,
+                                                String startTime,
+                                                String endTime,
+                                                int page,
+                                                int size) {
+        log.info("LawyerUserServiceImpl.getLawyerList?name={},phone={},firmId={},startTime={},endTime={},page={},pageSize={}",
+                name, phone, firmId, startTime, endTime, page, size);
+
+        // 查询分页数据(在SQL层面使用LIMIT进行分页)
+        List<LawyerUserVo> list = lawyerUserMapper.selectLawyerUserList(name, phone, firmId, startTime, endTime);
+        if (list == null) {
+            list = new ArrayList<>();
+        }
+
+        return R.data(ListToPage.setPage(list, page, size));
+    }
+
+    @Override
+    public void exportLawyerList(HttpServletResponse response,
+                                 String name,
+                                 String phone,
+                                 Integer firmId,
+                                 String startTime,
+                                 String endTime,
+                                 Integer pageNum,
+                                 Integer pageSize) throws Exception {
+        log.info("LawyerUserServiceImpl.exportLawyerList?name={},phone={},firmId={},startTime={},endTime={},pageNum={},pageSize={}",
+                name, phone, firmId, startTime, endTime, pageNum, pageSize);
+
+        // 如果pageNum为null或0,则导出全部数据(设置一个很大的pageSize)
+        int page = (pageNum == null || pageNum == 0) ? 1 : pageNum;
+        int size = (pageNum == null || pageNum == 0) ? Integer.MAX_VALUE : (pageSize == null ? 10 : pageSize);
+
+        // 调用查询接口获取数据
+        R<IPage<LawyerUserVo>> result = getLawyerList(name, phone, firmId, startTime, endTime, page, size);
+
+        if (result == null || !result.isSuccess()) {
+            log.warn("获取律师列表失败:{}", result != null ? result.getMsg() : "返回结果为空");
+            throw new RuntimeException(result != null ? result.getMsg() : "获取数据失败,请稍后重试");
+        }
+
+        IPage<LawyerUserVo> pageResult = result.getData();
+        if (pageResult == null || pageResult.getRecords() == null || pageResult.getRecords().isEmpty()) {
+            log.warn("律师列表导出:无数据可导出");
+            throw new IllegalArgumentException("暂无数据可导出");
+        }
+
+        List<LawyerUserVo> voList = pageResult.getRecords();
+        log.info("律师列表导出:查询到{}条数据,导出{}条数据", pageResult.getTotal(), voList.size());
+
+        // 转换为Excel VO
+        List<LawyerUserExcelVo> excelVoList = convertToExcelVo(voList);
+
+        // 使用EasyExcelUtil导出
+        EasyExcelUtil.exportExcel(response, excelVoList, LawyerUserExcelVo.class, "中台律师列表", "中台律师列表");
+    }
+
+    /**
+     * 将LawyerUserVo转换为LawyerUserExcelVo
+     *
+     * @param voList LawyerUserVo列表
+     * @return LawyerUserExcelVo列表
+     */
+    private List<LawyerUserExcelVo> convertToExcelVo(List<LawyerUserVo> voList) {
+        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
+        SimpleDateFormat dateTimeFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+
+        return voList.stream().map(vo -> {
+            LawyerUserExcelVo excelVo = new LawyerUserExcelVo();
+            excelVo.setId(vo.getId());
+            excelVo.setName(vo.getName());
+            excelVo.setPhone(vo.getPhone());
+            excelVo.setFirmName(vo.getFirmName());
+//            excelVo.setLawyerCertificateNo(vo.getLawyerCertificateNo());
+//            excelVo.setPracticeYears(vo.getPracticeYears());
+//            excelVo.setServiceScore(vo.getServiceScore());
+//            excelVo.setServiceCount(vo.getServiceCount());
+            excelVo.setFirmId(vo.getFirmId());
+
+            // 转换执业开始日期
+//            if (vo.getPracticeStartDate() != null) {
+//                excelVo.setPracticeStartDate(dateFormat.format(vo.getPracticeStartDate()));
+//            }
+//
+//            // 转换用户状态
+//            if (vo.getStatus() != null) {
+//                excelVo.setStatusValue(vo.getStatus());
+//                excelVo.setStatus(vo.getStatus() == 1 ? "启用" : "禁用");
+//            }
+
+            // 转换资质认证状态
+//            if (vo.getCertificationStatus() != null) {
+//                excelVo.setCertificationStatusValue(vo.getCertificationStatus());
+//                switch (vo.getCertificationStatus()) {
+//                    case 0:
+//                        excelVo.setCertificationStatus("未认证");
+//                        break;
+//                    case 1:
+//                        excelVo.setCertificationStatus("认证中");
+//                        break;
+//                    case 2:
+//                        excelVo.setCertificationStatus("已认证");
+//                        break;
+//                    case 3:
+//                        excelVo.setCertificationStatus("认证失败");
+//                        break;
+//                    default:
+//                        excelVo.setCertificationStatus("未知");
+//                }
+//            }
+
+            // 转换是否推荐
+            if (vo.getIsRecommended() != null) {
+                excelVo.setIsRecommendedValue(vo.getIsRecommended());
+                excelVo.setIsRecommended(vo.getIsRecommended() == 1 ? "推荐" : "不推荐");
+            }
+
+            // 转换法律场景
+            if (vo.getScenarioNames() != null && !vo.getScenarioNames().isEmpty()) {
+                excelVo.setScenarioNames(vo.getScenarioNames());
+            } else {
+                excelVo.setScenarioNames("");
+            }
+
+            // 转换接单状态
+            if (vo.getOrderReceivingStatus() != null) {
+                excelVo.setOrderReceivingStatusValue(vo.getOrderReceivingStatus());
+                excelVo.setOrderReceivingStatus(vo.getOrderReceivingStatus() == 1 ? "接单中" : "不接单");
+            } else {
+                excelVo.setOrderReceivingStatus("未知");
+            }
+
+            // 设置收款账号
+            if (vo.getPaymentNum() != null && !vo.getPaymentNum().isEmpty()) {
+                excelVo.setPaymentNum(vo.getPaymentNum());
+            } else {
+                excelVo.setPaymentNum("");
+            }
+
+            // 转换创建时间
+//            if (vo.getCreatedTime() != null) {
+//                excelVo.setCreatedTimeValue(vo.getCreatedTime());
+//                excelVo.setCreatedTime(dateTimeFormat.format(vo.getCreatedTime()));
+//            }
+
+            return excelVo;
+        }).collect(Collectors.toList());
+    }
 }
 

+ 1473 - 0
alien-store/src/main/java/shop/alien/store/service/impl/LawyerUserViolationServiceImpl.java

@@ -0,0 +1,1473 @@
+package shop.alien.store.service.impl;
+
+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.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.BeanUtils;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.CollectionUtils;
+import shop.alien.entity.store.*;
+import shop.alien.entity.store.dto.LawyerUserViolationDto;
+import shop.alien.entity.store.vo.LawyerUserViolationVo;
+import shop.alien.entity.store.vo.WebSocketVo;
+import shop.alien.mapper.*;
+import shop.alien.store.config.WebSocketProcess;
+import shop.alien.store.service.LawyerUserService;
+import shop.alien.store.service.LawyerUserViolationService;
+import shop.alien.store.util.ali.AliApi;
+import shop.alien.util.common.EnumUtil;
+import shop.alien.util.common.constant.LawyerStatusEnum;
+
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.text.SimpleDateFormat;
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * <p>
+ * 律师用户举报 服务实现类
+ * </p>
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class LawyerUserViolationServiceImpl extends ServiceImpl<LawyerUserViolationMapper, LawyerUserViolation> implements LawyerUserViolationService {
+    /**
+     * 用户类型:商户用户
+     */
+    private static final String USER_TYPE_STORE = "1";
+
+    /**
+     * 用户类型:普通用户
+     */
+    private static final String USER_TYPE_LIFE = "2";
+
+    /**
+     * 用户类型:律师用户
+     */
+    private static final String USER_TYPE_LAWYER = "3";
+
+    /**
+     * 系统发送者ID
+     */
+    private static final String SYSTEM_SENDER_ID = "system";
+
+    /**
+     * 日期时间格式
+     */
+    private static final String DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
+
+    /**
+     * 默认违规原因
+     */
+    private static final String DEFAULT_VIOLATION_REASON = "其他原因";
+
+    /**
+     * 默认通知消息
+     */
+    private static final String DEFAULT_NOTICE_MESSAGE = "平台已受理,感谢您的反馈!";
+
+    /**
+     * 处理状态:通过
+     */
+    private static final String PROCESSING_STATUS_APPROVED = "1";
+
+    /**
+     * 举报内容类型:订单举报
+     */
+    private static final String REPORT_CONTEXT_TYPE_ORDER = "6";
+
+    /**
+     * 删除标志:未删除
+     */
+    private static final Integer DELETE_FLAG_NOT_DELETED = 0;
+
+    /**
+     * 图片分隔符
+     */
+    private static final String IMAGE_SEPARATOR = ",";
+
+    /**
+     * 最大分页大小
+     */
+    private static final int MAX_PAGE_SIZE = 100;
+
+    private final LawyerUserViolationMapper lawyerUserViolationMapper;
+
+    private final LawyerUserMapper lawyerUserMapper;
+
+    private final StoreUserMapper storeUserMapper;
+
+    private final LifeUserMapper lifeUserMapper;
+
+    private final LawyerUserService lawyerUserService;
+
+    private final LifeNoticeMapper lifeNoticeMapper;
+
+    private final WebSocketProcess webSocketProcess;
+
+    private final StoreDictionaryMapper storeDictionaryMapper;
+
+    private final LawyerConsultationOrderMapper consultationOrderMapper;
+
+    private final AliApi aliApi;
+
+
+    /**
+     * 用户举报处理
+     * <p>
+     * 处理用户举报信息,包括:
+     * 1. 参数校验
+     * 2. 查询订单信息并设置举报人和被举报人ID
+     * 3. 检查订单是否已被举报(一笔订单只能举报一次)
+     * 4. 保存举报记录到数据库
+     * 5. 向举报人发送受理通知
+     * </p>
+     *
+     * @param lawyerUserViolation 举报信息对象,不能为null
+     * @return 插入成功的记录数,失败返回0
+     * @throws RuntimeException 当数据库操作失败或业务逻辑处理异常时抛出
+     * @author system
+     * @since 2025-01-XX
+     */
+    @Override
+    public int userReporting(LawyerUserViolation lawyerUserViolation) {
+        // 参数校验
+        validateReportingParams(lawyerUserViolation);
+
+        String orderNumber = lawyerUserViolation.getOrderNumber();
+
+        // 查询订单信息并设置举报人和被举报人ID
+        LawyerConsultationOrder consultationOrder = queryAndSetUserIds(lawyerUserViolation, orderNumber);
+        if (consultationOrder == null) {
+            log.warn("订单不存在或已删除,订单号:{}", orderNumber);
+            return 0;
+        }
+
+        try {
+            // 检查订单是否已被举报(一笔订单只能举报一次)
+            boolean hasReported = checkOrderAlreadyReported(orderNumber);
+            if (hasReported) {
+                log.warn("订单已被举报,订单号:{}", orderNumber);
+                throw new RuntimeException("该订单已被举报,无法重复举报");
+            }
+
+            // 保存举报记录
+            int result = saveViolationRecord(lawyerUserViolation, consultationOrder);
+            if (result <= 0) {
+                return 0;
+            }
+
+            log.info("用户举报记录保存成功,举报ID:{}", lawyerUserViolation.getId());
+
+            // 向举报人发送受理通知
+            sendReportNoticeToReporter(lawyerUserViolation);
+
+            log.info("用户举报处理完成,举报ID:{}", lawyerUserViolation.getId());
+            return result;
+
+        } catch (RuntimeException e) {
+            log.error("用户举报处理业务异常,订单号:{},异常信息:{}", orderNumber, e.getMessage());
+            throw e;
+        } catch (Exception e) {
+            log.error("用户举报处理异常,订单号:{},异常信息:{}", orderNumber, e.getMessage(), e);
+            throw new RuntimeException("用户举报处理失败:" + e.getMessage(), e);
+        }
+    }
+
+    /**
+     * 校验举报参数
+     *
+     * @param lawyerUserViolation 举报信息对象
+     * @throws RuntimeException 当参数无效时抛出
+     */
+    private void validateReportingParams(LawyerUserViolation lawyerUserViolation) {
+        if (lawyerUserViolation == null) {
+            log.warn("用户举报参数为空");
+            throw new RuntimeException("举报信息不能为空");
+        }
+
+        String orderNumber = lawyerUserViolation.getOrderNumber();
+        if (StringUtils.isEmpty(orderNumber)) {
+            log.warn("用户举报订单号为空");
+            throw new RuntimeException("订单号不能为空");
+        }
+    }
+
+    /**
+     * 查询订单信息并设置举报人和被举报人ID
+     *
+     * @param lawyerUserViolation 举报信息对象
+     * @param orderNumber         订单号
+     * @return 咨询订单对象,如果订单不存在返回null
+     */
+    private LawyerConsultationOrder queryAndSetUserIds(LawyerUserViolation lawyerUserViolation, String orderNumber) {
+        LambdaQueryWrapper<LawyerConsultationOrder> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(LawyerConsultationOrder::getOrderNumber, orderNumber)
+                .eq(LawyerConsultationOrder::getDeleteFlag, 0)
+                .last("LIMIT 1");
+
+        List<LawyerConsultationOrder> orderList = consultationOrderMapper.selectList(queryWrapper);
+        if (CollectionUtils.isEmpty(orderList)) {
+            return null;
+        }
+
+        LawyerConsultationOrder consultationOrder = orderList.get(0);
+        // 设置被举报人ID(律师用户ID)
+        if (consultationOrder.getLawyerUserId() != null) {
+            lawyerUserViolation.setReportedUserId(consultationOrder.getLawyerUserId().toString());
+        }
+        // 设置举报人ID(客户用户ID)
+        if (consultationOrder.getClientUserId() != null) {
+            lawyerUserViolation.setReportingUserId(consultationOrder.getClientUserId().toString());
+        }
+
+        return consultationOrder;
+    }
+
+    /**
+     * 保存举报记录
+     *
+     * @param lawyerUserViolation 举报信息对象
+     * @param consultationOrder   咨询订单对象
+     * @return 插入成功的记录数,失败返回0
+     */
+    private int saveViolationRecord(LawyerUserViolation lawyerUserViolation, LawyerConsultationOrder consultationOrder) {
+        int result = lawyerUserViolationMapper.insert(lawyerUserViolation);
+        if (result <= 0) {
+            log.warn("用户举报记录保存失败,被举报用户ID:{},举报用户ID:{}",
+                    consultationOrder.getLawyerUserId(), consultationOrder.getClientUserId());
+        }
+        return result;
+    }
+
+    /**
+     * 检查订单是否已被举报
+     * <p>
+     * 根据订单ID查询是否已存在举报记录(一笔订单只能举报一次)
+     * </p>
+     *
+     * @param orderNumber 订单号
+     * @return true-已举报,false-未举报
+     */
+    private boolean checkOrderAlreadyReported(String orderNumber) {
+        if (StringUtils.isEmpty(orderNumber)) {
+            return false;
+        }
+
+        try {
+            LambdaQueryWrapper<LawyerUserViolation> queryWrapper = new LambdaQueryWrapper<>();
+            queryWrapper.eq(LawyerUserViolation::getOrderNumber, orderNumber)
+                    .last("LIMIT 1");
+
+            LawyerUserViolation existingReport = lawyerUserViolationMapper.selectOne(queryWrapper);
+            return existingReport != null;
+        } catch (Exception e) {
+            log.error("检查订单举报状态异常,订单号:{},异常信息:{}",
+                    orderNumber, e.getMessage(), e);
+            // 查询异常时,为了安全起见,返回true,阻止重复举报
+            return true;
+        }
+    }
+
+    /**
+     * 向举报人发送受理通知
+     *
+     * @param lawyerUserViolation 举报信息对象
+     */
+    private void sendReportNoticeToReporter(LawyerUserViolation lawyerUserViolation) {
+        try {
+            LifeNotice lifeNotice = getLifeNotice(lawyerUserViolation);
+            if (lifeNotice == null) {
+                log.warn("生成举报人通知失败,举报ID:{}", lawyerUserViolation.getId());
+                return;
+            }
+
+            int noticeResult = lifeNoticeMapper.insert(lifeNotice);
+            if (noticeResult <= 0) {
+                log.warn("保存举报人通知失败,举报ID:{}", lawyerUserViolation.getId());
+                return;
+            }
+
+            // 发送WebSocket消息
+            WebSocketVo webSocketVo = buildWebSocketVo(lifeNotice);
+            webSocketProcess.sendMessage(lifeNotice.getReceiverId(),
+                    JSONObject.from(webSocketVo).toJSONString());
+
+            log.info("举报人通知发送成功,接收人ID:{}", lifeNotice.getReceiverId());
+
+        } catch (Exception e) {
+            log.error("向举报人发送通知异常,举报ID:{},异常信息:{}",
+                    lawyerUserViolation.getId(), e.getMessage(), e);
+        }
+    }
+
+    /**
+     * 向被举报人发送通知
+     *
+     * @param lawyerUserViolation 举报信息对象
+     */
+    private void sendReportNoticeToReported(LawyerUserViolation lawyerUserViolation) {
+        try {
+            LifeNotice lifeNoticeReported = getLifeReportedNotice(lawyerUserViolation);
+            if (lifeNoticeReported == null) {
+                log.debug("无需向被举报人发送通知,举报ID:{}", lawyerUserViolation.getId());
+                return;
+            }
+
+            int noticeResult = lifeNoticeMapper.insert(lifeNoticeReported);
+            if (noticeResult <= 0) {
+                log.warn("保存被举报人通知失败,举报ID:{}", lawyerUserViolation.getId());
+                return;
+            }
+
+            // 发送WebSocket消息
+            WebSocketVo webSocketVo = buildWebSocketVo(lifeNoticeReported);
+            webSocketProcess.sendMessage(lifeNoticeReported.getReceiverId(),
+                    JSONObject.from(webSocketVo).toJSONString());
+
+            log.info("被举报人通知发送成功,接收人ID:{}", lifeNoticeReported.getReceiverId());
+
+        } catch (Exception e) {
+            log.error("向被举报人发送通知异常,举报ID:{},异常信息:{}",
+                    lawyerUserViolation.getId(), e.getMessage(), e);
+        }
+    }
+
+    /**
+     * 构建WebSocket消息对象
+     *
+     * @param lifeNotice 通知对象
+     * @return WebSocketVo对象
+     */
+    private WebSocketVo buildWebSocketVo(LifeNotice lifeNotice) {
+        WebSocketVo webSocketVo = new WebSocketVo();
+        webSocketVo.setSenderId(SYSTEM_SENDER_ID);
+        webSocketVo.setReceiverId(lifeNotice.getReceiverId());
+        webSocketVo.setCategory("notice");
+        webSocketVo.setNoticeType("1");
+        webSocketVo.setIsRead(0);
+        webSocketVo.setText(JSONObject.from(lifeNotice).toJSONString());
+        return webSocketVo;
+    }
+
+    /**
+     * 生成举报人通知消息
+     *
+     * @param lawyerUserViolation 举报信息对象
+     * @return 通知对象,如果生成失败返回null
+     */
+    private LifeNotice getLifeNotice(LawyerUserViolation lawyerUserViolation) {
+        if (lawyerUserViolation == null) {
+            log.warn("生成举报人通知失败,举报信息对象为空");
+            return null;
+        }
+
+        try {
+            LifeNotice lifeNotice = new LifeNotice();
+            lifeNotice.setSenderId(SYSTEM_SENDER_ID);
+            lifeNotice.setBusinessId(lawyerUserViolation.getId());
+            lifeNotice.setTitle("咨询订单举报通知");
+
+            // 获取举报人接收ID
+            String receiverId = getReporterReceiverId(lawyerUserViolation);
+            if (StringUtils.isEmpty(receiverId)) {
+                log.warn("获取举报人接收ID失败,举报ID:{}", lawyerUserViolation.getId());
+                return null;
+            }
+            lifeNotice.setReceiverId(receiverId);
+
+            // 构建通知消息内容
+            String message = buildReporterMessage(lawyerUserViolation);
+            JSONObject jsonObject = new JSONObject();
+            jsonObject.put("title", "举报通知");
+            jsonObject.put("message", message);
+            lifeNotice.setContext(jsonObject.toJSONString());
+            lifeNotice.setNoticeType(1);
+
+            return lifeNotice;
+
+        } catch (Exception e) {
+            log.error("生成举报人通知异常,举报ID:{},异常信息:{}",
+                    lawyerUserViolation.getId(), e.getMessage(), e);
+            return null;
+        }
+    }
+
+    /**
+     * 获取举报人接收ID
+     *
+     * @param lawyerUserViolation 举报信息对象
+     * @return 接收人ID,格式:lawyer_/store_/user_ + 手机号
+     */
+    private String getReporterReceiverId(LawyerUserViolation lawyerUserViolation) {
+        String reportingUserType = lawyerUserViolation.getReportingUserType();
+        String reportingUserId = lawyerUserViolation.getReportingUserId();
+
+        if (StringUtils.isEmpty(reportingUserId)) {
+            log.warn("举报用户ID为空");
+            return null;
+        }
+
+        String phone = null;
+        if (USER_TYPE_LAWYER.equals(reportingUserType)) {
+            LawyerUser lawyerUser = lawyerUserMapper.selectById(reportingUserId);
+            if (lawyerUser != null && StringUtils.isNotEmpty(lawyerUser.getPhone())) {
+                phone = lawyerUser.getPhone();
+                return "lawyer_" + phone;
+            }
+        } else if (USER_TYPE_STORE.equals(reportingUserType)) {
+            StoreUser storeUser = storeUserMapper.selectById(reportingUserId);
+            if (storeUser != null && StringUtils.isNotEmpty(storeUser.getPhone())) {
+                phone = storeUser.getPhone();
+                return "store_" + phone;
+            }
+        } else if (USER_TYPE_LIFE.equals(reportingUserType)) {
+            LifeUser lifeUser = lifeUserMapper.selectById(reportingUserId);
+            if (lifeUser != null && StringUtils.isNotEmpty(lifeUser.getUserPhone())) {
+                phone = lifeUser.getUserPhone();
+                return "user_" + phone;
+            }
+        }
+
+        log.warn("获取举报人手机号失败,用户类型:{},用户ID:{}", reportingUserType, reportingUserId);
+        return null;
+    }
+
+    /**
+     * 构建举报人通知消息内容
+     *
+     * @param lawyerUserViolation 举报信息对象
+     * @return 消息内容
+     */
+    private String buildReporterMessage(LawyerUserViolation lawyerUserViolation) {
+        String reportContextType = lawyerUserViolation.getReportContextType();
+
+        // 如果举报内容类型不包含"6",返回默认消息
+        if (StringUtils.isEmpty(reportContextType) || !reportContextType.contains("6")) {
+            return DEFAULT_NOTICE_MESSAGE;
+        }
+
+        try {
+            // 获取被举报用户名称
+            String reportedUserName = getReportedUserName(lawyerUserViolation);
+            if (StringUtils.isEmpty(reportedUserName)) {
+                log.warn("获取被举报用户名称失败,使用默认消息");
+                return DEFAULT_NOTICE_MESSAGE;
+            }
+
+            // 格式化时间
+            SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_TIME_FORMAT);
+            String reportDate = dateFormat.format(new Date());
+
+            // 获取违规原因
+            String violationReason = getViolationReason(lawyerUserViolation);
+
+            return String.format("您%s举报%s律师%s,已提交至平台审核,1-3个工作日会发送您审核结果,请注意查收。",
+                    reportDate, reportedUserName, violationReason);
+
+        } catch (Exception e) {
+            log.error("构建举报人消息异常,使用默认消息,异常信息:{}", e.getMessage(), e);
+            return DEFAULT_NOTICE_MESSAGE;
+        }
+    }
+
+    /**
+     * 获取违规原因
+     *
+     * @param lawyerUserViolation 举报信息对象
+     * @return 违规原因文本
+     */
+    private String getViolationReason(LawyerUserViolation lawyerUserViolation) {
+        String violationReasonId = lawyerUserViolation.getViolationReason();
+        String violationReason = DEFAULT_VIOLATION_REASON;
+        if (StringUtils.isEmpty(violationReasonId)) {
+            return violationReason;
+        }
+
+        if(violationReasonId.equals("1")){
+            violationReason = "服务态度差";
+        } else if(violationReasonId.equals("2")){
+            violationReason = "专业能力差";
+        } else if(violationReasonId.equals("3")){
+            violationReason = "响应时间超过24小时";
+        }
+        return violationReason;
+    }
+
+    /**
+     * 获取被举报用户名称
+     *
+     * @param lawyerUserViolation 举报信息对象
+     * @return 被举报用户名称
+     */
+    private String getReportedUserName(LawyerUserViolation lawyerUserViolation) {
+        String reportedUserType = lawyerUserViolation.getReportedUserType();
+        String reportedUserId = lawyerUserViolation.getReportedUserId();
+
+        if (StringUtils.isEmpty(reportedUserId)) {
+            return null;
+        }
+
+        if (USER_TYPE_LAWYER.equals(reportedUserType)) {
+            LawyerUser lawyerUser = lawyerUserMapper.selectById(reportedUserId);
+            return lawyerUser != null ? lawyerUser.getName() : null;
+        } else if (USER_TYPE_STORE.equals(reportedUserType)) {
+            StoreUser storeUser = storeUserMapper.selectById(reportedUserId);
+            return storeUser != null ? storeUser.getNickName() : null;
+        } else {
+            LifeUser lifeUser = lifeUserMapper.selectById(reportedUserId);
+            return lifeUser != null ? lifeUser.getUserName() : null;
+        }
+    }
+
+    /**
+     * 获取举报用户名称
+     *
+     * @param lawyerUserViolation 举报信息对象
+     * @return 举报用户名称
+     */
+    private String getReportingUserName(LawyerUserViolation lawyerUserViolation) {
+        String reportingUserType = lawyerUserViolation.getReportingUserType();
+        String reportingUserId = lawyerUserViolation.getReportingUserId();
+
+        if (StringUtils.isEmpty(reportingUserId)) {
+            return null;
+        }
+
+        if (USER_TYPE_LAWYER.equals(reportingUserType)) {
+            LawyerUser lawyerUser = lawyerUserMapper.selectById(reportingUserId);
+            return lawyerUser != null ? lawyerUser.getName() : null;
+        } else if (USER_TYPE_STORE.equals(reportingUserType)) {
+            StoreUser storeUser = storeUserMapper.selectById(reportingUserId);
+            return storeUser != null ? storeUser.getNickName() : null;
+        } else {
+            LifeUser lifeUser = lifeUserMapper.selectById(reportingUserId);
+            return lifeUser != null ? lifeUser.getUserName() : null;
+        }
+    }
+
+    /**
+     * 生成被举报人通知消息
+     *
+     * @param lawyerUserViolation 举报信息对象
+     * @return 通知对象,如果不需要发送或生成失败返回null
+     */
+    private LifeNotice getLifeReportedNotice(LawyerUserViolation lawyerUserViolation) {
+        if (lawyerUserViolation == null) {
+            log.warn("生成被举报人通知失败,举报信息对象为空");
+            return null;
+        }
+
+        // 只有举报内容类型为"6"时才需要向被举报人发送通知
+        String reportContextType = lawyerUserViolation.getReportContextType();
+        if (!"6".equals(reportContextType)) {
+            return null;
+        }
+
+        try {
+            // 获取被举报人接收ID
+            String receiverId = getReportedReceiverId(lawyerUserViolation);
+            if (StringUtils.isEmpty(receiverId)) {
+                log.warn("获取被举报人接收ID失败,举报ID:{}", lawyerUserViolation.getId());
+                return null;
+            }
+
+            // 构建通知消息
+            LifeNotice lifeNotice = new LifeNotice();
+            lifeNotice.setSenderId(SYSTEM_SENDER_ID);
+            lifeNotice.setBusinessId(lawyerUserViolation.getId());
+            lifeNotice.setTitle("咨询订单举报通知");
+            lifeNotice.setReceiverId(receiverId);
+
+            // 构建消息内容
+            String message = buildReportedMessage(lawyerUserViolation);
+            if (StringUtils.isEmpty(message)) {
+                return null;
+            }
+
+            JSONObject jsonObject = new JSONObject();
+            jsonObject.put("message", message);
+            lifeNotice.setContext(jsonObject.toJSONString());
+            lifeNotice.setNoticeType(1);
+
+            return lifeNotice;
+
+        } catch (Exception e) {
+            log.error("生成被举报人通知异常,举报ID:{},异常信息:{}",
+                    lawyerUserViolation.getId(), e.getMessage(), e);
+            return null;
+        }
+    }
+
+    /**
+     * 获取被举报人接收ID
+     *
+     * @param lawyerUserViolation 举报信息对象
+     * @return 接收人ID,格式:lawyer_/store_/user_ + 手机号
+     */
+    private String getReportedReceiverId(LawyerUserViolation lawyerUserViolation) {
+        String reportedUserType = lawyerUserViolation.getReportedUserType();
+        String reportedUserId = lawyerUserViolation.getReportedUserId();
+
+        if (StringUtils.isEmpty(reportedUserId)) {
+            log.warn("被举报用户ID为空");
+            return null;
+        }
+
+        String phone = null;
+        if (USER_TYPE_LAWYER.equals(reportedUserType)) {
+            LawyerUser lawyerUser = lawyerUserMapper.selectById(reportedUserId);
+            if (lawyerUser != null && StringUtils.isNotEmpty(lawyerUser.getPhone())) {
+                phone = lawyerUser.getPhone();
+                return "lawyer_" + phone;
+            }
+        } else if (USER_TYPE_STORE.equals(reportedUserType)) {
+            StoreUser storeUser = storeUserMapper.selectById(reportedUserId);
+            if (storeUser != null && StringUtils.isNotEmpty(storeUser.getPhone())) {
+                phone = storeUser.getPhone();
+                return "store_" + phone;
+            }
+        } else {
+            LifeUser lifeUser = lifeUserMapper.selectById(reportedUserId);
+            if (lifeUser != null && StringUtils.isNotEmpty(lifeUser.getUserPhone())) {
+                phone = lifeUser.getUserPhone();
+                return "user_" + phone;
+            }
+        }
+
+        log.warn("获取被举报人手机号失败,用户类型:{},用户ID:{}", reportedUserType, reportedUserId);
+        return null;
+    }
+
+    /**
+     * 构建被举报人通知消息内容
+     *
+     * @param lawyerUserViolation 举报信息对象
+     * @return 消息内容
+     */
+    private String buildReportedMessage(LawyerUserViolation lawyerUserViolation) {
+        try {
+            // 获取违规类型文本
+            String violationType = lawyerUserViolation.getViolationReason();
+            if (StringUtils.isEmpty(violationType)) {
+                log.warn("违规类型为空,无法构建被举报人消息");
+                return null;
+            }
+
+            String violationText = EnumUtil.getStatusValue(Integer.parseInt(violationType));
+
+            // 格式化时间
+            SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_TIME_FORMAT);
+            String reportDate = dateFormat.format(new Date());
+
+            return String.format("您在%s被举报涉嫌%s,平台将会进行核实。如确实存在违规行为,平台将禁用您的账号**天,到期后账号可恢复使用,应用内的环境需要我们共同维护。",
+                    reportDate, violationText);
+
+        } catch (Exception e) {
+            log.error("构建被举报人消息异常,异常信息:{}", e.getMessage(), e);
+            return null;
+        }
+    }
+
+    @Override
+    public Map<String, Object> reportListByUserId(String userId) {
+        Map<String, Object> resultMap = new HashMap<String, Object>();
+        LambdaUpdateWrapper<LawyerUserViolation> wrapper = new LambdaUpdateWrapper<>();
+        wrapper.eq(LawyerUserViolation::getReportingUserId, userId).orderByDesc(LawyerUserViolation::getCreatedTime);
+        List<LawyerUserViolation> lawyerUserViolations = lawyerUserViolationMapper.selectList(wrapper);
+        List<LawyerUserViolation> filteredViolations = lawyerUserViolations.stream().filter(violation -> !"0".equals(violation.getProcessingStatus())).collect(Collectors.toList());
+        List<LawyerUserViolationVo> lawyerUserViolationsVo = new ArrayList<>();
+        for (LawyerUserViolation violation : filteredViolations) {
+            LawyerUserViolationVo vo = new LawyerUserViolationVo();
+            BeanUtils.copyProperties(violation, vo);
+            lawyerUserViolationsVo.add(vo);
+        }
+        lawyerUserViolationsVo.forEach(lawyerUserViolationVo -> {
+            lawyerUserViolationVo.setReportResultNotification("举报结果通知");
+        });
+        if (!lawyerUserViolations.isEmpty()) {
+            resultMap.put("createTime", lawyerUserViolations.get(0).getCreatedTime());
+            resultMap.put("data", lawyerUserViolationsVo);
+        }
+        return resultMap;
+    }
+
+    /**
+     * 根据ID查询举报详情
+     * <p>
+     * 根据举报ID查询举报详细信息,包括被举报人信息、举报原因、举报凭证等
+     * </p>
+     *
+     * @param id 举报记录ID,不能为空
+     * @return 举报详情VO对象,如果记录不存在返回null
+     * @throws RuntimeException 当参数无效或查询异常时抛出
+     * @author system
+     * @since 2025-01-XX
+     */
+    @Override
+    public LawyerUserViolationVo reportListById(String id) {
+        // 参数校验
+        if (StringUtils.isEmpty(id)) {
+            log.warn("查询举报详情参数为空,ID:{}", id);
+            throw new RuntimeException("举报ID不能为空");
+        }
+
+        try {
+            // 查询举报记录
+            LawyerUserViolation lawyerUserViolation = lawyerUserViolationMapper.selectById(id);
+            if (lawyerUserViolation == null) {
+                log.warn("举报记录不存在,ID:{}", id);
+                return null;
+            }
+
+            // 构建VO对象
+            LawyerUserViolationVo violationVo = new LawyerUserViolationVo();
+            BeanUtils.copyProperties(lawyerUserViolation, violationVo);
+
+            // 获取被举报用户名称
+            String reportedUserName = getReportedUserName(lawyerUserViolation);
+            violationVo.setReportedUserName(reportedUserName);
+
+            // 获取举报用户名称
+            String reportingUserName = getReportingUserName(lawyerUserViolation);
+            violationVo.setReportUserName(reportingUserName);
+
+            log.info("查询举报详情成功,ID:{}", id);
+            return violationVo;
+
+        } catch (Exception e) {
+            log.error("查询举报详情异常,ID:{},异常信息:{}", id, e.getMessage(), e);
+            throw new RuntimeException("查询举报详情失败:" + e.getMessage(), e);
+        }
+    }
+
+    /**
+     * 分页查询违规举报信息
+     * <p>
+     * 根据查询条件分页查询律师用户违规举报信息,支持按订单号、处理状态、
+     * 被举报人名称、违规原因、手机号、时间范围等条件进行筛选查询
+     * </p>
+     *
+     * @param pageNum          当前页码,从1开始,必须大于0
+     * @param pageSize         每页大小,必须大于0且不超过最大值
+     * @param orderNumber          订单号,支持模糊查询,可为空
+     * @param phone            举报人手机号,精确匹配,可为空
+     * @param processingStatus 处理状态,精确匹配,可为空
+     * @param reportedUserName 被举报人名称,支持模糊查询,可为空
+     * @param reportedPhone    被举报人手机号,精确匹配,可为空
+     * @param violationReason  违规原因,精确匹配,可为空
+     * @param startTime        开始时间,格式:yyyy-MM-dd HH:mm:ss,可为空
+     * @param endTime          结束时间,格式:yyyy-MM-dd HH:mm:ss,可为空
+     * @return 分页查询结果,包含违规举报DTO列表
+     * @throws RuntimeException 当分页参数无效或查询异常时抛出
+     * @author system
+     * @since 2025-01-XX
+     */
+    @Override
+    public IPage<LawyerUserViolationDto> getViolationPage(int pageNum, int pageSize, String orderNumber,
+                                                          String phone, String processingStatus,
+                                                          String reportedUserName, String reportedPhone,
+                                                          String violationReason, String startTime,
+                                                          String endTime) {
+        // 参数校验
+        validatePageParams(pageNum, pageSize);
+
+        try {
+            // 构建分页对象
+            IPage<LawyerUserViolationVo> pageRequest = new Page<>(pageNum, pageSize);
+
+            // 构建查询条件
+            QueryWrapper<LawyerUserViolationVo> queryWrapper = buildQueryWrapper(orderNumber, phone,
+                    processingStatus, reportedUserName, reportedPhone, violationReason, startTime, endTime);
+
+            // 执行分页查询
+            IPage<LawyerUserViolationVo> resultPage = lawyerUserViolationMapper.getViolationPage(pageRequest,
+                    queryWrapper);
+
+            // 转换为DTO并处理数据
+            return convertToDtoPage(resultPage);
+
+        } catch (RuntimeException e) {
+            log.error("分页查询违规举报信息业务异常,页码:{},每页大小:{},异常信息:{}",
+                    pageNum, pageSize, e.getMessage());
+            throw e;
+        } catch (Exception e) {
+            log.error("分页查询违规举报信息异常,页码:{},每页大小:{},异常信息:{}",
+                    pageNum, pageSize, e.getMessage(), e);
+            throw new RuntimeException("分页查询违规举报信息失败:" + e.getMessage(), e);
+        }
+    }
+
+    /**
+     * 校验分页参数
+     *
+     * @param page 当前页码
+     * @param size 每页大小
+     * @throws RuntimeException 当参数无效时抛出
+     */
+    private void validatePageParams(int page, int size) {
+        if (page < 1) {
+            log.warn("分页查询参数无效,页码:{}", page);
+            throw new RuntimeException("页码必须大于0");
+        }
+        if (size < 1) {
+            log.warn("分页查询参数无效,每页大小:{}", size);
+            throw new RuntimeException("每页大小必须大于0");
+        }
+        if (size > MAX_PAGE_SIZE) {
+            log.warn("分页查询参数无效,每页大小超过最大值:{}", size);
+            throw new RuntimeException("每页大小不能超过" + MAX_PAGE_SIZE);
+        }
+    }
+
+    /**
+     * 构建查询条件
+     * <p>
+     * 根据传入的查询参数动态构建查询条件,支持多条件组合查询
+     * </p>
+     *
+     * @param orderId          订单号,支持模糊查询
+     * @param phone            举报人手机号,精确匹配
+     * @param processingStatus 处理状态,精确匹配
+     * @param reportedUserName 被举报人名称,支持模糊查询
+     * @param reportedPhone    被举报人手机号,精确匹配
+     * @param violationReason  违规原因,精确匹配
+     * @param startTime        开始时间,大于等于查询
+     * @param endTime          结束时间,小于等于查询
+     * @return 查询条件包装器
+     */
+    private QueryWrapper<LawyerUserViolationVo> buildQueryWrapper(String orderId, String phone,
+                                                                  String processingStatus,
+                                                                  String reportedUserName,
+                                                                  String reportedPhone,
+                                                                  String violationReason,
+                                                                  String startTime, String endTime) {
+        QueryWrapper<LawyerUserViolationVo> queryWrapper = new QueryWrapper<>();
+
+        // 基础查询条件:未删除且为订单举报
+        queryWrapper.eq("luv.delete_flag", DELETE_FLAG_NOT_DELETED)
+                .eq("luv.report_context_type", REPORT_CONTEXT_TYPE_ORDER);
+
+        // 动态查询条件:订单号模糊查询
+        if (StringUtils.isNotEmpty(orderId)) {
+            queryWrapper.like("luv.order_number", orderId);
+        }
+
+        // 动态查询条件:举报人手机号精确匹配
+        if (StringUtils.isNotEmpty(phone)) {
+            queryWrapper.eq("ui.phone", phone);
+        }
+
+        // 动态查询条件:处理状态精确匹配
+        if (StringUtils.isNotEmpty(processingStatus)) {
+            queryWrapper.eq("luv.processing_status", processingStatus);
+        }
+
+        // 动态查询条件:被举报人名称模糊查询
+        if (StringUtils.isNotEmpty(reportedUserName)) {
+            queryWrapper.like("ui_reported.nick_name", reportedUserName);
+        }
+
+        // 动态查询条件:被举报人手机号精确匹配
+        if (StringUtils.isNotEmpty(reportedPhone)) {
+            queryWrapper.eq("ui_reported.phone", reportedPhone);
+        }
+
+        // 动态查询条件:违规原因精确匹配
+        if (StringUtils.isNotEmpty(violationReason)) {
+            queryWrapper.eq("luv.violation_reason", violationReason);
+        }
+
+        // 动态查询条件:开始时间范围查询(大于等于)
+        if (StringUtils.isNotEmpty(startTime)) {
+            queryWrapper.ge("luv.created_time", startTime);
+        }
+
+        // 动态查询条件:结束时间范围查询(小于等于)
+        if (StringUtils.isNotEmpty(endTime)) {
+            queryWrapper.le("luv.created_time", endTime);
+        }
+
+        // 排序:按更新时间倒序
+        queryWrapper.orderByDesc("luv.updated_time");
+
+        return queryWrapper;
+    }
+
+    /**
+     * 将VO分页结果转换为DTO分页结果
+     *
+     * @param voPage VO分页结果
+     * @return DTO分页结果
+     */
+    private IPage<LawyerUserViolationDto> convertToDtoPage(IPage<LawyerUserViolationVo> voPage) {
+        return voPage.convert(this::convertToDto);
+    }
+
+    /**
+     * 将VO对象转换为DTO对象
+     *
+     * @param vo VO对象
+     * @return DTO对象
+     */
+    private LawyerUserViolationDto convertToDto(LawyerUserViolationVo vo) {
+        LawyerUserViolationDto dto = new LawyerUserViolationDto();
+        BeanUtils.copyProperties(vo, dto);
+
+        // 处理举报凭证图片
+        processReportEvidenceImages(vo, dto);
+
+        // 设置用户名称信息
+        setUserNames(vo, dto);
+
+        return dto;
+    }
+
+    /**
+     * 处理举报凭证图片
+     *
+     * @param vo  VO对象
+     * @param dto DTO对象
+     */
+    private void processReportEvidenceImages(LawyerUserViolationVo vo, LawyerUserViolationDto dto) {
+        if (Objects.isNull(vo.getReportEvidenceImg())) {
+            dto.setImage("");
+            dto.setImageList(new ArrayList<>());
+            return;
+        }
+
+        List<String> imageList = Arrays.stream(vo.getReportEvidenceImg().split(IMAGE_SEPARATOR))
+                .map(String::trim)
+                .filter(StringUtils::isNotEmpty)
+                .collect(Collectors.toList());
+
+        if (!imageList.isEmpty()) {
+            dto.setImage(imageList.get(0));
+            dto.setImageList(imageList);
+        } else {
+            dto.setImage("");
+            dto.setImageList(new ArrayList<>());
+        }
+    }
+
+    /**
+     * 设置用户名称信息
+     *
+     * @param vo  VO对象
+     * @param dto DTO对象
+     */
+    private void setUserNames(LawyerUserViolationVo vo, LawyerUserViolationDto dto) {
+        dto.setNickname(vo.getNickName());
+        dto.setReportUserName(vo.getReportUserName());
+        dto.setReportedUserName(vo.getReportedUserName());
+    }
+
+    /**
+     * 审批举报
+     * <p>
+     * 处理举报审批,包括:
+     * 1. 更新举报处理状态
+     * 2. 更新订单状态为已退款
+     * 3. 调用支付宝退款接口进行退款(如果退款失败,会回滚订单状态更新)
+     * 4. 向举报人发送审批结果通知
+     * 5. 向被举报人发送通知(如需要)
+     * </p>
+     *
+     * @param id               举报记录ID
+     * @param processingStatus 处理状态(1-通过,其他-驳回)
+     * @param reportResult     处理结果说明
+     * @author system
+     * @since 2025-01-XX
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void approve(int id, String processingStatus, String reportResult) {
+        // 参数校验
+        if (id <= 0 || StringUtils.isBlank(processingStatus)) {
+            log.warn("审批举报参数无效,ID:{},处理状态:{}", id, processingStatus);
+            return;
+        }
+
+        try {
+            // 查询举报记录
+            LawyerUserViolation violation = lawyerUserViolationMapper.selectById(id);
+            if (violation == null) {
+                log.warn("举报记录不存在,ID:{}", id);
+                return;
+            }
+
+            // 更新举报记录状态
+            updateViolationStatus(violation, processingStatus, reportResult);
+
+            // 根据举报处理进行订单状态翻转
+            if (PROCESSING_STATUS_APPROVED.equals(processingStatus)) {
+                // 审批通过
+                LambdaUpdateWrapper<LawyerConsultationOrder> lawyerConsultationOrderLambdaUpdateWrapper = new LambdaUpdateWrapper<>();
+                lawyerConsultationOrderLambdaUpdateWrapper.eq(LawyerConsultationOrder::getOrderNumber, violation.getOrderNumber());
+                lawyerConsultationOrderLambdaUpdateWrapper.set(LawyerConsultationOrder::getOrderStatus, LawyerStatusEnum.REFUNDED.getStatus());
+                int result = consultationOrderMapper.update(null, lawyerConsultationOrderLambdaUpdateWrapper);
+                if (result > 0) {
+                    log.info("订单状态更新成功,订单编号:{}", violation.getOrderNumber());
+
+                    // 订单状态更新成功后进行退款
+                    processRefund(violation.getOrderNumber());
+
+                    // 构建并发送通知消息
+                    sendApprovalNotifications(violation, processingStatus, reportResult);
+                } else {
+                    log.warn("订单状态更新失败,订单编号:{}", violation.getOrderNumber());
+                }
+            } else {
+                // 审批驳回
+                log.info("订单状态更新成功,订单编号:{}", violation.getOrderNumber());
+
+                // 构建并发送通知消息
+                sendApprovalNotifications(violation, processingStatus, reportResult);
+            }
+
+
+            log.info("审批举报处理完成,举报ID:{},处理状态:{}", id, processingStatus);
+
+        } catch (Exception e) {
+            log.error("审批举报处理异常,举报ID:{},异常信息:{}", id, e.getMessage(), e);
+            throw new RuntimeException("审批举报处理失败:" + e.getMessage(), e);
+        }
+    }
+
+    /**
+     * 更新举报记录状态
+     *
+     * @param violation        举报记录
+     * @param processingStatus 处理状态
+     * @param reportResult     处理结果
+     */
+    private void updateViolationStatus(LawyerUserViolation violation, String processingStatus,
+                                       String reportResult) {
+        violation.setProcessingStatus(processingStatus);
+        violation.setProcessingTime(new Date());
+        violation.setReportResult(reportResult);
+        lawyerUserViolationMapper.updateById(violation);
+    }
+
+    /**
+     * 处理订单退款
+     * <p>
+     * 根据订单号查询订单信息,调用支付宝退款接口进行退款
+     * 如果退款失败,会抛出异常以触发事务回滚
+     * </p>
+     *
+     * @param orderNumber 订单号
+     * @throws RuntimeException 当退款失败时抛出异常,触发事务回滚
+     */
+    private void processRefund(String orderNumber) {
+        if (StringUtils.isEmpty(orderNumber)) {
+            log.error("处理退款失败:订单号为空");
+            throw new RuntimeException("订单号不能为空,无法处理退款");
+        }
+
+        try {
+            // 查询订单信息
+            LambdaQueryWrapper<LawyerConsultationOrder> queryWrapper = new LambdaQueryWrapper<>();
+            queryWrapper.eq(LawyerConsultationOrder::getOrderNumber, orderNumber)
+                    .eq(LawyerConsultationOrder::getDeleteFlag, 0)
+                    .last("LIMIT 1");
+            LawyerConsultationOrder order = consultationOrderMapper.selectOne(queryWrapper);
+
+            if (order == null) {
+                log.error("处理退款失败:订单不存在,订单号:{}", orderNumber);
+                throw new RuntimeException("订单不存在,无法处理退款,订单号:" + orderNumber);
+            }
+
+            // 检查订单是否有支付宝交易号
+            if (StringUtils.isEmpty(order.getAlipayNo())) {
+                log.error("处理退款失败:订单无支付宝交易号,订单号:{}", orderNumber);
+                throw new RuntimeException("订单无支付宝交易号,无法处理退款,订单号:" + orderNumber);
+            }
+
+            // 检查订单金额
+            if (order.getOrderAmount() == null || order.getOrderAmount() <= 0) {
+                log.error("处理退款失败:订单金额无效,订单号:{},订单金额:{}", orderNumber, order.getOrderAmount());
+                throw new RuntimeException("订单金额无效,无法处理退款,订单号:" + orderNumber);
+            }
+
+            // 将订单金额从分转换为元
+            BigDecimal refundAmount = new BigDecimal(order.getOrderAmount())
+                    .divide(new BigDecimal(100), 2, RoundingMode.HALF_UP);
+
+            // 退款原因
+            String refundReason = "举报审核通过,订单退款";
+
+            // 调用支付宝退款接口
+            log.info("开始处理订单退款,订单号:{},支付宝交易号:{},退款金额:{}元",
+                    orderNumber, order.getAlipayNo(), refundAmount.toString());
+
+            String refundResult = aliApi.processRefund(order.getAlipayNo(), refundAmount.toString(), refundReason, "");
+
+            if ("调用成功".equals(refundResult)) {
+                log.info("订单退款成功,订单号:{},退款金额:{}元", orderNumber, refundAmount.toString());
+            } else {
+                log.error("订单退款失败,订单号:{},退款结果:{}", orderNumber, refundResult);
+                // 退款失败时抛出异常,触发事务回滚
+                throw new RuntimeException("订单退款失败,订单号:" + orderNumber + ",退款结果:" + refundResult);
+            }
+
+        } catch (RuntimeException e) {
+            // 重新抛出RuntimeException,触发事务回滚
+            log.error("处理订单退款失败,订单号:{},异常信息:{}", orderNumber, e.getMessage(), e);
+            throw e;
+        } catch (Exception e) {
+            // 其他异常也转换为RuntimeException,触发事务回滚
+            log.error("处理订单退款异常,订单号:{},异常信息:{}", orderNumber, e.getMessage(), e);
+            throw new RuntimeException("处理订单退款时发生异常,订单号:" + orderNumber + ",异常信息:" + e.getMessage(), e);
+        }
+    }
+
+    /**
+     * 发送审批通知消息
+     *
+     * @param violation        举报记录
+     * @param processingStatus 处理状态
+     * @param reportResult     处理结果
+     */
+    private void sendApprovalNotifications(LawyerUserViolation violation, String processingStatus,
+                                           String reportResult) {
+        // 构建举报人通知消息
+        NotificationInfo reporterNotification = buildReporterNotification(violation, processingStatus,
+                reportResult);
+        if (reporterNotification != null && StringUtils.isNotEmpty(reporterNotification.getMessage())) {
+            sendNotificationToReporter(violation, reporterNotification);
+        }
+
+        // 构建被举报人通知消息(仅通过且为订单举报时)
+        if (PROCESSING_STATUS_APPROVED.equals(processingStatus)
+                && REPORT_CONTEXT_TYPE_ORDER.equals(violation.getReportContextType())) {
+            NotificationInfo reportedNotification = buildReportedNotification(violation);
+            if (reportedNotification != null && StringUtils.isNotEmpty(reportedNotification.getMessage())) {
+                sendNotificationToReported(violation, reportedNotification);
+            }
+        }
+    }
+
+    /**
+     * 构建举报人通知信息
+     *
+     * @param violation        举报记录
+     * @param processingStatus 处理状态
+     * @param reportResult     处理结果
+     * @return 通知信息
+     */
+    private NotificationInfo buildReporterNotification(LawyerUserViolation violation,
+                                                       String processingStatus, String reportResult) {
+        NotificationInfo notificationInfo = new NotificationInfo();
+        String reportContextType = violation.getReportContextType();
+
+        if (PROCESSING_STATUS_APPROVED.equals(processingStatus)) {
+            // 审批通过
+            if (REPORT_CONTEXT_TYPE_ORDER.equals(reportContextType)) {
+                notificationInfo.setMessage("您举报律师服务态度差,经核实,确实存在此行为,"
+                        + "订单金额将在1-3个工作日原路返还,请注意查收。");
+                notificationInfo.setTitle("举报成功通知");
+            }
+        } else {
+            // 审批驳回
+            if (REPORT_CONTEXT_TYPE_ORDER.equals(reportContextType)) {
+                String message = "您举报律师服务态度差,经核实,不存在此行为,订单金额不予退还。";
+                if (StringUtils.isNotEmpty(reportResult)) {
+                    message += "拒绝原因:" + reportResult;
+                }
+                notificationInfo.setMessage(message);
+                notificationInfo.setTitle("举报失败通知");
+            }
+        }
+
+        return notificationInfo;
+    }
+
+    /**
+     * 构建被举报人通知信息
+     *
+     * @param violation 举报记录
+     * @return 通知信息
+     */
+    private NotificationInfo buildReportedNotification(LawyerUserViolation violation) {
+        NotificationInfo notificationInfo = new NotificationInfo();
+        String orderId = violation.getOrderNumber();
+        String message = String.format("用户对编号为%s的订单进行申诉,经核实,用户举报属实,"
+                + "订单金额将会退还给用户。", orderId);
+        notificationInfo.setMessage(message);
+        notificationInfo.setTitle("被举报成功通知");
+        return notificationInfo;
+    }
+
+    /**
+     * 向举报人发送通知
+     *
+     * @param violation         举报记录
+     * @param notificationInfo 通知信息
+     */
+    private void sendNotificationToReporter(LawyerUserViolation violation,
+                                            NotificationInfo notificationInfo) {
+        try {
+            // 获取举报人接收ID
+            String receiverId = getReporterReceiverId(violation);
+            if (StringUtils.isEmpty(receiverId)) {
+                log.warn("获取举报人接收ID失败,举报ID:{}", violation.getId());
+                return;
+            }
+
+            // 创建并保存通知
+            LifeNotice lifeNotice = createLifeNotice(violation.getId(), receiverId,
+                    notificationInfo.getTitle(), notificationInfo.getMessage());
+            lifeNoticeMapper.insert(lifeNotice);
+
+            // 发送WebSocket消息
+            sendWebSocketMessage(receiverId, lifeNotice);
+
+
+
+            // 创建并保存到账通知
+
+            String notificationInfo1 = "您的编号为" + violation.getOrderNumber() + "的订单,订单金额已原路返还至您的支付渠道,请查收";
+            LifeNotice lifeNotice1 = createLifeNotice(violation.getId(), receiverId,
+                    "退款到账通知", notificationInfo1);
+            lifeNoticeMapper.insert(lifeNotice1);
+
+            // 发送WebSocket消息
+            sendWebSocketMessage(receiverId, lifeNotice1);
+
+
+
+            log.info("举报人通知发送成功,接收人ID:{}", receiverId);
+
+        } catch (Exception e) {
+            log.error("向举报人发送通知异常,举报ID:{},异常信息:{}", violation.getId(), e.getMessage(), e);
+        }
+    }
+
+    /**
+     * 向被举报人发送通知
+     *
+     * @param violation         举报记录
+     * @param notificationInfo 通知信息
+     */
+    private void sendNotificationToReported(LawyerUserViolation violation,
+                                            NotificationInfo notificationInfo) {
+        try {
+            // 获取被举报人接收ID
+            String receiverId = getReportedReceiverId(violation);
+            if (StringUtils.isEmpty(receiverId)) {
+                log.warn("获取被举报人接收ID失败,举报ID:{}", violation.getId());
+                return;
+            }
+
+            // 创建并保存通知
+            LifeNotice lifeNotice = createLifeNotice(violation.getId(), receiverId,
+                    notificationInfo.getTitle(), notificationInfo.getMessage());
+            lifeNoticeMapper.insert(lifeNotice);
+
+            // 发送WebSocket消息
+            sendWebSocketMessage(receiverId, lifeNotice);
+
+            log.info("被举报人通知发送成功,接收人ID:{}", receiverId);
+
+        } catch (Exception e) {
+            log.error("向被举报人发送通知异常,举报ID:{},异常信息:{}", violation.getId(), e.getMessage(), e);
+        }
+    }
+
+    /**
+     * 创建通知对象
+     *
+     * @param businessId 业务ID(举报ID)
+     * @param receiverId 接收人ID
+     * @param title      通知标题
+     * @param message    通知消息
+     * @return 通知对象
+     */
+    private LifeNotice createLifeNotice(Integer businessId, String receiverId, String title,
+                                        String message) {
+        LifeNotice lifeNotice = new LifeNotice();
+        lifeNotice.setSenderId(SYSTEM_SENDER_ID);
+        lifeNotice.setBusinessId(businessId);
+        lifeNotice.setReceiverId(receiverId);
+        lifeNotice.setTitle(title);
+        lifeNotice.setNoticeType(1);
+        lifeNotice.setIsRead(0);
+
+        JSONObject jsonObject = new JSONObject();
+        jsonObject.put("message", message);
+        lifeNotice.setContext(jsonObject.toJSONString());
+
+        return lifeNotice;
+    }
+
+    /**
+     * 发送WebSocket消息
+     *
+     * @param receiverId 接收人ID
+     * @param lifeNotice 通知对象
+     */
+    private void sendWebSocketMessage(String receiverId, LifeNotice lifeNotice) {
+        try {
+            WebSocketVo webSocketVo = buildWebSocketVo(lifeNotice);
+            webSocketProcess.sendMessage(receiverId, JSONObject.from(webSocketVo).toJSONString());
+        } catch (Exception e) {
+            log.error("发送WebSocket消息异常,接收人ID:{},异常信息:{}", receiverId, e.getMessage(), e);
+        }
+    }
+
+    /**
+     * 通知信息内部类
+     */
+    private static class NotificationInfo {
+        private String title;
+        private String message;
+
+        public String getTitle() {
+            return title;
+        }
+
+        public void setTitle(String title) {
+            this.title = title;
+        }
+
+        public String getMessage() {
+            return message;
+        }
+
+        public void setMessage(String message) {
+            this.message = message;
+        }
+    }
+
+
+    @Override
+    public LawyerUserViolationDto byId(Integer id) {
+        LawyerUserViolation entity = lawyerUserViolationMapper.selectById(id);
+        if (entity == null) return null;
+        LawyerUserViolationDto dto = new LawyerUserViolationDto();
+        BeanUtils.copyProperties(entity, dto);
+        if (Objects.nonNull(entity.getReportEvidenceImg())) {
+            List<String> list = Arrays.stream(entity.getReportEvidenceImg().split(",")).map(String::trim).collect(Collectors.toList());
+            dto.setImageList(list);
+        }
+        // 处理举报人信息
+        if ("3".equals(dto.getReportingUserType())) {
+            LawyerUser lawyerUser = lawyerUserService.getById(dto.getReportingUserId());
+            if (lawyerUser != null) {
+                dto.setNickname(lawyerUser.getName());
+                dto.setPhone(lawyerUser.getPhone());
+            }
+        } else if ("1".equals(dto.getReportingUserType())) {
+            StoreUser storeUser = storeUserMapper.selectById(dto.getReportingUserId());
+            if (storeUser != null) {
+                dto.setNickname(storeUser.getNickName());
+                dto.setPhone(storeUser.getPhone());
+            }
+        } else {
+            LifeUser lifeUser = lifeUserMapper.selectById(dto.getReportingUserId());
+            if (lifeUser != null) {
+                dto.setNickname(lifeUser.getUserName());
+                dto.setPhone(lifeUser.getUserPhone());
+            }
+        }
+        // 处理被举报人信息
+        if ("lawyer".equals(dto.getReportedUserType())) {
+            LawyerUser lawyerUser = lawyerUserService.getById(dto.getReportedUserId());
+            if (lawyerUser != null) {
+                dto.setAccount(lawyerUser.getPhone());
+            }
+        } else if ("1".equals(dto.getReportedUserType())) {
+            StoreUser storeUser = storeUserMapper.selectById(dto.getReportedUserId());
+            if (storeUser != null) {
+                dto.setAccount(storeUser.getPhone());
+            }
+        } else {
+            LifeUser lifeUser = lifeUserMapper.selectById(dto.getReportedUserId());
+            if (lifeUser != null) {
+                dto.setAccount(lifeUser.getUserPhone());
+            }
+        }
+        return dto;
+    }
+
+    @Override
+    public LawyerUserViolationDto byIdNotice(Integer id) {
+        LifeNotice lifeNotice = lifeNoticeMapper.selectById(id);
+        LawyerUserViolationDto dto = byId(lifeNotice.getBusinessId());
+        dto.setLifeNotice(lifeNotice.getContext());
+        return dto;
+    }
+
+    @Override
+    public String exportExcel(String nickName, String phone, String processingStatus) throws IOException {
+        // Excel导出功能,可以根据需要实现
+        return "";
+    }
+
+    @Override
+    public String level(UserLoginInfo userLoginInfo) {
+        if (null == userLoginInfo) {
+            return "O";
+        }
+        String type = userLoginInfo.getType();
+        LambdaQueryWrapper<LawyerUserViolation> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(LawyerUserViolation::getReportContextType, "1").eq(LawyerUserViolation::getReportedUserId, userLoginInfo.getUserId());
+        if (Objects.equals(type, "lawyer")) {
+            wrapper.eq(LawyerUserViolation::getReportedUserType, "lawyer");
+        } else if (Objects.equals(type, "store")) {
+            wrapper.eq(LawyerUserViolation::getReportedUserType, "1");
+        } else if (Objects.equals(type, "user")) {
+            wrapper.eq(LawyerUserViolation::getReportedUserType, "2");
+        }
+        int count = Optional.ofNullable(lawyerUserViolationMapper.selectList(wrapper)).map(List::size).orElse(0);
+        if (count == 0) return "O";
+        if (count >= 15) return "E";
+        if (count >= 10) return "D";
+        if (count >= 6) return "C";
+        if (count >= 3) return "B";
+        return "A";
+    }
+
+    /**
+     * 获取举报原因列表
+     * <p>
+     * 查询所有未删除的律师违规举报原因字典数据,按类型名称过滤
+     * </p>
+     *
+     * @return 举报原因字典列表,如果查询失败或没有数据则返回空列表
+     * @author system
+     * @since 2025-01-XX
+     */
+    @Override
+    public List<StoreDictionary> getViolationReason() {
+        log.info("开始查询举报原因列表");
+        try {
+            LambdaQueryWrapper<StoreDictionary> queryWrapper = new LambdaQueryWrapper<>();
+            queryWrapper.eq(StoreDictionary::getDeleteFlag, 0)
+                    .eq(StoreDictionary::getTypeName, "lawyerViolationReason")
+                    .orderByAsc(StoreDictionary::getId);
+
+            List<StoreDictionary> violationReasonList = storeDictionaryMapper.selectList(queryWrapper);
+            log.info("查询举报原因列表成功,共{}条记录", violationReasonList.size());
+            return violationReasonList != null ? violationReasonList : new ArrayList<>();
+        } catch (Exception e) {
+            log.error("查询举报原因列表异常,异常信息:{}", e.getMessage(), e);
+            throw new RuntimeException("查询举报原因列表失败:" + e.getMessage(), e);
+        }
+    }
+}

+ 685 - 0
alien-store/src/main/java/shop/alien/store/service/impl/OrderReviewServiceImpl.java

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

+ 435 - 0
alien-store/src/main/java/shop/alien/store/service/impl/ReviewCommentServiceImpl.java

@@ -0,0 +1,435 @@
+package shop.alien.store.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.context.annotation.Lazy;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.CollectionUtils;
+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.ReviewCommentRequestDto;
+import shop.alien.entity.store.dto.ReviewReplyDto;
+import shop.alien.entity.store.vo.ReviewCommentVo;
+import shop.alien.mapper.LifeLikeRecordMapper;
+import shop.alien.mapper.ReviewCommentMapper;
+import shop.alien.store.service.OrderReviewService;
+import shop.alien.store.service.ReviewCommentService;
+
+import java.util.Date;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * 评价评论 服务实现类
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Slf4j
+@Transactional
+@Service
+public class ReviewCommentServiceImpl extends ServiceImpl<ReviewCommentMapper, ReviewComment> implements ReviewCommentService {
+
+    private final ReviewCommentMapper reviewCommentMapper;
+    private final OrderReviewService orderReviewService;
+    private final LifeLikeRecordMapper lifeLikeRecordMapper;
+
+    public ReviewCommentServiceImpl(ReviewCommentMapper reviewCommentMapper,
+                                    @Lazy OrderReviewService orderReviewService,
+                                    LifeLikeRecordMapper lifeLikeRecordMapper) {
+        this.reviewCommentMapper = reviewCommentMapper;
+        this.orderReviewService = orderReviewService;
+        this.lifeLikeRecordMapper = lifeLikeRecordMapper;
+    }
+
+    @Override
+    public R<ReviewComment> createComment(ReviewComment comment) {
+        log.info("ReviewCommentServiceImpl.createComment?comment={}", comment);
+        // 参数校验
+        if (comment == null) {
+            return R.fail("评论信息不能为空");
+        }
+        if (comment.getReviewId() == null) {
+            return R.fail("评价ID不能为空");
+        }
+        if (comment.getCommentContent() == null || comment.getCommentContent().trim().isEmpty()) {
+            return R.fail("评论内容不能为空");
+        }
+        Integer userId = comment.getSendUserId();
+        if (userId == null) {
+            return R.fail("用户ID不能为空");
+        }
+
+        // 验证评价是否存在
+        OrderReview review = orderReviewService.getById(comment.getReviewId());
+        if (review == null || review.getDeleteFlag() == 1) {
+            return R.fail("评价不存在或已删除");
+        }
+
+        // 设置评论属性
+        // 接收用户ID为评价的创建者(如果未设置)
+        if (comment.getReceiveUserId() == null) {
+            comment.setReceiveUserId(review.getUserId());
+        }
+        // 设置默认值
+        if (comment.getLikeCount() == null) {
+            comment.setLikeCount(0);
+        }
+        if (comment.getReplyCount() == null) {
+            comment.setReplyCount(0);
+        }
+        if (comment.getHeadType() == null) {
+            comment.setHeadType(0); // 0:是首评
+        }
+        comment.setCreatedUserId(userId);
+        comment.setCreatedTime(new Date());
+
+        boolean success = this.save(comment);
+        if (success) {
+            // 更新评价的评论数(包括首评论和子评论)
+            review.setCommentCount((review.getCommentCount() == null ? 0 : review.getCommentCount()) + 1);
+            orderReviewService.updateById(review);
+            log.info("更新评价评论数成功,评价ID={}, 评论数={}", comment.getReviewId(), review.getCommentCount());
+
+            log.info("创建评论成功,评论ID={}", comment.getId());
+            return R.data(comment, "评论成功");
+        } else {
+            log.error("创建评论失败");
+            return R.fail("创建评论失败");
+        }
+    }
+
+    @Override
+    public R<List<ReviewCommentVo>> getCommentListByReviewId(Integer reviewId, Integer currentUserId) {
+        log.info("ReviewCommentServiceImpl.getCommentListByReviewId?reviewId={}, currentUserId={}", reviewId, currentUserId);
+
+        if (reviewId == null) {
+            return R.fail("评价ID不能为空");
+        }
+
+        List<ReviewCommentVo> comments = reviewCommentMapper.getCommentListByReviewId(reviewId, currentUserId);
+
+        // 为每个首评加载回复列表
+        if (comments != null && !comments.isEmpty()) {
+            for (ReviewCommentVo comment : comments) {
+                List<ReviewCommentVo> replies = reviewCommentMapper.getReplyListByHeadId(comment.getId(), currentUserId);
+                comment.setReplies(replies);
+            }
+        }
+
+        return R.data(comments);
+    }
+
+    @Override
+    public R<Boolean> likeComment(ReviewCommentRequestDto requestDto) {
+        log.info("ReviewCommentServiceImpl.likeComment?requestDto={}", requestDto);
+
+        Integer commentId = requestDto.getCommentId();
+        Integer userId = requestDto.getUserId();
+
+        if (commentId == null) {
+            return R.fail("评论ID不能为空");
+        }
+        if (userId == null) {
+            return R.fail("用户ID不能为空");
+        }
+
+        // 验证评论是否存在
+        ReviewComment comment = this.getById(commentId);
+        if (comment == null || comment.getDeleteFlag() == 1) {
+            return R.fail("评论不存在或已删除");
+        }
+
+        // 检查是否已点赞
+        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<ReviewComment> updateWrapper = new LambdaUpdateWrapper<>();
+            updateWrapper.eq(ReviewComment::getId, commentId);
+            updateWrapper.setSql("like_count = like_count + 1");
+            int result = reviewCommentMapper.update(null, updateWrapper);
+
+            if (result > 0) {
+                log.info("点赞评论成功,评论ID={}", commentId);
+                return R.data(true, "点赞成功");
+            } else {
+                return R.fail("点赞失败");
+            }
+        } else {
+            return R.data(true, "已点赞");
+        }
+    }
+
+    @Override
+    public R<Boolean> cancelLikeComment(ReviewCommentRequestDto requestDto) {
+        log.info("ReviewCommentServiceImpl.cancelLikeComment?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());
+            }
+
+            // 更新评论点赞数(移除 gt 条件,确保即使为 0 也能正确更新)
+            LambdaUpdateWrapper<ReviewComment> updateWrapper = new LambdaUpdateWrapper<>();
+            updateWrapper.eq(ReviewComment::getId, commentId);
+            updateWrapper.setSql("like_count = GREATEST(0, like_count - 1)");
+            int result = reviewCommentMapper.update(null, updateWrapper);
+
+            if (result > 0) {
+                log.info("取消点赞评论成功,评论ID={}", commentId);
+                return R.data(true, "取消点赞成功");
+            } else {
+                return R.fail("取消点赞失败");
+            }
+        } else {
+            return R.data(true, "未点赞");
+        }
+    }
+
+    @Override
+    public R<ReviewComment> createReply(ReviewReplyDto replyDto) {
+        log.info("ReviewCommentServiceImpl.createReply?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不能为空");
+        }
+
+        // 验证首评是否存在
+        ReviewComment headComment = this.getById(replyDto.getCommentId());
+        if (headComment == null || headComment.getDeleteFlag() == 1) {
+            return R.fail("评论不存在或已删除");
+        }
+        if (headComment.getHeadType() != 0) {
+            return R.fail("只能回复首评");
+        }
+
+        // 创建回复
+        ReviewComment reply = new ReviewComment();
+        reply.setReviewId(headComment.getReviewId());
+        reply.setSendUserId(userId);
+        reply.setReceiveUserId(replyDto.getReplyToUserId() != null ? replyDto.getReplyToUserId() : headComment.getSendUserId());
+        reply.setCommentContent(replyDto.getReplyContent());
+        reply.setLikeCount(0);
+        reply.setReplyCount(0);
+        reply.setHeadType(1); // 1:是回复
+        reply.setHeadId(replyDto.getCommentId()); // 指向首评ID
+        reply.setCreatedUserId(userId);
+        reply.setCreatedTime(new Date());
+
+        boolean success = this.save(reply);
+        if (success) {
+            // 更新首评的回复数
+            headComment.setReplyCount((headComment.getReplyCount() == null ? 0 : headComment.getReplyCount()) + 1);
+            this.updateById(headComment);
+
+            // 更新评价的评论数(包括子评论)
+            OrderReview review = orderReviewService.getById(reply.getReviewId());
+            if (review != null) {
+                review.setCommentCount((review.getCommentCount() == null ? 0 : review.getCommentCount()) + 1);
+                orderReviewService.updateById(review);
+                log.info("更新评价评论数成功,评价ID={}, 评论数={}", reply.getReviewId(), review.getCommentCount());
+            }
+
+            log.info("创建回复成功,回复ID={}", reply.getId());
+            return R.data(reply, "回复成功");
+        } else {
+            log.error("创建回复失败");
+            return R.fail("创建回复失败");
+        }
+    }
+
+    @Override
+    public R<List<ReviewCommentVo>> getReplyListByHeadId(Integer headId, Integer currentUserId) {
+        log.info("ReviewCommentServiceImpl.getReplyListByHeadId?headId={}, currentUserId={}", headId, currentUserId);
+
+        if (headId == null) {
+            return R.fail("首评ID不能为空");
+        }
+
+        List<ReviewCommentVo> replies = reviewCommentMapper.getReplyListByHeadId(headId, currentUserId);
+        return R.data(replies);
+    }
+
+    @Override
+    public R<Boolean> deleteReply(Integer replyId, Integer userId) {
+        log.info("ReviewCommentServiceImpl.deleteReply?replyId={}, userId={}", replyId, userId);
+
+        if (replyId == null) {
+            return R.fail("回复ID不能为空");
+        }
+        if (userId == null) {
+            return R.fail("用户ID不能为空");
+        }
+
+        // 查询回复
+        ReviewComment reply = this.getById(replyId);
+        if (reply == null) {
+            return R.fail("回复不存在");
+        }
+        if (reply.getHeadType() != 1) {
+            return R.fail("该记录不是回复");
+        }
+
+        // 验证是否为回复用户
+        if (!reply.getSendUserId().equals(userId)) {
+            return R.fail("只能删除自己的回复");
+        }
+
+        // 删除回复(逻辑删除)
+        boolean success = this.removeById(reply.getId());
+
+        if (success) {
+            // 更新首评的回复数
+            ReviewComment headComment = this.getById(reply.getHeadId());
+            if (headComment != null) {
+                headComment.setReplyCount((headComment.getReplyCount() == null ? 0 : headComment.getReplyCount()) - 1);
+                headComment.setReplyCount(Math.max(0, headComment.getReplyCount())); // 确保回复数不为负数
+                this.updateById(headComment);
+            }
+
+            // 更新评价的评论数(包括子评论)
+            OrderReview review = orderReviewService.getById(reply.getReviewId());
+            if (review != null) {
+                Integer currentCount = review.getCommentCount() == null ? 0 : review.getCommentCount();
+                review.setCommentCount(Math.max(0, currentCount - 1)); // 确保评论数不为负数
+                orderReviewService.updateById(review);
+                log.info("更新评价评论数成功,评价ID={}, 原评论数={}, 新评论数={}",
+                        reply.getReviewId(), currentCount, review.getCommentCount());
+            }
+
+            log.info("删除回复成功,回复ID={}", replyId);
+            return R.data(true, "删除成功");
+        } else {
+            log.error("删除回复失败,回复ID={}", replyId);
+            return R.fail("删除回复失败");
+        }
+    }
+
+
+    @Override
+    public R<Boolean> deleteReviewComment(ReviewComment reviewComment) {
+        Integer id = reviewComment.getId();
+        Integer userId = reviewComment.getUserId();
+        log.info("ReviewCommentServiceImpl.deleteReviewComment?id={}, userId={}", id, userId);
+        if (id == null) {
+            return R.fail("评论ID不能为空");
+        }
+
+        // 查询评论
+        ReviewComment comment = this.getById(id);
+        if (comment == null) {
+            return R.fail("评论不存在");
+        }
+
+        // 当userId有值时,验证是否为评论用户(只能删除自己的评论)
+        if (userId != null) {
+            if (!comment.getSendUserId().equals(userId)) {
+                return R.fail("只能删除自己的评论");
+            }
+        }
+
+        // 计算需要减少的评论数
+        int commentCountToReduce = 1; // 至少减少1(当前评论本身)
+
+        // 判断是否为首评论(headType == 0 表示是首评论)
+        if (comment.getHeadType() != null && comment.getHeadType() == 0) {
+            // 如果是首评论,需要先删除所有子评论
+            LambdaQueryWrapper<ReviewComment> childQueryWrapper = new LambdaQueryWrapper<>();
+            childQueryWrapper.eq(ReviewComment::getHeadId, id)
+                    .eq(ReviewComment::getHeadType, 1)
+                    .eq(ReviewComment::getDeleteFlag, 0);
+            List<ReviewComment> childComments = this.list(childQueryWrapper);
+
+            if (!CollectionUtils.isEmpty(childComments)) {
+                // 批量删除子评论
+                List<Integer> childIds = childComments.stream()
+                        .map(ReviewComment::getId)
+                        .collect(Collectors.toList());
+                boolean deleteChildrenResult = this.removeByIds(childIds);
+                if (!deleteChildrenResult) {
+                    log.warn("删除子评论失败,首评论ID={}, 子评论数量={}", id, childIds.size());
+                    return R.fail("删除子评论失败");
+                }
+                // 增加需要减少的评论数(包括所有子评论)
+                commentCountToReduce += childComments.size();
+                log.info("删除首评论下的子评论成功,首评论ID={}, 子评论数量={}", id, childIds.size());
+            }
+        }
+
+        // 删除评论本身(物理删除)
+        boolean result = this.removeById(id);
+        if (result) {
+            // 更新评价的评论数(包括首评论和子评论)
+            OrderReview review = orderReviewService.getById(comment.getReviewId());
+            if (review != null) {
+                Integer currentCount = review.getCommentCount() == null ? 0 : review.getCommentCount();
+                review.setCommentCount(Math.max(0, currentCount - commentCountToReduce)); // 确保评论数不为负数
+                orderReviewService.updateById(review);
+                log.info("更新评价评论数成功,评价ID={}, 原评论数={}, 减少数量={}, 新评论数={}",
+                        comment.getReviewId(), currentCount, commentCountToReduce, review.getCommentCount());
+            }
+
+            if (userId != null) {
+                log.info("删除评论成功,评论ID={}, userId={}, 是否首评论={}, 减少评论数={}", id, userId,
+                        comment.getHeadType() != null && comment.getHeadType() == 0, commentCountToReduce);
+            } else {
+                log.info("管理员删除评论成功,评论ID={}, 是否首评论={}, 减少评论数={}", id,
+                        comment.getHeadType() != null && comment.getHeadType() == 0, commentCountToReduce);
+            }
+            return R.success("删除成功");
+        }
+        log.warn("删除评论失败,评论ID={}, userId={}", id, userId);
+        return R.fail("删除失败");
+    }
+
+}

+ 27 - 0
alien-store/src/main/java/shop/alien/store/service/impl/StoreLawFirmPaymentServiceImpl.java

@@ -0,0 +1,27 @@
+package shop.alien.store.service.impl;
+
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import shop.alien.entity.store.LawFirmPayment;
+import shop.alien.mapper.LawFirmPaymentMapper;
+import shop.alien.store.service.StoreLawFirmPaymentService;
+
+
+/**
+ * 律所子表 服务实现类
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Slf4j
+@Transactional
+@Service
+@RequiredArgsConstructor
+public class StoreLawFirmPaymentServiceImpl extends ServiceImpl<LawFirmPaymentMapper, LawFirmPayment> implements StoreLawFirmPaymentService {
+
+    private final LawFirmPaymentMapper lawFirmPaymentMapper;
+}

+ 110 - 0
alien-store/src/main/java/shop/alien/store/service/impl/StoreLawFirmServiceImpl.java

@@ -0,0 +1,110 @@
+package shop.alien.store.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.StringUtils;
+import shop.alien.entity.result.R;
+import shop.alien.entity.store.LawFirm;
+import shop.alien.entity.store.LawFirmPayment;
+import shop.alien.entity.store.vo.LawFirmPaymentVO;
+import shop.alien.mapper.LawFirmMapper;
+import shop.alien.mapper.LawFirmPaymentMapper;
+import shop.alien.store.service.StoreLawFirmPaymentService;
+import shop.alien.store.service.StoreLawFirmService;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+/**
+ * 律所表 服务实现类
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Slf4j
+@Transactional
+@Service
+@RequiredArgsConstructor
+public class StoreLawFirmServiceImpl extends ServiceImpl<LawFirmMapper, LawFirm> implements StoreLawFirmService {
+
+    private final LawFirmPaymentMapper lawFirmPaymentMapper;
+    private final StoreLawFirmPaymentService storeLawFirmPaymentService;
+
+    @Override
+    public R<IPage<LawFirm>> getLawFirmList(int pageNum, int pageSize, String firmName, Integer status) {
+        log.info("LawFirmServiceImpl.getLawFirmList?pageNum={},pageSize={},firmName={},status={}", pageNum, pageSize, firmName, status);
+        Page<LawFirm> page = new Page<>(pageNum, pageSize);
+        LambdaQueryWrapper<LawFirm> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(LawFirm::getDeleteFlag, 0);
+        if (StringUtils.hasText(firmName)) {
+            queryWrapper.like(LawFirm::getFirmName, firmName);
+        }
+        if (status != null) {
+            queryWrapper.eq(LawFirm::getStatus, status);
+        }
+        queryWrapper.orderByDesc(LawFirm::getCreatedTime);
+        IPage<LawFirm> pageResult = this.page(page, queryWrapper);
+        return R.data(pageResult);
+    }
+
+    /**
+     * 批量填充律所的收款账号列表
+     *
+     * @param lawFirmList 律所列表
+     */
+    @Override
+    public void fillPaymentList(List<LawFirm> lawFirmList) {
+        if (lawFirmList == null || lawFirmList.isEmpty()) {
+            return;
+        }
+
+        // 收集所有律所ID
+        List<Integer> firmIds = lawFirmList.stream()
+                .map(LawFirm::getId)
+                .filter(Objects::nonNull)
+                .distinct()
+                .collect(Collectors.toList());
+
+        if (firmIds.isEmpty()) {
+            return;
+        }
+
+        // 批量查询所有收款账号
+        LambdaQueryWrapper<LawFirmPayment> paymentWrapper = new LambdaQueryWrapper<>();
+        paymentWrapper.in(LawFirmPayment::getFirmId, firmIds);
+        paymentWrapper.eq(LawFirmPayment::getDeleteFlag, 0);
+        List<LawFirmPayment> allPayments = storeLawFirmPaymentService.list(paymentWrapper);
+
+        // 按律所ID分组
+        Map<Integer, List<LawFirmPayment>> paymentMap = allPayments.stream()
+                .collect(Collectors.groupingBy(LawFirmPayment::getFirmId));
+
+        // 为每个律所设置收款账号列表
+        lawFirmList.forEach(lawFirm -> {
+            List<LawFirmPayment> paymentList = paymentMap.getOrDefault(lawFirm.getId(), Collections.emptyList());
+            lawFirm.setPaymentList(paymentList);
+        });
+    }
+
+    @Override
+    public R<IPage<LawFirmPaymentVO>> getPaymentPageWithFirm(int pageNum, int pageSize, Integer firmId, String paymentAccount, String firmName, String creditCode, Integer status, Integer certificationStatus, String directorName, String createdTimeStart, String createdTimeEnd) {
+        log.info("LawFirmServiceImpl.getPaymentPageWithFirm?pageNum={},pageSize={},firmId={},paymentAccount={},firmName={},creditCode={},status={},certificationStatus={},directorName={},createdTimeStart={},createdTimeEnd={}",
+                pageNum, pageSize, firmId, paymentAccount, firmName, creditCode, status, certificationStatus, directorName, createdTimeStart, createdTimeEnd);
+
+        Page<LawFirmPaymentVO> page = new Page<>(pageNum, pageSize);
+        IPage<LawFirmPaymentVO> pageResult = lawFirmPaymentMapper.selectPaymentPageWithFirm(
+                page, firmId, paymentAccount, firmName, creditCode, status, certificationStatus, directorName, createdTimeStart, createdTimeEnd
+        );
+        return R.data(pageResult);
+    }
+
+}