☰
Flask+微信校园助手:从Token校验到服务器部署完整实战
2026/9/29 2:54:09 网站建设 项目流程

简介:基于Python与Flask构建的微信公共系统校园助手项目,面向高校学生及开发者,适用于毕业设计、课程设计与项目实训场景。资源以完整可运行的源码为核心,涵盖Flask应用模块、HTML页面、JavaScript与CSS前端资源,以及两份Markdown部署文档和辅助配置数据。共48个文件,压缩包约187KB,其中31个Python文件负责业务逻辑与微信接口处理,7个HTML页面支撑后台展示,整体目录结构清晰,便于二次开发。项目经导师指导认可,答辩评审95分,代码已测试运行成功,可直接作为毕设、课设或学习进阶的基础。已有122人学习下载,适合希望快速搭建校园助手类微信应用、理解Flask项目分层设计的读者参考扩展。

1. 用 Flask 搭微信校园助手:先想明白它到底解决什么问题

看到“微信公共系统校园助手”这个项目标题,先别急着解压源码包。它的本质不复杂:一个跑在服务器上的 Flask 后端,接收微信公众号服务器转发的用户消息,查校园数据,再把结果以文本或图文形式回复给用户。用户在微信里看到的是一个会查课表、报失物的公众号,开发者眼里其实就是一条 HTTP 路由加几张数据表。这种“高分项目”的价值不在压缩包本身,而在于你能不能把它跑起来、讲清楚,并且换台机器还能重新部署。适合做课程设计、毕业设计的在校学生,也想给学院公众号写自动回复的同学。下面我按真实项目的落地顺序——握手、功能、部署、避坑——拆开讲。

2. 微信公众平台的握手逻辑:先过 token 校验,再谈消息收发

很多新手拿到 Flask 代码就跑去公众号后台填 URL,结果永远卡在“配置失败”。问题在于没搞懂微信的握手机制:公众号后台和你的服务器之间不是直接连的,微信需要先确认这个 URL 背后确实是你。这个确认动作叫服务器 URL 校验,它是 GET 请求;而真正的用户消息是 POST 请求。两套逻辑混在一起写,是第一个翻车点。

2.1 服务器配置里 URL、Token、EncodingAESKey 各在管什么

打开公众号后台的“设置与开发 → 基本配置”,你会看到三个必填项:

参数作用新手建议
URL微信服务器回调你的接口地址,用户消息会 POST 到这里形如http://你的域名/wechat
Token你自定义的字符串,相当于校验用的共享密钥随便写一串随机字符,越乱越好
EncodingAESKey消息加密模式下的密钥先用明文模式,跑通后再研究加密

当你点击“提交”时,微信会往这个 URL 发一个 GET 请求,带上signature、timestamp、nonce、echostr四个参数。你的服务器要做的是:把Token、timestamp、nonce三个字符串按字典序排序、拼接、做 SHA1 哈希,得到的值如果等于signature,就把echostr原样返回。微信收到正确的echostr,才认为这个 URL 是你的,之后才会把用户消息 POST 过来。

提示:微信公众号后台的服务器 URL 只支持 80 和 443 端口,本地调试时不要指望微信能访问你笔记本上的 5000 端口。

2.2 最小 Flask 路由跑通 URL 校验:SHA1 签名的计算顺序

先用最精简的代码把校验跑通,这是整个项目的“门卫”。网上流传的代码各有写法,但核心就三步:排序、拼接、哈希。

import hashlib from flask import Flask, request app = Flask(__name__) # 和公众号后台填写的 Token 保持一致,改一个字符都校验不过 TOKEN = "campus_helper_2024" @app.route("/wechat", methods=["GET", "POST"]) def wechat(): if request.method == "GET": # 微信服务器配置提交时,发的是 GET 校验请求 signature = request.args.get("signature") timestamp = request.args.get("timestamp") nonce = request.args.get("nonce") echostr = request.args.get("echostr") # 1. 三个参数放进列表 tmp_list = [TOKEN, timestamp, nonce] # 2. 字典序排序,这一步顺序错了哈希永远对不上 tmp_list.sort() # 3. 拼成字符串再做 SHA1 tmp_str = "".join(tmp_list) if hashlib.sha1(tmp_str.encode("utf-8")).hexdigest() == signature: # 校验通过,原样返回 echostr,注意是字符串 return echostr return "verify failed", 403 # POST 分支留给消息处理,下一节写 return "ok" if __name__ == "__main__": app.run(host="0.0.0.0", port=80)

