package com.cm.common.plugin.service.file.impl;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.cm.common.base.AbstractService;
import com.cm.common.config.properties.FileProperties;
import com.cm.common.enums.ErrorResultCodeEnum;
import com.cm.common.enums.UploadTypeEnum;
import com.cm.common.exception.*;
import com.cm.common.exception.base.SystemException;
import com.cm.common.plugin.dao.file.IFileDao;
import com.cm.common.plugin.pojo.dtos.file.FileInfoDTO;
import com.cm.common.plugin.pojo.dtos.file.FileInfoWithPathDTO;
import com.cm.common.plugin.service.file.IFileService;
import com.cm.common.pojo.ListPage;
import com.cm.common.pojo.dtos.FileDTO;
import com.cm.common.pojo.pos.FilePO;
import com.cm.common.result.SuccessResultData;
import com.cm.common.result.SuccessResultList;
import com.cm.common.utils.DateUtil;
import com.cm.common.utils.ResourceUtil;
import com.cm.common.utils.UUIDUtil;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import it.sauronsoftware.jave.Encoder;
import it.sauronsoftware.jave.EncoderException;
import it.sauronsoftware.jave.MultimediaInfo;
import net.coobird.thumbnailator.Thumbnails;
import org.apache.catalina.connector.ClientAbortException;
import org.apache.commons.fileupload.disk.DiskFileItem;
import org.apache.commons.lang3.StringUtils;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.commons.CommonsMultipartFile;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLEncoder;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.channels.WritableByteChannel;
import java.security.MessageDigest;
import java.util.*;

/**
 * @ClassName: FileServiceImpl
 * @Description: 文件处理
 * @Author: WangGeng
 * @Date: 2019/3/10 7:06 PM
 * @Version: 1.0
 **/
@Service
public class FileServiceImpl extends AbstractService implements IFileService {

    private static final Logger LOG = LoggerFactory.getLogger(FileServiceImpl.class);
    private static final char[] HEX_CODE = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
    /**
     * 文件MD5值开头
     */
    public static final String FILE_MD5_PREFIX = "MD5:";
    /**
     * 文件引用值开头
     */
    public static final String FILE_REF_PREFIX = "REF:";

    @Autowired
    private FileProperties fileProperties;
    @Autowired
    private IFileDao fileDao;

    private String[] imageTypes;
    private String[] videoTypes;
    private String[] audioTypes;
    private String[] fileTypes;

    public List<FileInfoDTO> listFileInfo(Map<String, Object> params) throws SearchException {
        return fileDao.listFileInfo(params);
    }

    @Override
    public SuccessResultList<List<FileInfoDTO>> listPageFileInfo(ListPage page) throws SearchException {
        PageHelper.startPage(page.getPage(), page.getRows());
        List<FileInfoDTO> fileInfoDTOs = fileDao.listFileInfo(page.getParams());
        PageInfo<FileInfoDTO> pageInfo = new PageInfo<>(fileInfoDTOs);
        return new SuccessResultList<>(fileInfoDTOs, pageInfo.getPageNum(), pageInfo.getTotal());
    }

    @Override
    public void removeFile(String ids) throws RemoveException {
        Map<String, Object> params = getHashMap(2);
        params.put("fileIds", Arrays.asList(ids.split("_")));
        setUpdateInfo(params);
        fileDao.removeFile(params);
    }

