package com.trafficaudit.config;
|
|
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.core.io.FileSystemResource;
|
import org.springframework.core.io.Resource;
|
import org.springframework.http.HttpHeaders;
|
import org.springframework.http.MediaType;
|
import org.springframework.http.ResponseEntity;
|
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.PathVariable;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RestController;
|
|
import java.io.File;
|
import java.io.IOException;
|
import java.net.URLEncoder;
|
|
/**
|
* 数据导入页「下载模板」:/api/templates/** -> docs 目录下的模板文件。
|
* 用 Controller 而非静态资源映射,便于设置中文文件名的下载头并防路径穿越。
|
*/
|
@RestController
|
@RequestMapping("/api/templates")
|
public class TemplateDownloadController {
|
|
@Value("${docs.template-root:docs}")
|
private String templateRoot;
|
|
@GetMapping("/{*path}")
|
public ResponseEntity<Resource> download(@PathVariable String path) throws IOException {
|
File file = resolveTemplateFile(path);
|
if (file == null) {
|
return ResponseEntity.notFound().build();
|
}
|
String encoded = URLEncoder.encode(file.getName(), "UTF-8").replace("+", "%20");
|
return ResponseEntity.ok()
|
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename*=UTF-8''" + encoded)
|
.contentType(MediaType.APPLICATION_OCTET_STREAM)
|
.contentLength(file.length())
|
.body(new FileSystemResource(file));
|
}
|
|
/** 模板定位:配置目录 → user.dir → user.dir/.. 逐级回退(与报表模块一致),并防路径穿越 */
|
private File resolveTemplateFile(String rel) throws IOException {
|
String r = rel;
|
while (r.startsWith("./")) r = r.substring(2);
|
if (r.contains("..")) {
|
return null;
|
}
|
String[] roots = {
|
templateRoot,
|
System.getProperty("user.dir") + "/" + templateRoot,
|
System.getProperty("user.dir") + "/../" + templateRoot
|
};
|
for (String root : roots) {
|
if (root == null || root.trim().isEmpty()) continue;
|
File f = new File(root, r);
|
if (f.exists() && f.isFile()) {
|
return f.getCanonicalFile();
|
}
|
}
|
return null;
|
}
|
}
|