目录结构:
├── main.py # 项目主入口
├── backend #后端目录
├── frontend #前端目录
├── data #数据目录(系统配置信息)
第一模块:微信消息接收接口
位于backend/wechat目录下
结合第一章的内容,构思需要的数据,并设计好数据内容
该接口需要:
验证微信签名,确认请求合法
数据库表需要WECHAT_APPSECRET WECHAT_APPSECRET WECHAT_TOKEN
解析消息体,获取用户 FromUserName(即openid)
查询数据库看是否已有该用户
如果无,创建新用户并存库
可以回复欢迎消息给用户
同时判断消息内容属于转链还是指令并相应调用方法。
需要在微信公众号后台去获取参数和配置好token
在测试阶段可以将参数写死在代码内或者是用json文件暂存参数,模拟读取数据库的动作。
直接编译接口代码,后续为监听程序。将该接口拆分为路由、校验、转发。
调用wechatpy:这是一个第三方库,专门用于对接微信公众号
解析微信发来的消息(XML → Python 对象)。
判断消息类型,如果是文本,就交给 handle_text。
如果不支持,就返回统一的提示回复。
路由接口:
调用wechatpy:这是一个第三方库,专门用于对接微信公众号
解析微信发来的消息(XML → Python 对象)。
判断消息类型,如果是文本,就交给 handle_text。如果不支持,就返回统一的提示回复。
/backend/wechat/router_wx.py
from wechatpy import parse_message
from wechatpy.replies import create_reply
from backend.wechat.handlers.text_handler import handle_text
from sqlalchemy.orm import Session
def dispatch_wechat_message(xml_body: bytes, db: Session):
msg = parse_message(xml_body)
if msg.type == "text":
return handle_text(msg, db)
return create_reply("暂不支持该类型消息", msg)
校验接口:
提供 签名校验函数(验证消息来源是否是微信服务器)
提供 access_token 获取与缓存(避免频繁请求微信)
提供 用户信息获取函数(通过 openid 拉取用户资料)
因为拉取用户头像和昵称需要公众号认证,我测试的时候就没进行。
/backend/wechat/utils_wx.py
import os # 用于读取环境变量
import requests # 发起 HTTP 请求
from wechatpy.utils import check_signature # 用于校验微信签名
from wechatpy.exceptions import InvalidSignatureException # 异常类
import time # 用于记录缓存时间
from dotenv import load_dotenv # 加载 .env 文件中的环境变量
# ============================ 加载 .env 配置 ============================
# 只执行一次,加载根目录的 .env 配置文件
load_dotenv()
# 从环境变量中读取 AppID 和 AppSecret(需确保 .env 文件中已配置)
APP_ID = os.getenv("WECHAT_APPID")
APP_SECRET = os.getenv("WECHAT_APPSECRET")
# ============================ 微信签名校验 ============================
def check_wechat_signature(token: str, signature: str, timestamp: str, nonce: str) -> bool:
"""
校验微信签名,确保请求来源于微信服务器。
"""
try:
check_signature(token, signature, timestamp, nonce)
return True
except InvalidSignatureException:
return False
# ============================ 缓存 access_token ============================
# 设置一个全局字典,用于缓存 access_token 及其过期时间
_access_token_cache = {
"access_token": None, # 当前 access_token
"expires_at": 0 # access_token 的过期时间戳
}
def get_access_token():
"""
获取微信 access_token,并做本地缓存(有效期7200秒)。
避免每次请求都去微信服务器,提高效率。
"""
now = time.time()
# 如果 access_token 有效,则直接返回缓存
if _access_token_cache["access_token"] and now < _access_token_cache["expires_at"]:
return _access_token_cache["access_token"]
# 向微信服务器请求新的 access_token
url = f"https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={APP_ID}&secret={APP_SECRET}"
resp = requests.get(url)
data = resp.json()
# 请求成功后,将 access_token 缓存
if "access_token" in data:
_access_token_cache["access_token"] = data["access_token"]
_access_token_cache["expires_at"] = now + data.get("expires_in", 7200) - 60 # 提前 60 秒防]()_
_access_token_cache["access_token"] = data["access_token"]
_access_token_cache["expires_at"] = now + data.get("expires_in", 7200) - 60 # 提前 60 秒刷新
return data["access_token"]
else:
# 如果接口返回错误,抛出异常
raise Exception(f"获取 access_token 失败: {data}")
# ============================ 拉取用户信息 ============================
def get_user_info(openid: str):
print(f"模拟返回用户信息 openid={openid}")
return {
"nickname": "测试用户",
"headimgurl": "https://example.com/avatar.jpg"
}
'''
def get_user_info(openid: str):
"""
拉取指定 openid 的微信用户信息,包括昵称、头像。
注意:需要公众号已获取用户关注权限(scope=snsapi_userinfo)
"""
access_token = get_access_token() # 获取有效 access_token
url = f"https://api.weixin.qq.com/cgi-bin/user/info?access_token={access_token}&openid={openid}&lang=zh_CN"
resp = requests.get(url)
user_data = resp.json()
print("微信接口返回:", resp.json())
# 返回用户数据,包含 nickname、headimgurl 等字段
return user_data'''
转发接口:
生成符合微信格式的回复 XML。
调用折淘客 API 做 返利转链。数据库操作存入消息和新用户信息(用户和消息表)。
/backend/wechat/handlers/ text_handler.py
from wechatpy.replies import create_reply
from backend.core.zhetaoke_api import ztk_convert_link
from backend.crud import user_crud,message_crud # 导入用户相关的CRUD模块
from sqlalchemy.orm import Session
from backend.wechat.utils_wx import get_user_info # 导入获取微信用户信息的方法
from data.schemas import UserMessageCreate # 导入 Pydantic 模型
def handle_text(msg, db: Session):
"""
处理微信文本消息,包含:
- 用户openid入库(新用户写库)
- 老用户更新昵称和头像
- 转链请求处理
- 默认欢迎回复
"""
openid = msg.source # 获取微信用户openid
# 查询数据库是否已有该用户
user = user_crud.get_user_by_openid(db, openid)
if not user:
# 如果数据库无该用户,说明是新用户,调用微信接口拉取用户昵称和头像
user_info = get_user_info(openid)
# 如果微信接口获取成功,则传入昵称和头像创建用户
if user_info:
print(f"新用户 openid={openid},微信用户信息:{user_info}")
user = user_crud.create_user(
db,
openid=openid,
nickname=user_info.get("nickname"),
avatar_url=user_info.get("headimgurl"),
)
else:
# 如果微信接口调用失败,仍然创建基本用户(无昵称头像)
user = user_crud.create_user(db, openid=openid)
else:
# 老用户,尝试更新昵称和头像(防止用户更换)
user_info = get_user_info(openid)
if user_info:
# 如果昵称或头像有变化则更新数据库
if (user.nickname != user_info.get("nickname") or
user.avatar_url != user_info.get("headimgurl")):
user_crud.update_user_info(
db,
openid=openid,
nickname=user_info.get("nickname"),
avatar_url=user_info.get("headimgurl"),
)
content = msg.content.strip() # 获取消息内容,去除前后空白
# —— 新增:写入用户发来的消息(direction = "in") ——
msg_in = UserMessageCreate(
openid=openid,
direction="in",
msg_type="text",
content=content
)
message_crud.create_message(db, msg_in)
# 判断是否包含转链关键字,触发转链逻辑
if any(k in content for k in ["http", "¥", "€", "淘宝", "京东", "拼多多"]):
result = ztk_convert_link(content)
if result.get("error"):
reply_content = " 转链失败:" + result["error"]
else:
reply_content = result["content"]
else:
reply_content = "欢迎使用返利助手!发送口令或商品链接即可自动返利~"
# 写入系统回复消息
msg_out = UserMessageCreate(
openid=openid,
direction="out",
msg_type="text",
content=reply_content
)
message_crud.create_message(db, msg_out)
# 最后再统一 return 回复
return create_reply(reply_content, msg)
并在main代码中导入以上模块
# 导入微信工具函数:用于验证微信服务器签名
from backend.wechat.utils_wx import check_wechat_signature
# 导入微信消息分发函数:处理文本、事件等消息
from backend.wechat.router_wx import dispatch_wechat_message
同时写入实现对微信公众号的监听
# ========= 微信接入接口 =========
# 微信服务器接口验证(GET 请求)
@app.get("/wx")
async def wechat_check(signature: str, timestamp: str, nonce: str, echostr: str):
"""
验证微信服务器的签名,公众号首次接入时微信会调用此接口。
参数:
- signature:签名
- timestamp:时间戳
- nonce:随机数
- echostr:原样返回,用于验证成功
返回:
- echostr:验证成功
- "Invalid signature":验证失败
"""
# 验证签名合法性
if check_wechat_signature(WECHAT_TOKEN, signature, timestamp, nonce):
return echostr # 验证通过,返回 echostr 给微信
return "Invalid signature" # 验证失败
# 微信消息接收接口(POST 请求)
@app.post("/wx")
async def wechat_msg(
request: Request, # 获取原始 XML 请求体
db: Session = Depends(get_db) # 注入数据库连接依赖
):
"""
接收用户发来的微信消息(如文本、事件等)
处理逻辑:
1. 读取 XML 请求体
2. 交给消息分发器处理(如自动回复等)
3. 返回构造好的 XML 响应
"""
xml = await request.body() # 异步读取原始 XML 消息体
reply = dispatch_wechat_message(xml, db) # 调用消息分发处理器
return Response(content=reply.render(), media_type="application/xml") # 返回微信要求的 XML 格式响应
配置完成后运行main函数进行监听,给公众号发消息就会有回复了


2万+

被折叠的 条评论
为什么被折叠?



