/*
* Copyright 2015-2016 http://hsweb.me
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.hsweb.web.controller.file;
import com.alibaba.fastjson.JSON;
import org.hsweb.commons.StringUtils;
import org.hsweb.expands.compress.Compress;
import org.hsweb.expands.compress.zip.ZIPWriter;
import org.hsweb.expands.office.excel.ExcelIO;
import org.hsweb.expands.office.excel.config.Header;
import org.hsweb.web.bean.po.resource.Resources;
import org.hsweb.web.core.authorize.annotation.Authorize;
import org.hsweb.web.core.exception.NotFoundException;
import org.hsweb.web.core.logger.annotation.AccessLogger;
import org.hsweb.web.core.message.ResponseMessage;
import org.hsweb.web.service.resource.FileService;
import org.hsweb.web.service.resource.ResourcesService;
import org.slf4j.LoggerFactory;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
/**
* 文件管理控制器,用于上传和下载资源文件
*
* @author zhouhao
* @since 1.0
*/
@RestController
@RequestMapping(value = "/file")
@AccessLogger("文件管理")
@Authorize
public class FileController {
private org.slf4j.Logger logger = LoggerFactory.getLogger(this.getClass());
@Resource
private ResourcesService resourcesService;
@Resource
private FileService fileService;
//文件名中不允许出现的字符 \ / : | ? < > "
private static final Pattern fileNameKeyWordPattern = Pattern.compile("(\\\\)|(/)|(:)(|)|(\\?)|(>)|(<)|(\")");
private static final Map<String, String> mediaTypeMapper = new HashMap<>();
static {
mediaTypeMapper.put(".png", MediaType.IMAGE_PNG_VALUE);
mediaTypeMapper.put(".jpg", MediaType.IMAGE_JPEG_VALUE);
mediaTypeMapper.put(".jpeg", MediaType.IMAGE_JPEG_VALUE);
mediaTypeMapper.put(".gif", MediaType.IMAGE_GIF_VALUE);
mediaTypeMapper.put(".bmp", MediaType.IMAGE_JPEG_VALUE);
mediaTypeMapper.put(".json", MediaType.APPLICATION_JSON_VALUE);
mediaTypeMapper.put(".txt", MediaType.TEXT_PLAIN_VALUE);
mediaTypeMapper.put(".css", MediaType.TEXT_PLAIN_VALUE);
mediaTypeMapper.put(".js", "application/javascript");
mediaTypeMapper.put(".html", MediaType.TEXT_HTML_VALUE);
mediaTypeMapper.put(".xml", MediaType.TEXT_XML_VALUE);
}
/**
* 构建并下载excel
*
* @param name excel文件名
* @param headerJson 表头配置JSON 格式:{@link Header}
* @param dataJson 数据JSON 格式:{@link List<Map<String,Object>}
* @param response {@link HttpServletResponse}
* @throws Exception 构建excel异常
* @throws IOException 写出excel异常
*/
@RequestMapping(value = "/download/{name}.xlsx", method = {RequestMethod.POST})
@AccessLogger("下载excel文件")
public void downloadExcel(@PathVariable("name") String name,
@RequestParam("header") String headerJson,
@RequestParam("data") String dataJson,
HttpServletResponse response) throws Exception {
response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
response.setHeader("Content-disposition", "attachment;filename=" + URLEncoder.encode(name, "utf-8") + ".xlsx");
List<Header> headers = JSON.parseArray(headerJson, Header.class);
List<Map> datas = JSON.parseArray(dataJson, Map.class);
ExcelIO.write(response.getOutputStream(), headers, (List) datas);
}
/**
* 构建并下载zip文件.仅支持POST请求
*
* @param name 文件名
* @param dataStr 数据,jsonArray. 格式:[{"name":"fileName","text":"fileText"}]
* @param response {@link HttpServletResponse}
* @throws IOException 写出zip文件错误
* @throws RuntimeException 构建zip文件错误
*/
@RequestMapping(value = "/download-zip/{name:.+}", method = {RequestMethod.POST})
@AccessLogger("下载zip文件")
public void downloadZip(@PathVariable("name") String name,
@RequestParam("data") String dataStr,
HttpServletResponse response) throws IOException {
response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
response.setHeader("Content-disposition", "attachment;filename=" + URLEncoder.encode(name, "utf-8"));
ZIPWriter writer = Compress.zip();
List<Map<String, String>> data = (List) JSON.parseArray(dataStr, Map.class);
data.forEach(map -> writer.addTextFile(map.get("name"), map.get("text")));
writer.write(response.getOutputStream());
}
/**
* 构建一个文本文件,并下载.支持GET,POST请求
*
* @param name 文件名
* @param text 文本内容
* @param response {@link HttpServletResponse}
* @throws IOException 写出文本内容错误
*/
@RequestMapping(value = "/download-text/{name:.+}", method = {RequestMethod.GET, RequestMethod.POST})
@AccessLogger("下载text文件")
public void downloadTxt(@PathVariable("name") String name,
@RequestParam("text") String text,
HttpServletResponse response) throws IOException {
response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
response.setHeader("Content-disposition", "attachment;filename=" + URLEncoder.encode(name, "utf-8"));
response.getWriter().write(text);
}
/**
* 使用restful风格,通过文件ID下载已经上传的文件,支持断点下载
* 如: http://host:port/file/download/aSk2a/file.zip 将下载 ID为aSk2a的文件.并命名为file.zip
*
* @param id 文件ID
* @param name 文件名
* @param response {@link HttpServletResponse}
* @param request {@link HttpServletRequest}
* @return 下载结果, 在下载失败时, 将返回错误信息
* @throws IOException 读写文件错误
* @throws NotFoundException 文件不存在
*/
@RequestMapping(value = "/download/{id}/{name:.+}", method = RequestMethod.GET)
@AccessLogger("下载文件")
public ResponseMessage restDownLoad(@PathVariable("id") String id,
@PathVariable("name") String name,
HttpServletResponse response, HttpServletRequest request) throws IOException {
return downLoad(id, name, response, request);
}
/**
* 通过文件ID下载已经上传的文件,支持断点下载
* 如: http://host:port/file/download/aSk2a/file.zip 将下载 ID为aSk2a的文件.并命名为file.zip
*
* @param id 要下载资源文件的id
* @param name 自定义文件名,该文件名不能存在非法字符.如果此参数为空(null).将使用文件上传时的文件名
* @param response {@link HttpServletResponse}
* @param request {@link HttpServletRequest}
* @return 下载结果, 在下载失败时, 将返回错误信息
* @throws IOException 读写文件错误
* @throws NotFoundException 文件不存在
*/
@RequestMapping(value = "/download/{id}", method = RequestMethod.GET)
@AccessLogger("下载文件")
public ResponseMessage downLoad(@PathVariable("id") String id,
@RequestParam(value = "name", required = false) String name,
HttpServletResponse response, HttpServletRequest request) throws IOException {
Resources resources = resourcesService.selectByPk(id);
if (resources == null || resources.getStatus() != 1) {
throw new NotFoundException("文件不存在");
} else {
if (!"file".equals(resources.getType()))
throw new NotFoundException("文件不存在");
//获取contentType,默认application/octet-stream
String contentType = mediaTypeMapper.get(resources.getSuffix().toLowerCase());
if (contentType == null)
contentType = "application/octet-stream";
//未自定义文件名,则使用上传时的文件名
if (StringUtils.isNullOrEmpty(name))
name = resources.getName();
//如果未指定文件拓展名,则追加默认的文件拓展名
if (!name.contains("."))
name = name.concat(".").concat(resources.getSuffix());
//关键字剔除
name = fileNameKeyWordPattern.matcher(name).replaceAll("");
int skip = 0;
long fSize = resources.getSize();
//尝试判断是否为断点下载
try {
//获取要继续下载的位置
String Range = request.getHeader("Range").replaceAll("bytes=", "").replaceAll("-", "");
skip = StringUtils.toInt(Range);
} catch (Exception e) {
}
response.setContentLength((int) fSize);//文件大小
response.setContentType(contentType);
response.setHeader("Content-disposition", "attachment;filename=" + URLEncoder.encode(name, "utf-8"));
//try with resource
try (BufferedInputStream inputStream = new BufferedInputStream(fileService.readResources(resources));
BufferedOutputStream stream = new BufferedOutputStream(response.getOutputStream())) {
//断点下载
if (skip > 0) {
inputStream.skip(skip);
response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
String contentRange = new StringBuffer("bytes ").append(skip).append("-").append(fSize - 1).append("/").append(fSize).toString();
response.setHeader("Content-Range", contentRange);
}
byte b[] = new byte[2048 * 10];
while ((inputStream.read(b)) != -1) {
stream.write(b);
}
stream.flush();
} catch (IOException e) {
logger.debug(String.format("download file error%s", e.getMessage()));
throw e;
}
return null;
}
}
/**
* 上传文件,支持多文件上传.获取到文件流后,调用{@link FileService#saveFile(InputStream, String)}进行文件保存
* 上传成功后,将返回资源信息如:[{"id":"fileId","name":"fileName","md5":"md5"}]
*
* @param files 文件列表
* @return 文件上传结果.
* @throws IOException 保存文件错误
*/
@RequestMapping(value = "/upload", method = RequestMethod.POST)
@AccessLogger("上传文件")
public ResponseMessage upload(@RequestParam("file") MultipartFile[] files) throws IOException {
if (logger.isInfoEnabled())
logger.info(String.format("start upload , file number:%s", files.length));
List<Resources> resourcesList = new LinkedList<>();
for (int i = 0; i < files.length; i++) {
MultipartFile file = files[i];
if (!file.isEmpty()) {
if (logger.isInfoEnabled())
logger.info("start write file:{}", file.getOriginalFilename());
String fileName = file.getOriginalFilename();
Resources resources = fileService.saveFile(file.getInputStream(), fileName);
resourcesList.add(resources);
}
}//响应上传成功的资源信息
return ResponseMessage.ok(resourcesList)
.include(Resources.class, "id", "name", "md5");
}
}