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 Request $request 请求体:words(换行/逗号分隔),reason(选填,共用) */ public function batchStore(Request $request): JsonResponse { $validated = $request->validate([ 'words' => ['required', 'string'], 'reason' => ['nullable', 'string', 'max:100'], ], [ 'words.required' => '请输入至少一个词语。', ]); // 净化输入:去除非法 UTF-8 字节(零宽字符、BOM、控制字符等),防止 json_encode 失败 $rawInput = $this->sanitizeUtf8($validated['words']); $reason = $this->sanitizeUtf8(trim($validated['reason'] ?? '')); // 支持换行、逗号、中文逗号、空格分隔 $rawWords = preg_split('/[\r\n,,\s]+/u', $rawInput); // 过滤空串、超长词、去重 $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); } // 批量查询已存在的词(一次查询) $existing = UsernameBlacklist::permanent() ->whereIn('username', $words->all()) ->pluck('username') ->flip(); $now = Carbon::now(); $added = 0; $rows = []; foreach ($words as $word) { if ($existing->has($word)) { continue; } $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} 个(已存在)" : '').'。'; 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; } /** * 更新指定禁用词的原因备注。 * * @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}」已从永久禁用列表移除。"]); } }