feat(wechat): 微信机器人全链路集成与稳定性修复
- 新增:管理员后台的微信机器人双向收发参数设置页面及扫码绑定能力。 - 新增:WechatBotApiService 与 KafkaConsumerService 模块打通过往僵尸进程导致的拒绝连接问题。 - 新增:下发所有群发/私聊通知时统一带上「[和平聊吧]」标注前缀。 - 优化:前端个人中心绑定逻辑支持一键生成及复制动态口令。 - 修复:闭环联调修补各个模型中产生的变量警告如 stdClass 对象获取等异常预警。
This commit is contained in:
@@ -54,6 +54,10 @@
|
||||
class="block px-4 py-3 rounded-md transition {{ request()->routeIs('admin.system.*') ? 'bg-indigo-600 font-bold' : 'hover:bg-white/10' }}">
|
||||
{!! '⚙️ 聊天室参数' !!}
|
||||
</a>
|
||||
<a href="{{ route('admin.wechat_bot.edit') }}"
|
||||
class="block px-4 py-3 rounded-md transition {{ request()->routeIs('admin.wechat_bot.*') ? 'bg-indigo-600 font-bold' : 'hover:bg-white/10' }}">
|
||||
{!! '🤖 微信机器人' !!}
|
||||
</a>
|
||||
<a href="{{ route('admin.currency-logs.index') }}"
|
||||
class="block px-4 py-3 rounded-md transition {{ request()->routeIs('admin.currency-logs.*') ? 'bg-indigo-600 font-bold' : 'hover:bg-white/10' }}">
|
||||
{!! '💴 用户流水' !!}
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
@extends('admin.layouts.app')
|
||||
|
||||
@section('title', '微信机器人配置')
|
||||
|
||||
@section('content')
|
||||
<div class="bg-white rounded-xl shadow-sm border border-gray-100 overflow-hidden">
|
||||
<div class="p-6 border-b border-gray-100 flex justify-between items-center bg-gray-50">
|
||||
<div>
|
||||
<h2 class="text-lg font-bold text-gray-800">微信机器人全站配置</h2>
|
||||
<p class="text-xs text-gray-500 mt-1">保存后如涉及 Kafka 参数修改,需要重启后端消息消费守护进程。</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (session('success'))
|
||||
<div class="mx-6 mt-4 p-3 bg-green-50 border border-green-200 rounded-lg text-green-700 text-sm">
|
||||
✅ {{ session('success') }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="p-6">
|
||||
<form action="{{ route('admin.wechat_bot.update') }}" method="POST" enctype="multipart/form-data">
|
||||
@csrf
|
||||
@method('PUT')
|
||||
|
||||
<!-- 1. Kafka 及 API 基础配置 -->
|
||||
<div class="mb-8">
|
||||
<h3 class="text-md font-bold text-gray-800 border-b pb-2 mb-4">核心参数 (Kafka / API)</h3>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div>
|
||||
<label class="block text-sm font-bold text-gray-700 mb-1">Kafka Brokers</label>
|
||||
<input type="text" name="kafka_brokers" value="{{ old('kafka_brokers', $config['kafka']['brokers'] ?? '') }}" class="w-full border-gray-300 rounded-md shadow-sm p-2.5 bg-gray-50 border" placeholder="如 10.10.11.18:9092">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-bold text-gray-700 mb-1">Kafka Topic</label>
|
||||
<input type="text" name="kafka_topic" value="{{ old('kafka_topic', $config['kafka']['topic'] ?? '') }}" class="w-full border-gray-300 rounded-md shadow-sm p-2.5 bg-gray-50 border" placeholder="监听的主题">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-bold text-gray-700 mb-1">Kafka Group ID</label>
|
||||
<input type="text" name="kafka_group_id" value="{{ old('kafka_group_id', $config['kafka']['group_id'] ?? 'chatroom_wechat_bot') }}" class="w-full border-gray-300 rounded-md shadow-sm p-2.5 bg-gray-50 border" placeholder="消费组ID">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-bold text-gray-700 mb-1">机器人微信号 (对外展示)</label>
|
||||
<input type="text" name="kafka_bot_wxid" value="{{ old('kafka_bot_wxid', $config['kafka']['bot_wxid'] ?? '') }}" class="w-full border-gray-300 rounded-md shadow-sm p-2.5 bg-gray-50 border" placeholder="供用户添加好友用的微信号">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-bold text-gray-700 mb-1">机器人二维码 (对外展示图)</label>
|
||||
<input type="file" name="qrcode_image" accept="image/*" class="w-full border-gray-300 rounded-md shadow-sm p-2 bg-gray-50 border">
|
||||
@if(!empty($config['api']['qrcode_image']))
|
||||
<div class="mt-2">
|
||||
<img src="{{ Storage::url($config['api']['qrcode_image']) }}" alt="QR Code" class="h-20 w-auto rounded border border-gray-200">
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-bold text-gray-700 mb-1">被调用方机器人服务基础API URL</label>
|
||||
<input type="text" name="api_base_url" value="{{ old('api_base_url', $config['api']['base_url'] ?? '') }}" class="w-full border-gray-300 rounded-md shadow-sm p-2.5 bg-gray-50 border" placeholder="如 http://10.10.11.14:8848">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-bold text-gray-700 mb-1">机器人 Key (必需)</label>
|
||||
<input type="text" name="api_bot_key" value="{{ old('api_bot_key', $config['api']['bot_key'] ?? '') }}" class="w-full border-gray-300 rounded-md shadow-sm p-2.5 bg-gray-50 border" placeholder="机器人的对接 Key">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 2. 群通知设置 -->
|
||||
<div class="mb-8">
|
||||
<h3 class="text-md font-bold text-gray-800 border-b pb-2 mb-4">群聊通知设置</h3>
|
||||
<div class="mb-6 max-w-lg">
|
||||
<label class="block text-sm font-bold text-gray-700 mb-1">目标微信群 Wxid</label>
|
||||
<input type="text" name="group_target_wxid" value="{{ old('group_target_wxid', $config['group_notify']['target_wxid'] ?? '') }}" class="w-full border-gray-300 rounded-md shadow-sm p-2.5 bg-gray-50 border" placeholder="群的wxid标识">
|
||||
<p class="text-xs mt-1 text-gray-500">以下开关针对此群发送通知</p>
|
||||
</div>
|
||||
|
||||
<div class="space-y-4">
|
||||
<label class="flex items-center cursor-pointer">
|
||||
<input type="hidden" name="toggle_admin_online" value="0">
|
||||
<input type="checkbox" name="toggle_admin_online" value="1" class="form-checkbox h-5 w-5 text-indigo-600 rounded border-gray-300 focus:ring-indigo-500" {{ !empty($config['group_notify']['toggle_admin_online']) ? 'checked' : '' }}>
|
||||
<span class="ml-2 text-sm text-gray-700 font-bold">管理员上线通知</span>
|
||||
</label>
|
||||
<label class="flex items-center cursor-pointer">
|
||||
<input type="hidden" name="toggle_baccarat_result" value="0">
|
||||
<input type="checkbox" name="toggle_baccarat_result" value="1" class="form-checkbox h-5 w-5 text-indigo-600 rounded border-gray-300 focus:ring-indigo-500" {{ !empty($config['group_notify']['toggle_baccarat_result']) ? 'checked' : '' }}>
|
||||
<span class="ml-2 text-sm text-gray-700 font-bold">百家乐开奖通知</span>
|
||||
</label>
|
||||
<label class="flex items-center cursor-pointer">
|
||||
<input type="hidden" name="toggle_lottery_result" value="0">
|
||||
<input type="checkbox" name="toggle_lottery_result" value="1" class="form-checkbox h-5 w-5 text-indigo-600 rounded border-gray-300 focus:ring-indigo-500" {{ !empty($config['group_notify']['toggle_lottery_result']) ? 'checked' : '' }}>
|
||||
<span class="ml-2 text-sm text-gray-700 font-bold">彩票开奖通知</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 3. 点对点私聊通知设置 -->
|
||||
<div class="mb-8">
|
||||
<h3 class="text-md font-bold text-gray-800 border-b pb-2 mb-4">一对一私聊通知设置 (目标为用户微信)</h3>
|
||||
<div class="space-y-4">
|
||||
<label class="flex items-center cursor-pointer">
|
||||
<input type="hidden" name="toggle_friend_online" value="0">
|
||||
<input type="checkbox" name="toggle_friend_online" value="1" class="form-checkbox h-5 w-5 text-indigo-600 rounded border-gray-300 focus:ring-indigo-500" {{ !empty($config['personal_notify']['toggle_friend_online']) ? 'checked' : '' }}>
|
||||
<span class="ml-2 text-sm text-gray-700 font-bold">好友上线 (带有30分钟冷却机制判断)</span>
|
||||
</label>
|
||||
<label class="flex items-center cursor-pointer">
|
||||
<input type="hidden" name="toggle_spouse_online" value="0">
|
||||
<input type="checkbox" name="toggle_spouse_online" value="1" class="form-checkbox h-5 w-5 text-indigo-600 rounded border-gray-300 focus:ring-indigo-500" {{ !empty($config['personal_notify']['toggle_spouse_online']) ? 'checked' : '' }}>
|
||||
<span class="ml-2 text-sm text-gray-700 font-bold">夫妻上线 (带有30分钟冷却机制判断)</span>
|
||||
</label>
|
||||
<label class="flex items-center cursor-pointer">
|
||||
<input type="hidden" name="toggle_level_change" value="0">
|
||||
<input type="checkbox" name="toggle_level_change" value="1" class="form-checkbox h-5 w-5 text-indigo-600 rounded border-gray-300 focus:ring-indigo-500" {{ !empty($config['personal_notify']['toggle_level_change']) ? 'checked' : '' }}>
|
||||
<span class="ml-2 text-sm text-gray-700 font-bold">等级变动</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-8 pt-6 border-t flex space-x-3">
|
||||
<button type="submit" class="px-6 py-2 bg-indigo-600 text-white rounded-md font-bold hover:bg-indigo-700 shadow-sm transition">保存配置</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
@@ -169,6 +169,49 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- 微信绑定 --}}
|
||||
<div style="margin-bottom:16px; border:1px solid #e0e0e0; border-radius:6px; padding:12px;">
|
||||
<div style="font-size:12px; font-weight:bold; color:#336699; margin-bottom:8px;">💬 微信绑定</div>
|
||||
<div style="display:flex; flex-direction:column; gap:6px;" id="wechat-bind-container">
|
||||
@if (empty(Auth::user()->wxid))
|
||||
@php
|
||||
$botConfigBody = \App\Models\SysParam::where('alias', 'wechat_bot_config')->value('body');
|
||||
$botConfig = $botConfigBody ? json_decode($botConfigBody, true) : [];
|
||||
$botWxid = $botConfig['kafka']['bot_wxid'] ?? '暂未配置';
|
||||
$qrcodeImage = $botConfig['api']['qrcode_image'] ?? null;
|
||||
@endphp
|
||||
<div style="font-size:12px; color:#666;">
|
||||
您尚未绑定微信。<br>
|
||||
@if($qrcodeImage)
|
||||
扫码添加机器人微信:<br>
|
||||
<img src="{{ \Illuminate\Support\Facades\Storage::url($qrcodeImage) }}" alt="机器人二维码" style="max-height:100px; display:block; margin: 6px 0; border: 1px solid #ddd; border-radius: 4px;">
|
||||
@else
|
||||
请添加机器人微信:<strong style="color:#d97706">{{ $botWxid }}</strong><br>
|
||||
@endif
|
||||
并发送以下绑定代码完成绑定:
|
||||
</div>
|
||||
<div style="display:flex; align-items:center; gap:8px;">
|
||||
<input id="wechat-bind-code" type="text" readonly value="点击生成"
|
||||
style="flex:1; padding:6px 8px; border:1px dashed #336699; background:#f9fafb; border-radius:4px; font-size:13px; font-weight:bold; color:#336699; text-align:center; cursor:text;">
|
||||
<button type="button" id="btn-copy-bind-code" onclick="copyWechatBindCode()"
|
||||
style="display:none; padding:5px 10px; border:1px solid #10b981; background:#ecfdf5; color:#10b981; border-radius:4px; font-size:12px; cursor:pointer; white-space:nowrap;">
|
||||
复制
|
||||
</button>
|
||||
<button type="button" id="btn-generate-bind-code" onclick="generateWechatBindCode()"
|
||||
style="padding:5px 10px; border:1px solid #336699; background:#eef5ff; color:#336699; border-radius:4px; font-size:12px; cursor:pointer; white-space:nowrap;">
|
||||
生成代码
|
||||
</button>
|
||||
</div>
|
||||
<div id="bind-code-tip" style="font-size:11px; color:#888; display:none; text-align:center;">有效时间 5 分钟,绑定成功后请刷新页面。</div>
|
||||
@else
|
||||
<div style="font-size:12px; color:#16a34a; font-weight:bold; display:flex; justify-content:space-between; align-items:center;">
|
||||
<span>已绑定微信,可接收提醒通知。</span>
|
||||
<button type="button" onclick="unbindWechat()" style="padding:4px 8px; background:#fee2e2; color:#dc2626; border:1px solid #fecaca; border-radius:4px; font-size:11px; cursor:pointer;">解除绑定</button>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- 内联操作结果提示(仿百家乐已押注卡片风格) --}}
|
||||
<div id="settings-inline-msg"
|
||||
style="display:none; border-radius:10px; padding:10px 14px;
|
||||
@@ -553,12 +596,103 @@
|
||||
btn.style.opacity = '1';
|
||||
btn.style.cursor = 'pointer';
|
||||
}
|
||||
} catch (e) {
|
||||
window.chatDialog.alert('网络异常,验证码发送失败,请稍后重试。', '错误', '#6b7280');
|
||||
btn.innerText = '获取验证码';
|
||||
btn.disabled = false;
|
||||
btn.style.opacity = '1';
|
||||
btn.style.cursor = 'pointer';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成微信绑定验证码
|
||||
*/
|
||||
async function generateWechatBindCode() {
|
||||
const btn = document.getElementById('btn-generate-bind-code');
|
||||
const input = document.getElementById('wechat-bind-code');
|
||||
const tip = document.getElementById('bind-code-tip');
|
||||
|
||||
btn.disabled = true;
|
||||
btn.innerText = '生成中...';
|
||||
|
||||
try {
|
||||
const res = await fetch('{{ route('user.generate_wechat_code') }}', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content,
|
||||
'Accept': 'application/json'
|
||||
}
|
||||
});
|
||||
const data = await res.json();
|
||||
|
||||
if (res.ok && data.status === 'success') {
|
||||
input.value = data.code;
|
||||
tip.style.display = 'block';
|
||||
document.getElementById('btn-copy-bind-code').style.display = 'inline-block';
|
||||
showInlineMsg('settings-inline-msg', '✅ 绑定代码生成成功,请在5分钟内发送给机器人', true);
|
||||
} else {
|
||||
showInlineMsg('settings-inline-msg', '❌ 生成失败:' + (data.message || '未知错误'), false);
|
||||
}
|
||||
} catch (e) {
|
||||
window.chatDialog.alert('网络异常,验证码发送失败,请稍后重试。', '错误', '#6b7280');
|
||||
btn.innerText = '获取验证码';
|
||||
btn.disabled = false;
|
||||
btn.style.opacity = '1';
|
||||
btn.style.cursor = 'pointer';
|
||||
showInlineMsg('settings-inline-msg', '🌐 网络异常,请稍后重试', false);
|
||||
}
|
||||
|
||||
btn.disabled = false;
|
||||
btn.innerText = '重新生成';
|
||||
}
|
||||
|
||||
/**
|
||||
* 复制微信绑定验证码
|
||||
*/
|
||||
function copyWechatBindCode() {
|
||||
const input = document.getElementById('wechat-bind-code');
|
||||
if (input.value && input.value !== '点击生成' && input.value !== '生成中...') {
|
||||
input.select();
|
||||
input.setSelectionRange(0, 99999);
|
||||
|
||||
try {
|
||||
if (navigator.clipboard && window.isSecureContext) {
|
||||
navigator.clipboard.writeText(input.value);
|
||||
} else {
|
||||
document.execCommand('copy');
|
||||
}
|
||||
const btn = document.getElementById('btn-copy-bind-code');
|
||||
const originalText = btn.innerText;
|
||||
btn.innerText = '已复制';
|
||||
setTimeout(() => {
|
||||
btn.innerText = originalText;
|
||||
}, 2000);
|
||||
} catch (err) {
|
||||
showInlineMsg('settings-inline-msg', '❌ 复制失败,请手动复制', false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解除微信绑定
|
||||
*/
|
||||
async function unbindWechat() {
|
||||
if (!confirm('确定要解除微信绑定吗?解除后将无法接收任何机器人推送通知。')) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const res = await fetch('{{ route('user.unbind_wechat') }}', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content,
|
||||
'Accept': 'application/json'
|
||||
}
|
||||
});
|
||||
const data = await res.json();
|
||||
if (res.ok && data.status === 'success') {
|
||||
alert('✅ 解绑成功!请刷新页面获取最新状态。');
|
||||
location.reload();
|
||||
} else {
|
||||
alert('❌ 解绑失败:' + (data.message || '未知错误'));
|
||||
}
|
||||
} catch (e) {
|
||||
alert('网络异常,解绑失败');
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user