inova/nova-admin

多站点复用的通用后台底座:广告、站点设置、ads.txt、robots.txt、后台中文、登录与默认管理员(Laravel 12 + Filament 5)。

Maintainers

Package info

github.com/eirtons/nova-admin

pkg:composer/inova/nova-admin

Transparency log

Statistics

Installs: 176

Dependents: 1

Suggesters: 0

Stars: 0

Open Issues: 0

v1.4.1 2026-08-28 08:10 UTC

README

多站点复用的通用后台底座(Laravel 12 + Filament 5):广告管理、站点设置、静态页面、ads.txt、robots.txt、后台中文、账号密码登录、默认管理员、后台 Logo 跳前台。

一、新建项目从零接入

1. 创建 Laravel 项目并安装本包

composer create-project laravel/laravel:^12.0 mysite
cd mysite

composer require inova/nova-admin

已依赖 Filament 5,无需单独安装。

默认 SQLite;用 MySQL 等先配 .env。 生产 APP_URL 须为完整 URL(如 https://example.com)——robots.txt 的 Sitemap 行按它生成。

2. 一键安装

php artisan nova-admin:install

一条命令搞定全部:接入 admin Panel、跑迁移、建默认管理员、初始化 robots/站点设置、 填充示例广告、发布静态资源、建 storage 软链。无需手动碰 AdminPanelProvider.php

附带处理:公开文件(robots.txt/ads.txt/vendor/livewire)加入 .gitignoreApp\Models\User 自动接入 FilamentUser(防生产 403)。自定义才需发布 config/nova-admin.php

3. 启动验证

php artisan serve

访问 http://127.0.0.1:8000/admin → 用 nova / nova 登录。

上线前立即在后台改默认密码。

二、接入后你立即拥有

能力 入口
广告管理(一位多条、按序输出、代码框语法高亮) 后台「广告管理」
站点设置(基础/SEO/媒体/品牌) 后台「站点设置」
静态页面(关于/隐私/条款等富文本落地页,可增删改) 后台「静态页面」
ads.txt 编辑(DB + 静态文件双写,支持几千行大清单) 后台「Ads.txt」+ GET /ads.txt
站点广告配置下发(webdeploy 协议) php artisan ads:import-site-ad-config <file>
robots.txt 编辑(含默认模板) 后台「Robots.txt」+ GET /robots.txt
sitemap.xml(静态条目 + 项目注册动态来源,带缓存) GET /sitemap.xml
系统日志(查看尾部 / 下载 / 删除,兼容单文件与按天分割) 后台「系统日志」
账号密码登录 /admin/login(账号字段可配)
后台中文 自动
默认管理员 nova / nova
Logo 点击跳前台 后台左上角品牌

三、前台使用

{{-- 放在 <head> 内,输出该广告位的 head_code --}}
<x-ad-head position="global_head" />

{{-- 放在页面展示位置,输出 body_code;无生效广告时不产生 DOM --}}
<x-ad-body position="home_banner1" />

{{-- 浮层类广告(anchor / interstitial 等)自己定位,不要套居中容器 --}}
<x-ad-body position="anchor" :wrapper="false" />

<x-ad-head><x-ad-body> 必须成对出现:只放 head 不放 body,后台填的 body 代码就永远不会渲染。

site_config('site_name');        // 读站点配置

// Facade
use Inova\NovaAdmin\Facades\SiteConfig;
SiteConfig::get('site_name', 'default');
SiteConfig::set('site_name', 'My Site');         // string
SiteConfig::set('ads_enabled', true, 'boolean'); // 按 type 存取

静态页面

后台「静态页面」管理关于、隐私政策、服务条款等富文本落地页。安装时按 nova-admin.static_pages.presets 预置一批页面(含 AdSense 法务五件套 + Cookie Policy), static_pages 表是唯一数据源——不要在项目里另建 pages 表镜像它,两套数据必然漂移。

约定:正文首个 <h1> 即页面标题(保存时自动提取为 title 并在 body_html 中剥离), Meta Description 留空时自动取正文摘要。前台模板契约三件套:

属性 说明
$page->title 页面标题(编辑器里的 H1)
$page->body_html 正文 HTML(已剥掉标题 H1,模板自行渲染 <h1>
$page->meta_description SEO 摘要

前台路由:包内自带,新项目零代码

nova-admin:install 会在 .env 写入 NOVA_STATIC_FRONTEND=true,包随即注册 GET /{slug}(仅限 presets 内的 slug,不劫持其他 URL),路由名 pages.show, 默认用包内简洁模板渲染,激活页面自动进 sitemap。后台保存,前台立即生效。

有自己视觉的项目只换视图,路由和数据流不动:

// config/nova-admin.php
'static_pages' => [
    'frontend' => [
        'enabled'    => env('NOVA_STATIC_FRONTEND', false),
        'view'       => 'pages.show',   // 换成你的 Blade,收 $page 变量
        'route_name' => 'pages.show',
    ],
],

多主题项目视图名需动态解析(如 theme_view('page'))时,关闭包路由自己写, 但数据仍读 static_page(),不要建自己的表

// routes/web.php(NOVA_STATIC_FRONTEND 保持 false)
Route::get('/{slug}', function (string $slug) {
    abort_unless($page = static_page($slug), 404);

    return view(theme_view('page'), compact('page'));
})->whereIn('slug', array_keys(config('nova-admin.static_pages.presets')))->name('pages.show');

零散场景仍可用 helper 按 slug 读取(仅返回已启用页面,未找到或停用返回 null):

@php($page = static_page('privacy-policy'))

@if ($page)
    <h1>{{ $page->title }}</h1>
    <div class="prose">{!! $page->body_html !!}</div>
@endif

body_html 为富文本 HTML,输出用 {!! !!}(内容由后台管理员录入,可信)。

Sitemap

包自带 GET /sitemap.xml(robots.txt 默认模板已指向它)。静态条目在 config nova-admin.sitemap.urls 配置;动态内容在项目 AppServiceProvider::boot 注册:

use Inova\NovaAdmin\Facades\Sitemap;

Sitemap::register(fn () => Article::published()->get()->map(fn ($a) => [
    'loc'      => route('articles.show', $a),
    'lastmod'  => $a->updated_at,          // 可选,DateTime 或字符串
    'priority' => '0.7',                   // 可选;changefreq 同理
]));

输出带缓存(sitemap.cache_ttl,默认 1800 秒),内容更新后可执行 php artisan nova-admin:clear-sitemap-cache 立即刷新;项目自带 sitemap 时置 sitemap.enabled = false 关闭包路由。

四、命令

php artisan nova-admin:install                  # 接入 Panel、建表并初始化
php artisan nova-admin:create-admin [--force]   # 创建/重置默认管理员
php artisan ad:seed [--off]                     # 填充测试广告(先清空)/ 禁用广告
php artisan nova-admin:clear-sitemap-cache       # 清 sitemap 缓存
php artisan ads:import-site-ad-config <file>    # 导入 webdeploy 下发的站点广告配置

站点广告配置下发协议(webdeploy)

webdeploy 把 GAM 广告位代码与 ads.txt 打成一个 JSON 下发到站点,本命令负责落库:

{
  "meta": { "protocol": 1 },
  "slots": {
    "global_head":   { "name": "Global", "head_code": "<script>…loader + enableServices…</script>" },
    "home_banner_1": { "name": "Home 1", "head_code": "", "body_code": "" }
  },
  "ads_txt": "google.com, pub-…, DIRECT, f08c47fec0942fa0"
}
  • slotsad_spots(按 position 覆盖式写入并置为启用),ads_txt → 与后台「Ads.txt」页同一条存储路径(DB + public/ads.txt)。 静态文件走「临时文件 + rename」原子替换,写到一半失败不会留下截断的 ads.txt;写文件失败仍会落库,由 /ads.txt 路由兜底动态输出。
  • 两个部件各自独立成败:未下发的部件不出现在回包里;下发了却写不进去的部件必须回 failed,不会静默略过。
  • slots 内部是一个事务:任一广告位结构非法、协议键未知、或映射目标未在 ad_positions 启用,整批回滚。
  • slots 必须包含 global_head(承载 loader 与 enableServices)。
  • 结果通过 stdout 的 marker 回传,这是 webdeploy 唯一认可的边界:
__SITE_AD_CONFIG_RESULT_BEGIN__{"slots":{"status":"success","written_positions":[…]},"ads_txt":{"status":"success"}}__SITE_AD_CONFIG_RESULT_END__

任一部件 failed 时命令退出码为 1。协议键与本包 position 的对应关系在 config('nova-admin.ads_protocol.position_map'):协议键带下划线(home_banner_1), 本包 position 不带(home_banner1),站点只用部分广告位时删掉对应行即可。

前台模板注意 GPT 的顺序要求——slot 定义必须早于 enableServices,即 global_head 放最后。 每个投了 <x-ad-head> 的 position,都必须在同页输出对应的 <x-ad-body>,否则后台 填的 body 代码永远不会出现在页面上(这类漏配没有任何报错,只是广告不展示):

{{-- <head> 内 --}}
<x-ad-head position="anchor" />
<x-ad-head position="interstitial" />
<x-ad-head position="global_head" />

{{-- <body> 开头,顺序与 head 一致;浮层广告自己定位,用 :wrapper="false" 去掉居中容器 --}}
<x-ad-body position="anchor" :wrapper="false" />
<x-ad-body position="interstitial" :wrapper="false" />
<x-ad-body position="global_head" :wrapper="false" />

五、配置

发布后编辑 config/nova-admin.php,常用项:

'panel'        => ['id' => 'admin'],
'ad_positions' => [ /* 自定义广告位枚举 */ ],
'ads_protocol' => ['version' => 1, 'position_map' => [ /* 协议键 => position */ ]],
'navigation'   => [
    'groups' => ['settings' => '基础设置', 'content' => '内容管理', 'system' => '系统'],
    'sort' => 90,
],
'admin'        => ['default_name' => 'nova', 'login_field' => 'name'],
'admin_brand'  => ['logo_link_to_front' => true, 'front_url' => '/', 'new_tab' => true],
'ads_txt'      => ['enabled' => true, 'empty_behavior' => 'delete'],
'site_settings' => [   // 站点设置页的上传限制,max_size 单位 KB,0 = 不限
    'favicon' => ['accepted_types' => ['image/x-icon', 'image/png'], 'max_size' => 1024],
    'logo'    => ['max_size' => 2048],
],
'robots_txt'   => ['enabled' => true, 'sitemap_url' => null],
'static_pages' => [
    'enabled' => true,
    'presets' => [ /* slug => [英文, 中文],安装时预置;置 enabled=false 关闭整个功能 */ ],
],

六、新项目如何扩展后台功能

  • 加纯业务功能(如 Game / Destination):项目正常写 Filament Resource/Page,与本包并列注册,互不干扰。
  • 给包的表加字段:项目写补充 ALTER 迁移加列,在项目自己的 Resource/Service 中使用扩展后的模型。
  • 简单业务配置:直接走 site_configs 键值(SiteConfig::set),无需建表。
  • 定制包页面视图:发布 vendor:publish --tag=nova-admin-views 后修改 Blade。

生产部署 / 升级

首次安装用 composer install,升级用 composer update inova/nova-admin,其余相同:

php artisan nova-admin:install --force
php artisan optimize:clear && php artisan optimize

nova-admin:install 会自动处理 FilamentUser 接入(避免后台 403)、发布 Filament / Livewire 静态资源、storage:link 与公开文件忽略。

生产服务器需确保 storage/public/vendor/livewire 归属 web 用户。若启用了 opcache.validate_timestamps=0,发布后 reload php-fpm。