package com.starcharge.component.datastore; import com.ihidea.core.base.CoreController; import com.ihidea.core.support.servlet.ServletHolderFilter; import com.ihidea.core.util.FileUtilsEx; import com.ihidea.core.util.ImageUtilsEx; import com.ihidea.core.util.JSONUtilsEx; import com.ihidea.core.util.ServletUtilsEx; import com.starcharge.base.redis.RedisClient; import com.starcharge.component.datastore.dao.CptDataInfoMapper; import com.starcharge.component.datastore.dao.model.CptDataInfo; import com.starcharge.component.datastore.fileio.FileIoEntity; import com.starcharge.component.datastore.fileio.IFileInputStream; import org.apache.catalina.connector.ClientAbortException; import org.apache.commons.fileupload.disk.DiskFileItem; import org.apache.commons.io.IOUtils; import org.apache.commons.lang.ArrayUtils; import org.apache.commons.lang.StringUtils; import org.apache.http.client.utils.DateUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.InputStream; import java.net.URLEncoder; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @Controller("fileStoreController") public class FileController extends CoreController { @Autowired private RedisClient redisClient; @Autowired private FileSupportService service; @Autowired private CptDataInfoMapper cptDataInfoMapper; private static final String MULTIPART_BOUNDARY = "MULTIPART_BYTERANGES"; private static Map<String, String> contentTypeMap = new HashMap<String, String>(); static { contentTypeMap.put("html", "text/html"); contentTypeMap.put("htm", "text/html"); contentTypeMap.put("shtml", "text/html"); contentTypeMap.put("apk", "application/vnd.android.package-archive"); contentTypeMap.put("sis", "application/vnd.symbian.install"); contentTypeMap.put("sisx", "application/vnd.symbian.install"); contentTypeMap.put("exe", "application/x-msdownload"); contentTypeMap.put("msi", "application/x-msdownload"); contentTypeMap.put("css", "text/css"); contentTypeMap.put("xml", "text/xml"); contentTypeMap.put("gif", "image/gif"); contentTypeMap.put("jpeg", "image/jpeg"); contentTypeMap.put("jpg", "image/jpeg"); contentTypeMap.put("js", "application/x-javascript"); contentTypeMap.put("atom", "application/atom+xml"); contentTypeMap.put("rss", "application/rss+xml"); contentTypeMap.put("mml", "text/mathml"); contentTypeMap.put("txt", "text/plain"); contentTypeMap.put("jad", "text/vnd.sun.j2me.app-descriptor"); contentTypeMap.put("wml", "text/vnd.wap.wml"); contentTypeMap.put("htc", "text/x-component"); contentTypeMap.put("png", "image/png"); contentTypeMap.put("tif", "image/tiff"); contentTypeMap.put("tiff", "image/tiff"); contentTypeMap.put("wbmp", "image/vnd.wap.wbmp"); contentTypeMap.put("ico", "image/x-icon"); contentTypeMap.put("jng", "image/x-jng"); contentTypeMap.put("bmp", "image/x-ms-bmp"); contentTypeMap.put("svg", "image/svg+xml"); contentTypeMap.put("jar", "application/java-archive"); contentTypeMap.put("war", "application/java-archive"); contentTypeMap.put("ear", "application/java-archive"); contentTypeMap.put("doc", "application/msword"); contentTypeMap.put("pdf", "application/pdf"); contentTypeMap.put("rtf", "application/rtf"); contentTypeMap.put("xls", "application/vnd.ms-excel"); contentTypeMap.put("ppt", "application/vnd.ms-powerpoint"); contentTypeMap.put("7z", "application/x-7z-compressed"); contentTypeMap.put("rar", "application/x-rar-compressed"); contentTypeMap.put("swf", "application/x-shockwave-flash"); contentTypeMap.put("rpm", "application/x-redhat-package-manager"); contentTypeMap.put("der", "application/x-x509-ca-cert"); contentTypeMap.put("pem", "application/x-x509-ca-cert"); contentTypeMap.put("crt", "application/x-x509-ca-cert"); contentTypeMap.put("xhtml", "application/xhtml+xml"); contentTypeMap.put("zip", "application/zip"); contentTypeMap.put("mid", "audio/midi"); contentTypeMap.put("midi", "audio/midi"); contentTypeMap.put("kar", "audio/midi"); contentTypeMap.put("mp3", "audio/mpeg"); contentTypeMap.put("ogg", "audio/ogg"); contentTypeMap.put("m4a", "audio/x-m4a"); contentTypeMap.put("ra", "audio/x-realaudio"); contentTypeMap.put("3gpp", "video/3gpp"); contentTypeMap.put("3gp", "video/3gpp"); contentTypeMap.put("mp4", "video/mp4"); contentTypeMap.put("mpeg", "video/mpeg"); contentTypeMap.put("mpg", "video/mpeg"); contentTypeMap.put("mov", "video/quicktime"); contentTypeMap.put("flv", "video/x-flv"); contentTypeMap.put("m4v", "video/x-m4v"); contentTypeMap.put("mng", "video/x-mng"); contentTypeMap.put("asx", "video/x-ms-asf"); contentTypeMap.put("asf", "video/x-ms-asf"); contentTypeMap.put("wmv", "video/x-ms-wmv"); contentTypeMap.put("avi", "video/x-msvideo"); } /** * 上传文件 * * @param request * @param response * @param resultFlag * 返回结果,2:手机端上传,返回手机端需要格式 */ @SuppressWarnings("unchecked") @RequestMapping(value = "/uploadFile.do") public void upload(HttpServletRequest request, HttpServletResponse response, String fileImgSize, String storeName, Integer resultFlag) { Map<String, Object> result = new HashMap<String, Object>(); try { Map<String, Object> param = ServletHolderFilter.getContext().getParamMap(); if (param.containsKey("resultFlag")) { resultFlag = Integer.valueOf(String.valueOf(param.get("resultFlag"))); } List<Object[]> fileList = new ArrayList<Object[]>(); // 根据里面参数自动判断 for (String nameKey : param.keySet()) { Object _obj = param.get(nameKey); if (_obj != null && _obj instanceof List && ((List) _obj).size() > 0) { Object fileItem = ((List) _obj).get(0); if (fileItem instanceof DiskFileItem) { // 如果是servlet2上传的文件 List<DiskFileItem> diskFileItemList = (List<DiskFileItem>) _obj; for (DiskFileItem file : diskFileItemList) { String fileName = FileUtilsEx.getFileNameByPath(file.getName()); byte[] fileContent = file.get(); fileList.add(new Object[] { fileName, fileContent }); } } else if (fileItem instanceof Object[] && ((Object[]) fileItem).length == 2 && ((Object[]) fileItem)[0] instanceof String && ((Object[]) fileItem)[1] instanceof byte[]) { // 如果是servlet3上传的文件 List<Object[]> diskFileItemList = (List<Object[]>) _obj; for (Object[] file : diskFileItemList) { fileList.add(file); } } } } if (fileList.size() > 0) { List<String> fileIdList = new ArrayList<String>(); for (Object[] file : fileList) { fileIdList.add(service.add((String) file[0], (byte[]) file[1], StringUtils.isBlank(storeName) ? "ds_upload" : storeName, fileImgSize)); } CptDataInfo cptDataInfo=cptDataInfoMapper.selectByPrimaryKey(fileIdList.get(0)); SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd"); String date=sdf.format(cptDataInfo.getCreateTime()).replace("-","/"); String url=date+"/"+fileIdList.get(0); fileIdList.add(url); // 返回文件id result.put("fileIdList", fileIdList); // TODO 如果是kindeditor,则需要返回以下2个参数,后期考虑分离开 if (resultFlag != null && resultFlag == 3) { result.put("error", 0); // 只能用数字 result.put("url", "download/" + fileIdList.get(0)); } } else { logger.error("上传文件没有接受到文件!"); } } catch (Exception e) { logger.error(e.getMessage(), e); result.put("errorMsg", e.getMessage()); } // TODO 后面这里需要统一 if (resultFlag == null) { ServletUtilsEx.renderJson(response, result); } else if (resultFlag == 2) { ServletUtilsEx.renderText(response, "{\"code\":\"200\",\"text\":null,\"data\":" + JSONUtilsEx.serialize(result) + "}"); } else if (resultFlag == 3) { ServletUtilsEx.renderJson(response, result); } } /** * 删除文件 * * @param fileId */ @RequestMapping(value = "/deleteFile.do") public void delete(String fileId) { service.remove(fileId); } /** * <pre> * 文件下载页面 * * 使用方式: * 页面定义<a href="downloadFile.do?id="+文件id target="blank">文件名</a> * * </pre> * * @param id * blob表id * @param downloadFlag * 默认下载 * @param response * @throws Exception */ @Deprecated @RequestMapping("/downloadFile.do") public void downloadFile(HttpServletRequest request, String id, String downloadFlag, HttpServletResponse response, String fileImgSize, String mineType) throws Exception { download(request, response, id, downloadFlag, fileImgSize, mineType); } private List<Range> getRange(HttpServletRequest request, HttpServletResponse response, FileIoEntity file) throws IOException { long length = file.getDataInfo().getFileSize().longValue(); long lastModified = file.getDataInfo().getCreateTime().getTime(); String eTag = file.getDataInfo().getFileName() + "_" + length + "_" + lastModified; // Prepare some variables. The full Range represents the complete file. Range full = new Range(0, length - 1, length); List<Range> ranges = new ArrayList<Range>(); // Validate and process Range and If-Range headers. String range = request.getHeader("Range"); if (range != null) { // Range header should match format "bytes=n-n,n-n,n-n...". If not, // then return 416. if (!range.matches("^bytes=\\d*-\\d*(,\\d*-\\d*)*$")) { response.setHeader("Content-Range", "bytes */" + length); // Required // in // 416. response.sendError(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE); return null; } // If-Range header should either match ETag or be greater then // LastModified. If not, // then return full file. String ifRange = request.getHeader("If-Range"); if (ifRange != null && !ifRange.equals(eTag)) { try { long ifRangeTime = request.getDateHeader("If-Range"); // Throws // IAE // if // invalid. if (ifRangeTime != -1 && ifRangeTime + 1000 < lastModified) { ranges.add(full); } } catch (IllegalArgumentException ignore) { ranges.add(full); } } // If any valid If-Range header, then process each part of byte // range. if (ranges.isEmpty()) { for (String part : range.substring(6).split(",")) { // Assuming a file with length of 100, the following // examples returns bytes at: // 50-80 (50 to 80), 40- (40 to length=100), -20 // (length-20=80 to length=100). long start = sublong(part, 0, part.indexOf("-")); long end = sublong(part, part.indexOf("-") + 1, part.length()); if (start == -1) { start = length - end; end = length - 1; } else if (end == -1 || end > length - 1) { end = length - 1; } else if (start == end) { start = 0; } // Check if Range is syntactically valid. If not, then // return 416. if (start > end) { response.setHeader("Content-Range", "bytes */" + length); // Required // in // 416. response.sendError(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE); return null; } // Add range. ranges.add(new Range(start, end, length)); } } } return ranges; } private String getContentType(String fileName) { String contentType = "application/octet-stream"; if (fileName.lastIndexOf(".") < 0) { return contentType; } fileName = FileUtilsEx.getSuffix(fileName).toLowerCase(); if (contentTypeMap.containsKey(fileName)) { contentType = contentTypeMap.get(fileName); } return contentType; } private static long sublong(String value, int beginIndex, int endIndex) { String substring = value.substring(beginIndex, endIndex); return (substring.length() > 0) ? Long.parseLong(substring) : -1; } protected class Range { long start; long end; long length; long total; public Range(long start, long end, long total) { this.start = start; this.end = end; this.length = end - start + 1; this.total = total; } } protected void download(final HttpServletRequest request, final HttpServletResponse response, String id, final String downloadFlag, final String fileImgSize, final String mineType) throws Exception { service.execute(id, new IFileInputStream() { @Override public void execute(FileIoEntity entity, InputStream is) throws Exception { response.reset(); ServletOutputStream os = response.getOutputStream(); try { if (entity != null && is != null) { // TODO String previousToken = // servletRequest.getHeader("If-None-Match"); response.setHeader("ETag", entity.getDataInfo().getId() + "_" + entity.getDataInfo().getCreateTime().getTime()); response.setDateHeader("Last-Modified", entity.getDataInfo().getCreateTime().getTime()); // 1周失效 response.setDateHeader("Expires", System.currentTimeMillis() + 604800000L); String contentType = StringUtils.isNotBlank(mineType) ? mineType : getContentType(entity.getDataInfo().getFileName()); // 如果没有设置flag或为0,则下载,否则直接打印在窗口上 String disposition = (StringUtils.isBlank(downloadFlag) || "true".equals(downloadFlag)) ? "attachment" : ""; response.setHeader("Content-Disposition", disposition + ";filename=" + URLEncoder.encode(entity.getDataInfo().getFileName(), "UTF-8")); // 支持range List<Range> ranges = getRange(request, response, entity); try { // 如果启用压缩图片,则先从缓存中获取 if (StringUtils.isNotBlank(fileImgSize)) { byte[] imgBytes = redisClient.get("imageCache:"+entity.getDataInfo().getId() + "#" + fileImgSize, byte[].class); if (ArrayUtils.isEmpty(imgBytes)) { String[] sizeArray = fileImgSize.replace(",", "|").replace("x", "|").split("\\|"); imgBytes = ImageUtilsEx.resizeImage(is, Integer.valueOf(sizeArray[0]), Integer.valueOf(sizeArray[1]), FileUtilsEx.getSuffix(entity.getDataInfo().getFileName())); // 缓存10天 redisClient.put("imageCache:"+entity.getDataInfo().getId() + "#" + fileImgSize, imgBytes, 864000); } response.setContentType(contentType); response.setHeader("Content-Range", "bytes " + 0L + "-" + String.valueOf(imgBytes.length) + "/" + String.valueOf(imgBytes.length)); response.addHeader("Content-Length", String.valueOf(imgBytes.length)); IOUtils.write(imgBytes, os); } else if (ranges.size() == 0) { response.setContentType(contentType); response.setHeader("Content-Range", "bytes " + 0L + "-" + entity.getDataInfo().getFileSize().longValue() + "/" + entity.getDataInfo().getFileSize().longValue()); response.addHeader("Content-Length", String.valueOf(entity.getDataInfo().getFileSize().longValue())); byte[] buff = new byte[307200]; int cnt = 0; while ((cnt = is.read(buff)) != -1) { IOUtils.write(buff, os); } } else if (ranges.size() == 1) { response.setContentType(contentType); Range r = ranges.get(0); response.setHeader("Content-Range", "bytes " + r.start + "-" + r.end + "/" + r.total); response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT); if (r.length < 2147483647L) { response.setContentLength((int) r.length); } else { response.setHeader("Content-Length", String.valueOf(r.length)); } is.skip(r.start); byte[] buff = new byte[307200]; int cnt = 0; while ((cnt = is.read(buff)) != -1) { IOUtils.write(buff, os); } } else { // TODO 未测试 response.setContentType("multipart/byteranges; boundary=" + MULTIPART_BOUNDARY); response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT); // 206. for (Range r : ranges) { os.println(); os.println("--" + MULTIPART_BOUNDARY); os.println("Content-Type: " + contentType); os.println("Content-Range: bytes " + r.start + "-" + r.end + "/" + r.total); is.skip(r.start); byte[] buff = new byte[307200]; int cnt = 0; while ((cnt = is.read(buff)) != -1) { IOUtils.write(buff, os); } } os.println(); os.println("--" + MULTIPART_BOUNDARY + "--"); } os.flush(); } catch (ClientAbortException e) { } } else { response.sendError(404); } } catch (Exception e) { logger.debug(e.getMessage(), e); } catch (Error e) { logger.debug(e.getMessage(), e); } finally { if (os != null) { os.close(); } } } }); } }