深入解析多平台API集成:高效模块化架构设计指南
深入解析多平台API集成高效模块化架构设计指南【免费下载链接】Online-disk-direct-link-download-assistant一个基于 JavaScript 的网盘文件下载地址获取工具。基于【网盘直链下载助手】修改 支持 百度网盘 / 阿里云盘 / 中国移动云盘 / 天翼云盘 / 迅雷云盘 / 夸克网盘 / UC网盘 / 123云盘 八大网盘项目地址: https://gitcode.com/GitHub_Trending/on/Online-disk-direct-link-download-assistantLinkSwift是一个基于JavaScript的网盘文件下载地址获取工具支持百度网盘、阿里云盘、中国移动云盘、天翼云盘、迅雷云盘、夸克网盘、UC网盘、123云盘等八大主流网盘平台。该项目采用模块化架构设计通过灵活的配置系统和API适配策略实现了对异构网盘平台的高效解析与下载功能。技术背景与挑战分析多平台API异构性挑战现代网盘平台采用多样化的API设计模式为开发者带来了显著的技术挑战。百度网盘基于RESTful接口设计阿里云盘采用GraphQL架构而移动云盘则使用传统的HTTP接口。这种异构性要求解析工具必须具备高度灵活的适配能力。主要技术挑战包括API协议差异各平台使用不同的请求/响应格式认证机制多样OAuth2.0、JWT令牌、Cookie验证等混合使用安全策略复杂请求签名、时间戳验证、频率限制等多层防护数据格式不统一JSON嵌套、GraphQL响应、XML等多种数据格式性能瓶颈与优化空间传统网盘下载方案面临的主要性能瓶颈性能维度传统方案LinkSwift优化方案解析速度3-5秒/文件0.5-1秒/文件并发处理单线程串行异步并发解析缓存机制无或简单缓存多层智能缓存错误恢复简单重试智能降级策略核心架构设计思路模块化分层架构LinkSwift采用分层架构设计将复杂功能分解为独立的处理单元├── 用户界面层 (UI Layer) │ ├── DOM注入模块 │ ├── 按钮生成器 │ └── 样式管理器 ├── 业务逻辑层 (Business Layer) │ ├── 平台检测引擎 │ ├── API调用协调器 │ └── 链接解析处理器 ├── 适配器层 (Adapter Layer) │ ├── 百度网盘适配器 │ ├── 阿里云盘适配器 │ ├── 移动云盘适配器 │ └── 其他平台适配器 └── 配置管理层 (Config Layer) ├── 平台配置文件 ├── 主题样式配置 └── 用户偏好设置配置文件驱动的平台适配每个网盘平台都有独立的JSON配置文件实现高度解耦的平台适配// 配置文件示例config/ali.json { platform: aliyun, api_endpoints: { file_list: https://api.aliyundrive.com/v2/file/list, download_token: https://api.aliyundrive.com/v2/file/download, direct_link: https://api.aliyundrive.com/v2/file/get_download_url }, authentication: { type: jwt, token_refresh_interval: 3600, signature_algorithm: HMAC-SHA256 }, rate_limiting: { requests_per_minute: 60, burst_limit: 10 } }关键技术实现细节异步处理与并发控制项目采用Promise链和async/await实现高效的异步操作class DownloadManager { constructor(maxConcurrent 5) { this.maxConcurrent maxConcurrent; this.activeDownloads 0; this.queue []; } async addDownload(task) { return new Promise((resolve, reject) { this.queue.push({ task, resolve, reject }); this.processQueue(); }); } async processQueue() { if (this.activeDownloads this.maxConcurrent || this.queue.length 0) { return; } this.activeDownloads; const { task, resolve, reject } this.queue.shift(); try { const result await this.executeDownload(task); resolve(result); } catch (error) { reject(error); } finally { this.activeDownloads--; this.processQueue(); } } async executeDownload(task) { // 执行下载任务的具体逻辑 const { platform, fileId } task; const adapter this.getAdapter(platform); return await adapter.downloadFile(fileId); } }智能平台检测机制平台检测采用多维度识别策略确保准确识别当前访问的网盘class PlatformDetector { detectPlatform() { const detectionMethods [ this.detectByURL(), this.detectByDOM(), this.detectByMeta(), this.detectByScript() ]; for (const method of detectionMethods) { const platform method(); if (platform) { return platform; } } return unknown; } detectByURL() { const url window.location.href; const platformPatterns { baidu: /pan\.baidu\.com/, aliyun: /aliyundrive\.com|alipan\.com/, quark: /quark\.cn/, tianyi: /cloud\.189\.cn/, xunlei: /pan\.xunlei\.com/, yidong: /cloud\.10086\.cn/ }; for (const [platform, pattern] of Object.entries(platformPatterns)) { if (pattern.test(url)) { return platform; } } return null; } detectByDOM() { // 通过DOM元素特征识别平台 const domSelectors { baidu: .pan-header, .file-list, aliyun: .drive-header, .file-item, quark: .quark-header, .file-container }; for (const [platform, selector] of Object.entries(domSelectors)) { if (document.querySelector(selector)) { return platform; } } return null; } }性能优化策略多层缓存机制设计class CacheManager { constructor() { this.memoryCache new Map(); this.localStorageCache window.localStorage; this.sessionStorageCache window.sessionStorage; this.defaultTTL 300000; // 5分钟 } async getWithCache(key, fetchFunction, ttl this.defaultTTL) { // 1. 检查内存缓存 const memoryItem this.memoryCache.get(key); if (memoryItem Date.now() memoryItem.expiry) { return memoryItem.value; } // 2. 检查sessionStorage缓存 const sessionItem this.sessionStorageCache.getItem(key); if (sessionItem) { const { value, expiry } JSON.parse(sessionItem); if (Date.now() expiry) { this.memoryCache.set(key, { value, expiry }); return value; } } // 3. 检查localStorage缓存 const localItem this.localStorageCache.getItem(key); if (localItem) { const { value, expiry } JSON.parse(localItem); if (Date.now() expiry) { this.memoryCache.set(key, { value, expiry }); this.sessionStorageCache.setItem(key, JSON.stringify({ value, expiry })); return value; } } // 4. 从源头获取并缓存 const freshValue await fetchFunction(); const expiry Date.now() ttl; this.memoryCache.set(key, { value: freshValue, expiry }); this.sessionStorageCache.setItem(key, JSON.stringify({ value: freshValue, expiry })); this.localStorageCache.setItem(key, JSON.stringify({ value: freshValue, expiry })); return freshValue; } }网络请求优化技术安全机制设计多层安全防护体系class SecurityManager { constructor() { this.requestSigner new RequestSigner(); this.tokenManager new TokenManager(); this.rateLimiter new RateLimiter(); } async secureRequest(url, options {}) { // 1. 令牌管理 const token await this.tokenManager.getValidToken(); if (!token) { throw new Error(Authentication failed); } // 2. 请求签名 const timestamp Date.now(); const nonce this.generateNonce(); const signature this.requestSigner.sign({ url, method: options.method || GET, timestamp, nonce, body: options.body }); // 3. 频率限制检查 if (!this.rateLimiter.canRequest(url)) { throw new Error(Rate limit exceeded); } // 4. 构造安全请求 const secureOptions { ...options, headers: { ...options.headers, Authorization: Bearer ${token}, X-Timestamp: timestamp, X-Nonce: nonce, X-Signature: signature } }; return fetch(url, secureOptions); } generateNonce() { return crypto.randomUUID(); } }错误处理与重试策略class ErrorHandler { static async withRetry(operation, maxRetries 3) { let lastError; for (let attempt 1; attempt maxRetries; attempt) { try { return await operation(); } catch (error) { lastError error; if (!this.shouldRetry(error)) { break; } if (attempt maxRetries) { const delay this.calculateRetryDelay(attempt, error); await this.sleep(delay); } } } throw lastError; } static shouldRetry(error) { const retryableErrors [ NETWORK_ERROR, TIMEOUT, RATE_LIMITED, SERVER_ERROR ]; return retryableErrors.includes(error.code) || error.message.includes(timeout) || error.message.includes(network); } static calculateRetryDelay(attempt, error) { // 指数退避算法 const baseDelay 1000; // 1秒 const maxDelay 30000; // 30秒 let delay baseDelay * Math.pow(2, attempt - 1); // 针对不同错误类型调整延迟 if (error.code RATE_LIMITED) { delay Math.max(delay, 5000); // 最少5秒 } return Math.min(delay, maxDelay); } static sleep(ms) { return new Promise(resolve setTimeout(resolve, ms)); } }扩展性与维护性插件化架构设计项目采用插件化设计便于添加新的网盘平台支持class PluginManager { constructor() { this.plugins new Map(); this.loadPlugins(); } async loadPlugins() { // 动态加载平台适配器 const pluginModules [ baidu-adapter, aliyun-adapter, quark-adapter, tianyi-adapter, xunlei-adapter, yidong-adapter ]; for (const moduleName of pluginModules) { try { const plugin await this.loadPlugin(moduleName); this.plugins.set(plugin.platform, plugin); } catch (error) { console.warn(Failed to load plugin ${moduleName}:, error); } } } getAdapter(platform) { const adapter this.plugins.get(platform); if (!adapter) { throw new Error(No adapter found for platform: ${platform}); } return adapter; } async registerPlugin(platform, adapterClass) { const adapter new adapterClass(); await adapter.initialize(); this.plugins.set(platform, adapter); } }配置热更新机制class ConfigManager { constructor() { this.configs new Map(); this.watchers new Map(); this.loadConfigs(); } async loadConfigs() { const configFiles [ config/ali.json, config/config.json, config/quark.json, config/tianyi.json, config/xunlei.json, config/yidong.json ]; for (const filePath of configFiles) { try { const response await fetch(filePath); const config await response.json(); this.configs.set(config.platform, config); // 设置配置变更监听 this.setupConfigWatcher(filePath, config.platform); } catch (error) { console.error(Failed to load config ${filePath}:, error); } } } setupConfigWatcher(filePath, platform) { if (typeof chrome ! undefined chrome.runtime chrome.runtime.onMessage) { chrome.runtime.onMessage.addListener((message, sender, sendResponse) { if (message.type CONFIG_UPDATED message.platform platform) { this.reloadConfig(filePath, platform); } }); } } async reloadConfig(filePath, platform) { try { const response await fetch(${filePath}?t${Date.now()}); const config await response.json(); this.configs.set(platform, config); console.log(Config updated for platform: ${platform}); } catch (error) { console.error(Failed to reload config ${filePath}:, error); } } getConfig(platform) { return this.configs.get(platform) || this.getDefaultConfig(); } }实践应用案例多下载器集成方案LinkSwift支持多种下载器的无缝集成提供灵活的文件下载方案class DownloaderIntegration { constructor() { this.downloaders { idm: new IDMDownloader(), aria2: new Aria2Downloader(), motrix: new MotrixDownloader(), curl: new CurlDownloader(), wget: new WgetDownloader() }; } async downloadFile(fileInfo, downloaderType auto) { // 自动选择最优下载器 if (downloaderType auto) { downloaderType this.detectOptimalDownloader(); } const downloader this.downloaders[downloaderType]; if (!downloader) { throw new Error(Unsupported downloader: ${downloaderType}); } // 配置下载参数 const downloadConfig { url: fileInfo.directUrl, filename: fileInfo.name, size: fileInfo.size, headers: fileInfo.headers || {}, referrer: fileInfo.referrer || window.location.href }; // 执行下载 return await downloader.download(downloadConfig); } detectOptimalDownloader() { const os this.detectOS(); const downloaderMap { windows: idm, macos: aria2, linux: aria2, android: adm }; return downloaderMap[os] || aria2; } detectOS() { const userAgent navigator.userAgent.toLowerCase(); if (userAgent.includes(win)) return windows; if (userAgent.includes(mac)) return macos; if (userAgent.includes(linux)) return linux; if (userAgent.includes(android)) return android; return unknown; } }批量文件处理优化class BatchProcessor { constructor(maxConcurrent 3) { this.maxConcurrent maxConcurrent; this.processingQueue []; this.results new Map(); } async processFiles(files, processor) { const chunks this.chunkArray(files, this.maxConcurrent); for (const chunk of chunks) { const promises chunk.map(async (file, index) { try { const result await processor(file); this.results.set(file.id, { success: true, data: result }); return { success: true, fileId: file.id }; } catch (error) { this.results.set(file.id, { success: false, error: error.message }); return { success: false, fileId: file.id, error: error.message }; } }); await Promise.all(promises); } return Array.from(this.results.values()); } chunkArray(array, size) { const chunks []; for (let i 0; i array.length; i size) { chunks.push(array.slice(i, i size)); } return chunks; } getProgress() { const total this.results.size; const completed Array.from(this.results.values()).filter(r r.success).length; return { completed, total, percentage: (completed / total * 100).toFixed(2) }; } }未来技术展望AI智能解析技术未来的发展方向包括利用机器学习算法智能识别网盘页面结构class AIParser { constructor() { this.model this.loadModel(); this.featureExtractor new FeatureExtractor(); } async analyzePageStructure() { // 提取页面特征 const features this.featureExtractor.extract({ url: window.location.href, domStructure: this.extractDOMFeatures(), networkRequests: this.analyzeNetworkPatterns(), scriptPatterns: this.detectScriptSignatures() }); // 使用AI模型预测平台类型 const prediction await this.model.predict(features); return { platform: prediction.platform, confidence: prediction.confidence, pageType: prediction.pageType, suggestedSelectors: prediction.selectors }; } extractDOMFeatures() { return { buttonCount: document.querySelectorAll(button).length, inputCount: document.querySelectorAll(input).length, fileElements: document.querySelectorAll([data-file]).length, specificClasses: Array.from(document.querySelectorAll(*[class])) .map(el el.className) .filter(className className.includes(file) || className.includes(download) || className.includes(pan)) }; } }分布式解析架构性能监控与自动化优化class PerformanceMonitor { constructor() { this.metrics { parseTime: [], successRate: [], cacheHitRate: [], errorCount: [] }; this.startTime Date.now(); } recordMetric(type, value) { if (!this.metrics[type]) { this.metrics[type] []; } this.metrics[type].push({ timestamp: Date.now(), value: value }); // 保持最近1000个数据点 if (this.metrics[type].length 1000) { this.metrics[type].shift(); } } getPerformanceReport() { const now Date.now(); const duration now - this.startTime; return { uptime: this.formatDuration(duration), averageParseTime: this.calculateAverage(parseTime), successRate: this.calculateSuccessRate(), cacheHitRate: this.calculateCacheHitRate(), totalRequests: this.metrics.parseTime.length, recentPerformance: this.getRecentMetrics(300000) // 最近5分钟 }; } calculateAverage(metricType) { const values this.metrics[metricType]; if (!values || values.length 0) return 0; const sum values.reduce((acc, item) acc item.value, 0); return sum / values.length; } calculateSuccessRate() { const total this.metrics.parseTime.length; const errors this.metrics.errorCount.length; return total 0 ? ((total - errors) / total * 100).toFixed(2) : 100; } formatDuration(ms) { const seconds Math.floor(ms / 1000); const minutes Math.floor(seconds / 60); const hours Math.floor(minutes / 60); const days Math.floor(hours / 24); if (days 0) return ${days}d ${hours % 24}h; if (hours 0) return ${hours}h ${minutes % 60}m; if (minutes 0) return ${minutes}m ${seconds % 60}s; return ${seconds}s; } }LinkSwift项目通过模块化架构设计、智能平台适配、多层缓存机制和强大的错误处理策略为多平台网盘解析提供了高效稳定的解决方案。其技术实现展示了现代前端工程在复杂业务场景下的最佳实践为开发者处理异构API集成提供了宝贵的技术参考。【免费下载链接】Online-disk-direct-link-download-assistant一个基于 JavaScript 的网盘文件下载地址获取工具。基于【网盘直链下载助手】修改 支持 百度网盘 / 阿里云盘 / 中国移动云盘 / 天翼云盘 / 迅雷云盘 / 夸克网盘 / UC网盘 / 123云盘 八大网盘项目地址: https://gitcode.com/GitHub_Trending/on/Online-disk-direct-link-download-assistant创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

