This commit is contained in:
2026-02-27 09:43:11 +08:00
commit ab7a7623b4
14 changed files with 983 additions and 0 deletions

95
README.md Normal file
View File

@@ -0,0 +1,95 @@
# ydoyun-report-mcp
一个基于 Spring Boot 和 MCP (Model Context Protocol) 的报表服务项目,用于将报表存储过程调用能力暴露给 AI 模型。
## 技术栈
- Java 17
- Spring Boot 3.2.6
- Spring AI MCP Server WebMVC 1.0.0
- Maven
## 项目结构
```
ydoyun-report-mcp/
├── pom.xml
├── src/
│ ├── main/
│ │ ├── java/
│ │ │ └── com/junchi/app/mcp/
│ │ │ ├── YdoyunReportMcpApplication.java # 主应用类
│ │ │ ├── config/ # 配置类
│ │ │ │ ├── McpToolConfig.java # MCP 工具配置
│ │ │ │ └── RestTemplateConfig.java # RestTemplate 配置
│ │ │ ├── mcptool/ # MCP 工具服务
│ │ │ │ └── ReportMcpToolsService.java # 报表 MCP 工具服务
│ │ │ └── report/ # 报表服务
│ │ │ ├── ReportService.java # 报表服务实现
│ │ │ └── vo/ # 值对象
│ │ │ ├── ProcedureRequestVO.java # 存储过程请求 VO
│ │ │ └── ProcedureResponseVO.java # 存储过程响应 VO
│ │ └── resources/
│ │ └── application.yml # 应用配置文件
│ └── test/ # 测试代码目录
└── README.md
```
## 功能说明
### 核心功能
1. **MCP 工具服务**: 提供 `executeReportProcedure` 工具方法,供 AI 模型调用
2. **存储过程调用**: 通过 HTTP 调用远程存储过程 API
3. **日期处理**: 支持 `today``yesterday``tomorrow` 关键字或具体日期格式yyyy-MM-dd
### 配置说明
`application.yml` 中配置:
- **服务端口**: 48090
- **MCP 服务器**: 异步类型,仅启用 tools 能力
- **远程 API**: 配置存储过程执行接口地址和 API 密钥
## 构建和运行
### 构建项目
```bash
mvn clean package
```
### 运行项目
```bash
mvn spring-boot:run
```
或者运行打包后的 jar
```bash
java -jar target/ydoyun-report-mcp-1.0.0.jar
```
## API 说明
### MCP 工具
**executeReportProcedure**
根据存储过程名称、日期和仓库代码调用后端存储过程接口。
参数:
- `name` (String): 存储过程名称
- `rq` (String): 日期,支持 `today`/`yesterday`/`tomorrow``2025-12-01` 格式
- `ckdm` (String): 仓库代码
- `p` (String): 秘钥
返回:执行结果的字符串表示
## 注意事项
1. 确保远程 API 服务可访问
2. 配置正确的 API 密钥
3. 根据实际需求调整日志级别

105
pom.xml Normal file
View File

