我們平時經常做的是上傳檔案,上傳資料夾與上傳檔案類似,但也有一些不同之處,這次做了上傳資料夾就記錄下以備後用。
首先我們需要了解的是上傳檔案三要素:
1.表單提交方式:post (get方式提交有大小限制,post沒有)
2.表單的enctype屬性:必須設定為multipart/form-data.
3.表單必須有檔案上傳項:file,且檔案項需要給定name值
上傳資料夾需要增加一個屬性webkitdirectory,像這樣:
不過webkitdirectory屬性有個問題,只能支援高版本的chrome,不能支援低版本的IE,如ie6,ie7,ie8,不能做到全瀏覽器適配,執行環境比較單一。
js中可以判斷資料夾中檔案數量及資料夾大小是否符合要求,不符合要求不能向後臺提交:
前臺HTML模板
this.GetHtmlFiles = function()
{
var acx = "";
acx += '<div class="file-item" id="tmpFile" name="fileItem">
<div class="img-box"><img name="file" src="js/file.png"/></div>
<div class="area-l">
<div class="file-head">
<div name="fileName" class="name">HttpUploader程式開發.pdf</div>
<div name="percent" class="percent">(35%)</div>
<div name="fileSize" class="size" child="1">1000.23MB</div>
</div>
<div class="process-border"><div name="process" class="process"></div></div>
<div name="msg" class="msg top-space">15.3MB 20KB/S 10:02:00</div>
</div>
<div class="area-r">
<span class="btn-box" name="cancel" title="取消"><img name="stop" src="js/stop.png"/><div>取消</div></span>
<span class="btn-box hide" name="post" title="繼續"><img name="post" src="js/post.png"/><div>繼續</div></span>
<span class="btn-box hide" name="stop" title="停止"><img name="stop" src="js/stop.png"/><div>停止</div></span>
<span class="btn-box hide" name="del" title="刪除"><img name="del" src="js/del.png"/><div>刪除</div></span>
</div>';
acx += '</div>';
//資料夾模板
acx += '<div class="file-item" name="folderItem">
<div class="img-box"><img name="folder" src="js/folder.png"/></div>
<div class="area-l">
<div class="file-head">
<div name="fileName" class="name">HttpUploader程式開發.pdf</div>
<div name="percent" class="percent">(35%)</div>
<div name="fileSize" class="size" child="1">1000.23MB</div>
</div>
<div class="process-border top-space"><div name="process" class="process"></div></div>
<div name="msg" class="msg top-space">15.3MB 20KB/S 10:02:00</div>
</div>
<div class="area-r">
<span class="btn-box" name="cancel" title="取消"><img name="stop" src="js/stop.png"/><div>取消</div></span>
<span class="btn-box hide" name="post" title="繼續"><img name="post" src="js/post.png"/><div>繼續</div></span>
<span class="btn-box hide" name="stop" title="停止"><img name="stop" src="js/stop.png"/><div>停止</div></span>
<span class="btn-box hide" name="del" title="刪除"><img name="del" src="js/del.png"/><div>刪除</div></span>
</div>';
acx += '</div>';
//上傳列表
acx += '<div class="files-panel" name="post_panel">
<div name="post_head" class="toolbar">
<span class="btn" name="btnAddFiles">選擇多個檔案</span>
<span class="btn" name="btnAddFolder">選擇資料夾</span>
<span class="btn" name="btnPasteFile">貼上檔案和目錄</span>
<span class="btn" name="btnSetup">安裝控制元件</span>
</div>
<div class="content" name="post_content">
<div name="post_body" class="file-post-view"></div>
</div>
<div class="footer" name="post_footer">
<span class="btn-footer" name="btnClear">清除已完成檔案</span>
</div>
</div>';
return acx;
};
選擇檔案,選擇資料夾,貼上檔案和資料夾的邏輯
this.open_files = function (json)
{
for (var i = 0, l = json.files.length; i < l; ++i)
{
this.addFileLoc(json.files[i]);
}
setTimeout(function () { _this.PostFirst(); },500);
};
this.open_folders = function (json)
{
for (var i = 0, l = json.folders.length; i < l; ++i) {
this.addFolderLoc(json.folders[i]);
}
setTimeout(function () { _this.PostFirst(); }, 500);
};
this.paste_files = function (json)
{
for (var i = 0, l = json.files.length; i < l; ++i)
{
this.addFileLoc(json.files[i]);
}
};
後臺在接收資料夾時不同之處在需要用MultipartHttpServletRequest
boolean isMultipart = ServletFileUpload.isMultipartContent(request);
FileItemFactory factory = new DiskFileItemFactory();
ServletFileUpload upload = new ServletFileUpload(factory);
List files = null;
try
{
files = upload.parseRequest(request);
}
catch (FileUploadException e)
{// 解析檔案資料錯誤
out.println("read file data error:" + e.toString());
return;
}
FileItem rangeFile = null;
// 得到所有上傳的檔案
Iterator fileItr = files.iterator();
// 迴圈處理所有檔案
while (fileItr.hasNext())
{
// 得到當前檔案
rangeFile = (FileItem) fileItr.next();
if(StringUtils.equals( rangeFile.getFieldName(),"pathSvr"))
{
pathSvr = rangeFile.getString();
pathSvr = PathTool.url_decode(pathSvr);
}
}
server端的包和類
檔案塊處頁面,驗證程式碼部分
boolean verify = false;
String msg = “”;
String md5Svr = “”;
long blockSizeSvr = rangeFile.getSize();
if(!StringUtils.isBlank(blockMd5))
{
md5Svr = Md5Tool.fileToMD5(rangeFile.getInputStream());
}
verify = Integer.parseInt(blockSize) == blockSizeSvr;
if(!verify)
{
msg = "block size error sizeSvr:" + blockSizeSvr + "sizeLoc:" + blockSize;
}
if(verify && !StringUtils.isBlank(blockMd5))
{
verify = md5Svr.equals(blockMd5);
if(!verify) msg = "block md5 error";
}
if(verify)
{
//儲存檔案塊資料
FileBlockWriter res = new FileBlockWriter();
//僅第一塊建立
if( Integer.parseInt(blockIndex)==1) res.CreateFile(pathSvr,Long.parseLong(lenLoc));
res.write( Long.parseLong(blockOffset),pathSvr,rangeFile);
up6_biz_event.file_post_block(id,Integer.parseInt(blockIndex));
JSONObject o = new JSONObject();
o.put("msg", "ok");
o.put("md5", md5Svr);
o.put("offset", blockOffset);//基於檔案的塊偏移位置
msg = o.toString();
}
rangeFile.delete();
out.write(msg);
生成檔名稱的邏輯
public String genFile(int uid, String md5,String nameLoc) throws IOException
{
SimpleDateFormat fmtDD = new SimpleDateFormat("dd");
SimpleDateFormat fmtMM = new SimpleDateFormat("MM");
SimpleDateFormat fmtYY = new SimpleDateFormat("yyyy");
Date date = new Date();
String strDD = fmtDD.format(date);
String strMM = fmtMM.format(date);
String strYY = fmtYY.format(date);
String path = this.getRoot() + "/";
path = path.concat(strYY);
path = path.concat("/");
path = path.concat(strMM);
path = path.concat("/");
path = path.concat(strDD);
path = path.concat("/");
path = path.concat(md5);
path = path.concat(".");
path = path.concat(PathTool.getExtention(nameLoc));
File fl = new File(path);
return fl.getCanonicalPath();//
}
以下是service層做的處理:
整體模組劃分如下:
其中資料類實體邏輯處理如下
public class FileInf {
public FileInf(){}
public String id="";
public String pid="";
public String pidRoot="";
/** * 表示當前項是否是一個資料夾項。 */
public boolean fdTask=false;
// /// 是否是資料夾中的子檔案 /// </summary>
public boolean fdChild=false;
/** * 使用者ID。與第三方系統整合使用。 */
public int uid=0;
/** * 檔案在本地電腦中的名稱 */
public String nameLoc="";
/** * 檔案在伺服器中的名稱。 */
public String nameSvr="";
/** * 檔案在本地電腦中的完整路徑。示例:D:\Soft\QQ2012.exe */
public String pathLoc="";
/** * 檔案在伺服器中的完整路徑。示例:F:\\ftp\\uer\\md5.exe */
public String pathSvr="";
/** * 檔案在伺服器中的相對路徑。示例:/www/web/upload/md5.exe */
public String pathRel="";
/** * 檔案MD5 */
public String md5="";
/** * 數字化的檔案長度。以位元組為單位,示例:120125 */
public long lenLoc=0;
/** * 格式化的檔案尺寸。示例:10.03MB */
public String sizeLoc="";
/** * 檔案續傳位置。 */
public long offset=0;
/** * 已上傳大小。以位元組為單位 */
public long lenSvr=0;
/** * 已上傳百分比。示例:10% */
public String perSvr="0%";
public boolean complete=false;
public Date PostedTime = new Date();
public boolean deleted=false;
/** * 是否已經掃描完畢,提供給大型資料夾使用,大型資料夾上傳完畢後開始掃描。 */
public boolean scaned=false;
}
後臺資料庫中的邏輯基本上都用到了上面的實體類
檔案資料表操作類如下
載入所有未完成的檔案列表
public String GetAllUnComplete(int f_uid)
{
StringBuilder sb = new StringBuilder();
sb.append("select ");
sb.append(" f_id");
sb.append(",f_fdTask");
sb.append(",f_nameLoc");
sb.append(",f_pathLoc");
sb.append(",f_md5");
sb.append(",f_lenLoc");
sb.append(",f_sizeLoc");
sb.append(",f_pos");
sb.append(",f_lenSvr");
sb.append(",f_perSvr");
sb.append(",f_complete");
sb.append(",f_pathSvr");//fix(2015-03-16):修復無法續傳檔案的問題。
sb.append(" from up6_files ");//change(2015-03-18):聯合查詢資料夾資料
sb.append(" where f_uid=? and f_deleted=0 and f_fdChild=0 and f_complete=0 and f_scan=0");//fix(2015-03-18):只載入未完成列表
ArrayList<FileInf> files = new ArrayList<FileInf>();
DbHelper db = new DbHelper();
PreparedStatement cmd = db.GetCommand(sb.toString());
try {
cmd.setInt(1, f_uid);
ResultSet r = db.ExecuteDataSet(cmd);
while(r.next())
{
FileInf f = new FileInf();
f.uid = f_uid;
f.id = r.getString(1);
f.fdTask = r.getBoolean(2);
f.nameLoc = r.getString(3);
f.pathLoc = r.getString(4);
f.md5 = r.getString(5);
f.lenLoc = r.getLong(6);
f.sizeLoc = r.getString(7);
f.offset = r.getLong(8);
f.lenSvr = r.getLong(9);
f.perSvr = r.getString(10);
f.complete = r.getBoolean(11);
f.pathSvr = r.getString(12);//fix(2015-03-19):修復無法續傳檔案的問題。
files.add(f);
}
r.close();
cmd.getConnection().close();
cmd.close();
} catch (SQLException e) {
// **TODO** Auto-generated catch block
e.printStackTrace();
}
if(files.size() < 1) return null;
Gson g = new Gson();
return g.toJson( files);//bug:arrFiles為空時,此行程式碼有異常
}
實現後的整體效果如下
資料夾上傳完後的效果
伺服器儲存的資料夾資料,而且層級結構與本地客戶端是一致的。這在OA系統中,或者網盤系統中使用時是非常有用的
後端程式碼邏輯大部分是相同的,目前能夠支援MySQL,Oracle,SQL。在使用前需要配置一下資料庫,可以參考我寫的這篇文章:blog.ncmem.com/wordpress/2019/08/12...
歡迎入群一起討論“374992201”
本作品採用《CC 協議》,轉載必須註明作者和本文連結