com.yixan.base.web.utils.WebFileUtil Maven / Gradle / Ivy
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 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);
}
}
// 获取文件实际保存路径, 如果是文件服务保存的, 根据文件服务配置获取文件; 否则获取WEB服务根路径下的文件
private String getRealPath(String path) throws ServiceException {
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, false)) {
throw new ServiceException(ResultCode.FORBIDDEN);
}
return FileUtil.concat(serverPath, path);
} else { // FileService
if (!isAllowDownload(path, true)) {
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 boolean isAllowExtension(String path, String key, String... keys) {
WebConfig config = WebConfig.getInstance();
// 判断文件名后缀
String[] suffixes = config.getArray(key);
if (suffixes == null && keys != null && keys.length > 0) {
for (String k : keys) {
suffixes = config.getArray(k);
if (suffixes != null) {
break;
}
}
}
if (suffixes != null) {
for (String suffix : suffixes) {
if (path.endsWith(suffix)) {
return true;
}
}
}
return false;
}
/** 判断是否允许下载 **/
private boolean isAllowDownload(String path, boolean inFileService) {
boolean allowSuffix = isAllowExtension(path, "allow.download.suffix");
// 判断文件夹
boolean allowFolder;
if (inFileService) {
allowFolder = true; // 文件服务路径下的都可以下载
} else {
allowFolder = false;
WebConfig config = WebConfig.getInstance();
String[] folders = config.getArray("allow.download.folder", false);
if (folders == null || folders.length == 0) {
folders = new String[] { "assets" }; // 兼容旧版本
}
for (String folder : folders) {
if (path.startsWith(folder + "/") || path.startsWith("/" + folder + "/")) {
allowFolder = true;
break;
}
}
}
return allowSuffix && allowFolder;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy