油猴脚本拦截fetch和xhr请求,实现修改服务端接口功能

使用说明

  1. 修改第7行的match
  2. 第15~34行,是我自己的业务逻辑,需要改成你自己的
  3. 第42行的API_CONFIG定义了每个接口的匹配方式和回调函数,也需要改成你自己的业务逻辑
  4. 修改完成后,复制到油猴脚本中即可

代码

javascript 复制代码
// ==UserScript==
// @name         接口修改脚本
// @namespace    http://tampermonkey.net/
// @version      1.1
// @description  通用接口拦截和响应修改脚本,支持自定义配置
// @author       You
// @match        *://*/*
// @grant        unsafeWindow
// @run-at       document-start
// ==/UserScript==
(function () {
    'use strict';

    // 测试环境账号与生产环境标识(业务场景,你可以自定义)
    const currentAccount = "acc1";

    const accountConf = {
        "acc1": {
            "总人数": 400,
            "deptMemberCount": {
                "部门1": 150,
                "部门2": 80,
            },
        },
        "acc2": {
            "总人数": 3574,
            "deptMemberCount": {
                "部门1": 3380,
                "部门2": 1256,
            },
        }
    }

    const config = accountConf[currentAccount] || accountConf["acc1"];

    // 用于存储请求信息的Map
    const requestMap = new Map();

    // 接口地址与修改回调函数的映射
    // 接口地址使用的是 url.includes 方式匹配,可根据需要修改
    // 回调函数接收两个参数:response(响应数据对象),requestInfo(请求信息对象)
    const API_CONFIG = {
        'get_sub_depts_page': function (response, requestInfo) {
            if (!response.result) return response;

            const list = response.result.list;
            for (let i = 0; i < list.length; i++) {
                if (list[i]['name'] in config['deptMemberCount']) {
                    list[i].memberCount = config['deptMemberCount'][list[i]['name']];
                    continue;
                }
            }
            return response;
        },
        'statistic_realtime': function (response, requestInfo) {
            if (!response.result) return response;

            response.result.activeCount = config['总人数'] - 1;
            response.result.memCount = config['总人数'];
            return response;
        },
        // ...
    };

    // XHR 拦截
    const originalOpen = XMLHttpRequest.prototype.open;
    XMLHttpRequest.prototype.open = function (method, url) {
        // 存储请求信息
        requestMap.set(this, {
            method: method,
            url: url,
            body: null // XHR的body会在send时设置
        });

        // 查找匹配的接口配置
        let matchedCallback = null;
        for (const [path, callback] of Object.entries(API_CONFIG)) {
            if (url.includes(path)) {
                console.log(`拦截到接口: ${url}`);
                matchedCallback = callback;
                break;
            }
        }

        if (matchedCallback) {
            const originalOnReadyStateChange = this.onreadystatechange;
            const xhr = this;

            this.onreadystatechange = function () {
                if (xhr.readyState === 4 && xhr.status === 200) {
                    try {
                        const response = JSON.parse(xhr.responseText);
                        const requestInfo = requestMap.get(xhr) || {};
                        const modifiedResponse = matchedCallback(response, requestInfo);

                        Object.defineProperty(xhr, 'responseText', {
                            writable: true,
                            value: JSON.stringify(modifiedResponse)
                        });

                        Object.defineProperty(xhr, 'response', {
                            writable: true,
                            value: modifiedResponse
                        });
                        console.log(`xhr - ${url} - 已修改`);
                    } catch (e) {
                        console.error('修改响应失败:', e);
                    }
                }

                if (originalOnReadyStateChange) {
                    originalOnReadyStateChange.apply(this, arguments);
                }
            };
        }

        originalOpen.apply(this, arguments);
    };

    // XHR send 拦截,用于获取请求体
    const originalSend = XMLHttpRequest.prototype.send;
    XMLHttpRequest.prototype.send = function (body) {
        // 更新请求信息中的body
        const requestInfo = requestMap.get(this);
        if (requestInfo) {
            requestInfo.body = body;
        }

        return originalSend.apply(this, arguments);
    };

    // Fetch 拦截
    const originalFetch = window.fetch;

    window.unsafeWindow.fetch = function (input, init) {
        const url = typeof input === 'string' ? input : input.url;
        const method = (init && init.method) || 'GET';
        const body = (init && init.body) || null;

        // 查找匹配的接口配置
        let matchedCallback = null;
        for (const [path, callback] of Object.entries(API_CONFIG)) {
            if (url.includes(path)) {
                console.log(`拦截到fetch请求: ${url}`);
                matchedCallback = callback;
                break;
            }
        }

        if (!matchedCallback) {
            return originalFetch(input, init);
        }

        // 创建请求信息对象
        const requestInfo = {
            method: method,
            url: url,
            body: body
        };

        return originalFetch(input, init).then(response => {
            return response.text().then(text => {
                try {
                    const data = JSON.parse(text);
                    const modifiedData = matchedCallback(data, requestInfo);
                    const modifiedText = JSON.stringify(modifiedData);

                    console.log(`fetch - ${url} - 已修改`);
                    return new Response(modifiedText, {
                        status: response.status,
                        statusText: response.statusText,
                        headers: response.headers
                    });
                } catch (e) {
                    console.error('修改fetch响应失败:', e);
                    return new Response(text, {
                        status: response.status,
                        statusText: response.statusText,
                        headers: response.headers
                    });
                }
            });
        });
    };

    console.log('接口修改脚本已加载,当前配置的接口数量:', Object.keys(API_CONFIG).length);
})();

其他说明

  1. 文章参考了油猴脚本重写fetch和xhr请求,再加上大模型改成批处理的方式,感谢大佬的分享。
  2. 除了使用油猴外,脚本猫也是一个不错的选择。
  3. 本地调试期间,为了方便写代码,可以利用@require调用本地脚本的方法
相关推荐
一 乐2 小时前
考公|考务考试|基于SprinBoot+vue的考公在线考试系统(源码+数据库+文档)
前端·javascript·数据库·vue.js·spring boot·课程设计
林太白2 小时前
跟着TRAE SOLO全链路看看项目部署服务器全流程吧
前端·javascript·后端
特级业务专家2 小时前
把 16MB 中文字体压到 400KB:我写了一个 Vite 字体子集插件
javascript·vue.js·vite
先生沉默先3 小时前
NodeJs 学习日志(8):雪花算法生成唯一 ID
javascript·学习·node.js
起这个名字3 小时前
Webpack——插件实现的理解
前端·javascript·node.js
二川bro4 小时前
第51节:Three.js源码解析 - 核心架构设计
开发语言·javascript·ecmascript
djk88885 小时前
多标签页导航后台模板 html+css+js 纯手写 无第三方UI框架 复制粘贴即用
javascript·css·html
Hilaku5 小时前
别再吹性能优化了:你的应用卡顿,纯粹是因为产品设计烂🤷‍♂️
前端·javascript·代码规范
驯狼小羊羔5 小时前
学习随笔-hooks和mixins
前端·javascript·vue.js·学习·hooks·mixins