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