@minson1994/ms-utils
v1.0.4
Published
小工具集合
Maintainers
Readme
@minson1994/ms-utils
一个实用型 TypeScript 工具库,提供跨运行时 API 编排、时间处理、算法/随机、数据验证、任务队列、并发去重、Promise Hook、Cookie 等常用能力。
- TypeScript 编写,自带
.d.ts类型提示。 - 同时支持 ESM / CommonJS。
- 核心能力不绑定浏览器、Node、微信小程序、uniapp。
- 浏览器、微信小程序、uniapp、axios 能力通过子路径按需导入。
安装
npm install @minson1994/ms-utils使用 axios 适配器时,项目里自行安装:
npm install axios qs导入
import {
$Use,
ApiConfig,
ApiRequest,
ApiStatusInterceptor,
Concurrency,
AlgorithmUtils,
Hook,
TaskQueue,
TimeUtils,
ValidateUtils,
} from "@minson1994/ms-utils";
import { AxiosHttpAdapter } from "@minson1994/ms-utils/axios";
import { CookieUtils } from "@minson1994/ms-utils/browser";
import { WxHttpAdapter } from "@minson1994/ms-utils/wx";CommonJS:
const { TimeUtils, AlgorithmUtils } = require("@minson1994/ms-utils");功能总览
| 功能 | 导入 | 说明 |
| --- | --- | --- |
| API 编排器 | @minson1994/ms-utils | 跨 Web、Node、小程序、uniapp 复用业务 API 代码 |
| axios 适配器 | @minson1994/ms-utils/axios | 用 axios 作为底层请求实现 |
| Cookie | @minson1994/ms-utils/browser | 浏览器 document.cookie 工具 |
| 小程序/uni 请求 | @minson1994/ms-utils/wx | 微信小程序 wx.request / uniapp uni.request 适配器 |
| 时间 | TimeUtils | 格式化、加减、差值、多久前、延迟 |
| 算法/随机 | AlgorithmUtils | Base64、MD5、AES、SHA256、HMAC、RSA、UUID、随机数 |
| 数据验证 | ValidateUtils | 空值、类型、邮箱、手机号、身份证、银行卡等 |
| 任务队列 | TaskQueue | 异步任务并发控制、超时、暂停恢复 |
| 并发去重 | Concurrency | 同 key 并发只执行一次 |
| Hook | Hook | 外部回调唤醒 Promise |
1. API 编排器
API 模块不是为了替代 axios、ky、ofetch 这类底层 HTTP client。它解决的是:在 Web、Node、微信小程序、uniapp 等环境里,尽量复用同一份业务 API 接口代码。
核心只暴露 ApiRequest、ApiConfig、ApiResponse、ApiAdapter、ApiInterceptor 等 Api* 命名;底层适配器保留 FetchHttpAdapter、XhrHttpAdapter、AxiosHttpAdapter 这类名称,表示它们负责对接具体 HTTP 实现。
快速开始
import { ApiRequest } from "@minson1994/ms-utils";
const api = new ApiRequest({
url: "https://api.example.com",
});
export function getUser(id) {
return api
.use({
url: "/user/info",
method: "GET",
data: { id },
})
.useAuth()
.emit();
}也可以使用 $Use 创建配置,再传入请求实例执行:
import { $Use, ApiRequest } from "@minson1994/ms-utils";
import { AxiosHttpAdapter } from "@minson1994/ms-utils/axios";
import axios from "axios";
import qs from "qs";
const request = new ApiRequest({
url: ["https://api.example.com"],
adapter: new AxiosHttpAdapter(axios, qs),
});
const result = await $Use({
url: "/user/info",
method: "GET",
data: { id: 1 },
})
.useJsonBody()
.useBackFailResult()
.request(request);分层设计
| 层级 | 类型 | 职责 |
| --- | --- | --- |
| API 编排器 | ApiRequest | 串起配置、适配器、拦截器、缓存、错误处理和多 base url 重试 |
| 单次配置 | ApiConfig | 描述一次请求,并提供链式 useXxx() 能力 |
| 响应对象 | ApiResponse | 统一包装 status、data、headers、原始响应 |
| 适配器 | ApiAdapter | 抽象底层请求能力,不绑定 axios、fetch、xhr、wx |
| 拦截器 | ApiInterceptor | 抽象请求前后处理,业务可继承扩展 |
适配器
| 适配器 | 入口 | 适用环境 | 说明 |
| --- | --- | --- | --- |
| XhrHttpAdapter | @minson1994/ms-utils | 浏览器 | 支持上传进度、下载进度、取消请求 |
| FetchHttpAdapter | @minson1994/ms-utils | Node / 浏览器 / Worker | 通用 fetch 实现,支持取消;下载进度依赖 ReadableStream |
| AxiosHttpAdapter | @minson1994/ms-utils/axios | 使用 axios 的项目 | axios 构造注入,不内置打包 axios |
| WxHttpAdapter | @minson1994/ms-utils/wx | 微信小程序 / uniapp | 适配 wx.request、wx.uploadFile 或 uni.request、uni.uploadFile |
| AjaxHttpAdapter | 源码路径按需引用 | jQuery/uni 风格 ajax | 适合已有 ajax runtime 的老项目 |
默认适配器策略:浏览器有 XMLHttpRequest 时用 XHR,其他环境用 fetch。
const api = new ApiRequest({
url: "https://api.example.com",
});微信小程序 / uniapp:
import { ApiRequest } from "@minson1994/ms-utils";
import { WxHttpAdapter } from "@minson1994/ms-utils/wx";
const request = new ApiRequest({
url: "https://api.example.com",
adapter: new WxHttpAdapter(wx), // uniapp 项目传 uni
});容灾与故障切换
ApiRequest 的 url 支持数组。底层请求失败时,会按数组顺序切换到下一个 base url 重试。
const api = new ApiRequest({
url: [
"https://api-a.example.com",
"https://api-b.example.com",
"https://api-c.example.com",
],
retryDelay: 1000,
});边界说明:
retryDelay单位是毫秒,默认1000。- 只在 adapter 层请求失败时切换,例如网络错误、超时、取消外的 reject。
- 业务状态失败不会自动切换域名,例如
{ code: 500 }这种由ApiStatusInterceptor处理。 - 所有 base url 都失败后,抛出最后一次错误。
拦截器
| 拦截器 | 默认类型 | 职责 |
| --- | --- | --- |
| UI 拦截器 | ApiUIInterceptor | 根据 loading、showErrorMessage 统一处理 loading 和错误提示钩子 |
| 状态拦截器 | ApiStatusInterceptor | 统一处理 HTTP 状态、业务 code、message、data 拆包、未授权回调 |
| 缓存拦截器 | ApiCacheInterceptor | 根据 cache + apiKey 缓存最终响应数据;浏览器默认使用 sessionStorage |
| 业务拦截器 | ApiInterceptor[] | 项目自定义鉴权、签名、埋点、灰度、错误结构处理 |
缓存不会自动根据请求参数计算 key,避免每次请求都序列化参数。需要缓存时必须显式传入 apiKey:
const user = await api
.use({
url: "/user/info",
method: "GET",
data: { id: 1 },
})
.useApiKey("user:1")
.useCache()
.emit();apiKey 是跨功能业务 key,后续也可用于并发去重、请求合并等能力。
如果只调用 useCache(),但没有调用 useApiKey(key),本次请求不会读取缓存,也不会写入缓存:
await api
.use({
url: "/user/info",
method: "GET",
data: { id: 1 },
})
.useCache()
.emit();
// 没有 apiKey,缓存等同禁用。缓存存储策略:
- 浏览器默认使用
sessionStorage,刷新页面后同一会话仍可命中。 - 非浏览器环境自动回退内存缓存。
- 可传
new ApiCacheInterceptor({ storage: "memory" })强制使用内存。 - 可传自定义 storage 对接其它缓存实现。
import { ApiCacheInterceptor, ApiRequest } from "@minson1994/ms-utils";
const api = new ApiRequest({
url: "https://api.example.com",
cacheInterceptor: new ApiCacheInterceptor({
storage: "session",
prefix: "my-app:api:",
}),
});简单业务状态:
import { ApiRequest, ApiStatusInterceptor } from "@minson1994/ms-utils";
const api = new ApiRequest({
url: "https://api.example.com",
statusInterceptor: new ApiStatusInterceptor({
status: "code",
message: "message",
data: "data",
success: 200,
onUnauthorized: (message) => {
console.log("登录失效", message);
},
}),
});复杂结构可以继承 ApiInterceptor:
import { ApiInterceptor, ApiRequest } from "@minson1994/ms-utils";
class CustomStatusInterceptor extends ApiInterceptor {
async before(_config) {
return undefined;
}
async after(_config, response) {
const body = response.data;
if (body.meta.code !== 0) {
throw new Error(body.error?.message || "请求失败");
}
response.data = body.payload;
}
}
const api = new ApiRequest({
url: "https://api.example.com",
statusInterceptor: new CustomStatusInterceptor(),
});单次请求配置
const result = await api
.use({
url: "/user/create",
method: "POST",
data: { name: "minson" },
})
.useApiKey("user:create")
.useAuth()
.useJsonBody()
.useLoading()
.useCancel((cancel) => {
// 需要取消时调用 cancel("用户取消")
})
.useProgress((progress) => {
console.log(progress.percent);
})
.emit();发送纯文本 body 时,data 可以直接传字符串;也可显式调用 useTextBody() 固定为 text/plain;charset=UTF-8:
await api
.use({
url: "/message/raw",
method: "POST",
data: "hello plain text",
})
.useTextBody()
.emit();| 方法 | 说明 |
| --- | --- |
| useAuth() | 标记需要登录态,给自定义拦截器使用 |
| useTimeout(seconds) | 设置超时时间 |
| useHeaders(headers) | 设置请求头 |
| useWithCredentials() | 携带 cookie / credentials |
| useApiKey(key) | 设置业务 key,用于缓存、并发去重等跨功能能力 |
| useUpload() | 标记上传请求,适配器会转 FormData 或平台上传 API |
| useJsonBody() | 按 JSON body 发送 |
| useTextBody() | 按纯文本 body 发送;非查询方法下字符串 data 会自动按 text/plain;charset=UTF-8 发送 |
| useSign() | 标记需要签名,给自定义签名拦截器使用 |
| useLoading() | 触发 UI loading 钩子 |
| useShowErrorMessage() | 控制是否展示错误消息 |
| useCheckHttpStatus() | 检查 HTTP status |
| useBackFailResult() | 业务失败时返回原始失败结果 |
| useBackOnlyData() | 成功时只返回业务 data |
| useCache() | 开启缓存拦截;需要配合 useApiKey(key) 使用 |
| useCancel(callback) | 获取取消函数,外部可主动取消请求 |
| useUploadProgress(callback) | 上传进度回调,适配器支持多少给多少 |
| useDownloadProgress(callback) | 下载进度回调,适配器支持多少给多少 |
| useProgress(callback) | 同时绑定上传和下载进度 |
| request(api) | 使用指定 ApiRequest 执行当前配置 |
| emit() / exec() | 使用绑定的 ApiRequest 执行当前配置 |
API 导出
| API | 说明 |
| --- | --- |
| ApiRequest | 请求编排器,串起 adapter 和 interceptor |
| ApiConfig | 单次请求配置,支持链式 useXxx |
| $Use(options) / $Api(options) | ApiConfig.use(options) 的快捷入口 |
| ApiGlobalConfig | 全局默认配置 |
| ApiResponse | 统一响应对象 |
| ApiAdapter | 抽象适配器基类 |
| ApiInterceptor | 抽象拦截器基类 |
| ApiStatusInterceptor | HTTP / 业务状态码处理 |
| ApiCacheInterceptor | 缓存拦截器 |
| ApiUIInterceptor | loading / error message UI 钩子 |
| ApiError / ApiCancelError / ApiTimeoutError / ApiNetworkError | API 错误类型 |
2. TimeUtils 时间工具
TimeUtils.now();
TimeUtils.timestamp();
TimeUtils.timestamp(new Date(), "second");
TimeUtils.toDate("2026-06-08");
TimeUtils.isValid("2026-06-08");
TimeUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss");
TimeUtils.add(new Date(), 1, "day");
TimeUtils.subtract(new Date(), 1, "hour");
TimeUtils.diff("2026-06-08", "2026-06-10", "day");
TimeUtils.fromNow(Date.now() - 60 * 1000); // 1 分钟前
TimeUtils.startOfDay(new Date());
TimeUtils.endOfDay(new Date());
await TimeUtils.wait(1000);| 方法 | 说明 |
| --- | --- |
| now() | 当前时间 Date |
| timestamp(date?, unit?) | 毫秒/秒时间戳 |
| toDate(value) | 转成 Date |
| isValid(value) | 是否是有效时间 |
| format(date?, pattern?) | 格式化时间 |
| add(date, amount, unit) | 增加时间 |
| subtract(date, amount, unit) | 减少时间 |
| diff(start, end, unit?) | 计算差值 |
| fromNow(date, base?) | 输出“刚刚 / x 分钟前 / x 小时后” |
| startOfDay(date?) | 当天开始时间 |
| endOfDay(date?) | 当天结束时间 |
| wait(ms) | 延迟等待 |
3. AlgorithmUtils 算法和随机工具
Base64、MD5、AES、SHA256、HMAC、UUID、随机数基于
crypto-js和运行时随机源;RSA 使用 WebCrypto。老环境没有crypto.subtle时,RSA 不可用。
AlgorithmUtils.base64Encode("hello");
AlgorithmUtils.base64Decode("aGVsbG8=");
AlgorithmUtils.md5("hello");
AlgorithmUtils.md5_16("hello");
AlgorithmUtils.md5_32("hello");
const encrypted = AlgorithmUtils.aesEncrypt("content", "password");
const decrypted = AlgorithmUtils.aesDecrypt(encrypted, "password");
AlgorithmUtils.sha256("hello");
AlgorithmUtils.hmacSha256("hello", "secret");
AlgorithmUtils.uuid();
AlgorithmUtils.randomInt(1, 100);
AlgorithmUtils.randomFloat();
AlgorithmUtils.randomString(16);
const keys = await AlgorithmUtils.rsaGeneratePemKeyPair();
const rsaText = await AlgorithmUtils.rsaEncrypt("secret", keys.publicKey);
await AlgorithmUtils.rsaDecrypt(rsaText, keys.privateKey);| 方法 | 说明 |
| --- | --- |
| base64Encode(text) / base64Decode(text) | Base64 编解码 |
| md5(text, length?, upper?) | MD5,支持 16/32 位和大小写 |
| md5_16(text, upper?) / md5_32(text, upper?) | 快捷 MD5 |
| aesEncrypt(text, options) / aesDecrypt(text, options) | AES 加解密 |
| sha256(text, upper?) | SHA256 |
| hmac(text, key, algorithm?, upper?) | HMAC,支持 SHA256/SHA1/MD5 |
| hmacSha256(text, key, upper?) | HMAC-SHA256 |
| uuid() | UUID v4 |
| randomInt(min, max) | 指定范围整数 |
| randomFloat() | 0 到 1 的随机小数 |
| randomString(length?, chars?) | 随机字符串 |
| rsaGenerateKeyPair() / rsaGeneratePemKeyPair() | 生成 RSA 密钥对 |
| rsaEncrypt(text, publicKey) / rsaDecrypt(ciphertext, privateKey) | RSA-OAEP 加解密 |
4. ValidateUtils 数据验证工具
ValidateUtils.isEmpty("");
ValidateUtils.isNotEmpty("abc");
ValidateUtils.isString("abc");
ValidateUtils.isNumber(123);
ValidateUtils.isArray([]);
ValidateUtils.isPlainObject({});
ValidateUtils.isEmail("[email protected]");
ValidateUtils.isMobileCN("13800138000");
ValidateUtils.isUrl("https://example.com");
ValidateUtils.isIPv4("127.0.0.1");
ValidateUtils.isJSON('{"a":1}');
ValidateUtils.isBankCard("4111111111111111");
ValidateUtils.isIdCardCN("11010519491231002X");
ValidateUtils.validateIdCardCN("11010519491231002X");
ValidateUtils.passwordStrength("Abcdef12!@");
ValidateUtils.isStrongPassword("Abcdef12!@");| 方法 | 说明 |
| --- | --- |
| isNil / isEmpty / isNotEmpty | 空值判断 |
| isString / isNumber / isInteger / isBoolean | 基础类型判断 |
| isArray / isPlainObject | 数组和普通对象判断 |
| lengthBetween / numberBetween / match | 通用规则判断 |
| isEmail / isMobileCN / isUrl | 常用格式判断 |
| isIPv4 / isIPv6 / isJSON | 网络和 JSON 判断 |
| isPostalCodeCN / isPlateNumberCN | 国内邮编、车牌判断 |
| isBankCard | 银行卡 Luhn 校验 |
| isIdCardCN / validateIdCardCN | 身份证校验,支持生日和性别返回 |
| passwordStrength / isStrongPassword | 密码强度判断 |
5. TaskQueue 任务队列
用于限制异步任务并发,支持超时、任务间隔、暂停、恢复、停止和等待空闲。
const queue = new TaskQueue({
concurrency: 1,
taskDelayMs: 100,
timeout: 0,
});
queue.add(async () => {
await TimeUtils.wait(1000);
return "done";
});
const results = await queue.addMany([() => 1, async () => 2]);
queue.pause();
queue.resume();
await queue.waitForIdle();| 方法/属性 | 说明 |
| --- | --- |
| add(task, timeout?) | 添加单个任务 |
| addMany(tasks) | 批量添加任务 |
| pause() / resume() | 暂停/恢复队列 |
| clear(reason?) | 清空等待队列 |
| stop(reason?) | 停止队列并拒绝新任务 |
| reset() | 重置队列状态 |
| waitForIdle() | 等待队列空闲 |
| setConcurrency(n) | 修改并发数 |
| setTimeout(ms) | 修改默认超时 |
| setTaskDelay(ms) | 修改任务间隔 |
| size / activeCount | 等待数 / 运行数 |
| TaskQueue.delay(ms, value?) | 静态延迟工具 |
6. Concurrency 并发去重
同名同参数的并发调用只执行一次,后续调用复用同一个 Promise。
const loadUserOnce = Concurrency.createConcurrentCall(
async (id) => fetchUser(id),
"load-user",
);
await Promise.all([loadUserOnce(1), loadUserOnce(1)]);| 方法 | 说明 |
| --- | --- |
| createConcurrentCall(fn, name, getKey?) | 创建并发去重函数 |
| pendingRequests | 当前正在执行的请求 Map |
7. Hook 回调工具
适合“先发起动作,后由外部回调唤醒”的场景,比如扫码、第三方 SDK 回调、postMessage。
const hook = new Hook();
const promise = Hook.on(hook);
setTimeout(() => {
hook.call("ok");
}, 1000);
const result = await promise;自动生成编号:
const result = await Hook.no(async (no) => {
window.postMessage({ no });
});| 方法/属性 | 说明 |
| --- | --- |
| new Hook(no?, timeout?) | 创建 Hook,timeout 单位秒 |
| Hook.on(hook) | 注册等待 |
| Hook.call(hook, data) / hook.call(data) | 唤醒等待 |
| Hook.clear(hook) | 清理 Hook |
| Hook.no(fn, timeout?) | 自动生成编号并等待回调 |
| hook.no / hook.timeout | 编号 / 超时时间 |
8. CookieUtils 浏览器 Cookie 工具
CookieUtils 依赖浏览器 document.cookie,请从 browser 子路径导入,避免 Node 环境误加载浏览器对象。
import { CookieUtils } from "@minson1994/ms-utils/browser";
CookieUtils.write("token", "xxx");
CookieUtils.read("token");
CookieUtils.has("token");
CookieUtils.delete("token");
CookieUtils.writeJSON("user", { id: 1 });
CookieUtils.readJSON("user");
CookieUtils.readAll();9. 发布前检查
npm run typecheck
npm run test
npm run build
npm pack --dry-runnpm pack --dry-run 应只看到 dist/、README.md、LICENSE 和 package metadata。
