From 3b540d299311d103c692ab6f3ec8d07239d4f248 Mon Sep 17 00:00:00 2001 From: chaoszhu Date: Sat, 25 Oct 2025 10:33:31 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96rdp=E8=BF=9E=E6=8E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/app/middlewares/rdp-proxy.js | 28 +++++++++++++++ server/app/rdp-server.js | 48 +++++++++++++++++++++++++ server/app/server.js | 19 ++++++++-- server/app/socket/rdp.js | 40 --------------------- server/package.json | 1 + web/src/views/rdp/components/rdp.vue | 2 +- yarn.lock | 52 +++++++++++++++++++++++++++- 7 files changed, 146 insertions(+), 44 deletions(-) create mode 100644 server/app/middlewares/rdp-proxy.js create mode 100644 server/app/rdp-server.js delete mode 100644 server/app/socket/rdp.js diff --git a/server/app/middlewares/rdp-proxy.js b/server/app/middlewares/rdp-proxy.js new file mode 100644 index 0000000..7977905 --- /dev/null +++ b/server/app/middlewares/rdp-proxy.js @@ -0,0 +1,28 @@ +const { createProxyMiddleware } = require('http-proxy-middleware') + +/** + * RDP代理中间件 + * 将/rdp-proxy/guac路径的请求代理到RDP独立端口 + */ +const createRdpProxyMiddleware = () => { + const RDP_PORT = process.env.RDP_PORT || 8083 + const RDP_HOST = process.env.RDP_HOST || '127.0.0.1' // 使用127.0.0.1更可靠 + const target = `http://${ RDP_HOST }:${ RDP_PORT }` + + console.log('Creating RDP proxy middleware with target:', target) + + // 创建WebSocket代理 + const wsProxy = createProxyMiddleware({ + target: target, + ws: true, + changeOrigin: true, + pathRewrite: { + '^/rdp-proxy': '' + }, + logLevel: 'debug' + }) + + return wsProxy +} + +module.exports = createRdpProxyMiddleware \ No newline at end of file diff --git a/server/app/rdp-server.js b/server/app/rdp-server.js new file mode 100644 index 0000000..c4abe02 --- /dev/null +++ b/server/app/rdp-server.js @@ -0,0 +1,48 @@ +const http = require('http') +const GuacamoleLite = require('guacamole-lite') + +const RDP_PORT = process.env.RDP_PORT || 8083 // 使用独立端口 +const GUACD_HOST = process.env.GUACD_HOST || 'guacd' +const GUACD_PORT = Number(process.env.GUACD_PORT) || 4822 + +// 创建独立的HTTP服务器用于RDP +const rdpServer = http.createServer() + +const websocketOptions = { + server: rdpServer, + path: '/guac' +} + +const guacdOptions = { + host: GUACD_HOST, + port: GUACD_PORT +} + +const clientOptions = { + crypt: { + cypher: 'AES-256-CBC', + key: global.rpdToken || Array.from({ length:32 },()=>Math.random().toString(36)[2]).join('') + } +} + +const startRdpServer = () => { + try { + const guacamole = new GuacamoleLite(websocketOptions, guacdOptions, clientOptions) + + guacamole.on('connection', () => { + consola.success('✔ RDP guacamole连接成功') + }) + + guacamole.on('error', (err) => { + consola.error('❌ RDP guacamole连接错误', err) + }) + + rdpServer.listen(RDP_PORT, () => { + consola.success(`RDP服务运行在端口: ${ RDP_PORT }`) + }) + } catch (error) { + consola.error('❌ RDP 初始化失败:', error.message) + } +} + +module.exports = { startRdpServer } diff --git a/server/app/server.js b/server/app/server.js index 8e13ec2..095e008 100644 --- a/server/app/server.js +++ b/server/app/server.js @@ -3,7 +3,7 @@ const compose = require('koa-compose') // 组合中间件,简化写法 const http = require('http') const { httpPort } = require('./config') const middlewares = require('./middlewares') -const wsRdp = require('./socket/rdp') +const { startRdpServer } = require('./rdp-server') const wsTerminal = require('./socket/terminal') const wsSftpV2 = require('./socket/sftp-v2') const wsDocker = require('./socket/docker') @@ -15,17 +15,32 @@ const { throwError } = require('./utils/tools') const httpServer = () => { const app = new Koa() const server = http.createServer(app.callback()) + + // 添加RDP WebSocket代理 + const createRdpProxyMiddleware = require('./middlewares/rdp-proxy') + const rdpProxy = createRdpProxyMiddleware() + + // 只处理WebSocket升级请求的代理(RDP只需要WebSocket) + server.on('upgrade', (request, socket, head) => { + if (request.url.startsWith('/rdp-proxy')) { + rdpProxy.upgrade(request, socket, head) + } + }) + serverHandler(app, server) + // ws一直报跨域的错误:参照官方文档使用createServer API创建服务 server.listen(httpPort, () => { consola.success(`Server(http) is running on: http://localhost:${ httpPort }`) }) + + // 启动独立的RDP服务 + startRdpServer() } // 服务 function serverHandler(app, server) { app.proxy = true // 用于nginx反代时获取真实客户端ip - wsRdp(server) // RDP wsTerminal(server) // 终端 wsSftpV2(server) // sftp-v2 wsDocker(server) // docker diff --git a/server/app/socket/rdp.js b/server/app/socket/rdp.js deleted file mode 100644 index a74ca13..0000000 --- a/server/app/socket/rdp.js +++ /dev/null @@ -1,40 +0,0 @@ -const GuacamoleLite = require('guacamole-lite') - -const GUACD_HOST = process.env.GUACD_HOST || 'guacd' -const GUACD_PORT = Number(process.env.GUACD_PORT) || 4822 - -const getOptions = (server) => { - const websocketOptions = { - server, - path: '/guac' - } - - const guacdOptions = { - host: GUACD_HOST, - port: GUACD_PORT - } - - const clientOptions = { - crypt: { - cypher: 'AES-256-CBC', - key: global.rpdToken - } - } - return { websocketOptions, guacdOptions, clientOptions } -} -module.exports = (httpServer) => { - const { websocketOptions, guacdOptions, clientOptions } = getOptions(httpServer) - let guacamole = new GuacamoleLite(websocketOptions, guacdOptions, clientOptions) - try { - guacamole.on('connection', () => { - consola.success('✔ RDP guacamole连接成功') - }) - guacamole.on('error', (err) => { - consola.error('❌ RDP guacamole连接错误', err) - }) - } catch (error) { - guacamole.close() - guacamole = null - consola.error('❌ RDP 初始化失败:', error.message) - } -} \ No newline at end of file diff --git a/server/package.json b/server/package.json index 5050de4..3c92122 100644 --- a/server/package.json +++ b/server/package.json @@ -29,6 +29,7 @@ "fs-extra": "^11.2.0", "global": "^4.4.0", "guacamole-lite": "^1.2.0", + "http-proxy-middleware": "^3.0.5", "iconv-lite": "^0.6.3", "jsonwebtoken": "^9.0.2", "koa": "2.16.2", diff --git a/web/src/views/rdp/components/rdp.vue b/web/src/views/rdp/components/rdp.vue index c47cc02..e66bb0f 100644 --- a/web/src/views/rdp/components/rdp.vue +++ b/web/src/views/rdp/components/rdp.vue @@ -371,7 +371,7 @@ const getRdpWsUrl = async () => { const { data } = await $api.getRdpToken({ hostId: hostId.value, width, height }) if (!data) return $message.error('获取RDP WS URL失败') const wsHost = $isDev ? `ws://${ location.hostname }:8082` : location.origin.replace('http', 'ws') - return `${ wsHost }/guac?token=${ encodeURIComponent(data) }` + return `${ wsHost }/rdp-proxy/guac?token=${ encodeURIComponent(data) }` } const connectRdp = async () => { diff --git a/yarn.lock b/yarn.lock index dd123f6..8df36f8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1278,6 +1278,13 @@ resolved "https://registry.yarnpkg.com/@types/http-errors/-/http-errors-2.0.4.tgz#7eb47726c391b7345a6ec35ad7f4de469cf5ba4f" integrity sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA== +"@types/http-proxy@^1.17.15": + version "1.17.17" + resolved "https://registry.yarnpkg.com/@types/http-proxy/-/http-proxy-1.17.17.tgz#d9e2c4571fe3507343cb210cd41790375e59a533" + integrity sha512-ED6LB+Z1AVylNTu7hdzuBqOgMnvG/ld6wGCG8wFnAzKX5uyW2K3WD52v0gnLCTK/VLpXtKckgWuyScYK6cSPaw== + dependencies: + "@types/node" "*" + "@types/keygrip@*": version "1.0.6" resolved "https://registry.yarnpkg.com/@types/keygrip/-/keygrip-1.0.6.tgz#1749535181a2a9b02ac04a797550a8787345b740" @@ -2294,6 +2301,13 @@ debug@^4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.3, dependencies: ms "^2.1.3" +debug@^4.3.6: + version "4.4.3" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.3.tgz#c6ae432d9bd9662582fce08709b038c58e9e3d6a" + integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA== + dependencies: + ms "^2.1.3" + debug@~4.3.1, debug@~4.3.2, debug@~4.3.4: version "4.3.5" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.5.tgz#e83444eceb9fedd4a1da56d671ae2446a01a6e1e" @@ -2823,6 +2837,11 @@ eventemitter3@^3.0.0: resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-3.1.2.tgz#2d3d48f9c346698fce83a85d7d664e98535df6e7" integrity sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q== +eventemitter3@^4.0.0: + version "4.0.7" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" + integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== + exsolve@^1.0.1: version "1.0.4" resolved "https://registry.yarnpkg.com/exsolve/-/exsolve-1.0.4.tgz#7de5c75af82ecd15998328fbf5f2295883be3a39" @@ -2919,6 +2938,11 @@ flatted@^3.2.7, flatted@^3.2.9: resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.1.tgz#21db470729a6734d4997002f439cb308987f567a" integrity sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw== +follow-redirects@^1.0.0: + version "1.15.11" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.11.tgz#777d73d72a92f8ec4d2e410eb47352a56b8e8340" + integrity sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ== + follow-redirects@^1.15.6: version "1.15.6" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.6.tgz#7f815c0cda4249c74ff09e95ef97c23b5fd0399b" @@ -3296,6 +3320,27 @@ http-errors@~1.6.2: setprototypeof "1.1.0" statuses ">= 1.4.0 < 2" +http-proxy-middleware@^3.0.5: + version "3.0.5" + resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-3.0.5.tgz#9dcde663edc44079bc5a9c63e03fe5e5d6037fab" + integrity sha512-GLZZm1X38BPY4lkXA01jhwxvDoOkkXqjgVyUzVxiEK4iuRu03PZoYHhHRwxnfhQMDuaxi3vVri0YgSro/1oWqg== + dependencies: + "@types/http-proxy" "^1.17.15" + debug "^4.3.6" + http-proxy "^1.18.1" + is-glob "^4.0.3" + is-plain-object "^5.0.0" + micromatch "^4.0.8" + +http-proxy@^1.18.1: + version "1.18.1" + resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.18.1.tgz#401541f0534884bbf95260334e72f88ee3976549" + integrity sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ== + dependencies: + eventemitter3 "^4.0.0" + follow-redirects "^1.0.0" + requires-port "^1.0.0" + http-signature@~1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.4.0.tgz#dee5a9ba2bf49416abc544abd6d967f6a94c8c3f" @@ -3519,6 +3564,11 @@ is-path-inside@^3.0.3: resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== +is-plain-object@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-5.0.0.tgz#4427f50ab3429e9025ea7d52e9043a9ef4159344" + integrity sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q== + is-regex@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.2.1.tgz#76d70a3ed10ef9be48eb577887d74205bf0cad22" @@ -4084,7 +4134,7 @@ methods@^1.1.2: resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== -micromatch@^4.0.5: +micromatch@^4.0.5, micromatch@^4.0.8: version "4.0.8" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==