Преглед изворни кода

- 递交、审核任务均已完成
- 文件检索初步完成
- AI设置已添加成功

Huanyi пре 3 месеци
родитељ
комит
b9efa720bf
23 измењених фајлова са 512 додато и 20 уклоњено
  1. 1 0
      ruoyi-admin/src/main/resources/application.yml
  2. 1 0
      ruoyi-common/pom.xml
  3. 6 0
      ruoyi-common/ruoyi-common-bom/pom.xml
  4. 1 1
      ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/CacheNames.java
  5. 44 0
      ruoyi-common/yingpaipay-common-file/pom.xml
  6. 157 0
      ruoyi-common/yingpaipay-common-file/src/main/java/com/yingpaipay/common/file/util/WatermarkUtils.java
  7. 5 0
      ruoyi-modules/yingpaipay-business/pom.xml
  8. 6 0
      ruoyi-modules/yingpaipay-business/src/main/java/com/yingpaipay/business/controller/DocumentController.java
  9. 27 0
      ruoyi-modules/yingpaipay-business/src/main/java/com/yingpaipay/business/controller/SearchController.java
  10. 8 4
      ruoyi-modules/yingpaipay-business/src/main/java/com/yingpaipay/business/controller/TaskCenterController.java
  11. 23 0
      ruoyi-modules/yingpaipay-business/src/main/java/com/yingpaipay/business/domain/bo/DocumentSearchBo.java
  12. 6 0
      ruoyi-modules/yingpaipay-business/src/main/java/com/yingpaipay/business/service/IDocumentService.java
  13. 7 0
      ruoyi-modules/yingpaipay-business/src/main/java/com/yingpaipay/business/service/impl/CommonFolderService.java
  14. 8 0
      ruoyi-modules/yingpaipay-business/src/main/java/com/yingpaipay/business/service/impl/CommonProjectService.java
  15. 81 0
      ruoyi-modules/yingpaipay-business/src/main/java/com/yingpaipay/business/service/impl/DocumentServiceImpl.java
  16. 9 0
      ruoyi-modules/yingpaipay-setting/src/main/java/com/yingpaipay/setting/constant/AiEnabledFlagConst.java
  17. 32 0
      ruoyi-modules/yingpaipay-setting/src/main/java/com/yingpaipay/setting/controller/AiSettingController.java
  18. 17 0
      ruoyi-modules/yingpaipay-setting/src/main/java/com/yingpaipay/setting/domain/AiSetting.java
  19. 16 0
      ruoyi-modules/yingpaipay-setting/src/main/java/com/yingpaipay/setting/mapper/AiSettingMapper.java
  20. 9 0
      ruoyi-modules/yingpaipay-setting/src/main/java/com/yingpaipay/setting/service/IAiSettingService.java
  21. 32 0
      ruoyi-modules/yingpaipay-setting/src/main/java/com/yingpaipay/setting/service/impl/AiSettingServiceImpl.java
  22. 2 2
      ruoyi-modules/yingpaipay-setting/src/main/java/com/yingpaipay/setting/service/impl/AppletSettingServiceImpl.java
  23. 14 13
      script/sql/business/create.sql

+ 1 - 0
ruoyi-admin/src/main/resources/application.yml

@@ -129,6 +129,7 @@ tenant:
         - applet_setting
         - sys_user_projects
         - sys_user_folders
+        - ai_setting
 
 # MyBatisPlus配置
 # https://baomidou.com/config/

+ 1 - 0
ruoyi-common/pom.xml

@@ -35,6 +35,7 @@
         <module>ruoyi-common-websocket</module>
         <module>ruoyi-common-sse</module>
         <module>yingpaipay-common-wechat</module>
+        <module>yingpaipay-common-file</module>
     </modules>
 
     <artifactId>ruoyi-common</artifactId>

+ 6 - 0
ruoyi-common/ruoyi-common-bom/pom.xml

@@ -185,6 +185,12 @@
                 <version>${revision}</version>
             </dependency>
 
+            <dependency>
+                <groupId>com.yingpaipay</groupId>
+                <artifactId>yingpaipay-common-file</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
         </dependencies>
     </dependencyManagement>
 

+ 1 - 1
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/CacheNames.java

