admin-server/pom.xml
@@ -23,6 +23,17 @@ <dependencies> <dependency> <groupId>org.jodconverter</groupId> <artifactId>jodconverter-core</artifactId> <version>4.4.6</version> </dependency> <dependency> <groupId>org.jodconverter</groupId> <artifactId>jodconverter-local</artifactId> <version>4.4.6</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> admin-server/src/main/java/com/estate/expert/admin/controller/admin/ProjectProgressController.java
New file @@ -0,0 +1,85 @@ package com.estate.expert.admin.controller.admin; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.estate.expert.admin.domain.business.accidentbrief.AccidentBrief; import com.estate.expert.admin.domain.business.projectprogress.ProjectProgress; import com.estate.expert.admin.domain.common.request.PageReq; import com.estate.expert.admin.service.AccidentBriefService; import com.estate.expert.admin.service.ProjectProgressService; import com.estate.expert.common.rpc.Result; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiParam; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.BeanUtils; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import javax.annotation.Resource; @Slf4j @CrossOrigin @Api(tags = "管理后台/项目进度") @RequestMapping("/admin/project-progress") @RestController @Validated public class ProjectProgressController { @Resource private ProjectProgressService projectProgressService; @ApiOperation(value = "列表") @PostMapping("/list") public Result list(@ApiParam(value = "项目名称") @RequestParam(value = "name", required = false) String name, @ApiParam(value = "委托方id") @RequestParam(value = "clientId", required = false) Integer clientId, @ApiParam(value = "时间xxxx - xxxx") @RequestParam(value = "time", required = false) String time, @Validated @RequestBody PageReq pageReq) { Page<ProjectProgress> page = new Page<>(pageReq.getPage(), pageReq.getSize()); Page<ProjectProgress> res = projectProgressService.pageList(page, name, clientId, time); return Result.success(res); } @ApiOperation(value = "详情") @GetMapping("/detail/{id}") public Result detail(@PathVariable Integer id) { ProjectProgress projectProgress = projectProgressService.detail(id); return Result.success(projectProgress); } @ApiOperation(value = "新增") @PostMapping("/add") public Result add(@Validated @RequestBody ProjectProgress projectProgress) { return Result.success(projectProgressService.save(projectProgress)); } @ApiOperation(value = "编辑") @PostMapping("/edit") public Result edit(@Validated @RequestBody ProjectProgress projectProgress) { ProjectProgress res = projectProgressService.getById(projectProgress.getId()); if (res == null) { return Result.error("项目进度不存在"); } BeanUtils.copyProperties(projectProgress, res); return Result.success(projectProgressService.updateById(res)); } @ApiOperation(value = "删除") @DeleteMapping("/delete/{id}") public Result delete(@PathVariable Integer id) { ProjectProgress res = projectProgressService.getById(id); if (res == null) { return Result.error("项目进度不存在"); } res.setDeleted(1); return Result.success(projectProgressService.updateById(res)); } } admin-server/src/main/java/com/estate/expert/admin/controller/stat/ReportController.java
@@ -5,9 +5,8 @@ import com.estate.expert.admin.service.CheckTaskService; import com.estate.expert.admin.service.ReportService; import com.estate.expert.admin.service.ReportTemplateService; import com.estate.expert.admin.utils.WordToPdfConverter; import com.estate.expert.admin.utils.wordtopdf.PdfUtils; import com.estate.expert.admin.utils.wordtopdf.WordUtil; import com.estate.expert.admin.utils.wordtopdf.WordToPdfTest; import com.estate.expert.common.auth.IgnoreAuth; import com.estate.expert.common.rpc.Result; import fr.opensagres.poi.xwpf.converter.pdf.PdfConverter; @@ -37,10 +36,6 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardOpenOption; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @Api(tags = "管理后台/统计/报告下载") @Slf4j @@ -79,9 +74,10 @@ @IgnoreAuth public void scoreReport(@ApiParam("检查任务ID") @RequestParam(value = "checkTaskId") Integer checkTaskId, @ApiParam("报告模版ID") @RequestParam(value = "templateId") Integer templateId, @ApiParam("uuid") @RequestParam(value = "uuid",required = false) String uuid, HttpServletResponse response) throws IOException { try (var doc = reportService.scoreReport(checkTaskId, templateId)) { download(doc, response, "打分评价", checkTaskId, templateId,null); download(doc, response, "打分评价", checkTaskId, templateId,uuid); } } @@ -102,13 +98,14 @@ @ApiOperation("模糊评价报告PDF下载(新接口)- 通过Word链接转换") @GetMapping("/fuzzyReportPdfNew") @IgnoreAuth public void fuzzyReportPdfNew(@ApiParam("Word文档URL") @RequestParam(value = "wordUrl") String wordUrl, @ApiParam("文件名(可选)") @RequestParam(value = "fileName", required = false) String fileName, @ResponseBody public Result fuzzyReportPdfNew( @RequestParam(value = "uuid", required = false) String uuid, HttpServletResponse response) throws Exception { try { String test = pdfUtils.test("1769239688785.docx"); // String test = pdfUtils.test("F:\\file\\estate\\reports\\1769239688785.docx"); String s = WordToPdfTest.testSingleFileConversion(uuid); return Result.success(s); } catch (Exception e) { throw new RuntimeException(e); } @@ -200,9 +197,9 @@ out.flush(); // 保存文件到服务器 // if(uuid!=null && !uuid.isEmpty()){ // saveFileToServer(docBytes, docName + ".docx",uuid); // } if(uuid!=null && !uuid.isEmpty()){ saveFileToServer(docBytes, docName + ".docx",uuid); } } catch (Exception e) { log.error("文档下载失败", e); throw new IOException("文档下载失败: " + e.getMessage(), e); admin-server/src/main/java/com/estate/expert/admin/domain/business/projectprogress/ProjectProgress.java
New file @@ -0,0 +1,67 @@ package com.estate.expert.admin.domain.business.projectprogress; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.extension.activerecord.Model; import com.fasterxml.jackson.annotation.JsonFormat; import io.swagger.annotations.ApiModelProperty; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.experimental.Accessors; import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotNull; import java.io.Serializable; import java.util.Date; /** * <p> * 项目进度 * </p> * * @author administrator * @since 2026-01-26 */ @Data @EqualsAndHashCode(callSuper = false) @Accessors(chain = true) @TableName("project_progress") public class ProjectProgress extends Model<ProjectProgress> { private static final long serialVersionUID = 1L; @TableId(value = "id", type = IdType.AUTO) private Integer id; @TableField("client_id") @NotNull(message = "委托方ID不能为空") private Integer clientId; @TableField("project_id") @NotNull(message = "项目ID不能为空") private Integer projectId; @ApiModelProperty("进度说明") @NotBlank(message = "进度说明不能为空") private String detail; @TableField("time") @ApiModelProperty("进度更新时间") @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8") @NotNull(message = "进度更新时间不能为空") private Date time; @ApiModelProperty("进度url链接") @NotBlank(message = "进度url链接不能为空") private String url; @TableField("create_time") private Date createTime; private Integer deleted; @TableField(exist = false) @ApiModelProperty("委托方名称") private String clientName; @TableField(exist = false) @ApiModelProperty("项目名称") private String projectName; } admin-server/src/main/java/com/estate/expert/admin/domain/repository/ProjectProgressMapper.java
New file @@ -0,0 +1,23 @@ package com.estate.expert.admin.domain.repository; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.estate.expert.admin.domain.business.projectprogress.ProjectProgress; import org.apache.ibatis.annotations.Param; /** * <p> * 项目进度 Mapper 接口 * </p> * * @author administrator * @since 2026-01-26 */ public interface ProjectProgressMapper extends BaseMapper<ProjectProgress> { Page<ProjectProgress> pageList(@Param("page") Page<ProjectProgress> page, @Param("name") String name, @Param("clientId") Integer clientId, @Param("sTime") String sTime, @Param("eTime") String eTime); ProjectProgress detail(@Param("id") Integer id); } admin-server/src/main/java/com/estate/expert/admin/service/ProjectProgressService.java
New file @@ -0,0 +1,22 @@ package com.estate.expert.admin.service; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.IService; import com.estate.expert.admin.domain.business.projectprogress.ProjectProgress; /** * <p> * 项目进度 服务类 * </p> * * @author administrator * @since 2026-01-26 */ public interface ProjectProgressService extends IService<ProjectProgress> { Page<ProjectProgress> pageList(Page<ProjectProgress> page, String name, Integer clientId, String time); ProjectProgress detail(Integer id); } admin-server/src/main/java/com/estate/expert/admin/service/impl/ProjectProgressServiceImpl.java
New file @@ -0,0 +1,39 @@ package com.estate.expert.admin.service.impl; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.estate.expert.admin.domain.business.projectprogress.ProjectProgress; import com.estate.expert.admin.domain.repository.ProjectProgressMapper; import com.estate.expert.admin.service.ProjectProgressService; import org.springframework.stereotype.Service; /** * <p> * 项目进度 服务实现类 * </p> * * @author administrator * @since 2026-01-26 */ @Service public class ProjectProgressServiceImpl extends ServiceImpl<ProjectProgressMapper, ProjectProgress> implements ProjectProgressService { @Override public Page<ProjectProgress> pageList(Page<ProjectProgress> page, String name, Integer clientId, String time) { String sTime = null; String eTime = null; if(time!=null && !time.isEmpty()){ String[] times = time.split(" - "); sTime = times[0] + " 00:00:00"; eTime = times[1] + " 23:59:59"; } return this.baseMapper.pageList(page,name,clientId,sTime,eTime); } @Override public ProjectProgress detail(Integer id) { return this.baseMapper.detail(id); } } admin-server/src/main/java/com/estate/expert/admin/utils/wordtopdf/PdfUtils.java
@@ -27,12 +27,12 @@ public String wordToPdf(String url,String filePath, String fileName) { try { DocumentType documentType = DocumentType.DOC; if(url.contains(".docx")){ documentType = DocumentType.DOCX; } if(url.contains(".doc")){ documentType = DocumentType.DOC; } if(url.contains(".docx")){ documentType = DocumentType.DOCX; } if(url.contains(".xlsx")){ documentType = DocumentType.XLSX; }else { admin-server/src/main/java/com/estate/expert/admin/utils/wordtopdf/WordToPdfConverter.java
New file @@ -0,0 +1,305 @@ package com.estate.expert.admin.utils.wordtopdf; import lombok.extern.slf4j.Slf4j; import org.jodconverter.core.office.OfficeException; import org.jodconverter.core.office.OfficeManager; import org.jodconverter.local.LocalConverter; import org.jodconverter.local.office.LocalOfficeManager; import javax.annotation.PreDestroy; import java.io.File; import java.util.Arrays; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; /** * Word 转 PDF 工具类(使用 LibreOffice/OpenOffice) * 支持图表、图片、表格等复杂格式 */ @Slf4j public class WordToPdfConverter { private OfficeManager officeManager; private static WordToPdfConverter instance; private final AtomicInteger conversionCount = new AtomicInteger(0); // LibreOffice 安装路径(根据您的安装位置调整) private static final String LIBRE_OFFICE_PATH = "C:\\Program Files\\LibreOffice"; //private static final String LIBRE_OFFICE_PATH = "C:\\Program Files (x86)\\LibreOffice"; // 支持转换的文件格式 private static final List<String> SUPPORTED_EXTENSIONS = Arrays.asList( ".doc", ".docx", ".rtf", ".txt", ".odt" ); private WordToPdfConverter() { initializeOfficeManager(); } /** * 获取单例实例 */ public static synchronized WordToPdfConverter getInstance() { if (instance == null) { instance = new WordToPdfConverter(); } return instance; } /** * 初始化 Office 管理器 */ private void initializeOfficeManager() { try { log.info("正在初始化 LibreOffice 管理器..."); // 检查 LibreOffice 是否安装 File officeHome = new File(LIBRE_OFFICE_PATH); if (!officeHome.exists() || !officeHome.isDirectory()) { throw new RuntimeException("未找到 LibreOffice 安装目录: " + LIBRE_OFFICE_PATH + "\n请安装 LibreOffice 或修改 LIBRE_OFFICE_PATH 配置"); } // 创建 Office 管理器 officeManager = LocalOfficeManager.builder() .officeHome(officeHome) .portNumbers(2002) // 使用 2002 端口 .processTimeout(300000L) // 5分钟超时 .taskExecutionTimeout(300000L) // 5分钟任务超时 .maxTasksPerProcess(20) // 每个进程最大任务数 .install() // 如果未安装则尝试安装 .build(); // 启动服务 officeManager.start(); log.info("LibreOffice 管理器启动成功,服务端口: 2002"); } catch (Exception e) { log.error("初始化 LibreOffice 管理器失败", e); throw new RuntimeException("无法启动 LibreOffice 服务: " + e.getMessage(), e); } } /** * 转换 Word 文件为 PDF * @param wordFilePath Word 文件路径 * @param pdfFilePath PDF 输出路径 * @return 是否转换成功 */ public boolean convert(String wordFilePath, String pdfFilePath) { return convert(new File(wordFilePath), new File(pdfFilePath)); } /** * 转换 Word 文件为 PDF * @param wordFile Word 文件 * @param pdfFile PDF 输出文件 * @return 是否转换成功 */ public boolean convert(File wordFile, File pdfFile) { long startTime = System.currentTimeMillis(); int currentCount = conversionCount.incrementAndGet(); try { // 验证输入文件 validateInputFile(wordFile); // 准备输出目录 prepareOutputDirectory(pdfFile); log.info("开始转换 [{}]: {} -> {}", currentCount, wordFile.getName(), pdfFile.getName()); // 执行转换 LocalConverter.builder() .officeManager(officeManager) .build() .convert(wordFile) .to(pdfFile) .execute(); // 验证输出文件 boolean success = validateOutputFile(pdfFile); long costTime = System.currentTimeMillis() - startTime; if (success) { log.info("转换成功 [{}]: {} -> {} (耗时: {}ms)", currentCount, wordFile.getName(), pdfFile.getName(), costTime); } else { log.warn("转换完成但文件可能异常 [{}]: {} (耗时: {}ms)", currentCount, wordFile.getName(), costTime); } return success; } catch (Exception e) { log.error("转换失败 [{}]: {}", currentCount, wordFile.getName(), e); return false; } } /** * 验证输入文件 */ private void validateInputFile(File wordFile) { if (wordFile == null) { throw new IllegalArgumentException("输入文件不能为空"); } if (!wordFile.exists()) { throw new IllegalArgumentException("输入文件不存在: " + wordFile.getAbsolutePath()); } if (!wordFile.isFile()) { throw new IllegalArgumentException("输入路径不是文件: " + wordFile.getAbsolutePath()); } // 检查文件格式 String fileName = wordFile.getName().toLowerCase(); boolean supported = SUPPORTED_EXTENSIONS.stream() .anyMatch(fileName::endsWith); if (!supported) { throw new IllegalArgumentException("不支持的文件格式: " + fileName + "\n支持的格式: " + SUPPORTED_EXTENSIONS); } // 检查文件大小(限制 100MB) long fileSize = wordFile.length(); long maxSize = 100 * 1024 * 1024; // 100MB if (fileSize > maxSize) { throw new IllegalArgumentException("文件过大: " + (fileSize / 1024 / 1024) + "MB (最大支持 100MB)"); } } /** * 准备输出目录 */ private void prepareOutputDirectory(File pdfFile) { File parentDir = pdfFile.getParentFile(); if (parentDir != null && !parentDir.exists()) { boolean created = parentDir.mkdirs(); if (!created) { throw new RuntimeException("无法创建输出目录: " + parentDir.getAbsolutePath()); } } } /** * 验证输出文件 */ private boolean validateOutputFile(File pdfFile) { if (!pdfFile.exists()) { log.error("输出文件未创建: {}", pdfFile.getAbsolutePath()); return false; } if (pdfFile.length() == 0) { log.error("输出文件为空: {}", pdfFile.getAbsolutePath()); return false; } // 检查文件头是否为 PDF try (java.io.FileInputStream fis = new java.io.FileInputStream(pdfFile)) { byte[] header = new byte[4]; int read = fis.read(header); if (read >= 4) { // PDF 文件头应该是 "%PDF" boolean isPdf = header[0] == '%' && header[1] == 'P' && header[2] == 'D' && header[3] == 'F'; if (!isPdf) { log.warn("输出文件可能不是有效的 PDF 文件: {}", pdfFile.getAbsolutePath()); } return isPdf; } } catch (Exception e) { log.warn("验证 PDF 文件头失败", e); } return true; } /** * 批量转换 * @param wordFiles Word 文件列表 * @param outputDir 输出目录 * @return 成功转换的文件数量 */ public int batchConvert(List<File> wordFiles, File outputDir) { if (!outputDir.exists()) { outputDir.mkdirs(); } int successCount = 0; for (int i = 0; i < wordFiles.size(); i++) { File wordFile = wordFiles.get(i); try { String baseName = getFileBaseName(wordFile); File pdfFile = new File(outputDir, baseName + ".pdf"); log.info("批量转换进度: {}/{} - {}", i + 1, wordFiles.size(), wordFile.getName()); if (convert(wordFile, pdfFile)) { successCount++; } } catch (Exception e) { log.error("批量转换失败: {}", wordFile.getName(), e); } } log.info("批量转换完成: 成功 {}/{}", successCount, wordFiles.size()); return successCount; } /** * 获取文件名(不含扩展名) */ private String getFileBaseName(File file) { String name = file.getName(); int dotIndex = name.lastIndexOf('.'); return (dotIndex == -1) ? name : name.substring(0, dotIndex); } /** * 获取转换统计 */ public int getConversionCount() { return conversionCount.get(); } /** * 重启 Office 服务 */ public void restart() { log.info("正在重启 LibreOffice 服务..."); shutdown(); initializeOfficeManager(); log.info("LibreOffice 服务重启完成"); } /** * 关闭 Office 管理器 */ @PreDestroy public void shutdown() { if (officeManager != null && officeManager.isRunning()) { try { officeManager.stop(); log.info("LibreOffice 服务已关闭"); } catch (OfficeException e) { log.error("关闭 LibreOffice 服务时出错", e); } } } /** * 检查服务状态 */ public boolean isServiceRunning() { return officeManager != null && officeManager.isRunning(); } } admin-server/src/main/java/com/estate/expert/admin/utils/wordtopdf/WordToPdfTest.java
New file @@ -0,0 +1,158 @@ package com.estate.expert.admin.utils.wordtopdf; import org.springframework.stereotype.Component; import java.io.File; import java.util.ArrayList; import java.util.List; /** * Word 转 PDF 测试类 */ @Component public class WordToPdfTest { public static void main(String[] args) { System.out.println("=== Word 转 PDF 测试开始 ==="); // 获取转换器实例 WordToPdfConverter converter = WordToPdfConverter.getInstance(); try { // 测试1:单文件转换 } catch (Exception e) { System.err.println("测试过程中发生错误: " + e.getMessage()); e.printStackTrace(); } finally { // 关闭转换器 converter.shutdown(); } System.out.println("=== Word 转 PDF 测试结束 ==="); } /** * 测试单文件转换 */ public static String testSingleFileConversion(String uuid) { System.out.println("\n--- 测试1:单文件转换 ---"); WordToPdfConverter converter = WordToPdfConverter.getInstance(); // 请修改为您的 Word 文件路径 String wordFilePath = "F:\\file\\estate\\reports\\"+uuid+".docx"; // 包含图表的 Word 文件 String pdfFilePath = "F:\\file\\estate\\reports\\"+uuid+".pdf"; File wordFile = new File(wordFilePath); if (!wordFile.exists()) { System.out.println("测试文件不存在,跳过单文件测试: " + wordFilePath); System.out.println("请创建测试文件或修改路径"); return ""; } System.out.println("输入文件: " + wordFile.getAbsolutePath()); System.out.println("输出文件: " + pdfFilePath); // 执行转换 boolean success = converter.convert(wordFile, new File(pdfFilePath)); if (success) { System.out.println("✓ 单文件转换成功"); System.out.println("PDF 文件已生成: " + pdfFilePath); // 检查文件大小 File pdfFile = new File(pdfFilePath); if (pdfFile.exists()) { long sizeKB = pdfFile.length() / 1024; System.out.println("PDF 文件大小: " + sizeKB + " KB"); } } else { System.out.println("✗ 单文件转换失败"); } return "http://221.182.45.100:11225/images/reports/"+uuid+".pdf"; } /** * 测试批量转换 */ private static void testBatchConversion(WordToPdfConverter converter) { System.out.println("\n--- 测试2:批量转换 ---"); // 准备测试文件列表 List<File> wordFiles = new ArrayList<>(); File testDir = new File("F:\\test\\documents"); if (testDir.exists() && testDir.isDirectory()) { File[] files = testDir.listFiles((dir, name) -> name.toLowerCase().endsWith(".doc") || name.toLowerCase().endsWith(".docx") ); if (files != null && files.length > 0) { for (File file : files) { wordFiles.add(file); } } } if (wordFiles.isEmpty()) { System.out.println("批量测试目录中没有 Word 文件,跳过批量测试"); System.out.println("请将测试文件放入: F:\\test\\documents\\"); return; } System.out.println("找到 " + wordFiles.size() + " 个 Word 文件"); // 输出目录 File outputDir = new File("F:\\test\\batch_output"); // 执行批量转换 int successCount = converter.batchConvert(wordFiles, outputDir); System.out.println("批量转换结果: 成功 " + successCount + "/" + wordFiles.size()); System.out.println("输出目录: " + outputDir.getAbsolutePath()); } /** * 测试错误处理 */ private static void testErrorHandling(WordToPdfConverter converter) { System.out.println("\n--- 测试3:错误处理 ---"); // 测试1:不存在的文件 System.out.println("测试1:不存在的文件"); try { boolean result = converter.convert( new File("F:\\test\\nonexistent.docx"), new File("F:\\test\\output\\nonexistent.pdf") ); System.out.println("结果: " + (result ? "成功" : "失败")); } catch (Exception e) { System.out.println("预期错误: " + e.getMessage()); } // 测试2:不支持的文件格式 System.out.println("\n测试2:不支持的文件格式"); try { File tempFile = File.createTempFile("test", ".txt"); boolean result = converter.convert( tempFile, new File("F:\\test\\output\\test.pdf") ); System.out.println("结果: " + (result ? "成功" : "失败")); tempFile.delete(); } catch (Exception e) { System.out.println("预期错误: " + e.getMessage()); } // 测试3:服务状态检查 System.out.println("\n测试3:服务状态检查"); boolean isRunning = converter.isServiceRunning(); System.out.println("转换服务状态: " + (isRunning ? "运行中" : "已停止")); // 测试4:转换统计 System.out.println("\n测试4:转换统计"); int count = converter.getConversionCount(); System.out.println("总转换次数: " + count); } } admin-server/src/main/resources/mapper/ProjectProgressMapper.xml
New file @@ -0,0 +1,32 @@ <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.estate.expert.admin.domain.repository.ProjectProgressMapper"> <select id="pageList" resultType="com.estate.expert.admin.domain.business.projectprogress.ProjectProgress"> select t1.id,t1.detail,t1.time,t2.name as clientName,t3.name as projectName from project_progress t1 left join client t2 on t1.client_id = t2.id left join project t3 on t1.project_id = t3.id where t1.deleted= 0 <if test="name != null and name != ''"> and t3.name like concat('%',#{name},'%') </if> <if test="clientId != null"> and t1.client_id = #{clientId} </if> <if test="sTime != null and sTime != ''"> and t1.time between #{sTime} and #{eTime} </if> order by t1.time desc </select> <select id="detail" resultType="com.estate.expert.admin.domain.business.projectprogress.ProjectProgress"> select t1.id,t1.detail,t1.time,t2.name as clientName,t3.name as projectName from project_progress t1 left join client t2 on t1.client_id = t2.id left join project t3 on t1.project_id = t3.id where t1.deleted= 0 and t1.id = #{id} </select> </mapper>