知启蒙大文件上传组件,该组件实现大文件分片上传,包含服务端接收碎片上传和管理功能,以及前端UI的进度条、上传速度等界面。同时还支持按文件内容MD5码、相同文件名称比对秒传等功能,是业务系统应用不可缺少的组件之一。

森中灵 最后提交于4月前 修改JS为8.0.6
Zuls.java7KB
/*
 * 版权所有 (C) 2015 知启蒙(ZHIQIM) 保留所有权利。[遇见知启蒙,邂逅框架梦,本文采用木兰宽松许可证第2版]
 * 
 * https://zhiqim.org/project/zhiqim_components/zhiqim_upload_large.htm
 *
 * Zhiqim UploadLarge is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *          http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 */
package org.zhiqim.uploadlarge;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;

import org.zhiqim.kernel.annotation.AnAlias;
import org.zhiqim.kernel.enumerated.LetterCase;
import org.zhiqim.kernel.model.codes.MD5;
import org.zhiqim.kernel.model.maps.HashMapSS;
import org.zhiqim.kernel.model.results.RE;
import org.zhiqim.kernel.util.Asserts;
import org.zhiqim.kernel.util.Bytes;
import org.zhiqim.kernel.util.Files;
import org.zhiqim.kernel.util.Hexs;
import org.zhiqim.kernel.util.Ids;
import org.zhiqim.kernel.util.Sqls;
import org.zhiqim.kernel.util.Strings;
import org.zhiqim.kernel.util.Validates;
import org.zhiqim.manager.dao.ZmrParamDao;
import org.zhiqim.orm.ORM;
import org.zhiqim.uploadlarge.dbo.UpllFile;

/**
 * 大文件相关方法
 *
 * @version v1.0.0 @author zhuzhiyuan 2019-1-10 新建与整理
 */
@AnAlias("Zuls")
public class Zuls implements ZulConstants
{
    /**
     * 生成48位的文件MD5码,由fileMd5+fileLength组成
     * 
     * @param fileMd5       文件MD5码
     * @param fileLength    文件长度
     * @return
     */
    public static String buildFileMd5(String fileMd5, long fileLength)
    {
        return fileMd5.toLowerCase() + Hexs.toHexString(Bytes.BU.toBytes(fileLength), LetterCase.LOWER);
    }
    
    /**
     * 获取文件上传根目录
     * 
     * @return              文件上传根目录
     */
    public static String getUploadRootDir()
    {
        String rootDir = ZmrParamDao.getString(ZUL_NAME, ZUL_ROOT_DIR);
        if (Validates.isEmptyBlank(rootDir) || !Files.mkDirectory(rootDir))
        {
            throw Asserts.exception("大文件上传根目录[%s],或配置的不是一个目录", ZUL_ROOT_DIR);
        }
        
        try
        {
            String uploadRootDir = Files.toLinuxPath(new File(rootDir).getCanonicalPath());
            return Strings.removeEndsWith(uploadRootDir, "/");
        }
        catch (IOException e)
        {
            throw Asserts.exception("大文件上传根目录[%s],或配置的不是一个目录", ZUL_ROOT_DIR);
        }
    }
    
    /********************************************************************************************/
    //通过文件编号或对象,找到文件绝对路径
    /********************************************************************************************/

    /**
     * 获取文件绝对路径
     * 
     * @param context       文件存储对应的环境(这个和配置大文件上传根路径有关)
     * @param file          文件对象
     * @return              文件绝对路径
     */
    public static String getFilePath(UpllFile file)
    {
        String fileRelativePath = Strings.addStartsWith(file.getFileRelativePath(), "/");
        return getUploadRootDir() + fileRelativePath;
    }
    
    /********************************************************************************************/
    //外部保存文件,指定文件内容,文件路径和文件类型
    /********************************************************************************************/

    /**
     * 提供给外部保存文件,文件大小不要太大
     * 
     * @param request           HTTP请求
     * @param ormId             
     * @param data              文件数据
     * @param fileCanonicalPath 保存的文件绝对路径,要求在大文件上传配置的根目录之下
     * @param contentType       文件内容类型
     * @throws Exception        异常
     */
    public static UpllFile saveFile(byte[] data, String fileCanonicalPath, String contentType) throws Exception
    {
        String uploadRootDir = getUploadRootDir();
        if (!Strings.startsWith(fileCanonicalPath, uploadRootDir))
        {
            throw Asserts.exception("保存的路径必须在根目录之下,否则不允许保存");
        }
        
        String fileId = Ids.uuid();
        String fileExt = Files.getFileExt(fileCanonicalPath);
        String fileName = Files.getFileName(fileCanonicalPath);
        long fileLength = data.length;
        String fileMd5 = Zuls.buildFileMd5(MD5.encode(data), fileLength);
        String fileRelativePath = Strings.removeStartsWith(fileCanonicalPath, uploadRootDir);
        
        //写入文件,写之前目录make一下
        Files.mkDirectory(Files.getFileDir(fileCanonicalPath));
        Files.write(fileCanonicalPath, data);
        
        //保存到数据库
        UpllFile file = new UpllFile();
        file.setFileId(fileId);
        file.setFileName(fileName);
        file.setFileExt(fileExt);
        file.setFileLength(fileLength);
        file.setFileMd5(fileMd5);
        file.setFileRelativePath(fileRelativePath);
        file.setFileUrl(_PATH_UPLOAD_LARGE_PREFIX_ + fileId + Files.fixFileExt(fileExt));
        file.setFileTime(Sqls.nowTimestamp());
        file.setFileType(contentType);

        ORM.table().replace(file, new HashMapSS());
        
        return file;
    }
    
    /**
     * 删除文件,并忽略异常
     * 
     * @param fileId            文件编号
     */
    public static RE<Integer> deleteFile(String fileId)
    {
        RE<UpllFile> result = ZulUploader.item(fileId);
        if (result.failure())
            return RE.error(result.exception());
        
        UpllFile file = result.value();
        if (file == null)
            return RE.value(0);
        
        //本地文件删除
        Files.deleteFile(getFilePath(file));
        return ORM.tabler().delete(UpllFile.class, fileId);
    }
    
    /**
     * 读取文件,本地文件指定文件大小
     * 
     * @param fileId        文件编号
     * @param max           最大文件字节数
     * @return              字节内容结果集
     */
    public static RE<byte[]> readFile(String fileId, int max)
    {
        RE<UpllFile> result = ZulUploader.item(fileId);
        if (result.failure())
        {//数据库异常
            return RE.error(result.exception());
        }
        
        UpllFile file = result.value();
        if (file == null)
        {//没有文件记录
            return RE.error(new FileNotFoundException("文件编号不在数据表中"));
        }
        
        if (file.getFileLength() > max)
        {//文件过大不支持直接读取
            return RE.error(new Exception("文件大小超出指定最大字节数"));
        }
        
        try
        {
            byte[] data = Files.read(new File(Zuls.getFilePath(file)), max);
            if (data == null)
                return RE.error(new Exception("文件不存在"));
            
            return RE.value(data);
        }
        catch(Exception e)
        {
            return RE.error(e);
        }
    }
}