软件开发流程案例(2)----微信公众号返利机器人开发

目录结构:

├── 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函数进行监听,给公众号发消息就会有回复了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值