Merge pull request #4114 from RedwindA/fix/4110
feat(token): add batch API for fetching token keys
This commit is contained in:
@@ -334,3 +334,26 @@ func DeleteTokenBatch(c *gin.Context) {
|
|||||||
"data": count,
|
"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})
|
||||||
|
}
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ const (
|
|||||||
MsgDeleteFailed = "common.delete_failed"
|
MsgDeleteFailed = "common.delete_failed"
|
||||||
MsgAlreadyExists = "common.already_exists"
|
MsgAlreadyExists = "common.already_exists"
|
||||||
MsgNameCannotBeEmpty = "common.name_cannot_be_empty"
|
MsgNameCannotBeEmpty = "common.name_cannot_be_empty"
|
||||||
|
MsgBatchTooMany = "common.batch_too_many"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Token related messages
|
// Token related messages
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ common.delete_success: "Deletion successful"
|
|||||||
common.delete_failed: "Deletion failed"
|
common.delete_failed: "Deletion failed"
|
||||||
common.already_exists: "Already exists"
|
common.already_exists: "Already exists"
|
||||||
common.name_cannot_be_empty: "Name cannot be empty"
|
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 messages
|
||||||
token.name_too_long: "Token name is too long"
|
token.name_too_long: "Token name is too long"
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ common.delete_success: "删除成功"
|
|||||||
common.delete_failed: "删除失败"
|
common.delete_failed: "删除失败"
|
||||||
common.already_exists: "已存在"
|
common.already_exists: "已存在"
|
||||||
common.name_cannot_be_empty: "名称不能为空"
|
common.name_cannot_be_empty: "名称不能为空"
|
||||||
|
common.batch_too_many: "批量请求数量过多,最多 {{.Max}} 条"
|
||||||
|
|
||||||
# Token messages
|
# Token messages
|
||||||
token.name_too_long: "令牌名称过长"
|
token.name_too_long: "令牌名称过长"
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ common.delete_success: "刪除成功"
|
|||||||
common.delete_failed: "刪除失敗"
|
common.delete_failed: "刪除失敗"
|
||||||
common.already_exists: "已存在"
|
common.already_exists: "已存在"
|
||||||
common.name_cannot_be_empty: "名稱不能為空"
|
common.name_cannot_be_empty: "名稱不能為空"
|
||||||
|
common.batch_too_many: "批次請求數量過多,最多 {{.Max}} 條"
|
||||||
|
|
||||||
# Token messages
|
# Token messages
|
||||||
token.name_too_long: "令牌名稱過長"
|
token.name_too_long: "令牌名稱過長"
|
||||||
|
|||||||
@@ -481,3 +481,11 @@ func BatchDeleteTokens(ids []int, userId int) (int, error) {
|
|||||||
|
|
||||||
return len(tokens), nil
|
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
|
||||||
|
}
|
||||||
|
|||||||
@@ -257,6 +257,7 @@ func SetApiRouter(router *gin.Engine) {
|
|||||||
tokenRoute.PUT("/", controller.UpdateToken)
|
tokenRoute.PUT("/", controller.UpdateToken)
|
||||||
tokenRoute.DELETE("/:id", controller.DeleteToken)
|
tokenRoute.DELETE("/:id", controller.DeleteToken)
|
||||||
tokenRoute.POST("/batch", controller.DeleteTokenBatch)
|
tokenRoute.POST("/batch", controller.DeleteTokenBatch)
|
||||||
|
tokenRoute.POST("/batch/keys", middleware.CriticalRateLimit(), middleware.DisableCache(), controller.GetTokenKeysBatch)
|
||||||
}
|
}
|
||||||
|
|
||||||
usageRoute := apiRouter.Group("/usage")
|
usageRoute := apiRouter.Group("/usage")
|
||||||
|
|||||||
Vendored
+14
@@ -33,6 +33,20 @@ export async function fetchTokenKey(tokenId) {
|
|||||||
return data.key;
|
return data.key;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量获取多个令牌的真实 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
|
* 获取可用的 token keys
|
||||||
* @returns {Promise<string[]>} 返回 active 状态的不带 sk- 前缀的真实 token key 数组
|
* @returns {Promise<string[]>} 返回 active 状态的不带 sk- 前缀的真实 token key 数组
|
||||||
|
|||||||
+10
-6
@@ -31,6 +31,7 @@ import { ITEMS_PER_PAGE } from '../../constants';
|
|||||||
import { useTableCompactMode } from '../common/useTableCompactMode';
|
import { useTableCompactMode } from '../common/useTableCompactMode';
|
||||||
import {
|
import {
|
||||||
fetchTokenKey as fetchTokenKeyById,
|
fetchTokenKey as fetchTokenKeyById,
|
||||||
|
fetchTokenKeysBatch,
|
||||||
getServerAddress,
|
getServerAddress,
|
||||||
encodeChannelConnectionString,
|
encodeChannelConnectionString,
|
||||||
} from '../../helpers/token';
|
} from '../../helpers/token';
|
||||||
@@ -408,14 +409,17 @@ export const useTokensData = (openFluentNotification, openCCSwitchModal) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const keys = await Promise.all(
|
const ids = selectedKeys.map((token) => token.id);
|
||||||
selectedKeys.map((token) => fetchTokenKey(token, { suppressError: true })),
|
const keysMap = await fetchTokenKeysBatch(ids);
|
||||||
);
|
|
||||||
|
setResolvedTokenKeys((prev) => ({ ...prev, ...keysMap }));
|
||||||
|
|
||||||
let content = '';
|
let content = '';
|
||||||
for (let i = 0; i < selectedKeys.length; i++) {
|
for (const token of selectedKeys) {
|
||||||
const fullKey = keys[i];
|
const fullKey = keysMap[token.id];
|
||||||
|
if (!fullKey) continue;
|
||||||
if (copyType === 'name+key') {
|
if (copyType === 'name+key') {
|
||||||
content += `${selectedKeys[i].name} sk-${fullKey}\n`;
|
content += `${token.name} sk-${fullKey}\n`;
|
||||||
} else {
|
} else {
|
||||||
content += `sk-${fullKey}\n`;
|
content += `sk-${fullKey}\n`;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user