POST /users/forgot_password.json
快速結論
- 狀態:已補 action / PHPUnit / staging diff。
- 2026-06-24 查 legacy 正式機 access log,
POST /users/forgot_password.json有 3900 筆200,另有 1226 筆OPTIONS 200。 - legacy JSON path 會進
forgot_password_from_api(),不是畫面版forgot_password()的 validation / captcha 流程。 - 成功時會補
users.mobile_app_hash(僅空值時)、寫入event_queue,並回{"error":0,"message":"Reset password email sent"}。 - 找不到 email 時不寫 DB / queue,回
{"error":1,"message":"Invalid email address"}。 - new 專案已補
Users::forgot_password(),用EmailSrc::appendForgotPasswordDynamicUrl()、EventQueue::insertEvent(),並用 legacy-shaped event payload,避免 new DO 多帶 legacy 沒有的 null 欄位。 - migration decision:本次經 reviewer 確認,legacy 有
forgot_password_from_apiendpoint 專用 request log,new 不再補這份 duplicate full request log;保留RouterV3generic request log。
Access Log
環境:
| 項目 | 值 |
|---|---|
| host | mei-te@ip-10-5-2-242 |
| log | /var/log/apache2/*access.log* |
| 查詢日期 | 2026-06-24 |
本 API 命中:
| Method | Path | Status | 筆數 |
|---|---|---|---|
| POST | /users/forgot_password.json | 200 | 3900 |
| OPTIONS | /users/forgot_password.json | 200 | 1226 |
Legacy 對照
| 檔案 | 行號 | 行為 |
|---|---|---|
/Users/mattsu/Documents/Site/get-lancer-php56/app/Controller/UsersController.php | 2715-2719 | JSON request 轉進 forgot_password_from_api() |
| 同上 | 2645-2658 | 讀 email、寫 application log、用 User.email = $provided_email 查 user,contain => ['UserProfile'] |
| 同上 | 2660-2668 | user 存在且 mobile_app_hash 空值時,產生 hash 並 updateAll() |
| 同上 | 2670-2683 | 組 email replace content 與 dynamic reset URL |
| 同上 | 2690-2701 | 建立 email task,templateKey = 420 User Change Password,寫 EventQueue::insertEvent('User_Change_Password', ...) |
| 同上 | 2703-2711 | 成功 / 失敗 JSON response |
/Users/mattsu/Documents/Site/get-lancer-php56/app/Controller/AppController.php | 544-552 | _unum() 產生 8 位數字亂碼,參與 mobile_app_hash |
/Users/mattsu/Documents/Site/get-lancer-php56/app/Model/UserProfile.php | 197-208 | getFullName();沒有姓名時 fallback 到 username,再 fallback User |
/Users/mattsu/Documents/Site/get-lancer-php56/app/Model/User.php | 1977-1989 | getMultiLangId();查不到時回 default lang |
legacy hash 來源:
md5($this->_unum() . $user['User'][Configure::read('user.using_to_login')] . $user['User']['password'] . Configure::read('Security.salt'))legacy config 來源:
| key | legacy 來源 | 值 |
|---|---|---|
user.using_to_login | /Users/mattsu/Documents/Site/get-lancer-php56/app/Config/settings.yml:332 | username |
Security.salt | /Users/mattsu/Documents/Site/get-lancer-php56/app/Config/core.phpc:191 | migration 實作使用 legacy salt 產生 mobile_app_hash |
Input / Response
| 項目 | legacy 行為 |
|---|---|
| method | POST |
| auth | 全域 API key;不需要 user session |
| input | form data email |
| user lookup | users.email = trim(email);沒有 is_active / is_deleted / role filter |
| success response | {"error":0,"message":"Reset password email sent"} |
| not found response | {"error":1,"message":"Invalid email address"} |
| missing email | legacy PHP 5.6 會 notice 後等同空字串 lookup;實作時需避免 PHP 8.2 trim(null) warning,不可自行加 required validation |
Side Effects
| 類型 | legacy 行為 |
|---|---|
| user update | 只在 mobile_app_hash 空值時更新;已有 hash 時重用舊值 |
| event queue | event_queue.event_key = User_Change_Password |
| email template | 420 User Change Password |
| email to | 使用 request 送入的 trimmed email,不是重新取 user row email 後格式化 |
| replace content | ##USER_NAME##、##DISPLAY_URL##、##RESET_PASSWORD_URL##、##REQUESTS_URL##、空白 action placeholders |
| language | User::getMultiLangId(user_id) |
| logging | legacy 有 application log channel forgot_password_from_api,message 含 email 與 request data;本次 migration decision:經 reviewer 確認不補 endpoint duplicate log,只保留 RouterV3 generic request log |
New 對照
| 項目 | 狀態 |
|---|---|
Endpoint/V1/Users.php:770-834 | forgot_password() action;API key、email lookup、hash update、event queue、response |
Endpoint/V1/Users.php:1293-1313 | findForgotPasswordUser();LEFT JOIN user_profiles,只用 users.email = ? |
Endpoint/V1/Users.php:1315-1323 | buildLegacyMobileAppHash();沿用 legacy hash 組成與 username |
Endpoint/V1/Users.php:1325-1333 | legacyUnum();8 位數字亂碼 |
Endpoint/V1/Users.php:1335-1351 | getForgotPasswordUserName();last_name first_name,再 fallback username / User |
Lib/Common/RouterV3.php:210,266-289 | generic request log;password/passwd key 過濾由 router 層統一處理 |
| routing | Origin routing 可依 method name 分派;不需新增 route mapping |
Lib/Util/EmailSrc.php::appendForgotPasswordDynamicUrl() | 已存在,補 ?pk=<user_id>&path=forgot_password&hash=<hash>;現行 DynamicLinkUtil 不短網址時回原址 |
Lib/Model/EventQueue.php::insertEvent() | 已存在,寫 event_queue |
| event payload | 使用局部 legacy-shaped object;只含 user_id、quote_service_id、tasks,task 不含 new shopList |
Lib/Model/User.php::getMultiLangId() | 已存在 |
tests/UsersForgotPasswordTest.php | 覆蓋空 hash、既有 hash、unknown email |
Staging / Local Diff
2026-06-25 用 luckynaming@gmail.com 驗證:
| 項目 | 結果 |
|---|---|
| legacy / staging response | {"error":0,"message":"Reset password email sent"} |
| new local response | {"error":0,"message":"Reset password email sent"} |
| user row | users.id = 8617、mobile_app_hash = 633c9e45829df5bb9f975b7481fb84fd;已有 hash 時不重生 |
| event row | worker 已搬到 event_queue_log_3 |
| event payload keys | user_id, quote_service_id, tasks |
| task keys | templateType, templateKey, to, from, replaceContent, notifyOptions, withPhone, formSummary, requestList, unsubscribeUrl, langId |
注意:new local 使用本地 site.url = http://localhost:12347,所以 reset / request URL host 與 staging 的 https://staging.pro360.com.tw 不同;payload shape 與欄位語意一致。
實作注意
- 不可加入 legacy 沒有的 guard,例如
is_active = 1、email blacklist、rate limit、captcha、session required。 - 不可改用
security_hashes取代users.mobile_app_hash,除非先標 migration decision;legacy reset_password path 是用mobile_app_hash。 mobile_app_hash已存在時不可重新產生。- PHP 8.2 實作要避免
trim(null)warning;missing / non-string email 等同空字串 lookup,不自行加 required validation。 - event payload 要維持 legacy shape;不可因 new
EventTaskDO/EmailTaskDO有額外 public property 就讓quote_request_id、quote_bid_id、shopList出現在這支 API。
測試結果
2026-06-25 Docker PHP 8.2:
php -l Endpoint/V1/Users.php
php -l tests/UsersForgotPasswordTest.php
./vendor/bin/phpunit tests/UsersForgotPasswordTest.php結果:
OK (3 tests, 39 assertions)覆蓋:
- existing email 且
mobile_app_hash空值:response success、補 hash、寫User_Change_Passwordqueue。 - negative assertion:fixture user 暫設
is_active = 0時仍成功,避免未來加上 legacy 沒有的 active guard。 - negative assertion:queue payload 不含 legacy 沒有的
quote_request_id、quote_bid_id、shopList。 - existing email 且已有
mobile_app_hash:response success、hash 不變、仍寫 queue。 - unknown email:response invalid,不寫 queue。