charsen / moo-banner
Banner management for Laravel applications, with reusable placement, media, publishing and ordering conventions.
Requires
- php: ^8.2
- charsen/moo-scaffold: ^2.1.7
- laravel/framework: ^10.0 || ^11.0 || ^12.0
- tucker-eric/eloquentfilter: ^3.0
Requires (Dev)
- laravel/pint: ^1.13
- orchestra/testbench: ^10.0
- pestphp/pest: ^3.0
- pestphp/pest-plugin-laravel: ^3.0
README
一个可被多个 Laravel admin 项目复用的 Banner 管理扩展包,统一提供展示位置、文案、桌面与移动图片、链接、启停、排序和软删除能力。
包依赖 charsen/moo-scaffold 提供 Snowflake、代码生成、后台资源和翻译合并能力;不依赖 moo-system 或 host 私有类。
字段、迁移、模型和管理面均以 scaffold/database/Banner.yaml 为设计源;包同时提供默认关闭的前台只读接口,频道目录和页面视觉由接入项目负责。
为什么有这个包
多个官网项目都需要相同的 Banner 管理骨架:上传桌面与移动图片、填写文案与跳转、按位置启停和排序。真正不同的通常只有频道目录、路由前缀和页面视觉。如果每个 host 分别维护表、上传转存、公开读取和后台 CRUD,同一类兼容问题会重复出现。
本包把稳定部分收口,差异通过窄契约交给 host,避免共享包反向依赖任一具体项目。
技术信息
- PHP 8.2+,Laravel 10 / 11 / 12
- Composer:
charsen/moo-banner - 命名空间:
Mooeen\Banner\ - 业务表:
moo_banners - Schema:
scaffold/database/Banner.yaml
设计边界
banner_position = NULL表示首页。- 非首页位置由 host 实现
BannerPositionResolver声明,数据库存稳定字符串代码。 - 状态由包固定为停用与启用,避免各 host 对公开读取产生不同语义。
- 包提供默认关闭的公开只读接口;host 配置路由前缀、中间件和数量上限,旧系统可另做响应兼容层。
- 图片默认通过 Laravel Filesystem 处理,host 可替换媒体契约接入其他存储实现。
所有语义字段都带 banner_ 前缀,避免各扩展包扁平合并 db.php 词条时相互覆盖;id、操作人字段、软删除与时间戳沿用跨包公共命名。
位置与状态的归属不同:
| 字段 | 归属 | 类型 | 原因 |
|---|---|---|---|
banner_position |
host 声明 | varchar(50) 可空 |
各项目频道集合不同,字符串代码可自解释;NULL 固定表示首页 |
banner_status |
包定义 | tinyint |
状态直接驱动公开读取,必须保持确定语义 |
对外边界
| 能力 | 入口 |
|---|---|
| 后台 CRUD | 包内 api/admin/banners 资源路由 |
| 前台只读 | 显式开启后提供 GET {public.prefix}/banners |
| 位置目录 | host 实现 BannerPositionResolver |
| 当前操作人 | scaffold 共享 OperatorResolver |
| 图片转存与 URL | 包内媒体契约,默认 Laravel Filesystem 实现 |
| 公开读取 | 包内 Web Controller / Request / Resource,默认关闭 |
| 管理端页面 | host 实现,并使用包返回的字段、options 与 action key |
本地开发接入
在 Laravel host 的 composer.json 中添加 path repository:
{
"repositories": {
"moo-banner": {
"type": "path",
"url": "../moo-banner",
"options": { "symlink": true }
}
}
}
安装开发版本后,将后台模块加入 host 的 scaffold 配置:
'extra_modules' => [ 'Banner' => 'Mooeen\\Banner\\Http\\Controllers\\Admin', ],
这一步不仅用于接口文档,也决定包控制器是否进入 ACL 扫描。模块名或 action 名改变会改变持久化授权 key,不能当作普通重命名。
host 可以绑定自己的位置目录:
use Mooeen\Banner\Contracts\BannerPositionResolver; $this->app->bind(BannerPositionResolver::class, AppBannerPositions::class);
然后发布配置并执行迁移:
php artisan vendor:publish --tag=moo-banner-config php artisan migrate
后台路由安全配置(必做)
config/moo-banner.php 中默认的 admin 仅是兼容性路由组名,扩展包无法知道 host 的认证实现,它不等于已经强制登录。host 必须在 bootstrap/app.php 为本包建立独立组,并让发布后的配置明确指向该组;不要借用需要放行登录接口的 admin,也不要复用 moo-system:
// bootstrap/app.php -> withMiddleware() $packageAdminMiddleware = [ 'jwt.assign.guard:admin', 'jwt.guard.auth:admin', 'jwt.auth.refresh', 'throttle:admin', 'set.locale', \Illuminate\Routing\Middleware\SubstituteBindings::class, ]; $middleware->appendToGroup('moo-banner', $packageAdminMiddleware);
// config/moo-banner.php 'admin' => [ 'prefix' => 'api/admin', 'name' => 'admin.', 'middleware' => 'moo-banner', ],
上面的中间件类名按 host 的真实认证栈调整,但必须保留“指定 admin 守卫 → 强制认证 → 续签/过期处理 → 限流 → 路由绑定”的完整边界。验收至少覆盖:匿名访问后台 Banner 返回 401、已登录但无对应 ACL 返回 403、授权账号成功。前台只读接口继续使用 public.middleware,不要套后台认证组。
前台只读接口
接口默认关闭,host 在发布后的配置中显式开启:
'public' => [ 'enabled' => true, 'prefix' => 'api', 'name' => 'banner.', 'middleware' => ['api', 'throttle:120,1'], 'default_limit' => 5, 'max_limit' => 20, 'resource' => \Mooeen\Banner\Http\Resources\PublicBannerResource::class, ],
启用后可读取首页或频道 Banner:
GET /api/banners GET /api/banners?position=news&limit=5
空位置表示首页。接口只返回启用且未软删除的数据,并按 banner_sort、id 倒序;响应仅包含展示字段和解析后的图片 URL,不暴露状态、排序、操作人及原始存储路径。
非首页位置必须存在于 host 的 BannerPositionResolver 目录,未知代码返回 422。既有站点需要保留旧响应字段时,可将 public.resource 配置为 host 自己的 JsonResource;这只适配响应形状,查询和公开范围仍由包统一处理。
管理端页面约定
Composer 包提供后端接口和 ACL 元数据,不向所有 host 强塞同一套前端页面。host 管理端至少需要:
- 列表展示图片、位置、标题、启停和排序;
- 创建/编辑表单消费包返回的位置与状态 options;
- Snowflake ID 全程按字符串处理;
- action 前缀与
Banner/BannerController的真实 ACL key 保持一致; - 使用实际 HTTP 客户端能力调用路由,不为迁就前端封装擅自改变正确的 HTTP 语义。
Schema-first 工作流
- 修改
scaffold/database/Banner.yaml。 - 在 path repository host 中运行
php artisan moo:fresh。 - 运行
php artisan moo:free admin Banner -a,生成物应落入本包。 - 在生成区外深化查询、媒体和表单行为。
- 分别运行包测试与 host 集成验证,并复核两边完整 diff。
验证
composer ci
License
MIT