相关新闻

AtlasOS:3步实现Windows性能提升60%的终极优化方案

AtlasOS:3步实现Windows性能提升60%的终极优化方案

AtlasOS:3步实现Windows性能提升60%的终极优化方案 【免费下载链接】Atlas 🚀 An open and lightweight modification to Windows, designed to optimize performance, privacy and usability. 项目地址: https://gitcode.com/GitHub_Trending/atlas1/…

2026/10/7 16:41:54 阅读更多 →
人工神经网络算法实战指南:从输入表征到模型优化的系统性决策框架

人工神经网络算法实战指南:从输入表征到模型优化的系统性决策框架

1. 从“黑箱”到“白盒”:为什么我们需要系统性地总结ANN算法?如果你在搜索引擎里输入“人工神经网络”,大概率会得到一堆充斥着复杂公式和抽象概念的文章,或者直接跳转到某个深度学习框架的教程。这就像你想学做一道菜&#xff0…

2026/9/15 6:59:21 阅读更多 →
出口罗马尼亚产品认证清单,各类品类合规认证要求一览

出口罗马尼亚产品认证清单,各类品类合规认证要求一览

罗马尼亚沿用整套欧盟产品合规体系,涵盖通用强制认证、细分行业资质、包装物流规范及涉外文件认证。外贸企业出海需配齐对应资质,规避通关受阻、市场合规处罚等风险。一、欧盟通用强制性认证CE认证是进入罗马尼亚及欧盟市场的基础门槛,适用范…

