QuoteServices External Review Submit
API:
POST /quote_services/external_review/submit.json功能說明
consumer 透過 /review/service/{quote_service_id}/{hash} 外部評價頁提交評價時呼叫。前端呼叫鏈是 ReviewContainer.reduceData() → serviceActions.postExternalReview() → APIManager.rateServiceExtenal()。
目前狀態
已補 action、routing 與 PHPUnit。
正式機流量
2026-06-04 查正式機 access log:
125 POST /quote_services/external_review/submit.json 200
82 OPTIONS /quote_services/external_review/submit.json 200同一流程的讀取端 GET /quote_services/external_review/link/{quote_service_id}.json 已另行搬移。
Legacy Rule
legacy controller:
/Users/mattsu/Documents/Site/get-lancer-php56/app/Plugin/Quotes/Controller/QuoteServicesController.php已確認規則:
- 預設 response 是
{"error":1,"message":"Invalid session"}。 - 若有有效 session,使用 session user;此時忽略
User[...]註冊 / 登入欄位。 - 若無有效 session,會走
_getUserIdFromInput(),用User[email]、User[password]、User[device_id]等欄位登入或自動建立 consumer user。 - 必須有
QuoteFeedbackinput,至少包含:QuoteFeedback[hash]QuoteFeedback[is_satisfied]QuoteFeedback[feedback]
SecurityHash::fulfill('QuoteService', hash)找不到時回{"error":4,"message":"Invalid hash"}。- 若
security_hashes.user_id等於提交者 user id,回{"error":5,"message":"Cannot review the same user"}。 - 成功時會:
- 新增
quote_feedbacks - 更新被評價 provider 的 user feedback stats
- 建立
AddReviewactivity,param1為原始QuoteFeedbackJSON - 回
{"error":0,"message":"Success"},若是無 session 自動登入 / 註冊路徑,會附session_id
- 新增
新舊程式對照
detail doc 必須保留實際行號,避免只看 controller 入口但漏掉 user resolve、activity 或統計 helper。
Legacy PHP 5.6
legacy controller:
/Users/mattsu/Documents/Site/get-lancer-php56/app/Plugin/Quotes/Controller/QuoteServicesController.php| legacy 行號 | 職責 |
|---|---|
3571-3575 | external_review('submit') dispatch 到 _submit_external_review()。 |
3620-3628 | _submit_external_review() 入口;預設 Invalid session,有有效 session 時使用 session user。 |
3629-3638 | 無有效 session 時呼叫 _getUserIdFromInput();成功時把 session_id 放入 response。 |
3639-3645 | 確認有 QuoteFeedback input,呼叫 SecurityHash::fulfill('QuoteService', hash)。 |
3646-3649 | hash owner 等於提交者時回 error=5。 |
3650-3659 | 建立 quote_feedbacks,欄位來源為 hash record 與 QuoteFeedback input。 |
3660-3664 | 成功 response;更新 user feedback stats;建立 QuoteService / AddReview activity。 |
3667-3669 | render JSON response。 |
3741-3755 | _getUserIdFromInput() 初始狀態與 QuoteUtil 載入。 |
3756-3772 | 讀 User[email],支援 Google / Facebook token 驗證與 social id lookup。 |
3774-3780 | social id 已綁定不同 email 時,視為 invalid session。 |
3782-3804 | Redis 重複呼叫 guard、讀 password / device_id、依 email 或 social id 找 user。 |
3806-3837 | 新 email 自動建立 consumer user;password 空值時用 device id;同步 social register flags。 |
3839-3853 | 建立 user_profiles 與 user_logins。 |
3854-3864 | referral hash 成功時建立 referral,並標記新 user signup 成功。 |
3865-3897 | 既有 social user 直接信任;既有 email 走 device session 或 password login。 |
3901-3910 | 新 user 寫 UserSignup activity,upsert api_sessions,回 user_id/session_id。 |
3911-3927 | invalid session、password required、invalid password response。 |
legacy helpers:
/Users/mattsu/Documents/Site/get-lancer-php56/app/Model/AppModel.php
/Users/mattsu/Documents/Site/get-lancer-php56/app/Plugin/Quotes/Controller/Component/QuotesUtilComponent.php| legacy 行號 | 職責 |
|---|---|
668-673 | createQuoteServiceActivityById() 建立 QuoteService activity。 |
QuotesUtilComponent.php:59-86 | updateUserFeedbackStats() 只統計 quote_feedbacks.model = 'QuoteBids' 的評論後更新 user stats。 |
New PHP 8.2
| new 檔案行號 | 對應 legacy | 職責 |
|---|---|---|
Lib/Common/RouterRule/Mapping.php:61-64 | access log path | route /quote_services/external_review/submit.json 到 QuoteServices::external_review_submit()。 |
Endpoint/V1/QuoteServices.php:248-317 | QuoteServicesController.php:3620-3669 | submit 主流程、session / external user resolve、hash guard、feedback write、stats、activity、response。 |
Endpoint/V1/QuoteServices.php:319-328 | 3627-3638 | 有 session 優先用 session user;無 session 才走 external input。 |
Endpoint/V1/QuoteServices.php:364-478 | 3741-3927 | 搬移 _getUserIdFromInput() 的 email、password、device session、新 user、api session response。 |
Endpoint/V1/QuoteServices.php:480-521 | 3759-3780 | Google / Facebook token 驗證、social id user lookup、email mismatch guard。 |
Endpoint/V1/QuoteServices.php:523-617 | 3806-3837 | 新 consumer user 建立;使用 explicit allowlist,避免複製既有 row。 |
Endpoint/V1/QuoteServices.php:619-653 | 3839-3849 | 建立 user_profiles。 |
Endpoint/V1/QuoteServices.php:655-668 | 3854-3861 | referral hash 成功時建立 referral。 |
Lib/Model/QuoteFeedback.php:979-1003 | QuotesUtilComponent.php:59-86 | 更新 user feedback stats;同 legacy,只統計 model = QuoteBids。 |
Lib/Model/QuoteActivity.php:108-129 | AppModel.php:668-673 | 建立 QuoteService activity;不寫 quote_activity_consumers。 |
tests/QuoteServicesExternalReviewSubmitTest.php:99-153 | 3650-3664 | 成功建立 feedback、hash fulfilled、AddReview activity。 |
tests/QuoteServicesExternalReviewSubmitTest.php:155-181 | 3646-3649 | same-user guard,並確認不建立 feedback。 |
tests/QuoteServicesExternalReviewSubmitTest.php:183-226 | 3629-3638, 3806-3910 | 無 session 時以 User[...] 自動建立 user / session 並提交評價。 |
tests/QuoteServicesExternalReviewSubmitTest.php:228-242 | 3639-3645 | invalid hash response。 |
逐段對照
| new 行號 | 對應 legacy | 職責 |
|---|---|---|
Mapping.php:61-64 | 3571-3575 | 將 legacy external_review('submit') path 拆成新版 explicit route。 |
QuoteServices.php:250-263, 319-328 | 3623-3638 | 預設 Invalid session;有 session 用 session user,無 session 解析 external input;無 session 成功時回 session_id。 |
QuoteServices.php:265-277 | 3639-3645 | 讀 QuoteFeedback,呼叫 SecurityHash::fulfill('QuoteService', hash);找不到回 error=4。 |
QuoteServices.php:279-284 | 3646-3649 | hash owner 與提交者相同時回 error=5。 |
QuoteServices.php:286-304 | 3650-3659 | 建立 quote_feedbacks,保留 model=QuoteService、foreign_id=quote_service_id、quote_service_id。 |
QuoteServices.php:306, QuoteFeedback.php:979-1003 | 3663, QuotesUtilComponent.php:59-86 | 成功後更新 user feedback stats;同 legacy,只統計 model = QuoteBids。 |
QuoteServices.php:307-312, QuoteActivity.php:108-129 | 3664, AppModel.php:668-673 | 建立 QuoteService / AddReview activity,param1 是原始 QuoteFeedback JSON。 |
QuoteServices.php:314-317 | 3660-3669 | 成功 response:{"error":0,"message":"Success"}。 |
QuoteServices.php:364-424 | 3741-3804, 3865-3885 | email、device id、既有 user、既有 session、空密碼 fallback 到 device id。 |
QuoteServices.php:426-432, 474-477 | 3887-3897, 3922-3925 | legacy crypt password 比對;失敗回 invalid password。 |
QuoteServices.php:433-459, 523-668 | 3806-3864, 3901-3910 | 新 user 建立、profile、login、referral、UserSignup activity、api session upsert。 |
QuoteServices.php:467-472 | 3916-3920 | 既有 user 空密碼且無 device session 時回 password required。 |
QuoteActivity.php:108-129 | AppModel.php:668-673 | 使用 QuoteService activity 寫入,不走 bid consumer index。 |
已確認實測
2026-06-04 legacy staging 實測 same-user guard:
request body:
QuoteFeedback[hash]=7d7de5abf902bc90549609ed35345d5e
QuoteFeedback[is_satisfied]=5
QuoteFeedback[feedback]=good
User[email]=luckynaming@gmail.com
User[name]=luckynaming
User[password]=
User[device_id]=e79ca347-5d20-4a96-98de-3c96dd01e90cresponse:
{"error":5,"message":"Cannot review the same user"}判讀:此 request 帶有效 user session,legacy 使用 session user 判斷,不使用 User[...] 欄位另建或登入 user;因此 hash owner 和 session user 相同時會被擋。
User Resolve
- 有有效 session 時:使用 session user,忽略
User[...]欄位。 - 無 session 時:依 legacy
_getUserIdFromInput()使用User[email]、User[password]、User[device_id]。 - 無 session 且新 email:建立 consumer user、user profile、api session,並寫
UserSignupactivity,param2 = external review。 - 無 session 且既有 email:
- 空密碼且同 device 已有
api_sessions:使用該 session。 - 有密碼:用 legacy
crypt()比對。 - 空密碼且沒有 device session:回
{"error":2,"message":"Failed to login, password is required"}。 - 密碼錯誤:回
{"error":3,"message":"Failed to login, invalid password"}。
- 空密碼且同 device 已有
User[id_token]/ Facebook token 仍依 legacy 優先驗 social id;若 social id 已綁定不同 email,回 Invalid session。
Side Effect
SecurityHash::fulfill('QuoteService', hash)會把security_hashes.is_fulfilled設為1;legacy 未使用 one-time guard,重複 hash 仍可能找到同一筆。- success 會新增
quote_feedbacks:model = QuoteServiceforeign_id = quote_service_idreviewed_user_id = security_hashes.user_idquote_service_id = quote_service_id
- success 會呼叫
QuoteFeedback::updateUserFeedbackStats(reviewed_user_id);legacy 這支統計只計入model = QuoteBids,所以外部 service review 本身不會增加 service stats。 - success 會寫
QuoteService/AddReviewactivity,param1是原始QuoteFeedbackJSON。
測試
2026-06-04:
php -l Endpoint/V1/QuoteServices.php
php -l Lib/Common/RouterRule/Mapping.php
php -l Lib/Model/QuoteActivity.php
php -l tests/QuoteServicesExternalReviewSubmitTest.php
docker exec -w /project-data cd63f9147e8d vendor/bin/phpunit tests/QuoteServicesExternalReviewSubmitTest.php結果:
OK (4 tests, 28 assertions)