Skip to content

Commit 93fe28a

Browse files
committed
feat: perf crypto
1 parent 1bff296 commit 93fe28a

3 files changed

Lines changed: 142 additions & 20 deletions

File tree

docs/encryption-examples.js

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -215,21 +215,49 @@ export const updateProfileAPI = async (profile) => {
215215
}
216216

217217
/**
218-
* 支付接口 - 卡号等敏感信息自动加密
218+
* 支付接口 - 卡号等敏感信息自动加密 (特定字段加密)
219219
*/
220220
export const createPaymentAPI = async (paymentData) => {
221221
return request.post('/api/payment/create', {
222222
amount: paymentData.amount,
223223
cardNumber: paymentData.cardNumber,
224224
cvv: paymentData.cvv,
225225
expiryDate: paymentData.expiryDate
226+
}, {
227+
// 仅加密敏感字段,amount 保持明文
228+
encryptFields: ['cardNumber', 'cvv', 'expiryDate']
226229
})
227230
}
228231

229232
// ========================================
230-
// 示例 6: 单个请求控制加密
233+
// 示例 6: 单个请求控制加密与 GET 请求加密
231234
// ========================================
232235

236+
/**
237+
* GET 请求参数加密
238+
* 如果启用了加密,params 参数会自动被加密传输
239+
* URL 示例: /api/search?encrypted=...
240+
*/
241+
export const searchUserAPI = async (keyword) => {
242+
return request.get('/api/users/search', {
243+
q: keyword,
244+
type: 'admin'
245+
})
246+
}
247+
248+
/**
249+
* GET 请求部分字段加密
250+
* URL 示例: /api/users/search?q=...&idCard=encrypted_string...
251+
*/
252+
export const searchSensitiveUserAPI = async (name, idCard) => {
253+
return request.get('/api/users/search', {
254+
q: name,
255+
idCard: idCard
256+
}, {
257+
encryptFields: ['idCard'] // 仅加密身份证号
258+
})
259+
}
260+
233261
/**
234262
* 获取公开数据 - 不需要加密
235263
*/

src/service/http.js

Lines changed: 67 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -148,12 +148,36 @@ export class EncryptionHandler {
148148
return this.decryptAES(encryptedData, aesKey)
149149
}
150150

151-
static encryptRequestData(data, config) {
151+
static encryptRequestData(data, config, encryptFields = []) {
152152
if (!config.enabled || !config.encryptRequest || !data) return data
153153
if (isFormData(data) || isBlob(data)) {
154154
logger.warn('FormData/Blob 类型数据不进行加密')
155155
return data
156156
}
157+
158+
// 1. 部分字段加密
159+
if (Array.isArray(encryptFields) && encryptFields.length > 0 && typeof data === 'object') {
160+
const newData = { ...data }
161+
encryptFields.forEach((field) => {
162+
if (Object.prototype.hasOwnProperty.call(newData, field)) {
163+
const value = newData[field]
164+
const jsonValue = typeof value === 'string' ? value : JSON.stringify(value)
165+
166+
if (config.mode === 'AES') {
167+
newData[field] = this.encryptAES(jsonValue, config.aesKey)
168+
} else if (config.mode === 'RSA') {
169+
newData[field] = this.encryptRSA(jsonValue, config.rsaPublicKey)
170+
} else if (config.mode === 'HYBRID') {
171+
// 混合加密部分字段:返回对象结构 { encrypted: '...', key: '...' }
172+
const { encryptedKey, encryptedData } = this.encryptHybrid(jsonValue, config.aesKey, config.rsaPublicKey)
173+
newData[field] = { encrypted: encryptedData, key: encryptedKey }
174+
}
175+
}
176+
})
177+
return newData
178+
}
179+
180+
// 2. 全量加密
157181
const jsonData = typeof data === 'string' ? data : JSON.stringify(data)
158182
try {
159183
switch (config.mode) {
@@ -237,21 +261,50 @@ class HttpClient {
237261
// 添加默认请求拦截器
238262
this.useRequestInterceptor((config) => {
239263
// 1. 处理加密
240-
if (encryptionConfig.enabled && config.encrypt !== false && (config.payload || config.body)) {
241-
const dataToEncrypt = config.payload || config.body
242-
try {
243-
const encrypted = EncryptionHandler.encryptRequestData(dataToEncrypt, encryptionConfig)
244-
if (encrypted && encrypted !== dataToEncrypt) {
245-
config.payload = encrypted
246-
// 确保 payload 生效,清除 body
247-
if (config.body) config.body = undefined
248-
config.headers = {
249-
...config.headers,
250-
[encryptionConfig.encryptionHeader]: encryptionConfig.mode,
264+
if (encryptionConfig.enabled && config.encrypt !== false) {
265+
// A. 处理 Body 加密 (POST/PUT/PATCH 等)
266+
if (config.payload || config.body) {
267+
const dataToEncrypt = config.payload || config.body
268+
try {
269+
const encrypted = EncryptionHandler.encryptRequestData(
270+
dataToEncrypt,
271+
encryptionConfig,
272+
config.encryptFields
273+
)
274+
if (encrypted && encrypted !== dataToEncrypt) {
275+
config.payload = encrypted
276+
// 确保 payload 生效,清除 body
277+
if (config.body) config.body = undefined
278+
279+
// 如果是全量加密(没有指定 partial fields),添加 header
280+
if (!config.encryptFields || config.encryptFields.length === 0) {
281+
config.headers = {
282+
...config.headers,
283+
[encryptionConfig.encryptionHeader]: encryptionConfig.mode,
284+
}
285+
}
251286
}
287+
} catch (error) {
288+
logger.error('Body 加密失败:', error)
289+
}
290+
}
291+
292+
// B. 处理 Params 加密 (GET/DELETE 等)
293+
if (config.params && Object.keys(config.params).length > 0) {
294+
try {
295+
// 复用 encryptRequestData 逻辑(支持全量和部分)
296+
const encryptedParams = EncryptionHandler.encryptRequestData(
297+
config.params,
298+
encryptionConfig,
299+
config.encryptFields
300+
)
301+
if (encryptedParams && encryptedParams !== config.params) {
302+
config.params = encryptedParams
303+
// 注意:Fetch/Axios 处理 params 对象时,如果变成 { encrypted: '...' } 形式,会自动序列化为 ?encrypted=...
304+
}
305+
} catch (error) {
306+
logger.error('Params 加密失败:', error)
252307
}
253-
} catch (error) {
254-
logger.error('请求加密失败:', error)
255308
}
256309
}
257310

src/service/request.js

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -303,7 +303,7 @@ class EncryptionHandler {
303303
* @param {EncryptionConfig} config - 加密配置
304304
* @returns {any} 加密后的数据
305305
*/
306-
static encryptRequestData(data, config) {
306+
static encryptRequestData(data, config, encryptFields = []) {
307307
if (!config.enabled || !config.encryptRequest) {
308308
return data
309309
}
@@ -314,6 +314,28 @@ class EncryptionHandler {
314314
return data
315315
}
316316

317+
// 1. 部分字段加密
318+
if (Array.isArray(encryptFields) && encryptFields.length > 0 && typeof data === 'object') {
319+
const newData = { ...data }
320+
encryptFields.forEach((field) => {
321+
if (Object.prototype.hasOwnProperty.call(newData, field)) {
322+
const value = newData[field]
323+
const jsonValue = typeof value === 'string' ? value : JSON.stringify(value)
324+
325+
if (config.mode === 'AES') {
326+
newData[field] = this.encryptAES(jsonValue, config.aesKey)
327+
} else if (config.mode === 'RSA') {
328+
newData[field] = this.encryptRSA(jsonValue, config.rsaPublicKey)
329+
} else if (config.mode === 'HYBRID') {
330+
const { encryptedKey, encryptedData } = this.encryptHybrid(jsonValue, config.aesKey, config.rsaPublicKey)
331+
newData[field] = { encrypted: encryptedData, key: encryptedKey }
332+
}
333+
}
334+
})
335+
return newData
336+
}
337+
338+
// 2. 全量加密
317339
// 转为 JSON 字符串
318340
const jsonData = typeof data === 'string' ? data : JSON.stringify(data)
319341

@@ -622,10 +644,29 @@ const applyEncryption = (config) => {
622644
if (config.encrypt === false) return
623645

624646
try {
625-
config.data = EncryptionHandler.encryptRequestData(config.data, encryptionConfig)
647+
// 1. 加密请求体 (Data)
648+
if (config.data) {
649+
// Support partial encryption or full encryption
650+
const encryptedData = EncryptionHandler.encryptRequestData(config.data, encryptionConfig, config.encryptFields)
651+
// If fields were encrypted, config.data is modified in place (new object)
652+
// Or if full encryption, config.data is replaced with { encrypted: '...', mode: '...' }
653+
config.data = encryptedData
654+
}
655+
656+
// 2. 加密请求参数 (Params) - 支持 GET 请求加密
657+
if (config.params && Object.keys(config.params).length > 0) {
658+
// 如果没有指定 partial fields,则整个 params 被加密为 { encrypted: '...' }
659+
// 也就是 ?encrypted=...
660+
// 如果指定 partial fields,则特定字段变密文 ?field=ciphertext
661+
config.params = EncryptionHandler.encryptRequestData(config.params, encryptionConfig, config.encryptFields)
662+
}
626663

627-
// 添加加密标识头
628-
if (encryptionConfig.enabled && encryptionConfig.encryptRequest) {
664+
// 添加加密标识头 (如果启用了加密且没有使用部分加密)
665+
// 注意:如果是部分加密,通常也会加头,但这里逻辑是 "mode" 标识整个 Payload 格式。
666+
// 如果是部分加密,Payload 格式并不是标准的 { encrypted: ... },所以加这个头可能误导后端(取决于后端实现)
667+
// 但为了保持一致性,我们还是加上。如果使用了部分加密,config.data 结构不同,后端需识别
668+
const isPartial = Array.isArray(config.encryptFields) && config.encryptFields.length > 0
669+
if (encryptionConfig.enabled && encryptionConfig.encryptRequest && !isPartial) {
629670
config.headers[encryptionConfig.encryptionHeader] = encryptionConfig.mode
630671
logger.log('已添加加密标识头')
631672
}

0 commit comments

Comments
 (0)