[{"data":1,"prerenderedAt":1487},["ShallowReactive",2],{"docs:/docs/dev/remote-internals":3},{"id":4,"title":5,"body":6,"description":1479,"extension":1480,"meta":1481,"navigation":1482,"path":1483,"seo":1484,"stem":1485,"__hash__":1486},"docs/docs/dev/remote-internals.md","远程桌面内部实现",{"type":7,"value":8,"toc":1466},"minimark",[9,12,34,66,70,269,272,279,441,475,567,570,580,677,680,686,692,745,749,760,766,771,785,794,800,852,938,941,1149,1152,1162,1168,1171,1174,1198,1224,1243,1254,1257,1270,1362,1390,1393,1430,1433,1462],[10,11,5],"h1",{"id":5},[13,14,15],"blockquote",{},[16,17,18,19,23,24,27,28,33],"p",{},"内部向文档，不进文档站导航与索引（见 ",[20,21,22],"code",{},"docs-nav.ts"," 的 ",[20,25,26],{},"UNLISTED_SECTION_IDS","）。用户向内容在 ",[29,30,32],"a",{"href":31},"/docs/remote","远程桌面文档","。",[16,35,36,37,43,44,47,48,51,52,55,56,59,60,59,63,33],{},"被控端网关是独立仓库的 Go 程序，交付名 ",[38,39,40],"strong",{},[20,41,42],{},"p2pee-remote","；Go module 路径是 ",[20,45,46],{},"gitee.com/xnkyn/p2pee-remote-desktop","（仓库私有，嵌入方配 ",[20,49,50],{},"GOPRIVATE=gitee.com/xnkyn/*"," + git 凭据后 ",[20,53,54],{},"go get","，或直接拷源码）。本仓库（控制台 + 站点）只负责三件事：",[38,57,58],{},"下发额度与信令凭证","、",[38,61,62],{},"服务入口页",[38,64,65],{},"托管前端静态资源",[67,68,69],"h2",{"id":69},"本仓库里的落点",[71,72,73,86],"table",{},[74,75,76],"thead",{},[77,78,79,83],"tr",{},[80,81,82],"th",{},"位置",[80,84,85],{},"职责",[87,88,89,120,133,151,172,198,224,242,252],"tbody",{},[77,90,91,97],{},[92,93,94],"td",{},[20,95,96],{},"server/core/ResUserInfo.js",[92,98,99,100,103,104,107,108,107,111,107,114,107,117],{},"唯一写 ",[20,101,102],{},"data.remote"," 的地方：",[20,105,106],{},"webhost"," / ",[20,109,110],{},"peers",[20,112,113],{},"connect",[20,115,116],{},"min_version",[20,118,119],{},"notice",[77,121,122,127],{},[92,123,124],{},[20,125,126],{},"server/api/p2p/connect.js",[92,128,129,130],{},"免 JWT 的凭证接口（网关用它换 MQTT 账号 + ICE + remote 额度），内部会 ",[20,131,132],{},"isVipCheckExpire",[77,134,135,140],{},[92,136,137],{},[20,138,139],{},"server/api/user/clients.js",[92,141,142,143,146,147,150],{},"给控制台列 ",[20,144,145],{},"&"," 前缀被控端，并算出 ",[20,148,149],{},"url","（双重 base64 凭证）",[77,152,153,158],{},[92,154,155],{},[20,156,157],{},"server/routes/r/[...].js",[92,159,160,161,164,165,23,168,171],{},"入口页 ",[20,162,163],{},"GET /r/\u003C凭证>/","：校验凭证 → 读 ",[20,166,167],{},"assets:page",[20,169,170],{},"remote.html"," 原样返回",[77,173,174,179],{},[92,175,176],{},[20,177,178],{},"server/page/remote.html",[92,180,181,182,189,190,193,194,197],{},"入口页模板（",[38,183,184,185,188],{},"手写在 ",[20,186,187],{},"server/page"," 根","，不在 ",[20,191,192],{},"PAGE_THEMES"," 体系里，也没有 ",[20,195,196],{},".htm"," 源）",[77,199,200,205],{},[92,201,202],{},[20,203,204],{},"public/remote/",[92,206,207,208,107,211,107,214,107,217,107,220,223],{},"前端静态资源整棵树（",[20,209,210],{},"app.js",[20,212,213],{},"boot.js",[20,215,216],{},"pipe.js",[20,218,219],{},"rdp.js",[20,221,222],{},"vnc.js"," + vendored noVNC）",[77,225,226,231],{},[92,227,228],{},[20,229,230],{},"server/api/mqtt/auth.js",[92,232,233,234,237,238,241],{},"EMQX HTTP 鉴权里那条 ",[20,235,236],{},"remote/${user}/#","（",[20,239,240],{},"action: 'all'","）ACL",[77,243,244,249],{},[92,245,246],{},[20,247,248],{},"app/pages/dashboard/remote.vue",[92,250,251],{},"控制台「远程桌面 → 在线设备」页",[77,253,254,259],{},[92,255,256],{},[20,257,258],{},"public/robots.txt",[92,260,261,264,265,268],{},[20,262,263],{},"Disallow: /r/","（地址即凭证，与 ",[20,266,267],{},"/n/"," 同理）",[67,270,271],{"id":271},"服务端下发的字段",[16,273,274,275,278],{},"网关只读 ",[20,276,277],{},"GET /api/p2p/connect?key="," 响应里的这些：",[71,280,281,294],{},[74,282,283],{},[77,284,285,288,291],{},[80,286,287],{},"字段",[80,289,290],{},"用途",[80,292,293],{},"未下发时",[87,295,296,315,332,352,369,381,398,415],{},[77,297,298,309,312],{},[92,299,300,107,303,107,306],{},[20,301,302],{},"data.Broker",[20,304,305],{},"data.user",[20,307,308],{},"data.password",[92,310,311],{},"MQTT s信令地址与账号（账号同时是 topic 前缀与访问凭证原料）",[92,313,314],{},"缺失即启动失败",[77,316,317,322,329],{},[92,318,319],{},[20,320,321],{},"data.is_vip",[92,323,324,325,328],{},"1 才允许 ",[20,326,327],{},"-mark"," 固定 markID",[92,330,331],{},"按非会员，强制随机 1000-9999",[77,333,334,339,346],{},[92,335,336],{},[20,337,338],{},"data.remote.webhost",[92,340,341,342,345],{},"访问地址前缀（可带 ",[20,343,344],{},"/r/","，网关会剥掉再统一补）",[92,347,348,349],{},"回落内置 ",[20,350,351],{},"https://p2pee.cn",[77,353,354,359,366],{},[92,355,356],{},[20,357,358],{},"data.remote.connect",[92,360,361,362,365],{},"最大 ",[20,363,364],{},"-connect"," 目标数",[92,367,368],{},"保底 2",[77,370,371,376,379],{},[92,372,373],{},[20,374,375],{},"data.remote.peers",[92,377,378],{},"最大并发桌面会话数",[92,380,368],{},[77,382,383,388,391],{},[92,384,385],{},[20,386,387],{},"data.remote.min_version",[92,389,390],{},"客户端最低版本，低于则终止启动",[92,392,393,394,397],{},"回落顶层 ",[20,395,396],{},"version2","；两边都缺则跳过校验",[77,399,400,405,412],{},[92,401,402],{},[20,403,404],{},"data.remote.notice",[92,406,407,408,411],{},"提示文案：",[38,409,410],{},"只在网关启动时打到控制台","（黄色高亮）并进运行日志，不进页面协议",[92,413,414],{},"不打印",[77,416,417,431,438],{},[92,418,419,107,422,107,425,107,428],{},[20,420,421],{},"data.serve.stun",[20,423,424],{},"turn",[20,426,427],{},"Username",[20,429,430],{},"Credential",[92,432,433,434,437],{},"ICE 服务器；",[20,435,436],{},"-turn"," 可覆盖 TURN，STUN 不可",[92,439,440],{},"无 STUN/TURN，仅本机候选",[16,442,443,444,446,447,107,449,451,452,454,455,33,458,466,467,470,471,474],{},"当前数值（",[20,445,96],{},"）：",[20,448,110],{},[20,450,113],{}," = 会员 10 / 免费 2，",[20,453,116],{}," = ",[20,456,457],{},"1.0.0",[38,459,460,461,107,464],{},"额度不复用 proxy 的 ",[20,462,463],{},"target",[20,465,110],{},"（两个产品的订阅语义不同）；",[20,468,469],{},"\u003C= 0"," 与缺字段同义，都落保底值 —— 要禁止某个账号用远程桌面，请让接口返回 ",[20,472,473],{},"code != 0","，不要靠下发 0。",[476,477,480],"callout",{"title":478,"type":479},"三个已知偏差","warning",[481,482,483,502,523,542],"ol",{},[484,485,486,494,495,498,499,501],"li",{},[38,487,488,490,491],{},[20,489,106],{}," 是硬编码的 ",[20,492,493],{},"https://p2pee.cn/r/","，不读 ",[20,496,497],{},"runtimeConfig.public.siteUrl"," —— 换域名时这一行要一起改（",[20,500,344],{}," 的页面路由本身按站点走，不受影响）。",[484,503,504,511,512,514,515,518,519,522],{},[38,505,506,507,510],{},"README 里约定的 ",[20,508,509],{},"t=3"," 应用标识服务端没实现","：",[20,513,126],{}," 不读 ",[20,516,517],{},"t","（未知参数被静默忽略），三个业务共用同一份 ",[20,520,521],{},"data","。真需要按应用分发额度时在这个路由里加分支。",[484,524,525,530,531,534,535,538,539,541],{},[38,526,527,528],{},"访问地址只认 ",[20,529,338],{},"：顶层 proxy 时代的 ",[20,532,533],{},"proxyhost"," / 形如 ",[20,536,537],{},"http://p2pee.cn/t/"," 的顶层 ",[20,540,106],{}," 一律不读，回落过去等于把用户导到打不开的地址。",[484,543,544,547,548,550,551,554,555,558,559,562,563,566],{},[38,545,546],{},"凭证校验失败的码不一样","：Go 侧 README 写的是 403，本站 ",[20,549,157],{}," 抛的是 ",[38,552,553],{},"404「地址无效」","（刻意与\"受限/额度用完\"的 429 区分开，不给探测者\"这里有个地址\"的信号）。另外 README 说「尾斜杠必须带」，本路由用 ",[20,556,557],{},"/^\\/r\\/(.*)$/"," 捕获后 ",[20,560,561],{},"replace(/^\\/+|\\/+$/g,'')","，",[38,564,565],{},"带不带都能打开"," —— 控制台列表拼出来的地址就不带尾斜杠。",[67,568,569],{"id":569},"入口路由与凭证编码",[571,572,578],"pre",{"className":573,"code":575,"language":576,"meta":577},[574],"language-text","https://p2pee.cn/r/\u003C凭证>/\n             └─┬─┘└──┬──┘\n           固定段   双重 base64(user:password:markID) 的 URL-safe 形式\n","text","",[20,579,575],{"__ignoreMap":577},[581,582,583,601,622,633,663],"ul",{},[484,584,585,586,589,590,593,594,597,598,33],{},"凭证段",[38,587,588],{},"按最后两个冒号切","（口令与 markID 之间、user 与口令之间），外层同时兼容标准 base64 与无 padding 的 URL-safe 形式；含 ",[20,591,592],{},"/"," 的整段会被路径捕获一起取走（",[20,595,596],{},"[...]"," 匹配多段），所以",[38,599,600],{},"路径段不能被 CDN 规范化",[484,602,603,606,607,610,611,614,615,618,619,621],{},[20,604,605],{},"password"," 段就是 ",[20,608,609],{},"users.id + 40000","，与 ",[20,612,613],{},"/api/user/clients.js"," 和 ",[20,616,617],{},"ResUserInfo"," 同源；校验不过（格式非法 / 用户不存在 / 已注销 / 口令段不等）一律 ",[38,620,553],{},"，不查 EMQX、不暴露\"这个地址存在但没开\"。",[484,623,624,625,628,629,632],{},"用户查询走 ",[20,626,627],{},"userCache","，键 ",[20,630,631],{},"remoteUser:\u003Cuser>","，TTL 60s。",[484,634,635,636,639,640,643,644,107,647,650,651,654,655,658,659,662],{},"页面",[38,637,638],{},"自身不调本站接口","（信令走 MQTT、桌面走 P2P），所以入口路由刻意",[38,641,642],{},"不注入"," ",[20,645,646],{},"__ICE_SERVERS__",[20,648,649],{},"__MAX_UPLOAD_SIZE__","，也不写 ",[20,652,653],{},"cookie_page"," —— 那套注入是 ",[20,656,657],{},"middleware/3.t.js"," 给 ",[20,660,661],{},"/t/**"," 分享页用的。",[484,664,665,666,669,670,673,674,33],{},"响应带 ",[20,667,668],{},"X-Robots-Tag: noindex, nofollow","；网关自带的 ",[20,671,672],{},"-debug"," 静态服务同样校验凭证，但不通过返回 ",[38,675,676],{},"403",[67,678,679],{"id":679},"静态资源映射",[16,681,682,683,511],{},"仓库目录与站点 URL 一一对应，",[38,684,685],{},"整份拷走即可部署，不需要改名",[571,687,690],{"className":688,"code":689,"language":576,"meta":577},[574],"web/remote/     →  \u003C站点根>/remote/     app.js、boot.js、pipe.js、rdp.js、vnc.js、vnc/ 整棵树\nweb/index.html  →  \u003C站点根>/r/\u003C凭证>/   入口页（一条通配路由把 /r/ 下的请求都指向这份 HTML）\n",[20,691,689],{"__ignoreMap":577},[581,693,694,720,731],{},[484,695,696,699,700,707,708,711,712,715,716,719],{},[20,697,698],{},"index.html"," 里引用资源",[38,701,702,703,706],{},"一律写 ",[20,704,705],{},"/remote/xxx"," 绝对路径","：页面自己在 ",[20,709,710],{},"/r/\u003C凭证>/"," 下，相对 ",[20,713,714],{},"./app.js"," 会解析成 ",[20,717,718],{},"/r/\u003C凭证>/app.js"," 而 404。",[484,721,722,723,726,727,730],{},"模块之间的 ",[20,724,725],{},"import \"./boot.js\""," 不用改：ES module 的相对说明符按",[38,728,729],{},"模块自身 URL"," 解析。",[484,732,733,734,737,738,741,742,744],{},"Go 侧 ",[20,735,736],{},"gorw/web.go"," 用 ",[20,739,740],{},"go:embed"," 内嵌同一份资源，",[20,743,672],{}," 本机服务按同样的对应关系发，形态与线上一致（还带 ETag / no-cache）。",[67,746,748],{"id":747},"信令-topicacl-与-clientid","信令 topic、ACL 与 clientId",[16,750,751,752,755,756,759],{},"顶级命名空间 ",[20,753,754],{},"remote/","，与穿透的 ",[20,757,758],{},"p2p/{user}/"," 分开，便于服务端按命名空间独立审计：",[571,761,764],{"className":762,"code":763,"language":576,"meta":577},[574],"remote/{user}/{markID}/offer | answer | ice | gw-health | gw-probe\n",[20,765,763],{"__ignoreMap":577},[16,767,768,769,446],{},"EMQX 授权里那条与穿透同构（",[20,770,230],{},[571,772,776],{"className":773,"code":774,"language":775,"meta":577,"style":577},"language-js shiki shiki-themes github-dark","{ permission: 'allow', action: 'all', topic: `remote/${user.user}/#` }\n","js",[20,777,778],{"__ignoreMap":577},[779,780,783],"span",{"class":781,"line":782},"line",1,[779,784,774],{},[16,786,787,789,790,793],{},[20,788,240],{}," 同时覆盖发布与订阅 —— 网关和页面用的是",[38,791,792],{},"同一个 MQTT 账号","（都来自 Key 换来的凭证），一条规则就够。缺这条规则时网关会在订阅阶段直接失败并打印 topic，不会静默连上却收不到 offer。",[16,795,796,797,446],{},"MQTT clientId 形状（README 约定 vs 本仓库现状，",[38,798,799],{},"待对齐",[71,801,802,815],{},[74,803,804],{},[77,805,806,809,812],{},[80,807,808],{},"端",[80,810,811],{},"README 约定",[80,813,814],{},"现状实现",[87,816,817,833],{},[77,818,819,822,827],{},[92,820,821],{},"网关",[92,823,824],{},[20,825,826],{},"&{主机名}_{GOOS}_{毫秒尾数}_{markID}",[92,828,829,830,832],{},"与约定一致（",[20,831,145],{}," 前缀 = 服务端设备）",[77,834,835,838,843],{},[92,836,837],{},"浏览器页面",[92,839,840],{},[20,841,842],{},"remoteweb_{markID}_{浏览器}_{系统}_{随机6位}",[92,844,845,848,849],{},[20,846,847],{},"public/remote/rtc.js"," 实际写的是 ",[20,850,851],{},"\"&web_\" + markID + \"_\" + randomID(4)",[476,853,856,892],{"title":854,"type":855},"页面端用 & 前缀带来两个后果","danger",[481,857,858,889],{},[484,859,860,861,863,864,867,868,871,872,874,875,237,878,881,882,237,885,888],{},"它会被 ",[20,862,139],{}," 当成",[38,865,866],{},"服务端设备","算出 ",[20,869,870],{},"markid","（取末段抹非数字），于是 ",[20,873,248],{}," 里 ",[20,876,877],{},"serverList",[20,879,880],{},"startsWith('&')","）会把浏览器标签页也收进被控端列表，而 ",[20,883,884],{},"clientList",[20,886,887],{},"/^remote/i","）匹配不到任何一条 → 「连接端」子表恒空。",[484,890,891],{},"broker 侧无法按前缀区分「哪个项目 / 哪一端」，也就没法按页面端限流。",[16,893,894,895,898,899,902,903,23,906,908,909,911,912,915,916,923,924,107,927,930,931,933,934,937],{},"改法二选一：把 ",[20,896,897],{},"rtc.js"," 的 clientId 换成 ",[20,900,901],{},"remote_web_"," 前缀（同时保持 ",[20,904,905],{},"remote.vue",[20,907,887],{}," 筛选），或者反过来把 ",[20,910,884],{}," 的判据改成 ",[20,913,914],{},"&web_","。注意",[38,917,918,919,922],{},"远程桌面的连接端只按 ",[20,920,921],{},"remote"," 前缀筛","，不能收全部非服务端设备 —— ",[20,925,926],{},"web_",[20,928,929],{},"p2pweb_"," 与 ",[20,932,145],{}," 被控端的 markid 号段可能同值，混进来就是错关联（见 ",[20,935,936],{},"agents.md"," 的 clientid markid 取法）。",[67,939,940],{"id":940},"前端链路细节",[71,942,943,953],{},[74,944,945],{},[77,946,947,950],{},[80,948,949],{},"细节",[80,951,952],{},"值 / 位置",[87,954,955,973,992,1012,1033,1057,1065,1073,1089,1116,1136],{},[77,956,957,960],{},[92,958,959],{},"MQTT broker 端点",[92,961,962,963,966,967,237,970,972],{},"按页面协议固定，页面无配置入口：https → ",[20,964,965],{},"wss://app.navjs.cn/mqtt","，http → ",[20,968,969],{},"ws://1.94.136.247:8083/mqtt",[20,971,213],{},"）",[77,974,975,978],{},[92,976,977],{},"浏览器侧 ICE",[92,979,980,511,983,59,986,237,989,991],{},[38,981,982],{},"只配 STUN",[20,984,985],{},"stun:vip.navjs.cn:3478",[20,987,988],{},"stun:stun.navjs.cn:3478",[20,990,213],{}," 写死），中继由网关侧 TURN 兜底",[77,993,994,997],{},[92,995,996],{},"DataChannel",[92,998,999,1000,1003,1004,1007,1008,1011],{},"发 offer ",[38,1001,1002],{},"之前","创建两条：",[20,1005,1006],{},"control","（JSON）+ ",[20,1009,1010],{},"desktop","（协议字节），SDP 一次谈定、不重新协商",[77,1013,1014,1017],{},[92,1015,1016],{},"控制协议",[92,1018,1019,1020,1023,1024,237,1027,1029,1030,972],{},"收 ",[20,1021,1022],{},"config | ready | error | closed","，发 ",[20,1025,1026],{},"select | release",[20,1028,210],{}," ↔ ",[20,1031,1032],{},"gorw/session.go",[77,1034,1035,1038],{},[92,1036,1037],{},"分片",[92,1039,1040,1041,562,1044,1047,1048,23,1050,930,1053,1056],{},"每条 DC 消息 = ",[20,1042,1043],{},"[1 字节续传标志][≤16KB 载荷]",[20,1045,1046],{},"0x01"," 表示还有后续分片；浏览器侧 ",[20,1049,216],{},[20,1051,1052],{},"CHUNK_SIZE = 16*1024",[20,1054,1055],{},"gorw/pipe.go"," 对称",[77,1058,1059,1062],{},[92,1060,1061],{},"重组上限",[92,1063,1064],{},"16MB，超限整帧作废（宁可丢一帧也不把残帧交给协议层错位解析）",[77,1066,1067,1070],{},[92,1068,1069],{},"发送流控",[92,1071,1072],{},"水位高 4MB 暂停 / 低 1MB 恢复；关闭前最多冲刷 1 秒",[77,1074,1075,1078],{},[92,1076,1077],{},"为什么要两条通道",[92,1079,1080,1081,1084,1085,1088],{},"VNC 通道一旦交给 noVNC，它的 ",[20,1082,1083],{},"onmessage"," 被 RFB 解析器独占；双通道顺带解决时序 —— 桌面通道在 ",[20,1086,1087],{},"select"," 之前不注册任何业务回调，预置口令与就绪信号都走控制通道",[77,1090,1091,1094],{},[92,1092,1093],{},"信令时序",[92,1095,1096,59,1099,59,1102,59,1105,59,1108,59,1111,237,1114,972],{},[20,1097,1098],{},"ANSWER_TIMEOUT 12000",[20,1100,1101],{},"MAX_RETRIES 3",[20,1103,1104],{},"RETRY_DELAY 3000",[20,1106,1107],{},"PROBE_TIMEOUT 3000",[20,1109,1110],{},"PROBE_TRIES 3",[20,1112,1113],{},"DISCONNECT_GRACE 8000",[20,1115,897],{},[77,1117,1118,1121],{},[92,1119,1120],{},"能力位",[92,1122,1123,562,1126,1129,1130,23,1132,1135],{},[20,1124,1125],{},"RDP_CAPS = { sas:true, clipboard:true, resize:true }",[20,1127,1128],{},"VNC_CAPS"," 全 false → ",[20,1131,210],{},[20,1133,1134],{},"applyCaps()"," 决定工具栏按钮",[77,1137,1138,1141],{},[92,1139,1140],{},"iframe",[92,1142,1143,23,1145,1148],{},[20,1144,213],{},[20,1146,1147],{},"embedded()"," 直接拒绝：一条 P2P 连接会占一份并发额度",[67,1150,1151],{"id":1151},"协议层范围",[16,1153,1154,1157,1158,1161],{},[38,1155,1156],{},"RDP","：TPKT / X.224 / TLS / CredSSP（NTLMv2 + MIC + v6 哈希绑定）、标准安全层（服务端证书解析、会话密钥推导、RC4、MAC、4096 包密钥更新、自动登录）、MCS / GCC / 能力协商 / 激活、静态虚拟通道（rdpdr / cliprdr / rdpsnd / drdynvc / display）、24bpp 位图与 Interleaved RLE、fastpath 分片重组、脏区合并 + 跨帧 zlib 流、鼠标 / 键盘（扫描码 + 扩展键）/ 中文 IME（Unicode 键盘事件）/ 远端光标、剪贴板文本双向、SAS、会话中切分辨率（失败自动带新尺寸重连）。",[38,1159,1160],{},"未实现","：音频播放、rdpdr 文件重定向、多显示器、RemoteFX / H264。",[16,1163,1164,1167],{},[38,1165,1166],{},"VNC","：RFB 3.3–3.8，认证与编码全部由 vendored noVNC 承担，因此任意 RFB 服务皆可；画面本地缩放，不请求远端改尺寸。",[16,1169,1170],{},"系统要求 Windows 10 / Server 2016 及以上；TLS + CredSSP 与标准 RDP 安全层自动协商回退。",[67,1172,1173],{"id":1173},"诊断子命令与测试",[571,1175,1179],{"className":1176,"code":1177,"language":1178,"meta":577,"style":577},"language-powershell shiki shiki-themes github-dark",".\\p2pee-remote.exe probe 192.168.10.7               # RDP 协商诊断，不需要凭据、不走 P2P 与信令\n.\\p2pee-remote.exe shot  192.168.10.7 -out shot.png # RDP 无头截图：出图即说明协议链路正常\n.\\p2pee-remote.exe -key \u003Ckey> -connect rdp:192.168.10.7 -debug -listen 127.0.0.1:8081\n","powershell",[20,1180,1181,1186,1192],{"__ignoreMap":577},[779,1182,1183],{"class":781,"line":782},[779,1184,1185],{},".\\p2pee-remote.exe probe 192.168.10.7               # RDP 协商诊断，不需要凭据、不走 P2P 与信令\n",[779,1187,1189],{"class":781,"line":1188},2,[779,1190,1191],{},".\\p2pee-remote.exe shot  192.168.10.7 -out shot.png # RDP 无头截图：出图即说明协议链路正常\n",[779,1193,1195],{"class":781,"line":1194},3,[779,1196,1197],{},".\\p2pee-remote.exe -key \u003Ckey> -connect rdp:192.168.10.7 -debug -listen 127.0.0.1:8081\n",[571,1199,1201],{"className":1176,"code":1200,"language":1178,"meta":577,"style":577},"go build -o p2pee-remote.exe .            # 唯一交付物（前端已 go:embed 进去）\ngo build -o fakevnc.exe ./cmd/fakevnc     # 测试用迷你 VNC 服务，不交付\ngo test ./... && go vet ./...\n$env:RDP_PROBE=1; go test ./internal/rdp  # 真机测试默认跳过\n",[20,1202,1203,1208,1213,1218],{"__ignoreMap":577},[779,1204,1205],{"class":781,"line":782},[779,1206,1207],{},"go build -o p2pee-remote.exe .            # 唯一交付物（前端已 go:embed 进去）\n",[779,1209,1210],{"class":781,"line":1188},[779,1211,1212],{},"go build -o fakevnc.exe ./cmd/fakevnc     # 测试用迷你 VNC 服务，不交付\n",[779,1214,1215],{"class":781,"line":1194},[779,1216,1217],{},"go test ./... && go vet ./...\n",[779,1219,1221],{"class":781,"line":1220},4,[779,1222,1223],{},"$env:RDP_PROBE=1; go test ./internal/rdp  # 真机测试默认跳过\n",[16,1225,1226,1227,1230,1231,1234,1235,1238,1239,1242],{},"Windows + Git Bash 上构建要先 ",[20,1228,1229],{},"export GOTOOLCHAIN=go1.25.1","：PATH 上的 go1.22.5 低于 go.mod 要求的 1.24.11，",[20,1232,1233],{},"GOTOOLCHAIN=auto"," 会去下载新 toolchain 而外网被重置，命令会挂死几分钟。",[20,1236,1237],{},"-race"," 在这台机器上对所有包都返回 ",[20,1240,1241],{},"exit status 0xc0000139","（测试二进制起不来），不要用它做验证。",[16,1244,1245,1246,1249,1250,1253],{},"服务端侧已知限制：部分机器的「登录会话」（无凭据连接）可能 CSR 初始化失败（LocalSessionManager 事件 36，",[20,1247,1248],{},"0x80004005","），填凭据直连即可、重启 Remote Desktop Services 可能修复；同一台 Windows 上无人完成登录的会话会在激活后约 30 秒被主机断开（Error Info ",[20,1251,1252],{},"0x0000000C","，事件 40 原因代码 12）。两条限制都只在目标机上。",[67,1255,1256],{"id":1256},"发行管线接入",[16,1258,1259,1261,1262,1265,1266,446],{},[20,1260,42],{}," 的下载入口与其它业务共用同一套管线（Gitee 仓库 ",[20,1263,1264],{},"xnkyn/assets","，tag ",[38,1267,1268],{},[20,1269,42],{},[71,1271,1272,1281],{},[74,1273,1274],{},[77,1275,1276,1278],{},[80,1277,82],{},[80,1279,1280],{},"接线",[87,1282,1283,1311,1331,1349],{},[77,1284,1285,1290],{},[92,1286,1287],{},[20,1288,1289],{},"scripts/fetch-releases.mjs",[92,1291,1292,1295,1296,1299,1300,1303,1304,107,1307,1310],{},[20,1293,1294],{},"MODULES"," 里的 ",[20,1297,1298],{},"{ key: 'remote', repo: 'xnkyn/assets', tag: 'p2pee-remote' }","；",[20,1301,1302],{},"META_FILES"," 把 ",[20,1305,1306],{},"manifest.txt",[20,1308,1309],{},"SHA256SUMS"," 从下载列表剔掉",[77,1312,1313,1318],{},[92,1314,1315],{},[20,1316,1317],{},"app/utils/releases.ts",[92,1319,1320,1323,1324,1299,1327,1330],{},[20,1321,1322],{},"ReleaseModuleId"," 含 ",[20,1325,1326],{},"'remote'",[20,1328,1329],{},"releaseDownloads()"," 对缺失模块返回空数组（tag 被删时不炸构建）",[77,1332,1333,1338],{},[92,1334,1335],{},[20,1336,1337],{},"content/changelog/index.ts",[92,1339,1340,23,1343,1346,1347],{},[20,1341,1342],{},"toRelease()",[20,1344,1345],{},"downloadsFrom === 'release'"," 白名单含 ",[20,1348,921],{},[77,1350,1351,1356],{},[92,1352,1353],{},[20,1354,1355],{},"content/changelog/remote.json",[92,1357,1358,1361],{},[20,1359,1360],{},"\"downloadsFrom\": \"release\""," —— 下载地址不手写，跟着发行版走",[16,1363,1364,1365,59,1368,59,1371,59,1374,1377,1378,930,1380,1382,1383,1386,1387,1389],{},"产物命名沿用穿透的规则（带架构段），1.0.0 共 11 个包：",[20,1366,1367],{},"windows-{amd64,arm64,386}.exe",[20,1369,1370],{},"linux-{amd64,arm64,arm,386}",[20,1372,1373],{},"darwin-{amd64,arm64}",[20,1375,1376],{},"freebsd-{amd64,arm64}","，另附 ",[20,1379,1309],{},[20,1381,1306],{},"（构建元数据，不进下载列表）。抓取失败只 warn、不阻断构建，此时 ",[20,1384,1385],{},"releases.json"," 里不会有 ",[20,1388,921],{}," 键。",[67,1391,1392],{"id":1392},"两处刻意不做的事",[581,1394,1395,1416],{},[484,1396,1397,511,1404,1407,1408,1411,1412,1415],{},[38,1398,1399,1400,1403],{},"设备主页 ",[20,1401,1402],{},"/n/\u003Cnav_code>"," 不列远程桌面",[20,1405,1406],{},"fetchServeDevices"," 只取 ",[20,1409,1410],{},"@","（文件共享）与 ",[20,1413,1414],{},"#","（穿透）。远程桌面的地址带凭证且每次重启可能变化，放进免登录的固定入口等于把凭证外发到公开页。",[484,1417,1418,1421,1422,1425,1426,1429],{},[38,1419,1420],{},"控制台不显示远程桌面额度","：额度数值由服务端下发给网关，页面状态行里的「并发上限 N」就是它。控制台列表只提供在线被控端与地址，",[20,1423,1424],{},"/api/user/clients"," 本身是 EMQX 的一次查询（",[20,1427,1428],{},"limit: 30","，全部业务共用），不要在这里加聚合统计。",[67,1431,1432],{"id":1432},"相关",[581,1434,1435,1441,1448,1455],{},[484,1436,1437,1440],{},[29,1438,1439],{"href":31},"远程桌面介绍","（用户向）",[484,1442,1443,1447],{},[29,1444,1446],{"href":1445},"/docs/remote/cli","CLI 命令行","：参数与常驻运行",[484,1449,1450,1454],{},[29,1451,1453],{"href":1452},"/docs/remote/security","安全与凭证","：地址准入与脱敏的用户视角",[484,1456,1457,1461],{},[29,1458,1460],{"href":1459},"/docs/guide/console","控制台介绍","：控制台页面职责总览",[1463,1464,1465],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"title":577,"searchDepth":1194,"depth":1194,"links":1467},[1468,1469,1470,1471,1472,1473,1474,1475,1476,1477,1478],{"id":69,"depth":1188,"text":69},{"id":271,"depth":1188,"text":271},{"id":569,"depth":1188,"text":569},{"id":679,"depth":1188,"text":679},{"id":747,"depth":1188,"text":748},{"id":940,"depth":1188,"text":940},{"id":1151,"depth":1188,"text":1151},{"id":1173,"depth":1188,"text":1173},{"id":1256,"depth":1188,"text":1256},{"id":1392,"depth":1188,"text":1392},{"id":1432,"depth":1188,"text":1432},"远程桌面的服务端与客户端内部约定：data.remote 下发字段、/r/ 入口路由与凭证编码、EMQX topic 与 clientId、静态页面部署映射、分片协议与发行管线接入。","md",{},true,"/docs/dev/remote-internals",{"title":5,"description":1479},"docs/dev/remote-internals","nKRL5p9-mCg0ggBQF1Dx5H221FTiYnK2FqhpQJQWuDU",1792518096662]