办学质量监测教学评价系统
ageerle
2025-05-16 031b7da19894c8539ff9fd6a7d4b5246f8a66b7d
Merge pull request #84 from janzhou123/main

feat:pdf文件解析图片和分析图片,上传向量数据库都修改成 成异步处理
已修改20个文件
已添加9个文件
3042 ■■■■ 文件已修改
ruoyi-admin/src/main/java/org/ruoyi/RuoYiAIApplication.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules-api/ruoyi-knowledge-api/pom.xml 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules-api/ruoyi-knowledge-api/src/main/java/org/ruoyi/constant/DealStatus.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules-api/ruoyi-knowledge-api/src/main/java/org/ruoyi/domain/KnowledgeAttach.java 84 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules-api/ruoyi-knowledge-api/src/main/java/org/ruoyi/domain/KnowledgeAttachPic.java 81 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules-api/ruoyi-knowledge-api/src/main/java/org/ruoyi/domain/bo/KnowledgeAttachBo.java 95 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules-api/ruoyi-knowledge-api/src/main/java/org/ruoyi/domain/bo/KnowledgeAttachPicBo.java 90 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules-api/ruoyi-knowledge-api/src/main/java/org/ruoyi/domain/vo/KnowledgeAttachPicVo.java 92 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules-api/ruoyi-knowledge-api/src/main/java/org/ruoyi/domain/vo/KnowledgeAttachVo.java 100 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules-api/ruoyi-knowledge-api/src/main/java/org/ruoyi/mapper/KnowledgeAttachPicMapper.java 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules-api/ruoyi-knowledge-api/src/main/java/org/ruoyi/service/IKnowledgeAttachPicService.java 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules-api/ruoyi-knowledge-api/src/main/java/org/ruoyi/service/IKnowledgeInfoService.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules-api/ruoyi-knowledge-api/src/main/java/org/ruoyi/service/impl/KnowledgeAttachPicServiceImpl.java 123 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules-api/ruoyi-knowledge-api/src/main/java/org/ruoyi/service/impl/KnowledgeAttachServiceImpl.java 212 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules-api/ruoyi-knowledge-api/src/main/java/org/ruoyi/service/impl/PdfImageExtractServiceImpl.java 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules-api/ruoyi-knowledge-api/src/main/java/org/ruoyi/service/impl/VectorStoreServiceImpl.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules-api/ruoyi-knowledge-api/src/main/java/org/ruoyi/utils/ZipUtils.java 88 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules-api/ruoyi-system-api/pom.xml 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules-api/ruoyi-system-api/src/main/java/org/ruoyi/system/service/ISysLogininforService.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules-api/ruoyi-system-api/src/main/java/org/ruoyi/system/service/ISysOperLogService.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules-api/ruoyi-system-api/src/main/java/org/ruoyi/system/service/ISysOssService.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules-api/ruoyi-system-api/src/main/java/org/ruoyi/system/service/impl/SysLogininforServiceImpl.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules-api/ruoyi-system-api/src/main/java/org/ruoyi/system/service/impl/SysOperLogServiceImpl.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules-api/ruoyi-system-api/src/main/java/org/ruoyi/system/service/impl/SysOssServiceImpl.java 264 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/controller/knowledge/KnowledgeController.java 20 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/knowledge/DealFileService.java 385 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/knowledge/KnowledgeInfoServiceImpl.java 534 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-generator/src/main/java/org/ruoyi/generator/util/VelocityUtils.java 718 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
script/sql/update/202505141010.sql 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/org/ruoyi/RuoYiAIApplication.java
@@ -3,6 +3,8 @@
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.metrics.buffering.BufferingApplicationStartup;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
/**
 * å¯åŠ¨ç¨‹åº
@@ -10,6 +12,8 @@
 * @author Lion Li
 */
@SpringBootApplication
@EnableScheduling
@EnableAsync
public class RuoYiAIApplication {
    public static void main(String[] args) {
ruoyi-modules-api/ruoyi-knowledge-api/pom.xml
@@ -114,6 +114,10 @@
            <artifactId>commons-io</artifactId>
            <version>2.17.0</version>
        </dependency>
      <dependency>
        <groupId>org.ruoyi</groupId>
        <artifactId>ruoyi-system-api</artifactId>
      </dependency>
    </dependencies>
ruoyi-modules-api/ruoyi-knowledge-api/src/main/java/org/ruoyi/constant/DealStatus.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,18 @@
package org.ruoyi.constant;
/**
 * @Description:
 * @Date: 2025/5/14 ä¸‹åˆ2:04
 */
public class DealStatus {
  //未开始
  public static final Integer STATUS_10 = 10;
  //进行中
  public static final Integer STATUS_20 = 20;
  //已结束
  public static final Integer STATUS_30 = 30;
  //处理失败
  public static final Integer STATUS_40 = 40;
}
ruoyi-modules-api/ruoyi-knowledge-api/src/main/java/org/ruoyi/domain/KnowledgeAttach.java
@@ -18,44 +18,66 @@
@TableName("knowledge_attach")
public class KnowledgeAttach extends BaseEntity {
    @Serial
    private static final long serialVersionUID = 1L;
  @Serial
  private static final long serialVersionUID = 1L;
    /**
     *
     */
    @TableId(value = "id")
    private Long id;
  /**
   *
   */
  @TableId(value = "id")
  private Long id;
    /**
     * çŸ¥è¯†åº“ID
     */
    private String kid;
  /**
   * çŸ¥è¯†åº“ID
   */
  private String kid;
    /**
     * æ–‡æ¡£ID
     */
    private String docId;
  /**
   * æ–‡æ¡£ID
   */
  private String docId;
    /**
     * æ–‡æ¡£åç§°
     */
    private String docName;
  /**
   * æ–‡æ¡£åç§°
   */
  private String docName;
    /**
     * æ–‡æ¡£ç±»åž‹
     */
    private String docType;
  /**
   * æ–‡æ¡£ç±»åž‹
   */
  private String docType;
    /**
     * æ–‡æ¡£å†…容
     */
    private String content;
  /**
   * æ–‡æ¡£å†…容
   */
  private String content;
    /**
     * å¤‡æ³¨
     */
    private String remark;
  /**
   * å¤‡æ³¨
   */
  private String remark;
  /**
   * å¯¹è±¡å­˜å‚¨ä¸»é”®
   */
  private Long ossId;
  /**
   * æ‹†è§£å›¾ç‰‡çŠ¶æ€10未开始,20进行中,30已完成
   */
  private Integer picStatus;
  /**
   * åˆ†æžå›¾ç‰‡çŠ¶æ€10未开始,20进行中,30已完成
   */
  private Integer picAnysStatus;
  /**
   * å†™å…¥å‘量数据库状态10未开始,20进行中,30已完成
   */
  private Integer vectorStatus;
}
ruoyi-modules-api/ruoyi-knowledge-api/src/main/java/org/ruoyi/domain/KnowledgeAttachPic.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,81 @@
package org.ruoyi.domain;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serial;
import org.ruoyi.core.domain.BaseEntity;
/**
 * çŸ¥è¯†åº“附件图片列对象 knowledge_attach_pic
 *
 * @author Albert
 * @date 2025-05-15
 */
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("knowledge_attach_pic")
public class KnowledgeAttachPic  extends BaseEntity {
  @Serial
  private static final long serialVersionUID = 1L;
  /**
   * ä¸»é”®
   */
  @TableId(value = "id")
  private Long id;
  /**
   * çŸ¥è¯†åº“id
   */
  private String kid;
  /**
   * é™„ä»¶id
   */
  private String aid;
  /**
   * æ–‡æ¡£åç§°
   */
  private String docName;
  /**
   * æ–‡æ¡£ç±»åž‹
   */
  private String docType;
  /**
   * æ–‡æ¡£å†…容
   */
  private String content;
  /**
   * æ‰€åœ¨é¡µæ•°
   */
  private Integer pageNum;
  /**
   * æ‰€åœ¨é¡µindex
   */
  private Integer indexNum;
  /**
   * åˆ†æžå›¾ç‰‡çŠ¶æ€10未开始,20进行中,30已完成
   */
  private Integer picAnysStatus;
  /**
   * å¯¹è±¡å­˜å‚¨ä¸»é”®
   */
  private Long ossId;
  /**
   * å¤‡æ³¨
   */
  private String remark;
}
ruoyi-modules-api/ruoyi-knowledge-api/src/main/java/org/ruoyi/domain/bo/KnowledgeAttachBo.java
@@ -20,47 +20,72 @@
@AutoMapper(target = KnowledgeAttach.class, reverseConvertGenerate = false)
public class KnowledgeAttachBo extends BaseEntity {
    /**
     *
     */
    @NotNull(message = "不能为空", groups = { EditGroup.class })
    private Long id;
  /**
   *
   */
  @NotNull(message = "不能为空", groups = {EditGroup.class})
  private Long id;
    /**
     * çŸ¥è¯†åº“ID
     */
    @NotBlank(message = "知识库ID不能为空", groups = { AddGroup.class, EditGroup.class })
    private String kid;
  /**
   * çŸ¥è¯†åº“ID
   */
  @NotBlank(message = "知识库ID不能为空", groups = {AddGroup.class, EditGroup.class})
  private String kid;
    /**
     * æ–‡æ¡£ID
     */
    @NotBlank(message = "文档ID不能为空", groups = { AddGroup.class, EditGroup.class })
    private String docId;
  /**
   * æ–‡æ¡£ID
   */
  @NotBlank(message = "文档ID不能为空", groups = {AddGroup.class, EditGroup.class})
  private String docId;
    /**
     * æ–‡æ¡£åç§°
     */
    @NotBlank(message = "文档名称不能为空", groups = { AddGroup.class, EditGroup.class })
    private String docName;
  /**
   * æ–‡æ¡£åç§°
   */
  @NotBlank(message = "文档名称不能为空", groups = {AddGroup.class, EditGroup.class})
  private String docName;
    /**
     * æ–‡æ¡£ç±»åž‹
     */
    @NotBlank(message = "文档类型不能为空", groups = { AddGroup.class, EditGroup.class })
    private String docType;
  /**
   * æ–‡æ¡£ç±»åž‹
   */
  @NotBlank(message = "文档类型不能为空", groups = {AddGroup.class, EditGroup.class})
  private String docType;
    /**
     * æ–‡æ¡£å†…容
     */
    @NotBlank(message = "文档内容不能为空", groups = { AddGroup.class, EditGroup.class })
    private String content;
  /**
   * æ–‡æ¡£å†…容
   */
  @NotBlank(message = "文档内容不能为空", groups = {AddGroup.class, EditGroup.class})
  private String content;
    /**
     * å¤‡æ³¨
     */
    @NotBlank(message = "备注不能为空", groups = { AddGroup.class, EditGroup.class })
    private String remark;
  /**
   * å¤‡æ³¨
   */
  @NotBlank(message = "备注不能为空", groups = {AddGroup.class, EditGroup.class})
  private String remark;
  /**
   * å¯¹è±¡å­˜å‚¨ä¸»é”®
   */
  @NotNull(message = "对象存储主键不能为空", groups = {AddGroup.class, EditGroup.class})
  private Long ossId;
  /**
   * æ‹†è§£å›¾ç‰‡çŠ¶æ€10未开始,20进行中,30已完成
   */
  @NotNull(message = "拆解图片状态10未开始,20进行中,30已完成不能为空", groups = { AddGroup.class, EditGroup.class })
  private Integer picStatus;
  /**
   * åˆ†æžå›¾ç‰‡çŠ¶æ€10未开始,20进行中,30已完成
   */
  @NotNull(message = "分析图片状态10未开始,20进行中,30已完成不能为空", groups = { AddGroup.class, EditGroup.class })
  private Integer picAnysStatus;
  /**
   * å†™å…¥å‘量数据库状态10未开始,20进行中,30已完成
   */
  @NotNull(message = "写入向量数据库状态10未开始,20进行中,30已完成不能为空", groups = { AddGroup.class, EditGroup.class })
  private Integer vectorStatus;
}
ruoyi-modules-api/ruoyi-knowledge-api/src/main/java/org/ruoyi/domain/bo/KnowledgeAttachPicBo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,90 @@
package org.ruoyi.domain.bo;
import org.ruoyi.common.core.validate.AddGroup;
import org.ruoyi.common.core.validate.EditGroup;
import org.ruoyi.core.domain.BaseEntity;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import lombok.EqualsAndHashCode;
import jakarta.validation.constraints.*;
import org.ruoyi.domain.KnowledgeAttachPic;
/**
 * çŸ¥è¯†åº“附件图片列业务对象 knowledge_attach_pic
 *
 * @author Albert
 * @date 2025-05-15
 */
@Data
@EqualsAndHashCode(callSuper = true)
@AutoMapper(target = KnowledgeAttachPic.class, reverseConvertGenerate = false)
public class KnowledgeAttachPicBo extends BaseEntity {
  /**
   * ä¸»é”®
   */
  @NotNull(message = "主键不能为空", groups = {EditGroup.class})
  private Long id;
  /**
   * çŸ¥è¯†åº“id
   */
  @NotBlank(message = "知识库id不能为空", groups = {AddGroup.class, EditGroup.class})
  private String kid;
  /**
   * é™„ä»¶id
   */
  @NotBlank(message = "附件id不能为空", groups = {AddGroup.class, EditGroup.class})
  private String aid;
  /**
   * æ–‡æ¡£åç§°
   */
  @NotBlank(message = "文档名称不能为空", groups = {AddGroup.class, EditGroup.class})
  private String docName;
  /**
   * æ–‡æ¡£ç±»åž‹
   */
  @NotBlank(message = "文档类型不能为空", groups = {AddGroup.class, EditGroup.class})
  private String docType;
  /**
   * æ–‡æ¡£å†…容
   */
  @NotBlank(message = "文档内容不能为空", groups = {AddGroup.class, EditGroup.class})
  private String content;
  /**
   * æ‰€åœ¨é¡µæ•°
   */
  @NotNull(message = "所在页数不能为空", groups = {AddGroup.class, EditGroup.class})
  private Integer pageNum;
  /**
   * æ‰€åœ¨é¡µindex
   */
  @NotNull(message = "所在页index不能为空", groups = {AddGroup.class, EditGroup.class})
  private Integer indexNum;
  /**
   * åˆ†æžå›¾ç‰‡çŠ¶æ€10未开始,20进行中,30已完成
   */
  @NotNull(message = "分析图片状态10未开始,20进行中,30已完成不能为空", groups = {AddGroup.class,
      EditGroup.class})
  private Integer picAnysStatus;
  /**
   * å¯¹è±¡å­˜å‚¨ä¸»é”®
   */
  @NotNull(message = "对象存储主键不能为空", groups = {AddGroup.class, EditGroup.class})
  private Long ossId;
  /**
   * å¤‡æ³¨
   */
  @NotBlank(message = "备注不能为空", groups = {AddGroup.class, EditGroup.class})
  private String remark;
}
ruoyi-modules-api/ruoyi-knowledge-api/src/main/java/org/ruoyi/domain/vo/KnowledgeAttachPicVo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,92 @@
package org.ruoyi.domain.vo;
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.alibaba.excel.annotation.ExcelProperty;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import org.ruoyi.domain.KnowledgeAttachPic;
/**
 * çŸ¥è¯†åº“附件图片列视图对象 knowledge_attach_pic
 *
 * @author Albert
 * @date 2025-05-15
 */
@Data
@ExcelIgnoreUnannotated
@AutoMapper(target = KnowledgeAttachPic.class)
public class KnowledgeAttachPicVo implements Serializable {
  @Serial
  private static final long serialVersionUID = 1L;
  /**
   * ä¸»é”®
   */
  @ExcelProperty(value = "主键")
  private Long id;
  /**
   * çŸ¥è¯†åº“id
   */
  @ExcelProperty(value = "知识库id")
  private String kid;
  /**
   * é™„ä»¶id
   */
  @ExcelProperty(value = "附件id")
  private String aid;
  /**
   * æ–‡æ¡£åç§°
   */
  @ExcelProperty(value = "文档名称")
  private String docName;
  /**
   * æ–‡æ¡£ç±»åž‹
   */
  @ExcelProperty(value = "文档类型")
  private String docType;
  /**
   * æ–‡æ¡£å†…容
   */
  @ExcelProperty(value = "文档内容")
  private String content;
  /**
   * æ‰€åœ¨é¡µæ•°
   */
  @ExcelProperty(value = "所在页数")
  private Integer pageNum;
  /**
   * æ‰€åœ¨é¡µindex
   */
  @ExcelProperty(value = "所在页index")
  private Integer indexNum;
  /**
   * åˆ†æžå›¾ç‰‡çŠ¶æ€10未开始,20进行中,30已完成
   */
  @ExcelProperty(value = "分析图片状态10未开始,20进行中,30已完成")
  private Integer picAnysStatus;
  /**
   * å¯¹è±¡å­˜å‚¨ä¸»é”®
   */
  @ExcelProperty(value = "对象存储主键")
  private Long ossId;
  /**
   * å¤‡æ³¨
   */
  @ExcelProperty(value = "备注")
  private String remark;
}
ruoyi-modules-api/ruoyi-knowledge-api/src/main/java/org/ruoyi/domain/vo/KnowledgeAttachVo.java
@@ -10,8 +10,6 @@
import java.io.Serializable;
/**
 * çŸ¥è¯†åº“附件视图对象 knowledge_attach
 *
@@ -23,50 +21,74 @@
@AutoMapper(target = KnowledgeAttach.class)
public class KnowledgeAttachVo implements Serializable {
    @Serial
    private static final long serialVersionUID = 1L;
  @Serial
  private static final long serialVersionUID = 1L;
    /**
     *
     */
    @ExcelProperty(value = "")
    private Long id;
  /**
   *
   */
  @ExcelProperty(value = "")
  private Long id;
    /**
     * çŸ¥è¯†åº“ID
     */
    @ExcelProperty(value = "知识库ID")
    private String kid;
  /**
   * çŸ¥è¯†åº“ID
   */
  @ExcelProperty(value = "知识库ID")
  private String kid;
    /**
     * æ–‡æ¡£ID
     */
    @ExcelProperty(value = "文档ID")
    private String docId;
  /**
   * æ–‡æ¡£ID
   */
  @ExcelProperty(value = "文档ID")
  private String docId;
    /**
     * æ–‡æ¡£åç§°
     */
    @ExcelProperty(value = "文档名称")
    private String docName;
  /**
   * æ–‡æ¡£åç§°
   */
  @ExcelProperty(value = "文档名称")
  private String docName;
    /**
     * æ–‡æ¡£ç±»åž‹
     */
    @ExcelProperty(value = "文档类型")
    private String docType;
  /**
   * æ–‡æ¡£ç±»åž‹
   */
  @ExcelProperty(value = "文档类型")
  private String docType;
    /**
     * æ–‡æ¡£å†…容
     */
    @ExcelProperty(value = "文档内容")
    private String content;
  /**
   * æ–‡æ¡£å†…容
   */
  @ExcelProperty(value = "文档内容")
  private String content;
    /**
     * å¤‡æ³¨
     */
    @ExcelProperty(value = "备注")
    private String remark;
  /**
   * å¤‡æ³¨
   */
  @ExcelProperty(value = "备注")
  private String remark;
  /**
   * å¯¹è±¡å­˜å‚¨ä¸»é”®
   */
  @ExcelProperty(value = "对象存储主键")
  private Long ossId;
  /**
   * æ‹†è§£å›¾ç‰‡çŠ¶æ€10未开始,20进行中,30已完成
   */
  @ExcelProperty(value = "拆解图片状态10未开始,20进行中,30已完成")
  private Integer picStatus;
  /**
   * åˆ†æžå›¾ç‰‡çŠ¶æ€10未开始,20进行中,30已完成
   */
  @ExcelProperty(value = "分析图片状态10未开始,20进行中,30已完成")
  private Integer picAnysStatus;
  /**
   * å†™å…¥å‘量数据库状态10未开始,20进行中,30已完成
   */
  @ExcelProperty(value = "写入向量数据库状态10未开始,20进行中,30已完成")
  private Integer vectorStatus;
}
ruoyi-modules-api/ruoyi-knowledge-api/src/main/java/org/ruoyi/mapper/KnowledgeAttachPicMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,15 @@
package org.ruoyi.mapper;
import org.ruoyi.core.mapper.BaseMapperPlus;
import org.ruoyi.domain.KnowledgeAttachPic;
import org.ruoyi.domain.vo.KnowledgeAttachPicVo;
/**
 * çŸ¥è¯†åº“附件图片列Mapper接口
 *
 * @author Albert
 * @date 2025-05-15
 */
public interface KnowledgeAttachPicMapper extends BaseMapperPlus<KnowledgeAttachPic, KnowledgeAttachPicVo> {
}
ruoyi-modules-api/ruoyi-knowledge-api/src/main/java/org/ruoyi/service/IKnowledgeAttachPicService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,47 @@
package org.ruoyi.service;
import java.util.Collection;
import java.util.List;
import org.ruoyi.core.page.PageQuery;
import org.ruoyi.core.page.TableDataInfo;
import org.ruoyi.domain.bo.KnowledgeAttachPicBo;
import org.ruoyi.domain.vo.KnowledgeAttachPicVo;
/**
 * çŸ¥è¯†åº“附件图片列Service接口
 *
 * @author Albert
 * @date 2025-05-15
 */
public interface IKnowledgeAttachPicService {
  /**
   * æŸ¥è¯¢çŸ¥è¯†åº“附件图片列
   */
  KnowledgeAttachPicVo queryById(Long id);
  /**
   * æŸ¥è¯¢çŸ¥è¯†åº“附件图片列列表
   */
  TableDataInfo<KnowledgeAttachPicVo> queryPageList(KnowledgeAttachPicBo bo, PageQuery pageQuery);
  /**
   * æŸ¥è¯¢çŸ¥è¯†åº“附件图片列列表
   */
  List<KnowledgeAttachPicVo> queryList(KnowledgeAttachPicBo bo);
  /**
   * æ–°å¢žçŸ¥è¯†åº“附件图片列
   */
  Boolean insertByBo(KnowledgeAttachPicBo bo);
  /**
   * ä¿®æ”¹çŸ¥è¯†åº“附件图片列
   */
  Boolean updateByBo(KnowledgeAttachPicBo bo);
  /**
   * æ ¡éªŒå¹¶æ‰¹é‡åˆ é™¤çŸ¥è¯†åº“附件图片列信息
   */
  Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
}
ruoyi-modules-api/ruoyi-knowledge-api/src/main/java/org/ruoyi/service/IKnowledgeInfoService.java
@@ -61,5 +61,5 @@
    /**
     * ä¸Šä¼ é™„ä»¶
     */
    void upload(KnowledgeInfoUploadBo bo);
    void upload(KnowledgeInfoUploadBo bo) throws Exception;
}
ruoyi-modules-api/ruoyi-knowledge-api/src/main/java/org/ruoyi/service/impl/KnowledgeAttachPicServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,123 @@
package org.ruoyi.service.impl;
import org.ruoyi.common.core.utils.MapstructUtils;
import org.ruoyi.common.core.utils.StringUtils;
import org.ruoyi.core.page.TableDataInfo;
import org.ruoyi.core.page.PageQuery;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.RequiredArgsConstructor;
import org.ruoyi.domain.KnowledgeAttachPic;
import org.ruoyi.domain.bo.KnowledgeAttachPicBo;
import org.ruoyi.domain.vo.KnowledgeAttachPicVo;
import org.ruoyi.mapper.KnowledgeAttachPicMapper;
import org.ruoyi.service.IKnowledgeAttachPicService;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Map;
import java.util.Collection;
/**
 * çŸ¥è¯†åº“附件图片列Service业务层处理
 *
 * @author ageerle
 * @date 2025-05-15
 */
