一、环境
SpringBoot  2.2.5.RELEASE

fastdfs-client 1.27.2

二、业务要求
由于FastDFS分布式文件系统预览图片时需要暴露FastDFS所在服务器的IP、端口、文件路径,这样对于网络安全要求较高的环境,是不允许出现的。

例如以下:

http//:xxxx:10000/group1/M00/01/02/CgADEl3PgkSSCAGAcwwwy0BBAAAKiXEYGJQ070.png?fileName=logo.png

效果图:

 

三、方案一
对文件路径进行加密处理

/preview/img/56801203E60385071EDBCA415054B57CD4EB5A943DC74E0D2559C97DF4270809F4C8F851078CE58BE2F1BC86D2BB4F65E8CC698C1E13DAA4C175C426911CEDAF6170B7542D11FC18?fileName=7.png

1. 后端代码
1. 1 加密工具类
 

public class EncryptUtil {
    public static final String DES = "DES";
 
    /**
     * 加密key,可以在配置文件中,进行定期更换
     */
    public static final String DES_KEY = "xxxxx";
    /**
     * 编码格式;默认使用uft-8
     */
    public String charset = "utf-8";
    /**
     * DES
     */
    public int keysizeDES = 0;
 
    public static EncryptUtil me;
 
    private EncryptUtil() {
        //单例
    }
 
    //双重锁
    public static EncryptUtil getInstance() {
        if (me == null) {
            synchronized (EncryptUtil.class) {
                if (me == null) {
                    me = new EncryptUtil();
                }
            }
        }
        return me;
    }
 
