feat(wechat): 微信机器人全链路集成与稳定性修复

- 新增:管理员后台的微信机器人双向收发参数设置页面及扫码绑定能力。
- 新增:WechatBotApiService 与 KafkaConsumerService 模块打通过往僵尸进程导致的拒绝连接问题。
- 新增:下发所有群发/私聊通知时统一带上「[和平聊吧]」标注前缀。
- 优化:前端个人中心绑定逻辑支持一键生成及复制动态口令。
- 修复:闭环联调修补各个模型中产生的变量警告如 stdClass 对象获取等异常预警。
This commit is contained in:
2026-04-02 14:56:51 +08:00
parent 8a809e3cc0
commit fc57f97c9e
19 changed files with 1552 additions and 6 deletions
@@ -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>