From 8fb7d476b8fe10e74cdc2784029c0e8b88e33118 Mon Sep 17 00:00:00 2001 From: QTom Date: Sun, 1 Mar 2026 21:06:53 +0800 Subject: [PATCH] =?UTF-8?q?feat(admin):=20=E4=BB=A3=E7=90=86=E5=AF=86?= =?UTF-8?q?=E7=A0=81=E5=8F=AF=E8=A7=81=E6=80=A7=20+=20=E5=A4=8D=E5=88=B6?= =?UTF-8?q?=E4=BB=A3=E7=90=86=20URL=20=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 AdminProxy / AdminProxyWithAccountCount DTO,遵循项目 Admin DTO 分层模式 - Proxy.Password 恢复 json:"-" 隐藏,ProxyFromService 不再赋值密码(纵深防御) - 管理员接口使用 ProxyFromServiceAdmin / ProxyWithAccountCountFromServiceAdmin - 前端代理列表新增 Auth 列:显示用户名 + 掩码密码 + 眼睛图标切换可见性 - Address 列新增复制按钮:左键复制完整 URL,右键选择格式 - 编辑模态框密码预填充 + 脏标记,避免误更新 --- .../internal/handler/admin/proxy_handler.go | 18 +- backend/internal/handler/dto/mappers.go | 46 ++++- backend/internal/handler/dto/types.go | 26 +++ frontend/src/i18n/locales/en.ts | 3 + frontend/src/i18n/locales/zh.ts | 3 + frontend/src/views/admin/ProxiesView.vue | 162 ++++++++++++++++-- 6 files changed, 230 insertions(+), 28 deletions(-) diff --git a/backend/internal/handler/admin/proxy_handler.go b/backend/internal/handler/admin/proxy_handler.go index 9fd187fc..e8ae0ce2 100644 --- a/backend/internal/handler/admin/proxy_handler.go +++ b/backend/internal/handler/admin/proxy_handler.go @@ -64,9 +64,9 @@ func (h *ProxyHandler) List(c *gin.Context) { return } - out := make([]dto.ProxyWithAccountCount, 0, len(proxies)) + out := make([]dto.AdminProxyWithAccountCount, 0, len(proxies)) for i := range proxies { - out = append(out, *dto.ProxyWithAccountCountFromService(&proxies[i])) + out = append(out, *dto.ProxyWithAccountCountFromServiceAdmin(&proxies[i])) } response.Paginated(c, out, total, page, pageSize) } @@ -83,9 +83,9 @@ func (h *ProxyHandler) GetAll(c *gin.Context) { response.ErrorFrom(c, err) return } - out := make([]dto.ProxyWithAccountCount, 0, len(proxies)) + out := make([]dto.AdminProxyWithAccountCount, 0, len(proxies)) for i := range proxies { - out = append(out, *dto.ProxyWithAccountCountFromService(&proxies[i])) + out = append(out, *dto.ProxyWithAccountCountFromServiceAdmin(&proxies[i])) } response.Success(c, out) return @@ -97,9 +97,9 @@ func (h *ProxyHandler) GetAll(c *gin.Context) { return } - out := make([]dto.Proxy, 0, len(proxies)) + out := make([]dto.AdminProxy, 0, len(proxies)) for i := range proxies { - out = append(out, *dto.ProxyFromService(&proxies[i])) + out = append(out, *dto.ProxyFromServiceAdmin(&proxies[i])) } response.Success(c, out) } @@ -119,7 +119,7 @@ func (h *ProxyHandler) GetByID(c *gin.Context) { return } - response.Success(c, dto.ProxyFromService(proxy)) + response.Success(c, dto.ProxyFromServiceAdmin(proxy)) } // Create handles creating a new proxy @@ -143,7 +143,7 @@ func (h *ProxyHandler) Create(c *gin.Context) { if err != nil { return nil, err } - return dto.ProxyFromService(proxy), nil + return dto.ProxyFromServiceAdmin(proxy), nil }) } @@ -176,7 +176,7 @@ func (h *ProxyHandler) Update(c *gin.Context) { return } - response.Success(c, dto.ProxyFromService(proxy)) + response.Success(c, dto.ProxyFromServiceAdmin(proxy)) } // Delete handles deleting a proxy diff --git a/backend/internal/handler/dto/mappers.go b/backend/internal/handler/dto/mappers.go index d811c7be..f8298067 100644 --- a/backend/internal/handler/dto/mappers.go +++ b/backend/internal/handler/dto/mappers.go @@ -293,7 +293,6 @@ func ProxyFromService(p *service.Proxy) *Proxy { Host: p.Host, Port: p.Port, Username: p.Username, - Password: p.Password, Status: p.Status, CreatedAt: p.CreatedAt, UpdatedAt: p.UpdatedAt, @@ -323,6 +322,51 @@ func ProxyWithAccountCountFromService(p *service.ProxyWithAccountCount) *ProxyWi } } +// ProxyFromServiceAdmin converts a service Proxy to AdminProxy DTO for admin users. +// It includes the password field - user-facing endpoints must not use this. +func ProxyFromServiceAdmin(p *service.Proxy) *AdminProxy { + if p == nil { + return nil + } + base := ProxyFromService(p) + if base == nil { + return nil + } + return &AdminProxy{ + Proxy: *base, + Password: p.Password, + } +} + +// ProxyWithAccountCountFromServiceAdmin converts a service ProxyWithAccountCount to AdminProxyWithAccountCount DTO. +// It includes the password field - user-facing endpoints must not use this. +func ProxyWithAccountCountFromServiceAdmin(p *service.ProxyWithAccountCount) *AdminProxyWithAccountCount { + if p == nil { + return nil + } + admin := ProxyFromServiceAdmin(&p.Proxy) + if admin == nil { + return nil + } + return &AdminProxyWithAccountCount{ + AdminProxy: *admin, + AccountCount: p.AccountCount, + LatencyMs: p.LatencyMs, + LatencyStatus: p.LatencyStatus, + LatencyMessage: p.LatencyMessage, + IPAddress: p.IPAddress, + Country: p.Country, + CountryCode: p.CountryCode, + Region: p.Region, + City: p.City, + QualityStatus: p.QualityStatus, + QualityScore: p.QualityScore, + QualityGrade: p.QualityGrade, + QualitySummary: p.QualitySummary, + QualityChecked: p.QualityChecked, + } +} + func ProxyAccountSummaryFromService(a *service.ProxyAccountSummary) *ProxyAccountSummary { if a == nil { return nil diff --git a/backend/internal/handler/dto/types.go b/backend/internal/handler/dto/types.go index c575c232..b5c0640f 100644 --- a/backend/internal/handler/dto/types.go +++ b/backend/internal/handler/dto/types.go @@ -221,6 +221,32 @@ type ProxyWithAccountCount struct { QualityChecked *int64 `json:"quality_checked,omitempty"` } +// AdminProxy 是管理员接口使用的 proxy DTO(包含密码等敏感字段)。 +// 注意:普通接口不得使用此 DTO。 +type AdminProxy struct { + Proxy + Password string `json:"password,omitempty"` +} + +// AdminProxyWithAccountCount 是管理员接口使用的带账号统计的 proxy DTO。 +type AdminProxyWithAccountCount struct { + AdminProxy + AccountCount int64 `json:"account_count"` + LatencyMs *int64 `json:"latency_ms,omitempty"` + LatencyStatus string `json:"latency_status,omitempty"` + LatencyMessage string `json:"latency_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"` + QualityChecked *int64 `json:"quality_checked,omitempty"` +} + type ProxyAccountSummary struct { ID int64 `json:"id"` Name string `json:"name"` diff --git a/frontend/src/i18n/locales/en.ts b/frontend/src/i18n/locales/en.ts index bd034c57..ddb63d75 100644 --- a/frontend/src/i18n/locales/en.ts +++ b/frontend/src/i18n/locales/en.ts @@ -2345,6 +2345,8 @@ export default { dataExportConfirm: 'Confirm Export', dataExported: 'Data exported successfully', dataExportFailed: 'Failed to export data', + copyProxyUrl: 'Copy Proxy URL', + urlCopied: 'Proxy URL copied', searchProxies: 'Search proxies...', allProtocols: 'All Protocols', allStatus: 'All Status', @@ -2358,6 +2360,7 @@ export default { name: 'Name', protocol: 'Protocol', address: 'Address', + auth: 'Auth', location: 'Location', status: 'Status', accounts: 'Accounts', diff --git a/frontend/src/i18n/locales/zh.ts b/frontend/src/i18n/locales/zh.ts index 0bace3f5..5151001e 100644 --- a/frontend/src/i18n/locales/zh.ts +++ b/frontend/src/i18n/locales/zh.ts @@ -2459,6 +2459,7 @@ export default { name: '名称', protocol: '协议', address: '地址', + auth: '认证', location: '地理位置', status: '状态', accounts: '账号数', @@ -2486,6 +2487,8 @@ export default { allStatuses: '全部状态' }, // Additional keys used in ProxiesView + copyProxyUrl: '复制代理 URL', + urlCopied: '代理 URL 已复制', allProtocols: '全部协议', allStatus: '全部状态', searchProxies: '搜索代理...', diff --git a/frontend/src/views/admin/ProxiesView.vue b/frontend/src/views/admin/ProxiesView.vue index 23d73109..147b3205 100644 --- a/frontend/src/views/admin/ProxiesView.vue +++ b/frontend/src/views/admin/ProxiesView.vue @@ -124,7 +124,54 @@ + +