Initial commit

This commit is contained in:
Docker7530
2026-03-01 01:43:46 +08:00
commit c6125c117b
3840 changed files with 415340 additions and 0 deletions
@@ -0,0 +1,100 @@
# 网关转发记录曲线图接口说明
## 接口概述
新增了一个高效的曲线图数据查询接口,用于展示网关转发记录的统计数据。该接口支持多种时间粒度的聚合查询,并提供丰富的过滤条件。
## 接口信息
- **接口路径**: `POST /gateway/record/chart`
- **请求方式**: POST
- **Content-Type**: application/json
## 请求参数
### 必填参数
| 参数名 | 类型 | 说明 | 示例 |
|--------|------|------|------|
| startTime | LocalDateTime | 查询开始时间 | "2024-01-01 00:00:00" |
| endTime | LocalDateTime | 查询结束时间 | "2024-01-01 23:59:59" |
| timeGranularity | String | 时间粒度 | "5MIN" |
### 时间粒度选项
| 值 | 说明 | 聚合方式 |
|----|------|----------|
| 5MIN | 5分钟 | 按5分钟间隔聚合(00:00-00:04, 00:05-00:09…) |
| 1HOUR | 1小时 | 按小时聚合 |
| 1DAY | 1天 | 按天聚合 |
| 1WEEK | 1周 | 按周聚合(周一为起始) |
| 1MONTH | 1月 | 按月聚合 |
### 可选过滤参数
| 参数名 | 类型 | 说明 | 是否支持模糊查询 |
|--------|------|------|------------------|
| requestUrl | String | 接口路径 | ✅ |
| method | String | 请求方法 | ❌ |
| targetEndpoint | String | 目的地址 | ✅ |
| httpCode | Integer | HTTP状态码 | ❌ |
| gatewayEndpoint | String | 网关地址 | ✅ |
| minCostTime | Integer | 最小耗时(ms) | ❌ |
| maxCostTime | Integer | 最大耗时(ms) | ❌ |
| routeId | Long | 路由ID | ❌ |
| routeName | String | 路由名称 | ✅ |
## 请求示例
```json
{
"startTime": "2024-01-01 00:00:00", "endTime": "2024-01-01 23:59:59", "timeGranularity": "1HOUR", "requestUrl": "/api/user", "method": "GET", "minCostTime": 100, "maxCostTime": 5000}
```
## 响应数据结构
```json
{
"code": 200, "msg": "操作成功",
"data": { "dataPoints": [ { "timePoint": "2024-01-01 00:00:00", "count2xx": 150, "count4xx": 10, "count5xx": 2, "countOther": 1, "avgCostTime": 245.67 }, { "timePoint": "2024-01-01 01:00:00", "count2xx": 200, "count4xx": 15, "count5xx": 3, "countOther": 0, "avgCostTime": 198.34 } ] }}
```
## 响应字段说明
### dataPoints 数组中每个元素的字段
| 字段名 | 类型 | 说明 |
|--------|------|------|
| timePoint | LocalDateTime | 时间点(X轴) |
| count2xx | Long | 2xx状态码请求次数(左Y轴) |
| count4xx | Long | 4xx状态码请求次数(左Y轴) |
| count5xx | Long | 5xx状态码请求次数(左Y轴) |
| countOther | Long | 其他状态码请求次数(左Y轴) |
| avgCostTime | BigDecimal | 平均耗时ms,保留2位小数(右Y轴) |
## 性能优化说明
1. **索引优化**: 建议在 `request_time` 字段上创建索引以提高查询效率
2. **模糊查询**: 仅在 `requestUrl`、`targetEndpoint`、`gatewayEndpoint`、`routeName` 字段支持模糊查询
3. **时间范围**: 建议查询时间范围不要过大,以保证响应速度
4. **聚合查询**: 使用数据库层面的聚合查询,减少内存占用
## 前端图表建议
- **左Y轴**: 显示请求次数(count2xx, count4xx, count5xx, countOther
- **右Y轴**: 显示平均耗时(avgCostTime
- **X轴**: 显示时间(timePoint
- **图表类型**: 建议使用折线图或柱状图
- **颜色建议**:
- 2xx: 绿色(成功)
- 4xx: 橙色(客户端错误)
- 5xx: 红色(服务器错误)
- 其他: 灰色
- 平均耗时: 蓝色
## 注意事项
1. 时间粒度为左闭右开区间
2. 大数据量查询时建议适当限制时间范围
3. 模糊查询会影响性能,请谨慎使用
4. 平均耗时为0表示该时间段内没有有效的耗时数据
@@ -0,0 +1,96 @@
这是我的请求记录表:
对应的 mapper 等文件在:
`D:\MyCode\Work\yd-hy\ibs-plus\plus-service\src\main\java\com\cmcc\plus\gateway`
`D:\MyCode\Work\yd-hy\ibs-plus\plus-service\src\main\resources\mapper\gateway`
表结构:
```java
@TableName(value = "gw_forward_record")
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class GwForwardRecord implements Serializable {
/**
* 记录ID
*/
@TableId(type = IdType.AUTO)
private Long recordId;
/**
* 请求UUID
*/
private String uuid;
/**
* 请求时间
*/
private LocalDateTime requestTime;
/**
* 接口路径
*/
private String requestUrl;
/**
* 请求方法
*/
private String method;
/**
* 目的地址
*/
private String targetEndpoint;
/**
* 请求响应码
*/
private Integer httpCode;
/**
* 链路追踪ID
*/
private String traceId;
/**
* 网关地址
*/
private String gatewayEndpoint;
/**
* 请求匹配的路由ID
*/
private Long routeId;
/**
* 请求耗时(ms)
*/
private Integer costTime;
@Serial
@TableField(exist = false)
private static final long serialVersionUID = 1L;
}
```
我现在有一个需求,需要给前端提供一个曲线图数据查询接口:
接口创建在 com.cmcc.plus.web.controller.gateway.GwForwardRecordController 下。
注意一定要保证代码执行效率。因为这是网关请求记录表,是一个量级非常大的表。
图中包含 5 条线:2xx、4xx、5xx、其他状态码(请求次数,对应表httpCode字段)、平均耗时(ms,对应表 costTime 字段)
我的图标左侧 Y 轴为 请求次数,右侧 Y 轴 平均耗时。下方 X 轴为时间坐标轴。
我想给我前端一个通用方法,查询某个时间段的这五个指标(也就是针对 requestTime 的过滤)。costTime(比如前端传 200ms-300ms 的查询)同时支持通过 requestUrl、method、targetEndpoint、httpCode、gatewayEndpoint 的过滤。
请仔细设计返回前端的结构体,做到最标准和效率。
仔细设计 requestUrl、targetEndpoint、gatewayEndpoint 是否可以使用模糊查询,前提是保证效率,因为这个表是一个非常大的表。
针对时间粒度。帮我提供时间区间内 5 分钟(例如 00:00:00 的数据就是 00:00:00-00:04:59),1小时,1天,一周,一月。时间粒度均为左闭右开。这个粒度根据前端选择进行聚合。不用一次全聚合出来。
@@ -0,0 +1,81 @@
# 客户运营数据统计-新增累计商用客户数试用客户数等6项统计及页面展示
NRWLXT-29935
## 需求评审
## 需求备注
## 需求开发
# 集客大厅前置校验新增信安校验多sheet提示
NRWLXT-29939
## 需求评审
## 需求备注
## 需求开发
# 用例评审
# 冒烟自测
## 订购迁移工单
1、IBS测试环境:https://test.p.cdn.10086.cn
2、IBS存在esop企业A、已业务开通:网页加速的订购ID:20230000810、企业CP_ID:83000001
esop企业B、已业务开通超低时延直播加速订购ID:20240000001,订购下域名test1.com、test2.com、test3.com,域名状态:已生效、企业CP_ID:83000002
---
网页加速
ECName1753237067、ECID1753237067、DevReq1753237067A.komect.com、PRODUCTID1753237241、83569731
超低时延直播
ECName1753237499、ECID1753237499、DevReq1753237499A.komect.com、PRODUCTID1753237683、83559505
## 订购迁移工单下发BPM同步平面信息
1、IBS测试环境:https://test.p.cdn.10086.cn
2、IBS存在esop企业A、已业务开通:网页加速的订购ID:20230000810、业务开通的产品类型-移动网内CDN 分发平面:华为平面
esop企业B、已业务开通超低时延直播加速订购ID:20240000001,订购下域名test1.com、test2.com、test3.com,域名状态:已生效、业务开通的产品类型-三网CDN
---
ECName1753239995
页面
PRODUCTID1753240145
域名状态已生效
---
1、IBS测试环境:https://test.p.cdn.10086.cn
2、IBS存在esop企业A、已业务开通:网页加速的订购ID:20230000810、业务开通的产品类型-移动网内CDN 分发平面:华为平面
esop企业B、已业务开通超低时延直播加速订购ID:20240000001,订购下域名test1.com、test2.com、test3.com,域名状态:已生效、业务开通的产品类型-三网CDN
## 企业客户域名列表展示
1、IBS测试环境:https://test.p.cdn.10086.cn
2、IBS已存在esop企业Test、企业下域名www.test.com
3、esop企业A账号下已新增冲突域名:www.test.com.01.cdnhwcqir15.com,产品类型:网页加速
## 通用客户域名列表查询接口-域名列表查询
1、IBS已存在归属A企业下域名testa.com,状态为已生效
2、IBS已存在归属B企业下真实域名:testa.com,虚拟域名:testa.com.01.cdnhwcqir15.com,下发平面为华为平面,状态为已生效
@@ -0,0 +1,224 @@
```
FOR 循环
"batchSize": 1000
2025-07-29 14:50:35 [XNIO-1 task-2] INFO c.c.p.g.s.GwForwardRecordService:364 - 测试数据生成完成,总计: 100 条
2025-07-29 14:50:35 [XNIO-1 task-2] INFO c.c.p.c.w.i.PlusWebInvokeTimeInterceptor:81 - [PLUS]结束请求 => URL[POST /gateway/record/generate-test-data],耗时:[12,736]毫秒
2025-07-29 14:53:15 [XNIO-1 task-2] INFO c.c.p.g.s.GwForwardRecordService:364 - 测试数据生成完成,总计: 1000 条
2025-07-29 14:53:15 [XNIO-1 task-2] INFO c.c.p.c.w.i.PlusWebInvokeTimeInterceptor:81 - [PLUS]结束请求 => URL[POST /gateway/record/generate-test-data],耗时:[65,119]毫秒
2025-07-29 15:05:18 [XNIO-1 task-2] INFO c.c.p.g.s.GwForwardRecordService:364 - 测试数据生成完成,总计: 2000 条
2025-07-29 15:05:18 [XNIO-1 task-2] INFO c.c.p.c.w.i.PlusWebInvokeTimeInterceptor:81 - [PLUS]结束请求 => URL[POST /gateway/record/generate-test-data],耗时:[131919]毫秒
批量插入
"batchSize": 1000
2025-07-29 15:13:02 [XNIO-1 task-2] INFO c.c.p.g.s.GwForwardRecordService:357 - 测试数据生成完成,总计: 1000 条
2025-07-29 15:13:02 [XNIO-1 task-2] INFO c.c.p.c.w.i.PlusWebInvokeTimeInterceptor:81 - [PLUS]结束请求 => URL[POST /gateway/record/generate-test-data],耗时:[1153]毫秒
2025-07-29 15:13:40 [XNIO-1 task-2] INFO c.c.p.g.s.GwForwardRecordService:357 - 测试数据生成完成,总计: 10000 条
2025-07-29 15:13:40 [XNIO-1 task-2] INFO c.c.p.c.w.i.PlusWebInvokeTimeInterceptor:81 - [PLUS]结束请求 => URL[POST /gateway/record/generate-test-data],耗时:[6060]毫秒
"batchSize": 2000
2025-07-29 15:14:32 [XNIO-1 task-2] INFO c.c.p.g.s.GwForwardRecordService:357 - 测试数据生成完成,总计: 10000 条
2025-07-29 15:14:32 [XNIO-1 task-2] INFO c.c.p.c.w.i.PlusWebInvokeTimeInterceptor:81 - [PLUS]结束请求 => URL[POST /gateway/record/generate-test-data],耗时:[8088]毫秒
"batchSize": 3000
2025-07-29 15:15:02 [XNIO-1 task-2] INFO c.c.p.g.s.GwForwardRecordService:357 - 测试数据生成完成,总计: 10000 条
2025-07-29 15:15:02 [XNIO-1 task-2] INFO c.c.p.c.w.i.PlusWebInvokeTimeInterceptor:81 - [PLUS]结束请求 => URL[POST /gateway/record/generate-test-data],耗时:[9107]毫秒
"batchSize": 1000
2025-07-29 15:16:40 [XNIO-1 task-2] INFO c.c.p.g.s.GwForwardRecordService:357 - 测试数据生成完成,总计: 50000 条
2025-07-29 15:16:40 [XNIO-1 task-2] INFO c.c.p.c.w.i.PlusWebInvokeTimeInterceptor:81 - [PLUS]结束请求 => URL[POST /gateway/record/generate-test-data],耗时:[39666]毫秒【数据犯了5倍,但时间6倍】
"batchSize": 5000
2025-07-29 15:18:37 [XNIO-1 task-2] INFO c.c.p.g.s.GwForwardRecordService:357 - 测试数据生成完成,总计: 50000 条
2025-07-29 15:18:37 [XNIO-1 task-2] INFO c.c.p.c.w.i.PlusWebInvokeTimeInterceptor:81 - [PLUS]结束请求 => URL[POST /gateway/record/generate-test-data],耗时:[45076]毫秒【多了十五秒】
```
### 最左前缀原则
```
/**
* 生成测试数据
*/
@PostMapping("/generate-test-data")
public R<String> generateTestData(@Valid @RequestBody GwForwardRecordTestDataReq request) {
long startTime = System.currentTimeMillis();
gwForwardRecordService.generateTestData(request);
long endTime = System.currentTimeMillis();
String message = String.format("成功生成 %d 条测试数据,耗时 %d 毫秒",
request.getCount(), endTime - startTime);
return R.ok(message);
}
/**
* 生成测试数据
*/
public void generateTestData(GwForwardRecordTestDataReq request) {
log.info("开始生成测试数据,数量: {}", request.getCount());
// 设置默认时间范围
LocalDateTime endTime = request.getEndTime() != null ? request.getEndTime() : LocalDateTime.now();
LocalDateTime startTime = request.getStartTime() != null ? request.getStartTime() : endTime.minusDays(7);
// 获取路由配置
Map<String, Long> routeNameToIdMap = gwRouteConfigService.selectRouteNameToIdMap();
if (routeNameToIdMap.isEmpty()) {
throw new RuntimeException("没有找到路由配置,请先配置路由");
}
List<String> routeNames = new ArrayList<>(routeNameToIdMap.keySet());
int batchSize = request.getBatchSize();
int totalCount = request.getCount();
int processedCount = 0;
try {
while (processedCount < totalCount) {
int currentBatchSize = Math.min(batchSize, totalCount - processedCount);
List<GwForwardRecord> records = generateBatchTestData(currentBatchSize, startTime, endTime,
routeNames, routeNameToIdMap);
// 批量插入主表数据
recordMapper.insertBatch(records, currentBatchSize);
processedCount += currentBatchSize;
if (processedCount % (batchSize * 10) == 0) {
log.info("已生成测试数据: {}/{}", processedCount, totalCount);
}
}
log.info("测试数据生成完成,总计: {} 条", totalCount);
} catch (Exception e) {
log.error("生成测试数据失败", e);
throw new RuntimeException("生成测试数据失败: " + e.getMessage());
}
}
/**
* 生成批量测试数据
*/
private List<GwForwardRecord> generateBatchTestData(int count, LocalDateTime startTime, LocalDateTime endTime,
List<String> routeNames, Map<String, Long> routeNameToIdMap) {
List<GwForwardRecord> records = new ArrayList<>(count);
ThreadLocalRandom random = ThreadLocalRandom.current();
// 预定义的API路径模板
String[] apiPaths = {
"/api/v1/user/login", "/api/v1/user/logout", "/api/v1/user/profile",
"/api/v2/sync/BBOSS/PreCheckServ", "/api/v2/sync/BBOSS/QueryServ", "/api/v2/sync/BBOSS/UpdateServ",
"/api/v1/order/create", "/api/v1/order/query", "/api/v1/order/update", "/api/v1/order/cancel",
"/api/v1/payment/create", "/api/v1/payment/query", "/api/v1/payment/callback",
"/api/v1/product/list", "/api/v1/product/detail", "/api/v1/product/search",
"/api/v1/system/health", "/api/v1/system/config", "/api/v1/system/monitor"
};
// HTTP方法
String[] methods = {"GET", "POST", "PUT", "DELETE"};
// 目标服务地址
String[] targetEndpoints = {
"127.0.0.1:8080", "127.0.0.1:8081", "127.0.0.1:8082",
"192.168.1.100:8080", "192.168.1.101:8080", "192.168.1.102:8080"
};
// 网关地址
String[] gatewayEndpoints = {
"192.168.208.16:8087", "192.168.208.17:8087", "192.168.208.18:8087"
};
// HTTP状态码权重分布(模拟真实场景)
int[] httpCodes = {200, 200, 200, 200, 200, 200, 200, 200, 200, 201, 400, 401, 403, 404, 500, 502, 503};
long startTimestamp = startTime.atZone(java.time.ZoneId.systemDefault()).toInstant().toEpochMilli();
long endTimestamp = endTime.atZone(java.time.ZoneId.systemDefault()).toInstant().toEpochMilli();
for (int i = 0; i < count; i++) {
// 随机生成时间
long randomTimestamp = random.nextLong(startTimestamp, endTimestamp);
LocalDateTime requestTime = LocalDateTime.ofInstant(
java.time.Instant.ofEpochMilli(randomTimestamp), java.time.ZoneId.systemDefault());
// 随机选择路由
String routeName = routeNames.get(random.nextInt(routeNames.size()));
// 生成UUID和TraceId
String uuid = java.util.UUID.randomUUID().toString();
String traceId = uuid.replace("-", "").substring(0, 16);
// 随机选择API路径
String requestUrl = apiPaths[random.nextInt(apiPaths.length)];
// 随机选择HTTP方法
String method = methods[random.nextInt(methods.length)];
// 随机选择目标地址
String targetEndpoint = targetEndpoints[random.nextInt(targetEndpoints.length)];
// 随机选择网关地址
String gatewayEndpoint = gatewayEndpoints[random.nextInt(gatewayEndpoints.length)];
// 随机选择HTTP状态码
Integer httpCode = httpCodes[random.nextInt(httpCodes.length)];
// 生成响应时间(模拟真实分布)
Integer costTime = generateRealisticCostTime(random, httpCode);
GwForwardRecord record = GwForwardRecord.builder()
.uuid(uuid)
.requestTime(requestTime)
.requestUrl(requestUrl)
.method(method)
.targetEndpoint(targetEndpoint)
.httpCode(httpCode)
.traceId(traceId)
.gatewayEndpoint(gatewayEndpoint)
.routeId(routeNameToIdMap.get(routeName))
.costTime(costTime)
.build();
records.add(record);
}
return records;
}
/**
* 生成真实的响应时间分布
*/
private Integer generateRealisticCostTime(ThreadLocalRandom random, Integer httpCode) {
// 根据HTTP状态码生成不同的响应时间分布
if (httpCode >= 200 && httpCode < 300) {
// 成功请求:大部分在100-2000ms之间,少数较慢
if (random.nextDouble() < 0.8) {
return random.nextInt(100, 2000); // 80%的请求在100-2000ms
} else if (random.nextDouble() < 0.95) {
return random.nextInt(2000, 5000); // 15%的请求在2-5秒
} else {
return random.nextInt(5000, 15000); // 5%的请求在5-15秒
}
} else if (httpCode >= 400 && httpCode < 500) {
// 客户端错误:通常较快
return random.nextInt(50, 1000);
} else if (httpCode >= 500) {
// 服务器错误:可能很慢或超时
if (random.nextDouble() < 0.7) {
return random.nextInt(1000, 10000); // 70%在1-10秒
} else {
return random.nextInt(10000, 30000); // 30%在10-30秒
}
} else {
// 其他状态码
return random.nextInt(100, 3000);
}
}
```