@@ -0,0 +1,105 @@
<?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>
<groupId>com.junchi.app</groupId>
<artifactId>ydoyun-report-mcp</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<name>ydoyun-report-mcp</name>
<properties>
<java.version>17</java.version>
<spring.boot.version>3.2.6</spring.boot.version>
<encoding>UTF-8</encoding>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.6</version>
<relativePath/>
</parent>
<dependencies>
<!-- Spring Boot Web 启动器:提供 Web 服务能力 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Boot 测试启动器:提供测试框架支持 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- Spring Boot Actuator提供应用监控和管理端点 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- Lombok简化 Java 代码,自动生成 getter/setter 等方法 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- Spring AI MCP Server WebMVC 启动器:提供 MCP 服务器能力 -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-server-webmvc</artifactId>
<version>1.0.0</version>
</dependency>
<!-- Logstash Logback 编码器:用于日志格式化,便于日志收集和分析 -->
<dependency>
<groupId>net.logstash.logback</groupId>
<artifactId>logstash-logback-encoder</artifactId>
<version>7.4</version>
</dependency>
<!-- MyBatis Spring Boot Starter提供 MyBatis 与 Spring Boot 集成 -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>3.0.3</version>
</dependency>
<!-- MySQL 驱动:用于连接 MySQL 数据库 -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
<build>
<plugins>
<!-- Spring Boot Maven 插件:用于打包可执行 jar 文件 -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring.boot.version}</version>
</plugin>
<!-- Maven 编译插件:配置 Java 编译版本和编码 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<source>17</source>
<target>17</target>
<encoding>${encoding}</encoding>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,29 @@
package com.junchi.app.mcp;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* 云朵云报表 MCP 服务主应用类
*
* <p>这是一个基于 Spring Boot 和 MCP (Model Context Protocol) 的报表服务应用,
* 用于将报表存储过程调用能力暴露给 AI 模型。</p>
*
* @author 欧浩蓝
* @version 1.0.0
* @since 2025-01-27
*/
@SpringBootApplication
@MapperScan("com.junchi.app.mcp.dal.mapper")
public class YdoyunReportMcpApplication {
/**
* 应用程序入口方法
*
* @param args 命令行参数
*/
public static void main(String[] args) {
SpringApplication.run(YdoyunReportMcpApplication.class, args);
}
}

View File

@@ -0,0 +1,35 @@
package com.junchi.app.mcp.config;
import com.junchi.app.mcp.mcptool.ReportMcpToolsService;
import org.springframework.ai.tool.ToolCallbackProvider;
import org.springframework.ai.tool.method.MethodToolCallbackProvider;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* MCP 工具配置类
*
* <p>用于注册 MCP 工具回调提供者,将报表工具服务暴露给 AI 模型使用。</p>
*
* @author 欧浩蓝
* @since 2025-01-27
*/
@Configuration
public class McpToolConfig {
/**
* 创建工具回调提供者
*
* <p>将 ReportMcpToolsService 中定义的工具方法注册到 MCP 服务器,
* 使得 AI 模型可以通过 MCP 协议调用这些工具。</p>
*
* @param reportMcpToolsService 报表 MCP 工具服务实例
* @return 工具回调提供者
*/
@Bean
public ToolCallbackProvider userTools(ReportMcpToolsService reportMcpToolsService) {
return MethodToolCallbackProvider.builder()
.toolObjects(new Object[]{reportMcpToolsService})
.build();
}
}

View File

@@ -0,0 +1,29 @@
package com.junchi.app.mcp.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
/**
* RestTemplate 配置类
*
* <p>用于配置 HTTP 客户端 RestTemplate用于调用远程存储过程 API。</p>
*
* @author 欧浩蓝
* @since 2025-01-27
*/
@Configuration
public class RestTemplateConfig {
/**
* 创建 RestTemplate Bean
*
* <p>用于执行 HTTP 请求,调用远程存储过程接口。</p>
*
* @return RestTemplate 实例
*/
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}

View File

@@ -0,0 +1,91 @@
package com.junchi.app.mcp.dal.dataobject;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 报表数据源 DO
*
* <p>用于封装报表数据源信息,对应数据库表 ydoyun_report_database。</p>
*
* @author 欧浩蓝
* @since 2025-01-27
*/
@Data
public class ReportDatabaseDO {
/**
* 主键ID
*/
private Long id;
/**
* 数据源名称
*/
private String dbName;
/**
* 数据库类型mysql、pgsql、oracle、sqlserver
*/
private String dbType;
/**
* 数据库地址
*/
private String host;
/**
* 数据库端口
*/
private Integer port;
/**
* 数据库账号
*/
private String username;
/**
* 数据库密码(建议加密存储)
*/
private String password;
/**
* 数据库名称
*/
private String databaseName;
/**
* 备注说明
*/
private String remark;
/**
* 创建者
*/
private String creator;
/**
* 创建时间
*/
private LocalDateTime createTime;
/**
* 更新者
*/
private String updater;
/**
* 更新时间
*/
private LocalDateTime updateTime;
/**
* 是否删除
*/
private Boolean deleted;
/**
* 租户编号
*/
private Long tenantId;
}

View File

@@ -0,0 +1,25 @@
package com.junchi.app.mcp.dal.mapper;
import com.junchi.app.mcp.dal.dataobject.ReportDatabaseDO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
/**
* 报表数据源 Mapper 接口
*
* <p>用于查询报表数据源信息。</p>
*
* @author 欧浩蓝
* @since 2025-01-27
*/
@Mapper
public interface ReportDatabaseMapper {
/**
* 根据 report_id 查询绑定的数据源信息
*
* @param reportId 报表ID
* @return 数据源信息
*/
ReportDatabaseDO selectReportDatabaseByReportId(@Param("reportId") Long reportId);
}

View File

@@ -0,0 +1,107 @@
package com.junchi.app.mcp.mcptool;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.junchi.app.mcp.report.ReportService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.LinkedHashMap;
/**
* 报表 MCP 工具服务类
*
* <p>提供 MCP 工具方法,供 AI 模型调用执行报表存储过程。</p>
*
* @author 欧浩蓝
* @since 2025-01-27
*/
@Service
public class ReportMcpToolsService {
private static final Logger log = LoggerFactory.getLogger(ReportMcpToolsService.class);
@Autowired
private ReportService reportService;
@Autowired
private ObjectMapper objectMapper;
/**
* 执行报表存储过程
*/
@Tool(
description = "根据【报表ID(reportId) + 存储过程名称(name)】执行对应的数据库存储过程,"
+ "并按 params 中提供的动态参数查询报表数据。"
+ "该工具用于获取指定报表在指定条件下的原始数据结果,"
+ "不做统计、不做分析,只负责数据查询。"
)
public String executeReportProcedure(
@ToolParam(description = "报表ID用于确定使用哪一个报表配置及其数据库连接")
Long reportId,
@ToolParam(description = "存储过程名称,对应数据库中的具体存储过程名")
String name,
@ToolParam(
description = "存储过程参数 JSON 字符串,"
+ "示例:{\"rq\":\"2025-12-15\",\"ckdm\":\"001\"}"
)
String params
) {
LinkedHashMap<String, Object> paramMap;
try {
if (params == null || params.isBlank()) {
paramMap = new LinkedHashMap<>();
} else {
paramMap = objectMapper.readValue(
params,
new TypeReference<LinkedHashMap<String, Object>>() {}
);
}
} catch (Exception e) {
throw new IllegalArgumentException("存储过程参数 params JSON 解析失败:" + params, e);
}
long startTime = System.currentTimeMillis();
// ===== MCP 调用开始 =====
log.info("[MCP] BEGIN executeReportProcedure, reportId={}, name={}", reportId, name);
log.debug("[MCP] params={}", paramMap);
try {
// 参数校验
if (name == null || name.isBlank()) {
log.warn("[MCP] 参数校验失败name 为空");
throw new IllegalArgumentException("参数 name(存储过程名称) 不能为空");
}
if (reportId == null) {
log.warn("[MCP] 参数校验失败reportId 为空");
throw new IllegalArgumentException("参数 reportId(报表ID) 不能为空");
}
// 执行存储过程
Object result = reportService.executeProcedure(reportId, name, paramMap);
String resultStr = result == null ? "" : result.toString();
long cost = System.currentTimeMillis() - startTime;
log.info("[MCP] END executeReportProcedure success, cost={}ms, resultLength={}",
cost, resultStr.length());
log.debug("[MCP] result={}", resultStr);
return resultStr;
} catch (IllegalArgumentException e) {
long cost = System.currentTimeMillis() - startTime;
log.warn("[MCP] PARAM ERROR, cost={}ms, msg={}", cost, e.getMessage());
throw e;
} catch (Exception e) {
long cost = System.currentTimeMillis() - startTime;
log.error("[MCP] ERROR executeReportProcedure, cost={}ms", cost, e);
throw e;
}
}
}

View File

@@ -0,0 +1,258 @@
package com.junchi.app.mcp.report;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.junchi.app.mcp.dal.dataobject.ReportDatabaseDO;
import com.junchi.app.mcp.dal.mapper.ReportDatabaseMapper;
import com.junchi.app.mcp.report.vo.ProcedureRequestVO;
import com.junchi.app.mcp.report.vo.ReportDatabaseRespVO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* 报表服务类
*
* <p>负责调用远程存储过程 API处理日期参数解析和响应数据格式化。</p>
*
* @author 欧浩蓝
* @since 2025-01-27
*/
@Service
public class ReportService {
/** 日志记录器 */
private static final Logger log = LoggerFactory.getLogger(ReportService.class);
/** 日期格式化器,用于将日期格式化为 yyyy-MM-dd 格式 */
private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
/** HTTP 客户端,用于调用远程 API */
private final RestTemplate restTemplate;
/** 报表数据源 Mapper */
@Autowired
private ReportDatabaseMapper reportDatabaseMapper;
/** 远程存储过程执行接口地址,从配置文件读取 */
@Value("${ydoyun.mssqljdbc.report.api.execute-url}")
private String executeUrl;
/** API 密钥,从配置文件读取 */
@Value("${ydoyun.mssqljdbc.report.api.key}")
private String apiKey;
/**
* 构造函数
*
* @param restTemplate HTTP 客户端实例
*/
public ReportService(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
/**
* 解析日期关键字或日期字符串
*
* <p>将日期关键字today/yesterday/tomorrow转换为具体的日期格式yyyy-MM-dd
* 如果输入已经是日期格式,则直接返回。</p>
*
* @param dateKeyword 日期关键字today/yesterday/tomorrow或日期字符串yyyy-MM-dd
* @return 格式化后的日期字符串yyyy-MM-dd 格式)
*/
private String resolveDate(String dateKeyword) {
LocalDate date = LocalDate.now();
switch (dateKeyword.toLowerCase()) {
case "yesterday":
// 昨天:当前日期减一天
date = date.minusDays(1);
break;
case "today":
// 今天:使用当前日期
break;
case "tomorrow":
// 明天:当前日期加一天
date = date.plusDays(1);
break;
default:
// 如果不是关键字,直接返回原值(假设是日期格式)
return dateKeyword;
}
return date.format(FORMATTER);
}
/**
* 执行存储过程
*
* <p>调用远程存储过程 API执行指定的存储过程并返回结果。
* 该方法会处理日期参数解析、构建请求、发送 HTTP 请求、解析响应等步骤。</p>
*
* @param name 存储过程名称
* @param rq 日期(支持 today/yesterday/tomorrow 或 yyyy-MM-dd 格式)
* @param ckdm 仓库代码
* @param reportId 报表ID
* @param p 秘钥
* @return 执行结果,可能是 List、Map、String 或其他对象类型
* @throws RuntimeException 当远程接口返回为空、调用失败或解析失败时抛出
*/
public Object executeProcedure(Long reportId, String name,LinkedHashMap<String, Object> params) {
log.info("【报表服务】开始执行存储过程: {}", name);
// 根据 reportId 查询数据源信息
ReportDatabaseRespVO reportDatabase = null;
try {
log.info("【报表服务】开始查询报表数据源信息reportId: {}", reportId);
ReportDatabaseDO databaseDO = reportDatabaseMapper.selectReportDatabaseByReportId(reportId);
if (databaseDO != null) {
// 将 DO 转换为 VO
reportDatabase = new ReportDatabaseRespVO();
BeanUtils.copyProperties(databaseDO, reportDatabase);
log.info("【报表服务】数据源信息查询成功: id={}, dbName={}, dbType={}, host={}, port={}",
reportDatabase.getId(), reportDatabase.getDbName(), reportDatabase.getDbType(),
reportDatabase.getHost(), reportDatabase.getPort());
} else {
log.warn("【报表服务】未找到报表ID对应的数据源信息: reportId={}", reportId);
}
} catch (Exception e) {
log.error("【报表服务】查询数据源信息失败: reportId={}", reportId, e);
// 查询失败不影响主流程,继续执行
}
// 构建请求对象
ProcedureRequestVO req = new ProcedureRequestVO();
req.setProcedureName(name);
req.setParams(params);
// 设置数据源信息
req.setReportDatabase(reportDatabase);
log.debug("【报表服务】请求对象构建完成: procedureName={}, reportDatabase={}",
name, reportDatabase != null ? "已设置" : "未设置");
// 设置 HTTP 请求头
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.set("X-API-KEY", apiKey); // 设置 API 密钥
log.debug("【报表服务】HTTP 请求头设置完成");
// 创建 HTTP 实体
HttpEntity<ProcedureRequestVO> entity = new HttpEntity<>(req, headers);
// 发送 POST 请求
log.info("【报表服务】开始调用远程接口: {}", executeUrl);
long requestStartTime = System.currentTimeMillis();
String rawResponse = restTemplate.postForObject(executeUrl, entity, String.class);
long requestEndTime = System.currentTimeMillis();
long requestDuration = requestEndTime - requestStartTime;
log.info("【报表服务】远程接口调用完成,耗时: {} 毫秒", requestDuration);
// 检查响应是否为空
if (rawResponse == null || rawResponse.trim().isEmpty()) {
log.error("【报表服务】远程接口返回为空");
throw new RuntimeException("远程接口返回为空");
}
// 记录原始响应(调试级别)
log.info("【报表服务】收到远程接口响应,响应长度: {} 字符", rawResponse.length());
log.debug("========================================");
log.debug("【报表服务】原始响应 JSON: {}", rawResponse);
log.debug("========================================");
try {
// 解析 JSON 响应
log.debug("【报表服务】开始解析 JSON 响应");
ObjectMapper objectMapper = new ObjectMapper();
TypeReference<Map<String, Object>> typeRef = new TypeReference<Map<String, Object>>() {};
Map<String, Object> responseMap = objectMapper.readValue(rawResponse, typeRef);
log.info("【报表服务】JSON 响应解析成功");
// 记录解析后的信息(调试级别)
log.debug("【报表服务】解析后的 responseMap: {}", responseMap);
log.debug("【报表服务】data 字段值: {}", responseMap.get("data"));
log.debug("【报表服务】data 字段类型: {}",
responseMap.get("data") != null ? responseMap.get("data").getClass().getName() : "null");
if (responseMap.get("data") instanceof List) {
int listSize = ((List<?>) responseMap.get("data")).size();
log.info("【报表服务】data 是 List 类型,包含 {} 条记录", listSize);
log.debug("【报表服务】data 是 List大小: {}", listSize);
}
log.debug("========================================");
// 检查响应码
Object codeObj = responseMap.get("code");
if (codeObj != null) {
int code = (codeObj instanceof Integer) ? (Integer) codeObj : Integer.parseInt(String.valueOf(codeObj));
log.info("【报表服务】响应码: {}", code);
if (code != 0) {
// 响应码不为 0表示调用失败
Object msgObj = responseMap.get("msg");
String msg = (msgObj != null) ? String.valueOf(msgObj) : "未知错误";
log.error("【报表服务】远程接口调用失败: code={}, msg={}", code, msg);
throw new RuntimeException("远程接口调用失败: code=" + code + ", msg=" + msg);
}
log.info("【报表服务】响应码检查通过code=0");
}
// 处理响应数据
Object data = responseMap.get("data");
// 如果 data 为 null返回空列表
if (data == null) {
log.warn("【报表服务】响应数据为 null返回空列表");
return new ArrayList<>();
}
// 如果 data 是字符串,尝试解析为 JSON
if (data instanceof String) {
String dataStr = (String) data;
log.debug("【报表服务】响应数据是字符串类型,长度: {}", dataStr.length());
// 空字符串返回空列表
if (dataStr.isEmpty() || dataStr.trim().isEmpty()) {
log.warn("【报表服务】响应数据为空字符串,返回空列表");
return new ArrayList<>();
}
// 如果字符串看起来像 JSON以 [ 或 { 开头),尝试解析
String trimmed = dataStr.trim();
if (trimmed.startsWith("[") || trimmed.startsWith("{")) {
log.debug("【报表服务】检测到 JSON 格式字符串,尝试解析");
try {
Object parsedData = objectMapper.readValue(dataStr, Object.class);
log.info("【报表服务】JSON 字符串解析成功");
return parsedData;
} catch (Exception e) {
log.error("【报表服务】解析 data JSON 字符串失败: {}", e.getMessage());
return dataStr; // 解析失败,返回原始字符串
}
}
log.debug("【报表服务】响应数据不是 JSON 格式,返回原始字符串");
return dataStr; // 不是 JSON 格式,返回原始字符串
}
// 其他类型直接返回
log.info("【报表服务】响应数据处理完成,类型: {}", data.getClass().getSimpleName());
return data;
} catch (JsonProcessingException e) {
log.error("【报表服务】解析响应 JSON 失败: {}", e.getMessage());
log.error("【报表服务】原始响应: {}", rawResponse);
throw new RuntimeException("解析响应 JSON 失败: " + e.getMessage() + ", 原始响应: " + rawResponse, e);
}
}
}

View File

@@ -0,0 +1,25 @@
package com.junchi.app.mcp.report.vo;
import lombok.Data;
import java.util.Map;
/**
* 存储过程请求值对象
*
* <p>用于封装调用远程存储过程 API 的请求参数。</p>
*
* @author 欧浩蓝
* @since 2025-01-27
*/
@Data
public class ProcedureRequestVO {
/** 存储过程名称 */
private String procedureName;
/** 存储过程参数映射key 为参数名value 为参数值 */
private Map<String, Object> params;
/** 报表数据源信息 */
private ReportDatabaseRespVO reportDatabase;
}

View File

@@ -0,0 +1,24 @@
package com.junchi.app.mcp.report.vo;
import lombok.Data;
/**
* 存储过程响应值对象
*
* <p>用于封装远程存储过程 API 返回的响应数据。</p>
*
* @author 欧浩蓝
* @since 2025-01-27
*/
@Data
public class ProcedureResponseVO {
/** 响应码0 表示成功,非 0 表示失败 */
private Integer code;
/** 响应消息,通常用于描述错误信息 */
private String msg;
/** 响应数据,可能是 List、Map、String 或其他类型 */
private Object data;
}

View File

@@ -0,0 +1,46 @@
package com.junchi.app.mcp.report.vo;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 报表数据源响应 VO
*
* <p>用于封装报表数据源信息,用于传递给存储过程接口。</p>
*
* @author 欧浩蓝
* @since 2025-01-27
*/
@Data
public class ReportDatabaseRespVO {
/** 主键ID */
private Long id;
/** 数据源名称 */
private String dbName;
/** 数据库类型mysql、pgsql、oracle、sqlserver */
private String dbType;
/** 数据库地址 */
private String host;
/** 数据库端口 */
private Integer port;
/** 数据库账号 */
private String username;
/** 数据库密码(建议加密存储) */
private String password;
/** 数据库名称 */
private String databaseName;
/** 备注说明 */
private String remark;
/** 创建时间 */
private LocalDateTime createTime;
}

View File

@@ -0,0 +1,78 @@
# 服务器配置
server:
# 服务端口号
port: 48090
# Spring 应用配置
spring:
application:
# 应用名称
name: ydoyun-report-mcp
# 禁用条件评估报告(避免启动时输出大量自动配置信息)
main:
log-startup-info: true # 只显示启动信息,不显示条件评估报告
# 数据源配置
datasource:
url: jdbc:mysql://118.253.178.8:23306/ydoyun-report-ai?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true&rewriteBatchedStatements=true
username: root
password: mysql_Fa4wpQ
driver-class-name: com.mysql.cj.jdbc.Driver
ai:
mcp:
server:
# MCP 服务基础信息
name: ydoyun-report-mcp
version: 1.0.0
# 服务类型ASYNC 表示异步模式
type: ASYNC
# 服务说明:描述 MCP 服务器的功能
instructions: "MCP server for Ydoyun report tools"
# SSE 消息推送接口地址:用于推送消息给客户端
sse-message-endpoint: /mcp/messages
# SSE 订阅端点:客户端订阅服务器端事件的端点
sse-endpoint: /mcp/sse
# MCP 能力声明:定义服务器支持的能力
capabilities:
tool: true # 支持工具调用
resource: false # 不支持资源访问
prompt: false # 不支持提示模板
completion: false # 不支持补全功能
# 云朵云报表服务配置
ydoyun:
mssqljdbc:
report:
api:
# API 密钥:用于调用远程存储过程接口的认证密钥
key: your_secret_api_key_123456
# 存储过程执行接口地址
execute-url: http://118.253.178.8:49090/api/procedure/execute
# MyBatis 配置
mybatis:
# Mapper XML 文件位置
mapper-locations: classpath*:mapper/**/*.xml
# 实体类包路径
type-aliases-package: com.junchi.app.mcp.dal.dataobject
configuration:
# 开启驼峰命名转换
map-underscore-to-camel-case: true
# 日志实现
log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl
# 日志配置
logging:
level:
root: INFO # 根日志级别
# MCP 相关包的日志级别设置为 DEBUG便于调试
com.junchi.app.mcp: DEBUG
org:
# Spring 框架日志级别INFO 用于正常运行时
# 注意:如果设置为 DEBUG会输出条件评估报告CONDITIONS EVALUATION REPORT
springframework: INFO
# 特别控制自动配置包的日志级别,避免输出条件评估报告
springframework.boot.autoconfigure: WARN
# 日志输出格式配置(可选,使用默认格式)
pattern:
console: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"

View File

@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.junchi.app.mcp.dal.mapper.ReportDatabaseMapper">
<!-- 结果映射 -->
<resultMap id="ReportDatabaseResultMap" type="com.junchi.app.mcp.dal.dataobject.ReportDatabaseDO">
<id column="id" property="id"/>
<result column="db_name" property="dbName"/>
<result column="db_type" property="dbType"/>
<result column="host" property="host"/>
<result column="port" property="port"/>
<result column="username" property="username"/>
<result column="password" property="password"/>
<result column="database_name" property="databaseName"/>
<result column="remark" property="remark"/>
<result column="creator" property="creator"/>
<result column="create_time" property="createTime"/>
<result column="updater" property="updater"/>
<result column="update_time" property="updateTime"/>
<result column="deleted" property="deleted"/>
<result column="tenant_id" property="tenantId"/>
</resultMap>
<!-- 根据 report_id 查询绑定的数据源信息 -->
<select id="selectReportDatabaseByReportId"
resultMap="ReportDatabaseResultMap"
parameterType="java.lang.Long">
SELECT db.*
FROM ydoyun_report_database db
INNER JOIN ydoyun_report r ON r.database_id = db.id
WHERE r.id = #{reportId}
AND db.deleted = 0
</select>
</mapper>