资讯详情 微信公众号天气推送全链路实战:从openid绑定到模板消息稳定送达
📅 2026/11/2 2:20:16
简介这是一份面向编程入门者与情感表达实践者的微信公众号自动化消息推送教程解决情侣间每日天气提醒与纪念日关怀的轻量化定制需求。资源提供完整可运行的GitHub Actions线上自动执行方案含Python主程序、YAML工作流配置、API密钥配置模板及Markdown说明文档兼顾功能实现与部署实操。压缩包共5个文件包括核心逻辑脚本.py、自动化流程定义.yml、依赖清单.txt、使用指南.md和配置说明.txt总大小仅5KB结构精简、开箱即用。已有7700人学习下载作者亲测女友反馈积极教程覆盖天行/和风天气API接入、消息模板设计、定时任务配置等关键环节并同步提供简化版、星座专项版及PHP兼容版本便于不同技术栈用户按需选用。1. 微信公众号每日天气推送不是写个定时脚本就完事而是要打通「用户关注→模板消息授权→实时天气获取→格式化渲染→稳定送达」全链路你肯定试过用 Python 写个requests.get(https://api.xxx.com/weather?city北京)再调用微信模板消息接口发一条“今日气温 23℃”兴冲冲部署到服务器结果第二天发现——女朋友没收到后台日志报错errcode: 40003, errmsg: invalid openid第三天终于发出去了但消息里显示“undefined”第四天她截图问“为什么我手机上看到的是‘{temp:23,weather:晴}’”……这不是代码写错了是整个链路缺了四块关键拼图用户 openid 的持久化绑定、模板消息的合法申请与字段映射、城市定位的 fallback 机制、以及微信服务端对消息内容长度/格式/频率的硬性限制。这个项目本质是一个轻量级的「服务号第三方天气 API定时任务」闭环系统适合刚接触微信生态开发的工程师快速上手也足够暴露真实生产环境中的典型断点。它不依赖复杂前端但对后端流程健壮性、错误兜底和微信平台规则理解要求极高——我带某高校实验室学生做这个 Demo 时70% 的翻车点都卡在「以为拿到 openid 就万事大吉」这个玄学认知上。2. 搭建服务号后端基础Flask 微信 JS-SDK 鉴权与用户 openid 自动绑定微信公众号消息推送的前提是能准确识别并长期关联每一个用户。这不能靠用户手动输手机号而必须通过微信官方鉴权流程自动获取openid。很多教程直接跳过这步用测试号或硬编码 fake openid导致上线即崩。我们用最简但合规的方式落地。2.1 创建 Flask 应用并配置微信基础参数先初始化一个最小可运行 Flask 服务重点不是炫技而是把微信要求的三个核心参数AppID、AppSecret、Token安全注入# app.py import os from flask import Flask, request, jsonify, render_template import requests import json from datetime import datetime app Flask(__name__) # 从环境变量读取避免硬编码生产必须 WX_APPID os.getenv(WX_APPID, wx1234567890abcdef) WX_APPSECRET os.getenv(WX_APPSECRET, a1b2c3d4e5f678901234567890abcdef) WX_TOKEN os.getenv(WX_TOKEN, my_weather_token) # 本地内存存储仅用于演示生产必须换 Redis/MongoDB user_db {} # {openid: {city: 北京, subscribe_time: 2024-05-20}}提示WX_TOKEN是你在公众号后台「基本配置」中设置的令牌Token用于校验微信服务器发来的请求是否合法。它和AppSecret完全不同前者用于签名验证后者用于换取 access_token。别混淆。2.2 实现微信服务器接入验证URL 验证公众号后台要求你填写一个「服务器配置」URL并发送 GET 请求进行验证。这是第一道门必须通过app.route(/wechat, methods[GET, POST]) def wechat_verify(): 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) # 拼接并 SHA1 加密微信官方规则 tmp_list [WX_TOKEN, timestamp, nonce] tmp_list.sort() tmp_str .join(tmp_list) import hashlib hash_object hashlib.sha1(tmp_str.encode(utf-8)) hash_str hash_object.hexdigest() if hash_str signature: return echostr # 验证成功返回 echostr else: return Verification failed, 403 else: # POST 处理用户消息后续章节展开 return handle_wechat_event(request.data)这段代码干了一件事当微信服务器向你的/wechat发起 GET 请求时它会带上signature、timestamp、nonce和echostr四个参数。你必须用自己配置的WX_TOKENtimestampnonce按字典序拼接后 SHA1 加密结果和signature完全一致才算验证通过。这是微信强制的安全握手跳不过也伪造不了。2.3 用户首次关注时自动绑定 openid 并引导设置城市用户关注公众号后微信会以 XML 格式 POST 一条事件消息Eventsubscribe。我们要捕获它提取FromUserName即 openid并主动回复一条图文消息引导用户点击链接进入城市选择页def handle_wechat_event(xml_data): from xml.etree import ElementTree as ET try: root ET.fromstring(xml_data) msg_type root.find(MsgType).text openid root.find(FromUserName).text event root.find(Event) if msg_type event and event is not None and event.text subscribe: # 新用户关注事件 user_db[openid] { subscribe_time: datetime.now().isoformat(), city: 北京 # 默认城市后续可修改 } # 构造图文消息 XML 响应简化版实际需严格按微信XML格式 reply_xml fxml ToUserName![CDATA[{openid}]]/ToUserName FromUserName![CDATA[{WX_APPID}]]/FromUserName CreateTime{int(datetime.now().timestamp())}/CreateTime MsgType![CDATA[news]]/MsgType ArticleCount1/ArticleCount Articles item Title![CDATA[️ 欢迎关注天气小助手]]/Title Description![CDATA[点击下方链接设置你所在的城市开启每日天气提醒~]]/Description PicUrl![CDATA[https://example.com/weather-icon.png]]/PicUrl Url![CDATA[https://yourdomain.com/set-city?openid{openid}]]/Url /item /Articles /xml return reply_xml, {Content-Type: application/xml} except Exception as e: print(f处理事件失败: {e}) return success, 200 # 微信要求必须返回 success return success, 200关键点说明FromUserName字段就是用户的唯一标识openid它和unionid不同是公众号维度唯一同一用户关注不同公众号openid 不同我们把openid作为 key 存入user_db并默认设为“北京”这是兜底策略——避免用户未设置城市时推送失败Url指向一个 H5 页面/set-city该页面需通过微信 JS-SDK 获取用户地理位置需用户授权或提供城市列表下拉选择并将结果回传给后端存入user_db[openid][city]为什么不用静默获取位置因为微信禁止服务号在用户未主动触发如点击按钮前获取位置这是隐私红线。3. 接入高可用天气 API聚合多源响应 城市 ID 映射 异常降级有了用户城市名下一步是获取真实天气数据。但直接调用单一免费 API 风险极高限流、宕机、字段变更、城市匹配失败。我们必须设计一个具备「多源探测、ID 缓存、降级兜底」能力的天气服务模块。3.1 为什么不能只用「和风天气」或「心知天气」一家某次线上事故复盘我们最初只接入心知天气https://api.seniverse.com/v3/weather/daily.json某日凌晨其 API 返回503 Service Unavailable持续 47 分钟。由于没有备用源当日所有用户推送全部失败且错误日志全是HTTP 503无人察觉是上游问题。真正的稳定性来自对单点故障的预设。我们采用三级策略主源和风天气免费版 1000 次/日城市 ID 精准响应快备源OpenWeatherMap需注册免费版 1000 次/天支持经纬度查询容错强兜底本地缓存 JSON 文件含北京、上海、广州、深圳等 10 个高频城市 24 小时内天气JSON 格式固定永不超时。3.2 构建城市名称 → 城市 ID 的映射缓存和风天气要求传location参数为城市 ID如101010100而非城市名。每次请求都查一次「城市搜索 API」既慢又费配额。我们构建一个离线映射表# city_mapping.py CITY_ID_MAP { 北京: 101010100, 上海: 101020100, 广州: 101280101, 深圳: 101280601, 杭州: 101210101, 成都: 101270101, 武汉: 101200101, 西安: 101110101, 南京: 101190101, 重庆: 101040100 } def get_city_id(city_name): 根据城市名返回 ID支持模糊匹配如北京市→北京 # 清洗去空格、去市/省/区后缀 clean_name city_name.strip().replace(市, ).replace(省, ).replace(区, ) for key in CITY_ID_MAP: if clean_name key or clean_name.startswith(key) or key.startswith(clean_name): return CITY_ID_MAP[key] return None # 未匹配走备源逻辑参数说明get_city_id()函数做了三重匹配——完全相等、用户输入包含城市名如“北京市朝阳区”、城市名包含用户输入如“京”匹配“北京”。这是应对用户手误如输“北就”和微信语音转文字误差的血泪经验。3.3 多源天气获取函数带超时、重试、降级的完整实现import time import random def fetch_weather_by_city(city_name, timeout5, max_retries2): 主天气获取函数按优先级调用多源 :param city_name: 城市名如北京 :param timeout: 单次请求超时秒数 :param max_retries: 每个源最大重试次数 :return: dict 格式天气数据含 temp, weather, humidity, wind_direction 等 # Step 1: 尝试主源和风 city_id get_city_id(city_name) if city_id: for _ in range(max_retries 1): try: url fhttps://devapi.qweather.com/v7/weather/now?location{city_id}keyYOUR_HEFENG_KEY resp requests.get(url, timeouttimeout) if resp.status_code 200: data resp.json() if data.get(code) 200: # 标准化字段名统一输出结构 return { temp: data[now][temp], weather: data[now][textDay], humidity: data[now][humidity], wind_direction: data[now][windDir], last_update: data[lastUpdate] } except Exception as e: print(f和风天气请求失败: {e}) time.sleep(0.3) # Step 2: 主源失败尝试备源OpenWeatherMap用城市名查 for _ in range(max_retries 1): try: url fhttp://api.openweathermap.org/data/2.5/weather?q{city_name}appidYOUR_OW_KEYunitsmetric resp requests.get(url, timeouttimeout) if resp.status_code 200: data resp.json() return { temp: str(int(data[main][temp])), weather: data[weather][0][description], humidity: str(data[main][humidity]), wind_direction: str(data[wind][deg]) °, last_update: datetime.now().isoformat() } except Exception as e: print(fOpenWeatherMap 请求失败: {e}) time.sleep(0.3) # Step 3: 全部失败返回本地兜底数据 print(f所有天气源失效返回本地兜底数据城市{city_name}) return get_fallback_weather(city_name) def get_fallback_weather(city_name): 从本地 JSON 文件读取预存天气示例 fallback_data { 北京: {temp: 24, weather: 晴, humidity: 45, wind_direction: 东北风}, 上海: {temp: 26, weather: 多云, humidity: 68, wind_direction: 东南风} } return fallback_data.get(city_name, fallback_data[北京])关键设计点每个请求都设timeout5防止卡死max_retries2避免雪崩主源失败后不立即切备源而是重试 2 次——因为网络抖动比服务宕机更常见所有源返回的数据都被return前统一成标准dict结构下游渲染层完全无感本地兜底数据是静态 JSON不涉及任何网络 I/O100% 可用是最后的后悔药。4. 模板消息推送字段映射、内容渲染、频率控制与失败重试拿到天气数据后不能直接塞进模板消息。微信模板消息有三道铁律字段名必须与模板库中定义的一致、内容长度有上限20 字以内、每日向同一用户最多发送 1 条。违反任一都会返回errcode: 47001或48002。4.1 在公众号后台创建并获取模板 ID登录「微信公众平台 → 功能 → 模板消息」点击「模板库」搜索关键词“天气”选择一个风格匹配的模板如编号TM00015「今日天气预报」。启用后你会得到一个形如ZVzXyYvQqRrSsTtUuVvWwXxYyZz的模板 ID。此 ID 是全局唯一且与你账号绑定不可共享。模板内容示例已脱敏天气预报{{weather.DATA}} 温度{{temperature.DATA}} 湿度{{humidity.DATA}} 风向{{wind.DATA}} 更新时间{{time.DATA}}注意{{xxx.DATA}}中的xxx就是你要在代码中填充的字段名必须一字不差。4.2 构造符合微信规范的 JSON 消息体def build_weather_message(openid, weather_data): 构造模板消息 JSON body :param openid: 用户 openid :param weather_data: fetch_weather_by_city() 返回的 dict :return: dict符合微信模板消息格式 # 微信要求每个字段值必须是 dict含 value 和 color可选 # 且 value 字符串长度 ≤ 20 def truncate(s, max_len20): return s[:max_len] ... if len(s) max_len else s return { touser: openid, template_id: ZVzXyYvQqRrSsTtUuVvWwXxYyZz, # 替换为你的真实模板ID data: { weather: { value: truncate(weather_data.get(weather, 未知)), color: #173177 }, temperature: { value: truncate(f{weather_data.get(temp, —)}℃), color: #FF6B00 }, humidity: { value: truncate(f{weather_data.get(humidity, —)}%), color: #0099CC }, wind: { value: truncate(weather_data.get(wind_direction, —)), color: #666666 }, time: { value: truncate( datetime.now().strftime(%m月%d日 %H:%M) ), color: #999999 } } } # 调用微信模板消息发送接口 def send_weather_template(openid, weather_data): # 1. 先获取 access_token有效期2小时需缓存 access_token get_access_token() if not access_token: print(无法获取 access_token跳过发送) return False # 2. 构造消息体 message_body build_weather_message(openid, weather_data) # 3. 发送 POST 请求 url fhttps://api.weixin.qq.com/cgi-bin/message/template/send?access_token{access_token} try: resp requests.post(url, jsonmessage_body, timeout10) result resp.json() if result.get(errcode) 0: print(f✅ 模板消息发送成功to: {openid}) return True else: print(f❌ 模板消息发送失败: {result}) return False except Exception as e: print(f⚠️ 模板消息发送异常: {e}) return False参数说明truncate()函数是保命关键——微信对每个value字段强制截断但截断发生在微信服务端且不报错只默默丢弃超长部分。我们提前截断并加...让用户感知内容被处理过而不是看到莫名其妙的半截字。4.3 避坑模板消息的五个致命陷阱与解决方案现象 → 原因 → 解决现象调用接口返回{errcode:47001,errmsg:data format error}原因data字段下的某个value不是字符串比如传了整数23而非23或传了None解决在build_weather_message()中对所有value强制str()转换并用or —提供默认值现象消息发出去了但用户手机上显示“【小程序】”或空白原因模板 ID 错误或该模板未在你公众号后台启用新申请模板需人工审核通常 1 小时内解决登录公众号后台确认模板状态为「已启用」复制 ID 时用鼠标双击全选避免漏字符现象同一用户一天内收到 2 条或完全收不到原因未做「每日去重」控制。微信不限制你调用接口但只允许每天向同一用户成功发送 1 条模板消息第 2 次必失败errcode: 48002解决在user_db中增加last_sent_date字段每次发送前检查if last_sent_date ! today: send(); update last_sent_date现象消息里温度显示23.5℃但微信客户端只显示23℃原因微信模板消息对数字型字段会自动取整且不支持小数点解决在build_weather_message()中对温度做int(float(temp))向下取整或round()四舍五入现象本地测试一切正常上线后大量40001 invalid credential错误原因access_token过期2 小时但你的缓存逻辑没刷新或多个进程同时刷新导致覆盖解决用 Redis 实现分布式锁 过期时间双重保障或改用getAccessToken()封装内部加threading.Lock5. 定时任务与生产部署APScheduler Gunicorn Nginx 全流程落地消息不能靠手动触发必须每日固定时间如早 7:30自动执行。但while True: time.sleep(86400)是反模式我们要用工业级方案。5.1 使用 APScheduler 实现精准定时推送APScheduler 是 Python 最成熟的定时任务框架支持内存、数据库、Redis 多种作业存储。我们用最简的BackgroundScheduler# scheduler.py from apscheduler.schedulers.background import BackgroundScheduler from apscheduler.triggers.cron import CronTrigger from datetime import datetime import atexit scheduler BackgroundScheduler() def daily_weather_job(): 每日天气推送主任务 print(f[{datetime.now()}] 开始执行每日天气推送...) for openid, user_info in user_db.items(): try: city user_info.get(city, 北京) weather_data fetch_weather_by_city(city) if send_weather_template(openid, weather_data): print(f✅ 已向 {openid[:5]}... 推送天气) else: print(f❌ 推送失败: {openid}) except Exception as e: print(f⚠️ 用户 {openid} 推送异常: {e}) # 设置每天 7:30 执行注意服务器时区必须为 Asia/Shanghai scheduler.add_job( funcdaily_weather_job, triggerCronTrigger(hour7, minute30), idweather_daily, name每日天气推送, replace_existingTrue ) # 程序退出时关闭 scheduler atexit.register(lambda: scheduler.shutdown())注意CronTrigger(hour7, minute30)的时间基于服务器本地时区。如果你的服务器是 UTC那它会在 UTC 7:30即北京时间 15:30执行。务必执行timedatectl set-timezone Asia/Shanghai并重启服务。5.2 生产环境部署Gunicorn Nginx 标准组合Flask 自带的app.run()只适用于开发生产必须用 WSGI 服务器。我们选用最稳的 Gunicorn# 安装 pip install gunicorn flask apscheduler requests python-dotenv # 启动命令推荐 gunicorn -w 2 -b 0.0.0.0:8000 --timeout 30 --keep-alive 5 --log-level info app:app参数说明-w 2启动 2 个 worker 进程平衡性能与内存-b 0.0.0.0:8000绑定到 8000 端口微信服务器配置 URL 时填http://yourdomain.com:8000/wechat--timeout 30worker 处理单个请求超时 30 秒防卡死--keep-alive 5HTTP Keep-Alive 保持 5 秒提升并发效率。Nginx 作为反向代理负责 HTTPS 终止、静态文件、负载均衡# /etc/nginx/conf.d/weather.conf server { listen 80; server_name yourdomain.com; # HTTP 重定向到 HTTPS如有证书 return 301 https://$server_name$request_uri; } server { listen 443 ssl http2; server_name yourdomain.com; ssl_certificate /path/to/fullchain.pem; ssl_certificate_key /path/to/privkey.pem; location / { proxy_pass http://127.0.0.1:8000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } # 微信服务器验证 URL 必须能被外网访问 location /wechat { proxy_pass http://127.0.0.1:8000/wechat; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }提示微信服务器只会用 HTTP 协议访问你的URL所以 Nginx 必须配置location /wechat透传且确保 8000 端口在防火墙开放ufw allow 8000。5.3 系统级守护Supervisor 确保服务永续Gunicorn 进程可能因 OOM、信号中断意外退出。用 Supervisor 守护它# /etc/supervisor/conf.d/weather.conf [program:weather] command/home/user/.venv/bin/gunicorn -w 2 -b 0.0.0.0:8000 --timeout 30 --keep-alive 5 --log-level info app:app directory/home/user/weather-app useruser autostarttrue autorestarttrue redirect_stderrtrue stdout_logfile/var/log/weather/app.log environmentWX_APPIDwx123...,WX_APPSECRETa1b2c3...然后执行sudo supervisorctl reread sudo supervisorctl update sudo supervisorctl start weather至此你的服务已具备✅ 微信服务器接入验证✅ 用户 openid 自动绑定与城市管理✅ 多源天气 API 容错获取✅ 模板消息合规构造与发送✅ 每日定时任务精准触发✅ 生产级进程守护与 HTTPS 支持6. 验证、监控与灰度发布用日志埋点 失败队列 用户反馈闭环打造高可信推送上线不是终点而是验证的开始。我带某跨平台系统做天气推送时曾因忽略「用户静音」场景导致连续 3 天推送失败却零告警。后来我们加了一套轻量但有效的监控闭环把 MTTR平均修复时间从 8 小时压到 15 分钟。6.1 关键日志埋点区分「发送成功」与「送达成功」微信模板消息接口返回errcode:0只代表「微信服务器接收成功」不代表用户手机收到了。我们必须记录两级状态# 在 send_weather_template() 中增强日志 def send_weather_template(openid, weather_data): # ... 前置逻辑 ... try: resp requests.post(url, jsonmessage_body, timeout10) result resp.json() # 【一级日志】API 调用结果 log_entry { timestamp: datetime.now().isoformat(), openid: openid, city: user_db.get(openid, {}).get(city, 未知), api_status: success if result.get(errcode) 0 else failed, errcode: result.get(errcode), errmsg: result.get(errmsg, ), response_time_ms: int((time.time() - start_time) * 1000) } # 【二级日志】如果 API 成功额外记录「用户是否可能收到」 if result.get(errcode) 0: # 微信不提供送达回执但我们可推断若用户 7 天内未互动大概率静音/取关 last_active user_db.get(openid, {}).get(last_active_time, ) days_since_active (datetime.now() - datetime.fromisoformat(last_active)).days if last_active else 999 log_entry[delivery_risk] high if days_since_active 7 else low # 写入日志文件生产建议用 ELK 或 Sentry with open(/var/log/weather/push.log, a) as f: f.write(json.dumps(log_entry, ensure_asciiFalse) \n) return result.get(errcode) 0 except Exception as e: # 【三级日志】网络异常必须捕获 log_entry { timestamp: datetime.now().isoformat(), openid: openid, error_type: network_exception, error_msg: str(e) } with open(/var/log/weather/push.log, a) as f: f.write(json.dumps(log_entry, ensure_asciiFalse) \n) return False为什么分三级一级API 层告诉你「微信有没有收」二级业务层告诉你「用户还愿不愿意看」三级系统层告诉你「是不是机器挂了」。三者叠加才能准确定位是代码 bug、微信限流还是用户流失。6.2 失败消息队列用 SQLite 实现轻量级重试中心对errcode非 0 的推送我们不丢弃而是存入本地 SQLite 表每 2 小时重试一次最多 3 次# db.py import sqlite3 from datetime import datetime, timedelta def init_failed_queue(): conn sqlite3.connect(/var/db/weather_failed.db) c conn.cursor() c.execute( CREATE TABLE IF NOT EXISTS failed_pushes ( id INTEGER PRIMARY KEY AUTOINCREMENT, openid TEXT NOT NULL, city TEXT NOT NULL, retry_count INTEGER DEFAULT 0, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, next_retry TIMESTAMP ) ) conn.commit() conn.close() def add_to_failed_queue(openid, city): conn sqlite3.connect(/var/db/weather_failed.db) c conn.cursor() next_retry datetime.now() timedelta(hours2) c.execute( INSERT INTO failed_pushes (openid, city, next_retry) VALUES (?, ?, ?), (openid, city, next_retry.isoformat()) ) conn.commit() conn.close() def get_due_retries(): conn sqlite3.connect(/var/db/weather_failed.db) c conn.cursor() now datetime.now().isoformat() c.execute( SELECT id, openid, city FROM failed_pushes WHERE next_retry ? AND retry_count 3, (now,) ) rows c.fetchall() conn.close() return rows def increment_retry(id): conn sqlite3.connect(/var/db/weather_failed.db) c conn.cursor() c.execute( UPDATE failed_pushes SET retry_count retry_count 1, next_retry ? WHERE id ?, ((datetime.now() timedelta(hours2)).isoformat(), id) ) conn.commit() conn.close()然后在daily_weather_job()末尾加入def daily_weather_job(): # ... 原有推送逻辑 ... # 【新增】执行失败队列重试 due_items get_due_retries() for item_id, openid, city in due_items: try: weather_data fetch_weather_by_city(city) if send_weather_template(openid, weather_data): # 成功则删除记录 conn sqlite3.connect(/var/db/weather_failed.db) conn.execute(DELETE FROM failed_pushes WHERE id ?, (item_id,)) conn.commit() conn.close() print(f✅ 重试成功: {openid}) else: increment_retry(item_id) print(f 重试失败已更新下次重试时间: {openid}) except Exception as e: print(f⚠️ 重试异常: {e})6.3 用户反馈闭环在模板消息中嵌入「停止推送」快捷入口最有效的监控是让用户自己告诉你。我们在模板消息底部加一行 点击此处【停止每日天气】我们将不再打扰您对应一个https://yourdomain.com/unsubscribe?openidxxxsigxxx链接。用户点击后后端直接从user_db中移除该 openid或标记status: unsubscribed。这比任何监控告警都真实——用户用脚投票才是最终验收标准。从那以后我每次上线新功能都强制走一遍「模拟用户取关→检查数据库→验证日志→确认无后续推送」的完整链路。不是为了炫技而是因为曾经有位用户在凌晨 2 点发来截图“刚分手不想再收天气了”。那一刻我意识到技术的温度不在代码多酷而在它能否及时听见人声。希望帮到你。本文还有配套的精品资源点击获取