All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.yixan.base.web.utils.WebFileUtil Maven / Gradle / Ivy

There is a newer version: 3.7.1
Show newest version
package com.yixan.base.web.utils;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.yixan.base.common.api.file.model.FolderOptions;
import com.yixan.base.common.api.file.model.IFolderName;
import com.yixan.base.common.api.file.service.IFileService;
import com.yixan.base.common.api.system.model.IUser;
import com.yixan.base.common.error.ErrorCode;
import com.yixan.base.common.svc.file.service.LocalFileService;
import com.yixan.base.core.exception.ResultCode;
import com.yixan.base.core.exception.ServiceException;
import com.yixan.tools.common.util.FileUtil;
import com.yixan.tools.common.util.StringUtil;
import com.yixan.tools.common.util.VerifyUtil;

/**
 * WEB文件上传下载处理工具
 *
 * @author zhaohuihua
 * @version V1.0 2017-11-30
 */
public class WebFileUtil {

    private static final Logger log = LoggerFactory.getLogger(WebFileUtil.class);

    private IFileService fileService;

    public WebFileUtil(IFileService fileService) {
        this.fileService = fileService;
    }

    /**
     * 生成保存文件的参数对象
     * 
     * @param folder 根文件夹, 可为空(为空时由fileService.groupUseDefIfNull决定默认用files还是不要根文件夹)
     * @param extra 附加参数, 用于替换文件路径和文件名中的占位符
     * @return
     */
    public static IFolderName generateFolderParams(String folder, Map extra) {

        IUser user = UserUtil.checkLoginUser();
        Map map = new HashMap<>();
        if (user != null) {
            map.put("factory-code", user.getFactoryCode());
            map.put("user-id", user.getUserId());
        } else {
            map.put("factory-code", "000000");
            map.put("user-id", "null");
        }
        if (VerifyUtil.isNotBlank(extra)) {
            map.putAll(extra);
        }
        FolderOptions options = new FolderOptions();
        options.setFolder(folder);
        options.setExtra(map);
        return options;
    }

    /**
     * 生成保存文件的参数对象
     * 
     * @param folder 根文件夹, 可为空(为空时由fileService.groupUseDefIfNull决定默认用files还是不要根文件夹)
     * @param extra 附加参数, 用于替换文件路径和文件名中的占位符
     * @return
     */
    public static IFolderName generateFolderParams(String folder, String extra) {
        JSONObject json = null;
        if (VerifyUtil.isNotBlank(extra)) {
            try {
                json = (JSONObject) JSON.parse(extra);
            } catch (Exception e) {
                log.error("File upload, extra params can't parse, {}, {}", e.toString(), extra);
            }
        }
        return generateFolderParams(folder, json);
    }

