Merge pull request #4114 from RedwindA/fix/4110

feat(token): add batch API for fetching token keys
This commit is contained in:
Calcium-Ion
2026-04-06 19:49:10 +08:00
committed by GitHub
9 changed files with 60 additions and 6 deletions
+23
View File
@@ -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})
}
+1
View File
@@ -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
+1
View File
@@ -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"
+1
View File
@@ -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: "令牌名称过长"
+1
View File
@@ -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: "令牌名稱過長"
+8
View File
@@ -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
}
+1
View File
@@ -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")
+14
View File
@@ -33,6 +33,20 @@ export async function fetchTokenKey(tokenId) {
return data.key;
}
/**
* 批量获取多个令牌的真实 key
* @param {number[]} tokenIds
* @returns {Promise<Record<number, string>>} 返回 {id: key} mapkey 不带 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 数组
+10 -6
View File
@@ -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`;
}