    @Override
    public void deleteFile(String ids) throws RemoveException {
        Map<String, Object> params = getHashMap(2);
        params.put("fileIds", Arrays.asList(ids.split("_")));
        Map<String, Object> fileParams = getHashMap(4);
        List<FileInfoWithPathDTO> fileInfoWithPathDTOs = fileDao.listFileInfoWithPath(params);
        // 删除文件
        for (FileInfoWithPathDTO fileInfoWithPathDTO : fileInfoWithPathDTOs) {
            // 如果文件描述为空，可以直接删除源文件
            if (StringUtils.isBlank(fileInfoWithPathDTO.getFileSummary())) {
                deleteSourceFile(fileInfoWithPathDTO.getFilePath());
                continue;
            }
            // 文件描述不为空时，需要判断是否删除的是源文件，源文件在一个系统中只保留一份
            // 如果是引用文件的数据，不删除源文件
            if (fileInfoWithPathDTO.getFileSummary().startsWith(FILE_REF_PREFIX)) {
                continue;
            }
            // 如果不是MD5源文件，略过
            if (!fileInfoWithPathDTO.getFileSummary().startsWith(FILE_MD5_PREFIX)) {
                continue;
            }
            // 如果删除的是源文件，需要查询系统中是否还存在引用的数据
            List<FileInfoDTO> fileInfoDTOs = fileDao.listFileByMd5(FILE_REF_PREFIX + fileInfoWithPathDTO.getFileId());
            // 如果不存在对源文件引用的数据，则直接删除源文件
            if (fileInfoDTOs.size() == 0) {
                deleteSourceFile(fileInfoWithPathDTO.getFilePath());
                continue;
            }
            fileParams.clear();
            // 如果存在引用数据，取出第一个修改为源文件，并将其他的引用更新为新的源文件ID
            FileInfoDTO fileInfoDTO = fileInfoDTOs.get(0);
            fileParams.put("fileSummary", fileInfoWithPathDTO.getFileSummary());
            fileParams.put("fileId", fileInfoDTO.getFileId());
            fileDao.updateFileSummary(fileParams);
            // 获取其他的ID列表，更新文件引用关系
            List<String> otherFileIds = new ArrayList<>();
            for (int i = 1; i < fileInfoDTOs.size(); i++) {
                otherFileIds.add(fileInfoDTOs.get(i).getFileId());
            }
            // 如果不存在其它的引用，略过
            if (otherFileIds.isEmpty()) {
                continue;
            }
            fileParams.remove("fileId");
            fileParams.put("fileSummary", FILE_REF_PREFIX + fileInfoDTO.getFileId());
            fileParams.put("fileIds", otherFileIds);
            fileDao.updateFileSummary(fileParams);
        }
        // 删除记录
        fileDao.deleteFile(params);
    }

    /**
     * 删除源文件
     *
     * @param sourceFilePath 源文件路径
     */
    private void deleteSourceFile(String sourceFilePath) {
        File file = new File(sourceFilePath);
        if (file.exists()) {
            boolean isDelete = file.delete();
            if (isDelete) {
                LOG.debug("文件删除成功");
            } else {
                LOG.debug("文件删除失败");
            }
        }
    }

    @Override
    public SuccessResultData<String> uploadSingle(MultipartFile uploadFile, UploadTypeEnum uploadTypeEnum, Map<String, Object> params) throws SystemException {
        uploadFile(null, uploadFile, uploadTypeEnum, params);
        return new SuccessResultData<>(params.get("fileId").toString());
    }

    @Override
    public SuccessResultData<String> uploadSingle(String token, MultipartFile uploadFile, UploadTypeEnum uploadTypeEnum, Map<String, Object> params) throws SystemException {
        uploadFile(token, uploadFile, uploadTypeEnum, params);
        return new SuccessResultData<>(params.get("fileId").toString());
    }

    @Override
    public FileDTO uploadSingleForFileDTO(String token, MultipartFile uploadFile, UploadTypeEnum uploadTypeEnum, Map<String, Object> params) throws SystemException {
        uploadFile(token, uploadFile, uploadTypeEnum, params);
        FileDTO fileDTO = new FileDTO();
        fileDTO.setFileId(params.get("fileId").toString());
        fileDTO.setFileName(params.get("fileName").toString());
        fileDTO.setFileUrl(params.get("fileUrl").toString());
        fileDTO.setFileType(params.get("fileType").toString());
        return fileDTO;
    }

