{"id":36500307,"url":"https://github.com/wenzi0github/utils","last_synced_at":"2026-01-12T02:20:01.955Z","repository":{"id":42800534,"uuid":"271593753","full_name":"wenzi0github/utils","owner":"wenzi0github","description":"常用的工具方法","archived":false,"fork":false,"pushed_at":"2023-01-07T19:02:50.000Z","size":1015,"stargazers_count":10,"open_issues_count":22,"forks_count":4,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-09-18T05:23:32.717Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/wenzi0github.png","metadata":{"files":{"readme":"README.md","changelog":"ChangeLog.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-06-11T16:22:31.000Z","updated_at":"2023-08-29T08:19:49.000Z","dependencies_parsed_at":"2023-02-07T22:01:03.811Z","dependency_job_id":null,"html_url":"https://github.com/wenzi0github/utils","commit_stats":null,"previous_names":[],"tags_count":9,"template":false,"template_full_name":null,"purl":"pkg:github/wenzi0github/utils","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wenzi0github%2Futils","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wenzi0github%2Futils/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wenzi0github%2Futils/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wenzi0github%2Futils/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/wenzi0github","download_url":"https://codeload.github.com/wenzi0github/utils/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wenzi0github%2Futils/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28331985,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-12T00:36:25.062Z","status":"online","status_checked_at":"2026-01-12T02:00:08.677Z","response_time":98,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-01-12T02:20:01.156Z","updated_at":"2026-01-12T02:20:01.944Z","avatar_url":"https://github.com/wenzi0github.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# 前端中常用的工具方法\n\n在日常工作中，我们积累了很多的工具方法，帮助我们提升效率。\n\n[![version](https://img.shields.io/npm/v/gh-qqnews-utils?color=brightgreen\u0026style=flat-square)](https://www.npmjs.com/package/gh-qqnews-utils)\n![size](https://img.shields.io/bundlephobia/min/gh-qqnews-utils)\n\n## 安装\n\n使用 tnpm:\n\n```shell\n$ tnpm install gh-qqnews-utils --save\n```\n\n使用 npm:\n\n```shell\n$ npm install gh-qqnews-utils --save\n```\n\n使用 bower:\n\n```shell\n$ bower install gh-qqnews-utils\n```\n\n使用 yarn:\n\n```shell\n$ yarn add gh-qqnews-utils\n```\n\n使用 jsDelivr 的 CDN 地址:\n\n```html\n\u003cscript src=\"https://cdn.jsdelivr.net/npm/gh-qqnews-utils\"\u003e\u003c/script\u003e\n```\n\n使用 unpkg 的 CDN 地址:\n\n```html\n\u003cscript src=\"https://unpkg.com/gh-qqnews-utils\"\u003e\u003c/script\u003e\n```\n\n## 模块的名称\n\n当前工具包中有如下的工具列表，各位开发者可以按需引入：\n\n-   cookie: 操作 cookie；\n    -   [setCookie](#cookie): 设置 cookie；\n    -   [getCookie](#cookie): 获取 cookie；\n    -   [delCookie](#cookie)：删除 cookie；\n-   date: 日期和时间的操作；\n    -   [isSameDay](#isSameDay): 两个时间戳或格式化的字符串是否在同一天；\n    -   [formatTime](#formatTime): 将时间戳转为格式化的字符串；\n    -   [getWeekStartAndEnd](#getWeekStartAndEnd): 获取当前星期的起始日期和结束日期\n    -   [sleep](#sleep): 延迟一段时间执行；\n-   debounceThrottle：防抖和节流；\n    -   [debounce](#debounce): 防抖；\n    -   [throttle](#throttle): 节流；\n    -   [debounceThrottle](#debounceThrottle): 防抖和节流函数的结合；\n-   querystring： 链接中的参数；\n    -   [parse](#parse): 解析 url 中所有的参数；\n    -   [getQueryString](#getQueryString): 获取 url 查询字符串中的参数；\n    -   [stringify](#stringify): 将 obj 类型的数据拼接为参数字符串('a=1\u0026b=2')\n-   random: 随机数据\n    -   [randomNumber](#randomNumber): 产生随机数字（小数）；\n    -   [randomInt](#randomInt): 产生随机整数；\n    -   [randomStr](#randomStr): 产生随机字符串；\n-   number: 数字操作\n    -   [toThousands](#toThousands): 添加千分位号；\n    -   [getClampNumber](#getClampNumber): 获取合理范围内的数字；\n-   regexp：常用的正则表达式；\n    -   [isUrl](#isUrl): 是否正确的 url 地址；\n    -   [isPhone](#isPhone): 是否正确的手机号码，11 位的数字；\n    -   [isEmail](#isEmail): 是否正确的 email 邮箱地址\n-   string：字符串操作；\n    -   [strReplace](#strReplace): 替换字符串中{key}为具体的数据；\n    -   [truncate](#truncate): 截取字符串；\n    -   [loadScript](#loadScript): 加载 js 文件；\n-   ua：常用的 ua 判断\n    -   [getSystemInfo](#getSystemInfo): 获取当前系统和版本号；\n    -   [getBrowserInfo](#getBrowserInfo): 获取当前 APP 和版本号；\n-   url：URL 操作；\n    -   [parse](#parse): 把 url 类型的字符串解析出各个的字段；\n    -   [stringify](#stringify): 将几个部分拼接为一个完整的 url；\n    -   [format](#format): 将几个部分拼接为一个完整的 url；\n    -   [isAbsolute](#isAbsolute): url 是否是绝对地址；\n-   [visibility](#visibility)： 页面的可见性；\n\n## 引入\n\n### 直接引入\n\n```javascript\nimport utils from \"gh-qqnews-utils\";\nutils.cookie.setCookie(\"name\", \"wenzi\");\n```\n\n### 按需引入\n\n```javascript\nimport { cookie } from \"gh-qqnews-utils\";\n// 其他的还有 date, debounceThrottle, querystring, regexp, string, ua, url, visibility\n\nconst { setCookie, getCookie, delCookie } = cookie;\n```\n\n### 按模块文件引入\n\n```javascript\nimport * as cookie from \"gh-qqnews-utils/cookie\";\n\ncookie.setCookie(\"name\", \"wenzi\");\n```\n\n```javascript\nimport { setCookie, getCookie, delCookie } from \"gh-qqnews-utils/cookie\";\nsetCookie(\"name\", \"skeetershi\", 30); // 设置cookie，有效期为30天，默认为365天\ngetCookie(\"name\"); // 获取cookie\ndelCookie(\"name\"); // 删除cookie\n```\n\n## 如何使用\n\n### cookie\n\n操作 cookie\n\n```javascript\nimport { setCookie, getCookie, delCookie } from \"gh-qqnews-utils/cookie\";\n\nsetCookie(\"name\", \"skeetershi\", 30); // 设置cookie，有效期为30天，默认为365天\ngetCookie(\"name\"); // 获取cookie\ndelCookie(\"name\"); // 删除cookie\n```\n\n### date\n\n日期的方法(date)\n\n引入：\n\n```javascript\nimport * as date from \"gh-qqnews-utils/date\";\n\n// 或单独按照方法分别引用也可以\nimport { isSameDay, formatTime, getWeekStartAndEnd, sleep } from \"gh-qqnews-utils/date\";\n```\n\n#### isSameDay\n\n判断两个日期或时间戳是否在同一天\n\n第二个参数缺省时则使用当前时刻的时间戳进行比较。\n\n```javascript\nisSameDay(\"2020/05/06\", \"2020/05/07\"); // 判断两个日期是否是同一天\nisSameDay(1591283730344, 1591283720344); // 判断两个毫秒级的时间戳是否在同一天\nisSameDay(1591283730344); // 判断此时间戳与当前时刻是否是同一天\n```\n\n#### formatTime\n\n格式化时间戳。\n\n根据输入的格式，格式化时间戳。\n\n```javascript\n/**\n * 格式化时间，时间戳-\u003e格式化\n * @param timestamp 传入的时间戳，单位（毫秒）\n * @param format 格式：yyyy/MM/dd hh:mm:ss\n * yyyy/MM/dd hh:mm:ss 分别表示年/月/日 时:分:秒\n */\nformatTime(1591283730344, \"yyyy/MM/dd hh:mm:ss\"); // 2020/06/04 23:15:30\nformatTime(1591283730344, \"yyyy年MM月dd日 hh时mm分ss秒\"); // 2020年06月04日 23时15分30秒\nformatTime(1591283730344, \"hh:mm:ss\"); // 23:15:30\n```\n\n#### getWeekStartAndEnd\n\n获取给定时间戳的周一和周日的时间\n\n格式的要求跟上面的一样。\n\n```javascript\n/**\n * 获取当前星期的起始日期和结束日期\n * @param {string} startFormat 周一的时间格式\n * @param {string} endFormat   周日的时间格式\n * @param {number} timestamp   所在周的时间戳，若不传入，则默认使用当前时刻的时间戳\n * @returns {string, string} {startDate, endDate} 返回的数据\n */\n\ngetWeekStartAndEnd(\"MM月dd日\", \"MM月dd日\", 1591283730344);\n/**\n * startDate: \"06月01日\"\n * endDate: \"06月07日\"\n */\n```\n\n#### sleep\n\n延迟一段时间后执行\n\n```javascript\nsleep(1000).then(() =\u003e {\n    console.log(\"1000ms 后执行当前代码\");\n});\n\nasync function fn() {\n    await sleep(800);\n    console.log(\"800ms 后执行\");\n}\n```\n\n### debounceThrottle\n\n防抖和节流。\n\n防抖可以类比影魔的大招，每次影魔摇大时被打断，都会重新开始摇。\n\n防抖的效果也这样，我们设定一定的时间后触发某个时间，当事件一直产生时，则时间重置，在短时间内多次触发同一个函数，只执行最后一次。\n\n节流，即一定时间间隔，只会触发一次函数，而且一定会触发。\n\n同时还有防抖和节流的结合体：防抖是防止短时间内多次触发，如果用户一直触发某个行为，那函数肯定就会永远也不执行，这里就需要一个节流的概念，当一定时间内还没触发函数，则主动触发一次。\n\n例如在向下滚动的图片懒加载过程中，若用户一直滚动，则图片就永远无法加载，这里我们可以设置防抖的间隔为 100ms，节流的间隔为 1000ms，意思是在用户 100ms 内再次产生滚动行为时，则取消执行加载图片的函数，但到 1000ms 的时候一定要触发一次加载图片的函数。\n\n#### 引入\n\n```javascript\nimport { debounce, throttle, debounceThrottle } from \"gh-qqnews-utils/debounce-throttle\";\n```\n\n#### debounce\n\n防抖\n\n```javascript\ndebounce(function () {\n    console.log(\"行为结束后的200ms后触发当前函数\");\n}, 200);\n```\n\n#### throttle\n\n节流\n\n```javascript\nthrottle(function () {\n    console.log(\"500ms内只执行一次当前函数\");\n}, 500);\n```\n\n#### debounceThrottle\n\n防抖和节流\n\n```javascript\ndebounceThrottle(\n    function () {\n        console.log(\"行为结束后的200ms后触发当前函数\");\n        console.log(\"若行为一直不结束，则1000ms时也会触发当前函数，然后重新计时\");\n    },\n    200,\n    1000\n);\n```\n\n### querystring\n\n网站地址中的参数操作。\n\n#### 引入\n\n```javascript\nimport { parse, stringify, getQueryString } from \"gh-qqnews-utils/querystring\";\n// 或\nimport querystring from \"gh-qqnews-utils/querystring\";\n```\n\n#### parse\n\n解析出所有的参数\n\n默认使用`window.URLSearchParams`进行解析，否则进行字符串的拆分。\n\n```javascript\nconst querys = querystring.parse(); // 默认解析当前链接中的search部分\nconsole.log(querys);\n\nquerystring.parse(\"?name=abcd\u0026age=123\"); // {name: \"abcd\",age: \"123\"}\n```\n\n#### getQueryString\n\n获取 url 查询字符串中的参数；\n\n```javascript\ngetQueryString(\"name\"); // abcd\ngetQueryString(\"name\", \"?name=abcd\u0026age=123\"); // abcd\n```\n\n#### stringify\n\n将 obj 类型的数据拼接位字符串\n\n参数配置：\n\n```javascript\n/**\n * 将obj类型的数据，拼接为可识别的url参数字符串\n * @param {object} query 要解析的obj\n * @param {string} sep 分隔符，默认为\u0026\n * @param {string} eq name和value中的连接符，默认为=\n * @param {StringiyOptions} 额外控制的配置\n * @returns {string}\n */\n```\n\n用法：\n\n```javascript\nquerystring.stringify({\n    name: \"wenzi\",\n    age: \"24\"\n}); // \"name=wenzi\u0026age=24\"\n\nquerystring.stringify(\n    {\n        name: \"wenzi\",\n        age: \"24\"\n    },\n    \"|\",\n    \"*\"\n); // \"name*wenzi|age*24\"\n\nquerystring.stringify(\n    {\n        a: 10,\n        b: 20\n    },\n    null,\n    null,\n    {\n        encode: (value) =\u003e {\n            return value * 2;\n        }\n    }\n); // \"a=20\u0026b=40\"\n```\n\n### random\n\n#### randomNumber\n\n产生随机的数字：大于等于 min，小于 max\n\n```javascript\nrandomNumber(12, 20); // 17.244502548891298 随机\n```\n\n#### randomInt\n\n产生随机的整数：大于等于 min，小于 max\n\n```javascript\nrandomInt(12, 20); // 16 随机\nrandomInt(12, 13); // 12 固定\n```\n\n#### randomStr\n\n产生随机的字符串，默认使用大写字母，小写字母和数字组成，可以通过第 2 个参数进行相关的配置：\n\n```javascript\nrandomStr(len, {\n    number?: boolean; // 是否有数字，默认为true\n    lowercase?: boolean; // 是否有小写字母，默认为true\n    uppercase?: boolean; // 是否有大写字母，默认为true\n    throughline?: boolean; // 是否有中划线，默认为false\n    underline?: boolean; // 是否有下划线，默认为false\n})\n```\n\n使用：\n\n```javascript\nrandomStr(15); // e4cMHFrp816pNEZ\nrandomStr(15, { uppercase: false }); // 7kj175n7ezda4cb 无大写字母\nrandomStr(15, { lowercase: false }); // 072WF4N34W2CPNA 无小写字母\nrandomStr(15, { number: false }); // tAPSiQrkNzZzTDY 无数字\nrandomStr(15, { throughline: true }); // dM6a-s-fpztGZNT 中划线加入到随机字符集中\nrandomStr(15, { underline: true }); // 0m5mYHtPep_H4ce 下划线加入到随机字符集中\n```\n\n### number\n\n#### 引入\n\n```javascript\nimport { toThousands, getClampNumber } from \"gh-qqnews-utils/regexp\";\n```\n\n#### toThousands\n\n设置千分位\n\n```javascript\ntoThousands(123456); // '123,456'\n```\n\n#### getClampNumber\n\n获取合理范围内的数字，当在设定的范围内，则返回原值；当超出设定的最大或者最小阈值时，则使用边界值。\n\n```javascript\ngetClampNumber(50, 0, 100); // 50\ngetClampNumber(-1, 0, 100); // 0\ngetClampNumber(110, 0, 100); // 100\n```\n\n### regexp\n\n常用的正则表达式\n\n例如，是否正确的手机号/email 邮箱/http 类型的 url。\n\n#### 引入\n\n```javascript\nimport { isUrl, isPhone, isEmail } from \"gh-qqnews-utils/regexp\";\n```\n\n#### isUrl\n\n是否是正确的 URL 地址\n\n```javascript\n/**\n * 判断字符串是否为正确的url地址\n * http://, https://, file://均认为是正确的\n *\n * @param {string} url 要判断的字符串\n * @returns {boolean} 是否为正确的url地址\n */\n\nisUrl(\"https://www.xiabingbao.com\"); // true\nisurl(\"http://www.xiabingbao.com\"); // true\nisUrl(\"file://\"); // true\nisUrl(\"//www.xiabingbao.com\"); //false\n```\n\n#### isPhone\n\n是否为正确的手机号\n\n```javascript\nisPhone(\"13012345678\"); // true\nisPhone(\"1301234567\"); // false\nisPhone(\"130123456789\"); // false\nisPhone(\"1301234567a\"); // false\n```\n\n#### isEmail\n\n是否为正确的邮箱地址\n\n```javascript\nisEmail(\"123456@qq.com\"); // true\nisEmail(\"abcdef@gmail.com\"); // true\nisEmail(\"12345c\"); // false\n```\n\n### string\n\n字符串操作(string)\n\n#### 引入\n\n```javascript\nimport { strReplace, truncate, loadScript } from \"gh-qqnews-utils/string\";\n```\n\n#### strReplace\n\n替换字符串中的变量\n\n```javascript\nconst str = \"my name is {name}, my age is {age}\"; // 注意，此括号不是ES6中模板字符串的变量\n\nstrReplace(str, {\n    name: \"wenzi\",\n    age: 24\n}); // \"my name is wenzi, my age is 24\"\n```\n\n#### truncate\n\n截取字符串，并添加后缀\n\n按照规定的长度 size 截取字符串，若 size 大于等于字符串的长度，或 size 小于等于 0，则字符串原样返回。\n\n若 size 符合要求，则正常截取字符串，一个中文字符按照 1 个长度计算；结尾默认`...`结束，不过可以自行选择。\n\n```javascript\ntruncate(\"hello world\", 12); // hello world\ntruncate(\"hello world\", 11); // hello world\ntruncate(\"hello world\", 4); // hell...\ntruncate(\"hello world\", 4, \"***\"); // hell***\n```\n\n#### loadScript\n\n加载一个 js 文件\n\n```javascript\nloadScript(\"https://mat1.gtimg.com/libs/jquery/jquery-1.11.1.js\")\n    .then(() =\u003e {\n        console.log(\"load js success\");\n    })\n    .catch(() =\u003e {\n        console.error(\"load js failed\");\n    });\n```\n\n### ua\n\n获取 ua 中的数据(ua)\n\n#### 引入\n\n```javascript\nimport { getSystemInfo, getBrowserInfo } from \"gh-qqnews-utils/ua\";\n```\n\n| 字段        | 数值                           | 说明                |\n| ----------- | ------------------------------ | ------------------- |\n| name        | iphone\\|ipad\\|android\\|windows | 操作系统的名称      |\n| version     | 6.0.1                          | 版本号              |\n| ios         | true                           | 是否是 iOS 系统     |\n| android     | false                          | 是否是 Android 系统 |\n| manufacture | huawei                         | 手机的品牌          |\n| model       | mt7-cl00                       | 型号                |\n| build       | mt7-cl00                       | 构建的流水线        |\n\n#### getSystemInfo\n\n获取系统级的数据\n\n```javascript\ngetSystemInfo();\n/*\n// iOS系统\n{\n    android: false,\n    ios: true,\n    manufacture: \"\",\n    name: \"iphone\",\n    version: \"13.2.3\"\n}\n\n// android系统\n{\n    android: true,\n    ios: false,\n    name: \"android\",\n    version: \"5.0\",\n    manufacture: \"huawei\",\n    model: 'mt7-cl00',\n    build: 'mt7-cl00'\n}\n*/\n```\n\n#### getBrowserInfo\n\n获取所在 APP 或者浏览器的数据\n\n```javascript\ngetBrowserInfo();\n/*\n// iOS系统\n{\n    name: \"safari\",\n    version: \"604.1\",\n    app: {\n        weixin: true,\n        qq: false\n    }\n}\n\n// android\n{\n    name: \"chrome\",\n    version: \"81.0\",\n    app: {\n        weixin: true,\n        qq: false\n    }\n}\n*/\n```\n\n### url\n\n操作 url\n\n#### 引入\n\n```javascript\nimport { parse, stringify, format, http2https } from \"gh-qqnews-utils/url\";\n// format与stringify的操作一样\n```\n\n#### parse\n\n解析 url 字符串的各个部分\n\n```javascript\n// 默认使用window.URL解析，否则创建一个a标签来解析\nparse(); // 参数默认为当前的url\n/*\n{\n    hash: \"\",\n    host: \"joke.qq.com:8080\",\n    hostname: \"joke.qq.com\",\n    href: \"http://joke.qq.com:8080/works/starlist/rank?a=1\u0026b=2\",\n    origin: \"http://joke.qq.com:8080\",\n    pathname: \"/works/starlist/rank\",\n    port: \"8080\",\n    protocol: \"http:\",\n    search: \"?a=1\u0026b=2\"\n}\n*/\n```\n\n#### stringify\n\n将各个部分组装成一个完成的 url 地址\n\n各个部分均可以缺省，然后使用默认值：\n\n```typescript\ninterface StringIfyOptions {\n    protocol?: \"http:\" | \"https:\" | \"file:\";\n    port?: string;\n    hostname?: string;\n    pathname?: string;\n    query?: {\n        [name: string]: any;\n    };\n}\n```\n\n使用：\n\n```javascript\nstringify({\n    hostname: \"www.xiabingbao.com\",\n    pathname: \"/post/fe/hash-history-router.html\"\n}); // \"https://www.xiabingbao.com/post/fe/hash-history-router.html\"\n\nstringify({\n    protocol: \"http:\",\n    port: \"8080\",\n    hostname: \"www.xiabingbao.com\",\n    pathname: \"/post/fe/hash-history-router.html\"\n}); // \"http://www.xiabingbao.com:8080/post/fe/hash-history-router.html\"\n\nstringify({\n    hostname: \"www.xiabingbao.com\",\n    query: {\n        from: \"utils\",\n        num: 1,\n        score: {\n            math: 80,\n            eng: 90\n        }\n    }\n}); // \"https://www.xiabingbao.com?from=utils\u0026num=1\u0026score=%7B%22math%22%3A80%2C%22eng%22%3A90%7D\"\n```\n\n#### http2https\n\n将`http://`开头的链接改为`https://`的，其他格式的保持不变，原样返回。\n\n```javascript\nhttp2https(\"http://www.xiabingbao.com\"); // https://www.xiabingbao.com\nhttp2https(\"https://www.xiabingbao.com\"); // https://www.xiabingbao.com\nhttp2https(\"//www.xiabingbao.com\"); // //www.xiabingbao.com\n```\n\n### visibility\n\n页面可见性的检测\n\n#### 引入\n\n```javascript\nimport PageVisibility from \"gh-qqnews-utils/visibility\";\n```\n\n#### 使用\n\n```javascript\nconst visibility = new PageVisibility();\n\n// 监听当前页面的变化\nvisibility.visibilityChange((isShow) =\u003e {\n    console.log(isShow); // 可见性切换时触发\n});\n\n// 直接获取当前页面的可见性\nvisibility.isShow(); // 当前页面的可见性\n\nvisibility.destory(); // 销毁 visibilityChange 监听事件\n```\n\n## 维护者\n\n-   [skeetershi](https://git.code.oa.com/u/skeetershi)\n-   [wenzi](https://github.com/wenzi0github)\n\n## 协议\n\n[MIT](./LICENSE)\n\n## ChangeLog\n\n[ChangeLog](./ChangeLog.md)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwenzi0github%2Futils","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fwenzi0github%2Futils","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwenzi0github%2Futils/lists"}