Files
UserhomeService/src/main/java/com/yunhai/userhome/loadtestmetrics/OutboundOapiMetricsAspect.java
T
chenxing 5f5ac3db71
build / build-push (push) Successful in 1m34s
feat(sdk+msgcenter): 对齐 SDK 重构包名(openapi.client→base)+ 消息中心代理后端(09-16/09-19 遗漏提交)
- import 全面切换 com.yunhai.platform.openapi.base.*(对齐 Nexus -57 SDK)
- MsgCenter 代理三件(Properties/Controller/ProxyService)+ 测试 11/11(09-19 消息中心二期本地完成态)
- Dockerfile 已在 563edb5 去掉写死 profile;本提交补齐源码侧遗漏
2026-09-19 13:08:23 +08:00

135 lines
5.2 KiB
Java

package com.yunhai.userhome.loadtestmetrics;
/* =============================================================================
* [LOADTEST-METRICS] 临时埋点 —— 《压测与oapi埋点统计方案.md》阶段二(方案 §3.2)
* 压测结束后整体移除:删除 loadtestmetrics 包 + 回退 pom.xml/application.yml
* 中 [LOADTEST-METRICS] 标记段 + DROP TABLE api_metrics。
* 移除步骤详见仓库根《埋点移除清单.md》。
* ============================================================================= */
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
/**
* [LOADTEST-METRICS] 出站 UserBase oapi 调用计数(direction=out,方案 §3.2)。<br>
* 切在 yh-openapi-client 的 {@code YschOpenCall}(SDK 重构后所有出站开放接口调用的唯一
* 公共入口,Spring Bean)上:YschBaseOpenApi 静态门面、OpenApiUserClient / OpenApiAppClient
* 注入式调用全部经它发出,一处切面覆盖全部出站,不改动任何业务 Service 代码。<br>
* 入站请求数(direction=in)与出站调用数(direction=out)相除即「放大系数」,
* 是缓存层次决策的直接依据(方案 §5)。
*/
@Slf4j
@Aspect
@Component
public class OutboundOapiMetricsAspect {
private static final int MAX_URI_LEN = 200;
private final ApiMetricsCollector collector;
public OutboundOapiMetricsAspect(ApiMetricsCollector collector) {
this.collector = collector;
}
/**
* 拦截 YschOpenCall 全部公开调用方法(get/getList/post/postMultipart/download)。<br>
* 按名重载第 2 参均为 path(如 /oapi/app/list),据此归一化记 uri。
*/
@Around("execution(* com.yunhai.platform.openapi.core.YschOpenCall.*(..))")
public Object around(ProceedingJoinPoint point) throws Throwable {
String uri = resolveUri(point);
String method = resolveHttpMethod(point.getSignature().getName());
boolean loadTest = inheritLoadTestFlag();
long start = System.currentTimeMillis();
boolean error = false;
try {
return point.proceed();
} catch (Throwable e) {
error = true;
throw e;
} finally {
try {
collector.record(uri, method, ApiMetricsEntity.DIRECTION_OUT,
System.currentTimeMillis() - start, error, loadTest);
} catch (Exception recordEx) {
// 埋点自身绝不影响出站调用
log.warn("[LOADTEST-METRICS] 出站计数失败: {}", recordEx.getMessage());
}
}
}
/** 方法名 → HTTP 方法(getJson/get→GET,postJson/post/postMultipart→POST) */
private String resolveHttpMethod(String signatureName) {
return signatureName.startsWith("post") ? "POST" : "GET";
}
/** 取第 2 参 path 归一化;防御签名变化时记方法名兜底,不抛错 */
private String resolveUri(ProceedingJoinPoint point) {
try {
Object[] args = point.getArgs();
if (args.length >= 2 && args[1] instanceof String) {
return normalize((String) args[1]);
}
} catch (Exception ignore) {
// 兜底
}
return "oapi:" + point.getSignature().getName();
}
/** 出站 path 归一化:保证前导 /、纯数字段归一为 {id}、截断 200 */
private String normalize(String path) {
String p = path == null ? "" : path.trim();
if (p.isEmpty()) {
return "/";
}
if (!p.startsWith("/")) {
p = "/" + p;
}
int q = p.indexOf('?');
if (q >= 0) {
p = p.substring(0, q);
}
String[] segs = p.split("/");
StringBuilder sb = new StringBuilder(p.length());
for (String seg : segs) {
if (seg.length() > 0 && isDigits(seg)) {
seg = "{id}";
}
sb.append('/').append(seg);
}
p = sb.toString();
return p.length() > MAX_URI_LEN ? p.substring(0, MAX_URI_LEN) : p;
}
private boolean isDigits(String s) {
for (int i = 0; i < s.length(); i++) {
if (!Character.isDigit(s.charAt(i))) {
return false;
}
}
return true;
}
/** 出站调用的压测标记继承自触发它的入站请求(X-Load-Test 头);定时任务等无请求上下文则为 false */
private boolean inheritLoadTestFlag() {
if (RequestContextHolder.getRequestAttributes() instanceof ServletRequestAttributes) {
HttpServletRequest req = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
String v = req.getHeader(ApiMetricsFilter.HEADER_LOAD_TEST);
if (v != null && !v.trim().isEmpty()) {
v = v.trim();
return !"0".equals(v) && !"false".equalsIgnoreCase(v);
}
}
return false;
}
}