Files
notes/work/移动杭研/AI 项目/getHitRatio接口分析.md
Docker7530 6b50219f55 1776654103
2026-04-20 11:01:47 +08:00

13 KiB
Raw Permalink Blame History

getHitRatio 接口分析

1. 接口概述

  • 路由: GET /statistics/getHitRatio
  • 所在类: StatisticsController
  • 方法: getHitRatio(QueryParam param)
  • 注解: @StaticQueryCpCheckByDomain(静态查询CP域名校验)
  • 返回: JSONObject,包含 errCodedata(命中率列表)、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 日期时间校验

默认行为:如果 startTimeendTime 为空,则默认当天 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 角色相关校验

根据 roleIdSecurityUserUtil.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 = 3005分钟粒度)

4.2 cpDomainProductAreaCheck(param) — 域名/产品/区域默认值补全

字段 空值时的默认值
cpId "all"
domainNames ["all"]
product "all"
affectAreas ["all"]
多域名(size>1 设置 isGenericDomain=truegenericDomain="domainCollect"

4.3 cpIds 非空二次检查

  • cpIds 为空或 null → 返回错误:"无法获得相关企业信息!"

4.4 状态码强制设值

param.setStatusCodes(new ArrayList<>());
param.getStatusCodes().add("all");

注意:这里忽略了传入的 statusCodes,直接强制设为 ["all"],意味着命中率接口不看状态码筛选。

4.5 运营商 ISP 默认值

if (CommonUtil.listIsNullOrSizeEqualZero(param.getIsps())) {
    param.setIsps(Arrays.asList("all"));
}

4.6 CROP 角色特殊处理:域名转换

Map<String, String> domainMap = getDomainAndCpDomainMap(param.getDomainNames(), param.getCpId());
convertDomain(domainMap, param, param.getCpId());
  • 作用:处理"冲突域名"场景,即 SelfServiceDomainConfigPOdomaincpDomain 字段不一致的情况
  • getDomainAndCpDomainMap:查询 selfServiceDomainConfigDao.findByTenantIdAndCpDomainIn,找出请求域名中哪些是冲突域名(domain ≠ cpDomain),返回 Map<domain, cpDomain>
  • convertDomain:如果请求的所有域名都在冲突域名 Map 中,则用转换后的域名列表替换

5. 参数转换工厂 StatisticParamFactory

5.1 getWebRequest2(param) — 构建请求数统计参数

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" 时设置
granularseconds hour→3600minute→300day→86400
operator 非空则传入
orderId productId 非空非"*"非"all" → 设置;否则 "all"

checkSeconds(start, end) 自动判断粒度

条件 seconds 含义
跨度 > 29天 86400 天粒度
跨度 > 1天 且 ≤29天 3600 小时粒度
跨度 ≤ 1天 300 5分钟粒度

5.2 getWebHitreq2(param) — 构建命中请求数统计参数

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 线程变量检查

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 数据转换逻辑

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 最终响应结构

{
  "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,防止线程污染