热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

前后端分离下解决文件下载权限问题

问题背景介绍前后端分离框架中,前端在发起Ajax请求时,在Header携带Token值,后端获取该Token进行权限校验。问题描述

问题


背景介绍

前后端分离框架中,前端在发起Ajax请求时,在Header携带Token值,后端获取该Token进行权限校验。


问题描述

在下载文件的场景,大部分浏览器是不支持Ajax请求文件下载,所以通常做法是直接请求下载地址,这时候是无法通过代码设置Header的参数,也就是无法知道当前用户信息,如何进行权限控制?


解决方案


该问题的痛点是数据权限问题。



初步思路


  1. 在用户要下载的时候,先进行权限校验
  2. 权限不足时,进行信息提示
  3. 权限足够时,则为用户生成一次性的下载链接(下载一次后失效,保证数据安全)

设计思路


  1. 流程设计
  2. 抽象功能
  3. 具体实现

流程设计

业务模块前端:业务模块的前端代码,例如用户信息下载的前端代码。
业务服务后端:业务模块的后端代码(非介绍重点,所以不做细节区分)。
文件服务类:文件服务的业务类,主要是处理业务逻辑,例如:生成下载地址的唯一编号,以及唯一编号映射的参数信息,文件生成。
文件控制类:提供统一的下载地址格式,放开权限。
在这里插入图片描述


代码设计

基于框架考虑,以下功能抽象出来:


  1. 持久化数据功能抽象(不同项目的具体情况不同)
  2. 生成文件功能抽象(因为不同业务生成文件的步骤是不一样的)

代码依赖关系

在这里插入图片描述


核心代码

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;/*** 文件下载控制器** @author guoyu.huang* @since 2020-10-16*/
@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;/*** @author guoyu.huang* @since 2020-10-16*/
@Component
public class DownloadService {&#64;Autowiredprivate IDownloadCodeService downloadCodeService;&#64;Autowired(required &#61; false)private List<IFileHandler> fileHandlerList;&#64;Value("${downloadServer}")private String downloadServerPath;/*** 获取下载地址** &#64;param type* &#64;param parameter* &#64;return*/public String getDownloadUrl(String type, String parameter) {String code &#61; downloadCodeService.buildCode(type, parameter);return downloadServerPath &#43; "download/" &#43; code;}/*** 获取文件** &#64;param code* &#64;return*/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;author guoyu.huang* &#64;since 2020-10-16*/
&#64;NoArgsConstructor
&#64;RequiredArgsConstructor
&#64;Data
public class FileResult {/*** 下载的文件名*/&#64;NonNullprivate String downloadFilename;/*** 文件*/&#64;NonNullprivate File file;/*** 下载后是否删除文件*/private boolean removeFileAfterDownload;
}

IFileHandler - 文件生成处理器接口

/*** 文件生成处理器接口** &#64;author guoyu.huang* &#64;since 2020-10-16*/
public interface IFileHandler {/*** 判断能否处理** &#64;param type 传入类型&#xff0c;用于判断是否能够处理* &#64;return*/boolean handle(String type);/*** 获取文件结果** &#64;param parameter* &#64;return*/FileResult getFileResult(String parameter);
}

IDownloadCodeService - 下载编号接口

/*** 下载编号接口** &#64;author guoyu.huang* &#64;since 2020-10-26*/
public interface IDownloadCodeService {/*** 生成编号** &#64;param type* &#64;param parameter* &#64;return*/String buildCode(String type, String parameter);/*** 根据编号获取内容** &#64;param code* &#64;return*/DownloadCodeContent getContent(String code);/*** 删除编号** &#64;param code* &#64;return*/void delete(String code);
}

推荐阅读
author-avatar
手机用户2502853007
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有