初始提交

This commit is contained in:
焦龙言
2026-07-24 09:43:10 +08:00
commit b5b4721456
793 changed files with 116305 additions and 0 deletions
+13
View File
@@ -0,0 +1,13 @@
const TokenKey = "App-Token";
export function getToken() {
return uni.getStorageSync(TokenKey);
}
export function setToken(token) {
return uni.setStorageSync(TokenKey, token);
}
export function removeToken() {
return uni.removeStorageSync(TokenKey);
}
+58
View File
@@ -0,0 +1,58 @@
/**
* 显示消息提示框
* @param content 提示的标题
*/
export function toast(content) {
uni.showToast({
icon: "none",
title: content,
});
}
/**
* 显示模态弹窗
* @param content 提示的标题
*/
export function showConfirm(content) {
return new Promise((resolve, reject) => {
uni.showModal({
title: "提示",
content: content,
cancelText: "取消",
confirmText: "确定",
success: function (res) {
resolve(res);
},
});
});
}
/**
* 参数处理
* @param params 参数
*/
export function tansParams(params) {
let result = "";
for (const propName of Object.keys(params)) {
const value = params[propName];
var part = encodeURIComponent(propName) + "=";
if (value !== null && value !== "" && typeof value !== "undefined") {
if (typeof value === "object") {
for (const key of Object.keys(value)) {
if (
value[key] !== null &&
value[key] !== "" &&
typeof value[key] !== "undefined"
) {
let params = propName + "[" + key + "]";
var subPart = encodeURIComponent(params) + "=";
result += subPart + encodeURIComponent(value[key]) + "&";
}
}
} else {
result += part + encodeURIComponent(value) + "&";
}
}
}
return result;
}
@@ -0,0 +1,9 @@
const constant = {
avatar: "user_avatar",
id: "user_id",
name: "user_name",
roles: "user_roles",
permissions: "user_permissions",
};
export default constant;
+30
View File
@@ -0,0 +1,30 @@
import useDictStore from "@/store/modules/dict";
import { getDicts } from "@/api/system/dict/data";
import { ref, toRefs } from "vue";
/**
* 获取字典数据
*/
export function useDict(...args) {
const res = ref({});
return (() => {
args.forEach((dictType, index) => {
res.value[dictType] = [];
const dicts = useDictStore().getDict(dictType);
if (dicts) {
res.value[dictType] = dicts;
} else {
getDicts(dictType).then((resp) => {
res.value[dictType] = resp.data.map((p) => ({
label: p.dictLabel,
value: p.dictValue,
elTagType: p.listClass,
elTagClass: p.cssClass,
}));
useDictStore().setDict(dictType, res.value[dictType]);
});
}
});
return toRefs(res.value);
})();
}
@@ -0,0 +1,6 @@
export default {
401: "认证失败,无法访问系统资源",
403: "当前操作没有权限",
404: "访问资源不存在",
default: "系统未知错误,请反馈给管理员",
};
@@ -0,0 +1,55 @@
import store from "@/store";
/**
* 字符权限校验
* @param {Array} value 校验值
* @returns {Boolean}
*/
export function checkPermi(value) {
if (value && value instanceof Array && value.length > 0) {
const permissions = store.getters && store.getters.permissions;
const permissionDatas = value;
const all_permission = "*:*:*";
const hasPermission = permissions.some((permission) => {
return (
all_permission === permission || permissionDatas.includes(permission)
);
});
if (!hasPermission) {
return false;
}
return true;
} else {
console.error(
`need roles! Like checkPermi="['system:user:add','system:user:edit']"`,
);
return false;
}
}
/**
* 角色权限校验
* @param {Array} value 校验值
* @returns {Boolean}
*/
export function checkRole(value) {
if (value && value instanceof Array && value.length > 0) {
const roles = store.getters && store.getters.roles;
const permissionRoles = value;
const super_admin = "admin";
const hasRole = roles.some((role) => {
return super_admin === role || permissionRoles.includes(role);
});
if (!hasRole) {
return false;
}
return true;
} else {
console.error(`need roles! Like checkRole="['admin','editor']"`);
return false;
}
}
+131
View File
@@ -0,0 +1,131 @@
import config from "@/config";
import { getToken } from "@/utils/auth";
import errorCode from "@/utils/errorCode";
import { useUserStore } from "@/store/modules/user";
import { toast, showConfirm, tansParams } from "@/utils/common";
import {
decryptTransportErrorResponse,
decryptTransportResponse,
encryptTransportRequest,
invalidateTransportKeyMeta,
resetTransportRequestConfig,
shouldRetryTransportWithFreshKey,
} from "@/utils/transportCrypto";
let timeout = 10000;
const baseUrl = config.baseUrl;
const request = async (config) => {
// 是否需要设置 token
const isToken = (config.headers || {}).isToken === false;
config.header = config.header || {};
config.headers = config.headers || {};
if (getToken() && !isToken) {
config.header["Authorization"] = "Bearer " + getToken();
}
try {
config = await encryptTransportRequest(config);
// get请求映射params参数
if (config.params) {
let url = config.url + "?" + tansParams(config.params);
url = url.slice(0, -1);
config.url = url;
}
return await new Promise((resolve, reject) => {
uni.request({
method: config.method || "get",
timeout: config.timeout || timeout,
url: config.baseUrl || baseUrl + config.url,
data: config.data,
header: config.header,
dataType: "json",
success: async (response) => {
try {
const res = await decryptTransportResponse(response, config);
if (shouldRetryTransportWithFreshKey(res) && !config.__transportRetried) {
invalidateTransportKeyMeta();
config.__transportRetried = true;
config.headers.repeatSubmit = false;
resetTransportRequestConfig(config);
resolve(await request(config));
return;
}
const code = res.data.code || 200;
const msg = errorCode[code] || res.data.msg || errorCode["default"];
if (code === 401) {
showConfirm(
"登录状态已过期,您可以继续留在该页面,或者重新登录?",
).then((res) => {
if (res.confirm) {
useUserStore()
.logOut()
.then(() => {
uni.reLaunch({ url: "/pages/login" });
});
}
});
const error = new Error("无效的会话,或者会话已过期,请重新登录。");
error.response = res;
reject(error);
} else if (code === 500) {
const error = new Error(msg);
error.response = res;
reject(error);
} else if (code !== 200) {
const error = new Error(msg);
error.response = res;
reject(error);
} else {
resolve(res.data);
}
} catch (error) {
reject(error);
}
},
fail: reject,
});
});
} catch (error) {
error = await decryptTransportErrorResponse(error, config);
if (shouldRetryTransportWithFreshKey(error) && !config.__transportRetried) {
invalidateTransportKeyMeta();
config.__transportRetried = true;
config.headers.repeatSubmit = false;
resetTransportRequestConfig(config);
return request(config);
}
const response = error.response;
const responseStatus = response?.status ?? response?.statusCode;
const responseCode = response?.data?.code;
const responseMsg = response?.data?.msg;
if (responseMsg) {
uni.showToast({
title: responseMsg,
icon: "none",
duration: responseStatus === 429 || responseCode === 429 ? 5000 : 3000,
});
throw error;
}
let { message } = error;
if (message === "Network Error") {
message = "后端接口连接异常";
} else if (message && message.includes("timeout")) {
message = "系统接口请求超时";
} else if (message && message.includes("Request failed with status code")) {
message = "系统接口" + message.substr(message.length - 3) + "异常";
}
if (message) {
toast(message);
}
throw error;
}
};
export default request;
@@ -0,0 +1,38 @@
import constant from "./constant";
// 存储变量名
let storageKey = "storage_data";
// 存储节点变量名
let storageNodeKeys = [
constant.avatar,
constant.id,
constant.name,
constant.roles,
constant.permissions,
];
const storage = {
set: function (key, value) {
if (storageNodeKeys.indexOf(key) != -1) {
let tmp = uni.getStorageSync(storageKey);
tmp = tmp ? tmp : {};
tmp[key] = value;
uni.setStorageSync(storageKey, tmp);
}
},
get: function (key) {
let storageData = uni.getStorageSync(storageKey) || {};
return storageData[key] || "";
},
remove: function (key) {
let storageData = uni.getStorageSync(storageKey) || {};
delete storageData[key];
uni.setStorageSync(storageKey, storageData);
},
clean: function () {
uni.removeStorageSync(storageKey);
},
};
export default storage;
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,436 @@
import config from "@/config";
const TRANSPORT_BASE_URL = config.baseUrl;
const EXCLUDED_URL_PATTERNS = [
"/transport/crypto/frontend-config",
"/transport/crypto/public-key",
"/common/download",
"/common/download/resource",
];
const TRANSPORT_FRONTEND_CONFIG_CACHE_KEY = "transportCryptoFrontendConfig";
const TRANSPORT_FRONTEND_CONFIG_URL = "/transport/crypto/frontend-config";
const TRANSPORT_FRONTEND_CONFIG_FALLBACK_TTL_SECONDS = 60;
const DEFAULT_TRANSPORT_ENVELOPE_VERSION = "1";
const DEFAULT_REQUEST_ENVELOPE_ALGORITHM = "RSA_OAEP_AES_256_GCM";
const DEFAULT_RESPONSE_ENVELOPE_ALGORITHM = "AES_256_GCM";
const DEFAULT_TRANSPORT_MAX_GET_URL_LENGTH = 4096;
let cachedTransportPolicy = null;
let inflightTransportPolicyPromise = null;
/**
* 获取当前 Unix 秒级时间戳。
*
* @returns {number} 当前时间戳
*/
function getNowTimestamp() {
return Math.floor(Date.now() / 1000);
}
/**
* 判断请求地址是否命中固定排除名单。
*
* @param {string} url 请求地址
* @returns {boolean} 是否命中排除规则
*/
function matchExcludedUrl(url = "") {
return EXCLUDED_URL_PATTERNS.some((pattern) => url.includes(pattern));
}
/**
* 判断请求路径是否命中路径前缀列表。
*
* @param {string} path 待匹配路径
* @param {string[]} pathPatterns 路径前缀集合
* @returns {boolean} 是否匹配成功
*/
function matchPathPrefix(path = "", pathPatterns = []) {
return pathPatterns.some(
(pattern) => path === pattern || path.startsWith(`${pattern}/`),
);
}
/**
* 从绝对地址中提取 pathname,或直接返回相对地址的路径部分。
*
* @param {string} url 请求地址
* @returns {string} 标准化后的路径
*/
function parseAbsoluteUrlPath(url = "") {
const normalizedUrl = String(url || "");
if (!normalizedUrl) {
return "/";
}
if (
!normalizedUrl.startsWith("http://") &&
!normalizedUrl.startsWith("https://")
) {
return normalizedUrl.split("?")[0] || "/";
}
const pathMatch = normalizedUrl.match(/^https?:\/\/[^/]+(\/[^?#]*)?/i);
return pathMatch?.[1] || "/";
}
/**
* 解析基础 API 地址对应的路径前缀。
*
* @param {string} baseUrl 基础 API 地址
* @returns {string} 基础路径前缀
*/
function getBaseApiPath(baseUrl = TRANSPORT_BASE_URL) {
if (!baseUrl) {
return "";
}
const baseApiPath = parseAbsoluteUrlPath(baseUrl);
return baseApiPath === "/" ? "" : baseApiPath;
}
/**
* 计算后端用于 AAD 与策略匹配的标准请求路径。
*
* @param {string} url 请求地址
* @param {string} baseUrl 基础 API 地址
* @returns {string} 标准化请求路径
*/
function getRequestPath(url = "", baseUrl = TRANSPORT_BASE_URL) {
const baseApiPath = getBaseApiPath(baseUrl);
const pathname = parseAbsoluteUrlPath(url);
if (baseApiPath && pathname.startsWith(baseApiPath)) {
const normalizedPath = pathname.slice(baseApiPath.length);
return normalizedPath || "/";
}
return pathname || "/";
}
/**
* 标准化后端下发的路径列表配置。
*
* @param {Array} paths 原始路径集合
* @returns {string[]} 标准化后的路径数组
*/
function normalizePaths(paths) {
if (!Array.isArray(paths)) {
return [];
}
return paths.map((path) => String(path || "").trim()).filter(Boolean);
}
/**
* 从请求头对象中读取指定字段。
*
* @param {Object} headers 请求头对象
* @param {string} name 请求头名称
* @returns {*} 请求头值
*/
function getHeaderValue(headers, name) {
if (!headers) {
return undefined;
}
return headers[name] ?? headers[name.toLowerCase()] ?? headers[name.toUpperCase()];
}
/**
* 将后端配置响应转换为前端统一使用的策略对象。
*
* @param {Object} payload 后端返回的配置数据
* @returns {Object} 标准化后的传输加密策略
*/
function normalizeTransportPolicy(payload) {
return {
transportCryptoEnabled: Boolean(payload?.transportCryptoEnabled),
transportCryptoMode: String(payload?.transportCryptoMode || "off"),
transportCryptoActive: Boolean(payload?.transportCryptoActive),
envelopeVersion: String(
payload?.envelopeVersion || DEFAULT_TRANSPORT_ENVELOPE_VERSION,
),
publicKeyUrl: String(payload?.publicKeyUrl || "/transport/crypto/public-key"),
requestEnvelopeAlgorithm: String(
payload?.requestEnvelopeAlgorithm || DEFAULT_REQUEST_ENVELOPE_ALGORITHM,
),
responseEnvelopeAlgorithm: String(
payload?.responseEnvelopeAlgorithm || DEFAULT_RESPONSE_ENVELOPE_ALGORITHM,
),
enabledPaths: normalizePaths(payload?.enabledPaths),
requiredPaths: normalizePaths(payload?.requiredPaths),
excludePaths: normalizePaths(payload?.excludePaths),
maxEncryptedGetUrlLength: Number(
payload?.maxEncryptedGetUrlLength || DEFAULT_TRANSPORT_MAX_GET_URL_LENGTH,
),
configExpireAt: Number(payload?.configExpireAt || 0),
retryAt: Number(payload?.retryAt || payload?.configExpireAt || 0),
};
}
/**
* 构建不可用场景下的本地兜底策略。
*
* @returns {Object} 明文回退策略
*/
function buildFallbackTransportPolicy() {
const nowTimestamp = getNowTimestamp();
return {
transportCryptoEnabled: false,
transportCryptoMode: "off",
transportCryptoActive: false,
envelopeVersion: DEFAULT_TRANSPORT_ENVELOPE_VERSION,
publicKeyUrl: "/transport/crypto/public-key",
requestEnvelopeAlgorithm: DEFAULT_REQUEST_ENVELOPE_ALGORITHM,
responseEnvelopeAlgorithm: DEFAULT_RESPONSE_ENVELOPE_ALGORITHM,
enabledPaths: [],
requiredPaths: [],
excludePaths: [...EXCLUDED_URL_PATTERNS],
maxEncryptedGetUrlLength: DEFAULT_TRANSPORT_MAX_GET_URL_LENGTH,
configExpireAt:
nowTimestamp + TRANSPORT_FRONTEND_CONFIG_FALLBACK_TTL_SECONDS,
retryAt: nowTimestamp + TRANSPORT_FRONTEND_CONFIG_FALLBACK_TTL_SECONDS,
};
}
/**
* 基于旧策略生成短期可重试的缓存策略。
*
* @param {Object} policy 旧的策略对象
* @returns {Object} 可重试策略
*/
function buildRetryableTransportPolicy(policy) {
const normalizedPolicy = normalizeTransportPolicy(policy);
return {
...normalizedPolicy,
retryAt: getNowTimestamp() + TRANSPORT_FRONTEND_CONFIG_FALLBACK_TTL_SECONDS,
};
}
/**
* 判断当前策略是否仍在可用期内。
*
* @param {Object} policy 待校验策略
* @returns {boolean} 是否可继续使用
*/
function isUsableTransportPolicy(policy) {
if (!policy || !policy.publicKeyUrl || !policy.retryAt) {
return false;
}
return policy.retryAt > getNowTimestamp();
}
/**
* 从本地缓存加载最近一次持久化的策略。
*
* @returns {Object|null} 缓存策略
*/
function loadPersistedTransportPolicy() {
const persistedTransportPolicy = uni.getStorageSync(
TRANSPORT_FRONTEND_CONFIG_CACHE_KEY,
);
if (!persistedTransportPolicy) {
return null;
}
return normalizeTransportPolicy(persistedTransportPolicy);
}
/**
* 请求后端传输加密前端配置。
* 这里直接使用原始 uni.request,避免策略初始化阶段反向依赖统一 request
* 包装器,导致“是否加密尚未判定时又要先走加密请求”的循环依赖。
*
* @returns {Promise<Object>} Uni 请求响应结果
*/
function requestFrontendConfig() {
return new Promise((resolve, reject) => {
uni.request({
url: `${TRANSPORT_BASE_URL}${TRANSPORT_FRONTEND_CONFIG_URL}`,
method: "GET",
timeout: 10000,
success: resolve,
fail: reject,
});
});
}
/**
* 获取请求加密使用的标准路径。
*
* @param {string} url 请求地址
* @param {string} baseUrl 基础 API 地址
* @returns {string} 标准请求路径
*/
export function getTransportRequestPath(
url = "",
baseUrl = TRANSPORT_BASE_URL,
) {
return getRequestPath(url, baseUrl);
}
/**
* 获取当前生效的传输加密策略。
*
* @returns {Object} 当前策略对象
*/
export function getTransportCryptoPolicy() {
return cachedTransportPolicy || buildFallbackTransportPolicy();
}
/**
* 清空当前策略缓存与持久化数据。
*
* @returns {void}
*/
export function invalidateTransportCryptoPolicy() {
cachedTransportPolicy = null;
inflightTransportPolicyPromise = null;
uni.removeStorageSync(TRANSPORT_FRONTEND_CONFIG_CACHE_KEY);
}
/**
* 确保本地已加载一份可用的传输加密策略。
*
* @param {boolean} forceRefresh 是否强制从后端刷新
* @returns {Promise<Object>} 当前可用策略
*/
export async function ensureTransportCryptoPolicyLoaded(forceRefresh = false) {
if (!forceRefresh && !cachedTransportPolicy) {
const persistedTransportPolicy = loadPersistedTransportPolicy();
if (isUsableTransportPolicy(persistedTransportPolicy)) {
cachedTransportPolicy = persistedTransportPolicy;
}
}
if (!forceRefresh && isUsableTransportPolicy(cachedTransportPolicy)) {
return cachedTransportPolicy;
}
if (inflightTransportPolicyPromise) {
return inflightTransportPolicyPromise;
}
inflightTransportPolicyPromise = requestFrontendConfig()
.then((response) => {
const payload = normalizeTransportPolicy(response?.data?.data || {});
cachedTransportPolicy = payload;
uni.setStorageSync(TRANSPORT_FRONTEND_CONFIG_CACHE_KEY, payload);
inflightTransportPolicyPromise = null;
return cachedTransportPolicy;
})
.catch((error) => {
const staleTransportPolicy =
cachedTransportPolicy || loadPersistedTransportPolicy();
inflightTransportPolicyPromise = null;
cachedTransportPolicy = staleTransportPolicy
? buildRetryableTransportPolicy(staleTransportPolicy)
: buildFallbackTransportPolicy();
uni.setStorageSync(
TRANSPORT_FRONTEND_CONFIG_CACHE_KEY,
cachedTransportPolicy,
);
if (staleTransportPolicy) {
console.warn(
"加载传输加密前端配置失败,当前继续沿用最近一次后端策略",
error,
);
} else {
console.warn("加载传输加密前端配置失败,当前回退为明文请求策略", error);
}
return cachedTransportPolicy;
});
return inflightTransportPolicyPromise;
}
/**
* 判断当前请求是否需要执行请求体加密。
*
* @param {Object} requestConfig 请求配置
* @param {Object} transportPolicy 传输加密策略
* @returns {boolean} 是否启用请求加密
*/
export function shouldEncryptRequest(
requestConfig,
transportPolicy = getTransportCryptoPolicy(),
) {
if (!transportPolicy.transportCryptoActive) {
return false;
}
const requestPath = getRequestPath(
requestConfig.url,
requestConfig.baseUrl || TRANSPORT_BASE_URL,
);
if (matchPathPrefix(requestPath, transportPolicy.excludePaths || [])) {
return false;
}
if (
(transportPolicy.enabledPaths || []).length &&
!matchPathPrefix(requestPath, transportPolicy.enabledPaths || [])
) {
return false;
}
if ((requestConfig.headers || {}).encrypt === false) {
return false;
}
if (matchExcludedUrl(requestConfig.url)) {
return false;
}
const contentType =
getHeaderValue(requestConfig.header, "Content-Type") ||
getHeaderValue(requestConfig.headers, "Content-Type") ||
"";
if (String(contentType).includes("multipart/form-data")) {
return false;
}
return true;
}
/**
* 判断当前响应是否需要执行自动解密。
*
* @param {Object} requestConfig 请求配置
* @param {Object} transportPolicy 传输加密策略
* @returns {boolean} 是否启用响应解密
*/
export function shouldEncryptResponse(
requestConfig,
transportPolicy = getTransportCryptoPolicy(),
) {
const requestPath = getRequestPath(
requestConfig.url,
requestConfig.baseUrl || TRANSPORT_BASE_URL,
);
if (matchPathPrefix(requestPath, transportPolicy.excludePaths || [])) {
return false;
}
if (
(transportPolicy.enabledPaths || []).length &&
!matchPathPrefix(requestPath, transportPolicy.enabledPaths || [])
) {
return false;
}
if ((requestConfig.headers || {}).encryptResponse === false) {
return false;
}
if (matchExcludedUrl(requestConfig.url)) {
return false;
}
if (requestConfig.__transportCryptoEnabledForRequest === true) {
return true;
}
if (requestConfig.__transportCryptoEnabledForRequest === false) {
return false;
}
return transportPolicy.transportCryptoActive;
}
/**
* 判断查询参数是否需要封装为加密信封。
*
* @param {Object} requestConfig 请求配置
* @param {Object} transportPolicy 传输加密策略
* @returns {boolean} 是否启用查询参数加密
*/
export function shouldEncryptQuery(
requestConfig,
transportPolicy = getTransportCryptoPolicy(),
) {
if ((requestConfig.headers || {}).encryptQuery === false) {
return false;
}
return shouldEncryptRequest(requestConfig, transportPolicy);
}
@@ -0,0 +1,214 @@
import forge from "node-forge/lib/forge";
import "node-forge/lib/util";
import "node-forge/lib/asn1";
import "node-forge/lib/oids";
import "node-forge/lib/cipher";
import "node-forge/lib/cipherModes";
import "node-forge/lib/aes";
import "node-forge/lib/jsbn";
import "node-forge/lib/pkcs1";
import "node-forge/lib/prime";
import "node-forge/lib/random";
import "node-forge/lib/md";
import "node-forge/lib/sha256";
import "node-forge/lib/mgf1";
import "node-forge/lib/pem";
import "node-forge/lib/pki";
let randomBytePool = "";
const FORGE_RANDOM_REFILL_BYTES = 4096;
/**
* 将 Uint8Array 转为 Forge 兼容的二进制字符串。
*
* @param {Uint8Array} uint8Array 字节数组
* @returns {string} 二进制字符串
*/
function uint8ArrayToBytes(uint8Array) {
return Array.from(uint8Array, (item) => String.fromCharCode(item)).join("");
}
/**
* 获取当前运行时的全局对象引用。
*
* @returns {Object|undefined} 全局对象
*/
function getRuntimeGlobal() {
if (typeof globalThis !== "undefined") {
return globalThis;
}
if (typeof self !== "undefined") {
return self;
}
if (typeof window !== "undefined") {
return window;
}
if (typeof global !== "undefined") {
return global;
}
return undefined;
}
/**
* 通过 Web Crypto 同步获取随机字节。
*
* @param {number} length 需要的字节长度
* @returns {string|null} 随机字节串,当前运行环境不支持时返回 null
*/
function getWebCryptoRandomBytes(length) {
const runtimeCrypto = getRuntimeGlobal()?.crypto;
if (!runtimeCrypto?.getRandomValues) {
return null;
}
const bytes = new Uint8Array(length);
runtimeCrypto.getRandomValues(bytes);
return uint8ArrayToBytes(bytes);
}
/**
* 在 app-plus iOS 端通过原生 NSUUID 获取随机字节兜底。
*
* @param {number} length 需要的字节长度
* @returns {string|null} 随机字节串,当前运行环境不支持时返回 null
*/
function getAppPlusIosRandomBytes(length) {
// #ifdef APP-PLUS
if (typeof plus === "undefined") {
return null;
}
const osName = String(plus.os?.name || "").toLowerCase();
if (osName !== "ios" || typeof plus.ios?.importClass !== "function") {
return null;
}
try {
const uuidClass = plus.ios.importClass("NSUUID");
let randomBytes = "";
while (randomBytes.length < length) {
const uuidObject = plus.ios.invoke(uuidClass, "UUID");
const uuidText = String(plus.ios.invoke(uuidObject, "UUIDString") || "")
.replace(/-/g, "")
.toLowerCase();
plus.ios.deleteObject(uuidObject);
if (!uuidText) {
return null;
}
for (
let index = 0;
index < uuidText.length && randomBytes.length < length;
index += 2
) {
const byteValue = parseInt(uuidText.slice(index, index + 2), 16);
if (isNaN(byteValue)) {
return null;
}
randomBytes += String.fromCharCode(byteValue);
}
}
return randomBytes;
} catch (error) {
return null;
}
// #endif
return null;
}
/**
* 在 app-plus Android 端通过原生 SecureRandom 获取随机字节兜底。
*
* @param {number} length 需要的字节长度
* @returns {string|null} 随机字节串,当前运行环境不支持时返回 null
*/
function getAppPlusAndroidRandomBytes(length) {
// #ifdef APP-PLUS
if (typeof plus === "undefined") {
return null;
}
const osName = String(plus.os?.name || "").toLowerCase();
if (osName !== "android" || typeof plus.android?.importClass !== "function") {
return null;
}
try {
plus.android.importClass("java.security.SecureRandom");
const secureRandom =
typeof plus.android.newObject === "function"
? plus.android.newObject("java.security.SecureRandom")
: null;
if (!secureRandom) {
return null;
}
plus.android.importClass(secureRandom);
let randomBytes = "";
for (let index = 0; index < length; index += 1) {
randomBytes += String.fromCharCode(secureRandom.nextInt(256));
}
if (typeof plus.android.autoCollection === "function") {
plus.android.autoCollection(secureRandom);
}
return randomBytes;
} catch (error) {
return null;
}
// #endif
return null;
}
/**
* 在随机池不足时尝试同步补充一段新的随机字节。
*
* @param {number} length 当前至少需要的字节长度
* @returns {void}
*/
function refillRandomBytePool(length) {
const refillLength = Math.max(length, FORGE_RANDOM_REFILL_BYTES);
const randomBytes =
getWebCryptoRandomBytes(refillLength) ||
getAppPlusAndroidRandomBytes(refillLength) ||
getAppPlusIosRandomBytes(refillLength);
if (randomBytes) {
randomBytePool += randomBytes;
}
}
/**
* 从随机数池中按需提取字节串。
*
* @param {number} length 需要提取的字节长度
* @returns {string} Forge 使用的二进制字符串
*/
function consumeRandomBytes(length) {
if (randomBytePool.length < length) {
refillRandomBytePool(length);
}
if (randomBytePool.length < length) {
throw new Error("传输加密随机数池不足,且当前运行环境无法同步补充安全随机数");
}
const bytes = randomBytePool.slice(0, length);
randomBytePool = randomBytePool.slice(length);
return bytes;
}
forge.random.getBytesSync = function getBytesSync(length) {
return consumeRandomBytes(length);
};
forge.random.getBytes = function getBytes(length, callback) {
const bytes = consumeRandomBytes(length);
if (typeof callback === "function") {
callback(null, bytes);
}
return bytes;
};
/**
* 向 Forge 随机数池预填充平台侧生成的随机字节。
*
* @param {string} bytes 待注入的二进制字符串
* @returns {void}
*/
export function primeForgeRandomBytes(bytes) {
randomBytePool += String(bytes || "");
}
export default forge;
+72
View File
@@ -0,0 +1,72 @@
import { useUserStore } from "@/store";
import config from "@/config";
import { getToken } from "@/utils/auth";
import errorCode from "@/utils/errorCode";
import { toast, showConfirm, tansParams } from "@/utils/common";
let timeout = 10000;
const baseUrl = config.baseUrl;
export default function upload(config) {
// 是否需要设置 token
const isToken = (config.headers || {}).isToken === false;
config.header = config.header || {};
if (getToken() && !isToken) {
config.header["Authorization"] = "Bearer " + getToken();
}
// get请求映射params参数
if (config.params) {
let url = config.url + "?" + tansParams(config.params);
url = url.slice(0, -1);
config.url = url;
}
return new Promise((resolve, reject) => {
uni.uploadFile({
timeout: config.timeout || timeout,
url: baseUrl + config.url,
filePath: config.filePath,
name: config.name || "file",
header: config.header,
formData: config.formData,
success: (res) => {
let result = JSON.parse(res.data);
const code = result.code || 200;
const msg = errorCode[code] || result.msg || errorCode["default"];
if (code === 200) {
resolve(result);
} else if (code == 401) {
showConfirm(
"登录状态已过期,您可以继续留在该页面,或者重新登录?",
).then((res) => {
if (res.confirm) {
useUserStore()
.logOut()
.then((res) => {
uni.reLaunch({ url: "/pages/login/login" });
});
}
});
reject("无效的会话,或者会话已过期,请重新登录。");
} else if (code === 500) {
toast(msg);
reject("500");
} else if (code !== 200) {
toast(msg);
reject(code);
}
},
fail: (error) => {
let { message } = error;
if (message == "Network Error") {
message = "后端接口连接异常";
} else if (message.includes("timeout")) {
message = "系统接口请求超时";
} else if (message.includes("Request failed with status code")) {
message = "系统接口" + message.substr(message.length - 3) + "异常";
}
toast(message);
reject(error);
},
});
});
}
+124
View File
@@ -0,0 +1,124 @@
/**
* 路径匹配器
* @param {string} pattern
* @param {string} path
* @returns {Boolean}
*/
export function isPathMatch(pattern, path) {
const regexPattern = pattern
.replace(/\//g, "\\/")
.replace(/\*\*/g, ".*")
.replace(/\*/g, "[^\\/]*");
const regex = new RegExp(`^${regexPattern}$`);
return regex.test(path);
}
/**
* 判断value字符串是否为空
* @param {string} value
* @returns {Boolean}
*/
export function isEmpty(value) {
if (
value == null ||
value == "" ||
value == undefined ||
value == "undefined"
) {
return true;
}
return false;
}
/**
* 判断url是否是http或https
* @param {string} url
* @returns {Boolean}
*/
export function isHttp(url) {
return url.indexOf("http://") !== -1 || url.indexOf("https://") !== -1;
}
/**
* 判断path是否为外链
* @param {string} path
* @returns {Boolean}
*/
export function isExternal(path) {
return /^(https?:|mailto:|tel:)/.test(path);
}
/**
* @param {string} str
* @returns {Boolean}
*/
export function validUsername(str) {
const valid_map = ["admin", "editor"];
return valid_map.indexOf(str.trim()) >= 0;
}
/**
* @param {string} url
* @returns {Boolean}
*/
export function validURL(url) {
const reg =
/^(https?|ftp):\/\/([a-zA-Z0-9.-]+(:[a-zA-Z0-9.&%$-]+)*@)*((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}|([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+\.(com|edu|gov|int|mil|net|org|biz|arpa|info|name|pro|aero|coop|museum|[a-zA-Z]{2}))(:[0-9]+)*(\/($|[a-zA-Z0-9.,?'\\+&%$#=~_-]+))*$/;
return reg.test(url);
}
/**
* @param {string} str
* @returns {Boolean}
*/
export function validLowerCase(str) {
const reg = /^[a-z]+$/;
return reg.test(str);
}
/**
* @param {string} str
* @returns {Boolean}
*/
export function validUpperCase(str) {
const reg = /^[A-Z]+$/;
return reg.test(str);
}
/**
* @param {string} str
* @returns {Boolean}
*/
export function validAlphabets(str) {
const reg = /^[A-Za-z]+$/;
return reg.test(str);
}
/**
* @param {string} email
* @returns {Boolean}
*/
export function validEmail(email) {
const reg =
/^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
return reg.test(email);
}
/**
* @param {string} str
* @returns {Boolean}
*/
export function isString(str) {
return typeof str === "string" || str instanceof String;
}
/**
* @param {Array} arg
* @returns {Boolean}
*/
export function isArray(arg) {
if (typeof Array.isArray === "undefined") {
return Object.prototype.toString.call(arg) === "[object Array]";
}
return Array.isArray(arg);
}