UNPKG

@da.li/core-libs

Version:

大沥网络函数库是大沥网络提供的一个公共 TypeScript 函数库,封装了基础操作、缓存、加密、文件处理、HTTP 请求等常用功能模块,旨在提高开发效率。

698 lines (691 loc) 23.4 kB
import { base64Encode } from "./chunk-4XIDRMUK.js"; import { cache_client_default, cache_server_default } from "./chunk-MVZ4K6RQ.js"; import { LRU } from "./chunk-6QCBU3HR.js"; import { $Global, DEBUG, SERVERMODE, TEST, hasArray, hasObject, hasObjectName, hasString, isArray, isFn, isObject, isString, modulesUpdate, sleep } from "./chunk-BOVD54NE.js"; // src/task.ts var Tasks = class { /** * 构造 * @param tasks 任务列表 * @param interval 轮询周期(单位:秒) */ constructor(tasks, interval = 30) { this.instance = hasArray(tasks) ? tasks : []; this.timer = void 0; this.interval = interval < 1 ? 30 : interval; this.counter = 0; this.last = /* @__PURE__ */ new Date(); this.busy = false; } /** 执行任务 */ async execute(task) { if (!task || task.busy || task.interval < 1) return; const now = Date.now(); if ((now - task.last) / 1e3 < task.interval) return; task.count += 1; task.busy = true; task.result = await task.execute(task, this.instance); task.busy = false; task.last = Date.now(); task.lastTime = (/* @__PURE__ */ new Date()).toLocaleString(); } /** 停止任务 */ stop() { if (!this.timer) return; clearTimeout(this.timer); this.timer = void 0; } /** 启动任务 */ start() { this.stop(); if (this.busy) return; if (!hasArray(this.instance)) return; this.busy = true; this.counter += 1; this.last = /* @__PURE__ */ new Date(); this.instance.forEach((task) => this.execute(task)); this.last = /* @__PURE__ */ new Date(); this.busy = false; this.timer = setTimeout(() => { this.start(); }, this.interval * 1e3); } }; var createTasks = (modules, interval = 30, mode = true) => { if (!mode) return; if (SERVERMODE && mode === "client") return; if (!SERVERMODE && mode === "server") return; if (!hasObject(modules) && !hasArray(modules)) return; const packages = modulesUpdate(modules); if (!hasObject(packages)) return; const tasks = []; Object.keys(packages).forEach((key) => { const obj = packages[key]; if (obj && isFn(obj.execute)) { obj.name = key; obj.interval = Number(obj.interval || 0); obj.last = 0; obj.count = 0; obj.busy = false; isFn(obj.init) && obj.init(); if (obj.interval > 0) tasks.push(obj); } }); return new Tasks(tasks, interval); }; // src/cache/index.ts var createCache = () => { if (!$Global.$cache) $Global.$cache = new (SERVERMODE ? cache_server_default : cache_client_default)(); return $Global.$cache; }; // src/http/utils.ts import { withBase, withQuery } from "ufo"; var payloadMethods = new Set(Object.freeze(["PATCH", "POST", "PUT", "DELETE"])); function isPayloadMethod(method = "GET") { return payloadMethods.has(method.toUpperCase()); } function getToken(context, tokenContent = "") { tokenContent = context.options.token || tokenContent; if (!tokenContent) return; if (tokenContent === true) { const dataToken = (data) => { if (hasObjectName(data, "token")) { const value = data.token; if (hasString(value)) { delete data.token; return value; } } }; return dataToken(context.options.body) || // dataToken(context.options.params) || dataToken(context.options.query); } else if (isFn(tokenContent)) { return tokenContent(context); } else { return tokenContent; } } function updateRequest(context, appenQuery = false) { const { request, options } = context; const sourceUrl = isString(request) ? request : request == null ? void 0 : request.url; const baseUrl = options.baseURL; let url = baseUrl ? withBase(sourceUrl, baseUrl) : sourceUrl; url = url.replace(/%7B/gi, "{").replace(/%7D/gi, "}"); const queryData = {}; const encode = hasArray(options.encode) ? options.encode.map((x) => x.toLowerCase()) : []; const fields = []; const update = (name, isQuery = true) => { const data = options[name]; if (!data || !hasObject(data)) return; for (const key of Object.keys(data)) { let value = data[key]; const isEncode = encode.includes(key.toLowerCase()); if (isEncode) { value = base64Encode(value); fields.push(key); } const reg = new RegExp(`{${key}}`, "gi"); if (reg.test(url)) { url = url.replace(reg, value); isQuery && delete data[key]; } if (isQuery) { queryData[key] = value; } else if (isEncode) { data[key] = value; } } if (isQuery) delete options[name]; }; update("headers", false); update("body", false); update("query", true); if (isPayloadMethod(options.method)) { if (hasArray(fields) && hasObject(options.body)) { options.body = { ...options.body, _encode: fields.join(",") }; } } else { delete options.body; if (hasArray(fields)) { queryData["_encode"] = fields.join(","); } } if (hasObject(queryData)) { if (appenQuery) { url = withQuery(url, queryData); } else { options.query = queryData; } } if (sourceUrl !== url) { if (isString(request)) { context.request = url; } else { context.request = new Request(url, request); } } return { url, request: context.request, options }; } function showError(config, error) { if (!config || !error || error.alerted) return false; if (error.alert && isFn(config.alert)) error.alerted = config.alert(error, config); return !!error.alerted; } function updateId(item) { var _a; const hasData = hasObjectName(item, "data"); const hasID = hasObjectName(item, "_ID_"); if (hasData && !hasID) { item.data = updateId(item.data); } else if (hasID) { item.id = (_a = item._ID_) != null ? _a : item.id; } else if (hasArray(item)) { item = item.map((x) => updateId(x)); } if (hasObjectName(item, "children")) item.children = updateId(item.children); return item; } function getResponseErrorMessage(code, message) { let title = ""; switch (code) { case 400: title = "\u65E0\u6548\u8BF7\u6C42"; message = message || "\u5F53\u524D\u64CD\u4F5C\u65E0\u6548"; break; case 401: title = "\u767B\u5F55\u5F02\u5E38"; message = message || "\u60A8\u7684\u767B\u5F55\u4FE1\u606F\u5DF2\u7ECF\u8FC7\u671F\uFF0C\u8BF7\u91CD\u65B0\u767B\u5F55\u540E\u518D\u64CD\u4F5C"; break; case 403: title = "\u65E0\u64CD\u4F5C\u6743\u9650"; message = message || "\u60A8\u65E0\u6743\u6267\u884C\u6B64\u64CD\u4F5C\uFF0C\u8BF7\u4E0E\u7BA1\u7406\u5458\u8054\u7CFB\u6388\u6743\uFF01"; break; case 404: title = "\u8D44\u6E90\u4E0D\u5B58\u5728"; message = message || "\u60A8\u8BF7\u6C42\u7684\u5730\u5740\u4E0D\u5B58\u5728\uFF0C\u8BF7\u68C0\u67E5\u9700\u8981\u8BBF\u95EE\u7684 API \u63A5\u53E3\u662F\u5426\u6B63\u786E"; break; case 500: title = "\u670D\u52A1\u7AEF\u5F02\u5E38"; message = message || "\u6267\u884C\u60A8\u7684\u64CD\u4F5C\u65F6\uFF0C\u670D\u52A1\u5668\u53D1\u751F\u5F02\u5E38\uFF0C\u8BF7\u7A0D\u540E\u518D\u8BD5"; break; default: title = "\u64CD\u4F5C\u5F02\u5E38"; message = "\u64CD\u4F5C\u53D1\u751F\u5F02\u5E38\uFF1A" + (message || "\u9519\u8BEF\u4EE3\u7801 " + code); } return { title, message }; } // src/http/hook.ts import { createFetch, createFetchError } from "ofetch"; import chalk from "chalk"; var HTTP_DEBUG = { /** * 输出模式:是否开启调试信息输出 * - false: 不输出 * - true: 输出详细的 http 结果 * - simple: 仅输出简单结果,及请求地址与方式,结果与参数不输出 */ output: true, /** 仅调试环境才生效,正式环境不生效 */ debugOnly: true, /** 是否输出参数使用提示 */ show: true }; function debug(succ, title, context, config) { if (TEST) return; if (HTTP_DEBUG.output === false) return; if (HTTP_DEBUG.debugOnly && !DEBUG) return; const space = chalk.reset(" ".repeat(4)); const outputs = []; if (HTTP_DEBUG.show) { HTTP_DEBUG.show = false; outputs.push(chalk.bgYellow("#".repeat(72))); outputs.push(""); outputs.push(space + `${chalk.bgRedBright("\u8C03\u8BD5\u6A21\u5F0F")}`); outputs.push(""); outputs.push( space + chalk.redBright("\u60A8\u5DF2\u7ECF\u5F00\u542F\u4E86 HTTP \u8BF7\u6C42\u7684\u8C03\u8BD5\u6A21\u5F0F\uFF0C\u5C06\u8F93\u51FA HTTP \u8BF7\u6C42\u7684\u76F8\u5173\u4FE1\u606F\u3002") ); outputs.push( space + chalk.redBright("\u5F53\u524D\u6A21\u5F0F\uFF1A") + chalk.bgGreen.white(` ${HTTP_DEBUG.output} `) ); outputs.push( space + chalk.redBright("\u60A8\u53EF\u4EE5\u901A\u8FC7\u4FEE\u6539 ") + chalk.bgMagentaBright.white(" HTTP_DEBUG.output ") + chalk.redBright(" \u53C2\u6570\u6765\u8C03\u6574\u8C03\u8BD5\u4FE1\u606F\u8F93\u51FA\u7684\u60C5\u51B5\u3002") ); outputs.push(""); outputs.push(chalk.bgYellow("#".repeat(72))); outputs.push(""); } const color = succ ? chalk.greenBright : chalk.redBright; const bgColor = succ ? chalk.bgGreen : chalk.bgRed; outputs.push(succ ? chalk.bgGreen(title) : chalk.bgRed(title)); const { request, response, options, error } = context; let url = response ? response.url : isObject(request) ? request.url : request; let method = options.method || "GET"; outputs.push(color(`[${method}] ${url}`)); if (HTTP_DEBUG.output !== "simple") { outputs.push(""); outputs.push(bgColor("[\u53C2\u6570]")); outputs.push(config); outputs.push(""); outputs.push(bgColor("[\u8BF7\u6C42]")); outputs.push(request); } outputs.push(""); outputs.push(bgColor("[\u8F93\u51FA]")); outputs.push(response || "\u65E0\u4EFB\u4F55\u8F93\u51FA\u7ED3\u679C"); outputs.push(""); outputs.push(bgColor("[\u9519\u8BEF]")); outputs.push(error || "\u65E0\u4EFB\u4F55\u9519\u8BEF\u4FE1\u606F"); outputs.push(""); outputs.forEach((item) => { console.log(item); }); } var defaultMap = { Id: "traceId", code: "code", message: "message", data: "data" }; function createHttp(globalOptions, globalConfig) { const runtime = { private: false, privateMap: defaultMap, globalErrorAlert: "toast", ...globalConfig, reLogin: 0 }; const options = { ...globalOptions, defaults: { credentials: "omit", baseURL: runtime.baseURL, timeout: runtime.timeout, onRequest: (context) => onRequest(context, runtime), onResponse: (context) => onResponse(context, runtime), onRequestError: (context) => onRequestError(context, runtime), onResponseError: (context) => onResponseError(context, runtime), ...globalOptions == null ? void 0 : globalOptions.defaults } }; const http2 = createFetch(options); http2.runtime = runtime; http2.cache = (request, options2) => HttpCache(http2, request, options2); http2.GET = (url, params, options2) => HttpFast(http2, url, "GET", params, options2); http2.POST = (url, params, options2) => HttpFast(http2, url, "POST", params, options2); http2.PUT = (url, params, options2) => HttpFast(http2, url, "PUT", params, options2); http2.PATCH = (url, params, options2) => HttpFast(http2, url, "PATCH", params, options2); http2.DELETE = (url, params, options2) => HttpFast(http2, url, "DELETE", params, options2); http2.FORM = (url, params, options2) => HttpFast(http2, url, "FORM", params, options2); http2.upload = (files, request, options2) => HttpUpload(http2, files, request, options2); http2.download = (request, options2) => HttpDownload(http2, request, options2); http2.api = (api, options2) => HttpApi(http2, api, options2); http2.resetLoginStatus = (state = 0) => runtime.reLogin = state > 4 || state < 0 ? 0 : state; http2.processRequest = processRequest; runtime.http = http2; return http2; } async function processRequest(request, options, config) { config.baseURL && options.baseURL !== config.baseURL && (options.baseURL = config.baseURL); config.timeout && config.timeout > 0 && (options.timeout = config.timeout); if (!options.headers) options.headers = new Headers(); const data = updateRequest({ request, options }); config.last = { url: isString(request) ? request : request.url, method: options.method || "GET", time: /* @__PURE__ */ new Date(), id: "", status: 0 }; if (options.auth && isFn(config.auth) && !config.auth(data.url, options.method, config)) { con.error("\u65E0\u6B64\u63A5\u53E3\u64CD\u4F5C\u6743\u9650", data.url, options.method); const error = new Error("[NotAllowedError]: No permission for this interface operation"); error.name = "NotAllowedError"; error.code = 35; throw error; } if (isFn(config.sign)) await config.sign(data.url, options, config); if (!options.headers.has("Authorization")) { const token = getToken({ request, options }, config.token); if (hasString(token)) { options.headers.set("Authorization", `Bearer ${token}`); } } if (!options.timeout) options.timeout = config.timeout; return data.url; } async function onRequest(context, config) { const { request, options } = context; const url = await processRequest(request, options, config); if (!url) return; if (isString(request)) { context.request = url; } else { context.request = new Request(url, request); } } function onResponse(context, config) { debug(true, "HTTP Response", context, config); const { request, response, options } = context; if (!response) return; config.last = { url: isString(request) ? request : request.url, method: options.method || "GET", time: /* @__PURE__ */ new Date(), id: "", status: response.status }; if (!config.private) return; const map = config.privateMap || defaultMap; response.traceId = hasObjectName(response._data, map.Id) ? response._data[map.Id] : ""; config.last.id = response.traceId; if (!response.ok) return; const message = hasObjectName(response._data, map.message) ? response._data[map.message] : ""; if (message && response.ok) { con.information("\u670D\u52A1\u5668\u53CD\u9988\u5F02\u5E38\u4FE1\u606F", response.url, options.method); showError(config, { name: "\u6E29\u99A8\u63D0\u793A", message, alert: "modal" }); } if (hasObjectName(response._data, map.data)) response._data = response._data[map.data]; if (options.convert) response._data = updateId(response._data); } function onRequestError(context, config) { debug(false, "HTTP Request Error", context, config); const httpError = { ...createFetchError(context), alert: context.options.alert || config.globalErrorAlert }; if (!config.private) throw httpError; showError(config, httpError); throw httpError; } async function onResponseError(context, config) { debug(false, "HTTP Response Error", context, config); if (!config.private) return; const { response, options } = context; if (!response) return; const { status, statusText, _data } = response; if (!SERVERMODE && status === 401 && options.autoLogin && isFn(config.login)) { const res = await RetryLogin(context, config); if (!res) return res; } const map = config.privateMap || defaultMap; const mapData = hasObjectName(_data, map.data) ? _data[map.data] : ""; const mapCode = hasObjectName(_data, map.code) ? _data[map.code] : status; const mapMessage = hasObjectName(_data, map.message) ? _data[map.message] : statusText; const url = response.url; if (hasObject(mapData)) throw { ..._data, code: mapCode, url, alert: context.options.alert, data: mapData, message: mapMessage }; if (!context.error) context.error = createFetchError(context); var errInfo = getResponseErrorMessage(status, mapMessage); const error = context.error; error.message = errInfo.message; error.name = errInfo.title; error.alert = context.options.alert || !status && config.globalErrorAlert; showError(config, error); throw error; } var cache = new LRU(30); var cacheStatus = /* @__PURE__ */ new Map(); async function HttpCache(http2, request, options) { if (!options) options = {}; const { cacheTime, cacheKey, cacheValue, cacheError } = await cacheRead( request, options, http2.runtime ); if (cacheValue && hasObjectName(cacheValue, "succ")) { con.information("HTTP \u7F13\u5B58\u547D\u4E2D", request, cacheTime, cacheKey, cacheValue); if (cacheValue.succ) { return cacheValue.result; } else if (cacheError) { throw cacheValue.result; } else { } } cacheTime > 0 && cacheStatus.set(cacheKey, true); let succ = false; let result; await http2(request, options).then((res) => { succ = true; result = res; }).catch((res) => { succ = false; result = res; }); if (cacheTime > 0) { const time = succ ? cacheTime : cacheError && cacheTime > 30 ? 30 : 0; time > 0 && cache.set(cacheKey, { succ, result }, time); cacheStatus.delete(cacheKey); } if (succ) { return result; } else { throw result; } } var cacheRead = async (request, options, config) => { var _a, _b; const nothing = () => { options.cacheTime = false; options.cacheError = false; return { cacheTime, cacheKey: "", cacheValue: void 0, cacheError: false }; }; let cacheTime = options.cacheTime === false ? -1 : options.cacheTime || 0; if (cacheTime === 0) { const isQuery = isPayloadMethod(options.method || "GET"); cacheTime = (isQuery ? (_a = config.cacheTime) == null ? void 0 : _a.GET : (_b = config.cacheTime) == null ? void 0 : _b.POST) || 0; } if (cacheTime < 1) return nothing(); let cacheKey = isString(options.cacheKey) ? options.cacheKey : isFn(options.cacheKey) ? options.cacheKey(request, options) : void 0; if (cacheKey === "") return nothing(); if (!cacheKey) { const url = isString(request) ? request : request.url; const method = (options.method || "GET").toUpperCase(); const data = {}; hasObject(options.query) && Object.assign(data, options.query); hasObject(options.body) && Object.assign(data, options.body); hasObject(options.headers) && Object.assign(data, options.headers); cacheKey = JSON.stringify({ url, method, data }); } let cacheValue = cache.get(cacheKey); if (cacheValue) return { cacheTime, cacheKey, cacheValue, cacheError: options.cacheError }; for (let i = 0; i < 10; i++) { await sleep(100); const status = !!cacheStatus.get(cacheKey); if (!status) break; } cacheValue = cache.get(cacheKey); return { cacheTime, cacheKey, cacheValue, cacheError: options.cacheError }; }; async function HttpFast(http2, url, method, params, options) { if (!options) options = {}; if (method === "FORM") { method = "POST"; options.headers = { ...options.headers, "Content-Type": "application/x-www-form-urlencoded" }; } options.method = method; if (isPayloadMethod(method)) { options.body = params; } else { options.query = params; } return HttpCache(http2, url, options); } async function HttpUpload(http2, files, request, options) { if (!files || !(files instanceof FormData)) { const error = { request, options, status: 400, statusCode: 400, name: "UploadError", message: "\u65E0\u6548\u4E0A\u4F20\u53C2\u6570\uFF01", alert: "toast" }; showError(http2.runtime, error); throw error; } if (!options) options = {}; if (!options.method) options.method = "POST"; options.body = files; return http2(request, options); } async function HttpDownload(http2, request, options) { if (SERVERMODE || !window) return false; let succ = false; await http2(request, options).then((res) => { const file = window.URL.createObjectURL(res); const dom = document.createElement("a"); dom.style.display = "none"; dom.href = file; dom.setAttribute("download", ""); document.body.appendChild(dom); dom.click(); window.URL.revokeObjectURL(file); showError(http2.runtime, { request, options, status: 200, statusCode: 200, name: "\u6587\u4EF6\u4E0B\u8F7D", message: "\u6587\u4EF6\u4E0B\u8F7D\u5B8C\u6210", data: res, alert: "toast" }); succ = true; }).catch((res) => { showError(http2.runtime, { request, options, status: 400, statusCode: 400, name: "\u6587\u4EF6\u4E0B\u8F7D", message: "\u6587\u4EF6\u4E0B\u8F7D\u5931\u8D25\uFF0C\u8BF7\u68C0\u67E5", data: res, alert: "toast" }); succ = false; }); return succ; } async function HttpApi(http2, api, options) { if (!hasObjectName(api, "url") || !isString(api.url)) throw new Error("\u65E0\u6548\u7684 API \u914D\u7F6E"); api.method = api.method || "GET"; api.method = api.method.toUpperCase(); !hasObject(options) && (options = {}); options.method = api.method; options.headers = api.headers; const isPayload = isPayloadMethod(api.method); if (isPayload || isArray(api.data)) { options.body = api.data; } else { options.query = api.data; } api.timeout && api.timeout > 0 && (options.timeout = api.timeout); api.keepalive && (options.keepalive = api.keepalive); let succ = false; let value; await (options.cacheTime && options.cacheTime > 0 ? HttpCache(http2, api.url, options) : http2(api.url, options)).then((res) => { value = res; succ = true; con.success("API \u8BF7\u6C42\u6210\u529F", api.url); }).catch((res) => { value = res; con.error("API \u8BF7\u6C42\u5F02\u5E38", api.url); }); if (succ) { isFn(api.success) && api.success(value); } else { isFn(api.fail) && api.fail(value); } return isFn(api.complete) ? await api.complete({ succ, value, api }) : { succ, value, api }; } async function RetryLogin(context, config) { if (SERVERMODE || !config || !config.http) return; const { request, options, response } = context; if (!response || response.status !== 401 || !options.autoLogin || !isFn(config.login)) return; const info = `[${options.method}]${response.url}`; con.debug("401 \u9519\u8BEF\uFF0C\u5C1D\u8BD5\u91CD\u65B0\u767B\u9646", config.reLogin, info); if (config.reLogin === 0) { config.reLogin = 1; con.debug("401 \u9519\u8BEF\uFF0C\u91CD\u65B0\u767B\u9646\u4E2D...", config.reLogin, info); const succ = await config.login(request, options, config); if (succ) { config.reLogin = 3; con.success("401 \u9519\u8BEF\uFF0C\u91CD\u65B0\u767B\u5F55\u6210\u529F", info); } else { config.reLogin = 2; con.error("401 \u9519\u8BEF\uFF0C\u91CD\u65B0\u767B\u5F55\u5931\u8D25", info); } } while (config.reLogin === 1) { await sleep(2e3); con.debug("\u7B49\u5F85\u5237\u65B0", config.reLogin, info); } if (config.reLogin === 3) { con.debug("\u91CD\u8BD5\u8BF7\u6C42", config.reLogin, info); config.reLogin = 4; return config.http(request, options).then((res) => { config.reLogin = 0; return res; }); } else { config.reLogin = 0; } } // src/http/index.ts var http; var createHttp2 = () => { if (!http) http = createHttp(); return http; }; export { Tasks, createTasks, createCache, HTTP_DEBUG, createHttp2 as createHttp };