api生成器初版确定提交
This commit is contained in:
@@ -40,7 +40,7 @@ body{font-family:'Microsoft YaHei',-apple-system,sans-serif;color:#1d1d1f;backgr
|
||||
.nav-group-title.open .arrow{transform:rotate(90deg)}
|
||||
.nav-group-title small{font-weight:400;color:#86909c;font-size:11px}
|
||||
.nav-group-items{overflow:hidden;max-height:0;transition:max-height .3s ease}
|
||||
.nav-group-title.open+.nav-group-items{max-height:2000px}
|
||||
.nav-group-title.open+.nav-group-items{max-height:99999px}
|
||||
.nav-item{display:flex;align-items:center;gap:8px;padding:6px 16px 6px 32px;font-size:12px;color:#4e5969;text-decoration:none;transition:.15s;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
||||
.nav-item:hover,.nav-item.active{color:#1677ff;background:#e8f3ff}
|
||||
.nav-item-icon{font-size:14px;flex-shrink:0}
|
||||
@@ -182,6 +182,57 @@ body{font-family:'Microsoft YaHei',-apple-system,sans-serif;color:#1d1d1f;backgr
|
||||
.api-card.search-hidden,.nav-item.search-hidden,.nav-group.search-hidden{display:none!important}
|
||||
.method-card.search-hidden{display:none!important}
|
||||
.search-highlight{background:#fff3cd;padding:1px 2px;border-radius:2px}
|
||||
|
||||
/* ===== Dubbo 调试按钮 ===== */
|
||||
.method-copy-btn.dubbo{background:#722ed1;color:#fff;border-color:#722ed1}
|
||||
.method-copy-btn.dubbo:hover{background:#531dab}
|
||||
|
||||
/* ===== Dubbo 调试弹窗(全屏双栏) ===== */
|
||||
.dubbo-modal-mask{position:fixed;inset:0;background:rgb(0 0 0/40%);z-index:9999;display:flex}
|
||||
.dubbo-modal{background:#fff;width:100vw;height:100vh;display:flex;flex-direction:column;border-radius:0;box-shadow:none}
|
||||
.dubbo-modal-header{display:flex;align-items:center;justify-content:space-between;padding:10px 20px;border-bottom:1px solid #e5e6eb;flex-shrink:0;background:#fafbfc}
|
||||
.dubbo-modal-title{font-size:15px;font-weight:700;color:#1d2129}
|
||||
.dubbo-modal-close{width:28px;height:28px;border:none;background:#f2f3f5;border-radius:4px;font-size:16px;cursor:pointer;display:flex;align-items:center;justify-content:center;color:#4e5969}
|
||||
.dubbo-modal-close:hover{background:#e5e6eb}
|
||||
|
||||
/* 双栏主体 */
|
||||
.dubbo-modal-body{flex:1;display:flex;overflow:hidden;min-height:0}
|
||||
.dubbo-modal-left{flex:1;overflow-y:auto;padding:16px 20px;min-width:0;display:flex;flex-direction:column}
|
||||
.dubbo-modal-right{width:42%;min-width:360px;max-width:600px;border-left:1px solid #e5e6eb;display:flex;flex-direction:column;background:#fafbfc;overflow:hidden}
|
||||
|
||||
.dubbo-modal-body label{display:block;font-size:13px;font-weight:600;color:#4e5969;margin-bottom:4px}
|
||||
.dubbo-modal-body label .hint{font-weight:400;color:#86909c}
|
||||
.dubbo-modal-body input,.dubbo-modal-body textarea,.dubbo-modal-body select{width:100%;padding:8px 12px;border:1px solid #d0d5dd;border-radius:4px;font-size:13px;font-family:'Consolas','Courier New',monospace;box-sizing:border-box}
|
||||
.dubbo-modal-body textarea{resize:vertical;min-height:120px;line-height:1.5}
|
||||
.dubbo-modal-body input:focus,.dubbo-modal-body textarea:focus,.dubbo-modal-body select:focus{outline:none;border-color:#1677ff;box-shadow:0 0 0 2px rgb(22 119 255/15%)}
|
||||
.dubbo-field-row{display:flex;gap:12px;margin-bottom:12px}
|
||||
.dubbo-field-row>div{flex:1;min-width:0}
|
||||
|
||||
/* 右栏响应区域 */
|
||||
.dubbo-response-panel{flex:1;display:flex;flex-direction:column;overflow:hidden}
|
||||
.dubbo-response-panel-header{display:flex;align-items:center;justify-content:space-between;padding:8px 12px;border-bottom:1px solid #e5e6eb;font-size:12px;background:#fff;flex-shrink:0}
|
||||
.dubbo-response-panel-empty{flex:1;display:flex;align-items:center;justify-content:center;color:#c9cdd4;font-size:14px}
|
||||
.dubbo-response-status{font-weight:700}
|
||||
.dubbo-response-status.ok{color:#00b42a}
|
||||
.dubbo-response-status.err{color:#f53f3f}
|
||||
.dubbo-response-content{flex:1;overflow-y:auto;padding:12px;font-size:12px;font-family:'Consolas','Courier New',monospace;white-space:pre-wrap;word-break:break-all;line-height:1.6;background:#fff;border-top:1px solid #e5e6eb}
|
||||
.dubbo-response-content-time{font-size:11px;color:#86909c;padding:4px 12px;background:#fafbfc;border-top:1px solid #f2f3f5}
|
||||
|
||||
/* 底部按钮栏 */
|
||||
.dubbo-modal-footer{display:flex;align-items:center;justify-content:flex-end;gap:10px;padding:10px 20px;border-top:1px solid #e5e6eb;flex-shrink:0;background:#fafbfc}
|
||||
.dubbo-btn{padding:8px 20px;border-radius:4px;font-size:13px;font-weight:600;cursor:pointer;border:1px solid #d0d5dd;background:#fff;color:#4e5969;transition:.15s}
|
||||
.dubbo-btn:hover{background:#f7f8fa}
|
||||
.dubbo-btn.primary{background:#722ed1;color:#fff;border-color:#722ed1}
|
||||
.dubbo-btn.primary:hover{background:#531dab}
|
||||
.dubbo-btn.send{background:#1677ff;color:#fff;border-color:#1677ff}
|
||||
.dubbo-btn.send:hover{background:#0958d9}
|
||||
.dubbo-btn.send:disabled{background:#94bfff;border-color:#94bfff;cursor:not-allowed}
|
||||
.dubbo-spinner{display:inline-block;width:14px;height:14px;border:2px solid #d0d5dd;border-top-color:#1677ff;border-radius:50%;animation:dubboSpin .6s linear infinite;vertical-align:middle;margin-right:4px}
|
||||
@keyframes dubboSpin{to{transform:rotate(360deg)}}
|
||||
|
||||
/* 响应式——窄屏时隐藏右栏 */
|
||||
@media(max-width:800px){.dubbo-modal-right{display:none}}
|
||||
|
||||
.search-match{box-shadow:0 0 0 2px rgba(22,119,255,.25)}
|
||||
|
||||
/* ===== 响应式 ===== */
|
||||
|
||||
@@ -7,7 +7,8 @@
|
||||
<style>
|
||||
{{CSS}}
|
||||
</style>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"></script>
|
||||
<script>var DUBBO_PROXY_URL='{{PROXY_URL}}';</script>
|
||||
<script>{{HTML2CANVAS}}</script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
|
||||
@@ -270,3 +270,818 @@ document.querySelectorAll('.api-card .api-body, .method-body').forEach(function(
|
||||
e.preventDefault();
|
||||
});
|
||||
});
|
||||
|
||||
/* ============================================================
|
||||
* Dubbo 接口调试
|
||||
* ============================================================ */
|
||||
|
||||
/** 默认网关 URL */
|
||||
var DUBBO_GW_URL_KEY = 'dubbo_gw_url';
|
||||
function getGwUrl() {
|
||||
return localStorage.getItem(DUBBO_GW_URL_KEY) || 'https://gw.test.bestpay.net/open/1.0/GenericFacade/genericInvoke';
|
||||
}
|
||||
function setGwUrl(url) { localStorage.setItem(DUBBO_GW_URL_KEY, url); }
|
||||
|
||||
/** 默认 header */
|
||||
function defaultHeaders() {
|
||||
return {
|
||||
'Content-Type': 'application/json',
|
||||
'User-Agent': 'ApiDoc-Debugger/1.0',
|
||||
'Accept': '*/*'
|
||||
};
|
||||
}
|
||||
|
||||
/** 构建 eventContext 的值 — 注意内外 env 是不同概念 */
|
||||
function buildEventContext(tntId, env, evtEnv) {
|
||||
return JSON.stringify({
|
||||
tntId:tntId||'0101', env:evtEnv||env||'DEV', arNo:'', evtCd:'', pdCd:'', pdPath:''
|
||||
});
|
||||
}
|
||||
|
||||
/** 根据 env 下拉选中值计算默认 eventContext.env */
|
||||
function getDefaultEvtEnv(envVal) {
|
||||
if (envVal === 'DEV') return 'DEV_1156339215001361525';
|
||||
if (envVal === 'SIT') {
|
||||
var now = new Date();
|
||||
var y = now.getFullYear();
|
||||
var m = pad2(now.getMonth() + 1);
|
||||
var d = pad2(now.getDate());
|
||||
return 'SIT-' + y + m + d + '01';
|
||||
}
|
||||
// STB 或其他 → 空
|
||||
return '';
|
||||
}
|
||||
|
||||
/** env 下拉变更时自动填充 eventContext.env */
|
||||
function onEnvChange() {
|
||||
var envEl = document.getElementById('dubboEnv');
|
||||
var evtEl = document.getElementById('dubboEvtEnv');
|
||||
if (!envEl || !evtEl) return;
|
||||
evtEl.value = getDefaultEvtEnv(envEl.value);
|
||||
}
|
||||
|
||||
/** 打开 Dubbo 调试弹窗 */
|
||||
function openDubboDebug(card) {
|
||||
window._dubboActiveCard = card;
|
||||
var iface = card.getAttribute('data-dubbo-interface');
|
||||
var method = card.getAttribute('data-dubbo-method');
|
||||
var paramTypes = card.getAttribute('data-dubbo-param-types') || '[]';
|
||||
var sampleArgs = card.getAttribute('data-dubbo-sample-args') || '[{}]';
|
||||
var stateKey = getDubboStateKey(iface, method);
|
||||
var saved = loadDubboState(stateKey);
|
||||
|
||||
var gwUrl = (saved && saved.gwUrl) ? saved.gwUrl : getGwUrl();
|
||||
var argsVal = (saved && saved.args) ? saved.args : formatJsonPretty(sampleArgs);
|
||||
var tntVal = (saved && saved.tntId) ? saved.tntId : '0101';
|
||||
var envVal = (saved && saved.env) ? saved.env : 'DEV';
|
||||
var appVal = (saved && saved.appId) ? saved.appId : '980010000114002';
|
||||
var evtEnvVal = (saved && saved.evtEnv) ? saved.evtEnv : getDefaultEvtEnv(envVal);
|
||||
|
||||
var envOpts = ['DEV','SIT','STB'].map(function(v) {
|
||||
return '<option' + (v === envVal ? ' selected' : '') + '>' + v + '</option>';
|
||||
}).join('');
|
||||
|
||||
var lastRespHtml = '';
|
||||
if (saved && saved.lastResponse) {
|
||||
lastRespHtml = '<div class="dubbo-response-panel" id="dubboResponsePanel">' +
|
||||
'<div class="dubbo-response-panel-header"><span>响应结果</span>' +
|
||||
'<span class="dubbo-response-status ' + (saved.lastResponse.ok ? 'ok' : 'err') + '" id="dubboStatus">' +
|
||||
escHtml(saved.lastResponse.status) + '</span></div>' +
|
||||
(saved.lastResponse.time ? '<div class="dubbo-response-content-time">📅 ' + escHtml(saved.lastResponse.time) + '</div>' : '') +
|
||||
'<pre class="dubbo-response-content" id="dubboRespContent">' + escHtml(saved.lastResponse.body) + '</pre></div>';
|
||||
} else {
|
||||
lastRespHtml = '<div class="dubbo-response-panel" id="dubboResponsePanel">' +
|
||||
'<div class="dubbo-response-panel-header"><span>响应结果</span><span class="dubbo-response-status" id="dubboStatus"></span></div>' +
|
||||
'<div class="dubbo-response-panel-empty">点击发送请求查看结果</div></div>';
|
||||
}
|
||||
|
||||
var html = '<div class="dubbo-modal-mask" id="dubboMask" onclick="if(event.target===this)closeDubboDebug()">' +
|
||||
'<div class="dubbo-modal">' +
|
||||
'<div class="dubbo-modal-header">' +
|
||||
'<span class="dubbo-modal-title">🔧 ' + escHtml(method) + ' — 接口调试</span>' +
|
||||
'<button class="dubbo-modal-close" onclick="closeDubboDebug()">✕</button>' +
|
||||
'</div>' +
|
||||
'<div class="dubbo-modal-body">' +
|
||||
// 左栏:表单
|
||||
'<div class="dubbo-modal-left">' +
|
||||
'<div class="dubbo-field-row">' +
|
||||
'<div><label>接口 <span class="hint">interfacePath</span></label><input value="' + escHtml(iface) + '" readonly style="background:#f7f8fa;color:#86909c"></div>' +
|
||||
'<div style="max-width:280px"><label>方法 <span class="hint">method</span></label><input value="' + escHtml(method) + '" readonly style="background:#f7f8fa;color:#86909c"></div>' +
|
||||
'</div>' +
|
||||
'<div class="dubbo-field-row"><div><label>参数类型 <span class="hint">parameterTypes</span></label><input value="' + escHtml(paramTypes) + '" readonly style="background:#f7f8fa;color:#86909c"></div></div>' +
|
||||
'<div style="flex:1;display:flex;flex-direction:column;min-height:0;margin-bottom:12px">' +
|
||||
'<label>请求参数 <span class="hint">args</span>' +
|
||||
' <button class="dubbo-btn" style="padding:2px 8px;font-size:11px" onclick="formatDubboArgs()">格式化</button>' +
|
||||
' <button class="dubbo-btn" style="padding:2px 8px;font-size:11px" onclick="resetDubboArgs()" title="恢复默认示例">重置</button>' +
|
||||
' <button class="dubbo-btn" style="padding:2px 8px;font-size:11px" onclick="showLogExtractPopup()" title="从日志提取入参">📋 日志提取</button></label>' +
|
||||
'<textarea id="dubboArgs" spellcheck="false" style="flex:1;min-height:200px">' + escHtml(argsVal) + '</textarea>' +
|
||||
'</div>' +
|
||||
'<div class="dubbo-field-row">' +
|
||||
'<div><label>网关 URL</label><input id="dubboGwUrl" value="' + escHtml(gwUrl) + '"></div>' +
|
||||
'</div>' +
|
||||
'<div class="dubbo-field-row">' +
|
||||
'<div style="max-width:140px"><label>tnt_id</label><input id="dubboTntId" value="' + escHtml(tntVal) + '"></div>' +
|
||||
'<div style="max-width:140px"><label>env</label><select id="dubboEnv" onchange="onEnvChange()">' + envOpts + '</select></div>' +
|
||||
'<div style="max-width:160px"><label>app_id</label><input id="dubboAppId" value="' + escHtml(appVal) + '"></div>' +
|
||||
'<div style="max-width:220px"><label>eventContext.env <span class="hint">实例ID</span></label><input id="dubboEvtEnv" value="' + escHtml(evtEnvVal) + '" placeholder="如 DEV_XQYFGL170183"></div>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
// 右栏:响应
|
||||
'<div class="dubbo-modal-right">' +
|
||||
lastRespHtml +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'<div class="dubbo-modal-footer">' +
|
||||
'<button class="dubbo-btn" onclick="copyDubboCurl()">📋 复制 cURL</button>' +
|
||||
'<button class="dubbo-btn" onclick="copyDubboBody()">📄 复制请求体</button>' +
|
||||
'<button class="dubbo-btn send" id="dubboSendBtn" onclick="sendDubboRequest()">▶ 发送请求</button>' +
|
||||
'</div>' +
|
||||
'</div></div>';
|
||||
|
||||
// 移除已有弹窗
|
||||
var old = document.getElementById('dubboMask');
|
||||
if (old) old.remove();
|
||||
document.body.insertAdjacentHTML('beforeend', html);
|
||||
|
||||
// 如果有保存的响应结果,展开显示
|
||||
if (saved && saved.lastResponse) {
|
||||
var respContent = document.getElementById('dubboRespContent');
|
||||
if (respContent) { respContent.style.display = ''; }
|
||||
var emptyEl = document.querySelector('.dubbo-response-panel-empty');
|
||||
if (emptyEl) { emptyEl.style.display = 'none'; }
|
||||
}
|
||||
}
|
||||
|
||||
/** 关闭弹窗 */
|
||||
function closeDubboDebug() {
|
||||
var mask = document.getElementById('dubboMask');
|
||||
if (mask) mask.remove();
|
||||
}
|
||||
|
||||
/** localStorage key */
|
||||
function getDubboStateKey(iface, method) {
|
||||
return 'dubbo_debug_' + (iface||'') + '_' + (method||'');
|
||||
}
|
||||
|
||||
/** 保存当前调试状态到 localStorage */
|
||||
function saveDubboState() {
|
||||
var card = getActiveDubboCard();
|
||||
if (!card) return;
|
||||
var iface = card.getAttribute('data-dubbo-interface');
|
||||
var method = card.getAttribute('data-dubbo-method');
|
||||
var key = getDubboStateKey(iface, method);
|
||||
var state = {
|
||||
gwUrl: (document.getElementById('dubboGwUrl') || {}).value || '',
|
||||
args: (document.getElementById('dubboArgs') || {}).value || '',
|
||||
tntId: (document.getElementById('dubboTntId') || {}).value || '',
|
||||
env: (document.getElementById('dubboEnv') || {}).value || '',
|
||||
appId: (document.getElementById('dubboAppId') || {}).value || '',
|
||||
evtEnv: (document.getElementById('dubboEvtEnv') || {}).value || ''
|
||||
};
|
||||
// 保留已有的响应结果(如果有)
|
||||
var existing = loadDubboState(key);
|
||||
if (existing && existing.lastResponse) {
|
||||
state.lastResponse = existing.lastResponse;
|
||||
}
|
||||
try { localStorage.setItem(key, JSON.stringify(state)); } catch(e) {}
|
||||
}
|
||||
|
||||
/** 读取调试状态 */
|
||||
function loadDubboState(key) {
|
||||
try {
|
||||
var raw = localStorage.getItem(key);
|
||||
return raw ? JSON.parse(raw) : null;
|
||||
} catch(e) { return null; }
|
||||
}
|
||||
|
||||
/** 保存响应结果 */
|
||||
function saveDubboResponse(statusText, body, ok) {
|
||||
var card = getActiveDubboCard();
|
||||
if (!card) return;
|
||||
var iface = card.getAttribute('data-dubbo-interface');
|
||||
var method = card.getAttribute('data-dubbo-method');
|
||||
var key = getDubboStateKey(iface, method);
|
||||
var state = loadDubboState(key) || {};
|
||||
state.lastResponse = { status: statusText, body: body, ok: ok, time: formatLocalDateTime() };
|
||||
// 同时保存当前表单字段
|
||||
state.gwUrl = (document.getElementById('dubboGwUrl') || {}).value || state.gwUrl || '';
|
||||
state.args = (document.getElementById('dubboArgs') || {}).value || state.args || '';
|
||||
state.tntId = (document.getElementById('dubboTntId') || {}).value || state.tntId || '';
|
||||
state.env = (document.getElementById('dubboEnv') || {}).value || state.env || '';
|
||||
state.appId = (document.getElementById('dubboAppId') || {}).value || state.appId || '';
|
||||
state.evtEnv = (document.getElementById('dubboEvtEnv') || {}).value || state.evtEnv || '';
|
||||
try { localStorage.setItem(key, JSON.stringify(state)); } catch(e) {}
|
||||
}
|
||||
|
||||
/** 重置 args 为默认示例 */
|
||||
function resetDubboArgs() {
|
||||
var card = getActiveDubboCard();
|
||||
if (!card) return;
|
||||
var sampleArgs = card.getAttribute('data-dubbo-sample-args') || '[{}]';
|
||||
var ta = document.getElementById('dubboArgs');
|
||||
if (ta) ta.value = formatJsonPretty(sampleArgs);
|
||||
}
|
||||
|
||||
// 去除 JSON 中的单行和多行注释(兼容 Apifox 等工具复制出来的格式)
|
||||
function stripJsonComments(jsonStr) {
|
||||
if (!jsonStr) return '';
|
||||
return jsonStr
|
||||
.replace(/\\"|"(?:\\"|[^"])*"|(\/\/.*|\/\*[\s\S]*?\*\/)/g, function(m, g) { return g ? '' : m; })
|
||||
.replace(/,\s*\}/g, '}').replace(/,\s*\]/g, ']'); // 去掉尾部逗号
|
||||
}
|
||||
|
||||
/** 格式化 JSON */
|
||||
function formatJsonPretty(jsonStr) {
|
||||
try {
|
||||
var obj = JSON.parse(stripJsonComments(jsonStr));
|
||||
return JSON.stringify(obj, null, 2);
|
||||
} catch(e) { return jsonStr; }
|
||||
}
|
||||
|
||||
/** 对 textarea 中的 JSON 进行格式化 */
|
||||
function formatDubboArgs() {
|
||||
var ta = document.getElementById('dubboArgs');
|
||||
if (!ta) return;
|
||||
ta.value = formatJsonPretty(ta.value);
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* 日志提取功能 — 从日志中解析 DTO 参数填入调试面板
|
||||
* ============================================================ */
|
||||
|
||||
/** 显示日志提取弹窗 */
|
||||
function showLogExtractPopup() {
|
||||
var old = document.getElementById('logExtractOverlay');
|
||||
if (old) old.remove();
|
||||
var overlay = document.createElement('div');
|
||||
overlay.id = 'logExtractOverlay';
|
||||
overlay.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:100000;display:flex;align-items:center;justify-content:center';
|
||||
overlay.innerHTML =
|
||||
'<div style="background:#fff;border-radius:8px;box-shadow:0 8px 40px rgba(0,0,0,.2);width:680px;max-width:92vw;max-height:85vh;display:flex;flex-direction:column">' +
|
||||
'<div style="padding:14px 20px;border-bottom:1px solid #e5e6eb;display:flex;align-items:center;justify-content:space-between">' +
|
||||
'<span style="font-size:15px;font-weight:600">📋 从日志提取入参</span>' +
|
||||
'<button onclick="closeLogExtractPopup()" style="background:none;border:none;font-size:22px;cursor:pointer;color:#86909c;padding:0;line-height:1">×</button>' +
|
||||
'</div>' +
|
||||
'<div style="padding:16px 20px;flex:1;display:flex;flex-direction:column;min-height:0">' +
|
||||
'<div style="color:#86909c;font-size:12px;margin-bottom:8px;line-height:1.6">粘贴日志内容,自动识别 <code style="background:#f2f3f5;padding:1px 6px;border-radius:3px">接口入参:ClassName(field=value,...)</code> 格式,<br>也支持只粘贴 DTO 参数部分。</div>' +
|
||||
'<textarea id="logExtractInput" placeholder="示例:QuerySplitAccountInfoReqDTO(loanNo=211254457192, seasonList=[QuerySplitAccountInfoReqDTO.Season(season=202305, subjectList=[...])])" style="flex:1;min-height:200px;border:1px solid #d0d5dd;border-radius:6px;padding:10px 12px;font-family:Consolas,Monaco,monospace;font-size:12px;resize:vertical;outline:none;line-height:1.6" spellcheck="false"></textarea>' +
|
||||
'<div style="margin-top:12px;display:flex;gap:8px">' +
|
||||
'<button onclick="doExtractLogArgs()" style="background:#165DFF;color:#fff;border:none;border-radius:4px;padding:7px 18px;font-size:13px;cursor:pointer">🔄 解析并比对</button>' +
|
||||
'<button onclick="closeLogExtractPopup()" style="background:#f2f3f5;border:1px solid #e5e6eb;border-radius:4px;padding:7px 18px;font-size:13px;cursor:pointer;color:#4e5969">取消</button>' +
|
||||
'</div>' +
|
||||
'</div></div>';
|
||||
document.body.appendChild(overlay);
|
||||
overlay.addEventListener('click', function(e) { if (e.target === overlay) closeLogExtractPopup(); });
|
||||
setTimeout(function() { var ta = document.getElementById('logExtractInput'); if (ta) ta.focus(); }, 100);
|
||||
}
|
||||
|
||||
function closeLogExtractPopup() {
|
||||
var el = document.getElementById('logExtractOverlay');
|
||||
if (el) el.remove();
|
||||
}
|
||||
|
||||
/** 执行日志解析 → 弹出比对窗口 */
|
||||
function doExtractLogArgs() {
|
||||
var raw = (document.getElementById('logExtractInput') || {}).value;
|
||||
if (!raw || !raw.trim()) { showToast('⚠ 请粘贴日志内容'); return; }
|
||||
raw = raw.trim();
|
||||
var dtoText = raw;
|
||||
var markers = ['接口入参:', '接口入参:', '入参:', '入参:'];
|
||||
for (var i = 0; i < markers.length; i++) {
|
||||
var idx = raw.lastIndexOf(markers[i]);
|
||||
if (idx >= 0) { dtoText = raw.substring(idx + markers[i].length).trim(); break; }
|
||||
}
|
||||
try {
|
||||
var logObj = flattenSuperChain(parseDtoExpr(dtoText));
|
||||
var card = getActiveDubboCard();
|
||||
var sampleObj = {};
|
||||
if (card) {
|
||||
try {
|
||||
var sampleArr = JSON.parse(card.getAttribute('data-dubbo-sample-args') || '[{}]');
|
||||
if (Array.isArray(sampleArr) && sampleArr.length > 0) sampleObj = sampleArr[0];
|
||||
} catch (ignored) {}
|
||||
}
|
||||
closeLogExtractPopup();
|
||||
showParamComparePopup(sampleObj, logObj);
|
||||
} catch(e) {
|
||||
showToast('❌ 解析失败:' + e.message);
|
||||
}
|
||||
}
|
||||
|
||||
/** 参数比对弹窗:默认参考值 vs 日志提取值 */
|
||||
function showParamComparePopup(sampleObj, logObj) {
|
||||
var old = document.getElementById('paramCompareOverlay');
|
||||
if (old) old.remove();
|
||||
|
||||
var diffs = diffObjects(sampleObj, logObj, '');
|
||||
var changedCount = 0, sampleOnlyCount = 0;
|
||||
diffs.forEach(function(d) {
|
||||
if (d.status === 'changed') changedCount++;
|
||||
else if (d.status === 'sample_only') sampleOnlyCount++;
|
||||
});
|
||||
|
||||
var rowsHtml = '';
|
||||
diffs.forEach(function(d, idx) {
|
||||
var badge, rowStyle;
|
||||
if (d.status === 'changed') {
|
||||
badge = '<span style="background:#fff7e6;color:#d46b08;padding:1px 8px;border-radius:3px;font-size:11px;font-weight:500">变更</span>';
|
||||
rowStyle = 'background:#fffbe6';
|
||||
} else if (d.status === 'sample_only') {
|
||||
badge = '<span style="background:#e6f7ff;color:#096dd9;padding:1px 8px;border-radius:3px;font-size:11px;font-weight:500">仅默认</span>';
|
||||
rowStyle = 'background:#f0f8ff';
|
||||
} else if (d.status === 'log_only') {
|
||||
badge = '<span style="background:#f6ffed;color:#389e0d;padding:1px 8px;border-radius:3px;font-size:11px;font-weight:500">仅日志</span>';
|
||||
rowStyle = 'background:#f6ffed';
|
||||
} else {
|
||||
badge = '<span style="color:#bbb;font-size:11px">一致</span>';
|
||||
rowStyle = '';
|
||||
}
|
||||
var sampleDisplay = d.sampleVal === undefined ? '<span style="color:#bbb">—</span>' : escHtml(fmtDiffVal(d.sampleVal));
|
||||
var logDisplay = d.logVal === undefined ? '<span style="color:#bbb">—</span>' : escHtml(fmtDiffVal(d.logVal));
|
||||
rowsHtml += '<tr style="' + rowStyle + '">' +
|
||||
'<td style="padding:5px 10px;font-size:11px">' + badge + '</td>' +
|
||||
'<td style="padding:5px 10px;font-family:Consolas,Monaco,monospace;font-size:12px;max-width:300px;word-break:break-all">' + escHtml(d.path || '(根)') + '</td>' +
|
||||
'<td style="padding:5px 10px;font-family:Consolas,Monaco,monospace;font-size:12px;max-width:280px;word-break:break-all;' + (d.status==='changed'?'color:#d46b08':'') + '">' + sampleDisplay + '</td>' +
|
||||
'<td style="padding:5px 10px;font-family:Consolas,Monaco,monospace;font-size:12px;max-width:280px;word-break:break-all;' + (d.status==='changed'?'color:#389e0d;font-weight:500':'') + '">' + logDisplay + '</td>' +
|
||||
'</tr>';
|
||||
});
|
||||
|
||||
var overlay = document.createElement('div');
|
||||
overlay.id = 'paramCompareOverlay';
|
||||
overlay.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,.5);z-index:100001;display:flex;align-items:center;justify-content:center';
|
||||
overlay.innerHTML =
|
||||
'<div style="background:#fff;border-radius:10px;box-shadow:0 12px 60px rgba(0,0,0,.25);width:1100px;max-width:96vw;max-height:90vh;display:flex;flex-direction:column">' +
|
||||
'<div style="padding:16px 24px;border-bottom:1px solid #e5e6eb;display:flex;align-items:center;justify-content:space-between;flex-shrink:0">' +
|
||||
'<div><span style="font-size:16px;font-weight:600">🔄 参数比对 — 默认值 vs 日志值</span>' +
|
||||
'<span style="margin-left:12px;font-size:12px;color:#86909c">' +
|
||||
(changedCount > 0 ? ' <span style="color:#d46b08;font-weight:500">' + changedCount + ' 项变更</span>' : '') +
|
||||
(sampleOnlyCount > 0 ? ' <span style="color:#096dd9;font-weight:500;margin-left:8px">' + sampleOnlyCount + ' 项仅默认</span>' : '') +
|
||||
'</span></div>' +
|
||||
'<button onclick="closeParamComparePopup()" style="background:none;border:none;font-size:22px;cursor:pointer;color:#86909c;padding:0;line-height:1">×</button>' +
|
||||
'</div>' +
|
||||
'<div style="flex:1;overflow:auto;min-height:0">' +
|
||||
'<table style="width:100%;border-collapse:collapse">' +
|
||||
'<thead><tr style="position:sticky;top:0;background:#fafafa;z-index:1">' +
|
||||
'<th style="text-align:left;padding:8px 10px;font-size:12px;color:#86909c;border-bottom:2px solid #e5e6eb;width:60px">状态</th>' +
|
||||
'<th style="text-align:left;padding:8px 10px;font-size:12px;color:#86909c;border-bottom:2px solid #e5e6eb">参数路径</th>' +
|
||||
'<th style="text-align:left;padding:8px 10px;font-size:12px;color:#86909c;border-bottom:2px solid #e5e6eb">默认参考值</th>' +
|
||||
'<th style="text-align:left;padding:8px 10px;font-size:12px;color:#86909c;border-bottom:2px solid #e5e6eb">日志提取值</th>' +
|
||||
'</tr></thead>' +
|
||||
'<tbody>' + rowsHtml + '</tbody>' +
|
||||
'</table>' +
|
||||
(diffs.length === 0 ? '<div style="text-align:center;padding:40px;color:#86909c">无差异,参数完全一致</div>' : '') +
|
||||
'</div>' +
|
||||
'<div style="padding:14px 24px;border-top:1px solid #e5e6eb;display:flex;gap:10px;flex-shrink:0">' +
|
||||
'<button onclick="applyCompareResult(\'merged\')" style="background:#165DFF;color:#fff;border:none;border-radius:6px;padding:8px 22px;font-size:13px;cursor:pointer">✅ 使用合并值(默认 + 日志)</button>' +
|
||||
'<button onclick="applyCompareResult(\'log\')" style="background:#f2f3f5;border:1px solid #d0d5dd;border-radius:6px;padding:8px 22px;font-size:13px;cursor:pointer;color:#4e5969">📋 仅使用日志值</button>' +
|
||||
'<button onclick="closeParamComparePopup()" style="background:#fff;border:1px solid #d0d5dd;border-radius:6px;padding:8px 22px;font-size:13px;cursor:pointer;color:#4e5969;margin-left:auto">取消</button>' +
|
||||
'</div></div>';
|
||||
document.body.appendChild(overlay);
|
||||
overlay.addEventListener('click', function(e) { if (e.target === overlay) closeParamComparePopup(); });
|
||||
|
||||
// 暂存原始对象
|
||||
overlay._sampleObj = sampleObj;
|
||||
overlay._logObj = logObj;
|
||||
overlay._diffCount = diffs.length;
|
||||
}
|
||||
|
||||
function closeParamComparePopup() {
|
||||
var el = document.getElementById('paramCompareOverlay');
|
||||
if (el) el.remove();
|
||||
}
|
||||
|
||||
/** 比对结果应用 */
|
||||
var _compareResultLogObj = null, _compareResultSampleObj = null;
|
||||
function applyCompareResult(mode) {
|
||||
var overlay = document.getElementById('paramCompareOverlay');
|
||||
var sampleObj = overlay._sampleObj || {};
|
||||
var logObj = overlay._logObj || {};
|
||||
var finalObj;
|
||||
if (mode === 'merged') {
|
||||
finalObj = deepMerge(sampleObj, logObj);
|
||||
} else {
|
||||
finalObj = logObj;
|
||||
}
|
||||
var json = JSON.stringify([finalObj], null, 2);
|
||||
var ta = document.getElementById('dubboArgs');
|
||||
if (ta) { ta.value = json; showToast('✅ 参数已填入(共 ' + (overlay._diffCount || 0) + ' 个字段参与比对)'); }
|
||||
closeParamComparePopup();
|
||||
}
|
||||
|
||||
/** 递归对比两个对象,返回差异列表 [{path, sampleVal, logVal, status}] */
|
||||
function diffObjects(sample, log, path) {
|
||||
var results = [];
|
||||
var sampleIsObj = sample && typeof sample === 'object' && !Array.isArray(sample);
|
||||
var logIsObj = log && typeof log === 'object' && !Array.isArray(log);
|
||||
|
||||
if (!sampleIsObj && !logIsObj) {
|
||||
// 都不是对象,直接比较值
|
||||
var sv = sample;
|
||||
var lv = log;
|
||||
if (sv === undefined && lv !== undefined) results.push({path: path, sampleVal: undefined, logVal: lv, status: 'log_only'});
|
||||
else if (lv === undefined && sv !== undefined) results.push({path: path, sampleVal: sv, logVal: undefined, status: 'sample_only'});
|
||||
else if (!eqVal(sv, lv)) results.push({path: path, sampleVal: sv, logVal: lv, status: 'changed'});
|
||||
return results;
|
||||
}
|
||||
|
||||
// 处理:一个对象一个非对象
|
||||
if (sampleIsObj !== logIsObj) {
|
||||
if (!eqVal(sample, log)) results.push({path: path, sampleVal: sample, logVal: log, status: 'changed'});
|
||||
return results;
|
||||
}
|
||||
|
||||
// 都是对象,递归比较 key
|
||||
var seen = {};
|
||||
for (var key in sample) {
|
||||
if (!sample.hasOwnProperty(key)) continue;
|
||||
seen[key] = true;
|
||||
var childPath = path ? path + '.' + key : key;
|
||||
results = results.concat(diffObjects(sample[key], log ? log[key] : undefined, childPath));
|
||||
}
|
||||
for (var key2 in log) {
|
||||
if (!log.hasOwnProperty(key2) || seen[key2]) continue;
|
||||
var childPath2 = path ? path + '.' + key2 : key2;
|
||||
results.push({path: childPath2, sampleVal: undefined, logVal: log[key2], status: 'log_only'});
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
function eqVal(a, b) {
|
||||
if (a === b) return true;
|
||||
if (a === undefined || b === undefined) return false;
|
||||
if (typeof a === 'object' || typeof b === 'object') return JSON.stringify(a) === JSON.stringify(b);
|
||||
return String(a) === String(b);
|
||||
}
|
||||
|
||||
function fmtDiffVal(v) {
|
||||
if (v === undefined || v === null) return 'null';
|
||||
if (typeof v === 'object') return JSON.stringify(v);
|
||||
return String(v);
|
||||
}
|
||||
|
||||
/** 解析 DTO 表达式 ClassName(field=value, ...),返回 JS 对象 */
|
||||
function parseDtoExpr(text) {
|
||||
text = text.trim();
|
||||
// 找出最外层 ClassName(...) 的括号范围
|
||||
var m = text.match(/^([\w.]+)\s*\(([\s\S]*)\)$/);
|
||||
if (!m) {
|
||||
var found = findOuterParens(text);
|
||||
if (!found) throw new Error('未识别到 DTO 表达式,期望格式: ClassName(field=value,...)');
|
||||
text = found;
|
||||
m = text.match(/^([\w.]+)\s*\(([\s\S]*)\)$/);
|
||||
if (!m) throw new Error('DTO 格式异常');
|
||||
}
|
||||
var obj = {};
|
||||
var content = m[2];
|
||||
var pairs = splitTopLevel(content, ',');
|
||||
for (var i = 0; i < pairs.length; i++) {
|
||||
var pair = pairs[i].trim();
|
||||
if (!pair) continue;
|
||||
// 在顶层找第一个 '='
|
||||
var eqIdx = findTopLevelChar(pair, '=');
|
||||
if (eqIdx < 0) continue;
|
||||
var key = pair.substring(0, eqIdx).trim();
|
||||
var valStr = pair.substring(eqIdx + 1).trim();
|
||||
obj[key] = parseDtoValue(valStr);
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
/** 在文本中定位最外层用括号包裹的 ClassName(...) 片段 */
|
||||
function findOuterParens(text) {
|
||||
var m = text.match(/([\w.]+)\s*\(/);
|
||||
if (!m) return null;
|
||||
var start = m.index;
|
||||
var openIdx = start + m[0].length - 1;
|
||||
var depth = 1;
|
||||
for (var i = openIdx + 1; i < text.length; i++) {
|
||||
if (text[i] === '(') depth++;
|
||||
else if (text[i] === ')') depth--;
|
||||
if (depth === 0) return text.substring(start, i + 1);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** 按分隔符拆分字符串,仅拆分括号深度为 0 的位置 */
|
||||
function splitTopLevel(str, sep) {
|
||||
var parts = [];
|
||||
var start = 0;
|
||||
var depth = 0;
|
||||
for (var i = 0; i < str.length; i++) {
|
||||
var ch = str[i];
|
||||
if (ch === '(' || ch === '[') depth++;
|
||||
else if (ch === ')' || ch === ']') depth--;
|
||||
else if (ch === sep && depth === 0) {
|
||||
parts.push(str.substring(start, i));
|
||||
start = i + 1;
|
||||
}
|
||||
}
|
||||
parts.push(str.substring(start));
|
||||
return parts;
|
||||
}
|
||||
|
||||
/** 在字符串中找括号深度为 0 的位置 */
|
||||
function findTopLevelChar(str, ch) {
|
||||
var depth = 0;
|
||||
for (var i = 0; i < str.length; i++) {
|
||||
if (str[i] === '(' || str[i] === '[') depth++;
|
||||
else if (str[i] === ')' || str[i] === ']') depth--;
|
||||
else if (str[i] === ch && depth === 0) return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/** 递归解析 DTO 值:嵌套 DTO / 列表 / 基本值 */
|
||||
function parseDtoValue(val) {
|
||||
val = val.trim();
|
||||
if (!val) return null;
|
||||
// 列表:[item, item, ...]
|
||||
if (val.charAt(0) === '[' && val.charAt(val.length - 1) === ']') {
|
||||
var inner = val.substring(1, val.length - 1).trim();
|
||||
if (!inner) return [];
|
||||
var items = splitTopLevel(inner, ',');
|
||||
var arr = [];
|
||||
for (var i = 0; i < items.length; i++) {
|
||||
var item = items[i].trim();
|
||||
if (!item) continue;
|
||||
arr.push(parseDtoValue(item));
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
// 嵌套 DTO:ClassName(...)
|
||||
if (/^[\w.]+\s*\(/.test(val)) return parseDtoExpr(val);
|
||||
// 匿名对象:super=(requestSystem="CF_MIX", ...)
|
||||
if (val.charAt(0) === '(' && val.charAt(val.length - 1) === ')') {
|
||||
return parseAnonymousDto(val.substring(1, val.length - 1));
|
||||
}
|
||||
// null
|
||||
if (val === 'null' || val === 'NULL') return null;
|
||||
// 日志中的 DTO 表达式不区分类型(string/number/boolean),统一按字符串返回,
|
||||
// Dubbo 序列化时由方法签名决定最终类型,避免误判(如 loanNo=211254457192 被当成数字截断)
|
||||
// Date 格式转换:Tue Jul 28 18:10:00 CST 2026 → 2026-07-28 18:10:00
|
||||
if (/^\w{3}\s\w{3}\s\d{1,2}\s\d{2}:\d{2}:\d{2}\s\w+\s\d{4}$/.test(val)) {
|
||||
var d = new Date(val);
|
||||
if (!isNaN(d.getTime())) {
|
||||
return d.getFullYear() + '-' +
|
||||
pad2(d.getMonth() + 1) + '-' +
|
||||
pad2(d.getDate()) + ' ' +
|
||||
pad2(d.getHours()) + ':' +
|
||||
pad2(d.getMinutes()) + ':' +
|
||||
pad2(d.getSeconds());
|
||||
}
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
/** 两位数补零 */
|
||||
function pad2(n) { return n < 10 ? '0' + n : '' + n; }
|
||||
|
||||
/** 解析匿名对象 (field=value, ...),没有类名前缀 */
|
||||
function parseAnonymousDto(content) {
|
||||
var obj = {};
|
||||
var pairs = splitTopLevel(content, ',');
|
||||
for (var i = 0; i < pairs.length; i++) {
|
||||
var pair = pairs[i].trim();
|
||||
if (!pair) continue;
|
||||
var eqIdx = findTopLevelChar(pair, '=');
|
||||
if (eqIdx < 0) continue;
|
||||
var key = pair.substring(0, eqIdx).trim();
|
||||
var valStr = pair.substring(eqIdx + 1).trim();
|
||||
obj[key] = parseDtoValue(valStr);
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
/** 扁平化 super 继承链。
|
||||
* 日志中 super=TradeRequestDTO(super=BaseRequestDTO(...), ...) 嵌套结构
|
||||
* 需要把 super 中的父类字段提升到当前层级,使比对路径与默认值对齐 */
|
||||
function flattenSuperChain(obj) {
|
||||
if (!obj || typeof obj !== 'object' || Array.isArray(obj)) return obj;
|
||||
var result = {};
|
||||
for (var key in obj) {
|
||||
if (!obj.hasOwnProperty(key)) continue;
|
||||
if (key === 'super' && obj[key] && typeof obj[key] === 'object' && !Array.isArray(obj[key])) {
|
||||
var superFlat = flattenSuperChain(obj[key]);
|
||||
for (var sKey in superFlat) {
|
||||
if (superFlat.hasOwnProperty(sKey)) result[sKey] = superFlat[sKey];
|
||||
}
|
||||
} else if (key !== 'super') {
|
||||
result[key] = obj[key];
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/** 深度合并:src 覆盖 base,base 补齐 src 缺失字段。仅处理普通对象,数组直接覆盖 */
|
||||
function deepMerge(base, src) {
|
||||
if (!base || typeof base !== 'object') return src;
|
||||
if (!src || typeof src !== 'object') return base;
|
||||
if (Array.isArray(base) || Array.isArray(src)) return src;
|
||||
var result = {};
|
||||
for (var key in base) {
|
||||
if (base.hasOwnProperty(key)) {
|
||||
if (src.hasOwnProperty(key)) {
|
||||
result[key] = deepMerge(base[key], src[key]);
|
||||
} else {
|
||||
result[key] = base[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
for (var key2 in src) {
|
||||
if (src.hasOwnProperty(key2) && !base.hasOwnProperty(key2)) {
|
||||
result[key2] = src[key2];
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/** HTML 转义 */
|
||||
function escHtml(s) {
|
||||
if (!s) return '';
|
||||
return s.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"').replace(/'/g,''');
|
||||
}
|
||||
|
||||
/** 获取本地时区的 yyyy-MM-dd HH:mm:ss */
|
||||
function formatLocalDateTime() {
|
||||
var d = new Date();
|
||||
var pad = function(n) { return (n < 10 ? '0' : '') + n; };
|
||||
return d.getFullYear() + '-' + pad(d.getMonth()+1) + '-' + pad(d.getDate()) + ' ' +
|
||||
pad(d.getHours()) + ':' + pad(d.getMinutes()) + ':' + pad(d.getSeconds());
|
||||
}
|
||||
|
||||
/** 构建请求 body */
|
||||
function buildDubboBody() {
|
||||
var argsText = document.getElementById('dubboArgs') ? document.getElementById('dubboArgs').value : '{}';
|
||||
var tntId = document.getElementById('dubboTntId') ? document.getElementById('dubboTntId').value : '0101';
|
||||
var env = document.getElementById('dubboEnv') ? document.getElementById('dubboEnv').value : 'STB';
|
||||
var evtEnv = document.getElementById('dubboEvtEnv') ? document.getElementById('dubboEvtEnv').value : 'DEV';
|
||||
var appId = document.getElementById('dubboAppId') ? document.getElementById('dubboAppId').value : '980010000114002';
|
||||
var card = getActiveDubboCard();
|
||||
var iface = card ? card.getAttribute('data-dubbo-interface') : '';
|
||||
var method = card ? card.getAttribute('data-dubbo-method') : '';
|
||||
var paramTypes = card ? card.getAttribute('data-dubbo-param-types') || '[]' : '[]';
|
||||
|
||||
var args;
|
||||
try { args = JSON.parse(stripJsonComments(argsText)); } catch(e) { alert('args JSON 格式错误: ' + e.message); return null; }
|
||||
|
||||
return {
|
||||
data: {
|
||||
interfacePath: iface,
|
||||
method: method,
|
||||
parameterTypes: JSON.parse(paramTypes),
|
||||
args: args,
|
||||
traceId: genTraceId(),
|
||||
zoneType: 'G',
|
||||
eventContext: {
|
||||
tntId: tntId, ipTId:'', ipId:'null', ipRId:'', chlNo: genTraceId(),
|
||||
originalArNo:'', originalEvtCd:'', arNo:'', chlEvtCd:'',
|
||||
prePdCd:'null', preEvtCd:'null', pdCd:'', evtCd:'', pdPath:'',
|
||||
env: evtEnv, tagExtensionInfo:'{}', shadow:'0'
|
||||
}
|
||||
},
|
||||
tnt_id: tntId,
|
||||
app_id: appId,
|
||||
timestamp: formatLocalDateTime(),
|
||||
env: env
|
||||
};
|
||||
}
|
||||
|
||||
/** 获取当前打开调试的那个 method-card */
|
||||
function getActiveDubboCard() {
|
||||
var mask = document.getElementById('dubboMask');
|
||||
if (!mask) return null;
|
||||
// 从触发按钮找:记录最后点击的 card
|
||||
return window._dubboActiveCard || document.querySelector('.method-card[data-dubbo-interface]');
|
||||
}
|
||||
|
||||
/** 生成 traceId */
|
||||
function genTraceId() {
|
||||
return 'xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
|
||||
var r = Math.random()*16|0, v = c==='x'? r : (r&0x3|0x8);
|
||||
return v.toString(16);
|
||||
});
|
||||
}
|
||||
|
||||
/** 发送 Dubbo 请求 — 通过本地 Java 代理转发,解决浏览器无法直连内网网关的问题 */
|
||||
function sendDubboRequest() {
|
||||
var body = buildDubboBody();
|
||||
if (!body) return;
|
||||
|
||||
// 发送前保存当前表单状态
|
||||
saveDubboState();
|
||||
|
||||
var gwUrl = document.getElementById('dubboGwUrl') ? document.getElementById('dubboGwUrl').value : getGwUrl();
|
||||
setGwUrl(gwUrl);
|
||||
|
||||
var btn = document.getElementById('dubboSendBtn');
|
||||
btn.disabled = true;
|
||||
btn.innerHTML = '<span class="dubbo-spinner"></span>请求中...';
|
||||
|
||||
// 确保右栏响应面板存在
|
||||
var statusEl = document.getElementById('dubboStatus');
|
||||
var contentEl = document.getElementById('dubboRespContent');
|
||||
var emptyEl = document.querySelector('.dubbo-response-panel-empty');
|
||||
if (emptyEl) emptyEl.style.display = 'none';
|
||||
if (!contentEl) {
|
||||
var rightCol = document.querySelector('.dubbo-modal-right');
|
||||
if (rightCol) {
|
||||
rightCol.innerHTML = '<div class="dubbo-response-panel" id="dubboResponsePanel">' +
|
||||
'<div class="dubbo-response-panel-header"><span>响应结果</span><span class="dubbo-response-status" id="dubboStatus"></span></div>' +
|
||||
'<pre class="dubbo-response-content" id="dubboRespContent"></pre></div>';
|
||||
contentEl = document.getElementById('dubboRespContent');
|
||||
statusEl = document.getElementById('dubboStatus');
|
||||
}
|
||||
}
|
||||
statusEl.textContent = '请求中...';
|
||||
statusEl.className = 'dubbo-response-status';
|
||||
contentEl.textContent = '';
|
||||
|
||||
// 去除旧的 time 行
|
||||
var oldTime = document.querySelector('.dubbo-response-content-time');
|
||||
if (oldTime) oldTime.remove();
|
||||
|
||||
var headers = defaultHeaders();
|
||||
var evtEnv = document.getElementById('dubboEvtEnv') ? document.getElementById('dubboEvtEnv').value : 'DEV';
|
||||
headers['Event-Context'] = buildEventContext(body.tnt_id, body.env, evtEnv);
|
||||
headers['X-Gateway-Url'] = gwUrl;
|
||||
|
||||
// 走本地 Java 代理,避免浏览器 CORS/网络限制
|
||||
fetch(DUBBO_PROXY_URL, {
|
||||
method: 'POST',
|
||||
headers: headers,
|
||||
body: JSON.stringify(body)
|
||||
}).then(function(res) {
|
||||
var statusOk = res.ok;
|
||||
var statusText = res.status + ' ' + res.statusText;
|
||||
statusEl.textContent = statusText;
|
||||
statusEl.className = 'dubbo-response-status ' + (statusOk ? 'ok' : 'err');
|
||||
return res.text().then(function(txt) {
|
||||
var displayText;
|
||||
try {
|
||||
var j = JSON.parse(txt);
|
||||
displayText = JSON.stringify(j, null, 2);
|
||||
} catch(e) {
|
||||
displayText = txt;
|
||||
}
|
||||
contentEl.textContent = displayText;
|
||||
// 保存响应结果到 localStorage
|
||||
saveDubboResponse(statusText, displayText, statusOk);
|
||||
// 追加时间戳显示
|
||||
var timeEl = document.querySelector('.dubbo-response-content-time');
|
||||
if (timeEl) timeEl.remove();
|
||||
var timeHtml = '<div class="dubbo-response-content-time">📅 ' + formatLocalDateTime() + '</div>';
|
||||
contentEl.insertAdjacentHTML('beforebegin', timeHtml);
|
||||
btn.disabled = false;
|
||||
btn.textContent = '▶ 发送请求';
|
||||
});
|
||||
}).catch(function(err) {
|
||||
var errMsg = err.message || String(err);
|
||||
statusEl.textContent = '网络错误';
|
||||
statusEl.className = 'dubbo-response-status err';
|
||||
contentEl.textContent = errMsg;
|
||||
saveDubboResponse('网络错误', errMsg, false);
|
||||
btn.disabled = false;
|
||||
btn.textContent = '▶ 发送请求';
|
||||
});
|
||||
}
|
||||
|
||||
/** 复制为 cURL */
|
||||
function copyDubboCurl() {
|
||||
var body = buildDubboBody();
|
||||
if (!body) return;
|
||||
var gwUrl = document.getElementById('dubboGwUrl') ? document.getElementById('dubboGwUrl').value : getGwUrl();
|
||||
var headers = defaultHeaders();
|
||||
var evtEnv = document.getElementById('dubboEvtEnv') ? document.getElementById('dubboEvtEnv').value : 'DEV';
|
||||
headers['Event-Context'] = buildEventContext(body.tnt_id, body.env, evtEnv);
|
||||
var curl = 'curl -X POST \\\n ' + JSON.stringify(gwUrl) + ' \\\n';
|
||||
for (var k in headers) {
|
||||
curl += ' -H ' + JSON.stringify(k + ': ' + headers[k]) + ' \\\n';
|
||||
}
|
||||
curl += ' -d ' + JSON.stringify(JSON.stringify(body, null, 2));
|
||||
copyToClip(curl, null);
|
||||
showToast('cURL 已复制');
|
||||
}
|
||||
|
||||
/** 复制请求体 JSON */
|
||||
function copyDubboBody() {
|
||||
var body = buildDubboBody();
|
||||
if (!body) return;
|
||||
copyToClip(JSON.stringify(body, null, 2), null);
|
||||
showToast('请求体已复制');
|
||||
}
|
||||
|
||||
/** Toast 提示 */
|
||||
function showToast(msg) {
|
||||
var t = document.getElementById('dubboToast');
|
||||
if (!t) { t = document.createElement('div'); t.id = 'dubboToast'; t.style.cssText = 'position:fixed;bottom:32px;left:50%;transform:translateX(-50%);background:#1d2129;color:#fff;padding:8px 24px;border-radius:6px;font-size:13px;z-index:99999;pointer-events:none;transition:opacity .3s'; document.body.appendChild(t); }
|
||||
t.textContent = msg;
|
||||
t.style.opacity = '1';
|
||||
clearTimeout(t._tid);
|
||||
t._tid = setTimeout(function(){ t.style.opacity = '0'; }, 1800);
|
||||
}
|
||||
|
||||
// 修复:track 最后点击的 dubbo card
|
||||
document.addEventListener('click', function(e) {
|
||||
var card = e.target.closest('.method-card[data-dubbo-interface]');
|
||||
if (card) window._dubboActiveCard = card;
|
||||
});
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user