订阅地址是获取配置的入口,Base64 是文本编码,vmess:// 是节点分享格式,原生 JSON 则描述内核如何运行。本文适合需要跨设备导入节点、检查订阅内容或迁移自定义配置的用户:先辨认响应格式,再在本地解码,最后核对协议、传输与 TLS 字段,避免把“解析成功”当成“配置完整”。
先分清订阅地址、节点列表与原生 JSON
“V2Ray 订阅”不是一种统一的文件格式。一个 HTTPS 地址可能返回 Base64 编码的分享链接列表,也可能返回明文列表、完整 JSON,甚至只是登录页面。文件扩展名和地址里有没有“subscribe”都不能决定内容,客户端最终处理的是服务器返回的响应正文。
常见的 Base64 订阅有两层结构:外层把整个列表编码一次;解码后,每行是一条分享链接。若其中一行是传统 JSON 型 vmess://,协议前缀后面的负载又是一次 Base64 编码。两层编码的对象不同,不能直接把整段订阅当作单个 VMess 节点解析。
分享链接列表订阅
推荐为常规节点管理提供多条分享链接。服务方更新地址、端口后,客户端可通过订阅更新重新取得列表;外层是否使用 Base64 取决于服务方输出。
适合:在 v2rayN 与 v2rayNG 中维护同一来源的节点
单条 vmess:// 分享链接
描述一个 VMess 节点的连接参数,不等于可持续更新的订阅地址。通常通过剪贴板或二维码导入。
适合:临时迁移一个节点、逐字段排查导入问题
内核原生 JSON
可以包含入站、多个出站、DNS、路由等内容。它不只是节点清单,且必须与实际使用的内核配置模式兼容。
适合:需要明确控制入站与路由的自定义配置
v2rayN 与 v2rayNG 应从哪个入口导入
先按内容选入口,再考虑转换。把一条 HTTPS 订阅地址从剪贴板导入为节点,或把完整 JSON 填进订阅地址框,通常都无法得到预期结果。下面采用常见中文界面名称;不同发行版本的菜单用词可能略有差异,应按入口功能核对。
双端使用同一份分享链接订阅
桌面端:v2rayN
- 打开「订阅分组」→「订阅分组设置」,新增分组并填写订阅地址。
- 保存后,在「订阅分组」菜单中执行「更新全部订阅(不通过代理)」;若该来源必须经代理访问,再选通过代理更新。
- 只有单条分享链接时,使用「服务器」→「从剪贴板导入分享链接」,不要新建一个地址为 vmess:// 的订阅分组。
安卓端:v2rayNG
- 侧栏打开「订阅分组设置」,点新增按钮,填写备注与订阅 URL 后保存。
- 回到主界面,在右上角菜单执行「更新订阅」。检查列表是否出现该分组的节点。
- 单条分享链接使用主界面「+」→「从剪贴板导入」;完整配置则使用自定义配置的文件或剪贴板导入入口。
同一来源不保证两端支持完全相同的参数;更新后仍需检查节点数量、传输类型与 TLS 设置。
两款客户端都能处理常见的传统 JSON 型 VMess 分享链接,以及常见的分享链接列表订阅。完整原生 JSON 应走自定义配置入口,不能推断“支持 VMess”就意味着支持任意 JSON 订阅结构。某些配置包含内核专属字段,必须同时核对客户端版本与所选内核。
v2rayNG 使用 Xray 内核;v2flyNG 使用 v2fly 内核。两者不是单纯换一个界面的同一配置解释器。将 Xray 专用配置交给 v2flyNG 前,应检查协议与字段支持情况,而不是只改文件名。v2rayN 则需要确认该配置实际交给了哪个内核执行。
本地解码:先看列表,再看 vmess:// 负载
下面的 JavaScript 仅用于在本地浏览器开发者工具中查看文本,不会发起网络请求。先阅读代码,再在离线空白页面的控制台运行。函数接收已经复制到本地的正文,不负责下载订阅;不要在陌生网页的控制台处理凭据。
function decodeBase64Utf8(input) {
let text = input.trim().replace(/\s+/g, "");
text = text.replace(/-/g, "+").replace(/_/g, "/");
if (text.length % 4 === 1) {
throw new Error("Base64 长度不合法");
}
text = text.padEnd(Math.ceil(text.length / 4) * 4, "=");
const bytes = Uint8Array.from(atob(text), ch => ch.charCodeAt(0));
return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
}
function encodeBase64Utf8(text) {
const bytes = new TextEncoder().encode(text);
let binary = "";
for (const byte of bytes) binary += String.fromCharCode(byte);
return btoa(binary);
}
function decodeVmessJson(uri) {
const text = uri.trim();
if (!text.startsWith("vmess://")) {
throw new Error("不是 VMess 分享链接");
}
return JSON.parse(decodeBase64Utf8(text.slice(8)));
}
// 自检只使用本地示例,不包含订阅凭据。
console.log(decodeBase64Utf8(encodeBase64Utf8("节点示例")));
- 识别响应:若正文已经按行出现
vmess://等协议前缀,就直接按行处理,不要再整体解码。若开头是 HTML 标签或登录提示,应先解决订阅请求问题。 - 解开外层:对确认为 Base64 的列表正文调用
decodeBase64Utf8,再用split(/\r?\n/)分行并过滤空行。 - 解开单条:将其中一条传统 JSON 型 VMess 链接交给
decodeVmessJson。预期结果是对象,含有add、port、id等字段。 - 停止盲目重试:若解码后不是 JSON,应核对分享格式变体。不是所有使用相同协议前缀的历史实现都采用本文的 JSON 负载约定。
atob 返回的是字节对应的字符串,不是直接完成 UTF-8 解码。省略 TextDecoder 时,中文节点名称可能乱码;直接对中文调用 btoa 则可能抛错。Base64URL 的 -、_ 与标准 Base64 的 +、/ 也要区分,不能把订阅 URL 本身拿去做上述替换。
vmess:// 与原生 JSON 的字段怎样对应
以下是教学用的传统 VMess 分享负载,域名与 UUID 仅用于展示结构,不代表可连接的服务。v 是分享格式版本,不是内核版本;aid 是 alterId,现代 VMess AEAD 配置通常为 0,但迁移时仍应以服务端要求为准。
{
"v": "2",
"ps": "示例 VMess WS TLS",
"add": "node.example.com",
"port": "443",
"id": "8a7e4f30-5b91-4f8e-9e42-2c6d1b0a73f5",
"aid": "0",
"scy": "auto",
"net": "ws",
"type": "none",
"host": "node.example.com",
"path": "/gateway",
"tls": "tls",
"sni": "node.example.com"
}
| 分享字段 | 常见内核配置位置 | 转换注意点 |
|---|---|---|
add / port | settings.vnext[].address / port | 分享中的端口常为字符串,内核配置使用数字。 |
id / aid / scy | users[].id / alterId / security | 分别表示 UUID、alterId、VMess 内容加密方式。 |
net | streamSettings.network | 本例为 ws;不能只换名称就转换成其他传输。 |
host / path | wsSettings.headers.Host / path | 这是 WebSocket 请求参数,不是固定等于服务器地址。 |
tls / sni | streamSettings.security / tlsSettings.serverName | TLS 与 VMess 内容加密是两层不同设置。 |
ps | 客户端备注或自定义出站标签 | 备注不是连接参数,也不会自动生成路由规则。 |
下面的完整示例采用常见的 vnext 结构,只展示一个本地 SOCKS 入站与一个 VMess 出站。客户端导入后是否保留入站设置,取决于其自定义配置运行方式。运行前还应核对当前内核对这套字段结构的支持情况。
{
"log": {
"loglevel": "warning"
},
"inbounds": [
{
"tag": "local-socks",
"listen": "127.0.0.1",
"port": 10808,
"protocol": "socks",
"settings": {
"auth": "noauth",
"udp": true
}
}
],
"outbounds": [
{
"tag": "proxy",
"protocol": "vmess",
"settings": {
"vnext": [
{
"address": "node.example.com",
"port": 443,
"users": [
{
"id": "8a7e4f30-5b91-4f8e-9e42-2c6d1b0a73f5",
"alterId": 0,
"security": "auto"
}
]
}
]
},
"streamSettings": {
"network": "ws",
"security": "tls",
"tlsSettings": {
"serverName": "node.example.com",
"allowInsecure": false
},
"wsSettings": {
"path": "/gateway",
"headers": {
"Host": "node.example.com"
}
}
}
}
]
}
重新编码与反向转换:哪些内容会丢失
从传统 JSON 型 VMess 负载生成分享链接,只需按 UTF-8 编码 JSON,再加上协议前缀。把多条链接按换行连接后整体编码,就得到常见的 Base64 列表正文。以下代码接续前面的编码函数,输入仍是教学示例。
const sample = {
v: "2",
ps: "示例 VMess WS TLS",
add: "node.example.com",
port: "443",
id: "8a7e4f30-5b91-4f8e-9e42-2c6d1b0a73f5",
aid: "0",
scy: "auto",
net: "ws",
type: "none",
host: "node.example.com",
path: "/gateway",
tls: "tls",
sni: "node.example.com"
};
const share = "vmess://" + encodeBase64Utf8(JSON.stringify(sample));
const links = [share];
const subscriptionBody = encodeBase64Utf8(links.join("\n"));
console.log(share);
console.log(subscriptionBody);
生成列表正文不等于生成订阅服务。只有把内容放到客户端能够访问的地址,并设置合适的访问控制,才形成可更新的远程入口。仅做设备间迁移时,直接使用客户端导出的分享链接即可,不必额外部署文件。
- 完整配置转单节点会丢上下文:DNS、分流规则、本地监听端口、多出站关系与出站链不能由一条普通 VMess 分享链接完整承载。
- 一个 JSON 可能有多个候选出站:先按
protocol和tag找到目标 VMess 出站,不要默认数组第一项就是要分享的节点。 - 传输扩展未必能无损表达:涉及额外 TLS 或传输参数时,应确认接收端支持的分享字段。导出后再导入一份临时配置,逐项对照。
- 更换协议不是编码转换:VMess 不能通过把前缀改成 VLESS 或 Trojan 就变成另一种节点;服务端必须提供对应协议与凭据。
导入失败、节点为零与中文乱码怎么排查
订阅更新完成了,为什么节点数量还是零?
先确认正在查看正确分组,并检查更新日志中的解析提示。在本地查看响应正文:若是登录页、令牌过期提示或不受支持的配置结构,重复 Base64 解码没有帮助。向来源方获取适用于 v2rayN、v2rayNG 的分享链接列表输出。
解码时报错,是不是少复制了一段?
检查文本是否被聊天工具插入省略号、是否混入引号,以及长度除以 4 是否余 1。缺少末尾填充符可以规范化处理,但正文被截断不能靠补等号恢复。不要对整个 HTTPS 订阅地址使用解码函数。
节点名变成乱码,连接参数还能用吗?
先按 UTF-8 重新解码,并检查负载中的 ps。乱码若只发生在备注,通常不直接影响连接;若路径或其他字段也有非 ASCII 字符,则必须逐项核对。保留原始文本,不要在乱码结果上反复转码。
导入成功但连接超时,还需要重新转格式吗?
先核对服务端地址、443 端口是否确为服务方给出的值,再检查 UUID、WS 路径、Host 与 SNI。若日志报告 10808 端口占用,应处理本地入站冲突;若是 TLS 名称错误,应修正服务器名称,而不是反复编码同一份内容。
手动改过的节点,订阅更新后为什么恢复原样?
订阅节点通常由远程内容重新生成。需要保留本地修改时,先导出或复制为独立配置,再调整字段;具体保留行为以客户端版本为准。订阅地址更新的是来源数据,不会自动把本地编辑同步回服务方。
一次可靠的迁移应留下三份可对照的信息:原始订阅正文、解码后的节点字段、导入后的客户端设置。验证时先用一个节点确认连接,再扩展到整个列表。涉及完整 JSON 的 DNS 与路由迁移,可结合进阶配置逐项检查;不熟悉字段含义时,先查术语表,比连续尝试多个转换器更容易定位差异。