1776654103

This commit is contained in:
Docker7530
2026-04-20 11:01:47 +08:00
parent ab41c81a53
commit 6b50219f55
209 changed files with 1922 additions and 1467 deletions
+1
View File
@@ -0,0 +1 @@
DDD
+77
View File
@@ -0,0 +1,77 @@
MCP 协议的实现,可以有2种方式,
1. stdio,把 jar 提供出去,别人本地配置引入即可使用,但这样的方式不太适合做统一网关服务。
2. sse
项目采用 [DDD](DDD.md) 架构,首先聚焦 domain 层。
### 会话管理服务
```java
private final ScheduledExecutorService cleanupScheduler = Executors.newSingleThreadScheduledExecutor();
```
其中 private 说明这个变量这能在类内部访问,声明为 final 则此变量只能赋值一次。`newSingleThreadScheduledExecutor` 代表只有一个工作线程。
> 一个带有定时功能的单线程定时队列。
```java
private final Map<String, SessionConfigVO> activeSessions = new ConcurrentHashMap<>();
```
考虑点在于这个会话是否会被多个线程操作处理。
```java
public SessionManagementService() {
cleanupScheduler.scheduleAtFixedRate(this::cleanupExpiredSessions, 5, 5, TimeUnit.MINUTES);
log.info("会话管理服务已启动,会话超时时间: {} 分钟", SESSION_TIMEOUT_MINUTES);
}
```
可以在构造中执行一些方法。
### 实时通讯
> 对立面轮训
SSEServer sent Events
基于普通的 HTTP 长连接,Content-type: text/event-stream
消息格式,每条消息以 \n\n 结尾,
![](../attachment/images-paste/Pasted%20image%2020260413135806.png)
```
SSE(协议)
需要一个好的服务器框架来实现它
Spring WebFlux(响应式 Web 框架) ← 最适合实现 SSE 的框架
因为它是响应式(非阻塞),非常适合长连接
在 WebFlux 里,用 Sinks + Flux<ServerSentEvent> 来产生和推送 SSE 事件
```
```
客户端发起 SSE 请求
Netty EventLoop 接收请求(线程A
Controller 返回 sink.asFlux() → 注册“有新事件时推送”的回调
线程A 立刻释放,去处理其他请求
...(连接保持打开,线程A 忙别的)
你的服务层调用 sink.tryEmitNext(新消息)
Reactor 通知 Netty:“这个连接有数据要写”
EventLoop 线程(可能是线程B)被唤醒
执行回调:把消息转成 "data: xxx\n\n" 格式 → 写入 Socket
写完后,线程B 立刻释放,继续干别的
```
-121
View File
@@ -1,121 +0,0 @@
```
# ========== 1. 请求数统计 ==========
curl -X POST http://localhost:7530/api/mcp-tools \
-H "Content-Type: application/json" \
-d '{
"name": "get_request_count",
"description": "查询请求数统计数据,返回各时间点的请求数列表",
"backendUrl": "http://dev.p.cdn.10086.cn:8080/mcp/requestCount",
"backendMethod": "GET",
"backendTimeout": 10000,
"forwardHeaders": ["Authorization", "X-User-Id"],
"responseDescription": "返回 JSONerrCode=0 为成功,data 为数据列表,示例:{\"errCode\":0,\"data\":[{\"name\":\"2026-03-27 10:00\",\"value\":12345}],\"unit\":\"\"}",
"parameters": [
{"name": "cpId", "type": "string", "description": "企业ID(空默认全部)", "required": false, "sortOrder": 0},
{"name": "domainNames", "type": "string", "description": "域名列表,逗号分隔(空默认全部)", "required": false, "sortOrder": 1},
{"name": "product", "type": "string", "description": "产品类型(空默认全部,下载加速:0,视音频点播加速:1,网页加速:2,直播加速:5,超低时延直播:7,全站四层加速:8,全站七层加速:9", "required": false, "sortOrder": 2},
{"name": "granular", "type": "string", "description": "时间粒度(5分钟:01小时:11天:2", "required": false, "sortOrder": 3},
{"name": "providers", "type": "string", "description": "加速厂商列表,逗号分隔(空默认全部,华为平面:1,中兴平面:2,HCDN:3,自研1.0:4,自研RTMP:5,自研2.0:8,国际平面:9,合作服务商1:10,合作服务商211", "required": false, "sortOrder": 4},
{"name": "affectAreas", "type": "string", "description": "加速区域列表,逗号分隔(空默认全部,例如安徽、北京、福建)", "required": false, "sortOrder": 5},
{"name": "isps", "type": "string", "description": "运营商列表,逗号分隔(空默认全部,移动:1,电信:2,联通:3,多线:4,国际:5,其他:0", "required": false, "sortOrder": 6},
{"name": "startTime", "type": "string", "description": "开始时间,格式 yyyy-MM-dd HH:mm", "required": false, "sortOrder": 7},
{"name": "endTime", "type": "string", "description": "结束时间,格式 yyyy-MM-dd HH:mm", "required": false, "sortOrder": 8}
]
}'
# ========== 2. 命中请求数统计 ==========
curl -X POST http://localhost:7530/api/mcp-tools \
-H "Content-Type: application/json" \
-d '{
"name": "get_hit_req_count",
"description": "查询命中请求数统计数据,返回各时间点的命中请求数列表",
"backendUrl": "http://dev.p.cdn.10086.cn:8080/mcp/hitReqCount",
"backendMethod": "GET",
"backendTimeout": 10000,
"forwardHeaders": ["Authorization", "X-User-Id"],
"responseDescription": "返回 JSONerrCode=0 为成功,data 为数据列表,示例:{\"errCode\":0,\"data\":[{\"name\":\"2026-03-27 10:00\",\"value\":10000}],\"unit\":\"\"}",
"parameters": [
{"name": "cpId", "type": "string", "description": "企业ID(空默认全部)", "required": false, "sortOrder": 0},
{"name": "domainNames", "type": "string", "description": "域名列表,逗号分隔(空默认全部)", "required": false, "sortOrder": 1},
{"name": "product", "type": "string", "description": "产品类型(空默认全部)", "required": false, "sortOrder": 2},
{"name": "granular", "type": "string", "description": "时间粒度(5分钟:01小时:11天:2", "required": false, "sortOrder": 3},
{"name": "providers", "type": "string", "description": "加速厂商列表,逗号分隔(空默认全部)", "required": false, "sortOrder": 4},
{"name": "affectAreas", "type": "string", "description": "加速区域列表,逗号分隔(空默认全部)", "required": false, "sortOrder": 5},
{"name": "isps", "type": "string", "description": "运营商列表,逗号分隔(空默认全部)", "required": false, "sortOrder": 6},
{"name": "startTime", "type": "string", "description": "开始时间,格式 yyyy-MM-dd HH:mm", "required": false, "sortOrder": 7},
{"name": "endTime", "type": "string", "description": "结束时间,格式 yyyy-MM-dd HH:mm", "required": false, "sortOrder": 8}
]
}'
# ========== 3. 命中率计算(一次性接口) ==========
curl -X POST http://localhost:7530/api/mcp-tools \
-H "Content-Type: application/json" \
-d '{
"name": "get_hit_ratio",
"description": "计算命中率,命中率 = 命中请求数 / 总请求数,返回各时间点的命中率百分比(2位小数)",
"backendUrl": "http://dev.p.cdn.10086.cn:8080/mcp/hitRatio",
"backendMethod": "GET",
"backendTimeout": 10000,
"forwardHeaders": ["Authorization", "X-User-Id"],
"responseDescription": "返回 JSONerrCode=0 为成功,data 为数据列表,unit 为 %,示例:{\"errCode\":0,\"data\":[{\"name\":\"2026-03-27 10:00\",\"value\":85.23}],\"unit\":\"%\"}",
"parameters": [
{"name": "cpId", "type": "string", "description": "企业ID(空默认全部)", "required": false, "sortOrder": 0},
{"name": "domainNames", "type": "string", "description": "域名列表,逗号分隔(空默认全部)", "required": false, "sortOrder": 1},
{"name": "product", "type": "string", "description": "产品类型(空默认全部)", "required": false, "sortOrder": 2},
{"name": "granular", "type": "string", "description": "时间粒度(5分钟:01小时:11天:2", "required": false, "sortOrder": 3},
{"name": "providers", "type": "string", "description": "加速厂商列表,逗号分隔(空默认全部)", "required": false, "sortOrder": 4},
{"name": "affectAreas", "type": "string", "description": "加速区域列表,逗号分隔(空默认全部)", "required": false, "sortOrder": 5},
{"name": "isps", "type": "string", "description": "运营商列表,逗号分隔(空默认全部)", "required": false, "sortOrder": 6},
{"name": "startTime", "type": "string", "description": "开始时间,格式 yyyy-MM-dd HH:mm", "required": false, "sortOrder": 7},
{"name": "endTime", "type": "string", "description": "结束时间,格式 yyyy-MM-dd HH:mm", "required": false, "sortOrder": 8}
]
}'
# ========== 4. 命中率工具(分步计算用:先调requestCount,再调hitReqCount,最后调此工具计算) ==========
curl -X POST http://localhost:7530/api/mcp-tools \
-H "Content-Type: application/json" \
-d '{
"name": "calculate_hit_ratio",
"description": "根据请求数和命中请求数计算命中率,返回百分比(2位小数)。适用于先调用 get_request_count 和 get_hit_req_count 获取原始数据后,再调用此工具计算命中率",
"backendUrl": "http://dev.p.cdn.10086.cn:8080/mcp/hitRatio/calculate",
"backendMethod": "GET",
"backendTimeout": 5000,
"forwardHeaders": ["Authorization", "X-User-Id"],
"responseDescription": "返回 JSONerrCode=0 为成功,示例:{\"errCode\":0,\"data\":[{\"name\":\"hitRatio\",\"value\":85.23}],\"unit\":\"%\"}",
"parameters": [
{"name": "requestCount", "type": "integer", "description": "总请求数", "required": true, "sortOrder": 0},
{"name": "hitReqCount", "type": "integer", "description": "命中请求数", "required": true, "sortOrder": 1}
]
}'
# ========== 5. 获取企业列表 ==========
curl -X POST http://localhost:7530/api/mcp-tools \
-H "Content-Type: application/json" \
-d '{
"name": "get_enterprise_list",
"description": "获取当前用户所属的企业列表,返回 enterpriseId 和 enterpriseName,供统计接口选择企业参数用",
"backendUrl": "http://dev.p.cdn.10086.cn:8080/mcp/enterprises",
"backendMethod": "GET",
"backendTimeout": 5000,
"forwardHeaders": ["Authorization", "X-User-Id"],
"responseDescription": "返回 JSON 列表,每个元素含 enterpriseId 和 enterpriseName,示例:[{\"enterpriseId\":\"abc123\",\"enterpriseName\":\"测试企业\"}]",
"parameters": []
}'
# ========== 6. 获取域名列表 ==========
curl -X POST http://localhost:7530/api/mcp-tools \
-H "Content-Type: application/json" \
-d '{
"name": "get_domain_list",
"description": "根据企业ID获取该企业下已生效的域名列表,供统计接口选择域名参数用",
"backendUrl": "http://dev.p.cdn.10086.cn:8080/mcp/domains",
"backendMethod": "GET",
"backendTimeout": 5000,
"forwardHeaders": ["Authorization", "X-User-Id"],
"responseDescription": "返回 JSON 列表,每个元素为域名字符串,示例:[\"www.example.com\",\"img.example.com\"]",
"parameters": [
{"name": "tenantId", "type": "string", "description": "企业IDtenantId,即 enterpriseId", "required": true, "sortOrder": 0}
]
}'
```
-1
View File
@@ -1 +0,0 @@
![](../attachment/Pasted%20image%2020260402090513.png)
-3
View File
@@ -1,3 +0,0 @@
```
{% if not add_generation_prompt is defined %}{% set add_generation_prompt = false %}{% endif %}{% set ns = namespace(is_first=false, is_tool=false, is_output_first=true, system_prompt='') %}{%- for message in messages %}{%- if message['role'] == 'system' %}{% set ns.system_prompt = message['content'] %}{%- endif %}{%- endfor %}{{bos_token}}{{ns.system_prompt}}{%- for message in messages %}{%- if message['role'] == 'user' %}{%- set ns.is_tool = false -%}{{'<User>' + message['content']}}{%- endif %}{%- if message['role'] == 'assistant' and message['content'] is none %}{%- set ns.is_tool = false -%}{%- for tool in message['tool_calls']%}{%- if not ns.is_first %}{{'<Assistant><tool▁calls▁begin><tool▁call▁begin>' + tool['type'] + '<tool▁sep>' + tool['function']['name'] + '\\n' + '```json' + '\\n' + tool['function']['arguments'] + '\\n' + '```' + '<tool▁call▁end>'}}{%- set ns.is_first = true -%}{%- else %}{{'\\n' + '<tool▁call▁begin>' + tool['type'] + '<tool▁sep>' + tool['function']['name'] + '\\n' + '```json' + '\\n' + tool['function']['arguments'] + '\\n' + '```' + '<tool▁call▁end>'}}{{'<tool▁calls▁end><end▁of▁sentence>'}}{%- endif %}{%- endfor %}{%- endif %}{%- if message['role'] == 'assistant' and message['content'] is not none %}{%- if ns.is_tool %}{{'<tool▁outputs▁end>' + message['content'] + '<end▁of▁sentence>'}}{%- set ns.is_tool = false -%}{%- else %}{% set content = message['content'] %}{% if '</think>' in content %}{% set content = message['content'].replace('</think>', '').split('<think>')[-1] %}{% endif %}{{'<Assistant>' + content + '<end▁of▁sentence>'}}{%- endif %}{%- endif %}{%- if message['role'] == 'tool' %}{%- set ns.is_tool = true -%}{%- if ns.is_output_first %}{{'<tool▁outputs▁begin><tool▁output▁begin>' + message['content'] + '<tool▁output▁end>'}}{%- set ns.is_output_first = false %}{%- else %}{{'\\n<tool▁output▁begin>' + message['content'] + '<tool▁output▁end>'}}{%- endif %}{%- endif %}{%- endfor -%}{% if ns.is_tool %}{{'<tool▁outputs▁end>'}}{% endif %}{% if add_generation_prompt and not ns.is_tool %}{{'<Assistant>'}}{% endif %}
```
+37
View File
@@ -0,0 +1,37 @@
```java
package cn.bugstack.ai.infrastructure.gateway;
import okhttp3.RequestBody;
import okhttp3.ResponseBody;
import retrofit2.Call;
import retrofit2.http.Body;
import retrofit2.http.GET;
import retrofit2.http.HeaderMap;
import retrofit2.http.POST;
import retrofit2.http.QueryMap;
import retrofit2.http.Url;
import java.util.Map;
/**
* 资料:<a href="https://bugstack.cn/md/road-map/http.html">HTTP 框架案例</a>
*/
public interface GenericHttpGateway {
@POST
Call<ResponseBody> post(
@Url String url,
@HeaderMap Map<String, Object> headers,
@Body RequestBody body
);
@GET
Call<ResponseBody> get(
@Url String url,
@HeaderMap Map<String, Object> headers,
@QueryMap Map<String, Object> queryParams
);
}
```
-44
View File
@@ -1,44 +0,0 @@
```
http://dev.p.cdn.10086.cn:8080
/statistics/getHitRatio
?cpId=*
&product=*
&affectAreas=
&domainNames=
&provider=*
&startTime=2026-03-27%2010:25
&endTime=2026-03-27%2010:25
&productId=
&isps=
&granular=0
&_=1774580169469
```
com.cmcc.cdn.platform.selfservice.controller.StatisticsController#getHitRatio
给我梳理下这个接口的功能,输出一个 markdown 文档到项目的根目录。
主要是用到哪些参数,有哪些校验,有没有参数转换和默认值。掉了哪些三方接口,调了接口后做了哪些事情。尽量清晰。注意细节。
---
com.cmcc.cdn.platform.selfservice.controller.StatisticsController#getHitRatio
getHitRatio接口分析.md 是我 getHitRatio 命中率接口的梳理,是在跑的业务。我现在给你安排一个艰巨的任务。
因为我们有一个 MCP 注册中心。我要接入进去。接入我们只需要提供标准的 http 接口就行了。所以按照这个接口做 3 个新接口出来:
1. 请求数
2. 命中请求数
3. 计算命中率(命中率 = 命中请求数 / 总请求数)的接口。
controller 在 ibs-portal\cdn-web\src\main\java\com\cmcc\cdn\platform 下建一个 mcp 文件夹写。
service 在 ibs-portal\cdn-service\src\main\java\com\cmcc\cdn\platform 下建一个 mcp 文件夹写。
因为是新接口代码一定要标准易读清晰,不要用原来的垃圾逻辑。
再帮我额外提供一个接口。可以实现比如 AI 获取 请求数 然后获取 命中请求数 。然后可以计算他们两个得出命中率的工具接口。方便我测试是调用一次计算命中率比较好,还是调用 请求数 然后调用 命中请求数 然后调用计算工具计算合适。