package com.vincent.rsf.server.system.controller;
|
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.vincent.rsf.framework.common.Cools;
|
import com.vincent.rsf.framework.common.R;
|
import com.vincent.rsf.framework.common.SnowflakeIdWorker;
|
import com.vincent.rsf.server.ai.constant.AiMcpConstants;
|
import com.vincent.rsf.server.ai.constant.AiSceneCode;
|
import com.vincent.rsf.server.ai.service.mcp.AiMcpPayloadMapper;
|
import com.vincent.rsf.server.ai.service.mcp.AiMcpRegistryService;
|
import com.vincent.rsf.server.common.annotation.OperationLog;
|
import com.vincent.rsf.server.common.domain.BaseParam;
|
import com.vincent.rsf.server.common.domain.PageParam;
|
import com.vincent.rsf.server.common.utils.ExcelUtil;
|
import com.vincent.rsf.server.system.entity.AiDiagnosticToolConfig;
|
import com.vincent.rsf.server.system.entity.AiMcpMount;
|
import com.vincent.rsf.server.system.service.AiDiagnosticToolConfigService;
|
import com.vincent.rsf.server.system.service.AiMcpMountService;
|
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.security.access.prepost.PreAuthorize;
|
import org.springframework.web.bind.annotation.*;
|
|
import javax.servlet.http.HttpServletResponse;
|
import java.util.Arrays;
|
import java.util.Date;
|
import java.util.LinkedHashMap;
|
import java.util.List;
|
import java.util.Map;
|
|
@RestController
|
public class AiMcpMountController extends BaseController {
|
|
@Autowired
|
private AiMcpMountService aiMcpMountService;
|
@Autowired
|
private AiMcpRegistryService aiMcpRegistryService;
|
@Autowired
|
private AiDiagnosticToolConfigService aiDiagnosticToolConfigService;
|
@Autowired
|
private SnowflakeIdWorker snowflakeIdWorker;
|
@Autowired
|
private AiMcpPayloadMapper aiMcpPayloadMapper;
|
|
@PreAuthorize("hasAuthority('system:aiMcpMount:list')")
|
@PostMapping("/aiMcpMount/page")
|
public R page(@RequestBody Map<String, Object> map) {
|
BaseParam baseParam = buildParam(map, BaseParam.class);
|
PageParam<AiMcpMount, BaseParam> pageParam = new PageParam<>(baseParam, AiMcpMount.class);
|
com.baomidou.mybatisplus.core.conditions.query.QueryWrapper<AiMcpMount> wrapper = pageParam.buildWrapper(true);
|
wrapper.eq("tenant_id", getTenantId());
|
return R.ok().add(aiMcpMountService.page(pageParam, wrapper));
|
}
|
|
@PreAuthorize("hasAuthority('system:aiMcpMount:list')")
|
@GetMapping("/aiMcpMount/{id}")
|
public R get(@PathVariable("id") Long id) {
|
AiMcpMount mount = aiMcpMountService.getTenantMount(getTenantId(), id);
|
if (mount == null) {
|
return R.error("mount not found");
|
}
|
return R.ok().add(mount);
|
}
|
|
@PreAuthorize("hasAuthority('system:aiMcpMount:save')")
|
@OperationLog("Create AiMcpMount")
|
@PostMapping("/aiMcpMount/save")
|
public R save(@RequestBody AiMcpMount mount) {
|
if (Cools.isEmpty(mount.getName()) || Cools.isEmpty(mount.getMountCode()) || Cools.isEmpty(mount.getTransportType())) {
|
return R.error("名称、挂载编码和传输类型不能为空");
|
}
|
Date now = new Date();
|
mount.setUuid(String.valueOf(snowflakeIdWorker.nextId()).substring(3));
|
mount.setEnabledFlag(mount.getEnabledFlag() == null ? 1 : mount.getEnabledFlag());
|
mount.setTimeoutMs(mount.getTimeoutMs() == null ? 10000 : mount.getTimeoutMs());
|
mount.setStatus(mount.getStatus() == null ? 1 : mount.getStatus());
|
mount.setTransportType(mount.getTransportType().toUpperCase());
|
if (AiMcpConstants.TRANSPORT_INTERNAL.equals(mount.getTransportType())) {
|
mount.setUrl("/ai/mcp");
|
}
|
mount.setTenantId(getTenantId());
|
mount.setCreateBy(getLoginUserId());
|
mount.setCreateTime(now);
|
mount.setUpdateBy(getLoginUserId());
|
mount.setUpdateTime(now);
|
if (!aiMcpMountService.save(mount)) {
|
return R.error("Save Fail");
|
}
|
return R.ok("Save Success").add(mount);
|
}
|
|
@PreAuthorize("hasAuthority('system:aiMcpMount:update')")
|
@OperationLog("Update AiMcpMount")
|
@PostMapping("/aiMcpMount/update")
|
public R update(@RequestBody AiMcpMount mount) {
|
AiMcpMount existed = aiMcpMountService.getTenantMount(getTenantId(), mount.getId());
|
if (existed == null) {
|
return R.error("mount not found");
|
}
|
if (AiMcpConstants.TRANSPORT_INTERNAL.equalsIgnoreCase(existed.getTransportType())) {
|
return R.error("内置挂载由系统托管,不支持直接编辑");
|
}
|
if (Cools.isEmpty(mount.getName()) || Cools.isEmpty(mount.getMountCode()) || Cools.isEmpty(mount.getTransportType())) {
|
return R.error("名称、挂载编码和传输类型不能为空");
|
}
|
mount.setTransportType(mount.getTransportType().toUpperCase());
|
if (AiMcpConstants.TRANSPORT_INTERNAL.equals(mount.getTransportType())) {
|
mount.setUrl("/ai/mcp");
|
}
|
mount.setTenantId(getTenantId());
|
mount.setCreateBy(existed.getCreateBy());
|
mount.setCreateTime(existed.getCreateTime());
|
mount.setLastTestResult(existed.getLastTestResult());
|
mount.setLastTestTime(existed.getLastTestTime());
|
mount.setLastTestMessage(existed.getLastTestMessage());
|
mount.setLastToolCount(existed.getLastToolCount());
|
mount.setUpdateBy(getLoginUserId());
|
mount.setUpdateTime(new Date());
|
if (!aiMcpMountService.updateById(mount)) {
|
return R.error("Update Fail");
|
}
|
return R.ok("Update Success").add(mount);
|
}
|
|
@PreAuthorize("hasAuthority('system:aiMcpMount:remove')")
|
@OperationLog("Delete AiMcpMount")
|
@PostMapping("/aiMcpMount/remove/{ids}")
|
public R remove(@PathVariable Long[] ids) {
|
List<Long> idList = Arrays.asList(ids);
|
List<AiMcpMount> mounts = aiMcpMountService.list(new LambdaQueryWrapper<AiMcpMount>()
|
.eq(AiMcpMount::getTenantId, getTenantId())
|
.in(AiMcpMount::getId, idList));
|
for (AiMcpMount mount : mounts) {
|
if (AiMcpConstants.TRANSPORT_INTERNAL.equalsIgnoreCase(mount.getTransportType())) {
|
return R.error("内置挂载由系统托管,不支持删除");
|
}
|
}
|
if (mounts.size() != idList.size() || !aiMcpMountService.removeByIds(idList)) {
|
return R.error("Delete Fail");
|
}
|
return R.ok("Delete Success").add(ids);
|
}
|
|
@PreAuthorize("hasAuthority('system:aiMcpMount:list')")
|
@PostMapping("/aiMcpMount/export")
|
public void export(@RequestBody Map<String, Object> map, HttpServletResponse response) throws Exception {
|
ExcelUtil.build(ExcelUtil.create(aiMcpMountService.list(new LambdaQueryWrapper<AiMcpMount>()
|
.eq(AiMcpMount::getTenantId, getTenantId())), AiMcpMount.class), response);
|
}
|
|
@PreAuthorize("hasAuthority('system:aiMcpMount:list')")
|
@GetMapping("/ai/mcp/mount/list")
|
public R list() {
|
aiMcpRegistryService.ensureDefaultMount(getTenantId(), getLoginUserId());
|
return R.ok().add(aiMcpMountService.listTenantMounts(getTenantId()));
|
}
|
|
@PreAuthorize("hasAnyAuthority('system:aiMcpMount:save','system:aiMcpMount:update')")
|
@PostMapping("/ai/mcp/mount/save")
|
public R customSave(@RequestBody AiMcpMount mount) {
|
if (mount.getId() == null) {
|
return save(mount);
|
}
|
return update(mount);
|
}
|
|
@PreAuthorize("hasAuthority('system:aiMcpMount:update')")
|
@PostMapping("/ai/mcp/mount/initDefaults")
|
public R initDefaults() {
|
aiMcpRegistryService.ensureDefaultMount(getTenantId(), getLoginUserId());
|
return R.ok();
|
}
|
|
@PreAuthorize("hasAuthority('system:aiMcpMount:list')")
|
@GetMapping("/ai/mcp/mount/toolList")
|
public R toolList(@RequestParam(required = false) Long mountId) {
|
return R.ok().add(aiMcpRegistryService.listTools(getTenantId(), mountId));
|
}
|
|
@PreAuthorize("hasAuthority('system:aiMcpMount:update')")
|
@PostMapping("/ai/mcp/mount/test")
|
public R test(@RequestBody Map<String, Object> map) {
|
Long id = Long.valueOf(String.valueOf(map.get("id")));
|
return R.ok().add(aiMcpRegistryService.testMount(getTenantId(), id));
|
}
|
|
@PreAuthorize("hasAuthority('system:aiMcpMount:list')")
|
@PostMapping("/ai/mcp/mount/toolPreview")
|
public R toolPreview(@RequestBody Map<String, Object> map) {
|
return R.ok().add(aiMcpRegistryService.previewTool(
|
getTenantId(),
|
String.valueOf(map.get("mountCode")),
|
String.valueOf(map.get("toolCode")),
|
map.get("sceneCode") == null ? null : String.valueOf(map.get("sceneCode")),
|
map.get("question") == null ? null : String.valueOf(map.get("question"))
|
));
|
}
|
|
@PreAuthorize("hasAuthority('system:aiMcpMount:list')")
|
@GetMapping("/ai/mcp/console/overview")
|
public R consoleOverview() {
|
aiMcpRegistryService.ensureDefaultMount(getTenantId(), getLoginUserId());
|
Map<String, Object> payload = new LinkedHashMap<>();
|
payload.put("builtInMount", null);
|
payload.put("builtInTools", new java.util.ArrayList<>());
|
payload.put("externalServices", new java.util.ArrayList<>());
|
for (AiMcpMount mount : aiMcpMountService.listTenantMounts(getTenantId())) {
|
if (AiMcpConstants.TRANSPORT_INTERNAL.equalsIgnoreCase(mount.getTransportType())) {
|
payload.put("builtInMount", buildMountView(mount, true));
|
payload.put("builtInTools", buildBuiltInToolViews());
|
} else {
|
((List<Object>) payload.get("externalServices")).add(buildMountView(mount, false));
|
}
|
}
|
return R.ok().add(payload);
|
}
|
|
@PreAuthorize("hasAnyAuthority('system:aiMcpMount:save','system:aiMcpMount:update')")
|
@PostMapping("/ai/mcp/console/service/save")
|
public R consoleSaveService(@RequestBody AiMcpMount mount) {
|
if (Cools.isEmpty(mount.getName()) || Cools.isEmpty(mount.getUrl())) {
|
return R.error("名称和地址不能为空");
|
}
|
Date now = new Date();
|
AiMcpMount existed = mount.getId() == null ? null : aiMcpMountService.getTenantMount(getTenantId(), mount.getId());
|
if (existed != null && AiMcpConstants.TRANSPORT_INTERNAL.equalsIgnoreCase(existed.getTransportType())) {
|
return R.error("内置挂载由系统托管,不支持编辑");
|
}
|
String transportType = Cools.isEmpty(mount.getTransportType()) ? AiMcpConstants.TRANSPORT_AUTO : mount.getTransportType().toUpperCase();
|
String usageScope = normalizeUsageScope(mount.getUsageScope());
|
AiMcpMount target = existed == null ? new AiMcpMount() : existed;
|
target.setName(mount.getName());
|
target.setUrl(mount.getUrl());
|
target.setTransportType(transportType);
|
target.setAuthType(Cools.isEmpty(mount.getAuthType()) ? AiMcpConstants.AUTH_TYPE_NONE : mount.getAuthType().toUpperCase());
|
target.setAuthValue(mount.getAuthValue());
|
target.setUsageScope(usageScope);
|
target.setEnabledFlag(mount.getEnabledFlag() == null ? 1 : mount.getEnabledFlag());
|
target.setTimeoutMs(mount.getTimeoutMs() == null ? 10000 : mount.getTimeoutMs());
|
target.setStatus(mount.getStatus() == null ? 1 : mount.getStatus());
|
target.setMemo(mount.getMemo());
|
target.setTenantId(getTenantId());
|
target.setUpdateBy(getLoginUserId());
|
target.setUpdateTime(now);
|
if (existed == null) {
|
target.setUuid(String.valueOf(snowflakeIdWorker.nextId()).substring(3));
|
target.setMountCode(generateMountCode(target.getName()));
|
target.setCreateBy(getLoginUserId());
|
target.setCreateTime(now);
|
if (!aiMcpMountService.save(target)) {
|
return R.error("Save Fail");
|
}
|
} else if (!aiMcpMountService.updateById(target)) {
|
return R.error("Update Fail");
|
}
|
return R.ok().add(buildMountView(target, false));
|
}
|
|
@PreAuthorize("hasAuthority('system:aiMcpMount:update')")
|
@PostMapping("/ai/mcp/console/service/test")
|
public R consoleTestService(@RequestBody Map<String, Object> map) {
|
Long id = Long.valueOf(String.valueOf(map.get("id")));
|
Map<String, Object> result = (Map<String, Object>) aiMcpRegistryService.testMount(getTenantId(), id);
|
AiMcpMount mount = aiMcpMountService.getTenantMount(getTenantId(), id);
|
if (mount == null) {
|
return R.error("mount not found");
|
}
|
result.put("service", buildMountView(mount, false));
|
return R.ok().add(result);
|
}
|
|
@PreAuthorize("hasAuthority('system:aiMcpMount:remove')")
|
@PostMapping("/ai/mcp/console/service/remove/{id}")
|
public R consoleRemoveService(@PathVariable("id") Long id) {
|
AiMcpMount mount = aiMcpMountService.getTenantMount(getTenantId(), id);
|
if (mount == null) {
|
return R.error("mount not found");
|
}
|
if (AiMcpConstants.TRANSPORT_INTERNAL.equalsIgnoreCase(mount.getTransportType())) {
|
return R.error("内置挂载由系统托管,不支持删除");
|
}
|
return aiMcpMountService.removeById(id) ? R.ok() : R.error("Delete Fail");
|
}
|
|
@PreAuthorize("hasAnyAuthority('system:aiToolConfig:save','system:aiToolConfig:update')")
|
@PostMapping("/ai/mcp/console/builtin-tool/save")
|
public R consoleSaveBuiltInTool(@RequestBody AiDiagnosticToolConfig config) {
|
if (Cools.isEmpty(config.getToolCode())) {
|
return R.error("工具编码不能为空");
|
}
|
String usageScope = aiMcpPayloadMapper.normalizeUsageScope(config.getUsageScope());
|
String sceneCode = resolveSceneCodeByUsageScope(usageScope);
|
LambdaQueryWrapper<AiDiagnosticToolConfig> wrapper = new LambdaQueryWrapper<AiDiagnosticToolConfig>()
|
.eq(AiDiagnosticToolConfig::getTenantId, getTenantId())
|
.eq(AiDiagnosticToolConfig::getToolCode, config.getToolCode());
|
if (Cools.isEmpty(sceneCode)) {
|
wrapper.and(w -> w.isNull(AiDiagnosticToolConfig::getSceneCode).or().eq(AiDiagnosticToolConfig::getSceneCode, ""));
|
} else {
|
wrapper.eq(AiDiagnosticToolConfig::getSceneCode, sceneCode);
|
}
|
AiDiagnosticToolConfig existed = aiDiagnosticToolConfigService.getOne(wrapper.last("limit 1"));
|
Date now = new Date();
|
AiDiagnosticToolConfig target = existed == null ? new AiDiagnosticToolConfig() : existed;
|
if (existed == null) {
|
target.setUuid(String.valueOf(snowflakeIdWorker.nextId()).substring(3));
|
target.setCreateBy(getLoginUserId());
|
target.setCreateTime(now);
|
}
|
target
|
.setToolCode(config.getToolCode())
|
.setToolName(config.getToolName())
|
.setSceneCode(sceneCode)
|
.setUsageScope(usageScope)
|
.setEnabledFlag(AiMcpConstants.USAGE_SCOPE_DISABLED.equals(usageScope) ? 0 : (config.getEnabledFlag() == null ? 1 : config.getEnabledFlag()))
|
.setPriority(config.getPriority() == null ? 10 : config.getPriority())
|
.setToolPrompt(config.getToolPrompt())
|
.setStatus(config.getStatus() == null ? 1 : config.getStatus())
|
.setTenantId(getTenantId())
|
.setUpdateBy(getLoginUserId())
|
.setUpdateTime(now)
|
.setMemo(config.getMemo());
|
if (existed == null) {
|
aiDiagnosticToolConfigService.save(target);
|
} else {
|
aiDiagnosticToolConfigService.updateById(target);
|
}
|
return R.ok().add(buildBuiltInToolViews());
|
}
|
|
private List<Map<String, Object>> buildBuiltInToolViews() {
|
List<Map<String, Object>> output = new java.util.ArrayList<>();
|
for (com.vincent.rsf.server.ai.model.AiMcpToolDescriptor descriptor : aiMcpRegistryService.listInternalTools(getTenantId())) {
|
Map<String, Object> item = new LinkedHashMap<>();
|
item.put("toolCode", descriptor.getToolCode());
|
item.put("toolName", descriptor.getToolName());
|
item.put("description", descriptor.getDescription());
|
item.put("enabledFlag", descriptor.getEnabledFlag());
|
item.put("priority", descriptor.getPriority());
|
item.put("toolPrompt", descriptor.getToolPrompt());
|
item.put("usageScope", aiMcpPayloadMapper.resolveUsageScope(descriptor.getSceneCode(), descriptor.getEnabledFlag(), descriptor.getUsageScope()));
|
item.put("advanced", buildAdvancedFlag(descriptor));
|
output.add(item);
|
}
|
return output;
|
}
|
|
private Map<String, Object> buildMountView(AiMcpMount mount, boolean internalManaged) {
|
Map<String, Object> item = new LinkedHashMap<>();
|
item.put("id", mount.getId());
|
item.put("name", mount.getName());
|
item.put("mountCode", mount.getMountCode());
|
item.put("transportType", mount.getTransportType());
|
item.put("displayTransportType", AiMcpConstants.TRANSPORT_INTERNAL.equalsIgnoreCase(mount.getTransportType()) ? "内置" : mount.getTransportType());
|
item.put("url", mount.getUrl());
|
item.put("enabledFlag", mount.getEnabledFlag());
|
item.put("timeoutMs", mount.getTimeoutMs());
|
item.put("lastTestResult", mount.getLastTestResult());
|
item.put("lastTestResult$", mount.getLastTestResult$());
|
item.put("lastTestTime", mount.getLastTestTime$());
|
item.put("lastTestMessage", mount.getLastTestMessage());
|
item.put("lastToolCount", mount.getLastToolCount());
|
item.put("authType", mount.getAuthType());
|
item.put("hasAuth", !Cools.isEmpty(mount.getAuthValue()));
|
item.put("usageScope", normalizeUsageScope(mount.getUsageScope()));
|
item.put("internalManaged", internalManaged);
|
return item;
|
}
|
|
private boolean buildAdvancedFlag(com.vincent.rsf.server.ai.model.AiMcpToolDescriptor descriptor) {
|
return descriptor.getPriority() != null && descriptor.getPriority() != 10
|
|| (descriptor.getToolPrompt() != null && !descriptor.getToolPrompt().trim().isEmpty());
|
}
|
|
private String normalizeUsageScope(String usageScope) {
|
return aiMcpPayloadMapper.normalizeUsageScope(usageScope);
|
}
|
|
private String resolveUsageScope(String sceneCode, Integer enabledFlag) {
|
return aiMcpPayloadMapper.resolveUsageScope(sceneCode, enabledFlag, null);
|
}
|
|
private String resolveSceneCodeByUsageScope(String usageScope) {
|
return AiMcpConstants.USAGE_SCOPE_DIAGNOSE_ONLY.equals(usageScope) ? AiSceneCode.SYSTEM_DIAGNOSE : "";
|
}
|
|
private String generateMountCode(String name) {
|
String normalized = name == null ? "remote_mcp" : name.toLowerCase().replaceAll("[^a-z0-9]+", "_");
|
normalized = normalized.replaceAll("^_+|_+$", "");
|
if (normalized.isEmpty()) {
|
normalized = "remote_mcp";
|
}
|
return normalized + "_" + String.valueOf(System.currentTimeMillis()).substring(7);
|
}
|
}
|