chore: initialize qiming workspace repository
This commit is contained in:
@@ -0,0 +1,112 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>app-platform-log</artifactId>
|
||||
<groupId>com.xspaceagi</groupId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>app-platform-log-api</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.xspaceagi</groupId>
|
||||
<artifactId>app-platform-log-application</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.xspaceagi</groupId>
|
||||
<artifactId>system-domain</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<!-- 打包插件 -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-assembly-plugin</artifactId>
|
||||
<version>2.4</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>make-assembly</id>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>single</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<descriptors>
|
||||
<!-- 打包配置:必须在对应目录下创建repack.xml的打包配置文件 -->
|
||||
<descriptor>src/main/resources/assemblies/repack.xml</descriptor>
|
||||
</descriptors>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<!-- install插件,定制在mvn install过程使用哪个jar包安装到本地 -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-install-plugin</artifactId>
|
||||
<version>2.5.2</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>install-file</id>
|
||||
<goals>
|
||||
<goal>install-file</goal>
|
||||
</goals>
|
||||
<phase>install</phase>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<!-- install配置,指定将哪个目录下的jar包install为这个工程的jar -->
|
||||
<file>${project.build.directory}/${project.build.finalName}-repack.jar</file>
|
||||
<!--
|
||||
install配置,指定将install的jar打包使用的pom.xml文件,
|
||||
即其他依赖这个jar包的工程按照哪个pom.xml文件来解析依赖
|
||||
-->
|
||||
<pomFile>src/main/resources/install-pom.xml</pomFile>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<!-- deploy插件,定制在mvn deploy过程使用哪个jar包发布到仓库 -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-deploy-plugin</artifactId>
|
||||
<version>2.8.2</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>deploy-file</id>
|
||||
<goals>
|
||||
<goal>deploy-file</goal>
|
||||
</goals>
|
||||
<!-- 跳过默认的发布包 -->
|
||||
<phase>none</phase>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<!-- deploy配置,指定将哪个目录下的jar包发布为这个工程的jar -->
|
||||
<file>${project.build.directory}/${project.build.finalName}-repack.jar</file>
|
||||
<!-- 发布地址 -->
|
||||
<url>http://local-maven.space.com/nexus/content/repositories/snapshots/</url>
|
||||
<repositoryId>nexus-snapshots</repositoryId>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<artifactId>${project.artifactId}</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<packaging>jar</packaging>
|
||||
<!--
|
||||
deploy配置,指定将deploy的jar打包使用的pom.xml文件,
|
||||
即其他依赖这个jar包的工程按照哪个pom.xml文件来解析依赖
|
||||
-->
|
||||
<pomFile>src/main/resources/install-pom.xml</pomFile>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
|
||||
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,94 @@
|
||||
package com.xspaceagi.log.api.convert;
|
||||
|
||||
import com.xspaceagi.domain.model.AgentLogModel;
|
||||
import com.xspaceagi.domain.model.valueobj.AgentLogEntry;
|
||||
import com.xspaceagi.domain.model.valueobj.AgentLogSearchParams;
|
||||
import com.xspaceagi.log.sdk.reponse.AgentLogModelResponse;
|
||||
import com.xspaceagi.log.sdk.request.AgentLogEntryRequest;
|
||||
import com.xspaceagi.log.sdk.request.AgentLogSearchParamsRequest;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
public class AgentLogEntryConvert {
|
||||
|
||||
public AgentLogEntry convertFrom(AgentLogEntryRequest request) {
|
||||
AgentLogEntry agentLogEntry = AgentLogEntry.builder()
|
||||
.requestId(request.getRequestId())
|
||||
.messageId(request.getMessageId())
|
||||
.conversationId(request.getConversationId())
|
||||
.agentId(request.getAgentId())
|
||||
.userUid(request.getUserUid())
|
||||
.tenantId(request.getTenantId())
|
||||
.spaceId(request.getSpaceId())
|
||||
.userInput(request.getUserInput())
|
||||
.output(request.getOutput())
|
||||
.executeResult(request.getExecuteResult())
|
||||
.inputToken(request.getInputToken())
|
||||
.outputToken(request.getOutputToken())
|
||||
.requestStartTime(request.getRequestStartTime())
|
||||
.requestEndTime(request.getRequestEndTime())
|
||||
.elapsedTimeMs(request.getElapsedTimeMs())
|
||||
.nodeType(request.getNodeType())
|
||||
.status(request.getStatus())
|
||||
.nodeName(request.getNodeName())
|
||||
.createdAt(LocalDateTime.now())
|
||||
.updatedAt(LocalDateTime.now())
|
||||
.userId(request.getUserId())
|
||||
.userName(request.getUserName())
|
||||
.build();
|
||||
return agentLogEntry;
|
||||
|
||||
}
|
||||
|
||||
public AgentLogSearchParams convertFrom(AgentLogSearchParamsRequest request) {
|
||||
AgentLogSearchParams agentLogSearchParams = AgentLogSearchParams.builder()
|
||||
.requestId(request.getRequestId())
|
||||
.conversationId(request.getConversationId())
|
||||
.messageId(request.getMessageId())
|
||||
.agentId(request.getAgentId())
|
||||
.userUid(request.getUserUid())
|
||||
.userInput(request.getUserInput())
|
||||
.output(request.getOutput())
|
||||
.startTime(request.getStartTime())
|
||||
.endTime(request.getEndTime())
|
||||
.tenantId(request.getTenantId())
|
||||
.spaceId(request.getSpaceId())
|
||||
.build();
|
||||
return agentLogSearchParams;
|
||||
|
||||
}
|
||||
|
||||
public AgentLogModelResponse convertTo(AgentLogModel agentLogModel) {
|
||||
AgentLogModelResponse agentLogModelResponse = AgentLogModelResponse.builder()
|
||||
.requestId(agentLogModel.getRequestId())
|
||||
.messageId(agentLogModel.getMessageId())
|
||||
.agentId(agentLogModel.getAgentId())
|
||||
.conversationId(agentLogModel.getConversationId())
|
||||
.userUid(agentLogModel.getUserUid())
|
||||
.tenantId(agentLogModel.getTenantId())
|
||||
.spaceId(agentLogModel.getSpaceId())
|
||||
.userInput(agentLogModel.getUserInput())
|
||||
.output(agentLogModel.getOutput())
|
||||
.executeResult(agentLogModel.getExecuteResult())
|
||||
.inputToken(agentLogModel.getInputToken())
|
||||
.outputToken(agentLogModel.getOutputToken())
|
||||
.requestStartTime(agentLogModel.getRequestStartTime())
|
||||
.requestEndTime(agentLogModel.getRequestEndTime())
|
||||
.elapsedTimeMs(agentLogModel.getElapsedTimeMs())
|
||||
.nodeType(agentLogModel.getNodeType())
|
||||
.status(agentLogModel.getStatus())
|
||||
.nodeName(agentLogModel.getNodeName())
|
||||
.createdAt(agentLogModel.getCreatedAt())
|
||||
.updatedAt(agentLogModel.getUpdatedAt())
|
||||
.userId(agentLogModel.getUserId())
|
||||
.userName(agentLogModel.getUserName())
|
||||
.build();
|
||||
return agentLogModelResponse;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package com.xspaceagi.log.api.service;
|
||||
|
||||
import com.xspaceagi.log.api.convert.AgentLogEntryConvert;
|
||||
import com.xspaceagi.system.domain.log.LogRecordPrint;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.xspaceagi.domain.adaptor.impl.LogHttpClientAdapter;
|
||||
import com.xspaceagi.domain.model.AgentLogModel;
|
||||
import com.xspaceagi.domain.model.valueobj.AgentLogSearchParams;
|
||||
import com.xspaceagi.domain.service.ILogPlatformDomainService;
|
||||
import com.xspaceagi.log.sdk.reponse.AgentLogModelResponse;
|
||||
import com.xspaceagi.log.sdk.request.AgentLogEntryRequest;
|
||||
import com.xspaceagi.log.sdk.request.AgentLogSearchParamsRequest;
|
||||
import com.xspaceagi.log.sdk.service.LogPlatformRpcService;
|
||||
import com.xspaceagi.system.spec.utils.ValidateUtil;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
public class LogPlatformRpcServiceImpl implements LogPlatformRpcService {
|
||||
|
||||
@Resource
|
||||
private AgentLogEntryConvert agentLogEntryConvert;
|
||||
|
||||
@Resource
|
||||
private LogHttpClientAdapter logHttpClientAdapter;
|
||||
|
||||
@Resource
|
||||
private ILogPlatformDomainService logPlatformDomainService;
|
||||
|
||||
@LogRecordPrint(content = "[智能体日志]-新增日志")
|
||||
@Override
|
||||
public boolean addLog(AgentLogEntryRequest logEntryRequest) {
|
||||
log.info("Adding agent log, requestId: {}", logEntryRequest.getRequestId());
|
||||
// 校验必填参数
|
||||
ValidateUtil.validate(logEntryRequest);
|
||||
|
||||
var agentLogEntry = agentLogEntryConvert.convertFrom(logEntryRequest);
|
||||
return logHttpClientAdapter.addAgentLog(agentLogEntry);
|
||||
}
|
||||
|
||||
@LogRecordPrint(content = "[智能体日志]-搜索日志")
|
||||
@Override
|
||||
public IPage<AgentLogModelResponse> searchAgentLogs(AgentLogSearchParamsRequest searchParams, Long current,
|
||||
Long pageSize) {
|
||||
|
||||
AgentLogSearchParams agentLogSearchParams = agentLogEntryConvert.convertFrom(searchParams);
|
||||
IPage<AgentLogModel> result = logPlatformDomainService.searchAgentLogs(agentLogSearchParams, current, pageSize);
|
||||
return result.convert(agentLogEntryConvert::convertTo);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<assembly xmlns="http://maven.apache.org/ASSEMBLY/2.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.0.0 http://maven.apache.org/xsd/assembly-2.0.0.xsd">
|
||||
<id>repack</id>
|
||||
<formats>
|
||||
<format>jar</format>
|
||||
</formats>
|
||||
|
||||
<includeBaseDirectory>false</includeBaseDirectory>
|
||||
<dependencySets>
|
||||
<dependencySet>
|
||||
<!--
|
||||
重新打包需要包含的jar包,这个过程会解压所有的jar包并按照jar的规则重新组合成一个jar包
|
||||
这里需要包含当前这个工程 ,格式是{groupId}:{artifactId}
|
||||
-->
|
||||
<includes>
|
||||
<!-- 包含项目自己 -->
|
||||
<include>com.xspaceagi:app-platform-log-api</include>
|
||||
<include>com.xspaceagi:app-platform-log-application</include>
|
||||
<include>com.xspaceagi:app-platform-log-domain</include>
|
||||
<include>com.xspaceagi:app-platform-log-infra</include>
|
||||
<include>com.xspaceagi:app-platform-log-spec</include>
|
||||
</includes>
|
||||
<!-- 打包结果目录:/ 表示target/ -->
|
||||
<outputDirectory>/</outputDirectory>
|
||||
<!-- 是否解压依赖 -->
|
||||
<unpack>true</unpack>
|
||||
<!-- 打包的scope -->
|
||||
<!-- <scope>system</scope>-->
|
||||
</dependencySet>
|
||||
</dependencySets>
|
||||
</assembly>
|
||||
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>app-platform-log-api</artifactId>
|
||||
<groupId>com.xspaceagi</groupId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>app-platform-log-api</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>app-platform-log</artifactId>
|
||||
<groupId>com.xspaceagi</groupId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>app-platform-log-application</artifactId>
|
||||
|
||||
<properties>
|
||||
<maven.deploy.skip>true</maven.deploy.skip>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.xspaceagi</groupId>
|
||||
<artifactId>system-sdk</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.xspaceagi</groupId>
|
||||
<artifactId>app-platform-log-domain</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.xspaceagi</groupId>
|
||||
<artifactId>app-platform-log-infra</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,102 @@
|
||||
package com.xspaceagi.log.app.service;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.xspaceagi.log.sdk.request.DocumentSearchRequest;
|
||||
import com.xspaceagi.log.sdk.service.ILogRpcService;
|
||||
import com.xspaceagi.log.sdk.service.ISearchRpcService;
|
||||
import com.xspaceagi.log.sdk.vo.LogDocument;
|
||||
import com.xspaceagi.log.sdk.vo.SearchDocument;
|
||||
import com.xspaceagi.log.sdk.vo.SearchResult;
|
||||
import com.xspaceagi.system.sdk.common.TraceContext;
|
||||
import com.xspaceagi.system.sdk.service.AbstractTaskExecuteService;
|
||||
import com.xspaceagi.system.sdk.service.ScheduleTaskApiService;
|
||||
import com.xspaceagi.system.sdk.service.dto.ScheduleTaskDto;
|
||||
import com.xspaceagi.system.spec.utils.RedisUtil;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Slf4j
|
||||
@Service("logApplicationService")
|
||||
public class LogApplicationServiceImpl extends AbstractTaskExecuteService implements ILogRpcService {
|
||||
|
||||
@Resource
|
||||
private ScheduleTaskApiService scheduleTaskApiService;
|
||||
|
||||
@Resource
|
||||
private RedisUtil redisUtil;
|
||||
|
||||
@Resource
|
||||
private ISearchRpcService iSearchRpcService;
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
scheduleTaskApiService.start(ScheduleTaskDto.builder()
|
||||
.taskId("logApplicationService")
|
||||
.beanId("logApplicationService")
|
||||
.maxExecTimes(Long.MAX_VALUE)
|
||||
.cron(ScheduleTaskDto.Cron.EVERY_2_SECOND.getCron())
|
||||
.params(Map.of())
|
||||
.build());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean execute(ScheduleTaskDto scheduleTaskDto) {
|
||||
try {
|
||||
checkAndPushLogDocument0();
|
||||
} catch (Exception e) {
|
||||
log.error("checkAndPushLogDocument0 error", e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void checkAndPushLogDocument0() {
|
||||
List<LogDocument> logDocumentList = new ArrayList<>();
|
||||
Object val = redisUtil.rightPop("log:queue");
|
||||
while (val != null) {
|
||||
LogDocument logDocument = JSON.parseObject(val.toString(), LogDocument.class);
|
||||
if (logDocument != null) {
|
||||
logDocumentList.add(logDocument);
|
||||
}
|
||||
val = redisUtil.rightPop("log:queue");
|
||||
}
|
||||
if (CollectionUtils.isNotEmpty(logDocumentList)) {
|
||||
log.info("checkAndPushLogDocument0 logDocumentList size: {}", logDocumentList.size());
|
||||
iSearchRpcService.bulkIndex(logDocumentList.stream().map(logDocument -> (SearchDocument) logDocument).collect(Collectors.toList()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteLogDocument(String id) {
|
||||
iSearchRpcService.deleteDocument(LogDocument.class, id);
|
||||
}
|
||||
|
||||
public void bulkIndex(List<LogDocument> list) {
|
||||
if (CollectionUtils.isEmpty(list)) {
|
||||
return;
|
||||
}
|
||||
list.forEach(item -> redisUtil.rightPush("log:queue", JSON.toJSONString(item)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void pushTraceLog(Object traceContext0) {
|
||||
if (traceContext0 == null) {
|
||||
return;
|
||||
}
|
||||
redisUtil.rightPush("log:queue", JSON.toJSONString(((TraceContext) traceContext0).getLog()));
|
||||
((TraceContext) traceContext0).setLog(null);
|
||||
redisUtil.rightPush("bill:queue", JSON.toJSONString(traceContext0));
|
||||
}
|
||||
|
||||
public SearchResult search(DocumentSearchRequest documentSearchRequest) {
|
||||
return iSearchRpcService.search(documentSearchRequest);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package com.xspaceagi.log.app.service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.xspaceagi.domain.model.AgentLogModel;
|
||||
import com.xspaceagi.domain.model.valueobj.AgentLogEntry;
|
||||
import com.xspaceagi.domain.model.valueobj.AgentLogSearchParams;
|
||||
import com.xspaceagi.domain.service.ILogPlatformDomainService;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* 日志平台应用服务
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class LogPlatformApplicationService {
|
||||
|
||||
@Resource
|
||||
private ILogPlatformDomainService logPlatformDomainService;
|
||||
|
||||
/**
|
||||
* 搜索智能体日志
|
||||
*/
|
||||
public IPage<AgentLogModel> searchAgentLogs(AgentLogSearchParams searchParams,
|
||||
Long current,
|
||||
Long pageSize) {
|
||||
// 调用领域服务
|
||||
return logPlatformDomainService.searchAgentLogs(searchParams, current, pageSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* 详情
|
||||
*/
|
||||
public AgentLogModel queryOneAgentLog(AgentLogSearchParams searchParams) {
|
||||
return logPlatformDomainService.queryOneAgentLog(searchParams);
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增单个智能体日志
|
||||
*/
|
||||
public boolean addAgentLog(AgentLogEntry logEntry) {
|
||||
return logPlatformDomainService.addAgentLog(logEntry);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量新增智能体日志
|
||||
*/
|
||||
public boolean batchAddAgentLogs(List<AgentLogEntry> logEntries) {
|
||||
return logPlatformDomainService.batchAddAgentLogs(logEntries);
|
||||
}
|
||||
|
||||
/**
|
||||
* 健康检查
|
||||
*/
|
||||
public boolean healthCheck() {
|
||||
return logPlatformDomainService.healthCheck();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,269 @@
|
||||
# LogHttpClientAdapter 使用说明
|
||||
|
||||
## 概述
|
||||
|
||||
`LogHttpClientAdapter` 是一个用于与 Rust 日志服务进行 HTTP 通信的客户端适配器。它提供了与 Rust 工程接口一一对应的 HTTP 请求方法,支持智能体日志的新增、批量新增和搜索功能。
|
||||
|
||||
## 功能特性
|
||||
|
||||
- **健康检查**:检查 Rust 服务的健康状态
|
||||
- **就绪检查**:检查 Rust 服务是否就绪
|
||||
- **日志新增**:支持单个和批量新增智能体日志
|
||||
- **日志搜索**:支持条件查询、分页和排序
|
||||
- **异步支持**:所有方法都提供异步和同步两个版本
|
||||
- **配置化**:通过配置文件管理连接参数
|
||||
- **配置验证**:启动时验证必需配置,缺失时抛出异常
|
||||
|
||||
## 配置说明
|
||||
|
||||
在主配置文件(如 `application-dev.yml`)中添加以下配置:
|
||||
|
||||
```yaml
|
||||
log-module:
|
||||
log:
|
||||
rust-service:
|
||||
# Rust 日志服务的基础 URL(必填)
|
||||
base-url: http://localhost:8098
|
||||
# 连接超时时间(秒),默认 10
|
||||
connect-timeout-seconds: 10
|
||||
# 写入超时时间(秒),默认 30
|
||||
write-timeout-seconds: 30
|
||||
# 读取超时时间(秒),默认 30
|
||||
read-timeout-seconds: 30
|
||||
# 是否启用连接池,默认 true
|
||||
enable-connection-pool: true
|
||||
# 最大空闲连接数,默认 5
|
||||
max-idle-connections: 5
|
||||
# 连接保持存活时间(分钟),默认 5
|
||||
keep-alive-duration-minutes: 5
|
||||
|
||||
# 日志级别配置(复用现有的 log.level 配置)
|
||||
log:
|
||||
level:
|
||||
com.xspaceagi.domain.service.LogHttpClientAdapter: DEBUG
|
||||
```
|
||||
|
||||
**注意**:`base-url` 是必填配置项,如果未配置或为空,应用启动时会抛出异常。
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 1. 依赖注入
|
||||
|
||||
```java
|
||||
@Service
|
||||
public class LogService {
|
||||
|
||||
@Autowired
|
||||
private LogHttpClientAdapter logHttpClientAdapter;
|
||||
|
||||
// ... 业务方法
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 健康检查
|
||||
|
||||
```java
|
||||
// 异步方式
|
||||
CompletableFuture<Boolean> healthFuture = logHttpClientAdapter.healthCheck();
|
||||
Boolean isHealthy = healthFuture.get();
|
||||
|
||||
// 同步方式(不推荐在高并发场景使用)
|
||||
Boolean isHealthy = logHttpClientAdapter.healthCheck().join();
|
||||
```
|
||||
|
||||
### 3. 新增单个日志
|
||||
|
||||
```java
|
||||
AgentLogEntry logEntry = AgentLogEntry.builder()
|
||||
.requestId("req_001")
|
||||
.conversationId("session_001")
|
||||
.userUid("user_123")
|
||||
.tenantId("tenant_001")
|
||||
.userInput("用户输入内容")
|
||||
.output("系统输出内容")
|
||||
.inputToken(50)
|
||||
.outputToken(100)
|
||||
.requestStartTime(Instant.now().minusSeconds(5))
|
||||
.requestEndTime(Instant.now())
|
||||
.elapsedTimeMs(5000L)
|
||||
.status("success")
|
||||
.build();
|
||||
|
||||
// 异步方式
|
||||
CompletableFuture<Boolean> result = logHttpClientAdapter.addAgentLog(logEntry);
|
||||
|
||||
// 同步方式
|
||||
boolean success = logHttpClientAdapter.addAgentLogSync(logEntry);
|
||||
```
|
||||
|
||||
### 4. 批量新增日志
|
||||
|
||||
```java
|
||||
List<AgentLogEntry> logEntries = Arrays.asList(
|
||||
AgentLogEntry.builder()...build(),
|
||||
AgentLogEntry.builder()...build()
|
||||
);
|
||||
|
||||
// 异步方式
|
||||
CompletableFuture<Boolean> result = logHttpClientAdapter.batchAddAgentLogs(logEntries);
|
||||
|
||||
// 同步方式
|
||||
boolean success = logHttpClientAdapter.batchAddAgentLogsSync(logEntries);
|
||||
```
|
||||
|
||||
### 5. 搜索日志
|
||||
|
||||
```java
|
||||
// 构建搜索条件
|
||||
AgentLogSearchParams searchParams = AgentLogSearchParams.builder()
|
||||
.tenantId("tenant_001")
|
||||
.userUid("user_123")
|
||||
.userInput("搜索关键词")
|
||||
.startTime(Instant.now().minus(Duration.ofDays(1)))
|
||||
.endTime(Instant.now())
|
||||
.build();
|
||||
|
||||
// 构建搜索请求
|
||||
AgentLogSearchRequest searchRequest = AgentLogSearchRequest.builder()
|
||||
.queryFilter(searchParams)
|
||||
.current(1L)
|
||||
.pageSize(20L)
|
||||
.orders(Arrays.asList(
|
||||
AgentLogSearchRequest.OrderBy.builder()
|
||||
.column("request_start_time")
|
||||
.asc(false)
|
||||
.build()
|
||||
))
|
||||
.build();
|
||||
|
||||
// 异步搜索
|
||||
CompletableFuture<AgentLogSearchResult> future = logHttpClientAdapter.searchAgentLogs(searchRequest);
|
||||
AgentLogSearchResult result = future.get();
|
||||
|
||||
// 同步搜索
|
||||
AgentLogSearchResult result = logHttpClientAdapter.searchAgentLogsSync(searchRequest);
|
||||
|
||||
// 处理搜索结果
|
||||
System.out.println("总记录数: " + result.getTotal());
|
||||
System.out.println("当前页: " + result.getCurrent());
|
||||
System.out.println("每页大小: " + result.getSize());
|
||||
result.getRecords().forEach(log -> {
|
||||
System.out.println("日志ID: " + log.getRequestId());
|
||||
System.out.println("用户输入: " + log.getUserInput());
|
||||
});
|
||||
```
|
||||
|
||||
## 模型类说明
|
||||
|
||||
### AgentLogEntry
|
||||
智能体日志条目,包含以下主要字段:
|
||||
- `requestId`: 请求ID(必填)
|
||||
- `conversationId`: 会话ID(必填)
|
||||
- `userUid`: 用户UID(必填)
|
||||
- `tenantId`: 租户ID(必填)
|
||||
- `spaceId`: 空间ID(可选)
|
||||
- `userInput`: 用户输入内容
|
||||
- `output`: 系统输出内容
|
||||
- `inputToken/outputToken`: Token 数量
|
||||
- `requestStartTime/requestEndTime`: 请求时间
|
||||
- `elapsedTimeMs`: 耗时(毫秒)
|
||||
- `nodeType/nodeName/status`: 节点信息
|
||||
|
||||
### AgentLogSearchParams
|
||||
搜索参数,支持多条件组合查询:
|
||||
- 基础过滤:`requestId`, `conversationId`, `userUid`, `tenantId`, `spaceId`
|
||||
- 全文搜索:`userInput`, `output`
|
||||
- 时间范围:`startTime`, `endTime`
|
||||
|
||||
### AgentLogSearchRequest
|
||||
搜索请求包装类:
|
||||
- `queryFilter`: 查询条件
|
||||
- `current`: 当前页码
|
||||
- `pageSize`: 每页大小
|
||||
- `orders`: 排序条件列表
|
||||
|
||||
### AgentLogSearchResult
|
||||
搜索结果:
|
||||
- `records`: 日志记录列表
|
||||
- `total`: 总记录数
|
||||
- `current`: 当前页
|
||||
- `size`: 每页大小
|
||||
- `elapsedTimeMs`: 查询耗时
|
||||
|
||||
## 错误处理
|
||||
|
||||
### 配置验证
|
||||
客户端适配器在启动时会验证配置:
|
||||
- 如果 `base-url` 未配置或为空,会抛出 `IllegalArgumentException`
|
||||
- 错误信息会记录到日志中
|
||||
|
||||
### 运行时异常
|
||||
客户端适配器已经内置了基本的错误处理:
|
||||
- 网络异常会被记录并返回 false 或抛出异常
|
||||
- HTTP 错误状态码会被记录并处理
|
||||
- JSON 序列化/反序列化错误会被捕获
|
||||
|
||||
在业务代码中建议使用 try-catch 块处理异常:
|
||||
|
||||
```java
|
||||
try {
|
||||
AgentLogSearchResult result = logHttpClientAdapter.searchAgentLogsSync(searchRequest);
|
||||
// 处理搜索结果
|
||||
} catch (Exception e) {
|
||||
log.error("搜索日志失败", e);
|
||||
// 处理错误情况
|
||||
}
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **必填配置**:`log-module.log.rust-service.base-url` 是必填配置,不能为空
|
||||
2. **服务依赖**:确保 Rust 日志服务正在运行并可访问
|
||||
3. **线程安全**:`LogHttpClientAdapter` 是线程安全的,可以在多线程环境中使用
|
||||
4. **资源管理**:应用关闭时会自动清理 HTTP 客户端资源
|
||||
5. **配置优化**:根据实际网络环境调整超时时间和连接池参数
|
||||
6. **日志级别**:通过 `log.level` 配置控制日志输出级别
|
||||
|
||||
## 测试
|
||||
|
||||
运行单元测试前确保:
|
||||
1. Rust 服务正在运行
|
||||
2. 配置正确的服务地址
|
||||
|
||||
```bash
|
||||
# 运行特定测试类
|
||||
mvn test -Dtest=LogHttpClientAdapterTest
|
||||
|
||||
# 运行所有测试
|
||||
mvn test
|
||||
```
|
||||
|
||||
## 配置示例
|
||||
|
||||
### 开发环境配置
|
||||
```yaml
|
||||
log-module:
|
||||
log:
|
||||
rust-service:
|
||||
base-url: http://localhost:8098
|
||||
|
||||
log:
|
||||
level:
|
||||
com.xspaceagi.domain.service.LogHttpClientAdapter: DEBUG
|
||||
```
|
||||
|
||||
### 生产环境配置
|
||||
```yaml
|
||||
log-module:
|
||||
log:
|
||||
rust-service:
|
||||
base-url: http://rust-log-service:8098
|
||||
connect-timeout-seconds: 5
|
||||
write-timeout-seconds: 60
|
||||
read-timeout-seconds: 60
|
||||
max-idle-connections: 10
|
||||
|
||||
log:
|
||||
level:
|
||||
com.xspaceagi.domain.service.LogHttpClientAdapter: INFO
|
||||
```
|
||||
@@ -0,0 +1,46 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>app-platform-log</artifactId>
|
||||
<groupId>com.xspaceagi</groupId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>app-platform-log-domain</artifactId>
|
||||
|
||||
<properties>
|
||||
<maven.deploy.skip>true</maven.deploy.skip>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.xspaceagi</groupId>
|
||||
<artifactId>app-platform-log-spec</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 其他原有依赖保持不变 -->
|
||||
<dependency>
|
||||
<groupId>com.xspaceagi</groupId>
|
||||
<artifactId>app-platform-log-sdk</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<configuration>
|
||||
<annotationProcessorPaths>
|
||||
<path>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>${lombok.version}</version>
|
||||
</path>
|
||||
</annotationProcessorPaths>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -0,0 +1,268 @@
|
||||
package com.xspaceagi.domain.adaptor.impl;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
|
||||
import com.xspaceagi.system.spec.exception.BizExceptionCodeEnum;
|
||||
import com.xspaceagi.system.spec.exception.LogPlatformException;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.xspaceagi.domain.model.valueobj.AgentLogEntry;
|
||||
import com.xspaceagi.domain.model.valueobj.AgentLogSearchParams;
|
||||
import com.xspaceagi.domain.model.valueobj.AgentLogSearchRequest;
|
||||
import com.xspaceagi.domain.model.valueobj.AgentLogSearchResult;
|
||||
import com.xspaceagi.domain.model.valueobj.RustServiceResponse;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import okhttp3.MediaType;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.RequestBody;
|
||||
import okhttp3.Response;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
public class LogHttpClientAdapter {
|
||||
|
||||
private static final MediaType JSON = MediaType.get("application/json; charset=utf-8");
|
||||
|
||||
private final OkHttpClient httpClient;
|
||||
|
||||
private final String baseUrl;
|
||||
@Resource
|
||||
private ObjectMapper objectMapper;
|
||||
|
||||
@Autowired
|
||||
public LogHttpClientAdapter(LogHttpClientConfiguration config) {
|
||||
// 验证配置
|
||||
if (!StringUtils.hasText(config.getBaseUrl())) {
|
||||
String errorMsg = "日志服务配置错误:log-module.log.rust-service.base-url 不能为空,请检查配置文件";
|
||||
log.error(errorMsg);
|
||||
throw new IllegalArgumentException(errorMsg);
|
||||
}
|
||||
|
||||
this.baseUrl = config.getBaseUrl();
|
||||
log.info("初始化日志 HTTP 客户端,服务地址: {}", this.baseUrl);
|
||||
|
||||
this.httpClient = new OkHttpClient.Builder()
|
||||
.connectTimeout(Duration.ofSeconds(config.getConnectTimeoutSeconds()))
|
||||
.writeTimeout(Duration.ofSeconds(config.getWriteTimeoutSeconds()))
|
||||
.readTimeout(Duration.ofSeconds(config.getReadTimeoutSeconds()))
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 健康检查 - 直接使用同步API
|
||||
* GET /health
|
||||
*/
|
||||
public boolean healthCheck() {
|
||||
Request request = new Request.Builder()
|
||||
.url(baseUrl + "/health")
|
||||
.get()
|
||||
.build();
|
||||
|
||||
try (Response response = httpClient.newCall(request).execute()) {
|
||||
return response.isSuccessful();
|
||||
} catch (IOException e) {
|
||||
log.error("健康检查失败", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 就绪检查 - 直接使用同步API
|
||||
* GET /ready
|
||||
*/
|
||||
public boolean readyCheck() {
|
||||
Request request = new Request.Builder()
|
||||
.url(baseUrl + "/ready")
|
||||
.get()
|
||||
.build();
|
||||
|
||||
try (Response response = httpClient.newCall(request).execute()) {
|
||||
return response.isSuccessful();
|
||||
} catch (IOException e) {
|
||||
log.error("就绪检查失败", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增单个智能体日志 - 直接使用同步API
|
||||
* POST /api/agent/log/add
|
||||
*/
|
||||
public boolean addAgentLog(AgentLogEntry logEntry) {
|
||||
try {
|
||||
String json = objectMapper.writeValueAsString(logEntry);
|
||||
RequestBody body = RequestBody.create(json, JSON);
|
||||
|
||||
Request request = new Request.Builder()
|
||||
.url(baseUrl + "/api/agent/log/add")
|
||||
.post(body)
|
||||
.build();
|
||||
|
||||
try (Response response = httpClient.newCall(request).execute()) {
|
||||
boolean success = response.isSuccessful();
|
||||
if (!success) {
|
||||
log.warn("新增单个智能体日志失败,状态码: {}, 响应: {}",
|
||||
response.code(), response.body().string());
|
||||
}
|
||||
return success;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("新增单个智能体日志失败", e);
|
||||
throw LogPlatformException.build(BizExceptionCodeEnum.logPlatformInsertFailed, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量新增智能体日志 - 直接使用同步API
|
||||
* POST /api/agent/log/batch
|
||||
*/
|
||||
public boolean batchAddAgentLogs(List<AgentLogEntry> logEntries) {
|
||||
try {
|
||||
String json = objectMapper.writeValueAsString(logEntries);
|
||||
RequestBody body = RequestBody.create(json, JSON);
|
||||
|
||||
Request request = new Request.Builder()
|
||||
.url(baseUrl + "/api/agent/log/batch")
|
||||
.post(body)
|
||||
.build();
|
||||
|
||||
try (Response response = httpClient.newCall(request).execute()) {
|
||||
boolean success = response.isSuccessful();
|
||||
if (!success) {
|
||||
log.warn("批量新增智能体日志失败,状态码: {}, 响应: {}",
|
||||
response.code(), response.body().string());
|
||||
}
|
||||
return success;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("批量新增智能体日志失败", e);
|
||||
throw LogPlatformException.build(BizExceptionCodeEnum.logPlatformBatchInsertFailed, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 搜索智能体日志 - 直接使用同步API
|
||||
* POST /api/agent/log/search
|
||||
*/
|
||||
public AgentLogSearchResult searchAgentLogs(AgentLogSearchRequest searchRequest) {
|
||||
try {
|
||||
// 确保 queryFilter 不为空,Rust 服务要求必须有这个字段
|
||||
if (searchRequest.getQueryFilter() == null) {
|
||||
searchRequest.setQueryFilter(new AgentLogSearchParams());
|
||||
}
|
||||
|
||||
String json = objectMapper.writeValueAsString(searchRequest);
|
||||
log.debug("Sending log search request: {}", json);
|
||||
RequestBody body = RequestBody.create(json, JSON);
|
||||
|
||||
Request request = new Request.Builder()
|
||||
.url(baseUrl + "/api/agent/log/search")
|
||||
.post(body)
|
||||
.build();
|
||||
|
||||
try (Response response = httpClient.newCall(request).execute()) {
|
||||
if (response.isSuccessful()) {
|
||||
String responseBody = response.body().string();
|
||||
log.debug("Search response: {}", responseBody);
|
||||
|
||||
// 解析包装的响应
|
||||
RustServiceResponse<AgentLogSearchResult> rustResponse = objectMapper.readValue(responseBody,
|
||||
objectMapper.getTypeFactory().constructParametricType(
|
||||
RustServiceResponse.class, AgentLogSearchResult.class));
|
||||
|
||||
if (rustResponse.isSuccess()) {
|
||||
return rustResponse.getData();
|
||||
} else {
|
||||
String errorMsg = String.format("服务返回错误:code=%s, message=%s",
|
||||
rustResponse.getCode(), rustResponse.getMessage());
|
||||
log.warn(errorMsg);
|
||||
throw new LogPlatformException(BizExceptionCodeEnum.logPlatformSearchFailed.getCode(),
|
||||
errorMsg);
|
||||
}
|
||||
} else {
|
||||
String errorBody = response.body().string();
|
||||
log.warn("Agent log search failed, status: {}, body: {}", response.code(), errorBody);
|
||||
throw new LogPlatformException(BizExceptionCodeEnum.logPlatformSearchFailed.getCode(),
|
||||
"搜索失败,HTTP状态码: " + response.code() + ", 响应: " + errorBody);
|
||||
}
|
||||
}
|
||||
} catch (LogPlatformException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
log.error("Agent log search failed", e);
|
||||
throw LogPlatformException.build(BizExceptionCodeEnum.logPlatformSearchFailed, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 搜索智能体日志 - 直接使用同步API
|
||||
* POST /api/agent/log/search
|
||||
*/
|
||||
public AgentLogSearchResult queryOneAgentLog(AgentLogSearchRequest searchRequest) {
|
||||
try {
|
||||
// 确保 queryFilter 不为空,Rust 服务要求必须有这个字段
|
||||
if (searchRequest.getQueryFilter() == null) {
|
||||
searchRequest.setQueryFilter(new AgentLogSearchParams());
|
||||
}
|
||||
|
||||
String json = objectMapper.writeValueAsString(searchRequest);
|
||||
log.debug("Sending log search request: {}", json);
|
||||
RequestBody body = RequestBody.create(json, JSON);
|
||||
|
||||
Request request = new Request.Builder()
|
||||
.url(baseUrl + "/api/agent/log/detail")
|
||||
.post(body)
|
||||
.build();
|
||||
|
||||
try (Response response = httpClient.newCall(request).execute()) {
|
||||
if (response.isSuccessful()) {
|
||||
String responseBody = response.body().string();
|
||||
log.debug("Search response: {}", responseBody);
|
||||
|
||||
// 解析包装的响应
|
||||
RustServiceResponse<AgentLogSearchResult> rustResponse = objectMapper.readValue(responseBody,
|
||||
objectMapper.getTypeFactory().constructParametricType(
|
||||
RustServiceResponse.class, AgentLogSearchResult.class));
|
||||
|
||||
if (rustResponse.isSuccess()) {
|
||||
return rustResponse.getData();
|
||||
} else {
|
||||
String errorMsg = String.format("服务返回错误:code=%s, message=%s",
|
||||
rustResponse.getCode(), rustResponse.getMessage());
|
||||
log.warn(errorMsg);
|
||||
throw new LogPlatformException(BizExceptionCodeEnum.logPlatformSearchFailed.getCode(),
|
||||
errorMsg);
|
||||
}
|
||||
} else {
|
||||
String errorBody = response.body().string();
|
||||
log.warn("Agent log search failed, status: {}, body: {}", response.code(), errorBody);
|
||||
throw new LogPlatformException(BizExceptionCodeEnum.logPlatformSearchFailed.getCode(),
|
||||
"搜索失败,HTTP状态码: " + response.code() + ", 响应: " + errorBody);
|
||||
}
|
||||
}
|
||||
} catch (LogPlatformException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
log.error("Agent log search failed", e);
|
||||
throw LogPlatformException.build(BizExceptionCodeEnum.logPlatformSearchFailed, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭资源
|
||||
*/
|
||||
public void close() {
|
||||
if (httpClient != null) {
|
||||
httpClient.dispatcher().executorService().shutdown();
|
||||
httpClient.connectionPool().evictAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package com.xspaceagi.domain.adaptor.impl;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 日志 HTTP 客户端配置
|
||||
*/
|
||||
@Data
|
||||
@Configuration
|
||||
@ConfigurationProperties(prefix = "log-module.log.rust-service")
|
||||
public class LogHttpClientConfiguration {
|
||||
|
||||
/**
|
||||
* Rust 服务的基础 URL(必填)
|
||||
*/
|
||||
private String baseUrl;
|
||||
|
||||
/**
|
||||
* 连接超时时间(秒)
|
||||
*/
|
||||
private Integer connectTimeoutSeconds = 10;
|
||||
|
||||
/**
|
||||
* 写入超时时间(秒)
|
||||
*/
|
||||
private Integer writeTimeoutSeconds = 30;
|
||||
|
||||
/**
|
||||
* 读取超时时间(秒)
|
||||
*/
|
||||
private Integer readTimeoutSeconds = 30;
|
||||
|
||||
/**
|
||||
* 是否启用连接池
|
||||
*/
|
||||
private Boolean enableConnectionPool = true;
|
||||
|
||||
/**
|
||||
* 最大空闲连接数
|
||||
*/
|
||||
private Integer maxIdleConnections = 5;
|
||||
|
||||
/**
|
||||
* 连接保持存活时间(分钟)
|
||||
*/
|
||||
private Integer keepAliveDurationMinutes = 5;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
package com.xspaceagi.domain.adaptor.impl;
|
||||
@@ -0,0 +1,152 @@
|
||||
package com.xspaceagi.domain.model;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 智能体调试大模型的日志结构定义
|
||||
* 对应 Rust 的 AgentLogEntry 结构
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class AgentLogModel {
|
||||
|
||||
/**
|
||||
* 请求ID,唯一标识一次请求(必填)
|
||||
*/
|
||||
@Schema(description = "请求ID,唯一标识一次请求(必填)")
|
||||
private String requestId;
|
||||
|
||||
/**
|
||||
* 消息ID,(必填)
|
||||
*/
|
||||
@Schema(description = "消息ID,(必填)")
|
||||
private String messageId;
|
||||
|
||||
/**
|
||||
* 智能体ID
|
||||
*/
|
||||
@Schema(description = "智能体ID")
|
||||
private String agentId;
|
||||
|
||||
/**
|
||||
* 会话ID(必填)
|
||||
*/
|
||||
@Schema(description = "会话ID(必填)")
|
||||
private String conversationId;
|
||||
|
||||
/**
|
||||
* 用户UID(必填)
|
||||
*/
|
||||
@Schema(description = "用户UID(必填)")
|
||||
private String userUid;
|
||||
|
||||
/**
|
||||
* 租户ID,用于租户隔离(必填)
|
||||
*/
|
||||
@Schema(description = "租户ID,用于租户隔离(必填)")
|
||||
private String tenantId;
|
||||
|
||||
/**
|
||||
* 空间ID,用户可以有多个空间
|
||||
*/
|
||||
@Schema(description = "空间ID,用户可以有多个空间")
|
||||
private String spaceId;
|
||||
|
||||
/**
|
||||
* 用户输入的内容
|
||||
*/
|
||||
@Schema(description = "用户输入的内容")
|
||||
private String userInput;
|
||||
|
||||
/**
|
||||
* 系统输出的内容
|
||||
*/
|
||||
@Schema(description = "系统输出的内容")
|
||||
private String output;
|
||||
|
||||
/**
|
||||
* 执行结果,扩展字段,字段类型存储的是json文本
|
||||
*/
|
||||
@Schema(description = "执行结果,扩展字段,字段类型存储的是json文本")
|
||||
private String executeResult;
|
||||
|
||||
/**
|
||||
* 输入token数量
|
||||
*/
|
||||
@Schema(description = "输入token数量")
|
||||
private Integer inputToken;
|
||||
|
||||
/**
|
||||
* 输出token数量
|
||||
*/
|
||||
@Schema(description = "输出token数量")
|
||||
private Integer outputToken;
|
||||
|
||||
/**
|
||||
* 请求开始时间
|
||||
*/
|
||||
@Schema(description = "请求开始时间")
|
||||
private LocalDateTime requestStartTime;
|
||||
|
||||
/**
|
||||
* 请求结束时间
|
||||
*/
|
||||
@Schema(description = "请求结束时间")
|
||||
private LocalDateTime requestEndTime;
|
||||
|
||||
/**
|
||||
* 耗时(毫秒)
|
||||
*/
|
||||
@Schema(description = "耗时(毫秒)")
|
||||
private Long elapsedTimeMs;
|
||||
|
||||
/**
|
||||
* 节点类型
|
||||
*/
|
||||
@Schema(description = "节点类型")
|
||||
private String nodeType;
|
||||
|
||||
/**
|
||||
* 节点状态
|
||||
*/
|
||||
@Schema(description = "节点状态")
|
||||
private String status;
|
||||
|
||||
/**
|
||||
* 节点名称
|
||||
*/
|
||||
@Schema(description = "节点名称")
|
||||
private String nodeName;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@Schema(description = "创建时间")
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
@Schema(description = "更新时间")
|
||||
private LocalDateTime updatedAt;
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
@Schema(description = "用户ID")
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* 用户名称
|
||||
*/
|
||||
@Schema(description = "用户名称")
|
||||
private String userName;
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
package com.xspaceagi.domain.model.valueobj;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonNaming;
|
||||
|
||||
import com.xspaceagi.log.spec.FlexibleDateTimeDeserializer;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 智能体调试大模型的日志结构定义
|
||||
* 对应 Rust 的 AgentLogEntry 结构
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
|
||||
public class AgentLogEntry {
|
||||
|
||||
/**
|
||||
* 请求ID,唯一标识一次请求(必填)
|
||||
*/
|
||||
private String requestId;
|
||||
|
||||
/**
|
||||
* 消息ID,(必填)
|
||||
*/
|
||||
private String messageId;
|
||||
|
||||
/**
|
||||
* 会话ID(必填)
|
||||
*/
|
||||
private String conversationId;
|
||||
|
||||
/**
|
||||
* 智能体ID
|
||||
*/
|
||||
private String agentId;
|
||||
|
||||
/**
|
||||
* 用户UID(必填)
|
||||
*/
|
||||
private String userUid;
|
||||
|
||||
/**
|
||||
* 租户ID,用于租户隔离(必填)
|
||||
*/
|
||||
private String tenantId;
|
||||
|
||||
/**
|
||||
* 空间ID,用户可以有多个空间
|
||||
*/
|
||||
private String spaceId;
|
||||
|
||||
/**
|
||||
* 用户输入的内容
|
||||
*/
|
||||
private String userInput;
|
||||
|
||||
/**
|
||||
* 系统输出的内容
|
||||
*/
|
||||
private String output;
|
||||
|
||||
/**
|
||||
* 执行结果,扩展字段,字段类型存储的是json文本
|
||||
*/
|
||||
private String executeResult;
|
||||
|
||||
/**
|
||||
* 输入token数量
|
||||
*/
|
||||
private Integer inputToken;
|
||||
|
||||
/**
|
||||
* 输出token数量
|
||||
*/
|
||||
private Integer outputToken;
|
||||
|
||||
/**
|
||||
* 请求开始时间
|
||||
*/
|
||||
@JsonDeserialize(using = FlexibleDateTimeDeserializer.class)
|
||||
private LocalDateTime requestStartTime;
|
||||
|
||||
/**
|
||||
* 请求结束时间
|
||||
*/
|
||||
@JsonDeserialize(using = FlexibleDateTimeDeserializer.class)
|
||||
private LocalDateTime requestEndTime;
|
||||
|
||||
/**
|
||||
* 耗时(毫秒)
|
||||
*/
|
||||
private Long elapsedTimeMs;
|
||||
|
||||
/**
|
||||
* 节点类型
|
||||
*/
|
||||
private String nodeType;
|
||||
|
||||
/**
|
||||
* 节点状态
|
||||
*/
|
||||
private String status;
|
||||
|
||||
/**
|
||||
* 节点名称
|
||||
*/
|
||||
private String nodeName;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@JsonDeserialize(using = FlexibleDateTimeDeserializer.class)
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
@JsonDeserialize(using = FlexibleDateTimeDeserializer.class)
|
||||
private LocalDateTime updatedAt;
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
private Long userId;
|
||||
/**
|
||||
* 用户名称
|
||||
*/
|
||||
private String userName;
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package com.xspaceagi.domain.model.valueobj;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonNaming;
|
||||
import com.xspaceagi.log.spec.FlexibleDateTimeDeserializer;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 日志搜索请求参数
|
||||
* 对应 Rust 的 AgentLogSearchParams 结构
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
|
||||
public class AgentLogSearchParams {
|
||||
|
||||
/**
|
||||
* 请求ID
|
||||
*/
|
||||
private String requestId;
|
||||
|
||||
/**
|
||||
* 会话ID
|
||||
*/
|
||||
private String conversationId;
|
||||
|
||||
/**
|
||||
* 消息ID
|
||||
*/
|
||||
private String messageId;
|
||||
/**
|
||||
* 智能体ID
|
||||
*/
|
||||
private String agentId;
|
||||
|
||||
/**
|
||||
* 用户UID
|
||||
*/
|
||||
private String userUid;
|
||||
|
||||
/**
|
||||
* 用户输入,需要支持全文检索,支持多个关键字(AND关系)
|
||||
*/
|
||||
private List<String> userInput;
|
||||
|
||||
/**
|
||||
* 系统输出,需要支持全文检索,支持多个关键字(AND关系)
|
||||
*/
|
||||
private List<String> output;
|
||||
|
||||
/**
|
||||
* 开始时间
|
||||
*/
|
||||
@JsonDeserialize(using = FlexibleDateTimeDeserializer.class)
|
||||
@JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss'Z'", shape = JsonFormat.Shape.STRING)
|
||||
private LocalDateTime startTime;
|
||||
|
||||
/**
|
||||
* 结束时间
|
||||
*/
|
||||
@JsonDeserialize(using = FlexibleDateTimeDeserializer.class)
|
||||
@JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss'Z'", shape = JsonFormat.Shape.STRING)
|
||||
private LocalDateTime endTime;
|
||||
|
||||
/**
|
||||
* 租户ID,用于租户隔离,确保只查询特定租户的日志
|
||||
*/
|
||||
private String tenantId;
|
||||
|
||||
/**
|
||||
* 空间ID,可选,用于查询特定空间的日志,支持多个ID(OR关系)
|
||||
*/
|
||||
private List<String> spaceId;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package com.xspaceagi.domain.model.valueobj;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 日志搜索请求包装类
|
||||
* 用于包装搜索参数、分页和排序信息
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class AgentLogSearchRequest {
|
||||
|
||||
/**
|
||||
* 查询过滤条件
|
||||
*/
|
||||
private AgentLogSearchParams queryFilter;
|
||||
|
||||
/**
|
||||
* 当前页码
|
||||
*/
|
||||
@Builder.Default
|
||||
private Long current = 1L;
|
||||
|
||||
/**
|
||||
* 每页大小
|
||||
*/
|
||||
@Builder.Default
|
||||
private Long pageSize = 10L;
|
||||
|
||||
/**
|
||||
* 排序条件
|
||||
*/
|
||||
private List<OrderBy> orders;
|
||||
|
||||
/**
|
||||
* 排序条件
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public static class OrderBy {
|
||||
/**
|
||||
* 排序字段
|
||||
*/
|
||||
private String column;
|
||||
|
||||
/**
|
||||
* 是否升序
|
||||
*/
|
||||
private Boolean asc;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package com.xspaceagi.domain.model.valueobj;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonNaming;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 日志分页结果
|
||||
* 对应 Rust 的 AgentLogSearchResult 结构
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
|
||||
public class AgentLogSearchResult {
|
||||
|
||||
/**
|
||||
* 处理耗时(毫秒)
|
||||
*/
|
||||
private Long elapsedTimeMs;
|
||||
|
||||
/**
|
||||
* 查询数据列表
|
||||
*/
|
||||
private List<AgentLogEntry> records;
|
||||
|
||||
/**
|
||||
* 总数
|
||||
*/
|
||||
private Long total;
|
||||
|
||||
/**
|
||||
* 每页显示条数,默认10
|
||||
*/
|
||||
@Builder.Default
|
||||
private Long size = 10L;
|
||||
|
||||
/**
|
||||
* 当前页
|
||||
*/
|
||||
@Builder.Default
|
||||
private Long current = 1L;
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package com.xspaceagi.domain.model.valueobj;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* Rust 服务响应包装类
|
||||
* 用于包装 Rust 服务返回的标准响应格式
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class RustServiceResponse<T> {
|
||||
|
||||
/**
|
||||
* 响应代码
|
||||
*/
|
||||
private String code;
|
||||
|
||||
/**
|
||||
* 响应消息
|
||||
*/
|
||||
private String message;
|
||||
|
||||
/**
|
||||
* 响应数据
|
||||
*/
|
||||
private T data;
|
||||
|
||||
/**
|
||||
* 事务ID
|
||||
*/
|
||||
private String tid;
|
||||
|
||||
/**
|
||||
* 判断响应是否成功
|
||||
*/
|
||||
public boolean isSuccess() {
|
||||
return "0000".equals(code);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
package com.xspaceagi.domain.model.valueobj;
|
||||
@@ -0,0 +1 @@
|
||||
package com.xspaceagi.domain;
|
||||
@@ -0,0 +1 @@
|
||||
package com.xspaceagi.domain.repository;
|
||||
@@ -0,0 +1,58 @@
|
||||
package com.xspaceagi.domain.service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.xspaceagi.domain.model.AgentLogModel;
|
||||
import com.xspaceagi.domain.model.valueobj.AgentLogEntry;
|
||||
import com.xspaceagi.domain.model.valueobj.AgentLogSearchParams;
|
||||
|
||||
/**
|
||||
* 日志平台领域服务接口
|
||||
*/
|
||||
public interface ILogPlatformDomainService {
|
||||
|
||||
/**
|
||||
* 搜索智能体日志
|
||||
*
|
||||
* @param searchParams 搜索参数
|
||||
* @param current 当前页
|
||||
* @param pageSize 每页大小
|
||||
* @return 分页日志结果
|
||||
*/
|
||||
IPage<AgentLogModel> searchAgentLogs(AgentLogSearchParams searchParams, Long current, Long pageSize);
|
||||
|
||||
|
||||
/**
|
||||
* 详情
|
||||
*
|
||||
* @param searchParams 搜索参数
|
||||
* @param current 当前页
|
||||
* @param pageSize 每页大小
|
||||
* @return 日志详情
|
||||
*/
|
||||
AgentLogModel queryOneAgentLog(AgentLogSearchParams searchParams);
|
||||
|
||||
/**
|
||||
* 新增单个智能体日志
|
||||
*
|
||||
* @param logEntry 日志条目
|
||||
* @return 是否成功
|
||||
*/
|
||||
boolean addAgentLog(AgentLogEntry logEntry);
|
||||
|
||||
/**
|
||||
* 批量新增智能体日志
|
||||
*
|
||||
* @param logEntries 日志条目列表
|
||||
* @return 是否成功
|
||||
*/
|
||||
boolean batchAddAgentLogs(List<AgentLogEntry> logEntries);
|
||||
|
||||
/**
|
||||
* 健康检查
|
||||
*
|
||||
* @return Rust 服务是否健康
|
||||
*/
|
||||
boolean healthCheck();
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
package com.xspaceagi.domain.service;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.xspaceagi.domain.adaptor.impl.LogHttpClientAdapter;
|
||||
import com.xspaceagi.domain.model.AgentLogModel;
|
||||
import com.xspaceagi.domain.model.valueobj.AgentLogEntry;
|
||||
import com.xspaceagi.domain.model.valueobj.AgentLogSearchParams;
|
||||
import com.xspaceagi.domain.model.valueobj.AgentLogSearchRequest;
|
||||
import com.xspaceagi.domain.model.valueobj.AgentLogSearchResult;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
public class LogPlatformDomainService implements ILogPlatformDomainService {
|
||||
|
||||
@Resource
|
||||
private LogHttpClientAdapter logHttpClientAdapter;
|
||||
|
||||
@Override
|
||||
public IPage<AgentLogModel> searchAgentLogs(AgentLogSearchParams searchParams, Long current, Long pageSize) {
|
||||
log.info("开始搜索智能体日志,参数: {}, 当前页: {}, 每页大小: {}", searchParams, current, pageSize);
|
||||
|
||||
// 构建搜索请求
|
||||
AgentLogSearchRequest searchRequest = AgentLogSearchRequest.builder()
|
||||
.queryFilter(searchParams)
|
||||
.current(current != null ? current : 1L)
|
||||
.pageSize(pageSize != null ? pageSize : 10L)
|
||||
.build();
|
||||
|
||||
// 调用 Rust 服务搜索
|
||||
AgentLogSearchResult searchResult = logHttpClientAdapter.searchAgentLogs(searchRequest);
|
||||
|
||||
// 转换结果
|
||||
List<AgentLogModel> records = convertToAgentLogModels(searchResult.getRecords());
|
||||
|
||||
// 构建分页结果
|
||||
Page<AgentLogModel> page = new Page<>(
|
||||
searchResult.getCurrent(),
|
||||
searchResult.getSize());
|
||||
page.setRecords(records);
|
||||
page.setTotal(searchResult.getTotal());
|
||||
|
||||
log.info("Search done, total: {}, page: {}, elapsed: {}ms",
|
||||
searchResult.getTotal(), searchResult.getCurrent(), searchResult.getElapsedTimeMs());
|
||||
|
||||
return page;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addAgentLog(AgentLogEntry logEntry) {
|
||||
log.info("Adding agent log, requestId: {}", logEntry.getRequestId());
|
||||
|
||||
boolean result = logHttpClientAdapter.addAgentLog(logEntry);
|
||||
if (result) {
|
||||
log.info("新增智能体日志成功,请求ID: {}", logEntry.getRequestId());
|
||||
} else {
|
||||
log.warn("新增智能体日志失败,请求ID: {}", logEntry.getRequestId());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean batchAddAgentLogs(List<AgentLogEntry> logEntries) {
|
||||
if (CollectionUtils.isEmpty(logEntries)) {
|
||||
log.warn("批量新增智能体日志失败:日志列表为空");
|
||||
return false;
|
||||
}
|
||||
|
||||
log.info("开始批量新增智能体日志,数量: {}", logEntries.size());
|
||||
|
||||
boolean result = logHttpClientAdapter.batchAddAgentLogs(logEntries);
|
||||
if (result) {
|
||||
log.info("批量新增智能体日志成功,数量: {}", logEntries.size());
|
||||
} else {
|
||||
log.warn("批量新增智能体日志失败,数量: {}", logEntries.size());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean healthCheck() {
|
||||
log.debug("开始检查 Rust 日志服务健康状态");
|
||||
|
||||
boolean isHealthy = logHttpClientAdapter.healthCheck();
|
||||
log.debug("Rust 日志服务健康状态: {}", isHealthy ? "健康" : "不健康");
|
||||
return isHealthy;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 AgentLogEntry 转换为 AgentLogModel
|
||||
*/
|
||||
private List<AgentLogModel> convertToAgentLogModels(List<AgentLogEntry> logEntries) {
|
||||
if (CollectionUtils.isEmpty(logEntries)) {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
return logEntries.stream()
|
||||
.map(this::convertToAgentLogModel)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* 将单个 AgentLogEntry 转换为 AgentLogModel
|
||||
*/
|
||||
private AgentLogModel convertToAgentLogModel(AgentLogEntry logEntry) {
|
||||
return AgentLogModel.builder()
|
||||
.requestId(logEntry.getRequestId())
|
||||
.messageId(logEntry.getMessageId())
|
||||
.conversationId(logEntry.getConversationId())
|
||||
.agentId(logEntry.getAgentId())
|
||||
.userUid(logEntry.getUserUid())
|
||||
.tenantId(logEntry.getTenantId())
|
||||
.spaceId(logEntry.getSpaceId())
|
||||
.userInput(logEntry.getUserInput())
|
||||
.output(logEntry.getOutput())
|
||||
.inputToken(logEntry.getInputToken())
|
||||
.outputToken(logEntry.getOutputToken())
|
||||
.executeResult(logEntry.getExecuteResult())
|
||||
.requestStartTime(logEntry.getRequestStartTime())
|
||||
.requestEndTime(logEntry.getRequestEndTime())
|
||||
.elapsedTimeMs(logEntry.getElapsedTimeMs())
|
||||
.nodeType(logEntry.getNodeType())
|
||||
.status(logEntry.getStatus())
|
||||
.nodeName(logEntry.getNodeName())
|
||||
.createdAt(logEntry.getCreatedAt())
|
||||
.updatedAt(logEntry.getUpdatedAt())
|
||||
.userId(logEntry.getUserId())
|
||||
.userName(logEntry.getUserName())
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AgentLogModel queryOneAgentLog(AgentLogSearchParams searchParams) {
|
||||
log.info("开始搜索智能体日志,参数: {}", searchParams);
|
||||
|
||||
// 构建搜索请求
|
||||
AgentLogSearchRequest searchRequest = AgentLogSearchRequest.builder()
|
||||
.queryFilter(searchParams)
|
||||
.current(1L)
|
||||
.pageSize(1L)
|
||||
.build();
|
||||
|
||||
// 调用 Rust 服务搜索
|
||||
AgentLogSearchResult searchResult = logHttpClientAdapter.queryOneAgentLog(searchRequest);
|
||||
|
||||
// 转换结果
|
||||
List<AgentLogModel> records = convertToAgentLogModels(searchResult.getRecords());
|
||||
|
||||
log.info("Search done, total: {}, page: {}, elapsed: {}ms",
|
||||
searchResult.getTotal(), searchResult.getCurrent(), searchResult.getElapsedTimeMs());
|
||||
|
||||
return records.stream().findFirst().orElse(null);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
package com.xspaceagi.domain.service;
|
||||
@@ -0,0 +1 @@
|
||||
package com.xspaceagi.domain.specification;
|
||||
@@ -0,0 +1,228 @@
|
||||
package com.xspaceagi.domain.service;
|
||||
|
||||
import com.xspaceagi.domain.adaptor.impl.LogHttpClientAdapter;
|
||||
import com.xspaceagi.domain.adaptor.impl.LogHttpClientConfiguration;
|
||||
import com.xspaceagi.domain.model.valueobj.AgentLogEntry;
|
||||
import com.xspaceagi.domain.model.valueobj.AgentLogSearchParams;
|
||||
import com.xspaceagi.domain.model.valueobj.AgentLogSearchRequest;
|
||||
import com.xspaceagi.domain.model.valueobj.AgentLogSearchResult;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* LogHttpClientAdapter 单元测试
|
||||
* 注意:这些测试需要 Rust 服务正在运行
|
||||
*/
|
||||
@Slf4j
|
||||
public class LogHttpClientAdapterTest {
|
||||
|
||||
private LogHttpClientAdapter logHttpClientAdapter;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
// 创建配置对象
|
||||
LogHttpClientConfiguration config = new LogHttpClientConfiguration();
|
||||
config.setBaseUrl("http://localhost:8098");
|
||||
config.setConnectTimeoutSeconds(10);
|
||||
config.setWriteTimeoutSeconds(30);
|
||||
config.setReadTimeoutSeconds(30);
|
||||
config.setEnableConnectionPool(true);
|
||||
config.setMaxIdleConnections(5);
|
||||
config.setKeepAliveDurationMinutes(5);
|
||||
|
||||
logHttpClientAdapter = new LogHttpClientAdapter(config);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testHealthCheck() throws Exception {
|
||||
Boolean result = logHttpClientAdapter.healthCheck();
|
||||
log.info("健康检查结果: {}", result);
|
||||
// 注意:如果 Rust 服务未运行,这个测试会失败
|
||||
}
|
||||
|
||||
@Test
|
||||
void testReadyCheck() throws Exception {
|
||||
Boolean result = logHttpClientAdapter.readyCheck();
|
||||
log.info("就绪检查结果: {}", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAddAgentLog() throws Exception {
|
||||
AgentLogEntry logEntry = AgentLogEntry.builder()
|
||||
.requestId("req_test_001")
|
||||
.conversationId("session_test_001")
|
||||
.userUid("user_test_123")
|
||||
.tenantId("tenant_test_001")
|
||||
.spaceId("space_test_001")
|
||||
.userInput("测试用户输入")
|
||||
.output("测试系统输出")
|
||||
.inputToken(50)
|
||||
.outputToken(100)
|
||||
.requestStartTime(LocalDateTime.now().minusSeconds(5))
|
||||
.requestEndTime(LocalDateTime.now())
|
||||
.elapsedTimeMs(5000L)
|
||||
.nodeType("test_node")
|
||||
.status("success")
|
||||
.nodeName("测试节点")
|
||||
.build();
|
||||
|
||||
Boolean result = logHttpClientAdapter.addAgentLog(logEntry);
|
||||
log.info("新增单个日志结果: {}", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBatchAddAgentLogs() throws Exception {
|
||||
List<AgentLogEntry> logEntries = Arrays.asList(
|
||||
AgentLogEntry.builder()
|
||||
.requestId("req_batch_001")
|
||||
.conversationId("session_batch_001")
|
||||
.userUid("user_batch_123")
|
||||
.tenantId("tenant_batch_001")
|
||||
.spaceId("space_batch_001")
|
||||
.userInput("批量测试输入1")
|
||||
.output("批量测试输出1")
|
||||
.inputToken(30)
|
||||
.outputToken(60)
|
||||
.requestStartTime(LocalDateTime.now().minusSeconds(10))
|
||||
.requestEndTime(LocalDateTime.now().minusSeconds(7))
|
||||
.elapsedTimeMs(3000L)
|
||||
.status("success")
|
||||
.build(),
|
||||
AgentLogEntry.builder()
|
||||
.requestId("req_batch_002")
|
||||
.conversationId("session_batch_001")
|
||||
.userUid("user_batch_123")
|
||||
.tenantId("tenant_batch_001")
|
||||
.spaceId("space_batch_001")
|
||||
.userInput("批量测试输入2")
|
||||
.output("批量测试输出2")
|
||||
.inputToken(40)
|
||||
.outputToken(80)
|
||||
.requestStartTime(LocalDateTime.now().minusSeconds(7))
|
||||
.requestEndTime(LocalDateTime.now().minusSeconds(4))
|
||||
.elapsedTimeMs(3000L)
|
||||
.status("success")
|
||||
.build()
|
||||
);
|
||||
|
||||
Boolean result = logHttpClientAdapter.batchAddAgentLogs(logEntries);
|
||||
log.info("批量新增日志结果: {}", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSearchAgentLogs() throws Exception {
|
||||
AgentLogSearchParams searchParams = AgentLogSearchParams.builder()
|
||||
.tenantId("tenant_test_001")
|
||||
.userUid("user_test_123")
|
||||
.build();
|
||||
|
||||
AgentLogSearchRequest searchRequest = AgentLogSearchRequest.builder()
|
||||
.queryFilter(searchParams)
|
||||
.current(1L)
|
||||
.pageSize(10L)
|
||||
.build();
|
||||
|
||||
AgentLogSearchResult result = logHttpClientAdapter.searchAgentLogs(searchRequest);
|
||||
log.info("搜索结果: 总数={}, 当前页={}, 每页大小={}, 记录数={}",
|
||||
result.getTotal(), result.getCurrent(), result.getSize(),
|
||||
result.getRecords() != null ? result.getRecords().size() : 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSyncMethods() {
|
||||
// 测试同步方法
|
||||
AgentLogEntry logEntry = AgentLogEntry.builder()
|
||||
.requestId("req_sync_001")
|
||||
.conversationId("session_sync_001")
|
||||
.userUid("user_sync_123")
|
||||
.tenantId("tenant_sync_001")
|
||||
.userInput("同步测试输入")
|
||||
.output("同步测试输出")
|
||||
.build();
|
||||
|
||||
boolean addResult = logHttpClientAdapter.addAgentLog(logEntry);
|
||||
log.info("同步新增日志结果: {}", addResult);
|
||||
|
||||
// 搜索测试
|
||||
AgentLogSearchParams searchParams = AgentLogSearchParams.builder()
|
||||
.tenantId("tenant_sync_001")
|
||||
.build();
|
||||
|
||||
AgentLogSearchRequest searchRequest = AgentLogSearchRequest.builder()
|
||||
.queryFilter(searchParams)
|
||||
.current(1L)
|
||||
.pageSize(5L)
|
||||
.build();
|
||||
|
||||
try {
|
||||
AgentLogSearchResult searchResult = logHttpClientAdapter.searchAgentLogs(searchRequest);
|
||||
log.info("同步搜索结果: 总数={}", searchResult.getTotal());
|
||||
} catch (Exception e) {
|
||||
log.error("同步搜索失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testConfigurationValidation() {
|
||||
// 测试配置验证 - 空 URL
|
||||
LogHttpClientConfiguration emptyConfig = new LogHttpClientConfiguration();
|
||||
emptyConfig.setBaseUrl("");
|
||||
|
||||
try {
|
||||
new LogHttpClientAdapter(emptyConfig);
|
||||
// 如果没有抛出异常,测试失败
|
||||
log.error("Expected exception but none was thrown");
|
||||
} catch (IllegalArgumentException e) {
|
||||
log.info("Caught expected config exception: {}", e.getMessage());
|
||||
}
|
||||
|
||||
// 测试配置验证 - null URL
|
||||
LogHttpClientConfiguration nullConfig = new LogHttpClientConfiguration();
|
||||
nullConfig.setBaseUrl(null);
|
||||
|
||||
try {
|
||||
new LogHttpClientAdapter(nullConfig);
|
||||
// 如果没有抛出异常,测试失败
|
||||
log.error("Expected exception but none was thrown");
|
||||
} catch (IllegalArgumentException e) {
|
||||
log.info("Caught expected config exception: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSearchWithNullQueryFilter() {
|
||||
// 创建配置
|
||||
LogHttpClientConfiguration config = new LogHttpClientConfiguration();
|
||||
config.setBaseUrl("http://localhost:8097");
|
||||
|
||||
// 创建适配器
|
||||
LogHttpClientAdapter adapter = new LogHttpClientAdapter(config);
|
||||
|
||||
try {
|
||||
// 创建搜索请求,queryFilter 为 null
|
||||
AgentLogSearchRequest searchRequest = AgentLogSearchRequest.builder()
|
||||
.current(1L)
|
||||
.pageSize(10L)
|
||||
.queryFilter(null) // 故意设置为 null
|
||||
.build();
|
||||
|
||||
log.info("测试 queryFilter 为 null 的情况");
|
||||
log.info("原始 searchRequest: {}", searchRequest);
|
||||
|
||||
// 执行搜索
|
||||
AgentLogSearchResult result = adapter.searchAgentLogs(searchRequest);
|
||||
|
||||
log.info("搜索成功,结果: {}", result);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("测试失败", e);
|
||||
} finally {
|
||||
adapter.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>app-platform-log</artifactId>
|
||||
<groupId>com.xspaceagi</groupId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>com.xspaceagi</groupId>
|
||||
<artifactId>app-platform-log-infra</artifactId>
|
||||
|
||||
<properties>
|
||||
<maven.deploy.skip>true</maven.deploy.skip>
|
||||
</properties>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.xspaceagi</groupId>
|
||||
<artifactId>app-platform-log-domain</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>co.elastic.clients</groupId>
|
||||
<artifactId>elasticsearch-java</artifactId>
|
||||
<version>9.2.1</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,4 @@
|
||||
package com.xspaceagi.search.infra;
|
||||
|
||||
public interface SearchService {
|
||||
}
|
||||
@@ -0,0 +1,282 @@
|
||||
package com.xspaceagi.search.infra.elasticsearch;
|
||||
|
||||
import cn.hutool.core.collection.ConcurrentHashSet;
|
||||
import co.elastic.clients.elasticsearch.ElasticsearchClient;
|
||||
import co.elastic.clients.elasticsearch._types.FieldValue;
|
||||
import co.elastic.clients.elasticsearch._types.SortOrder;
|
||||
import co.elastic.clients.elasticsearch._types.query_dsl.*;
|
||||
import co.elastic.clients.elasticsearch.core.*;
|
||||
import co.elastic.clients.transport.endpoints.BooleanResponse;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.xspaceagi.log.sdk.annotation.SearchField;
|
||||
import com.xspaceagi.log.sdk.annotation.SearchIndex;
|
||||
import com.xspaceagi.log.sdk.request.DocumentSearchRequest;
|
||||
import com.xspaceagi.log.sdk.service.ISearchRpcService;
|
||||
import com.xspaceagi.log.sdk.vo.SearchDocument;
|
||||
import com.xspaceagi.log.sdk.vo.SearchResult;
|
||||
import com.xspaceagi.search.infra.SearchService;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import jakarta.annotation.PreDestroy;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Field;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
public class ElasticsearchServiceImpl implements SearchService, ISearchRpcService {
|
||||
|
||||
private final Set<String> indexNameSet = new ConcurrentHashSet<>();
|
||||
|
||||
@Value("${search.elasticsearch.url}")
|
||||
private String esUrl;
|
||||
|
||||
@Value("${search.elasticsearch.api_key:}")
|
||||
private String apiKey;
|
||||
|
||||
@Value("${search.elasticsearch.username:}")
|
||||
private String username;
|
||||
|
||||
@Value("${search.elasticsearch.password:}")
|
||||
private String password;
|
||||
|
||||
private ElasticsearchClient client;
|
||||
|
||||
@PostConstruct
|
||||
private void init() {
|
||||
if (StringUtils.isNotBlank(username) && StringUtils.isNotBlank(password)) {
|
||||
client = ElasticsearchClient.of(b -> b
|
||||
.host(esUrl)
|
||||
.usernameAndPassword(username, password)
|
||||
);
|
||||
} else {
|
||||
client = ElasticsearchClient.of(b -> b
|
||||
.host(esUrl)
|
||||
.apiKey(apiKey)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@PreDestroy
|
||||
private void destroy() {
|
||||
client.shutdown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bulkIndex(List<SearchDocument> list) {
|
||||
Assert.noNullElements(list, "list cannot be left blank.");
|
||||
String finalIndexName = createIndexIfNotExists(list.get(0).getClass());
|
||||
BulkRequest.Builder br = new BulkRequest.Builder();
|
||||
for (SearchDocument object : list) {
|
||||
log.info("构建文档索引, id {}, finalIndexName {}", object.getId(), finalIndexName);
|
||||
br.operations(op -> op.index(idx -> idx.index(finalIndexName).id(object.getId()).document(object)));
|
||||
}
|
||||
|
||||
BulkResponse result;
|
||||
try {
|
||||
result = client.bulk(br.build());
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
if (result.errors()) {
|
||||
throw new RuntimeException("Failed to batch build indexes.");
|
||||
}
|
||||
}
|
||||
|
||||
private String createIndexIfNotExists(Class<? extends SearchDocument> searchDocumentClazz) {
|
||||
Assert.notNull(searchDocumentClazz, "searchDocumentClazz cannot be left blank.");
|
||||
SearchIndex annotation = searchDocumentClazz.getAnnotation(SearchIndex.class);
|
||||
String indexName = searchDocumentClazz.getSimpleName();
|
||||
int shards = 3;
|
||||
int replicas = 1;
|
||||
if (annotation != null) {
|
||||
indexName = annotation.indexName();
|
||||
shards = annotation.shards();
|
||||
replicas = annotation.replicas();
|
||||
}
|
||||
if (indexNameSet.contains(indexName)) {
|
||||
return indexName;
|
||||
}
|
||||
final String finalIndexName = indexName;
|
||||
final int finalShards = shards;
|
||||
final int finalReplicas = replicas;
|
||||
synchronized (indexNameSet) {
|
||||
BooleanResponse exists;
|
||||
try {
|
||||
exists = client.indices().exists(request -> request.index(finalIndexName));
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
if (!exists.value()) {
|
||||
try {
|
||||
log.info("创建索引, indexName {}", finalIndexName);
|
||||
client.indices().create(request -> request.index(finalIndexName)
|
||||
.settings(builder -> builder.numberOfShards(String.valueOf(finalShards)).numberOfReplicas(String.valueOf(finalReplicas)))
|
||||
.mappings(builder -> {
|
||||
Field[] declaredFields = searchDocumentClazz.getDeclaredFields();
|
||||
for (Field declaredField : declaredFields) {
|
||||
AtomicBoolean index = new AtomicBoolean(true);
|
||||
AtomicBoolean keyword = new AtomicBoolean(false);
|
||||
AtomicBoolean store = new AtomicBoolean(false);
|
||||
AtomicReference<String> name = new AtomicReference<>(declaredField.getName());
|
||||
SearchField fieldAnnotation = declaredField.getAnnotation(SearchField.class);
|
||||
if (fieldAnnotation != null) {
|
||||
index.set(fieldAnnotation.index());
|
||||
keyword.set(fieldAnnotation.keyword());
|
||||
store.set(fieldAnnotation.store());
|
||||
}
|
||||
JsonProperty annotation1 = declaredField.getAnnotation(JsonProperty.class);
|
||||
if (annotation1 != null) {
|
||||
name.set(annotation1.value());
|
||||
}
|
||||
if (declaredField.getType() == String.class) {
|
||||
if (keyword.get()) {
|
||||
builder.properties(name.get(), property -> property.keyword(kw -> kw.index(index.get()).store(store.get())));
|
||||
} else {
|
||||
builder.properties(name.get(), property -> property.text(text -> text.analyzer("ik_max_word")
|
||||
.searchAnalyzer("ik_smart").store(store.get()).index(index.get())
|
||||
));
|
||||
}
|
||||
} else if (declaredField.getType() == Integer.class) {
|
||||
builder.properties(name.get(), property -> property.integer(integer -> integer.index(index.get()).store(store.get())));
|
||||
} else if (declaredField.getType() == Long.class) {
|
||||
builder.properties(name.get(), property -> property.long_(long_ -> long_.index(index.get()).store(store.get())));
|
||||
} else if (declaredField.getType() == Double.class || declaredField.getType() == BigDecimal.class) {
|
||||
builder.properties(name.get(), property -> property.double_(double_ -> double_.index(index.get()).store(store.get())));
|
||||
} else if (declaredField.getType() == Float.class) {
|
||||
builder.properties(name.get(), property -> property.float_(float_ -> float_.index(index.get()).store(store.get())));
|
||||
} else if (declaredField.getType() == Boolean.class) {
|
||||
builder.properties(name.get(), property -> property.boolean_(boolean_ -> boolean_.index(index.get()).store(store.get())));
|
||||
} else if (declaredField.getType() == Date.class) {
|
||||
builder.properties(name.get(), property -> property.date(date -> date.index(index.get()).store(store.get())));
|
||||
} else {
|
||||
builder.properties(name.get(), property -> property.text(text -> text.analyzer("ik_max_word")
|
||||
.searchAnalyzer("ik_smart").store(store.get()).index(index.get())
|
||||
));
|
||||
}
|
||||
}
|
||||
return builder;
|
||||
})
|
||||
);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
indexNameSet.add(finalIndexName);
|
||||
}
|
||||
return finalIndexName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteDocument(Class<? extends SearchDocument> searchDocumentClazz, String id) {
|
||||
String indexName = createIndexIfNotExists(searchDocumentClazz);
|
||||
log.info("删除文档, indexName {}, id {}", indexName, id);
|
||||
DeleteRequest request = new DeleteRequest.Builder().index(indexName).id(id).build();
|
||||
try {
|
||||
client.delete(request);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public SearchResult search(DocumentSearchRequest documentSearchRequest) {
|
||||
if (documentSearchRequest == null) {
|
||||
throw new IllegalArgumentException("Search parameters cannot be empty");
|
||||
}
|
||||
log.info("搜索文档, documentSearchRequest {}", documentSearchRequest);
|
||||
Integer from = documentSearchRequest.getFrom();
|
||||
Integer size = documentSearchRequest.getSize();
|
||||
if (from == null || from < 0) {
|
||||
from = 0;
|
||||
}
|
||||
if (size == null || size <= 0) {
|
||||
size = 10;
|
||||
}
|
||||
String indexName = createIndexIfNotExists(documentSearchRequest.getSearchDocumentClazz());
|
||||
SearchRequest.Builder requestBuilder = new SearchRequest.Builder().index(indexName).query(q -> {
|
||||
q.bool(bool -> {
|
||||
if (CollectionUtils.isNotEmpty(documentSearchRequest.getFilterFieldsAndValues())) {
|
||||
for (Map<String, Object> filterFieldAndValue : documentSearchRequest.getFilterFieldsAndValues()) {
|
||||
bool.filter(f -> {
|
||||
for (Map.Entry<String, Object> entry : filterFieldAndValue.entrySet()) {
|
||||
if (entry.getValue() instanceof List<?>) {
|
||||
f.terms(TermsQuery.of(t -> t.field(entry.getKey()).terms(
|
||||
TermsQueryField.of(t1 -> t1.value(((List<?>) entry.getValue()).stream().map(m -> FieldValue.of(m)).collect(Collectors.toList())))
|
||||
)));
|
||||
} else if (entry.getValue() instanceof Map<?, ?> && ((Map<?, ?>) entry.getValue()).containsKey("express")) {
|
||||
Object express = ((Map<?, ?>) entry.getValue()).get("express");
|
||||
if (express != null) {
|
||||
if (express.equals("range")) {
|
||||
f.range(RangeQuery.of(r -> r.term(TermRangeQuery.of(t -> {
|
||||
t.field(entry.getKey());
|
||||
Object gt = ((Map<?, ?>) entry.getValue()).get("gt");
|
||||
if (gt != null) {
|
||||
t.gt(gt.toString());
|
||||
}
|
||||
Object lt = ((Map<?, ?>) entry.getValue()).get("lt");
|
||||
if (lt != null) {
|
||||
t.lt(lt.toString());
|
||||
}
|
||||
return t;
|
||||
}))));
|
||||
}
|
||||
if (express.equals("match")) {
|
||||
f.match(MatchQuery.of(m -> m.field(entry.getKey()).query(((Map<?, ?>) entry.getValue()).get("query").toString())));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
f.term(TermQuery.of(t -> t.field(entry.getKey()).value(FieldValue.of(entry.getValue()))));
|
||||
}
|
||||
}
|
||||
return f;
|
||||
});
|
||||
}
|
||||
}
|
||||
if (CollectionUtils.isNotEmpty(documentSearchRequest.getSearchFields()) && StringUtils.isNotBlank(documentSearchRequest.getKeyword())) {
|
||||
bool.must(m1 -> m1.multiMatch(m -> m.query(documentSearchRequest.getKeyword()).fields(documentSearchRequest.getSearchFields())));
|
||||
}
|
||||
return bool;
|
||||
});
|
||||
|
||||
return q;
|
||||
})
|
||||
.from(from)
|
||||
.size(size);
|
||||
if (documentSearchRequest.getSortFieldsAndValues() != null) {
|
||||
requestBuilder.sort(s -> {
|
||||
for (Map.Entry<String, Object> entry : documentSearchRequest.getSortFieldsAndValues().entrySet()) {
|
||||
s.field(f -> f.field(entry.getKey()).order(SortOrder.valueOf(entry.getValue().toString())));
|
||||
}
|
||||
return s;
|
||||
});
|
||||
}
|
||||
try {
|
||||
SearchResponse<?> response = client.search(requestBuilder.build(), documentSearchRequest.getSearchDocumentClazz());
|
||||
List<SearchResult.SearchResultItem> collect = response.hits().hits().stream().map(hit -> SearchResult.SearchResultItem.builder()
|
||||
.score(hit.score())
|
||||
.document((SearchDocument) hit.source())
|
||||
.build()).collect(Collectors.toList());
|
||||
log.info("搜索结果, total {}, items {}", response.hits().total().value(), collect);
|
||||
return SearchResult.builder()
|
||||
.total(response.hits().total().value())
|
||||
.items(collect)
|
||||
.build();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>app-platform-log</artifactId>
|
||||
<groupId>com.xspaceagi</groupId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>app-platform-log-job</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.xspaceagi</groupId>
|
||||
<artifactId>app-platform-log-application</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<!-- 打包插件 -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-assembly-plugin</artifactId>
|
||||
<version>2.4</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>make-assembly</id>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>single</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<descriptors>
|
||||
<!-- 打包配置:必须在对应目录下创建repack.xml的打包配置文件 -->
|
||||
<descriptor>src/main/resources/assemblies/repack.xml</descriptor>
|
||||
</descriptors>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<!-- install插件,定制在mvn install过程使用哪个jar包安装到本地 -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-install-plugin</artifactId>
|
||||
<version>2.5.2</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>install-file</id>
|
||||
<goals>
|
||||
<goal>install-file</goal>
|
||||
</goals>
|
||||
<phase>install</phase>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<!-- install配置,指定将哪个目录下的jar包install为这个工程的jar -->
|
||||
<file>${project.build.directory}/${project.build.finalName}-repack.jar</file>
|
||||
<!--
|
||||
install配置,指定将install的jar打包使用的pom.xml文件,
|
||||
即其他依赖这个jar包的工程按照哪个pom.xml文件来解析依赖
|
||||
-->
|
||||
<pomFile>src/main/resources/install-pom.xml</pomFile>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<!-- deploy插件,定制在mvn deploy过程使用哪个jar包发布到仓库 -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-deploy-plugin</artifactId>
|
||||
<version>2.8.2</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>deploy-file</id>
|
||||
<goals>
|
||||
<goal>deploy-file</goal>
|
||||
</goals>
|
||||
<!-- 跳过默认的发布包 -->
|
||||
<phase>none</phase>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<!-- deploy配置,指定将哪个目录下的jar包发布为这个工程的jar -->
|
||||
<file>${project.build.directory}/${project.build.finalName}-repack.jar</file>
|
||||
<!-- 发布地址 -->
|
||||
<url>http://local-maven.space.com/nexus/content/repositories/snapshots/</url>
|
||||
<repositoryId>nexus-snapshots</repositoryId>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<artifactId>${project.artifactId}</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<packaging>jar</packaging>
|
||||
<!--
|
||||
deploy配置,指定将deploy的jar打包使用的pom.xml文件,
|
||||
即其他依赖这个jar包的工程按照哪个pom.xml文件来解析依赖
|
||||
-->
|
||||
<pomFile>src/main/resources/install-pom.xml</pomFile>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,32 @@
|
||||
<assembly xmlns="http://maven.apache.org/ASSEMBLY/2.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.0.0 http://maven.apache.org/xsd/assembly-2.0.0.xsd">
|
||||
<id>repack</id>
|
||||
<formats>
|
||||
<format>jar</format>
|
||||
</formats>
|
||||
|
||||
<includeBaseDirectory>false</includeBaseDirectory>
|
||||
<dependencySets>
|
||||
<dependencySet>
|
||||
<!--
|
||||
重新打包需要包含的jar包,这个过程会解压所有的jar包并按照jar的规则重新组合成一个jar包
|
||||
这里需要包含当前这个工程 ,格式是{groupId}:{artifactId}
|
||||
-->
|
||||
<includes>
|
||||
<!-- 包含项目自己 -->
|
||||
<include>com.xspaceagi:app-platform-log-job</include>
|
||||
<include>com.xspaceagi:app-platform-log-application</include>
|
||||
<include>com.xspaceagi:app-platform-log-domain</include>
|
||||
<include>com.xspaceagi:app-platform-log-infra</include>
|
||||
<include>com.xspaceagi:app-platform-log-spec</include>
|
||||
</includes>
|
||||
<!-- 打包结果目录:/ 表示target/ -->
|
||||
<outputDirectory>/</outputDirectory>
|
||||
<!-- 是否解压依赖 -->
|
||||
<unpack>true</unpack>
|
||||
<!-- 打包的scope -->
|
||||
<!-- <scope>system</scope>-->
|
||||
</dependencySet>
|
||||
</dependencySets>
|
||||
</assembly>
|
||||
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>app-platform-log-job</artifactId>
|
||||
<groupId>com.xspaceagi</groupId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>app-platform-log-job</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>com.xspaceagi</groupId>
|
||||
<artifactId>app-platform-log</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>app-platform-log-sdk</artifactId>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>17</maven.compiler.source>
|
||||
<maven.compiler.target>17</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.xspaceagi.log.sdk.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Target(ElementType.FIELD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface SearchField {
|
||||
|
||||
/**
|
||||
* 是否索引字段
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
boolean index() default true;
|
||||
|
||||
/**
|
||||
* 是否为keyword字段
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
boolean keyword() default false;
|
||||
|
||||
/**
|
||||
* 是否存储字段
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
boolean store() default false;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.xspaceagi.log.sdk.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface SearchIndex {
|
||||
|
||||
/**
|
||||
* 索引名称,默认类名
|
||||
*/
|
||||
String indexName() default "";
|
||||
|
||||
/**
|
||||
* 分片数量
|
||||
*/
|
||||
int shards() default 3;
|
||||
|
||||
/**
|
||||
* 副本数量
|
||||
*/
|
||||
int replicas() default 1;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
package com.xspaceagi.log.sdk.reponse;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 智能体调试大模型的日志结构定义
|
||||
* 对应 Rust 的 AgentLogEntry 结构
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class AgentLogModelResponse {
|
||||
|
||||
/**
|
||||
* 请求ID,唯一标识一次请求(必填)
|
||||
*/
|
||||
@Schema(description = "请求ID,唯一标识一次请求(必填)")
|
||||
private String requestId;
|
||||
|
||||
/**
|
||||
* 消息ID,(必填)
|
||||
*/
|
||||
@Schema(description = "消息ID,(必填)")
|
||||
private String messageId;
|
||||
|
||||
/**
|
||||
* 智能体ID
|
||||
*/
|
||||
@Schema(description = "智能体ID")
|
||||
private String agentId;
|
||||
|
||||
/**
|
||||
* 会话ID(必填)
|
||||
*/
|
||||
@Schema(description = "会话ID(必填)")
|
||||
private String conversationId;
|
||||
|
||||
/**
|
||||
* 用户UID(必填)
|
||||
*/
|
||||
@Schema(description = "用户UID(必填)")
|
||||
private String userUid;
|
||||
|
||||
/**
|
||||
* 租户ID,用于租户隔离(必填)
|
||||
*/
|
||||
@Schema(description = "租户ID,用于租户隔离(必填)")
|
||||
private String tenantId;
|
||||
|
||||
/**
|
||||
* 空间ID,用户可以有多个空间
|
||||
*/
|
||||
@Schema(description = "空间ID,用户可以有多个空间")
|
||||
private String spaceId;
|
||||
|
||||
/**
|
||||
* 用户输入的内容
|
||||
*/
|
||||
@Schema(description = "用户输入的内容")
|
||||
private String userInput;
|
||||
|
||||
/**
|
||||
* 系统输出的内容
|
||||
*/
|
||||
@Schema(description = "系统输出的内容")
|
||||
private String output;
|
||||
|
||||
/**
|
||||
* 执行结果,扩展字段,字段类型存储的是json文本
|
||||
*/
|
||||
@Schema(description = "执行结果,扩展字段,字段类型存储的是json文本")
|
||||
private String executeResult;
|
||||
|
||||
/**
|
||||
* 输入token数量
|
||||
*/
|
||||
@Schema(description = "输入token数量")
|
||||
private Integer inputToken;
|
||||
|
||||
/**
|
||||
* 输出token数量
|
||||
*/
|
||||
@Schema(description = "输出token数量")
|
||||
private Integer outputToken;
|
||||
|
||||
/**
|
||||
* 请求开始时间
|
||||
*/
|
||||
@Schema(description = "请求开始时间")
|
||||
private LocalDateTime requestStartTime;
|
||||
|
||||
/**
|
||||
* 请求结束时间
|
||||
*/
|
||||
@Schema(description = "请求结束时间")
|
||||
private LocalDateTime requestEndTime;
|
||||
|
||||
/**
|
||||
* 耗时(毫秒)
|
||||
*/
|
||||
@Schema(description = "耗时(毫秒)")
|
||||
private Long elapsedTimeMs;
|
||||
|
||||
/**
|
||||
* 节点类型
|
||||
*/
|
||||
@Schema(description = "节点类型")
|
||||
private String nodeType;
|
||||
|
||||
/**
|
||||
* 节点状态
|
||||
*/
|
||||
@Schema(description = "节点状态")
|
||||
private String status;
|
||||
|
||||
/**
|
||||
* 节点名称
|
||||
*/
|
||||
@Schema(description = "节点名称")
|
||||
private String nodeName;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@Schema(description = "创建时间")
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
@Schema(description = "更新时间")
|
||||
private LocalDateTime updatedAt;
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
@Schema(description = "用户ID")
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* 用户名称
|
||||
*/
|
||||
@Schema(description = "用户名称")
|
||||
private String userName;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.xspaceagi.log.sdk.request;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 日志搜索请求参数
|
||||
* 对应 Rust 的 AgentLogSearchParams 结构
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class AgentLogDetailParamsRequest {
|
||||
|
||||
/**
|
||||
* 请求ID
|
||||
*/
|
||||
private String requestId;
|
||||
|
||||
/**
|
||||
* 智能体ID
|
||||
*/
|
||||
private String agentId;
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
package com.xspaceagi.log.sdk.request;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 智能体调试大模型的日志结构定义
|
||||
* 对应 Rust 的 AgentLogEntry 结构
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class AgentLogEntryRequest {
|
||||
|
||||
/**
|
||||
* 请求ID,唯一标识一次请求(必填)
|
||||
*/
|
||||
@Schema(description = "请求ID,唯一标识一次请求(必填)", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotNull(message = "requestId is required")
|
||||
private String requestId;
|
||||
/**
|
||||
* 消息ID,(必填)
|
||||
*/
|
||||
private String messageId;
|
||||
|
||||
/**
|
||||
* 会话ID(必填)
|
||||
*/
|
||||
private String conversationId;
|
||||
|
||||
/**
|
||||
* 智能体ID
|
||||
*/
|
||||
private String agentId;
|
||||
|
||||
/**
|
||||
* 用户UID(必填)
|
||||
*/
|
||||
private String userUid;
|
||||
|
||||
/**
|
||||
* 租户ID,用于租户隔离(必填)
|
||||
*/
|
||||
private String tenantId;
|
||||
|
||||
/**
|
||||
* 空间ID,用户可以有多个空间
|
||||
*/
|
||||
private String spaceId;
|
||||
|
||||
/**
|
||||
* 用户输入的内容
|
||||
*/
|
||||
private String userInput;
|
||||
|
||||
/**
|
||||
* 系统输出的内容
|
||||
*/
|
||||
private String output;
|
||||
/**
|
||||
* 执行结果,扩展字段,字段类型存储的是json文本
|
||||
*/
|
||||
private String executeResult;
|
||||
|
||||
/**
|
||||
* 输入token数量
|
||||
*/
|
||||
private Integer inputToken;
|
||||
|
||||
/**
|
||||
* 输出token数量
|
||||
*/
|
||||
private Integer outputToken;
|
||||
|
||||
/**
|
||||
* 请求开始时间
|
||||
*/
|
||||
private LocalDateTime requestStartTime;
|
||||
|
||||
/**
|
||||
* 请求结束时间
|
||||
*/
|
||||
private LocalDateTime requestEndTime;
|
||||
|
||||
/**
|
||||
* 耗时(毫秒)
|
||||
*/
|
||||
private Long elapsedTimeMs;
|
||||
|
||||
/**
|
||||
* 节点类型
|
||||
*/
|
||||
private String nodeType;
|
||||
|
||||
/**
|
||||
* 节点状态
|
||||
*/
|
||||
private String status;
|
||||
|
||||
/**
|
||||
* 节点名称
|
||||
*/
|
||||
private String nodeName;
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
private Long userId;
|
||||
/**
|
||||
* 用户名称
|
||||
*/
|
||||
private String userName;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package com.xspaceagi.log.sdk.request;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 日志搜索请求参数
|
||||
* 对应 Rust 的 AgentLogSearchParams 结构
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class AgentLogSearchParamsRequest {
|
||||
|
||||
/**
|
||||
* 请求ID
|
||||
*/
|
||||
private String requestId;
|
||||
|
||||
/**
|
||||
* 会话ID
|
||||
*/
|
||||
private String conversationId;
|
||||
|
||||
/**
|
||||
* 消息ID
|
||||
*/
|
||||
private String messageId;
|
||||
/**
|
||||
* 智能体ID
|
||||
*/
|
||||
private String agentId;
|
||||
|
||||
/**
|
||||
* 用户UID
|
||||
*/
|
||||
private String userUid;
|
||||
|
||||
/**
|
||||
* 用户输入,需要支持全文检索,支持多个关键字(AND关系)
|
||||
*/
|
||||
private List<String> userInput;
|
||||
|
||||
/**
|
||||
* 系统输出,需要支持全文检索,支持多个关键字(AND关系)
|
||||
*/
|
||||
private List<String> output;
|
||||
|
||||
/**
|
||||
* 开始时间
|
||||
*/
|
||||
private LocalDateTime startTime;
|
||||
|
||||
/**
|
||||
* 结束时间
|
||||
*/
|
||||
private LocalDateTime endTime;
|
||||
|
||||
/**
|
||||
* 租户ID,用于租户隔离,确保只查询特定租户的日志
|
||||
*/
|
||||
private String tenantId;
|
||||
|
||||
/**
|
||||
* 空间ID,可选,用于查询特定空间的日志,支持多个ID(OR关系)
|
||||
*/
|
||||
private List<String> spaceId;
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package com.xspaceagi.log.sdk.request;
|
||||
|
||||
import com.xspaceagi.log.sdk.vo.SearchDocument;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class DocumentSearchRequest implements Serializable {
|
||||
|
||||
@Schema(description = "搜索文档类", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private Class<? extends SearchDocument> searchDocumentClazz;
|
||||
|
||||
@Schema(description = "搜索字段", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
|
||||
private List<String> searchFields;
|
||||
|
||||
@Schema(description = "搜索关键字", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
|
||||
private String keyword;
|
||||
|
||||
@Schema(description = "过滤字段,全匹配", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
|
||||
private List<Map<String, Object>> filterFieldsAndValues;
|
||||
|
||||
//排序
|
||||
@Schema(description = "排序字段", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
|
||||
private Map<String, Object> sortFieldsAndValues;
|
||||
|
||||
@Schema(description = "分页起始位置", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
|
||||
private Integer from;
|
||||
|
||||
@Schema(description = "分页大小", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
|
||||
private Integer size;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.xspaceagi.log.sdk.service;
|
||||
|
||||
import com.xspaceagi.log.sdk.request.DocumentSearchRequest;
|
||||
import com.xspaceagi.log.sdk.vo.LogDocument;
|
||||
import com.xspaceagi.log.sdk.vo.SearchResult;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface ILogRpcService {
|
||||
|
||||
void bulkIndex(List<LogDocument> list);
|
||||
|
||||
void pushTraceLog(Object traceContext);
|
||||
|
||||
void deleteLogDocument(String id);
|
||||
|
||||
SearchResult search(DocumentSearchRequest documentSearchRequest);
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.xspaceagi.log.sdk.service;
|
||||
|
||||
import com.xspaceagi.log.sdk.request.DocumentSearchRequest;
|
||||
import com.xspaceagi.log.sdk.vo.SearchDocument;
|
||||
import com.xspaceagi.log.sdk.vo.SearchResult;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface ISearchRpcService {
|
||||
|
||||
void bulkIndex(List<SearchDocument> list);
|
||||
|
||||
void deleteDocument(Class<? extends SearchDocument> searchDocumentClazz, String id);
|
||||
|
||||
SearchResult search(DocumentSearchRequest documentSearchRequest);
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.xspaceagi.log.sdk.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.xspaceagi.log.sdk.reponse.AgentLogModelResponse;
|
||||
import com.xspaceagi.log.sdk.request.AgentLogEntryRequest;
|
||||
import com.xspaceagi.log.sdk.request.AgentLogSearchParamsRequest;
|
||||
|
||||
/**
|
||||
* 日志平台的 RPC 服务,用于新增日志
|
||||
*/
|
||||
public interface LogPlatformRpcService {
|
||||
|
||||
/**
|
||||
* 新增日志
|
||||
*
|
||||
* @param logEntryRequest
|
||||
* @return
|
||||
*/
|
||||
boolean addLog(AgentLogEntryRequest logEntryRequest);
|
||||
|
||||
/**
|
||||
* 搜索日志
|
||||
*
|
||||
* @param searchParams 搜索参数
|
||||
* @param current 页码
|
||||
* @param pageSize 页大小
|
||||
* @return
|
||||
*/
|
||||
IPage<AgentLogModelResponse> searchAgentLogs(AgentLogSearchParamsRequest searchParams,
|
||||
Long current,
|
||||
Long pageSize);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
package com.xspaceagi.log.sdk.vo;
|
||||
|
||||
import com.xspaceagi.log.sdk.annotation.SearchField;
|
||||
import com.xspaceagi.log.sdk.annotation.SearchIndex;
|
||||
import com.xspaceagi.log.sdk.vo.SearchDocument;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@SearchIndex(indexName = "req_log")
|
||||
public class LogDocument extends SearchDocument {
|
||||
|
||||
@Schema(description = "租户ID", hidden = true)
|
||||
private Long tenantId;
|
||||
|
||||
@Schema(description = "日志ID, 不用展示,用于查询详情")
|
||||
private String id;
|
||||
|
||||
@Schema(description = "请求唯一标识可以用于关联一次请求中所有相关的操作")
|
||||
@SearchField(keyword = true)
|
||||
private String requestId;
|
||||
|
||||
@Schema(description = "日志产生对象所在的空间ID")
|
||||
private Long spaceId;
|
||||
|
||||
@Schema(description = "请求发起的用户ID")
|
||||
private Long userId;
|
||||
|
||||
@Schema(description = "用户名")
|
||||
@SearchField(keyword = true)
|
||||
private String userName;
|
||||
|
||||
@Schema(description = "日志对象类型")
|
||||
@SearchField(keyword = true)
|
||||
private String targetType;
|
||||
|
||||
@Schema(description = "日志对象名称")
|
||||
@SearchField(keyword = true)
|
||||
private String targetName;
|
||||
|
||||
@Schema(description = "日志对象ID")
|
||||
@SearchField(keyword = true)
|
||||
private String targetId;
|
||||
|
||||
@Schema(description = "会话ID")
|
||||
@SearchField(keyword = true)
|
||||
private String conversationId;
|
||||
|
||||
@Schema(description = "输入参数")
|
||||
@SearchField(store = true)
|
||||
private String input;
|
||||
|
||||
@Schema(description = "执行结果")
|
||||
@SearchField(store = true)
|
||||
private String output;
|
||||
|
||||
@Schema(description = "执行过程数据")
|
||||
@SearchField(store = true)
|
||||
private String processData;
|
||||
|
||||
@Schema(description = "缓存输入token数量")
|
||||
private Integer cacheInputToken;
|
||||
|
||||
@Schema(description = "输入token数量")
|
||||
private Integer inputToken;
|
||||
|
||||
@Schema(description = "输出token数量")
|
||||
private Integer outputToken;
|
||||
|
||||
@Schema(description = "请求开始时间")
|
||||
private Long requestStartTime;
|
||||
|
||||
@Schema(description = "请求结束时间")
|
||||
private Long requestEndTime;
|
||||
|
||||
@Schema(description = "执行结果码 0000为成功")
|
||||
@SearchField(keyword = true)
|
||||
private String resultCode;
|
||||
|
||||
@Schema(description = "执行结果描述")
|
||||
@SearchField(store = true)
|
||||
private String resultMsg;
|
||||
|
||||
@Schema(description = "日志产生时间")
|
||||
private Long createTime;
|
||||
|
||||
@Schema(description = "日志产生来源", hidden = true)
|
||||
@SearchField(keyword = true)
|
||||
private String from;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.xspaceagi.log.sdk.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
@Data
|
||||
public class SearchDocument implements Serializable {
|
||||
|
||||
private String id;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.xspaceagi.log.sdk.vo;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class SearchResult implements Serializable {
|
||||
|
||||
private Long total;
|
||||
private List<SearchResultItem> items;
|
||||
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Data
|
||||
public static class SearchResultItem {
|
||||
private Double score;
|
||||
private SearchDocument document;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>app-platform-log</artifactId>
|
||||
<groupId>com.xspaceagi</groupId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>app-platform-log-spec</artifactId>
|
||||
|
||||
<properties>
|
||||
<maven.deploy.skip>true</maven.deploy.skip>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.xspaceagi</groupId>
|
||||
<artifactId>system-sdk</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,43 @@
|
||||
package com.xspaceagi.log.spec;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.databind.SerializerProvider;
|
||||
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
/**
|
||||
* 自定义 LocalDateTime 的序列化器,按照UTC时间格式输出
|
||||
*/
|
||||
public class CustomLocalDateTimeSerializer extends StdSerializer<LocalDateTime> {
|
||||
|
||||
|
||||
private static final String UTC_DATE_TIME_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'";
|
||||
private static final ZoneId BEIJING_ZONE_ID = ZoneId.of("Asia/Shanghai");
|
||||
private static final ZoneId UTC_ZONE_ID = ZoneId.of("UTC");
|
||||
|
||||
|
||||
private final DateTimeFormatter formatter = DateTimeFormatter.ofPattern(UTC_DATE_TIME_PATTERN);
|
||||
|
||||
|
||||
public CustomLocalDateTimeSerializer() {
|
||||
super(LocalDateTime.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serialize(LocalDateTime value, JsonGenerator gen, SerializerProvider provider) throws IOException {
|
||||
if (value != null) {
|
||||
// LocalDateTime 是东八区的本地时间,将其转换为 UTC 时间
|
||||
ZonedDateTime beijingDateTime = value.atZone(BEIJING_ZONE_ID);
|
||||
ZonedDateTime utcDateTime = beijingDateTime.withZoneSameInstant(UTC_ZONE_ID);
|
||||
// 使用指定的格式输出 UTC 时间
|
||||
gen.writeString(utcDateTime.format(formatter));
|
||||
} else {
|
||||
gen.writeNull();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
package com.xspaceagi.log.spec;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonParseException;
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.core.JsonToken;
|
||||
import com.fasterxml.jackson.databind.DeserializationContext;
|
||||
import com.fasterxml.jackson.databind.JsonDeserializer;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.format.DateTimeParseException;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* 反序列化 {@link LocalDateTime}:支持 UTC/带 Z 的字符串、ISO 日期时间、仅日期 {@code yyyy-MM-dd}、以及 Unix 时间戳。
|
||||
*/
|
||||
@Slf4j
|
||||
public class FlexibleDateTimeDeserializer extends JsonDeserializer<LocalDateTime> {
|
||||
|
||||
private static final DateTimeFormatter FORMATTER_WITH_NANOS =
|
||||
DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSSSS'Z'").withZone(ZoneId.of("UTC"));
|
||||
private static final DateTimeFormatter FORMATTER_WITH_MILLIS =
|
||||
DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'").withZone(ZoneId.of("UTC"));
|
||||
private static final DateTimeFormatter FORMATTER_WITHOUT_FRACTION =
|
||||
DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss'Z'").withZone(ZoneId.of("UTC"));
|
||||
|
||||
private static final DateTimeFormatter SPACE_SEPARATED =
|
||||
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss", Locale.ROOT);
|
||||
|
||||
private static final ZoneId BEIJING_ZONE = ZoneId.of("Asia/Shanghai");
|
||||
|
||||
@Override
|
||||
public LocalDateTime deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
|
||||
JsonToken token = p.currentToken();
|
||||
if (token == JsonToken.VALUE_NULL) {
|
||||
return null;
|
||||
}
|
||||
if (token == JsonToken.VALUE_NUMBER_INT || token == JsonToken.VALUE_NUMBER_FLOAT) {
|
||||
return fromEpoch(p.getLongValue());
|
||||
}
|
||||
if (token == JsonToken.VALUE_STRING) {
|
||||
String dateString = p.getText().trim();
|
||||
if (StringUtils.isBlank(dateString)) {
|
||||
return null;
|
||||
}
|
||||
return parseString(dateString, p);
|
||||
}
|
||||
throw new JsonParseException(p, "无法解析 LocalDateTime:需要字符串或数字,实际为 " + token);
|
||||
}
|
||||
|
||||
private static LocalDateTime fromEpoch(long raw) {
|
||||
Instant instant = raw > 10_000_000_000L ? Instant.ofEpochMilli(raw) : Instant.ofEpochSecond(raw);
|
||||
return LocalDateTime.ofInstant(instant, BEIJING_ZONE);
|
||||
}
|
||||
|
||||
private LocalDateTime parseString(String dateString, JsonParser p) throws IOException {
|
||||
try {
|
||||
if (dateString.matches(".*\\.\\d{6}Z$")) {
|
||||
Instant instant = Instant.from(FORMATTER_WITH_NANOS.parse(dateString));
|
||||
return LocalDateTime.ofInstant(instant, BEIJING_ZONE);
|
||||
}
|
||||
if (dateString.matches(".*\\.\\d{3}Z$")) {
|
||||
Instant instant = Instant.from(FORMATTER_WITH_MILLIS.parse(dateString));
|
||||
return LocalDateTime.ofInstant(instant, BEIJING_ZONE);
|
||||
}
|
||||
if (dateString.endsWith("Z") && dateString.contains("T")) {
|
||||
Instant instant = Instant.from(FORMATTER_WITHOUT_FRACTION.parse(dateString));
|
||||
return LocalDateTime.ofInstant(instant, BEIJING_ZONE);
|
||||
}
|
||||
} catch (DateTimeParseException ignored) {
|
||||
// 走下方通用解析
|
||||
}
|
||||
try {
|
||||
return LocalDateTime.ofInstant(OffsetDateTime.parse(dateString).toInstant(), BEIJING_ZONE);
|
||||
} catch (DateTimeParseException ignored) {
|
||||
}
|
||||
try {
|
||||
return LocalDateTime.parse(dateString, DateTimeFormatter.ISO_LOCAL_DATE_TIME);
|
||||
} catch (DateTimeParseException ignored) {
|
||||
}
|
||||
try {
|
||||
return LocalDateTime.parse(dateString, SPACE_SEPARATED);
|
||||
} catch (DateTimeParseException ignored) {
|
||||
}
|
||||
try {
|
||||
// 纯日期 yyyy-MM-dd → 当天 00:00:00(东八区墙钟)
|
||||
return LocalDate.parse(dateString, DateTimeFormatter.ISO_LOCAL_DATE).atStartOfDay(BEIJING_ZONE).toLocalDateTime();
|
||||
} catch (DateTimeParseException ignored) {
|
||||
}
|
||||
log.error("无法解析日期时间: {}", dateString);
|
||||
throw new JsonParseException(p, "无法解析日期时间: " + dateString);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>app-platform-log</artifactId>
|
||||
<groupId>com.xspaceagi</groupId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>app-platform-log-web</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.xspaceagi</groupId>
|
||||
<artifactId>app-platform-log-application</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.xspaceagi</groupId>
|
||||
<artifactId>system-application</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<!-- 打包插件 -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-assembly-plugin</artifactId>
|
||||
<version>2.4</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>make-assembly</id>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>single</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<descriptors>
|
||||
<!-- 打包配置:必须在对应目录下创建repack.xml的打包配置文件 -->
|
||||
<descriptor>src/main/resources/assemblies/repack.xml</descriptor>
|
||||
</descriptors>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<!-- install插件,定制在mvn install过程使用哪个jar包安装到本地 -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-install-plugin</artifactId>
|
||||
<version>2.5.2</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>install-file</id>
|
||||
<goals>
|
||||
<goal>install-file</goal>
|
||||
</goals>
|
||||
<phase>install</phase>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<!-- install配置,指定将哪个目录下的jar包install为这个工程的jar -->
|
||||
<file>${project.build.directory}/${project.build.finalName}-repack.jar</file>
|
||||
<!--
|
||||
install配置,指定将install的jar打包使用的pom.xml文件,
|
||||
即其他依赖这个jar包的工程按照哪个pom.xml文件来解析依赖
|
||||
-->
|
||||
<pomFile>src/main/resources/install-pom.xml</pomFile>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<!-- deploy插件,定制在mvn deploy过程使用哪个jar包发布到仓库 -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-deploy-plugin</artifactId>
|
||||
<version>2.8.2</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>deploy-file</id>
|
||||
<goals>
|
||||
<goal>deploy-file</goal>
|
||||
</goals>
|
||||
<!-- 跳过默认的发布包 -->
|
||||
<phase>none</phase>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<!-- deploy配置,指定将哪个目录下的jar包发布为这个工程的jar -->
|
||||
<file>${project.build.directory}/${project.build.finalName}-repack.jar</file>
|
||||
<!-- 发布地址 -->
|
||||
<url>http://local-maven.space.com/nexus/content/repositories/snapshots/</url>
|
||||
<repositoryId>nexus-snapshots</repositoryId>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<artifactId>${project.artifactId}</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<packaging>jar</packaging>
|
||||
<!--
|
||||
deploy配置,指定将deploy的jar打包使用的pom.xml文件,
|
||||
即其他依赖这个jar包的工程按照哪个pom.xml文件来解析依赖
|
||||
-->
|
||||
<pomFile>src/main/resources/install-pom.xml</pomFile>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>com.github.shalousun</groupId>
|
||||
<artifactId>smart-doc-maven-plugin</artifactId>
|
||||
<version>2.3.6</version>
|
||||
<configuration>
|
||||
<!--指定生成文档的使用的配置文件,配置文件放在自己的项目中-->
|
||||
<configFile>./src/main/resources/smart-doc.json</configFile>
|
||||
<!--指定项目名称-->
|
||||
<projectName>${project.artifactId}</projectName>
|
||||
<!--smart-doc实现自动分析依赖树加载第三方依赖的源码,如果一些框架依赖库加载不到导致报错,这时请使用excludes排除掉-->
|
||||
<excludes>
|
||||
<!--格式为:groupId:artifactId;参考如下-->
|
||||
<!--也可以支持正则式如:com.alibaba:.* -->
|
||||
<exclude>com.alibaba:fastjson</exclude>
|
||||
</excludes>
|
||||
<!--includes配置用于配置加载外部依赖源码,配置后插件会按照配置项加载外部源代码而不是自动加载所有,因此使用时需要注意-->
|
||||
<!--smart-doc能自动分析依赖树加载所有依赖源码,原则上会影响文档构建效率,因此你可以使用includes来让插件加载你配置的组件-->
|
||||
<includes></includes>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<!--smart-doc提供了html、openapi、markdown等goal,可按需配置-->
|
||||
<goal>rpc-html</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
|
||||
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,99 @@
|
||||
package com.xspaceagi.log.web.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.xspaceagi.log.sdk.request.DocumentSearchRequest;
|
||||
import com.xspaceagi.log.sdk.service.ISearchRpcService;
|
||||
import com.xspaceagi.log.sdk.vo.LogDocument;
|
||||
import com.xspaceagi.log.sdk.vo.SearchResult;
|
||||
import com.xspaceagi.log.web.controller.base.BaseController;
|
||||
import com.xspaceagi.log.web.controller.dto.LogDetailQueryDto;
|
||||
import com.xspaceagi.log.web.controller.dto.LogQueryDto;
|
||||
import com.xspaceagi.system.sdk.permission.SpacePermissionService;
|
||||
import com.xspaceagi.system.spec.annotation.RequireResource;
|
||||
import com.xspaceagi.system.spec.common.RequestContext;
|
||||
import com.xspaceagi.system.spec.dto.ReqResult;
|
||||
import com.xspaceagi.system.spec.page.PageQueryVo;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static com.xspaceagi.system.spec.enums.ResourceEnum.SPACE_LOG_QUERY_DETAIL;
|
||||
import static com.xspaceagi.system.spec.enums.ResourceEnum.SPACE_LOG_QUERY_LIST;
|
||||
|
||||
@Tag(name = "日志平台-搜索接口")
|
||||
@RestController
|
||||
@RequestMapping("/api/requestLogs")
|
||||
@Slf4j
|
||||
public class LogQueryController extends BaseController {
|
||||
|
||||
@Resource
|
||||
private ISearchRpcService iSearchRpcService;
|
||||
|
||||
@Resource
|
||||
private SpacePermissionService spacePermissionService;
|
||||
|
||||
@RequireResource(SPACE_LOG_QUERY_LIST)
|
||||
@Operation(summary = "统一日志查询(工作空间)")
|
||||
@RequestMapping(path = "/list", method = RequestMethod.POST)
|
||||
public ReqResult<IPage<LogDocument>> search(@RequestBody PageQueryVo<LogQueryDto> pageQueryVo) {
|
||||
var filter = pageQueryVo.getQueryFilter();
|
||||
if (filter == null) {
|
||||
return ReqResult.error("查询条件不能为空");
|
||||
}
|
||||
if (filter.getSpaceId() == null) {
|
||||
return ReqResult.error("空间ID不能为空");
|
||||
}
|
||||
|
||||
spacePermissionService.checkSpaceUserPermission(filter.getSpaceId());
|
||||
return search0(pageQueryVo);
|
||||
}
|
||||
|
||||
@Operation(summary = "统一日志查询(API调用)")
|
||||
@RequestMapping(path = "/apikey/list", method = RequestMethod.POST)
|
||||
public ReqResult<IPage<LogDocument>> apiInvokeSearch(@RequestBody PageQueryVo<LogQueryDto> pageQueryVo) {
|
||||
var filter = pageQueryVo.getQueryFilter();
|
||||
if (filter == null) {
|
||||
pageQueryVo.setQueryFilter(new LogQueryDto());
|
||||
filter = pageQueryVo.getQueryFilter();
|
||||
}
|
||||
filter.setTargetType("ApiKey");
|
||||
filter.setUserId(RequestContext.get().getUserId());
|
||||
return search0(pageQueryVo);
|
||||
}
|
||||
|
||||
|
||||
@RequireResource(SPACE_LOG_QUERY_DETAIL)
|
||||
@Operation(summary = "日志详情")
|
||||
@RequestMapping(path = "/detail", method = RequestMethod.POST)
|
||||
public ReqResult<LogDocument> detail(@RequestBody LogDetailQueryDto logDetailQueryDto) {
|
||||
|
||||
DocumentSearchRequest.DocumentSearchRequestBuilder builder = DocumentSearchRequest.builder();
|
||||
builder.searchDocumentClazz(LogDocument.class);
|
||||
List<Map<String, Object>> filterFieldsAndValues = new ArrayList<>();
|
||||
builder.filterFieldsAndValues(filterFieldsAndValues);
|
||||
if (StringUtils.isNoneBlank(logDetailQueryDto.getId())) {
|
||||
filterFieldsAndValues.add(Map.of("id", logDetailQueryDto.getId()));
|
||||
}
|
||||
|
||||
SearchResult search = iSearchRpcService.search(builder.build());
|
||||
LogDocument logDocument = search.getItems().stream().map(item -> (LogDocument) item.getDocument()).findFirst().orElse(null);
|
||||
if (logDocument == null) {
|
||||
return ReqResult.error("日志不存在");
|
||||
}
|
||||
if (!RequestContext.get().getUserId().equals(logDocument.getUserId())) {
|
||||
spacePermissionService.checkSpaceUserPermission(logDocument.getSpaceId());
|
||||
}
|
||||
return ReqResult.success(logDocument);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package com.xspaceagi.log.web.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.xspaceagi.log.sdk.request.DocumentSearchRequest;
|
||||
import com.xspaceagi.log.sdk.service.ISearchRpcService;
|
||||
import com.xspaceagi.log.sdk.vo.LogDocument;
|
||||
import com.xspaceagi.log.sdk.vo.SearchResult;
|
||||
import com.xspaceagi.log.web.controller.base.BaseController;
|
||||
import com.xspaceagi.log.web.controller.dto.LogDetailQueryDto;
|
||||
import com.xspaceagi.log.web.controller.dto.LogQueryDto;
|
||||
import com.xspaceagi.system.spec.annotation.RequireResource;
|
||||
import com.xspaceagi.system.spec.common.RequestContext;
|
||||
import com.xspaceagi.system.spec.dto.ReqResult;
|
||||
import com.xspaceagi.system.spec.exception.LogPlatformException;
|
||||
import com.xspaceagi.system.spec.page.PageQueryVo;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static com.xspaceagi.system.spec.enums.ResourceEnum.SYSTEM_RUNNING_LOG_QUERY_DETAIL;
|
||||
import static com.xspaceagi.system.spec.enums.ResourceEnum.SYSTEM_RUNNING_LOG_QUERY_LIST;
|
||||
|
||||
@Tag(name = "日志平台管理端-搜索接口")
|
||||
@RestController
|
||||
@RequestMapping("/api/system/requestLogs")
|
||||
@Slf4j
|
||||
public class LogQueryManController extends BaseController {
|
||||
|
||||
@Resource
|
||||
private ISearchRpcService iSearchRpcService;
|
||||
|
||||
@RequireResource({SYSTEM_RUNNING_LOG_QUERY_LIST})
|
||||
@Operation(summary = "统一日志查询")
|
||||
@RequestMapping(path = "/list", method = RequestMethod.POST)
|
||||
public ReqResult<IPage<LogDocument>> search(@RequestBody PageQueryVo<LogQueryDto> pageQueryVo) {
|
||||
return search0(pageQueryVo);
|
||||
}
|
||||
|
||||
@RequireResource({SYSTEM_RUNNING_LOG_QUERY_DETAIL})
|
||||
@Operation(summary = "日志详情")
|
||||
@RequestMapping(path = "/detail", method = RequestMethod.POST)
|
||||
public ReqResult<LogDocument> detail(@RequestBody LogDetailQueryDto logDetailQueryDto) {
|
||||
|
||||
DocumentSearchRequest.DocumentSearchRequestBuilder builder = DocumentSearchRequest.builder();
|
||||
builder.searchDocumentClazz(LogDocument.class);
|
||||
List<Map<String, Object>> filterFieldsAndValues = new ArrayList<>();
|
||||
builder.filterFieldsAndValues(filterFieldsAndValues);
|
||||
if (StringUtils.isNoneBlank(logDetailQueryDto.getId())) {
|
||||
filterFieldsAndValues.add(Map.of("id", logDetailQueryDto.getId()));
|
||||
}
|
||||
|
||||
SearchResult search = iSearchRpcService.search(builder.build());
|
||||
LogDocument logDocument = search.getItems().stream().map(item -> (LogDocument) item.getDocument()).findFirst().orElse(null);
|
||||
if (logDocument == null) {
|
||||
return ReqResult.error("日志不存在");
|
||||
}
|
||||
return ReqResult.success(logDocument);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
package com.xspaceagi.log.web.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.xspaceagi.domain.model.AgentLogModel;
|
||||
import com.xspaceagi.domain.model.valueobj.AgentLogSearchParams;
|
||||
import com.xspaceagi.log.app.service.LogPlatformApplicationService;
|
||||
import com.xspaceagi.log.sdk.request.AgentLogDetailParamsRequest;
|
||||
import com.xspaceagi.log.web.controller.base.BaseController;
|
||||
import com.xspaceagi.log.web.controller.dto.AgentLogSearchParamsRequest;
|
||||
import com.xspaceagi.system.sdk.operate.ActionType;
|
||||
import com.xspaceagi.system.sdk.operate.OperationLogReporter;
|
||||
import com.xspaceagi.system.sdk.operate.SystemEnum;
|
||||
import com.xspaceagi.system.spec.dto.ReqResult;
|
||||
import com.xspaceagi.system.spec.exception.LogPlatformException;
|
||||
import com.xspaceagi.system.spec.page.PageQueryVo;
|
||||
import com.xspaceagi.system.application.service.SpaceApplicationService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@Tag(name = "日志平台-搜索接口")
|
||||
@RestController
|
||||
@RequestMapping("/api/logPlatform/agent")
|
||||
@Slf4j
|
||||
public class LogSearchController extends BaseController {
|
||||
|
||||
@Resource
|
||||
private SpaceApplicationService spaceApplicationService;
|
||||
|
||||
@Resource
|
||||
private LogPlatformApplicationService logPlatformApplicationService;
|
||||
|
||||
@OperationLogReporter(actionType = ActionType.QUERY, action = "数据列表查询", objectName = "智能体日志", systemCode = SystemEnum.LOG_PLATFORM)
|
||||
@Operation(summary = "日志查询")
|
||||
@RequestMapping(path = "/list", method = RequestMethod.POST)
|
||||
public ReqResult<IPage<AgentLogModel>> search(
|
||||
@RequestBody PageQueryVo<AgentLogSearchParamsRequest> pageQueryVo) {
|
||||
var userContext = this.getUser();
|
||||
var userId = userContext.getUserId();
|
||||
|
||||
var filter = pageQueryVo.getQueryFilter();
|
||||
if (filter == null) {
|
||||
return ReqResult.error("查询条件不能为空");
|
||||
}
|
||||
|
||||
// 查询用户有权限的空间,限制访问空间
|
||||
var spaceList = this.spaceApplicationService.queryListByUserId(userId);
|
||||
var authSpaceIds = spaceList.stream().map(space -> String.valueOf(space.getId())).toList();
|
||||
|
||||
var tenantId = userContext.getTenantId();
|
||||
|
||||
// 转换前端请求为领域模型
|
||||
AgentLogSearchParams searchParams = AgentLogSearchParamsRequest.convertFrom(filter);
|
||||
|
||||
// 设置租户ID(从用户上下文获取)
|
||||
searchParams.setTenantId(tenantId.toString());
|
||||
searchParams.setSpaceId(authSpaceIds);
|
||||
|
||||
try {
|
||||
// 使用 logPlatformApplicationService 接口查询日志
|
||||
IPage<AgentLogModel> result = logPlatformApplicationService.searchAgentLogs(
|
||||
searchParams,
|
||||
pageQueryVo.getCurrent(),
|
||||
pageQueryVo.getPageSize());
|
||||
|
||||
return ReqResult.success(result);
|
||||
} catch (LogPlatformException e) {
|
||||
log.error("Log search failed: {}", e.getMessage(), e);
|
||||
return ReqResult.error(e.getCode(), e.getMessage());
|
||||
} catch (Exception e) {
|
||||
log.error("Unknown error during log search", e);
|
||||
return ReqResult.error("日志搜索发生未知错误: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@OperationLogReporter(actionType = ActionType.QUERY, action = "数据列表查询", objectName = "智能体日志", systemCode = SystemEnum.LOG_PLATFORM)
|
||||
@Operation(summary = "日志详情")
|
||||
@RequestMapping(path = "/detail", method = RequestMethod.POST)
|
||||
public ReqResult<AgentLogModel> detail(
|
||||
@RequestBody AgentLogDetailParamsRequest request) {
|
||||
var userContext = this.getUser();
|
||||
var userId = userContext.getUserId();
|
||||
|
||||
// 查询用户有权限的空间,限制访问空间
|
||||
var spaceList = this.spaceApplicationService.queryListByUserId(userId);
|
||||
var authSpaceIds = spaceList.stream().map(space -> String.valueOf(space.getId())).toList();
|
||||
|
||||
var tenantId = userContext.getTenantId();
|
||||
|
||||
// 转换前端请求为领域模型
|
||||
AgentLogSearchParams searchParams = AgentLogSearchParamsRequest.convertFrom(request);
|
||||
|
||||
// 设置租户ID(从用户上下文获取)
|
||||
searchParams.setTenantId(tenantId.toString());
|
||||
searchParams.setSpaceId(authSpaceIds);
|
||||
|
||||
try {
|
||||
// 使用 logPlatformApplicationService 接口查询日志
|
||||
AgentLogModel result = logPlatformApplicationService.queryOneAgentLog(searchParams);
|
||||
|
||||
return ReqResult.success(result);
|
||||
} catch (LogPlatformException e) {
|
||||
log.error("日志详情失败: {}", e.getMessage(), e);
|
||||
return ReqResult.error(e.getCode(), e.getMessage());
|
||||
} catch (Exception e) {
|
||||
log.error("Unknown error while loading log detail", e);
|
||||
return ReqResult.error("日志搜索发生未知错误: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
package com.xspaceagi.log.web.controller.base;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.xspaceagi.log.sdk.request.DocumentSearchRequest;
|
||||
import com.xspaceagi.log.sdk.service.ISearchRpcService;
|
||||
import com.xspaceagi.log.sdk.vo.LogDocument;
|
||||
import com.xspaceagi.log.web.controller.dto.LogQueryDto;
|
||||
import com.xspaceagi.system.spec.common.RequestContext;
|
||||
import com.xspaceagi.system.spec.common.UserContext;
|
||||
import com.xspaceagi.system.application.dto.UserDto;
|
||||
import com.xspaceagi.system.infra.dao.entity.User;
|
||||
import com.xspaceagi.system.spec.dto.ReqResult;
|
||||
import com.xspaceagi.system.spec.exception.LogPlatformException;
|
||||
import com.xspaceagi.system.spec.page.PageQueryVo;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Slf4j
|
||||
public abstract class BaseController {
|
||||
|
||||
@Resource
|
||||
private ISearchRpcService iSearchRpcService;
|
||||
|
||||
/**
|
||||
* 获取当前登录用户信息
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public UserContext getUser() {
|
||||
var userDto = (UserDto) RequestContext.get().getUser();
|
||||
return UserContext.builder()
|
||||
.userId(userDto.getId())
|
||||
.userName(userDto.getUserName())
|
||||
.nickName(userDto.getNickName())
|
||||
.email(userDto.getEmail())
|
||||
.phone(userDto.getPhone())
|
||||
.status(userDto.getStatus() == User.Status.Enabled ? 1 : -1)
|
||||
.tenantId(userDto.getTenantId())
|
||||
.tenantName(null)
|
||||
.orgId(null)
|
||||
.orgName(null)
|
||||
.roleType(null)
|
||||
.build();
|
||||
}
|
||||
|
||||
|
||||
protected ReqResult<IPage<LogDocument>> search0(@RequestBody PageQueryVo<LogQueryDto> pageQueryVo) {
|
||||
var filter = pageQueryVo.getQueryFilter();
|
||||
if (filter == null) {
|
||||
filter = new LogQueryDto();
|
||||
}
|
||||
if (pageQueryVo.getPageSize() == null || pageQueryVo.getPageSize() <= 0) {
|
||||
pageQueryVo.setPageSize(10L);
|
||||
}
|
||||
if (pageQueryVo.getCurrent() == null || pageQueryVo.getCurrent() <= 0) {
|
||||
pageQueryVo.setCurrent(1L);
|
||||
}
|
||||
// 避免到最后一页报错
|
||||
if (pageQueryVo.getCurrent() * pageQueryVo.getPageSize() > 10000) {
|
||||
pageQueryVo.setCurrent(pageQueryVo.getCurrent() - 1);
|
||||
}
|
||||
DocumentSearchRequest.DocumentSearchRequestBuilder builder = DocumentSearchRequest.builder();
|
||||
builder.searchDocumentClazz(LogDocument.class);
|
||||
builder.from((int) (pageQueryVo.getPageSize() * (pageQueryVo.getCurrent() - 1)));
|
||||
builder.size(pageQueryVo.getPageSize().intValue());
|
||||
List<Map<String, Object>> filterFieldsAndValues = new ArrayList<>();
|
||||
builder.filterFieldsAndValues(filterFieldsAndValues);
|
||||
filterFieldsAndValues.add(Map.of("tenantId", RequestContext.get().getTenantId()));
|
||||
if (StringUtils.isNoneBlank(filter.getInput())) {
|
||||
filterFieldsAndValues.add(Map.of("input", Map.of("express", "match", "query", filter.getInput())));
|
||||
}
|
||||
if (StringUtils.isNoneBlank(filter.getOutput())) {
|
||||
filterFieldsAndValues.add(Map.of("output", Map.of("express", "match", "query", filter.getOutput())));
|
||||
}
|
||||
if (StringUtils.isNoneBlank(filter.getProcessData())) {
|
||||
filterFieldsAndValues.add(Map.of("processData", filter.getProcessData()));
|
||||
}
|
||||
if (StringUtils.isNoneBlank(filter.getRequestId())) {
|
||||
filterFieldsAndValues.add(Map.of("requestId", filter.getRequestId()));
|
||||
}
|
||||
if (StringUtils.isNoneBlank(filter.getResultCode())) {
|
||||
filterFieldsAndValues.add(Map.of("resultCode", filter.getResultCode()));
|
||||
}
|
||||
if (StringUtils.isNoneBlank(filter.getConversationId())) {
|
||||
filterFieldsAndValues.add(Map.of("conversationId", filter.getConversationId()));
|
||||
}
|
||||
if (StringUtils.isNoneBlank(filter.getTargetId())) {
|
||||
filterFieldsAndValues.add(Map.of("targetId", filter.getTargetId()));
|
||||
}
|
||||
if (StringUtils.isNoneBlank(filter.getTargetName())) {
|
||||
filterFieldsAndValues.add(Map.of("targetName", filter.getTargetName()));
|
||||
}
|
||||
if (StringUtils.isNoneBlank(filter.getTargetType())) {
|
||||
filterFieldsAndValues.add(Map.of("targetType", filter.getTargetType()));
|
||||
}
|
||||
if (StringUtils.isNoneBlank(filter.getUserName())) {
|
||||
filterFieldsAndValues.add(Map.of("userName", filter.getUserName()));
|
||||
}
|
||||
if (StringUtils.isNoneBlank(filter.getFrom())) {
|
||||
filterFieldsAndValues.add(Map.of("from", filter.getFrom()));
|
||||
}
|
||||
if (filter.getUserId() != null) {
|
||||
filterFieldsAndValues.add(Map.of("userId", filter.getUserId()));
|
||||
}
|
||||
if (filter.getSpaceId() != null) {
|
||||
filterFieldsAndValues.add(Map.of("spaceId", filter.getSpaceId()));
|
||||
}
|
||||
Map<String, Object> createTimeRange = new HashMap<>();
|
||||
if (filter.getCreateTimeGt() != null) {
|
||||
createTimeRange.put("gt", filter.getCreateTimeGt());
|
||||
}
|
||||
if (filter.getCreateTimeLt() != null) {
|
||||
createTimeRange.put("lt", filter.getCreateTimeLt());
|
||||
}
|
||||
if (!createTimeRange.isEmpty()) {
|
||||
createTimeRange.put("express", "range");
|
||||
filterFieldsAndValues.add(Map.of("createTime", createTimeRange));
|
||||
}
|
||||
builder.sortFieldsAndValues(Map.of("createTime", "Desc"));
|
||||
var result = iSearchRpcService.search(builder.build());
|
||||
|
||||
try {
|
||||
IPage<LogDocument> page = new Page<>(pageQueryVo.getCurrent().intValue(), pageQueryVo.getPageSize().intValue());
|
||||
page.setTotal(result.getTotal());
|
||||
page.setRecords(result.getItems().stream().map(item -> {
|
||||
LogDocument document = (LogDocument) item.getDocument();
|
||||
document.setProcessData(null);
|
||||
return document;
|
||||
}).collect(Collectors.toList()));
|
||||
return ReqResult.success(page);
|
||||
} catch (LogPlatformException e) {
|
||||
log.error("Log search failed: {}", e.getMessage(), e);
|
||||
return ReqResult.error(e.getCode(), e.getMessage());
|
||||
} catch (Exception e) {
|
||||
log.error("Unknown error during log search", e);
|
||||
return ReqResult.error("日志搜索发生未知错误: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
package com.xspaceagi.log.web.controller.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||
import com.xspaceagi.domain.model.valueobj.AgentLogSearchParams;
|
||||
import com.xspaceagi.log.sdk.request.AgentLogDetailParamsRequest;
|
||||
import com.xspaceagi.log.spec.FlexibleDateTimeDeserializer;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 日志搜索请求参数
|
||||
* 对应 Rust 的 AgentLogSearchParams 结构
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class AgentLogSearchParamsRequest {
|
||||
|
||||
/**
|
||||
* 请求ID
|
||||
*/
|
||||
@Schema(description = "请求ID")
|
||||
private String requestId;
|
||||
|
||||
/**
|
||||
* 消息ID
|
||||
*/
|
||||
@Schema(description = "消息ID")
|
||||
private String messageId;
|
||||
|
||||
/**
|
||||
* 智能体ID
|
||||
*/
|
||||
@Schema(description = "智能体ID")
|
||||
private String agentId;
|
||||
|
||||
/**
|
||||
* 会话ID
|
||||
*/
|
||||
@Schema(description = "会话ID")
|
||||
private String conversationId;
|
||||
|
||||
/**
|
||||
* 用户UID
|
||||
*/
|
||||
@Schema(description = "用户UID")
|
||||
private String userUid;
|
||||
|
||||
/**
|
||||
* 用户输入,需要支持全文检索,支持多个关键字(AND关系)
|
||||
*/
|
||||
@Schema(description = "用户输入,需要支持全文检索,支持多个关键字(AND关系)")
|
||||
private List<String> userInput;
|
||||
|
||||
/**
|
||||
* 系统输出,需要支持全文检索,支持多个关键字(AND关系)
|
||||
*/
|
||||
@Schema(description = "系统输出,需要支持全文检索,支持多个关键字(AND关系)")
|
||||
private List<String> output;
|
||||
|
||||
/**
|
||||
* 开始时间
|
||||
*/
|
||||
@Schema(description = "开始时间,支持RFC 3339格式,例如:2024-05-15T10:30:00Z")
|
||||
@JsonDeserialize(using = FlexibleDateTimeDeserializer.class)
|
||||
@JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss'Z'", shape = JsonFormat.Shape.STRING)
|
||||
private LocalDateTime startTime;
|
||||
|
||||
/**
|
||||
* 结束时间
|
||||
*/
|
||||
@Schema(description = "结束时间,支持RFC 3339格式,例如:2024-05-15T10:30:00Z")
|
||||
@JsonDeserialize(using = FlexibleDateTimeDeserializer.class)
|
||||
@JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss'Z'", shape = JsonFormat.Shape.STRING)
|
||||
private LocalDateTime endTime;
|
||||
|
||||
/**
|
||||
* 租户ID,用于租户隔离,确保只查询特定租户的日志
|
||||
*/
|
||||
@Schema(description = "租户ID,用于租户隔离,确保只查询特定租户的日志")
|
||||
private String tenantId;
|
||||
|
||||
/**
|
||||
* 空间ID,可选,用于查询特定空间的日志,支持多个ID(OR关系)
|
||||
*/
|
||||
@Schema(description = "空间ID,可选,用于查询特定空间的日志,支持多个ID(OR关系)")
|
||||
private List<String> spaceId;
|
||||
|
||||
public static AgentLogSearchParams convertFrom(AgentLogSearchParamsRequest request) {
|
||||
if (request == null) {
|
||||
return null;
|
||||
}
|
||||
// request 处理下面的String字段的2边的空白,使用trim(),如果不为空的话
|
||||
if (request.getRequestId() != null) {
|
||||
request.setRequestId(request.getRequestId().trim());
|
||||
}
|
||||
if (request.getMessageId() != null) {
|
||||
request.setMessageId(request.getMessageId().trim());
|
||||
}
|
||||
if (request.getAgentId() != null) {
|
||||
request.setAgentId(request.getAgentId().trim());
|
||||
}
|
||||
if (request.getConversationId() != null) {
|
||||
request.setConversationId(request.getConversationId().trim());
|
||||
}
|
||||
if (request.getUserUid() != null) {
|
||||
request.setUserUid(request.getUserUid().trim());
|
||||
}
|
||||
if (request.getTenantId() != null) {
|
||||
request.setTenantId(request.getTenantId().trim());
|
||||
}
|
||||
if (request.getSpaceId() != null) {
|
||||
var spaceId = request.getSpaceId().stream().map(item -> {
|
||||
if (item != null) {
|
||||
item = item.trim();
|
||||
}
|
||||
return item;
|
||||
}).toList();
|
||||
request.setSpaceId(spaceId);
|
||||
}
|
||||
if (request.getUserInput() != null) {
|
||||
var userInput = request.getUserInput().stream().map(item -> {
|
||||
if (item != null) {
|
||||
item = item.trim();
|
||||
}
|
||||
return item;
|
||||
}).toList();
|
||||
request.setUserInput(userInput);
|
||||
}
|
||||
if (request.getOutput() != null) {
|
||||
var output = request.getOutput().stream().map(item -> {
|
||||
if (item != null) {
|
||||
item = item.trim();
|
||||
}
|
||||
return item;
|
||||
}).toList();
|
||||
request.setOutput(output);
|
||||
}
|
||||
return AgentLogSearchParams.builder()
|
||||
.requestId(request.getRequestId())
|
||||
.messageId(request.getMessageId())
|
||||
.agentId(request.getAgentId())
|
||||
.conversationId(request.getConversationId())
|
||||
.userUid(request.getUserUid())
|
||||
.tenantId(request.getTenantId())
|
||||
.spaceId(request.getSpaceId())
|
||||
.userInput(request.getUserInput())
|
||||
.output(request.getOutput())
|
||||
.startTime(request.getStartTime())
|
||||
.endTime(request.getEndTime())
|
||||
.build();
|
||||
}
|
||||
|
||||
public static AgentLogSearchParams convertFrom(AgentLogDetailParamsRequest request) {
|
||||
return AgentLogSearchParams.builder()
|
||||
.requestId(request.getRequestId())
|
||||
.messageId(null)
|
||||
.agentId(request.getAgentId())
|
||||
.conversationId(null)
|
||||
.userUid(null)
|
||||
.tenantId(null)
|
||||
.spaceId(null)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.xspaceagi.log.web.controller.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class LogDetailQueryDto {
|
||||
private String id;
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package com.xspaceagi.log.web.controller.dto;
|
||||
|
||||
import com.xspaceagi.log.sdk.annotation.SearchField;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class LogQueryDto {
|
||||
@Schema(description = "请求唯一标识可以用于关联一次请求中所有相关的操作")
|
||||
@SearchField(keyword = true)
|
||||
private String requestId;
|
||||
|
||||
@Schema(description = "日志产生对象所在的空间ID")
|
||||
private Long spaceId;
|
||||
|
||||
@Schema(description = "请求发起的用户ID")
|
||||
private Long userId;
|
||||
|
||||
@Schema(description = "用户名")
|
||||
@SearchField(keyword = true)
|
||||
private String userName;
|
||||
|
||||
@Schema(description = "日志对象类型,Agent、Plugin、Workflow、Mcp")
|
||||
@SearchField(keyword = true)
|
||||
private String targetType;
|
||||
|
||||
@Schema(description = "日志对象名称")
|
||||
@SearchField(keyword = true)
|
||||
private String targetName;
|
||||
|
||||
@Schema(description = "日志对象ID")
|
||||
@SearchField(keyword = true)
|
||||
private String targetId;
|
||||
|
||||
@Schema(description = "会话ID")
|
||||
@SearchField(keyword = true)
|
||||
private String conversationId;
|
||||
|
||||
@Schema(description = "输入参数")
|
||||
@SearchField(store = true)
|
||||
private String input;
|
||||
|
||||
@Schema(description = "执行结果")
|
||||
@SearchField(store = true)
|
||||
private String output;
|
||||
|
||||
@Schema(description = "执行过程数据")
|
||||
@SearchField(store = true)
|
||||
private String processData;
|
||||
|
||||
@Schema(description = "执行结果码 0000为成功")
|
||||
@SearchField(keyword = true)
|
||||
private String resultCode;
|
||||
|
||||
@Schema(description = "日志产生时间(大于)")
|
||||
private Long createTimeGt;
|
||||
|
||||
@Schema(description = "日志产生时间(小于)")
|
||||
private Long createTimeLt;
|
||||
|
||||
@Schema(description = "日志产生来源,task_center 任务中心(从任务中心点击查看详情时带上该参数)")
|
||||
private String from;
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<assembly xmlns="http://maven.apache.org/ASSEMBLY/2.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.0.0 http://maven.apache.org/xsd/assembly-2.0.0.xsd">
|
||||
<id>repack</id>
|
||||
<formats>
|
||||
<format>jar</format>
|
||||
</formats>
|
||||
|
||||
<includeBaseDirectory>false</includeBaseDirectory>
|
||||
<dependencySets>
|
||||
<dependencySet>
|
||||
<!--
|
||||
重新打包需要包含的jar包,这个过程会解压所有的jar包并按照jar的规则重新组合成一个jar包
|
||||
这里需要包含当前这个工程 ,格式是{groupId}:{artifactId}
|
||||
-->
|
||||
<includes>
|
||||
<!-- 包含项目自己 -->
|
||||
<include>com.xspaceagi:app-platform-log-web</include>
|
||||
<include>com.xspaceagi:app-platform-log-application</include>
|
||||
<include>com.xspaceagi:app-platform-log-domain</include>
|
||||
<include>com.xspaceagi:app-platform-log-infra</include>
|
||||
<include>com.xspaceagi:app-platform-log-spec</include>
|
||||
</includes>
|
||||
<!-- 打包结果目录:/ 表示target/ -->
|
||||
<outputDirectory>/</outputDirectory>
|
||||
<!-- 是否解压依赖 -->
|
||||
<unpack>true</unpack>
|
||||
<!-- 打包的scope -->
|
||||
<!-- <scope>system</scope>-->
|
||||
</dependencySet>
|
||||
</dependencySets>
|
||||
</assembly>
|
||||
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>app-platform-log-web</artifactId>
|
||||
<groupId>com.xspaceagi</groupId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>app-platform-log-web</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
91
qiming-backend/app-platform-modules/app-platform-log/pom.xml
Normal file
91
qiming-backend/app-platform-modules/app-platform-log/pom.xml
Normal file
@@ -0,0 +1,91 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<groupId>com.xspaceagi</groupId>
|
||||
<artifactId>app-platform-modules</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>app-platform-log</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
<packaging>pom</packaging>
|
||||
<properties/>
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.xspaceagi</groupId>
|
||||
<artifactId>system-sdk</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.xspaceagi</groupId>
|
||||
<artifactId>app-platform-log-application</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.xspaceagi</groupId>
|
||||
<artifactId>app-platform-log-infra</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.xspaceagi</groupId>
|
||||
<artifactId>app-platform-log-domain</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.xspaceagi</groupId>
|
||||
<artifactId>app-platform-log-spec</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.xspaceagi</groupId>
|
||||
<artifactId>app-platform-log-web</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.xspaceagi</groupId>
|
||||
<artifactId>app-platform-log-api</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.xspaceagi</groupId>
|
||||
<artifactId>app-platform-log-sdk</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
<modules>
|
||||
<module>app-platform-log-application</module>
|
||||
<module>app-platform-log-domain</module>
|
||||
<module>app-platform-log-infra</module>
|
||||
<module>app-platform-log-spec</module>
|
||||
<module>app-platform-log-api</module>
|
||||
<module>app-platform-log-web</module>
|
||||
<module>app-platform-log-job</module>
|
||||
<module>app-platform-log-sdk</module>
|
||||
</modules>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.12.1</version>
|
||||
<configuration>
|
||||
<source>17</source>
|
||||
<target>17</target>
|
||||
<annotationProcessorPaths>
|
||||
<path>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>${lombok.version}</version> <!-- 使用最新版本 -->
|
||||
</path>
|
||||
</annotationProcessorPaths>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
Reference in New Issue
Block a user