1776654103
This commit is contained in:
@@ -0,0 +1,121 @@
|
||||
```
|
||||
# ========== 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": "返回 JSON,errCode=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分钟:0,1小时:1,1天: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,合作服务商2:11)", "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": "返回 JSON,errCode=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分钟:0,1小时:1,1天: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": "返回 JSON,errCode=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分钟:0,1小时:1,1天: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": "返回 JSON,errCode=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": "企业ID(tenantId,即 enterpriseId)", "required": true, "sortOrder": 0}
|
||||
]
|
||||
}'
|
||||
|
||||
```
|
||||
@@ -0,0 +1,44 @@
|
||||
|
||||
```
|
||||
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 获取 请求数 然后获取 命中请求数 。然后可以计算他们两个得出命中率的工具接口。方便我测试是调用一次计算命中率比较好,还是调用 请求数 然后调用 命中请求数 然后调用计算工具计算合适。
|
||||
@@ -1,31 +1,31 @@
|
||||
# CLAUDE.md
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
本文件用于指导 Claude Code(claude.ai/code)在此仓库中开展工作。
|
||||
本文件用于指导 Claude Code(claude.ai/code)在此仓库中开展工作。
|
||||
|
||||
## 技术栈
|
||||
## 技术栈
|
||||
|
||||
- Java:`21`(`<java.version>`)
|
||||
- 后端框架:Spring Boot `4.0.3`
|
||||
- 后端框架:Spring Ai `1.1.2`
|
||||
- Java:`21`(`<java.version>`)
|
||||
- 后端框架:Spring Boot `4.0.5`
|
||||
- 后端框架:Spring Ai `1.1.4`
|
||||
|
||||
## 开发规范
|
||||
## 开发规范
|
||||
|
||||
如果涉及改动对外提供的 RESTful API 需对 MCP 动态工具管理 API 文档进行及时更新。
|
||||
如果涉及改动对外提供的 RESTful API 需对 `docs/API-概览.md` 及对应模块文档进行及时更新。
|
||||
|
||||
开发 MCP 相关功能需要遵守 MCP 协议说明。
|
||||
开发 MCP 相关功能需要遵守 `docs/MCP-协议说明.md`。
|
||||
|
||||
## 日志和注释
|
||||
## 日志和注释
|
||||
|
||||
- 日志和注释使用中文
|
||||
- 关键位置须有日志,日志级别仅需要 `info` 和 `error` 两个级别
|
||||
- public 方法必须提供注释说明,关键位置也可以适量增加注释说明
|
||||
- 日志和注释使用中文
|
||||
- 关键位置须有日志,日志级别仅需要 `info` 和 `error` 两个级别
|
||||
- public 方法必须提供注释说明,关键位置也可以适量增加注释说明
|
||||
|
||||
## 测试规范(强约束)
|
||||
## 测试规范(强约束)
|
||||
|
||||
- 新增功能、修复问题、修改业务行为:必须新增/更新对应的测试用例(优先单元测试),覆盖核心规则与边界条件
|
||||
- 修复 Bug:必须提供回归测试(修复前失败、修复后通过)
|
||||
- 纯代码格式/注释调整:可以不新增测试,但必须确保现有测试全部通过
|
||||
- 必要时才写集成测试(如持久化/序列化/校验):控制数量,避免把所有测试都写成 `@SpringBootTest`
|
||||
- 新增功能、修复问题、修改业务行为:必须新增/更新对应的测试用例(优先单元测试),覆盖核心规则与边界条件
|
||||
- 修复 Bug:必须提供回归测试(修复前失败、修复后通过)
|
||||
- 纯代码格式/注释调整:可以不新增测试,但必须确保现有测试全部通过
|
||||
- 必要时才写集成测试(如持久化/序列化/校验):控制数量,避免把所有测试都写成 `@SpringBootTest`
|
||||
- 提交前必须在仓库根目录运行 `mvn test` 并确保全部通过;测试未通过禁止提交
|
||||
|
||||
@@ -0,0 +1,358 @@
|
||||
# `getHitRatio` 接口分析
|
||||
|
||||
## 1. 接口概述
|
||||
|
||||
- **路由**: `GET /statistics/getHitRatio`
|
||||
- **所在类**: `StatisticsController`
|
||||
- **方法**: `getHitRatio(QueryParam param)`
|
||||
- **注解**: `@StaticQueryCpCheckByDomain`(静态查询CP域名校验)
|
||||
- **返回**: `JSONObject`,包含 `errCode`、`data`(命中率列表)、`reqUnit`("%")
|
||||
- **功能**: 查询命中率数据,以时间维度返回各时间点的命中率百分比
|
||||
|
||||
---
|
||||
|
||||
## 2. 输入参数(`QueryParam`)
|
||||
|
||||
| 参数名 | 类型 | 说明 |
|
||||
|--------|------|------|
|
||||
| `cpId` | String | 企业ID |
|
||||
| `domainNames` | List\<String\> | 域名列表 |
|
||||
| `product` | String | 产品类型(如 "all"、具体产品ID) |
|
||||
| `granular` | String | 时间粒度(minute/hour/day) |
|
||||
| `providers` | List\<String\> | 加速厂商/平台列表 |
|
||||
| `affectAreas` | List\<String\> | 加速区域/省份 |
|
||||
| `isps` | List\<String\> | 运营商列表 |
|
||||
| `operator` | String | 运营商(具体值) |
|
||||
| `productId` | String | 订购ID |
|
||||
| `startTime` | String | 开始时间(yyyy-MM-dd HH:mm) |
|
||||
| `endTime` | String | 结束时间(yyyy-MM-dd HH:mm) |
|
||||
| `userType` | String | 用户类型(省代码,SA_PRV角色使用) |
|
||||
| `cpIds` | List\<String\> | 企业ID列表(内部使用) |
|
||||
| `historyCpIdsByDomains` | List\<String\> | 按域名的历史企业ID |
|
||||
|
||||
---
|
||||
|
||||
## 3. 校验流程
|
||||
|
||||
### 3.1 注解级校验
|
||||
|
||||
`@StaticQueryCpCheckByDomain` — 在调用方法前进行静态查询CP域名的权限校验(切面/拦截器实现),确保当前用户有权限查询所提供域名的数据。
|
||||
|
||||
### 3.2 `paramVerify(param, roleId)` — 核心参数校验
|
||||
|
||||
#### 3.2.1 用户登录校验
|
||||
|
||||
- 通过 `getUser()` 获取当前用户,为 null 则返回错误:"无用户登录!"
|
||||
|
||||
#### 3.2.2 加速厂商(平台)校验
|
||||
|
||||
- 调用 `validServicePlatform(param)`:
|
||||
- `provider` 为 `"*"` 或 `"all"` → 重置为 `"all"`,返回 true
|
||||
- 否则检查 `ServicePlatformEnum.getByCode(provider)` 是否存在,不存在返回错误
|
||||
|
||||
#### 3.2.3 带宽单位进制校验
|
||||
|
||||
- `unitScale < 1` → 返回错误:"所选带宽单位进制不规范!"
|
||||
|
||||
#### 3.2.4 日期时间校验
|
||||
|
||||
**默认行为**:如果 `startTime` 或 `endTime` 为空,则默认当天 00:00 ~ 23:59。
|
||||
|
||||
**日期参数明确时**:
|
||||
|
||||
| 条件 | 错误信息 |
|
||||
|------|---------|
|
||||
| 结束时间在未来1小时之后 | "只能查询到1小时前的数据" |
|
||||
| 开始时间距今超过5年 | "只能查询五年内的数据" |
|
||||
| 结束时间距今超过2分钟 且 跨度超过90天 | "跨度不能超过90天" |
|
||||
| 超过1年 且 粒度为"小时" | "小时粒度数据仅支持1年内数据查询" |
|
||||
|
||||
**粒度与时间跨度校验**:
|
||||
|
||||
| 时间跨度条件 | 支持的粒度 |
|
||||
|------------|-----------|
|
||||
| ≤2分钟 且 距今≤60分钟,或 跨度=1分钟 | minute / hour |
|
||||
| ≤2分钟 且 距今>60分钟 | hour / day |
|
||||
| >2分钟 且 ≤31天 | hour / day |
|
||||
| >31天 | day(仅支持按天) |
|
||||
|
||||
#### 3.2.5 角色相关校验
|
||||
|
||||
根据 `roleId`(`SecurityUserUtil.getRoleId()`)进行不同处理:
|
||||
|
||||
**角色 = ROLE_CROP(企业门户)**:
|
||||
|
||||
- 必须有且仅有一个匹配的企业
|
||||
- 只能查询自己所属企业的数据(越权检查)
|
||||
- `cpIds` 只含自己的 `enterpriseId`
|
||||
|
||||
**角色 = ROLE_MANAGER / ROLE_MANAGER_ZQ(经理类)**:
|
||||
|
||||
- `cpId` 不能为空且不能为 `"*"` → 错误:"请选定一个企业!"
|
||||
|
||||
**角色 = ROLE_OPT(运维类)**:
|
||||
|
||||
- `cpId` 为空或 `"*"` → `cpIds` 设为 `["all"]`(查所有)
|
||||
|
||||
#### 3.2.6 域名越权校验
|
||||
|
||||
- 获取用户有权查看的所有域名列表
|
||||
- 遍历请求的 `domainNames`,必须在有权域名列表中,或在删除域名记录表中
|
||||
- 若不在,且为 SA_PRV 角色,还会额外检查是否在 `STATISTICS_KEY_ENTERPRISE` 配置的企业列表中
|
||||
- 不在任何一个列表中 → 错误:"has no domain in cps!"
|
||||
|
||||
#### 3.2.7 加速区域(省份)处理
|
||||
|
||||
- 区域为空 → 默认为 `["all"]`(全国)
|
||||
- 区域非空 → 将省份名称转换为省份短码(`provinceRepository.findByName`)
|
||||
- 特殊值 `Constants.OTHER_PROVINCE`(其他省)→ 替换为 `Constants.OTHER_PROVINCE_SHORT_CODE`
|
||||
|
||||
#### 3.2.8 产品类型默认值
|
||||
|
||||
- `"*"` → 重置为 `"all"`
|
||||
|
||||
#### 3.2.9 历史企业ID合并
|
||||
|
||||
- 若 `historyCpIdsByDomains` 非空,且 `cpIds` 首个值不是 `"*"` 或 `"all"`,则合并历史企业ID
|
||||
|
||||
#### 3.2.10 SA_PRV 角色的特殊处理
|
||||
|
||||
- 若角色为 SA_PRV(非企业门户),且用户的省份简称非空 → 设置 `param.userType = 用户省份短码`
|
||||
- 否则 `userType` 设为 null
|
||||
|
||||
#### 3.2.11 `paramVerify` 输出
|
||||
|
||||
- 成功时返回 `code=Constants.OK`,附带 `param`(处理后的 `StatisticQueryParam`)和 `cpIds`
|
||||
|
||||
---
|
||||
|
||||
## 4. `getHitRatio` 方法体内的后续处理
|
||||
|
||||
### 4.1 `hitReqCheck(param)` — 时间粒度调整
|
||||
|
||||
根据开始时间和结束时间差,自动修正 `seconds` 参数(查询粒度):
|
||||
|
||||
```
|
||||
if (开始+29天 之前于结束) → seconds = 86400(天粒度)
|
||||
else if (开始+1天 之前于结束) → seconds = 3600(小时粒度)
|
||||
else → seconds = 300(5分钟粒度)
|
||||
```
|
||||
|
||||
### 4.2 `cpDomainProductAreaCheck(param)` — 域名/产品/区域默认值补全
|
||||
|
||||
| 字段 | 空值时的默认值 |
|
||||
|------|--------------|
|
||||
| `cpId` | `"all"` |
|
||||
| `domainNames` | `["all"]` |
|
||||
| `product` | `"all"` |
|
||||
| `affectAreas` | `["all"]` |
|
||||
| 多域名(size>1)| 设置 `isGenericDomain=true`,`genericDomain="domainCollect"` |
|
||||
|
||||
### 4.3 cpIds 非空二次检查
|
||||
|
||||
- 若 `cpIds` 为空或 null → 返回错误:"无法获得相关企业信息!"
|
||||
|
||||
### 4.4 状态码强制设值
|
||||
|
||||
```java
|
||||
param.setStatusCodes(new ArrayList<>());
|
||||
param.getStatusCodes().add("all");
|
||||
```
|
||||
|
||||
> 注意:这里忽略了传入的 `statusCodes`,直接强制设为 `["all"]`,意味着命中率接口不看状态码筛选。
|
||||
|
||||
### 4.5 运营商 ISP 默认值
|
||||
|
||||
```java
|
||||
if (CommonUtil.listIsNullOrSizeEqualZero(param.getIsps())) {
|
||||
param.setIsps(Arrays.asList("all"));
|
||||
}
|
||||
```
|
||||
|
||||
### 4.6 CROP 角色特殊处理:域名转换
|
||||
|
||||
```java
|
||||
Map<String, String> domainMap = getDomainAndCpDomainMap(param.getDomainNames(), param.getCpId());
|
||||
convertDomain(domainMap, param, param.getCpId());
|
||||
```
|
||||
|
||||
- **作用**:处理"冲突域名"场景,即 `SelfServiceDomainConfigPO` 中 `domain` 和 `cpDomain` 字段不一致的情况
|
||||
- `getDomainAndCpDomainMap`:查询 `selfServiceDomainConfigDao.findByTenantIdAndCpDomainIn`,找出请求域名中哪些是冲突域名(domain ≠ cpDomain),返回 Map\<domain, cpDomain\>
|
||||
- `convertDomain`:如果请求的所有域名都在冲突域名 Map 中,则用转换后的域名列表替换
|
||||
|
||||
---
|
||||
|
||||
## 5. 参数转换工厂 `StatisticParamFactory`
|
||||
|
||||
### 5.1 `getWebRequest2(param)` — 构建请求数统计参数
|
||||
|
||||
```java
|
||||
getByWebStatisticParam(param)
|
||||
.metric(StatisticEnum.PARAMMETRIC.REQ.getValue()) // "req"
|
||||
.isps(param.getIsps())
|
||||
.dimensions([DOMAIN, TIME, AREA])
|
||||
.needParamSum(15)
|
||||
```
|
||||
|
||||
**`getByWebStatisticParam(param)` 内部逻辑**:
|
||||
|
||||
| 字段 | 转换逻辑 |
|
||||
|------|--------|
|
||||
| `cpIds` | `cpId` 含逗号则 split,否则单值;历史域名则用 `historyCpIdsByDomains` |
|
||||
| `domainNames` | 多个域名设 `genericDomain=true` |
|
||||
| `productIds` | `"*"/空`→`all`;`"11"`→`[0,1,2]`(点播);`"12"`→`[5,6]`(直播);`"13"`→`[8,9]`(全站);`"14"`→`[7]`(超低时延);其他→原值 |
|
||||
| `areas` | 空→`["all"]`;否则传入 |
|
||||
| `providers` | 空或不匹配→`ServicePlatformEnum.getCRSPlatformCodesNew()`;否则传入 |
|
||||
| `startTime/endTime` | 格式化为 ISO_OFFSET_DATE_TIME |
|
||||
| `seconds` | 根据时间跨度自动判断(见下方 `checkSeconds`) |
|
||||
| `userType` | 仅当 `cpId="all"` 时设置 |
|
||||
| `granular` → `seconds` | `hour`→3600;`minute`→300;`day`→86400 |
|
||||
| `operator` | 非空则传入 |
|
||||
| `orderId` | `productId` 非空非"*"非"all" → 设置;否则 `"all"` |
|
||||
|
||||
**`checkSeconds(start, end)` 自动判断粒度**:
|
||||
|
||||
| 条件 | seconds | 含义 |
|
||||
|------|---------|------|
|
||||
| 跨度 > 29天 | 86400 | 天粒度 |
|
||||
| 跨度 > 1天 且 ≤29天 | 3600 | 小时粒度 |
|
||||
| 跨度 ≤ 1天 | 300 | 5分钟粒度 |
|
||||
|
||||
### 5.2 `getWebHitreq2(param)` — 构建命中请求数统计参数
|
||||
|
||||
```java
|
||||
getByWebStatisticParam(param)
|
||||
.isps(param.getIsps())
|
||||
.needParamSum(13)
|
||||
```
|
||||
|
||||
> 与 `getWebRequest2` 的区别:**不设置 `metric`**,且 `needParamSum=13`(请求数参数 15,命中请求 13)
|
||||
|
||||
---
|
||||
|
||||
## 6. 三方接口调用
|
||||
|
||||
### 6.1 `HttpStateService.getHitRatio(request, hitReq)`
|
||||
|
||||
调用链路(`data-service/HttpStateServiceImpl`):
|
||||
|
||||
```
|
||||
1. getHitRatio(request, hitReq)
|
||||
└→ getRequest(request) // 请求数(metric=req)
|
||||
└→ handleToDataProcess(url: MULTI_METRIC_URL, data: request)
|
||||
└→ getHitReq(hitReq) // 命中请求数(无metric)
|
||||
└→ handleToDataProcess(url: MULTI_METRIC_URL, data: hitReq)
|
||||
└→ req.getData().mergeHitReqIntoThis(hit.getData()) // 合并,计算命中率
|
||||
```
|
||||
|
||||
- 调用 `getDataIP() + MULTI_METRIC_URL`(大数据平台接口)
|
||||
- 将请求数结果 `DataProcess` 和命中请求数结果 `DataProcess` 通过 `mergeHitReqIntoThis` 合并
|
||||
- 合并内部逻辑:命中率 = 命中请求数 / 总请求数
|
||||
|
||||
### 6.2 `DATACHECK` 线程变量检查
|
||||
|
||||
```java
|
||||
if (httpStateService.DATACHECK.get() != null) {
|
||||
resultMap.put("errCode", Constants.ERROR);
|
||||
resultMap.put("error", "查询失败");
|
||||
httpStateService.DATACHECK.remove();
|
||||
return new JSONObject(resultMap);
|
||||
}
|
||||
```
|
||||
|
||||
> `DATACHECK` 是一个 `ThreadLocal<Boolean>`,三方接口内部可能将其设置为非 null 以标记查询失败。接口返回前必须清理。
|
||||
|
||||
---
|
||||
|
||||
## 7. 响应数据处理
|
||||
|
||||
### 7.1 数据转换逻辑
|
||||
|
||||
```java
|
||||
hitRatioList = vo.getData().getDomains().get(0).getTimes().stream()
|
||||
.sorted((t1, t2) -> t1.getTime().compareTo(t2.getTime())) // 时间升序
|
||||
.map(t -> {
|
||||
StatisticResult temp = new StatisticResult();
|
||||
// 根据粒度决定时间格式
|
||||
formatter = DAY粒度 ? "yyyy-MM-dd" : "yyyy-MM-dd HH:mm"
|
||||
temp.setName(formatter.format(解析(t.getTime())))
|
||||
// 原始值 * 100,转百分比,小数保留2位
|
||||
temp.setValue(UnitConverUtil.getDecimal(t.getProvinces().get(0).getValue().doubleValue() * 100))
|
||||
return temp;
|
||||
}).collect(Collectors.toList());
|
||||
```
|
||||
|
||||
### 7.2 最终响应结构
|
||||
|
||||
```json
|
||||
{
|
||||
"errCode": 0,
|
||||
"data": [
|
||||
{ "name": "2026-03-27 10:00", "value": 85.23 },
|
||||
{ "name": "2026-03-27 10:05", "value": 86.45 }
|
||||
],
|
||||
"reqUnit": "%"
|
||||
}
|
||||
```
|
||||
|
||||
| 字段 | 说明 |
|
||||
|------|------|
|
||||
| `errCode` | 0=成功,其他=失败 |
|
||||
| `data[].name` | 时间字符串,格式由粒度决定(day="yyyy-MM-dd",其他="yyyy-MM-dd HH:mm") |
|
||||
| `data[].value` | 命中率百分比,2位小数(如 85.23 表示 85.23%) |
|
||||
| `reqUnit` | 固定为 "%" |
|
||||
|
||||
---
|
||||
|
||||
## 8. 完整调用时序
|
||||
|
||||
```
|
||||
前端请求
|
||||
↓
|
||||
@StaticQueryCpCheckByDomain 注解校验
|
||||
↓
|
||||
paramVerify(param, roleId)
|
||||
├─ 用户登录校验
|
||||
├─ 加速厂商有效性校验
|
||||
├─ 带宽单位进制校验
|
||||
├─ 日期时间范围校验 + 粒度校验
|
||||
├─ 角色相关cpId/域名越权校验
|
||||
├─ 省份区域转换
|
||||
└─ SA_PRV角色 userType 注入
|
||||
↓
|
||||
hitReqCheck(param) // 自动修正 seconds 粒度
|
||||
↓
|
||||
cpDomainProductAreaCheck(param) // cpId/domain/product/area 默认值
|
||||
↓
|
||||
cpIds 非空检查
|
||||
↓
|
||||
[仅CROP角色] getDomainAndCpDomainMap + convertDomain // 冲突域名转换
|
||||
↓
|
||||
isps 默认值 ["all"]
|
||||
↓
|
||||
statisticParamFactory.getWebRequest2(param) // 构建请求数参数
|
||||
↓
|
||||
statisticParamFactory.getWebHitreq2(param) // 构建命中请求数参数
|
||||
↓
|
||||
httpStateService.getHitRatio(request, hitReq)
|
||||
├─ getRequest(request) → 大数据平台(请求数)
|
||||
├─ getHitReq(hitReq) → 大数据平台(命中请求数)
|
||||
└─ mergeHitReqIntoThis() → 计算命中率 = 命中/请求
|
||||
↓
|
||||
数据排序 + 时间格式化 + *100 转百分比
|
||||
↓
|
||||
返回 JSONObject { errCode, data[], reqUnit:"%" }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. 关键细节总结
|
||||
|
||||
1. **时间默认**:前后端均未传时间时,默认当天 00:00~23:59(分钟粒度数据)
|
||||
2. **粒度自动推断**:不传 `granular` 时,`checkSeconds` 根据跨度自动判断(300s/3600s/86400s)
|
||||
3. **statusCodes 被强制覆盖**:接口内部强制将 `statusCodes` 设为 `["all"]`,忽略前端传入值
|
||||
4. **isps 默认 ["all"]**:只有 `isps` 为空时才默认全选,不为空时使用传入值
|
||||
5. **CROP 角色域名转换**:仅企业门户角色会走冲突域名转换逻辑,普通角色不转换
|
||||
6. **SA_PRV 的 userType**:仅当 `cpId="all"` 时才设置 `userType`(省份短码),用于限制只能查询本省数据
|
||||
7. **needParamSum 参数校验**:内部通过参数个数校验来确保必填参数完整
|
||||
8. **ThreadLocal 清理**:`DATACHECK` 用完必须 remove,防止线程污染
|
||||
Reference in New Issue
Block a user