feat: 完善本地MCP与扩展市场能力
This commit is contained in:
@@ -27,7 +27,7 @@ spring:
|
||||
datasource:
|
||||
# 主数据源 (MySQL)
|
||||
master:
|
||||
url: jdbc:mysql://${DB_HOST:localhost}:3306/${DB_NAME:agent_platform}?serverTimezone=Asia/Shanghai&characterEncoding=utf-8&allowMultiQueries=true&socketTimeout=300000&useSSL=false
|
||||
url: jdbc:mysql://${DB_HOST:localhost}:3306/${DB_NAME:agent_platform}?serverTimezone=Asia/Shanghai&characterEncoding=utf-8&allowMultiQueries=true&socketTimeout=300000&allowPublicKeyRetrieval=true&useSSL=false
|
||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||
username: ${DB_USERNAME:root}
|
||||
password: ${DB_PASSWORD:your_mysql_password}
|
||||
@@ -48,7 +48,7 @@ spring:
|
||||
filters: stat # 启用 stat filter 用于监控
|
||||
# Doris 数据源
|
||||
doris:
|
||||
url: jdbc:mysql://${DORIS_HOST:localhost}:3306/${DORIS_DB_NAME:agent_custom_table}?serverTimezone=Asia/Shanghai&characterEncoding=utf-8&allowMultiQueries=true&socketTimeout=300000&useSSL=false
|
||||
url: jdbc:mysql://${DORIS_HOST:localhost}:3306/${DORIS_DB_NAME:agent_custom_table}?serverTimezone=Asia/Shanghai&characterEncoding=utf-8&allowMultiQueries=true&socketTimeout=300000&allowPublicKeyRetrieval=true&useSSL=false
|
||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||
username: ${DORIS_USERNAME:root}
|
||||
password: ${DORIS_PASSWORD:your_doris_password}
|
||||
@@ -165,6 +165,8 @@ search:
|
||||
username: ${ES_USERNAME:elastic}
|
||||
password: ${ES_PASSWORD:your_elasticsearch_password}
|
||||
api_key: ${ES_API_KEY:}
|
||||
text_analyzer: ${ES_TEXT_ANALYZER:standard}
|
||||
search_analyzer: ${ES_SEARCH_ANALYZER:standard}
|
||||
# 生态市场模块配置
|
||||
eco-market:
|
||||
# 服务端配置
|
||||
@@ -230,4 +232,4 @@ model-api-proxy:
|
||||
enable-model-proxy: ${MODEL_PROXY_ENABLE:true}
|
||||
port: ${MODEL_PROXY_PORT:18086}
|
||||
#记录日志
|
||||
save-log: ${MODEL_PROXY_SAVE_LOG:true}
|
||||
save-log: ${MODEL_PROXY_SAVE_LOG:true}
|
||||
|
||||
@@ -27,7 +27,7 @@ spring:
|
||||
datasource:
|
||||
# 主数据源 (MySQL)
|
||||
master:
|
||||
url: jdbc:mysql://${DB_HOST:localhost}:3306/${DB_NAME:agent_platform}?serverTimezone=Asia/Shanghai&characterEncoding=utf-8&allowMultiQueries=true&socketTimeout=300000&useSSL=false
|
||||
url: jdbc:mysql://${DB_HOST:localhost}:3306/${DB_NAME:agent_platform}?serverTimezone=Asia/Shanghai&characterEncoding=utf-8&allowMultiQueries=true&socketTimeout=300000&allowPublicKeyRetrieval=true&useSSL=false
|
||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||
username: ${DB_USERNAME:root}
|
||||
password: ${DB_PASSWORD:Byyagz@121}
|
||||
@@ -48,7 +48,7 @@ spring:
|
||||
filters: stat # 启用 stat filter 用于监控
|
||||
# Doris 数据源
|
||||
doris:
|
||||
url: jdbc:mysql://${DORIS_HOST:localhost}:3306/${DORIS_DB_NAME:agent_platform}?serverTimezone=Asia/Shanghai&characterEncoding=utf-8&allowMultiQueries=true&socketTimeout=300000&useSSL=false
|
||||
url: jdbc:mysql://${DORIS_HOST:localhost}:3306/${DORIS_DB_NAME:agent_platform}?serverTimezone=Asia/Shanghai&characterEncoding=utf-8&allowMultiQueries=true&socketTimeout=300000&allowPublicKeyRetrieval=true&useSSL=false
|
||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||
username: ${DORIS_USERNAME:root}
|
||||
password: ${DORIS_PASSWORD:Byyagz@121}
|
||||
@@ -165,6 +165,8 @@ search:
|
||||
username: ${ES_USERNAME:}
|
||||
password: ${ES_PASSWORD:}
|
||||
api_key: ${ES_API_KEY:}
|
||||
text_analyzer: ${ES_TEXT_ANALYZER:standard}
|
||||
search_analyzer: ${ES_SEARCH_ANALYZER:standard}
|
||||
# 生态市场模块配置
|
||||
eco-market:
|
||||
# 服务端配置
|
||||
|
||||
@@ -53,6 +53,12 @@ public class ElasticsearchServiceImpl implements SearchService, ISearchRpcServic
|
||||
@Value("${search.elasticsearch.password:}")
|
||||
private String password;
|
||||
|
||||
@Value("${search.elasticsearch.text_analyzer:standard}")
|
||||
private String textAnalyzer;
|
||||
|
||||
@Value("${search.elasticsearch.search_analyzer:standard}")
|
||||
private String searchAnalyzer;
|
||||
|
||||
private ElasticsearchClient client;
|
||||
|
||||
@PostConstruct
|
||||
@@ -75,6 +81,14 @@ public class ElasticsearchServiceImpl implements SearchService, ISearchRpcServic
|
||||
client.shutdown();
|
||||
}
|
||||
|
||||
private String getTextAnalyzer() {
|
||||
return StringUtils.defaultIfBlank(textAnalyzer, "standard");
|
||||
}
|
||||
|
||||
private String getSearchAnalyzer() {
|
||||
return StringUtils.defaultIfBlank(searchAnalyzer, getTextAnalyzer());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bulkIndex(List<SearchDocument> list) {
|
||||
Assert.noNullElements(list, "list cannot be left blank.");
|
||||
@@ -146,8 +160,8 @@ public class ElasticsearchServiceImpl implements SearchService, ISearchRpcServic
|
||||
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())
|
||||
builder.properties(name.get(), property -> property.text(text -> text.analyzer(getTextAnalyzer())
|
||||
.searchAnalyzer(getSearchAnalyzer()).store(store.get()).index(index.get())
|
||||
));
|
||||
}
|
||||
} else if (declaredField.getType() == Integer.class) {
|
||||
@@ -163,8 +177,8 @@ public class ElasticsearchServiceImpl implements SearchService, ISearchRpcServic
|
||||
} 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())
|
||||
builder.properties(name.get(), property -> property.text(text -> text.analyzer(getTextAnalyzer())
|
||||
.searchAnalyzer(getSearchAnalyzer()).store(store.get()).index(index.get())
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ 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.dto.LogQueryDto;
|
||||
import com.xspaceagi.system.spec.common.RequestContext;
|
||||
import com.xspaceagi.system.spec.common.UserContext;
|
||||
@@ -19,6 +20,7 @@ import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -125,13 +127,15 @@ public abstract class BaseController {
|
||||
createTimeRange.put("express", "range");
|
||||
filterFieldsAndValues.add(Map.of("createTime", createTimeRange));
|
||||
}
|
||||
builder.sortFieldsAndValues(Map.of("createTime", "Desc"));
|
||||
var result = iSearchRpcService.search(builder.build());
|
||||
|
||||
try {
|
||||
builder.sortFieldsAndValues(Map.of("createTime", "Desc"));
|
||||
var result = iSearchRpcService.search(builder.build());
|
||||
IPage<LogDocument> page = new Page<>(pageQueryVo.getCurrent().intValue(), pageQueryVo.getPageSize().intValue());
|
||||
page.setTotal(result.getTotal());
|
||||
page.setRecords(result.getItems().stream().map(item -> {
|
||||
page.setTotal(result == null || result.getTotal() == null ? 0 : result.getTotal());
|
||||
List<SearchResult.SearchResultItem> items = result == null || result.getItems() == null
|
||||
? Collections.emptyList()
|
||||
: result.getItems();
|
||||
page.setRecords(items.stream().map(item -> {
|
||||
LogDocument document = (LogDocument) item.getDocument();
|
||||
document.setProcessData(null);
|
||||
return document;
|
||||
|
||||
@@ -140,7 +140,16 @@ public class McpDeployTaskServiceImpl extends AbstractDeployTaskService implemen
|
||||
deployStatus = new McpDeployStatusResponse();
|
||||
deployStatus.setStatus(McpDeployStatusEnum.Ready);
|
||||
} else {
|
||||
deployStatus = mcpDeployRpcService.deploy(String.valueOf(id), serverConfig, McpPersistentTypeEnum.OneShot);
|
||||
try {
|
||||
deployStatus = mcpDeployRpcService.deploy(String.valueOf(id), serverConfig, McpPersistentTypeEnum.OneShot);
|
||||
} catch (Exception e) {
|
||||
log.warn("MCP service [{}] deploy request failed", mcpName, e);
|
||||
if (scheduleTask.getExecTimes() + 1 >= scheduleTask.getMaxExecTimes()) {
|
||||
markDeployFailed(userDto, id, mcpName, e.getMessage());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (deployStatus.getStatus() == McpDeployStatusEnum.Ready) {
|
||||
try {
|
||||
@@ -161,15 +170,7 @@ public class McpDeployTaskServiceImpl extends AbstractDeployTaskService implemen
|
||||
}
|
||||
}
|
||||
if (deployStatus.getStatus() == McpDeployStatusEnum.Error) {
|
||||
McpConfig mcpConfig = new McpConfig();
|
||||
mcpConfig.setId(id);
|
||||
mcpConfig.setDeployStatus(DeployStatusEnum.DeployFailed);
|
||||
mcpConfigDomainService.update(mcpConfig);
|
||||
notifyMessageApplicationService.sendNotifyMessage(SendNotifyMessageDto.builder()
|
||||
.scope(NotifyMessage.MessageScope.System)
|
||||
.content(I18nUtil.systemMessage(userDto.getLangMap(), "Backend.Mcp.Deploy.Failed", mcpName, deployStatus.getMessage()))
|
||||
.userIds(List.of(userId))
|
||||
.build());
|
||||
markDeployFailed(userDto, id, mcpName, deployStatus.getMessage());
|
||||
return true;
|
||||
}
|
||||
checkAndSendFailedNotifyMessage(userDto, id, mcpName, scheduleTask);
|
||||
@@ -178,10 +179,7 @@ public class McpDeployTaskServiceImpl extends AbstractDeployTaskService implemen
|
||||
|
||||
private void checkAndSendFailedNotifyMessage(UserDto userDto, Long id, String mcpName, ScheduleTaskDto scheduleTask) {
|
||||
if (scheduleTask.getExecTimes() + 1 >= scheduleTask.getMaxExecTimes()) {
|
||||
McpConfig mcpConfig = new McpConfig();
|
||||
mcpConfig.setId(id);
|
||||
mcpConfig.setDeployStatus(DeployStatusEnum.DeployFailed);
|
||||
mcpConfigDomainService.update(mcpConfig);
|
||||
updateDeployFailed(id);
|
||||
notifyMessageApplicationService.sendNotifyMessage(SendNotifyMessageDto.builder()
|
||||
.scope(NotifyMessage.MessageScope.System)
|
||||
.content(I18nUtil.systemMessage(userDto.getLangMap(), "Backend.Mcp.Deploy.Timeout", mcpName))
|
||||
@@ -189,4 +187,20 @@ public class McpDeployTaskServiceImpl extends AbstractDeployTaskService implemen
|
||||
.build());
|
||||
}
|
||||
}
|
||||
|
||||
private void markDeployFailed(UserDto userDto, Long id, String mcpName, String message) {
|
||||
updateDeployFailed(id);
|
||||
notifyMessageApplicationService.sendNotifyMessage(SendNotifyMessageDto.builder()
|
||||
.scope(NotifyMessage.MessageScope.System)
|
||||
.content(I18nUtil.systemMessage(userDto.getLangMap(), "Backend.Mcp.Deploy.Failed", mcpName, message))
|
||||
.userIds(List.of(userDto.getId()))
|
||||
.build());
|
||||
}
|
||||
|
||||
private void updateDeployFailed(Long id) {
|
||||
McpConfig mcpConfig = new McpConfig();
|
||||
mcpConfig.setId(id);
|
||||
mcpConfig.setDeployStatus(DeployStatusEnum.DeployFailed);
|
||||
mcpConfigDomainService.update(mcpConfig);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -226,12 +226,7 @@ public class McpController {
|
||||
@Operation(summary = "MCP列表(官方服务)")
|
||||
@GetMapping("/official/list")
|
||||
public ReqResult<List<McpDto>> officialList() {
|
||||
List<McpDto> mcpDtos = mcpConfigApplicationService.queryMcpListBySpaceId(-1L);
|
||||
//移除未发布的
|
||||
mcpDtos.removeIf(mcpDto -> mcpDto.getDeployStatus() != DeployStatusEnum.Deployed);
|
||||
List<SpaceObjectPermissionEnum> permissionList = new ArrayList<>();
|
||||
mcpDtos.forEach(mcpDto -> mcpDto.setPermissions(permissionList.stream().map(SpaceObjectPermissionEnum::name).collect(Collectors.toList())));
|
||||
return ReqResult.success(mcpDtos);
|
||||
return ReqResult.success(List.of());
|
||||
}
|
||||
|
||||
@RequireResource(MCP_DELETE)
|
||||
|
||||
@@ -1,39 +1,23 @@
|
||||
package com.xspaceagi.system.web.controller;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.alibaba.fastjson2.TypeReference;
|
||||
import com.xspaceagi.eco.market.sdk.reponse.ClientSecretResponse;
|
||||
import com.xspaceagi.system.application.dto.*;
|
||||
import com.xspaceagi.system.application.service.AuthService;
|
||||
import com.xspaceagi.system.application.service.NotifyMessageApplicationService;
|
||||
import com.xspaceagi.system.infra.dao.entity.NotifyMessage;
|
||||
import com.xspaceagi.system.infra.dao.entity.User;
|
||||
import com.xspaceagi.system.infra.rpc.ClientSecretRpcService;
|
||||
import com.xspaceagi.system.spec.common.RequestContext;
|
||||
import com.xspaceagi.system.spec.dto.ReqResult;
|
||||
import com.xspaceagi.system.spec.utils.HttpClient;
|
||||
import com.xspaceagi.system.web.dto.EventRespDto;
|
||||
import com.xspaceagi.system.web.dto.PullMessageAckDto;
|
||||
import com.xspaceagi.system.web.dto.PullMessageRequestDto;
|
||||
import com.xspaceagi.system.web.dto.PullMessageResponseDto;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
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.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Slf4j
|
||||
@Tag(name = "消息通知相关接口")
|
||||
@RestController
|
||||
@RequestMapping("/api/notify")
|
||||
@@ -45,22 +29,9 @@ public class NotifyMessageController {
|
||||
@Resource
|
||||
private AuthService authService;
|
||||
|
||||
@Resource
|
||||
private HttpClient httpClient;
|
||||
|
||||
@Value("${installation-source}")
|
||||
private String installationSource;
|
||||
|
||||
@Value("${app.version}")
|
||||
private String version;
|
||||
|
||||
@Value("${eco-market.server.base-url}")
|
||||
private String cloudApi;
|
||||
|
||||
@Resource
|
||||
private ClientSecretRpcService clientSecretRpcService;
|
||||
|
||||
|
||||
@Operation(summary = "查询用户消息列表")
|
||||
@RequestMapping(path = "/message/list", method = RequestMethod.POST)
|
||||
public ReqResult<List<NotifyMessageDto>> listQuery(@RequestBody NotifyMessageQueryDto messageQueryDto) {
|
||||
@@ -68,43 +39,6 @@ public class NotifyMessageController {
|
||||
return ReqResult.success(notifyMessageApplicationService.queryNotifyMessageList(messageQueryDto));
|
||||
}
|
||||
|
||||
private void pullMessage() {
|
||||
UserDto userDto = (UserDto) RequestContext.get().getUser();
|
||||
TenantConfigDto tenantConfigDto = (TenantConfigDto) RequestContext.get().getTenantConfig();
|
||||
if (userDto.getRole() == User.Role.Admin) {
|
||||
// 管理员拉取云端消息
|
||||
ClientSecretResponse clientSecretResponse = clientSecretRpcService.queryClientSecret(tenantConfigDto.getTenantId());
|
||||
if (clientSecretResponse != null) {
|
||||
PullMessageRequestDto pullMessageRequestDto = new PullMessageRequestDto();
|
||||
pullMessageRequestDto.setTenantName(tenantConfigDto.getSiteName());
|
||||
pullMessageRequestDto.setSiteUrl(tenantConfigDto.getSiteUrl());
|
||||
pullMessageRequestDto.setClientId(clientSecretResponse.getClientId());
|
||||
pullMessageRequestDto.setClientSecret(clientSecretResponse.getClientSecret());
|
||||
pullMessageRequestDto.setInstallationSource(installationSource);
|
||||
pullMessageRequestDto.setVersion(version);
|
||||
pullMessageRequestDto.setUser(userDto.getNickName() != null ? userDto.getNickName() : userDto.getUserName());
|
||||
pullMessageRequestDto.setUid(userDto.getUid());
|
||||
String content = httpClient.post(cloudApi + "/api/message/pull", JSON.toJSONString(pullMessageRequestDto), Map.of("Authorization", "Bearer " + clientSecretResponse.getClientSecret()));
|
||||
ReqResult<List<PullMessageResponseDto>> pullMessageResponseDtos = JSON.parseObject(content, new TypeReference<ReqResult<List<PullMessageResponseDto>>>() {
|
||||
});
|
||||
if (pullMessageResponseDtos != null && CollectionUtils.isNotEmpty(pullMessageResponseDtos.getData())) {
|
||||
pullMessageResponseDtos.getData().forEach(pullMessageResponseDto -> notifyMessageApplicationService.sendNotifyMessage(SendNotifyMessageDto.builder()
|
||||
.scope(NotifyMessage.MessageScope.System)
|
||||
.content(pullMessageResponseDto.getContent())
|
||||
.senderId(RequestContext.get().getUserId())
|
||||
.userIds(Arrays.asList(RequestContext.get().getUserId()))
|
||||
.build()));
|
||||
PullMessageAckDto pullMessageAckDto = new PullMessageAckDto();
|
||||
pullMessageAckDto.setClientId(clientSecretResponse.getClientId());
|
||||
pullMessageAckDto.setClientSecret(clientSecretResponse.getClientSecret());
|
||||
pullMessageAckDto.setUid(userDto.getUid());
|
||||
pullMessageAckDto.setMessageIds(pullMessageResponseDtos.getData().stream().map(PullMessageResponseDto::getMessageId).collect(Collectors.toList()));
|
||||
httpClient.post(cloudApi + "/api/message/ack", JSON.toJSONString(pullMessageAckDto), Map.of("Authorization", "Bearer " + clientSecretResponse.getClientSecret()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Operation(summary = "查询用户未读消息数量")
|
||||
@RequestMapping(path = "/message/unread/count", method = RequestMethod.GET)
|
||||
public ReqResult<Long> unReadCount() {
|
||||
@@ -134,12 +68,6 @@ public class NotifyMessageController {
|
||||
eventRespDto.setHasEvent(!eventDtos.isEmpty());
|
||||
eventRespDto.setEventList(eventDtos);
|
||||
eventRespDto.setVersion(version);
|
||||
try {
|
||||
// 之前的逻辑
|
||||
pullMessage();
|
||||
} catch (Exception e) {
|
||||
log.error("触发消息拉取事件失败", e);
|
||||
}
|
||||
return ReqResult.success(eventRespDto);
|
||||
}
|
||||
|
||||
@@ -151,4 +79,4 @@ public class NotifyMessageController {
|
||||
return ReqResult.success();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user