2026/10/13 15:34:33 阅读更多 →

最新新闻

从二进制到可编辑网格:owmdl模型在Blender中的导入解析

从二进制到可编辑网格:owmdl模型在Blender中的导入解析

简介:基于Blender的《守望先锋》owmdl模型导入插件源码包,主要面向需要在Blender中查看和编辑这一专有游戏格式的玩家、三维建模学习者和游戏资产研究者。插件通过io_scene_owm扩展Blender的导入能力,可解析模型网格、纹理、骨骼与动画数据&a…

2026/10/14 12:09:12 阅读更多 →
金融表格抽取新思路:用图神经网络还原复杂表格结构

金融表格抽取新思路:用图神经网络还原复杂表格结构

我拿到这个标题的第一反应是:终于有人认真对待金融表格抽取这件事了。业界做文档解析的不少,但大多数都把表格当成图像里的一个矩形区域,“识别出来了”就算交差。可在金融场景里,表格抽取的终点根本不是“框出表格”,…

2026/10/14 12:09:12 阅读更多 →
TCP窗口扩张系数WSCALE原理与实战配置指南

TCP窗口扩张系数WSCALE原理与实战配置指南

1. 什么是TCP窗口扩张系数(WSCALE)?它到底在解决什么问题? 你可能在抓包分析时见过TCP三次握手的SYN报文里,选项字段中赫然写着“Window Scale: 7”或者“WS8”,但未必真正理解它背后那场持续二十多年的带宽…

