Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
P
Pangea-Agent
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
2
Merge Requests
2
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Gavin-Group
Pangea-Agent
Commits
2a026455
Commit
2a026455
authored
Dec 30, 2025
by
王舵
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: 监听browser连接断开,重新启动 browser
parent
a8007006
Changes
3
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
77 additions
and
57 deletions
+77
-57
PlaywrightManagerImpl.java
...pangea/hiagent/tool/playwright/PlaywrightManagerImpl.java
+62
-48
application-dev.yml
backend/src/main/resources/application-dev.yml
+5
-5
vite.config.ts
frontend/vite.config.ts
+10
-4
No files found.
backend/src/main/java/pangea/hiagent/tool/playwright/PlaywrightManagerImpl.java
View file @
2a026455
...
@@ -14,36 +14,35 @@ import java.util.concurrent.*;
...
@@ -14,36 +14,35 @@ import java.util.concurrent.*;
@Slf4j
@Slf4j
@Component
// Spring默认单例模式
@Component
// Spring默认单例模式
public
class
PlaywrightManagerImpl
implements
PlaywrightManager
{
public
class
PlaywrightManagerImpl
implements
PlaywrightManager
{
// 共享的Playwright实例
// 共享的Playwright实例
private
volatile
Playwright
playwright
;
private
volatile
Playwright
playwright
;
// 共享的浏览器实例
// 共享的浏览器实例
private
volatile
Browser
browser
;
private
volatile
Browser
browser
;
// 用户浏览器上下文映射表(用户ID -> BrowserContext)
// 用户浏览器上下文映射表(用户ID -> BrowserContext)
private
final
ConcurrentMap
<
String
,
BrowserContext
>
userContexts
=
new
ConcurrentHashMap
<>();
private
final
ConcurrentMap
<
String
,
BrowserContext
>
userContexts
=
new
ConcurrentHashMap
<>();
// 用户上下文创建时间映射表(用于超时清理)
// 用户上下文创建时间映射表(用于超时清理)
private
final
ConcurrentMap
<
String
,
Long
>
contextCreationTimes
=
new
ConcurrentHashMap
<>();
private
final
ConcurrentMap
<
String
,
Long
>
contextCreationTimes
=
new
ConcurrentHashMap
<>();
// 用户上下文超时时间(毫秒),默认30分钟
// 用户上下文超时时间(毫秒),默认30分钟
private
static
final
long
CONTEXT_TIMEOUT
=
30
*
60
*
1000
;
private
static
final
long
CONTEXT_TIMEOUT
=
30
*
60
*
1000
;
// 清理任务调度器
// 清理任务调度器
private
final
ScheduledExecutorService
cleanupScheduler
=
private
final
ScheduledExecutorService
cleanupScheduler
=
Executors
.
newSingleThreadScheduledExecutor
(
r
->
{
Executors
.
newSingleThreadScheduledExecutor
(
r
->
{
Thread
t
=
new
Thread
(
r
,
"PlaywrightCleanupScheduler"
);
Thread
t
=
new
Thread
(
r
,
"PlaywrightCleanupScheduler"
);
t
.
setDaemon
(
true
);
// 设置为守护线程
t
.
setDaemon
(
true
);
// 设置为守护线程
return
t
;
return
t
;
});
});
// 标记是否已经初始化
// 标记是否已经初始化
private
volatile
boolean
initialized
=
false
;
private
volatile
boolean
initialized
=
false
;
// 用于同步初始化过程
// 用于同步初始化过程
private
final
Object
initLock
=
new
Object
();
private
final
Object
initLock
=
new
Object
();
/**
/**
* 延迟初始化Playwright和浏览器实例
* 延迟初始化Playwright和浏览器实例
*/
*/
...
@@ -53,10 +52,10 @@ public class PlaywrightManagerImpl implements PlaywrightManager {
...
@@ -53,10 +52,10 @@ public class PlaywrightManagerImpl implements PlaywrightManager {
if
(!
initialized
)
{
if
(!
initialized
)
{
try
{
try
{
log
.
info
(
"正在初始化Playwright管理器..."
);
log
.
info
(
"正在初始化Playwright管理器..."
);
// 创建Playwright实例
// 创建Playwright实例
this
.
playwright
=
Playwright
.
create
();
this
.
playwright
=
Playwright
.
create
();
// 启动Chrome浏览器,无头模式
// 启动Chrome浏览器,无头模式
this
.
browser
=
playwright
.
chromium
().
launch
(
new
BrowserType
.
LaunchOptions
()
this
.
browser
=
playwright
.
chromium
().
launch
(
new
BrowserType
.
LaunchOptions
()
.
setHeadless
(
true
)
.
setHeadless
(
true
)
...
@@ -65,11 +64,23 @@ public class PlaywrightManagerImpl implements PlaywrightManager {
...
@@ -65,11 +64,23 @@ public class PlaywrightManagerImpl implements PlaywrightManager {
"--disable-dev-shm-usage"
,
"--disable-dev-shm-usage"
,
"--disable-gpu"
,
"--disable-gpu"
,
"--remote-allow-origins=*"
)));
"--remote-allow-origins=*"
)));
this
.
browser
.
onDisconnected
((
browser
)
->
{
log
.
info
(
"浏览器实例已断开连接"
);
this
.
browser
.
close
();
this
.
browser
=
playwright
.
chromium
().
launch
(
new
BrowserType
.
LaunchOptions
()
.
setHeadless
(
true
)
.
setArgs
(
java
.
util
.
Arrays
.
asList
(
"--no-sandbox"
,
"--disable-dev-shm-usage"
,
"--disable-gpu"
,
"--remote-allow-origins=*"
)));
});
// 每5分钟检查一次超时的用户上下文
// 每5分钟检查一次超时的用户上下文
cleanupScheduler
.
scheduleAtFixedRate
(
this
::
cleanupExpiredContexts
,
cleanupScheduler
.
scheduleAtFixedRate
(
this
::
cleanupExpiredContexts
,
5
,
3600
,
TimeUnit
.
MINUTES
);
5
,
3600
,
TimeUnit
.
MINUTES
);
this
.
initialized
=
true
;
this
.
initialized
=
true
;
log
.
info
(
"Playwright管理器初始化成功"
);
log
.
info
(
"Playwright管理器初始化成功"
);
}
catch
(
Exception
e
)
{
}
catch
(
Exception
e
)
{
...
@@ -80,15 +91,15 @@ public class PlaywrightManagerImpl implements PlaywrightManager {
...
@@ -80,15 +91,15 @@ public class PlaywrightManagerImpl implements PlaywrightManager {
}
}
}
}
}
}
// 移除@PostConstruct注解,避免在Spring初始化时自动调用
// 移除@PostConstruct注解,避免在Spring初始化时自动调用
/*
/*
@PostConstruct
*
@PostConstruct
public void initialize() {
*
public void initialize() {
lazyInitialize();
*
lazyInitialize();
}
*
}
*/
*/
@Override
@Override
public
Playwright
getPlaywright
()
{
public
Playwright
getPlaywright
()
{
lazyInitialize
();
lazyInitialize
();
...
@@ -97,7 +108,7 @@ public class PlaywrightManagerImpl implements PlaywrightManager {
...
@@ -97,7 +108,7 @@ public class PlaywrightManagerImpl implements PlaywrightManager {
}
}
return
playwright
;
return
playwright
;
}
}
@Override
@Override
public
Browser
getBrowser
()
{
public
Browser
getBrowser
()
{
lazyInitialize
();
lazyInitialize
();
...
@@ -106,30 +117,33 @@ public class PlaywrightManagerImpl implements PlaywrightManager {
...
@@ -106,30 +117,33 @@ public class PlaywrightManagerImpl implements PlaywrightManager {
}
}
return
browser
;
return
browser
;
}
}
@Override
@Override
public
BrowserContext
getUserContext
(
String
userId
)
{
public
BrowserContext
getUserContext
(
String
userId
)
{
lazyInitialize
();
lazyInitialize
();
Browser
.
NewContextOptions
options
=
new
Browser
.
NewContextOptions
()
Browser
.
NewContextOptions
options
=
new
Browser
.
NewContextOptions
()
.
setViewportSize
(
1920
,
1080
)
// 设置视口大小为全高清分辨率,适用于Windows 11桌面环境
.
setViewportSize
(
1920
,
1080
)
// 设置视口大小为全高清分辨率,适用于Windows 11桌面环境
.
setUserAgent
(
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36"
);
// 设置用户代理为Windows 11 Chrome浏览器
.
setUserAgent
(
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36"
);
// 设置用户代理为Windows
// 11
// Chrome浏览器
return
getUserContext
(
userId
,
options
);
return
getUserContext
(
userId
,
options
);
}
}
@Override
@Override
public
BrowserContext
getUserContext
(
String
userId
,
Browser
.
NewContextOptions
options
)
{
public
BrowserContext
getUserContext
(
String
userId
,
Browser
.
NewContextOptions
options
)
{
lazyInitialize
();
lazyInitialize
();
if
(
userId
==
null
||
userId
.
isEmpty
())
{
if
(
userId
==
null
||
userId
.
isEmpty
())
{
throw
new
IllegalArgumentException
(
"User ID cannot be null or empty"
);
throw
new
IllegalArgumentException
(
"User ID cannot be null or empty"
);
}
}
if
(
options
==
null
)
{
if
(
options
==
null
)
{
options
=
new
Browser
.
NewContextOptions
();
options
=
new
Browser
.
NewContextOptions
();
}
}
// 尝试从缓存中获取已存在的上下文
// 尝试从缓存中获取已存在的上下文
BrowserContext
context
=
userContexts
.
get
(
userId
);
BrowserContext
context
=
userContexts
.
get
(
userId
);
// 如果上下文不存在或已关闭,则创建新的
// 如果上下文不存在或已关闭,则创建新的
if
(
context
==
null
||
isContextClosed
(
context
))
{
if
(
context
==
null
||
isContextClosed
(
context
))
{
try
{
try
{
...
@@ -142,19 +156,19 @@ public class PlaywrightManagerImpl implements PlaywrightManager {
...
@@ -142,19 +156,19 @@ public class PlaywrightManagerImpl implements PlaywrightManager {
throw
new
RuntimeException
(
"Failed to create browser context for user: "
+
userId
,
e
);
throw
new
RuntimeException
(
"Failed to create browser context for user: "
+
userId
,
e
);
}
}
}
}
return
context
;
return
context
;
}
}
@Override
@Override
public
void
releaseUserContext
(
String
userId
)
{
public
void
releaseUserContext
(
String
userId
)
{
if
(
userId
==
null
||
userId
.
isEmpty
())
{
if
(
userId
==
null
||
userId
.
isEmpty
())
{
return
;
return
;
}
}
BrowserContext
context
=
userContexts
.
remove
(
userId
);
BrowserContext
context
=
userContexts
.
remove
(
userId
);
contextCreationTimes
.
remove
(
userId
);
contextCreationTimes
.
remove
(
userId
);
if
(
context
!=
null
)
{
if
(
context
!=
null
)
{
try
{
try
{
context
.
close
();
context
.
close
();
...
@@ -164,7 +178,7 @@ public class PlaywrightManagerImpl implements PlaywrightManager {
...
@@ -164,7 +178,7 @@ public class PlaywrightManagerImpl implements PlaywrightManager {
}
}
}
}
}
}
/**
/**
* 检查BrowserContext是否已关闭
* 检查BrowserContext是否已关闭
*
*
...
@@ -182,14 +196,14 @@ public class PlaywrightManagerImpl implements PlaywrightManager {
...
@@ -182,14 +196,14 @@ public class PlaywrightManagerImpl implements PlaywrightManager {
return
true
;
return
true
;
}
}
}
}
/**
/**
* 清理过期的用户上下文
* 清理过期的用户上下文
*/
*/
private
void
cleanupExpiredContexts
()
{
private
void
cleanupExpiredContexts
()
{
long
currentTime
=
System
.
currentTimeMillis
();
long
currentTime
=
System
.
currentTimeMillis
();
long
expiredThreshold
=
currentTime
-
CONTEXT_TIMEOUT
;
long
expiredThreshold
=
currentTime
-
CONTEXT_TIMEOUT
;
for
(
String
userId
:
contextCreationTimes
.
keySet
())
{
for
(
String
userId
:
contextCreationTimes
.
keySet
())
{
Long
creationTime
=
contextCreationTimes
.
get
(
userId
);
Long
creationTime
=
contextCreationTimes
.
get
(
userId
);
if
(
creationTime
!=
null
&&
creationTime
<
expiredThreshold
)
{
if
(
creationTime
!=
null
&&
creationTime
<
expiredThreshold
)
{
...
@@ -198,7 +212,7 @@ public class PlaywrightManagerImpl implements PlaywrightManager {
...
@@ -198,7 +212,7 @@ public class PlaywrightManagerImpl implements PlaywrightManager {
}
}
}
}
}
}
/**
/**
* 销毁所有资源
* 销毁所有资源
*/
*/
...
@@ -206,7 +220,7 @@ public class PlaywrightManagerImpl implements PlaywrightManager {
...
@@ -206,7 +220,7 @@ public class PlaywrightManagerImpl implements PlaywrightManager {
@Override
@Override
public
void
destroy
()
{
public
void
destroy
()
{
log
.
info
(
"开始销毁Playwright管理器资源..."
);
log
.
info
(
"开始销毁Playwright管理器资源..."
);
try
{
try
{
// 关闭清理任务调度器
// 关闭清理任务调度器
cleanupScheduler
.
shutdown
();
cleanupScheduler
.
shutdown
();
...
@@ -216,12 +230,12 @@ public class PlaywrightManagerImpl implements PlaywrightManager {
...
@@ -216,12 +230,12 @@ public class PlaywrightManagerImpl implements PlaywrightManager {
}
catch
(
Exception
e
)
{
}
catch
(
Exception
e
)
{
log
.
warn
(
"关闭清理任务调度器时发生异常"
,
e
);
log
.
warn
(
"关闭清理任务调度器时发生异常"
,
e
);
}
}
// 关闭所有用户上下文
// 关闭所有用户上下文
for
(
String
userId
:
userContexts
.
keySet
())
{
for
(
String
userId
:
userContexts
.
keySet
())
{
releaseUserContext
(
userId
);
releaseUserContext
(
userId
);
}
}
// 关闭浏览器
// 关闭浏览器
try
{
try
{
if
(
browser
!=
null
&&
browser
.
isConnected
())
{
if
(
browser
!=
null
&&
browser
.
isConnected
())
{
...
@@ -231,7 +245,7 @@ public class PlaywrightManagerImpl implements PlaywrightManager {
...
@@ -231,7 +245,7 @@ public class PlaywrightManagerImpl implements PlaywrightManager {
}
catch
(
Exception
e
)
{
}
catch
(
Exception
e
)
{
log
.
warn
(
"关闭浏览器实例时发生异常"
,
e
);
log
.
warn
(
"关闭浏览器实例时发生异常"
,
e
);
}
}
// 关闭Playwright
// 关闭Playwright
try
{
try
{
if
(
playwright
!=
null
)
{
if
(
playwright
!=
null
)
{
...
@@ -241,7 +255,7 @@ public class PlaywrightManagerImpl implements PlaywrightManager {
...
@@ -241,7 +255,7 @@ public class PlaywrightManagerImpl implements PlaywrightManager {
}
catch
(
Exception
e
)
{
}
catch
(
Exception
e
)
{
log
.
warn
(
"关闭Playwright实例时发生异常"
,
e
);
log
.
warn
(
"关闭Playwright实例时发生异常"
,
e
);
}
}
log
.
info
(
"Playwright管理器资源已全部销毁"
);
log
.
info
(
"Playwright管理器资源已全部销毁"
);
}
}
}
}
\ No newline at end of file
backend/src/main/resources/application-dev.yml
View file @
2a026455
...
@@ -2,10 +2,10 @@
...
@@ -2,10 +2,10 @@
spring
:
spring
:
# 开发环境数据源配置
# 开发环境数据源配置
datasource
:
datasource
:
url
:
jdbc:
h2:file:./data/hiagent_dev_db;DB_CLOSE_ON_EXIT=FALSE
url
:
jdbc:
mysql://${DB_HOST:127.0.0.1}:3306/hiagent?allowMultiQueries=true&allowPublicKeyRetrieval=true&useSSL=false&serverTimezone=Asia/Shanghai
driver-class-name
:
org.h2.Driver
driver-class-name
:
${DB_DRIVER:com.mysql.cj.jdbc.Driver}
username
:
sa
username
:
${DB_NAME:root}
password
:
sa
password
:
${DB_PASSWORD:password}
# 开发环境JPA配置
# 开发环境JPA配置
jpa
:
jpa
:
...
@@ -21,7 +21,7 @@ spring:
...
@@ -21,7 +21,7 @@ spring:
init
:
init
:
schema-locations
:
classpath:schema.sql
schema-locations
:
classpath:schema.sql
data-locations
:
classpath:data.sql
data-locations
:
classpath:data.sql
mode
:
always
# 总是执行创建表和数据脚本,实现重新初始化
mode
:
never
# 总是执行创建表和数据脚本,实现重新初始化
# 开启H2控制台
# 开启H2控制台
h2
:
h2
:
...
...
frontend/vite.config.ts
View file @
2a026455
/*
* @Date: 2025-12-29 14:42:27
* @LastEditors: wangduo3 wangduo3@hisense.com
* @LastEditTime: 2025-12-29 16:56:52
* @FilePath: /pangea-agent/frontend/vite.config.ts
*/
import
{
defineConfig
}
from
"vite"
;
import
{
defineConfig
}
from
"vite"
;
import
vue
from
"@vitejs/plugin-vue"
;
import
vue
from
"@vitejs/plugin-vue"
;
import
path
from
"path"
;
import
path
from
"path"
;
...
@@ -30,14 +36,14 @@ export default defineConfig({
...
@@ -30,14 +36,14 @@ export default defineConfig({
},
},
proxy
:
{
proxy
:
{
"/api"
:
{
"/api"
:
{
//
target: "http://localhost:8080",
target
:
"http://localhost:8080"
,
target
:
"http://agent-backend.clouddev.hisense.com"
,
//
target: "http://agent-backend.clouddev.hisense.com",
changeOrigin
:
true
,
changeOrigin
:
true
,
rewrite
:
(
path
)
=>
path
.
replace
(
/^
\/
api/
,
"/api"
),
rewrite
:
(
path
)
=>
path
.
replace
(
/^
\/
api/
,
"/api"
),
},
},
"/ws"
:
{
"/ws"
:
{
//
target: "http://localhost:8080",
target
:
"http://localhost:8080"
,
target
:
"http://agent-backend.clouddev.hisense.com"
,
//
target: "http://agent-backend.clouddev.hisense.com",
ws
:
true
,
// 启用WebSocket代理
ws
:
true
,
// 启用WebSocket代理
changeOrigin
:
true
,
changeOrigin
:
true
,
},
},
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment