2026-03-01 14:00:38 +08:00
|
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 文件功能:禁用用户名管理控制器(站长专用)
|
|
|
|
|
|
*
|
|
|
|
|
|
* 管理 username_blacklist 表中 type=permanent 的永久禁用词列表。
|
|
|
|
|
|
* 包含:国家领导人名称、攻击性词汇、违禁词等不允许注册或改名的词语。
|
|
|
|
|
|
*
|
|
|
|
|
|
* 用户在注册(AuthController)和改名(ShopService::useRenameCard)时均会经过该表检测。
|
|
|
|
|
|
*
|
|
|
|
|
|
* @author ChatRoom Laravel
|
|
|
|
|
|
*
|
|
|
|
|
|
* @version 1.0.0
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
namespace App\Http\Controllers\Admin;
|
|
|
|
|
|
|
|
|
|
|
|
use App\Http\Controllers\Controller;
|
|
|
|
|
|
use App\Models\UsernameBlacklist;
|
|
|
|
|
|
use Illuminate\Http\JsonResponse;
|
|
|
|
|
|
use Illuminate\Http\Request;
|
|
|
|
|
|
use Illuminate\Support\Carbon;
|
|
|
|
|
|
|
|
|
|
|
|
class ForbiddenUsernameController extends Controller
|
|
|
|
|
|
{
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 分页列出所有永久禁用词。
|
|
|
|
|
|
* 支持关键词模糊搜索(GET ?q=xxx)。
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function index(Request $request): \Illuminate\View\View
|
|
|
|
|
|
{
|
|
|
|
|
|
$q = $request->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}」已加入永久禁用列表。"]);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-01 14:04:28 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 批量添加永久禁用词。
|
|
|
|
|
|
*
|
|
|
|
|
|
* 接受多行文本或逗号分隔的词语列表,自动去重并过滤已存在者。
|
|
|
|
|
|
* 返回成功添加数量和跳过数量。
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param Request $request 请求体:words(换行/逗号分隔),reason(选填,共用)
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function batchStore(Request $request): JsonResponse
|
|
|
|
|
|
{
|
|
|
|
|
|
$validated = $request->validate([
|
|
|
|
|
|
'words' => ['required', 'string'],
|
|
|
|
|
|
'reason' => ['nullable', 'string', 'max:100'],
|
|
|
|
|
|
], [
|
|
|
|
|
|
'words.required' => '请输入至少一个词语。',
|
|
|
|
|
|
]);
|
|
|
|
|
|
|
2026-03-01 14:10:13 +08:00
|
|
|
|
// 净化输入:去除非法 UTF-8 字节(零宽字符、BOM、控制字符等),防止 json_encode 失败
|
|
|
|
|
|
$rawInput = $this->sanitizeUtf8($validated['words']);
|
|
|
|
|
|
$reason = $this->sanitizeUtf8(trim($validated['reason'] ?? ''));
|
|
|
|
|
|
|
2026-03-01 14:04:28 +08:00
|
|
|
|
// 支持换行、逗号、中文逗号、空格分隔
|
2026-03-01 14:10:13 +08:00
|
|
|
|
$rawWords = preg_split('/[\r\n,,\s]+/u', $rawInput);
|
2026-03-01 14:04:28 +08:00
|
|
|
|
|
2026-03-01 14:10:13 +08:00
|
|
|
|
// 过滤空串、超长词、去重
|
2026-03-01 14:04:28 +08:00
|
|
|
|
$words = collect($rawWords)
|
|
|
|
|
|
->map(fn ($w) => trim($w))
|
|
|
|
|
|
->filter(fn ($w) => $w !== '' && mb_strlen($w) <= 50)
|
|
|
|
|
|
->unique()
|
|
|
|
|
|
->values();
|
|
|
|
|
|
|
|
|
|
|
|
if ($words->isEmpty()) {
|
|
|
|
|
|
return response()->json(['status' => 'error', 'message' => '没有有效的词语,请检查输入。'], 422);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-01 14:10:13 +08:00
|
|
|
|
// 批量查询已存在的词(一次查询)
|
2026-03-01 14:04:28 +08:00
|
|
|
|
$existing = UsernameBlacklist::permanent()
|
|
|
|
|
|
->whereIn('username', $words->all())
|
|
|
|
|
|
->pluck('username')
|
2026-03-01 14:10:13 +08:00
|
|
|
|
->flip();
|
2026-03-01 14:04:28 +08:00
|
|
|
|
|
|
|
|
|
|
$now = Carbon::now();
|
|
|
|
|
|
$added = 0;
|
|
|
|
|
|
$rows = [];
|
|
|
|
|
|
|
|
|
|
|
|
foreach ($words as $word) {
|
|
|
|
|
|
if ($existing->has($word)) {
|
2026-03-01 14:10:13 +08:00
|
|
|
|
continue;
|
2026-03-01 14:04:28 +08:00
|
|
|
|
}
|
|
|
|
|
|
$rows[] = [
|
|
|
|
|
|
'username' => $word,
|
|
|
|
|
|
'type' => 'permanent',
|
|
|
|
|
|
'reserved_until' => null,
|
|
|
|
|
|
'reason' => $reason ?: null,
|
|
|
|
|
|
'created_at' => $now,
|
|
|
|
|
|
];
|
|
|
|
|
|
$added++;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (! empty($rows)) {
|
|
|
|
|
|
UsernameBlacklist::insert($rows);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
$skipped = $words->count() - $added;
|
|
|
|
|
|
$msg = "成功添加 {$added} 个词语".($skipped > 0 ? ",跳过 {$skipped} 个(已存在)" : '').'。';
|
|
|
|
|
|
|
2026-03-01 14:10:13 +08:00
|
|
|
|
return response()->json(['status' => 'success', 'message' => $msg, 'added' => $added], 200, [], JSON_UNESCAPED_UNICODE);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 净化字符串,移除非法 UTF-8 字节及常见控制/零宽字符。
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param string $str 待净化字符串
|
|
|
|
|
|
* @return string 合法的 UTF-8 字符串
|
|
|
|
|
|
*/
|
|
|
|
|
|
private function sanitizeUtf8(string $str): string
|
|
|
|
|
|
{
|
|
|
|
|
|
// 去除 BOM
|
|
|
|
|
|
$str = str_replace("\xEF\xBB\xBF", '', $str);
|
|
|
|
|
|
// 去除零宽字符(零宽空格、零宽不连字等)
|
|
|
|
|
|
$str = preg_replace('/[\x{200B}-\x{200D}\x{FEFF}\x{00AD}]/u', '', $str);
|
|
|
|
|
|
// 转换为合法 UTF-8,忽略非法字节
|
|
|
|
|
|
$str = mb_convert_encoding($str, 'UTF-8', 'UTF-8');
|
|
|
|
|
|
// 保底:去除控制字符(保留换行 \r\n)
|
|
|
|
|
|
$str = preg_replace('/[^\P{C}\r\n]+/u', '', $str);
|
|
|
|
|
|
|
|
|
|
|
|
return $str;
|
2026-03-01 14:04:28 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-01 14:00:38 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 更新指定禁用词的原因备注。
|
|
|
|
|
|
*
|
|
|
|
|
|
* @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}」已从永久禁用列表移除。"]);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|