2026/10/14 12:09:12 阅读更多 →
零代码RPA工具灵梭:办公自动化实战指南

零代码RPA工具灵梭:办公自动化实战指南

1. 项目概述 "灵梭RPA"是一款面向非技术人员的办公自动化工具,它让普通办公人员也能像专业人士一样创建自动化流程。我使用过市面上十几款RPA工具,发现大多数都需要编程基础,而灵梭RPA真正做到了"零代码"操作&#xff0c…

2026/10/14 12:09:12 阅读更多 →
Android Compose AI组件库:从架构设计到流式渲染的完整实践

Android Compose AI组件库:从架构设计到流式渲染的完整实践

1. 从命令式到声明式:AI组件库的架构选型思考Android 原生 UI 开发在过去很长一段时间里,基本是ViewXML的天下。写一个聊天界面,你得先定义RecyclerView,再写Adapter,再处理ViewHolder,再手动 diff 刷新&am…

2026/10/14 12:09:12 阅读更多 →
Oracle Long、Raw、Blob字段类型读写与避坑指南

Oracle Long、Raw、Blob字段类型读写与避坑指南

简介:这是一份针对Oracle数据库特殊字段类型的实用技术文档,面向需要处理Long、Raw、Blob等非标准字段的数据库开发、运维人员及.NET技术从业者,系统讲解了三类字段的存储上限、适用场景与选型建议(如Long在10g后宜改用Clob&#…

2026/10/14 12:08:11 阅读更多 →

日新闻

AnyPS5通用适配层设计:兼容性与性能的工程取舍

AnyPS5通用适配层设计:兼容性与性能的工程取舍

1. 从"AnyPS5"这个名字说起:它到底想解决什么问题第一次看到"AnyPS5"这个标题,我脑子里冒出来的第一个念头是:这大概率是一个围绕"跨平台运行"或者"通用适配"做文章的项目。名字里的"Any"…

2026/10/14 0:00:41 阅读更多 →
Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

1. 项目概述:Flutter跨平台二手交易应用开发作为一名长期从事移动应用开发的工程师,我最近完成了一个基于Flutter框架的二手物品交易平台项目。这个项目最吸引我的地方在于它完美结合了Flutter的跨平台优势与HarmonyOS的生态特性,实现了"…

2026/10/14 0:00:41 阅读更多 →
DFS算法解决飞机降落调度问题实战

DFS算法解决飞机降落调度问题实战

1. 项目背景与核心思路飞机降落问题是一个经典的调度优化场景,也是算法竞赛和面试中的高频题目。这个题目要求我们合理安排多架飞机的降落顺序和时间,确保所有飞机都能安全着陆而不发生冲突。在实际航空管制中,这类问题直接关系到飞行安全和机…

2026/10/14 0:00:41 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/14 3:30:28 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/13 16:22:04 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/14 3:29:58 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 22:01:27 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 14:57:39 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/14 7:53:34 阅读更多 →