使用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);
});
相关推荐
懂点Tech2 小时前
ctfshow misc28:GIF 高度修改解题与脚本实现
开发语言·windows·python
comedate2 小时前
SMLib 的 Examples 导读:PyAPI / SM_API / SMLib 三组示例怎么读
python·cad·smlib·examples·sm_api
云和数据.ChenGuang2 小时前
Python 重写(Override)深度解析:从 MRO 到 super() 的完整机制
java·开发语言·人工智能·python·langchain·1024程序员节
摇滚侠3 小时前
《On Java 中文版 基础卷》阅读笔记 实现隐藏 12
java·数据库·笔记
川石课堂软件测试3 小时前
学习软件测试需掌握的一些知识
数据库·python·sql·功能测试·网络协议·mysql·http
万化冥合4 小时前
PyBox屏幕键盘如何辅助演示键盘操作?
python·计算机外设
车软学堂4 小时前
从测试转开发,到底要补什么
网络·人工智能·python·功能测试·单片机·车载系统·自动化
天空依然蔚蓝4 小时前
分享一个自动检测网站在线状态的python程序
python·运维开发·1024程序员节
岁月宁静4 小时前
一个 AI 项目从本地到上线,要经过哪些步骤?全流程讲清楚
python·docker·agent
codists5 小时前
《crafting interpreters》阅读笔记:p45-p45
python