这段代码看着短,有两个容易忽略的点。第一,sort()是 Python 默认的字典序,微信那边也是同样的排序规则,两边的排序结果必须完全一致,所以别手动拼接,直接让sort()处理。第二,返回echostr时不要加引号、不要格式化,微信拿它做严格比对,多一个空格都会失败。为什么用 SHA1?因为微信在早期就定了这套签名协议,至今没变,你只需要照做。

2.3 POST 路线:把微信的 XML 包解析成校园业务能用的数据

校验通过后,用户在公众号里发的每条消息都会以 POST 形式转发过来,请求体是一段 XML。比如用户发了一句“查课表”,你收到的原始数据长这样:

<xml> <ToUserName><![CDATA[gh_xxxxxx]]></ToUserName> <FromUserName><![CDATA[oABCDEF123]]></FromUserName> <CreateTime>1720000000</CreateTime> <MsgType><![CDATA[text]]></MsgType> <Content><![CDATA[查课表]]></Content> <MsgId>2400000001</MsgId> </xml>

FromUserName是用户的 openid,ToUserName是公众号的原始 ID,Content是用户输入的文本,MsgId是这条消息的唯一编号,后面做去重靠它。解析之后要拼一段新的 XML 回去,注意收发双方要互换:

import time import xml.etree.ElementTree as ET from flask import request, make_response @app.route("/wechat", methods=["GET", "POST"]) def wechat(): if request.method == "GET": # 校验逻辑同上,省略 pass # 解析微信转发的 XML xml_data = request.data root = ET.fromstring(xml_data) from_user = root.findtext("FromUserName") # 用户 openid to_user = root.findtext("ToUserName") # 公众号原始 ID msg_type = root.findtext("MsgType") content = root.findtext("Content") # 按消息类型分发给校园助手的业务模块 if msg_type == "text": reply_text = handle_campus_biz(content, from_user) reply = build_text_reply(from_user, to_user, reply_text) else: reply = build_text_reply(from_user, to_user, "暂不支持这类消息") resp = make_response(reply) resp.content_type = "application/xml; charset=utf-8" return resp def build_text_reply(from_user, to_user, content): """构造文本回复 XML,注意 ToUserName 和 FromUserName 互换""" reply = f"""<xml> <ToUserName><![CDATA[{from_user}]]></ToUserName> <FromUserName><![CDATA[{to_user}]]></FromUserName> <CreateTime>{int(time.time())}</CreateTime> <MsgType><![CDATA[text]]></MsgType> <Content><![CDATA[{content}]]></Content> </xml>""" return reply

这里有三个新手必踩的细节。第一,回复 XML 里ToUserName要填用户的 openid,FromUserName要填公众号的 ID,跟收到的消息正好相反,写反了微信会提示“该公众号提供的服务出现故障”。第二,CreateTime用 Unix 时间戳,int(time.time())就够了。第三,所有用户输入的内容必须包在<![CDATA[]]>里,否则用户发个&或<就直接把 XML 搞坏了。到这一步,你的 Flask 服务已经具备和微信公众号对话的资格,接下来才轮到业务功能。

3. 把校园助手功能做厚:课表查询与失物招领的落地写法

握手通了,接下来是这个项目的重头戏:校园业务。市面上这类“校园助手”源码包,功能再花哨,核心也就几块——课表查询、成绩查询、失物招领、校园通知。这里我挑课表和失物招领两块展开,因为它们代表了两种典型写法:一个查自己的数据,一个搜公共的数据池。

3.1 SQLite 轻量化存储:课表和失物招领两张表就够

先说存储选型。很多课程设计一上来就上 MySQL,但校园助手这种场景,用户量几百到几千,数据量撑死几万行,用 SQLite 完全够。SQLite 是单文件数据库,零配置,备份直接把.db文件拷走,部署时不用额外装服务,这对 Flask 项目来说省掉一大半运维成本。我一般在项目根目录建一个init_db.py:

import sqlite3 def init_db(): conn = sqlite3.connect("campus.db") cur = conn.cursor() # 课表:按学生 openid 存,weekday 1-7 表示周一到周日 cur.execute(""" CREATE TABLE IF NOT EXISTS course ( id INTEGER PRIMARY KEY AUTOINCREMENT, openid TEXT, course_name TEXT, weekday INTEGER, start_section INTEGER, end_section INTEGER, location TEXT ) """) # 失物招领:kind 区分失物还是招领,title 和 description 存描述 cur.execute(""" CREATE TABLE IF NOT EXISTS lost_found ( id INTEGER PRIMARY KEY AUTOINCREMENT, kind TEXT, title TEXT, description TEXT, contact TEXT, create_time TEXT ) """) conn.commit() conn.close() if __name__ == "__main__": init_db()

这段设计决定了后续所有查询逻辑的形态。course表用openid做用户维度,是因为微信不给你用户手机号,openid 就是用户在公众号里的唯一身份。weekday和start_section、end_section分开存,是为了支持“今天星期几上什么课”这种动态查询,而不是只存一个“周一第一大节”的字符串。lost_found表的kind字段是关键,失物和招领是两种方向,用户发“我丢了钥匙”和“我捡到一串钥匙”,匹配逻辑完全不同。

3.2 课表查询与失物招领的关键词路由:从精确匹配到相似度匹配

业务函数handle_campus_biz的写法决定了助手的“智商”。最朴素的版本就是字符串判断:

def handle_campus_biz(content, openid): content = content.strip() if content.startswith("课表"): return query_course(openid, content) if "失物" in content or "招领" in content or "丢" in content: return search_lost_found(content) return "试试这样问我:\n课表\n失物招领"

课表查询相对直接,用户发“课表”就查全部,发“课表 周一”就按 weekday 过滤:

import sqlite3, datetime def query_course(openid, content): conn = sqlite3.connect("campus.db") cur = conn.cursor() # 如果没指定星期,默认查今天 if "周" in content: weekday = int(content[-1]) # 取“周一”里的 1 else: weekday = datetime.datetime.now().isoweekday() cur.execute( "SELECT course_name, start_section, end_section, location " "FROM course WHERE openid=? AND weekday=?", (openid, weekday) ) rows = cur.fetchall() conn.close() if not rows: return f"周{weekday}没有课" return "\n".join(f"{r[0]} 第{r[1]}-{r[2]}节 @ {r[3]}" for r in rows)

注意这里用了参数化查询?,别用 f-string 拼 SQL,既防注入也避免引号转义的坑。失物招领要复杂一些,因为用户说“找钥匙”,你的数据里可能写的是“一串钥匙落在三教”,这需要相似度匹配而不是完全相等。这里有个热门的做法是参考“基于 Flask 的校园失物招领智能匹配平台”的思路,用中文分词加集合交集做关键词精准匹配:

def search_lost_found(content): conn = sqlite3.connect("campus.db") cur = conn.cursor() cur.execute("SELECT kind, title, description, contact FROM lost_found") rows = cur.fetchall() conn.close() # 用 jieba 分词,把用户输入拆成关键词集合 import jieba query_words = set(jieba.cut(content)) results = [] for kind, title, desc, contact in rows: text = f"{title}{desc}" title_words = set(jieba.cut(text)) # 交集词数就是相似度分数 score = len(query_words & title_words) if score > 0: results.append((score, kind, title, desc, contact)) results.sort(key=lambda x: x[0], reverse=True) if not results: return "暂时没有匹配的失物招领信息" # 取前 3 条返回 lines = [] for score, kind, title, desc, contact in results[:3]: tag = "招领" if kind == "found" else "寻物" lines.append(f"[{tag}]{title}\n{desc}\n联系:{contact}") return "\n\n".join(lines)

jieba是中文分词库,pip install jieba就能装,分词后取交集,交集越大匹配度越高。这个方案比正则匹配聪明在:用户说“钥匙丢了”,数据里写“一串钥匙”,分词后都有“钥匙”这个词,就匹配上了;而纯字符串in判断会因为“丢了”和“钥匙”的顺序问题漏掉。匹配精度的调参主要看分词的粒度,jieba.cut默认模式对校园场景够用,如果你发现误匹配太多,可以换成jieba.lcut再手动加停用词表过滤“的、了、吗”这类无意义词。

3.3 图文消息回复:让查询结果从一行字变成可点击卡片

文本回复够用,但体验一般。微信公众号支持图文消息(news 类型),一屏能展示标题、描述、封面图和跳转链接。校园助手最常见的做法是:失物招领结果用图文卡片,点进去看详情。构造图文回复的 XML 和文本略有不同:

def build_news_reply(from_user, to_user, articles): """articles 是列表,每个元素是 {'title':..., 'description':..., 'pic_url':..., 'url':...}""" items = "" for a in articles: items += f"""<item> <Title><![CDATA[{a['title']}]]></Title> <Description><![CDATA[{a['description']}]]></Description> <PicUrl><![CDATA[{a['pic_url']}]]></PicUrl> <Url><![CDATA[{a['url']}]]></Url> </item>""" reply = f"""<xml> <ToUserName><![CDATA[{from_user}]]></ToUserName> <FromUserName><![CDATA[{to_user}]]></FromUserName> <CreateTime>{int(time.time())}</CreateTime> <MsgType><![CDATA[news]]></MsgType> <ArticleCount>{len(articles)}</ArticleCount> <Articles>{items}</Articles> </xml>""" return reply

图文消息有两个隐藏要求。第一,PicUrl和Url必须是公网可访问的完整地址,不能填相对路径,否则卡片显示空白;开发阶段可以先用一个占位图地址顶上。第二,ArticleCount必须和<item>数量一致,写错数字微信会直接丢弃整条回复。你要是把失物招领的查询结果转成这种卡片格式,整个项目的完成度立刻上一个档次,这也是“高分项目”和普通作业的明显分水岭。

4. 云服务器上的 Flask 部署:systemd 托管 + Nginx 反向代理组合拳

功能写完了,下一步是把项目部署到服务器上。说到底,公众号要求你的 URL 必须公网可达,这一步躲不开。这里说的部署方案是我个人最常用的组合:云服务器上装 Python 环境,Systemd 让 Flask 常驻,Nginx 做反向代理统一收流量。整个过程按顺序做,半小时能跑通。

4.1 服务器环境准备:Python 3 + venv 虚拟环境 + 依赖安装

拿到源码包后的第一件事永远是看依赖清单。正规项目会带一个requirements.txt,里面列着flask、requests、jieba这些包。先建虚拟环境再装依赖,这是避免把服务器系统 Python 环境搞乱的后悔药:

# 以 Ubuntu 为例,先更新系统并安装基础组件 apt-get update && apt-get install -y python3 python3-venv nginx # 建项目目录并进入 mkdir -p /opt/campus_helper && cd /opt/campus_helper # 把源码包解压到当前目录(假设压缩包已上传到服务器) unzip campus_helper.zip -d . # 创建虚拟环境 python3 -m venv venv source venv/bin/activate # 安装依赖,国内服务器建议加清华镜像源,速度快很多 pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple

如果你的源码包没有requirements.txt,也不要慌,看app.py顶部的import语句,缺什么装什么,flask和requests是跑不掉的。这里有个细节:生产环境不要用python app.py直接跑,Flask 自带的开发服务器性能差,换个思路用gunicorn这个 WSGI 服务器来托管 Flask 应用,它支持多 worker 并发,扛几百个公众号用户毫无压力。

4.2 用 systemd 把 Flask 服务变成常驻进程

gunicorn装好后,手动启动没问题,但服务器一重启服务就没了,这不叫部署。正确做法是写一个 systemd 服务单元文件,让系统帮你管理进程,崩溃自动拉起,开机自动启动。在/etc/systemd/system/campus_helper.service里写:

[Unit] Description=Campus Helper Flask Service After=network.target [Service] User=www-data WorkingDirectory=/opt/campus_helper # 2 个 worker 进程,绑定本机 5000 端口,不直接暴露公网 ExecStart=/opt/campus_helper/venv/bin/gunicorn -w 2 -b 127.0.0.1:5000 app:app Restart=always RestartSec=3 [Install] WantedBy=multi-user.target

写完后依次执行三条命令:

systemctl daemon-reload systemctl enable campus_helper # 开机自启 systemctl start campus_helper systemctl status campus_helper # 查看运行状态

ExecStart里app:app的含义是:从app.py文件里找名为app的 Flask 实例。如果你的主文件叫main.py,就要写成main:app。-w 2是 worker 进程数,通常设为 CPU 核数的两倍即可,不要盲目加大,因为每个 worker 都会占用内存。Restart=always是血泪经验,公众号接口偶发超时导致 gunicorn worker 异常退出,没有这个配置服务就静默死掉了,用户发消息永远没人回。

4.3 Nginx 反向代理:让公众号只认 80 端口

Flask 服务现在监听在127.0.0.1:5000,公网访问不到。公众号后台要求 URL 走 80 或 443 端口,所以用 Nginx 把 80 端口的请求转发给内网的 5000 端口。在/etc/nginx/sites-available/campus_helper里写:

