文章导入知识库
This commit is contained in:
@@ -0,0 +1,206 @@
|
||||
package org.xyzh.news.config;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.CommandLineRunner;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.xyzh.api.ai.knowledge.AiKnowledgeService;
|
||||
import org.xyzh.api.system.config.SysConfigService;
|
||||
import org.xyzh.common.core.domain.ResultDomain;
|
||||
import org.xyzh.common.dto.ai.TbAiKnowledge;
|
||||
|
||||
/**
|
||||
* 文章知识库初始化配置
|
||||
* 系统启动时自动创建"时事资源"知识库,用于存储发布的文章
|
||||
*/
|
||||
@Configuration
|
||||
public class ArticleKnowledgeInit {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(ArticleKnowledgeInit.class);
|
||||
|
||||
/** 知识库ID */
|
||||
private static final String KNOWLEDGE_ID = "article_news_resource";
|
||||
|
||||
/** 知识库标题 */
|
||||
private static final String KNOWLEDGE_TITLE = "时事资源";
|
||||
|
||||
/** 知识库描述 */
|
||||
private static final String KNOWLEDGE_DESCRIPTION = "存储系统发布的文章资源,用于AI智能问答";
|
||||
|
||||
/** 知识库分类 */
|
||||
private static final String KNOWLEDGE_CATEGORY = "article";
|
||||
|
||||
@Autowired
|
||||
private AiKnowledgeService aiKnowledgeService;
|
||||
|
||||
@Autowired
|
||||
private SysConfigService sysConfigService;
|
||||
|
||||
@Value("${article.knowledge.auto-init:true}")
|
||||
private boolean autoInit;
|
||||
|
||||
@Bean
|
||||
public CommandLineRunner articleKnowledgeInitRunner() {
|
||||
return args -> {
|
||||
if (!autoInit) {
|
||||
logger.info("文章知识库自动初始化已禁用");
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info("开始初始化文章知识库...");
|
||||
|
||||
try {
|
||||
// 检查知识库是否已存在
|
||||
if (checkKnowledgeExists(KNOWLEDGE_ID)) {
|
||||
logger.info("文章知识库已存在,跳过初始化");
|
||||
// 更新配置文件中的知识库ID
|
||||
updateKnowledgeIdConfig();
|
||||
return;
|
||||
}
|
||||
|
||||
// 创建知识库
|
||||
TbAiKnowledge knowledge = buildKnowledgeConfig();
|
||||
if (createKnowledge(knowledge)) {
|
||||
logger.info("文章知识库初始化成功: {}", KNOWLEDGE_TITLE);
|
||||
// 更新配置文件中的知识库ID
|
||||
updateKnowledgeIdConfig();
|
||||
} else {
|
||||
logger.error("文章知识库初始化失败");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("文章知识库初始化异常: {}", e.getMessage(), e);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建知识库配置
|
||||
*/
|
||||
private TbAiKnowledge buildKnowledgeConfig() {
|
||||
TbAiKnowledge knowledge = new TbAiKnowledge();
|
||||
|
||||
// 基本信息
|
||||
knowledge.setId(KNOWLEDGE_ID);
|
||||
knowledge.setTitle(KNOWLEDGE_TITLE);
|
||||
knowledge.setDescription(KNOWLEDGE_DESCRIPTION);
|
||||
knowledge.setCategory(KNOWLEDGE_CATEGORY);
|
||||
knowledge.setStatus(1); // 启用状态
|
||||
|
||||
// 从系统配置获取Dify相关参数(使用 dify.dataset.* 格式的配置key)
|
||||
try {
|
||||
String indexingTechnique = sysConfigService.getStringConfig("dify.dataset.defaultIndexingTechnique");
|
||||
if (indexingTechnique != null && !indexingTechnique.isEmpty()) {
|
||||
knowledge.setDifyIndexingTechnique(indexingTechnique);
|
||||
} else {
|
||||
knowledge.setDifyIndexingTechnique("high_quality"); // 默认高质量索引
|
||||
}
|
||||
|
||||
String embeddingModel = sysConfigService.getStringConfig("dify.dataset.defaultEmbeddingModel");
|
||||
if (embeddingModel != null && !embeddingModel.isEmpty()) {
|
||||
knowledge.setEmbeddingModel(embeddingModel);
|
||||
}
|
||||
|
||||
String embeddingModelProvider = sysConfigService.getStringConfig("dify.dataset.embeddingModelProvider");
|
||||
if (embeddingModelProvider != null && !embeddingModelProvider.isEmpty()) {
|
||||
knowledge.setEmbeddingModelProvider(embeddingModelProvider);
|
||||
}
|
||||
|
||||
Boolean rerankingEnable = sysConfigService.getBooleanConfig("dify.dataset.rerankingEnable");
|
||||
knowledge.setRerankingEnable(rerankingEnable != null ? rerankingEnable : false);
|
||||
|
||||
String rerankModel = sysConfigService.getStringConfig("dify.dataset.rerankModel");
|
||||
if (rerankModel != null && !rerankModel.isEmpty()) {
|
||||
knowledge.setRerankModel(rerankModel);
|
||||
}
|
||||
|
||||
String rerankModelProvider = sysConfigService.getStringConfig("dify.dataset.rerankModelProvider");
|
||||
if (rerankModelProvider != null && !rerankModelProvider.isEmpty()) {
|
||||
knowledge.setRerankModelProvider(rerankModelProvider);
|
||||
}
|
||||
|
||||
Integer retrievalTopK = sysConfigService.getIntConfig("dify.dataset.retrievalTopK");
|
||||
knowledge.setRetrievalTopK(retrievalTopK != null ? retrievalTopK : 5);
|
||||
|
||||
Double scoreThreshold = sysConfigService.getDoubleConfig("dify.dataset.retrievalScoreThreshold");
|
||||
knowledge.setRetrievalScoreThreshold(scoreThreshold != null ? scoreThreshold : 0.5);
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.warn("获取Dify配置失败,使用默认值: {}", e.getMessage());
|
||||
// 使用默认值
|
||||
knowledge.setDifyIndexingTechnique("high_quality");
|
||||
knowledge.setRetrievalTopK(5);
|
||||
knowledge.setRetrievalScoreThreshold(0.5);
|
||||
knowledge.setRerankingEnable(false);
|
||||
}
|
||||
|
||||
return knowledge;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查知识库是否存在(使用内部方法,不依赖登录)
|
||||
*/
|
||||
private boolean checkKnowledgeExists(String knowledgeId) {
|
||||
try {
|
||||
// 使用 createKnowledgeInternal 的逻辑:先尝试创建,如果已存在会返回已存在的知识库
|
||||
// 这里直接返回 false,让 createKnowledgeInternal 内部处理重复检查
|
||||
// createKnowledgeInternal 会检查 ID 是否已存在并返回已存在的知识库
|
||||
return false;
|
||||
} catch (Exception e) {
|
||||
logger.warn("检查知识库是否存在时发生异常: {}", e.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建知识库
|
||||
*/
|
||||
private boolean createKnowledge(TbAiKnowledge knowledge) {
|
||||
try {
|
||||
// 使用内部创建方法,无需登录
|
||||
ResultDomain<TbAiKnowledge> result = aiKnowledgeService.createKnowledgeInternal(knowledge);
|
||||
|
||||
if (result.isSuccess()) {
|
||||
TbAiKnowledge created = result.getData();
|
||||
if (created != null) {
|
||||
logger.info("知识库创建成功: id={}, difyDatasetId={}",
|
||||
created.getId(), created.getDifyDatasetId());
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
logger.error("知识库创建失败: {}", result.getMessage());
|
||||
return false;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("创建知识库异常: {}", e.getMessage(), e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新配置中的知识库ID
|
||||
* 将创建的知识库ID写入系统配置,供ArticleKnowledgeService使用
|
||||
*/
|
||||
private void updateKnowledgeIdConfig() {
|
||||
try {
|
||||
// 检查配置是否已存在
|
||||
String existingId = sysConfigService.getStringConfig("article.knowledge.default-id");
|
||||
if (existingId == null || existingId.isEmpty()) {
|
||||
// 可以通过SysConfigService设置配置(如果支持的话)
|
||||
logger.info("文章知识库ID: {},请在配置文件中设置 article.knowledge.default-id={}",
|
||||
KNOWLEDGE_ID, KNOWLEDGE_ID);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.warn("更新知识库ID配置失败: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文章知识库ID
|
||||
*/
|
||||
public static String getArticleKnowledgeId() {
|
||||
return KNOWLEDGE_ID;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package org.xyzh.news.controller;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.xyzh.common.core.domain.ResultDomain;
|
||||
import org.xyzh.common.dto.resource.TbResource;
|
||||
import org.xyzh.news.service.ArticleKnowledgeService;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 文章知识库管理控制器
|
||||
* 提供手动将文章导入/移除知识库的接口
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/news/article-knowledge")
|
||||
public class ArticleKnowledgeController {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(ArticleKnowledgeController.class);
|
||||
|
||||
@Autowired
|
||||
private ArticleKnowledgeService articleKnowledgeService;
|
||||
|
||||
/**
|
||||
* 将单篇文章导入知识库
|
||||
* @param resourceId 资源ID
|
||||
* @return 导入结果
|
||||
*/
|
||||
@PostMapping("/import/{resourceId}")
|
||||
public ResultDomain<TbResource> importToKnowledge(@PathVariable("resourceId") String resourceId) {
|
||||
logger.info("手动导入文章到知识库: resourceId={}", resourceId);
|
||||
return articleKnowledgeService.importArticleToDefaultKnowledgeById(resourceId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量将文章导入知识库
|
||||
* @param params 包含resourceIds的参数
|
||||
* @return 导入结果
|
||||
*/
|
||||
@PostMapping("/import/batch")
|
||||
@SuppressWarnings("unchecked")
|
||||
public ResultDomain<TbResource> batchImportToKnowledge(@RequestBody Map<String, Object> params) {
|
||||
List<String> resourceIds = (List<String>) params.get("resourceIds");
|
||||
logger.info("批量导入文章到知识库: count={}", resourceIds != null ? resourceIds.size() : 0);
|
||||
return articleKnowledgeService.batchImportToDefaultKnowledge(resourceIds);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从知识库移除文章
|
||||
* @param resourceId 资源ID
|
||||
* @return 移除结果
|
||||
*/
|
||||
@DeleteMapping("/remove/{resourceId}")
|
||||
public ResultDomain<Boolean> removeFromKnowledge(@PathVariable("resourceId") String resourceId) {
|
||||
logger.info("从知识库移除文章: resourceId={}", resourceId);
|
||||
return articleKnowledgeService.removeArticleFromKnowledge(resourceId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package org.xyzh.news.service;
|
||||
|
||||
import org.xyzh.common.core.domain.ResultDomain;
|
||||
import org.xyzh.common.dto.resource.TbResource;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 文章知识库服务接口
|
||||
* 负责将发布的文章导入到AI知识库
|
||||
*/
|
||||
public interface ArticleKnowledgeService {
|
||||
|
||||
/**
|
||||
* 将文章导入到知识库
|
||||
* @param resource 文章资源
|
||||
* @param knowledgeId 目标知识库ID
|
||||
* @return 导入结果
|
||||
*/
|
||||
ResultDomain<TbResource> importArticleToKnowledge(TbResource resource, String knowledgeId);
|
||||
|
||||
/**
|
||||
* 将文章导入到默认知识库
|
||||
* @param resource 文章资源
|
||||
* @return 导入结果
|
||||
*/
|
||||
ResultDomain<TbResource> importArticleToDefaultKnowledge(TbResource resource);
|
||||
|
||||
/**
|
||||
* 根据资源ID将文章导入到默认知识库
|
||||
* @param resourceId 资源ID
|
||||
* @return 导入结果
|
||||
*/
|
||||
ResultDomain<TbResource> importArticleToDefaultKnowledgeById(String resourceId);
|
||||
|
||||
/**
|
||||
* 批量将文章导入到默认知识库
|
||||
* @param resourceIds 资源ID列表
|
||||
* @return 导入结果
|
||||
*/
|
||||
ResultDomain<TbResource> batchImportToDefaultKnowledge(List<String> resourceIds);
|
||||
|
||||
/**
|
||||
* 从知识库中删除文章
|
||||
* @param resourceId 文章资源ID
|
||||
* @return 删除结果
|
||||
*/
|
||||
ResultDomain<Boolean> removeArticleFromKnowledge(String resourceId);
|
||||
}
|
||||
@@ -0,0 +1,318 @@
|
||||
package org.xyzh.news.service.impl;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.mock.web.MockMultipartFile;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.xyzh.ai.mapper.AiKnowledgeMapper;
|
||||
import org.xyzh.api.ai.file.AiUploadFileService;
|
||||
import org.xyzh.common.core.domain.ResultDomain;
|
||||
import org.xyzh.common.dto.ai.TbAiKnowledge;
|
||||
import org.xyzh.common.dto.ai.TbAiUploadFile;
|
||||
import org.xyzh.common.dto.resource.TbResource;
|
||||
import org.xyzh.news.mapper.ResourceMapper;
|
||||
import org.xyzh.news.service.ArticleKnowledgeService;
|
||||
import org.xyzh.news.util.ArticlePdfGenerator;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 文章知识库服务实现
|
||||
*/
|
||||
@Service
|
||||
public class ArticleKnowledgeServiceImpl implements ArticleKnowledgeService {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(ArticleKnowledgeServiceImpl.class);
|
||||
|
||||
/** 固定的文章知识库ID */
|
||||
private static final String DEFAULT_KNOWLEDGE_ID = "article_news_resource";
|
||||
|
||||
@Autowired
|
||||
private ArticlePdfGenerator pdfGenerator;
|
||||
|
||||
@Autowired
|
||||
private AiUploadFileService aiUploadFileService;
|
||||
|
||||
@Autowired
|
||||
private ResourceMapper resourceMapper;
|
||||
|
||||
@Autowired
|
||||
private AiKnowledgeMapper aiKnowledgeMapper;
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public ResultDomain<TbResource> importArticleToKnowledge(TbResource resource, String knowledgeId) {
|
||||
ResultDomain<TbResource> resultDomain = new ResultDomain<>();
|
||||
File pdfFile = null;
|
||||
|
||||
try {
|
||||
// 参数验证
|
||||
if (resource == null) {
|
||||
resultDomain.fail("文章资源不能为空");
|
||||
return resultDomain;
|
||||
}
|
||||
if (!StringUtils.hasText(knowledgeId)) {
|
||||
resultDomain.fail("知识库ID不能为空");
|
||||
return resultDomain;
|
||||
}
|
||||
if (!StringUtils.hasText(resource.getContent())) {
|
||||
resultDomain.fail("文章内容不能为空");
|
||||
return resultDomain;
|
||||
}
|
||||
|
||||
// 检查是否已导入
|
||||
if (Boolean.TRUE.equals(resource.getIsInKnowledge())) {
|
||||
resultDomain.fail("文章已导入知识库,无需重复导入");
|
||||
return resultDomain;
|
||||
}
|
||||
|
||||
logger.info("开始将文章导入知识库: resourceId={}, title={}, knowledgeId={}",
|
||||
resource.getResourceID(), resource.getTitle(), knowledgeId);
|
||||
|
||||
// 1. 生成PDF文件
|
||||
pdfFile = pdfGenerator.generatePdf(resource);
|
||||
logger.info("PDF文件生成成功: {}, 大小: {} bytes", pdfFile.getName(), pdfFile.length());
|
||||
|
||||
// 2. 构建文件名
|
||||
String fileName = buildFileName(resource);
|
||||
|
||||
// 3. 将PDF文件转换为MultipartFile
|
||||
MultipartFile multipartFile = convertToMultipartFile(pdfFile, fileName);
|
||||
|
||||
// 4. 上传到知识库
|
||||
ResultDomain<TbAiUploadFile> uploadResult = aiUploadFileService.uploadToKnowledge(
|
||||
knowledgeId,
|
||||
multipartFile,
|
||||
"high_quality"
|
||||
);
|
||||
|
||||
if (uploadResult.isSuccess() && uploadResult.getData() != null) {
|
||||
TbAiUploadFile uploadFile = uploadResult.getData();
|
||||
|
||||
// 5. 更新资源的知识库状态
|
||||
TbResource updateResource = new TbResource();
|
||||
updateResource.setResourceID(resource.getResourceID());
|
||||
updateResource.setIsInKnowledge(true);
|
||||
updateResource.setKnowledgeFileId(uploadFile.getId());
|
||||
updateResource.setKnowledgeImportTime(new Date());
|
||||
updateResource.setUpdateTime(new Date());
|
||||
|
||||
int updateResult = resourceMapper.updateResource(updateResource);
|
||||
if (updateResult > 0) {
|
||||
logger.info("文章成功导入知识库: resourceId={}, knowledgeFileId={}",
|
||||
resource.getResourceID(), uploadFile.getId());
|
||||
|
||||
// 重新查询返回完整数据
|
||||
TbResource updated = resourceMapper.selectByResourceId(resource.getResourceID());
|
||||
resultDomain.success("文章导入知识库成功", updated);
|
||||
} else {
|
||||
logger.warn("文章上传成功但更新状态失败: resourceId={}", resource.getResourceID());
|
||||
resultDomain.success("文章导入知识库成功,但状态更新失败", resource);
|
||||
}
|
||||
} else {
|
||||
logger.error("文章导入知识库失败: {}", uploadResult.getMessage());
|
||||
resultDomain.fail("文章导入知识库失败: " + uploadResult.getMessage());
|
||||
}
|
||||
|
||||
return resultDomain;
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("文章导入知识库异常: resourceId={}, error={}",
|
||||
resource != null ? resource.getResourceID() : "null", e.getMessage(), e);
|
||||
resultDomain.fail("文章导入知识库异常: " + e.getMessage());
|
||||
return resultDomain;
|
||||
} finally {
|
||||
// 清理临时PDF文件
|
||||
if (pdfFile != null && pdfFile.exists()) {
|
||||
try {
|
||||
Files.deleteIfExists(pdfFile.toPath());
|
||||
logger.debug("临时PDF文件已清理: {}", pdfFile.getName());
|
||||
} catch (Exception e) {
|
||||
logger.warn("清理临时PDF文件失败: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResultDomain<TbResource> importArticleToDefaultKnowledge(TbResource resource) {
|
||||
ResultDomain<TbResource> resultDomain = new ResultDomain<>();
|
||||
|
||||
// 从数据库查询固定ID的知识库
|
||||
TbAiKnowledge knowledge = aiKnowledgeMapper.selectKnowledgeById(DEFAULT_KNOWLEDGE_ID);
|
||||
if (knowledge == null || knowledge.getDeleted()) {
|
||||
logger.warn("默认文章知识库不存在: {}, 跳过知识库导入", DEFAULT_KNOWLEDGE_ID);
|
||||
resultDomain.success("默认知识库不存在,跳过导入", resource);
|
||||
return resultDomain;
|
||||
}
|
||||
|
||||
return importArticleToKnowledge(resource, DEFAULT_KNOWLEDGE_ID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResultDomain<TbResource> importArticleToDefaultKnowledgeById(String resourceId) {
|
||||
ResultDomain<TbResource> resultDomain = new ResultDomain<>();
|
||||
|
||||
if (!StringUtils.hasText(resourceId)) {
|
||||
resultDomain.fail("资源ID不能为空");
|
||||
return resultDomain;
|
||||
}
|
||||
|
||||
// 从数据库查询固定ID的知识库
|
||||
TbAiKnowledge knowledge = aiKnowledgeMapper.selectKnowledgeById(DEFAULT_KNOWLEDGE_ID);
|
||||
if (knowledge == null || knowledge.getDeleted()) {
|
||||
resultDomain.fail("默认文章知识库不存在");
|
||||
return resultDomain;
|
||||
}
|
||||
|
||||
// 查询资源
|
||||
TbResource resource = resourceMapper.selectByResourceId(resourceId);
|
||||
if (resource == null || resource.getDeleted()) {
|
||||
resultDomain.fail("资源不存在");
|
||||
return resultDomain;
|
||||
}
|
||||
|
||||
// 检查文章状态
|
||||
if (resource.getStatus() != 1) {
|
||||
resultDomain.fail("只有已发布的文章才能导入知识库");
|
||||
return resultDomain;
|
||||
}
|
||||
|
||||
return importArticleToKnowledge(resource, DEFAULT_KNOWLEDGE_ID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResultDomain<TbResource> batchImportToDefaultKnowledge(List<String> resourceIds) {
|
||||
ResultDomain<TbResource> resultDomain = new ResultDomain<>();
|
||||
|
||||
if (resourceIds == null || resourceIds.isEmpty()) {
|
||||
resultDomain.fail("资源ID列表不能为空");
|
||||
return resultDomain;
|
||||
}
|
||||
|
||||
// 从数据库查询固定ID的知识库
|
||||
TbAiKnowledge knowledge = aiKnowledgeMapper.selectKnowledgeById(DEFAULT_KNOWLEDGE_ID);
|
||||
if (knowledge == null || knowledge.getDeleted()) {
|
||||
resultDomain.fail("默认文章知识库不存在");
|
||||
return resultDomain;
|
||||
}
|
||||
|
||||
List<TbResource> successList = new ArrayList<>();
|
||||
List<String> failedList = new ArrayList<>();
|
||||
|
||||
for (String resourceId : resourceIds) {
|
||||
try {
|
||||
ResultDomain<TbResource> result = importArticleToDefaultKnowledgeById(resourceId);
|
||||
if (result.isSuccess() && result.getData() != null) {
|
||||
successList.add(result.getData());
|
||||
} else {
|
||||
failedList.add(resourceId + ": " + result.getMessage());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
failedList.add(resourceId + ": " + e.getMessage());
|
||||
logger.error("批量导入知识库失败: resourceId={}", resourceId, e);
|
||||
}
|
||||
}
|
||||
|
||||
if (failedList.isEmpty()) {
|
||||
resultDomain.success("批量导入成功,共导入 " + successList.size() + " 篇文章", successList);
|
||||
} else if (successList.isEmpty()) {
|
||||
resultDomain.fail("批量导入全部失败: " + String.join("; ", failedList));
|
||||
} else {
|
||||
resultDomain.success("部分导入成功: 成功 " + successList.size() + " 篇,失败 " + failedList.size() + " 篇", successList);
|
||||
}
|
||||
|
||||
return resultDomain;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public ResultDomain<Boolean> removeArticleFromKnowledge(String resourceId) {
|
||||
ResultDomain<Boolean> resultDomain = new ResultDomain<>();
|
||||
|
||||
if (!StringUtils.hasText(resourceId)) {
|
||||
resultDomain.fail("资源ID不能为空");
|
||||
return resultDomain;
|
||||
}
|
||||
|
||||
// 查询资源
|
||||
TbResource resource = resourceMapper.selectByResourceId(resourceId);
|
||||
if (resource == null || resource.getDeleted()) {
|
||||
resultDomain.fail("资源不存在");
|
||||
return resultDomain;
|
||||
}
|
||||
|
||||
if (!Boolean.TRUE.equals(resource.getIsInKnowledge())) {
|
||||
resultDomain.fail("文章未导入知识库");
|
||||
return resultDomain;
|
||||
}
|
||||
|
||||
try {
|
||||
// 删除知识库中的文件
|
||||
if (StringUtils.hasText(resource.getKnowledgeFileId())) {
|
||||
ResultDomain<Boolean> deleteResult = aiUploadFileService.deleteFile(resource.getKnowledgeFileId());
|
||||
if (!deleteResult.isSuccess()) {
|
||||
logger.warn("删除知识库文件失败,继续更新资源状态: {}", deleteResult.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// 更新资源状态
|
||||
TbResource updateResource = new TbResource();
|
||||
updateResource.setResourceID(resourceId);
|
||||
updateResource.setIsInKnowledge(false);
|
||||
updateResource.setKnowledgeFileId(null);
|
||||
updateResource.setKnowledgeImportTime(null);
|
||||
updateResource.setUpdateTime(new Date());
|
||||
|
||||
int updateResult = resourceMapper.updateResource(updateResource);
|
||||
if (updateResult > 0) {
|
||||
logger.info("文章已从知识库移除: resourceId={}", resourceId);
|
||||
resultDomain.success("文章已从知识库移除", true);
|
||||
} else {
|
||||
resultDomain.fail("更新资源状态失败");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("从知识库移除文章异常: resourceId={}", resourceId, e);
|
||||
resultDomain.fail("从知识库移除文章异常: " + e.getMessage());
|
||||
}
|
||||
|
||||
return resultDomain;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建PDF文件名
|
||||
*/
|
||||
private String buildFileName(TbResource resource) {
|
||||
String title = resource.getTitle();
|
||||
// 移除文件名中的非法字符
|
||||
title = title.replaceAll("[\\\\/:*?\"<>|]", "_");
|
||||
// 限制文件名长度
|
||||
if (title.length() > 50) {
|
||||
title = title.substring(0, 50);
|
||||
}
|
||||
return "article_" + resource.getResourceID() + "_" + title + ".pdf";
|
||||
}
|
||||
|
||||
/**
|
||||
* 将File转换为MultipartFile
|
||||
*/
|
||||
private MultipartFile convertToMultipartFile(File file, String fileName) throws Exception {
|
||||
try (FileInputStream fis = new FileInputStream(file)) {
|
||||
return new MockMultipartFile(
|
||||
"file",
|
||||
fileName,
|
||||
"application/pdf",
|
||||
fis
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -28,6 +28,7 @@ import org.xyzh.api.news.resource.ResourceService;
|
||||
import org.xyzh.api.system.permission.ResourcePermissionService;
|
||||
import org.xyzh.common.vo.UserDeptRoleVO;
|
||||
import org.xyzh.common.core.enums.ResourceType;
|
||||
import org.xyzh.news.service.ArticleKnowledgeService;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
@@ -59,6 +60,9 @@ public class NCResourceServiceImpl implements ResourceService {
|
||||
@Autowired
|
||||
private ResourceAuditService auditService;
|
||||
|
||||
@Autowired
|
||||
private ArticleKnowledgeService articleKnowledgeService;
|
||||
|
||||
@Override
|
||||
public ResultDomain<TbResource> getResourceList(TbResource filter) {
|
||||
ResultDomain<TbResource> resultDomain = new ResultDomain<>();
|
||||
@@ -328,6 +332,20 @@ public class NCResourceServiceImpl implements ResourceService {
|
||||
logger.error("创建资源权限异常,但不影响资源创建: {}", e.getMessage(), e);
|
||||
}
|
||||
|
||||
// 如果文章直接发布(status=1且审核通过),导入知识库
|
||||
if (resource.getStatus() == 1 && resource.getIsAudited()) {
|
||||
try {
|
||||
ResultDomain<TbResource> knowledgeResult = articleKnowledgeService.importArticleToDefaultKnowledge(resource);
|
||||
if (knowledgeResult.isSuccess() && knowledgeResult.getData() != null) {
|
||||
logger.info("新建文章已成功导入知识库: {}", resource.getResourceID());
|
||||
} else {
|
||||
logger.warn("新建文章导入知识库跳过或失败: {}, 原因: {}", resource.getResourceID(), knowledgeResult.getMessage());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("新建文章导入知识库异常,但不影响创建: {}", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
resultDomain.success("创建资源成功", resourceVO);
|
||||
return resultDomain;
|
||||
} else {
|
||||
@@ -529,7 +547,22 @@ public class NCResourceServiceImpl implements ResourceService {
|
||||
if (result > 0) {
|
||||
logger.info("更新资源状态成功: {}", resourceID);
|
||||
// 重新查询返回完整数据
|
||||
TbResource updated = resourceMapper.selectByResourceId(resource.getId());
|
||||
TbResource updated = resourceMapper.selectByResourceId(resource.getResourceID());
|
||||
|
||||
// 如果状态变为发布且审核通过,导入知识库
|
||||
if (status == 1 && resource.getIsAudited()) {
|
||||
try {
|
||||
ResultDomain<TbResource> knowledgeResult = articleKnowledgeService.importArticleToDefaultKnowledge(updated);
|
||||
if (knowledgeResult.isSuccess() && knowledgeResult.getData() != null) {
|
||||
logger.info("文章状态更新后已成功导入知识库: {}", resourceID);
|
||||
} else {
|
||||
logger.warn("文章状态更新后导入知识库跳过或失败: {}, 原因: {}", resourceID, knowledgeResult.getMessage());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("文章状态更新后导入知识库异常,但不影响状态更新: {}", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
resultDomain.success("更新资源状态成功", updated);
|
||||
return resultDomain;
|
||||
} else {
|
||||
@@ -586,7 +619,21 @@ public class NCResourceServiceImpl implements ResourceService {
|
||||
if (result > 0) {
|
||||
logger.info("发布资源成功: {}", resourceID);
|
||||
// 重新查询返回完整数据
|
||||
TbResource updated = resourceMapper.selectByResourceId(resource.getId());
|
||||
TbResource updated = resourceMapper.selectByResourceId(resourceID);
|
||||
|
||||
// 异步将文章导入知识库
|
||||
try {
|
||||
ResultDomain<TbResource> knowledgeResult = articleKnowledgeService.importArticleToDefaultKnowledge(updated);
|
||||
if (knowledgeResult.isSuccess() && knowledgeResult.getData() != null) {
|
||||
logger.info("文章已成功导入知识库: {}", resourceID);
|
||||
} else {
|
||||
logger.warn("文章导入知识库跳过或失败: {}, 原因: {}", resourceID, knowledgeResult.getMessage());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// 知识库导入失败不影响文章发布
|
||||
logger.error("文章导入知识库异常,但不影响发布: {}", e.getMessage(), e);
|
||||
}
|
||||
|
||||
resultDomain.success("发布资源成功", updated);
|
||||
return resultDomain;
|
||||
} else {
|
||||
@@ -626,7 +673,7 @@ public class NCResourceServiceImpl implements ResourceService {
|
||||
if (result > 0) {
|
||||
logger.info("下架资源成功: {}", resourceID);
|
||||
// 重新查询返回完整数据
|
||||
TbResource updated = resourceMapper.selectByResourceId(resource.getId());
|
||||
TbResource updated = resourceMapper.selectByResourceId(resource.getResourceID());
|
||||
resultDomain.success("下架资源成功", updated);
|
||||
return resultDomain;
|
||||
} else {
|
||||
@@ -695,7 +742,7 @@ public class NCResourceServiceImpl implements ResourceService {
|
||||
if (result > 0) {
|
||||
logger.info("增加资源点赞次数成功: {}", resourceID);
|
||||
// 重新查询返回完整数据
|
||||
TbResource updated = resourceMapper.selectByResourceId(resource.getId());
|
||||
TbResource updated = resourceMapper.selectByResourceId(resource.getResourceID());
|
||||
resultDomain.success("增加点赞次数成功", updated);
|
||||
return resultDomain;
|
||||
} else {
|
||||
@@ -767,7 +814,7 @@ public class NCResourceServiceImpl implements ResourceService {
|
||||
if (result > 0) {
|
||||
logger.info("设置资源推荐状态成功: {} -> {}", resourceID, isRecommend);
|
||||
// 重新查询返回完整数据
|
||||
TbResource updated = resourceMapper.selectByResourceId(resource.getId());
|
||||
TbResource updated = resourceMapper.selectByResourceId(resource.getResourceID());
|
||||
resultDomain.success("设置推荐状态成功", updated);
|
||||
return resultDomain;
|
||||
} else {
|
||||
@@ -811,7 +858,7 @@ public class NCResourceServiceImpl implements ResourceService {
|
||||
if (result > 0) {
|
||||
logger.info("设置资源轮播状态成功: {} -> {}", resourceID, isBanner);
|
||||
// 重新查询返回完整数据
|
||||
TbResource updated = resourceMapper.selectByResourceId(resource.getId());
|
||||
TbResource updated = resourceMapper.selectByResourceId(resource.getResourceID());
|
||||
resultDomain.success("设置轮播状态成功", updated);
|
||||
return resultDomain;
|
||||
} else {
|
||||
|
||||
@@ -0,0 +1,270 @@
|
||||
package org.xyzh.news.util;
|
||||
|
||||
import com.lowagie.text.pdf.BaseFont;
|
||||
import org.jsoup.Jsoup;
|
||||
import org.jsoup.nodes.Document;
|
||||
import org.jsoup.safety.Safelist;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.xhtmlrenderer.pdf.ITextRenderer;
|
||||
import org.xyzh.common.dto.resource.TbResource;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 文章PDF生成工具类
|
||||
* 将富文本内容转换为PDF文件
|
||||
*/
|
||||
@Component
|
||||
public class ArticlePdfGenerator {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(ArticlePdfGenerator.class);
|
||||
|
||||
/**
|
||||
* 将文章内容生成PDF文件
|
||||
* @param resource 文章资源
|
||||
* @return 生成的PDF文件
|
||||
*/
|
||||
public File generatePdf(TbResource resource) throws Exception {
|
||||
if (resource == null || resource.getContent() == null) {
|
||||
throw new IllegalArgumentException("文章内容不能为空");
|
||||
}
|
||||
|
||||
// 创建临时文件
|
||||
Path tempFile = Files.createTempFile("article_" + resource.getResourceID() + "_", ".pdf");
|
||||
File pdfFile = tempFile.toFile();
|
||||
|
||||
try (OutputStream os = new FileOutputStream(pdfFile)) {
|
||||
// 构建HTML内容
|
||||
String htmlContent = buildHtmlContent(resource);
|
||||
|
||||
// 使用Flying Saucer生成PDF
|
||||
ITextRenderer renderer = new ITextRenderer();
|
||||
|
||||
// 添加中文字体支持
|
||||
addFontSupport(renderer);
|
||||
|
||||
// 设置HTML内容
|
||||
renderer.setDocumentFromString(htmlContent);
|
||||
renderer.layout();
|
||||
renderer.createPDF(os);
|
||||
|
||||
logger.info("PDF生成成功: {}, 文件大小: {} bytes", pdfFile.getName(), pdfFile.length());
|
||||
return pdfFile;
|
||||
} catch (Exception e) {
|
||||
// 清理临时文件
|
||||
Files.deleteIfExists(tempFile);
|
||||
logger.error("PDF生成失败: {}", e.getMessage(), e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建HTML内容
|
||||
*/
|
||||
private String buildHtmlContent(TbResource resource) {
|
||||
StringBuilder html = new StringBuilder();
|
||||
html.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
|
||||
html.append("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">");
|
||||
html.append("<html xmlns=\"http://www.w3.org/1999/xhtml\">");
|
||||
html.append("<head>");
|
||||
html.append("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\"/>");
|
||||
html.append("<style type=\"text/css\">");
|
||||
html.append(getStyleContent());
|
||||
html.append("</style>");
|
||||
html.append("</head>");
|
||||
html.append("<body>");
|
||||
|
||||
// 文章标题
|
||||
html.append("<div class=\"article-header\">");
|
||||
html.append("<h1 class=\"title\">").append(escapeHtml(resource.getTitle())).append("</h1>");
|
||||
|
||||
// 文章元信息
|
||||
html.append("<div class=\"meta\">");
|
||||
if (resource.getAuthor() != null && !resource.getAuthor().isEmpty()) {
|
||||
html.append("<span>作者: ").append(escapeHtml(resource.getAuthor())).append("</span>");
|
||||
}
|
||||
if (resource.getSource() != null && !resource.getSource().isEmpty()) {
|
||||
html.append("<span>来源: ").append(escapeHtml(resource.getSource())).append("</span>");
|
||||
}
|
||||
if (resource.getPublishTime() != null) {
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm");
|
||||
html.append("<span>发布时间: ").append(sdf.format(resource.getPublishTime())).append("</span>");
|
||||
}
|
||||
html.append("</div>");
|
||||
html.append("</div>");
|
||||
|
||||
// 文章内容
|
||||
html.append("<div class=\"article-content\">");
|
||||
html.append(cleanHtmlContent(resource.getContent()));
|
||||
html.append("</div>");
|
||||
|
||||
html.append("</body>");
|
||||
html.append("</html>");
|
||||
|
||||
return html.toString();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取CSS样式
|
||||
*/
|
||||
private String getStyleContent() {
|
||||
return """
|
||||
@page {
|
||||
size: A4;
|
||||
margin: 2cm;
|
||||
}
|
||||
body {
|
||||
font-family: SimSun, 'Microsoft YaHei', Arial, sans-serif;
|
||||
font-size: 12pt;
|
||||
line-height: 1.8;
|
||||
color: #333;
|
||||
}
|
||||
.article-header {
|
||||
margin-bottom: 30px;
|
||||
border-bottom: 2px solid #1890ff;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
.title {
|
||||
font-size: 22pt;
|
||||
font-weight: bold;
|
||||
color: #1a1a1a;
|
||||
margin-bottom: 15px;
|
||||
text-align: center;
|
||||
}
|
||||
.meta {
|
||||
font-size: 10pt;
|
||||
color: #666;
|
||||
text-align: center;
|
||||
}
|
||||
.meta span {
|
||||
margin-right: 20px;
|
||||
}
|
||||
.article-content {
|
||||
text-align: justify;
|
||||
}
|
||||
.article-content p {
|
||||
text-indent: 2em;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.article-content img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
display: block;
|
||||
margin: 15px auto;
|
||||
}
|
||||
.article-content h1, .article-content h2, .article-content h3 {
|
||||
margin-top: 20px;
|
||||
margin-bottom: 10px;
|
||||
color: #1a1a1a;
|
||||
}
|
||||
.article-content table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: 15px 0;
|
||||
}
|
||||
.article-content table td, .article-content table th {
|
||||
border: 1px solid #ddd;
|
||||
padding: 8px;
|
||||
}
|
||||
.article-content blockquote {
|
||||
border-left: 4px solid #1890ff;
|
||||
padding-left: 15px;
|
||||
margin: 15px 0;
|
||||
color: #666;
|
||||
}
|
||||
""";
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理HTML内容,使其符合XHTML规范
|
||||
*/
|
||||
private String cleanHtmlContent(String htmlContent) {
|
||||
if (htmlContent == null || htmlContent.isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
// 使用Jsoup解析和清理HTML
|
||||
Document doc = Jsoup.parseBodyFragment(htmlContent);
|
||||
doc.outputSettings()
|
||||
.syntax(Document.OutputSettings.Syntax.xml)
|
||||
.escapeMode(org.jsoup.nodes.Entities.EscapeMode.xhtml)
|
||||
.charset(StandardCharsets.UTF_8);
|
||||
|
||||
// 处理图片标签,确保闭合
|
||||
doc.select("img").forEach(img -> {
|
||||
if (!img.hasAttr("alt")) {
|
||||
img.attr("alt", "");
|
||||
}
|
||||
});
|
||||
|
||||
// 处理br标签
|
||||
doc.select("br").forEach(br -> br.tagName("br"));
|
||||
|
||||
// 移除不支持的标签和属性
|
||||
String cleaned = Jsoup.clean(doc.body().html(), "",
|
||||
Safelist.relaxed()
|
||||
.addTags("div", "span", "p", "br", "h1", "h2", "h3", "h4", "h5", "h6")
|
||||
.addTags("table", "thead", "tbody", "tr", "td", "th")
|
||||
.addTags("ul", "ol", "li", "blockquote", "pre", "code")
|
||||
.addTags("strong", "em", "b", "i", "u", "s", "sub", "sup")
|
||||
.addTags("img", "a")
|
||||
.addAttributes("img", "src", "alt", "width", "height")
|
||||
.addAttributes("a", "href")
|
||||
.addAttributes(":all", "style", "class"),
|
||||
new Document.OutputSettings().syntax(Document.OutputSettings.Syntax.xml));
|
||||
|
||||
return cleaned;
|
||||
}
|
||||
|
||||
/**
|
||||
* HTML转义
|
||||
*/
|
||||
private String escapeHtml(String text) {
|
||||
if (text == null) {
|
||||
return "";
|
||||
}
|
||||
return text.replace("&", "&")
|
||||
.replace("<", "<")
|
||||
.replace(">", ">")
|
||||
.replace("\"", """)
|
||||
.replace("'", "'");
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加中文字体支持
|
||||
*/
|
||||
private void addFontSupport(ITextRenderer renderer) {
|
||||
try {
|
||||
// 尝试添加系统字体
|
||||
String[] fontPaths = {
|
||||
"C:/Windows/Fonts/simsun.ttc", // Windows 宋体
|
||||
"C:/Windows/Fonts/msyh.ttc", // Windows 微软雅黑
|
||||
"/usr/share/fonts/truetype/wqy/wqy-microhei.ttc", // Linux 文泉驿微米黑
|
||||
"/usr/share/fonts/truetype/wqy/wqy-zenhei.ttc", // Linux 文泉驿正黑
|
||||
"/System/Library/Fonts/PingFang.ttc" // macOS 苹方
|
||||
};
|
||||
|
||||
for (String fontPath : fontPaths) {
|
||||
File fontFile = new File(fontPath);
|
||||
if (fontFile.exists()) {
|
||||
renderer.getFontResolver().addFont(
|
||||
fontPath,
|
||||
BaseFont.IDENTITY_H,
|
||||
BaseFont.NOT_EMBEDDED
|
||||
);
|
||||
logger.debug("已加载字体: {}", fontPath);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.warn("加载字体失败,将使用默认字体: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,9 @@
|
||||
<result column="is_audited" property="isAudited" jdbcType="BOOLEAN"/>
|
||||
<result column="is_recommend" property="isRecommend" jdbcType="BOOLEAN"/>
|
||||
<result column="is_banner" property="isBanner" jdbcType="BOOLEAN"/>
|
||||
<result column="is_in_knowledge" property="isInKnowledge" jdbcType="BOOLEAN"/>
|
||||
<result column="knowledge_file_id" property="knowledgeFileId" jdbcType="VARCHAR"/>
|
||||
<result column="knowledge_import_time" property="knowledgeImportTime" jdbcType="TIMESTAMP"/>
|
||||
<result column="publish_time" property="publishTime" jdbcType="TIMESTAMP"/>
|
||||
<result column="creator" property="creator" jdbcType="VARCHAR"/>
|
||||
<result column="updater" property="updater" jdbcType="VARCHAR"/>
|
||||
@@ -34,8 +37,8 @@
|
||||
<sql id="Base_Column_List">
|
||||
id, resource_id, title, content, summary, cover_image, tag_id, author, source,
|
||||
source_url, view_count, like_count, collect_count, status, is_audited, is_recommend,
|
||||
is_banner, publish_time, creator, updater, create_time, update_time,
|
||||
delete_time, deleted
|
||||
is_banner, is_in_knowledge, knowledge_file_id, knowledge_import_time, publish_time,
|
||||
creator, updater, create_time, update_time, delete_time, deleted
|
||||
</sql>
|
||||
|
||||
<!-- 通用条件 -->
|
||||
@@ -260,6 +263,15 @@
|
||||
<if test="isBanner != null">
|
||||
is_banner = #{isBanner},
|
||||
</if>
|
||||
<if test="isInKnowledge != null">
|
||||
is_in_knowledge = #{isInKnowledge},
|
||||
</if>
|
||||
<if test="knowledgeFileId != null">
|
||||
knowledge_file_id = #{knowledgeFileId},
|
||||
</if>
|
||||
<if test="knowledgeImportTime != null">
|
||||
knowledge_import_time = #{knowledgeImportTime},
|
||||
</if>
|
||||
<if test="isAudited != null">
|
||||
is_audited = #{isAudited},
|
||||
</if>
|
||||
@@ -368,6 +380,9 @@
|
||||
<result column="is_audited" property="isAudited" jdbcType="BOOLEAN"/>
|
||||
<result column="is_recommend" property="isRecommend" jdbcType="BOOLEAN"/>
|
||||
<result column="is_banner" property="isBanner" jdbcType="BOOLEAN"/>
|
||||
<result column="is_in_knowledge" property="isInKnowledge" jdbcType="BOOLEAN"/>
|
||||
<result column="knowledge_file_id" property="knowledgeFileId" jdbcType="VARCHAR"/>
|
||||
<result column="knowledge_import_time" property="knowledgeImportTime" jdbcType="TIMESTAMP"/>
|
||||
<result column="publish_time" property="publishTime" jdbcType="TIMESTAMP"/>
|
||||
<result column="creator" property="creator" jdbcType="VARCHAR"/>
|
||||
<result column="updater" property="updater" jdbcType="VARCHAR"/>
|
||||
|
||||
Reference in New Issue
Block a user