diff --git a/app/Http/Controllers/Admin/ForbiddenUsernameController.php b/app/Http/Controllers/Admin/ForbiddenUsernameController.php new file mode 100644 index 0000000..5850019 --- /dev/null +++ b/app/Http/Controllers/Admin/ForbiddenUsernameController.php @@ -0,0 +1,115 @@ +query('q', ''); + + $items = UsernameBlacklist::permanent() + ->when($q, fn ($query) => $query->where('username', 'like', "%{$q}%")) + ->orderByDesc('created_at') + ->paginate(20) + ->withQueryString(); + + return view('admin.forbidden-usernames.index', [ + 'items' => $items, + 'q' => $q, + ]); + } + + /** + * 新增一条永久禁用词。 + * + * @param Request $request 请求体:username(必填),reason(选填) + */ + public function store(Request $request): JsonResponse + { + $validated = $request->validate([ + 'username' => ['required', 'string', 'max:50'], + 'reason' => ['nullable', 'string', 'max:100'], + ], [ + 'username.required' => '禁用词不能为空。', + 'username.max' => '禁用词最长50字符。', + ]); + + $username = trim($validated['username']); + + // 已存在同名的永久记录则不重复插入 + $exists = UsernameBlacklist::permanent() + ->where('username', $username) + ->exists(); + + if ($exists) { + return response()->json(['status' => 'error', 'message' => '该词语已在永久禁用列表中。'], 422); + } + + UsernameBlacklist::create([ + 'username' => $username, + 'type' => 'permanent', + 'reserved_until' => null, + 'reason' => $validated['reason'] ?? null, + 'created_at' => Carbon::now(), + ]); + + return response()->json(['status' => 'success', 'message' => "「{$username}」已加入永久禁用列表。"]); + } + + /** + * 更新指定禁用词的原因备注。 + * + * @param int $id 记录 ID + * @param Request $request 请求体:reason + */ + public function update(Request $request, int $id): JsonResponse + { + $item = UsernameBlacklist::permanent()->findOrFail($id); + + $validated = $request->validate([ + 'reason' => ['nullable', 'string', 'max:100'], + ]); + + $item->update(['reason' => $validated['reason']]); + + return response()->json(['status' => 'success', 'message' => '备注已更新。']); + } + + /** + * 删除指定永久禁用词。 + * + * @param int $id 记录 ID + */ + public function destroy(int $id): JsonResponse + { + $item = UsernameBlacklist::permanent()->findOrFail($id); + $name = $item->username; + $item->delete(); + + return response()->json(['status' => 'success', 'message' => "「{$name}」已从永久禁用列表移除。"]); + } +} diff --git a/app/Http/Controllers/AuthController.php b/app/Http/Controllers/AuthController.php index 58d80e6..d151e42 100644 --- a/app/Http/Controllers/AuthController.php +++ b/app/Http/Controllers/AuthController.php @@ -11,10 +11,11 @@ namespace App\Http\Controllers; use App\Http\Requests\LoginRequest; +use App\Models\Sysparam; use App\Models\User; +use App\Models\UsernameBlacklist; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; -use App\Models\Sysparam; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Redis; @@ -99,6 +100,11 @@ class AuthController extends Controller return response()->json(['status' => 'error', 'message' => '您所在的 IP 地址已被管理员封禁,禁止注册新账号。'], 403); } + // 检测用户名是否在禁用词列表(永久禁用 或 改名临时保留期内) + if (UsernameBlacklist::isBlocked($username)) { + return response()->json(['status' => 'error', 'message' => '该用户名已被系统禁止注册,请更换其他名称。'], 422); + } + $newUser = User::create([ 'username' => $username, 'password' => Hash::make($password), @@ -123,7 +129,7 @@ class AuthController extends Controller // 递增访问次数 $user->increment('visit_num'); - + // 更新最后登录IP和时间 $user->update([ 'last_ip' => $ip, diff --git a/app/Models/UsernameBlacklist.php b/app/Models/UsernameBlacklist.php index 10f92ca..91c1c76 100644 --- a/app/Models/UsernameBlacklist.php +++ b/app/Models/UsernameBlacklist.php @@ -2,7 +2,16 @@ /** * 文件功能:用户名黑名单模型 - * 用户改名后旧名称写入此表,保留期间其他人无法注册该名称 + * + * 支持两种类型的禁用记录: + * temp — 用户改名后旧名称的临时保留(30天),到期后其他人可注册 + * permanent — 管理员设置的永久禁用词(攻击性词汇、领导人名称等),永不可注册 + * + * 凡是 isBlocked() 返回 true 的名称,无论注册还是改名均不允许使用。 + * + * @author ChatRoom Laravel + * + * @version 2.0.0 */ namespace App\Models; @@ -13,22 +22,76 @@ class UsernameBlacklist extends Model { protected $table = 'username_blacklist'; - public $timestamps = false; // 只有 created_at 无 updated_at + /** 只有 created_at,无 updated_at */ + public $timestamps = false; - protected $fillable = ['username', 'reserved_until', 'created_at']; + protected $fillable = ['username', 'type', 'reserved_until', 'reason', 'created_at']; - protected $casts = [ - 'reserved_until' => 'datetime', - 'created_at' => 'datetime', - ]; + protected function casts(): array + { + return [ + 'reserved_until' => 'datetime', + 'created_at' => 'datetime', + ]; + } + + // ────────────────────────────────────────── + // 公共查询方法 + // ────────────────────────────────────────── /** - * 判断给定名称是否在黑名单有效期内 + * 判断给定名称是否被禁止使用。 + * + * 满足以下任一条件时返回 true: + * 1. 存在 type=permanent 的永久禁用记录 + * 2. 存在 type=temp 且 reserved_until 尚未过期的临时保留记录 + * + * @param string $username 要检测的用户名 + */ + public static function isBlocked(string $username): bool + { + return static::where('username', $username) + ->where(function ($q) { + // 永久禁用 + $q->where('type', 'permanent') + // 或:临时保留且尚未到期 + ->orWhere(function ($q2) { + $q2->where('type', 'temp') + ->where('reserved_until', '>', now()); + }); + }) + ->exists(); + } + + /** + * 兼容旧调用:isReserved() 等同于 isBlocked()。 + * + * @param string $username 要检测的用户名 + * + * @deprecated 请使用 isBlocked() */ public static function isReserved(string $username): bool { - return static::where('username', $username) - ->where('reserved_until', '>', now()) - ->exists(); + return static::isBlocked($username); + } + + // ────────────────────────────────────────── + // 作用域(Scopes) + // ────────────────────────────────────────── + + /** + * 仅查询永久禁用词(管理员维护的列表)。 + */ + public function scopePermanent($query) + { + return $query->where('type', 'permanent'); + } + + /** + * 仅查询临时保留记录(改名后旧名)。 + */ + public function scopeTemp($query) + { + return $query->where('type', 'temp'); } } diff --git a/database/migrations/2026_03_01_135706_add_type_reason_to_username_blacklist.php b/database/migrations/2026_03_01_135706_add_type_reason_to_username_blacklist.php new file mode 100644 index 0000000..e88a6b7 --- /dev/null +++ b/database/migrations/2026_03_01_135706_add_type_reason_to_username_blacklist.php @@ -0,0 +1,45 @@ +enum('type', ['temp', 'permanent']) + ->default('temp') + ->after('username') + ->comment('temp=改名临时保留 | permanent=管理员永久禁用词'); + + // 禁用原因(permanent 时填写) + $table->string('reason', 100) + ->nullable() + ->after('reserved_until') + ->comment('永久禁用原因(攻击性词汇、领导人名称等)'); + }); + } + + /** + * 回滚:删除新增列。 + */ + public function down(): void + { + Schema::table('username_blacklist', function (Blueprint $table) { + $table->dropColumn(['type', 'reason']); + }); + } +}; diff --git a/resources/views/admin/forbidden-usernames/index.blade.php b/resources/views/admin/forbidden-usernames/index.blade.php new file mode 100644 index 0000000..a66d1a4 --- /dev/null +++ b/resources/views/admin/forbidden-usernames/index.blade.php @@ -0,0 +1,210 @@ +{{-- + 文件功能:后台禁用用户名管理页面(仅站长 id=1 可访问) + + 管理 username_blacklist.type=permanent 的永久禁止词列表。 + 用户在注册或使用改名卡时,系统自动查询此表进行拦截。 + + @extends admin.layouts.app +--}} +@extends('admin.layouts.app') + +@section('title', '禁用用户名管理') + +@section('content') + + {{-- 标题 --}} +
+
+

🚫 禁用用户名管理

+

+ 以下词语永久禁止注册或改名使用(领导人名称、攻击性词汇、违禁词等)。 + 临时保留的旧昵称(改名后30天)不在此列表中显示。 +

+
+
+ + {{-- 新增表单 + 搜索栏 --}} +
+ + {{-- 新增卡片 --}} +
+

➕ 新增禁用词

+
+
+ + +
+
+ + +
+ +

+
+
+ + {{-- 搜索卡片 --}} +
+

🔍 搜索禁用词

+
+ +
+ + @if ($q) + + ✕ 清除 + + @endif +
+
+

+ 共 {{ $items->total() }} 条永久禁用词 + @if ($q) + (当前筛选"{{ $q }}") + @endif +

+
+
+ + {{-- 禁用词列表 --}} +
+ + + + + + + + + + + @forelse ($items as $item) + + {{-- 词语 --}} + + {{-- 原因(点击编辑) --}} + + {{-- 时间 --}} + + {{-- 操作 --}} + + + @empty + + + + @endforelse + +
词语禁用原因添加时间操作
+ + {{ $item->username }} + + + +
+ + + +
+
+ {{ $item->created_at?->format('Y-m-d H:i') ?? '-' }} + + +
+

🚫

+

暂无禁用词

+

使用左侧表单添加第一条禁用词

+
+
+ + {{-- 分页 --}} + @if ($items->hasPages()) +
+ {{ $items->links() }} +
+ @endif + +@endsection diff --git a/resources/views/admin/layouts/app.blade.php b/resources/views/admin/layouts/app.blade.php index 17535ea..223b65d 100644 --- a/resources/views/admin/layouts/app.blade.php +++ b/resources/views/admin/layouts/app.blade.php @@ -101,6 +101,10 @@ class="bg-orange-500 text-white text-xs px-1.5 py-0.5 rounded-full font-bold">{{ $pendingFeedback }} @endif + + 🚫 禁用用户名 + @endif @endif diff --git a/routes/web.php b/routes/web.php index 917cb44..53fb87c 100644 --- a/routes/web.php +++ b/routes/web.php @@ -261,5 +261,11 @@ Route::middleware(['chat.auth', 'chat.has_position'])->prefix('admin')->name('ad // 用户反馈管理 Route::get('/feedback', [\App\Http\Controllers\Admin\FeedbackManagerController::class, 'index'])->name('feedback.index'); Route::put('/feedback/{id}', [\App\Http\Controllers\Admin\FeedbackManagerController::class, 'update'])->name('feedback.update'); + + // 禁用用户名管理(永久禁止注册/改名的词语:领导人名称、攻击性词汇等) + Route::get('/forbidden-usernames', [\App\Http\Controllers\Admin\ForbiddenUsernameController::class, 'index'])->name('forbidden-usernames.index'); + Route::post('/forbidden-usernames', [\App\Http\Controllers\Admin\ForbiddenUsernameController::class, 'store'])->name('forbidden-usernames.store'); + Route::put('/forbidden-usernames/{id}', [\App\Http\Controllers\Admin\ForbiddenUsernameController::class, 'update'])->name('forbidden-usernames.update'); + Route::delete('/forbidden-usernames/{id}', [\App\Http\Controllers\Admin\ForbiddenUsernameController::class, 'destroy'])->name('forbidden-usernames.destroy'); }); });