server { listen 80; server_name your_domain.com; # 换成你自己的域名 location /wechat { proxy_pass http://127.0.0.1:5000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }

然后启用这个配置并重载:

ln -s /etc/nginx/sites-available/campus_helper /etc/nginx/sites-enabled/ nginx -t # 先验证配置语法,有错别急着重载 systemctl reload nginx

这样公众号后台的 URL 就填http://your_domain.com/wechat。proxy_set_header那几行不是可选项,微信的回调请求会带上原始 HOST 头,不转发的話 Flask 里取到的域名是127.0.0.1:5000,后续做消息回复里的跳转链接时全部会拼错。到这一步,你的服务已经具备上线条件。

4.4 部署后的自测:用 curl 模拟微信的校验请求

部署完别急着去公众号后台点提交,先用 curl 在服务器本机模拟一次微信校验,这时候出错成本最低。先算出一个合法签名:

python3 -c "import hashlib; print(hashlib.sha1(''.join(sorted(['campus_helper_2024','1720000000','123456'])).encode('utf-8')).hexdigest())"

再把算出来的签名拼进 URL 请求:

curl "http://127.0.0.1/wechat?signature=上面算出的值&timestamp=1720000000&nonce=123456&echostr=hello"

如果一切正常,curl 会原样返回hello。如果返回 403,按这个顺序排查:Token是否和公众号后台一致、timestamp和nonce是否和你算签名时用的完全一致、TOKEN字符串有没有空格。本机通了,再去公众号后台提交配置,基本一次过。之后想看实时日志,用journalctl -u campus_helper -f跟踪,所有print输出都会出现在这里。

5. 微信校园助手避坑指南:5 个高频翻车现场与排查顺序

这部分是付费都未必有人教的踩坑合集,全是真实项目里反复出现的翻车现场。每一条我都按“现象 → 原因 → 解决”写清楚,你对照着排查,能省下大半天。

5.1 服务器 URL 校验一直失败,先查排序和 Token 一致性

现象:公众号后台点“提交”,提示“配置失败”,检查了好几遍 URL 和 Token 都没写错。

原因:绝大多数情况是签名计算顺序不对。微信的签名规则是先把Token、timestamp、nonce三个字符串放进列表,按字典序sort(),然后再拼接做 SHA1。有人手写排序逻辑,有人拼接时把Token放在了最后,哈希值就对不上。另一种可能是服务器代码里TOKEN变量末尾多了换行符或空格,肉眼看不出来,但哈希对不上。

解决:把签名计算单独抽成一个函数,加日志打出来对比。在本地把signature、timestamp、nonce三个值打出来,和公众号后台生成的请求参数逐字比对,重点看末尾有没有空白字符。另外,本地调试时别用微信后台触发,用 4.4 节的 curl 方法自己造请求,调通了再让后台来校验。

5.2 消息处理超过 5 秒,微信直接把回复丢掉

现象:后端日志显示已经处理了用户消息,回复也拼好了,但用户那边始终看不到任何回应。

原因:微信公众平台要求被动回复必须在 5 秒内返回。如果你的处理逻辑里有慢查询、外部 API 调用、或者图片下载,很容易超时。超时后微信会重试同一个请求,最多重试 3 次,每次都超时的话,这条消息就彻底丢了。

解决:把重活拆出去。常见做法是收到消息后先立刻返回一个空字符串(合法的被动回复),同时把任务丢进后台线程去处理,最后通过客服消息接口主动推送给用户。另一种更轻的办法是缩短处理链:课表查询这种数据库操作控制在 5 秒内通常没问题,但像“联网查天气再回复”这种就别塞进被动回复里。给你的每个业务函数加耗时统计,超过 1 秒就要警惕。

5.3 同一条消息被处理两次,失物招领数据写重了

现象:用户发布一条失物信息,后台表里出现了两条一模一样的记录。

原因:微信对 5 秒内没有正常回复的消息会自动重试,重试的请求里带着同一个MsgId。如果你的处理逻辑没有做幂等控制,第二次请求进来时会把同一条数据再插一遍。还有一种情况是你自己在超时后手动补发回复,又把处理逻辑跑了一遍。

解决:用MsgId做唯一性约束。在失物招领表里加一列msg_id,插入前先查一下存在不存在,或者直接给msg_id加唯一索引。更通用的做法是维护一张msg_log表,记录所有收到过的MsgId和处理时间,每次请求先查这张表,存在就跳过。这也是我强烈建议每个公众号项目都做的事,后面排查问题全靠它。

5.4 中文乱码和 XML 解析报错:charset 和 CDATA 缺一不可

现象:用户消息里含中文时,解析出来是乱码;或者用户发了个表情符号,ET.fromstring直接抛异常。

原因:第一,Flask 返回响应时没有指定content_type,默认可能不是 UTF-8 编码,微信收到后按错误编码解读就变乱码。第二,用户消息内容里含有&、<、>之类的 XML 特殊字符,没有用CDATA包裹,解析器直接报错。

解决:所有make_response都显式声明resp.content_type = "application/xml; charset=utf-8"。所有动态内容(用户名、消息正文、查询结果)在拼 XML 时一律包进<![CDATA[]]>,不要偷懒用转义函数替代。另外解析时用ET.fromstring(request.data),不要用request.get_data(as_text=True)再手动编码,request.data是原始字节流,交给 ElementTree 处理编码更稳。

5.5 Windows 上跑得好好的,传到 Linux 服务器就找不到附件文件

现象:本地开发时用户发的图片能正常保存和回复,部署到服务器后报“FileNotFoundError”或者图片 404。

原因:这是 Windows 和 Linux 路径差异的经典翻车。“高分项目”里常见代码写的是D:/uploads/xxx.jpg这种带盘符的绝对路径,或者uploads\xxx.jpg这种反斜杠路径。Windows 下能跑,换到 Linux 服务器上路径不存在,目录还不能写。

解决:路径统一用os.path.join拼,不要手写斜杠。文件存储目录用相对app.root_path的路径,启动时先os.makedirs建目录。比如UPLOAD_DIR = os.path.join(app.root_path, "uploads"),确保目录存在并给予写权限。如果 Nginx 也参与图片服务,要单独配一个location /uploads指向真实目录,否则 Flask 返回的图片 URL 会被 Nginx 拦下来。排查时先ls服务器上的实际文件路径,再对比代码里拼出来的路径,十有八九是少了一层目录。

6. 进阶玩法:从被动回复到主动推送,做一个会找人的校园助手

被动回复只是公众号最基础的能力。真正让校园助手“活”起来的,是主动推送——比如有人发布了一条“在三教捡到校园卡”的招领信息,系统自动把这条消息推给所有发布过寻物启事的用户。这个功能的骨架是自定义菜单加模板消息,代码量不大,但需要你理清一个关键前提:微信公众号的接口调用需要access_token。

6.1 自定义菜单:把文本口令变成按钮

用户不用记“课表”“失物”这些口令,直接点菜单就行。创建菜单是主动调微信接口,你只需要在 Flask 里加一个初始化路由:

import requests def create_menu(access_token): url = f"https://api.weixin.qq.com/cgi-bin/menu/create?access_token={access_token}" menu_data = { "button": [ {"type": "click", "name": "今日课表", "key": "COURSE_TODAY"}, {"type": "click", "name": "失物招领", "key": "LOST_FOUND"}, ] } resp = requests.post(url, json=menu_data) print(resp.json()) # {"errcode": 0, "errmsg": "ok"} 才算成功

key值会在用户点击菜单时以event消息 POST 到你的服务器,在MsgType == "event"分支里按EventKey分发就行,逻辑和文本消息完全一致。

6.2 用 access_token 推模板消息,把失物招领主动送到用户手里

access_token是调用所有微信接口的通行证,有效期 7200 秒,获取接口本身需要你的appid和appsecret。注意这里的坑:access_token必须缓存起来,频繁调用获取接口会被微信限流。

import time, requests class TokenManager: def __init__(self, appid, secret): self.appid = appid self.secret = secret self.token = None self.expires_at = 0 def get_token(self): # 提前 5 分钟过期,留出刷新余量 if self.token and time.time() < self.expires_at - 300: return self.token url = "https://api.weixin.qq.com/cgi-bin/token" resp = requests.get(url, params={ "grant_type": "client_credential", "appid": self.appid, "secret": self.secret }).json() self.token = resp["access_token"] self.expires_at = time.time() + resp["expires_in"] return self.token

拿到 token 后推模板消息,先要在公众号后台申请模板拿到模板 ID,然后拼一个touser(用户 openid)加data的 JSON 发出去。模板消息最适合的场景就是失物招领成功匹配后的即时提醒。我现在的固定习惯是:不管项目多小,先建一张msg_log表记录每条收发消息和每次接口调用的响应码,出问题先查日志再动代码。这个习惯帮我躲过了无数次“黑匣子”式的排查。校园助手这个方向,技术上不复杂,但把握手、部署、去重、推送这些细节都理顺,它就是一个能真正跑起来服务学生的完整系统。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询