From f1b9d1e1c38979a0e49203416c04d95fd334ce20 Mon Sep 17 00:00:00 2001 From: ak Date: Thu, 13 Aug 2026 02:05:32 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E5=A4=96=E9=93=BE=E4=B8=AD?= =?UTF-8?q?=E8=BD=AC=E6=8B=86=E4=B8=BA=E5=86=85=E7=BD=AE=E6=8F=92=E4=BB=B6?= =?UTF-8?q?=20neatstudio.link-tracker?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 插件承载全部逻辑:redirects 迁移(hasTable 兼容旧库)、Redirect 模型、UrlRedirector、/go 路由+控制器、Filament 外链中转资源 - 核心只留 hook 接入点: - tracked_url() 助手走 link.redirect 过滤器(评论正文/作者网址/友链模板统一调用) - 正文外链改写移到插件的 post.rendered 过滤器(优先级 20,晚于会员付费过滤) - 停用插件:链接恢复直连、内容不受影响;后台「管理 → 外链中转」查看点击量/启停 - 测试更新为插件上下文(含无过滤器降级断言);97 通过 --- README.md | 6 ++-- app/Blog/Services/PostContentRenderer.php | 26 +---------------- app/Support/helpers.php | 13 +++++---- config/plugins.php | 1 + ...26_08_12_400000_create_redirects_table.php | 5 ++++ plugins/neatstudio.link-tracker/plugin.json | 12 ++++++++ .../neatstudio.link-tracker/routes/web.php | 8 ++++++ .../Resources}/Pages/ListRedirects.php | 4 +-- .../Filament/Resources/RedirectResource.php | 6 ++-- .../src/Http}/GoController.php | 4 +-- .../src}/Models/Redirect.php | 2 +- .../src/ServiceProvider.php | 27 ++++++++++++++++++ .../src}/Services/UrlRedirector.php | 25 +++++++++++++++-- resources/views/links.blade.php | 2 +- resources/views/partials/sidebar.blade.php | 2 +- resources/views/show.blade.php | 2 +- routes/web.php | 3 -- tests/Feature/GoRedirectTest.php | 28 +++++++++++-------- .../sablog/views/partials/sidebar.blade.php | 2 +- themes/sablog/views/show.blade.php | 2 +- 20 files changed, 118 insertions(+), 62 deletions(-) rename {database => plugins/neatstudio.link-tracker/database}/migrations/2026_08_12_400000_create_redirects_table.php (86%) create mode 100644 plugins/neatstudio.link-tracker/plugin.json create mode 100644 plugins/neatstudio.link-tracker/routes/web.php rename {app/Filament/Resources/RedirectResource => plugins/neatstudio.link-tracker/src/Filament/Resources}/Pages/ListRedirects.php (57%) rename {app => plugins/neatstudio.link-tracker/src}/Filament/Resources/RedirectResource.php (94%) rename {app/Blog/Controllers => plugins/neatstudio.link-tracker/src/Http}/GoController.php (82%) rename {app => plugins/neatstudio.link-tracker/src}/Models/Redirect.php (91%) create mode 100644 plugins/neatstudio.link-tracker/src/ServiceProvider.php rename {app/Blog => plugins/neatstudio.link-tracker/src}/Services/UrlRedirector.php (71%) diff --git a/README.md b/README.md index 91fab89..032a167 100644 --- a/README.md +++ b/README.md @@ -104,11 +104,13 @@ php artisan workerman:serve stop - 失败重试:`WORKERMAN_MAX_TRIES`(默认 3),超时 `WORKERMAN_TIMEOUT`(默认 60s),重试耗尽进 `failed_jobs` 表 - 降级路径:`php artisan queue:work` 照常可用(同一队列) -## 外链中转(/go) +## 外链中转(/go,内置插件) +内置插件 `neatstudio.link-tracker`(外链中转): - 正文外链、评论正文 URL、评论作者网址、友链统一改写为 `/go/{hash}`:302 跳转 + **点击统计**、可**停用拦截**(返回 410)、隐藏真实链接、为未来广告位预留 - 站内链接/相对路径/mailto/tel/锚点不中转;同一 URL 复用同一短码 -- 后台「外链中转」可查看点击量、按状态筛选、启停 +- 后台「管理 → 外链中转」可查看点击量、启停、删除 +- **插件化设计**:核心提供 `tracked_url()` 钩子(`link.redirect` 过滤器)+ 正文 `post.rendered` 过滤器接入点,停用插件后链接恢复直连、不影响内容显示 ## 前台缓存 diff --git a/app/Blog/Services/PostContentRenderer.php b/app/Blog/Services/PostContentRenderer.php index e24c0df..c01a381 100644 --- a/app/Blog/Services/PostContentRenderer.php +++ b/app/Blog/Services/PostContentRenderer.php @@ -46,31 +46,7 @@ class PostContentRenderer $html = app(\App\Blog\Support\PluginManager::class)->applyFilters('post.rendered', $html, $post); // 兜底:任何情况下不把裸 [paid] 标签输出到页面(如会员插件被停用时) - $html = preg_replace('/\[(\/?paid)\]/i', '', $html); - - // 外链中转:站外链接改写为 /go/{hash}(点击统计/拦截) - return $this->rewriteExternalLinks($html); - } - - /** - * 把正文里的站外 改写为 /go 中转地址(站内/相对/锚点/mailto 不变)。 - */ - private function rewriteExternalLinks(string $html): string - { - return preg_replace_callback( - '/]*?href=["\'])([^"\']+)(["\'][^>]*)>(.*?)<\/a>/is', - function (array $m): string { - $url = html_entity_decode(trim($m[2])); - $go = go_url($url); - - if ($go === $url) { - return $m[0]; - } - - return ''.$m[4].''; - }, - $html - ); + return preg_replace('/\[(\/?paid)\]/i', '', $html); } /** diff --git a/app/Support/helpers.php b/app/Support/helpers.php index 40631ac..a0be646 100644 --- a/app/Support/helpers.php +++ b/app/Support/helpers.php @@ -43,19 +43,20 @@ if (! function_exists('blog_setting')) { } } -if (! function_exists('go_url')) { +if (! function_exists('tracked_url')) { /** - * 外链中转:站外链接改写为 /go/{hash}(点击统计/拦截),站内链接原样返回。 + * 外链追踪钩子:核心视图统一调用,由插件(外链中转)改写为 /go/{hash}; + * 插件停用时原样返回。站内链接等由插件自行判断。 */ - function go_url(string $url): string + function tracked_url(string $url): string { - return app(\App\Blog\Services\UrlRedirector::class)->goUrl($url); + return app(\App\Blog\Support\PluginManager::class)->applyFilters('link.redirect', $url); } } if (! function_exists('comment_body')) { /** - * 评论正文:转义 + 自动链接 URL(走 /go 中转,nofollow)。 + * 评论正文:转义 + 自动链接 URL(经 tracked_url 走中转,nofollow)。 */ function comment_body(string $text): string { @@ -63,7 +64,7 @@ if (! function_exists('comment_body')) { $text = preg_replace_callback( '#(https?://[^\s<>"\'()]+)#i', - fn (array $m) => ''.$m[1].'', + fn (array $m) => ''.$m[1].'', $text ); diff --git a/config/plugins.php b/config/plugins.php index dd6acfc..36e38d2 100644 --- a/config/plugins.php +++ b/config/plugins.php @@ -25,6 +25,7 @@ return [ 'neatstudio.ai-moderation', 'neatstudio.payment', 'neatstudio.membership', + 'neatstudio.link-tracker', ], /* diff --git a/database/migrations/2026_08_12_400000_create_redirects_table.php b/plugins/neatstudio.link-tracker/database/migrations/2026_08_12_400000_create_redirects_table.php similarity index 86% rename from database/migrations/2026_08_12_400000_create_redirects_table.php rename to plugins/neatstudio.link-tracker/database/migrations/2026_08_12_400000_create_redirects_table.php index e6645c9..a22788a 100644 --- a/database/migrations/2026_08_12_400000_create_redirects_table.php +++ b/plugins/neatstudio.link-tracker/database/migrations/2026_08_12_400000_create_redirects_table.php @@ -10,6 +10,11 @@ return new class extends Migration { public function up(): void { + // 兼容已由核心迁移建过表的旧库 + if (Schema::hasTable('redirects')) { + return; + } + Schema::create('redirects', function (Blueprint $table) { $table->id(); $table->string('hash', 16)->unique(); diff --git a/plugins/neatstudio.link-tracker/plugin.json b/plugins/neatstudio.link-tracker/plugin.json new file mode 100644 index 0000000..9d3ed02 --- /dev/null +++ b/plugins/neatstudio.link-tracker/plugin.json @@ -0,0 +1,12 @@ +{ + "title": "外链中转", + "version": "1.0.0", + "description": "站外链接改写为 /go/{hash}:302 跳转 + 点击统计 + 可停用拦截 + 隐藏真实链接,为广告位预留", + "usage": "启用后自动生效:\n1. 正文外链、评论正文 URL、评论作者网址、友链统一改写为 /go/{hash}\n2. 点击 /go/{hash} 302 跳转并计数;后台「管理 → 外链中转」查看点击量/启停\n3. 停用插件后链接恢复直连(不影响正文显示)", + "author": "LaraLog", + "type": "core", + "provider": "Plugins\\Neatstudio\\LinkTracker\\ServiceProvider", + "filament_resources": [ + "Plugins\\Neatstudio\\LinkTracker\\Filament\\Resources\\RedirectResource" + ] +} diff --git a/plugins/neatstudio.link-tracker/routes/web.php b/plugins/neatstudio.link-tracker/routes/web.php new file mode 100644 index 0000000..5119b83 --- /dev/null +++ b/plugins/neatstudio.link-tracker/routes/web.php @@ -0,0 +1,8 @@ +name('go.show'); diff --git a/app/Filament/Resources/RedirectResource/Pages/ListRedirects.php b/plugins/neatstudio.link-tracker/src/Filament/Resources/Pages/ListRedirects.php similarity index 57% rename from app/Filament/Resources/RedirectResource/Pages/ListRedirects.php rename to plugins/neatstudio.link-tracker/src/Filament/Resources/Pages/ListRedirects.php index 5616a2b..62996bb 100644 --- a/app/Filament/Resources/RedirectResource/Pages/ListRedirects.php +++ b/plugins/neatstudio.link-tracker/src/Filament/Resources/Pages/ListRedirects.php @@ -2,10 +2,10 @@ declare(strict_types=1); -namespace App\Filament\Resources\RedirectResource\Pages; +namespace Plugins\Neatstudio\LinkTracker\Filament\Resources\Pages; -use App\Filament\Resources\RedirectResource; use Filament\Resources\Pages\ListRecords; +use Plugins\Neatstudio\LinkTracker\Filament\Resources\RedirectResource; class ListRedirects extends ListRecords { diff --git a/app/Filament/Resources/RedirectResource.php b/plugins/neatstudio.link-tracker/src/Filament/Resources/RedirectResource.php similarity index 94% rename from app/Filament/Resources/RedirectResource.php rename to plugins/neatstudio.link-tracker/src/Filament/Resources/RedirectResource.php index 257d41c..20e6225 100644 --- a/app/Filament/Resources/RedirectResource.php +++ b/plugins/neatstudio.link-tracker/src/Filament/Resources/RedirectResource.php @@ -2,10 +2,8 @@ declare(strict_types=1); -namespace App\Filament\Resources; +namespace Plugins\Neatstudio\LinkTracker\Filament\Resources; -use App\Filament\Resources\RedirectResource\Pages\ListRedirects; -use App\Models\Redirect; use BackedEnum; use Filament\Actions\Action; use Filament\Actions\DeleteAction; @@ -14,6 +12,8 @@ use Filament\Schemas\Schema; use Filament\Support\Icons\Heroicon; use Filament\Tables\Columns\TextColumn; use Filament\Tables\Table; +use Plugins\Neatstudio\LinkTracker\Filament\Resources\Pages\ListRedirects; +use Plugins\Neatstudio\LinkTracker\Models\Redirect; class RedirectResource extends Resource { diff --git a/app/Blog/Controllers/GoController.php b/plugins/neatstudio.link-tracker/src/Http/GoController.php similarity index 82% rename from app/Blog/Controllers/GoController.php rename to plugins/neatstudio.link-tracker/src/Http/GoController.php index 0d1f08c..76392a1 100644 --- a/app/Blog/Controllers/GoController.php +++ b/plugins/neatstudio.link-tracker/src/Http/GoController.php @@ -2,9 +2,9 @@ declare(strict_types=1); -namespace App\Blog\Controllers; +namespace Plugins\Neatstudio\LinkTracker\Http; -use App\Models\Redirect; +use Plugins\Neatstudio\LinkTracker\Models\Redirect; class GoController { diff --git a/app/Models/Redirect.php b/plugins/neatstudio.link-tracker/src/Models/Redirect.php similarity index 91% rename from app/Models/Redirect.php rename to plugins/neatstudio.link-tracker/src/Models/Redirect.php index 24774d0..aa6094d 100644 --- a/app/Models/Redirect.php +++ b/plugins/neatstudio.link-tracker/src/Models/Redirect.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace App\Models; +namespace Plugins\Neatstudio\LinkTracker\Models; use Illuminate\Database\Eloquent\Model; diff --git a/plugins/neatstudio.link-tracker/src/ServiceProvider.php b/plugins/neatstudio.link-tracker/src/ServiceProvider.php new file mode 100644 index 0000000..7010417 --- /dev/null +++ b/plugins/neatstudio.link-tracker/src/ServiceProvider.php @@ -0,0 +1,27 @@ +loadRoutes(__DIR__.'/../routes/web.php'); + + // 核心视图(评论正文/作者网址/友链)通过 tracked_url() 走此过滤器改写 + $manager->addFilter('link.redirect', function (string $url) { + return app(UrlRedirector::class)->goUrl($url); + }, 10); + + // 正文外链改写:优先级 20,晚于会员付费内容过滤(10),保证 paywall 也覆盖 + $manager->addFilter('post.rendered', function (string $html, \App\Models\Post $post) { + return app(UrlRedirector::class)->rewriteExternalLinks($html); + }, 20); + } +} diff --git a/app/Blog/Services/UrlRedirector.php b/plugins/neatstudio.link-tracker/src/Services/UrlRedirector.php similarity index 71% rename from app/Blog/Services/UrlRedirector.php rename to plugins/neatstudio.link-tracker/src/Services/UrlRedirector.php index 7000d54..21b945c 100644 --- a/app/Blog/Services/UrlRedirector.php +++ b/plugins/neatstudio.link-tracker/src/Services/UrlRedirector.php @@ -2,11 +2,11 @@ declare(strict_types=1); -namespace App\Blog\Services; +namespace Plugins\Neatstudio\LinkTracker\Services; -use App\Models\Redirect; use Illuminate\Database\UniqueConstraintViolationException; use Illuminate\Support\Facades\Cache; +use Plugins\Neatstudio\LinkTracker\Models\Redirect; /** * 外链中转:把站外链接改写为 /go/{hash},用于点击统计、二次拦截与未来广告位。 @@ -69,4 +69,25 @@ class UrlRedirector return $hash; }); } + + /** + * 把 HTML 里的站外 改写为 /go 中转地址(站内/相对/锚点/mailto 不变)。 + */ + public function rewriteExternalLinks(string $html): string + { + return preg_replace_callback( + '/]*?href=["\'])([^"\']+)(["\'][^>]*)>(.*?)<\/a>/is', + function (array $m): string { + $url = html_entity_decode(trim($m[2])); + $go = $this->goUrl($url); + + if ($go === $url) { + return $m[0]; + } + + return ''.$m[4].''; + }, + $html + ); + } } diff --git a/resources/views/links.blade.php b/resources/views/links.blade.php index 42d9e35..fb7384c 100644 --- a/resources/views/links.blade.php +++ b/resources/views/links.blade.php @@ -8,7 +8,7 @@