@@ -5,12 +5,14 @@ import { fileURLToPath } from 'url'
55import { getConfigUtil , webuiTokenUtil } from '@/common/config'
66import { Config , WebUIConfig } from '@/common/types'
77import { Server } from 'http'
8+ import { Socket } from 'net'
89import { Context , Service } from 'cordis'
910import { selfInfo , LOG_DIR } from '@/common/globalVars'
1011import { getAvailablePort } from '@/common/utils/port'
1112import { pmhq } from '@/ntqqapi/native/pmhq'
1213import { ReqConfig , ResConfig } from './types'
1314import { appendFileSync } from 'node:fs'
15+ import { sleep } from '@/common/utils'
1416
1517const __filename = fileURLToPath ( import . meta. url )
1618const __dirname = path . dirname ( __filename )
@@ -72,12 +74,34 @@ setInterval(() => {
7274 }
7375} , 60 * 60 * 1000 )
7476
75- abstract class WebUIServerBase extends Service {
76- protected server : Server | null = null
77- protected app : Express = express ( )
78- abstract appName : string
77+ export class WebUIServer extends Service {
78+ private server : Server | null = null
79+ private app : Express = express ( )
80+ private connections = new Set < Socket > ( )
81+ private currentPort ?: number
82+ public port ?: number = undefined
83+ static inject = [ 'ntLoginApi' ]
84+
85+ constructor ( ctx : Context , public config : WebUIServerConfig ) {
86+ super ( ctx , 'webuiServer' , true )
87+ // 初始化服务器路由
88+ this . initServer ( )
89+ // 监听 config 更新事件
90+ ctx . on ( 'llob/config-updated' , ( newConfig : Config ) => {
91+ const oldConfig = { ...this . config }
92+ this . setConfig ( newConfig )
93+ const forcePort = ( oldConfig . port === newConfig . webui ?. port ) ? this . currentPort : undefined
94+ if ( oldConfig . onlyLocalhost != newConfig . onlyLocalhost
95+ || oldConfig . enable != newConfig . webui ?. enable
96+ || oldConfig . port != newConfig . webui ?. port
97+ ) {
98+ this . ctx . logger . info ( 'WebUI 配置已更新:' , this . config )
99+ setTimeout ( ( ) => this . restart ( forcePort ) , 1000 )
100+ }
101+ } )
102+ }
79103
80- protected initServer ( ) {
104+ private initServer ( ) {
81105 this . app . use ( express . static ( feDistPath ) )
82106 this . app . use ( express . json ( ) )
83107 this . app . use ( cors ( ) )
@@ -252,62 +276,81 @@ abstract class WebUIServerBase extends Service {
252276 } )
253277 }
254278
255- abstract getHostPort ( ) : { host : string , port : number }
279+ private getHostPort ( ) : { host : string ; port : number } {
280+ const host = this . config . onlyLocalhost ? '127.0.0.1' : ''
281+ return { host, port : this . config . port }
282+ }
256283
257- async startServer ( ) {
284+ private async startServer ( forcePort ?: number ) {
258285 const { host, port } = this . getHostPort ( )
259- console . log ( `Starting server: ${ host } :${ port } ` )
260- const availablePort = await getAvailablePort ( port )
261- this . server = this . app . listen ( availablePort , host , ( ) => {
262- this . ctx . logger . info ( `${ this . appName } 端口: ${ port } ` )
286+ const targetPort = forcePort !== undefined ? forcePort : await getAvailablePort ( port )
287+ this . server = this . app . listen ( targetPort , host , ( ) => {
288+ this . currentPort = targetPort
289+ this . ctx . logger . info ( `Webui 服务器已启动 ${ host } :${ targetPort } ` )
290+ } )
291+
292+ // 跟踪所有连接,以便在停止时能够关闭它们
293+ this . server . on ( 'connection' , ( conn ) => {
294+ this . connections . add ( conn )
295+ conn . on ( 'close' , ( ) => {
296+ this . connections . delete ( conn )
297+ } )
263298 } )
299+
264300 this . server . on ( 'error' , ( err : any ) => {
265301 if ( err . code === 'EADDRINUSE' ) {
266- this . ctx . logger . error ( `${ this . appName } 端口 ${ port } 被占用,启动失败!` )
302+ this . ctx . logger . error ( `Webui 端口 ${ targetPort } 被占用,启动失败!` )
267303 }
268304 else {
269- this . ctx . logger . error ( `${ this . appName } 启动失败:` , err )
305+ this . ctx . logger . error ( `Webui 启动失败:` , err )
270306 }
271307 } )
272- return availablePort
308+ return targetPort
273309 }
274310
275311 stop ( ) {
276- this . server ?. close ( )
277- }
278-
279- restart ( ) {
280- this . stop ( )
281- this . start ( )
282- }
283- }
284-
285- export class WebUIServer extends WebUIServerBase {
286- appName = 'Webui'
287- public port ?: number = undefined
288- static inject = [ 'ntLoginApi' ]
312+ return new Promise < void > ( ( resolve ) => {
313+ if ( this . server ) {
314+ // 先关闭所有现有连接
315+ if ( this . connections . size > 0 ) {
316+ this . ctx . logger . info ( `Webui 正在关闭 ${ this . connections . size } 个连接...` )
317+ for ( const conn of this . connections ) {
318+ conn . destroy ( )
319+ }
320+ this . connections . clear ( )
321+ }
289322
290- constructor ( ctx : Context , public config : WebUIServerConfig ) {
291- super ( ctx , 'webuiServer' , true )
292- // 获取配置接口
293- this . initServer ( )
294- // 监听 config 更新事件
295- ctx . on ( 'llob/config-updated' , ( newConfig : Config ) => {
296- const oldConfig = { ...this . config }
297- this . config = { onlyLocalhost : newConfig . onlyLocalhost , ...newConfig . webui }
298- if ( oldConfig . onlyLocalhost != newConfig . onlyLocalhost
299- || oldConfig . enable != newConfig . webui ?. enable
300- || oldConfig . port != newConfig . webui ?. port
301- ) {
302- this . ctx . logger . info ( 'WebUI 配置已更新:' , this . config )
303- this . restart ( )
323+ // 然后关闭服务器
324+ this . server . close ( ( err ) => {
325+ if ( err ) {
326+ this . ctx . logger . error ( `Webui 停止时出错:` , err )
327+ }
328+ else {
329+ this . ctx . logger . info ( `Webui 服务器已停止` )
330+ }
331+ this . server = null
332+ // 不清空 currentPort,以便 restart 时复用
333+ resolve ( )
334+ } )
335+ }
336+ else {
337+ this . ctx . logger . info ( `Webui 服务器未运行` )
338+ resolve ( )
304339 }
305340 } )
306341 }
307342
308- getHostPort ( ) : { host : string ; port : number } {
309- const host = this . config . onlyLocalhost ? '127.0.0.1' : ''
310- return { host, port : this . config . port }
343+ async restart ( forcePort ?: number ) {
344+ await this . stop ( )
345+ // 等待端口完全释放(Windows 上需要)
346+ await new Promise ( resolve => setTimeout ( resolve , 1000 ) )
347+ await this . startWithPort ( forcePort )
348+ }
349+
350+ private setConfig ( newConfig : Config ) {
351+ const oldConfig = { ...this . config }
352+ this . config = { onlyLocalhost : newConfig . onlyLocalhost , ...newConfig . webui }
353+
311354 }
312355
313356 async start ( ) {
@@ -319,5 +362,15 @@ export class WebUIServer extends WebUIServerBase {
319362 this . ctx . logger . error ( '记录 WebUI 端口失败:' , err )
320363 } )
321364 }
365+
366+ private async startWithPort ( forcePort ?: number ) : Promise < void > {
367+ if ( ! this . config ?. enable ) {
368+ return
369+ }
370+ this . port = await this . startServer ( forcePort )
371+ pmhq . tellPort ( this . port ) . catch ( ( err : Error ) => {
372+ this . ctx . logger . error ( '记录 WebUI 端口失败:' , err )
373+ } )
374+ }
322375}
323376
0 commit comments