用 frp 实现家用 Windows 远程桌面内网穿透:从零到可用的完整实践

本文记录一次真实的内网穿透部署过程:用一台云服务器作为 frp 服务端,把家里 Windows 电脑的远程桌面(RDP)安全地暴露到公网。服务端与客户端均已实际部署、长期运行,文内所有踩坑均为实录。

一、为什么需要内网穿透?

家里电脑通常在路由器后面,只有运营商分配的内网 IP(如 192.168.x.x),公网无法直接访问。想在外部连回家里的电脑,常见方案有:

方案 优点 缺点
路由器端口映射 零成本 需要公网 IP,多数家宽没有;且运营商可能封 80/443
TeamViewer / 向日葵 零配置 免费版限速、限商业用途、需信任第三方
Tailscale / ZeroTier 组网优雅、P2P 直连 两端都要装客户端,手机端支持一般
frp 自建 完全可控、带宽自建、不限速 需要一台有公网 IP 的服务器

frp(Fast Reverse Proxy)是一个高性能的反向代理工具,采用 Go 编写,单一二进制文件,部署极其简单。本次选择它,理由是:已有云服务器、需要完全掌控数据、不想受第三方限速。

二、架构设计

frp 内网穿透架构图:外部设备经云服务器代理端口,通过 frp 隧道访问家里 Windows 的远程桌面
整体数据流:外部请求打到云服务器的代理端口,frps 通过 frpc 主动建立的控制隧道转发回家里电脑的本机端口

关键设计决策:

决策 选择 理由
服务端部署方式 宿主机 systemd(非 Docker) frps 仅 20MB 二进制,省一层网络转换,资源占用更低
RDP 对外端口 33390 避开默认 3389 的扫描;低于 49152(Windows 动态端口范围)不冲突
认证方式 token 简单可靠,够用
面板 仅监听 127.0.0.1 不占公网端口,通过 SSH 隧道访问,零暴露面
端口白名单 精确单端口 {33390} 纵深防御,token 泄露时限制破坏面

三、前置条件

服务端(云服务器):

  • 一台有公网 IP 的 Linux 服务器(本文以 Ubuntu 22.04 为例)
  • 建议配置:1 核 1GB 起够用,frps 本身极轻量
  • 能放行端口的防火墙(云厂商安全组 + 系统防火墙)

客户端(家里 Windows):

  • Windows 10/11 专业版(家庭版默认不支持 RDP 被控)
  • 远程桌面功能——别假设它是开着的,本文实机就是关的,见 5.2 节

四、服务端部署(frps)

4.1 放行防火墙端口

在云厂商控制台(安全组/防火墙)放行以下端口:

端口 用途 是否必须对公网
7000 frps 控制端口(frpc 连接用) ✅ 是
33390 RDP 对外代理端口 ✅ 是

💡 面板 7500 不需要放行,它只监听 127.0.0.1。

4.2 下载并安装 frp

# 下载最新版(本文使用 v0.61.2)
cd /tmp
wget https://github.com/fatedier/frp/releases/download/v0.61.2/frp_0.61.2_linux_amd64.tar.gz -O frp.tar.gz

# 解压
tar -xzf frp.tar.gz

# 安装到 /opt/frp
sudo mkdir -p /opt/frp /etc/frp
sudo cp frp_0.61.2_linux_amd64/frps /opt/frp/
sudo cp frp_0.61.2_linux_amd64/frpc /opt/frp/   # frpc 也留着,调试有用
sudo chmod +x /opt/frp/frps /opt/frp/frpc

# 验证
/opt/frp/frps --version
# 输出:0.61.2

4.3 生成安全凭证

# 生成 frp token(64 位十六进制)
openssl rand -hex 32

# 生成面板密码(32 位十六进制)
openssl rand -hex 16

把输出保存好,后面配置要用到。这两个值一旦泄露,等于把内网通道交给别人。

4.4 编写配置文件

创建 /etc/frp/frps.toml:

# ===== 基础绑定 =====
bindAddr = "0.0.0.0"
bindPort = 7000

# ===== 鉴权 =====
auth.method = "token"
auth.token = "在此填入你生成的64位token"

# ===== 传输优化(RDP 友好) =====
transport.tcpMux = true
transport.tcpMuxKeepaliveInterval = 30
transport.tcpKeepalive = 7200

# ===== 对外代理端口白名单(精确收紧,纵深防御) =====
allowPorts = [
  { single = 33390 }
]

# ===== Web 管理面板(仅本地监听,不暴露公网) =====
webServer.addr = "127.0.0.1"
webServer.port = 7500
webServer.user = "admin"
webServer.password = "在此填入面板密码"

# ===== 日志 =====
log.to = "/var/log/frps.log"
log.level = "info"
log.maxDays = 14

# ===== 连接限制 =====
transport.maxPoolCount = 10

设置权限并验证语法:

sudo chmod 640 /etc/frp/frps.toml
sudo chown root:root /etc/frp/frps.toml

# 语法验证(很实用,能提前发现配置错误)
sudo /opt/frp/frps verify -c /etc/frp/frps.toml
# 输出:frps: the configuration file /etc/frp/frps.toml syntax is ok

关于 allowPorts:为什么不用端口范围?

很多人图省事写成 { start = 10000, end = 20000 },留一万多个端口备用。但这有安全隐患:

allowPorts 拦的是已经通过 token 认证的 frpc。正常情况下 frpc 只开配置里写的端口,白名单形同虚设;但一旦 token 泄露,攻击者就能连上来开任意端口做内网横向移动——一万端口等于送他一万个入口。

收紧成精确单端口后,即使 token 泄露,攻击者也只能开这一个端口。成本几乎为零(以后加代理改一次配置重启即可),收益明显。

4.5 创建 systemd 服务

创建 /etc/systemd/system/frps.service:

[Unit]
Description=frp server (frps)
After=network.target network-online.target
Wants=network-online.target

[Service]
Type=simple
ExecStart=/opt/frp/frps -c /etc/frp/frps.toml
Restart=on-failure
RestartSec=5s
LimitNOFILE=1048576

# 安全加固
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/var/log /etc/frp

[Install]
WantedBy=multi-user.target

启动并设置开机自启:

sudo systemctl daemon-reload
sudo systemctl enable --now frps
sudo systemctl status frps

看到 Active: active (running) 即成功。

4.6 验证服务端

# 检查端口监听
sudo ss -tlnp | grep -E "(7000|7500)"
# 期望:
# LISTEN  ... 127.0.0.1:7500 ...   (面板,仅本地)
# LISTEN  ... *:7000       ...     (控制端口,公网)

# 测试本地连通性
nc -zv 127.0.0.1 7000
nc -zv 127.0.0.1 7500

# 测试面板 API
curl -s -u admin:你的面板密码 http://127.0.0.1:7500/api/serverinfo

API 返回 JSON 且包含 "version":"0.61.2" 说明一切正常。

查看实际资源占用:

ps -o pid,rss,cmd -p $(pgrep frps)

实测:约 17~20 MB 内存,CPU 接近 0%。frps 极其轻量,1GB 内存的机器也毫无压力。

五、客户端部署(Windows 上的 frpc)

以下为在家用 Windows 电脑上实际执行过的完整流程,按顺序照做即可复现。全程使用「以管理员身份」打开的 PowerShell(Win → 输入 powershell → Ctrl+Shift+Enter)。

5.0 目录规划

C:\frp\
├── bin\frpc.exe      ← 客户端主程序
├── nssm.exe          ← 服务托管工具(后面用)
└── frpc.toml         ← 配置文件

5.1 下载 frpc

cd C:\
Invoke-WebRequest -Uri "https://github.com/fatedier/frp/releases/download/v0.61.2/frp_0.61.2_windows_amd64.zip" -OutFile frp.zip
Expand-Archive frp.zip -DestinationPath C:\frp
Rename-Item C:\frp\frp_0.61.2_windows_amd64 C:\frp\bin

国内网络直连 GitHub 大概率超时,先挂好代理再下载(或用镜像源)。下载完确认 C:\frp\bin\frpc.exe 存在;包里的 frps.exe 是服务端程序,Windows 上用不上,可删。

5.2 第一步:先确认远程桌面功能本身是开着的

实机(Windows 10 专业版)远程桌面默认是关闭的:注册表 fDenyTSConnections = 1,3389 无监听。如果跳过这步直接往下配,frpc 隧道打通了也连不上——这是最靠后才发现、排查最烦的一类问题,所以放在最前面。

# 图形界面方式:设置 → 系统 → 远程桌面 → 启用(专业版)

# 或注册表方式:
Set-ItemProperty "HKLM:\System\CurrentControlSet\Control\Terminal Server" -Name fDenyTSConnections -Value 0

# 同时开启 NLA(网络级别身份验证,强烈推荐)
Set-ItemProperty "HKLM:\System\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp" -Name UserAuthentication -Value 1

注意:用注册表方式开启后,GUI 里”远程桌面”开关同步生效,但防火墙规则要自己确认;GUI 开关则会自动创建。验证方式:Get-NetTCPConnection -LocalPort 3389 -State Listen 有输出即监听成功。建议先确认 3389 通了,再进入改端口的步骤——一次只改一个变量,排错面小。

5.3 修改 Windows RDP 监听端口(可选但推荐)

⚠️ 务必在电脑本机操作,不要用远程桌面连着改——改端口会重启远程桌面服务,远程连接会立刻断开。

# 1. 修改注册表(3389 → 33390)
reg add "HKLM\System\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp" /v PortNumber /t REG_DWORD /d 33390 /f

# 2. 验证
Get-ItemProperty "HKLM:\System\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp" -Name PortNumber
# 应输出:PortNumber : 33390

# 3. 防火墙放行新端口(见第七节:更推荐收成 LocalOnly 版本)
New-NetFirewallRule -DisplayName "RDP-33390" -Direction Inbound -Protocol TCP -LocalPort 33390 -Action Allow

# 4. 删除旧规则(按系统语言,规则名可能不同)
Remove-NetFirewallRule -DisplayName "远程桌面(TCP-In)" -ErrorAction SilentlyContinue
Remove-NetFirewallRule -DisplayName "Remote Desktop (TCP-In)" -ErrorAction SilentlyContinue

💥 实录大坑:改完端口执行 Restart-Service TermService -Force 后,服务状态显示 Running,但新端口不监听(3389 也没了)。改端口后的 RDP 监听器只有在服务真正冷启动时才会用新值重建,原地重启服务大概率不生效。实测结论:改端口这类操作,做完注册表直接重启电脑最稳。重启后再 netstat -ano | findstr 33390 确认监听,再继续后面的步骤。

端口选择避坑:

端口 是否可用 原因
3389 ❌ 默认端口,扫描器必扫
3390/33890/13389/23389 ⚠️ 热门”替代端口”,扫描器同样会试
49152–65535 ❌ Windows 动态端口范围,系统出站会占用,会随机冲突
20000–49151 内的随机值 ✅ 推荐

老实说:改端口只挡盲扫,挡不了定向扫描。真正的主力防线是后面的强密码 + NLA。别把安全权重放反了。

5.4 编写 frpc.toml

在 C:\frp\frpc.toml:

# ===== 服务器连接信息 =====
serverAddr = "你的服务器公网IP"
serverPort = 7000

# ===== 鉴权(必须与服务端一致) =====
auth.method = "token"
auth.token = "服务端生成的64位token"

# ===== 传输优化 =====
transport.tcpMux = true
transport.tcpMuxKeepaliveInterval = 30
transport.heartbeatInterval = 30
transport.heartbeatTimeout = 90

log.to = "C:\\frp\\frpc.log"
log.level = "info"
log.maxDays = 7

# ===== 本地面板(可选,查看代理状态) =====
webServer.addr = "127.0.0.1"
webServer.port = 7400
webServer.user = "admin"
webServer.password = "本地面板密码"

# ===== RDP 代理 =====
# localPort 必须与 Windows 注册表改的端口一致
[[proxies]]
name = "rdp"
type = "tcp"
localIP = "127.0.0.1"
localPort = 33390
remotePort = 33390

5.5 首次手动测试

先别急着注册服务,手动跑一次确认能连上:

cd C:\frp
.\bin\frpc.exe -c frpc.toml

成功输出类似:

[I] [client/service.go:295] try to connect to server...
[I] [client/service.go:287] login to server success, get run id [xxxx]
[I] [proxy/proxy_manager.go:173] [xxxx] proxy added: [rdp]
[I] [client/control.go:170] [xxxx] [rdp] start proxy success

看到 start proxy success 就成功了,按 Ctrl+C 停止。

常见失败原因:

报错 原因 / 解决
connection refused 服务器防火墙没放行 7000;或本地网络屏蔽了非标准端口
auth failed token 不匹配,重新核对
port not allowed 服务端 allowPorts 没包含你的 remotePort
connect to local 127.0.0.1:33390 failed Windows RDP 端口还没改好(或没重启),检查 5.2 / 5.3 步

5.6 注册为 Windows 服务(开机自启)

用 NSSM(Non-Sucking Service Manager)把 frpc 注册为服务,比计划任务稳定得多。下载:https://nssm.cc/release/nssm-2.24.zip,取 win64\nssm.exe 放到 C:\frp\。

cd C:\frp

# 注册服务
.\nssm.exe install frpc "C:\frp\bin\frpc.exe" "-c C:\frp\frpc.toml"
.\nssm.exe set frpc DisplayName "frp Client (RDP Tunnel)"
.\nssm.exe set frpc Start SERVICE_AUTO_START

# 日志轮转(防止日志无限增长)
.\nssm.exe set frpc AppStdout "C:\frp\frpc_stdout.log"
.\nssm.exe set frpc AppStderr "C:\frp\frpc_stderr.log"
.\nssm.exe set frpc AppRotateFiles 1
.\nssm.exe set frpc AppRotateBytes 1048576

# 启动
.\nssm.exe start frpc
.\nssm.exe status frpc          # 应为 SERVICE_RUNNING

5.7 重启电脑 + 端到端验证(部署自检清单)

所有配置就绪后重启一次电脑(顺带解决 5.3 的端口重建问题),frpc 是自动启动,开机后无需任何操作。然后过一遍自检清单:

# ① 本机端口监听:RDP(33390) / frpc面板(7400)
Get-NetTCPConnection -State Listen | Where-Object { $_.LocalPort -in 33390,7400 }

# ② frpc 服务状态 + 日志
Get-Service frpc                                   # 应为 Running
Get-Content C:\frp\frpc.log -Tail 10
# 应看到:login to server success + [rdp] start proxy success

# ③ 端到端回环测试:在本机连"服务器的代理端口"
#    流量会走完整隧道(服务器 → frpc → 回到本机),能 TCP 握手成功即全链路通
Test-NetConnection 你的服务器公网IP -Port 33390

# ④ 浏览器打开本地面板看代理在线状态:http://127.0.0.1:7400

四关全过 = 链路就绪,接下来只剩安全加固(第七节)。

六、连接测试

在外部网络(手机热点、公司电脑)打开远程桌面连接:

电脑:    你的服务器公网IP:33390
用户名:  Windows 登录用户名
密码:    Windows 登录密码

提醒:RDP 走的也是 Windows 账户验证,同样受账户锁定策略约束(见第七节)——外部输错密码的次数和坐在电脑前输错是同一个计数器。

卡顿优化:在远程桌面客户端的”显示”设置里,把颜色深度降到 16 位、关闭桌面背景和字体平滑,能显著降低带宽占用。

七、安全加固(RDP 暴露公网必做)

按实际防护力排序(这点很重要,别把精力放错地方):

🥇 第一优先级:强密码 + NLA

# 开启 NLA(网络级别身份验证)
sysdm.cpl → 远程 → 勾选"仅允许运行使用网络级别身份验证的远程桌面的计算机连接(推荐)"

NLA 不会影响 frp 工作——frp 只转发 TCP 字节流,不解析 RDP 协议,NLA 握手是「客户端 ↔ Windows」端到端进行的。所有现代 RDP 客户端都支持 NLA。

密码要求:≥12 位,含大小写 + 数字 + 符号。

🥈 第二优先级:账户锁定策略(含两个实测坑)

路径:secpol.msc → 账户策略 → 账户锁定策略。三个参数的含义:锁定阈值(连续错几次锁)、锁定时间(锁多久)、锁定计数器重置(多久没输错就清零重算)。

坑一:两个工具显示的值可能对不上。全新工作站上 secpol.msc 显示阈值”0 次无效登录”(未配置),而 net accounts 显示阈值 10。以 net accounts 的输出为准判断当前生效值。

坑二(更重要):管理员账户默认豁免锁定!策略列表里有一项「允许管理员帐户锁定」,默认禁用——不启用它,你平时用来登录的管理员账户无论输错多少次都不会被锁,整套策略对最常见的账户形同虚设。必须手动改成「已启用」。

参数怎么选:网传”5 次锁 30 分钟”偏严——自己远程时输错几次就真被锁住干瞪眼;设 0(禁用)则等于给公网爆破开门。本文最终采用折中参数,把爆破限速到约 1.4 万次/天(无实用价值),而正常使用几乎无感:

# 管理员 PowerShell(secpol.msc 改也行)
net accounts /lockoutthreshold:50     # 50 次无效登录触发锁定
net accounts /lockoutduration:5       # 锁定 5 分钟自动解锁
net accounts /lockoutwindow:15        # 15 分钟无失败则计数清零
# "允许管理员帐户锁定" 在 secpol.msc 中改为已启用

🥈 补充:两条密码策略命令

# 1. 强制密码最小长度(防止以后改密码手滑设弱)
net accounts /minpwlen:12

# 2. 检查并修复账户的"需要密码"标志
#    用 net user 用户名 查看输出,若"需要密码"显示 No,
#    说明账户标志位允许空密码(隐患),执行:
net user 用户名 /passwordreq:yes

💡 想知道有没有人敲门:事件查看器 → Windows 日志 → 安全,筛选事件 ID 4625(登录失败),可看到每次失败的来源和账户名。

🥉 第三优先级:专用账户,禁用 Administrator 远程登录

# 新建专用远程账户
$pwd = ConvertTo-SecureString "你的强密码" -AsPlainText -Force
New-LocalUser -Name "remote" -Password $pwd
Add-LocalGroupMember -Group "Remote Desktop Users" -Member "remote"

补充:Windows 防火墙限制来源 IP

既然只有 frpc 需要连 RDP,可以限制为仅允许本机访问:

Remove-NetFirewallRule -DisplayName "RDP-33390"
New-NetFirewallRule -DisplayName "RDP-33390-LocalOnly" -Direction Inbound -Protocol TCP -LocalPort 33390 -RemoteAddress 127.0.0.1 -Action Allow

这样连同一 WiFi 下的其他设备都摸不到你的 RDP。

安全措施真实性价比参考

措施 防护力 说明
强密码 + NLA ★★★★★ 挡住 99% 爆破,主力防线
token 不泄露 ★★★★★ frp 根本,泄露即全线崩溃
云平台防火墙最小化 ★★★★☆ 第一道关口
防火墙限制来源 IP ★★★☆☆ 成本低收益高
账户锁定 + 管理员锁定启用 ★★★☆☆ 限速爆破;注意管理员默认豁免,必须手动启用
改非标端口 ★★☆☆☆ 防盲扫,防不了定向
allowPorts 收紧 ★★☆☆☆ 仅 token 泄露时才有意义

八、日常运维

服务端(Linux)

sudo systemctl status frps
sudo systemctl restart frps
sudo tail -f /var/log/frps.log

# 通过 SSH 隧道访问面板(在你自己的电脑上执行)
ssh -L 7500:127.0.0.1:7500 ubuntu@你的服务器IP
# 然后浏览器打开 http://127.0.0.1:7500

客户端(Windows)

Get-Service frpc                      # 查看状态
Restart-Service frpc                  # 重启
Get-Content C:\frp\frpc.log -Wait -Tail 30   # 实时日志

# 卸载服务(如需重装)
cd C:\frp
.\nssm.exe stop frpc
.\nssm.exe remove frpc confirm

九、带宽与性能实测

frps 资源占用(实测):内存 17~20 MB;CPU 接近 0%;磁盘 <50 MB。

真正的瓶颈是带宽:frp 的所有流量都要经过服务器的公网带宽弯一下。无论家里宽带上行多快,外部访问速度上限 ≈ 服务器带宽。

场景 体验
SSH / 命令行 ✅ 流畅
RDP 远程桌面(1080P) ✅ 可用,建议降画质到 720P/16位色
大文件传输 ⚠️ 受服务器带宽限制
视频串流 / 游戏 ❌ 不适合

结论:frp 适合远程桌面和命令行这类低带宽场景。NAS 大文件传输或视频串流需要更高带宽的服务器,或改用支持 P2P 打洞的方案(如 Tailscale)。

十、排错清单

现象 排查方向
frpc 连不上服务器 服务器防火墙 7000 没放行;本地网络屏蔽非标端口
auth failed token 前后不一致(注意别复制时带空格)
port not allowed 服务端 allowPorts 未包含该端口
frpc 连上了但 RDP 连不上 Windows RDP 功能没开(5.2);端口没改对;改端口后只 Restart-Service 没重启电脑(监听器不会用新端口重建)
RDP 能连但黑屏 Windows 锁屏;NVIDIA 独显可能需要虚拟显示器驱动
重启后没自动连上 Get-Service frpc 看状态;查 C:\frp\frpc_stdout.log
连接频繁断开 检查 heartbeatInterval/tcpKeepalive;家用 NAT 超时可能需调小
锁定策略改了不生效 ① 用 net accounts 看生效值(secpol 未配置时显示 0)② 管理员账户默认豁免锁定,需启用「允许管理员帐户锁定」

十一、总结

整个部署的核心步骤:

  1. 服务端:放行端口 → 装 frps → 写 frps.toml(token + 精确白名单 + 本地面板)→ systemd 托管
  2. 客户端:开 RDP + NLA → 改端口(改完重启电脑)→ 写 frpc.toml → NSSM 注册服务 → 端到端自检
  3. 安全:强密码 + NLA + 锁定策略(记得启用管理员锁定)+ minpwlen=12 + 专用账户——这几项比改端口重要得多
  4. 验证:本机端口监听(33390/7400)→ frpc 日志 [rdp] start proxy success → 回环测试 → 外部真连

资源开销几乎可以忽略(20MB 内存),真正的限制是服务器带宽。只要需求是远程桌面这类低带宽场景,这套方案稳定且完全自主可控。

本文基于 frp v0.61.2 实测部署整理,服务端与客户端流程均已实机全流程跑通。

本地部署 ComfyUI + Flux.2 生图:从驱动冲突排查到 SageAttention 编译加速实录

最近需要长期做企业 LOGO 与品牌图形的自用生成,考虑到素材的保密性和出图的自由度,决定在本地部署一套 ComfyUI + Flux.2 的生图环境。整个过程走下来,最大的收获不是“装好了”,而是排掉了两个非常有代表性的坑:一个是 CUDA 启动即崩溃的驱动级冲突,另一个是 Windows 上源码编译 CUDA 扩展的连环兼容性问题。这篇文章按“部署 → 排障 → 加速 → 应用”的顺序完整记录,给同样在 Windows 上折腾本地生图的朋友做个参考。

一、硬件与选型

  • CPU:AMD Ryzen 9 9950X3D
  • GPU:NVIDIA GeForce RTX 5090(32GB 显存)
  • 系统:Windows 10 22H2
  • 生图框架:ComfyUI 便携版 + Flux.2 模型族

Flux.2 是当前质量与可控性平衡很好的开源生图模型族,在 5090 上整个谱系都能跑。我的实际组合:

用途 模型 说明
日常主力 Flux.2 klein 9B(FP8) 速度与质量均衡,出草稿和正稿都够用
质量上限 Flux.2 dev(Q4 GGUF) 精修时用,细节更足
快速草稿 Flux.2 klein 4B(蒸馏) 多方案发散时快速过
文本编码器 Qwen3-8B klein 9B 的配套编码器,注意 4B 模型要配 4B 编码器
VAE flux2-vae 全家通用

另外配套部署了 BiRefNet(抠图透明底)和 4x-UltraSharp(放大导出),形成一条“发散 → 精修 → 抠图 → 放大”的完整流水线。

二、第一个大坑:ComfyUI 启动即崩溃(0xC0000005)

装好之后启动 ComfyUI,进程秒崩,错误是经典的 nvcuda64.dll 0xC0000005(访问冲突)。一开始按常规思路排查:

  1. 换 PyTorch 版本:2.13、2.11、2.9.1 三版全试,全崩。
  2. 换显卡驱动:从旧驱动升到最新 WHQL,重启后依然崩。
  3. 查 DLL 完整性:签名、哈希全部正常。
  4. 看显存:nvidia-smi 显示 32GB 显存只剩 13MB、BAR1 只剩 29MB,差点以为显存爆了。

前三步全部排除后,偶然发现 nvidia-smi 的显存占用与 Windows 系统计数器严重不一致——系统权威计数器显示 5090 只用了约 3.8GB。查证后确认:这是 WDDM 模式下 nvidia-smi 的已知误报,显存根本不是瓶颈。

于是转向多显示适配器枚举冲突排查:临时禁用 AMD 核显,仍然崩;最后抱着试试的心态禁用了向日葵远程控制软件的虚拟显示器驱动,cuInit 立即返回成功,恢复后立刻崩。实锤:向日葵虚拟显示器驱动与 CUDA 初始化存在冲突。

解决方案

不能永久禁用(远程开机和控制是刚需),所以做了一套“运行时切换”机制:

  • 两个免 UAC 的计划任务:一个禁用虚拟显示器驱动,一个恢复;
  • 一个一键启动脚本:启动前禁用驱动 → 拉起 ComfyUI → 退出后恢复驱动。

平时远程不受影响,只有生图会话期间短暂切走,实测稳定。另外补装了与 PyTorch 匹配的 torchvision / torchaudio 版本,三者版本对齐后 CUDA 全链路正常。

三、第二个大坑:Windows 源码编译 SageAttention

ComfyUI 支持 --use-sage-attention 加速,但 PyPI 上的 SageAttention 旧版不支持新架构,GitHub Releases 又没有 Windows 预编译包,只能源码编译。这一路踩了四个连环坑:

  1. 缺 Python.h:ComfyUI 的便携版 Python 是精简的,没有开发头文件。解决:从 Python 官方 NuGet 包提取 include/libs 补齐。
  2. 编译期 torch import 崩溃:又是虚拟显示器驱动在干扰 CUDA 初始化,编译期间临时禁用即解决。
  3. C2872:std 符号不明确:这是 PyTorch 2.9 的 compiled_autograd.h 在 nvcc 下的已知兼容问题,网上有完全同款的案例(RTX 50 系 + Windows 编译 CUDA 扩展必踩)。关键解法是给 nvcc 加 -DUSE_CUDA,触发 PyTorch 头文件内置的 Windows 保护分支,绕开冲突代码段。
  4. C2429:嵌套命名空间需要 C++17:nvcc 编译 .cu 时默认不会把 C++17 传给 MSVC,需要显式加 -Xcompiler /std:c++17。

编译完成后还发现 Windows 版 PyTorch 不带 triton,而 SageAttention 硬依赖它,补装社区维护的 triton-windows 解决。

验证效果:ComfyUI 启动日志出现 Using sage attention,Flux.2 klein 9B 生成 768×768 图、20 步采样全流程约 22 秒,加速生效。

四、应用:YoungSonny 企业 LOGO 生成

部署完成后用它跑了几轮品牌 LOGO 设计。基于“YS 字母组合 + 电路板纹路 + 深蓝/青蓝科技配色”的方向,一次性发散了六组候选:经典双色版、深色反白版、极简单色版、渐变科技版、圆形徽章版、电路线框版。

全部为 1024×1024 高清输出,逐张检查无任何平台水印与多余元素——这正是本地部署的核心价值:素材完全本地生成、无平台痕迹、可无限迭代。

五、总结

  • 遇到 CUDA 启动崩溃,先查设备枚举层面的冲突(虚拟显示器、采集卡等),比反复换驱动/换 torch 版本高效得多;
  • WDDM 下 nvidia-smi 的显存读数可能误导排障,以系统权威计数器为准;
  • torch 2.9 + nvcc 编译扩展时,-DUSE_CUDA 与 -Xcompiler /std:c++17 是绕过 Windows 兼容问题的高频组合;
  • 本地生图的核心收益不是省钱,而是可控、可复现、无平台水印,对品牌素材类需求尤其重要。

欢迎交流,后续会继续更新 Flux.2 工作流与 LOGO 精修方向的实践。

给博客加上全站搜索:方案设计、REST API 实现与两次部署事故实录

起因

这个博客用 WordPress 搭的,主题是一款经典主题(Arke)。文章还不算多,但想找一篇旧文越来越费劲:原生 ?s= 搜索只做标题和内容的模糊匹配,不支持按分类缩小范围,没有关键词高亮,主题本身也没给搜索一个像样的入口。于是给自己提了个需求:

给博客加一个文章检索功能:能按标题、内容、分类搜,界面干净美观,别给站点增加负担和依赖。

方案选型

老规矩,先摆选项再动手:

方案 思路 评估
A · 换主题 / 装搜索插件 用自带现代搜索的主题,或上 SearchWP 之类插件 见效最快,但要动整站外观;好用的搜索插件按年收费,为一个功能不值
B · 前端轻量定制 + REST API 主题不动,注入一段全站前端脚本,数据走 WordPress 自带的 REST API 改动小、可随时摘除、体验完全自主可控,最终选用
C · 独立全文搜索引擎 Meilisearch / Algolia 建索引 对个人博客的量级是杀鸡用牛刀,运维成本不划算

选 B,内部代号 YSS Search。核心思路一句话:WordPress 只当数据源(REST API 本来就在),所有检索体验放在前端完成。

功能设计

两个入口,一套体验

  • 导航搜索胶囊:往导航栏注入一枚白底圆角胶囊——放大镜 + 「搜索…」占位文字 + Ctrl K 键位帽,窄屏下自动收缩成纯图标;
  • 首页内联过滤框:首页文章列表上方再放一个过滤框,输入即筛,适合「就在首页扫一眼」的场景。

检索链路

  • 输入 250ms 防抖后请求 /wp-json/wp/v2/posts?search=…&categories=…,REST 的 search 参数本身就覆盖标题 + 内容;
  • 客户端按权重重排:标题命中 ×3、摘要 ×2、分类 ×2、正文 ×1,比 REST 默认的相关性排序更贴合直觉;分类名也并入匹配池,所以搜「网络技术」能直接命中该分类的文章;
  • 面板顶部分类 chips 从 /wp-json/wp/v2/categories 动态拉取,点一下就带着分类参数重新检索;
  • 命中词 <mark> 高亮;支持相关性 / 最新两种排序,↑↓ 选择、Enter 打开、Esc 关闭;
  • 降级兜底:REST 不可用时自动跳转原生 ?s= 搜索——功能可以变笨,但不能坏。
另外带了全局快捷键:Ctrl+K 或 / 随时呼出面板,移动端自动切全屏。整个功能是一个自包含的 HTML 片段(CSS + JS 约 500 行),不依赖任何外部库。

部署的现实:两个「没想到」

没想到一:搜索插件没有 REST API

代码注入用的是 WPCode 这款插件,本想全程用 REST API 自动化部署,结果发现 WPCode Lite 的 REST 端点全是 404——它自定义的内容类型压根没对 REST 开放。能用的只有它自带的「页头/页脚编辑器」表单。

于是把部署流程固化成一个幂等脚本,本质是模拟浏览器走表单:

  1. 登录 wp-admin(浏览器自动化完成,会话导出供脚本复用);
  2. GET 编辑器页面,取表单 nonce 和三个输入框的现值;
  3. 本地把新代码与旧内容做幂等合并(只替换自己的标记块,不碰别人注入的内容);
  4. POST 保存 → 回读校验 → 再抓线上页面验证。

没想到二:SVG 图标渲染成小黑点

上线第一版后,导航角落的搜索图标缩成了一个小黑点。原因很初学:手绘的放大镜 SVG 只有 circle 和 path,没写 fill="none" 和 stroke 样式——浏览器默认填充黑色,15px 尺寸下线稿直接糊成一坨。教训:手写 SVG 图标,填充和描边必须显式声明。

事故主角:$& 替换事故

这次最精彩的坑,值得单独一节。

部署脚本更新代码的方式最初是「删旧块、插新块」,用了正则替换:

const merged = oldContent.replace(
  /<!-- MARK-START -->[\s\S]*?<!-- MARK-END -->/,
  newCode   // ← 问题在这里
);

而新代码里恰好有一段正则转义的工具函数:

function escReg(s) {
  return String(s).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}

'\\$&' 是 JS 正则转义的惯用写法(把特殊字符替换成「反斜杠 + 自身」)。但当整段代码作为 replace 的替换字符串时,$& 是特殊序列,含义是「插入整个匹配」——于是旧版代码被原样插回了新代码内部,形成 46KB 的「块中块」,页面里躺着两份搜索脚本。

第一次修复,留了个尾巴

改成「成对标记删除」:找到开始标记,删到其后最近的结束标记。重新部署后体积仍有 40KB——比预期多了 15.7KB,直觉不对。

挖下去才发现:旧块当初嵌在新代码内部,成对删除删到的是旧块的结束标记,新代码的后半截被留下了——一段孤立的 </script> 加 15.7KB 裸 JS 文本。这些文本落在 <head> 里,浏览器会提前结束 head、隐式打开 body,把 JS 代码当正文渲染出来。

最终方案

// a) 循环删除所有成对标记块
while ((i = s.indexOf(START)) !== -1) {
  const j = s.indexOf(END, i);
  s = j > -1 ? s.slice(0, i) + s.slice(j + END.length) : s.slice(0, i);
}
// b) 兜底删除「无头残尾」:只有结束标记、没有开始标记的残留
while ((j = s.indexOf(END)) !== -1) {
  s = s.slice(j + END.length);
}
// c) 三处硬校验:合并结果、表单回读、线上 HTML,
//    开始/结束标记都要「各恰好 1 份」——只查开始标记检不出残尾

