Skip to content

fix(payment): 修复支付同步跳转返回 null 触发的重定向异常 - #145

Open
MindOfqc wants to merge 1 commit into
jeequan:devfrom
MindOfqc:fix/pay-return-redirect-view
Open

MindOfqc wants to merge 1 commit into
jeequan:devfrom
MindOfqc:fix/pay-return-redirect-view

Conversation

@MindOfqc

Copy link
Copy Markdown

问题与验收标准

  • 关联 Issue(小修复可直接描述):支付同步跳转完成后出现 NoResourceFoundException(response committed already) #125
  • 问题 / 需求与预期结果:
    • 现象:支付宝 PC 网页支付完成、浏览器同步返回 /api/pay/return/alipay 后,jeepay-payment 稳定产生一条告警:
      WARN [DefaultHandlerExceptionResolver] - Ignoring exception, response committed already: org.springframework.web.servlet.resource.NoResourceFoundException: No static resource api/pay/return/api/pay/return/alipay.
      订单状态与 302 跳转本身都正常,只是每次同步跳转都会多出这条异常告警。
    • 根因:ChannelNoticeController#doReturn 是返回视图名的 @Controller 方法,先调用 response.sendRedirect(...) 再 return null。
      响应已经提交,但 Spring MVC 认为请求未被处理,会按请求路径推导默认视图名并继续做资源解析,最终抛出 NoResourceFoundException;
      又因为响应已提交,异常被 DefaultHandlerExceptionResolver 忽略并降级为 WARN 告警。
      onlyJump(订单号带 ONLYJUMP_ 前缀的直跳分支)是同样的写法:response.sendRedirect(...) 后由外层 return null。
    • 预期:重定向响应发送后结束请求处理,不再触发默认视图与静态资源解析,也不产生上述告警。
  • 本次范围与不涉及的内容:
    • 只改 jeepay-payment 的同步跳转(doReturn / onlyJump)视图返回方式,并补回归测试。
    • 不涉及异步回调 doNotify、渠道 SDK、签名算法、数据库与配置;AlipayBizController、ChannelUserIdController 的重定向方法返回类型为 void,不受该问题影响,本次不动。

改动与兼容性

  • 实现要点:
    • doReturn:把 response.sendRedirect(url); return null; 改为 return "redirect:" + url;,由 Spring MVC 统一处理重定向(UrlBasedViewResolver 的 redirect: 前缀会生成 RedirectView,同样走 response.sendRedirect(response.encodeRedirectURL(url)),对浏览器而言 302 与 Location 行为不变)。
    • onlyJump:由 void 改为返回视图名,直接跳转时返回 "redirect:" + url;订单不存在或未配置 returnUrl 时返回支付结果页视图。
      顺带修正了原代码里 this.toReturnPage(...) 返回值被丢弃的问题——原先订单不存在时仍会继续执行并对空对象取 getMchNo(),最终以空指针进入外层兜底分支。
    • 新增 jeepay-payment 的 spring-boot-starter-test(test scope)依赖,用于运行 JUnit 5 / Mockito 回归测试。
  • 配套前端 / Skills PR 或版本(无则写不涉及):不涉及,前端与收银台页面均未改动。
  • API、权限、签名、数据库 / 配置影响与升级、回滚说明:
    • 无接口路径、参数、权限、签名、数据库结构与配置变更;仅同步跳转的响应由「控制器自己写响应」改为「Spring MVC 写重定向响应」,状态码与 Location 保持一致(302)。
    • 升级无额外步骤,回滚只需还原 ChannelNoticeController 的改动。

验证

  • 命令 / 人工步骤与结果:
    • mvn -B -pl jeepay-payment -am install -DskipTests → BUILD SUCCESS
    • mvn -B -pl jeepay-payment -am test → BUILD SUCCESS
      • jeepay-core:Tests run: 10, Failures: 0, Errors: 0, Skipped: 0
      • jeepay-components-mq:Tests run: 4, Failures: 0, Errors: 0, Skipped: 0
      • jeepay-payment:Tests run: 3, Failures: 0, Errors: 0, Skipped: 0
    • 本地环境:JDK 21 + Maven 3.9.16(项目 java.version=17,编译参数 release 17;仓库 CI 使用 JDK 17)。
  • 新增回归测试(修复前失败、修复后通过;无法自动化请说明):
    • 新增 jeepay-payment/src/test/java/com/jeequan/jeepay/pay/ctrl/payorder/ChannelNoticeControllerTest.java,用 Mockito 打桩渠道通知服务、订单服务与商户配置,覆盖:携带 returnUrl 的同步跳转、ONLYJUMP_ 直跳订单号、未携带 returnUrl 三种场景(纯单元测试,不需要数据库、Redis 或真实支付渠道)。
    • 修复前(mvn -B -pl jeepay-payment test -Dtest=ChannelNoticeControllerTest):
      [ERROR] Tests run: 3, Failures: 2, Errors: 0, Skipped: 0 <<< FAILURE! -- in com.jeequan.jeepay.pay.ctrl.payorder.ChannelNoticeControllerTest
      [ERROR]   ChannelNoticeControllerTest.doReturnWithOnlyJumpOrderIdShouldReturnRedirectView:134 expected: <redirect:https://mch.example.com/pay/return?sign=abc> but was: <null>
      [ERROR]   ChannelNoticeControllerTest.doReturnWithReturnUrlShouldReturnRedirectView:114 expected: <redirect:https://mch.example.com/pay/return?sign=abc> but was: <null>
      [INFO] BUILD FAILURE
      
    • 修复后(同一命令):
      [INFO] Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 1.726 s -- in com.jeequan.jeepay.pay.ctrl.payorder.ChannelNoticeControllerTest
      [INFO] BUILD SUCCESS
      
      未携带 returnUrl 的用例修复前后均为 cashier/returnPage,用于保证原有行为不变。
  • 未验证项、已知风险和原因(不要把离线检查写成真实支付验收):
    • 未做真实支付渠道的端到端联调(本地无支付宝沙箱环境),重定向响应的一致性依据 Spring RedirectView 与 response.sendRedirect 的等价行为以及单元测试断言。
    • 未在 JDK 17 下本地执行(本机为 JDK 21),仓库 CI 会以 JDK 17 重新执行 mvn -B test。

合并前核对

  • 普通贡献目标为 dev;稳定热修复到 master 并关联回同步 dev 的计划 / PR
  • 范围、验收标准和相关文档已对齐;不兼容变化已明确标注
  • 相关测试通过,未提交凭据、真实商户数据或敏感日志
  • 维护者评审意见已处理,必需 CI 检查通过
  • 若为发布 PR,已列出前后端版本、升级 / 回滚步骤及联调结果

Closes #125

ChannelNoticeController#doReturn 是返回视图名的 @controller 方法,先调用
response.sendRedirect() 再返回 null,响应已提交后 Spring MVC 仍会按请求路径
推导视图名并解析静态资源,产生 NoResourceFoundException(response committed
already)告警。改为返回 "redirect:" + url,由 Spring MVC 统一处理重定向。

onlyJump 同样改为返回视图名(直接跳转地址或支付结果页),并补上原先被忽略的
toReturnPage 返回值,避免订单不存在时继续执行导致的空指针。

同时补充 ChannelNoticeControllerTest 回归用例,覆盖携带 returnUrl、直接跳转
(ONLYJUMP_)与未携带 returnUrl 三种场景。
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant