You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot表单校验修正后提交遇404,如何留存原页面完成提交?

问题原因分析
  1. 404错误直接原因:表单验证失败时,你直接返回expense_transaction视图,但未将walletId(路径变量中的id)存入Model。表单的th:action依赖${id}生成提交URL,该变量缺失会导致生成的URL不完整,再次提交时触发404。
  2. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.05 07:05:14