
简介面向微信公众号开发者与Django学习者这份源码提供了一个完整的前后端集成示例涵盖公众号接口对接、页面渲染、数据配置与样式交互等常见开发环节适合有一定Python基础、希望从实际项目中理解公众号开发流程的中级工程师。压缩包共493个文件约70.77MB以Python脚本、JavaScript脚本、GIF动图、CSS样式、HTML页面为主其中Python处理后端逻辑JavaScript与CSS负责前端交互GIF和HTML构成展示素材同时配有XML/JSON配置、Markdown笔记、SQLite数据库和Android客户端安装包方便直接研究或改造复用。资源按功能模块组织目录层次清晰可快速定位公众号菜单配置、消息处理、Web展示、静态资源管理等代码片段。同时附带的批处理脚本、配置模板和日志文件为本地部署调试与排错提供了参考。目前已有326人学习下载适合需要完整项目样例来巩固Django与微信公众号开发技能的读者。1. 基于 Django 框架的微信公众号开发与设计源码解决的是工程而不是接口示例搜「基于 Django 框架的微信公众号开发与设计源码」的人通常不缺微信官方文档缺的是一份能直接跑的 Django 工程。公众号后台的「服务器配置」要求填一个 URL之后微信服务器会把验证请求、用户消息、菜单事件全部 POST 到这个地址。也就是说公众号开发的第一道坎不是调接口而是先有一个长期可用的 Django 入口再组织好验签、消息路由和 access_token 缓存。这个标题把 Django 与微信公众号绑在一起讲的是用 URL 路由、ORM、缓存把微信回调与业务数据做成完整系统。适合刚接手公众号项目的 Python 工程师也适合把公众号能力并入现有 Django 服务的团队。一个反直觉的结论最耗时的是签名校验、超时重试、token 过期这类边界问题把源码按「回调层、业务层、API 封装层」切分才是标题里「设计」二字的真正落点。2. 微信公众号回调的三个约定签名校验、XML 消息路由与 access_token 生命周期2.1 服务器配置里的 Token 签名校验怎么算公众号后台「基本配置」里URL 和 Token 决定回调能否生效。保存配置时微信服务器会向你的 URL 发一个 GET 请求带 signature、timestamp、nonce、echostr 四个参数。校验算法是固定的把 Token、timestamp、nonce 三个字符串按字典序排序后拼接再做 SHA1结果与 signature 相等就说明请求确实来自微信此时把 echostr 原样返回配置保存成功。import hashlib from django.conf import settings def check_wechat_signature(signature, timestamp, nonce): token settings.WECHAT_TOKEN tmp .join(sorted([token, timestamp, nonce])) return hashlib.sha1(tmp.encode(utf-8)).hexdigest() signature这里的sorted是字典序排序不是按长度排timestamp 和 nonce 都要保持字符串原样不要转 int否则排序结果变化、哈希对不上。公众号后台保存配置时微信端用的就是同一套算法任何一方拼接顺序不同都会验签失败。后续网页授权、JS-SDK 签名也共用 sha1 工具函数建议把验签放独立的 crypto.py 统一维护。2.2 用户消息是 POST 的 XML路由表就是设计图配置生效后用户发消息、点菜单微信都会 POST 一段 XML 到你的 URL。字段包括 ToUserName、FromUserName、CreateTime、MsgType、Content、MsgId菜单点击则是 MsgType 为 event 的事件带 Event 和 EventKey。回调入口只有一个进入后要按消息类型分流路由表应当是开发前第一张画出来的图。MsgType触发场景常用处理text用户发文本关键词回复、业务查询image / voice / video发媒体素材落库、内容审核eventsubscribe关注 / 扫码写用户表、发欢迎语eventCLICK菜单点击按 EventKey 路由eventLOCATION上报位置门店、区域运营我一般把{消息类型: 处理函数}维护成字典注册表代替一长串 if-else新增类型只需注册新函数。还要记住一个硬约束微信要求在 5 秒内响应超时会重试三次业务超过 5 秒查库、调外部接口时回调里先返回空串真正的回复交给异步任务走客服消息接口这是公众号开发最常见的解耦方式。2.3 access_token 两小时过期缓存策略决定接口可用性access_token 是公众号所有业务接口的全局票据通过GET https://api.weixin.qq.com/cgi-bin/token?grant_typeclient_credentialappidAPPIDsecretAPPSECRET获取有效期 7200 秒。最差的写法是每次调用都重新获取很快撞上频率限制正确做法是全局维持一份快过期时再刷新。缓存方案适用规模注意点进程内字典加时间戳单机调试多进程会各自刷 tokenDjango cacheLocMem单机生产TIMEOUT 要小于 7200Redis / Memcached多机负载均衡加锁防并发刷新我一般直接用 Django cachetimeout 设 7000 秒留 200 秒余量给网络抖动。多机部署必须换 Redis并在刷新逻辑上加锁否则两个进程同时发现 token 过期、各自刷新后刷新的覆盖先刷新的全站接口会间歇性报 40001。这个故障单机开发时完全复现不出来却是生产环境最常见的微信接口故障来源。3. 用 Django 写最小可运行源码从 URL 配置到文本消息收发3.1 项目骨架与 settings 里的关键项建项目和 app 走标准流程这就是 python django 搭建 web 项目里最典型的一个最小闭环python -m venv venv source venv/bin/activate pip install django requests django-admin startproject mp_project python manage.py startapp wechatpython manage.py startapp wechat创建 app 之后要立刻把 wechat 加进 INSTALLED_APPS否则 urlconf 和 management command 不会被加载新手在这一步最容易卡住。数据库用 SQLite 起步即可量大了再切 MySQL如果直接上 MySQL先解决 django install mysqlclient 的编译依赖Ubuntu 下先装 libmysqlclient-devWindows 下用预编译 wheelDjango ORM 层切库基本不用改代码。# settings.py 追加 WECHAT_TOKEN your_token_here # 与后台基本配置完全一致 WECHAT_APPID wx1234567890abcdef WECHAT_SECRET secret_here ALLOWED_HOSTS [*] # 上线前改为真实域名WECHAT_TOKEN就是公众号后台填的那个字符串不要加盐、不要二次编码否则验签永远失败。密钥建议从环境变量读取提交源码到仓库或交付外包时尤其重要。3.2 GET 分支验签与 echostr 返回回调 view 同时处理 GET 和 POSTGET 是后台保存配置时的验证请求POST 是后续所有消息。验签结果直接决定配置能否保存成功# wechat/views.py import time import xml.etree.ElementTree as ET from django.http import HttpResponse, HttpResponseForbidden from django.views.decorators.csrf import csrf_exempt from wechat.crypto import check_wechat_signature csrf_exempt def wechat_callback(request): if request.method GET: signature request.GET.get(signature, ) timestamp request.GET.get(timestamp, ) nonce request.GET.get(nonce, ) if check_wechat_signature(signature, timestamp, nonce): return HttpResponse(request.GET.get(echostr, )) return HttpResponseForbidden(signature mismatch) # POST 分支见 3.3验签通过后必须把 echostr 原样返回不能加换行或前后缀否则后台提示「Token 验证失败」。失败时优先检查Token 是否完全一致、服务器本机时间与标准时间偏差是否过大。时区配错或时间不同步会导致签名对不上这在容器环境里很常见。3.3 POST 分支解析 XML 并回包消息体是固定结构的 XML用标准库 ElementTree 解析即可。微信服务器不会带 Django 的 CSRF cookie所以必须加csrf_exempt安全性由签名校验兜底。# wechat/views.py 续 def build_text_reply(to_user, from_user, content): xml ( xmlToUserName![CDATA[{}]]/ToUserName FromUserName![CDATA[{}]]/FromUserName CreateTime{}/CreateTime MsgType![CDATA[text]]/MsgType Content![CDATA[{}]]/Content/xml ) return xml.format(to_user, from_user, int(time.time()), content) def _handle_post(request): try: root ET.fromstring(request.body.decode(utf-8)) except ET.ParseError: return HttpResponse() msg_type root.findtext(MsgType) from_user root.findtext(FromUserName) to_user root.findtext(ToUserName) if msg_type text: content root.findtext(Content, ).strip() # 在这里接业务逻辑关键词、ORM 查询、内部服务 reply build_text_reply(to_user, from_user, f收到{content}) return HttpResponse(reply, content_typeapplication/xml) if msg_type event and root.findtext(Event) subscribe: return HttpResponse() # 新关注逻辑见第 4 章 return HttpResponse()三个坑先说清楚。第一不要用 request.POST微信的 content-type 是 text/xmlDjango 不会把它解析进 POST 字典必须读 request.body 后自己解析。第二回复 XML 里 FromUserName 和 ToUserName 要和收到时调换位置——收到的 FromUserName 是用户 openid回复时要放到 ToUserName写反了用户会看到「该公众号暂时无法回复」第三识别不了的消息类型直接返回空响应不要回一段格式错误的 XML那会触发微信侧的错误统计。ElementTree 不擅长输出 CDATA所以回复文本直接用字符串拼接这是公众号 XML 回包常见的保留写法。提示解析失败、消息类型未知时统一返回空字符串微信不会重试也不会计入错误这是回调开发里最安全的兜底行为。3.4 urlconf 挂载与本地 curl 验证最后把回调挂进 urls本地用 curl 模拟微信的 GET 验证请求# wechat/urls.py from django.urls import path from . import views urlpatterns [path(callback/, views.wechat_callback, namewechat_callback)]# mp_project/urls.py from django.urls import include, path urlpatterns [path(wechat/, include(wechat.urls))]curl 验证时 signature 需要自己按同一算法算出来填入用任意时间戳即可因为校验只比较哈希不校验时间窗口。返回 echostr 说明整条链路已经通了这时再去公众号后台保存配置一次就能过。4. 把公众号能力接进 Django 工程自定义菜单、模板消息与用户落库4.1 用管理命令批量创建自定义菜单菜单接口本质是一次 HTTP POST写成 Django management command 而不是页面按钮触发好处是可以幂等执行、上线可重放。菜单是嵌套 JSON一级菜单最多 3 个每个一级下最多 5 个二级菜单。# wechat/management/commands/create_menu.py import requests from django.core.management.base import BaseCommand from wechat.utils import get_access_token class Command(BaseCommand): help 创建公众号自定义菜单 def handle(self, *args, **options): menu { button: [ {type: view, name: 官网, url: https://www.example.com}, {type: click, name: 今日推荐, key: DAILY_PICK}, { name: 更多, sub_button: [ {type: view, name: 历史文章, url: https://www.example.com/articles}, ], }, ] } resp requests.post( https://api.weixin.qq.com/cgi-bin/menu/create, params{access_token: get_access_token()}, jsonmenu, ).json() self.stdout.write(str(resp))执行python manage.py create_menu即可。按钮 type 有 click、view、miniprogram、scancode_push 等click 按钮靠 key 唯一标识view 按钮直接跳网页。前后端分离架构下view 的 url 指向前端独立域名公众号内打开时微信会追加 fromsinglemessage 参数前端要兼容。公众号网页授权回调则是另一个重定向高发场景django 重定向传递数据最常见的做法是把目标地址编码进 state 参数授权跳回后再按 state 解码分发避免把业务参数直接暴露在 URL 上。4.2 模板消息ORM 用户表 活动开始提醒模板消息用于服务通知典型场景是活动开始提醒、订单状态变更。发送前提是拿到 openid 且用户关注了公众号先建用户表# wechat/models.py from django.db import models class WxUser(models.Model): openid models.CharField(max_length64, uniqueTrue) nickname models.CharField(max_length64, blankTrue, default) subscribe_time models.DateTimeField(nullTrue, blankTrue) created_at models.DateTimeField(auto_now_addTrue)把 WxUser 注册进 django admin 后运营可以直接按 openid 查用户admin 的 list_display 就是常见的 django admin 界面美化做法不引第三方主题也能得到一个可用的运营后台。发送函数统一封装# wechat/services.py import requests from wechat.utils import get_access_token def send_template_message(openid, template_id, data, page): payload { touser: openid, template_id: template_id, page: page, data: {key: {value: value} for key, value in data.items()}, } return requests.post( https://api.weixin.qq.com/cgi-bin/message/template/send, params{access_token: get_access_token()}, jsonpayload, ).json()调用时把{thing1: 技术分享会, time2: 19:00}传给 datatemplate_id 从后台「模板消息」申请。data 的 key 必须与模板占位符一一对应thing 类型限 20 字超长直接报错。高频错误码 43004 表示用户未关注业务层过滤45009 表示接口超限发送端要做节流。更稳妥的是维护一张发送记录表记录 openid、template_id、状态定时任务扫失败记录重试这样每条通知都可审计。4.3 access_token 统一走 Django cachetoken 获取逻辑收敛到一个函数所有调用方都走它# wechat/utils.py import requests from django.conf import settings from django.core.cache import cache def get_access_token(): token cache.get(wechat_access_token) if token: return token resp requests.get( https://api.weixin.qq.com/cgi-bin/token, params{ grant_type: client_credential, appid: settings.WECHAT_APPID, secret: settings.WECHAT_SECRET, }, timeout10, ).json() if access_token not in resp: raise RuntimeError(ftoken 获取失败: {resp}) cache.set(wechat_access_token, resp[access_token], timeout7000) return resp[access_token]timeout 设 7000 而不是 7200留出刷新余量。多机部署时把 CACHES 切到 Redis并加刷新锁进程 A 拿不到 token 去刷新、进程 B 也同时刷新后写覆盖先写就会出现间歇性 40001。还要确认 settings 里的全局CACHES[default][TIMEOUT]没有把 7000 覆盖掉这是缓存配置里很隐蔽的一个坑。4.4 批量发送的异步边界批量发模板消息不要在 view 里同步循环那会拖垮响应。小规模用线程池量再大上 celery 或 django-q# wechat/tasks.py from concurrent.futures import ThreadPoolExecutor from django.db import close_old_connections from wechat.models import WxUser from wechat.services import send_template_message def notify_all(template_id, data): openids list(WxUser.objects.values_list(openid, flatTrue)) def send(openid): try: send_template_message(openid, template_id, data) finally: close_old_connections() with ThreadPoolExecutor(max_workers8) as pool: pool.map(send, openids)Django ORM 的连接绑定线程线程池里跑完必须close_old_connections()否则连接池被占满这是后台任务最常见的坑。微信对模板消息有分钟级限额发送前按 openid 维度做节流避免一个活动把额度打光。5. 公众号上线前的验证清单调试工具回放、错误码速查与验签装饰器5.1 用接口调试工具回放请求功能开发完别急着用手机反复发消息。公众号后台「开发」菜单下的在线接口调试工具可以直接回放请求、看原始报文比手机更可控选择接口、填 access_token 和参数、点「检查问题」工具返回微信服务器原始响应。这对「文档一模一样却不生效」的场景定位非常有效。开发期建议申请一个测试号AppID 和 Secret 独立随便折腾不污染线上公众号。5.2 高频错误码速查errcode含义处理40001access_token 无效或过期检查多进程覆盖重新获取40164调用 IP 不在白名单后台白名单加服务器出口 IP45009接口频率超限本地限流查循环调用40001 不一定是真过期更多是多个进程各持一份 token、后获取的覆盖先获取的。排查时看日志里 token 刷新时间是否过于密集是就去加固 4.3 的缓存锁。40164 是换服务器、加负载均衡节点后最容易漏的新节点出口 IP 没加白名单接口会批量失败现象是本地调试全通、线上全挂。5.3 一个可复用的验签装饰器把验签从 view 里抽成装饰器任何需要微信侧调用的 view 都能一行注解搞定安全校验# wechat/decorators.py from functools import wraps from django.http import HttpResponseForbidden from wechat.crypto import check_wechat_signature def wechat_signature_required(view_func): wraps(view_func) def _wrapped(request, *args, **kwargs): signature request.GET.get(signature, ) timestamp request.GET.get(timestamp, ) nonce request.GET.get(nonce, ) if not check_wechat_signature(signature, timestamp, nonce): return HttpResponseForbidden(invalid signature) return view_func(request, *args, **kwargs) return _wrapped回调 view 声明成csrf_exempt加wechat_signature_required两层注解先验签再进业务分发任何没通过验签的请求在进入业务代码前就被拦下这是回调地址暴露公网后最基础的一层防线。把这个装饰器、get_access_token 和 build_text_reply 三个组件抽出来就是一套能复制到任意 Django 项目的公众号最小工具箱最后把 WxUser 注册进 admin运营人员在 Django 后台就能直接检索 openid、看关注时间整个源码闭环到这里就完整了。本文还有配套的精品资源点击获取