使用Java HttpServletResponse和JavaScript Fetch下载文件

Java端:

java 复制代码
@Autowired
private RestTemplate restTemplate;

@Override
public void download(HttpServletResponse response) {
	String baseUrl = "/contract/download";
	String fullUrl = SignApiUtil.BASE_DOMAIN + baseUrl;

	String timestamp = SignApiUtil.getTimestamp();
	String nonce = SignApiUtil.getNonce();
	String signature = SignApiUtil.getSignature(timestamp, nonce);

	// 请求头
	HttpHeaders headers = new HttpHeaders();
	headers.set("x-qys-accesstoken", SignApiUtil.APP_TOKEN);
	headers.set("x-qys-timestamp", timestamp);
	headers.set("x-qys-nonce", nonce);
	headers.set("x-qys-signature", signature);

	// 请求参数
	MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
	params.add("contractId", "3499343568015819654");

	UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(fullUrl).queryParams(params);

	HttpEntity<?> requestEntity = new HttpEntity<>(headers);

	restTemplate.setErrorHandler(new DefaultResponseErrorHandler() {
		@Override
		protected boolean hasError(HttpStatus statusCode) {
			return false;
		}
	});

	try {
		ResponseEntity<byte[]> responseEntity = restTemplate.exchange(
				builder.toUriString(),
				HttpMethod.GET,
				requestEntity,
				byte[].class
		);

		byte[] fileBytes = responseEntity.getBody();
		if (fileBytes == null) {
			throw new RuntimeException("文件内容为空");
		}

		String realFileName = "download";
		String contentDisposition = responseEntity.getHeaders().getFirst(HttpHeaders.CONTENT_DISPOSITION);

		if (contentDisposition != null && contentDisposition.trim().length() > 0) {
			// 解析文件名
			String fileNameEncoded = contentDisposition
					.replaceFirst("(?i)^.*filename=\"?", "")
					.replaceFirst("\"?$", "");

			// URL解码
			realFileName = java.net.URLDecoder.decode(fileNameEncoded, "UTF-8");
		}

		// 设置响应
		response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
		response.setCharacterEncoding(StandardCharsets.UTF_8.name());

		// 正确设置下载文件名
		String encodedFileName = URLEncoder.encode(realFileName, StandardCharsets.UTF_8.name());
		response.setHeader(HttpHeaders.CONTENT_DISPOSITION,
				"attachment;filename=" + encodedFileName +
				";filename*=UTF-8''" + encodedFileName);

		// 暴露给前端,让JS能读取到文件名
		response.setHeader("Access-Control-Expose-Headers", HttpHeaders.CONTENT_DISPOSITION);

		// 输出文件
		try (OutputStream os = response.getOutputStream()) {
			os.write(fileBytes);
			os.flush();
		}

	} catch (Exception e) {
		e.printStackTrace();
		throw new RuntimeException("文件下载失败:" + e.getMessage(), e);
	}
}

JavaScript端:

javascript 复制代码
fetch("/main/download", {
    method: "GET"
}).then(async (resp) => {

    if (!resp.ok) {
        throw new Error(`请求失败,状态码:${resp.status}`);
    }

    // 从响应头拿文件名
    let disposition = resp.headers.get("Content-Disposition");
    let fileName = "download.pdf";

    if (disposition) {
        const match = disposition.match(/filename\*?=(?:UTF-8'')?([^;]+)/);
        if (match && match[1]) {
            fileName = decodeURIComponent(match[1].replace(/"/g, ""));
        }
    }

    const blob = await resp.blob();

    const downloadUrl = window.URL.createObjectURL(blob);
    const a = document.createElement("a");
    a.href = downloadUrl;
    a.download = fileName;
    document.body.appendChild(a);
    a.click();
    document.body.removeChild(a);

    window.URL.revokeObjectURL(downloadUrl);
}).catch(error => {
    console.error("模板下载失败:", error);
});
相关推荐
kyriewen20 分钟前
我排查了一个React内存泄漏——罪魁祸首是这3个被忽略的清理函数
前端·javascript·面试
matlabgoodboy20 分钟前
计算机毕设代做|Java Python Matlab APP 全套开发设计
java·python·课程设计
用户83562907805130 分钟前
Python Word 转 PDF 和 PDF 转 Word 指南
后端·python
用户83562907805140 分钟前
如何使用 Python 加密和保护 Word 文档
后端·python
Fluxproxy1 小时前
AI数据集采集、批量模型推理报错频发?AI项目网络稳定性优化实战
python·ai·ai编程
其美杰布-富贵-李1 小时前
03 ref、reactive 与 computed 响应式数据
前端·javascript·vue.js
用户938515635071 小时前
useRef + Web Worker 实战:React 如何优雅地拥抱多线程
前端·javascript·react.js
嘟嘟07171 小时前
useRef + Web Worker:React 中的耗时计算不卡页面
javascript·vue.js
mONESY1 小时前
React Router v7 完全实战指南:从入门到鉴权,一套代码全搞定
javascript
To_OC1 小时前
LC 74 搜索二维矩阵:换皮的二分查找,我居然一开始没看出来
javascript·算法·leetcode