Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
Menu
Open sidebar
陈曦
sub2api
Commits
d14c24bb
Commit
d14c24bb
authored
Feb 20, 2026
by
yangjianbo
Browse files
feat(proxy): 持久化质量检测结果并在列表展示
parent
48dc011b
Changes
9
Hide whitespace changes
Inline
Side-by-side
backend/internal/handler/dto/mappers.go
View file @
d14c24bb
...
...
@@ -303,6 +303,11 @@ func ProxyWithAccountCountFromService(p *service.ProxyWithAccountCount) *ProxyWi
CountryCode
:
p
.
CountryCode
,
Region
:
p
.
Region
,
City
:
p
.
City
,
QualityStatus
:
p
.
QualityStatus
,
QualityScore
:
p
.
QualityScore
,
QualityGrade
:
p
.
QualityGrade
,
QualitySummary
:
p
.
QualitySummary
,
QualityChecked
:
p
.
QualityChecked
,
}
}
...
...
backend/internal/handler/dto/types.go
View file @
d14c24bb
...
...
@@ -202,6 +202,11 @@ type ProxyWithAccountCount struct {
CountryCode
string
`json:"country_code,omitempty"`
Region
string
`json:"region,omitempty"`
City
string
`json:"city,omitempty"`
QualityStatus
string
`json:"quality_status,omitempty"`
QualityScore
*
int
`json:"quality_score,omitempty"`
QualityGrade
string
`json:"quality_grade,omitempty"`
QualitySummary
string
`json:"quality_summary,omitempty"`
QualityChecked
*
int64
`json:"quality_checked,omitempty"`
}
type
ProxyAccountSummary
struct
{
...
...
backend/internal/service/admin_service.go
View file @
d14c24bb
...
...
@@ -1796,6 +1796,7 @@ func (s *adminServiceImpl) CheckProxyQuality(ctx context.Context, id int64) (*Pr
})
result
.
FailedCount
++
finalizeProxyQualityResult
(
result
)
s
.
saveProxyQualitySnapshot
(
ctx
,
id
,
result
,
nil
)
return
result
,
nil
}
...
...
@@ -1809,6 +1810,7 @@ func (s *adminServiceImpl) CheckProxyQuality(ctx context.Context, id int64) (*Pr
})
result
.
FailedCount
++
finalizeProxyQualityResult
(
result
)
s
.
saveProxyQualitySnapshot
(
ctx
,
id
,
result
,
nil
)
return
result
,
nil
}
...
...
@@ -1838,6 +1840,7 @@ func (s *adminServiceImpl) CheckProxyQuality(ctx context.Context, id int64) (*Pr
})
result
.
FailedCount
++
finalizeProxyQualityResult
(
result
)
s
.
saveProxyQualitySnapshot
(
ctx
,
id
,
result
,
exitInfo
)
return
result
,
nil
}
...
...
@@ -1857,6 +1860,7 @@ func (s *adminServiceImpl) CheckProxyQuality(ctx context.Context, id int64) (*Pr
}
finalizeProxyQualityResult
(
result
)
s
.
saveProxyQualitySnapshot
(
ctx
,
id
,
result
,
exitInfo
)
return
result
,
nil
}
...
...
@@ -1954,6 +1958,80 @@ func proxyQualityGrade(score int) string {
}
}
func
proxyQualityOverallStatus
(
result
*
ProxyQualityCheckResult
)
string
{
if
result
==
nil
{
return
""
}
if
result
.
ChallengeCount
>
0
{
return
"challenge"
}
if
result
.
FailedCount
>
0
{
return
"failed"
}
if
result
.
WarnCount
>
0
{
return
"warn"
}
if
result
.
PassedCount
>
0
{
return
"healthy"
}
return
"failed"
}
func
proxyQualityFirstCFRay
(
result
*
ProxyQualityCheckResult
)
string
{
if
result
==
nil
{
return
""
}
for
_
,
item
:=
range
result
.
Items
{
if
item
.
CFRay
!=
""
{
return
item
.
CFRay
}
}
return
""
}
func
proxyQualityBaseConnectivityPass
(
result
*
ProxyQualityCheckResult
)
bool
{
if
result
==
nil
{
return
false
}
for
_
,
item
:=
range
result
.
Items
{
if
item
.
Target
==
"base_connectivity"
{
return
item
.
Status
==
"pass"
}
}
return
false
}
func
(
s
*
adminServiceImpl
)
saveProxyQualitySnapshot
(
ctx
context
.
Context
,
proxyID
int64
,
result
*
ProxyQualityCheckResult
,
exitInfo
*
ProxyExitInfo
)
{
if
result
==
nil
{
return
}
score
:=
result
.
Score
checkedAt
:=
result
.
CheckedAt
info
:=
&
ProxyLatencyInfo
{
Success
:
proxyQualityBaseConnectivityPass
(
result
),
Message
:
result
.
Summary
,
QualityStatus
:
proxyQualityOverallStatus
(
result
),
QualityScore
:
&
score
,
QualityGrade
:
result
.
Grade
,
QualitySummary
:
result
.
Summary
,
QualityCheckedAt
:
&
checkedAt
,
QualityCFRay
:
proxyQualityFirstCFRay
(
result
),
UpdatedAt
:
time
.
Now
(),
}
if
result
.
BaseLatencyMs
>
0
{
latency
:=
result
.
BaseLatencyMs
info
.
LatencyMs
=
&
latency
}
if
exitInfo
!=
nil
{
info
.
IPAddress
=
exitInfo
.
IP
info
.
Country
=
exitInfo
.
Country
info
.
CountryCode
=
exitInfo
.
CountryCode
info
.
Region
=
exitInfo
.
Region
info
.
City
=
exitInfo
.
City
}
s
.
saveProxyLatency
(
ctx
,
proxyID
,
info
)
}
func
(
s
*
adminServiceImpl
)
probeProxyLatency
(
ctx
context
.
Context
,
proxy
*
Proxy
)
{
if
s
.
proxyProber
==
nil
||
proxy
==
nil
{
return
...
...
@@ -2064,6 +2142,11 @@ func (s *adminServiceImpl) attachProxyLatency(ctx context.Context, proxies []Pro
proxies
[
i
]
.
CountryCode
=
info
.
CountryCode
proxies
[
i
]
.
Region
=
info
.
Region
proxies
[
i
]
.
City
=
info
.
City
proxies
[
i
]
.
QualityStatus
=
info
.
QualityStatus
proxies
[
i
]
.
QualityScore
=
info
.
QualityScore
proxies
[
i
]
.
QualityGrade
=
info
.
QualityGrade
proxies
[
i
]
.
QualitySummary
=
info
.
QualitySummary
proxies
[
i
]
.
QualityChecked
=
info
.
QualityCheckedAt
}
}
...
...
@@ -2071,7 +2154,27 @@ func (s *adminServiceImpl) saveProxyLatency(ctx context.Context, proxyID int64,
if
s
.
proxyLatencyCache
==
nil
||
info
==
nil
{
return
}
if
err
:=
s
.
proxyLatencyCache
.
SetProxyLatency
(
ctx
,
proxyID
,
info
);
err
!=
nil
{
merged
:=
*
info
if
latencies
,
err
:=
s
.
proxyLatencyCache
.
GetProxyLatencies
(
ctx
,
[]
int64
{
proxyID
});
err
==
nil
{
if
existing
:=
latencies
[
proxyID
];
existing
!=
nil
{
if
merged
.
QualityCheckedAt
==
nil
&&
merged
.
QualityScore
==
nil
&&
merged
.
QualityGrade
==
""
&&
merged
.
QualityStatus
==
""
&&
merged
.
QualitySummary
==
""
&&
merged
.
QualityCFRay
==
""
{
merged
.
QualityStatus
=
existing
.
QualityStatus
merged
.
QualityScore
=
existing
.
QualityScore
merged
.
QualityGrade
=
existing
.
QualityGrade
merged
.
QualitySummary
=
existing
.
QualitySummary
merged
.
QualityCheckedAt
=
existing
.
QualityCheckedAt
merged
.
QualityCFRay
=
existing
.
QualityCFRay
}
}
}
if
err
:=
s
.
proxyLatencyCache
.
SetProxyLatency
(
ctx
,
proxyID
,
&
merged
);
err
!=
nil
{
logger
.
LegacyPrintf
(
"service.admin"
,
"Warning: store proxy latency cache failed: %v"
,
err
)
}
}
...
...
backend/internal/service/proxy.go
View file @
d14c24bb
...
...
@@ -40,6 +40,11 @@ type ProxyWithAccountCount struct {
CountryCode
string
Region
string
City
string
QualityStatus
string
QualityScore
*
int
QualityGrade
string
QualitySummary
string
QualityChecked
*
int64
}
type
ProxyAccountSummary
struct
{
...
...
backend/internal/service/proxy_latency_cache.go
View file @
d14c24bb
...
...
@@ -6,15 +6,21 @@ import (
)
type
ProxyLatencyInfo
struct
{
Success
bool
`json:"success"`
LatencyMs
*
int64
`json:"latency_ms,omitempty"`
Message
string
`json:"message,omitempty"`
IPAddress
string
`json:"ip_address,omitempty"`
Country
string
`json:"country,omitempty"`
CountryCode
string
`json:"country_code,omitempty"`
Region
string
`json:"region,omitempty"`
City
string
`json:"city,omitempty"`
UpdatedAt
time
.
Time
`json:"updated_at"`
Success
bool
`json:"success"`
LatencyMs
*
int64
`json:"latency_ms,omitempty"`
Message
string
`json:"message,omitempty"`
IPAddress
string
`json:"ip_address,omitempty"`
Country
string
`json:"country,omitempty"`
CountryCode
string
`json:"country_code,omitempty"`
Region
string
`json:"region,omitempty"`
City
string
`json:"city,omitempty"`
QualityStatus
string
`json:"quality_status,omitempty"`
QualityScore
*
int
`json:"quality_score,omitempty"`
QualityGrade
string
`json:"quality_grade,omitempty"`
QualitySummary
string
`json:"quality_summary,omitempty"`
QualityCheckedAt
*
int64
`json:"quality_checked_at,omitempty"`
QualityCFRay
string
`json:"quality_cf_ray,omitempty"`
UpdatedAt
time
.
Time
`json:"updated_at"`
}
type
ProxyLatencyCache
interface
{
...
...
frontend/src/i18n/locales/en.ts
View file @
d14c24bb
...
...
@@ -2181,6 +2181,8 @@ export default {
qualityTableStatus
:
'
Status
'
,
qualityTableLatency
:
'
Latency
'
,
qualityTableMessage
:
'
Message
'
,
qualityInline
:
'
Quality {grade}/{score}
'
,
qualityStatusHealthy
:
'
Healthy
'
,
qualityStatusPass
:
'
Pass
'
,
qualityStatusWarn
:
'
Warn
'
,
qualityStatusFail
:
'
Fail
'
,
...
...
frontend/src/i18n/locales/zh.ts
View file @
d14c24bb
...
...
@@ -2310,6 +2310,8 @@ export default {
qualityTableStatus
:
'
状态
'
,
qualityTableLatency
:
'
延迟
'
,
qualityTableMessage
:
'
说明
'
,
qualityInline
:
'
质量 {grade}/{score}
'
,
qualityStatusHealthy
:
'
优质
'
,
qualityStatusPass
:
'
通过
'
,
qualityStatusWarn
:
'
告警
'
,
qualityStatusFail
:
'
失败
'
,
...
...
frontend/src/types/index.ts
View file @
d14c24bb
...
...
@@ -512,6 +512,11 @@ export interface Proxy {
country_code
?:
string
region
?:
string
city
?:
string
quality_status
?:
'
healthy
'
|
'
warn
'
|
'
challenge
'
|
'
failed
'
quality_score
?:
number
quality_grade
?:
string
quality_summary
?:
string
quality_checked
?:
number
created_at
:
string
updated_at
:
string
}
...
...
frontend/src/views/admin/ProxiesView.vue
View file @
d14c24bb
...
...
@@ -160,20 +160,32 @@
<
/template
>
<
template
#
cell
-
latency
=
"
{ row
}
"
>
<
span
v
-
if
=
"
row.latency_status === 'failed'
"
class
=
"
badge badge-danger
"
:
title
=
"
row.latency_message || undefined
"
>
{{
t
(
'
admin.proxies.latencyFailed
'
)
}}
<
/span
>
<
span
v
-
else
-
if
=
"
typeof row.latency_ms === 'number'
"
:
class
=
"
['badge', row.latency_ms < 200 ? 'badge-success' : 'badge-warning']
"
>
{{
row
.
latency_ms
}}
ms
<
/span
>
<
span
v
-
else
class
=
"
text-sm text-gray-400
"
>-<
/span
>
<
div
class
=
"
flex flex-col gap-1
"
>
<
span
v
-
if
=
"
row.latency_status === 'failed'
"
class
=
"
badge badge-danger
"
:
title
=
"
row.latency_message || undefined
"
>
{{
t
(
'
admin.proxies.latencyFailed
'
)
}}
<
/span
>
<
span
v
-
else
-
if
=
"
typeof row.latency_ms === 'number'
"
:
class
=
"
['badge', row.latency_ms < 200 ? 'badge-success' : 'badge-warning']
"
>
{{
row
.
latency_ms
}}
ms
<
/span
>
<
span
v
-
else
class
=
"
text-sm text-gray-400
"
>-<
/span
>
<
div
v
-
if
=
"
typeof row.quality_checked === 'number'
"
class
=
"
flex items-center gap-1 text-xs text-gray-500 dark:text-gray-400
"
:
title
=
"
row.quality_summary || undefined
"
>
<
span
>
{{
t
(
'
admin.proxies.qualityInline
'
,
{
grade
:
row
.
quality_grade
||
'
-
'
,
score
:
row
.
quality_score
??
'
-
'
}
)
}}
<
/span
>
<
span
class
=
"
badge
"
:
class
=
"
qualityOverallClass(row.quality_status)
"
>
{{
qualityOverallLabel
(
row
.
quality_status
)
}}
<
/span
>
<
/div
>
<
/div
>
<
/template
>
<
template
#
cell
-
status
=
"
{ value
}
"
>
...
...
@@ -1250,6 +1262,23 @@ const applyLatencyResult = (
target
.
latency_message
=
result
.
message
}
const
summarizeQualityStatus
=
(
result
:
ProxyQualityCheckResult
):
Proxy
[
'
quality_status
'
]
=>
{
if
(
result
.
challenge_count
>
0
)
return
'
challenge
'
if
(
result
.
failed_count
>
0
)
return
'
failed
'
if
(
result
.
warn_count
>
0
)
return
'
warn
'
return
'
healthy
'
}
const
applyQualityResult
=
(
proxyId
:
number
,
result
:
ProxyQualityCheckResult
)
=>
{
const
target
=
proxies
.
value
.
find
((
proxy
)
=>
proxy
.
id
===
proxyId
)
if
(
!
target
)
return
target
.
quality_status
=
summarizeQualityStatus
(
result
)
target
.
quality_score
=
result
.
score
target
.
quality_grade
=
result
.
grade
target
.
quality_summary
=
result
.
summary
target
.
quality_checked
=
result
.
checked_at
}
const
formatLocation
=
(
proxy
:
Proxy
)
=>
{
const
parts
=
[
proxy
.
country
,
proxy
.
city
].
filter
(
Boolean
)
as
string
[]
return
parts
.
join
(
'
·
'
)
...
...
@@ -1330,6 +1359,7 @@ const handleQualityCheck = async (proxy: Proxy) => {
country_code
:
result
.
country_code
}
)
}
applyQualityResult
(
proxy
.
id
,
result
)
appStore
.
showSuccess
(
t
(
'
admin.proxies.qualityCheckDone
'
,
{
score
:
result
.
score
,
grade
:
result
.
grade
}
)
...
...
@@ -1374,6 +1404,7 @@ const runBatchProxyQualityChecks = async (ids: number[]) => {
}
)
}
}
applyQualityResult
(
current
,
result
)
if
(
result
.
challenge_count
>
0
)
{
challenge
++
}
else
if
(
result
.
failed_count
>
0
)
{
...
...
@@ -1422,6 +1453,20 @@ const qualityStatusLabel = (status: string) => {
return
t
(
'
admin.proxies.qualityStatusFail
'
)
}
const
qualityOverallClass
=
(
status
?:
string
)
=>
{
if
(
status
===
'
healthy
'
)
return
'
badge-success
'
if
(
status
===
'
warn
'
)
return
'
badge-warning
'
if
(
status
===
'
challenge
'
)
return
'
badge-danger
'
return
'
badge-danger
'
}
const
qualityOverallLabel
=
(
status
?:
string
)
=>
{
if
(
status
===
'
healthy
'
)
return
t
(
'
admin.proxies.qualityStatusHealthy
'
)
if
(
status
===
'
warn
'
)
return
t
(
'
admin.proxies.qualityStatusWarn
'
)
if
(
status
===
'
challenge
'
)
return
t
(
'
admin.proxies.qualityStatusChallenge
'
)
return
t
(
'
admin.proxies.qualityStatusFail
'
)
}
const
qualityTargetLabel
=
(
target
:
string
)
=>
{
switch
(
target
)
{
case
'
base_connectivity
'
:
...
...
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