前端接口安全与性能优化实战
文章目录
- 接口请求封装
- 请求拦截器
- 设置请求头防止重放攻击:
- X-Timestamp 时间戳
- X-Nonce 长度16位的随机字符串
- X-Signateure api请求签名
- X-Client-ID 客户端id
- 响应拦截器
- 缓存接口数据防止重复请求
- 职责链模式
- 用时间切片优化项目速度
- webworker执行耗时任务
- 注意事项
- vue2项目改用vite
- 从资源加载优化角度加速项目
- 浏览器缓存的两种机制
- 强缓存
- 协商缓存
接口请求封装
请求拦截器
请求前
设置请求头防止重放攻击:
X-Timestamp 时间戳
后端代码
/*** 验证时间戳有效性(例如5分钟内有效)*/private boolean isValidTimestamp(String timestampStr, HttpServletResponse response) throws IOException {if (timestampStr == null) {sendErrorResponse(response, "缺少时间戳");return false;}try {long timestamp = Long.parseLong(timestampStr);long currentTime = System.currentTimeMillis();long timeDiff = Math.abs(currentTime - timestamp);if (timeDiff > 5 * 60 * 1000) {sendErrorResponse(response, "请求已过期");return false;}} catch (NumberFormatException e) {sendErrorResponse(response, "时间戳格式错误");return false;}return true;}
X-Nonce 长度16位的随机字符串
/*** 验证随机字符串有效性** @param nonce* @param response* @return* @throws IOException*/private boolean isValidNonce(String nonce, HttpServletResponse response) throws IOException {if (nonce == null || nonce.isEmpty()) {sendErrorResponse(response, "缺少随机字符串");return false;}// 验证nonce是否已使用(防止重放攻击)String nonceKey = "nonce:" + nonce;Boolean isNewNonce = redisTemplate.opsForValue().setIfAbsent(nonceKey, "1", 5, TimeUnit.MINUTES);if (Boolean.FALSE.equals(isNewNonce)) {sendErrorResponse(response, "重复的请求");return false;}return true;}
X-Signateure api请求签名
// 生成请求签名
export function generateSignature(secretKey, method, url, timestamp, nonce, body = '') {const key = String(secretKey || '')const httpMethod = String(method || '').toUpperCase()const requestUrl = String(url || '')const time = String(timestamp || '')const nonceStr = String(nonce || '')const finalUrl = processUrl(requestUrl)const bodyStr = serializeBody(httpMethod, body)const data = buildSignatureData(httpMethod, finalUrl, time, nonceStr, bodyStr)console.log('Signature data:', data)const signature = sha256.hmac(key, data)console.log('Generated signature:', signature)return signature
}
/*** 验证签名有效性** @param signature* @param response* @param dataForSigning* @return* @throws IOException*/
private boolean isValidSignature(String signature, HttpServletResponse response, String dataForSigning) throws IOException {if (signature == null || signature.isEmpty()) {sendErrorResponse(response, "缺少签名");return true;}// 构造待签名数据(不包含请求体)log.info("Data for signing (multipart): {}", dataForSigning);// 计算签名String computedSignature = computeSignature(dataForSigning, clientSecret);log.info("Computed signature: {}", computedSignature);log.info("Received signature: {}", signature);// 验证签名if (!computedSignature.equals(signature)) {sendErrorResponse(response, "无效的签名");return true;}return false;
}
/*** 计算HMAC-SHA256签名*/
private String computeSignature(String data, String secret) {try {Mac mac = Mac.getInstance("HmacSHA256");SecretKeySpec secretKeySpec = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256");mac.init(secretKeySpec);byte[] signatureBytes = mac.doFinal(data.getBytes(StandardCharsets.UTF_8));return bytesToHex(signatureBytes);} catch (Exception e) {log.error("签名计算失败", e);throw new RuntimeException("签名计算失败", e);}
}
X-Client-ID 客户端id
如果token存在并且请求路径不是白名单则添加认证头Authorization
为每个请求生成唯一标识
const requestId = `${config.url}-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`
config.requestId = requestId
只有当配置中 showLoading 不为 false 时才显示 loading或者进度条
记录请求开始时间
请求失败
请求错误时关闭 loading或者进度条
关闭对应请求的 loading或者进度条
…
响应拦截器
成功
响应成功时关闭对应请求的 loading或者进度条
计算接口耗时
如果response.data instanceof Blob return resopnse.data
如果业务状态码表示成功则返回response.data.data
其他情况:业务码[code]?.()
失败
响应错误时关闭对应请求的 loading或者进度条
计算接口耗时(即使出错也记录)
处理401状态码清除token跳转到登录页或刷新token,拿短token换长token
处理其他错误:httpErrorCode[status]?.()

缓存接口数据防止重复请求

职责链模式
缓存模块-》防止重复提交模块-》请求模块》请求处理模块


用时间切片优化项目速度


webworker执行耗时任务
注意事项



vue2项目改用vite
删除node_modules
删除package.json中的开发依赖
删除babel配置文件
删除package.json中的eslint配置
删除postcss配置


从资源加载优化角度加速项目





浏览器缓存的两种机制

强缓存
优点:一定期限内,根本不用向服务器询问,一定是拿到缓存。速度最快
缺点:如何不配合hash,无法感知到文件更新

协商缓存
优点:能够保证每次前端打包后丢上服务器资源一定更新
缺点:只要文件是新放的,即使文件内容没变也不缓存

