#模式一:直接扫码(直连上报)⭐ 推荐
#适用场景
- App 内部已经封装了 HTTP 请求拦截/日志记录能力
- 不想安装 CA 证书
- 想最小化改动
#工作原理
┌────────┐ ┌────────────┐ ┌────────┐
│ App │ ──拦截 HTTP 请求─────> │ App 拦截器 │ ──POST 上报──> │ 服务器 │
│ │ <──正常返回结果────── │ (业务代码) │ <──200 OK─── │ 443 │
└────────┘ └────────────┘ └────────┘App 在自己封装的 HTTP 请求层(OkHttp/AFNetworking/axios 等)拦截到请求和响应后,主动调用上报接口推送到沙特协作服务器。
#App 端接入步骤
#步骤 1:扫码获取接入信息
桌面端启动后,左侧入口码区域显示二维码。手机 App 扫描后自动获取:
{
"shater_url": "https://shater.online/shater/raw-data?sessionId=7a58592c72f04fe",
"shater_type": "web",
"sessionId": "7a58592c72f04fe"
}#步骤 2:在 App 公共请求方法中上报
Android(OkHttp 示例):
// 全局 OkHttp 拦截器
class ShaterCollabInterceptor(private val sessionId: String, private val reportUrl: String) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
val startTime = System.currentTimeMillis()
// 执行原请求
val response = chain.proceed(request)
val endTime = System.currentTimeMillis()
val timeConsuming = endTime - startTime
// 异步上报(不阻塞业务)
val body = response.peekBody(1024 * 1024) // 最多 1MB
Thread {
try {
val reportBody = FormBody.Builder()
.add("api", request.url.encodedPath)
.add("method", request.method)
.add("requestHeader", request.headers.toString())
.add("request", bodyToString(request.body))
.add("responseHeader", response.headers.toString())
.add("response", body.string())
.add("timeConsuming", timeConsuming.toString())
.build()
val report = Request.Builder()
.url("$reportUrl?sessionId=$sessionId")
.post(reportBody)
.build()
OkHttpClient().newCall(report).execute().close()
} catch (e: Exception) {
// 上报失败不影响业务
}
}.start()
return response
}
}iOS(AFNetworking/URLSession 示例):
// URLSession 拦截示例
- (void)URLSession:(NSURLSession *)session
dataTask:(NSURLSessionDataTask *)dataTask
didReceiveResponse:(NSURLResponse *)response
completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
NSMutableURLRequest *request = dataTask.originalRequest;
NSTimeInterval startTime = [self.startTimes[@(dataTask.taskIdentifier)] doubleValue];
NSTimeInterval timeConsuming = ([[NSDate date] timeIntervalSince1970] * 1000) - startTime;
// 异步上报
NSString *reportUrl = [NSString stringWithFormat:@"%@/shater/raw-data?sessionId=%@", self.serverUrl, self.sessionId];
NSMutableURLRequest *reportReq = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:reportUrl]];
reportReq.HTTPMethod = @"POST";
[reportReq setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
NSString *body = [NSString stringWithFormat:@"api=%@&method=%@&requestHeader=%@&request=%@&responseHeader=%@&response=%@&timeConsuming=%f",
[self urlEncode:request.URL.path],
request.HTTPMethod,
[self urlEncode:request.allHTTPHeaderFields.description],
@"", // 请求体
[self urlEncode:httpResponse.allHeaderFields.description],
@"", // 响应体
timeConsuming];
reportReq.HTTPBody = [body dataUsingEncoding:NSUTF8StringEncoding];
NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithRequest:reportReq];
[task resume];
completionHandler(NSURLSessionResponseAllow);
}Web/JavaScript(fetch 示例):
// 全局 fetch 拦截
const originalFetch = window.fetch;
window.fetch = async function(input, init) {
const startTime = Date.now();
const response = await originalFetch(input, init);
const timeConsuming = Date.now() - startTime;
// 异步上报
fetch(`${REPORT_URL}?sessionId=${SESSION_ID}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
api: new URL(input, location.href).pathname,
method: init?.method || 'GET',
requestHeader: JSON.stringify(init?.headers || {}),
request: init?.body ? String(init.body) : '',
responseHeader: JSON.stringify(Object.fromEntries(response.headers.entries())),
response: await response.clone().text(),
timeConsuming,
}),
}).catch(() => {});
return response;
};#步骤 3:服务端接收上报
上报接口:POST /shater/raw-data?sessionId=<sessionId>
详细字段说明见 推送拦截数据 章节。
#优势
- ✅ 无需安装 CA 证书
- ✅ 不修改请求地址
- ✅ HTTPS 明文可见(因为 App 自己能解密)
- ✅ 不影响业务性能(异步上报)
#限制
- ❌ 需要 App 端配合(在拦截器中主动调用)
- ❌ 请求体/响应体需要在内存中可读(
peekBody/clone())