@@ -86,6 +86,6 @@ public interface CacheNames {
      */
     String ONLINE_TOKEN = "online_tokens";
 
-    String APPLET_SETTING = "applet_setting";
+    String SETTING = "setting";
 
 }

+ 44 - 0
ruoyi-common/yingpaipay-common-file/pom.xml

@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.dromara</groupId>
+        <artifactId>ruoyi-common</artifactId>
+        <version>${revision}</version>
+    </parent>
+
+    <groupId>com.yingpaipay</groupId>
+    <artifactId>yingpaipay-common-file</artifactId>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.pdfbox</groupId>
+            <artifactId>pdfbox</artifactId>
+            <version>2.0.27</version>
+        </dependency>
+        <dependency>
+            <groupId>com.itextpdf</groupId>
+            <artifactId>itext7-core</artifactId>
+            <version>7.2.5</version>
+            <type>pom</type>
+        </dependency>
+        <dependency>
+            <groupId>org.docx4j</groupId>
+            <artifactId>docx4j-core</artifactId>
+            <version>11.4.9</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.poi</groupId>
+            <artifactId>poi-ooxml</artifactId>
+            <version>5.2.4</version>
+        </dependency>
+        <dependency>
+            <groupId>com.twelvemonkeys.imageio</groupId>
+            <artifactId>imageio-jpeg</artifactId>
+            <version>3.9.4</version>
+        </dependency>
+    </dependencies>
+
+</project>

+ 157 - 0
ruoyi-common/yingpaipay-common-file/src/main/java/com/yingpaipay/common/file/util/WatermarkUtils.java

