---
# System prepended metadata

title: 開發環境

---

開發環境
===
- 開發語言
    - 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`)