GET /quote_feedbacks/sent/sort:{sort}.json
文件狀態:本地 legacy parity 實作與測試完成,待部署 new staging 複驗
最後更新:2026-07-24
快速結論
- query、nested data、排序、response shape 與 JSON 型別已對齊 legacy。
- auth 使用 endpoint 專用流程,保留 legacy Redis cache、blocked check、access update 與 returned-service queue 時機;不呼叫 shared session validator。
- invalid session 保留 legacy warning log,但不多送 legacy 沒有的 expired
Set-Cookie。 - Docker PHP 8.2 測試為
10 tests, 76 assertions;new staging 尚待部署後複驗。
目的
將目前仍由 legacy 處理的「使用者送出的評價」查詢搬到 pro360_api_82,預設要求 legacy parity。
實際常用路徑:
GET /quote_feedbacks/sent/sort:time.json保留路徑:
GET /quote_feedbacks/sent.json
GET /quote_feedbacks/sent/sort:.json
GET /quote_feedbacks/sent/sort:score_high.json
GET /quote_feedbacks/sent/sort:score_low.json前端觸發方式
Staging Web 操作路徑:
- 登入後切到「案主/消費者模式」。
- 開啟「帳戶設定 → 評價紀錄」,或直接進入
/dashboard/settings/reviews。 - 頁面載入時會呼叫
GET /quote_feedbacks/sent/sort:time.json。 - 切換排序選單時,會改呼叫
sort:score_high或sort:score_low。
專家模式不會顯示「評價紀錄」選單,需要先切回案主模式。