    /**
     * 上传文件
     *
     * @param token
     * @param uploadFile
     * @param uploadTypeEnum
     * @param params
     * @throws SystemException
     */
    private void uploadFile(String token, MultipartFile uploadFile, UploadTypeEnum uploadTypeEnum, Map<String, Object> params) throws SystemException {
        String baseUploadPath = fileProperties.getUploadPath();
        if (StringUtils.isBlank(baseUploadPath)) {
            throw new SystemException("上传路径未配置");
        }
        String fileName = uploadFile.getOriginalFilename();
        // 文件大小
        long fileSize = uploadFile.getSize();
        // 文件类型
        String fileType = getFileType(fileName);
        // 文件保存路径
        String uploadPath = getUploadPath(baseUploadPath, uploadTypeEnum, fileType);
        // 文件保存名称
        String uploadFileName = getUploadFileName(fileType);
        String fileMd5 = uploadFile(uploadFile, uploadPath, uploadFileName);
        if (fileMd5 == null) {
            throw new SaveException("文件上传失败");
        }
        // 获取MD5相同的文件
        List<FileInfoDTO> fileInfoDTOs = fileDao.listFileByMd5(FILE_MD5_PREFIX + fileMd5);
        if (fileInfoDTOs.size() > 0) {
            // 删除新增的文件
            File uploadedFile = new File(uploadPath + File.separator + uploadFileName);
            if (uploadedFile.exists()) {
                uploadedFile.delete();
            }
            // 保存记录，但文件信息都是原有的文件
            params.clear();
            FileInfoDTO fileInfoDTO = fileInfoDTOs.get(0);
            params.put("fileSummary", "REF:" + fileInfoDTO.getFileId());
            saveFile(token, params, fileInfoDTO.getFileName(), fileInfoDTO.getFilePath(), fileInfoDTO.getFileUrl(), fileInfoDTO.getFileType(), fileInfoDTO.getFileSize());
            return;
        }
        params.put("fileSummary", "MD5:" + fileMd5);
        saveUploadFileInfo(token, fileName, uploadPath, uploadFileName, fileType, fileSize, params);
    }

    @Override
    public void saveUploadErrorExcelFileInfo(String fileName, String uploadPath, long fileSize, Map<String, Object> params) {
        saveUploadFileInfo(null, fileName, uploadPath, fileName, "xls", fileSize, params);
    }

    @Override
    public String getUploadExcelErrorPath() {
        String baseUploadPath = fileProperties.getUploadPath();
        if (StringUtils.isBlank(baseUploadPath)) {
            throw new SystemException("上传路径未配置");
        }
        return getUploadPath(baseUploadPath, UploadTypeEnum.ERROR_EXCEL, null);
    }

    @Override
    public List<FileDTO> listFileByFileId(List<String> idList) throws SearchException {
        Map<String, Object> params = getHashMap(1);
        params.put("ids", idList);
        return fileDao.listFile(params);
    }

    /**
     * 保存文件信息
     *
     * @param token
     * @param fileName
     * @param uploadPath
     * @param uploadFileName
     * @param fileType
     * @param fileSize
     */
    private void saveUploadFileInfo(String token, String fileName, String uploadPath, String uploadFileName, String fileType, long fileSize, Map<String, Object> params) {
        String fixPath = uploadPath.replace(fileProperties.getUploadPath(), "");
        if ("\\".equals(File.separator)) {
            fixPath = fixPath.replace("\\", "/");
        }
        if (fixPath.startsWith("/")) {
            fixPath = fixPath.substring(1, fixPath.length() - 1);
        }
        String fileFullPath = String.format("%s%s%s", uploadPath, File.separator, uploadFileName);
        // 压缩图片
        if (isImageFile(fileType)) {
            compressImage(fileFullPath);
            File photo = new File(fileFullPath);
            fileSize = photo.length();
        }
        saveFile(token, params, fileName, fileFullPath, String.format("files/%s/%s", fixPath, uploadFileName), fileType, fileSize);
    }

    /**
     * 保存文件
     *
     * @param token        token
     * @param params       参数
     * @param fileName     文件名
     * @param fileFullPath 文件全路径
     * @param fileUrl      文件相对地址
     * @param fileType     文件类型
     * @param fileSize     文件大小
     */
    private void saveFile(String token, Map<String, Object> params, String fileName, String fileFullPath, String fileUrl, String fileType, long fileSize) {
        params.put("fileId", UUIDUtil.getUUID());
        params.put("fileName", fileName);
        params.put("filePath", fileFullPath);
        params.put("fileUrl", fileUrl);
        params.put("fileType", fileType);
        params.put("fileSize", fileSize);
        params.put("isBack", 0);
        if (StringUtils.isBlank(token)) {
            setSaveInfo(params);
        } else {
            setSaveInfo(token, params);
        }
        fileDao.saveFile(params);
    }

    @Override
    public void downLoadFile(HttpServletResponse response, Map<String, Object> params) throws FileException {
        downLoadFile(null, response, params);
    }

