现在很多人都随身携带两部手机,原因多种多样,但是肯定有一个主力机,有一个副机,我就是其中之一,解决了很多工作和生活中的分离问题。但这随之带来了一个硬核痛点——消息断层。当主力机在手边时,如何无缝、安全且及时地接收另一台手机上的重要短信通知、验证码和来电通知?运营商给了我们最简单的方案:呼叫转移,但这需要收费,而且按分钟收费。好像短信也可以转移,但是也是要收费的。如果两部手机不是一个运营商的,似乎也很难实现。那有没有一个办法能够让我们这种双持佬平时在用主力机的时候,能够及时的知道备用机收到了短信或者是重要的电话?之前我用两部安卓手机,通过云服务器、短信转发器、Nginx和Gotify实现了双机互通短信和电话通知。但是因为最近更换了鸿蒙座舱,我把主力机换成了鸿蒙系统的华为手机,原来的转接方案突然不能用了:Gotify不是卓易通可下载的软件,将APK在鸿蒙系统安装后,无法正常通知,这有解决的办法吗?答案是肯定的,所有的问题在技术面前都是纸老虎。
经过几天的折腾与重构,我借助短信转发器、京东云服务器、Caddy 网关、企业微信,彻底再造打通了这条“加密专线”。今天就把这套无保活、全加密、超低延迟的转发方案,以及期间踩过无数次的“惊天大坑”完整记录下来,希望能帮到同样有着双持需求的折腾党。
⚠️ 战前准备:两个极其重要的前提
由于这套方案我们需要用到国内的云服务器,并且要与企业微信的官方接口进行双向通信,所以在动手敲代码之前,你必须确保满足以下两个硬性条件,否则后面一定会卡死:
1. 必须拥有一个已完成 ICP 备案的域名 例如 wx.yourdomain.com。如果你的域名没有备案,国内云服务器的 Web 端口是无法对外提供访问的,企业微信的爬虫也无法验证你的服务器所有权。
2. 必须在云服务器控制台开放 80 和 443 端口(新手最易踩坑!) 不要以为在服务器系统里把程序跑起来就行了。国内云厂商(如京东云、阿里云等)默认有外部防火墙(安全组)拦截。
- 你必须登录云服务商的网页控制台,找到这台服务器的【安全组】或【防火墙】设置,手动添加放行规则:允许 TCP 协议的 80 和 443 端口入站。
- 为什么要开? 80 端口用于 Caddy 网关全自动为你申请和续期免费的 HTTPS 证书;443 端口则是我们这条安全加密专线数据传输的“必经大门”。不开这两扇门,后续的所有测试都会直接“连接超时”。
🏗️ 架构概览
- 发射端(备用机):Android 手机安装开源的「短信转发器(SmsForwarder)」,负责抓取短信和来电。
- 中转站(云服务器):使用 Docker 部署一个极简 Python 接收程序。外层用 Caddy 作为网关处理 HTTPS 证书和反向代理。
- 接收端(主力机):企业微信原生支持鸿蒙系统级推送,Python 程序将消息推送到企业微信,主力机瞬间亮屏提醒。
⚙️ 核心部署步骤(保姆级实操)
第一步:搞定企业微信的“官方通行证”与“域名验证文件”
我们需要让微信官方允许我们通过代码发消息,同时必须在微信后台配置并验证你的备案域名。
- 注册企业微信:个人可以直接在官网注册一个企业(随便填个工作室名字即可)。
- 获取 CorpID:登录网页版后台,在「我的企业」 -> 「企业信息」最下方,复制
企业ID (CorpID)。 - 创建专属应用:在「应用管理」最下方点击「创建应用」(可命名为“短信中转站”)。创建后,复制应用详情页里的
AgentId和Secret。 - 配置可信域名与 IP:在应用详情页最下方找到「企业可信 IP」,填入你的服务器公网 IP。接着找到「网页授权及JS-SDK」(或接收消息配置),填入你的备案域名。
- 下载核心校验文件:填入域名后,企业微信会要求你证明该域名的所有权。它会提供一个类似
WW_verify_xxxxxx.txt的文件下载。记住这个文件名和文件里面那一串乱码字符,我们在配置 Caddy 时会用到!
第二步:服务器端部署 Python 桥接程序
在服务器上新建一个目录:mkdir -p ~/sms-bridge && cd ~/sms-bridge。
1. 编写 server.py
创建 server.py,粘贴以下纯标准库、免安装任何依赖的极简代码:
Python
import os
import json
import urllib.request
from urllib.parse import urlparse, parse_qs
from http.server import BaseHTTPRequestHandler, HTTPServer
CORPID = os.environ.get('CORPID')
AGENTID = os.environ.get('AGENTID')
SECRET = os.environ.get('SECRET')
SECURITY_TOKEN = os.environ.get('SECURITY_TOKEN')
def get_access_token():
url = f"https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid={CORPID}&corpsecret={SECRET}"
req = urllib.request.Request(url)
with urllib.request.urlopen(req) as response:
return json.loads(response.read().decode('utf-8')).get('access_token')
def send_wechat_msg(content):
token = get_access_token()
if not token: return False
url = f"https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token={token}"
data = {"touser": "@all", "msgtype": "text", "agentid": AGENTID, "text": {"content": content}, "safe": 0}
req = urllib.request.Request(url, data=json.dumps(data).encode('utf-8'), headers={'Content-Type': 'application/json'})
with urllib.request.urlopen(req) as response:
return json.loads(response.read().decode('utf-8')).get('errcode') == 0
class RequestHandler(BaseHTTPRequestHandler):
def do_POST(self):
query_params = parse_qs(urlparse(self.path).query)
if query_params.get('token', [None])[0] != SECURITY_TOKEN:
self.send_response(403); self.end_headers(); self.wfile.write(b"Forbidden"); return
content_length = int(self.headers.get('Content-Length', 0))
post_data = self.rfile.read(content_length).decode('utf-8')
msg_content = post_data
try:
parsed_json = json.loads(post_data)
msg_content = parsed_json.get("text", post_data)
except: pass
success = send_wechat_msg(msg_content)
self.send_response(200 if success else 500)
self.end_headers()
self.wfile.write(b"Success" if success else b"Error")
if __name__ == '__main__':
server_address = ('0.0.0.0', 5000)
print("🚀 短信中转站已启动...")
HTTPServer(server_address, RequestHandler).serve_forever()
2. 编写 docker-compose.yml
创建文件并填入配置。核心安全设计在于我们将端口死死锁在本地的 6688,对公网隐身。
YAML
version: '3'
services:
sms-wechat-bridge:
image: python:3.10-slim
container_name: sms_wechat_bridge
volumes:
- ./server.py:/app/server.py
ports:
- "172.17.0.1:6688:5000" # 核心防线:只绑定本地网卡 6688 端口
environment:
# 以下信息请务必替换为你自己的真实数据,切勿泄露
- CORPID=填入CorpID
- AGENTID=填入AgentID
- SECRET=填入Secret
- SECURITY_TOKEN=自定义一个暗号(例如:hMupWJovHFi...)
command: python /app/server.py
restart: always
执行 docker-compose up -d 启动容器。
第三步:关键插曲与配置 Caddy 网关
很多人的做法是找个 FTP 工具把微信域名验证用的 TXT 文件传进服务器,但既然我们用了现代化的 Caddy 网关,完全不需要上传任何物理文件!我们直接在配置里用两行代码“凭空捏造”出这个文件即可。
打开 Caddy 配置文件(通常为 /etc/caddy/Caddyfile),填入以下“防弹版”垂直写法配置:
Code snippet
http://wx.yourdomain.com, https://wx.yourdomain.com {
# 1. 微信验证专属防弹通道(垂直写法,绝不断行)
# 【注意】将 WW_verify_xxxxxx.txt 和 respond 后的 "xxxxxx" 替换成你在企微后台获取的真实文件名与乱码
route /WW_verify_xxxxxx.txt {
header Content-Type text/plain
respond "xxxxxx" 200
}
# 2. 短信转发器通道
# 【注意】这里的 6688 端口必须和你 docker-compose.yml 里映射的本地端口保持一致
handle_path /sms-bridge/* {
reverse_proxy 172.17.0.1:6688
}
}
保存后执行 docker restart caddy。
此时,立刻回到企业微信后台,点击刚才卡住的“确定验证”,依靠这种内存级别的响应,绝对秒过绿灯!
第四步:备用机 SmsForwarder 设置
在安卓备用机打开「短信转发器」,新建发送通道 (Webhook):
- WebServer:
https://wx.yourdomain.com/sms-bridge/msg?token=填入你刚才自定义的暗号 - 请求方式:POST
- 其他设置:全部留空点击测试!如果配置无误,中转站就已经全线贯通。最后在 App 中配置转发规则(匹配所有短信/来电,绑定此通道)即可。
第五步:主力机微信接收设置(打通最后一公里)
难道我还要在主力机上专门装一个企业微信 App 天天挂着后台吗?当然不需要!对于我们的鸿蒙主力机来说,最完美的方案是直接用普通版微信,来“白嫖”最高级别的推送通道。
- 找到微信插件:回到电脑端的企业微信管理后台,点击顶部菜单的「我的企业」,然后在左侧菜单栏找到「微信插件」。
- 扫码绑定:在这个页面往下拉,你会看到一个包含你刚才创建的企业名称的专属二维码。
- 微信直连:拿出你的主力机,打开日常用的普通微信,直接“扫一扫”这个二维码,点击关注你的企业号。
- 体验秒推:关注成功后,你的微信消息列表里会多出一个企业联系人图标。建议把它置顶。现在,你可以把企业微信 App 彻底从主力机上卸载了!
以后所有备用机转发过来的验证码和电话,都会依托微信在鸿蒙系统里的系统级保活机制,像微信好友发消息一样瞬间弹出,做到真正的“超低延迟、零漏接”!
☠️ 血泪避坑指南(纯干货总结)
这套方案我重构了几次,期间踩中了几个极其隐蔽的“雷区”,如果你也卡在某一步,请直接对号入座:
坑一:企业微信域名验证的“一字之差”与“308 重定向”机制
- 死穴:从微信后台复制验证码时极易在末尾多复制一个空格或字母。微信机器校验极其死板。此外,Caddy 默认会将 HTTP 强制重定向至 HTTPS,而微信爬虫只要没一口吃到 200 状态码,遇到 308 跳转就会直接报错“所有权检验不通过”。
- 解法:如上文 Caddy 配置所示,使用
http://, https://声明允许双协议,并采用垂直结构的route语法,用respond指令直接返回那串精确的乱码。
坑二:Caddyfile 变态的“语法洁癖”
- 死穴:用 SSH 终端粘贴配置时,长句容易被终端自动换行,把内容挤到了下一行。Caddy 只要发现大括号
{后面还有字符,直接启动暴毙。 - 解法:采用精简的“垂直排版”,避免单行过长。每次修改完,养成先用
docker logs caddy --tail 20看一眼死亡日记的好习惯。
坑三:Docker 环境变量的“记忆固化”
- 死穴:在手机端测试一直报
HTTP 403,明明docker-compose.yml里的暗号改对了却依然不通。 - 解法:修改完配置,仅仅保存文件是不够的!正在运行的容器脑子里还是旧密码。必须执行强制换锁命令:
docker-compose down && docker-compose up -d。
坑四:Android 手机极其固执的 SSL 证书缓存
- 死穴:电脑浏览器访问域名有绿锁,但安卓 App 测试永远提示“证书验证失败”。
- 解法:安卓底层网络库经常把上一次握手失败的记忆“死死缓存”。去系统设置里对「短信转发器」执行“强制停止”并“清除缓存”,或者开启再关闭一次飞行模式强制刷新 DNS。
折腾的乐趣,就在于最终看到两端设备成功握手的那一刻。希望这套方案能让你的安卓鸿蒙双持体验更加完美!