圖:2026-07-24 staging 前端觸發方式;切換成「最低至最高分」後,Network 收到 sort:score_low.json,HTTP 200。此圖只證明 caller 與操作路徑,不代表 new endpoint 已部署。
流量證據
2026-07-24 查詢 legacy get-lancer_access.log* 保留區間:
GET /quote_feedbacks/sent...:213 次,皆為 HTTP 200。OPTIONS /quote_feedbacks/sent...:64 次,皆為 HTTP 200。- 絕大多數為
sort:time;另有sort:score_high。 - caller 包含 consumer iOS App 與
www.pro360.com.twWeb。
Legacy 實際路徑
Controller
- 檔案:
get-lancer-php56/app/Plugin/Quotes/Controller/QuoteFeedbacksController.php - action:
sent(),第 38–128 行。 - session guard:第 41–43 行。
- sort mapping:第 45–59 行。
- feedback query:第 61–96 行。
- service photo / feedback attachment mapping:第 98–121 行。
- raw array response:第 123 行。
Shared helper / model
get-lancer-php56/app/Controller/RestApiHelper.phphasValidApiSession():第 75–153 行。getUserId()與 legacy request info log:第 162–182 行。
get-lancer-php56/app/Plugin/Acl/Controller/Component/AclFilterComponent.php- invalid-session warning 與 403 response:第 30–64 行。
get-lancer-php56/app/Plugin/Quotes/Model/QuoteFeedback.phpQuoteFeedbackCommentassociation:第 57–65 行,依id排序。
get-lancer-php56/app/Plugin/Quotes/Model/QuoteFeedbackAttachment.phpgetAttachmentMap():第 12–29 行,只取is_active = 1的 thumb URL。
get-lancer-php56/app/Controller/AppController.phphandleProApiException():第 1320–1343 行。
New 對應
- route:
pro360_api_82/Lib/Common/RouterRule/Mapping.php第 152–155 行。 - endpoint:
pro360_api_82/Endpoint/V1/QuoteFeedbacks.phpsent()與 error mapping:第 24–59 行。- legacy session/cache 與 side effects:第 61–145 行。
- legacy info/warning log:第 147–182 行。
- model:
pro360_api_82/Lib/Model/QuoteFeedback.phpgetLegacySentFeedbacks():第 30–100 行。
- test:
pro360_api_82/tests/QuoteFeedbacksSentTest.php- setup / cleanup:第 59–127 行。
- parity cases:第 129–421 行。
- fixtures / dispatch helpers:第 423 行起。
既有 QuoteFeedback::getFormatFeedbackByUserId() 不可直接重用:它會改以服務擁有者查評價、過濾 abnormal feedback、查 User、隱藏姓名、解析 tag 並改變 comment shape,與本 API 不同。為避免影響既有 caller,本 API 使用專用 method。
逐段對照
| 階段 | Legacy | New | 結論 |
|---|---|---|---|
| guard | API key、Redis session cache、session/user、cache miss blocked check | endpoint 專用 resolveLegacySentUser(),不呼叫 shared validator | 已對齊 |
| input | named parameter sort,包含空值 | route 映射到 sort param,接受 sort:.json | 已對齊 |
| sort | high / low;其餘一律 modified DESC | 專用 allowlist mapping | 已對齊,沒有把 input 拼成任意 SQL |
| feedback query | user condition、bid LEFT JOIN、service INNER JOIN | getLegacySentFeedbacks() 相同 join / condition | 已對齊 |
| nested query | comments、service photo、active feedback attachments | 專用 comments / photo mapping,重用 attachment active filter | 已對齊 |
| DB write | 無 domain write | 無 domain write | 已對齊 |
| payment | 不適用 | 不適用 | 無 |
| auth side effect | cache miss 才更新 access、檢查 blocked/returned service、寫 15 分鐘 cache | endpoint 專用實作相同時機;不寫 multi_lang_id / daily activity | 已對齊 |
| request log | success info;invalid session warning | endpoint-specific LogHelper | 已對齊並實際查 log |
| response | raw array | raw array | 已對齊 |
| config | 無 endpoint-specific key | 無 endpoint-specific key | 不需部署設定 |
Guard 與 input
| 項目 | Legacy | New 目標 |
|---|---|---|
| request format | .json 且 RequestHandler->prefers('json') | mapping 僅接受 .json |
| API key | 必須有效 | 相同;失敗 HTTP 401 |
| session | 必須能取得有效 user | 相同;失敗 HTTP 403 |
| user | 只從 session 取得,不接受 request user id | 相同 |
users.is_active | legacy 沒有此 guard | 不新增 active guard |
sort=score_high | is_satisfied DESC | 相同 |
sort=score_low | is_satisfied ASC | 相同 |
sort=time、缺少、空字串或未知值 | modified DESC | 相同 |
| tie-breaker | 無 | 不可自行新增 |
Staging legacy 於 2026-07-24 實測:
- missing session:HTTP 403,
{"error":99999,"message":"invalid_user"}。 - invalid API key:HTTP 401,
{"error":99999,"message":"INVALID API KEY"}。
Query parity
主查詢:
quote_feedbacks.user_id = session user id。LEFT JOIN quote_bids,且 join condition 必須同時包含:QuoteFeedback.model = 'QuoteBids'QuoteFeedback.foreign_id = QuoteBid.id
INNER JOIN quote_services:QuoteFeedback.quote_service_id = QuoteService.id。- 不加
is_public、is_rejected、is_active、abnormal feedback 或其他 visibility filter。 - 不分頁、不設 limit。
額外查詢:
quote_feedback_comments:回傳全部欄位,不加is_customer等 filter,依id ASC。quote_feedback_attachments:只取is_active = 1,回傳每筆 feedback 的 thumb URL array。attachments:只取class = 'QuoteService'與命中的 service id,映射amazon_s3_thumb_url;不加review_statusfilter。
Response contract
成功 response 是 raw JSON array,不包 error、message 或 data。
每個 item 順序與 shape:
{
"QuoteService": {
"business_name": "string",
"photo_url": "string|null"
},
"QuoteFeedback": {},
"QuoteBid": {
"quote_request_id": "string|null"
},
"QuoteFeedbackComment": [],
"FeedbackAttachment": []
}重要型別與 negative contract:
QuoteFeedback是quote_feedbacks全欄位;DB id、flag、score 維持 JSON string,DB null 維持 null。QuoteFeedback.tag保持 DB 原始字串或 null,不做json_decode()。QuoteFeedbackComment每列保留全部欄位,包括quote_feedback_id與is_customer。- 不回
User、UserProfile、category title 或 Nshop formatter 欄位。 - 非
QuoteBidsmodel 或找不到對應 bid 時,QuoteBid.quote_request_id為 null;feedback 本身仍可回傳。 - 找不到對應
quote_servicesrow 的 feedback 因 legacyINNER JOIN不回傳。
2026-07-24 staging legacy baseline:user 8617,回傳 3 筆;已確認 comment、feedback attachment、service photo、排序及上述 JSON 型別。
Side effects、log 與 config
Domain 查詢本身不寫入 feedback、comment、attachment、service 或 bid table,也沒有 payment 或 notification。
Legacy auth 行為:
- Redis key:
api_session_{session_id},TTL 15 分鐘。 - cache hit:直接使用 cached user,不重查 blocked user、不更新 access。
- cache miss:查 session/user、檢查 blocked user;超過 180 天的 provider 可能新增
task_queue.action=returnedQuoteService。 - cache miss 只更新
users.iphone_last_access、last_access_client;不更新multi_lang_id,也不寫 daily access activity。 - legacy 沒有
users.is_activeguard。 - session 不存在時只清除本次 PHP process 的 cookie 值,不送 expired
Set-Cookieheader。
Legacy RestApiHelper::getUserId() 會寫 request info log:
- channel:實際 request path 加
::GET。 - message:session user id。
- context:至少有
api_key;有 request data / form 時才加入data/form。
New 必須保留上述 endpoint-specific log;RouterV3 generic request log 不能取代它。
Legacy ACL 在 invalid session 時另寫 warning:channel 為實際 path 加 ::GET、message 為 got 403、context 為原始 API key 與 session-token header。這會把 credential 寫入 log;目前依 legacy parity 保留。若要遮罩,必須另做 reviewer 明確接受的 migration decision。
本 API 沒有新增 deployment config、URL、template 或 payment key,也不再透過 invalid-session path 讀取 api.url 產生 Cookie header。returnedQuoteService 使用既有 task_queue。
測試矩陣
- 有效 session、無 feedback:回
[]。 time、缺少 sort、sort:空字串、未知 sort:modified DESC。score_high/score_low:只按 score 排序,不加 tie-breaker。- nested comment 回完整欄位且依 id 排序。
- active feedback attachment 回 thumb array;inactive attachment 不回。
- service photo 有值 / 無值。
QuoteBidsjoin 出quote_request_id;非QuoteBids為 null。- orphan service feedback 被 inner join 排除。
- abnormal feedback 不可被額外排除。
- raw tag 不可被 decode。
- missing session / invalid API key response 與 HTTP status。
- invalid session warning 保留 path、API key 與原始 session token;不呼叫 shared cookie remover。
- inactive session user 不可被 new-only active guard 擋掉。
- blocked user 回 legacy HTTP 403 與
Blocked Userbody。 - cache miss 只更新 legacy access 欄位並可新增 returned-service queue;cache hit 不重複更新。
- endpoint-specific log 保留完整 sort path、user id message 與 api key context。
- 呼叫前後 domain table row count 不變。
驗證結果
- Docker PHP
8.2.29lint:route、endpoint、model、test 全部通過。 - PHPUnit:
10 tests, 76 assertions,全部通過;涵蓋 auth cache、side effects、invalid-session warning 與空 sort route。 - legacy staging user
8617baseline 與本地 model output 以 canonical JSON 比對:time、score_high、score_low、未知 sort 的結構、值、array order 與 JSON 型別一致;不宣稱 raw bytes 相同。 - local monolog 實查:channel 保留完整 path,例如
quote_feedbacks/sent/sort:score_low.json::GET;message 為 user id;context 為api_key。 - invalid-session warning 實查:
monolog_warning的 path、got 403與 context 符合 legacy。 - 測後 fixture 清理確認:users、api_sessions、quote_services、blocked_users、queue 與 Redis session cache 均無殘留。
部署後待驗證
- 部署 new staging 後,以同 user / session 比對 raw response 與 JSON 型別。
- 切換
time、score_high、score_low,確認畫面排序與 HTTP 200。 - 查 new staging monolog,確認 endpoint-specific 與 RouterV3 generic log 都存在,且完整 path 沒被 rewrite 截短。
- 確認 domain table 無新增 / 更新;cache miss 只出現文件列出的 legacy auth side effects。