From 516b204b3852611ddcfce673e276d53b6451f26a Mon Sep 17 00:00:00 2001 From: hongawen <83944980@qq.com> Date: Mon, 30 Mar 2026 16:24:00 +0800 Subject: [PATCH] =?UTF-8?q?feat(components):=20=E8=B0=83=E6=95=B4=E5=8A=A0?= =?UTF-8?q?=E5=AF=86=E6=96=B9=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 1 + packages/scripts/src/locales/index.ts | 2 - pnpm-lock.yaml | 8 ++ src/service/request/api-encrypt.ts | 121 ++++++++++++++++++++++++++ src/service/request/index.ts | 2 + 5 files changed, 132 insertions(+), 2 deletions(-) create mode 100644 src/service/request/api-encrypt.ts diff --git a/package.json b/package.json index 9dc494b..136cf0c 100644 --- a/package.json +++ b/package.json @@ -62,6 +62,7 @@ "echarts": "6.0.0", "element-plus": "^2.11.1", "jsbarcode": "3.12.1", + "jsencrypt": "^3.5.4", "json5": "2.2.3", "nprogress": "0.2.0", "pinia": "3.0.3", diff --git a/packages/scripts/src/locales/index.ts b/packages/scripts/src/locales/index.ts index 74321d0..c1af45c 100644 --- a/packages/scripts/src/locales/index.ts +++ b/packages/scripts/src/locales/index.ts @@ -11,7 +11,6 @@ export const locales = { }, gitCommitTypes: [ ['feat', '新功能'], - ['feat-wip', '开发中的功能,比如某功能的部分代码'], ['fix', '修复Bug'], ['docs', '只涉及文档更新'], ['typo', '代码或文档勘误,比如错误拼写'], @@ -49,7 +48,6 @@ export const locales = { }, gitCommitTypes: [ ['feat', 'A new feature'], - ['feat-wip', 'Features in development, such as partial code for a certain feature'], ['fix', 'A bug fix'], ['docs', 'Documentation only changes'], ['typo', 'Code or document corrections, such as spelling errors'], diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 037815f..1323948 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -83,6 +83,9 @@ importers: jsbarcode: specifier: 3.12.1 version: 3.12.1 + jsencrypt: + specifier: ^3.5.4 + version: 3.5.4 json5: specifier: 2.2.3 version: 2.2.3 @@ -3754,6 +3757,9 @@ packages: jsbarcode@3.12.1: resolution: {integrity: sha512-QZQSqIknC2Rr/YOUyOkCBqsoiBAOTYK+7yNN3JsqfoUtJtkazxNw1dmPpxuv7VVvqW13kA3/mKiLq+s/e3o9hQ==} + jsencrypt@3.5.4: + resolution: {integrity: sha512-kNjfYEMNASxrDGsmcSQh/rUTmcoRfSUkxnAz+MMywM8jtGu+fFEZ3nJjHM58zscVnwR0fYmG9sGkTDjqUdpiwA==} + jsesc@3.0.2: resolution: {integrity: sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==} engines: {node: '>=6'} @@ -9172,6 +9178,8 @@ snapshots: jsbarcode@3.12.1: {} + jsencrypt@3.5.4: {} + jsesc@3.0.2: {} jsesc@3.1.0: {} diff --git a/src/service/request/api-encrypt.ts b/src/service/request/api-encrypt.ts new file mode 100644 index 0000000..f974970 --- /dev/null +++ b/src/service/request/api-encrypt.ts @@ -0,0 +1,121 @@ +import type { InternalAxiosRequestConfig } from 'axios'; +import { JSEncrypt } from 'jsencrypt'; +import { SYSTEM_SERVICE_PREFIX } from '@/constants/service'; + +const API_ENCRYPT_HEADER = 'X-Api-Encrypt'; +const API_ENCRYPT_HEADER_VALUE = 'true'; + +const API_ENCRYPT_CONFIG_ERROR_MSG = '前端加密配置异常,请联系管理员'; +const API_ENCRYPT_REQUEST_ERROR_MSG = '请求加密失败,请刷新页面后重试'; + +const API_ENCRYPT_PUBLIC_KEY = `-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAv2kobVo5aSDWvmWZIKeL +G7dowOLTwjdWIwy7+UmTBw3e6vJW5BNEjrnW7kiqeT97VQj2V6MMmaJufpeegACG +AmhuTnG83kVLQeiXL5rlPUmdNPM8O89gSM3iMzLSUhn+rvAaHFXjKNu2xssodYn1 +F26SlVO1ewwS82AAwEPSaotL7Kq8Qxg7vmZty6RcEjp7/OaYAtHfva3uewiGMp11 +ZkywKPleQ3nT7HHjQgAckbNZFMhTMMqDzW5oI3KSm3sA+pWsUfRrZxUf2ws358/F +KewDbbhwj3u731NbXlO+WUfv3FvbdhktXtU/15FC0b+Tx+YHIUhkNTRzuIpiG7+X +cwIDAQAB +-----END PUBLIC KEY-----`; + +const API_ENCRYPT_RULE_MAP = { + [`POST ${SYSTEM_SERVICE_PREFIX}/auth/login`]: ['password'], + [`POST ${SYSTEM_SERVICE_PREFIX}/auth/register`]: ['password'], + [`PUT ${SYSTEM_SERVICE_PREFIX}/user/profile/update-password`]: ['oldPassword', 'newPassword'], + [`POST ${SYSTEM_SERVICE_PREFIX}/user/create`]: ['password'], + [`PUT ${SYSTEM_SERVICE_PREFIX}/user/update-password`]: ['password'] +} as const satisfies Record; + +type ApiEncryptRuleKey = keyof typeof API_ENCRYPT_RULE_MAP; + +interface ApiEncryptRequestConfig extends InternalAxiosRequestConfig { + apiEncryptProcessed?: boolean; +} + +let encryptor: JSEncrypt | null = null; + +function isPlainObject(value: unknown): value is Record { + return Object.prototype.toString.call(value) === '[object Object]'; +} + +function normalizeRequestPath(url: string) { + try { + return new URL(url, 'http://localhost').pathname; + } catch { + return url.split('?')[0] || ''; + } +} + +function getApiEncryptRule(config: InternalAxiosRequestConfig) { + const method = config.method?.toUpperCase(); + const url = config.url; + + if (!method || !url) { + return null; + } + + const ruleKey = `${method} ${normalizeRequestPath(url)}` as ApiEncryptRuleKey; + + return API_ENCRYPT_RULE_MAP[ruleKey] ?? null; +} + +function getEncryptor() { + if (encryptor) { + return encryptor; + } + + const publicKey = API_ENCRYPT_PUBLIC_KEY.trim(); + + if (!publicKey.includes('BEGIN PUBLIC KEY') || !publicKey.includes('END PUBLIC KEY')) { + throw new Error(API_ENCRYPT_CONFIG_ERROR_MSG); + } + + encryptor = new JSEncrypt(); + encryptor.setPublicKey(publicKey); + + return encryptor; +} + +function encryptFieldValue(value: unknown) { + if (typeof value !== 'string' || value.length === 0) { + throw new Error(API_ENCRYPT_REQUEST_ERROR_MSG); + } + + const encryptedValue = getEncryptor().encrypt(value); + + if (!encryptedValue) { + throw new Error(API_ENCRYPT_REQUEST_ERROR_MSG); + } + + return encryptedValue; +} + +export function applyApiEncrypt(config: InternalAxiosRequestConfig) { + const encryptConfig = config as ApiEncryptRequestConfig; + + if (encryptConfig.apiEncryptProcessed) { + return config; + } + + const encryptFields = getApiEncryptRule(config); + + if (!encryptFields) { + return config; + } + + if (!isPlainObject(config.data)) { + throw new Error(API_ENCRYPT_REQUEST_ERROR_MSG); + } + + const nextData = { ...config.data }; + + encryptFields.forEach(field => { + nextData[field] = encryptFieldValue(nextData[field]); + }); + + config.data = nextData; + config.headers.set(API_ENCRYPT_HEADER, API_ENCRYPT_HEADER_VALUE); + encryptConfig.apiEncryptProcessed = true; + + return config; +} diff --git a/src/service/request/index.ts b/src/service/request/index.ts index bff4cc4..e23368a 100644 --- a/src/service/request/index.ts +++ b/src/service/request/index.ts @@ -4,6 +4,7 @@ import { useAuthStore } from '@/store/modules/auth'; import { localStg } from '@/utils/storage'; import { getServiceBaseURL } from '@/utils/service'; import { $t } from '@/locales'; +import { applyApiEncrypt } from './api-encrypt'; import { getAuthorization, handleExpiredRequest, showErrorMsg } from './shared'; import type { RequestInstanceState } from './type'; @@ -28,6 +29,7 @@ export const request = createFlatRequest( async onRequest(config) { const Authorization = getAuthorization(); Object.assign(config.headers, { Authorization }); + applyApiEncrypt(config); return config; },