    @Override
    public void downLoadFile(HttpServletRequest request, HttpServletResponse response, Map<String, Object> params) throws FileException {
        downLoadFile(request, response, params, true);
    }

    @Override
    public void downLoadFile(HttpServletRequest request, HttpServletResponse response, Map<String, Object> params, boolean canRange) throws FileException {
        FilePO filePO = fileDao.getFile(params);
        if (null == filePO) {
            throw new SearchException("文件获取失败");
        }
        try (
                RandomAccessFile randomAccessFile = new RandomAccessFile(filePO.getFilePath(), "r");
                FileChannel fileChannel = randomAccessFile.getChannel();
                OutputStream outputStream = response.getOutputStream();
                WritableByteChannel writableByteChannel = Channels.newChannel(outputStream);
        ) {
            boolean isOpen = Boolean.valueOf(params.get("isOpen").toString());
            response.setHeader("Content-Length", filePO.getFileSize());
            response.setContentType(getContentType(filePO.getFileType()));
            if (!isOpen) {
                response.setHeader("Content-Disposition", "attachment;fileName=" + URLEncoder.encode(filePO.getFileName(), "UTF-8"));
            } else {
                response.setHeader("Content-Disposition", "inline;fileName=" + URLEncoder.encode(filePO.getFileName(), "UTF-8"));
                // 如果是图片资源，开启缓存
                if (isImageFile(filePO.getFileType())) {
                    // 如果存在校验修改时间且未做修改，返回304，使用本地资源
                    String ifModifiedSince = request.getHeader("If-Modified-Since");
                    if (StringUtils.isNotBlank(ifModifiedSince) && StringUtils.equalsIgnoreCase(ifModifiedSince, filePO.getGmtModified())) {
                        response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
                        return;
                    }
                    // 缓存有效时间为7天
                    response.setHeader("Expires", DateTime.now().plusDays(7).toDateTime(DateTimeZone.forID("GMT")).toString());
                    // 60秒之内不发送新请求
                    response.setHeader("max-age", "60");
                    response.setHeader("Last-Modified", filePO.getGmtModified());
                }
            }
            String rangeString = null;
            if (canRange && request != null) {
                rangeString = request.getHeader("Range");
                LOG.debug("range: {}", rangeString);
            }
            long contentLength = Long.valueOf(filePO.getFileSize());
            long startRange = 0;
            long endRange = contentLength - 1;
            if (!StringUtils.isBlank(rangeString)) {
                if (!isOpen) {
                    response.setContentType("multipart/byteranges");
                }
                String[] rangeArray = rangeString.substring(rangeString.indexOf("=") + 1).split("-");
                startRange = Long.valueOf(rangeArray[0]);
                if (rangeArray.length > 1) {
                    endRange = Long.valueOf(rangeArray[1]);
                }
                setRangeHeader(startRange, endRange, response, filePO.getFileId(), contentLength);
                randomAccessFile.seek(startRange);
            }
            LOG.debug("startRange: {}, endRange: {}", startRange, endRange);
            long totalOutputLength = endRange - startRange + 1;
            fileChannel.transferTo(startRange, totalOutputLength, writableByteChannel);
            outputStream.flush();
        } catch (Exception e) {
            if (e instanceof ClientAbortException) {
                LOG.debug("客户端断开连接");
            } else {
                throw new FileException("文件输出异常", e);
            }
        }
    }

