This commit is contained in:
Zhenguan.Lin 2025-06-08 19:13:34 +08:00
parent cf9a422870
commit 10ed726b8f
2 changed files with 891 additions and 0 deletions

590
wechat_bot_userfilter.py Normal file
View File

@ -0,0 +1,590 @@
# -*- coding: utf-8 -*-
"""
完整的微信客服系统支持
1. SQLite 存储用户信息和对话历史
2. 白名单机制根据 openid 判断是否允许调用 AI
3. Dify 返回的 conversation_id 持久化到数据库users.preferences 字段
并在程序启动时加载实现跨重启的多轮会话上下文记忆
4. 管理接口用于添加/移除白名单用户
5. 用户可通过发送指定指令加入白名单需验证密码
6. 异步回复机制收到用户消息后立即返回空串给微信后台线程调用 AI 再通过客服接口发送回复
请务必替换下列配置
WX_TOKENWX_APPIDWX_APPSECRETDIFY_API_BASEDIFY_API_KEY
WHITELIST_PASSWORD - 用户加入白名单所需的密码
"""
from flask import Flask, request, jsonify, make_response
import sqlite3 # SQLite 驱动
import requests # 发送 HTTP 请求
import hashlib # 用于微信签名验证
import time # 获取时间戳
import threading # 异步线程
import json # 处理 JSON
import xml.etree.ElementTree as ET # 解析微信 XML 消息
import re # 正则表达式用于解析指令
app = Flask(__name__)
# ======================================
# 全局配置区 —— 请务必替换成你自己的配置
# ======================================
WX_TOKEN = 'your_wechat_token' # 微信公众号后台“服务器配置”中的 Token
WX_APPID = 'your_appid' # 微信公众号的 AppID
WX_APPSECRET = 'your_appsecret' # 微信公众号的 AppSecret
DIFY_API_BASE = 'https://api.dify.ai/v1' # Dify API 基础 URL例如 https://api.dify.ai/v1
DIFY_API_KEY = 'your_dify_api_key' # Dify 平台分配给你的 API Key
WHITELIST_PASSWORD = "your_secure_password" # 用户加入白名单所需的密码
DATABASE = 'wechat.db' # SQLite 数据库文件名
# conv_map 用于保存 <openid, conversation_id>,实现多轮对话上下文管理
conv_map = {}
# ======================================
# 1. 数据库相关函数:初始化与增删改查
# ======================================
def init_db():
"""
说明
初始化 SQLite 数据库创建必要的表users用户表 messages消息表
如果表已存在则不会重复创建
表结构
users:
openid TEXT PRIMARY KEY -- 用户唯一标识微信 openid
whitelisted INTEGER DEFAULT 0 -- 0 表示未授权用户1 表示白名单用户
preferences TEXT -- 存放用户个性化设置可为 JSON 字符串包含 conversation_id
messages:
id INTEGER PRIMARY KEY AUTOINCREMENT -- 消息自增 ID
openid TEXT -- 关联用户 openid
role TEXT -- 'user'用户消息 'assistant'AI 回复
content TEXT -- 消息内容
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP -- 消息记录时间
"""
conn = sqlite3.connect(DATABASE)
cursor = conn.cursor()
# 创建 users 表
cursor.execute('''
CREATE TABLE IF NOT EXISTS users (
openid TEXT PRIMARY KEY,
whitelisted INTEGER DEFAULT 0,
preferences TEXT
)
''')
# 创建 messages 表
cursor.execute('''
CREATE TABLE IF NOT EXISTS messages (
id INTEGER PRIMARY KEY AUTOINCREMENT,
openid TEXT,
role TEXT,
content TEXT,
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP
)
''')
conn.commit()
conn.close()
def load_conversations_from_db():
"""
说明
服务启动后调用此函数 users 表的 preferences 字段中加载所有保存的 conversation_id
并填充到内存 conv_map 实现跨重启的会话上下文恢复
"""
conn = sqlite3.connect(DATABASE)
cursor = conn.cursor()
cursor.execute("SELECT openid, preferences FROM users WHERE preferences IS NOT NULL")
for openid, prefs_json in cursor.fetchall():
try:
prefs = json.loads(prefs_json)
if 'conversation_id' in prefs:
conv_map[openid] = prefs['conversation_id']
except Exception:
# 如果解析失败,跳过该条记录
pass
conn.close()
def get_user(openid):
"""
根据 openid users 表查询用户记录
返回若存在返回元组 (openid, whitelisted, preferences)否则返回 None
"""
conn = sqlite3.connect(DATABASE)
cursor = conn.cursor()
cursor.execute("SELECT openid, whitelisted, preferences FROM users WHERE openid=?", (openid,))
user = cursor.fetchone()
conn.close()
return user
def add_user(openid, whitelisted=0, preferences=None):
"""
如果用户不存在则插入新记录若已存在则忽略插入
参数
openid (str) 用户 openid
whitelisted (int) 0 1默认为 0
preferences (dict/None) 用户偏好设置会自动转换成 JSON 字符串
"""
conn = sqlite3.connect(DATABASE)
cursor = conn.cursor()
prefs_json = json.dumps(preferences) if preferences else None
cursor.execute(
"INSERT OR IGNORE INTO users (openid, whitelisted, preferences) VALUES (?, ?, ?)",
(openid, whitelisted, prefs_json)
)
conn.commit()
conn.close()
def set_user_whitelist(openid, status):
"""
设置用户是否在白名单中如果用户不存在则先插入再设置状态
参数
openid (str) 用户 openid
status (bool) True 表示加入白名单False 表示移出白名单
"""
conn = sqlite3.connect(DATABASE)
cursor = conn.cursor()
# 检查用户是否已存在
cursor.execute("SELECT openid FROM users WHERE openid=?", (openid,))
exists = cursor.fetchone()
if exists:
# 更新已存在用户的 whitelisted 字段
cursor.execute("UPDATE users SET whitelisted=? WHERE openid=?", (1 if status else 0, openid))
else:
# 用户不存在时先插入,然后设置 whitelisted
cursor.execute("INSERT INTO users (openid, whitelisted) VALUES (?, ?)", (openid, 1 if status else 0))
conn.commit()
conn.close()
return True
def update_user_preferences(openid, new_prefs):
"""
更新用户的 preferences 字段 new_prefsdict转换为 JSON 存储
参数
openid (str) 用户 openid
new_prefs (dict) 需要存储的偏好设置字典例如包含 conversation_id
"""
prefs_json = json.dumps(new_prefs, ensure_ascii=False)
conn = sqlite3.connect(DATABASE)
cursor = conn.cursor()
# 如果用户不存在,则先插入一条空记录,再更新 preferences
cursor.execute("INSERT OR IGNORE INTO users (openid, preferences) VALUES (?, NULL)", (openid,))
cursor.execute("UPDATE users SET preferences=? WHERE openid=?", (prefs_json, openid))
conn.commit()
conn.close()
def log_message(openid, role, content):
"""
messages 表中插入一条消息记录保存用户对话历史
参数
openid (str) 用户 openid
role (str) 'user' 'assistant'
content (str) 消息内容
"""
conn = sqlite3.connect(DATABASE)
cursor = conn.cursor()
cursor.execute(
"INSERT INTO messages (openid, role, content) VALUES (?, ?, ?)",
(openid, role, content)
)
conn.commit()
conn.close()
# 初始化数据库并加载会话上下文
init_db()
load_conversations_from_db()
# ======================================
# 2. 微信签名与接口调用相关函数
# ======================================
def verify_signature(token, signature, timestamp, nonce):
"""
验证来自微信服务器的签名是否合法用于 GET 请求接入验证
参数
token (str) 公众号后台配置的 Token
signature (str) 微信服务器传来的签名
timestamp (str) 时间戳
nonce (str) 随机数
返回
True: 验证通过False: 验证失败
"""
tmp_list = [token, timestamp, nonce]
tmp_list.sort()
tmp_str = ''.join(tmp_list)
sha1 = hashlib.sha1(tmp_str.encode('utf-8')).hexdigest()
return sha1 == signature
# 全局缓存微信 access_token减少重复向微信服务器拉取
wechat_token_cache = {
'access_token': None,
'expires_at': 0 # token 到期时间戳
}
def get_access_token():
"""
获取微信公众号全局接口调用凭证 access_token并缓存起来
如果缓存中的 token 未过期则直接返回否则重新向微信服务器拉取
返回access_token (str)若获取失败则返回 None
"""
# 如果缓存中有且未过期就直接返回
if wechat_token_cache['access_token'] and wechat_token_cache['expires_at'] > time.time():
return wechat_token_cache['access_token']
# 向微信接口拉取新的 access_token
url = f"https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={WX_APPID}&secret={WX_APPSECRET}"
try:
response = requests.get(url, timeout=5)
res_json = response.json()
token = res_json.get("access_token")
expires_in = res_json.get("expires_in", 0)
if token:
# 提前 60 秒失效,避免临界情况
wechat_token_cache['access_token'] = token
wechat_token_cache['expires_at'] = time.time() + int(expires_in) - 60
return token
else:
print("Failed to get access_token:", res_json)
return None
except Exception as e:
print("Exception in get_access_token:", str(e))
return None
def send_text_message_to_user(openid, text):
"""
通过微信客服消息接口向指定用户发送文本消息
参数
openid (str) 用户 openid
text (str) 要发送的文本内容
返回
True: 发送成功False: 发送失败
"""
token = get_access_token()
if not token:
print("Cannot send message: access_token is None")
return False
url = f"https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token={token}"
payload = {
"touser": openid,
"msgtype": "text",
"text": {"content": text}
}
try:
response = requests.post(url, json=payload, timeout=5)
res_json = response.json()
if res_json.get("errcode") == 0:
return True
else:
print("send_text_message_to_user failed:", res_json)
return False
except Exception as e:
print("Exception in send_text_message_to_user:", str(e))
return False
# ======================================
# 3. AI 回复获取函数get_ai_reply含持久化 conversation_id
# ======================================
def get_ai_reply(user_openid, user_message):
"""
调用 Dify 平台的 Chat API获取 AI 回复并维护 conversation_id实现跨进程/跨重启的上下文关联
参数
user_openid (str) 用户 openid用作 user 字段传给 Dify以实现多用户区分
user_message (str) 用户发送的消息文本
返回
reply (str) AI 对用户消息的回复文本如果出错则返回提示文本
"""
# 1. 构造 Dify 请求 payload
payload = {
"inputs": {}, # 保留字段,可放入其他上下文信息
"query": user_message, # 用户输入文本
"response_mode": "blocking", # 阻塞式返回
"user": user_openid # 将 openid 传给 Dify用于区分多用户
}
# 2. 如果 conv_map 中已有 conversation_id则带上以实现上下文关联
if user_openid in conv_map:
payload["conversation_id"] = conv_map[user_openid]
headers = {"Content-Type": "application/json"}
# 如果配置了 DIFY_API_KEY则在请求头中添加 Authorization
if DIFY_API_KEY:
headers["Authorization"] = f"Bearer {DIFY_API_KEY}"
try:
# 3. 调用 Dify Chat 接口
resp = requests.post(
f"{DIFY_API_BASE}/chat-messages",
headers=headers,
json=payload,
timeout=10
)
# 打印调试信息
print("Dify API status code:", resp.status_code)
print("Dify API response:", resp.text)
# 4. 非200状态码视为失败
if resp.status_code != 200:
return "AI 服务暂时不可用,请稍后再试。"
res_json = resp.json()
# 5. 提取 answer 字段。如果没有就给出默认错误提示
reply = res_json.get("answer", "抱歉,我暂时无法回答您的问题。")
# 6. Dify 返回的 answer 可能带有 Unicode 转义(如 "\\u4f60\\u597d"
# 下面尝试用 json.loads 解码成真实的中文
try:
reply = json.loads(f'"{reply}"')
except Exception:
pass # 如果解析失败,就保留原样
# 7. 如果 Dify 返回了新的 conversation_id则更新 conv_map并持久化到 users.preferences
new_cid = res_json.get("conversation_id")
if new_cid:
# 更新内存字典
conv_map[user_openid] = new_cid
# 持久化到数据库:先读取旧的 preferences再合并更新 conversation_id
conn = sqlite3.connect(DATABASE)
cursor = conn.cursor()
cursor.execute("SELECT preferences FROM users WHERE openid=?", (user_openid,))
row = cursor.fetchone()
try:
old_prefs = json.loads(row[0]) if row and row[0] else {}
except Exception:
old_prefs = {}
old_prefs['conversation_id'] = new_cid
# 更新用户的 preferences 字段
cursor.execute("UPDATE users SET preferences=? WHERE openid=?",
(json.dumps(old_prefs, ensure_ascii=False), user_openid))
conn.commit()
conn.close()
return reply
except Exception as e:
print("Exception in get_ai_reply:", str(e))
return f"AI 接口调用失败,请稍后再试。错误信息:{str(e)}"
# ======================================
# 4. 后台线程:处理用户消息并发送 AI 回复
# ======================================
def process_and_reply(user_openid, user_message):
"""
在线程中调用 get_ai_reply 获取 AI 回复然后
1. 将用户消息及 AI 回复记录到数据库messages
2. 通过微信客服消息接口发送回复给用户
参数
user_openid (str) 用户 openid
user_message (str) 用户发送的消息内容
"""
# 1. 获取 AI 回复
reply = get_ai_reply(user_openid, user_message)
# 2. 将 AI 回复写入数据库role='assistant'
log_message(user_openid, "assistant", reply)
# 3. 通过客服接口发送给用户
send_text_message_to_user(user_openid, reply)
# ======================================
# 5. 微信消息接入与路由
# ======================================
def handle_whitelist_command(openid, content):
"""
处理用户加入白名单的指令
支持的指令格式
"加入白名单"
"加入白名单 密码"
"""
# 检查是否包含密码
password_match = re.search(r'加入白名单\s+(\S+)', content)
if password_match:
# 指令格式:加入白名单 + 密码
password = password_match.group(1)
if password == WHITELIST_PASSWORD:
# 密码正确,加入白名单
set_user_whitelist(openid, True)
return "✅ 您已成功加入白名单现在可以使用AI服务了。"
else:
# 密码错误
return "❌ 密码不正确,请确认后重试。"
else:
# 指令格式:加入白名单(无密码)
if WHITELIST_PASSWORD == "":
# 未设置密码,直接加入
set_user_whitelist(openid, True)
return "✅ 您已成功加入白名单现在可以使用AI服务了。"
else:
# 需要密码但未提供
return "⚠️ 请提供密码:加入白名单 [密码]"
@app.route('/wx', methods=['GET', 'POST'])
def wechat():
if request.method == 'GET':
# --------------------------------
# 微信服务器接入验证
# --------------------------------
args = request.args
signature = args.get("signature", "")
timestamp = args.get("timestamp", "")
nonce = args.get("nonce", "")
echostr = args.get("echostr", "")
# 验证签名,若通过返回 echostr否则返回 400 错误
if verify_signature(WX_TOKEN, signature, timestamp, nonce):
return echostr
else:
return "Invalid signature", 400
else:
# --------------------------------
# 处理微信服务器 POST 过来的消息
# --------------------------------
xml_data = request.data # 获取原始 XML
try:
xml_root = ET.fromstring(xml_data)
except ET.ParseError:
# 解析失败则直接返回空串,微信服务器不会重试
return ""
# 提取常用字段
to_user = xml_root.findtext('ToUserName') # 公众号原始 ID
from_user = xml_root.findtext('FromUserName') # 用户 openid
msg_type = xml_root.findtext('MsgType') # 消息类型
# 仅处理文本消息 (text),其它消息类型直接返回空
if msg_type != 'text':
return ""
content = xml_root.findtext('Content', default='').strip()
user_openid = from_user
# 确保该用户已存在于数据库若不存在则插入新用户默认不在白名单preferences 为空)
if not get_user(user_openid):
add_user(user_openid)
# 将用户发送的消息记录到 messages 表role='user'
log_message(user_openid, "user", content)
# 检查用户是否在白名单
user_record = get_user(user_openid)
whitelisted = (user_record[1] == 1) # user_record = (openid, whitelisted, preferences)
# 处理加入白名单指令(无论用户是否在白名单,都可以发送此指令)
if content.startswith("加入白名单"):
reply_text = handle_whitelist_command(user_openid, content)
# 将回复也写入数据库role='assistant'
log_message(user_openid, "assistant", reply_text)
# 构造标准的被动回复 XML
reply_xml = f"""
<xml>
<ToUserName><![CDATA[{user_openid}]]></ToUserName>
<FromUserName><![CDATA[{to_user}]]></FromUserName>
<CreateTime>{int(time.time())}</CreateTime>
<MsgType><![CDATA[text]]></MsgType>
<Content><![CDATA[{reply_text}]]></Content>
</xml>
""".strip()
response = make_response(reply_xml)
response.headers['Content-Type'] = 'application/xml'
return response
if not whitelisted:
# 如果不在白名单中,不调用 AI直接同步回复提示
reply_text = "⚠️ 您不在白名单中无法使用AI服务。\n\n请发送【加入白名单】指令申请加入(如需密码请提供)。"
# 将回复也写入数据库role='assistant'
log_message(user_openid, "assistant", reply_text)
# 构造标准的被动回复 XML
reply_xml = f"""
<xml>
<ToUserName><![CDATA[{user_openid}]]></ToUserName>
<FromUserName><![CDATA[{to_user}]]></FromUserName>
<CreateTime>{int(time.time())}</CreateTime>
<MsgType><![CDATA[text]]></MsgType>
<Content><![CDATA[{reply_text}]]></Content>
</xml>
""".strip()
response = make_response(reply_xml)
response.headers['Content-Type'] = 'application/xml'
return response
else:
# 白名单用户:异步调用 AI并通过客服消息接口发送回复
# 使用守护线程,避免阻塞主线程
thread = threading.Thread(target=process_and_reply, args=(user_openid, content))
thread.daemon = True
thread.start()
# 主线程即时返回空串给微信服务器,表示后续通过客服接口回复
return ""
# ======================================
# 6. 管理接口:添加/移除白名单用户
# ======================================
@app.route('/admin/whitelist/add', methods=['GET', 'POST'])
def add_whitelist():
"""
管理接口将指定 openid 的用户加入白名单
请求方式
GET: /admin/whitelist/add?openid=XXX
POST: /admin/whitelist/add form-data JSON 中包含 {"openid": "XXX"}
返回JSON 格式 {"success": True/False, "message": "提示内容"}
"""
# 从 URL 查询参数或 POST 表单中获取 openid
openid = request.args.get("openid") or request.form.get("openid")
if not openid:
return jsonify({"success": False, "message": "参数 openid 不能为空"}), 400
set_user_whitelist(openid, True)
return jsonify({"success": True, "message": f"User {openid} 已加入白名单"})
@app.route('/admin/whitelist/remove', methods=['GET', 'POST'])
def remove_whitelist():
"""
管理接口将指定 openid 的用户从白名单中移除
请求方式
GET: /admin/whitelist/remove?openid=XXX
POST: /admin/whitelist/remove form-data JSON 中包含 {"openid": "XXX"}
返回JSON 格式 {"success": True/False, "message": "提示内容"}
"""
openid = request.args.get("openid") or request.form.get("openid")
if not openid:
return jsonify({"success": False, "message": "参数 openid 不能为空"}), 400
set_user_whitelist(openid, False)
return jsonify({"success": True, "message": f"User {openid} 已移出白名单"})
# ======================================
# 7. 应用启动
# ======================================
if __name__ == '__main__':
# debug=True 仅用于开发调试,生产环境请 False 并使用 WSGI 部署
app.run(host="0.0.0.0", port=8010, debug=True)

