專案概述
DenTalk 是一個專為牙醫師及相關領域打造的綜合性平台。 這是一個功能複雜、需求繁多的大型專案,整合了論壇、求職、商城、課程四大核心功能, 創造一個完整的牙醫師生態圈。
專案類型: Fullstack 全端開發
開發時間: 2024
主要技術: Laravel, Astro, 綠界金物流
核心功能: 論壇、求職、商城、課程
平台特色
DenTalk 最大的特色在於其複雜的多角色架構。 這並不是由業主在後台上傳資料,然後顯示到前端的傳統模式, 而是作為一個平台,讓不同角色的前端用戶可以在上面刊登、使用。
這使得 API 架構變得極為複雜,必須考慮:
- 不同用戶視角 - 醫師、診所、講師、學員等多種身份
- 不同需求用途 - 瀏覽、刊登、管理、購買等多種操作
- 權限與資料可見性 - 根據身份決定可見與可操作的資料範圍
💬 論壇功能
符合權限的用戶可以在平台上刊登文章,討論牙醫相關議題。 系統提供彈性的身份顯示設定,用戶可以選擇使用自訂暱稱或完全匿名發文, 保護隱私的同時也能暢所欲言。
論壇具備完整的社交功能,包括按讚、留言、分享等互動機制。 文章依照討論主題分類,用戶可以依據感興趣的類別, 輕鬆找到相關討論並參與互動。
討論論壇介面
🛒 商城功能
擁有販賣資格的用戶可以建立自己的商城,類似蝦皮的賣家帳號概念。 系統提供完整的商店管理功能,包括設定寄件資訊、上架商品、填寫金流物流相關內容等。
商城支援多規格類型的產品展示,買家可以像在蝦皮一樣點選不同規格組合。 完整的購物車與結帳流程,並串接綠界金流與物流,提供安全便利的交易體驗。
商品列表頁
賣家後台管理
多規格選擇
購物車系統
💼 求職功能
醫院、診所可以刊登職缺,而牙醫師、牙醫事相關領域人員、實習生則可以刊登履歷。 平台提供雙向媒合機制,讓雇主找到合適人才,求職者找到理想職缺。
列表頁具備細緻的篩選功能,可以根據地點、薪資、職務類別、評分高低等條件精準搜尋。
最特別的是針對牙醫產業客製化的班表功能。 一般牙醫診所的職缺可以區分為早診、午診、晚診, 系統提供行事曆班表設定,讓醫師能夠清楚看到可配合的上班時間, 大幅提升媒合效率。
新增職缺
篩選功能
特製班表
履歷列表
履歷詳情
職缺詳情
班表系統
📚 課程功能
講師身份的用戶可以在平台上架課程,每一個課程可以設定多個場次循環, 同一個場次又能包含多個課程設定,提供彈性的課程安排。
系統提供QR Code 掃描點名功能,讓講師能夠快速完成學員簽到。
更進階的是完整的考卷系統。講師可以在每個課程場次結束後發布考試, 題型支援單選、多選、是非、申論等多種形式, 讓講師能夠自訂適合的測驗題目,檢驗學員學習成效。
課程列表
課程詳情頁
專案成果
4 大
核心功能模組
多角色
複雜權限系統
完整
金物流串接
客製化
產業專屬功能
多重身份的設定
User 資料表是系統中最頻繁被查詢的資源之一。為了減少查詢次數和 JSON 資料的消耗, 我們選擇將多重身份資訊儲存在同一張表的單一欄位中,採用 Bitmask(位元遮罩)的方式處理。
Bitmask 實作
以下是 EnumIdentity 的實作,透過位元運算(1 << n)定義 12 種不同身份類型,
包括牙醫師、診所、醫院、講師、學生等。每個身份對應一個獨立的位元位置,可以透過位元 OR 運算組合多重身份。
<?php
namespace App\Enum;
use Illuminate\Support\Collection;
enum EnumIdentity: int
{
case NULL = 0;
case DENTIST = 1 << 0; // 牙醫師
case DENTAL_ASSISTANT = 1 << 1; // 牙科助理
case DENTAL_HYGIENIST = 1 << 2; // 口腔衛生師
case DENTAL_TECHNICIAN = 1 << 3; // 牙體技術師
case INTERN = 1 << 4; // 實習生
case STUDENT = 1 << 5; // 學生
case CLINIC = 1 << 6; // 診所
case HOSPITAL = 1 << 7; // 醫院
case VENDOR = 1 << 8; // 廠商
case INSTRUCTOR = 1 << 9; // 講師
case ASSOCIATION = 1 << 10; // 公會
case SOCIETY = 1 << 11; // 協會
public function getLabel(): string { /* ... */ }
public function getGroup(): string { /* ... */ }
// 權限判斷方法
public function isJobProvider(): bool { /* 醫院、診所 */ }
public function isJobSeeker(): bool { /* 牙醫師、助理等 */ }
public function isCourseProvider(): bool { /* 講師 */ }
public function canSell(): bool { /* 可販售權限 */ }
// Bitmask 雙向轉換
public static function fromBitmask(int $bitmask): Collection
{
// 將整數轉換為身份集合
return collect(self::cases())
->filter(fn ($case) => ($bitmask & $case->value) !== 0);
}
public static function toBitmask(array|Collection $identities): int
{
// 將身份集合轉換為整數
return collect($identities)
->map(fn ($identity) => $identity->value)
->reduce(fn ($carry, $val) => $carry | $val, 0);
}
} 技術優勢
- 資料庫效能 - 單一欄位儲存,減少 JOIN 查詢,提升查詢速度
- 彈性組合 - 使用者可同時擁有多種身份(如:牙醫師 + 講師)
- 權限管理 - 透過
isJobProvider()、canSell()等方法快速判斷權限 - 身份分組 -
getGroup()方法可將相關身份歸類(如:牙醫事相關人員)
聊天室系統架構
考慮到 Pusher.js 的費用高昂,本專案採用 Laravel 官方推薦的 Laravel Reverb 自架 WebSocket Server,大幅降低營運成本。
架構說明
- 前端用戶 ↔ Reverb - 前端透過 WebSocket 與 Reverb Server 建立持久連線
- Laravel → Reverb - Laravel 後端透過 Broadcasting 將訊息推送至 Reverb
- Reverb → 前端 - Reverb Server 即時轉發訊息給所有訂閱該頻道的前端用戶
技術優勢
- 成本控制 - 自架 Server,無需支付第三方 WebSocket 服務費用
- 官方支援 - Laravel 原生整合,API 使用簡單直覺
- 擴展性佳 - 可依需求調整 Server 規格,靈活控制連線數
- 即時通訊 - WebSocket 連線提供低延遲的雙向通訊體驗
Laravel Reverb 架構圖
訂單複雜的狀態管理
商城功能涉及複雜的訂單狀態轉換,從下單、付款、出貨到完成,每個階段都有嚴格的狀態限制。
為了避免程式碼中充斥大量的 if/else 條件判斷,本專案採用 State Machine(狀態機)的設計模式。
State Machine 優勢
- 清晰的狀態定義 - 明確定義所有可能的訂單狀態(待付款、已付款、配送中、已完成、已取消等)
- 嚴格的轉換規則 - 限制狀態只能按照預定規則轉換,避免非法狀態出現
- 易於維護 - 避免複雜的條件判斷巢狀,新增狀態或修改流程時更加安全
- 程式碼可讀性 - 狀態轉換邏輯集中管理,容易理解整體業務流程
透過 State Machine 模式,即使訂單流程再複雜,也能保持程式碼的簡潔與可維護性。