@RequiredArgsConstructor
@Service
public class KnowledgeAttachPicServiceImpl implements IKnowledgeAttachPicService {
  private final KnowledgeAttachPicMapper baseMapper;
  /**
   * æŸ¥è¯¢çŸ¥è¯†åº“附件图片列
   */
  @Override
  public KnowledgeAttachPicVo queryById(Long id) {
    return baseMapper.selectVoById(id);
  }
  /**
   * æŸ¥è¯¢çŸ¥è¯†åº“附件图片列列表
   */
  @Override
  public TableDataInfo<KnowledgeAttachPicVo> queryPageList(KnowledgeAttachPicBo bo,
      PageQuery pageQuery) {
    LambdaQueryWrapper<KnowledgeAttachPic> lqw = buildQueryWrapper(bo);
    Page<KnowledgeAttachPicVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
    return TableDataInfo.build(result);
  }
  /**
   * æŸ¥è¯¢çŸ¥è¯†åº“附件图片列列表
   */
  @Override
  public List<KnowledgeAttachPicVo> queryList(KnowledgeAttachPicBo bo) {
    LambdaQueryWrapper<KnowledgeAttachPic> lqw = buildQueryWrapper(bo);
    return baseMapper.selectVoList(lqw);
  }
  private LambdaQueryWrapper<KnowledgeAttachPic> buildQueryWrapper(KnowledgeAttachPicBo bo) {
    Map<String, Object> params = bo.getParams();
    LambdaQueryWrapper<KnowledgeAttachPic> lqw = Wrappers.lambdaQuery();
    lqw.eq(StringUtils.isNotBlank(bo.getKid()), KnowledgeAttachPic::getKid, bo.getKid());
    lqw.eq(StringUtils.isNotBlank(bo.getAid()), KnowledgeAttachPic::getAid, bo.getAid());
    lqw.like(StringUtils.isNotBlank(bo.getDocName()), KnowledgeAttachPic::getDocName,
        bo.getDocName());
    lqw.eq(StringUtils.isNotBlank(bo.getDocType()), KnowledgeAttachPic::getDocType,
        bo.getDocType());
    lqw.eq(StringUtils.isNotBlank(bo.getContent()), KnowledgeAttachPic::getContent,
        bo.getContent());
    lqw.eq(bo.getPageNum() != null, KnowledgeAttachPic::getPageNum, bo.getPageNum());
    lqw.eq(bo.getIndexNum() != null, KnowledgeAttachPic::getIndexNum, bo.getIndexNum());
    lqw.eq(bo.getPicAnysStatus() != null, KnowledgeAttachPic::getPicAnysStatus,
        bo.getPicAnysStatus());
    lqw.eq(bo.getOssId() != null, KnowledgeAttachPic::getOssId, bo.getOssId());
    return lqw;
  }
  /**
   * æ–°å¢žçŸ¥è¯†åº“附件图片列
   */
  @Override
  public Boolean insertByBo(KnowledgeAttachPicBo bo) {
    KnowledgeAttachPic add = MapstructUtils.convert(bo, KnowledgeAttachPic.class);
    validEntityBeforeSave(add);
    boolean flag = baseMapper.insert(add) > 0;
    if (flag) {
      bo.setId(add.getId());
    }
    return flag;
  }
  /**
   * ä¿®æ”¹çŸ¥è¯†åº“附件图片列
   */
  @Override
  public Boolean updateByBo(KnowledgeAttachPicBo bo) {
    KnowledgeAttachPic update = MapstructUtils.convert(bo, KnowledgeAttachPic.class);
    validEntityBeforeSave(update);
    return baseMapper.updateById(update) > 0;
  }
  /**
   * ä¿å­˜å‰çš„æ•°æ®æ ¡éªŒ
   */
  private void validEntityBeforeSave(KnowledgeAttachPic entity) {
    //TODO åšä¸€äº›æ•°æ®æ ¡éªŒ,如唯一约束
  }
  /**
   * æ‰¹é‡åˆ é™¤çŸ¥è¯†åº“附件图片列
   */
  @Override
  public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
    if (isValid) {
      //TODO åšä¸€äº›ä¸šåŠ¡ä¸Šçš„æ ¡éªŒ,判断是否需要校验
    }
    return baseMapper.deleteBatchIds(ids) > 0;
  }
}
ruoyi-modules-api/ruoyi-knowledge-api/src/main/java/org/ruoyi/service/impl/KnowledgeAttachServiceImpl.java
@@ -1,5 +1,7 @@
package org.ruoyi.service.impl;
import cn.hutool.core.util.ObjectUtil;
import java.util.stream.Collectors;
import org.ruoyi.common.core.utils.MapstructUtils;
import org.ruoyi.common.core.utils.StringUtils;
import org.ruoyi.core.page.TableDataInfo;
@@ -8,8 +10,11 @@
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.RequiredArgsConstructor;
import org.ruoyi.domain.KnowledgeAttachPic;
import org.ruoyi.domain.vo.KnowledgeAttachVo;
import org.ruoyi.mapper.KnowledgeAttachPicMapper;
import org.ruoyi.mapper.KnowledgeFragmentMapper;
import org.ruoyi.system.service.ISysOssService;
import org.springframework.stereotype.Service;
import org.ruoyi.domain.bo.KnowledgeAttachBo;
@@ -33,99 +38,130 @@
@Service
public class KnowledgeAttachServiceImpl implements IKnowledgeAttachService {
    private final KnowledgeAttachMapper baseMapper;
    private final KnowledgeFragmentMapper fragmentMapper;
  private final KnowledgeAttachMapper baseMapper;
  private final KnowledgeFragmentMapper fragmentMapper;
    /**
     * æŸ¥è¯¢çŸ¥è¯†åº“附件
     */
    @Override
    public KnowledgeAttachVo queryById(Long id){
        return baseMapper.selectVoById(id);
  private final ISysOssService ossService;
  private final KnowledgeAttachPicMapper picMapper;
  /**
   * æŸ¥è¯¢çŸ¥è¯†åº“附件
   */
  @Override
  public KnowledgeAttachVo queryById(Long id) {
    return baseMapper.selectVoById(id);
  }
  /**
   * æŸ¥è¯¢çŸ¥è¯†åº“附件列表
   */
  @Override
  public TableDataInfo<KnowledgeAttachVo> queryPageList(KnowledgeAttachBo bo, PageQuery pageQuery) {
    LambdaQueryWrapper<KnowledgeAttach> lqw = buildQueryWrapper(bo);
    Page<KnowledgeAttachVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
    return TableDataInfo.build(result);
  }
  /**
   * æŸ¥è¯¢çŸ¥è¯†åº“附件列表
   */
  @Override
  public List<KnowledgeAttachVo> queryList(KnowledgeAttachBo bo) {
    LambdaQueryWrapper<KnowledgeAttach> lqw = buildQueryWrapper(bo);
    return baseMapper.selectVoList(lqw);
  }
  private LambdaQueryWrapper<KnowledgeAttach> buildQueryWrapper(KnowledgeAttachBo bo) {
    Map<String, Object> params = bo.getParams();
    LambdaQueryWrapper<KnowledgeAttach> lqw = Wrappers.lambdaQuery();
    lqw.eq(StringUtils.isNotBlank(bo.getKid()), KnowledgeAttach::getKid, bo.getKid());
    lqw.eq(StringUtils.isNotBlank(bo.getDocId()), KnowledgeAttach::getDocId, bo.getDocId());
    lqw.like(StringUtils.isNotBlank(bo.getDocName()), KnowledgeAttach::getDocName, bo.getDocName());
    lqw.eq(StringUtils.isNotBlank(bo.getDocType()), KnowledgeAttach::getDocType, bo.getDocType());
    lqw.eq(StringUtils.isNotBlank(bo.getContent()), KnowledgeAttach::getContent, bo.getContent());
    return lqw;
  }
  /**
   * æ–°å¢žçŸ¥è¯†åº“附件
   */
  @Override
  public Boolean insertByBo(KnowledgeAttachBo bo) {
    KnowledgeAttach add = MapstructUtils.convert(bo, KnowledgeAttach.class);
    validEntityBeforeSave(add);
    boolean flag = baseMapper.insert(add) > 0;
    if (flag) {
      bo.setId(add.getId());
    }
    return flag;
  }
    /**
     * æŸ¥è¯¢çŸ¥è¯†åº“附件列表
     */
    @Override
    public TableDataInfo<KnowledgeAttachVo> queryPageList(KnowledgeAttachBo bo, PageQuery pageQuery) {
        LambdaQueryWrapper<KnowledgeAttach> lqw = buildQueryWrapper(bo);
        Page<KnowledgeAttachVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
        return TableDataInfo.build(result);
  /**
   * ä¿®æ”¹çŸ¥è¯†åº“附件
   */
  @Override
  public Boolean updateByBo(KnowledgeAttachBo bo) {
    KnowledgeAttach update = MapstructUtils.convert(bo, KnowledgeAttach.class);
    validEntityBeforeSave(update);
    return baseMapper.updateById(update) > 0;
  }
  /**
   * ä¿å­˜å‰çš„æ•°æ®æ ¡éªŒ
   */
  private void validEntityBeforeSave(KnowledgeAttach entity) {
    //TODO åšä¸€äº›æ•°æ®æ ¡éªŒ,如唯一约束
  }
  /**
   * æ‰¹é‡åˆ é™¤çŸ¥è¯†åº“附件
   */
  @Override
  public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
    if (isValid) {
      //TODO åšä¸€äº›ä¸šåŠ¡ä¸Šçš„æ ¡éªŒ,判断是否需要校验
    }
    return baseMapper.deleteBatchIds(ids) > 0;
  }
    /**
     * æŸ¥è¯¢çŸ¥è¯†åº“附件列表
     */
    @Override
    public List<KnowledgeAttachVo> queryList(KnowledgeAttachBo bo) {
        LambdaQueryWrapper<KnowledgeAttach> lqw = buildQueryWrapper(bo);
        return baseMapper.selectVoList(lqw);
  @Override
  public void removeKnowledgeAttach(String docId) {
    Map<String, Object> map = new HashMap<>();
    map.put("doc_id", docId);
    List<KnowledgeAttachVo> knowledgeAttachVos = baseMapper.selectVoByMap(map);
    if (ObjectUtil.isNotEmpty(knowledgeAttachVos)) {
      Collection<Long> ossIds = knowledgeAttachVos.stream()
          .map(KnowledgeAttachVo::getOssId)
          .collect(Collectors.toList());
      //删除oss
      ossService.deleteWithValidByIds(ossIds, false);
      //删除图片oss
      List<KnowledgeAttachPic> knowledgeAttachPics = picMapper.selectList(
          new LambdaQueryWrapper<KnowledgeAttachPic>()
              .in(KnowledgeAttachPic::getKid,
                  knowledgeAttachVos.stream().map(KnowledgeAttachVo::getKid)
                      .collect(Collectors.toList()))
              .in(KnowledgeAttachPic::getAid,
                  knowledgeAttachVos.stream().map(KnowledgeAttachVo::getId)
                      .collect(Collectors.toList()))
          );
      if (ObjectUtil.isNotEmpty(knowledgeAttachPics)) {
        Collection<Long> tossIds = knowledgeAttachPics.stream()
            .map(KnowledgeAttachPic::getOssId)
            .collect(Collectors.toList());
        ossService.deleteWithValidByIds(tossIds, false);
        List<Long> collect = knowledgeAttachPics.stream().map(KnowledgeAttachPic::getId)
            .collect(Collectors.toList());
        picMapper.deleteByIds(collect);
      }
    }
    baseMapper.deleteByMap(map);
    fragmentMapper.deleteByMap(map);
  }
    private LambdaQueryWrapper<KnowledgeAttach> buildQueryWrapper(KnowledgeAttachBo bo) {
        Map<String, Object> params = bo.getParams();
        LambdaQueryWrapper<KnowledgeAttach> lqw = Wrappers.lambdaQuery();
        lqw.eq(StringUtils.isNotBlank(bo.getKid()), KnowledgeAttach::getKid, bo.getKid());
        lqw.eq(StringUtils.isNotBlank(bo.getDocId()), KnowledgeAttach::getDocId, bo.getDocId());
        lqw.like(StringUtils.isNotBlank(bo.getDocName()), KnowledgeAttach::getDocName, bo.getDocName());
        lqw.eq(StringUtils.isNotBlank(bo.getDocType()), KnowledgeAttach::getDocType, bo.getDocType());
        lqw.eq(StringUtils.isNotBlank(bo.getContent()), KnowledgeAttach::getContent, bo.getContent());
        return lqw;
    }
    /**
     * æ–°å¢žçŸ¥è¯†åº“附件
     */
    @Override
    public Boolean insertByBo(KnowledgeAttachBo bo) {
        KnowledgeAttach add = MapstructUtils.convert(bo, KnowledgeAttach.class);
        validEntityBeforeSave(add);
        boolean flag = baseMapper.insert(add) > 0;
        if (flag) {
            bo.setId(add.getId());
        }
        return flag;
    }
    /**
     * ä¿®æ”¹çŸ¥è¯†åº“附件
     */
    @Override
    public Boolean updateByBo(KnowledgeAttachBo bo) {
        KnowledgeAttach update = MapstructUtils.convert(bo, KnowledgeAttach.class);
        validEntityBeforeSave(update);
        return baseMapper.updateById(update) > 0;
    }
    /**
     * ä¿å­˜å‰çš„æ•°æ®æ ¡éªŒ
     */
    private void validEntityBeforeSave(KnowledgeAttach entity){
        //TODO åšä¸€äº›æ•°æ®æ ¡éªŒ,如唯一约束
    }
    /**
     * æ‰¹é‡åˆ é™¤çŸ¥è¯†åº“附件
     */
    @Override
    public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
        if(isValid){
            //TODO åšä¸€äº›ä¸šåŠ¡ä¸Šçš„æ ¡éªŒ,判断是否需要校验
        }
        return baseMapper.deleteBatchIds(ids) > 0;
    }
    @Override
    public void removeKnowledgeAttach(String docId) {
        Map<String,Object> map = new HashMap<>();
        map.put("doc_id",docId);
        baseMapper.deleteByMap(map);
        fragmentMapper.deleteByMap(map);
    }
    @Override
    public String translationByFile(MultipartFile file, String targetLanguage) {
  @Override
  public String translationByFile(MultipartFile file, String targetLanguage) {
        /*String fileName = file.getOriginalFilename();
        String docType = fileName.substring(fileName.lastIndexOf(".")+1);
        String content = "";
@@ -173,6 +209,6 @@
            throw new BaseException("调用大模型失败,请检查密钥是否正确!");
        }
        return chatCompletionResponse.getChoices().get(0).getMessage().getContent().toString();*/
        return "接口开发中!";
    }
    return "接口开发中!";
  }
}
ruoyi-modules-api/ruoyi-knowledge-api/src/main/java/org/ruoyi/service/impl/PdfImageExtractServiceImpl.java
@@ -4,6 +4,8 @@
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import okhttp3.MediaType;
import okhttp3.MultipartBody;
@@ -23,16 +25,19 @@
/**
 * PDF图片提取服务实现类
 */
@Service
//@Service
@Slf4j
public class PdfImageExtractServiceImpl implements PdfImageExtractService {
@Data
@AllArgsConstructor
//public class PdfImageExtractServiceImpl implements PdfImageExtractService {
public class PdfImageExtractServiceImpl  {
  @Value("${pdf.extract.service.url}")
//  @Value("${pdf.extract.service.url}")
  private String serviceUrl;
  @Value("${pdf.extract.ai-api.url}")
//  @Value("${pdf.extract.ai-api.url}")
  private String aiApiUrl;
  @Value("${pdf.extract.ai-api.key}")
  private String aiApiKey ;
//  @Value("${pdf.extract.ai-api.key}")
  private String aiApiKey;
  private final OkHttpClient client = new Builder()
      .connectTimeout(100, TimeUnit.SECONDS)
@@ -43,7 +48,7 @@
  private static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
  @Override
//  @Override
  public byte[] extractImages(MultipartFile pdfFile, String imageFormat, boolean allowDuplicates)
      throws IOException {
    // æž„建multipart请求
@@ -77,7 +82,7 @@
   * @return æ–‡ä»¶å†…容结果列表
   * @throws IOException å¦‚æžœAPI调用过程中发生错误
   */
  @Override
//  @Override
  public List<PdfFileContentResult> dealFileContent(String[] unzip) throws IOException {
    List<PdfFileContentResult> results = new ArrayList<>();
    int i = 0;
@@ -110,6 +115,7 @@
      // æ‰§è¡Œè¯·æ±‚
      try {
        log.info("=============call=" + ++i);
        Response response = client.newCall(request).execute();
        log.info("=============response=" + response);
        if (!response.isSuccessful()) {
@@ -126,11 +132,10 @@
        throw new RuntimeException(e);
      }
    }
    return results;
  }
  @Override
//  @Override
  public List<PdfFileContentResult> extractImages(MultipartFile file) throws IOException {
    String format = "png";
    boolean allowDuplicates = true;
ruoyi-modules-api/ruoyi-knowledge-api/src/main/java/org/ruoyi/service/impl/VectorStoreServiceImpl.java
@@ -97,7 +97,7 @@
        for (int i = 0; i < chunkList.size(); i++) {
            Map<String, Object> dataSchema = new HashMap<>();
            dataSchema.put("kid", storeEmbeddingBo.getKid());
            dataSchema.put("docId", storeEmbeddingBo.getKid());
            dataSchema.put("docId", storeEmbeddingBo.getDocId());
            dataSchema.put("fid", storeEmbeddingBo.getFids().get(i));
            Embedding embedding = embeddingModel.embed(chunkList.get(i)).content();
            TextSegment segment = TextSegment.from(chunkList.get(i));
ruoyi-modules-api/ruoyi-knowledge-api/src/main/java/org/ruoyi/utils/ZipUtils.java
@@ -11,6 +11,8 @@
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.web.multipart.MultipartFile;
/**
 * ZIP文件处理工具类
@@ -92,4 +94,90 @@
        }
        return base64Contents.toArray(new String[0]);
    }
  /**
   * è§£åŽ‹ZIP文件并返回MultipartFile数组
   *
   * @param zipData ZIP文件的字节数组
   * @return MultipartFile数组
   * @throws IOException å¦‚果解压过程中发生错误
   */
  public static MultipartFile[] unzipToMultipartFiles(byte[] zipData) throws IOException {
    List<MultipartFile> multipartFiles = new ArrayList<>();
    try (ByteArrayInputStream bis = new ByteArrayInputStream(zipData);
        ZipInputStream zis = new ZipInputStream(bis)) {
      ZipEntry zipEntry;
      while ((zipEntry = zis.getNextEntry()) != null) {
        if (!zipEntry.isDirectory()) {
          // è¯»å–文件内容到内存
          ByteArrayOutputStream baos = new ByteArrayOutputStream();
          byte[] buffer = new byte[4096];
          int read;
          while ((read = zis.read(buffer)) != -1) {
            baos.write(buffer, 0, read);
          }
          // åˆ›å»ºMultipartFile对象
          String fileName = zipEntry.getName();
          byte[] content = baos.toByteArray();
          String contentType = determineContentType(fileName);
          MultipartFile multipartFile = new MockMultipartFile(
              fileName,                  // æ–‡ä»¶å
              fileName,                  // åŽŸå§‹æ–‡ä»¶å
              contentType,               // å†…容类型
              content                    // æ–‡ä»¶å†…容
          );
          multipartFiles.add(multipartFile);
        }
        zis.closeEntry();
      }
    }
    return multipartFiles.toArray(new MultipartFile[0]);
  }
  /**
   * æ ¹æ®æ–‡ä»¶åç¡®å®šå†…容类型
   *
   * @param fileName æ–‡ä»¶å
   * @return å†…容类型
   */
  private static String determineContentType(String fileName) {
    String extension = "";
    int i = fileName.lastIndexOf('.');
    if (i > 0) {
      extension = fileName.substring(i + 1).toLowerCase();
    }
    switch (extension) {
      case "txt":
        return "text/plain";
      case "html":
      case "htm":
        return "text/html";
      case "pdf":
        return "application/pdf";
      case "jpg":
      case "jpeg":
        return "image/jpeg";
      case "png":
        return "image/png";
      case "gif":
        return "image/gif";
      case "doc":
      case "docx":
        return "application/msword";
      case "xls":
      case "xlsx":
        return "application/vnd.ms-excel";
      case "xml":
        return "application/xml";
      case "json":
        return "application/json";
      default:
        return "application/octet-stream";
    }
  }
}
ruoyi-modules-api/ruoyi-system-api/pom.xml
@@ -17,5 +17,11 @@
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
  <dependencies>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-test</artifactId>
    </dependency>
  </dependencies>
</project>
ruoyi-modules-api/ruoyi-system-api/src/main/java/org/ruoyi/system/service/ISysLogininforService.java
@@ -1,5 +1,6 @@
package org.ruoyi.system.service;
import org.ruoyi.common.log.event.LogininforEvent;
import org.ruoyi.core.page.PageQuery;
import org.ruoyi.core.page.TableDataInfo;
import org.ruoyi.system.domain.bo.SysLogininforBo;
@@ -44,4 +45,6 @@
     * æ¸…空系统登录日志
     */
    void cleanLogininfor();
  void recordLogininfor(LogininforEvent logininforEvent);
}
ruoyi-modules-api/ruoyi-system-api/src/main/java/org/ruoyi/system/service/ISysOperLogService.java
@@ -1,5 +1,6 @@
package org.ruoyi.system.service;
import org.ruoyi.common.log.event.OperLogEvent;
import org.ruoyi.core.page.PageQuery;
import org.ruoyi.core.page.TableDataInfo;
import org.ruoyi.system.domain.bo.SysOperLogBo;
@@ -51,4 +52,5 @@
     * æ¸…空操作日志
     */
    void cleanOperLog();
  void recordOper(OperLogEvent operLogEvent);
}
ruoyi-modules-api/ruoyi-system-api/src/main/java/org/ruoyi/system/service/ISysOssService.java
@@ -18,16 +18,20 @@
 */
public interface ISysOssService {
    TableDataInfo<SysOssVo> queryPageList(SysOssBo sysOss, PageQuery pageQuery);
  TableDataInfo<SysOssVo> queryPageList(SysOssBo sysOss, PageQuery pageQuery);
    List<SysOssVo> listByIds(Collection<Long> ossIds);
  List<SysOssVo> listByIds(Collection<Long> ossIds);
    SysOssVo getById(Long ossId);
  SysOssVo getById(Long ossId);
    SysOssVo upload(MultipartFile file);
  SysOssVo upload(MultipartFile file);
    void download(Long ossId, HttpServletResponse response) throws IOException;
  void download(Long ossId, HttpServletResponse response) throws IOException;
    Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
  MultipartFile downloadByFile(Long ossId) throws IOException;
  String downloadByByte(Long ossId) throws IOException;
  Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
}
ruoyi-modules-api/ruoyi-system-api/src/main/java/org/ruoyi/system/service/impl/SysLogininforServiceImpl.java
@@ -48,6 +48,7 @@
     */
    @Async
    @EventListener
    @Override
    public void recordLogininfor(LogininforEvent logininforEvent) {
        HttpServletRequest request = logininforEvent.getRequest();
        final UserAgent userAgent = UserAgentUtil.parse(request.getHeader("User-Agent"));
ruoyi-modules-api/ruoyi-system-api/src/main/java/org/ruoyi/system/service/impl/SysOperLogServiceImpl.java
@@ -42,6 +42,7 @@
     */
    @Async
    @EventListener
    @Override
    public void recordOper(OperLogEvent operLogEvent) {
        SysOperLogBo operLog = MapstructUtils.convert(operLogEvent, SysOperLogBo.class);
        // è¿œç¨‹æŸ¥è¯¢æ“ä½œåœ°ç‚¹
ruoyi-modules-api/ruoyi-system-api/src/main/java/org/ruoyi/system/service/impl/SysOssServiceImpl.java
@@ -7,6 +7,7 @@
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import jakarta.servlet.http.HttpServletResponse;
import java.util.Base64;
import lombok.RequiredArgsConstructor;
import org.ruoyi.common.core.constant.CacheNames;
import org.ruoyi.common.core.exception.ServiceException;
@@ -29,6 +30,7 @@
import org.ruoyi.system.service.ISysOssService;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
@@ -48,127 +50,167 @@
@Service
public class SysOssServiceImpl implements ISysOssService, OssService {
    private final SysOssMapper baseMapper;
  private final SysOssMapper baseMapper;
    @Override
    public TableDataInfo<SysOssVo> queryPageList(SysOssBo bo, PageQuery pageQuery) {
        LambdaQueryWrapper<SysOss> lqw = buildQueryWrapper(bo);
        Page<SysOssVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
        List<SysOssVo> filterResult = StreamUtils.toList(result.getRecords(), this::matchingUrl);
        result.setRecords(filterResult);
        return TableDataInfo.build(result);
  @Override
  public TableDataInfo<SysOssVo> queryPageList(SysOssBo bo, PageQuery pageQuery) {
    LambdaQueryWrapper<SysOss> lqw = buildQueryWrapper(bo);
    Page<SysOssVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
    List<SysOssVo> filterResult = StreamUtils.toList(result.getRecords(), this::matchingUrl);
    result.setRecords(filterResult);
    return TableDataInfo.build(result);
  }
  @Override
  public List<SysOssVo> listByIds(Collection<Long> ossIds) {
    List<SysOssVo> list = new ArrayList<>();
    for (Long id : ossIds) {
      SysOssVo vo = SpringUtils.getAopProxy(this).getById(id);
      if (ObjectUtil.isNotNull(vo)) {
        list.add(this.matchingUrl(vo));
      }
    }
    return list;
  }
  @Override
  public String selectUrlByIds(String ossIds) {
    List<String> list = new ArrayList<>();
    for (Long id : StringUtils.splitTo(ossIds, Convert::toLong)) {
      SysOssVo vo = SpringUtils.getAopProxy(this).getById(id);
      if (ObjectUtil.isNotNull(vo)) {
        list.add(this.matchingUrl(vo).getUrl());
      }
    }
    return String.join(StringUtils.SEPARATOR, list);
  }
  private LambdaQueryWrapper<SysOss> buildQueryWrapper(SysOssBo bo) {
    Map<String, Object> params = bo.getParams();
    LambdaQueryWrapper<SysOss> lqw = Wrappers.lambdaQuery();
    lqw.like(StringUtils.isNotBlank(bo.getFileName()), SysOss::getFileName, bo.getFileName());
    lqw.like(StringUtils.isNotBlank(bo.getOriginalName()), SysOss::getOriginalName,
        bo.getOriginalName());
    lqw.eq(StringUtils.isNotBlank(bo.getFileSuffix()), SysOss::getFileSuffix, bo.getFileSuffix());
    lqw.eq(StringUtils.isNotBlank(bo.getUrl()), SysOss::getUrl, bo.getUrl());
    lqw.between(params.get("beginCreateTime") != null && params.get("endCreateTime") != null,
        SysOss::getCreateTime, params.get("beginCreateTime"), params.get("endCreateTime"));
    lqw.eq(ObjectUtil.isNotNull(bo.getCreateBy()), SysOss::getCreateBy, bo.getCreateBy());
    lqw.eq(StringUtils.isNotBlank(bo.getService()), SysOss::getService, bo.getService());
    return lqw;
  }
  @Cacheable(cacheNames = CacheNames.SYS_OSS, key = "#ossId")
  @Override
  public SysOssVo getById(Long ossId) {
    return baseMapper.selectVoById(ossId);
  }
  @Override
  public void download(Long ossId, HttpServletResponse response) throws IOException {
    SysOssVo sysOss = SpringUtils.getAopProxy(this).getById(ossId);
    if (ObjectUtil.isNull(sysOss)) {
      throw new ServiceException("文件数据不存在!");
    }
    FileUtils.setAttachmentResponseHeader(response, sysOss.getOriginalName());
    response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE + "; charset=UTF-8");
    OssClient storage = OssFactory.instance();
    try (InputStream inputStream = storage.getObjectContent(sysOss.getUrl())) {
      int available = inputStream.available();
      IoUtil.copy(inputStream, response.getOutputStream(), available);
      response.setContentLength(available);
    } catch (Exception e) {
      throw new ServiceException(e.getMessage());
    }
  }
  @Override
  public String downloadByByte(Long ossId) throws IOException {
    SysOssVo sysOss = SpringUtils.getAopProxy(this).getById(ossId);
    if (ObjectUtil.isNull(sysOss)) {
      throw new ServiceException("文件数据不存在!");
    }
    @Override
    public List<SysOssVo> listByIds(Collection<Long> ossIds) {
        List<SysOssVo> list = new ArrayList<>();
        for (Long id : ossIds) {
            SysOssVo vo = SpringUtils.getAopProxy(this).getById(id);
            if (ObjectUtil.isNotNull(vo)) {
                list.add(this.matchingUrl(vo));
            }
        }
        return list;
    OssClient storage = OssFactory.instance();
    try (InputStream inputStream = storage.getObjectContent(sysOss.getUrl())) {
      // è¯»å–输入流中的所有字节
      byte[] bytes = IoUtil.readBytes(inputStream);
      // å°†å­—节数组转换为Base64编码的字符串
      return Base64.getEncoder().encodeToString(bytes);
    } catch (Exception e) {
      throw new ServiceException(e.getMessage());
    }
  }
  @Override
  public MultipartFile downloadByFile(Long ossId) throws IOException {
    SysOssVo sysOss = SpringUtils.getAopProxy(this).getById(ossId);
    if (ObjectUtil.isNull(sysOss)) {
      throw new ServiceException("文件数据不存在!");
    }
    @Override
    public String selectUrlByIds(String ossIds) {
        List<String> list = new ArrayList<>();
        for (Long id : StringUtils.splitTo(ossIds, Convert::toLong)) {
            SysOssVo vo = SpringUtils.getAopProxy(this).getById(id);
            if (ObjectUtil.isNotNull(vo)) {
                list.add(this.matchingUrl(vo).getUrl());
            }
        }
        return String.join(StringUtils.SEPARATOR, list);
    OssClient storage = OssFactory.instance();
    try (InputStream inputStream = storage.getObjectContent(sysOss.getUrl())) {
      byte[] content = IoUtil.readBytes(inputStream);
      return new MockMultipartFile(
          sysOss.getFileName(),
          sysOss.getOriginalName(),
          MediaType.APPLICATION_OCTET_STREAM_VALUE,
          content
      );
    } catch (Exception e) {
      throw new ServiceException(e.getMessage());
    }
  }
    private LambdaQueryWrapper<SysOss> buildQueryWrapper(SysOssBo bo) {
        Map<String, Object> params = bo.getParams();
        LambdaQueryWrapper<SysOss> lqw = Wrappers.lambdaQuery();
        lqw.like(StringUtils.isNotBlank(bo.getFileName()), SysOss::getFileName, bo.getFileName());
        lqw.like(StringUtils.isNotBlank(bo.getOriginalName()), SysOss::getOriginalName, bo.getOriginalName());
        lqw.eq(StringUtils.isNotBlank(bo.getFileSuffix()), SysOss::getFileSuffix, bo.getFileSuffix());
        lqw.eq(StringUtils.isNotBlank(bo.getUrl()), SysOss::getUrl, bo.getUrl());
        lqw.between(params.get("beginCreateTime") != null && params.get("endCreateTime") != null,
            SysOss::getCreateTime, params.get("beginCreateTime"), params.get("endCreateTime"));
        lqw.eq(ObjectUtil.isNotNull(bo.getCreateBy()), SysOss::getCreateBy, bo.getCreateBy());
        lqw.eq(StringUtils.isNotBlank(bo.getService()), SysOss::getService, bo.getService());
        return lqw;
  @Override
  public SysOssVo upload(MultipartFile file) {
    String originalfileName = file.getOriginalFilename();
    String suffix = StringUtils.substring(originalfileName, originalfileName.lastIndexOf("."),
        originalfileName.length());
    OssClient storage = OssFactory.instance();
    UploadResult uploadResult;
    try {
      uploadResult = storage.uploadSuffix(file.getBytes(), suffix, file.getContentType());
    } catch (IOException e) {
      throw new ServiceException(e.getMessage());
    }
    // ä¿å­˜æ–‡ä»¶ä¿¡æ¯
    SysOss oss = new SysOss();
    oss.setUrl(uploadResult.getUrl());
    oss.setFileSuffix(suffix);
    oss.setFileName(uploadResult.getFilename());
    oss.setOriginalName(originalfileName);
    oss.setService(storage.getConfigKey());
    baseMapper.insert(oss);
    SysOssVo sysOssVo = MapstructUtils.convert(oss, SysOssVo.class);
    return this.matchingUrl(sysOssVo);
  }
    @Cacheable(cacheNames = CacheNames.SYS_OSS, key = "#ossId")
    @Override
    public SysOssVo getById(Long ossId) {
        return baseMapper.selectVoById(ossId);
  @Override
  public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
    if (isValid) {
      // åšä¸€äº›ä¸šåŠ¡ä¸Šçš„æ ¡éªŒ,判断是否需要校验
    }
    List<SysOss> list = baseMapper.selectBatchIds(ids);
    for (SysOss sysOss : list) {
      OssClient storage = OssFactory.instance(sysOss.getService());
      storage.delete(sysOss.getUrl());
    }
    return baseMapper.deleteBatchIds(ids) > 0;
  }
    @Override
    public void download(Long ossId, HttpServletResponse response) throws IOException {
        SysOssVo sysOss = SpringUtils.getAopProxy(this).getById(ossId);
        if (ObjectUtil.isNull(sysOss)) {
            throw new ServiceException("文件数据不存在!");
        }
        FileUtils.setAttachmentResponseHeader(response, sysOss.getOriginalName());
        response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE + "; charset=UTF-8");
        OssClient storage = OssFactory.instance();
        try(InputStream inputStream = storage.getObjectContent(sysOss.getUrl())) {
            int available = inputStream.available();
            IoUtil.copy(inputStream, response.getOutputStream(), available);
            response.setContentLength(available);
        } catch (Exception e) {
            throw new ServiceException(e.getMessage());
        }
  /**
   * åŒ¹é…Url
   *
   * @param oss OSS对象
   * @return oss åŒ¹é…Url的OSS对象
   */
  private SysOssVo matchingUrl(SysOssVo oss) {
    OssClient storage = OssFactory.instance(oss.getService());
    // ä»…修改桶类型为 private çš„URL,临时URL时长为120s
    if (AccessPolicyType.PRIVATE == storage.getAccessPolicy()) {
      oss.setUrl(storage.getPrivateUrl(oss.getFileName(), 120));
    }
    @Override
    public SysOssVo upload(MultipartFile file) {
        String originalfileName = file.getOriginalFilename();
        String suffix = StringUtils.substring(originalfileName, originalfileName.lastIndexOf("."), originalfileName.length());
        OssClient storage = OssFactory.instance();
        UploadResult uploadResult;
        try {
            uploadResult = storage.uploadSuffix(file.getBytes(), suffix, file.getContentType());
        } catch (IOException e) {
            throw new ServiceException(e.getMessage());
        }
        // ä¿å­˜æ–‡ä»¶ä¿¡æ¯
        SysOss oss = new SysOss();
        oss.setUrl(uploadResult.getUrl());
        oss.setFileSuffix(suffix);
        oss.setFileName(uploadResult.getFilename());
        oss.setOriginalName(originalfileName);
        oss.setService(storage.getConfigKey());
        baseMapper.insert(oss);
        SysOssVo sysOssVo = MapstructUtils.convert(oss, SysOssVo.class);
        return this.matchingUrl(sysOssVo);
    }
    @Override
    public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
        if (isValid) {
            // åšä¸€äº›ä¸šåŠ¡ä¸Šçš„æ ¡éªŒ,判断是否需要校验
        }
        List<SysOss> list = baseMapper.selectBatchIds(ids);
        for (SysOss sysOss : list) {
            OssClient storage = OssFactory.instance(sysOss.getService());
            storage.delete(sysOss.getUrl());
        }
        return baseMapper.deleteBatchIds(ids) > 0;
    }
    /**
     * åŒ¹é…Url
     *
     * @param oss OSS对象
     * @return oss åŒ¹é…Url的OSS对象
     */
    private SysOssVo matchingUrl(SysOssVo oss) {
        OssClient storage = OssFactory.instance(oss.getService());
        // ä»…修改桶类型为 private çš„URL,临时URL时长为120s
        if (AccessPolicyType.PRIVATE == storage.getAccessPolicy()) {
            oss.setUrl(storage.getPrivateUrl(oss.getFileName(), 120));
        }
        return oss;
    }
    return oss;
  }
}
ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/controller/knowledge/KnowledgeController.java
@@ -52,7 +52,7 @@
  private final IKnowledgeFragmentService fragmentService;
  private final PdfImageExtractService pdfImageExtractService;
//  private final PdfImageExtractService pdfImageExtractService;
  /**
   * æ ¹æ®ç”¨æˆ·ä¿¡æ¯æŸ¥è¯¢æœ¬åœ°çŸ¥è¯†åº“
@@ -118,7 +118,7 @@
   * ä¸Šä¼ çŸ¥è¯†åº“附件
   */
  @PostMapping(value = "/attach/upload")
  public R<String> upload(KnowledgeInfoUploadBo bo) {
  public R<String> upload(KnowledgeInfoUploadBo bo) throws Exception {
    knowledgeInfoService.upload(bo);
    return R.ok("上传知识库附件成功!");
  }
@@ -168,13 +168,13 @@
   * æå–PDF中的图片并调用gpt-4o-mini,识别图片内容并返回
   *
   * @param file PDF文件
   * @return ä¿å­˜çš„æ–‡ä»¶è·¯å¾„信息
   * @return æ–‡ä»¶åç§°å’Œå›¾ç‰‡å†…容
   */
  @PostMapping("/extract-images")
  @Operation(summary = "提取PDF中的图片并调用大模型,识别图片内容并返回", description = "提取PDF中的图片并调用gpt-4o-mini,识别图片内容并返回")
  public R<List<PdfFileContentResult>> extractImages(
      @RequestPart("file") MultipartFile file
  ) throws IOException {
    return R.ok(pdfImageExtractService.extractImages(file));
  }
//  @PostMapping("/extract-images")
//  @Operation(summary = "提取PDF中的图片并调用大模型,识别图片内容并返回", description = "提取PDF中的图片并调用gpt-4o-mini,识别图片内容并返回")
//  public R<List<PdfFileContentResult>> extractImages(
//      @RequestPart("file") MultipartFile file
//  ) throws IOException {
//    return R.ok(pdfImageExtractService.extractImages(file));
//  }
}
ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/knowledge/DealFileService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,385 @@
package org.ruoyi.chat.service.knowledge;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.RandomUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import org.ruoyi.chain.loader.ResourceLoaderFactory;
import org.ruoyi.constant.DealStatus;
import org.ruoyi.domain.KnowledgeAttach;
import org.ruoyi.domain.KnowledgeAttachPic;
import org.ruoyi.domain.KnowledgeFragment;
import org.ruoyi.domain.KnowledgeInfo;
import org.ruoyi.domain.PdfFileContentResult;
import org.ruoyi.domain.bo.StoreEmbeddingBo;
import org.ruoyi.domain.vo.ChatModelVo;
import org.ruoyi.domain.vo.KnowledgeAttachVo;
import org.ruoyi.domain.vo.KnowledgeInfoVo;
import org.ruoyi.mapper.KnowledgeAttachMapper;
import org.ruoyi.mapper.KnowledgeAttachPicMapper;
import org.ruoyi.mapper.KnowledgeFragmentMapper;
import org.ruoyi.mapper.KnowledgeInfoMapper;
import org.ruoyi.service.IChatModelService;
import org.ruoyi.service.VectorStoreService;
import org.ruoyi.service.impl.PdfImageExtractServiceImpl;
import org.ruoyi.system.domain.vo.SysOssVo;
import org.ruoyi.system.service.ISysOssService;
import org.ruoyi.utils.ZipUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
 * @Description:
 * @Date: 2025/5/15 ä¸‹åˆ4:29
 */
@Service
@RequiredArgsConstructor
public class DealFileService {
  private static final Logger log = LoggerFactory.getLogger(DealFileService.class);
  private final KnowledgeInfoMapper baseMapper;
  private final VectorStoreService vectorStoreService;
  private final ResourceLoaderFactory resourceLoaderFactory;
  private final KnowledgeFragmentMapper fragmentMapper;
  private final KnowledgeAttachMapper attachMapper;
  private final IChatModelService chatModelService;
  private final ISysOssService ossService;
//  private final PdfImageExtractService pdfImageExtractService;
  private final KnowledgeAttachPicMapper picMapper;
  @Value("${pdf.extract.service.url}")
  private String serviceUrl;
  @Value("${pdf.extract.ai-api.url}")
  private String aiApiUrl;
  @Value("${pdf.extract.ai-api.key}")
  private String aiApiKey;
  @Async
  public void dealVectorStatus(KnowledgeAttach attachItem) throws Exception {
    try {
      //锁定数据 æ›´æ”¹VectorStatus åˆ°è¿›è¡Œä¸­
      if (attachMapper.update(new LambdaUpdateWrapper<KnowledgeAttach>()
          .set(KnowledgeAttach::getVectorStatus, DealStatus.STATUS_20)
          .eq(KnowledgeAttach::getPicStatus, DealStatus.STATUS_30)
          .eq(KnowledgeAttach::getPicAnysStatus, DealStatus.STATUS_30)
          .eq(KnowledgeAttach::getVectorStatus, DealStatus.STATUS_10)
          .eq(KnowledgeAttach::getId, attachItem.getId())
      ) == 0) {
        return;
      }
      List<KnowledgeFragment> knowledgeFragments = fragmentMapper.selectList(
          new LambdaQueryWrapper<KnowledgeFragment>()
              .eq(KnowledgeFragment::getKid, attachItem.getKid())
              .eq(KnowledgeFragment::getDocId, attachItem.getDocId())
      );
      if (ObjectUtil.isEmpty(knowledgeFragments)) {
        throw new Exception("文件段落为空");
      }
      List<String> fids = knowledgeFragments.stream()
          .map(KnowledgeFragment::getFid)
          .collect(Collectors.toList());
      if (ObjectUtil.isEmpty(fids)) {
        throw new Exception("fids ä¸ºç©º");
      }
      List<String> chunkList = knowledgeFragments.stream()
          .map(KnowledgeFragment::getContent)
          .collect(Collectors.toList());
      if (ObjectUtil.isEmpty(chunkList)) {
        throw new Exception("chunkList ä¸ºç©º");
      }
      // é€šè¿‡kid查询知识库信息
      KnowledgeInfoVo knowledgeInfoVo = baseMapper.selectVoOne(Wrappers.<KnowledgeInfo>lambdaQuery()
          .eq(KnowledgeInfo::getId, attachItem.getKid()));
      // é€šè¿‡å‘量模型查询模型信息
      ChatModelVo chatModelVo = chatModelService.selectModelByName(
          knowledgeInfoVo.getEmbeddingModelName());
      StoreEmbeddingBo storeEmbeddingBo = new StoreEmbeddingBo();
      storeEmbeddingBo.setKid(attachItem.getKid());
      storeEmbeddingBo.setDocId(attachItem.getDocId());
      storeEmbeddingBo.setFids(fids);
      storeEmbeddingBo.setChunkList(chunkList);
      storeEmbeddingBo.setVectorModelName(knowledgeInfoVo.getVectorModelName());
      storeEmbeddingBo.setEmbeddingModelName(knowledgeInfoVo.getEmbeddingModelName());
      storeEmbeddingBo.setApiKey(chatModelVo.getApiKey());
      storeEmbeddingBo.setBaseUrl(chatModelVo.getApiHost());
      vectorStoreService.storeEmbeddings(storeEmbeddingBo);
      //设置处理完成
      attachMapper.update(new LambdaUpdateWrapper<KnowledgeAttach>()
          .set(KnowledgeAttach::getVectorStatus, DealStatus.STATUS_30)
          .eq(KnowledgeAttach::getPicStatus, DealStatus.STATUS_30)
          .eq(KnowledgeAttach::getPicAnysStatus, DealStatus.STATUS_30)
          .eq(KnowledgeAttach::getVectorStatus, DealStatus.STATUS_20)
          .eq(KnowledgeAttach::getId, attachItem.getId()));
    } catch (Exception e) {
      //设置处理失败
      attachMapper.update(new LambdaUpdateWrapper<KnowledgeAttach>()
          .set(KnowledgeAttach::getVectorStatus, DealStatus.STATUS_40)
          .set(KnowledgeAttach::getRemark, attachItem.getRemark() + e.getMessage())
          .eq(KnowledgeAttach::getPicStatus, DealStatus.STATUS_30)
          .eq(KnowledgeAttach::getPicAnysStatus, DealStatus.STATUS_30)
          .eq(KnowledgeAttach::getVectorStatus, DealStatus.STATUS_20)
          .eq(KnowledgeAttach::getId, attachItem.getId()));
      throw new RuntimeException(e);
    }
  }
  @Async
  public void dealPicStatus(KnowledgeAttach attachItem) throws Exception {
    try {
      //锁定数据 æ›´æ”¹picStatus åˆ°è¿›è¡Œä¸­
      if (attachMapper.update(new LambdaUpdateWrapper<KnowledgeAttach>()
          .set(KnowledgeAttach::getPicStatus, DealStatus.STATUS_20)
          .eq(KnowledgeAttach::getPicStatus, DealStatus.STATUS_10)
          .eq(KnowledgeAttach::getPicAnysStatus, DealStatus.STATUS_10)
          .eq(KnowledgeAttach::getVectorStatus, DealStatus.STATUS_10)
          .eq(KnowledgeAttach::getId, attachItem.getId())
      ) == 0) {
        return;
      }
      //获取附件
      if (ObjectUtil.isEmpty(attachItem.getOssId())) {
        log.error("==========OssId ä¸ºç©ºï¼ŒattachItem={}", attachItem);
        throw new Exception("OssId ä¸ºç©º");
      }
      //获取oss文件
      MultipartFile multipartFile = ossService.downloadByFile(attachItem.getOssId());
      //拆解出图片ZIP
      PdfImageExtractServiceImpl pdfImageExtractService = new PdfImageExtractServiceImpl(serviceUrl,
          aiApiUrl, aiApiKey);
      byte[] pngs = pdfImageExtractService.extractImages(multipartFile, "png", true);
      //解压zip,得到图片文件
      MultipartFile[] multipartFiles = ZipUtils.unzipToMultipartFiles(pngs);
      //上传文件到OSS,写入表
      for (MultipartFile file : multipartFiles) {
        //先查找是否有相同图片名称,先做删除
        List<KnowledgeAttachPic> knowledgeAttachPics = picMapper.selectList(
            new LambdaQueryWrapper<KnowledgeAttachPic>()
                .eq(KnowledgeAttachPic::getKid, attachItem.getKid())
                .eq(KnowledgeAttachPic::getAid, attachItem.getId())
                .eq(KnowledgeAttachPic::getDocName, file.getOriginalFilename())
        );
        if (ObjectUtil.isNotEmpty(knowledgeAttachPics)) {
          Collection<Long> ossIds = knowledgeAttachPics.stream()
              .map(KnowledgeAttachPic::getOssId)
              .collect(Collectors.toList());
          ossService.deleteWithValidByIds(ossIds, false);
          List<Long> collect = knowledgeAttachPics.stream().map(KnowledgeAttachPic::getId)
              .collect(Collectors.toList());
          picMapper.deleteByIds(collect);
        }
        SysOssVo upload = ossService.upload(file);
        KnowledgeAttachPic entity = new KnowledgeAttachPic();
        entity.setKid(attachItem.getKid());
        entity.setAid(String.valueOf(attachItem.getId()));
        entity.setDocName(file.getOriginalFilename());
        entity.setDocType(
            file.getOriginalFilename().substring(file.getOriginalFilename().lastIndexOf(".") + 1));
        entity.setOssId(upload.getOssId());
        int[] ints = extractPageNumbers(file.getOriginalFilename());
        if (ObjectUtil.isNotEmpty(ints)) {
          assert ints != null;
          if (ints.length == 2) {
            entity.setPageNum(ints[0]);
            entity.setIndexNum(ints[1]);
          }
        }
        picMapper.insert(entity);
      }
      //设置处理完成
      attachMapper.update(new LambdaUpdateWrapper<KnowledgeAttach>()
          .set(KnowledgeAttach::getPicStatus, DealStatus.STATUS_30)
          .eq(KnowledgeAttach::getPicStatus, DealStatus.STATUS_20)
          .eq(KnowledgeAttach::getPicAnysStatus, DealStatus.STATUS_10)
          .eq(KnowledgeAttach::getVectorStatus, DealStatus.STATUS_10)
          .eq(KnowledgeAttach::getId, attachItem.getId()));
    } catch (Exception e) {
      //设置处理失败
      attachMapper.update(new LambdaUpdateWrapper<KnowledgeAttach>()
          .set(KnowledgeAttach::getVectorStatus, DealStatus.STATUS_40)
          .set(KnowledgeAttach::getRemark, attachItem.getRemark() + e.getMessage())
          .eq(KnowledgeAttach::getPicStatus, DealStatus.STATUS_20)
          .eq(KnowledgeAttach::getPicAnysStatus, DealStatus.STATUS_10)
          .eq(KnowledgeAttach::getVectorStatus, DealStatus.STATUS_10)
          .eq(KnowledgeAttach::getId, attachItem.getId()));
      throw new RuntimeException(e);
    }
  }
  @Async
  public void dealPicAnysStatus(KnowledgeAttachPic picItem) throws Exception {
    try {
      //锁定数据 æ›´æ”¹ getPicAnysStatus åˆ°è¿›è¡Œä¸­
      if (picMapper.update(new LambdaUpdateWrapper<KnowledgeAttachPic>()
          .set(KnowledgeAttachPic::getPicAnysStatus, DealStatus.STATUS_20)
          .eq(KnowledgeAttachPic::getPicAnysStatus, DealStatus.STATUS_10)
          .eq(KnowledgeAttachPic::getId, picItem.getId())
      ) == 0) {
        return;
      }
      SysOssVo ossVo = ossService.getById(picItem.getOssId());
      if (ObjectUtil.isNotEmpty(ossVo)) {
        String fileStr = ossService.downloadByByte(picItem.getOssId());
        //调用第三方 åˆ†æžå›¾ç‰‡å†…容
        PdfImageExtractServiceImpl pdfImageExtractService = new PdfImageExtractServiceImpl(
            serviceUrl,
            aiApiUrl, aiApiKey);
        List<PdfFileContentResult> pdfFileContentResults = pdfImageExtractService.dealFileContent(
            new String[]{fileStr});
        if (ObjectUtil.isNotEmpty(pdfFileContentResults)) {
          for (PdfFileContentResult resultItem : pdfFileContentResults) {
            //图片解析内容回写到pic表
            picMapper.update(new LambdaUpdateWrapper<KnowledgeAttachPic>()
                .set(KnowledgeAttachPic::getContent, parseContent(resultItem.getContent()))
                .set(KnowledgeAttachPic::getPicAnysStatus, DealStatus.STATUS_30)
                .eq(KnowledgeAttachPic::getId, picItem.getId()));
            //将图片解析内容 å†™å…¥æ®µè½è¡¨ fragment
            KnowledgeAttachVo knowledgeAttachVo = attachMapper.selectVoById(picItem.getAid());
            if (ObjectUtil.isNotEmpty(knowledgeAttachVo)) {
              String fid = RandomUtil.randomString(10);
              KnowledgeFragment knowledgeFragment = new KnowledgeFragment();
              knowledgeFragment.setKid(knowledgeAttachVo.getKid());
              knowledgeFragment.setDocId(knowledgeAttachVo.getDocId());
              knowledgeFragment.setFid(fid);
              knowledgeFragment.setIdx(0);
              knowledgeFragment.setContent(parseContent(resultItem.getContent()));
              knowledgeFragment.setCreateTime(new Date());
              fragmentMapper.insert(knowledgeFragment);
              //更新attach表,需要所有图片都处理完毕
              // æŸ¥è¯¢éž30状态(完成状态)的记录数量
              long nonStatus30Count = picMapper.selectCount(
                  new LambdaQueryWrapper<KnowledgeAttachPic>()
                      .ne(KnowledgeAttachPic::getPicAnysStatus, DealStatus.STATUS_30)
                      .eq(KnowledgeAttachPic::getAid, picItem.getAid())
              );
              if (nonStatus30Count == 0) {
                // æ‰§è¡Œè¡¨æ›´æ–°æ“ä½œ
                attachMapper.update(new LambdaUpdateWrapper<KnowledgeAttach>()
                    .set(KnowledgeAttach::getPicAnysStatus, DealStatus.STATUS_30)
                    .eq(KnowledgeAttach::getPicStatus, DealStatus.STATUS_30)
                    .eq(KnowledgeAttach::getPicAnysStatus, DealStatus.STATUS_10)
                    .eq(KnowledgeAttach::getVectorStatus, DealStatus.STATUS_10)
                    .eq(KnowledgeAttach::getId, picItem.getAid()));
              }
            }
          }
        }
      }
    } catch (Exception e) {
      //失败
      picMapper.update(new LambdaUpdateWrapper<KnowledgeAttachPic>()
          .set(KnowledgeAttachPic::getPicAnysStatus, DealStatus.STATUS_40)
          .set(KnowledgeAttachPic::getRemark, picItem.getRemark() + e.getMessage())
          .eq(KnowledgeAttachPic::getPicAnysStatus, DealStatus.STATUS_20)
          .eq(KnowledgeAttachPic::getId, picItem.getId()));
      throw new RuntimeException(e);
    }
  }
  /**
   * ä»Žæ–‡ä»¶åä¸­æå–page后面的两个数字
   *
   * @param fileName æ–‡ä»¶å
   * @return åŒ…含两个数字的数组,如果未找到则返回null
   */
  public static int[] extractPageNumbers(String fileName) {
    // æŸ¥æ‰¾"page_"的位置
    int pageIndex = fileName.indexOf("page_");
    if (pageIndex == -1) {
      return null;
    }
    // ä»Ž"page_"后开始截取
    String afterPage = fileName.substring(pageIndex + 5);
    // æŒ‰ä¸‹åˆ’线分割
    String[] parts = afterPage.split("_");
    if (parts.length >= 2) {
      try {
        // æå–两个数字
        int firstNumber = Integer.parseInt(parts[0]);
        // å¯¹äºŽç¬¬äºŒä¸ªæ•°å­—,需要去掉可能的文件扩展名
        String secondPart = parts[1];
        int dotIndex = secondPart.indexOf(".");
        if (dotIndex != -1) {
          secondPart = secondPart.substring(0, dotIndex);
        }
        int secondNumber = Integer.parseInt(secondPart);
        return new int[]{firstNumber, secondNumber};
      } catch (NumberFormatException e) {
        return null;
      }
    }
    return null;
  }
  public static String parseContent(String jsonString) {
    try {
      // åˆ›å»ºObjectMapper实例
      ObjectMapper objectMapper = new ObjectMapper();
      // è§£æžJSON字符串
      JsonNode rootNode = objectMapper.readTree(jsonString);
      // èŽ·å–choices数组的第一个元素
      JsonNode choicesNode = rootNode.get("choices");
      if (choicesNode != null && choicesNode.isArray() && choicesNode.size() > 0) {
        // èŽ·å–ç¬¬ä¸€ä¸ªchoice
        JsonNode firstChoice = choicesNode.get(0);
        // èŽ·å–message节点
        JsonNode messageNode = firstChoice.get("message");
        if (messageNode != null) {
          // èŽ·å–content字段的值
          JsonNode contentNode = messageNode.get("content");
          if (contentNode != null) {
            return contentNode.asText();
          }
        }
      }
      return "无法找到content内容";
    } catch (Exception e) {
      e.printStackTrace();
      return "解析JSON时发生错误: " + e.getMessage();
    }
  }
}
ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/knowledge/KnowledgeInfoServiceImpl.java
@@ -1,43 +1,62 @@
package org.ruoyi.chat.service.knowledge;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.RandomUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.ruoyi.chain.loader.ResourceLoader;
import org.ruoyi.chain.loader.ResourceLoaderFactory;
import org.ruoyi.common.core.domain.model.LoginUser;
import org.ruoyi.common.core.utils.MapstructUtils;
import org.ruoyi.common.core.utils.StringUtils;
import org.ruoyi.common.satoken.utils.LoginHelper;
import org.ruoyi.constant.DealStatus;
import org.ruoyi.constant.FileType;
import org.ruoyi.core.page.PageQuery;
import org.ruoyi.core.page.TableDataInfo;
import org.ruoyi.domain.ChatModel;
import org.ruoyi.domain.KnowledgeAttach;
import org.ruoyi.domain.KnowledgeAttachPic;
import org.ruoyi.domain.KnowledgeFragment;
import org.ruoyi.domain.KnowledgeInfo;
import org.ruoyi.domain.PdfFileContentResult;
import org.ruoyi.domain.bo.KnowledgeInfoBo;
import org.ruoyi.domain.bo.KnowledgeInfoUploadBo;
import org.ruoyi.domain.bo.StoreEmbeddingBo;
import org.ruoyi.domain.vo.ChatModelVo;
import org.ruoyi.domain.vo.KnowledgeAttachVo;
import org.ruoyi.domain.vo.KnowledgeInfoVo;
import org.ruoyi.mapper.KnowledgeAttachMapper;
import org.ruoyi.mapper.KnowledgeAttachPicMapper;
import org.ruoyi.mapper.KnowledgeFragmentMapper;
import org.ruoyi.mapper.KnowledgeInfoMapper;
import org.ruoyi.service.IChatModelService;
import org.ruoyi.service.PdfImageExtractService;
import org.ruoyi.service.VectorStoreService;
import org.ruoyi.service.IKnowledgeInfoService;
import org.ruoyi.service.impl.PdfImageExtractServiceImpl;
import org.ruoyi.system.domain.vo.SysOssVo;
import org.ruoyi.utils.ZipUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import org.ruoyi.system.service.ISysOssService;
import java.io.IOException;
import java.util.*;
/**
 * çŸ¥è¯†åº“Service业务层处理
@@ -49,216 +68,339 @@
@Service
public class KnowledgeInfoServiceImpl implements IKnowledgeInfoService {
    private static final Logger log = LoggerFactory.getLogger(KnowledgeInfoServiceImpl.class);
    private final KnowledgeInfoMapper baseMapper;
  private static final Logger log = LoggerFactory.getLogger(KnowledgeInfoServiceImpl.class);
  private final KnowledgeInfoMapper baseMapper;
    private final VectorStoreService vectorStoreService;
  private final VectorStoreService vectorStoreService;
    private final ResourceLoaderFactory resourceLoaderFactory;
  private final ResourceLoaderFactory resourceLoaderFactory;
    private final KnowledgeFragmentMapper fragmentMapper;
  private final KnowledgeFragmentMapper fragmentMapper;
    private final KnowledgeAttachMapper attachMapper;
  private final KnowledgeAttachMapper attachMapper;
    private final IChatModelService chatModelService;
  private final IChatModelService chatModelService;
    /**
     * æŸ¥è¯¢çŸ¥è¯†åº“
     */
    @Override
    public KnowledgeInfoVo queryById(Long id){
        return baseMapper.selectVoById(id);
  private final ISysOssService ossService;
//  private final PdfImageExtractService pdfImageExtractService;
  private final KnowledgeAttachPicMapper picMapper;
  private final DealFileService dealFileService;
  @Value("${pdf.extract.service.url}")
  private String serviceUrl;
  @Value("${pdf.extract.ai-api.url}")
  private String aiApiUrl;
  @Value("${pdf.extract.ai-api.key}")
  private String aiApiKey;
  /**
   * æŸ¥è¯¢çŸ¥è¯†åº“
   */
  @Override
  public KnowledgeInfoVo queryById(Long id) {
    return baseMapper.selectVoById(id);
  }
  /**
   * æŸ¥è¯¢çŸ¥è¯†åº“列表
   */
  @Override
  public TableDataInfo<KnowledgeInfoVo> queryPageList(KnowledgeInfoBo bo, PageQuery pageQuery) {
    LambdaQueryWrapper<KnowledgeInfo> lqw = buildQueryWrapper(bo);
    Page<KnowledgeInfoVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
    return TableDataInfo.build(result);
  }
  /**
   * æŸ¥è¯¢çŸ¥è¯†åº“列表
   */
  @Override
  public List<KnowledgeInfoVo> queryList(KnowledgeInfoBo bo) {
    LambdaQueryWrapper<KnowledgeInfo> lqw = buildQueryWrapper(bo);
    return baseMapper.selectVoList(lqw);
  }
  private LambdaQueryWrapper<KnowledgeInfo> buildQueryWrapper(KnowledgeInfoBo bo) {
    Map<String, Object> params = bo.getParams();
    LambdaQueryWrapper<KnowledgeInfo> lqw = Wrappers.lambdaQuery();
    lqw.eq(StringUtils.isNotBlank(bo.getKid()), KnowledgeInfo::getKid, bo.getKid());
    lqw.eq(bo.getUid() != null, KnowledgeInfo::getUid, bo.getUid());
    lqw.like(StringUtils.isNotBlank(bo.getKname()), KnowledgeInfo::getKname, bo.getKname());
    lqw.eq(bo.getShare() != null, KnowledgeInfo::getShare, bo.getShare());
    lqw.eq(StringUtils.isNotBlank(bo.getDescription()), KnowledgeInfo::getDescription,
        bo.getDescription());
    lqw.eq(StringUtils.isNotBlank(bo.getKnowledgeSeparator()), KnowledgeInfo::getKnowledgeSeparator,
        bo.getKnowledgeSeparator());
    lqw.eq(StringUtils.isNotBlank(bo.getQuestionSeparator()), KnowledgeInfo::getQuestionSeparator,
        bo.getQuestionSeparator());
    lqw.eq(bo.getOverlapChar() != null, KnowledgeInfo::getOverlapChar, bo.getOverlapChar());
    lqw.eq(bo.getRetrieveLimit() != null, KnowledgeInfo::getRetrieveLimit, bo.getRetrieveLimit());
    lqw.eq(bo.getTextBlockSize() != null, KnowledgeInfo::getTextBlockSize, bo.getTextBlockSize());
    return lqw;
  }
  /**
   * æ–°å¢žçŸ¥è¯†åº“
   */
  @Override
  public Boolean insertByBo(KnowledgeInfoBo bo) {
    KnowledgeInfo add = MapstructUtils.convert(bo, KnowledgeInfo.class);
    validEntityBeforeSave(add);
    boolean flag = baseMapper.insert(add) > 0;
    if (flag) {
      bo.setId(add.getId());
    }
    return flag;
  }
  /**
   * ä¿®æ”¹çŸ¥è¯†åº“
   */
  @Override
  public Boolean updateByBo(KnowledgeInfoBo bo) {
    KnowledgeInfo update = MapstructUtils.convert(bo, KnowledgeInfo.class);
    validEntityBeforeSave(update);
    return baseMapper.updateById(update) > 0;
  }
  /**
   * ä¿å­˜å‰çš„æ•°æ®æ ¡éªŒ
   */
  private void validEntityBeforeSave(KnowledgeInfo entity) {
    //TODO åšä¸€äº›æ•°æ®æ ¡éªŒ,如唯一约束
  }
  /**
   * æ‰¹é‡åˆ é™¤çŸ¥è¯†åº“
   */
  @Override
  public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
    if (isValid) {
      //TODO åšä¸€äº›ä¸šåŠ¡ä¸Šçš„æ ¡éªŒ,判断是否需要校验
    }
    return baseMapper.deleteBatchIds(ids) > 0;
  }
  @Override
  @Transactional(rollbackFor = Exception.class)
  public void saveOne(KnowledgeInfoBo bo) {
    KnowledgeInfo knowledgeInfo = MapstructUtils.convert(bo, KnowledgeInfo.class);
    if (StringUtils.isBlank(bo.getKid())) {
      String kid = RandomUtil.randomString(10);
      if (knowledgeInfo != null) {
        knowledgeInfo.setKid(kid);
        knowledgeInfo.setUid(LoginHelper.getLoginUser().getUserId());
      }
      baseMapper.insert(knowledgeInfo);
      if (knowledgeInfo != null) {
        vectorStoreService.createSchema(String.valueOf(knowledgeInfo.getId()),
            bo.getVectorModelName());
      }
    } else {
      baseMapper.updateById(knowledgeInfo);
    }
  }
  @Override
  @Transactional(rollbackFor = Exception.class)
  public void removeKnowledge(String id) {
    Map<String, Object> map = new HashMap<>();
    map.put("kid", id);
    List<KnowledgeInfoVo> knowledgeInfoList = baseMapper.selectVoByMap(map);
    check(knowledgeInfoList);
    // åˆ é™¤å‘量库信息
    knowledgeInfoList.forEach(knowledgeInfoVo -> {
      vectorStoreService.removeByKid(String.valueOf(knowledgeInfoVo.getId()),
          knowledgeInfoVo.getVectorModelName());
    });
    // åˆ é™¤é™„件和知识片段
    fragmentMapper.deleteByMap(map);
    List<KnowledgeAttachVo> knowledgeAttachVos = attachMapper.selectVoByMap(map);
    if (ObjectUtil.isNotEmpty(knowledgeAttachVos)) {
      Collection<Long> ossIds = knowledgeAttachVos.stream()
          .map(KnowledgeAttachVo::getOssId)
          .collect(Collectors.toList());
      //删除oss
      ossService.deleteWithValidByIds(ossIds, false);
      //删除图片oss
      List<KnowledgeAttachPic> knowledgeAttachPics = picMapper.selectList(
          new LambdaQueryWrapper<KnowledgeAttachPic>()
              .in(KnowledgeAttachPic::getKid,
                  knowledgeAttachVos.stream().map(KnowledgeAttachVo::getKid)
                      .collect(Collectors.toList()))
              .in(KnowledgeAttachPic::getAid,
                  knowledgeAttachVos.stream().map(KnowledgeAttachVo::getId)
                      .collect(Collectors.toList()))
      );
      if (ObjectUtil.isNotEmpty(knowledgeAttachPics)) {
        Collection<Long> tossIds = knowledgeAttachPics.stream()
            .map(KnowledgeAttachPic::getOssId)
            .collect(Collectors.toList());
        ossService.deleteWithValidByIds(tossIds, false);
        List<Long> collect = knowledgeAttachPics.stream().map(KnowledgeAttachPic::getId)
            .collect(Collectors.toList());
        picMapper.deleteByIds(collect);
      }
    }
    attachMapper.deleteByMap(map);
    // åˆ é™¤çŸ¥è¯†åº“
    baseMapper.deleteByMap(map);
  }
  @Override
  public void upload(KnowledgeInfoUploadBo bo) {
    storeContent(bo.getFile(), bo.getKid());
  }
  public void storeContent(MultipartFile file, String kid) {
    if (file == null || file.isEmpty()) {
      throw new IllegalArgumentException("File cannot be null or empty");
    }
    /**
     * æŸ¥è¯¢çŸ¥è¯†åº“列表
     */
    @Override
    public TableDataInfo<KnowledgeInfoVo> queryPageList(KnowledgeInfoBo bo, PageQuery pageQuery) {
        LambdaQueryWrapper<KnowledgeInfo> lqw = buildQueryWrapper(bo);
        Page<KnowledgeInfoVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
        return TableDataInfo.build(result);
    }
    /**
     * æŸ¥è¯¢çŸ¥è¯†åº“列表
     */
    @Override
    public List<KnowledgeInfoVo> queryList(KnowledgeInfoBo bo) {
        LambdaQueryWrapper<KnowledgeInfo> lqw = buildQueryWrapper(bo);
        return baseMapper.selectVoList(lqw);
    }
    private LambdaQueryWrapper<KnowledgeInfo> buildQueryWrapper(KnowledgeInfoBo bo) {
        Map<String, Object> params = bo.getParams();
        LambdaQueryWrapper<KnowledgeInfo> lqw = Wrappers.lambdaQuery();
        lqw.eq(StringUtils.isNotBlank(bo.getKid()), KnowledgeInfo::getKid, bo.getKid());
        lqw.eq(bo.getUid() != null, KnowledgeInfo::getUid, bo.getUid());
        lqw.like(StringUtils.isNotBlank(bo.getKname()), KnowledgeInfo::getKname, bo.getKname());
        lqw.eq(bo.getShare() != null, KnowledgeInfo::getShare, bo.getShare());
        lqw.eq(StringUtils.isNotBlank(bo.getDescription()), KnowledgeInfo::getDescription, bo.getDescription());
        lqw.eq(StringUtils.isNotBlank(bo.getKnowledgeSeparator()), KnowledgeInfo::getKnowledgeSeparator, bo.getKnowledgeSeparator());
        lqw.eq(StringUtils.isNotBlank(bo.getQuestionSeparator()), KnowledgeInfo::getQuestionSeparator, bo.getQuestionSeparator());
        lqw.eq(bo.getOverlapChar() != null, KnowledgeInfo::getOverlapChar, bo.getOverlapChar());
        lqw.eq(bo.getRetrieveLimit() != null, KnowledgeInfo::getRetrieveLimit, bo.getRetrieveLimit());
        lqw.eq(bo.getTextBlockSize() != null, KnowledgeInfo::getTextBlockSize, bo.getTextBlockSize());
        return lqw;
    }
    /**
     * æ–°å¢žçŸ¥è¯†åº“
     */
    @Override
    public Boolean insertByBo(KnowledgeInfoBo bo) {
        KnowledgeInfo add = MapstructUtils.convert(bo, KnowledgeInfo.class);
        validEntityBeforeSave(add);
        boolean flag = baseMapper.insert(add) > 0;
        if (flag) {
            bo.setId(add.getId());
    SysOssVo uploadDto = null;
    String fileName = file.getOriginalFilename();
    List<String> chunkList = new ArrayList<>();
    KnowledgeAttach knowledgeAttach = new KnowledgeAttach();
    knowledgeAttach.setKid(kid);
    String docId = RandomUtil.randomString(10);
    knowledgeAttach.setDocId(docId);
    knowledgeAttach.setDocName(fileName);
    knowledgeAttach.setDocType(fileName.substring(fileName.lastIndexOf(".") + 1));
    String content = "";
    ResourceLoader resourceLoader = resourceLoaderFactory.getLoaderByFileType(
        knowledgeAttach.getDocType());
    List<String> fids = new ArrayList<>();
    try {
      content = resourceLoader.getContent(file.getInputStream());
      chunkList = resourceLoader.getChunkList(content, kid);
      List<KnowledgeFragment> knowledgeFragmentList = new ArrayList<>();
      if (CollUtil.isNotEmpty(chunkList)) {
        // Upload file to OSS
        uploadDto = ossService.upload(file);
        for (int i = 0; i < chunkList.size(); i++) {
          String fid = RandomUtil.randomString(10);
          fids.add(fid);
          KnowledgeFragment knowledgeFragment = new KnowledgeFragment();
          knowledgeFragment.setKid(kid);
          knowledgeFragment.setDocId(docId);
          knowledgeFragment.setFid(fid);
          knowledgeFragment.setIdx(i);
          knowledgeFragment.setContent(chunkList.get(i));
          knowledgeFragment.setCreateTime(new Date());
          knowledgeFragmentList.add(knowledgeFragment);
        }
        return flag;
      }
      fragmentMapper.insertBatch(knowledgeFragmentList);
    } catch (IOException e) {
      log.error("保存知识库信息失败!{}", e.getMessage());
    }
    /**
     * ä¿®æ”¹çŸ¥è¯†åº“
     */
    @Override
    public Boolean updateByBo(KnowledgeInfoBo bo) {
        KnowledgeInfo update = MapstructUtils.convert(bo, KnowledgeInfo.class);
        validEntityBeforeSave(update);
        return baseMapper.updateById(update) > 0;
    knowledgeAttach.setContent(content);
    knowledgeAttach.setCreateTime(new Date());
    if (ObjectUtil.isNotEmpty(uploadDto) && ObjectUtil.isNotEmpty(uploadDto.getOssId())) {
      knowledgeAttach.setOssId(uploadDto.getOssId());
      //只有pdf文件 æ‰éœ€è¦æ‹†è§£å›¾ç‰‡å’Œåˆ†æžå›¾ç‰‡å†…容
      if (FileType.PDF.equals(knowledgeAttach.getDocType())) {
        knowledgeAttach.setPicStatus(DealStatus.STATUS_10);
        knowledgeAttach.setPicAnysStatus(DealStatus.STATUS_10);
      } else {
        knowledgeAttach.setPicStatus(DealStatus.STATUS_30);
        knowledgeAttach.setPicAnysStatus(DealStatus.STATUS_30);
      }
      //所有文件上传后,都需要同步到向量数据库
      knowledgeAttach.setVectorStatus(DealStatus.STATUS_10);
    }
    attachMapper.insert(knowledgeAttach);
    /**
     * ä¿å­˜å‰çš„æ•°æ®æ ¡éªŒ
     */
    private void validEntityBeforeSave(KnowledgeInfo entity){
        //TODO åšä¸€äº›æ•°æ®æ ¡éªŒ,如唯一约束
  }
  /**
   * æ£€æŸ¥ç”¨æˆ·æ˜¯å¦æœ‰åˆ é™¤çŸ¥è¯†åº“权限
   *
   * @param knowledgeInfoList çŸ¥è¯†åº“列表
   */
  public void check(List<KnowledgeInfoVo> knowledgeInfoList) {
    LoginUser loginUser = LoginHelper.getLoginUser();
    for (KnowledgeInfoVo knowledgeInfoVo : knowledgeInfoList) {
      if (!knowledgeInfoVo.getUid().equals(loginUser.getUserId())) {
        throw new SecurityException("权限不足");
      }
    }
    /**
     * æ‰¹é‡åˆ é™¤çŸ¥è¯†åº“
     */
    @Override
    public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
        if(isValid){
            //TODO åšä¸€äº›ä¸šåŠ¡ä¸Šçš„æ ¡éªŒ,判断是否需要校验
        }
        return baseMapper.deleteBatchIds(ids) > 0;
  }
  /**
   * ç¬¬ä¸€æ­¥ å®šæ—¶ æ‹†è§£PDF文件中的图片
   */
  @Scheduled(fixedDelay = 15000) // æ¯3秒执行一次
  public void dealKnowledgeAttachPic() throws Exception {
    //处理 æ‹†è§£PDF文件中的图片的记录
    List<KnowledgeAttach> knowledgeAttaches = attachMapper.selectList(
        new LambdaQueryWrapper<KnowledgeAttach>()
            .eq(KnowledgeAttach::getPicStatus, DealStatus.STATUS_10)
            .eq(KnowledgeAttach::getPicAnysStatus, DealStatus.STATUS_10)
            .eq(KnowledgeAttach::getVectorStatus, DealStatus.STATUS_10)
    );
    log.info("===============拆解PDF文件中的图片 size = {}", knowledgeAttaches.size());
    if (ObjectUtil.isNotEmpty(knowledgeAttaches)) {
      for (KnowledgeAttach attachItem : knowledgeAttaches) {
        dealFileService.dealPicStatus(attachItem);
      }
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void saveOne(KnowledgeInfoBo bo) {
        KnowledgeInfo knowledgeInfo = MapstructUtils.convert(bo, KnowledgeInfo.class);
        if (StringUtils.isBlank(bo.getKid())){
            String kid = RandomUtil.randomString(10);
            if (knowledgeInfo != null) {
                knowledgeInfo.setKid(kid);
                knowledgeInfo.setUid(LoginHelper.getLoginUser().getUserId());
            }
            baseMapper.insert(knowledgeInfo);
            if (knowledgeInfo != null) {
                vectorStoreService.createSchema(String.valueOf(knowledgeInfo.getId()),bo.getVectorModelName());
            }
        }else {
            baseMapper.updateById(knowledgeInfo);
        }
  }
  /**
   * ç¬¬äºŒæ­¥ å®šæ—¶ è§£æžå›¾ç‰‡å†…容
   */
  @Scheduled(fixedDelay = 15000)
  public void dealKnowledgeAttachPicAnys() throws Exception {
    //获取未处理的图片记录
    List<KnowledgeAttachPic> knowledgeAttachPics = picMapper.selectList(
        new LambdaQueryWrapper<KnowledgeAttachPic>()
            .eq(KnowledgeAttachPic::getPicAnysStatus, DealStatus.STATUS_10)
            .last("LIMIT 20")
    );
    if (ObjectUtil.isNotEmpty(knowledgeAttachPics)) {
      for (KnowledgeAttachPic picItem : knowledgeAttachPics) {
        dealFileService.dealPicAnysStatus(picItem);
      }
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void removeKnowledge(String id) {
        Map<String,Object> map = new HashMap<>();
        map.put("kid",id);
        List<KnowledgeInfoVo> knowledgeInfoList = baseMapper.selectVoByMap(map);
        check(knowledgeInfoList);
        // åˆ é™¤å‘量库信息
        knowledgeInfoList.forEach(knowledgeInfoVo -> {
            vectorStoreService.removeByKid(String.valueOf(knowledgeInfoVo.getId()),knowledgeInfoVo.getVectorModelName());
        });
        // åˆ é™¤é™„件和知识片段
        fragmentMapper.deleteByMap(map);
        attachMapper.deleteByMap(map);
        // åˆ é™¤çŸ¥è¯†åº“
        baseMapper.deleteByMap(map);
  }
  /**
   * ç¬¬ä¸‰æ­¥ å®šæ—¶ å¤„理 é™„件上传后上传向量数据库
   */
  @Scheduled(fixedDelay = 30000) // æ¯3秒执行一次
  public void dealKnowledgeAttachVector() throws Exception {
    //处理 éœ€è¦ä¸Šä¼ å‘量数据库的记录
    List<KnowledgeAttach> knowledgeAttaches = attachMapper.selectList(
        new LambdaQueryWrapper<KnowledgeAttach>()
            .eq(KnowledgeAttach::getPicStatus, DealStatus.STATUS_30)
            .eq(KnowledgeAttach::getPicAnysStatus, DealStatus.STATUS_30)
            .eq(KnowledgeAttach::getVectorStatus, DealStatus.STATUS_10)
    );
    log.info("===============上传向量数据库 size = {}", knowledgeAttaches.size());
    if (ObjectUtil.isNotEmpty(knowledgeAttaches)) {
      for (KnowledgeAttach attachItem : knowledgeAttaches) {
        dealFileService.dealVectorStatus(attachItem);
      }
    }
    @Override
    public void upload(KnowledgeInfoUploadBo bo) {
        storeContent(bo.getFile(), bo.getKid());
    }
    public void storeContent(MultipartFile file, String kid) {
        String fileName = file.getOriginalFilename();
        List<String> chunkList = new ArrayList<>();
        KnowledgeAttach knowledgeAttach = new KnowledgeAttach();
        knowledgeAttach.setKid(kid);
        String docId = RandomUtil.randomString(10);
        knowledgeAttach.setDocId(docId);
        knowledgeAttach.setDocName(fileName);
        knowledgeAttach.setDocType(fileName.substring(fileName.lastIndexOf(".")+1));
        String content = "";
        ResourceLoader resourceLoader = resourceLoaderFactory.getLoaderByFileType(knowledgeAttach.getDocType());
        List<String> fids = new ArrayList<>();
        try {
            content = resourceLoader.getContent(file.getInputStream());
            chunkList = resourceLoader.getChunkList(content, kid);
            List<KnowledgeFragment> knowledgeFragmentList = new ArrayList<>();
            if (CollUtil.isNotEmpty(chunkList)) {
                for (int i = 0; i < chunkList.size(); i++) {
                    String fid = RandomUtil.randomString(10);
                    fids.add(fid);
                    KnowledgeFragment knowledgeFragment = new KnowledgeFragment();
                    knowledgeFragment.setKid(kid);
                    knowledgeFragment.setDocId(docId);
                    knowledgeFragment.setFid(fid);
                    knowledgeFragment.setIdx(i);
                    knowledgeFragment.setContent(chunkList.get(i));
                    knowledgeFragment.setCreateTime(new Date());
                    knowledgeFragmentList.add(knowledgeFragment);
                }
            }
            fragmentMapper.insertBatch(knowledgeFragmentList);
        } catch (IOException e) {
            log.error("保存知识库信息失败!{}", e.getMessage());
        }
        knowledgeAttach.setContent(content);
        knowledgeAttach.setCreateTime(new Date());
        attachMapper.insert(knowledgeAttach);
        // é€šè¿‡kid查询知识库信息
        KnowledgeInfoVo knowledgeInfoVo = baseMapper.selectVoOne(Wrappers.<KnowledgeInfo>lambdaQuery()
                .eq(KnowledgeInfo::getId, kid));
        // é€šè¿‡å‘量模型查询模型信息
        ChatModelVo chatModelVo = chatModelService.selectModelByName(knowledgeInfoVo.getEmbeddingModelName());
        StoreEmbeddingBo storeEmbeddingBo = new StoreEmbeddingBo();
        storeEmbeddingBo.setKid(kid);
        storeEmbeddingBo.setDocId(docId);
        storeEmbeddingBo.setFids(fids);
        storeEmbeddingBo.setChunkList(chunkList);
        storeEmbeddingBo.setVectorModelName(knowledgeInfoVo.getVectorModelName());
        storeEmbeddingBo.setEmbeddingModelName(knowledgeInfoVo.getEmbeddingModelName());
        storeEmbeddingBo.setApiKey(chatModelVo.getApiKey());
        storeEmbeddingBo.setBaseUrl(chatModelVo.getApiHost());
        vectorStoreService.storeEmbeddings(storeEmbeddingBo);
    }
    /**
     * æ£€æŸ¥ç”¨æˆ·æ˜¯å¦æœ‰åˆ é™¤çŸ¥è¯†åº“权限
     *
     * @param knowledgeInfoList çŸ¥è¯†åº“列表
     */
    public void check(List<KnowledgeInfoVo> knowledgeInfoList){
        LoginUser loginUser = LoginHelper.getLoginUser();
        for (KnowledgeInfoVo knowledgeInfoVo : knowledgeInfoList) {
            if(!knowledgeInfoVo.getUid().equals(loginUser.getUserId())){
                throw new SecurityException("权限不足");
            }
        }
    }
  }
  /**
   * ç¬¬å››æ­¥ å®šæ—¶ å¤„理 å¤±è´¥æ•°æ®
   */
  @Scheduled(fixedDelay = 30 * 60 * 1000)
  public void dealKnowledge40Status() throws Exception {
      //拆解PDF失败 é‡æ–°è®¾ç½®çŠ¶æ€
      attachMapper.update(new LambdaUpdateWrapper<KnowledgeAttach>()
          .set(KnowledgeAttach::getPicStatus, DealStatus.STATUS_10)
          .eq(KnowledgeAttach::getPicStatus, DealStatus.STATUS_40));
      //将图片分析失败的数据 é‡æ–°è®¾ç½®çŠ¶æ€
      picMapper.update(new LambdaUpdateWrapper<KnowledgeAttachPic>()
          .set(KnowledgeAttachPic::getPicAnysStatus, DealStatus.STATUS_10)
          .eq(KnowledgeAttachPic::getPicAnysStatus, DealStatus.STATUS_40));
      //上传向量库失败 é‡æ–°è®¾ç½®çŠ¶æ€
      attachMapper.update(new LambdaUpdateWrapper<KnowledgeAttach>()
          .set(KnowledgeAttach::getVectorStatus, DealStatus.STATUS_10)
          .eq(KnowledgeAttach::getVectorStatus, DealStatus.STATUS_40));
  }
}
ruoyi-modules/ruoyi-generator/src/main/java/org/ruoyi/generator/util/VelocityUtils.java
@@ -25,386 +25,386 @@
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class VelocityUtils {
    /**
     * é¡¹ç›®ç©ºé—´è·¯å¾„
     */
    private static final String PROJECT_PATH = "main/java";
  /**
   * é¡¹ç›®ç©ºé—´è·¯å¾„
   */
  private static final String PROJECT_PATH = "main/java";
    /**
     * mybatis空间路径
     */
    private static final String MYBATIS_PATH = "main/resources/mapper";
  /**
   * mybatis空间路径
   */
  private static final String MYBATIS_PATH = "main/resources/mapper";
    /**
     * é»˜è®¤ä¸Šçº§èœå•,系统工具
     */
    private static final String DEFAULT_PARENT_MENU_ID = "3";
  /**
   * é»˜è®¤ä¸Šçº§èœå•,系统工具
   */
  private static final String DEFAULT_PARENT_MENU_ID = "3";
    /**
     * è®¾ç½®æ¨¡æ¿å˜é‡ä¿¡æ¯
     *
     * @return æ¨¡æ¿åˆ—表
     */
    public static VelocityContext prepareContext(GenTable genTable) {
        String moduleName = genTable.getModuleName();
        String businessName = genTable.getBusinessName();
        String packageName = genTable.getPackageName();
        String tplCategory = genTable.getTplCategory();
        String functionName = genTable.getFunctionName();
  /**
   * è®¾ç½®æ¨¡æ¿å˜é‡ä¿¡æ¯
   *
   * @return æ¨¡æ¿åˆ—表
   */
  public static VelocityContext prepareContext(GenTable genTable) {
    String moduleName = genTable.getModuleName();
    String businessName = genTable.getBusinessName();
    String packageName = genTable.getPackageName();
    String tplCategory = genTable.getTplCategory();
    String functionName = genTable.getFunctionName();
        VelocityContext velocityContext = new VelocityContext();
        velocityContext.put("tplCategory", genTable.getTplCategory());
        velocityContext.put("tableName", genTable.getTableName());
        velocityContext.put("functionName", StringUtils.isNotEmpty(functionName) ? functionName : "【请填写功能名称】");
        velocityContext.put("ClassName", genTable.getClassName());
        velocityContext.put("className", StringUtils.uncapitalize(genTable.getClassName()));
        velocityContext.put("moduleName", genTable.getModuleName());
        velocityContext.put("BusinessName", StringUtils.capitalize(genTable.getBusinessName()));
        velocityContext.put("businessName", genTable.getBusinessName());
        velocityContext.put("basePackage", getPackagePrefix(packageName));
        velocityContext.put("packageName", packageName);
        velocityContext.put("author", genTable.getFunctionAuthor());
        velocityContext.put("datetime", DateUtils.getDate());
        velocityContext.put("pkColumn", genTable.getPkColumn());
        velocityContext.put("importList", getImportList(genTable));
        velocityContext.put("permissionPrefix", getPermissionPrefix(moduleName, businessName));
        velocityContext.put("columns", genTable.getColumns());
        velocityContext.put("table", genTable);
        velocityContext.put("dicts", getDicts(genTable));
        setMenuVelocityContext(velocityContext, genTable);
        if (GenConstants.TPL_TREE.equals(tplCategory)) {
            setTreeVelocityContext(velocityContext, genTable);
        }
        // åˆ¤æ–­æ˜¯modal还是drawer
        Dict paramsObj = JsonUtils.parseMap(genTable.getOptions());
        if (ObjectUtil.isNotNull(paramsObj)) {
            String popupComponent = Optional
                .ofNullable(paramsObj.getStr("popupComponent"))
                .orElse("modal");
            velocityContext.put("popupComponent", popupComponent);
            velocityContext.put("PopupComponent", StringUtils.capitalize(popupComponent));
        } else {
            velocityContext.put("popupComponent", "modal");
            velocityContext.put("PopupComponent", "Modal");
        }
        // åˆ¤æ–­æ˜¯åŽŸç”Ÿantd表单还是useForm表单
        // native åŽŸç”Ÿantd表单
        // useForm useVbenForm
        if (ObjectUtil.isNotNull(paramsObj)) {
            String formComponent = Optional
                .ofNullable(paramsObj.getStr("formComponent"))
                .orElse("useForm");
            velocityContext.put("formComponent", formComponent);
        }
        return velocityContext;
    VelocityContext velocityContext = new VelocityContext();
    velocityContext.put("tplCategory", genTable.getTplCategory());
    velocityContext.put("tableName", genTable.getTableName());
    velocityContext.put("functionName", StringUtils.isNotEmpty(functionName) ? functionName : "【请填写功能名称】");
    velocityContext.put("ClassName", genTable.getClassName());
    velocityContext.put("className", StringUtils.uncapitalize(genTable.getClassName()));
    velocityContext.put("moduleName", genTable.getModuleName());
    velocityContext.put("BusinessName", StringUtils.capitalize(genTable.getBusinessName()));
    velocityContext.put("businessName", genTable.getBusinessName());
    velocityContext.put("basePackage", getPackagePrefix(packageName));
    velocityContext.put("packageName", packageName);
    velocityContext.put("author", genTable.getFunctionAuthor());
    velocityContext.put("datetime", DateUtils.getDate());
    velocityContext.put("pkColumn", genTable.getPkColumn());
    velocityContext.put("importList", getImportList(genTable));
    velocityContext.put("permissionPrefix", getPermissionPrefix(moduleName, businessName));
    velocityContext.put("columns", genTable.getColumns());
    velocityContext.put("table", genTable);
    velocityContext.put("dicts", getDicts(genTable));
    setMenuVelocityContext(velocityContext, genTable);
    if (GenConstants.TPL_TREE.equals(tplCategory)) {
      setTreeVelocityContext(velocityContext, genTable);
    }
    public static void setMenuVelocityContext(VelocityContext context, GenTable genTable) {
        String options = genTable.getOptions();
        Dict paramsObj = JsonUtils.parseMap(options);
        String parentMenuId = getParentMenuId(paramsObj);
        context.put("parentMenuId", parentMenuId);
    // åˆ¤æ–­æ˜¯modal还是drawer
    Dict paramsObj = JsonUtils.parseMap(genTable.getOptions());
    if (ObjectUtil.isNotNull(paramsObj)) {
      String popupComponent = Optional
          .ofNullable(paramsObj.getStr("popupComponent"))
          .orElse("modal");
      velocityContext.put("popupComponent", popupComponent);
      velocityContext.put("PopupComponent", StringUtils.capitalize(popupComponent));
    } else {
      velocityContext.put("popupComponent", "modal");
      velocityContext.put("PopupComponent", "Modal");
    }
    // åˆ¤æ–­æ˜¯åŽŸç”Ÿantd表单还是useForm表单
    // native åŽŸç”Ÿantd表单
    // useForm useVbenForm
    if (ObjectUtil.isNotNull(paramsObj)) {
      String formComponent = Optional
          .ofNullable(paramsObj.getStr("formComponent"))
          .orElse("useForm");
      velocityContext.put("formComponent", formComponent);
    }
    return velocityContext;
  }
    public static void setTreeVelocityContext(VelocityContext context, GenTable genTable) {
        String options = genTable.getOptions();
        Dict paramsObj = JsonUtils.parseMap(options);
        String treeCode = getTreecode(paramsObj);
        String treeParentCode = getTreeParentCode(paramsObj);
        String treeName = getTreeName(paramsObj);
  public static void setMenuVelocityContext(VelocityContext context, GenTable genTable) {
    String options = genTable.getOptions();
    Dict paramsObj = JsonUtils.parseMap(options);
    String parentMenuId = getParentMenuId(paramsObj);
    context.put("parentMenuId", parentMenuId);
  }
        context.put("treeCode", treeCode);
        context.put("treeParentCode", treeParentCode);
        context.put("treeName", treeName);
        context.put("expandColumn", getExpandColumn(genTable));
        if (paramsObj.containsKey(GenConstants.TREE_PARENT_CODE)) {
            context.put("tree_parent_code", paramsObj.get(GenConstants.TREE_PARENT_CODE));
        }
        if (paramsObj.containsKey(GenConstants.TREE_NAME)) {
            context.put("tree_name", paramsObj.get(GenConstants.TREE_NAME));
        }
  public static void setTreeVelocityContext(VelocityContext context, GenTable genTable) {
    String options = genTable.getOptions();
    Dict paramsObj = JsonUtils.parseMap(options);
    String treeCode = getTreecode(paramsObj);
    String treeParentCode = getTreeParentCode(paramsObj);
    String treeName = getTreeName(paramsObj);
    context.put("treeCode", treeCode);
    context.put("treeParentCode", treeParentCode);
    context.put("treeName", treeName);
    context.put("expandColumn", getExpandColumn(genTable));
    if (paramsObj.containsKey(GenConstants.TREE_PARENT_CODE)) {
      context.put("tree_parent_code", paramsObj.get(GenConstants.TREE_PARENT_CODE));
    }
    if (paramsObj.containsKey(GenConstants.TREE_NAME)) {
      context.put("tree_name", paramsObj.get(GenConstants.TREE_NAME));
    }
  }
  /**
   * èŽ·å–æ¨¡æ¿ä¿¡æ¯
   *
   * @return æ¨¡æ¿åˆ—表
   */
  public static List<String> getTemplateList(String tplCategory) {
    List<String> templates = new ArrayList<>();
    templates.add("vm/java/domain.java.vm");
    templates.add("vm/java/vo.java.vm");
    templates.add("vm/java/bo.java.vm");
    templates.add("vm/java/mapper.java.vm");
    templates.add("vm/java/service.java.vm");
    templates.add("vm/java/serviceImpl.java.vm");
    templates.add("vm/java/controller.java.vm");
    templates.add("vm/xml/mapper.xml.vm");
    if (DataBaseHelper.isOracle()) {
      templates.add("vm/sql/oracle/sql.vm");
    } else if (DataBaseHelper.isPostgerSql()) {
      templates.add("vm/sql/postgres/sql.vm");
    } else if (DataBaseHelper.isSqlServer()) {
      templates.add("vm/sql/sqlserver/sql.vm");
    } else {
      templates.add("vm/sql/sql.vm");
    }
    templates.add("vm/ts/api.ts.vm");
    templates.add("vm/ts/types.ts.vm");
    if (GenConstants.TPL_CRUD.equals(tplCategory)) {
      templates.add("vm/vue/index.vue.vm");
    } else if (GenConstants.TPL_TREE.equals(tplCategory)) {
      templates.add("vm/vue/index-tree.vue.vm");
    }
    /**
     * èŽ·å–æ¨¡æ¿ä¿¡æ¯
     *
     * @return æ¨¡æ¿åˆ—表
     * æ·»åŠ vben5
     */
    public static List<String> getTemplateList(String tplCategory) {
        List<String> templates = new ArrayList<>();
        templates.add("vm/java/domain.java.vm");
        templates.add("vm/java/vo.java.vm");
        templates.add("vm/java/bo.java.vm");
        templates.add("vm/java/mapper.java.vm");
        templates.add("vm/java/service.java.vm");
        templates.add("vm/java/serviceImpl.java.vm");
        templates.add("vm/java/controller.java.vm");
        templates.add("vm/xml/mapper.xml.vm");
        if (DataBaseHelper.isOracle()) {
            templates.add("vm/sql/oracle/sql.vm");
        } else if (DataBaseHelper.isPostgerSql()) {
            templates.add("vm/sql/postgres/sql.vm");
        } else if (DataBaseHelper.isSqlServer()) {
            templates.add("vm/sql/sqlserver/sql.vm");
        } else {
            templates.add("vm/sql/sql.vm");
        }
        templates.add("vm/ts/api.ts.vm");
        templates.add("vm/ts/types.ts.vm");
        if (GenConstants.TPL_CRUD.equals(tplCategory)) {
            templates.add("vm/vue/index.vue.vm");
        } else if (GenConstants.TPL_TREE.equals(tplCategory)) {
            templates.add("vm/vue/index-tree.vue.vm");
        }
        /**
         * æ·»åŠ vben5
         */
        templates.add("vm/vben5/api/index.ts.vm");
        templates.add("vm/vben5/api/model.d.ts.vm");
        templates.add("vm/vben5/views/data.ts.vm");
        if (GenConstants.TPL_CRUD.equals(tplCategory)) {
            templates.add("vm/vben5/views/index_vben.vue.vm");
            templates.add("vm/vben5/views/popup.vue.vm");
        } else if (GenConstants.TPL_TREE.equals(tplCategory)) {
            templates.add("vm/vben5/views/index_vben_tree.vue.vm");
            templates.add("vm/vben5/views/popup_tree.vue.vm");
        }
        return templates;
    templates.add("vm/vben5/api/index.ts.vm");
    templates.add("vm/vben5/api/model.d.ts.vm");
    templates.add("vm/vben5/views/data.ts.vm");
    if (GenConstants.TPL_CRUD.equals(tplCategory)) {
      templates.add("vm/vben5/views/index_vben.vue.vm");
      templates.add("vm/vben5/views/popup.vue.vm");
    } else if (GenConstants.TPL_TREE.equals(tplCategory)) {
      templates.add("vm/vben5/views/index_vben_tree.vue.vm");
      templates.add("vm/vben5/views/popup_tree.vue.vm");
    }
    /**
     * èŽ·å–æ–‡ä»¶å
     */
    public static String getFileName(String template, GenTable genTable) {
        // æ–‡ä»¶åç§°
        String fileName = "";
        // åŒ…路径
        String packageName = genTable.getPackageName();
        // æ¨¡å—名
        String moduleName = genTable.getModuleName();
        // å¤§å†™ç±»å
        String className = genTable.getClassName();
        // ä¸šåŠ¡åç§°
        String businessName = genTable.getBusinessName();
    return templates;
  }
        String javaPath = PROJECT_PATH + "/" + StringUtils.replace(packageName, ".", "/");
        String mybatisPath = MYBATIS_PATH + "/" + moduleName;
        String vuePath = "vue";
  /**
   * èŽ·å–æ–‡ä»¶å
   */
  public static String getFileName(String template, GenTable genTable) {
    // æ–‡ä»¶åç§°
    String fileName = "";
    // åŒ…路径
    String packageName = genTable.getPackageName();
    // æ¨¡å—名
    String moduleName = genTable.getModuleName();
    // å¤§å†™ç±»å
    String className = genTable.getClassName();
    // ä¸šåŠ¡åç§°
    String businessName = genTable.getBusinessName();
        if (template.contains("domain.java.vm")) {
            fileName = StringUtils.format("{}/domain/{}.java", javaPath, className);
        }
        if (template.contains("vo.java.vm")) {
            fileName = StringUtils.format("{}/domain/vo/{}Vo.java", javaPath, className);
        }
        if (template.contains("bo.java.vm")) {
            fileName = StringUtils.format("{}/domain/bo/{}Bo.java", javaPath, className);
        }
        if (template.contains("mapper.java.vm")) {
            fileName = StringUtils.format("{}/mapper/{}Mapper.java", javaPath, className);
        } else if (template.contains("service.java.vm")) {
            fileName = StringUtils.format("{}/service/I{}Service.java", javaPath, className);
        } else if (template.contains("serviceImpl.java.vm")) {
            fileName = StringUtils.format("{}/service/impl/{}ServiceImpl.java", javaPath, className);
        } else if (template.contains("controller.java.vm")) {
            fileName = StringUtils.format("{}/controller/{}Controller.java", javaPath, className);
        } else if (template.contains("mapper.xml.vm")) {
            fileName = StringUtils.format("{}/{}Mapper.xml", mybatisPath, className);
        } else if (template.contains("sql.vm")) {
            fileName = businessName + "Menu.sql";
        } else if (template.contains("api.ts.vm")) {
            fileName = StringUtils.format("{}/api/{}/{}/index.ts", vuePath, moduleName, businessName);
        } else if (template.contains("types.ts.vm")) {
            fileName = StringUtils.format("{}/api/{}/{}/types.ts", vuePath, moduleName, businessName);
        } else if (template.contains("index.vue.vm")) {
            fileName = StringUtils.format("{}/views/{}/{}/index.vue", vuePath, moduleName, businessName);
        } else if (template.contains("index-tree.vue.vm")) {
            fileName = StringUtils.format("{}/views/{}/{}/index.vue", vuePath, moduleName, businessName);
        }
    String javaPath = PROJECT_PATH + "/" + StringUtils.replace(packageName, ".", "/");
    String mybatisPath = MYBATIS_PATH + "/" + moduleName;
    String vuePath = "vue";
        // åˆ¤æ–­æ˜¯modal还是drawer
        Dict paramsObj = JsonUtils.parseMap(genTable.getOptions());
        String popupComponent = "modal";
        if (ObjectUtil.isNotNull(paramsObj)) {
            popupComponent = Optional
                .ofNullable(paramsObj.getStr("popupComponent"))
                .orElse("modal");
        }
        String vben5Path = "vben5";
        if (template.contains("vm/vben5/api/index.ts.vm")) {
            fileName = StringUtils.format("{}/api/{}/{}/index.ts", vben5Path, moduleName, businessName);
        }
        if (template.contains("vm/vben5/api/model.d.ts.vm")) {
            fileName = StringUtils.format("{}/api/{}/{}/model.d.ts", vben5Path, moduleName, businessName);
        }
        if (template.contains("vm/vben5/views/index_vben.vue.vm")) {
            fileName = StringUtils.format("{}/views/{}/{}/index.vue", vben5Path, moduleName, businessName);
        }
        if (template.contains("vm/vben5/views/index_vben_tree.vue.vm")) {
            fileName = StringUtils.format("{}/views/{}/{}/index.vue", vben5Path, moduleName, businessName);
        }
        if (template.contains("vm/vben5/views/data.ts.vm")) {
            fileName = StringUtils.format("{}/views/{}/{}/data.ts", vben5Path, moduleName, businessName);
        }
        if (template.contains("vm/vben5/views/popup.vue.vm")) {
            fileName = StringUtils.format("{}/views/{}/{}/{}-{}.vue", vben5Path, moduleName, businessName, businessName, popupComponent);
        }
        if (template.contains("vm/vben5/views/popup_tree.vue.vm")) {
            fileName = StringUtils.format("{}/views/{}/{}/{}-{}.vue", vben5Path, moduleName, businessName, businessName, popupComponent);
        }
        return fileName;
    if (template.contains("domain.java.vm")) {
      fileName = StringUtils.format("{}/domain/{}.java", javaPath, className);
    }
    if (template.contains("vo.java.vm")) {
      fileName = StringUtils.format("{}/domain/vo/{}Vo.java", javaPath, className);
    }
    if (template.contains("bo.java.vm")) {
      fileName = StringUtils.format("{}/domain/bo/{}Bo.java", javaPath, className);
    }
    if (template.contains("mapper.java.vm")) {
      fileName = StringUtils.format("{}/mapper/{}Mapper.java", javaPath, className);
    } else if (template.contains("service.java.vm")) {
      fileName = StringUtils.format("{}/service/I{}Service.java", javaPath, className);
    } else if (template.contains("serviceImpl.java.vm")) {
      fileName = StringUtils.format("{}/service/impl/{}ServiceImpl.java", javaPath, className);
    } else if (template.contains("controller.java.vm")) {
      fileName = StringUtils.format("{}/controller/{}Controller.java", javaPath, className);
    } else if (template.contains("mapper.xml.vm")) {
      fileName = StringUtils.format("{}/{}Mapper.xml", mybatisPath, className);
    } else if (template.contains("sql.vm")) {
      fileName = businessName + "Menu.sql";
    } else if (template.contains("api.ts.vm")) {
      fileName = StringUtils.format("{}/api/{}/{}/index.ts", vuePath, moduleName, businessName);
    } else if (template.contains("types.ts.vm")) {
      fileName = StringUtils.format("{}/api/{}/{}/types.ts", vuePath, moduleName, businessName);
    } else if (template.contains("index.vue.vm")) {
      fileName = StringUtils.format("{}/views/{}/{}/index.vue", vuePath, moduleName, businessName);
    } else if (template.contains("index-tree.vue.vm")) {
      fileName = StringUtils.format("{}/views/{}/{}/index.vue", vuePath, moduleName, businessName);
    }
    /**
     * èŽ·å–åŒ…å‰ç¼€
     *
     * @param packageName åŒ…名称
     * @return åŒ…前缀名称
     */
    public static String getPackagePrefix(String packageName) {
        int lastIndex = packageName.lastIndexOf(".");
        return StringUtils.substring(packageName, 0, lastIndex);
    // åˆ¤æ–­æ˜¯modal还是drawer
    Dict paramsObj = JsonUtils.parseMap(genTable.getOptions());
    String popupComponent = "modal";
    if (ObjectUtil.isNotNull(paramsObj)) {
      popupComponent = Optional
          .ofNullable(paramsObj.getStr("popupComponent"))
          .orElse("modal");
    }
    String vben5Path = "vben5";
    if (template.contains("vm/vben5/api/index.ts.vm")) {
      fileName = StringUtils.format("{}/api/{}/{}/index.ts", vben5Path, moduleName, businessName);
    }
    if (template.contains("vm/vben5/api/model.d.ts.vm")) {
      fileName = StringUtils.format("{}/api/{}/{}/model.d.ts", vben5Path, moduleName, businessName);
    }
    if (template.contains("vm/vben5/views/index_vben.vue.vm")) {
      fileName = StringUtils.format("{}/views/{}/{}/index.vue", vben5Path, moduleName, businessName);
    }
    if (template.contains("vm/vben5/views/index_vben_tree.vue.vm")) {
      fileName = StringUtils.format("{}/views/{}/{}/index.vue", vben5Path, moduleName, businessName);
    }
    if (template.contains("vm/vben5/views/data.ts.vm")) {
      fileName = StringUtils.format("{}/views/{}/{}/data.ts", vben5Path, moduleName, businessName);
    }
    if (template.contains("vm/vben5/views/popup.vue.vm")) {
      fileName = StringUtils.format("{}/views/{}/{}/{}-{}.vue", vben5Path, moduleName, businessName, businessName, popupComponent);
    }
    if (template.contains("vm/vben5/views/popup_tree.vue.vm")) {
      fileName = StringUtils.format("{}/views/{}/{}/{}-{}.vue", vben5Path, moduleName, businessName, businessName, popupComponent);
    }
    /**
     * æ ¹æ®åˆ—类型获取导入包
     *
     * @param genTable ä¸šåŠ¡è¡¨å¯¹è±¡
     * @return è¿”回需要导入的包列表
     */
    public static HashSet<String> getImportList(GenTable genTable) {
        List<GenTableColumn> columns = genTable.getColumns();
        HashSet<String> importList = new HashSet<>();
        for (GenTableColumn column : columns) {
            if (!column.isSuperColumn() && GenConstants.TYPE_DATE.equals(column.getJavaType())) {
                importList.add("java.util.Date");
                importList.add("com.fasterxml.jackson.annotation.JsonFormat");
            } else if (!column.isSuperColumn() && GenConstants.TYPE_BIGDECIMAL.equals(column.getJavaType())) {
                importList.add("java.math.BigDecimal");
            } else if (!column.isSuperColumn() && "imageUpload".equals(column.getHtmlType())) {
                importList.add("org.dromara.common.translation.annotation.Translation");
                importList.add("org.dromara.common.translation.constant.TransConstant");
            }
    return fileName;
  }
  /**
   * èŽ·å–åŒ…å‰ç¼€
   *
   * @param packageName åŒ…名称
   * @return åŒ…前缀名称
   */
  public static String getPackagePrefix(String packageName) {
    int lastIndex = packageName.lastIndexOf(".");
    return StringUtils.substring(packageName, 0, lastIndex);
  }
  /**
   * æ ¹æ®åˆ—类型获取导入包
   *
   * @param genTable ä¸šåŠ¡è¡¨å¯¹è±¡
   * @return è¿”回需要导入的包列表
   */
  public static HashSet<String> getImportList(GenTable genTable) {
    List<GenTableColumn> columns = genTable.getColumns();
    HashSet<String> importList = new HashSet<>();
    for (GenTableColumn column : columns) {
      if (!column.isSuperColumn() && GenConstants.TYPE_DATE.equals(column.getJavaType())) {
        importList.add("java.util.Date");
        importList.add("com.fasterxml.jackson.annotation.JsonFormat");
      } else if (!column.isSuperColumn() && GenConstants.TYPE_BIGDECIMAL.equals(column.getJavaType())) {
        importList.add("java.math.BigDecimal");
      } else if (!column.isSuperColumn() && "imageUpload".equals(column.getHtmlType())) {
        importList.add("org.dromara.common.translation.annotation.Translation");
        importList.add("org.dromara.common.translation.constant.TransConstant");
      }
    }
    return importList;
  }
  /**
   * æ ¹æ®åˆ—类型获取字典组
   *
   * @param genTable ä¸šåŠ¡è¡¨å¯¹è±¡
   * @return è¿”回字典组
   */
  public static String getDicts(GenTable genTable) {
    List<GenTableColumn> columns = genTable.getColumns();
    Set<String> dicts = new HashSet<>();
    addDicts(dicts, columns);
    return StringUtils.join(dicts, ", ");
  }
  /**
   * æ·»åŠ å­—å…¸åˆ—è¡¨
   *
   * @param dicts å­—典列表
   * @param columns åˆ—集合
   */
  public static void addDicts(Set<String> dicts, List<GenTableColumn> columns) {
    for (GenTableColumn column : columns) {
      if (!column.isSuperColumn() && StringUtils.isNotEmpty(column.getDictType()) && StringUtils.equalsAny(
          column.getHtmlType(),
          new String[] { GenConstants.HTML_SELECT, GenConstants.HTML_RADIO, GenConstants.HTML_CHECKBOX })) {
        dicts.add("'" + column.getDictType() + "'");
      }
    }
  }
  /**
   * èŽ·å–æƒé™å‰ç¼€
   *
   * @param moduleName   æ¨¡å—名称
   * @param businessName ä¸šåŠ¡åç§°
   * @return è¿”回权限前缀
   */
  public static String getPermissionPrefix(String moduleName, String businessName) {
    return StringUtils.format("{}:{}", moduleName, businessName);
  }
  /**
   * èŽ·å–ä¸Šçº§èœå•ID字段
   *
   * @param paramsObj ç”Ÿæˆå…¶ä»–选项
   * @return ä¸Šçº§èœå•ID字段
   */
  public static String getParentMenuId(Dict paramsObj) {
    if (CollUtil.isNotEmpty(paramsObj) && paramsObj.containsKey(GenConstants.PARENT_MENU_ID)
        && StringUtils.isNotEmpty(paramsObj.getStr(GenConstants.PARENT_MENU_ID))) {
      return paramsObj.getStr(GenConstants.PARENT_MENU_ID);
    }
    return DEFAULT_PARENT_MENU_ID;
  }
  /**
   * èŽ·å–æ ‘ç¼–ç 
   *
   * @param paramsObj ç”Ÿæˆå…¶ä»–选项
   * @return æ ‘编码
   */
  public static String getTreecode(Map<String, Object> paramsObj) {
    if (CollUtil.isNotEmpty(paramsObj) && paramsObj.containsKey(GenConstants.TREE_CODE)) {
      return StringUtils.toCamelCase(Convert.toStr(paramsObj.get(GenConstants.TREE_CODE)));
    }
    return StringUtils.EMPTY;
  }
  /**
   * èŽ·å–æ ‘çˆ¶ç¼–ç 
   *
   * @param paramsObj ç”Ÿæˆå…¶ä»–选项
   * @return æ ‘父编码
   */
  public static String getTreeParentCode(Dict paramsObj) {
    if (CollUtil.isNotEmpty(paramsObj) && paramsObj.containsKey(GenConstants.TREE_PARENT_CODE)) {
      return StringUtils.toCamelCase(paramsObj.getStr(GenConstants.TREE_PARENT_CODE));
    }
    return StringUtils.EMPTY;
  }
  /**
   * èŽ·å–æ ‘åç§°
   *
   * @param paramsObj ç”Ÿæˆå…¶ä»–选项
   * @return æ ‘名称
   */
  public static String getTreeName(Dict paramsObj) {
    if (CollUtil.isNotEmpty(paramsObj) && paramsObj.containsKey(GenConstants.TREE_NAME)) {
      return StringUtils.toCamelCase(paramsObj.getStr(GenConstants.TREE_NAME));
    }
    return StringUtils.EMPTY;
  }
  /**
   * èŽ·å–éœ€è¦åœ¨å“ªä¸€åˆ—ä¸Šé¢æ˜¾ç¤ºå±•å¼€æŒ‰é’®
   *
   * @param genTable ä¸šåŠ¡è¡¨å¯¹è±¡
   * @return å±•开按钮列序号
   */
  public static int getExpandColumn(GenTable genTable) {
    String options = genTable.getOptions();
    Dict paramsObj = JsonUtils.parseMap(options);
    String treeName = paramsObj.getStr(GenConstants.TREE_NAME);
    int num = 0;
    for (GenTableColumn column : genTable.getColumns()) {
      if (column.isList()) {
        num++;
        String columnName = column.getColumnName();
        if (columnName.equals(treeName)) {
          break;
        }
        return importList;
      }
    }
    /**
     * æ ¹æ®åˆ—类型获取字典组
     *
     * @param genTable ä¸šåŠ¡è¡¨å¯¹è±¡
     * @return è¿”回字典组
     */
    public static String getDicts(GenTable genTable) {
        List<GenTableColumn> columns = genTable.getColumns();
        Set<String> dicts = new HashSet<>();
        addDicts(dicts, columns);
        return StringUtils.join(dicts, ", ");
    }
    /**
     * æ·»åŠ å­—å…¸åˆ—è¡¨
     *
     * @param dicts å­—典列表
     * @param columns åˆ—集合
     */
    public static void addDicts(Set<String> dicts, List<GenTableColumn> columns) {
        for (GenTableColumn column : columns) {
            if (!column.isSuperColumn() && StringUtils.isNotEmpty(column.getDictType()) && StringUtils.equalsAny(
                column.getHtmlType(),
                new String[] { GenConstants.HTML_SELECT, GenConstants.HTML_RADIO, GenConstants.HTML_CHECKBOX })) {
                dicts.add("'" + column.getDictType() + "'");
            }
        }
    }
    /**
     * èŽ·å–æƒé™å‰ç¼€
     *
     * @param moduleName   æ¨¡å—名称
     * @param businessName ä¸šåŠ¡åç§°
     * @return è¿”回权限前缀
     */
    public static String getPermissionPrefix(String moduleName, String businessName) {
        return StringUtils.format("{}:{}", moduleName, businessName);
    }
    /**
     * èŽ·å–ä¸Šçº§èœå•ID字段
     *
     * @param paramsObj ç”Ÿæˆå…¶ä»–选项
     * @return ä¸Šçº§èœå•ID字段
     */
    public static String getParentMenuId(Dict paramsObj) {
        if (CollUtil.isNotEmpty(paramsObj) && paramsObj.containsKey(GenConstants.PARENT_MENU_ID)
            && StringUtils.isNotEmpty(paramsObj.getStr(GenConstants.PARENT_MENU_ID))) {
            return paramsObj.getStr(GenConstants.PARENT_MENU_ID);
        }
        return DEFAULT_PARENT_MENU_ID;
    }
    /**
     * èŽ·å–æ ‘ç¼–ç 
     *
     * @param paramsObj ç”Ÿæˆå…¶ä»–选项
     * @return æ ‘编码
     */
    public static String getTreecode(Map<String, Object> paramsObj) {
        if (CollUtil.isNotEmpty(paramsObj) && paramsObj.containsKey(GenConstants.TREE_CODE)) {
            return StringUtils.toCamelCase(Convert.toStr(paramsObj.get(GenConstants.TREE_CODE)));
        }
        return StringUtils.EMPTY;
    }
    /**
     * èŽ·å–æ ‘çˆ¶ç¼–ç 
     *
     * @param paramsObj ç”Ÿæˆå…¶ä»–选项
     * @return æ ‘父编码
     */
    public static String getTreeParentCode(Dict paramsObj) {
        if (CollUtil.isNotEmpty(paramsObj) && paramsObj.containsKey(GenConstants.TREE_PARENT_CODE)) {
            return StringUtils.toCamelCase(paramsObj.getStr(GenConstants.TREE_PARENT_CODE));
        }
        return StringUtils.EMPTY;
    }
    /**
     * èŽ·å–æ ‘åç§°
     *
     * @param paramsObj ç”Ÿæˆå…¶ä»–选项
     * @return æ ‘名称
     */
    public static String getTreeName(Dict paramsObj) {
        if (CollUtil.isNotEmpty(paramsObj) && paramsObj.containsKey(GenConstants.TREE_NAME)) {
            return StringUtils.toCamelCase(paramsObj.getStr(GenConstants.TREE_NAME));
        }
        return StringUtils.EMPTY;
    }
    /**
     * èŽ·å–éœ€è¦åœ¨å“ªä¸€åˆ—ä¸Šé¢æ˜¾ç¤ºå±•å¼€æŒ‰é’®
     *
     * @param genTable ä¸šåŠ¡è¡¨å¯¹è±¡
     * @return å±•开按钮列序号
     */
    public static int getExpandColumn(GenTable genTable) {
        String options = genTable.getOptions();
        Dict paramsObj = JsonUtils.parseMap(options);
        String treeName = paramsObj.getStr(GenConstants.TREE_NAME);
        int num = 0;
        for (GenTableColumn column : genTable.getColumns()) {
            if (column.isList()) {
                num++;
                String columnName = column.getColumnName();
                if (columnName.equals(treeName)) {
                    break;
                }
            }
        }
        return num;
    }
    return num;
  }
}
script/sql/update/202505141010.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,10 @@
ALTER TABLE `knowledge_attach`
ADD COLUMN `oss_id` bigint(20) NOT NULL COMMENT '对象存储主键' AFTER `remark`,
ADD COLUMN `pic_status` tinyint(1) NOT NULL DEFAULT 10 COMMENT '拆解图片状态10未开始,20进行中,30已完成' AFTER `oss_id`,
ADD COLUMN `pic_anys_status` tinyint(1) NOT NULL DEFAULT 10 COMMENT '分析图片状态10未开始,20进行中,30已完成' AFTER `pic_status`,
ADD COLUMN `vector_status` tinyint(1) NOT NULL DEFAULT 10 COMMENT '写入向量数据库状态10未开始,20进行中,30已完成' AFTER `pic_anys_status`,
DROP PRIMARY KEY,
ADD PRIMARY KEY (`id`) USING BTREE;
ALTER TABLE `knowledge_attach`
MODIFY COLUMN `remark` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '备注' AFTER `update_time`;