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
64795a03
"vscode:/vscode.git/clone" did not exist on "195e227c04cba30e76169a3e0095a34d9429fba0"
Commit
64795a03
authored
Jan 20, 2026
by
song
Browse files
新增账号凭证邮箱查询接口
parent
86d63f91
Changes
9
Show whitespace changes
Inline
Side-by-side
backend/internal/handler/admin/account_handler.go
View file @
64795a03
...
@@ -129,6 +129,13 @@ type BulkUpdateAccountsRequest struct {
...
@@ -129,6 +129,13 @@ type BulkUpdateAccountsRequest struct {
ConfirmMixedChannelRisk
*
bool
`json:"confirm_mixed_channel_risk"`
// 用户确认混合渠道风险
ConfirmMixedChannelRisk
*
bool
`json:"confirm_mixed_channel_risk"`
// 用户确认混合渠道风险
}
}
// AccountLookupRequest 用于凭证身份信息查找账号
type
AccountLookupRequest
struct
{
Platform
string
`json:"platform" binding:"required"`
Emails
[]
string
`json:"emails" binding:"required,min=1"`
IdentityType
string
`json:"identity_type"`
}
// AccountWithConcurrency extends Account with real-time concurrency info
// AccountWithConcurrency extends Account with real-time concurrency info
type
AccountWithConcurrency
struct
{
type
AccountWithConcurrency
struct
{
*
dto
.
Account
*
dto
.
Account
...
@@ -258,6 +265,87 @@ func (h *AccountHandler) List(c *gin.Context) {
...
@@ -258,6 +265,87 @@ func (h *AccountHandler) List(c *gin.Context) {
response
.
Paginated
(
c
,
result
,
total
,
page
,
pageSize
)
response
.
Paginated
(
c
,
result
,
total
,
page
,
pageSize
)
}
}
// Lookup 根据凭证身份信息查找账号
// POST /api/v1/admin/accounts/lookup
func
(
h
*
AccountHandler
)
Lookup
(
c
*
gin
.
Context
)
{
var
req
AccountLookupRequest
if
err
:=
c
.
ShouldBindJSON
(
&
req
);
err
!=
nil
{
response
.
BadRequest
(
c
,
"Invalid request: "
+
err
.
Error
())
return
}
identityType
:=
strings
.
TrimSpace
(
req
.
IdentityType
)
if
identityType
==
""
{
identityType
=
"credential_email"
}
if
identityType
!=
"credential_email"
{
response
.
BadRequest
(
c
,
"Unsupported identity_type"
)
return
}
platform
:=
strings
.
TrimSpace
(
req
.
Platform
)
if
platform
==
""
{
response
.
BadRequest
(
c
,
"Platform is required"
)
return
}
normalized
:=
make
([]
string
,
0
,
len
(
req
.
Emails
))
seen
:=
make
(
map
[
string
]
struct
{})
for
_
,
email
:=
range
req
.
Emails
{
cleaned
:=
strings
.
ToLower
(
strings
.
TrimSpace
(
email
))
if
cleaned
==
""
{
continue
}
if
_
,
ok
:=
seen
[
cleaned
];
ok
{
continue
}
seen
[
cleaned
]
=
struct
{}{}
normalized
=
append
(
normalized
,
cleaned
)
}
if
len
(
normalized
)
==
0
{
response
.
BadRequest
(
c
,
"Emails is required"
)
return
}
accounts
,
err
:=
h
.
adminService
.
LookupAccountsByCredentialEmail
(
c
.
Request
.
Context
(),
platform
,
normalized
)
if
err
!=
nil
{
response
.
ErrorFrom
(
c
,
err
)
return
}
matchedMap
:=
make
(
map
[
string
]
service
.
Account
)
for
_
,
account
:=
range
accounts
{
email
:=
strings
.
ToLower
(
strings
.
TrimSpace
(
account
.
GetCredential
(
"email"
)))
if
email
==
""
{
continue
}
if
_
,
ok
:=
matchedMap
[
email
];
ok
{
continue
}
matchedMap
[
email
]
=
account
}
matched
:=
make
([]
gin
.
H
,
0
,
len
(
matchedMap
))
missing
:=
make
([]
string
,
0
)
for
_
,
email
:=
range
normalized
{
if
account
,
ok
:=
matchedMap
[
email
];
ok
{
matched
=
append
(
matched
,
gin
.
H
{
"email"
:
email
,
"account_id"
:
account
.
ID
,
"platform"
:
account
.
Platform
,
"name"
:
account
.
Name
,
})
continue
}
missing
=
append
(
missing
,
email
)
}
response
.
Success
(
c
,
gin
.
H
{
"matched"
:
matched
,
"missing"
:
missing
,
})
}
// GetByID handles getting an account by ID
// GetByID handles getting an account by ID
// GET /api/v1/admin/accounts/:id
// GET /api/v1/admin/accounts/:id
func
(
h
*
AccountHandler
)
GetByID
(
c
*
gin
.
Context
)
{
func
(
h
*
AccountHandler
)
GetByID
(
c
*
gin
.
Context
)
{
...
...
backend/internal/repository/account_repo.go
View file @
64795a03
...
@@ -473,6 +473,37 @@ func (r *accountRepository) ListByPlatform(ctx context.Context, platform string)
...
@@ -473,6 +473,37 @@ func (r *accountRepository) ListByPlatform(ctx context.Context, platform string)
return
r
.
accountsToService
(
ctx
,
accounts
)
return
r
.
accountsToService
(
ctx
,
accounts
)
}
}
func
(
r
*
accountRepository
)
ListByPlatformAndCredentialEmails
(
ctx
context
.
Context
,
platform
string
,
emails
[]
string
,
)
([]
service
.
Account
,
error
)
{
if
len
(
emails
)
==
0
{
return
[]
service
.
Account
{},
nil
}
args
:=
make
([]
any
,
0
,
len
(
emails
))
for
_
,
email
:=
range
emails
{
if
email
==
""
{
continue
}
args
=
append
(
args
,
email
)
}
if
len
(
args
)
==
0
{
return
[]
service
.
Account
{},
nil
}
accounts
,
err
:=
r
.
client
.
Account
.
Query
()
.
Where
(
dbaccount
.
PlatformEQ
(
platform
))
.
Where
(
func
(
s
*
entsql
.
Selector
)
{
s
.
Where
(
sqljson
.
ValueIn
(
dbaccount
.
FieldCredentials
,
args
,
sqljson
.
Path
(
"email"
)))
})
.
All
(
ctx
)
if
err
!=
nil
{
return
nil
,
err
}
return
r
.
accountsToService
(
ctx
,
accounts
)
}
func
(
r
*
accountRepository
)
UpdateLastUsed
(
ctx
context
.
Context
,
id
int64
)
error
{
func
(
r
*
accountRepository
)
UpdateLastUsed
(
ctx
context
.
Context
,
id
int64
)
error
{
now
:=
time
.
Now
()
now
:=
time
.
Now
()
_
,
err
:=
r
.
client
.
Account
.
Update
()
.
_
,
err
:=
r
.
client
.
Account
.
Update
()
.
...
...
backend/internal/server/api_contract_test.go
View file @
64795a03
...
@@ -11,6 +11,7 @@ import (
...
@@ -11,6 +11,7 @@ import (
"net/http"
"net/http"
"net/http/httptest"
"net/http/httptest"
"sort"
"sort"
"strings"
"testing"
"testing"
"time"
"time"
...
@@ -341,6 +342,44 @@ func TestAPIContracts(t *testing.T) {
...
@@ -341,6 +342,44 @@ func TestAPIContracts(t *testing.T) {
}
}
}`
,
}`
,
},
},
{
name
:
"POST /api/v1/admin/accounts/lookup"
,
setup
:
func
(
t
*
testing
.
T
,
deps
*
contractDeps
)
{
t
.
Helper
()
deps
.
accountRepo
.
lookupAccounts
=
[]
service
.
Account
{
{
ID
:
101
,
Name
:
"Alice Account"
,
Platform
:
"antigravity"
,
Credentials
:
map
[
string
]
any
{
"email"
:
"alice@example.com"
,
},
},
}
},
method
:
http
.
MethodPost
,
path
:
"/api/v1/admin/accounts/lookup"
,
body
:
`{"platform":"antigravity","emails":["Alice@Example.com","bob@example.com"]}`
,
headers
:
map
[
string
]
string
{
"Content-Type"
:
"application/json"
,
},
wantStatus
:
http
.
StatusOK
,
wantJSON
:
`{
"code": 0,
"message": "success",
"data": {
"matched": [
{
"email": "alice@example.com",
"account_id": 101,
"platform": "antigravity",
"name": "Alice Account"
}
],
"missing": ["bob@example.com"]
}
}`
,
},
{
{
name
:
"POST /api/v1/admin/accounts/bulk-update"
,
name
:
"POST /api/v1/admin/accounts/bulk-update"
,
method
:
http
.
MethodPost
,
method
:
http
.
MethodPost
,
...
@@ -387,6 +426,7 @@ type contractDeps struct {
...
@@ -387,6 +426,7 @@ type contractDeps struct {
apiKeyRepo
*
stubApiKeyRepo
apiKeyRepo
*
stubApiKeyRepo
usageRepo
*
stubUsageLogRepo
usageRepo
*
stubUsageLogRepo
settingRepo
*
stubSettingRepo
settingRepo
*
stubSettingRepo
accountRepo
*
stubAccountRepo
}
}
func
newContractDeps
(
t
*
testing
.
T
)
*
contractDeps
{
func
newContractDeps
(
t
*
testing
.
T
)
*
contractDeps
{
...
@@ -482,6 +522,7 @@ func newContractDeps(t *testing.T) *contractDeps {
...
@@ -482,6 +522,7 @@ func newContractDeps(t *testing.T) *contractDeps {
v1Admin
.
Use
(
adminAuth
)
v1Admin
.
Use
(
adminAuth
)
v1Admin
.
GET
(
"/settings"
,
adminSettingHandler
.
GetSettings
)
v1Admin
.
GET
(
"/settings"
,
adminSettingHandler
.
GetSettings
)
v1Admin
.
POST
(
"/accounts/bulk-update"
,
adminAccountHandler
.
BulkUpdate
)
v1Admin
.
POST
(
"/accounts/bulk-update"
,
adminAccountHandler
.
BulkUpdate
)
v1Admin
.
POST
(
"/accounts/lookup"
,
adminAccountHandler
.
Lookup
)
return
&
contractDeps
{
return
&
contractDeps
{
now
:
now
,
now
:
now
,
...
@@ -489,6 +530,7 @@ func newContractDeps(t *testing.T) *contractDeps {
...
@@ -489,6 +530,7 @@ func newContractDeps(t *testing.T) *contractDeps {
apiKeyRepo
:
apiKeyRepo
,
apiKeyRepo
:
apiKeyRepo
,
usageRepo
:
usageRepo
,
usageRepo
:
usageRepo
,
settingRepo
:
settingRepo
,
settingRepo
:
settingRepo
,
accountRepo
:
&
accountRepo
,
}
}
}
}
...
@@ -674,6 +716,7 @@ func (stubGroupRepo) DeleteAccountGroupsByGroupID(ctx context.Context, groupID i
...
@@ -674,6 +716,7 @@ func (stubGroupRepo) DeleteAccountGroupsByGroupID(ctx context.Context, groupID i
type
stubAccountRepo
struct
{
type
stubAccountRepo
struct
{
bulkUpdateIDs
[]
int64
bulkUpdateIDs
[]
int64
lookupAccounts
[]
service
.
Account
}
}
func
(
s
*
stubAccountRepo
)
Create
(
ctx
context
.
Context
,
account
*
service
.
Account
)
error
{
func
(
s
*
stubAccountRepo
)
Create
(
ctx
context
.
Context
,
account
*
service
.
Account
)
error
{
...
@@ -724,6 +767,36 @@ func (s *stubAccountRepo) ListByPlatform(ctx context.Context, platform string) (
...
@@ -724,6 +767,36 @@ func (s *stubAccountRepo) ListByPlatform(ctx context.Context, platform string) (
return
nil
,
errors
.
New
(
"not implemented"
)
return
nil
,
errors
.
New
(
"not implemented"
)
}
}
func
(
s
*
stubAccountRepo
)
ListByPlatformAndCredentialEmails
(
ctx
context
.
Context
,
platform
string
,
emails
[]
string
)
([]
service
.
Account
,
error
)
{
if
len
(
s
.
lookupAccounts
)
==
0
{
return
nil
,
nil
}
emailSet
:=
make
(
map
[
string
]
struct
{},
len
(
emails
))
for
_
,
email
:=
range
emails
{
normalized
:=
strings
.
ToLower
(
strings
.
TrimSpace
(
email
))
if
normalized
==
""
{
continue
}
emailSet
[
normalized
]
=
struct
{}{}
}
var
matches
[]
service
.
Account
for
i
:=
range
s
.
lookupAccounts
{
account
:=
&
s
.
lookupAccounts
[
i
]
if
account
.
Platform
!=
platform
{
continue
}
accountEmail
:=
strings
.
ToLower
(
strings
.
TrimSpace
(
account
.
GetCredential
(
"email"
)))
if
accountEmail
==
""
{
continue
}
if
_
,
ok
:=
emailSet
[
accountEmail
];
!
ok
{
continue
}
matches
=
append
(
matches
,
*
account
)
}
return
matches
,
nil
}
func
(
s
*
stubAccountRepo
)
UpdateLastUsed
(
ctx
context
.
Context
,
id
int64
)
error
{
func
(
s
*
stubAccountRepo
)
UpdateLastUsed
(
ctx
context
.
Context
,
id
int64
)
error
{
return
errors
.
New
(
"not implemented"
)
return
errors
.
New
(
"not implemented"
)
}
}
...
...
backend/internal/server/routes/admin.go
View file @
64795a03
...
@@ -197,6 +197,7 @@ func registerAccountRoutes(admin *gin.RouterGroup, h *handler.Handlers) {
...
@@ -197,6 +197,7 @@ func registerAccountRoutes(admin *gin.RouterGroup, h *handler.Handlers) {
accounts
:=
admin
.
Group
(
"/accounts"
)
accounts
:=
admin
.
Group
(
"/accounts"
)
{
{
accounts
.
GET
(
""
,
h
.
Admin
.
Account
.
List
)
accounts
.
GET
(
""
,
h
.
Admin
.
Account
.
List
)
accounts
.
POST
(
"/lookup"
,
h
.
Admin
.
Account
.
Lookup
)
accounts
.
GET
(
"/:id"
,
h
.
Admin
.
Account
.
GetByID
)
accounts
.
GET
(
"/:id"
,
h
.
Admin
.
Account
.
GetByID
)
accounts
.
POST
(
""
,
h
.
Admin
.
Account
.
Create
)
accounts
.
POST
(
""
,
h
.
Admin
.
Account
.
Create
)
accounts
.
POST
(
"/sync/crs"
,
h
.
Admin
.
Account
.
SyncFromCRS
)
accounts
.
POST
(
"/sync/crs"
,
h
.
Admin
.
Account
.
SyncFromCRS
)
...
...
backend/internal/service/account_service.go
View file @
64795a03
...
@@ -33,6 +33,7 @@ type AccountRepository interface {
...
@@ -33,6 +33,7 @@ type AccountRepository interface {
ListByGroup
(
ctx
context
.
Context
,
groupID
int64
)
([]
Account
,
error
)
ListByGroup
(
ctx
context
.
Context
,
groupID
int64
)
([]
Account
,
error
)
ListActive
(
ctx
context
.
Context
)
([]
Account
,
error
)
ListActive
(
ctx
context
.
Context
)
([]
Account
,
error
)
ListByPlatform
(
ctx
context
.
Context
,
platform
string
)
([]
Account
,
error
)
ListByPlatform
(
ctx
context
.
Context
,
platform
string
)
([]
Account
,
error
)
ListByPlatformAndCredentialEmails
(
ctx
context
.
Context
,
platform
string
,
emails
[]
string
)
([]
Account
,
error
)
UpdateLastUsed
(
ctx
context
.
Context
,
id
int64
)
error
UpdateLastUsed
(
ctx
context
.
Context
,
id
int64
)
error
BatchUpdateLastUsed
(
ctx
context
.
Context
,
updates
map
[
int64
]
time
.
Time
)
error
BatchUpdateLastUsed
(
ctx
context
.
Context
,
updates
map
[
int64
]
time
.
Time
)
error
...
...
backend/internal/service/account_service_delete_test.go
View file @
64795a03
...
@@ -87,6 +87,10 @@ func (s *accountRepoStub) ListByPlatform(ctx context.Context, platform string) (
...
@@ -87,6 +87,10 @@ func (s *accountRepoStub) ListByPlatform(ctx context.Context, platform string) (
panic
(
"unexpected ListByPlatform call"
)
panic
(
"unexpected ListByPlatform call"
)
}
}
func
(
s
*
accountRepoStub
)
ListByPlatformAndCredentialEmails
(
ctx
context
.
Context
,
platform
string
,
emails
[]
string
)
([]
Account
,
error
)
{
panic
(
"unexpected ListByPlatformAndCredentialEmails call"
)
}
func
(
s
*
accountRepoStub
)
UpdateLastUsed
(
ctx
context
.
Context
,
id
int64
)
error
{
func
(
s
*
accountRepoStub
)
UpdateLastUsed
(
ctx
context
.
Context
,
id
int64
)
error
{
panic
(
"unexpected UpdateLastUsed call"
)
panic
(
"unexpected UpdateLastUsed call"
)
}
}
...
...
backend/internal/service/admin_service.go
View file @
64795a03
...
@@ -40,6 +40,7 @@ type AdminService interface {
...
@@ -40,6 +40,7 @@ type AdminService interface {
CreateAccount
(
ctx
context
.
Context
,
input
*
CreateAccountInput
)
(
*
Account
,
error
)
CreateAccount
(
ctx
context
.
Context
,
input
*
CreateAccountInput
)
(
*
Account
,
error
)
UpdateAccount
(
ctx
context
.
Context
,
id
int64
,
input
*
UpdateAccountInput
)
(
*
Account
,
error
)
UpdateAccount
(
ctx
context
.
Context
,
id
int64
,
input
*
UpdateAccountInput
)
(
*
Account
,
error
)
DeleteAccount
(
ctx
context
.
Context
,
id
int64
)
error
DeleteAccount
(
ctx
context
.
Context
,
id
int64
)
error
LookupAccountsByCredentialEmail
(
ctx
context
.
Context
,
platform
string
,
emails
[]
string
)
([]
Account
,
error
)
RefreshAccountCredentials
(
ctx
context
.
Context
,
id
int64
)
(
*
Account
,
error
)
RefreshAccountCredentials
(
ctx
context
.
Context
,
id
int64
)
(
*
Account
,
error
)
ClearAccountError
(
ctx
context
.
Context
,
id
int64
)
(
*
Account
,
error
)
ClearAccountError
(
ctx
context
.
Context
,
id
int64
)
(
*
Account
,
error
)
SetAccountError
(
ctx
context
.
Context
,
id
int64
,
errorMsg
string
)
error
SetAccountError
(
ctx
context
.
Context
,
id
int64
,
errorMsg
string
)
error
...
@@ -793,6 +794,13 @@ func (s *adminServiceImpl) GetAccount(ctx context.Context, id int64) (*Account,
...
@@ -793,6 +794,13 @@ func (s *adminServiceImpl) GetAccount(ctx context.Context, id int64) (*Account,
return
s
.
accountRepo
.
GetByID
(
ctx
,
id
)
return
s
.
accountRepo
.
GetByID
(
ctx
,
id
)
}
}
func
(
s
*
adminServiceImpl
)
LookupAccountsByCredentialEmail
(
ctx
context
.
Context
,
platform
string
,
emails
[]
string
)
([]
Account
,
error
)
{
if
platform
==
""
||
len
(
emails
)
==
0
{
return
[]
Account
{},
nil
}
return
s
.
accountRepo
.
ListByPlatformAndCredentialEmails
(
ctx
,
platform
,
emails
)
}
func
(
s
*
adminServiceImpl
)
GetAccountsByIDs
(
ctx
context
.
Context
,
ids
[]
int64
)
([]
*
Account
,
error
)
{
func
(
s
*
adminServiceImpl
)
GetAccountsByIDs
(
ctx
context
.
Context
,
ids
[]
int64
)
([]
*
Account
,
error
)
{
if
len
(
ids
)
==
0
{
if
len
(
ids
)
==
0
{
return
[]
*
Account
{},
nil
return
[]
*
Account
{},
nil
...
...
backend/internal/service/gateway_multiplatform_test.go
View file @
64795a03
...
@@ -96,6 +96,9 @@ func (m *mockAccountRepoForPlatform) ListActive(ctx context.Context) ([]Account,
...
@@ -96,6 +96,9 @@ func (m *mockAccountRepoForPlatform) ListActive(ctx context.Context) ([]Account,
func
(
m
*
mockAccountRepoForPlatform
)
ListByPlatform
(
ctx
context
.
Context
,
platform
string
)
([]
Account
,
error
)
{
func
(
m
*
mockAccountRepoForPlatform
)
ListByPlatform
(
ctx
context
.
Context
,
platform
string
)
([]
Account
,
error
)
{
return
nil
,
nil
return
nil
,
nil
}
}
func
(
m
*
mockAccountRepoForPlatform
)
ListByPlatformAndCredentialEmails
(
ctx
context
.
Context
,
platform
string
,
emails
[]
string
)
([]
Account
,
error
)
{
return
nil
,
nil
}
func
(
m
*
mockAccountRepoForPlatform
)
UpdateLastUsed
(
ctx
context
.
Context
,
id
int64
)
error
{
func
(
m
*
mockAccountRepoForPlatform
)
UpdateLastUsed
(
ctx
context
.
Context
,
id
int64
)
error
{
return
nil
return
nil
}
}
...
...
backend/internal/service/gemini_multiplatform_test.go
View file @
64795a03
...
@@ -81,6 +81,9 @@ func (m *mockAccountRepoForGemini) ListActive(ctx context.Context) ([]Account, e
...
@@ -81,6 +81,9 @@ func (m *mockAccountRepoForGemini) ListActive(ctx context.Context) ([]Account, e
func
(
m
*
mockAccountRepoForGemini
)
ListByPlatform
(
ctx
context
.
Context
,
platform
string
)
([]
Account
,
error
)
{
func
(
m
*
mockAccountRepoForGemini
)
ListByPlatform
(
ctx
context
.
Context
,
platform
string
)
([]
Account
,
error
)
{
return
nil
,
nil
return
nil
,
nil
}
}
func
(
m
*
mockAccountRepoForGemini
)
ListByPlatformAndCredentialEmails
(
ctx
context
.
Context
,
platform
string
,
emails
[]
string
)
([]
Account
,
error
)
{
return
nil
,
nil
}
func
(
m
*
mockAccountRepoForGemini
)
UpdateLastUsed
(
ctx
context
.
Context
,
id
int64
)
error
{
return
nil
}
func
(
m
*
mockAccountRepoForGemini
)
UpdateLastUsed
(
ctx
context
.
Context
,
id
int64
)
error
{
return
nil
}
func
(
m
*
mockAccountRepoForGemini
)
BatchUpdateLastUsed
(
ctx
context
.
Context
,
updates
map
[
int64
]
time
.
Time
)
error
{
func
(
m
*
mockAccountRepoForGemini
)
BatchUpdateLastUsed
(
ctx
context
.
Context
,
updates
map
[
int64
]
time
.
Time
)
error
{
return
nil
return
nil
...
...
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