@@ -0,0 +1,157 @@
+package com.yingpaipay.common.file.util;
+
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDPage;
+import org.apache.pdfbox.pdmodel.PDPageContentStream;
+import org.apache.pdfbox.pdmodel.common.PDRectangle;
+import org.apache.pdfbox.pdmodel.font.PDFontDescriptor;
+import org.apache.pdfbox.pdmodel.font.PDType1Font;
+
+import org.docx4j.jaxb.Context;
+import org.docx4j.openpackaging.packages.WordprocessingMLPackage;
+import org.docx4j.openpackaging.parts.WordprocessingML.FooterPart;
+import org.docx4j.openpackaging.parts.WordprocessingML.MainDocumentPart;
+import org.docx4j.relationships.Relationship;
+import org.docx4j.wml.*;
+
+import javax.imageio.ImageIO;
+import java.awt.*;
+import java.awt.Color;
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.IOException;
+
+public class WatermarkUtils {
+
+    public static File processFile(File tempFile, String remark) {
+        String fileName = tempFile.getName().toLowerCase();
+
+        try {
+            if (fileName.endsWith(".pdf")) {
+                return addPdfFooterWatermark(tempFile, remark);
+            } else if (fileName.endsWith(".jpg") || fileName.endsWith(".jpeg") || fileName.endsWith(".png")) {
+                return addImageFooterWatermark(tempFile, remark);
+            } else if (fileName.endsWith(".docx")) {
+                return addDocxFooterWatermark(tempFile, remark);
+            } else {
+                return tempFile;
+            }
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private static File addPdfFooterWatermark(File inputFile, String watermark) throws IOException {
+        PDDocument document = PDDocument.load(inputFile);
+        PDType1Font font = PDType1Font.HELVETICA_BOLD;
+        float fontSize = 10f;
+
+        for (PDPage page : document.getPages()) {
+//            PDRectangle pageSize = page.getMediaBox();
+            float x = 50;
+            float y = 30;
+
+            PDFontDescriptor fontDesc = font.getFontDescriptor();
+            float textWidth = font.getStringWidth(watermark) / 1000 * fontSize;
+            float textHeight = fontDesc != null ? fontDesc.getAscent() / 1000 * fontSize : fontSize;
+            float descent = fontDesc != null ? fontDesc.getDescent() / 1000 * fontSize : 0;
+
+            float padding = 4f;
+            float rectX = x - padding;
+            float rectY = y + descent - padding;
+            float rectWidth = textWidth + 2 * padding;
+            float rectHeight = textHeight - descent + 2 * padding;
+
+            try (PDPageContentStream cs = new PDPageContentStream(document, page, PDPageContentStream.AppendMode.APPEND, true, true)) {
+                Color color = new Color(255, 99, 99);
+                cs.setStrokingColor(color);
+                cs.setLineWidth(1f);
+                cs.addRect(rectX, rectY, rectWidth, rectHeight);
+                cs.stroke();
+
+                cs.beginText();
+                cs.setFont(font, fontSize);
+                cs.setNonStrokingColor(color);
+                cs.newLineAtOffset(x, y);
+                cs.showText(watermark);
+                cs.endText();
+            }
+        }
+
+        File outputFile = File.createTempFile("watermarked_", ".pdf");
+        document.save(outputFile);
+        document.close();
+        return outputFile;
+    }
+
+    // ===== 图片水印 =====
+    private static File addImageFooterWatermark(File inputFile, String watermark) throws IOException {
+        BufferedImage image = ImageIO.read(inputFile);
+        Graphics2D g2d = image.createGraphics();
+        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+        g2d.setColor(new Color(128, 128, 128, 180)); // 半透明灰色
+        g2d.setFont(new Font("SansSerif", Font.BOLD, 16));
+
+        g2d.getFontMetrics();
+        int x = 20;
+        int y = image.getHeight() - 10; // 底部留一点空间
+
+        g2d.drawString(watermark, x, y);
+        g2d.dispose();
+
+        String ext = getFileExtension(inputFile.getName());
+        File outputFile = File.createTempFile("watermarked_", "." + ext);
+        ImageIO.write(image, ext, outputFile);
+        return outputFile;
+    }
+
+    // ===== Word (.docx) 页脚水印 =====
+    private static File addDocxFooterWatermark(File inputFile, String watermark) throws Exception {
+        WordprocessingMLPackage wordMLPackage = WordprocessingMLPackage.load(inputFile);
+        MainDocumentPart documentPart = wordMLPackage.getMainDocumentPart();
+
+        Document document = documentPart.getJaxbElement();
+        Body body = document.getBody();
+        if (body == null) {
+            body = Context.getWmlObjectFactory().createBody();
+            document.setBody(body);
+        }
+
+        SectPr sectPr = body.getSectPr();
+        if (sectPr == null) {
+            sectPr = Context.getWmlObjectFactory().createSectPr();
+            body.setSectPr(sectPr);
+        }
+
+        // 创建页脚内容
+        FooterPart footerPart = new FooterPart();
+        Ftr ftr = Context.getWmlObjectFactory().createFtr();
+
+        P paragraph = Context.getWmlObjectFactory().createP();
+        R run = Context.getWmlObjectFactory().createR();
+        Text text = Context.getWmlObjectFactory().createText();
+        text.setValue(watermark);
+        run.getContent().add(text);
+        paragraph.getContent().add(run);
+        ftr.getContent().add(paragraph);
+
+        footerPart.setJaxbElement(ftr);
+
+        // 关联页脚到文档
+        Relationship rel = documentPart.addTargetPart(footerPart);
+        FooterReference footerRef = Context.getWmlObjectFactory().createFooterReference();
+        footerRef.setId(rel.getId());
+        footerRef.setType(HdrFtrRef.DEFAULT);
+        sectPr.getEGHdrFtrReferences().add(footerRef);
+
+        File outputFile = File.createTempFile("watermarked_", ".docx");
+        wordMLPackage.save(outputFile);
+        return outputFile;
+    }
+
+    // ===== 工具方法 =====
+    private static String getFileExtension(String name) {
+        int lastDot = name.lastIndexOf('.');
+        return (lastDot > 0) ? name.substring(lastDot + 1).toLowerCase() : "jpg";
+    }
+}

+ 5 - 0
ruoyi-modules/yingpaipay-business/pom.xml

@@ -102,6 +102,11 @@
             <groupId>org.dromara</groupId>
             <artifactId>ruoyi-common-sse</artifactId>
         </dependency>
+
+        <dependency>
+            <groupId>com.yingpaipay</groupId>
+            <artifactId>yingpaipay-common-file</artifactId>
+        </dependency>
     </dependencies>
 
 </project>

+ 6 - 0
ruoyi-modules/yingpaipay-business/src/main/java/com/yingpaipay/business/controller/DocumentController.java

@@ -1,5 +1,6 @@
 package com.yingpaipay.business.controller;
 
+import java.io.IOException;
 import java.util.List;
 
 import com.yingpaipay.business.domain.bo.*;
@@ -145,4 +146,9 @@ public class DocumentController extends BaseController {
         return toAjax(documentService.filing(bo));
     }
 
+    @PostMapping(value = "/download/{ossId}")
+    public void download(@PathVariable Long ossId, HttpServletResponse response) throws IOException {
+        documentService.download(ossId, response);
+    }
+
 }

+ 27 - 0
ruoyi-modules/yingpaipay-business/src/main/java/com/yingpaipay/business/controller/SearchController.java

@@ -0,0 +1,27 @@
+package com.yingpaipay.business.controller;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import com.yingpaipay.business.domain.bo.DocumentSearchBo;
+import com.yingpaipay.business.domain.vo.DocumentVo;
+import com.yingpaipay.business.service.IDocumentService;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequestMapping("/search")
+@RequiredArgsConstructor
+public class SearchController {
+
+    private final IDocumentService documentService;
+
+    @SaCheckPermission("search:index:list")
+    @GetMapping("/list")
+    public TableDataInfo<DocumentVo> list(DocumentSearchBo bo, PageQuery pageQuery) {
+        return documentService.listOnSearch(bo, pageQuery);
+    }
+
+}

+ 8 - 4
ruoyi-modules/yingpaipay-business/src/main/java/com/yingpaipay/business/controller/TaskCenterController.java

@@ -1,10 +1,8 @@
 package com.yingpaipay.business.controller;
 
 import cn.dev33.satoken.annotation.SaCheckPermission;
-import com.yingpaipay.business.domain.bo.DocumentAuditBo;
-import com.yingpaipay.business.domain.bo.DocumentSubmitBo;
-import com.yingpaipay.business.domain.bo.TaskCenterAuditListBo;
-import com.yingpaipay.business.domain.bo.TaskCenterSubmissionListBo;
+import com.yingpaipay.business.domain.bo.*;
+import com.yingpaipay.business.domain.vo.DocumentAuditLogVo;
 import com.yingpaipay.business.domain.vo.TaskCenterAuditListVo;
 import com.yingpaipay.business.domain.vo.TaskCenterSubmissionListVo;
 import com.yingpaipay.business.service.IDocumentService;
@@ -37,6 +35,12 @@ public class TaskCenterController extends BaseController {
         return toAjax(documentService.submit(bo));
     }
 
+    @SaCheckPermission("taskCenter:submission:logAudit")
+    @GetMapping("/logAudit")
+    public TableDataInfo<DocumentAuditLogVo> logAudit(DocumentAuditLogBo bo, PageQuery pageQuery) {
+        return documentService.logAudit(bo, pageQuery);
+    }
+
     @SaCheckPermission("taskCenter:audit:list")
     @GetMapping("/audit/list")
     public TableDataInfo<TaskCenterAuditListVo> listAudit(TaskCenterAuditListBo bo, PageQuery pageQuery) {

+ 23 - 0
ruoyi-modules/yingpaipay-business/src/main/java/com/yingpaipay/business/domain/bo/DocumentSearchBo.java

@@ -0,0 +1,23 @@
+package com.yingpaipay.business.domain.bo;
+
+import lombok.Data;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@Data
+public class DocumentSearchBo {
+
+    private String name;
+
+    private String projectCode;
+
+    private String projectName;
+
+    private Integer type;
+
+    private Integer status;
+
+    private Map<String, Object> params = new HashMap<>();
+
+}

+ 6 - 0
ruoyi-modules/yingpaipay-business/src/main/java/com/yingpaipay/business/service/IDocumentService.java

@@ -5,9 +5,11 @@ import com.yingpaipay.business.domain.vo.DocumentAuditLogVo;
 import com.yingpaipay.business.domain.vo.DocumentVo;
 import com.yingpaipay.business.domain.vo.TaskCenterAuditListVo;
 import com.yingpaipay.business.domain.vo.TaskCenterSubmissionListVo;
+import jakarta.servlet.http.HttpServletResponse;
 import org.dromara.common.mybatis.core.page.TableDataInfo;
 import org.dromara.common.mybatis.core.page.PageQuery;
 
+import java.io.IOException;
 import java.util.Collection;
 import java.util.List;
 
@@ -84,4 +86,8 @@ public interface IDocumentService {
     TableDataInfo<TaskCenterSubmissionListVo> listOnSubmission(TaskCenterSubmissionListBo bo, PageQuery pageQuery);
 
     TableDataInfo<TaskCenterAuditListVo> listOnAudit(TaskCenterAuditListBo bo, PageQuery pageQuery);
+
+    void download(Long ossId, HttpServletResponse response) throws IOException;
+
+    TableDataInfo<DocumentVo> listOnSearch(DocumentSearchBo bo, PageQuery pageQuery);
 }

+ 7 - 0
ruoyi-modules/yingpaipay-business/src/main/java/com/yingpaipay/business/service/impl/CommonFolderService.java

@@ -92,4 +92,11 @@ public class CommonFolderService {
             }
         }
     }