三条教训

  1. 把代码文本传给 String.replace 第二个参数之前,先想想里面有没有 $&、$1、$` 这些特殊序列。要么用函数形式 () => code,要么用 indexOf 手动拼接;
  2. 幂等脚本的完整性校验,开合标记都要断言数量,最好再配体积、行数这类硬指标;
  3. 修完 bug 用「总量守恒」复核:删了 A、加了 B,最终应该恰好是 B,而不是「A 的一部分 + B」。

最终效果

现在导航栏是一枚搜索胶囊,点开或 Ctrl+K 呼出全局面板:搜索框、分类 chips、排序切换、高亮列表、键盘导航一应俱全;首页文章列表上方有内联过滤框,输入即筛。整个功能零外部依赖、零后端改动,随时可以整块摘除。

回头再可以打磨的方向:把搜索词带进文章页做落地高亮、热搜词统计、拼音首字母匹配。不过对目前的文章量来说,先把「找得到」这件事做扎实就够了。

小结

这次改造最大的收获不是搜索功能本身,而是三条工程习惯:方案上,能不动后端就不动后端,把 WordPress 当数据源用;实现上,外部依赖越少越好,一个自包含片段胜过一串插件;部署上,自动化脚本必须幂等,而幂等的可信度来自严格的机器校验,不是「应该没问题」。

AI 机器人代发的邮件进 139 垃圾箱?一次 EX-QQ-AI-BOTID 指纹定位与 SMTP 直发对照实验

上一篇《企业邮箱发 139 邮箱总进垃圾箱:一次 SPF / DKIM / DMARC 排查实录》解决了 DNS 认证缺项,邮件恢复正常进收件箱,我以为这事就完结了。结果当天晚上,同一套企业邮箱、同一个收件人、三项认证齐全,测试邮件又进了 139 的垃圾箱——区别只有一个:这封信不是我自己点的发送,而是让 AI 助手(企业微信机器人)代发的。这篇记录怎么用三封对照邮件把问题钉死,以及顺手解掉的另一个谜:为什么自己的邮件会被 139 标注”来自境外”。

先说结论

  1. 机器人代发指纹是进垃圾箱的决定性因素。企业微信接口代发的邮件,源码里带有 EX-QQ-AI-BOTID 头,且 From 显示名是机器人名——收件方反垃圾引擎据此加权,直接进垃圾箱。
  2. 发件地址用主地址还是别名,对落点没有影响。两条地址走 SMTP 直发都进了收件箱。
  3. SMTP 直发的邮件会被腾讯调度到海外网关投出,因此被 139 标注”来自境外”——但实测垃圾分为 0、正常进收件箱,标签没有实际负面影响。

现象:同一邮箱,三种发法,三种待遇

域名是同一个 *.site 子域,SPF / DKIM / DMARC 三项认证前一天刚全部补齐并验证生效,收件人都是我自己的 139 邮箱:

# 发件地址 发信通道 发件人显示名 139 落点
1 别名地址 企业微信机器人接口代发 机器人名(YSBot) ❌ 垃圾箱
2 主地址 SMTP 直连 本人名字 ✅ 收件箱
3 别名地址 SMTP 直连 本人名字 ✅ 收件箱

三封信主题相近、正文长短相近、发送时间在一小时内。变量只剩两个:发信通道和发件地址。对照实验正好能把它们分开。

定位:邮件源码里写着答案

在 139 邮箱网页版打开进垃圾箱的那封 →「更多」→「查看邮件源码」。这里有个小坑:139 的 RichMail 系统通常不回注 Authentication-Results 行,上一篇提到的”看认证结果”技巧在 139 侧经常派不上用场。但没有关系,源码里的另外几个头部足够说明问题:

X-RM-SPAM-FLAG:00000005          ← 139 反垃圾引擎的垃圾判定,非 0 即被判了
EX-QQ-AI-BOTID: 33777000132280559 ← 腾讯给这封信打上的"AI 机器人发送"标记
From: "YSBot" <you@mail.example.com>  ← 发件人显示名是机器人名
DKIM-Signature: v=1; a=rsa-sha256; d=mail.example.com; s=qfog2609; ...
                                  ← 签名正常,通道认证没问题

判读思路:

  • X-RM-SPAM-FLAG:00000005 说明垃圾判定是 139 自己的反垃圾引擎打出来的;
  • EX-QQ-AI-BOTID + 机器人显示名,等于明明白白告诉收件方”这是机器批量发的”——个人邮箱收到一封显示名为机器人的来信,像垃圾邮件几乎是合理怀疑;
  • DKIM 签名的 d=、s= 与后台配置完全一致,说明 DNS 认证链路完好,问题不在”谁发的”认证上,而在”怎么发的”方式上。

关键是:企业微信的邮件发送接口没有任何参数可以去掉这些标记——EX-QQ-AI-BOTID 是接口代发的固有指纹,显示名取自机器人配置。想不带指纹,只能换通道。

解决:绕开接口,SMTP 直连

腾讯企业邮箱本身提供标准 SMTP 服务,直连发送的邮件没有机器人指纹,而且发件地址、显示名完全由自己指定——顺便解决了另一个痛点:企业微信接口代发时 From 自动取邮箱的”默认发信地址”,想用主地址还是别名,得去网页版手动切换;SMTP 直发想用哪个就用哪个。

前提准备(一次性):

  1. 邮箱网页版「设置 → 收发信设置」开启 IMAP/SMTP 服务(服务器 smtp.exmail.qq.com,SSL 端口 465);
  2. 「设置 → 账户 → 账户安全」生成客户端专用密码(安全密码)。它是独立于登录密码的专用凭据,泄露了可以随时单独重置;不要把登录密码写进脚本。

发送端用 Python 标准库就够了,不需要任何第三方依赖:

import smtplib
from email.mime.text import MIMEText
from email.header import Header
from email.utils import formataddr

msg = MIMEText(open("body.txt", encoding="utf-8").read(), "plain", "utf-8")
msg["Subject"] = Header("测试邮件", "utf-8")
msg["From"] = formataddr((str(Header("你的名字", "utf-8")), "you@example.com"))
msg["To"] = "18xxxxxxxxx@139.com"

with smtplib.SMTP_SSL("smtp.exmail.qq.com", 465, timeout=30) as s:
    s.login("you@example.com", "客户端专用密码")   # 密码放本地配置文件,别提交进仓库
    s.sendmail("you@example.com", ["18xxxxxxxxx@139.com"], msg.as_string())

发完做对照实验,三封信的落点就是开头那张表:SMTP 直发的两封(主地址、别名)全部进收件箱,接口代发的那封进垃圾箱。变量分离干净利落。

彩蛋:为什么 SMTP 直发的信被标”来自境外”

SMTP 直发的两封虽然进了收件箱,列表里却被 139 标注了”来自境外”,而机器人代发那封反而没有。继续看源码最后一跳:

# SMTP 直发(标"来自境外"):
Received: from smtpbguseast1.qq.com (unknown[54.204.34.129])
    by rmmx-lg_mx13-12072 (RichMail) with SMTP id ...

# 机器人接口代发(不标):
Received: from bg5.exmail.qq.com (unknown[43.154.155.102])
    by rmmx-lg_mx18-12111 (RichMail) with SMTP id ...

54.204.x.x 是 AWS 美东(弗吉尼亚)网段,主机名里的 useast1 就是 US-East-1——腾讯把 SMTP 提交的邮件调度到了海外边界网关投出,139 按 IP 归属地打了标签;接口代发走内部队列,从国内出口集群投出,所以没标签。这个调度由腾讯内部决定,客户端无法指定。

要不要紧?看数据:境外出口那封 X-RM-SPAM-FLAG:00000000——垃圾分是 0,比代发那封的 00000005 还干净,且正常进收件箱。所以这个标签目前纯属观感问题,接受现状即可,后续留意它是否开始影响落点就行。

排查速查表

现象 结论与动作
认证三项齐全,仍进垃圾箱,且 From 显示名是机器人名 查源码有无 EX-QQ-AI-BOTID 等代发标记;换 SMTP 直连发送
139 源码里找不到 Authentication-Results 正常,RichMail 不回注;改看 X-RM-SPAM-FLAG 和 DKIM-Signature 的 d=/s=
想按次指定发件地址(主地址/别名) 企微接口无 from 参数;SMTP 直发可任意指定自有地址和显示名
SMTP 直发的邮件被标”来自境外” 腾讯出口网关调度所致(实测 AWS US-East-1),垃圾分不受影响,可接受
子域发信地址是不是必须改绑主域 子域确属减分项,但实测 SMTP 直发进收件箱;先跑起来观察,不急着动 DNS

写在最后

上一篇把”DNS 认证”这层确定性问题解决后,我以为邮件投递的坑已经见底,结果”发信通道”这个更隐蔽的变量紧跟着浮了出来——同一个邮箱,换个发送方式,命运截然不同。AI 助手代发确实方便,但收件方的反垃圾系统认的是种种”像人”的信号:显示名、发送指纹、历史行为。让邮件看起来是”你”发的,而不只是”以你的地址”发的,这件事在接口代发场景下做不到,那就换一条做得到的路。

方法论上,这次再次验证了两把刀的好用:对照实验(控制变量,一封封发)和邮件源码(所有结论都要落到具体头部字段上)。玄学问题的答案,通常就写在 header 里。

企业邮箱发 139 邮箱总进垃圾箱:一次 SPF / DKIM / DMARC 排查实录

用自有域名搭企业邮箱之后,遇到的最烦人的一件事就是:给部分收件人发信明明”发送成功”,对方却在垃圾邮件箱里才找到你的邮件。我这次的场景是从企业微信邮箱发往139 邮箱(中国移动),配好域名解析的第一周,测试邮件几乎全军覆没进垃圾箱。下面把完整的排查过程记录下来。

先说结论

  1. SPF、DKIM、DMARC 三项认证里缺了 DKIM 和 DMARC,是进垃圾箱的直接原因。补全后当天恢复正常投递。
  2. 配置修好只是及格线。新域名、.site 这类廉价后缀、以及 mail. 子域地址,都会让域名信誉长期吃亏,需要时间养。

第一步:别猜内容,先查发信认证三件套

邮件被判垃圾时,第一反应通常是”标题里是不是有营销词””是不是带链接了”。但内容维度非常玄学,而发信认证是确定性的——收件方服务器一定会查 DNS,查不到就打问号。所以先查这三项:

认证 作用 查询的 DNS 名称
SPF 声明哪些服务器有权用你的域名发信 <域名> 的 TXT 记录
DKIM 给邮件加不可伪造的数字签名 <selector>._domainkey.<域名> 的 TXT 记录
DMARC 告诉收件方,认证失败时怎么处理,并接收报告 _dmarc.<域名> 的 TXT 记录

Windows 上查 DNS,建议用 PowerShell 的 Resolve-DnsName 而不是 nslookup——后者有时只返回 SOA 段,看不到实际的记录条目:

# MX:定位收信服务器
Resolve-DnsName mail.example.com -Type MX -Server 119.29.29.29

# SPF
Resolve-DnsName mail.example.com -Type TXT -Server 119.29.29.29

# DMARC
Resolve-DnsName _dmarc.mail.example.com -Type TXT -Server 119.29.29.29

# DKIM(selector 由邮件服务商生成,形如 xxxx._domainkey,以面板显示为准)
Resolve-DnsName xxxx._domainkey.mail.example.com -Type TXT -Server 119.29.29.29

建议多找几家 DNS 交叉验证:119.29.29.29(DNSPod)、223.5.5.5(阿里)、114.114.114.114,以及域名自己的权威 NS——直接问权威服务器可以完全排除缓存干扰,这是我这次能快速定位问题的关键手段。

我当时的查询结果是:MX、SPF 都在,DKIM 和 DMARC 完全缺失。三项里缺两项,进垃圾箱是必然的。

第二步:补 DKIM 和 DMARC(含企业微信的具体路径)

要注意的是,企业微信版的企业邮箱,管理入口和独立版腾讯企业邮(exmail.qq.com)完全不一样。DKIM 不在「邮箱域名」页,而是在这里:

企业微信管理后台 → 协作 → 邮件 → 安全管理 → DKIM 验证 → 配置

点「配置」后系统会生成一条记录,复制它的主机记录(形如 xxxx._domainkey)和记录值(形如 v=DKIM1; k=rsa; p=MIGf...),到 DNS 服务商处添加 TXT 记录,再回后台点「已完成配置,立即验证」。

DMARC 则是一条固定写法的 TXT 记录。rua 参数指定聚合报告的接收地址,腾讯官方建议填它自己的报告邮箱,这样报告汇总给腾讯做投递质量监测,自己不会被一堆 XML 附件刷屏:

主机记录 类型 记录值
@ MX mxbiz1.qq.com(优先级 5)、mxbiz2.qq.com(优先级 10)
@ TXT v=spf1 include:spf.mail.qq.com -all
_dmarc TXT v=DMARC1; p=none; rua=mailto:mailauth-reports@qq.com
服务商生成的 selector TXT v=DKIM1; k=rsa; p=...(在后台生成后复制)

DMARC 的 p 参数建议先用 p=none(只监测、不拦截),观察一两周投递稳定后,再逐步收紧到 quarantine(标记垃圾)或 reject(直接拒收)。

三条录入注意点:

  • 主机记录里的 . 和 _ 必须是英文半角。全角符号在界面里和半角几乎看不出差别(浏览器里 . 和 . 长得太像了),但发布出去的域名就是错的。
  • 记录值不能有多余空格,字母大小写不要改。建议先在记事本里中转一遍再粘贴,避免网页复制带进格式字符。
  • DKIM 的 selector 由服务商随机生成,以面板里当前显示的为准。核对 DNS 记录时,要用这条记录的主机记录原文去查,而不是凭记忆或早先的截图。

第三步:如果配置全对还是进垃圾箱,那是信誉问题

补全 DKIM + DMARC 后,我的邮件当天就正常进收件箱了。但如果你三项认证全 pass 仍被归类垃圾,那就不是配置问题,而是域名信誉问题,它靠配置解决不了,只能靠时间:

  • 域名越新越吃亏。零发信历史的新域名突然开始发信,任何反垃圾系统都会先观望。
  • 廉价后缀默认信誉低。.site、.xyz、.top 这类后缀被垃圾邮件滥用得厉害,初始信任度天然低于 .com、.cn。
  • 子域发信地址是明显的减分项。形如 xxx@mail.yourdomain.com 的地址会降低信任度。如果域名解析是在子域上建的(比如 DNS 服务商里添加的”域名”本身就是 mail.example.com),而邮箱也绑定在这个子域上,长期最优解是把邮箱改绑到主域,去掉 mail. 前缀。
  • 养号节奏很重要。配置修好后 2 到 4 周内,只做一对一的真人通信:发给真实联系人,对方打开、回复,并顺手点一次”不是垃圾邮件”。这段时间绝对不要群发任何通知或营销邮件,否则信誉会被重新打回原点。

排查速查表

现象 结论与动作
邮件能发出但进垃圾箱 先查 SPF / DKIM / DMARC 三项 DNS 记录,缺哪项补哪项
DKIM 记录查不到 用 DNS 面板里那条记录的主机记录原文再查,确认 selector 名称、记录值是否被截断或含空格
nslookup 只返回 SOA、看不到记录 Windows nslookup 的行为问题,改用 Resolve-DnsName 或直连权威 NS
三项全 pass,仍进垃圾箱 域名信誉问题(新域名、廉价后缀、子域地址),需 2~4 周低频真人通信养号
只有部分收件人收到垃圾箱 对方账号曾举报过该域名,或对方反垃圾等级设置更严;可请对方加入白名单
SPF 结尾该用 ~all 还是 -all 都能通过校验,-all 更严格;没有第三方代发需求时优先 -all

写在最后

这次排查最大的感受是:邮件投递问题看起来玄学,其实完全可以按确定性证据一步步排除。DNS 记录查得到查不到、邮件原文里的认证结果是什么,这些都是非黑即白的事实;把这几项事实摆清楚,剩下的才轮到”内容是不是太像营销邮件””域名信誉够不够”这类软性因素。

顺带一个实用技巧:收件方邮箱里查看邮件源码(139 邮箱在”更多 → 查看邮件源码”)里的 Authentication-Results 行,会直接给出 spf= / dkim= / dmarc= 三项的实际校验结果。这是判断问题卡在哪一项最快的方法,比反复猜 DNS 高效得多。

顶部