1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
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;
    }
}