    /**
     * 使用KeyGenerator双向加密,DES/AES,注意这里转化为字符串的时候是将2进制转为16进制格式的字符串,不是直接转,因为会出错
     *
     * @param res       加密的原文
     * @param algorithm 加密使用的算法名称
     * @param key       加密的秘钥
     * @param keysize
     * @param isEncode
     * @return
     */
    private String keyGeneratorES(String res, String algorithm, String key, int keysize, boolean isEncode) {
        try {
            KeyGenerator kg = KeyGenerator.getInstance(algorithm);
            if (keysize == 0) {
                byte[] keyBytes = charset == null ? key.getBytes() : key.getBytes(charset);
                kg.init(getSecureRandom(keyBytes));
            } else if (key == null) {
                kg.init(keysize);
            } else {
                byte[] keyBytes = charset == null ? key.getBytes() : key.getBytes(charset);
                kg.init(keysize, getSecureRandom(keyBytes));
            }
            SecretKey sk = kg.generateKey();
            SecretKeySpec sks = new SecretKeySpec(sk.getEncoded(), algorithm);
            Cipher cipher = Cipher.getInstance(algorithm);
            if (isEncode) {
                cipher.init(Cipher.ENCRYPT_MODE, sks);
                byte[] resBytes = charset == null ? res.getBytes() : res.getBytes(charset);
                return parseByte2HexStr(cipher.doFinal(resBytes));
            } else {
                cipher.init(Cipher.DECRYPT_MODE, sks);
                return new String(cipher.doFinal(parseHexStr2Byte(res)));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
 
    /**
     * (解决在linux中无法解密问题)
     * @param keyBytes
     * @return
     * @throws NoSuchAlgorithmException
     */
    private SecureRandom getSecureRandom(byte[] keyBytes) throws NoSuchAlgorithmException {
        SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG") ;
        secureRandom.setSeed(keyBytes);
        return secureRandom;
    }
 
    /**
     * 将二进制转换成16进制
     */
    public static String parseByte2HexStr(byte buf[]) {
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < buf.length; i++) {
            String hex = Integer.toHexString(buf[i] & 0xFF);
            if (hex.length() == 1) {
                hex = '0' + hex;
            }
            sb.append(hex.toUpperCase());
        }
        return sb.toString();
    }
 
    /**
     * 将16进制转换为二进制
     */
    public static byte[] parseHexStr2Byte(String hexStr) {
        if (hexStr.length() < 1) {
            return null;
        }
        byte[] result = new byte[hexStr.length() / 2];
        for (int i = 0; i < hexStr.length() / 2; i++) {
            int high = Integer.parseInt(hexStr.substring(i * 2, i * 2 + 1), 16);
            int low = Integer.parseInt(hexStr.substring(i * 2 + 1, i * 2 + 2), 16);
            result[i] = (byte) (high * 16 + low);
        }
        return result;
    }
 
 
 
    /**
     * 使用DES加密算法进行加密(可逆)
     *
     * @param res 需要加密的原文
     * @param key 秘钥
     * @return
     */
    public String DESencode(String res, String key) {
        return keyGeneratorES(res, DES, key, keysizeDES, true);
    }
 
    /**
     * 对使用DES加密算法的密文进行解密(可逆)
     *
     * @param res 需要解密的密文
     * @param key 秘钥
     * @return
     */
    public String DESdecode(String res, String key) {
        return keyGeneratorES(res, DES, key, keysizeDES, false);
    }
}

1.2  文件路径处理工具类

public class FileUrlEncryptUtils {
 
    /**
     * 拼接文件路径
     *
     * @param path
     *        接口地址
     * @param val
     *        文件路径
     * @return
     *        加密后的文件路径
     */
    public static String concatUrl(String path,String val){
 
        if(StringUtils.isBlank(val)){
            return val;
        }
 
        String[] split = val.split("fileName=");
        String fileName = StringUtils.EMPTY;
        if(split.length>0){
            fileName = split[split.length-1];
        }
        String encodeStr = EncryptUtil.getInstance().DESencode(val,EncryptUtil.DES_KEY);
        return path+ encodeStr+ "?fileName="+ fileName;
    }
}


1.3  配置文件application.yml配置接口地址

#图片预览地址
preview-img-path: /preview/img/
 
#图片下载地址
download-img-path: /download/img/

1.4  后端返回文件路径时进行加密

 
/**
 * 预览图片的接口地址,在配置文件中配置
 */
@Value("${preview-img-path}")
private String previewImgPath;
 
 
/**
 * 预览图片的接口地址,在配置文件中配置
 */
@Value("${download-img-path}")
private String downloadImgPath;
 
 
 
//使用文件的地方,调用方法进行加密
String previewImgPath = FileUrlEncryptUtils.concatUrl(previewImgPath,val)
 
String downloadImgPath= FileUrlEncryptUtils.concatUrl(downloadImgPath,val)

1.5 后端文件处理

/**
     * 下载附件
     *
     * @param fileName
     *            文件名称
     * @param response
     *            响应流
     * @throws Exception
     *             遇到任何异常时
     */
    @GetMapping(value = "/download/img/{fileUrl}")
    public void downloadFile(@PathVariable(value = "fileUrl") String fileUrl,@RequestParam("fileName") String fileName, HttpServletResponse response, HttpServletRequest request){
        try {
 
            String url = fileUrl;
            url = EncryptUtil.getInstance().DESdecode(url, EncryptUtil.DES_KEY);
            resourceService.download(url,fileName, response, request);
        } catch (Exception e) {
            e.printStackTrace();
            log.error("------------------下载文件异常-----------------"+e.getMessage());
        }
    }
 
 
    /**
     * 预览图片
     *
     * @param fileName
     *            文件名称
     * @param response
     *            响应流
     * @throws Exception
     *             遇到任何异常时
     */
    @GetMapping(value = "/preview/img/{fileUrl}")
    public void previewImg(@PathVariable(value = "fileUrl") String fileUrl,@RequestParam("fileName") String fileName, HttpServletResponse response, HttpServletRequest request){
        try {
            String url = fileUrl;
            url = EncryptUtil.getInstance().DESdecode(url, EncryptUtil.DES_KEY);
            resourceService.previewImg(url,fileName, response,request);
        } catch (Exception e) {
            e.printStackTrace();
            log.error("------------------预览图片异常-----------------"+e.getMessage());
        }
    }
    
/**
     *     支持上传的文件类型
     */
    private static final List<String> PREVIEW_IMG = Arrays.asList("image/png","image/jpeg","image/jpg","image/gif");
 
 
 
    @Override
    public void download(String fileUrl,String fileName,HttpServletResponse response, HttpServletRequest request)
        throws IOException {
        if (StringUtils.isBlank(fileUrl)) {
            throw new MyException("文件路径不能为空");
        }
        byte[] bytes = fastDfsUtil.downloadFile(fileUrl);
        if (null == bytes && bytes.length < 1) {
            throw new MyException("文件路径不能为空");
        }
 
        response.addHeader("Pragma", "No-cache");
        response.addHeader("Cache-Control", "No-cache");
        response.setCharacterEncoding("UTF-8");
 
        String suffix = fileUrl.split("\\.")[1];
        if (StringUtils.isBlank(fileName)) {
            fileName = String.valueOf(System.currentTimeMillis()) + sp + suffix;
        }
       
        fileName = new String(fileName.getBytes("ISO8859-1"), "UTF-8");
        response.setHeader("Content-Disposition", "attachment;filename=" + fileName);
        String s = fileUrl.split("/")[fileUrl.split("/").length - 1];
        log.info("下载文件-》文件类型{}", request.getServletContext().getMimeType(s));
        response.setContentType("application/json;charset=utf-8");
        OutputStream out = response.getOutputStream();
        BufferedOutputStream bos = new BufferedOutputStream(out);
        try {
            bos.write(bytes, 0, bytes.length);
            bos.flush();
        } catch (Exception e) {
            e.printStackTrace();
            throw new MyException("文件下载异常");
        } finally {
            if (bos != null) {
                bos.close();
            }
            if (out != null) {
                out.close();
            }
        }
    }
 
 
    @Override
    public void previewImg(String fileUrl,String fileName,HttpServletRequest request,HttpServletResponse response) throws IOException{
 
        if (StringUtils.isBlank(fileUrl)) {
            log.error("图片路径不能为空");
            throw new myException("图片路径不能为空");
 
        }
        
        byte[] bytes = fastDfsUtil.downloadFile(fileUrl);
        if (null == bytes && bytes.length < 1) {
            throw new myException("图片预览异常");
 
        }
 
        response.addHeader("Pragma", "No-cache");
        response.addHeader("Cache-Control", "no-store,No-cache");
        response.setCharacterEncoding("UTF-8");
        log.info("------------------图片名称-----------------" + fileName);
 
        String s = fileUrl.split("/")[fileUrl.split("/").length - 1];
        String mimeType = request.getServletContext().getMimeType(s);
        log.info("图片预览-》图片类型{}", mimeType);
 
        if (!PREVIEW_IMG.contains(mimeType)) {
            throw new myException("图片类型不匹配");
        }
        response.setContentType(request.getServletContext().getMimeType(s)+";charset=utf-8");
        OutputStream out = response.getOutputStream();
        BufferedOutputStream bos = new BufferedOutputStream(out);
        try {
            bos.write(bytes, 0, bytes.length);
            bos.flush();
        } catch (Exception e) {
            e.printStackTrace();
            throw new myException("图片预览异常");
        } finally {
            if (bos != null) {
                bos.close();
            }
            if (out != null) {
                out.close();
            }
        }
 
    }

2 前端代码

<!-- 图片预览 -->
    <el-dialog
      title="浏览图像"
      :visible.sync="dialogVisible"
      :close-on-click-modal="false"
      width="40%">
      <el-image style="max-width:300px;max-height:300px;" :src="imgUrl"></el-image>
      <span slot="footer" class="dialog-footer">
        <el-button type="primary" @click="downloadImg(beforeImgUrl)">下载</el-button>
        <el-button @click="dialogVisible = false">关 闭</el-button>
      </span>
    </el-dialog>

2.1 main.js

#定义全局变量
Vue.prototype.$BASEAPI = process.env.BASE_API;

2.2 dev.js

BASE_API: '"http://xxxxxxx:8080"',

2.3 预览图片

    // 预览图片
    previewImg(fileUrl){
      this.imgUrl = Vue.prototype.$BASEAPI + fileUrl;
      this.dialogVisible = true;
    }

2.4 下载图片

// 下载文件
    downloadImg(fileUrl){
     
      let filePath = fileUrl.split('preview/img');
      filePath  = Vue.prototype.$BASEAPI+'download/img'+filePath[1];
 
      var a = document.createElement('a');
      a.download = name || 'pic';
      // 设置图片地址
      a.href = filePath;
      a.click();
    },