功能:VIP 赞助会员系统
- 新建 vip_levels 表(名称、图标、颜色、经验/金币倍率、专属进入/离开模板) - 默认4个等级种子:白银🥈(×1.5)、黄金🥇(×2.0)、钻石💎(×3.0)、至尊👑(×5.0) - 后台 VIP 等级 CRUD 管理(新增/编辑/删除,配置模板和倍率) - 后台用户编辑弹窗支持设置 VIP 等级和到期时间 - ChatController 心跳经验按 VIP 倍率加成 - FishingController 正向奖励按 VIP 倍率加成(负面惩罚不变) - 在线名单显示 VIP 图标和管理员🛡️标识 - VIP 用户进入/离开使用专属颜色和标题 - 后台侧栏新增「👑 VIP 会员等级」入口
This commit is contained in:
218
app/Http/Controllers/Admin/AiProviderController.php
Normal file
218
app/Http/Controllers/Admin/AiProviderController.php
Normal file
@@ -0,0 +1,218 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* 文件功能:AI 厂商配置管理控制器(管理后台)
|
||||
*
|
||||
* 提供完整的 AI 厂商 CRUD 管理功能,包括:
|
||||
* - 列表/新增/编辑/删除 AI 厂商配置
|
||||
* - 切换启用/禁用状态
|
||||
* - 设置默认厂商(互斥,同时只有一个默认)
|
||||
* - 全局开关控制机器人是否启用
|
||||
*
|
||||
* @author ChatRoom Laravel
|
||||
*
|
||||
* @version 1.0.0
|
||||
*/
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\AiProviderConfig;
|
||||
use App\Models\Sysparam;
|
||||
use App\Services\ChatStateService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Crypt;
|
||||
use Illuminate\View\View;
|
||||
|
||||
class AiProviderController extends Controller
|
||||
{
|
||||
/**
|
||||
* 构造函数
|
||||
*/
|
||||
public function __construct(
|
||||
private readonly ChatStateService $chatState,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* 显示 AI 厂商配置列表页面
|
||||
*
|
||||
* 列出所有已配置的 AI 厂商,并显示全局开关状态。
|
||||
*
|
||||
* @return View AI 厂商管理页面
|
||||
*/
|
||||
public function index(): View
|
||||
{
|
||||
$providers = AiProviderConfig::orderBy('sort_order')->get();
|
||||
$chatbotEnabled = Sysparam::getValue('chatbot_enabled', '0') === '1';
|
||||
|
||||
return view('admin.ai-providers.index', compact('providers', 'chatbotEnabled'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增 AI 厂商配置
|
||||
*
|
||||
* @param Request $request 请求对象
|
||||
* @return RedirectResponse 重定向到列表页
|
||||
*/
|
||||
public function store(Request $request): RedirectResponse
|
||||
{
|
||||
$data = $request->validate([
|
||||
'provider' => 'required|string|max:50',
|
||||
'name' => 'required|string|max:100',
|
||||
'api_key' => 'required|string',
|
||||
'api_endpoint' => 'required|url|max:255',
|
||||
'model' => 'required|string|max:100',
|
||||
'temperature' => 'nullable|numeric|min:0|max:2',
|
||||
'max_tokens' => 'nullable|integer|min:100|max:32000',
|
||||
'sort_order' => 'nullable|integer|min:0',
|
||||
]);
|
||||
|
||||
// 加密 API Key
|
||||
$data['api_key'] = Crypt::encryptString($data['api_key']);
|
||||
$data['temperature'] = $data['temperature'] ?? 0.3;
|
||||
$data['max_tokens'] = $data['max_tokens'] ?? 2048;
|
||||
$data['sort_order'] = $data['sort_order'] ?? 0;
|
||||
$data['is_enabled'] = true;
|
||||
$data['is_default'] = false;
|
||||
|
||||
AiProviderConfig::create($data);
|
||||
|
||||
return redirect()->route('admin.ai-providers.index')
|
||||
->with('success', '已成功添加 AI 厂商配置!');
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新 AI 厂商配置
|
||||
*
|
||||
* @param Request $request 请求对象
|
||||
* @param int $id 厂商配置 ID
|
||||
* @return RedirectResponse 重定向到列表页
|
||||
*/
|
||||
public function update(Request $request, int $id): RedirectResponse
|
||||
{
|
||||
$provider = AiProviderConfig::findOrFail($id);
|
||||
|
||||
$data = $request->validate([
|
||||
'provider' => 'required|string|max:50',
|
||||
'name' => 'required|string|max:100',
|
||||
'api_key' => 'nullable|string',
|
||||
'api_endpoint' => 'required|url|max:255',
|
||||
'model' => 'required|string|max:100',
|
||||
'temperature' => 'nullable|numeric|min:0|max:2',
|
||||
'max_tokens' => 'nullable|integer|min:100|max:32000',
|
||||
'sort_order' => 'nullable|integer|min:0',
|
||||
]);
|
||||
|
||||
// 只在用户提供了新 API Key 时才更新(空值表示不修改)
|
||||
if (! empty($data['api_key'])) {
|
||||
$data['api_key'] = Crypt::encryptString($data['api_key']);
|
||||
} else {
|
||||
unset($data['api_key']);
|
||||
}
|
||||
|
||||
$provider->update($data);
|
||||
|
||||
return redirect()->route('admin.ai-providers.index')
|
||||
->with('success', "已更新 {$provider->name} 的配置!");
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换 AI 厂商的启用/禁用状态
|
||||
*
|
||||
* @param int $id 厂商配置 ID
|
||||
* @return JsonResponse 操作结果
|
||||
*/
|
||||
public function toggleEnabled(int $id): JsonResponse
|
||||
{
|
||||
$provider = AiProviderConfig::findOrFail($id);
|
||||
$provider->is_enabled = ! $provider->is_enabled;
|
||||
$provider->save();
|
||||
|
||||
$status = $provider->is_enabled ? '启用' : '禁用';
|
||||
|
||||
return response()->json([
|
||||
'status' => 'success',
|
||||
'message' => "{$provider->name} 已{$status}",
|
||||
'is_enabled' => $provider->is_enabled,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置指定厂商为默认使用
|
||||
*
|
||||
* 互斥操作:将其他厂商的 is_default 全部置为 false。
|
||||
*
|
||||
* @param int $id 厂商配置 ID
|
||||
* @return JsonResponse 操作结果
|
||||
*/
|
||||
public function setDefault(int $id): JsonResponse
|
||||
{
|
||||
$provider = AiProviderConfig::findOrFail($id);
|
||||
|
||||
// 先将所有厂商的默认标记清除
|
||||
AiProviderConfig::where('is_default', true)->update(['is_default' => false]);
|
||||
|
||||
// 设置当前厂商为默认
|
||||
$provider->is_default = true;
|
||||
$provider->is_enabled = true; // 默认的必须是启用状态
|
||||
$provider->save();
|
||||
|
||||
return response()->json([
|
||||
'status' => 'success',
|
||||
'message' => "{$provider->name} 已设为默认 AI 厂商",
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换聊天机器人全局开关
|
||||
*
|
||||
* 通过 sysparam 的 chatbot_enabled 参数控制是否在聊天室中显示 AI 机器人。
|
||||
*
|
||||
* @param Request $request 请求对象
|
||||
* @return JsonResponse 操作结果
|
||||
*/
|
||||
public function toggleChatBot(Request $request): JsonResponse
|
||||
{
|
||||
$current = Sysparam::getValue('chatbot_enabled', '0');
|
||||
$newValue = $current === '1' ? '0' : '1';
|
||||
|
||||
// 更新 sysparam
|
||||
Sysparam::updateOrCreate(
|
||||
['alias' => 'chatbot_enabled'],
|
||||
[
|
||||
'body' => $newValue,
|
||||
'guidetxt' => 'AI聊天机器人开关(1=开启,0=关闭)',
|
||||
]
|
||||
);
|
||||
|
||||
// 刷新缓存
|
||||
$this->chatState->setSysParam('chatbot_enabled', $newValue);
|
||||
Sysparam::clearCache('chatbot_enabled');
|
||||
|
||||
$status = $newValue === '1' ? '开启' : '关闭';
|
||||
|
||||
return response()->json([
|
||||
'status' => 'success',
|
||||
'message' => "聊天机器人已{$status}",
|
||||
'enabled' => $newValue === '1',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除 AI 厂商配置
|
||||
*
|
||||
* @param int $id 厂商配置 ID
|
||||
* @return RedirectResponse 重定向到列表页
|
||||
*/
|
||||
public function destroy(int $id): RedirectResponse
|
||||
{
|
||||
$provider = AiProviderConfig::findOrFail($id);
|
||||
$name = $provider->name;
|
||||
$provider->delete();
|
||||
|
||||
return redirect()->route('admin.ai-providers.index')
|
||||
->with('success', "已删除 {$name}!");
|
||||
}
|
||||
}
|
||||
@@ -36,7 +36,10 @@ class UserManagerController extends Controller
|
||||
// 分页获取用户
|
||||
$users = $query->orderBy('id', 'desc')->paginate(20);
|
||||
|
||||
return view('admin.users.index', compact('users'));
|
||||
// VIP 等级选项列表(供编辑弹窗使用)
|
||||
$vipLevels = \App\Models\VipLevel::orderBy('sort_order')->get();
|
||||
|
||||
return view('admin.users.index', compact('users', 'vipLevels'));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -64,6 +67,8 @@ class UserManagerController extends Controller
|
||||
'qianming' => 'sometimes|nullable|string|max:255',
|
||||
'headface' => 'sometimes|string|max:50',
|
||||
'password' => 'nullable|string|min:6',
|
||||
'vip_level_id' => 'sometimes|nullable|integer|exists:vip_levels,id',
|
||||
'hy_time' => 'sometimes|nullable|date',
|
||||
]);
|
||||
|
||||
// 如果传了且没超权,直接赋予
|
||||
@@ -94,6 +99,14 @@ class UserManagerController extends Controller
|
||||
$targetUser->headface = $validated['headface'];
|
||||
}
|
||||
|
||||
// VIP 会员等级设置
|
||||
if (array_key_exists('vip_level_id', $validated)) {
|
||||
$targetUser->vip_level_id = $validated['vip_level_id'] ?: null;
|
||||
}
|
||||
if (array_key_exists('hy_time', $validated)) {
|
||||
$targetUser->hy_time = $validated['hy_time'] ?: null;
|
||||
}
|
||||
|
||||
if (! empty($validated['password'])) {
|
||||
$targetUser->password = Hash::make($validated['password']);
|
||||
}
|
||||
|
||||
123
app/Http/Controllers/Admin/VipController.php
Normal file
123
app/Http/Controllers/Admin/VipController.php
Normal file
@@ -0,0 +1,123 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* 文件功能:后台 VIP 会员等级管理控制器
|
||||
* 提供会员等级的 CRUD(增删改查)功能
|
||||
* 后台可自由创建、修改、删除会员等级
|
||||
*
|
||||
* @author ChatRoom Laravel
|
||||
*
|
||||
* @version 1.0.0
|
||||
*/
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\VipLevel;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\View\View;
|
||||
|
||||
class VipController extends Controller
|
||||
{
|
||||
/**
|
||||
* 会员等级管理列表页
|
||||
*/
|
||||
public function index(): View
|
||||
{
|
||||
$levels = VipLevel::orderBy('sort_order')->get();
|
||||
|
||||
return view('admin.vip.index', compact('levels'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增会员等级
|
||||
*/
|
||||
public function store(Request $request): RedirectResponse
|
||||
{
|
||||
$data = $request->validate([
|
||||
'name' => 'required|string|max:50',
|
||||
'icon' => 'required|string|max:20',
|
||||
'color' => 'required|string|max:10',
|
||||
'exp_multiplier' => 'required|numeric|min:1|max:99',
|
||||
'jjb_multiplier' => 'required|numeric|min:1|max:99',
|
||||
'sort_order' => 'required|integer|min:0',
|
||||
'price' => 'required|integer|min:0',
|
||||
'duration_days' => 'required|integer|min:0',
|
||||
'join_templates' => 'nullable|string',
|
||||
'leave_templates' => 'nullable|string',
|
||||
]);
|
||||
|
||||
// 将文本框的多行模板转为 JSON 数组
|
||||
$data['join_templates'] = $this->textToJson($data['join_templates'] ?? '');
|
||||
$data['leave_templates'] = $this->textToJson($data['leave_templates'] ?? '');
|
||||
|
||||
VipLevel::create($data);
|
||||
|
||||
return redirect()->route('admin.vip.index')->with('success', '会员等级创建成功!');
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新会员等级
|
||||
*
|
||||
* @param int $id 等级ID
|
||||
*/
|
||||
public function update(Request $request, int $id): RedirectResponse
|
||||
{
|
||||
$level = VipLevel::findOrFail($id);
|
||||
|
||||
$data = $request->validate([
|
||||
'name' => 'required|string|max:50',
|
||||
'icon' => 'required|string|max:20',
|
||||
'color' => 'required|string|max:10',
|
||||
'exp_multiplier' => 'required|numeric|min:1|max:99',
|
||||
'jjb_multiplier' => 'required|numeric|min:1|max:99',
|
||||
'sort_order' => 'required|integer|min:0',
|
||||
'price' => 'required|integer|min:0',
|
||||
'duration_days' => 'required|integer|min:0',
|
||||
'join_templates' => 'nullable|string',
|
||||
'leave_templates' => 'nullable|string',
|
||||
]);
|
||||
|
||||
$data['join_templates'] = $this->textToJson($data['join_templates'] ?? '');
|
||||
$data['leave_templates'] = $this->textToJson($data['leave_templates'] ?? '');
|
||||
|
||||
$level->update($data);
|
||||
|
||||
return redirect()->route('admin.vip.index')->with('success', '会员等级更新成功!');
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除会员等级(关联用户的 vip_level_id 会自动置 null)
|
||||
*
|
||||
* @param int $id 等级ID
|
||||
*/
|
||||
public function destroy(int $id): RedirectResponse
|
||||
{
|
||||
$level = VipLevel::findOrFail($id);
|
||||
$level->delete();
|
||||
|
||||
return redirect()->route('admin.vip.index')->with('success', '会员等级已删除!');
|
||||
}
|
||||
|
||||
/**
|
||||
* 将多行文本转为 JSON 数组字符串
|
||||
* 每行一个模板,空行忽略
|
||||
*
|
||||
* @param string $text 多行文本
|
||||
* @return string|null JSON 字符串
|
||||
*/
|
||||
private function textToJson(string $text): ?string
|
||||
{
|
||||
$lines = array_filter(
|
||||
array_map('trim', explode("\n", $text)),
|
||||
fn ($line) => $line !== ''
|
||||
);
|
||||
|
||||
if (empty($lines)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return json_encode(array_values($lines), JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user