+
+    public List<Folder> queryListByIds(List<Long> folderIds) {
+        if (folderIds.isEmpty()) {
+            return List.of();
+        }
+        return baseMapper.selectByIds(folderIds);
+    }
 }

+ 8 - 0
ruoyi-modules/yingpaipay-business/src/main/java/com/yingpaipay/business/service/impl/CommonProjectService.java

@@ -41,4 +41,12 @@ public class CommonProjectService {
         baseMapper.selectList(wrapper).forEach(e -> ids.add(e.getId()));
         return ids;
     }
+
+    public List<Project> queryOnSearch(String name, String code) {
+        return baseMapper.selectList(
+            Wrappers.lambdaQuery(Project.class)
+                .like(StringUtils.isNotBlank(name), Project::getName, name)
+                .like(StringUtils.isNotBlank(code), Project::getCode, code)
+        );
+    }
 }

+ 81 - 0
ruoyi-modules/yingpaipay-business/src/main/java/com/yingpaipay/business/service/impl/DocumentServiceImpl.java

@@ -1,20 +1,30 @@
 package com.yingpaipay.business.service.impl;
 
+import cn.hutool.core.util.ObjectUtil;
+import com.baomidou.mybatisplus.core.conditions.Wrapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.yingpaipay.business.constant.DictTypeConst;
 import com.yingpaipay.business.constant.DocumentAuditorTypeConst;
 import com.yingpaipay.business.constant.DocumentStatusConst;
 import com.yingpaipay.business.constant.DocumentTypeConst;
 import com.yingpaipay.business.domain.DocumentAuditLog;
