◆ wport | pm_46

獵人頭模組 PRD

獵人頭模組

來源 doc/feature/pm_46/headhunter-module-prd.md

獵人頭模組 PRD

產品名稱:獵人頭模組(Headhunter Module)
一句話:讓「誰帶來這份履歷」可追蹤、讓 KOC 能打包推薦、讓夥伴獵頭(奧德士)能獨佔曝光職缺——應徵仍走 WPORT 人才庫管線。
PRD 代號pm_46(文件夾序號;Coda / Trello 建議用產品名「獵人頭模組」)
子能力:① 推薦歸因(ref / utm_source)② 職缺推薦清單 ③ 夥伴職缺綁定與官網同步
不重複定義:API 金鑰與企業人才庫 UI 沿用 pm_41
產出 persona:CEO with PM Background(SaaS / HR tech lens)。


1. 文件資訊

項目內容
功能名稱獵人頭模組(Headhunter Module)
檔案路徑doc/feature/pm_46/headhunter-module-prd.md
文件類型產品需求規格書(歸因 + 清單 + 夥伴曝光分流)
適用對象前端、後端、產品、QA、BD(夥伴獵頭)
最後更新2026/07/03
版本1.1.0
PRD 代號pm_46
Coda rowIdi-Hnzf5ItlAO
Coda / Backlog 名稱獵人頭模組
關聯 PRDpm_41(企業 API 金鑰自助 + 人才庫 UI)、pm_45(企業人才 CRM)、pm_37(Enterprise REST API 基建)
對應 StorybookN/A(v1 以歸因基建為主;模組 2 清單頁待後續 mockup)
建立日期2026/07/03
優先級模組 1 P0 → 模組 2 P1 → 模組 3 P2

2. 專案背景與範圍

2.1 戰略背景:什麼是「獵人頭模組」?

獵人頭模組服務 WPORT 三類獵頭場景,共用同一條應徵與人才庫管線:

場景角色本模組提供的能力
C2C 全民獵頭學生、獨立獵頭、KOC?ref= 歸因、職缺蒐藏清單
B2B 夥伴獵頭奧德士(Odexus)等簽約夥伴公司職缺綁定夥伴、API 同步至夥伴官網
企業 HR發職缺的雇主可見性三態、綁定/解約夥伴、查看推薦來源

核心目標:每一筆應徵都能回答「是誰帶來的?」;夥伴獨佔職缺僅改變 職缺在哪裡曝光,不改變 應徵進哪條人才庫

2.2 與 pm_41 的邊界(Eric 確認,2026/07/03)

pm_41 是企業端 API 金鑰自助wpk_live_)與 人才庫畫面 的 SoT;獵人頭模組 不另建 第二套 Key 或獨立機構後台。

能力歸屬 PRD獵人頭模組是否重複定義
公司後台申請 / 輪替 API Keypm_41❌ 沿用
企業人才庫列表、應徵/來訪 UIpm_41 / pm_45❌ 沿用
?ref= / ?utm_source= 歸因pm_46 模組 1✅ 本 PRD
KOC 職缺蒐藏清單pm_46 模組 2✅ 本 PRD
職缺綁定奧德士、曝光分流pm_46 模組 3✅ 本 PRD

綁定奧德士後的產品行為(關鍵)

面向WPORT 公開站(搜尋/大廳)發職缺企業後台(人才庫)奧德士官網
職缺 listing不出現✅ HR 仍可管理該職缺✅ 經 API 同步後露出
求職者應徵僅能透過夥伴導流連結進入照常出現在此職缺的人才庫✅ 從官網投遞
推薦歸因✅ 顯示獵頭/夥伴來源(模組 1)utm_source 標記奧德士

換句話說:綁給奧德士的人才,發職缺的公司在 WPORT 人才庫裡一樣看得到;唯一差別是該職缺 不會再出現在 WPORT 公開求職站,改在 奧德士官網 曝光。人才庫體驗對 HR 不變(pm_41 畫面)。

企業 HR 可 主動解約(解除夥伴綁定)→ 職缺恢復 WPORT 公開曝光規則,奧德士 API 與官網同步停止收錄該職缺。

2.3 模組依賴架構

flowchart TB
    subgraph pm41 [pm_41 沿用]
        API[企業 API 金鑰 wpk_live_]
        CRM[企業人才庫 UI]
    end
    subgraph hunter [獵人頭模組 pm_46]
        M1[模組 1:UTM 歸因 P0]
        M2[模組 2:職缺蒐藏清單 P1]
        M3[模組 3:夥伴獨佔職缺綁定 P2]
    end
    M1 --> M2
    M1 --> M3
    M3 --> API
    M3 --> CRM
模組名稱優先級依賴
1UTM 歸因系統P0無(地基)
2中間獵頭/KOC 職缺蒐藏清單P1模組 1
3夥伴獨佔職缺綁定與同步P2模組 1、pm_41 API

2.4 範圍界定

