使用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);
});
相关推荐
Nebula_g12 分钟前
JavaSE加强:IO流
java·开发语言·windows·stream·javase
rannn_11120 分钟前
JVM 面试题:类加载过程详解(附高频考点)
java·jvm·后端
程序猿乐锅4 小时前
从0-1一文详解RabbitMQ
java·分布式·后端·中间件·rabbitmq·ruby
考虑考虑7 小时前
JDK26中的List.ofLazy()
java·后端·java ee
朝朝辞暮i7 小时前
VLA 系统学习第 4 课:一个 Batch 进入神经网络后,模型到底是怎么“学会”的?
人工智能·python·神经网络·vla
小蒜学长7 小时前
基于SpringBoot的公寓报修管理系统的设计与实现(代码+数据库+LW)
java·spring boot·后端·公寓报修管理系统·多角色协同
for_ever_love__8 小时前
MySQL 主从复制讲透:binlog 原理、搭建步骤与读写分离
java·数据库·mysql·binlog·读写分离·原理·主从复制
Ada's8 小时前
【计算机基础系列】003:Python数据结构
开发语言·数据结构·python
2601_962885728 小时前
如何用 Python 计算 TRIX 三重指数平滑均线指标?
开发语言·python
还卿一钵无情泪9 小时前
Unsloth 微调 构建自己的大模型 没有GPU也能微调
linux·开发语言·人工智能·python·大模型·nlp·unsloth