油猴脚本拦截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调用本地脚本的方法
相关推荐
拉不动的猪1 小时前
webpack编译中为什么不建议load替换ast中节点删除consolg.log
前端·javascript·webpack
阿蒙Amon3 小时前
JavaScript学习笔记:6.表达式和运算符
javascript·笔记·学习
小a杰.4 小时前
Flutter 设计系统构建指南
开发语言·javascript·ecmascript
kgduu6 小时前
js之事件系统
javascript
前端老宋Running6 小时前
“受控组件”的诅咒:为什么你需要 React Hook Form + Zod 来拯救你的键盘?
前端·javascript·react.js
阿蒙Amon6 小时前
JavaScript学习笔记:7.数字和字符串
javascript·笔记·学习
Highcharts.js6 小时前
官方文档|Angular 框架集成 Highcharts Dashboards
前端·javascript·angular.js·highcharts·看板·使用文档·dashboards
韭菜炒大葱7 小时前
React 新手村通关指南:状态、组件与魔法 UI 🧙‍♂️
前端·javascript·react.js
小明记账簿7 小时前
JavaScript浮点数精度问题及解决方案
开发语言·javascript·ecmascript
1024肥宅8 小时前
JavaScript性能与优化:手写实现关键优化技术
前端·javascript·面试