387 lines
13 KiB
Markdown
387 lines
13 KiB
Markdown
---
|
||
name: module-scaffold
|
||
version: 2.1.0
|
||
description: "生成 Hyperf 模块化业务模块脚手架(ConfigProvider/Controller/Service/Model)。当需要新建业务模块、创建 module、添加模块化功能时使用。基于 ModuleLoader 自动发现 + ConfigProvider 机制,无需修改 composer.json。"
|
||
requires: [hyperf-service]
|
||
---
|
||
|
||
> ⚠️ 核心执行流程已在 `.cursor/rules/skill-module-scaffold.mdc` 中由 Cursor 自动注入。
|
||
> 本文件提供完整模板、代码示例和边缘场景处理,供 Agent 按需深入 Read。
|
||
|
||
# Hyperf Module Scaffold
|
||
|
||
## 触发条件
|
||
|
||
用户要求创建新的业务模块、在 `modules/` 下新建功能模块、或提到"模块化"、"新模块"、"module"。
|
||
|
||
## 前置条件
|
||
|
||
项目已完成模块化架构初始化:
|
||
- `Case-Database-Backend/modules/` 目录存在
|
||
- `app/Support/ModuleLoader.php` 已存在并注册到 `composer.json` `autoload.files`
|
||
- `config/autoload/annotations.php` 已包含 `BASE_PATH . '/modules'` 扫描路径
|
||
|
||
若未初始化,先引导用户完成架构搭建。
|
||
|
||
## 工作原理
|
||
|
||
`ModuleLoader.php` 在 `vendor/autoload.php` 加载时(Hyperf DI 容器初始化之前)自动执行:
|
||
1. 扫描 `modules/*/composer.json`
|
||
2. 读取每个模块的 `extra.hyperf.config`
|
||
3. 通过反射注入 `Hyperf\Support\Composer::$extra`
|
||
4. 同时注册模块的 PSR-4 命名空间到 Composer ClassLoader
|
||
|
||
这使得 `ProviderConfig::load()` 能发现所有模块的 ConfigProvider,**无需将模块添加到主项目 `composer.json` 的 `require` 中**。
|
||
|
||
## 执行流程
|
||
|
||
### 1. 确认模块规格
|
||
|
||
| 字段 | 必填 | 默认值 | 说明 |
|
||
|------|------|--------|------|
|
||
| 模块名称 | ✅ | — | PascalCase,如 `Order`、`UserCenter` |
|
||
| 模块描述 | ❌ | 推断 | 一句话说明模块职责 |
|
||
| 接口类型 | ❌ | `api` | `api`(用户端)/ `admin`(管理端)/ `both`(两者都有) |
|
||
| 需要 Service? | ❌ | true | 业务逻辑层 |
|
||
| 需要 Model? | ❌ | false | 数据模型 |
|
||
| 需要 Repository? | ❌ | false | 复杂查询时 |
|
||
| 需要 Middleware? | ❌ | false | 模块级中间件 |
|
||
| 需要 Request? | ❌ | false | 表单验证 |
|
||
| 路由前缀 | ❌ | 见下方规则 | 自动推断,也可手动指定 |
|
||
|
||
### 2. Controller 与 Request 目录结构规则(核心)
|
||
|
||
根据接口类型,Controller 和 Request **均放入对应子目录**,命名空间随之变更:
|
||
|
||
| 接口类型 | 路由前缀 | Controller 目录 | Request 目录 | PHP 命名空间(Controller) |
|
||
|----------|----------|-----------------|--------------|--------------------------|
|
||
| 用户端 API(`/api/*`) | `/api/{module-kebab}` | `Http/Controller/Api/` | `Http/Request/Api/` | `Modules\{Name}\Http\Controller\Api` |
|
||
| 管理端 API(`/admin/*`) | `/admin/{module-kebab}` | `Http/Controller/Admin/` | `Http/Request/Admin/` | `Modules\{Name}\Http\Controller\Admin` |
|
||
| 两者都有(`both`) | 各自前缀 | 两个 Controller 子目录 | 两个 Request 子目录 | 各自命名空间 |
|
||
|
||
**判断规则**:
|
||
- 路由前缀包含 `/api/` → Controller 放 `Controller/Api/`,Request 放 `Request/Api/`
|
||
- 路由前缀包含 `/admin/` → Controller 放 `Controller/Admin/`,Request 放 `Request/Admin/`
|
||
- 用户未指定接口类型时,优先询问,或根据模块职责推断
|
||
- 若模块同时有用户端和管理端接口,两组子目录都创建
|
||
- **类名不需要加接口类型前缀**,子目录本身已区分,类名保持简洁
|
||
|
||
### 3. 生成目录结构
|
||
|
||
**仅有用户端 API(最常见)**:
|
||
```
|
||
modules/{ModuleName}/
|
||
├── composer.json
|
||
└── src/
|
||
├── ConfigProvider.php
|
||
├── Http/
|
||
│ ├── Controller/
|
||
│ │ └── Api/ # 用户端控制器
|
||
│ │ └── {Name}Controller.php
|
||
│ ├── Request/
|
||
│ │ └── Api/ # 用户端请求验证
|
||
│ │ └── {Name}Request.php
|
||
│ └── Middleware/ # 模块中间件(按需)
|
||
├── Service/
|
||
├── Model/ # 按需
|
||
├── Repository/ # 按需
|
||
├── Event/ # 按需
|
||
├── Listener/ # 按需
|
||
└── Constants/ # 按需
|
||
```
|
||
|
||
**仅有管理端 API**:
|
||
```
|
||
modules/{ModuleName}/
|
||
└── src/
|
||
├── Http/
|
||
│ ├── Controller/
|
||
│ │ └── Admin/ # 管理端控制器
|
||
│ │ └── {Name}Controller.php
|
||
│ ├── Request/
|
||
│ │ └── Admin/ # 管理端请求验证
|
||
│ │ └── {Name}Request.php
|
||
...
|
||
```
|
||
|
||
**同时有用户端 + 管理端(both)**:
|
||
```
|
||
modules/{ModuleName}/
|
||
└── src/
|
||
├── Http/
|
||
│ ├── Controller/
|
||
│ │ ├── Api/ # 用户端控制器
|
||
│ │ │ └── {Name}Controller.php
|
||
│ │ └── Admin/ # 管理端控制器
|
||
│ │ └── {Name}Controller.php
|
||
│ ├── Request/
|
||
│ │ ├── Api/ # 用户端请求验证
|
||
│ │ │ └── {Name}Request.php
|
||
│ │ └── Admin/ # 管理端请求验证
|
||
│ │ └── {Name}Request.php
|
||
...
|
||
```
|
||
|
||
### 4. 生成 composer.json
|
||
|
||
```json
|
||
{
|
||
"name": "modules/{module-kebab}",
|
||
"description": "{模块描述}",
|
||
"type": "library",
|
||
"autoload": {
|
||
"psr-4": {
|
||
"Modules\\{ModuleName}\\": "src/"
|
||
}
|
||
},
|
||
"extra": {
|
||
"hyperf": {
|
||
"config": "Modules\\{ModuleName}\\ConfigProvider"
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
**命名规则**:
|
||
- Composer 包名:`modules/{kebab-case}`,如 `modules/user-center`
|
||
- PHP 命名空间:`Modules\{PascalCase}`,如 `Modules\UserCenter`
|
||
|
||
如模块有独立的第三方依赖,可在模块 `composer.json` 的 `require` 中声明,`wikimedia/composer-merge-plugin` 会在下次 `composer update` 时合并到主项目。
|
||
|
||
### 5. 生成 ConfigProvider.php
|
||
|
||
```php
|
||
<?php
|
||
|
||
declare(strict_types=1);
|
||
|
||
namespace Modules\{ModuleName};
|
||
|
||
class ConfigProvider
|
||
{
|
||
public function __invoke(): array
|
||
{
|
||
return [
|
||
'dependencies' => [
|
||
// Interface::class => Implementation::class,
|
||
],
|
||
'annotations' => [
|
||
'scan' => [
|
||
'paths' => [
|
||
__DIR__,
|
||
],
|
||
],
|
||
],
|
||
'publish' => [],
|
||
];
|
||
}
|
||
}
|
||
```
|
||
|
||
**ConfigProvider 职责**:
|
||
- `dependencies`:注册模块的 DI 绑定(接口 → 实现)
|
||
- `annotations.scan.paths`:注册注解扫描路径(`__DIR__` 指向 `src/`)
|
||
- `publish`:可发布的配置文件(可选)
|
||
|
||
### 6. 生成 Controller 模板
|
||
|
||
**用户端 API Controller**(放在 `Http/Controller/Api/`):
|
||
|
||
```php
|
||
<?php
|
||
|
||
declare(strict_types=1);
|
||
|
||
namespace Modules\{ModuleName}\Http\Controller\Api;
|
||
|
||
use App\Controller\AbstractController;
|
||
use App\Support\ResponseTrait;
|
||
use Hyperf\Di\Annotation\Inject;
|
||
use Hyperf\HttpServer\Annotation\Controller;
|
||
use Hyperf\HttpServer\Annotation\GetMapping;
|
||
use Modules\{ModuleName}\Service\{ModuleName}Service;
|
||
use Psr\Http\Message\ResponseInterface;
|
||
|
||
#[Controller(prefix: '/api/{module-kebab}')]
|
||
class {ModuleName}Controller extends AbstractController
|
||
{
|
||
use ResponseTrait;
|
||
|
||
#[Inject]
|
||
protected {ModuleName}Service ${moduleName}Service;
|
||
|
||
#[GetMapping(path: '')]
|
||
public function index(): ResponseInterface
|
||
{
|
||
return $this->success([]);
|
||
}
|
||
}
|
||
```
|
||
|
||
**管理端 API Controller**(放在 `Http/Controller/Admin/`):
|
||
|
||
```php
|
||
<?php
|
||
|
||
declare(strict_types=1);
|
||
|
||
namespace Modules\{ModuleName}\Http\Controller\Admin;
|
||
|
||
use App\Controller\AbstractController;
|
||
use App\Middleware\JwtAuthMiddleware;
|
||
use App\Support\ResponseTrait;
|
||
use Hyperf\Di\Annotation\Inject;
|
||
use Hyperf\HttpServer\Annotation\Controller;
|
||
use Hyperf\HttpServer\Annotation\GetMapping;
|
||
use Hyperf\HttpServer\Annotation\Middleware;
|
||
use Modules\{ModuleName}\Service\{ModuleName}Service;
|
||
use Psr\Http\Message\ResponseInterface;
|
||
|
||
#[Controller(prefix: '/admin/{module-kebab}')]
|
||
#[Middleware(JwtAuthMiddleware::class)]
|
||
class {ModuleName}Controller extends AbstractController
|
||
{
|
||
use ResponseTrait;
|
||
|
||
#[Inject]
|
||
protected {ModuleName}Service ${moduleName}Service;
|
||
|
||
#[GetMapping(path: '')]
|
||
public function index(): ResponseInterface
|
||
{
|
||
return $this->success([]);
|
||
}
|
||
}
|
||
```
|
||
|
||
**关键区别**:
|
||
- `Api/` 下:前缀 `/api/`,命名空间含 `\Api\`
|
||
- `Admin/` 下:前缀 `/admin/`,命名空间含 `\Admin\`,自动加 `JwtAuthMiddleware`
|
||
- **两者类名相同**(均为 `{Name}Controller`),由命名空间区分,无需加接口类型前缀
|
||
|
||
### 7. 生成 Request 模板
|
||
|
||
**用户端 Request**(放在 `Http/Request/Api/`):
|
||
|
||
```php
|
||
<?php
|
||
|
||
declare(strict_types=1);
|
||
|
||
namespace Modules\{ModuleName}\Http\Request\Api;
|
||
|
||
use Hyperf\Validation\Request\FormRequest;
|
||
|
||
class {Name}Request extends FormRequest
|
||
{
|
||
public function authorize(): bool
|
||
{
|
||
return true;
|
||
}
|
||
|
||
public function rules(): array
|
||
{
|
||
return [
|
||
// 'field' => 'required|string|max:255',
|
||
];
|
||
}
|
||
|
||
public function messages(): array
|
||
{
|
||
return [];
|
||
}
|
||
}
|
||
```
|
||
|
||
**管理端 Request**(放在 `Http/Request/Admin/`):
|
||
|
||
```php
|
||
<?php
|
||
|
||
declare(strict_types=1);
|
||
|
||
namespace Modules\{ModuleName}\Http\Request\Admin;
|
||
|
||
use Hyperf\Validation\Request\FormRequest;
|
||
|
||
class {Name}Request extends FormRequest
|
||
{
|
||
public function authorize(): bool
|
||
{
|
||
return true;
|
||
}
|
||
|
||
public function rules(): array
|
||
{
|
||
return [
|
||
// 'field' => 'required|string|max:255',
|
||
];
|
||
}
|
||
|
||
public function messages(): array
|
||
{
|
||
return [];
|
||
}
|
||
}
|
||
```
|
||
|
||
**关键区别**:
|
||
- `Api/` 下:命名空间含 `\Request\Api\`,类名 `{Name}Request`
|
||
- `Admin/` 下:命名空间含 `\Request\Admin\`,类名 `{Name}Request`
|
||
- **两者类名相同**,由命名空间区分,无需加接口类型前缀
|
||
- Controller 中 `use` 路径需对应各自子命名空间
|
||
|
||
### 8. 验证模块自动发现
|
||
|
||
创建文件后**无需任何 composer 操作**,直接验证:
|
||
|
||
```bash
|
||
docker exec hyperf-skeleton php -r "
|
||
define('BASE_PATH', '/opt/www');
|
||
require '/opt/www/vendor/autoload.php';
|
||
\$extra = Hyperf\Support\Composer::getMergedExtra('hyperf');
|
||
echo in_array('Modules\\{ModuleName}\\ConfigProvider', \$extra['config'] ?? []) ? 'OK' : 'FAIL';
|
||
"
|
||
```
|
||
|
||
## 模块间依赖
|
||
|
||
若模块 A 依赖模块 B 的类:
|
||
- 通过 DI 注入模块 B 的 Service(不直接依赖 Controller/Model)
|
||
- 遵循依赖方向:上层模块 → 下层模块,禁止循环依赖
|
||
- ModuleLoader 的 glob 扫描天然支持多模块并存,无需额外配置
|
||
|
||
## 已有模块示例参考
|
||
|
||
当前项目 `modules/Auth/` 的实际结构(**参考标准**):
|
||
|
||
```
|
||
modules/Auth/src/Http/Controller/
|
||
├── Api/ # 用户端接口(推荐新规范)
|
||
│ ├── AuthController.php # prefix: /api/auth
|
||
│ ├── CaptchaController.php # prefix: /api/auth/captcha
|
||
│ └── SmsController.php # prefix: /api/auth/sms
|
||
└── Admin/ # 管理端接口(推荐新规范)
|
||
└── AdminAuthController.php # prefix: /admin/auth
|
||
```
|
||
|
||
> 注:当前 Auth 模块尚未按此规范重构(所有 Controller 平铺在 `Controller/` 下),新模块应从一开始遵循子目录规范。
|
||
|
||
## 验证
|
||
|
||
1. [ ] 模块目录结构完整(composer.json + src/ConfigProvider.php + Http/Controller/)
|
||
2. [ ] **Controller 按接口类型放入正确子目录**:`/api/*` → `Controller/Api/`,`/admin/*` → `Controller/Admin/`
|
||
3. [ ] Controller 命名空间包含正确子层级:`...\Http\Controller\Api\` 或 `...\Http\Controller\Admin\`
|
||
4. [ ] `Admin/` 下的 Controller 自动添加 `JwtAuthMiddleware`;类名与 `Api/` 下保持一致,无需加前缀
|
||
5. [ ] **Request 按接口类型放入正确子目录**:`/api/*` → `Request/Api/`,`/admin/*` → `Request/Admin/`
|
||
6. [ ] Request 命名空间包含正确子层级:`...\Http\Request\Api\` 或 `...\Http\Request\Admin\`
|
||
7. [ ] Controller 中 `use` 的 Request 路径与子目录一致
|
||
8. [ ] composer.json 的 `name` 使用 `modules/{kebab-case}` 格式
|
||
9. [ ] namespace 使用 `Modules\{PascalCase}` 格式
|
||
10. [ ] ConfigProvider 注册了 `__DIR__` 注解扫描路径
|
||
11. [ ] `extra.hyperf.config` 指向正确的 ConfigProvider 类
|
||
12. [ ] 无需修改主项目 `composer.json`,模块被 ModuleLoader 自动发现
|
||
13. [ ] `ProviderConfig::load()` 能发现模块 ConfigProvider
|
||
14. [ ] `php -l` 所有 PHP 文件语法正确
|
||
15. [ ] Controller 路由前缀遵循 RESTful 命名(kebab-case)
|