+import com.yingpaipay.business.domain.Folder;
+import com.yingpaipay.business.domain.Project;
 import com.yingpaipay.business.domain.bo.*;
 import com.yingpaipay.business.domain.vo.DocumentAuditLogVo;
 import com.yingpaipay.business.domain.vo.TaskCenterAuditListVo;
 import com.yingpaipay.business.domain.vo.TaskCenterSubmissionListVo;
 import com.yingpaipay.business.mapper.DocumentAuditLogMapper;
+import com.yingpaipay.common.file.util.WatermarkUtils;
+import jakarta.servlet.http.HttpServletResponse;
+import org.apache.commons.io.FilenameUtils;
 import org.dromara.common.core.exception.BusinessException;
+import org.dromara.common.core.exception.ServiceException;
 import org.dromara.common.core.utils.MapstructUtils;
 import org.dromara.common.core.utils.MessageUtils;
+import org.dromara.common.core.utils.SpringUtils;
 import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.core.utils.file.FileUtils;
 import org.dromara.common.mybatis.core.page.TableDataInfo;
 import org.dromara.common.mybatis.core.page.PageQuery;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
@@ -22,6 +32,8 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
+import org.dromara.common.oss.core.OssClient;
+import org.dromara.common.oss.factory.OssFactory;
 import org.dromara.common.satoken.utils.LoginHelper;
 import org.dromara.common.tenant.helper.TenantHelper;
 import org.dromara.system.domain.vo.SysOssVo;
@@ -29,6 +41,7 @@ import org.dromara.system.service.ISysDictDataService;
 import org.dromara.system.service.ISysDictTypeService;
 import org.dromara.system.service.ISysOssService;
 import org.dromara.system.service.ISysUserService;
