開發環境 === - 開發語言 - php 8.4.1 - 資料庫 - MariaDB 10.6.20 - 框架 - laravel 11 - 所有資料庫異動、測試資料或新資料安裝,規定一律透過 Laravel 的 factories, migrations (需包含 up 與 down 功能), seeders, Factory, Schema 進行管控。 - Laravel 套件一律依賴 composer.json,Git 不儲存 vendor 目錄。 - squizlabs/php_codesniffer 套件用於驗證程式碼是否符合 PSR-12 風格。 - API 長時間執行一律使用 Jobs queue 處理並立即返回,不讓前端等待。執行完成後的通知機制,應根據專案需求在開發初期明確定義,可選用 WebSocket 或輪詢等方式。 - 資料傳入驗證,規定統一使用 Illuminate\Foundation\Http\FormRequest 進行。中間層可使用 Middleware 進行判斷,複雜驗證則封裝為 ValidationRule 供 FormRequest 使用。 - 傳入 class 參數,應使用自動注入 (Dependency Injection)。 - 變數一律明定型別。 - Model 關聯應使用 hasMany, belongsTo, hasOne 等方法定義。查詢時必須使用 with() 進行預載入以避免 N+1 問題。為確保查詢效能,在必要時可使用 join,但需經過評估。 - 共用函數統一寫在 helpers.php 檔案中進行管理。 - 共用跨 class 的方法,統一使用 Trait 進行管理。 - 系統應建立快取機制。設定檔等不常變動的資料,應優先從快取 (如 Redis, file) 讀取,避免直接存取資料庫。 - 所有程式方法都必須附上中文註解,清楚說明其用途。 - 每個 API 應使用獨立的 Controller,並以 `__invoke` 方法作為統一進入點。 - Controller 依功能模組區分資料夾,例如:發票 (Invoice)、金流 (Payment)。 - Route 設定檔 (如 payment.php) 應依功能模組區分,並在 `app\Providers\RouteServiceProvider` 的 boot 方法中引入。每個 API 都應有獨立的 route 設定。 - vendor 目錄不提交至 Git。composer.json 中的版本號必須寫死固定版本 (例如 "1.2.3"),禁止使用 `*` 或 `^` (例如 "^1.0"),以確保每次部署時都使用穩定且一致的套件版本。更新套件時,應直接修改版本號。 - 系統 - alpine 3.20 - swoole - v5.1.2 - octane - v2.3.6 - bref/bref - 2.1.19 - linux-firmware-cis - linux-firmware-cis 3.20版 - 必要模組 - laravel/octane (swoole),參考 https://laravel.com/docs/11.x/octane - bref/bref - 程式風格 - PSR-12:規定使用 laravel 套件 squizlabs/php_codesniffer 檢查,通過後才可執行後續測試。 - 變數命名:採小駝峰命名法 (lowerCamelCase),例:`$cashFlowCode = new CashFlowCode();` - Class 命名:採大駝峰命名法 (UpperCamelCase),例:`class CashFlowCode {}` - 方法與函數命名:採小駝峰命名法 (lowerCamelCase),且命名必須具體有意義,使開發者能從名稱直接理解其用途。例如,「重新格式化總和記錄」命名為 `reformatSumRecords`。回傳多筆資料時,名稱使用複數 (records);單筆則使用單數 (record)。 - 單獨函數:採 snake_case 風格,即全小寫並以底線 `_` 分隔,例:`reformat_sum_records`。 - 對外API命名規則不在此限,依循對外發布的標準。 - 獨立方法設計:在設計連動方法時,每個方法都必須保持獨立,各自負責單一功能。例如,建立訂單後觸發的「寄信」與「開發票」應為兩個獨立的 Queue Job,各自接收訂單 ID 並撈取所需資料。此設計可確保其中一項任務失敗時,不影響其他任務,並可針對失敗任務單獨重試。 - 錯誤處理 - 通知:透過 `TelegramNotifier` 方法,經由 Google Chat 發送通知。 - 紀錄:使用 `Log::error` 記錄錯誤,內容必須明確標示錯誤的檔案與方法。 - 範例:`CompanyInvoiceService manualPosting 手動入帳觸發交易異常,交易的tmp id:` - 機敏資訊:禁止在 Log 中記錄任何機敏資訊。若流程需要處理機敏資訊,應將資料存入資料庫,由 Queue Job 讀取執行。若發生錯誤,僅記錄該筆資料的 ID,待修復後,可針對此 ID 進行手動重試。 - 原生方法微調 - Queue 使用 `dispatch` (排隊執行) 或 `dispatch_sync` (立即執行)。若需立即執行但不想等待回應,使用 `dispatch(new Job())->afterResponse()`。 - 如果有排隊要使用,概念是同一個群組依序執行->dispatch(new InventoryCenter('Voucher', []))->onConnection(config('queue.fifo'))->withMessageGroupId('這是群組名'); - 測試 - 測試分為 PHPUnit 的 Feature (整合測試) 與 Unit (單元測試)。 - 所有 API 端點皆須提供 Postman Tests 作為輔助驗證。 - Docker - 專案需包含 `docker-compose.yml` 檔案,可使用 `docker-compose up -d` 運行及 `docker-compose down` 中止程式。 - 登入API驗證碼 - 前後端統一使用 Cloudflare Turnstile: https://www.cloudflare.com/zh-tw/products/turnstile/ - 開發重要目錄結構說明 ``` ├─Http │ ├─Controllers │ │ ├─Api 負責內部的API │ │ ├─OuterApi 負責對外的API │ │ └─WebApi 負責WEB內部的API,底下必須包含自己的處裡目錄 │ │ └─Product 例如:商品相關,再往下細分商品各自的項目,基本上就是2層 │ │ ├─Class 例如:商品分類 │ │ └─Main 例如:商品本身 │ └─Requests 這邊負責validation的定義 │ ├─Api 負責外部的API │ ├─Constants 各自功能的常數定義 │ ├─Cors 核心常數定義,亦即他的常數是大家公認的例如CashFlowCode │ └─WebApi 負責內部的API │ ├─Class 例如:商品分類validation │ └─Main 例如:商品本身validation ├─Libraries │ └─Definition 沒有再目錄夾內的為全域共用 │ ├─Common 常數定義檔,不區分內外部,依樣劃分處裡目錄 │ │ └─Product 例如:商品 │ │ └─Main 例如:商品本身 │ │ │ ├─Inputs 內部API請求Request,劃分處裡目錄,如果Request的資料有常數定義,但常數不屬於共用的,也放在這 │ │ └─Product 例如:商品 │ │ ├─Class 例如:商品分類 │ │ └─Main 例如:商品本身 │ │ │ ├─Outputs 內部API回應Response,劃分處裡目錄 │ │ └─Product 例如:商品 │ │ ├─Class 例如:商品分類 │ │ └─Main 例如:商品本身 │ │ │ ├─Requests 外部API請求Request,劃分處裡目錄,如果Request的資料有常數定義,但常數不屬於共用的,也放在這 │ │ └─Product │ │ ├─Class 例如:商品分類 │ │ └─Main 例如:商品本身 │ │ │ │─Responses 內部API回應Response,劃分處裡目錄 │ │ └─Product 例如:商品 │ │ ├─Class 例如:商品分類 │ │ └─Main 例如:商品本身 │ │ │ └─Services 邏輯處裡層的核心定義,劃分處裡目錄 │ └─Product 例如:商品 │ └─Main 例如:商品本身 │ ├─Repositories 資料庫互動處裡,基本上只做資料庫的存取,除非不得已,不然不再此做邏輯處裡 │ └─Api 不區分目錄 │ └─Services 邏輯處裡層 │ ├─Core 邏輯核心處哩,這部份主要是處里內外層都會用到的處理邏輯,並將請求跟輸出的格式都定義清楚,嚴格依照定義的格式內容,每個定義的欄位都需要寫好預設值 │ └─Product 例如:商品 │ └─Main 例如:商品本身 │ ├─Validation 負責定義檔的驗證 │ ├─Api 負責外部的API │ └─WebApi 負責內部的API,底下必須包含自己的處裡目錄 │ └─Product 例如:商品 │ └─Main 例如:商品本身 │ ├─WebApi 接收內部請求的邏輯處裡,基本上都是將接收的資料,轉成給邏輯核心處理,核心處理完後,輸出的結果若有要調整,也是在自己的內部處理調整 │ └─Product 例如:商品 │ └─Main 例如:商品本身 │ └─Api 接收外部請求的邏輯處裡,基本上都是將接收的資料,轉成給邏輯核心處理,核心處理完後,輸出的結果若有要調整,也是在自己的內部處理調整 ``` - Drone (CI/CD) //現階段人手不足放棄 - **觸發**:利用 Drone 連結 GitHub,當 git push 事件發生後,自動觸發 CI/CD 流程。 - **部署目標**:依 git flow 規則,`staging` 分支對應測試區,`master` 分支對應正式區。 - **CI 流程**: 1. 驗證 PHPCS (PSR-12 風格)。 2. `install-packages` (安裝 Laravel 套件)。 3. 執行 PHPUnit Feature (整合測試)。 4. 執行 PHPUnit Unit (單元測試)。 5. 所有測試通過後,方可進行 CD 流程。 - **CD 流程**: 1. 封裝 Image 並上傳至 Docker Hub。 2. 部署至對應的正式或測試環境。 3. 上傳最新的 Swagger yml 至 Swagger Server。 - **部署策略**:部署流程中的資料庫遷移 (migrations) 規定採用手動執行模式,以確保生產環境的穩定與可控性。自動化遷移僅限於測試或開發環境。 - **版本管理**:Docker Hub 用於存放各版本 Image,當線上發生問題時,可手動選擇特定版本進行退版,以維持服務穩定。 - API文檔說明 - 文檔規定使用 Swagger (OpenAPI) 撰寫 yml 檔案,並提供 Swagger Server 進行線上即時測試與查閱回應內容。 - WEB API基本傳入格式 - `rqTS`:傳入時間,格式為 `(GMT+8)2015-12-02 04:34:55`。 - `cmd`:指令名稱。 - `locale`:資料交換語系。 - WEB API 當有需要時 - `search`:搜尋條件。 - `pagination`:分頁設定,格式為 `{"perPage": 10, "currentPage": 1}`。 - WEB API回傳格式 - **HTTP Status Code**:嚴格遵守 RESTful API 標準。 - `200`:請求成功。 - `201`:資源建立成功。 - `202`:請求已接收但未完成 (用於異步任務)。 - `204`:請求成功但無內容回傳。 - `3xx`:重定向。 - `4xx`:客戶端錯誤。 - `5xx`:伺服器錯誤。 - **Body 結構**: - `code`:API 內部狀態碼 ([200]正常, [100]資料不正確, [210]處理中, [300]處理失敗, [400]系統錯誤,[401]無效的登入)。若有新定義需經討論。 - `msg`:回應訊息。 - `rspTS`:回應時間,格式同 `rqTS`。 - `cmd`:同傳入指令。 - `locale`:同傳入語系。 - `errors`:錯誤細節,當 `code=300` 時提供。 - `data`:回傳資料。無資料時回傳 `null`。除了登入 API,所有 `data` 內容均需使用 token 進行加密。 - `exp`:回傳登出剩餘時間。 - `errors` 格式 (當 `code=300`) - `code` (string): 錯誤代碼。 - `msg` (string): 錯誤訊息。 - `pathData` (array): 指出發生錯誤的傳入值路徑。 - `path` (string): 欄位路徑,例如 `a3:aa1`。 - `msg` (array): 錯誤的詳細資訊。 - **範例**: - 傳入值:`{"a1":1,"a2":2,"a3":[{"aa1":"AA"}]}`,其中 `aa1` 名稱重複。 - 回應:`{"code":"A0001","msg":"名稱重複","pathData":[{"path":"a3:aa1","msg":["AA"]}]}` - 加密規則 - **PHP (Server-side)**: - 使用 `openssl_encrypt` 和 `openssl_decrypt`。 - `iv` 固定為登入 API 回傳 token 的前 16 碼。 - 範例: `openssl_encrypt($text, $method, $secret, 0, $iv);` - **JavaScript (Client-side)**: - 使用 CryptoJS。 - `iv` 同樣固定為登入 API 回傳 token 的前 16 碼。 - validation rule 規則 - 在撰寫 FormRequest validation 時,若遇到無法用單純 rule 處理的複雜情境,應使用 `withValidator` 或 `after` 方法。 - **範例一:根據 type 決定 content 的型別** ```php public function withValidator($validator) { $validator->sometimes('content', 'array', fn($input) => $input->type == 1); $validator->sometimes('content', 'string', fn($input) => $input->type == 2); } ``` - **範例二:根據陣列中某個值,決定另一個值是否必填** ```php public function withValidator($validator) { $data = $this->input('data'); foreach ($data as $index => $item) { $validator->sometimes("data.$index.cell_phone", 'required', fn($input) => $item && $item['phone_type'] === 1); $validator->sometimes("data.$index.phone", 'required', fn($input) => $item && $item['phone_type'] === 2); } } ``` - **範例三:根據不同條件,提供不同錯誤訊息** ```php public function withValidator($validator) { $validator->after(function ($validator) { $data = $this->get('data') ?? []; foreach ($data as $index => $item) { if ($item && $item['type'] === 1 && !in_array($item['mode'], [1, 2])) { $validator->errors()->add("data.mode", trans('error.mode_invalid_for_type1')); } if ($item && $item['type'] === 2 && !in_array($item['mode'], [1, 2, 3])) { $validator->errors()->add("data.mode", trans('error.mode_invalid_for_type2')); } } }); } ``` - 常數共用 - `MyPayAllPayModeForNumber`: 記錄所有支付工具的<span style="color:red;">數字常數</span>。 - `MyPayAllPayModeForString`: 記錄所有支付工具的<span style="color:red;">字串常數</span>。 - `CashSupportPayMode`: 定義現金交易類型支援的支付工具。 - `CashSupportPayModeGroup`: 現金類支付工具的群組化定義。 - **規則**:所有支付工具相關的常數,均須以此四個檔案為唯一來源,不得在其他地方另行定義。 - 程式命名規定 - **關於時間** - 起始時間: `startDate` - 結束時間: `endDate` - **狀態 (State vs. Status)** - `State`: 代表程式內部的運作狀態 (e.g., `isPlaying`)。 - `Status`: 代表向使用者顯示的結果 (e.g., 「正在播放」)。 - **規則**:若同時用於內部程序與外部顯示,則統一使用 `status`。 - **Class 命名** - **規則**:必須使用名詞,並依據功能使用以下強制定義的後綴。 - `list`: 分類 - `creator`: 新增 - `update`: 更新 - `delete`: 刪除 - `item`: 單筆資訊 - `import`: 匯入 - `view`: 純檢視 (後台,無權限) - `frontendView`: 純檢視 (前台) - `download`: 下載 - `inquirer`: 查詢 - `export`: 匯出 - `store`: 資料儲存 (新增/修改) - `changeStatus`: 改變狀態 - `front`: 無需登入的前台功能 - 資料庫命名 - **資料表** - **規則**:名稱一律使用複數,以 `s` 結尾。 - **關於時間** - 起始時間: `start_date` - 結束時間: `end_date` - **狀態 (State vs. Status)** - `State`: 內部運作狀態。 - `Status`: 外部顯示結果。 - **規則**:若同時使用,統一用 `status`。 - **其它** - 操作者: `operator` (關聯 `users->uid`)