作者:手机用户2502853007 | 来源:互联网 | 2023-09-14 16:18
问题
背景介绍
前后端分离框架中,前端在发起Ajax请求时,在Header携带Token值,后端获取该Token进行权限校验。
问题描述
在下载文件的场景,大部分浏览器是不支持Ajax请求文件下载,所以通常做法是直接请求下载地址,这时候是无法通过代码设置Header的参数,也就是无法知道当前用户信息,如何进行权限控制?
解决方案
该问题的痛点是数据权限问题。
初步思路
- 在用户要下载的时候,先进行权限校验
- 权限不足时,进行信息提示
- 权限足够时,则为用户生成一次性的下载链接(下载一次后失效,保证数据安全)
设计思路
- 流程设计
- 抽象功能
- 具体实现
流程设计
业务模块前端:业务模块的前端代码,例如用户信息下载的前端代码。
业务服务后端:业务模块的后端代码(非介绍重点,所以不做细节区分)。
文件服务类:文件服务的业务类,主要是处理业务逻辑,例如:生成下载地址的唯一编号,以及唯一编号映射的参数信息,文件生成。
文件控制类:提供统一的下载地址格式,放开权限。
代码设计
基于框架考虑,以下功能抽象出来:
- 持久化数据功能抽象(不同项目的具体情况不同)
- 生成文件功能抽象(因为不同业务生成文件的步骤是不一样的)
代码依赖关系
核心代码
DownloadController - 文件下载控制器,不做权限控制
package com.accloud.core.file.download.controller;import com.accloud.core.file.download.FileResult;
import com.accloud.core.file.download.service.DownloadService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;import javax.servlet.http.HttpServletResponse;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URLEncoder;
@Slf4j
@Controller
@RequestMapping("/download")
public class DownloadController {@Autowiredprivate DownloadService downloadService;@GetMapping("/{code}")public void download(@PathVariable(value = "code") String code, HttpServletResponse response) throws IOException {try {FileResult fileResult = downloadService.getFile(code);String filename = fileResult.getDownloadFilename();response.setHeader("content-type", "application/octet-stream");response.setContentType("application/octet-stream;charset=UTF-8");response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(filename, "UTF-8"));response.setCharacterEncoding("UTF-8");FileInputStream fis = new FileInputStream(fileResult.getFile());ByteArrayOutputStream bos = new ByteArrayOutputStream();byte[] b = new byte[1024];int n;while ((n = fis.read(b)) != -1) {bos.write(b, 0, n);}fis.close();bos.close();byte[] fileByte = bos.toByteArray();OutputStream toClient = new BufferedOutputStream(response.getOutputStream());toClient.write(fileByte);toClient.flush();toClient.close();if (fileResult.isRemoveFileAfterDownload()) {fileResult.getFile().delete();}} catch (Exception e) {log.error(e.getMessage());response.reset();response.setCharacterEncoding("UTF-8");response.setContentType("application/json;charset=utf-8");response.getWriter().print(e.getMessage());}}
}
DownloadService - 文件下载控制器
package com.accloud.core.file.download.service;import com.accloud.core.file.download.FileResult;
import com.accloud.core.file.download.code.DownloadCodeContent;
import com.accloud.core.file.download.code.IDownloadCodeService;
import com.accloud.core.file.handler.IFileHandler;
import com.accloud.core.exception.BusinessException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;import java.util.List;
@Component
public class DownloadService {&#64;Autowiredprivate IDownloadCodeService downloadCodeService;&#64;Autowired(required &#61; false)private List<IFileHandler> fileHandlerList;&#64;Value("${downloadServer}")private String downloadServerPath;public String getDownloadUrl(String type, String parameter) {String code &#61; downloadCodeService.buildCode(type, parameter);return downloadServerPath &#43; "download/" &#43; code;}public FileResult getFile(String code) {DownloadCodeContent content &#61; downloadCodeService.getContent(code);if (content &#61;&#61; null) {throw new BusinessException("无此文件");} else {downloadCodeService.delete(code);if (fileHandlerList &#61;&#61; null) {throw new BusinessException("功能异常&#xff0c;目前文件处理器列表为空");} else {for (IFileHandler fileHandler : fileHandlerList) {if (fileHandler.handle(content.getType())) {return fileHandler.getFileResult(content.getParameter());}}throw new BusinessException("功能异常&#xff0c;无法处理类型为&#xff1a;%s 的文件", content.getType());}}}
}
FileResult - 文件结果
&#64;NoArgsConstructor
&#64;RequiredArgsConstructor
&#64;Data
public class FileResult {&#64;NonNullprivate String downloadFilename;&#64;NonNullprivate File file;private boolean removeFileAfterDownload;
}
IFileHandler - 文件生成处理器接口
public interface IFileHandler {boolean handle(String type);FileResult getFileResult(String parameter);
}
IDownloadCodeService - 下载编号接口
public interface IDownloadCodeService {String buildCode(String type, String parameter);DownloadCodeContent getContent(String code);void delete(String code);
}