Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
phsl
/
new-api
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Members
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Unverified
Commit
960bf9c4
authored
Apr 06, 2026
by
Calcium-Ion
Committed by
GitHub
Apr 06, 2026
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #4114 from RedwindA/fix/4110
feat(token): add batch API for fetching token keys
parents
eacc245b
12a48c62
Hide whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
60 additions
and
6 deletions
+60
-6
controller/token.go
+23
-0
i18n/keys.go
+1
-0
i18n/locales/en.yaml
+1
-0
i18n/locales/zh-CN.yaml
+1
-0
i18n/locales/zh-TW.yaml
+1
-0
model/token.go
+8
-0
router/api-router.go
+1
-0
web/src/helpers/token.js
+14
-0
web/src/hooks/tokens/useTokensData.jsx
+10
-6
No files found.
controller/token.go
View file @
960bf9c4
...
...
@@ -334,3 +334,26 @@ func DeleteTokenBatch(c *gin.Context) {
"data"
:
count
,
})
}
func
GetTokenKeysBatch
(
c
*
gin
.
Context
)
{
tokenBatch
:=
TokenBatch
{}
if
err
:=
c
.
ShouldBindJSON
(
&
tokenBatch
);
err
!=
nil
||
len
(
tokenBatch
.
Ids
)
==
0
{
common
.
ApiErrorI18n
(
c
,
i18n
.
MsgInvalidParams
)
return
}
if
len
(
tokenBatch
.
Ids
)
>
100
{
common
.
ApiErrorI18n
(
c
,
i18n
.
MsgBatchTooMany
,
map
[
string
]
any
{
"Max"
:
100
})
return
}
userId
:=
c
.
GetInt
(
"id"
)
tokens
,
err
:=
model
.
GetTokenKeysByIds
(
tokenBatch
.
Ids
,
userId
)
if
err
!=
nil
{
common
.
ApiError
(
c
,
err
)
return
}
keysMap
:=
make
(
map
[
int
]
string
)
for
_
,
t
:=
range
tokens
{
keysMap
[
t
.
Id
]
=
t
.
GetFullKey
()
}
common
.
ApiSuccess
(
c
,
gin
.
H
{
"keys"
:
keysMap
})
}
i18n/keys.go
View file @
960bf9c4
...
...
@@ -25,6 +25,7 @@ const (
MsgDeleteFailed
=
"common.delete_failed"
MsgAlreadyExists
=
"common.already_exists"
MsgNameCannotBeEmpty
=
"common.name_cannot_be_empty"
MsgBatchTooMany
=
"common.batch_too_many"
)
// Token related messages
...
...
i18n/locales/en.yaml
View file @
960bf9c4
...
...
@@ -21,6 +21,7 @@ common.delete_success: "Deletion successful"
common.delete_failed
:
"
Deletion
failed"
common.already_exists
:
"
Already
exists"
common.name_cannot_be_empty
:
"
Name
cannot
be
empty"
common.batch_too_many
:
"
Too
many
items
in
batch
request,
maximum
is
{{.Max}}"
# Token messages
token.name_too_long
:
"
Token
name
is
too
long"
...
...
i18n/locales/zh-CN.yaml
View file @
960bf9c4
...
...
@@ -22,6 +22,7 @@ common.delete_success: "删除成功"
common.delete_failed
:
"
删除失败"
common.already_exists
:
"
已存在"
common.name_cannot_be_empty
:
"
名称不能为空"
common.batch_too_many
:
"
批量请求数量过多,最多
{{.Max}}
条"
# Token messages
token.name_too_long
:
"
令牌名称过长"
...
...
i18n/locales/zh-TW.yaml
View file @
960bf9c4
...
...
@@ -22,6 +22,7 @@ common.delete_success: "刪除成功"
common.delete_failed
:
"
刪除失敗"
common.already_exists
:
"
已存在"
common.name_cannot_be_empty
:
"
名稱不能為空"
common.batch_too_many
:
"
批次請求數量過多,最多
{{.Max}}
條"
# Token messages
token.name_too_long
:
"
令牌名稱過長"
...
...
model/token.go
View file @
960bf9c4
...
...
@@ -481,3 +481,11 @@ func BatchDeleteTokens(ids []int, userId int) (int, error) {
return
len
(
tokens
),
nil
}
func
GetTokenKeysByIds
(
ids
[]
int
,
userId
int
)
([]
Token
,
error
)
{
var
tokens
[]
Token
err
:=
DB
.
Select
(
"id"
,
commonKeyCol
)
.
Where
(
"user_id = ? AND id IN (?)"
,
userId
,
ids
)
.
Find
(
&
tokens
)
.
Error
return
tokens
,
err
}
router/api-router.go
View file @
960bf9c4
...
...
@@ -257,6 +257,7 @@ func SetApiRouter(router *gin.Engine) {
tokenRoute
.
PUT
(
"/"
,
controller
.
UpdateToken
)
tokenRoute
.
DELETE
(
"/:id"
,
controller
.
DeleteToken
)
tokenRoute
.
POST
(
"/batch"
,
controller
.
DeleteTokenBatch
)
tokenRoute
.
POST
(
"/batch/keys"
,
middleware
.
CriticalRateLimit
(),
middleware
.
DisableCache
(),
controller
.
GetTokenKeysBatch
)
}
usageRoute
:=
apiRouter
.
Group
(
"/usage"
)
...
...
web/src/helpers/token.js
View file @
960bf9c4
...
...
@@ -34,6 +34,20 @@ export async function fetchTokenKey(tokenId) {
}
/**
* 批量获取多个令牌的真实 key
* @param {number[]} tokenIds
* @returns {Promise<Record<number, string>>} 返回 {id: key} map,key 不带 sk- 前缀
*/
export
async
function
fetchTokenKeysBatch
(
tokenIds
)
{
const
response
=
await
API
.
post
(
'/api/token/batch/keys'
,
{
ids
:
tokenIds
});
const
{
success
,
data
,
message
}
=
response
.
data
||
{};
if
(
!
success
||
!
data
?.
keys
)
{
throw
new
Error
(
message
||
'Failed to fetch token keys'
);
}
return
data
.
keys
;
}
/**
* 获取可用的 token keys
* @returns {Promise<string[]>} 返回 active 状态的不带 sk- 前缀的真实 token key 数组
*/
...
...
web/src/hooks/tokens/useTokensData.jsx
View file @
960bf9c4
...
...
@@ -31,6 +31,7 @@ import { ITEMS_PER_PAGE } from '../../constants';
import
{
useTableCompactMode
}
from
'../common/useTableCompactMode'
;
import
{
fetchTokenKey
as
fetchTokenKeyById
,
fetchTokenKeysBatch
,
getServerAddress
,
encodeChannelConnectionString
,
}
from
'../../helpers/token'
;
...
...
@@ -408,14 +409,17 @@ export const useTokensData = (openFluentNotification, openCCSwitchModal) => {
return
;
}
try
{
const
keys
=
await
Promise
.
all
(
selectedKeys
.
map
((
token
)
=>
fetchTokenKey
(
token
,
{
suppressError
:
true
})),
);
const
ids
=
selectedKeys
.
map
((
token
)
=>
token
.
id
);
const
keysMap
=
await
fetchTokenKeysBatch
(
ids
);
setResolvedTokenKeys
((
prev
)
=>
({
...
prev
,
...
keysMap
}));
let
content
=
''
;
for
(
let
i
=
0
;
i
<
selectedKeys
.
length
;
i
++
)
{
const
fullKey
=
keys
[
i
];
for
(
const
token
of
selectedKeys
)
{
const
fullKey
=
keysMap
[
token
.
id
];
if
(
!
fullKey
)
continue
;
if
(
copyType
===
'name+key'
)
{
content
+=
`
${
selectedKeys
[
i
]
.
name
}
sk-
${
fullKey
}
\n`
;
content
+=
`
${
token
.
name
}
sk-
${
fullKey
}
\n`
;
}
else
{
content
+=
`sk-
${
fullKey
}
\n`
;
}
...
...
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