在傳統 Web 開發中,文件上傳往往伴隨著頁面刷新,用戶體驗較差。本文詳細介紹如何在 SpringMVC + MyBatis 架構下,利用 ajaxFileUpload.js 插件實現表單數據與文件的「異步同時上傳」,並解決高版本 jQuery 兼容性、表單序列化為 JSON 等常見坑點。
ajaxFileUpload.js(底層通過動態創建 <iframe> 模擬異步提交)formToJson.js(將表單數據序列化為 JSON 對象)MultipartFile 及表單綁定對象enctype="multipart/form-data"、文件域 id 與 JS 配置一致、後端事務管理<form:form id="addOrUpdateModalForm" method="post"
enctype="multipart/form-data" modelAttribute="userInfo">
<!-- 隱藏域綁定 ID 等 -->
<form:hidden path="userId" />
<form:hidden path="imageUrl" />
<!-- 文件上傳域:id 必須與 JS 中 fileElementId 一致 -->
<input type="file" id="userImage" name="userImage"
accept="image/*" onchange="upfileName.value=this.value" />
<input type="text" id="upfileName" readonly />
<input type="button" value="選擇圖片" onclick="userImage.click()" />
<!-- 其他業務字段:姓名、工號、部門、崗位等 -->
<form:input path="name" />
<form:select path="departmentId"><form:options /></form:select>
<!-- ... 省略其餘字段 -->
</form:form>
function saveOrUpdateForm() {
// 1. 圖片格式校驗 & 表單驗證
if (!checkImageFileType() || !$('#addOrUpdateModalForm').validationAjax()) {
return;
}
var postUrl = appCtx + "userInfo/insertOrUpdate.do";
// 2. 關鍵:將表單序列化為 JSON 對象
var params = $('#addOrUpdateModalForm').serializeJson();
$('#loading').modal('show');
// 3. 調用 ajaxFileUpload 核心方法
$.ajaxFileUpload({
url: postUrl,
secureuri: false,
fileElementId: 'userImage', // 對應 input file 的 id
dataType: 'json',
data: params, // 附帶的表單 JSON 數據
success: function(data, status) {
// 成功回調:關閉遮罩、彈窗、刷新列表
$('#loading').modal('hide');
doSuccess();
successModalStatus(...);
succesListFormReflush();
},
error: function(data, status, e) {
console.error("上傳失敗:", e);
$('#loading').modal('hide');
}
});
return false; // 阻止默認表單提交
}
handleError 問題ajaxFileUpload.js 內部依賴 jQuery.handleError,該方法在 jQuery 1.9+ 被移除。解決方案:在項目加載的 JS 中手動擴展:
// 放在 jquery.min.js 之後,ajaxFileUpload.js 之前
jQuery.extend({
handleError: function(s, xhr, status, e) {
if (s.error) s.error.call(s.context || s, xhr, status, e);
if (s.global) $(s.context || document).trigger("ajaxError", [xhr, s, e]);
}
});
@Controller
@RequestMapping("/userInfo")
public class UserInfoController {
@Autowired private UserInfoService userInfoService;
@Autowired private UserPositionService userPositionService;
@RequestMapping("insertOrUpdate")
@ResponseBody
public ActionResult insertOrUpdate(
UserInfo userInfo,
HttpSession session,
@RequestParam(value="userImage", required=false) MultipartFile userImage) {
ActionResult result = new ActionResult();
try {
// 1. 判斷新增/修改
boolean isAdd = (userInfo.getUserId() == null);
// 2. 文件上傳處理(新增與修改邏輯略有不同)
String savePath = session.getServletContext().getRealPath("/resources/images/userImage");
String fileName = handleFileUpload(userImage, savePath, isAdd, userInfo);
if (fileName != null) userInfo.setImageUrl(fileName);
// 3. 業務邏輯:保存用戶 & 維護用戶-崗位關係表
if (isAdd) {
userInfo.setUserUuid(UUIDUtil.getUUID());
userInfo.setPassword("123456"); // 默認密碼
userInfoService.insertSelective(userInfo);
// 插入崗位關係
insertOrUpdatePositionRel(userInfo.getUserId(), userInfo.getPositionId());
} else {
// 修改邏輯:刪除舊圖片、更新用戶、同步崗位關係
deleteOldImageIfNeeded(userInfo, savePath);
userInfoService.updateByPrimaryKeySelective(userInfo);
insertOrUpdatePositionRel(userInfo.getUserId(), userInfo.getPositionId());
}
// 4. 日誌記錄 (SLF4J + MDC)
logOperation(session, isAdd, userInfo);
result.setResult(OPERATOR_RESULT_SUCCESS_KEY);
result.setMessage(isAdd ? OPERATOR_RESULT_SUCCESS_ADD_VALUE : OPERATOR_RESULT_SUCCESS_UPDATE_VALUE);
} catch (Exception e) {
e.printStackTrace();
result.setResult(OPERATOR_RESULT_ERROR_KEY);
result.setMessage(OPERATOR_RESULT_ERROR_VALUE);
}
return result;
}
// --- 私有輔助方法:文件處理、崗位關係維護、日誌記錄 ---
// 代碼省略,核心邏輯見原文 controller 完整代碼
}
<!-- spring-mvc.xml -->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="maxUploadSize" value="10485760" /> <!-- 10MB -->
<property name="defaultEncoding" value="UTF-8" />
</bean>
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.3</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
</dependency>
ajaxFileUpload.js 屬於「偽異步」(iframe 方案),現代項目建議遷移至 FormData + XMLHttpRequest/fetch 原生方案,或使用 jQuery.ajax 配合 processData: false, contentType: false。