办学质量监测教学评价系统
zhouweiyi
2025-05-14 dc9bf3e25d2bfeb736fc9801363cee9fea99910d
pdf文件解析成异步处理
已修改8个文件
已添加2个文件
1526 ■■■■■ 文件已修改
ruoyi-admin/src/main/java/org/ruoyi/RuoYiAIApplication.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules-api/ruoyi-knowledge-api/src/main/java/org/ruoyi/constant/DealStatus.java 16 ●●●●● 补丁 | 查看 | 原始文档 | 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/bo/KnowledgeAttachBo.java 95 ●●●●● 补丁 | 查看 | 原始文档 | 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/service/IKnowledgeInfoService.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/controller/knowledge/KnowledgeController.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/knowledge/KnowledgeInfoServiceImpl.java 501 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-generator/src/main/java/org/ruoyi/generator/util/VelocityUtils.java 718 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
script/sql/update/202505141010.sql 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/org/ruoyi/RuoYiAIApplication.java
@@ -3,6 +3,7 @@
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.metrics.buffering.BufferingApplicationStartup;
import org.springframework.scheduling.annotation.EnableScheduling;
/**
 * å¯åŠ¨ç¨‹åº
@@ -10,6 +11,7 @@
 * @author Lion Li
 */
@SpringBootApplication
@EnableScheduling
public class RuoYiAIApplication {
    public static void main(String[] args) {
ruoyi-modules-api/ruoyi-knowledge-api/src/main/java/org/ruoyi/constant/DealStatus.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,16 @@
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;
}
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/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/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/service/IKnowledgeInfoService.java
@@ -61,5 +61,5 @@
    /**
     * ä¸Šä¼ é™„ä»¶
     */
    void upload(KnowledgeInfoUploadBo bo);
    void upload(KnowledgeInfoUploadBo bo) throws Exception;
}
ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/controller/knowledge/KnowledgeController.java
@@ -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("上传知识库附件成功!");
  }
ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/knowledge/KnowledgeInfoServiceImpl.java
@@ -1,11 +1,14 @@
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 org.ruoyi.chain.loader.ResourceLoader;
import org.ruoyi.chain.loader.ResourceLoaderFactory;
@@ -13,6 +16,8 @@
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;
@@ -30,11 +35,15 @@
import org.ruoyi.service.IChatModelService;
import org.ruoyi.service.VectorStoreService;
import org.ruoyi.service.IKnowledgeInfoService;
import org.ruoyi.system.domain.vo.SysOssVo;
import org.ruoyi.system.service.ISysOssService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.springframework.scheduling.annotation.Async;
import java.io.IOException;
import java.util.*;
@@ -49,217 +58,321 @@
@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;
  /**
   * æŸ¥è¯¢çŸ¥è¯†åº“
   */
  @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());
    lqw.eq(StringUtils.isNotBlank(bo.getVector()), KnowledgeInfo::getVector, bo.getVector());
    lqw.eq(StringUtils.isNotBlank(bo.getVectorModel()), KnowledgeInfo::getVectorModel,
        bo.getVectorModel());
    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.getVector());
      }
    } 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()));
    });
    // åˆ é™¤é™„件和知识片段
    fragmentMapper.deleteByMap(map);
    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);
    }
    SysOssVo uploadDto = null;
    /**
     * æŸ¥è¯¢çŸ¥è¯†åº“列表
     */
    @Override
    public List<KnowledgeInfoVo> queryList(KnowledgeInfoBo bo) {
        LambdaQueryWrapper<KnowledgeInfo> lqw = buildQueryWrapper(bo);
        return baseMapper.selectVoList(lqw);
    }
    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);
    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());
        lqw.eq(StringUtils.isNotBlank(bo.getVector()), KnowledgeInfo::getVector, bo.getVector());
        lqw.eq(StringUtils.isNotBlank(bo.getVectorModel()), KnowledgeInfo::getVectorModel, bo.getVectorModel());
        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());
        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());
    }
    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);
    }
    /**
     * ä¿®æ”¹çŸ¥è¯†åº“
     */
    @Override
    public Boolean updateByBo(KnowledgeInfoBo bo) {
        KnowledgeInfo update = MapstructUtils.convert(bo, KnowledgeInfo.class);
        validEntityBeforeSave(update);
        return baseMapper.updateById(update) > 0;
    attachMapper.insert(knowledgeAttach);
  }
  /**
   * æ£€æŸ¥ç”¨æˆ·æ˜¯å¦æœ‰åˆ é™¤çŸ¥è¯†åº“权限
   *
   * @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("权限不足");
      }
    }
  }
    /**
     * ä¿å­˜å‰çš„æ•°æ®æ ¡éªŒ
     */
    private void validEntityBeforeSave(KnowledgeInfo entity){
        //TODO åšä¸€äº›æ•°æ®æ ¡éªŒ,如唯一约束
  /**
   * å®šæ—¶ å¤„理 é™„件上传后上传向量数据库和PDF文件图片拆解和分析内容
   */
  @Scheduled(fixedDelay = 3000) // æ¯3秒执行一次
  public void dealKnowledgeAttach() 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)
    );
    if (ObjectUtil.isNotEmpty(knowledgeAttaches)) {
      for (KnowledgeAttach attachItem : knowledgeAttaches) {
        this.dealVectorStatus(attachItem);
      }
    }
  }
    /**
     * æ‰¹é‡åˆ é™¤çŸ¥è¯†åº“
     */
    @Override
    public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
        if(isValid){
            //TODO åšä¸€äº›ä¸šåŠ¡ä¸Šçš„æ ¡éªŒ,判断是否需要校验
        }
        return baseMapper.deleteBatchIds(ids) > 0;
  @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;
      }
      // é€šè¿‡kid查询知识库信息
      KnowledgeInfoVo knowledgeInfoVo = baseMapper.selectVoOne(Wrappers.<KnowledgeInfo>lambdaQuery()
          .eq(KnowledgeInfo::getKid, attachItem.getKid()));
      // é€šè¿‡å‘量模型查询模型信息
      ChatModelVo chatModelVo = chatModelService.selectModelByName(
          knowledgeInfoVo.getVectorModel());
      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 ä¸ºç©º");
      }
      StoreEmbeddingBo storeEmbeddingBo = new StoreEmbeddingBo();
      storeEmbeddingBo.setKid(attachItem.getKid());
      storeEmbeddingBo.setDocId(attachItem.getDocId());
      storeEmbeddingBo.setFids(fids);
      storeEmbeddingBo.setChunkList(chunkList);
      storeEmbeddingBo.setModelName(knowledgeInfoVo.getVectorModel());
      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_10)
          .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);
    }
    @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.getVector());
            }
        }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()));
        });
        // åˆ é™¤é™„件和知识片段
        fragmentMapper.deleteByMap(map);
        attachMapper.deleteByMap(map);
        // åˆ é™¤çŸ¥è¯†åº“
        baseMapper.deleteByMap(map);
    }
    @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::getKid, kid));
        // é€šè¿‡å‘量模型查询模型信息
        ChatModelVo chatModelVo = chatModelService.selectModelByName(knowledgeInfoVo.getVectorModel());
        StoreEmbeddingBo storeEmbeddingBo = new StoreEmbeddingBo();
        storeEmbeddingBo.setKid(kid);
        storeEmbeddingBo.setDocId(docId);
        storeEmbeddingBo.setFids(fids);
        storeEmbeddingBo.setChunkList(chunkList);
        storeEmbeddingBo.setModelName(knowledgeInfoVo.getVectorModel());
        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("权限不足");
            }
        }
    }
  }
}
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,6 @@
ALTER TABLE `knowledge_attach`
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;