外观
Beta 模块长期标准结构
适用范围:
beta/app/Modules/*目标:统一 beta 侧业务模块的目录结构、资源归属和加载方式,避免继续回到“代码在模块里、视图/路由散在全局目录”的平铺模式。
1. 总原则
1.1 模块内聚
业务模块自己的内容,原则上都放在:
text
beta/app/Modules/<ModuleName>/包括但不限于:
- Http/Controllers
- Http/Requests
- Http/Resources
- Http/Middleware
- Services
- Repositories
- Models
- DTOs
- Support
- Extensions
- views
- config
- module routes
- 与模块相关但统一存放的 migrations
1.2 全局目录只留平台级共享能力
以下内容才应放在全局目录:
app/Foundation/*- 全局中间件
- 全局异常处理
- 全局布局(layout)
- 跨模块共享组件
- 平台级公共服务
换句话说:
模块专属的,尽量留在模块内;跨模块共享的,才放全局。
1.3 过渡结构允许存在,但不作为长期目标
下面这类目录可以在迁移过渡期保留:
beta/resources/views/modules/*beta/routes/modules/*
但它们不应作为最终形态。长期应逐步收敛回模块目录。
2. 推荐标准结构
以 FileManagement 为例:
text
beta/
├── app/
│ ├── Foundation/
│ └── Modules/
│ └── FileManagement/
│ ├── src/
│ │ ├── Http/
│ │ │ ├── Controllers/
│ │ │ ├── Requests/
│ │ │ ├── Resources/
│ │ │ └── Middleware/
│ │ ├── Services/
│ │ ├── Repositories/
│ │ ├── Models/
│ │ ├── DTOs/
│ │ ├── Support/
│ │ ├── Extensions/
│ │ └── Providers/
│ │ └── FileManagementServiceProvider.php
│ ├── tests/
│ │ ├── Unit/
│ │ └── Feature/
│ ├── routes/
│ │ ├── web.php
│ │ └── api.php
│ ├── resources/
│ │ └── views/
│ │ ├── index.blade.php
│ │ ├── form.blade.php
│ │ ├── show.blade.php
│ │ ├── logs/
│ │ ├── exports/
│ │ └── partials/
│ ├── config/
│ │ └── filemanagement.php
│ └── README.md
├── database/
│ ├── migrations/
│ └── seeders/
├── resources/
│ └── views/
│ └── layouts/
└── routes/
└── beta.php3. 各目录职责
3.1 Http/Controllers
只负责:
- 接收 HTTP 参数
- 调用 Service
- 返回 View / JSON / File Response
不要把复杂业务判断长期堆在 Controller 里。
3.2 Http/Requests
负责参数验证、格式约束、基本边界控制。
3.3 Services
负责业务编排,是模块核心逻辑层。
建议区分:
- Query Service:查
- Command Service:写
- 场景型 Service:预览、导出、打印、日志等
3.4 Repositories
负责数据访问细节:
- 查询条件拼装
- Eloquent / Query Builder 查询
- 数据持久化封装
3.5 Models
负责模块领域实体和表映射。
3.6 DTOs
用于明确 Controller、Service、Repository 之间的内部数据传输形状,避免复杂流程长期传递含义不清的散装数组。DTO 不负责定义对外 JSON 格式。
3.7 Http/Resources
用于定义稳定的 JSON 输出契约,包括字段选择、字段改名、嵌套关系和集合输出。简单的模块内部 AJAX 可以直接返回 JsonResponse,不要求为每个响应创建 Resource。
这里不能使用模块根目录的 Resources/:它会与存放视图和静态资源的 resources/ 在大小写不敏感文件系统上冲突。
3.8 Http/Middleware
只放模块专属 HTTP 中间件,并由模块 Provider 注册别名。跨模块公共中间件才放全局 app/Http/Middleware。
3.9 Support
放模块内部通用辅助类,例如:
- Breadcrumbs
- PathResolver
- Formatter
- Mapper
3.10 Extensions
放扩展点、插件点和可替换策略接口,例如:
- ActionRegistry
- PreviewPolicyInterface
- DownloadPolicyInterface
3.11 resources
当前模块目录只自动加载 Blade 视图:
resources/views
模块 JS/CSS 不会被 Vite 自动发现,应由 Beta 根目录 resources/js、resources/css 入口显式引入。语言文件在建立模块翻译加载约定前继续放根 lang,脚手架不预建无加载链路的目录。
3.12 config
模块配置分为三种:
features.php:可独立启停的完整业务能力。settings.php:站点可维护的运行配置定义。<module-key>.php:Laravelconfig()运行配置,仅在模块确实需要时创建。
ModuleServiceProvider 默认不加载运行配置;只有真实存在 <module-key>.php 的模块才覆盖 moduleConfigKey()。
3.13 database
数据库迁移、Seeder 和 Factory 统一放在 Beta 根目录 database,沿用 Laravel 默认加载链路。文件名和类名体现业务模块归属,不在各 Module 下另建一套未被加载的数据库目录。
3.14 分层使用边界
目录结构用于明确代码归属,不代表每个接口都必须使用完整分层。
- 简单下拉、状态查询和模块内部 AJAX:Controller 可直接调用简单查询并返回 JsonResponse。
- 普通增删改查:写入参数先校验;有业务规则或事务时再引入 Service。
- DTO:字段较多、跨多层传递或数组含义不清时使用。
- Repository:复杂查询需要复用,或需要隔离遗留数据访问细节时使用。
- Resource:公开接口、分页列表或 JSON 契约需要长期稳定时使用。
- 审批、委托生成任务等复杂流程:按需要组合 Request、DTO、Service、Repository 和 Resource。
禁止为了填满目录而创建只做一层转发的空类。新增抽象应能减少重复、固定边界或承载明确业务规则。
3.15 模块启用契约
模块是否启用,以 bootstrap/providers.php 中是否显式注册其 ModuleServiceProvider 为唯一事实来源。 app/Modules/* 下的目录本身不代表模块已启用。
Feature、Settings、权限配置和模块视图都只处理已注册 Provider 对应的模块;未注册目录不会被这些系统自动发现。 因此,临时目录或尚未完成的模块可以存在,但如果目录内已经出现 config/features.php、config/settings.php、 config/permissions.php、模块路由或业务源码,必须同步注册 Provider,或者在 CI 中被报告为孤立模块。
脚手架会在创建目录的同时写入 Provider 注册。CI 应校验以下关系:
- 已注册的模块 Provider 类必须存在,并且位于对应模块目录;
- 模块 Provider 文件不得遗漏注册;
- 模块目录、Provider、模块 key 和配置定义不能互相冲突。
各子系统不得自行扫描整个 app/Modules 目录;统一通过 App\Foundation\Module\ModuleRegistry 获取已启用模块。
4. 视图规范
4.1 视图应放模块内
长期推荐:
text
app/Modules/<Module>/resources/views/*不推荐长期停留在:
text
resources/views/modules/<module>/*4.2 视图命名空间
统一使用模块命名空间:
php
return view('filemanagement::show');而不是:
php
return view('modules.filemanagement.show');原因:
- 更符合 Laravel 模块视图加载习惯
- 更容易做自动注册
- 模块迁移目录时,调用侧更稳定
4.3 全局 views 的职责
全局 resources/views 只建议保留:
- layouts
- 跨模块共享 partial
- 平台级页面
5. 路由规范
5.1 模块路由放模块内
推荐:
text
app/Modules/<Module>/routes/web.php过渡方案:
text
routes/modules/<module>.php长期应回收进模块目录。
5.2 路由注册方式
推荐由模块 ServiceProvider 注册:
php
final class FileManagementServiceProvider extends ModuleServiceProvider
{
// 只有存在 config/filemanagement.php 时才声明运行配置 key。
protected function moduleConfigKey(): string
{
return 'filemanagement';
}
}其中 ModuleServiceProvider 负责标准样板:
config/<key>.php -> mergeConfigFrom(...)routes/web.php -> loadRoutesFrom(...)
这样每个模块 Provider 只保留少量声明,不重复写样板代码。
6. 资源加载规范
6.1 视图自动注册
标准结构下:
text
app/Modules/*/resources/views会由 AppServiceProvider 通过 ModuleRegistry,按“模块目录名小写”注册视图命名空间。
例如:
text
app/Modules/FileManagement/resources/views/form.blade.php调用方式:
php
view('filemanagement::form');因此模块 Provider 不需要再重复写:
php
$this->loadViewsFrom(...);6.2 兼容旧目录
过渡期仍兼容:
text
resources/views/modules/*但仅作为历史兼容层,不再作为长期标准。
如果模块内目录和旧目录存在同名 namespace,优先使用模块内目录。
6.3 后台模块路由必须显式进入 web 栈
对于 beta 后台模块,模块路由文件内必须显式挂上:
Route::middleware(['web', 'legacy.auth']) ->group(function (): void { Route::prefix('filemanagement') ->name('filemanagement.') ->middleware('filemanagement.permission:view') ->group(function (): void { // ... }); });
原因:
- `LegacySessionBridge` 挂在 `web` 组上
- `legacy.auth` 只校验桥接结果,不负责桥接本身
- 漏掉 `web` 时,模块页面和写接口会直接 302 到 `/login.php`
这是 beta 后台模块迁移时的硬约束。
---
## 7. 配置读取规范
模块配置优先统一走自己的顶级 key,例如:
```php
config('filemanagement.storage_root')不建议长期混用多套路径,例如:
modules.filemanagement.*lims.filemanagement.*
兼容读取可以暂时保留,但长期应收敛成一套正式 key。
8. 新模块落地规则
从本规范生效起:
- 新迁移的业务模块,优先按本标准新建目录。
- 新增 views 不再优先放
resources/views/modules/*。 - 新增 module routes 不再优先放
routes/modules/*。 - 模块 views 和 config 随模块落地;前端 assets、语言文件、迁移与 Seeder 沿用各自现有的根目录加载链路。
- beta 后台模块路由必须显式进入
['web', 'legacy.auth']链路。 - 视图统一走
<模块目录名小写>::*命名空间。 - 模块 Provider 优先继承
App\Foundation\Module\ModuleServiceProvider。
8.1 使用脚手架命令
新模块使用 Artisan 命令生成最小骨架:
bash
php artisan lims:make-module Customer --key=customer --name="客户与委托"正式生成前可先预览文件清单:
bash
php artisan lims:make-module Customer --key=customer --name="客户与委托" --dry-run命令约束:
- 默认拒绝处理已经存在的模块;需要补齐缺失骨架时使用
--missing-only。 --missing-only只创建不存在的文件,绝不覆盖现有 Provider、配置、路由或 README。- 会预建
Http/Controllers、Http/Requests、Services和Models四个基础目录;Repository、DTO、Resource、Middleware 等目录在出现真实用途时再创建。 - 生成过程中任一步失败都会回滚本次新建文件,避免留下半套模块。
- migrations、Seeder 和 Factory 继续统一放在根目录
database/,不在模块下建立 database 目录。 - 生成模块后,必须在
composer.json添加App\Modules\<Module>\到app/Modules/<Module>/src/的 PSR-4 映射;模块存在测试时,还要添加Tests\Modules\<Module>\到app/Modules/<Module>/tests/的开发映射。随后执行composer dump-autoload,不能只依赖app/Modules/autoload.php的兼容兜底。
9. 对现有 FileManagement 的落地建议
9.1 当前状态
FileManagement 现在已作为标准结构参考实现:
- 代码主目录:
beta/app/Modules/FileManagement/src/ - 视图目录:
beta/app/Modules/FileManagement/resources/views/ - 路由目录:
beta/app/Modules/FileManagement/routes/web.php - 配置目录:
beta/app/Modules/FileManagement/config/filemanagement.php - 视图调用:
filemanagement::...
9.2 建议的下一步
后续新模块或迁移模块,优先直接按 FileManagement 这套骨架落地,不再新增模块外平铺资源。
9.3 原则
迁移时只做“目录内聚”和“注册收敛”,不要顺手扩大业务改动范围。
10. 一句话规则
业务模块内聚:代码、视图、路由、配置、资源尽量都放
app/Modules/<Module>内;全局目录只保留平台级共享能力。
状态建模、数据范围、Laravel 特性和前端技术选型以代码规范为准;涉及具体模块时再结合模块 README 和对应专项文档确认。