專案

一般

配置概況

動作

Bug #1392

進行中

[Backend/Frontend Bug] richmenu「填寫個人基本資料/健康日誌」打不開——LIFF 取不到 userId 導致 /check/completed/profile/undefined 回 500

是由 陳國瑋7 天 前加入.

狀態:
New
優先權:
High
被分派者:
-
開始日期:
2026-07-27
完成日期:
預估工時:

概述

現象

客戶回報部分會員點擊 richmenu「填寫個人基本資料/健康日誌」後,畫面卡在「確認中,請稍候…」,既沒有進入「填寫基本資料」表單、也沒有進入「會員中心」,等於整顆按鈕打不開、無法填寫。

happy path 正常(2026-07-27 當天 654 筆成功 vs 21 筆失敗,約 3% 失敗),因此是「部分會員」踩到,符合客戶描述。

資料流

richmenu 目前由 OmniChat 管理(選單名稱皆帶 _260716)。此按鈕為 URI action,實際連結為 OmniChat redirect 再進 LIFF:

api.omnichat.ai/restapi/v1/redirect/line_richmenu?url=…liff.line.me/2006305147-4ZbabE3q…&id=49988_3

2006305147-4ZbabE3q = 前端 NEXT_PUBLIC_LIFF_CHECK_PROFILE,對應 web 的 /check-profile 頁。

根因(前後端皆缺防呆)

  1. 前端 react/web/src/utils/LineHelper.jsgetProfile(),在「LIFF 未登入」或 liff.getProfile() 失敗時,會回傳空物件 {}(userId = undefined)而不報錯:
    • 未登入分支只呼叫 liff.login()return this.profile(此時為 {}),呼叫端在重導前仍會繼續往下跑。
    • liff.getProfile().catch()console.log 就吞掉,profile 維持 {}
  2. react/web/src/app/check-profile/page.js 未檢查就直接 checkCompletedProfile(res?.userId),送出 GET /api/user/check/completed/profile/undefined
  3. 後端 UserServiceImpl.checkIfUserCompletedProfile()(UserServiceImpl.java:282)把字串 "undefined" 原封不動丟給 messagingApiClient.getProfile("undefined") → LINE 回 400 The value for the 'userId' parameter is invalid → 例外未捕捉 → 回前端 HTTP 500。
  4. check-profile/page.js.then() 沒有 .catch(),API 一旦 500 整個 promise reject,router.push 不會執行 → 使用者永遠卡在載入畫面。

證據(Source-backed)

  • production api.log 反覆出現:
    Request: /api/user/check/completed/profile/undefined raised MessagingApiClientException: code=400 requestUrl=.../v2/bot/profile/undefined error='The value for the 'userId' parameter is invalid'
  • 直接打 production API 重現:
    curl https://api.shuxinclinic.com/api/user/check/completed/profile/undefinedHTTP 500 {"errorType":"ExecutionException","message":"系統發生錯誤,請連絡系統管理員"}
  • 失敗數自 2026-07-23 起明顯暴增(每日 undefined 出現次數):7/22:4 → 7/23:80 → 7/24:96 → 7/25:60 → 7/26:20 → 7/27:36。程式碼與 api.jar 皆未變動(前端 container 已跑約 10 個月、api.jar 為 2026-01),故觸發點在外部——與 OmniChat 於 7 月接管並重發 richmenu(_260716)時間吻合。

#1383 / #1384 的關係

三者共同上游皆為「OmniChat 於 7 月接管 richmenu(_260716)」,於 2026-07-23 同時爆發,但分屬不同流程:

  • #1383 / #1384 走 postback → webhook /callback 路徑(OmniChat 把 data 轉 JSON → NPE;回錯誤訊息時 reply token 失效)。
  • 本單走 URI → OmniChat redirect → LIFF → 前端 → REST API 路徑,不經過 webhook、也不用 reply token,與前兩單無程式碼交集,但同源。
    推測 OmniChat redirect 多一層轉址,使部分使用者進入 LIFF 時登入 context 遺失(liff.isLoggedIn() 為 false),放大了原本潛伏的前端防呆缺口。

建議修法

前端(治本、可自行 rebuild hm-web 止血):

  • LineHelper.getProfile():未登入時 liff.login() 後中止流程(勿回傳 {});getProfile() 失敗要向上拋錯或回 null
  • check-profile/page.jsres?.userId 為空就不打 API(改為重新登入或顯示重試),並補 .catch() 處理 API 失敗,不要靜默卡住。

後端(多一層保護):

  • checkIfUserCompletedProfile():先擋掉 id 為空/"undefined",回 400(InvalidParameterException)而非 500;並 catch MessagingApiClientException 避免直接噴 500。

維運面:

  • 與 OmniChat 確認該 URI 按鈕能否直接指向 https://liff.line.me/2006305147-4ZbabE3q、拿掉中間 redirect 包裝,以降低失敗率。

影響範圍

所有點擊「填寫個人基本資料/健康日誌」按鈕、且當下 LIFF 登入 context 未建立的會員(約 3%,7/23 後升高),會卡在載入畫面無法填寫基本資料,等同無法進入自費療程前置流程。

沒有任何資料可供顯示

動作

匯出至 Atom PDF