# Public Stock Order API Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** Add two read-only public APIs (PI list, VIN list) for internal systems to query approved stock orders, authenticated via API Key. **Architecture:** New `PublicApiController` in `app/common/controller/` with a dedicated `ApiKeyAuth` middleware. Routes registered in a standalone route group that bypasses JWT and BasicData middleware. Pagination via Eloquent's `paginate()`. **Tech Stack:** PHP 8.1+, Webman framework, Eloquent ORM, illuminate/pagination --- ## File Structure | Action | File | Responsibility | |--------|------|----------------| | Create | `app/middleware/ApiKeyAuth.php` | Validate `Fdesk-Api-Key` header against env config | | Create | `app/common/controller/PublicApiController.php` | Two endpoints: PI list, VIN list | | Modify | `config/route.php` | Add `/common/public/*` route group with ApiKeyAuth middleware | --- ### Task 1: Create ApiKeyAuth Middleware **Files:** - Create: `app/middleware/ApiKeyAuth.php` - [ ] **Step 1: Create the middleware file** ```php header('Fdesk-Api-Key'); $validKey = getenv('PUBLIC_API_KEY'); if (!$apiKey || !$validKey || $apiKey !== $validKey) { $body = [ 'status' => 'fail', 'code' => 401, 'message' => '无效的 API Key', 'data' => [], ]; return response(json_encode($body, JSON_UNESCAPED_UNICODE), 401, ['Content-Type' => 'application/json']); } return $handler($request); } } ``` - [ ] **Step 2: Commit** ```bash git add app/middleware/ApiKeyAuth.php git commit -m "feat: add ApiKeyAuth middleware for public API endpoints" ``` --- ### Task 2: Create PublicApiController **Files:** - Create: `app/common/controller/PublicApiController.php` - [ ] **Step 1: Create the controller file** ```php input('page', 1)); $pageSize = (int)($request->input('page_size', 20)); $piNo = $request->input('pi_no', ''); if ($page < 1) $page = 1; if ($pageSize < 1 || $pageSize > 200) $pageSize = 20; $query = StockOrders::where('approval_status', 2) ->whereNotNull('pi_no') ->where('pi_no', '<>', ''); if ($piNo !== '') { $query->where('pi_no', 'like', "%{$piNo}%"); } $query->with(['items' => function ($q) { $q->select('id', 'order_id', 'vin', 'model_snapshot'); }]); $paginator = $query->orderBy('id', 'desc') ->paginate($pageSize, ['id', 'pi_no'], 'page', $page); $data = $paginator->getCollection()->map(function ($order) { return [ 'pi_no' => $order->pi_no, 'vins' => $order->items->pluck('vin')->filter()->values()->toArray(), 'model_names' => $order->items->pluck('model_snapshot')->filter()->values()->toArray(), ]; })->toArray(); return $this->success([ 'total' => $paginator->total(), 'per_page' => $paginator->perPage(), 'current_page' => $paginator->currentPage(), 'data' => $data, ]); } catch (\Throwable $e) { return $this->error($e->getMessage()); } } /** * VIN 号列表(含车型名称) * GET /common/public/vin/list */ public function vinList(Request $request) { try { $page = (int)($request->input('page', 1)); $pageSize = (int)($request->input('page_size', 20)); $vin = $request->input('vin', ''); if ($page < 1) $page = 1; if ($pageSize < 1 || $pageSize > 200) $pageSize = 20; $query = StockOrderItems::whereHas('order', function ($q) { $q->where('approval_status', 2); })->whereNotNull('vin') ->where('vin', '<>', ''); if ($vin !== '') { $query->where('vin', 'like', "%{$vin}%"); } $paginator = $query->orderBy('id', 'desc') ->paginate($pageSize, ['id', 'vin', 'model_snapshot'], 'page', $page); $data = $paginator->getCollection()->map(function ($item) { return [ 'vin' => $item->vin, 'model_name' => $item->model_snapshot, ]; })->toArray(); return $this->success([ 'total' => $paginator->total(), 'per_page' => $paginator->perPage(), 'current_page' => $paginator->currentPage(), 'data' => $data, ]); } catch (\Throwable $e) { return $this->error($e->getMessage()); } } } ``` - [ ] **Step 2: Commit** ```bash git add app/common/controller/PublicApiController.php git commit -m "feat: add PublicApiController with PI and VIN list endpoints" ``` --- ### Task 3: Register Routes **Files:** - Modify: `config/route.php` - [ ] **Step 1: Add the route group** 在 `config/route.php` 文件末尾(最后一个 `});` 之后)追加: ```php // 公共 API(API Key 鉴权,不走 JWT / BasicData) Route::group('/common/public', function () { Route::any('/pi/list', [app\common\controller\PublicApiController::class, 'piList']); Route::any('/vin/list', [app\common\controller\PublicApiController::class, 'vinList']); })->middleware([app\middleware\AccessControl::class, app\middleware\ApiKeyAuth::class]); ``` This route group only uses `AccessControl` (CORS) + `ApiKeyAuth`. It explicitly bypasses `BasicData` and `AuthCheck`. - [ ] **Step 2: Commit** ```bash git add config/route.php git commit -m "feat: register public API routes with ApiKeyAuth middleware" ``` --- ### Task 4: Configure API Key in Environment **Files:** - Modify: `.env` (or equivalent environment config) - [ ] **Step 1: Add the API key to the environment config** Add to the `.env` file (or equivalent environment variable source): ``` PUBLIC_API_KEY=your-secret-api-key-here ``` Generate a real key, e.g.: ```bash php -r "echo bin2hex(random_bytes(32)) . PHP_EOL;" ``` Use the generated value as the key. - [ ] **Step 2: Verify the middleware reads it correctly** The middleware uses `getenv('PUBLIC_API_KEY')`. Confirm this is consistent with how other env vars are read in the project (e.g., `getenv('DB_HOST')` in `config/database.php`). --- ### Task 5: Manual Verification - [ ] **Step 1: Test PI list endpoint (no auth)** ```bash curl -s http://localhost:8787/common/public/pi/list | php -r 'echo json_encode(json_decode(file_get_contents("php://stdin")), JSON_PRETTY_PRINT);' ``` Expected: `{"status":"fail","code":401,"message":"无效的 API Key"}` - [ ] **Step 2: Test PI list endpoint (with auth)** ```bash curl -s -H "Fdesk-Api-Key: your-secret-api-key-here" "http://localhost:8787/common/public/pi/list?page=1&page_size=5" | php -r 'echo json_encode(json_decode(file_get_contents("php://stdin")), JSON_PRETTY_PRINT);' ``` Expected: JSON with `status: success`, `data` containing `total`, `per_page`, `current_page`, and array of `{pi_no, vins, model_names}`. - [ ] **Step 3: Test VIN list endpoint (with auth)** ```bash curl -s -H "Fdesk-Api-Key: your-secret-api-key-here" "http://localhost:8787/common/public/vin/list?page=1&page_size=5" | php -r 'echo json_encode(json_decode(file_get_contents("php://stdin")), JSON_PRETTY_PRINT);' ``` Expected: JSON with `status: success`, `data` containing array of `{vin, model_name}`. - [ ] **Step 4: Test with pi_no filter** ```bash curl -s -H "Fdesk-Api-Key: your-secret-api-key-here" "http://localhost:8787/common/public/pi/list?pi_no=PI2025" | php -r 'echo json_encode(json_decode(file_get_contents("php://stdin")), JSON_PRETTY_PRINT);' ``` Expected: filtered results or empty data array. - [ ] **Step 5: Commit final verification** No code changes expected. If fixes were needed during testing, commit them: ```bash git add -A git commit -m "fix: adjustments from manual verification of public stock API" ```