+import org.springframework.http.MediaType;
 import org.springframework.stereotype.Service;
 import com.yingpaipay.business.domain.vo.DocumentVo;
 import com.yingpaipay.business.domain.Document;
@@ -36,6 +49,7 @@ import com.yingpaipay.business.mapper.DocumentMapper;
 import com.yingpaipay.business.service.IDocumentService;
 import org.springframework.transaction.annotation.Transactional;
 
+import java.io.*;
 import java.util.*;
 
 /**
@@ -104,6 +118,7 @@ public class DocumentServiceImpl implements IDocumentService {
             folderIds.add(e.getFolderId());
             userIds.add(e.getSubmitterId());
         });
+        folderService.queryListByIds(folderIds).forEach(e -> folderMap.put(e.getId(), e.getName()));
         ossService.queryListByIds(ossIds).forEach(e -> ossMap.put(e.getOssId(), e));
         documents.forEach(e -> {
             SysOssVo ossVo = ossMap.get(e.getOssId());
@@ -111,6 +126,7 @@ public class DocumentServiceImpl implements IDocumentService {
                 e.setUrl(ossVo.getUrl());
                 e.setFileName(ossVo.getOriginalName());
             }
+            e.setFolderName(folderMap.get(e.getFolderId()));
         });
     }
 
@@ -350,6 +366,71 @@ public class DocumentServiceImpl implements IDocumentService {
         }));
     }
 
+    @Override
+    public void download(Long ossId, HttpServletResponse response) throws IOException {
+        SysOssVo sysOss = ossService.getById(ossId);
+        if (sysOss == null) {
+            throw new ServiceException("文件数据不存在!");
+        }
+
+        String originalExt = FilenameUtils.getExtension(sysOss.getOriginalName());
+        File tempFile = File.createTempFile("oss_download_", "." + originalExt);
+        tempFile.deleteOnExit();
+
+        try {
+            OssClient storage = OssFactory.instance(sysOss.getService());
+            try (FileOutputStream fos = new FileOutputStream(tempFile)) {
+                storage.download(sysOss.getFileName(), fos, null);
+            }
+
+            File processedFile = WatermarkUtils.processFile(tempFile, "123456");
+
+            FileUtils.setAttachmentResponseHeader(response, sysOss.getOriginalName());
+            response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE + "; charset=UTF-8");
+            response.setContentLengthLong(processedFile.length());
+
+            try (FileInputStream fis = new FileInputStream(processedFile);
+                 BufferedInputStream bis = new BufferedInputStream(fis);
+                 OutputStream os = response.getOutputStream()) {
+
+                byte[] buffer = new byte[8192];
+                int len;
+                while ((len = bis.read(buffer)) != -1) {
+                    os.write(buffer, 0, len);
+                }
+                os.flush();
+            }
+
+        } finally {
+            if (tempFile.exists()) {
+                tempFile.delete();
+            }
+        }
+    }
+
+    @Override
+    public TableDataInfo<DocumentVo> listOnSearch(DocumentSearchBo bo, PageQuery pageQuery) {
+        List<Long> projectIds = new ArrayList<>();
+        projectService.queryOnSearch(bo.getProjectName(), bo.getProjectCode())
+            .forEach(e -> projectIds.add(e.getId()));
+        IPage<DocumentVo> page = baseMapper.selectVoPage(pageQuery.build(), buildOnSearchWrapper(bo, projectIds));
+        dealVo(page.getRecords());
+        return TableDataInfo.build(page);
+    }
+
+    private LambdaQueryWrapper<Document> buildOnSearchWrapper(DocumentSearchBo bo, List<Long> projectIds) {
+        return Wrappers.lambdaQuery(Document.class)
+            .in(StringUtils.isNotBlank(bo.getProjectName()) || StringUtils.isNotBlank(bo.getProjectCode()), Document::getProjectId, projectIds.isEmpty() ? List.of(-1L) : projectIds)
+            .like(StringUtils.isNotBlank(bo.getName()), Document::getName, bo.getName())
+            .eq(bo.getStatus() != null, Document::getStatus, bo.getStatus())
+            .eq(bo.getType() != null, Document::getPlanType, bo.getType())
+            .ge(bo.getParams().containsKey("createTimeEarliest"), Document::getCreateTime, bo.getParams().get("createTimeEarliest"))
+            .le(bo.getParams().containsKey("createTimeLatest"), Document::getCreateTime, bo.getParams().get("createTimeLatest"))
+            .ge(bo.getParams().containsKey("updateTimeEarliest"), Document::getUpdateTime, bo.getParams().get("updateTimeEarliest"))
+            .le(bo.getParams().containsKey("updateTimeLatest"), Document::getUpdateTime, bo.getParams().get("updateTimeLatest"))
+            .orderByDesc(Document::getId);
+    }
+
     private LambdaQueryWrapper<Document> buildAuditListWrapper(TaskCenterAuditListBo bo, List<Long> folderIds) {
         return Wrappers.lambdaQuery(Document.class)
             .like(StringUtils.isNotBlank(bo.getName()), Document::getName, bo.getName())

+ 9 - 0
ruoyi-modules/yingpaipay-setting/src/main/java/com/yingpaipay/setting/constant/AiEnabledFlagConst.java

@@ -0,0 +1,9 @@
+package com.yingpaipay.setting.constant;
+
+public interface AiEnabledFlagConst {
+
+    Integer BANNED = 0;
+
+    Integer ENABLED = 1;
+
+}

+ 32 - 0
ruoyi-modules/yingpaipay-setting/src/main/java/com/yingpaipay/setting/controller/AiSettingController.java

@@ -0,0 +1,32 @@
+package com.yingpaipay.setting.controller;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import com.yingpaipay.setting.domain.AiSetting;
+import com.yingpaipay.setting.service.IAiSettingService;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.web.core.BaseController;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/setting/ai")
+public class AiSettingController extends BaseController {
+
+    private final IAiSettingService aiSettingService;
+
+    @SaCheckPermission("setting:ai:index")
+    @GetMapping()
+    public R<AiSetting> query() {
+        return R.ok(aiSettingService.query());
+    }
+
+    @SaCheckPermission("setting:ai:edit")
+    @PutMapping()
+    public R<Void> edit(@RequestBody AiSetting request) {
+        return toAjax(aiSettingService.edit(request));
+    }
+
+}

+ 17 - 0
ruoyi-modules/yingpaipay-setting/src/main/java/com/yingpaipay/setting/domain/AiSetting.java

@@ -0,0 +1,17 @@
+package com.yingpaipay.setting.domain;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("ai_setting")
+public class AiSetting extends BaseEntity {
+
+    private Long id;
+
+    private Integer enabledFlag;
+
+}

+ 16 - 0
ruoyi-modules/yingpaipay-setting/src/main/java/com/yingpaipay/setting/mapper/AiSettingMapper.java

@@ -0,0 +1,16 @@
+package com.yingpaipay.setting.mapper;
+
+import com.baomidou.mybatisplus.annotation.InterceptorIgnore;
+import com.yingpaipay.setting.domain.AiSetting;
+import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+
+/**
+ * 小程序设置Mapper接口
+ *
+ * @author Huanyi
+ * @date 2025-11-28
+ */
+@InterceptorIgnore(tenantLine = "true")
+public interface AiSettingMapper extends BaseMapperPlus<AiSetting, AiSetting> {
+
+}