    /**
     * 发现并上传文件
* 每个需要上传的文件, 在前端构建两个input
* 一个type="file"用于上传文件流, 一个type="hidden", 用于保存修改前的图片地址; 用序号一一对应
* <p> <input type="file" name="files0"/> <input type="hidden" name="images0"/> <p>
* <p> <input type="file" name="files1"/> <input type="hidden" name="images1"/> <p>
* <p> <input type="file" name="files2"/> <input type="hidden" name="images2"/> <p>
* <p> <input type="file" name="files3"/> <input type="hidden" name="images3"/> <p>
* <p> <input type="file" name="files4"/> <input type="hidden" name="images4"/> <p>
* 后台调用findAndUploadFiles(request, "images", "files")查找图片
* 图片列表是有顺序的
* 因此如果5张图片, 其中1/2(序号从0开始)修改了, 那么仍然应该是5条记录
* 其中0/3/4的type="hidden"是原图片URL,type="file"为null, 1/2的type="hidden"是新图片的原URL,type="file"为新图片输入流
* images0/images1/images2/images3/images4=原图片URL; files1/files2=文件流
* 此时应将files1/files2上传至文件服务器, 取得URL, 替换到images1/images2的位置
*
* 另外, 如果文件直接上传至文件服务器而不通过JAVA服务器中转
* 那么在前端就已已经把images1/images2替换成新图片地址了
* 应将type="file"设为disabled, 相当于每次type="file"都为null, 然后这个处理逻辑也就没有问题, 可以兼容了
* * @param request * @param imageFieldPrefix 图片字段的前缀 * @param fileFieldPrefix 文件字段的前缀 * @return 上传后的图片地址 * @throws ServiceException */ public List findAndUploadFiles(HttpServletRequest request, String imageFieldPrefix, String fileFieldPrefix) throws ServiceException { String folder = request.getParameter("folder"); String filename = request.getParameter("filename"); String extra = request.getParameter("extra"); int size = 100; int max = -1; String[] urls = new String[size]; for (int i = 0; i < size; i++) { // images0, images3, images4 原图片URL String url = request.getParameter(imageFieldPrefix + i); if (VerifyUtil.isNotBlank(url)) { urls[i] = url; if (max < i) { max = i; } } } if (request instanceof MultipartHttpServletRequest) { MultipartHttpServletRequest mrequest = (MultipartHttpServletRequest) request; for (Entry entry : mrequest.getFileMap().entrySet()) { if (entry.getKey().startsWith(fileFieldPrefix)) { // files1, files2 文件流 String suffix = entry.getKey().substring(fileFieldPrefix.length()); if (VerifyUtil.isNotBlank(suffix) && StringUtil.isDigit(suffix)) { int i = Integer.valueOf(suffix); if (i < size && entry.getValue().getSize() > 0) { urls[i] = upload(entry.getValue(), folder, filename, extra); } if (max < i) { max = i; } } } } } List list = new ArrayList<>(); for (int i = 0; i <= max; i++) { list.add(urls[i]); } return list; } /** * 文件上传
* 文件扩展名必须在allow.upload.suffix配置中
* * @param file 上传的文件内容 * @param folder 根文件夹, 可为空(为空时由fileService.groupUseDefIfNull决定默认用files还是不要根文件夹) * @param filename 文件名, 用来获取文件扩展名 * @param extra 附加参数, 用于替换文件路径和文件名中的占位符 * @return 上传后的URL路径 */ public String upload(MultipartFile file, String folder, String filename, Map extra) throws ServiceException { IFolderName options = generateFolderParams(folder, extra); return upload(file, folder, filename, options); } /** * 文件上传
* 文件扩展名必须在allow.upload.suffix配置中
* * @param file 上传的文件内容 * @param folder 根文件夹, 可为空(为空时由fileService.groupUseDefIfNull决定默认用files还是不要根文件夹) * @param filename 文件名, 用来获取文件扩展名 * @param extra 附加参数, 用于替换文件路径和文件名中的占位符 * @return 上传后的URL路径 */ public String upload(MultipartFile file, String folder, String filename, String extra) throws ServiceException { IFolderName options = generateFolderParams(folder, extra); return upload(file, folder, filename, options); } /** * 文件上传
* 文件扩展名必须在allow.upload.suffix配置中
* * @param file 上传的文件内容 * @param folder 根文件夹, 可为空(为空时由fileService.groupUseDefIfNull决定默认用files还是不要根文件夹) * @param filename 文件名, 用来获取文件扩展名 * @param options 附加参数, 用于替换文件路径和文件名中的占位符 * @return 上传后的URL路径 */ public String upload(MultipartFile file, String folder, String filename, IFolderName options) throws ServiceException { if (filename == null) { filename = file.getName(); } if (!isAllowExtension(filename, "allow.upload.suffix", "allow.download.suffix")) { throw new ServiceException(ErrorCode.FILE_FORMAT_NONSUPPORT); } InputStream input; try { input = file.getInputStream(); } catch (IOException e) { throw new ServiceException(ErrorCode.FILE_UPLOAD_ERROR, e); } long size = file.getSize(); return fileService.upload(options, filename, input, size); } /** * 下载文件
* 文件扩展名必须在allow.download.suffix配置中
* 文件必须位于文件服务配置的文件夹或allow.download.folder配置中
* * @param path 文件的保存路径(不可为空) * @param filename 下载时浏览器提示的文件名(可为空,从保存路径中截取) * @throws IOException * @throws ServiceException */ public void download(HttpServletRequest request, HttpServletResponse response, String path, String filename) throws ServiceException { if (VerifyUtil.isBlank(path)) { throw new ServiceException(ResultCode.PARAMTER_NOT_NULL); } if (VerifyUtil.isBlank(filename)) { filename = new File(path).getName(); } // 1.获取要下载的文件的绝对路径 String realPath = getRealPath(path); if (!new File(realPath).exists()) { throw new ServiceException(ErrorCode.FILE_NOT_FOUND); } try { // 2.设置content-disposition响应头控制浏览器以下载的形式打开文件 response.setHeader("content-disposition", FileIoUtil.toAttachmentDesc(request, filename)); } catch (Exception e) { String extension = VerifyUtil.nvl(FileUtil.getExtension(filename), ".tmp"); response.setHeader("content-disposition", "attachment;filename=file" + extension); } // 3.获取要下载的文件输入流 try (InputStream in = new FileInputStream(realPath);) { // 3.1.通过response对象获取OutputStream流 OutputStream out = response.getOutputStream(); // 3.2.复制文件流数据 FileUtil.copy(in, out); } catch (IOException e) { throw new ServiceException(ErrorCode.FILE_DOWNLOAD_ERROR, e); } } // 获取文件实际保存路径 // 如果是download.location.xxx指定的路径, 根据配置获取文件 // 如果是文件服务保存的, 根据文件服务配置获取文件 // 否则获取WEB服务根路径下的文件 private String getRealPath(String path) throws ServiceException { if (!StringUtil.isUrl(path)) { String savePath = getSpecialLocationPath(path); if (savePath != null) { return savePath; } } String serverPath = WebUtil.getInstance().getWebRoot(); // 先判断文件路径是不是文件服务 LocalFileService fs = null; if (fileService instanceof LocalFileService) { LocalFileService lfs = (LocalFileService) fileService; if (fileService.isInsideFile(path)) { fs = lfs; } } if (fs == null) { // WebRoot if (StringUtil.isUrl(path)) { throw new ServiceException(ResultCode.PARAMTER_VALUE_ERROR); } if (!isAllowDownload(path, "web", true)) { throw new ServiceException(ResultCode.FORBIDDEN); } return FileUtil.concat(serverPath, path); } else { // FileService if (!isAllowDownload(path, "filecenter", false)) { throw new ServiceException(ResultCode.FORBIDDEN); } // baseUrl = http://img.xx.com/ 或 /filecenter/ String baseUrl = fs.getBaseUrl(); String relativePath = path.substring(baseUrl.length()); String savePath = fs.getSavePath(); if (FileUtil.isAbsolutePath(savePath)) { // 绝对路径 // savePath = /home/filecenter/ return FileUtil.concat(savePath, relativePath); } else { // 相对路径 // savePath = ../filecenter/ return FileUtil.concat(serverPath, savePath, relativePath); } } } private static final Pattern FIRST_FOLDER_NAME = Pattern.compile("\\s*[/\\\\]?([^/\\\\]+)[/\\\\](.*+)\\s*"); private static final Pattern DOWNLOAD_LOCATION_CONFIG = Pattern.compile("\\s*(?:(alias|root)\\s+)?(.*+)\\s*"); // 通过配置指定允许下载指定文件夹中的文件 // 如果: download.location.xxx = alias /home/tomcat/runtimelogs/ // 那么: path=xxx/web-user-error.log = /home/tomcat/runtimelogs/web/user-error.log文件 // 如果: download.location.xxx = root /home/tomcat/runtimelogs/ // 那么: path=xxx/web/user-error.log = /home/tomcat/runtimelogs/xxx/web/user-error.log文件 // 完整配置 // download.location.xxx = alias /home/tomcat/runtimelogs/ // allow.download.suffix.location.xxx = .log|.err|.txt private String getSpecialLocationPath(String path) throws ServiceException { String folder; String file; { Matcher matcher = FIRST_FOLDER_NAME.matcher(path); if (matcher.matches()) { folder = matcher.group(1); file = matcher.group(2); } else { return null; } } String type; String root; { WebConfig config = WebConfig.getInstance(); String string = config.getString("download.location." + folder); if (VerifyUtil.isBlank(string)) { return null; } Matcher matcher = DOWNLOAD_LOCATION_CONFIG.matcher(string); if (matcher.matches()) { type = matcher.group(1); root = matcher.group(2); } else { return null; } } if (!isAllowDownload(path, "location." + folder, false)) { throw new ServiceException(ResultCode.FORBIDDEN); } if (VerifyUtil.isBlank(type) || "alias".equals(type)) { return FileUtil.concat(root, file); } else if ("root".equals(type)) { return FileUtil.concat(root, folder, file); } else { return null; } } private boolean isAllowExtension(String path, String key, String... keys) { WebConfig config = WebConfig.getInstance(); String string = config.getStringUseDefKeys(key, keys); return isAllowExtension(path, StringUtil.split(string)); } /** 判断是否允许下载 **/ // allow.download.suffix = default // allow.download.suffix.web = WebRoot // allow.download.suffix.filecenter = FileService // allow.download.suffix.location = download.location // allow.download.suffix.location.xxx = download.location.xxx private boolean isAllowDownload(String path, String type, boolean checkFolder) { WebConfig config = WebConfig.getInstance(); String suffixes = config.getStringUseSuffix("allow.download.suffix", type); boolean allowSuffix = isAllowExtension(path, StringUtil.split(suffixes)); boolean allowFolder = true; // WebRoot下的文件需要判断文件夹 if (checkFolder) { String string = config.getStringUseSuffix("allow.download.folder", type); String[] folders = StringUtil.split(string); if (folders == null || folders.length == 0) { folders = new String[] { "assets" }; // 兼容旧版本 } allowFolder = isAllowFolder(path, folders); } return allowSuffix && allowFolder; } private boolean isAllowExtension(String path, String[] suffixes) { // 判断文件名后缀 if (VerifyUtil.isNotBlank(suffixes)) { for (String suffix : suffixes) { if ("*".equals(suffix)) { return true; } if (path.endsWith(suffix)) { return true; } } } return false; } private boolean isAllowFolder(String path, String[] folders) { if (VerifyUtil.isNotBlank(folders)) { for (String folder : folders) { if ("*".equals(folder)) { return true; } if (path.startsWith(folder + "/") || path.startsWith("/" + folder + "/")) { return true; } } } return false; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy