本文速览

先辨认收到的是订阅地址、编码后的链接列表,还是完整的内核配置,再选择对应入口。本文适合遇到“订阅导入为空”“JSON 无法识别”“转换后丢失设置”的用户:从 VMess 与 VLESS 字段开始,演示本地 Base64 转换、客户端导入及 Xray 配置映射,并说明哪些内容无法随分享链接保留。

一、订阅地址、Base64 与 JSON 的层级区别

“V2Ray 订阅”不是一种唯一的文件格式。日常收到的 HTTPS 地址只是获取入口;客户端访问它之后,才得到实际响应。响应可能是每行一条的分享链接,也可能是把整个链接列表做了一次 Base64 编码的文本。某些服务还会根据请求头返回不同内容,因此不能只看地址后缀判断格式。

对象识别特征适合的处理方式
订阅地址通常以 https:// 开头,可能带访问令牌填入订阅分组,按需更新
明文链接列表每行以 vmess:// 或 vless:// 等协议头开头从剪贴板批量导入
Base64 订阅正文通常是一长段编码字符,解码后出现分享链接由订阅解析器处理,或先在本地解码
VMess 链接内部 JSON常见字段有 v、add、id、net用于生成或解析单条 VMess 分享链接
Xray 原生配置常见顶层字段有 inbounds、outbounds、routing交给对应内核,或通过自定义配置入口加载

两个对象即使都是 JSON,也不代表能够互换。VMess 分享对象描述一台服务器及其传输参数;Xray 配置描述内核如何监听本地端口、建立出站以及执行路由。把前者保存成 config.json,不会自动变成后者;把后者整体做 Base64,也不会自动成为客户端支持的订阅。

二、VMess 与 VLESS 分享链接的字段结构

常见的 VMess 分享格式是 vmess:// 加上一段 Base64,解码结果为 JSON。下面仅展示该 JSON 的内容,所有域名和 UUID 都是教学用虚构数据,不能用于连接。v: "2" 是这种分享对象的格式标记,不是客户端版本,也不是内核版本。

{
  "v": "2",
  "ps": "示例 VMess WS",
  "add": "edge.example.com",
  "port": "443",
  "id": "00000000-0000-4000-8000-000000000001",
  "aid": "0",
  "scy": "auto",
  "net": "ws",
  "type": "none",
  "host": "edge.example.com",
  "path": "/ws",
  "tls": "tls",
  "sni": "edge.example.com"
}

add 是连接地址,port 是远端端口,id 是用户标识;net、host、path 分别参与传输方式及 WebSocket 请求设置。aid: "0" 对应当前常见 VMess AEAD 配置,不应为了消除报错而任意改成其他数字。示例中的 type: "none" 也不能当成关闭 TLS 的开关。

VLESS 分享链接通常直接使用 URI 结构,身份信息放在 @ 前,服务器与端口放在其后,传输参数写在查询串中。下面同样是不可连接的示例,链接应保持为完整的一行:

vless://[email protected]:443?encryption=none&security=tls&type=tcp&sni=edge.example.com#demo-vless

VMess 示例字段

地址与端口
add 与 port
身份标识
id
传输方式
net: "ws"
请求路径
path: "/ws"
TLS 名称
sni

这些字段属于常见分享对象,映射到内核配置时键名和层级会变化。

VLESS 示例参数

用户标识
URI 中 @ 之前的 UUID
协议加密
encryption=none
连接安全
security=tls
传输方式
type=tcp
TLS 名称
sni=edge.example.com

encryption=none 不等于禁用 TLS;两者位于不同层级。

URI 参数值需要按 URL 规则编码。例如路径 /ws?ed=2048 作为一个参数值时,应写成 path=%2Fws%3Fed%3D2048,避免其中的问号被误解为另一层查询串。备注中的中文、空格和特殊符号也应正确编码。不要对整条 URI 一次性做百分号编码,否则协议头与分隔符也会被破坏。

三、Base64 订阅的本地解码与重新编码

典型转换链条是“多条分享链接 → 用换行符连接 → 对整个文本做 Base64”。如果其中包含 VMess,就会出现两层编码:外层包住列表,内层包住单条 VMess 的 JSON。外层解开后已经出现 vmess:// 时,应先按行拆分,而不是把带协议头的整段内容继续当成 Base64。

