Spring Boot表单校验修正后提交遇404,如何留存原页面完成提交?
问题原因分析
- 404错误直接原因:表单验证失败时,你直接返回
expense_transaction视图,但未将walletId(路径变量中的id)存入Model。表单的th:action依赖${id}生成提交URL,该变量缺失会导致生成的URL不完整,再次提交时触发404。 - URL跳转问题:验证失败后直接返回视图,浏览器URL停留在
saveExpense/4,不符合PRG(Post/Redirect/Get)最佳实践,也导致用户修正后提交的URL偏离原页面对应地址。
解决方案
方案一:快速修复404(保留现有逻辑)
在saveExpense方法的错误分支中,补充userId和walletId到Model,确保表单能生成正确的提交URL:
@PostMapping("/saveExpense/{walletId}") public String saveExpense(@PathVariable(value = "walletId") long walletId, @Valid Transaction transaction, BindingResult result, Model model) { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); UserDetailsImpl user = (UserDetailsImpl) authentication.getPrincipal(); long userId = user.getId(); Wallet wallet = walletService.getWalletById(walletId); boolean thereAreErrors = result.hasErrors(); if (thereAreErrors) { transaction.setId(walletId); model.addAttribute("userId", userId); // 补充视图依赖的userId model.addAttribute("id", walletId); // 将walletId以id名存入model,匹配表单中的${id} model.addAttribute("expenseCategories", ExpenseCategories.values()); return "expense_transaction"; } transaction.setWallet(wallet); transactionService.saveExpense(transaction, walletId, userId); return "redirect:/api/wallet/userWallet/balance/" + userId; }
此方案可解决404,但浏览器URL仍会停留在saveExpense/4。
方案二:使用PRG模式,回到原页面URL
通过Post/Redirect/Get模式,让验证失败时重定向回原页面URL,同时保留错误提示和用户输入内容:
步骤1:修改saveExpense方法的错误分支
@PostMapping("/saveExpense/{walletId}") public String saveExpense(@PathVariable(value = "walletId") long walletId, @Valid Transaction transaction, BindingResult result, RedirectAttributes redirectAttributes) { // 用RedirectAttributes传递Flash属性 Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); UserDetailsImpl user = (UserDetailsImpl) authentication.getPrincipal(); long userId = user.getId(); Wallet wallet = walletService.getWalletById(walletId); boolean thereAreErrors = result.hasErrors(); if (thereAreErrors) { // 存入错误信息和表单数据,重定向后仍可获取 redirectAttributes.addFlashAttribute("org.springframework.validation.BindingResult.transaction", result); redirectAttributes.addFlashAttribute("transaction", transaction); // 重定向到原GET页面 return "redirect:/api/transaction/expenseTransaction/" + walletId; } transaction.setWallet(wallet); transactionService.saveExpense(transaction, walletId, userId); return "redirect:/api/wallet/userWallet/balance/" + userId; }
步骤2:原GET方法无需修改
原expenseTransaction方法会自动绑定Flash属性中的transaction和错误信息,保持现有代码即可:
@GetMapping("/expenseTransaction/{id}") public String expenseTransaction(@PathVariable(value = "id") long id, Transaction transaction, Model model) { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); UserDetailsImpl user = (UserDetailsImpl) authentication.getPrincipal(); long userId = user.getId(); model.addAttribute("userId", userId); model.addAttribute("transaction", transaction); model.addAttribute("expenseCategories", ExpenseCategories.values()); return "expense_transaction"; }
效果
- 验证失败时,浏览器URL自动回到
/api/transaction/expenseTransaction/4,保持原页面地址 - 用户输入内容和错误提示会被保留,修正后提交可正常完成流程
额外表单优化
日期字段的错误提示语法需统一,将th:errors="date"改为th:errors="*{date}":
<span th:if="${#fields.hasErrors('date')}" th:errors="*{date}" class="text-danger"></span>
内容的提问来源于stack exchange,提问作者asu
相关产品推荐
相关产品推荐

