知识库文件日志
This commit is contained in:
@@ -169,3 +169,20 @@ COMMENT ON COLUMN ai.tb_knowledge_file.update_time IS '更新时间';
|
||||
COMMENT ON COLUMN ai.tb_knowledge_file.delete_time IS '删除时间';
|
||||
COMMENT ON COLUMN ai.tb_knowledge_file.deleted IS '是否删除';
|
||||
|
||||
DROP TABLE IF EXISTS ai.tb_knowledge_file_log CASCADE;
|
||||
CREATE TABLE ai.tb_knowledge_file_log(
|
||||
optsn VARCHAR(50) NOT NULL, -- 流水号
|
||||
log_id VARCHAR(50) NOT NULL, -- 日志ID
|
||||
knowledge_id VARCHAR(50) NOT NULL, -- 知识库ID
|
||||
file_root_id VARCHAR(50) NOT NULL, -- 文件根ID
|
||||
file_id VARCHAR(50) NOT NULL, -- 文件ID
|
||||
file_name VARCHAR(100) NOT NULL, -- 文件名
|
||||
service VARCHAR(50) NOT NULL, -- 所属服务 workcase、bidding
|
||||
version INTEGER NOT NULL DEFAULT 1, -- 文件版本
|
||||
action VARCHAR(50) NOT NULL, -- 操作类型 upload、update、delete
|
||||
creator VARCHAR(50) NOT NULL, -- 创建者(用户ID)
|
||||
creator_name VARCHAR(100) NOT NULL, -- 创建者姓名
|
||||
create_time TIMESTAMPTZ NOT NULL DEFAULT now(), -- 创建时间
|
||||
PRIMARY KEY (optsn),
|
||||
UNIQUE (knowledge_id, file_id)
|
||||
);
|
||||
|
||||
@@ -7,7 +7,6 @@ import jakarta.validation.constraints.NotEmpty;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import jakarta.annotation.Resource;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.MediaType;
|
||||
@@ -16,9 +15,11 @@ import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.xyzh.api.ai.service.DifyProxyService;
|
||||
import org.xyzh.api.ai.service.KnowledgeFileLogService;
|
||||
import org.xyzh.api.ai.service.KnowledgeService;
|
||||
import org.xyzh.api.ai.dto.TbKnowledge;
|
||||
import org.xyzh.api.ai.dto.TbKnowledgeFile;
|
||||
import org.xyzh.api.ai.dto.TbKnowledgeFileLog;
|
||||
import org.xyzh.api.ai.vo.KnowledgeFileVO;
|
||||
import org.xyzh.common.core.domain.ResultDomain;
|
||||
import org.xyzh.common.core.page.PageRequest;
|
||||
@@ -46,6 +47,9 @@ public class KnowledgeController {
|
||||
@Autowired
|
||||
private KnowledgeService knowledgeService;
|
||||
|
||||
@Autowired
|
||||
private KnowledgeFileLogService knowledgeFileLogService;
|
||||
|
||||
@Autowired
|
||||
private DifyProxyService difyProxyService;
|
||||
|
||||
@@ -347,4 +351,29 @@ public class KnowledgeController {
|
||||
logger.info("更新文档状态: datasetId={}, action={}", datasetId, action);
|
||||
return difyProxyService.updateDocumentStatus(datasetId, action, requestBody);
|
||||
}
|
||||
|
||||
// ================================ 知识库文件操作日志 =======================
|
||||
/**
|
||||
* @description 查询知识库操作日志列表
|
||||
* @param fileLog
|
||||
* @author yslg
|
||||
* @since 2025-12-18
|
||||
*/
|
||||
@PreAuthorize("hasAuthority('ai:knowledge:file:view')")
|
||||
@PostMapping("/datasets/log/list")
|
||||
public ResultDomain<TbKnowledgeFileLog> getKnowledgeFileLogList(@RequestBody TbKnowledgeFileLog fileLog){
|
||||
return knowledgeFileLogService.getKnowledgeFileLogList(fileLog);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 查询知识库操作日志分页
|
||||
* @param pageRequest
|
||||
* @author yslg
|
||||
* @since 2025-12-18
|
||||
*/
|
||||
@PreAuthorize("hasAuthority('ai:knowledge:file:view')")
|
||||
@PostMapping("/datasets/log/page")
|
||||
public ResultDomain<TbKnowledgeFileLog> getKnowledgeFileLogPage(@RequestBody PageRequest<TbKnowledgeFileLog> pageRequest){
|
||||
return knowledgeFileLogService.getKnowledgeFileLogPage(pageRequest);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
package org.xyzh.ai.mapper;
|
||||
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.xyzh.api.ai.dto.TbKnowledgeFile;
|
||||
import org.xyzh.api.ai.dto.TbKnowledgeFileLog;
|
||||
import org.xyzh.api.ai.vo.KnowledgeFileVO;
|
||||
import org.xyzh.common.core.page.PageParam;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @description 知识库文件数据访问层
|
||||
* @filename KnowledgeFileMapper.java
|
||||
* @author yslg
|
||||
* @copyright xyzh
|
||||
* @since 2025-12-17
|
||||
*/
|
||||
@Mapper
|
||||
public interface TbKnowledgeFileLogMapper {
|
||||
|
||||
int addKnowledgeFileLog(TbKnowledgeFileLog tbKnowledgeFileLog);
|
||||
|
||||
List<TbKnowledgeFileLog> getKnowledgeFileLogList(@Param("filter") TbKnowledgeFileLog filter);
|
||||
|
||||
List<TbKnowledgeFileLog> getKnowledgeFileLogPage(@Param("pageParam") PageParam pageParam,@Param("filter") TbKnowledgeFileLog filter);
|
||||
|
||||
|
||||
int countKnowledgeFileLog(@Param("filter") TbKnowledgeFileLog filter);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
package org.xyzh.ai.service.impl;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.dubbo.config.annotation.DubboService;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.xyzh.ai.mapper.TbKnowledgeFileLogMapper;
|
||||
import org.xyzh.api.ai.dto.TbKnowledgeFileLog;
|
||||
import org.xyzh.api.ai.service.KnowledgeFileLogService;
|
||||
import org.xyzh.common.core.domain.ResultDomain;
|
||||
import org.xyzh.common.core.page.PageDomain;
|
||||
import org.xyzh.common.core.page.PageParam;
|
||||
import org.xyzh.common.core.page.PageRequest;
|
||||
import org.xyzh.common.utils.NonUtils;
|
||||
import org.xyzh.common.utils.id.IdUtil;
|
||||
import org.xyzh.common.utils.validation.ValidationResult;
|
||||
import org.xyzh.common.utils.validation.ValidationUtils;
|
||||
|
||||
@DubboService(version="1.0.0", group="ai", timeout=3000, retries=0)
|
||||
public class KnowledgeFileLogServiceImpl implements KnowledgeFileLogService{
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(KnowledgeServiceImpl.class);
|
||||
|
||||
@Autowired
|
||||
private TbKnowledgeFileLogMapper knowledgeFileLogMapper;
|
||||
|
||||
/**
|
||||
* @description 新增知识库文件操作日志
|
||||
* @param knowledgeFileLog
|
||||
* @return 日志
|
||||
* @author yslg
|
||||
* @since 2025-12-31
|
||||
*/
|
||||
@Override
|
||||
public ResultDomain<TbKnowledgeFileLog> addKnowledgeFileLog(TbKnowledgeFileLog knowledgeFileLog){
|
||||
knowledgeFileLog.setOptsn(IdUtil.getOptsn());
|
||||
knowledgeFileLog.setLogId(IdUtil.generateID());
|
||||
ValidationResult rt = ValidationUtils.validate(knowledgeFileLog, Arrays.asList(
|
||||
|
||||
));
|
||||
if(!rt.isValid()){
|
||||
return ResultDomain.failure("日志参数校验失败");
|
||||
}
|
||||
int result = knowledgeFileLogMapper.addKnowledgeFileLog(knowledgeFileLog);
|
||||
if(result >0){
|
||||
return ResultDomain.success("添加知识库文件日志成功", knowledgeFileLog);
|
||||
}else {
|
||||
return ResultDomain.failure("添加知识库文件日志失败");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 查询知识库日志操作列表
|
||||
* @param filter
|
||||
* @return 日志列表
|
||||
* @author yslg
|
||||
* @since 2025-12-31
|
||||
*/
|
||||
@Override
|
||||
public ResultDomain<TbKnowledgeFileLog> getKnowledgeFileLogList(TbKnowledgeFileLog filter){
|
||||
List<TbKnowledgeFileLog> logs = knowledgeFileLogMapper.getKnowledgeFileLogList(filter);
|
||||
return ResultDomain.success("查询知识库日志成功",logs);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 查询知识库日志操作分页
|
||||
* @param pageRequest
|
||||
* @return 日志分页数据
|
||||
* @author yslg
|
||||
* @since 2025-12-31
|
||||
*/
|
||||
@Override
|
||||
public ResultDomain<TbKnowledgeFileLog> getKnowledgeFileLogPage(PageRequest<TbKnowledgeFileLog> pageRequest){
|
||||
List<TbKnowledgeFileLog> logs = knowledgeFileLogMapper.getKnowledgeFileLogPage(pageRequest.getPageParam(), pageRequest.getFilter());
|
||||
int total = knowledgeFileLogMapper.countKnowledgeFileLog(pageRequest.getFilter());
|
||||
PageDomain<TbKnowledgeFileLog> pageDomain = new PageDomain<>();
|
||||
pageDomain.setDataList(logs);
|
||||
PageParam pageParam = pageRequest.getPageParam();
|
||||
pageParam.setTotal(total);
|
||||
pageDomain.setPageParam(pageParam);
|
||||
return ResultDomain.success("查询知识库日志成功", pageDomain);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -16,7 +16,10 @@ import org.xyzh.ai.mapper.TbKnowledgeFileMapper;
|
||||
import org.xyzh.ai.mapper.TbKnowledgeMapper;
|
||||
import org.xyzh.api.ai.dto.TbKnowledge;
|
||||
import org.xyzh.api.ai.dto.TbKnowledgeFile;
|
||||
import org.xyzh.api.ai.dto.TbKnowledgeFileLog;
|
||||
import org.xyzh.api.ai.constance.KnowledgeFileLogAction;
|
||||
import org.xyzh.api.ai.service.AIFileUploadService;
|
||||
import org.xyzh.api.ai.service.KnowledgeFileLogService;
|
||||
import org.xyzh.api.ai.service.KnowledgeService;
|
||||
import org.xyzh.api.ai.vo.KnowledgeFileVO;
|
||||
import org.xyzh.api.file.dto.TbSysFileDTO;
|
||||
@@ -58,6 +61,9 @@ public class KnowledgeServiceImpl implements KnowledgeService {
|
||||
@DubboReference(version = "1.0.0", group = "file", timeout = 30000, retries = 0)
|
||||
private FileService fileService;
|
||||
|
||||
@Autowired
|
||||
private KnowledgeFileLogService knowledgeFileLogService;
|
||||
|
||||
@Autowired
|
||||
private AIFileUploadService aiFileUploadService;
|
||||
|
||||
@@ -537,6 +543,18 @@ public class KnowledgeServiceImpl implements KnowledgeService {
|
||||
int rows = knowledgeFileMapper.insertKnowledgeFile(knowledgeFile);
|
||||
if (rows > 0) {
|
||||
logger.info("保存知识库文件记录成功: knowledgeId={}, fileId={}, difyFileId={}", knowledgeId, fileId, difyFileId);
|
||||
// 记录日志
|
||||
TbKnowledgeFileLog log = new TbKnowledgeFileLog();
|
||||
log.setKnowledgeId(knowledgeId);
|
||||
log.setFileRootId(fileId);
|
||||
log.setFileId(fileId);
|
||||
log.setFileName(file.getOriginalFilename());
|
||||
log.setVersion(1);
|
||||
log.setAction(KnowledgeFileLogAction.UPLOAD.getAction());
|
||||
log.setService("workcase");
|
||||
log.setCreator(LoginUtil.getCurrentUserId());
|
||||
log.setCreatorName(LoginUtil.getCurrentUserName());
|
||||
knowledgeFileLogService.addKnowledgeFileLog(log);
|
||||
return ResultDomain.success("上传成功", knowledgeFile);
|
||||
}
|
||||
|
||||
@@ -683,6 +701,18 @@ public class KnowledgeServiceImpl implements KnowledgeService {
|
||||
int rows = knowledgeFileMapper.insertKnowledgeFile(newKnowledgeFile);
|
||||
if (rows > 0) {
|
||||
logger.info("保存新版本记录成功: knowledgeId={}, fileRootId={}, newVersion={}", knowledgeId, fileRootId, newVersion);
|
||||
// 记录日志
|
||||
TbKnowledgeFileLog log = new TbKnowledgeFileLog();
|
||||
log.setKnowledgeId(knowledgeId);
|
||||
log.setFileRootId(fileRootId);
|
||||
log.setFileId(newFileId);
|
||||
log.setFileName(file.getOriginalFilename());
|
||||
log.setVersion(newVersion);
|
||||
log.setAction(KnowledgeFileLogAction.UPDATE.getAction());
|
||||
log.setService("workcase");
|
||||
log.setCreator(LoginUtil.getCurrentUserId());
|
||||
log.setCreatorName(LoginUtil.getCurrentUserName());
|
||||
knowledgeFileLogService.addKnowledgeFileLog(log);
|
||||
return ResultDomain.success("更新成功", newKnowledgeFile);
|
||||
}
|
||||
|
||||
@@ -735,6 +765,15 @@ public class KnowledgeServiceImpl implements KnowledgeService {
|
||||
for (TbKnowledgeFile file : versions) {
|
||||
fileService.deleteFile(file.getFileId());
|
||||
}
|
||||
// 记录日志
|
||||
TbKnowledgeFileLog log = new TbKnowledgeFileLog();
|
||||
log.setKnowledgeId(knowledge.getKnowledgeId());
|
||||
log.setFileRootId(fileRootId);
|
||||
log.setAction(KnowledgeFileLogAction.DELETE.getAction());
|
||||
log.setService("workcase");
|
||||
log.setCreator(LoginUtil.getCurrentUserId());
|
||||
log.setCreatorName(LoginUtil.getCurrentUserName());
|
||||
knowledgeFileLogService.addKnowledgeFileLog(log);
|
||||
return ResultDomain.success("删除成功", true);
|
||||
}
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
<AppenderRef ref="RollingFile"/>
|
||||
</Logger>
|
||||
|
||||
<Logger name="org.xyzh.agent" level="debug" additivity="false">
|
||||
<Logger name="org.xyzh.ai" level="debug" additivity="false">
|
||||
<AppenderRef ref="Console"/>
|
||||
<AppenderRef ref="RollingFile"/>
|
||||
</Logger>
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="org.xyzh.ai.mapper.TbKnowledgeFileLogMapper">
|
||||
|
||||
<resultMap id="BaseResultMap" type="org.xyzh.api.ai.dto.TbKnowledgeFileLog">
|
||||
<result column="optsn" property="optsn" jdbcType="VARCHAR"/>
|
||||
<result column="log_id" property="logId" jdbcType="VARCHAR"/>
|
||||
<result column="knowledge_id" property="knowledgeId" jdbcType="VARCHAR"/>
|
||||
<result column="file_root_id" property="fileRootId" jdbcType="VARCHAR"/>
|
||||
<result column="file_id" property="fileId" jdbcType="VARCHAR"/>
|
||||
<result column="file_name" property="fileName" jdbcType="VARCHAR"/>
|
||||
<result column="action" property="action" jdbcType="VARCHAR"/>
|
||||
<result column="version" property="version" jdbcType="INTEGER"/>
|
||||
<result column="creator" property="creator" jdbcType="VARCHAR"/>
|
||||
<result column="creator_name" property="creatorName" jdbcType="VARCHAR"/>
|
||||
<result column="create_time" property="createTime" jdbcType="TIMESTAMP"/>
|
||||
</resultMap>
|
||||
|
||||
<sql id="Base_Column_List">
|
||||
optsn, log_id, knowledge_id, file_root_id, file_id, file_name, version,
|
||||
action, creator, creator_name, create_time
|
||||
</sql>
|
||||
|
||||
<insert id="addKnowledgeFileLog" parameterType="org.xyzh.api.ai.dto.TbKnowledgeFileLog">
|
||||
INSERT INTO ai.tb_knowledge_file_log (
|
||||
optsn, log_id, knowledge_id, file_root_id, file_id, file_name, version,
|
||||
action, service, creator, creator_name, create_time
|
||||
) VALUES (
|
||||
#{optsn}, #{logId}, #{knowledgeId}, #{fileRootId}, #{fileId}, #{fileName}, #{version},
|
||||
#{action}, #{service}, #{creator}, #{creatorName}, NOW()
|
||||
)
|
||||
</insert>
|
||||
|
||||
<select id="getKnowledgeFileLogList" resultMap="BaseResultMap">
|
||||
SELECT
|
||||
<include refid="Base_Column_List"/>
|
||||
FROM ai.tb_knowledge_file_log
|
||||
WHERE 1=1
|
||||
<if test="filter.knowledgeId != null and filter.knowledgeId != ''">
|
||||
AND knowledge_id = #{filter.knowledgeId}
|
||||
</if>
|
||||
<if test="filter.service != null and filter.service != ''">
|
||||
AND service = #{filter.service}
|
||||
</if>
|
||||
<if test="filter.fileName != null and filter.fileName != ''">
|
||||
AND file_name LIKE CONCAT('%', #{filter.fileName}, '%')
|
||||
</if>
|
||||
<if test="filter.creatorName != null and filter.creatorName != ''">
|
||||
AND creator_name LIKE CONCAT('%', #{filter.creatorName}, '%')
|
||||
</if>
|
||||
ORDER BY create_time DESC
|
||||
</select>
|
||||
|
||||
<select id="getKnowledgeFileLogPage" resultMap="BaseResultMap">
|
||||
SELECT
|
||||
<include refid="Base_Column_List"/>
|
||||
FROM ai.tb_knowledge_file_log
|
||||
WHERE 1=1
|
||||
<if test="filter.knowledgeId != null and filter.knowledgeId != ''">
|
||||
AND knowledge_id = #{filter.knowledgeId}
|
||||
</if>
|
||||
<if test="filter.service != null and filter.service != ''">
|
||||
AND service = #{filter.service}
|
||||
</if>
|
||||
<if test="filter.fileName != null and filter.fileName != ''">
|
||||
AND file_name LIKE CONCAT('%', #{filter.fileName}, '%')
|
||||
</if>
|
||||
<if test="filter.creatorName != null and filter.creatorName != ''">
|
||||
AND creator_name LIKE CONCAT('%', #{filter.creatorName}, '%')
|
||||
</if>
|
||||
ORDER BY create_time DESC
|
||||
LIMIT #{pageParam.pageSize} OFFSET #{pageParam.offset}
|
||||
</select>
|
||||
|
||||
<select id="countKnowledgeFileLog" resultType="int">
|
||||
SELECT
|
||||
count(log_id)
|
||||
FROM ai.tb_knowledge_file_log
|
||||
WHERE 1=1
|
||||
<if test="filter.knowledgeId != null and filter.knowledgeId != ''">
|
||||
AND knowledge_id = #{filter.knowledgeId}
|
||||
</if>
|
||||
<if test="filter.service != null and filter.service != ''">
|
||||
AND service = #{filter.service}
|
||||
</if>
|
||||
<if test="filter.fileName != null and filter.fileName != ''">
|
||||
AND file_name LIKE CONCAT('%', #{filter.fileName}, '%')
|
||||
</if>
|
||||
<if test="filter.creatorName != null and filter.creatorName != ''">
|
||||
AND creator_name LIKE CONCAT('%', #{filter.creatorName}, '%')
|
||||
</if>
|
||||
</select>
|
||||
</mapper>
|
||||
@@ -0,0 +1,28 @@
|
||||
package org.xyzh.api.ai.constance;
|
||||
|
||||
|
||||
public enum KnowledgeFileLogAction {
|
||||
UPLOAD("upload", "上传文件"),
|
||||
DELETE("delete", "删除文件"),
|
||||
DOWNLOAD("download", "下载文件"),
|
||||
UPDATE("update", "更新文件"),
|
||||
SEGMENT_CREATE("segment_create", "创建分段"),
|
||||
SEGMENT_UPDATE("segment_update", "更新分段"),
|
||||
SEGMENT_DELETE("segment_delete", "更新分段");
|
||||
|
||||
private String action;
|
||||
private String description;
|
||||
private KnowledgeFileLogAction(String action, String description) {
|
||||
this.action = action;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public String getAction() {
|
||||
return action;
|
||||
}
|
||||
|
||||
public String getDescription(){
|
||||
return description;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package org.xyzh.api.ai.dto;
|
||||
|
||||
import org.xyzh.common.dto.BaseDTO;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@Schema(description = "知识库文件日志")
|
||||
public class TbKnowledgeFileLog extends BaseDTO {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "日志Id")
|
||||
private String logId;
|
||||
|
||||
@Schema(description = "知识库Id")
|
||||
private String knowledgeId;
|
||||
|
||||
@Schema(description = "文件根Id")
|
||||
private String fileRootId;
|
||||
|
||||
@Schema(description = "文件Id")
|
||||
private String fileId;
|
||||
|
||||
@Schema(description = "文件名称")
|
||||
private String fileName;
|
||||
|
||||
@Schema(description = "服务名称")
|
||||
private String service;
|
||||
|
||||
@Schema(description = "文件版本")
|
||||
private Integer version;
|
||||
|
||||
@Schema(description = "操作")
|
||||
private String action;
|
||||
|
||||
@Schema(description = "操作人用户名")
|
||||
private String creatorName;
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package org.xyzh.api.ai.service;
|
||||
|
||||
import org.xyzh.api.ai.dto.TbKnowledgeFileLog;
|
||||
import org.xyzh.common.core.domain.ResultDomain;
|
||||
import org.xyzh.common.core.page.PageRequest;
|
||||
|
||||
/**
|
||||
* @description 知识库文件操作日志
|
||||
* @filename KnowledgeFileLogService.java
|
||||
* @author yslg
|
||||
* @copyright yslg
|
||||
* @since 2025-12-31
|
||||
*/
|
||||
public interface KnowledgeFileLogService {
|
||||
|
||||
/**
|
||||
* @description 新增知识库文件操作日志
|
||||
* @param knowledgeFileLog
|
||||
* @return 日志
|
||||
* @author yslg
|
||||
* @since 2025-12-31
|
||||
*/
|
||||
ResultDomain<TbKnowledgeFileLog> addKnowledgeFileLog(TbKnowledgeFileLog knowledgeFileLog);
|
||||
|
||||
/**
|
||||
* @description 查询知识库日志操作列表
|
||||
* @param filter
|
||||
* @return 日志列表
|
||||
* @author yslg
|
||||
* @since 2025-12-31
|
||||
*/
|
||||
ResultDomain<TbKnowledgeFileLog> getKnowledgeFileLogList(TbKnowledgeFileLog filter);
|
||||
|
||||
/**
|
||||
* @description 查询知识库日志操作分页
|
||||
* @param pageRequest
|
||||
* @return 日志分页数据
|
||||
* @author yslg
|
||||
* @since 2025-12-31
|
||||
*/
|
||||
ResultDomain<TbKnowledgeFileLog> getKnowledgeFileLogPage(PageRequest<TbKnowledgeFileLog> pageRequest);
|
||||
|
||||
}
|
||||
@@ -54,7 +54,7 @@
|
||||
|
||||
<insert id="insertChatMessage" parameterType="org.xyzh.api.workcase.dto.TbChatRoomMessageDTO">
|
||||
INSERT INTO workcase.tb_chat_room_message (
|
||||
optsn, message_id, room_id, sender_id, sender_type, sender_name, content, creator
|
||||
optsn, message_id, room_id, sender_id, sender_type, sender_name, content, creator,send_time
|
||||
<if test="messageType != null">, message_type</if>
|
||||
<if test="files != null">, files</if>
|
||||
<if test="contentExtra != null">, content_extra</if>
|
||||
@@ -62,10 +62,9 @@
|
||||
<if test="isAiMessage != null">, is_ai_message</if>
|
||||
<if test="aiMessageId != null">, ai_message_id</if>
|
||||
<if test="status != null">, status</if>
|
||||
<if test="sendTime != null">, send_time</if>
|
||||
<if test="createTime != null">, create_time</if>
|
||||
) VALUES (
|
||||
#{optsn}, #{messageId}, #{roomId}, #{senderId}, #{senderType}, #{senderName}, #{content}, #{creator}
|
||||
#{optsn}, #{messageId}, #{roomId}, #{senderId}, #{senderType}, #{senderName}, #{content}, #{creator},#{sendTime}
|
||||
<if test="messageType != null">, #{messageType}</if>
|
||||
<if test="files != null">, #{files, typeHandler=org.xyzh.common.jdbc.handler.StringArrayTypeHandler}</if>
|
||||
<if test="contentExtra != null">, #{contentExtra, typeHandler=org.xyzh.common.jdbc.handler.FastJson2TypeHandler}::jsonb</if>
|
||||
@@ -73,7 +72,6 @@
|
||||
<if test="isAiMessage != null">, #{isAiMessage}</if>
|
||||
<if test="aiMessageId != null">, #{aiMessageId}</if>
|
||||
<if test="status != null">, #{status}</if>
|
||||
<if test="sendTime != null">, send_time</if>
|
||||
<if test="createTime != null">, #{createTime}</if>
|
||||
)
|
||||
</insert>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { api } from '@/api/index'
|
||||
import type { ResultDomain, PageRequest } from '@/types'
|
||||
import type { TbKnowledge, TbKnowledgeFile, KnowledgeFileVO, SegmentRequestBody, DocumentStatusRequestBody } from '@/types/ai'
|
||||
import type { TbKnowledge, TbKnowledgeFile, KnowledgeFileVO, SegmentRequestBody, DocumentStatusRequestBody, TbKnowledgeFileLog } from '@/types/ai'
|
||||
|
||||
/**
|
||||
* @description AI知识库相关接口
|
||||
@@ -263,5 +263,25 @@ export const aiKnowledgeAPI = {
|
||||
requestBody
|
||||
)
|
||||
return response.data
|
||||
},
|
||||
|
||||
// ====================== 日志管理 ======================
|
||||
|
||||
/**
|
||||
* 查询知识库操作日志列表
|
||||
* @param fileLog 查询条件
|
||||
*/
|
||||
async getFileLogList(fileLog: TbKnowledgeFileLog): Promise<ResultDomain<TbKnowledgeFileLog>> {
|
||||
const response = await api.post<TbKnowledgeFileLog>(`${this.baseUrl}/datasets/log/list`, fileLog)
|
||||
return response.data
|
||||
},
|
||||
|
||||
/**
|
||||
* 分页查询知识库操作日志
|
||||
* @param pageRequest 分页请求
|
||||
*/
|
||||
async getFileLogPage(pageRequest: PageRequest<TbKnowledgeFileLog>): Promise<ResultDomain<TbKnowledgeFileLog>> {
|
||||
const response = await api.post<TbKnowledgeFileLog>(`${this.baseUrl}/datasets/log/page`, pageRequest)
|
||||
return response.data
|
||||
}
|
||||
}
|
||||
@@ -106,3 +106,27 @@ export interface SegmentRequestBody {
|
||||
export interface DocumentStatusRequestBody {
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
/**
|
||||
* 知识库文件操作日志
|
||||
*/
|
||||
export interface TbKnowledgeFileLog extends BaseDTO {
|
||||
/** 日志ID */
|
||||
logId?: string
|
||||
/** 知识库ID */
|
||||
knowledgeId?: string
|
||||
/** 文件根ID */
|
||||
fileRootId?: string
|
||||
/** 文件ID */
|
||||
fileId?: string
|
||||
/** 文件名称 */
|
||||
fileName?: string
|
||||
/** 服务名称 */
|
||||
service?: string
|
||||
/** 文件版本 */
|
||||
version?: number
|
||||
/** 操作类型 upload/download/delete/update */
|
||||
action?: string
|
||||
/** 操作人用户名 */
|
||||
creatorName?: string
|
||||
}
|
||||
@@ -139,7 +139,8 @@ declare module 'shared/types' {
|
||||
ChatMessageListParam,
|
||||
SSEMessageData,
|
||||
SSECallbacks,
|
||||
SSETask
|
||||
SSETask,
|
||||
TbKnowledgeFileLog
|
||||
} from '../../../shared/src/types/ai'
|
||||
|
||||
// 重新导出 menu
|
||||
|
||||
@@ -11,49 +11,72 @@
|
||||
<!-- 筛选区域 -->
|
||||
<el-card class="filter-card">
|
||||
<div class="ticket-filters">
|
||||
<el-date-picker v-model="dateRange" type="daterange" range-separator="至" start-placeholder="开始日期" end-placeholder="结束日期" style="width: 280px;" />
|
||||
<el-date-picker
|
||||
v-model="dateRange"
|
||||
type="daterange"
|
||||
range-separator="至"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
style="width: 280px;"
|
||||
@change="handleSearch"
|
||||
/>
|
||||
|
||||
<div class="filter-right">
|
||||
<el-select v-model="operationFilter" placeholder="操作类型" clearable style="width: 140px;">
|
||||
<el-select v-model="filter.action" placeholder="操作类型" clearable style="width: 140px;" @change="handleSearch">
|
||||
<el-option label="上传" value="upload" />
|
||||
<el-option label="下载" value="download" />
|
||||
<el-option label="删除" value="delete" />
|
||||
<el-option label="更新" value="update" />
|
||||
</el-select>
|
||||
<el-select v-model="kbTypeFilter" placeholder="知识库类型" clearable style="width: 140px;">
|
||||
<el-option label="外部知识库" value="external" />
|
||||
<el-option label="内部知识库" value="internal" />
|
||||
<el-select v-model="filter.knowledgeId" placeholder="知识库" clearable style="width: 180px;" @change="handleSearch">
|
||||
<el-option
|
||||
v-for="kb in knowledgeList"
|
||||
:key="kb.knowledgeId"
|
||||
:label="kb.title"
|
||||
:value="kb.knowledgeId"
|
||||
/>
|
||||
</el-select>
|
||||
<el-input v-model="searchKeyword" placeholder="搜索文件名/操作人" style="width: 200px;" :prefix-icon="Search" clearable />
|
||||
<el-input
|
||||
v-model="filter.fileName"
|
||||
placeholder="搜索文件名"
|
||||
style="width: 200px;"
|
||||
:prefix-icon="Search"
|
||||
clearable
|
||||
@clear="handleSearch"
|
||||
@keyup.enter="handleSearch"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 日志列表 -->
|
||||
<el-card>
|
||||
<el-table :data="filteredLogs" style="width: 100%">
|
||||
<el-table-column prop="logId" label="日志ID" width="120">
|
||||
<el-card v-loading="loading">
|
||||
<el-table :data="logs" style="width: 100%">
|
||||
<el-table-column prop="logId" label="日志ID" width="180">
|
||||
<template #default="{ row }">
|
||||
<span style="color: #409eff; font-weight: 500;">{{ row.logId }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="fileName" label="文件名" min-width="200" />
|
||||
<el-table-column prop="operation" label="操作类型" width="100">
|
||||
<el-table-column prop="fileName" label="文件名" min-width="200" show-overflow-tooltip />
|
||||
<el-table-column prop="action" label="操作类型" width="100">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="getOperationType(row.operation)" size="small">
|
||||
{{ row.operationName }}
|
||||
<el-tag :type="getOperationType(row.action)" size="small">
|
||||
{{ getOperationName(row.action) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="kbType" label="知识库" width="100">
|
||||
<el-table-column prop="version" label="版本" width="80">
|
||||
<template #default="{ row }">
|
||||
<span>{{ row.kbType === 'external' ? '外部' : '内部' }}</span>
|
||||
<span>v{{ row.version || 1 }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="operator" label="操作人" width="100" />
|
||||
<el-table-column prop="operationTime" label="操作时间" width="160" />
|
||||
<el-table-column prop="fileSize" label="文件大小" width="100" />
|
||||
<el-table-column label="操作" width="120" fixed="right">
|
||||
<el-table-column prop="creatorName" label="操作人" width="120" />
|
||||
<el-table-column prop="createTime" label="操作时间" width="180">
|
||||
<template #default="{ row }">
|
||||
{{ formatTime(row.createTime) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="100" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button type="primary" link size="small" @click="viewDetail(row)">详情</el-button>
|
||||
</template>
|
||||
@@ -61,7 +84,15 @@
|
||||
</el-table>
|
||||
|
||||
<div class="table-pagination">
|
||||
<el-pagination v-model:current-page="currentPage" :page-size="10" :total="logs.length" layout="total, prev, pager, next" />
|
||||
<el-pagination
|
||||
v-model:current-page="pagination.page"
|
||||
v-model:page-size="pagination.pageSize"
|
||||
:page-sizes="[10, 20, 50]"
|
||||
:total="pagination.total"
|
||||
layout="total, sizes, prev, pager, next"
|
||||
@size-change="loadLogs"
|
||||
@current-change="loadLogs"
|
||||
/>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
@@ -76,19 +107,22 @@
|
||||
<span>{{ selectedLog.fileName }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="操作类型">
|
||||
<el-tag :type="getOperationType(selectedLog.operation)">{{ selectedLog.operationName }}</el-tag>
|
||||
<el-tag :type="getOperationType(selectedLog.action)">{{ getOperationName(selectedLog.action) }}</el-tag>
|
||||
</el-form-item>
|
||||
<el-form-item label="知识库">
|
||||
<span>{{ selectedLog.kbType === 'external' ? '外部知识库' : '内部知识库' }}</span>
|
||||
<el-form-item label="文件版本">
|
||||
<span>v{{ selectedLog.version || 1 }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="操作人">
|
||||
<span>{{ selectedLog.operator }}</span>
|
||||
<span>{{ selectedLog.creatorName }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="操作时间">
|
||||
<span>{{ selectedLog.operationTime }}</span>
|
||||
<span>{{ formatTime(selectedLog.createTime) }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="文件大小">
|
||||
<span>{{ selectedLog.fileSize }}</span>
|
||||
<el-form-item label="知识库ID">
|
||||
<span>{{ selectedLog.knowledgeId }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="文件ID">
|
||||
<span>{{ selectedLog.fileId }}</span>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-dialog>
|
||||
@@ -96,63 +130,140 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import AdminLayout from '@/views/admin/AdminLayout.vue'
|
||||
import { Download, Search } from 'lucide-vue-next'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { aiKnowledgeAPI } from 'shared/api/ai'
|
||||
import type { TbKnowledgeFileLog, TbKnowledge } from 'shared/types'
|
||||
|
||||
const loading = ref(false)
|
||||
const dateRange = ref<[Date, Date] | null>(null)
|
||||
const operationFilter = ref('')
|
||||
const kbTypeFilter = ref('')
|
||||
const searchKeyword = ref('')
|
||||
const currentPage = ref(1)
|
||||
const showDetailDialog = ref(false)
|
||||
const selectedLog = ref<any>(null)
|
||||
const selectedLog = ref<TbKnowledgeFileLog | null>(null)
|
||||
|
||||
const logs = ref([
|
||||
{ logId: 'LOG001', fileName: 'TH-500GF操作手册.pdf', operation: 'upload', operationName: '上传', kbType: 'external', operator: '张三', operationTime: '2024-12-10 14:30', fileSize: '2.5MB' },
|
||||
{ logId: 'LOG002', fileName: 'TH-300D故障排查指南.pdf', operation: 'upload', operationName: '上传', kbType: 'external', operator: '李四', operationTime: '2024-12-09 10:15', fileSize: '1.8MB' },
|
||||
{ logId: 'LOG003', fileName: '内部技术规范v2.0.pdf', operation: 'update', operationName: '更新', kbType: 'internal', operator: '赵六', operationTime: '2024-12-11 16:20', fileSize: '3.2MB' },
|
||||
{ logId: 'LOG004', fileName: '售后服务流程.pdf', operation: 'download', operationName: '下载', kbType: 'internal', operator: '孙七', operationTime: '2024-12-10 11:00', fileSize: '1.1MB' },
|
||||
{ logId: 'LOG005', fileName: '员工培训手册.pdf', operation: 'delete', operationName: '删除', kbType: 'internal', operator: '周八', operationTime: '2024-12-09 15:30', fileSize: '2.0MB' }
|
||||
])
|
||||
|
||||
const filteredLogs = computed(() => {
|
||||
let result = logs.value
|
||||
if (operationFilter.value) {
|
||||
result = result.filter(l => l.operation === operationFilter.value)
|
||||
}
|
||||
if (kbTypeFilter.value) {
|
||||
result = result.filter(l => l.kbType === kbTypeFilter.value)
|
||||
}
|
||||
if (searchKeyword.value) {
|
||||
const keyword = searchKeyword.value.toLowerCase()
|
||||
result = result.filter(l =>
|
||||
l.fileName.toLowerCase().includes(keyword) ||
|
||||
l.operator.toLowerCase().includes(keyword)
|
||||
)
|
||||
}
|
||||
return result.slice((currentPage.value - 1) * 10, currentPage.value * 10)
|
||||
// 筛选条件
|
||||
const filter = reactive<TbKnowledgeFileLog>({
|
||||
action: '',
|
||||
knowledgeId: '',
|
||||
fileName: '',
|
||||
service: 'workcase'
|
||||
})
|
||||
|
||||
const getOperationType = (operation: string) => {
|
||||
const map: Record<string, string> = {
|
||||
// 分页
|
||||
const pagination = reactive({
|
||||
page: 1,
|
||||
pageSize: 10,
|
||||
total: 0
|
||||
})
|
||||
|
||||
// 日志列表
|
||||
const logs = ref<TbKnowledgeFileLog[]>([])
|
||||
|
||||
// 知识库列表(用于筛选)
|
||||
const knowledgeList = ref<TbKnowledge[]>([])
|
||||
|
||||
// 操作类型映射
|
||||
const operationTypeMap: Record<string, string> = {
|
||||
upload: 'success',
|
||||
download: 'info',
|
||||
delete: 'danger',
|
||||
update: 'warning'
|
||||
}
|
||||
return map[operation] || 'info'
|
||||
}
|
||||
|
||||
const viewDetail = (row: any) => {
|
||||
const operationNameMap: Record<string, string> = {
|
||||
upload: '上传',
|
||||
download: '下载',
|
||||
delete: '删除',
|
||||
update: '更新'
|
||||
}
|
||||
|
||||
const getOperationType = (action: string) => operationTypeMap[action] || 'info'
|
||||
const getOperationName = (action: string) => operationNameMap[action] || action
|
||||
|
||||
// 格式化时间
|
||||
const formatTime = (time?: string | number): string => {
|
||||
if (!time) return '-'
|
||||
const date = new Date(time)
|
||||
return date.toLocaleString('zh-CN', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit'
|
||||
})
|
||||
}
|
||||
|
||||
// 加载知识库列表
|
||||
const loadKnowledgeList = async () => {
|
||||
try {
|
||||
const result = await aiKnowledgeAPI.listKnowledges({})
|
||||
if (result.success && result.dataList) {
|
||||
knowledgeList.value = result.dataList
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载知识库列表失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 加载日志列表
|
||||
const loadLogs = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
// 构建查询条件
|
||||
const queryFilter: TbKnowledgeFileLog = { ...filter }
|
||||
console.log(queryFilter)
|
||||
// 处理日期范围
|
||||
if (dateRange.value && dateRange.value[0] && dateRange.value[1]) {
|
||||
queryFilter.createTimeStart = dateRange.value[0].toISOString()
|
||||
queryFilter.createTimeEnd = dateRange.value[1].toISOString()
|
||||
}
|
||||
|
||||
const result = await aiKnowledgeAPI.getFileLogPage({
|
||||
filter: queryFilter,
|
||||
pageParam: {
|
||||
page: pagination.page,
|
||||
pageSize: pagination.pageSize,
|
||||
total: 0
|
||||
}
|
||||
})
|
||||
|
||||
if (result.success) {
|
||||
logs.value = result.dataList || []
|
||||
pagination.total = result.pageParam?.total || 0
|
||||
} else {
|
||||
ElMessage.error(result.message || '加载日志失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载日志失败:', error)
|
||||
ElMessage.error('加载日志失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 搜索
|
||||
const handleSearch = () => {
|
||||
pagination.page = 1
|
||||
loadLogs()
|
||||
}
|
||||
|
||||
// 查看详情
|
||||
const viewDetail = (row: TbKnowledgeFileLog) => {
|
||||
selectedLog.value = row
|
||||
showDetailDialog.value = true
|
||||
}
|
||||
|
||||
// 导出日志
|
||||
const exportLogs = () => {
|
||||
ElMessage.success('日志导出成功')
|
||||
ElMessage.success('日志导出功能开发中')
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadKnowledgeList()
|
||||
loadLogs()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
Reference in New Issue
Block a user