包含(v1)

  • 職缺可見性三態:PUBLIC / SECRET_URL / DRAFT
  • 前端歸因參數擷取與持久化(Cookie + LocalStorage,30 天);Cookie 橫幅照常顯示,歸因 Cookie 列功能性必要(§11 #2 已決)
  • 應徵寫入時自動帶入推薦人 / 夥伴歸因;v1 僅在 applications 加欄位,不建獨立 referrals(§11 #3 已決)
  • Last Click 歸因、防自推、防重複投遞作弊
  • 獨立獵頭職缺蒐藏清單 CRUD 與分享 URL
  • 清單內職缺連結自動注入 ref
  • 職缺 綁定夥伴獵頭公司(如奧德士):WPORT 公開站隱藏、夥伴官網 API 同步露出
  • 企業 HR 主動解約(解除綁定)
  • 夥伴透過 pm_41 公司後台 wpk_live_ Key 拉取已綁定職缺(不另建 wak_live_

排除(v1 不做)

  • 推薦人分潤 / 佣金結算(另案)
  • 推薦人儀表板與即時報表(v2)
  • 獨立 agency_users 或機構專用登入站(夥伴 = WPORT 公司帳號,Key 走 pm_41)
  • 第二套 API Key 前綴(wak_live_)或獨立機構 api_keys
  • 點擊未投遞的 referral_click_events 漏斗表(v1.1 再議)
  • 多點觸控歸因(First Click / Linear)— v1 僅 Last Click
  • Webhook 推送應徵事件給夥伴(v2)
  • 重複定義 pm_41 人才庫 UI / API 金鑰自助流程

2.5 成功條件(6 個月 KPI)

指標目標
ref / utm_source 的職缺點擊佔總點擊≥ 15%
可歸因應徵佔總應徵≥ 10%
活躍職缺蒐藏清單數≥ 200
簽約夥伴獵頭公司數(含 Odexus)≥ 3
歸因資料與應徵紀錄一致率(抽樣稽核)≥ 99.5%
夥伴 bound jobs API p95 latency< 500ms

3. User Stories(使用者故事)

3.1 企業 HR

ID身為…我想要…以便…優先級
US-HR-01企業 HR將職缺設為 PUBLIC(公開大廳)求職者可在搜尋結果找到並投遞P0
US-HR-02企業 HR將職缺設為 SECRET_URL(僅連結可見)只讓指定獵頭或內推對象看到,不曝光於公開搜尋P0
US-HR-03企業 HR將職缺存為 DRAFT 草稿完成內容後再上架,避免半成品外洩P0
US-HR-04企業 HR指派 SECRET_URL 職缺給特定獵頭用戶該獵頭可將職缺加入自己的推薦清單P1
US-HR-05企業 HR將職缺綁定給夥伴獵頭公司(如奧德士)職缺不在 WPORT 公開站露出,改由夥伴官網曝光;應徵仍進我方人才庫P2
US-HR-06企業 HR在應徵名單 / 人才庫看到「推薦來源」(獵頭用戶 / 夥伴 / 直接投遞)評估各渠道人才品質;沿用 pm_41 人才庫畫面P0
US-HR-07企業 HR複製帶歸因參數的職缺分享連結追蹤自家員工內推成效P0
US-HR-08企業 HR主動解約(解除夥伴綁定)職缺恢復 WPORT 公開曝光;夥伴官網停止同步P2

3.2 一般用戶 / 中間獵頭(Middle Hunter / KOC)

ID身為…我想要…以便…優先級
US-MH-01獨立獵頭取得個人專屬推薦連結 ?ref={my_user_id}分享後系統能歸因我帶來的應徵P0
US-MH-02獨立獵頭建立「職涯推薦清單」並勾選多個職缺一次打包推薦給求職者,像播放清單一樣方便P1
US-MH-03獨立獵頭取得清單專屬 URL wport.co/list/{list_id}?ref={hunter_id}在社群一次分享多個機會P1
US-MH-04獨立獵頭清單內每個職缺連結自動帶我的 ref不用手動逐條改 URL,降低操作失誤P1
US-MH-05獨立獵頭只能加入 PUBLIC 或「已指派給我的 SECRET_URL」職缺不會把未授權的私密職缺外洩P1
US-MH-06一般用戶(學生/KOC)分享單一職缺連結給同學若同學應徵成功,平台能記錄是我推薦的P0
US-MH-07獨立獵頭編輯 / 下架自己的推薦清單職缺過期或已關閉時維護清單品質P1

3.3 夥伴獵頭公司(如奧德士 Odexus)

奧德士在 WPORT 上為一般公司帳號(夥伴獵頭公司);API Key 申請 / 輪替走 pm_41 公司後台,本模組不重複定義 Key UI。

ID身為…我想要…以便…優先級
US-AG-01奧德士管理員公司後台(pm_41)申請 / 輪替 wpk_live_ Key與我方 ATS / 官網系統對接P2
US-AG-02奧德士工程師用 Key 拉取已綁定給我們的職缺同步至奧德士官網,不需手動篩選P2
US-AG-03奧德士工程師API 回傳的職缺含可對外投放的導流 URL求職者從官網投遞,歸因標記奧德士P2
US-AG-04奧德士管理員在 pm_41 撤銷外洩 Key 並輪替降低憑證外洩風險P2
US-AG-05奧德士營運透過 ?utm_source={partner_company_id} 追蹤夥伴層級歸因與模組 1 歸因系統對接P0
US-AG-06發職缺企業 HR綁定後仍能在 WPORT 人才庫看到該職缺的應徵者招募流程不中斷;僅曝光渠道分流P2

3.4 求職者(附帶)

ID身為…我想要…以便…優先級
US-JS-01求職者透過獵頭分享的連結順利瀏覽並投遞不會因為技術問題投遞失敗P0
US-JS-02求職者在 30 天內從不同推薦連結進入後仍能正確投遞最後點擊的推薦人獲得歸因(Last Click)P0

4. Functional Requirements(功能性需求)

4.1 模組 1:UTM 歸因系統(P0)

FR-1.1 職缺可見性三態

狀態代碼搜尋可見URL 直達可投遞備註
公開大廳PUBLIC對齊 BR-009 上架(Published)
秘密連結SECRET_URL✅(需有效 URL)對齊 BR-009 私人(Private)正式落地
草稿DRAFT❌(僅作者/公司成員)對齊草稿態;未滿足 BR-008 不可上架
  • visibility 與既有 status(上架/下架/已關閉/已刪除)正交:終態職缺(已關閉/已刪除)一律不可投遞,不受 visibility 影響。
  • PUBLIC 職缺須滿足 BR-008(必填欄位 + 公司已驗證)方可生效。

FR-1.2 歸因參數擷取(前端)

進入 WPORT 職缺相關頁面時,若 URL 含以下任一參數,前端必須寫入瀏覽器:

參數語意儲存值
refC2C 個人推薦人user_id(WPORT 內部 ID,非 email)
utm_sourceB2B 夥伴獵頭公司來源partner_company_id(夥伴在 WPORT 的 companies.id

儲存策略

  • 主要HttpOnly 不可行(需前端讀取)→ 使用 第一方 Cookie wport_ref(JSON payload,Secure + SameSite=Lax)。
  • 備援localStorage key wport_ref_v1(Cookie 被阻擋時 fallback)。
  • 效期:30 天(max-age=2592000);每次有效點擊帶新參數時覆寫(Last Click)。
  • 儲存結構(語意)
{
  "referrer_type": "user | partner",
  "referrer_id": "string",
  "captured_at": "ISO8601",
  "landing_url": "string",
  "expires_at": "ISO8601"
}

FR-1.3 歸因寫入(後端)

建立 Applications 紀錄時:

  1. 讀取請求附帶的歸因 payload(由前端從 Cookie 注入 request header X-Wport-Referral 或 body 欄位 referral)。
  2. 後端獨立驗證(不信任前端單方面宣告):
    • referrer_id 存在且帳號狀態有效(未刪除,BR-012)。
    • referrer_type=user 時,referrer_id ≠ applicant_user_id禁止自推,BR-039)。
    • referrer_type=partner 時,partner_company_id 存在、為已認證之夥伴獵頭公司且 status=active
    • 歸因時間未超過 30 天。
  3. 驗證通過 → 寫入 Applications.referrer_typeApplications.referrer_idApplications.referral_captured_at
  4. 驗證失敗 → 應徵仍成功建立,但 referrer_*null(歸因為增值能力,不阻斷核心投遞)。

FR-1.4 歸因原則:Last Click

  • 30 天窗口內,最後一次帶有效 refutm_source 的點擊覆寫先前歸因。
  • 若最後一次點擊為無參數的直接進入 → 清除歸因(視為直接投遞)。
  • refutm_source 同時存在於同一 URL 時,優先序:ref > utm_source(個人歸因優先於夥伴)。

FR-1.5 防作弊機制

規則說明對應 BR
禁止自推推薦人 ID = 應徵者 ID → 不寫入歸因BR-039(新)
重複投遞同一用戶同一職缺 72h 內不可再投(既有規則)BR-011
無效推薦人推薦人帳號已刪除 / 停權 → 不寫入歸因BR-012
過期歸因超過 30 天 → 不寫入歸因BR-039
機器刷量同一 IP + 同一 ref + 同一 job 每小時 > 50 次點擊不更新歸因 cookie(防刷)BR-039

FR-1.6 與 SEO / Canonical 相容

  • 對齊 pm_33:ref / utm_source 為追蹤參數,不影響 Canonical URL(Canonical 指向無查詢參數的乾淨職缺 URL)。
  • 搜尋引擎爬蟲不寫入歸因 Cookie(辨識 bot User-Agent 跳過)。

4.2 模組 2:中間獵頭/KOC 職缺蒐藏清單(P1)

FR-2.1 清單生命週期

動作說明
建立登入用戶可建立清單;必填:標題;選填:描述、封面圖
編輯僅擁有者可編輯標題 / 描述 / 職缺成員
刪除軟刪除;已分享 URL 回 410 Gone
上限每用戶最多 20 組清單;每清單最多 50 個職缺(BR-041)

FR-2.2 職缺加入條件

獵頭用戶僅可加入符合以下任一條件的職缺:

  1. visibility = PUBLICstatus = 上架bound_partner_company_id IS NULL(未綁定夥伴)
  2. visibility = SECRET_URLstatus = 上架 且存在 job_hunter_assignments 指派給該用戶

綁定夥伴的職缺不在 WPORT 公開站露出,亦不可加入 KOC 蒐藏清單(見 E-24)。

不符合條件的職缺在 UI 顯示為不可選,API 回傳 403 JOB_NOT_ELIGIBLE

FR-2.3 分享 URL

  • 格式:https://wport.co/list/{list_id}?ref={hunter_user_id}
  • list_id:不透明 UUID(非自增整數,防枚舉)
  • 公開頁:不需登入即可瀏覽清單與職缺摘要
  • 清單頁載入時:將 ref 寫入模組 1 歸因 Cookie(與直接職缺連結行為一致)

FR-2.4 動態注入 ref(核心)

當求職者在清單頁點擊任一職缺時,系統必須

  1. 取清單擁有者的 hunter_user_id(來自清單 owner_user_id,非 URL 參數—防篡改)。
  2. 生成職缺 URL:/job/{id}/{slug}?ref={owner_user_id}
  3. 若 URL 已有 ref以清單擁有者覆寫(清單場景優先)。
  4. 同時更新歸因 Cookie(Last Click)。

設計理由:URL 的 ref 可被使用者手動修改,但清單點擊行為以伺服器端/前端路由守衛強制注入,確保歸因正確。

FR-2.5 清單內容動態更新

  • 職缺進入終態(已關閉/已刪除/下架)→ 清單內該項顯示 boundary「此職缺已關閉」,連結停用(對齊 BR-009 / pm_42)。
  • 職缺從 SECRET_URL 移除獵頭指派 → 下次清單擁有者儲存時自動剔除該項並通知。

4.3 模組 3:夥伴獨佔職缺綁定與同步(P2)

FR-3.1 夥伴獵頭公司(Partner Company)

  • 夥伴(如奧德士)在 WPORT 上為一般 Company 實體,另標記 is_headhunter_partner = true(或等價旗標)。
  • 不建獨立 agency_users 登入體系;夥伴員工用 WPORT 統一帳號進入該公司企業後台
  • API Key 核發 / 輪替 / 撤銷 完全沿用 pm_41(公司後台「API 金鑰自助申請」或 AMS 軌道 B);本模組僅擴充 API 回傳範圍職缺曝光規則

FR-3.2 職缺綁定夥伴(原「獨佔」)

企業 HR 發布職缺時可選擇「綁定夥伴獵頭」(如奧德士)→ 寫入 bound_partner_company_id(FK → companies.id)。

綁定後行為

面向行為
WPORT 公開站(搜尋 / 職缺大廳 / SEO)不出現(即使 visibility=PUBLIC
發職缺企業後台✅ 職缺可正常管理;人才庫照常收到應徵(pm_41 / pm_45)
奧德士官網✅ 經 API 同步後露出;求職者從官網導流進入投遞
歸因從夥伴導流進入時寫入 utm_source={partner_company_id}

解約(企業主動解除綁定)

  • HR 執行「解除夥伴綁定」→ bound_partner_company_id = NULL
  • 職缺恢復原本 visibility 的 WPORT 曝光規則。
  • 奧德士下次 API 同步不再收到該職缺;官網應下架(由奧德士端排程處理)。
  • 既有應徵紀錄與人才庫資料保留,不刪除。

非綁定職缺:bound_partner_company_id = NULL;夥伴 API 不可取得其他企業的職缺。

FR-3.3 API Key:沿用 pm_41(不重複定義)

能力說明
Key 類型wpk_live_(pm_41 SoT)
申請入口夥伴公司企業後台 → API 金鑰(pm_41 DR-8)
多把具名 Key≤ 10 把 / 公司(BR-API-CLI-004)
Scope 勾選沿用 pm_41;新增 scope:jobs:partner_bound_read(僅夥伴公司可勾)
到期 / 輪替強制 30/60/90 天 + key-for-key 輪替(BR-API-CLI-008/009)
權限該 scope 下唯讀;僅 GET 已綁定給該夥伴公司的職缺

FR-3.4 夥伴拉取已綁定職缺(擴充 pm_41 Enterprise API)

Authorization: Bearer wpk_live_xxxx
GET /v1/jobs?filter=partner_bound

後端流程(Key 須含 jobs:partner_bound_read scope):

  1. 驗證 wpk_live_ → 解析 company_id(奧德士的公司 ID)
  2. 確認該 company_idis_headhunter_partner = true
  3. 查詢條件強制注入:bound_partner_company_id = {company_id} AND status = 上架
  4. 不回傳未綁定職缺、草稿、已關閉職缺
  5. 分頁:page, page_size(max 100)

發職缺企業用自家 wpk_live_(職缺 scope)管自己的職缺;奧德士用自家 wpk_live_partner_bound scope)管別人綁給自己的職缺——同一套 Key 機制,不同 scope 與過濾邏輯

FR-3.5 夥伴官網導流 URL

API 回傳的 job_url 供奧德士官網投放,格式語意:

https://wport.co/job/{job_id}/{slug}?token={signed_token}&utm_source={partner_company_id}
  • signed_token:HMAC-SHA256,payload 含 {job_id, partner_company_id, bound_at}效期跟隨綁定生命週期(綁定存在即有效,解約即失效)。
  • 驗證失敗或已解約 → 職缺頁 403「此連結已失效」。
  • 無 token 的 WPORT 公開直達:綁定中職缺 → 404(不在公開站露出)。
  • Token 驗證通過 → 可投遞;寫入 utm_source 歸因 Cookie;應徵進發職缺企業的人才庫。

FR-3.6 與 pm_41 的邊界(已決)

維度pm_41pm_46 模組 3
API Key 機制wpk_live_、自助申請、scope、到期沿用,僅新增 jobs:partner_bound_read scope
人才庫 UI企業後台列表、應徵/來訪沿用;綁定職缺應徵照常出現
職缺曝光一般上架規則綁定夥伴 → WPORT 公開隱藏、夥伴官網露出
夥伴帳號公司帳號 + 企業後台沿用,不建獨立機構後台

5. Non-Functional Requirements(非功能性需求)

5.1 資安(Security)

項目要求
API Key 儲存僅存 bcrypt / SHA-256 hash;明文不落庫
Key 傳輸僅 HTTPS;禁止出現在 URL query
Signed TokenHMAC-SHA256 + 伺服器端 secret(KMS 管理);secret 每季輪替
歸因防篡改後端驗證推薦人有效性;不信任客戶端單方面宣告
IDOR 防護清單/職缺/夥伴綁定操作皆驗證擁有權或指派關係
Secret scanningwpk_live_ 前綴(pm_41)支援 GitHub Secret Scanning
Audit Log沿用 pm_41 Key 使用紀錄;partner_bound 查詢另記 partner_company_id
PII歸因紀錄不額外收集求職者敏感資料;僅關聯既有 Applications

5.2 效能(Performance)

項目目標
歸因 Cookie 寫入前端同步完成,不阻塞頁面渲染(< 5ms)
應徵寫入含歸因驗證p95 < 100ms 額外開銷
GET /v1/jobs?filter=partner_boundp95 < 500ms(100 筆/頁)
清單公開頁LCP < 2.5s(50 職缺以內)
歸因驗證查詢推薦人有效性查詢走 Redis 快取(TTL 5min)

5.3 API Rate Limiting

端點限制回應
GET /v1/jobs?filter=partner_bound300 req/min per API Key(沿用 pm_41)429 + Retry-After
GET /v1/jobs/{id}600 req/min per API Key同上
歸因點擊寫入(前端 beacon)60 req/min per IP靜默丟棄
清單公開頁120 req/min per IP429
月配額沿用 pm_41 公司月配額 5000(夥伴查詢計入該公司 quota)429 + 用量 header

回應 Header(沿用 pm_41):

X-RateLimit-Limit: 300
X-RateLimit-Remaining: 287
X-RateLimit-Reset: 1720000000

5.4 可用性與可靠性

項目要求
歸因系統可用性歸因服務故障時,應徵主流程不受阻(降級為無歸因)
API Key 輪替沿用 pm_41 key-for-key 輪替與過期換新(BR-API-CLI-009)
資料一致性Applications.referrer_* 一經寫入不可修改(防事後篡改)
跨裝置歸因以 Cookie 為主;跨裝置不延續(v1 不支援)

5.5 合規與隱私

  • Cookie 橫幅:v1 顯示 Cookie 同意橫幅(業界常規);wport_ref 歸因 Cookie 列為功能性必要 Cookie,於 Cookie / 隱私政策揭露用途(記錄推薦來源以完成應徵歸因);使用者接受網站 Cookie 後寫入(§11 #2 已決)。
  • 推薦人可見範圍:企業 HR 可看應徵的推薦來源(pm_41 人才庫);推薦人不可看到求職者 PII(v1)。
  • 夥伴 API 回傳資料不含求職者個資(僅職缺資訊)。

5.6 可觀測性

  • 指標:referral_capture_rate, referral_apply_attribution_rate, partner_bound_api_error_rate
  • 告警:夥伴 API 5xx > 1% 持續 5 分鐘 → PagerDuty
  • 歸因異常:單日自推嘗試 > 100 次 → 安全日誌

6. Data Models(資料結構模型)

本節為 RD 建表參考。外鍵關聯以 標示。索引建議附於各表末。

6.1 ER 關聯總覽

erDiagram
    Companies ||--o{ Jobs : publishes
    Companies ||--o{ Jobs : "bound_partner"
    Users ||--o{ JobLists : owns
    JobLists ||--o{ JobListItems : contains
    Jobs ||--o{ JobListItems : "in list"
    Users ||--o{ JobHunterAssignments : "assigned to"
    Jobs ||--o{ JobHunterAssignments : "assigned job"
    Users ||--o{ Applications : applies
    Jobs ||--o{ Applications : "applied to"
    Users ||--o{ Applications : "referrer (user)"
    Companies ||--o{ Applications : "referrer (partner)"

ApiKey 實體沿用 pm_41,不在此重複定義 ER。

6.2 jobs(升級)

在既有 jobs 表新增欄位:

欄位型態約束說明
visibilityENUM('PUBLIC','SECRET_URL','DRAFT')NOT NULL, DEFAULT 'DRAFT'可見性三態
bound_partner_company_idBIGINT UNSIGNEDNULL, FK → companies.id綁定之夥伴獵頭公司(如奧德士);NULL = 未綁定
bound_atTIMESTAMPNULL綁定生效時間
secret_url_tokenVARCHAR(64)NULL, UNIQUESECRET_URL 職缺的非猜測路徑 token

索引

  • idx_jobs_visibility_status (visibility, status) — 搜尋過濾
  • idx_jobs_bound_partner (bound_partner_company_id, status) — 夥伴 API 查詢
  • idx_jobs_secret_token (secret_url_token) — URL 直達

遷移策略

  • 既有上架職缺 → visibility = 'PUBLIC'
  • 既有草稿 → visibility = 'DRAFT'
  • bound_partner_company_id 預設 NULL(不影響既有職缺)

6.3 companies 擴充(夥伴獵頭公司)

在既有 companies 表新增(或等價設定表):

欄位型態約束說明
is_headhunter_partnerBOOLEANNOT NULL, DEFAULT false是否為簽約夥伴獵頭(如奧德士)
partner_slugVARCHAR(100)NULL, UNIQUE對外識別用(如 odexus

索引idx_companies_partner (is_headhunter_partner) WHERE is_headhunter_partner = true

奧德士 = WPORT 上的一間 Company + is_headhunter_partner=true;API Key 走 pm_41 api_keys 表,不另建 agency_api_keys

6.4 job_lists(蒐藏清單主表)

欄位型態約束說明
idBIGINT UNSIGNEDPK
enc_idVARCHAR(32)NOT NULL, UNIQUE對外 UUID
owner_user_idBIGINT UNSIGNEDNOT NULL, FK → users.id獵頭 / KOC
titleVARCHAR(100)NOT NULL清單標題
descriptionTEXTNULL
cover_image_urlVARCHAR(500)NULL
statusENUM('active','archived','deleted')NOT NULL, DEFAULT 'active'
created_atTIMESTAMPNOT NULL
updated_atTIMESTAMPNOT NULL

索引idx_job_lists_owner (owner_user_id, status)

約束:每 owner_user_idactive 清單 ≤ 20

6.5 job_list_items(清單職缺關聯表)

欄位型態約束說明
idBIGINT UNSIGNEDPK
job_list_idBIGINT UNSIGNEDNOT NULL, FK → job_lists.id
job_idBIGINT UNSIGNEDNOT NULL, FK → jobs.id
sort_orderINT UNSIGNEDNOT NULL, DEFAULT 0手動排序
added_atTIMESTAMPNOT NULL

索引

  • uniq_list_job (job_list_id, job_id) — UNIQUE,防重複加入
  • idx_list_items_job (job_id)

約束:每 job_list_id 的 items ≤ 50

6.6 job_hunter_assignments(秘密職缺指派)

欄位型態約束說明
idBIGINT UNSIGNEDPK
job_idBIGINT UNSIGNEDNOT NULL, FK → jobs.id
hunter_user_idBIGINT UNSIGNEDNOT NULL, FK → users.id
assigned_by_user_idBIGINT UNSIGNEDNOT NULL, FK → users.id企業 HR
assigned_atTIMESTAMPNOT NULL
revoked_atTIMESTAMPNULL

索引uniq_job_hunter (job_id, hunter_user_id) — UNIQUE

6.7 applications(歸因欄位擴充)

在既有 applications 表新增:

欄位型態約束說明
referrer_typeENUM('user','partner')NULL推薦人類型;NULL = 直接投遞
referrer_idBIGINT UNSIGNEDNULLuserusers.idpartnercompanies.id(夥伴公司)
referral_captured_atTIMESTAMPNULL歸因點擊時間(非應徵時間)
referral_landing_urlVARCHAR(500)NULL首次著陸 URL(稽核用)
referral_job_list_idBIGINT UNSIGNEDNULL, FK → job_lists.id若來自清單,記錄清單 ID

索引idx_applications_referrer (referrer_type, referrer_id, created_at)

設計決策(Eric 已決):v1 不建獨立 referrals / referral_click_events 表;僅在投遞成功時寫入 applications 歸因欄位。應徵者照常進入發職缺企業的人才庫(pm_41 UI)。


7. Edge Cases(異常邊界情況與防呆設計)

7.1 歸因相關

#情境系統行為UI / API 回饋
E-01使用者點擊 ?ref=自己 的連結寫入 Cookie,但應徵時後端拒絕歸因應徵成功,無推薦標記
E-0230 天後才投遞歸因過期,不寫入 referrer_*應徵成功,顯示「直接投遞」
E-03先點獵頭 A,再點獵頭 B,再投遞Last Click → 歸因 BHR 看到推薦人 B
E-04先點獵頭連結,再從 Google 直接進入同職缺若第二次無 ref → 清除歸因直接投遞
E-05推薦人帳號在投遞前被刪除驗證失敗,不寫入歸因應徵成功
E-06Cookie 被阻擋,僅 localStorage 可用fallback 讀取 localStorage對使用者透明
E-07跨瀏覽器投遞(手機點、桌機投)v1 不延續歸因直接投遞
E-08refutm_source 同時存在優先 ref(個人)
E-09同一職缺 72h 內重複投遞拒絕投遞(BR-011)顯示冷卻倒數
E-10機器刷 ref 點擊同 IP+ref+job 每小時 > 50 次不更新 Cookie靜默

7.2 職缺可見性

#情境系統行為UI / API 回饋
E-11存取 DRAFT 職缺 URL403(非公司成員)「職缺不存在」
E-12SECRET_URL 無 token 直達404「職缺不存在」
E-13職缺上架中改為 DRAFT已分享連結失效404
E-14綁定夥伴的職缺無 Signed Token 從 WPORT 公開進入404(不在公開站露出)「職缺不存在」
E-15企業 HR 主動解約(解除綁定)bound_partner_company_id 清空;舊導流 URL / Token 立即失效403「此連結已失效」;夥伴 API 下次同步撈不到
E-16綁定夥伴的職缺仍有應徵者應徵紀錄保留於發職缺企業人才庫(pm_41)HR 照常於人才庫查看
E-17職缺已關閉但清單仍含該項清單顯示灰階 boundary「此職缺已關閉」,連結停用

7.3 職缺蒐藏清單

#情境系統行為UI / API 回饋
E-18加入未指派的 SECRET_URL 職缺API 拒絕403 JOB_NOT_ELIGIBLE
E-19清單擁有者被停權清單 URL 回 410「此清單已下架」
E-20清單超過 50 職缺上限拒絕新增「已達清單上限(50)」
E-21使用者已有 20 組清單拒絕建立「已達清單數量上限(20)」
E-22URL ref 與清單擁有者不同點擊職缺時以清單擁有者為準對獵頭透明(防止搶歸因爭議)
E-23職缺從公開改秘密且未指派自動從所有清單移除通知清單擁有者
E-24綁定夥伴的職缺被加入 KOC 清單拒絕加入(不在 WPORT 公開可選範圍)403 JOB_NOT_ELIGIBLE

7.4 夥伴 API(pm_41 擴充)

#情境系統行為UI / API 回饋
E-25API Key 已撤銷(pm_41)401{"error":"invalid_api_key"}
E-26超過 rate limit429 + Retry-After標準 header
E-27Key 無 jobs:partner_bound_read scope403insufficient_scope
E-28非夥伴公司用 partner_bound 查詢403not_a_headhunter_partner
E-29夥伴請求未綁定給自己的職缺 ID404不揭露職缺存在
E-30Key 過期輪替(pm_41 BR-API-CLI-009)僅 rotate endpoint 接受過期 Key

7.5 Edge Case Coverage Matrix

類別情境系統行為UI 回饋可重試資料一致性
Network應徵 API timeout不寫入 application逾時提示 + 重試Yes冪等鍵防重複
Network夥伴 API 高延遲超時 30s 斷開429/504Yes唯讀,無副作用
Data/Input無效 ref 非數字忽略,不寫 Cookie正常瀏覽N/AN/A
Data/Inputref 指向不存在用戶不寫 Cookie正常瀏覽N/AN/A
Interruption投遞中離開頁面以 server 應徵結果為準回訪顯示結果Yes冪等
AuthSession 過期後投遞導登入,保留歸因 Cookie登入後回跳YesCookie 30 天內有效
Logical歸因服務掛掉降級:應徵成功無歸因無感知N/A事後補償不允許

8. 商業規則對齊與新增提案

8.1 沿用的既有規則

規則 ID摘要關聯模組
BR-001Email 驗證後才能登入建立清單、投遞
BR-008職缺發布必填欄位PUBLIC 上架
BR-009職缺狀態與可見性三態擴充
BR-01172h 再投冷卻防重複投遞
BR-012帳號刪除檢查推薦人有效性
BR-015搜尋僅顯示上架職缺PUBLIC未綁定夥伴
BR-016角色權限HR 指派秘密職缺、解約夥伴綁定
BR-API-CLI-004~010pm_41 API 金鑰規則夥伴 Key 沿用

8.2 建議新增規則(待回寫 business-rules.md)

規則 ID摘要
BR-039應徵歸因:Last Click、30 天窗口、禁止自推、歸因寫入後不可修改;v1 僅寫 applications
BR-040職缺可見性三態;綁定夥伴的職缺不進 WPORT 公開搜尋,改由夥伴官網曝光
BR-041職缺蒐藏清單:每用戶 ≤ 20 組、每組 ≤ 50 職缺
BR-042夥伴綁定:HR 可綁定/解約;綁定期間僅夥伴可 API 同步;應徵仍進發職缺企業人才庫
BR-043夥伴拉取職缺:沿用 pm_41 wpk_live_ + scope jobs:partner_bound_read(不另建 Key 體系)

9. Flowcharts

9.1 歸因 User Flow

flowchart TD
    A[求職者點擊帶 ref/utm 的連結] --> B{URL 參數有效?}
    B -->|否| C[正常瀏覽,不寫歸因]
    B -->|是| D[寫入 Cookie/LocalStorage 30天]
    D --> E[瀏覽職缺/清單]
    E --> F{點擊投遞?}
    F -->|否| G[離開或繼續瀏覽]
    F -->|是| H{已登入且履歷就緒?}
    H -->|否| I[導登入/補資料]
    I --> F
    H -->|是| J[後端驗證歸因]
    J --> K{通過?}
    K -->|是| L[建立 Application + referrer]
    K -->|否| M[建立 Application,referrer=null]
    L --> N[投遞成功]
    M --> N

9.2 清單 → 歸因 System Flow

sequenceDiagram
    participant Seeker as 求職者
    participant ListPage as 清單頁
    participant Cookie as 歸因 Cookie
    participant JobPage as 職缺頁
    participant API as 後端 API

    Seeker->>ListPage: GET /list/{id}?ref={hunter}
    ListPage->>Cookie: 寫入 ref=hunter(Last Click)
    Seeker->>ListPage: 點擊職缺 A
    ListPage->>JobPage: 導向 /job/A?ref={owner_id}(強制注入)
    JobPage->>Cookie: 覆寫 ref=owner_id
    Seeker->>API: POST 應徵 + referral payload
    API->>API: 驗證 ref≠self、未過期、推薦人有效
    API-->>Seeker: 201 應徵成功

9.3 夥伴職缺同步 System Flow

sequenceDiagram
    participant Odexus as 奧德士官網/ATS
    participant API as WPORT API(pm_41)
    participant DB as 資料庫
    participant HR as 發職缺企業人才庫

    Odexus->>API: GET /v1/jobs?filter=partner_bound(wpk_live_)
    API->>API: 驗證 scope + is_headhunter_partner
    API->>DB: SELECT jobs WHERE bound_partner_company_id=奧德士 AND status=上架
    DB-->>API: 職缺列表(含導流 URL)
    API-->>Odexus: 200 JSON
    Note over Odexus: 職缺露出於奧德士官網(非 WPORT 公開站)
    Odexus->>API: 求職者經導流 URL 投遞
    API->>HR: 應徵寫入 applications + 人才庫(pm_41 UI)

10. 版本凍結(v1 / v2)

項目v1v2Blocking?
歸因原則Last ClickFirst Click / Multi-touchNo
跨裝置歸因不支援登入後帳號級歸因No
推薦人儀表板No
佣金結算No
夥伴 API 寫入職缺視合約No
Webhook應徵事件推送No
referral_click_events不做點擊漏斗(v2)No
清單 Kanban / 排序拖拉手動 sort_order拖拉 UINo

11. PRD 問題清單

#PRD 章節問題決議狀態
1FR-3.1機構管理員帳號體系公司帳號 + pm_41 公司後台自助 Key;奧德士 = is_headhunter_partner 的 Company✅ 已決(Eric 2026/07/03)
2FR-1.2Cookie 同意橫幅要顯示wport_ref 列功能性必要 Cookie,接受後寫入✅ 已決
3§6.7獨立 referralsv1 不建;僅 applications 歸因欄位✅ 已決
4FR-3.5Signed Token 效期跟隨綁定生命週期;解約即失效(非固定 90 天)✅ 已決
5§2.2綁定後人才庫行為應徵仍進發職缺企業 pm_41 人才庫;僅職缺 listing 不在 WPORT 公開露出✅ 已決(Eric 2026/07/03)

12. 下一步

  1. 回寫 BR-039 ~ BR-043 至 business-rules.md
  2. 模組 1(P0)後端 + 前端 Cookie 邏輯先行開發
  3. 與 pm_41 RD 對齊:新增 scope jobs:partner_bound_readGET /v1/jobs?filter=partner_bound
  4. 模組 2 清單頁 Storybook mockup(gen-storybook-mockup-gen
  5. 與奧德士 BD 對齊官網同步與解約流程

文件結束