<!DOCTYPE html>
<html>
<head>
<title>多文件上傳預覽演示</title>
<link rel="stylesheet" href="/js/uploadify-v3.1/uploadify.css" />
<script src="/js/jquery-3.6.0.min.js"></script>
<script src="/js/uploadify-v3.1/jquery.uploadify-3.1.min.js"></script>
<style>
#uploader { position: relative; margin: 20px; }
#uploader_queue { position: absolute; left: 130px; top: 0; width: 600px; }
#uploader_view img { max-height: 80px; margin: 5px; border: 1px solid #ddd; }
</style>
</head>
<body>
<div id="uploader">
<input type="file" name="file_upload" id="file_upload" />
<a href="javascript:$('#file_upload').uploadify('upload','*')">開始上傳</a>
<a href="javascript:$('#file_upload').uploadify('cancel','*')">取消全部</a>
<div id="uploader_queue"></div>
<div id="uploader_msg"></div>
<div id="uploader_view"></div>
</div>
<script>
$(function() {
$('#file_upload').uploadify({
'auto' : false, // 手動觸發上傳
'method' : 'post',
'formData' : { 'folder': 'file' },
'swf' : '/js/uploadify-v3.1/uploadify.swf',
'uploader' : '/uploadAttach.do',
'buttonText' : '選擇文件',
'fileTypeDesc' : '支持圖片/文檔/視頻/壓縮包',
'fileTypeExts' : '*.jpg;*.jpeg;*.png;*.gif;*.bmp;*.pdf;*.doc;*.docx;*.xls;*.xlsx;*.zip;*.rar;*.mp4;*.avi;*.mov',
'fileSizeLimit' : '50MB',
'uploadLimit' : 20,
'queueSizeLimit' : 20,
'queueID' : 'uploader_queue',
'progressData' : 'speed',
'removeCompleted' : false,
'removeTimeout' : 3,
'onUploadSuccess' : function(file, data, response) {
// 後端返回文件訪問路徑,前端追加預覽
var url = '/upload/' + encodeURI(data);
var isImg = /\.(jpg|jpeg|png|gif|bmp)$/i.test(file.name);
var html = isImg
? '<img src="' + url + '" alt="' + file.name + '" title="' + file.name + '" />'
: '<div class="file-tag">📄 ' + file.name + '</div>';
$('#uploader_view').append(html);
},
'onQueueComplete' : function(queueData) {
$('#uploader_msg').html('成功上傳 ' + queueData.uploadsSuccessful + ' 個文件');
},
'onUploadError' : function(file, errorCode, errorMsg, errorString) {
console.error('上傳失敗:', file.name, errorCode, errorMsg);
}
});
});
</script>
</body>
</html>
@Controller
@RequestMapping("/upload")
public class UploadController {
private static final Logger log = LoggerFactory.getLogger(UploadController.class);
private static final String[] ALLOWED_EXTS = {".jpg",".jpeg",".png",".gif",".bmp",".pdf",".doc",".docx",".xls",".xlsx",".zip",".rar",".mp4",".avi",".mov"};
@PostMapping("/uploadAttach")
@ResponseBody
public void uploadAttach(HttpServletRequest request, HttpServletResponse response) throws Exception {
MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request;
Map<String, MultipartFile> fileMap = multipartRequest.getFileMap();
String basePath = request.getSession().getServletContext().getRealPath("/") + "upload/";
File baseDir = new File(basePath);
if (!baseDir.exists()) baseDir.mkdirs();
StringBuilder resultPaths = new StringBuilder();
long timestamp = System.currentTimeMillis();
for (Map.Entry<String, MultipartFile> entry : fileMap.entrySet()) {
MultipartFile file = entry.getValue();
if (file.isEmpty()) continue;
// 1. 文件類型校驗
String originalName = file.getOriginalFilename();
String ext = originalName.substring(originalName.lastIndexOf(".")).toLowerCase();
if (!Arrays.asList(ALLOWED_EXTS).contains(ext)) {
log.warn("非法文件類型: {}", originalName);
continue; // 或拋異常
}
// 2. 生成唯一文件名:時間戳_UUID_原始名
String saveName = timestamp + "_" + UUID.randomUUID().toString().replace("-", "") + ext;
File dest = new File(baseDir + saveName);
// 3. 保存文件
try (FileOutputStream fos = new FileOutputStream(dest)) {
fos.write(file.getBytes());
fos.flush();
}
resultPaths.append(saveName).append(",");
}
// 4. 返回文件名列表,前端拼接預覽 URL
String resp = resultPaths.length() > 0 ? resultPaths.substring(0, resultPaths.length() - 1) : "";
response.setContentType("text/plain;charset=UTF-8");
response.getWriter().write(resp);
}
}
| 配置項 | 作用 | 建議值 |
|---|---|---|
| auto: false | 手動觸發上傳,支持用戶確認後統一上傳 | false |
| fileSizeLimit | 單文件大小限制 | 50MB |
| uploadLimit | 單次隊列最大文件數 | 20 |
| queueSizeLimit | 隊列最大文件數 | 20 |
| removeCompleted | 上傳成功後是否自動移除隊列項 | false (保留預覽) |
| progressData: 'speed' | 顯示上傳速度而非百分比 | 'speed' |
accept 只能攔截善意用戶,後端必須校驗 Content-Type + Magic Number (文件頭字節)。location /upload { internal; } 防止腳本執行。UUID + timestamp 避免衝突,高併發下可加分佈式鎖或雪花算法。本文提供了 Uploadify + Spring MVC 多文件上傳預覽的完整落地代碼。但受限於 Flash 技術棧,新項目請直接選用 WebUploader 或 Dropzone.js,架構邏輯完全一致,僅前端組件替換即可。