+ 9 - 0
ruoyi-modules/yingpaipay-setting/src/main/java/com/yingpaipay/setting/service/IAiSettingService.java

@@ -0,0 +1,9 @@
+package com.yingpaipay.setting.service;
+
+import com.yingpaipay.setting.domain.AiSetting;
+
+public interface IAiSettingService {
+    AiSetting query();
+
+    boolean edit(AiSetting request);
+}

+ 32 - 0
ruoyi-modules/yingpaipay-setting/src/main/java/com/yingpaipay/setting/service/impl/AiSettingServiceImpl.java

@@ -0,0 +1,32 @@
+package com.yingpaipay.setting.service.impl;
+
+import com.yingpaipay.setting.domain.AiSetting;
+import com.yingpaipay.setting.mapper.AiSettingMapper;
+import com.yingpaipay.setting.service.IAiSettingService;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.dromara.common.core.constant.CacheNames;
+import org.springframework.cache.annotation.CacheEvict;
+import org.springframework.cache.annotation.Cacheable;
+import org.springframework.stereotype.Service;
+
+@Slf4j
+@RequiredArgsConstructor
+@Service
+public class AiSettingServiceImpl implements IAiSettingService {
+
+    private final AiSettingMapper baseMapper;
+
+    @Cacheable(cacheNames = CacheNames.SETTING, key = "'ai'")
+    @Override
+    public AiSetting query() {
+        return baseMapper.selectById(1L);
+    }
+
+    @CacheEvict(cacheNames = CacheNames.SETTING, key = "'ai'")
+    @Override
+    public boolean edit(AiSetting request) {
+        return baseMapper.updateById(request) > 0;
+    }
+
+}

