diff --git a/wechat_bot_userfilter.py b/wechat_bot_userfilter.py new file mode 100644 index 0000000..afb1a04 --- /dev/null +++ b/wechat_bot_userfilter.py @@ -0,0 +1,590 @@ +# -*- coding: utf-8 -*- +""" +完整的微信客服系统,支持: +1. SQLite 存储用户信息和对话历史。 +2. 白名单机制,根据 openid 判断是否允许调用 AI。 +3. 将 Dify 返回的 conversation_id 持久化到数据库(users.preferences 字段), + 并在程序启动时加载,实现跨重启的多轮会话上下文记忆。 +4. 管理接口,用于添加/移除白名单用户。 +5. 用户可通过发送指定指令加入白名单(需验证密码)。 +6. 异步回复机制:收到用户消息后立即返回空串给微信,后台线程调用 AI 再通过客服接口发送回复。 + +请务必替换下列配置: + WX_TOKEN、WX_APPID、WX_APPSECRET、DIFY_API_BASE、DIFY_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 用于保存 ,实现多轮对话上下文管理 +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_prefs(dict)转换为 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""" + + + + {int(time.time())} + + + +""".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""" + + + + {int(time.time())} + + + +""".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) \ No newline at end of file diff --git a/wechat_bot_userfilter技术文档.md b/wechat_bot_userfilter技术文档.md new file mode 100644 index 0000000..eeb2a7b --- /dev/null +++ b/wechat_bot_userfilter技术文档.md @@ -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 + + + + 1678901234 + + + 1234567890123456 + +``` + +### 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; +``` \ No newline at end of file