3 → 4
Base64 每 3 字节对应 4 个字符,不计分隔换行
443
本文示例的远端 TLS 端口,不代表所有节点
10808
后文配置的本地 SOCKS 端口,需确认未被占用

以下原生 JavaScript 可在本地空白页的开发者工具控制台中运行。先阅读代码,只把自己的数据放入本地变量;它不发起网络请求。编码函数使用 UTF-8,避免中文备注直接传给 btoa() 时出错;解码函数同时处理标准 Base64 与常见的 URL 安全字符变体。

function encodeSubscription(text) {
  const bytes = new TextEncoder().encode(text);
  let binary = "";
  for (const byte of bytes) {
    binary += String.fromCharCode(byte);
  }
  return btoa(binary);
}

function decodeSubscription(text) {
  let encoded = text.trim()
    .replace(/\s+/g, "")
    .replace(/-/g, "+")
    .replace(/_/g, "/");

  if (!/^[A-Za-z0-9+/]*={0,2}$/.test(encoded)) {
    throw new Error("内容不是有效的 Base64 字符序列");
  }

  encoded = encoded.replace(/=+$/, "");
  if (encoded.length % 4 === 1) {
    throw new Error("Base64 长度异常,请检查是否复制完整");
  }

  encoded += "=".repeat((4 - encoded.length % 4) % 4);
  const bytes = Uint8Array.from(atob(encoded), char => char.charCodeAt(0));
  return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
}

const demoLink =
  "vless://[email protected]:443"
  + "?encryption=none&security=tls&type=tcp&sni=edge.example.com#demo-vless";

const encodedDemo = encodeSubscription(demoLink);
console.log(encodedDemo);
console.log(decodeSubscription(encodedDemo));

四、v2rayN 与 v2rayNG 的导入入口

选择入口的依据是“手上有什么”。订阅地址放进订阅分组,单条或多条分享链接走剪贴板,完整内核 JSON 走自定义配置。桌面版与安卓端的菜单位置会随版本调整,下列使用常见中文菜单名称;若界面文字略有差异,应以对应的导入功能为准,不要把订阅地址误填进服务器地址栏。

v2rayN:先建分组,再更新订阅

  1. 创建分组

    在主窗口打开「订阅分组」→「订阅分组设置」,使用「添加」建立分组,填写备注和订阅地址,保存后返回服务器列表。

  2. 获取内容

    在「订阅分组」菜单中选择「更新当前订阅(不通过代理)」或对应的通过代理更新项。当前网络能够直达订阅服务时先用前者;后者需要已有可用代理及正确的代理通路。

  3. 导入链接

    如果只有分享文本,复制完整链接,在「服务器」→「从剪贴板导入批量 URL」执行导入;常见桌面界面也可使用 Ctrl+V。此方式添加的是节点快照,不会建立远程订阅更新关系。

  4. 核对参数

    打开导入条目的编辑窗口,检查服务器地址、端口、传输协议、TLS 与 SNI。只有列表中出现条目还不够,应启动该条目并检查底部核心日志是否出现配置解析错误。

v2rayNG:订阅与剪贴板分别处理

如果把订阅解码后手工导入,后续服务端改了地址、端口或身份信息,本地快照不会自动跟着变。需要持续更新时保留原订阅分组;迁移时也应分别备份订阅地址和本地自定义设置,而不是只保存一份分享链接列表。

五、分享链接映射到 Xray 原生 JSON

客户端生成内核配置时,不只是把字段改名,还会加入本地入站、出站标签、日志与路由等运行信息。下面将前文 VLESS TCP + TLS 示例映射为一份最小 SOCKS 配置:本地仅监听 127.0.0.1:10808,远端仍是教学域名,因此只用于理解结构,不能直接联网使用。

{
  "log": {
    "loglevel": "warning"
  },
  "inbounds": [
    {
      "tag": "socks-in",
      "listen": "127.0.0.1",
      "port": 10808,
      "protocol": "socks",
      "settings": {
        "auth": "noauth",
        "udp": true
      }
    }
  ],
  "outbounds": [
    {
      "tag": "proxy",
      "protocol": "vless",
      "settings": {
        "vnext": [
          {
            "address": "edge.example.com",
            "port": 443,
            "users": [
              {
                "id": "00000000-0000-4000-8000-000000000001",
                "encryption": "none"
              }
            ]
          }
        ]
      },
      "streamSettings": {
        "network": "tcp",
        "security": "tls",
        "tlsSettings": {
          "serverName": "edge.example.com"
        }
      }
    }
  ]
}
分享链接内容JSON 对应位置转换注意事项
服务器与 :443settings.vnext[0].address 与 portJSON 中端口使用数字
UUIDsettings.vnext[0].users[0].id保留完整内容,不能使用备注替代
encryption=noneusers[0].encryption位于 VLESS 用户设置,不是 TLS 设置
type=tcpstreamSettings.network需由当前内核支持该传输名称及语法
security=tls 与 snistreamSettings.security 与 tlsSettings.serverNameSNI 不应凭服务器 IP 猜测
#demo-vless通常保存为客户端备注备注不一定写入运行配置

本地端口 10808 是这里明确设置的示例值,不应据此推断所有客户端当前都监听此端口。运行前先确认端口未被其他程序占用。只监听回环地址可以避免把未设认证的 SOCKS 入站暴露到局域网;也不要为了修复远端连接而把它改成所有网卡监听。

完整 JSON 反向导出为分享链接时,通常只能提取某一个兼容的代理出站。routing.rules、DNS、多个入站、多个出站之间的组合关系,不属于单节点分享链接的完整表达范围。原配置如果依赖这些设置,应继续保留 JSON,而不是认为导出一个二维码就完成了完整迁移。

六、导入为空、解码报错与转换丢字段

排查时按“获取 → 解码 → 解析 → 运行”分层记录结果。订阅更新成功仅说明客户端完成了某次处理;是否生成条目、字段是否完整、内核是否启动、远端是否可达,仍是不同的检查点。先在最早失败的环节修正,避免同时修改 TLS、端口和订阅地址而失去对照。

订阅能打开,为什么导入后是空列表?

检查响应实际内容,而不是只看请求是否成功。若是网页、权限提示或当前客户端不支持的结构,应向订阅提供方确认适用格式;若解码后是分享链接,复制完整的一条到剪贴板入口测试,区分响应格式问题与单条链接问题。

Base64 解码提示字符或长度不正确怎么办?

先确认没有把订阅网址本身拿来解码,也没有带入引号、代码围栏或省略号。移除正文中的分隔空白,检查 URL 安全字符与尾部填充。若内容被截断,补几个等号不能恢复缺失数据,应重新获取完整正文。

JSON 能通过语法检查,为什么内核仍然报错?

语法正确只说明逗号、引号和括号合法,不说明字段结构受内核支持。先确认它是 Xray 运行配置,而不是 VMess 分享对象;再看核心日志指向的字段、传输类型和缺失文件,检查导入的配置与实际内核是否匹配。

导出再导入,为什么节点名称相同却连不上?

名称不是连接参数。逐项比对地址、端口、UUID、传输、路径、Host、SNI,以及存在时的 Flow 和 Reality 参数。不要用关闭证书验证替代核对;如果导出器丢失了某个必需字段,改用完整配置备份或原始订阅恢复。

本地改好的节点,更新订阅后怎么又变回去了?

订阅更新通常会按远端内容刷新所属条目,本地修改未必保留。把需要独立维护的条目复制到非订阅分组,并明确记录修改项;仍由服务端维护的节点则优先在订阅来源修正,避免每次更新后重复手改。

七、转换后的验证与备份顺序

最稳妥的迁移单位不是“一个看起来很长的字符串”,而是明确用途的三份数据:原订阅地址用于后续更新,分享链接用于节点交换,完整配置或客户端备份用于恢复本地行为。三者可以同时保存,但不能彼此替代;包含身份信息的备份应放在受访问控制的本地位置。

  1. 保留原件:在转换前保存原始响应或原始文件,避免只剩一份经过多次处理的结果。
  2. 核对单条:先选一个条目比对协议、地址、端口与传输参数,确认没有把 UUID、Host 与 SNI 混为一项。
  3. 检查启动:启用条目并查看核心日志;先解决配置解析和端口占用,再处理证书、握手与远端连通问题。
  4. 检查应用:桌面应用还需正确接入系统代理或指定的本地代理端口;安卓端则需确认已启动连接并授予系统要求的 VPN 权限。
  5. 验证更新:若迁移目标包含订阅,在目标客户端手动更新一次,确认新增内容进入预期分组,而不是仅存在一批静态节点。