+ 2 - 2
ruoyi-modules/yingpaipay-setting/src/main/java/com/yingpaipay/setting/service/impl/AppletSettingServiceImpl.java

@@ -45,7 +45,7 @@ public class AppletSettingServiceImpl implements IAppletSettingService {
      * @param id 主键
      * @return 小程序设置
      */
-    @Cacheable(cacheNames = CacheNames.APPLET_SETTING, key = "#id")
+    @Cacheable(cacheNames = CacheNames.SETTING, key = "'applet'")
     @Override
     public AppletSettingVo queryById(Long id){
         return baseMapper.selectVoById(id);
@@ -109,7 +109,7 @@ public class AppletSettingServiceImpl implements IAppletSettingService {
      * @param bo 小程序设置
      * @return 是否修改成功
      */
-    @CacheEvict(cacheNames = CacheNames.APPLET_SETTING, key = "#bo.id")
+    @CacheEvict(cacheNames = CacheNames.SETTING, key = "'applet'")
     @Override
     public Boolean updateByBo(AppletSettingBo bo) {
         AppletSetting update = MapstructUtils.convert(bo, AppletSetting.class);

+ 14 - 13
script/sql/business/create.sql

@@ -138,19 +138,6 @@ CREATE TABLE `document_quality_control_task_detail`
 ) ENGINE = InnoDB
   DEFAULT CHARSET = utf8mb4 COMMENT ='文档质控细节';
 
-CREATE TABLE `project_setting`
-(
-    `id`          bigint unsigned PRIMARY KEY NOT NULL AUTO_INCREMENT COMMENT '序号',
-    `create_dept` bigint(20) COMMENT '创建部门',
-    `create_by`   bigint(20) COMMENT '创建者',
-    `create_time` datetime COMMENT '创建时间',
-    `update_by`   bigint(20) COMMENT '更新者',
-    `update_time` datetime COMMENT '更新时间',
-    `del_flag`    char(1) DEFAULT '0' COMMENT '删除标志(0代表存在 1代表删除)',
-    `tenant_id`   varchar(40) COMMENT '租户id'
-) ENGINE = InnoDB
-  DEFAULT CHARSET = utf8mb4 COMMENT ='设置';
-
 CREATE TABLE `applet_setting`
 (
     `id`                bigint unsigned PRIMARY KEY NOT NULL AUTO_INCREMENT COMMENT '序号',
@@ -165,3 +152,17 @@ CREATE TABLE `applet_setting`
     `tenant_id`         varchar(40) COMMENT '租户id'
 ) ENGINE = InnoDB
   DEFAULT CHARSET = utf8mb4 COMMENT ='微信小程序设置';
+
+CREATE TABLE `ai_setting`
+(
+    `id` bigint unsigned PRIMARY KEY NOT NULL AUTO_INCREMENT COMMENT '序号',
+    `enabled_flag`      tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否开启',
+    `create_dept`       bigint(20) COMMENT '创建部门',
+    `create_by`         bigint(20) COMMENT '创建者',
+    `create_time`       datetime COMMENT '创建时间',
+    `update_by`         bigint(20) COMMENT '更新者',
+    `update_time`       datetime COMMENT '更新时间',
+    `del_flag`          char(1) DEFAULT '0' COMMENT '删除标志(0代表存在 1代表删除)',
+    `tenant_id`         varchar(40) COMMENT '租户id'
+) ENGINE = InnoDB
+  DEFAULT CHARSET = utf8mb4 COMMENT ='AI设置';