View File

@ -0,0 +1,301 @@
## 概述
本系统是一个基于微信公众号的智能客服解决方案集成了Dify AI平台提供自然语言处理能力。系统采用白名单机制管理用户权限支持多轮对话上下文记忆并实现异步消息处理机制以优化用户体验。
## 核心功能
1. **用户管理与权限控制**
- 基于openid的白名单机制
- 用户自助加入白名单功能(可选密码保护)
- 管理员手动管理白名单
2. **对话管理**
- 完整的对话历史记录
- 跨重启的多轮会话上下文记忆
- 基于Dify AI的智能回复
3. **系统架构**
- Flask Web框架
- SQLite数据库存储
- 多线程异步处理
- 微信API集成
## 配置说明
### 必需配置项(全局配置区)
```python
# 微信相关配置
WX_TOKEN = 'your_wechat_token' # 微信公众号后台"服务器配置"中的Token
WX_APPID = 'your_appid' # 微信公众号的AppID
WX_APPSECRET = 'your_appsecret' # 微信公众号的AppSecret
# Dify AI平台配置
DIFY_API_BASE = 'https://api.dify.ai/v1' # Dify API基础URL
DIFY_API_KEY = 'your_dify_api_key' # Dify平台分配的API Key
# 白名单安全配置
WHITELIST_PASSWORD = "your_secure_password" # 用户加入白名单所需的密码
# 数据库配置
DATABASE = 'wechat.db' # SQLite数据库文件名
```
### 配置注意事项
1. 所有配置项**必须**在部署前替换为实际值
2. `WHITELIST_PASSWORD` 应设置为强密码建议12位以上包含大小写字母、数字和特殊字符
3. 生产环境应使用HTTPS协议保证通信安全
4. 建议定期轮换API密钥和密码
## 数据库设计
### users表结构
| 字段名 | 类型 | 说明 |
|-------------|---------|-------------------------------|
| openid | TEXT | 用户唯一标识(主键) |
| whitelisted | INTEGER | 白名单状态0=否1=是) |
| preferences | TEXT | JSON格式的用户偏好设置 |
### messages表结构
| 字段名 | 类型 | 说明 |
|------------|---------|-------------------------------|
| id | INTEGER | 自增主键 |
| openid | TEXT | 关联用户openid |
| role | TEXT | 消息角色user/assistant |
| content | TEXT | 消息内容 |
| timestamp | DATETIME| 消息时间(默认当前时间) |
## 白名单操作指南
### 用户自助加入白名单
#### 1. 无密码加入当WHITELIST_PASSWORD为空时
- 用户发送消息:`加入白名单`
- 系统回复:`✅ 您已成功加入白名单现在可以使用AI服务了。`
#### 2. 密码验证加入
- 用户发送消息:`加入白名单 密码内容`
- 示例:`加入白名单 MySecurePass123!`
- 密码正确时回复:`✅ 您已成功加入白名单现在可以使用AI服务了。`
- 密码错误时回复:`❌ 密码不正确,请确认后重试。`
### 管理员管理白名单
#### 1. 添加用户到白名单
**请求方式**
- GET: `/admin/whitelist/add?openid=用户OPENID`
- POST: `/admin/whitelist/add` (表单或JSON包含openid参数)
**示例**
```bash
# GET请求
curl "http://yourserver:8010/admin/whitelist/add?openid=o123456789"
# POST请求
curl -X POST -d "openid=o123456789" http://yourserver:8010/admin/whitelist/add
```
**成功响应**
```json
{
"success": true,
"message": "User o123456789 已加入白名单"
}
```
#### 2. 从白名单移除用户
**请求方式**
- GET: `/admin/whitelist/remove?openid=用户OPENID`
- POST: `/admin/whitelist/remove` (表单或JSON包含openid参数)
**示例**
```bash
# GET请求
curl "http://yourserver:8010/admin/whitelist/remove?openid=o123456789"
# POST请求
curl -X POST -d "openid=o123456789" http://yourserver:8010/admin/whitelist/remove
```
**成功响应**
```json
{
"success": true,
"message": "User o123456789 已移出白名单"
}
```
### 获取用户openid的方法
1. 让用户向公众号发送任意消息
2. 查看数据库users表
```sql
SELECT openid FROM users;
```
3. 查看日志文件中的用户消息记录
4. 在微信开发者工具中调试获取
## 系统工作流程
### 新用户首次交互
1. 用户发送消息到公众号
2. 系统检查用户不在白名单
3. 同步回复提示信息:
```
⚠️ 您不在白名单中无法使用AI服务。
请发送【加入白名单】指令申请加入(如需密码请提供)。
```
4. 用户发送加入指令
5. 系统验证并加入白名单
6. 用户后续消息正常获得AI回复
### 白名单用户消息处理
1. 用户发送消息
2. 系统确认白名单状态
3. 主线程立即返回空响应
4. 后台线程处理:
- 调用Dify API获取AI回复
- 保存对话记录到数据库
- 通过微信客服接口发送回复
5. 用户收到AI回复
## 部署与运行
### 运行要求
- Python 3.7+
- 依赖库:`flask`, `requests`, `sqlite3`
### 安装依赖
```bash
pip install flask requests
```
### 启动服务
```bash
python app.py
```
### 生产环境部署建议
1. 使用WSGI服务器如Gunicorn
```bash
gunicorn -w 4 -b 0.0.0.0:8010 app:app
```
2. 配置Nginx反向代理
3. 设置HTTPS加密
4. 使用进程管理工具如systemd确保服务持续运行
5. 定期备份数据库文件wechat.db
### 常见问题及解决方法
1. **用户无法加入白名单**
- 检查`WHITELIST_PASSWORD`配置是否正确
- 验证数据库写入权限
- 查看应用日志中的错误信息
2. **AI服务无响应**
- 检查Dify API密钥和URL配置
- 验证网络连接能否访问Dify API
- 查看Dify平台状态
3. **微信消息无法接收**
- 验证`WX_TOKEN``WX_APPID``WX_APPSECRET`配置
- 检查服务器网络配置80/443端口开放
- 确认微信公众号服务器配置正确
4. **管理接口无响应**
- 检查防火墙设置8010端口开放
- 验证服务是否正常运行
- 确认openid格式正确
### 日志分析
系统关键操作都会在控制台输出日志,包括:
- 微信签名验证结果
- 数据库操作状态
- Dify API调用详情
- 白名单变更记录
## 系统优化建议
1. **数据库优化**
- 定期归档历史消息
- 添加索引优化查询性能
- 实现数据库连接池
2. **安全性增强**
- 实现IP白名单限制
- 添加请求频率限制
- 敏感操作审计日志
3. **功能扩展**
- 添加多客服支持
- 实现对话记录导出
- 集成更多AI平台
- 添加用户反馈机制
## 注意事项
1. **微信API限制**
- 客服消息接口有频率限制最多5条/秒)
- 48小时内需回复用户消息
- access_token有效期7200秒需缓存复用
2. **数据隐私**
- 用户对话记录包含敏感信息,需加密存储
- 遵守GDPR和当地数据保护法规
- 定期清理不必要的数据
3. **性能考虑**
- 单实例适合中小规模使用
- 大规模部署需改用MySQL/PostgreSQL
- 考虑添加Redis缓存提升性能
4. **备份策略**
- 每日自动备份数据库
- 保留最近7天的备份
- 定期验证备份可恢复性
## 附录
### 微信消息XML格式示例
```xml
<xml>
<ToUserName><![CDATA[公众号ID]]></ToUserName>
<FromUserName><![CDATA[用户openid]]></FromUserName>
<CreateTime>1678901234</CreateTime>
<MsgType><![CDATA[text]]></MsgType>
<Content><![CDATA[测试消息]]></Content>
<MsgId>1234567890123456</MsgId>
</xml>
```
### Dify API响应示例
```json
{
"conversation_id": "12345678-90ab-cdef-ghij-klmnopqrstuv",
"answer": "你好!有什么我可以帮助你的吗?",
"metadata": {
"usage": {
"prompt_tokens": 15,
"completion_tokens": 10,
"total_tokens": 25
}
}
}
```
### 数据库维护命令
```sql
-- 查看白名单用户
SELECT openid, whitelisted FROM users WHERE whitelisted=1;
-- 查询用户对话历史
SELECT role, content, timestamp FROM messages
WHERE openid='用户openid'
ORDER BY timestamp DESC
LIMIT 10;
-- 优化数据库性能
VACUUM;
ANALYZE;
```