    /**
     * 同步下载文件
     *
     * @param request
     * @param response
     * @param params
     * @param canRange
     * @throws FileException
     */
    @Deprecated
    public void downLoadFileOld(HttpServletRequest request, HttpServletResponse response, Map<String, Object> params, boolean canRange) throws FileException {
        FilePO filePO = fileDao.getFile(params);
        if (null == filePO) {
            throw new SearchException("文件获取失败");
        }
        try (BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream(new File(filePO.getFilePath())));
             OutputStream outputStream = new BufferedOutputStream(response.getOutputStream());
        ) {
            boolean isOpen = Boolean.valueOf(params.get("isOpen").toString());
            response.setHeader("Content-Length", filePO.getFileSize());
            response.setContentType(getContentType(filePO.getFileType()));
            if (!isOpen) {
                response.setHeader("Content-Disposition", "attachment;fileName=" + URLEncoder.encode(filePO.getFileName(), "UTF-8"));
            } else {
                response.setHeader("Content-Disposition", "inline;fileName=" + URLEncoder.encode(filePO.getFileName(), "UTF-8"));
            }
            String rangeString = null;
            if (canRange && request != null) {
                rangeString = request.getHeader("Range");
                LOG.debug("range: {}", rangeString);
            }
            long contentLength = Long.valueOf(filePO.getFileSize());
            long startRange = 0;
            long endRange = contentLength - 1;
            if (!StringUtils.isBlank(rangeString)) {
                if (!isOpen) {
                    response.setContentType("multipart/byteranges");
                }
                String[] rangeArray = rangeString.substring(rangeString.indexOf("=") + 1).split("-");
                startRange = Long.valueOf(rangeArray[0]);
                if (rangeArray.length > 1) {
                    endRange = Long.valueOf(rangeArray[1]);
                }
                setRangeHeader(startRange, endRange, response, filePO.getFileId(), contentLength);
                bufferedInputStream.skip(startRange);
            }
            LOG.debug("startRange: {}, endRange: {}", startRange, endRange);
            long totalOutputLength = endRange - startRange + 1;
            long outputLength = 0;
            int readLength;
            for (byte[] buf = new byte[INPUT_STREAM_SIZE]; (readLength = bufferedInputStream.read(buf)) > -1 && outputLength < totalOutputLength; ) {
                long remainingLength = totalOutputLength - outputLength;
                int bufSize = remainingLength < INPUT_STREAM_SIZE ? (int) remainingLength : readLength;
                outputStream.write(buf, 0, bufSize);
                outputLength += bufSize;
            }
            outputStream.flush();
        } catch (Exception e) {
            if (e instanceof ClientAbortException) {
                LOG.debug("客户端断开连接");
            } else {
                throw new FileException("文件输出异常", e);
            }
        }
    }

    /**
     * 处理视频流问题
     *
     * @param startRange
     * @param endRange
     * @param response
     * @param fileId
     * @param contentLength
     */
    private void setRangeHeader(long startRange, long endRange, HttpServletResponse response, String fileId, long contentLength) {
        // 这里不设置，会出现第一次加载很慢的情况
        response.setHeader("Content-Length", String.valueOf(endRange - startRange + 1));
        response.setHeader("Content-Range", String.format("bytes %d-%d/%d", startRange, endRange, contentLength));
        response.setHeader("Accept-Ranges", "bytes");
        response.setHeader("Etag", fileId);
        response.setStatus(206);
    }

    @Override
    public JSONObject uEditor(MultipartFile file, HttpServletRequest request, Map<String, Object> params) throws SystemException, IOException {
        if (params.get(UEDITOR_ACTION) == null || StringUtils.isBlank(params.get(UEDITOR_ACTION).toString())) {
            throw new ParamsException("参数错误");
        }
        String action = params.get(UEDITOR_ACTION).toString();
        JSONObject result = null;
        if (UEDITOR_CONFIG.equals(action)) {
            result = uEditorConfig();
        }
        if (UEDITOR_UPLOAD_IMAGE.equals(action) || UEDITOR_UPLOAD_VIDEO.equals(action) || UEDITOR_UPLOAD_FILE.equals(action)) {
            result = uEditorUpload(file, request, action, params);
        }
        if (UEDITOR_LIST_IMAGE.equals(action) || UEDITOR_LIST_FILE.equals(action)) {
            result = uEditorFileList(request, action, params);
        }
        return result;
    }

    @Override
    public JSONObject wangEditorImage(MultipartFile image, UploadTypeEnum image1, Map<String, Object> params) {
        JSONObject result = new JSONObject();
        try {
            FileDTO fileDTO = uploadSingleForFileDTO(null, image, UploadTypeEnum.IMAGE, params);
            result.put("errno", 0);
            JSONArray fileArray = new JSONArray();
            fileArray.add(String.format("route/file/downloadfile/true/%s", fileDTO.getFileId()));
            result.put("data", fileArray);
        } catch (Exception e) {
            LOG.error(e.getMessage(), e);
            result.put("errno", ErrorResultCodeEnum.FILE_ERROR.getValue());
        }
        return result;
    }

    @Override
    public FilePO getFile(Map<String, Object> params) throws SearchException {
        return fileDao.getFile(params);
    }

    @Override
    public void checkVideoDurationAllow(MultipartFile uploadFile, long maxDuration) {
        if (maxDuration <= 0) {
            return;
        }
        checkDurationAllow(uploadFile, maxDuration, "视频");
    }

    @Override
    public void checkAudioDurationAllow(MultipartFile uploadFile, long maxDuration) {
        if (maxDuration <= 0) {
            return;
        }
        checkDurationAllow(uploadFile, maxDuration, "音频");
    }

    /**
     * 校验音、视频长度是否符合
     *
     * @param uploadFile
     * @param maxDuration
     */
    private void checkDurationAllow(MultipartFile uploadFile, long maxDuration, String name) {
        CommonsMultipartFile commonsMultipartFile = (CommonsMultipartFile) uploadFile;
        DiskFileItem diskFileItem = (DiskFileItem) commonsMultipartFile.getFileItem();
        File source = diskFileItem.getStoreLocation();
        Encoder encoder = new Encoder();
        MultimediaInfo multimediaInfo;
        try {
            multimediaInfo = encoder.getInfo(source);
        } catch (EncoderException e) {
            throw new SystemException("文件解析错误");
        }
        long fileDuration = multimediaInfo.getDuration() / 1000;
        if (fileDuration > maxDuration) {
            throw new FileException(name + "时间超过 " + maxDuration + " 秒");
        }
    }

    /**
     * uEditor文件列表
     *
     * @param action
     * @param params
     * @return
     */
    private JSONObject uEditorFileList(HttpServletRequest request, String action, Map<String, Object> params) throws SearchException {
        LOG.debug("uEditor文件列表");
        int start = Integer.parseInt(params.get("start").toString());
        int pageSize = Integer.parseInt(params.get("size").toString());
        int pageNum = start / pageSize + 1;
        String downloadFile = "true";
        if (UEDITOR_LIST_IMAGE.equals(action)) {
            params.put("fileUrl", "images");
        } else if (UEDITOR_LIST_FILE.equals(action)) {
            params.put("fileUrl", "files");
            downloadFile = "false";
        }
        PageHelper.startPage(pageNum, pageSize);
        List<FileDTO> listFileDTOs = fileDao.listFile(params);
        PageInfo<FileDTO> pageInfo = new PageInfo<>(listFileDTOs);
        List<Map<String, String>> fileList = new ArrayList<>();
        for (FileDTO fileDTO : listFileDTOs) {
            Map<String, String> fileMap = new HashMap<>(0);
            fileMap.put("url", String.format("%s/route/file/downloadfile/%s/%s.%s", request.getContextPath(), downloadFile, fileDTO.getFileId(), fileDTO.getFileType()));
            fileList.add(fileMap);
        }
        JSONObject uEditorListResult = new JSONObject();
        uEditorListResult.put("state", "SUCCESS");
        uEditorListResult.put("start", start);
        uEditorListResult.put("total", pageInfo.getTotal());
        uEditorListResult.put("list", fileList);
        return uEditorListResult;
    }

    /**
     * uEditor上传文件
     *
     * @param file
     * @param request
     * @param action
     * @param params
     * @return
     * @throws SystemException
     */
    private JSONObject uEditorUpload(MultipartFile file, HttpServletRequest request, String action, Map<String, Object> params) throws SystemException {
        LOG.debug("UEditor上传文件");
        FileDTO fileDTO = null;
        String downloadFile = "true";
        if (UEDITOR_UPLOAD_IMAGE.equals(action)) {
            fileDTO = uploadSingleForFileDTO(null, file, UploadTypeEnum.IMAGE, params);
        } else if (UEDITOR_UPLOAD_VIDEO.equals(action)) {
            fileDTO = uploadSingleForFileDTO(null, file, UploadTypeEnum.VIDEO, params);
            downloadFile = "true";
        } else if (UEDITOR_UPLOAD_FILE.equals(action)) {
            fileDTO = uploadSingleForFileDTO(null, file, UploadTypeEnum.FILE, params);
            downloadFile = "false";
        }
        if (fileDTO == null) {
            throw new SystemException("文件上传失败");
        }
        String fileUrl = String.format("route/file/downloadfile/%s/%s.%s", downloadFile, fileDTO.getFileId(), fileDTO.getFileType());
        JSONObject fileUploadResult = new JSONObject();
        fileUploadResult.put("state", "SUCCESS");
        fileUploadResult.put("url", fileUrl);
        fileUploadResult.put("title", fileDTO.getFileName());
        fileUploadResult.put("original", fileDTO.getFileName());
        return fileUploadResult;
    }

    /**
     * 获取ueditor配置文件
     *
     * @return
     * @throws IOException
     */
    private JSONObject uEditorConfig() throws IOException {
        LOG.debug("加载UEditor配置文件");
        String uEditorConfig = ResourceUtil.inputStreamToText(ResourceUtil.getResourceInputStream("classpath:ueditorconfig.json"));
        return JSONObject.parseObject(uEditorConfig);
    }


    public String[] getImageTypes() {
        return imageTypes == null ? fileProperties.getImageTypes().split(",") : imageTypes;
    }

    public void setImageTypes(String[] imageTypes) {
        this.imageTypes = imageTypes;
    }

    public String[] getVideoTypes() {
        return videoTypes == null ? fileProperties.getVideoTypes().split(",") : videoTypes;
    }

    public void setVideoTypes(String[] videoTypes) {
        this.videoTypes = videoTypes;
    }

    public String[] getAudioTypes() {
        return audioTypes == null ? fileProperties.getAudioTypes().split(",") : audioTypes;
    }

    public void setAudioTypes(String[] audioTypes) {
        this.audioTypes = audioTypes;
    }

    public String[] getFileTypes() {
        return fileTypes == null ? fileProperties.getFileTypes().split(",") : fileTypes;
    }

    public void setFileTypes(String[] fileTypes) {
        this.fileTypes = fileTypes;
    }

    /**
     * 校验类型
     *
     * @param types
     * @param fileType
     * @return
     */
    private boolean isTypeCorrect(String[] types, String fileType) {
        for (String type : types) {
            if (StringUtils.equalsIgnoreCase(fileType, type)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 获取文件类型
     *
     * @param fileName
     * @return
     */
    private String getFileType(String fileName) {
        String[] names = fileName.split("\\.");
        if (names != null) {
            return names[names.length - 1].toLowerCase();
        }
        return "";
    }

    /**
     * 获取上传文件路径
     *
     * @param baseUploadPath
     * @param uploadTypeEnum
     * @param fileType
     * @return
     * @throws FileException
     */
    private String getUploadPath(String baseUploadPath, UploadTypeEnum uploadTypeEnum, String fileType) throws FileException {
        StringBuilder filePath = new StringBuilder();
        if (!baseUploadPath.endsWith(File.separator)) {
            filePath.append(baseUploadPath).append(File.separator);
        } else {
            filePath.append(baseUploadPath);
        }
        boolean hasFileType = !StringUtils.isBlank(fileType);
        if (uploadTypeEnum.getValue() == UploadTypeEnum.IMAGE.getValue()) {
            if (hasFileType && !isTypeCorrect(getImageTypes(), fileType)) {
                throw new FileException("图片格式不支持上传");
            }
            filePath.append("images");
        } else if (uploadTypeEnum.getValue() == UploadTypeEnum.VIDEO.getValue()) {
            if (hasFileType && !isTypeCorrect(getVideoTypes(), fileType)) {
                throw new FileException("视频格式不支持上传");
            }
            filePath.append("videos");
        } else if (uploadTypeEnum.getValue() == UploadTypeEnum.AUDIO.getValue()) {
            if (hasFileType && !isTypeCorrect(getAudioTypes(), fileType)) {
                throw new FileException("音频格式不支持上传");
            }
            filePath.append("audios");
        } else if (uploadTypeEnum.getValue() == UploadTypeEnum.ERROR_EXCEL.getValue()) {
            filePath.append("errorexcels");
        } else {
            if (hasFileType && !isTypeCorrect(getFileTypes(), fileType)) {
                throw new FileException("文件格式不支持上传");
            }
            filePath.append("files");
        }
        filePath.append(File.separator).append(DateUtil.getDays());
        return filePath.toString();
    }

    /**
     * 获取上传文件名称
     *
     * @param fileType
     * @return
     */
    private String getUploadFileName(String fileType) {
        String uploadFileName = UUIDUtil.get32UUID();
        if (!StringUtils.isEmpty(fileType)) {
            uploadFileName += "." + fileType;
        }
        return uploadFileName;
    }

    /**
     * 保存文件到本地
     *
     * @param uploadFile
     * @param filePath
     * @param uploadFileName
     * @return
     */
    private String uploadFile(MultipartFile uploadFile, String filePath, String uploadFileName) throws FileException {
        String fileMd5;
        File uploadFolder = new File(filePath);
        if (!uploadFolder.exists()) {
            uploadFolder.mkdirs();
        }
        InputStream uploadFileInputStream = null;
        FileOutputStream uploadFileOutputStream = null;
        try {
            uploadFileInputStream = uploadFile.getInputStream();
            uploadFileOutputStream = new FileOutputStream(new File(uploadFolder + "/" + uploadFileName));
            MessageDigest messageDigest = MessageDigest.getInstance("MD5");
            for (byte[] buf = new byte[INPUT_STREAM_SIZE]; uploadFileInputStream.read(buf) > -1; ) {
                uploadFileOutputStream.write(buf, 0, buf.length);
                messageDigest.update(buf, 0, buf.length);
            }
            uploadFileOutputStream.flush();
            // 计算文件的MD5
            byte[] data = messageDigest.digest();
            StringBuilder fileMd5SB = new StringBuilder(data.length * 2);
            for (byte b : data) {
                fileMd5SB.append(HEX_CODE[(b >> 4) & 0xF]);
                fileMd5SB.append(HEX_CODE[(b & 0xF)]);
            }
            fileMd5 = fileMd5SB.toString();
        } catch (Exception e) {
            LOG.error(e.getMessage(), e);
            throw new FileException("文件上传失败");
        } finally {
            try {
                if (null != uploadFileOutputStream) {
                    uploadFileOutputStream.close();
                }
                if (null != uploadFileInputStream) {
                    uploadFileInputStream.close();
                }
            } catch (Exception e1) {
                LOG.error(e1.getMessage());
                throw new FileException("文件上传失败");
            }
        }
        return fileMd5;
    }

    /**
     * 获取ContentType
     *
     * @param fileType
     * @return
     */
    private String getContentType(String fileType) {
        String contentType;
        switch (fileType) {
            case "png":
                contentType = "image/png";
                break;
            case "blob":
            case "jpg":
            case "jpeg":
                contentType = "image/jpeg";
                break;
            case "gif":
                contentType = "image/gif";
                break;
            case "mp4":
                contentType = "video/mp4";
                break;
            case "rmvb":
                contentType = "application/vnd.rn-realmedia-vbr";
                break;
            case "mp3":
                contentType = "audio/mp3";
                break;
            case "wmv":
                contentType = "video/x-ms-wmv";
                break;
            case "wav":
                contentType = "audio/wav";
                break;
            case "doc":
                contentType = "application/msword";
                break;
            case "docx":
                contentType = "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
                break;
            case "xls":
                contentType = "application/vnd.ms-excel";
                break;
            case "xlsx":
                contentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
                break;
            case "ppt":
                contentType = "application/vnd.ms-powerpoint";
                break;
            case "pptx":
                contentType = "application/vnd.openxmlformats-officedocument.presentationml.presentation";
                break;
            case "txt":
                contentType = "text/plain";
                break;
            case "apk":
                contentType = "application/vnd.android.package-archive";
                break;
            case "pdf":
                contentType = "application/pdf";
                break;
            default:
                contentType = "application/octet-stream";
        }
        return contentType;
    }

    /**
     * 是否是图片文件, GIF图片不压缩
     *
     * @param fileType
     * @return
     */
    private boolean isImageFile(String fileType) {
        if (StringUtils.equalsIgnoreCase("gif", fileType)) {
            return false;
        }
        String imageTypes = fileProperties.getImageTypes();
        for (String imageType : imageTypes.split(",")) {
            if (StringUtils.equalsIgnoreCase(fileType, imageType)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 压缩图片
     *
     * @param fileFullPath
     */
    private void compressImage(String fileFullPath) {
        if (fileFullPath.endsWith(".blob")) {
            return;
        }
        try {
            Thumbnails.of(fileFullPath).scale(1.0f).outputQuality(fileProperties.getImageOutputQuality()).toFile(fileFullPath);
        } catch (IOException e) {
            LOG.error(e.getMessage(), e);
        }
    }

}
