first commit

This commit is contained in:
2026-02-17 13:06:23 +08:00
commit 7cbd3d061d
349 changed files with 126558 additions and 0 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 542 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

+763
View File
@@ -0,0 +1,763 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Swagger UI</title>
<link rel="stylesheet" type="text/css" href="./swagger-ui.css" />
<link rel="icon" type="image/png" href="./favicon-32x32.png" sizes="32x32" />
<link rel="icon" type="image/png" href="./favicon-16x16.png" sizes="16x16" />
<style>
/* Reset */
html,
body {
box-sizing: border-box;
margin: 0;
padding: 0;
overflow-y: scroll;
}
*,
*::before,
*::after {
box-sizing: inherit;
}
body {
padding: 20px;
font-family: Arial, sans-serif;
background-color: #f8f9fa;
}
/* Swagger UI Container */
#swagger-ui {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
background-color: rgba(255, 255, 255, 0.9);
border-radius: 10px;
box-shadow: 0 0 20px rgba(0, 0, 0, 0.1);
}
/* Floating Window */
.floating-window {
position: fixed;
bottom: 20px;
right: 20px;
padding: 15px 25px;
background-color: rgba(255, 255, 255, 0.8);
border-radius: 10px;
box-shadow: 0 0 20px rgba(0, 0, 0, 0.1);
z-index: 2;
animation: glow 2s infinite alternate;
}
.close-btn {
position: absolute;
top: 5px;
right: 5px;
font-size: 18px;
color: #e74c3c;
cursor: pointer;
}
/* Form and Input Styling */
#xxcaibi {
background-color: #f5f5f5;
border: 1px solid #ddd;
border-radius: 8px;
padding: 20px;
margin: 20px auto;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.05);
width: 90%;
}
.xxcaibi-group {
display: flex;
align-items: center;
margin-bottom: 15px;
}
.xxcaibi-label {
color: #555;
font-size: 14px;
width: 120px;
}
.xxcaibi-input,
.xxcaibi-switch {
flex: 1;
padding: 8px;
border: 1px solid #ccc;
border-radius: 4px;
font-size: 14px;
}
.xxcaibi-input:focus {
border-color: #4CAF50;
outline: none;
}
.xxcaibi-switch {
width: 40px;
height: 20px;
appearance: none;
background-color: #ccc;
/* 默认状态下的开关背景颜色 */
outline: none;
border-radius: 20px;
position: relative;
cursor: pointer;
transition: background-color 0.3s ease;
}
.xxcaibi-switch:checked {
background-color: #4CAF50;
}
.xxcaibi-switch::before {
content: "";
position: absolute;
width: 18px;
height: 18px;
border-radius: 50%;
background-color: white;
top: 1px;
left: 1px;
transition: transform 0.3s ease;
}
.xxcaibi-switch:checked::before {
transform: translateX(20px);
}
/* Radio Button Group */
.radio-group {
display: flex;
justify-content: space-between;
width: 80%;
}
.radio-group label {
display: flex;
align-items: center;
}
.radio-group input[type="radio"] {
margin-right: 10px;
transform: scale(1.5);
}
.radio-group input[type="radio"]:focus {
outline: none;
}
/* Glow Animation */
@keyframes glow {
from {
box-shadow: 0 0 10px rgba(0, 255, 255, 0.4);
}
to {
box-shadow: 0 0 20px rgba(0, 255, 255, 0.7);
}
}
/* Link Card */
.card {
position: absolute;
top: 0;
left: 0;
width: 100px;
z-index: 2;
transition: transform 0.5s ease, filter 0.5s ease;
}
.card:hover {
transform: scale(1.2);
filter: drop-shadow(0 0 10px rgba(0, 255, 255, 0.7));
}
/* Card Movement Animation */
@keyframes cardMove {
0% {
transform: translate(0, 0);
}
50% {
transform: translate(50vw, 50vh);
}
100% {
transform: translate(0, 0);
}
}
.base-url {
display: none;
}
/* Telegram Link Container */
.tg-link {
display: flex;
margin-top: 20px;
}
/* Telegram Button */
.tg-button {
display: flex;
align-items: center;
padding: 10px 20px;
background-color: #5cadff !important;
color: white !important;
font-size: 16px;
text-decoration: none;
border-radius: 30px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
transition: background-color 0.3s ease, transform 0.3s ease;
}
/* Telegram Button Hover Effect */
.tg-button:hover {
background-color: #007ab8 !important;
transform: translateY(-2px);
}
/* Telegram Icon */
.tg-icon {
width: 24px;
height: 24px;
margin-right: 10px;
}
.container {
display: flex;
justify-content: center;
align-items: center;
margin-bottom: 20px;
width: 100%;
}
.container input[type="text"] {
flex: 1;
width: 300px;
/* 初始宽度 */
max-width: 500px;
padding: 10px;
border: 1px solid #ccc;
border-radius: 4px;
margin-right: 10px;
font-size: 16px;
}
.container button {
padding: 10px 15px;
border: none;
border-radius: 4px;
background-color: #007bff;
color: white;
font-size: 16px;
cursor: pointer;
transition: background-color 0.3s;
margin-left: 12px;
}
.container button:disabled {
background-color: #ccc;
cursor: not-allowed;
}
.container button:hover:not(:disabled) {
background-color: #0056b3;
}
#messages {
margin-top: 12px;
border: 1px solid #ccc;
border-radius: 4px;
padding: 10px;
max-height: 300px;
overflow-y: auto;
background-color: #f9f9f9;
}
</style>
</head>
<body>
<div id="xxcaibi" class="wrapper" style="display: none;">
<div class="xxcaibi-group">
<label for="admin-key" class="xxcaibi-label">ADMIN_KEY</label>
<input type="text" id="admin-key" class="xxcaibi-input" placeholder="管理员接口 ADMIN_KEY">
</div>
<div class="xxcaibi-group">
<label for="token-key" class="xxcaibi-label">TOKEN_KEY</label>
<input type="text" id="token-key" class="xxcaibi-input" placeholder="普通API TOKEN_KEY">
</div>
<div class="xxcaibi-group">
<label class="xxcaibi-label">Swagger 展开方式</label>
<div class="radio-group">
<label>
<input type="radio" name="expand-options" value="none" checked>不展开
</label>
<label>
<input type="radio" name="expand-options" value="list">只展开API
</label>
<label>
<input type="radio" name="expand-options" value="full">展开所有
</label>
</div>
</div>
<div class="xxcaibi-group">
<label for="copy-base-url" class="xxcaibi-label">是否复制根路径</label>
<input style="flex: none !important" type="checkbox" id="copy-base-url" class="xxcaibi-switch" checked>
<span style="margin-left: 20px;" id="BASE_URL"></span>
</div>
<h4 style="text-align: center;line-height: 1;margin-bottom: 10px">WS 客户端 (同步消息)</h4>
<div class="container">
<input id="urlInput" type="text" placeholder="输入 WebSocket URL" />
<button id="connectButton">连接</button>
<button id="disconnectButton" disabled>取消连接</button>
<button id="clearMessagesButton">清理消息</button>
</div>
<div id="messages"></div>
</div>
<script>
let socket;
document.getElementById('urlInput').value = window.location.origin.replace(/^http/, 'ws') + '/ws/GetSyncMsg?key='
// 更新按钮文本
function updateButton(button, text, isLoading) {
button.innerHTML = isLoading ? text + '...' : text;
button.disabled = isLoading;
}
// 连接按钮点击事件
document.getElementById('connectButton').onclick = function () {
const url = document.getElementById('urlInput').value;
// 如果已经有连接,先关闭
if (socket && socket.readyState === WebSocket.OPEN) {
socket.close();
}
// 更新按钮状态为连接中
updateButton(this, '连接中', true);
// 创建 WebSocket 连接
socket = new WebSocket(url);
// 连接成功时
socket.onopen = function () {
console.log('已连接到服务器');
document.getElementById('messages').innerHTML += '<p>已连接到服务器</p>';
updateButton(document.getElementById('connectButton'), '连接', false); // 重置连接按钮
document.getElementById('disconnectButton').disabled = false; // 启用取消连接按钮
document.getElementById('connectButton').disabled = true;
};
// 接收消息
socket.onmessage = function (event) {
console.log('收到来自服务器的消息:', event.data);
document.getElementById('messages').innerHTML += '<p>服务器: ' + event.data + '</p>';
};
// 连接关闭时
socket.onclose = function () {
console.log('与服务器的连接已关闭');
document.getElementById('messages').innerHTML += '<p>与服务器的连接已关闭</p>';
updateButton(document.getElementById('connectButton'), '连接', false); // 启用连接按钮
document.getElementById('disconnectButton').innerHTML = "取消连接",
document.getElementById('disconnectButton').disabled = true; // 启用连接按钮
document.getElementById('connectButton').disabled = false; // 启用连接按钮
};
// 处理错误
socket.onerror = function (error) {
console.error('WebSocket 错误:', error);
document.getElementById('messages').innerHTML += '<p>WebSocket 错误: ' + error.message + '</p>';
updateButton(document.getElementById('connectButton'), '连接', false); // 重置连接按钮
};
};
// 取消连接按钮点击事件
document.getElementById('disconnectButton').onclick = function () {
updateButton(this, '取消中', true); // 更新为取消中状态
if (socket) {
setTimeout(function () {
socket.close();
}, 30)
}
};
// 清理消息按钮点击事件
document.getElementById('clearMessagesButton').onclick = function () {
document.getElementById('messages').innerHTML = ''; // 清空消息区域
};
</script>
<div id="swagger-ui"></div>
<!-- Floating window -->
<div class="floating-window" style="display: none !important">
</div>
<script src="./swagger-ui-bundle.js" charset="UTF-8"></script>
<script src="./swagger-ui-standalone-preset.js" charset="UTF-8"></script>
<script src="./jquery-3.7.1.slim.min.js"></script>
<!-- 微信验证集成脚本 -->
<script src="./wechat-verify-integration.js"></script>
<script>
function closeDiv(element) {
element.parentElement.style.display = 'none';
}
function isAdminApi(pathKey) {
if (pathKey.startsWith("/admin/")) {
return true
}
return window.xxcaibi.adminUrls[pathKey]
}
const CustomPlugin = function (system) {
return {
statePlugins: {
spec: {
actions: {
updateQueryParam: (paramName, paramValue, isAdminKey = false) => (state) => {
const spec = state.specSelectors.specJson().toJS();
const paths = spec.paths;
Object.keys(paths).forEach(pathKey => {
if (!isAdminKey && isAdminApi(pathKey)) {
// 非 ADMIN_KEY 并且是 ADMIN 接口
return;
}
if (isAdminKey && !isAdminApi(pathKey)) {
// 是 ADMIN_KEY 并且 不是 ADMIN 接口
return;
}
Object.keys(paths[pathKey]).forEach(method => {
const params = paths[pathKey][method].parameters;
params.forEach(param => {
if (param.name === paramName && param.in === 'query') {
param.default = paramValue;
}
});
});
});
state.specActions.updateSpec(JSON.stringify(spec));
},
updateConfigs: (spec) => (state) => {
state.specActions.updateSpec(JSON.stringify(spec));
},
},
wrapActions: {
updateSpec: (oriAction, system) => (...args) => {
return oriAction(...args)
},
},
},
},
};
};
// 检查 Swagger-UI 内的 img.opblock-loading-animation 请求体节点是否加载完成
function checkSwaggerLoadingComplete(targetFunc) {
const targetNode = document.getElementById('swagger-ui');
const config = { childList: true, subtree: true };
const callback = function (mutationsList, observer) {
const loadingElements = targetNode.querySelectorAll('img.opblock-loading-animation');
if (loadingElements.length === 0) {
console.log('Swagger UI loaded successfully');
observer.disconnect(); // 停止观察
targetFunc && targetFunc();
}
};
const observer = new MutationObserver(callback);
setTimeout(function () {
$("#swagger-ui").append("&nbsp;")
}, 100)
observer.observe(targetNode, config); // DOM 节点无变化则不会触发监听
}
// 粘贴到系统剪切板1
function copy(text) {
if (navigator.clipboard && navigator.clipboard.writeText) {
// navigator.clipboard 只能在 HTTPS 环境使用; 旧版浏览器兼容性不好
navigator.clipboard.writeText(text).then(function () {
}).catch(function (error) {
console.error('Failed to copy text: ', error);
fallbackCopy(text);
});
} else {
fallbackCopy(text);
}
}
// 粘贴到系统剪切板2
function fallbackCopy(text) {
// 使用 textarea 标签 可以复制 pre 代码标签里的换行
let textArea = document.createElement("textarea");
textArea.value = text;
document.body.appendChild(textArea);
textArea.select();
try {
document.execCommand('copy');
} catch (err) {
console.error('Fallback copy failed: ', err);
}
document.body.removeChild(textArea);
}
// 复制 API 的路径即描述
function addCopy1(selector) {
let pathElements;
// opblock-summary-path opblock-summary-description
if (selector) {
pathElements = $(selector).parent().find(".opblock-summary-description");
} else {
pathElements = $(".opblock-summary-description");
addCopy2();
}
if (pathElements.length === 0) {
return;
}
console.log("API数量:", pathElements.length);
$(".opblock-summary-post").click(function () {
let status = $(this).children().first().attr("aria-expanded");
if (status === "true") {
return;
}
let curId = $(this).parent().attr("id");
// console.log(curId, status);
setTimeout(function () {
addCopy2(`#${curId}`);
}, 100);
});
pathElements.each(function (index, pathElement) {
pathElement = $(pathElement);
let isCopy = pathElement.attr("copy");
if (isCopy === "true") { // 已经添加过 copy 按钮
return;
}
pathElement.after(
$("<button>复制</button>").css("cursor", "pointer").css("color", "#FFF").css("background", "#2b85e4").css("border-color", "#2b85e4").css("padding", "4px 8px").css("border-radius", "4px").css("margin-right", "10px").click(function () {
// 获取接口文本
let api = this.parentNode.children[1].dataset.path;
let desc = this.parentNode.children[2].textContent;
let text = api + " " + desc;
if (window.xxcaibi.copy) {
text = window.xxcaibi.BASE_URL + text;
}
// console.log(api, desc)
// 调用函数,复制到剪切板
text = text.replace(/([^:])\/+/g, '$1/')
copy(text);
// 提示复制成功
$(this).text("复制成功!");
// 定时改变显示文字
setTimeout(() => {
$(this).text("复制");
}, 500);
return false;
})
);
pathElement.attr("copy", "true");
});
}
// 复制 POST 请求的 JSON 请求体
function addCopy2(selector) {
if (!selector) {
selector = "#swagger-ui"
}
let lis = $(selector + ' li[role="presentation"]');
if (lis.length < 2) {
return;
}
lis.each(function (index, element) {
if (index % 2 === 0) {
return;
}
element = $(element);
let isCopy = element.attr("copy");
if (isCopy === "true") { // 已经添加过 copy 按钮
return;
}
element.after(
$("<button>复制</button>").css("cursor", "pointer").css("color", "#FFF").css("background", "#515a6e").css("border-color", "#17233d").css("padding", "4px 10px").css("border-radius", "4px").css("margin-right", "10px").click(function () {
// 获取接口文本
let preTag = $(selector + " .highlight-code pre")[0];
if (!preTag) {
alert("无法复制");
return;
}
let text = preTag.textContent;
// 调用函数,复制到剪切板
copy(text);
// 提示复制成功
$(this).text("复制成功!");
// 定时改变显示文字
setTimeout(() => {
$(this).text("复制");
}, 500);
})
);
element.attr("copy", "true");
});
}
window.onload = function () {
// Initialize Swagger UI
const ui = SwaggerUIBundle({
url: "swagger.json",
dom_id: '#swagger-ui',
deepLinking: true,
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIStandalonePreset
],
plugins: [
SwaggerUIBundle.plugins.DownloadUrl,
CustomPlugin,
],
docExpansion: "none", // none(不展开) list(展开operations) full(全部展开)
onComplete: function () {
// Swagger-UI 渲染完成后回调; updateSpec 成功也会调用
// 但是如果数据太多会导致部分 HTML 元素在此时还未加载完成
console.log("Swagger UI onComplete...")
console.log("Swagger UI Version:", window.versions.swaggerUi.version)
parseBaseUrl();
addXXCaiBi();
checkSwaggerLoadingComplete(function () {
addCopy1();
addCopy2();
// Operations API 接口的点击事件
$(".opblock-tag").click(function () {
checkSwaggerLoadingComplete(function () {
addCopy1();
addCopy2();
});
});
});
},
// parameterMacro: function (operation, parameter) {
// // 展开 API 对应的 operations 时触发, 可以在此修改 指定参数的默认值
// console.log(operation, parameter);
// return "";
// },
});
window.ui = ui;
window.xxcaibi = {
"BASE_URL": "",
"copy": true,
"ADMIN_KEY": "",
"TOKEN_KEY": "",
"adminUrls": {
"/login/GenAuthKey": true,
"/login/GenAuthKey2": true,
},
}
// 监听状态变化
$('#copy-base-url').change(function () {
window.xxcaibi.copy = $(this).is(':checked');
// console.log(window.xxcaibi);
});
$('#admin-key').change(function () {
window.xxcaibi.ADMIN_KEY = $(this).val().trim();
// console.log(window.xxcaibi);
ui.specActions.updateQueryParam("key", window.xxcaibi.ADMIN_KEY, true);
});
$('#token-key').change(function () {
window.xxcaibi.TOKEN_KEY = $(this).val().trim();
// console.log(window.xxcaibi);
if (window.xxcaibi.TOKEN_KEY) {
document.getElementById('urlInput').value = window.location.origin.replace(/^http/, 'ws') + '/ws/GetSyncMsg?key=' + window.xxcaibi.TOKEN_KEY
}
ui.specActions.updateQueryParam("key", window.xxcaibi.TOKEN_KEY);
});
$('.radio-group input[type="radio"]').change(function () {
let value = $(this).val();
let configs = ui.getConfigs();
configs.docExpansion = value
// // 更新整个 UI 数据; 会刷新 UI 导致 onComplete 回调
// const spec = ui.specSelectors.specJson().toJS();
// ui.specActions.updateConfigs(spec);
// 只更新配置; 不会刷新 UI 导致 onComplete 回调
ui.configsActions.toggle();
checkSwaggerLoadingComplete(function () {
addCopy1();
addCopy2();
// Operations API 接口的点击事件
$(".opblock-tag").click(function () {
checkSwaggerLoadingComplete(function () {
addCopy1();
addCopy2();
});
});
}); // DOM 节点无变化不会触发监听
});
let parseBaseUrl = function () {
let config = window.ui.specSelectors.specJson().toJS(); // 获取 spec 对象
let basePath = config.basePath || ''; // 获取 basePath
basePath = "" ? basePath === "/" : basePath;
// 获取当前网址的协议、域名和端口
let protocol = window.location.protocol;
let hostname = window.location.hostname;
let port = window.location.port;
// 组合协议、域名和端口
const baseUrl = protocol + "//" + hostname + (port ? ":" + port : "") + basePath;
// 显示结果
console.log("BASE_URL:", baseUrl);
window.xxcaibi.BASE_URL = baseUrl;
}
let addXXCaiBi = function () {
$("#xxcaibi").insertAfter('.information-container:first').css('display', 'block');
// 判断 tg-link 不存在
if (!document.getElementsByClassName("tg-link").length) {
$(".info__contact").append(
'<div class="tg-link">' +
'<a href="https://t.me/wx_ipad" target="_blank" class="tg-button">' +
'<img src="https://upload.wikimedia.org/wikipedia/commons/8/82/Telegram_logo.svg" alt="Telegram Logo" class="tg-icon">' +
'加入 Telegram 群' +
'</a>' +
'</div>'
);
}
$("#BASE_URL").html("BASE_URL: " + window.xxcaibi.BASE_URL);
}
};
</script>
</body>
</html>
File diff suppressed because one or more lines are too long
+75
View File
@@ -0,0 +1,75 @@
<!doctype html>
<html lang="en-US">
<head>
<title>Swagger UI: OAuth2 Redirect</title>
</head>
<body>
<script>
'use strict';
function run () {
var oauth2 = window.opener.swaggerUIRedirectOauth2;
var sentState = oauth2.state;
var redirectUrl = oauth2.redirectUrl;
var isValid, qp, arr;
if (/code|token|error/.test(window.location.hash)) {
qp = window.location.hash.substring(1);
} else {
qp = location.search.substring(1);
}
arr = qp.split("&");
arr.forEach(function (v,i,_arr) { _arr[i] = '"' + v.replace('=', '":"') + '"';});
qp = qp ? JSON.parse('{' + arr.join() + '}',
function (key, value) {
return key === "" ? value : decodeURIComponent(value);
}
) : {};
isValid = qp.state === sentState;
if ((
oauth2.auth.schema.get("flow") === "accessCode" ||
oauth2.auth.schema.get("flow") === "authorizationCode" ||
oauth2.auth.schema.get("flow") === "authorization_code"
) && !oauth2.auth.code) {
if (!isValid) {
oauth2.errCb({
authId: oauth2.auth.name,
source: "auth",
level: "warning",
message: "Authorization may be unsafe, passed state was changed in server Passed state wasn't returned from auth server"
});
}
if (qp.code) {
delete oauth2.state;
oauth2.auth.code = qp.code;
oauth2.callback({auth: oauth2.auth, redirectUrl: redirectUrl});
} else {
let oauthErrorMsg;
if (qp.error) {
oauthErrorMsg = "["+qp.error+"]: " +
(qp.error_description ? qp.error_description+ ". " : "no accessCode received from the server. ") +
(qp.error_uri ? "More info: "+qp.error_uri : "");
}
oauth2.errCb({
authId: oauth2.auth.name,
source: "auth",
level: "error",
message: oauthErrorMsg || "[Authorization failed]: no accessCode received from the server"
});
}
} else {
oauth2.callback({auth: oauth2.auth, token: qp, isValid: isValid, redirectUrl: redirectUrl});
}
window.close();
}
window.addEventListener('DOMContentLoaded', function () {
run();
});
</script>
</body>
</html>
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
View File
File diff suppressed because one or more lines are too long
+5989
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long