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
Commit
20e233ea
authored
Jun 22, 2025
by
CaIon
Browse files
Options
Browse Files
Download
Plain Diff
Merge remote-tracking branch 'origin/alpha' into alpha
parents
9121a593
d11798cc
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
164 additions
and
11 deletions
+164
-11
controller/token.go
+29
-0
model/token.go
+34
-0
router/api-router.go
+1
-0
web/src/components/table/TokensTable.js
+90
-10
web/src/i18n/locales/en.json
+10
-1
No files found.
controller/token.go
View file @
20e233ea
...
...
@@ -258,3 +258,32 @@ func UpdateToken(c *gin.Context) {
})
return
}
type
TokenBatch
struct
{
Ids
[]
int
`json:"ids"`
}
func
DeleteTokenBatch
(
c
*
gin
.
Context
)
{
tokenBatch
:=
TokenBatch
{}
if
err
:=
c
.
ShouldBindJSON
(
&
tokenBatch
);
err
!=
nil
||
len
(
tokenBatch
.
Ids
)
==
0
{
c
.
JSON
(
http
.
StatusOK
,
gin
.
H
{
"success"
:
false
,
"message"
:
"参数错误"
,
})
return
}
userId
:=
c
.
GetInt
(
"id"
)
count
,
err
:=
model
.
BatchDeleteTokens
(
tokenBatch
.
Ids
,
userId
)
if
err
!=
nil
{
c
.
JSON
(
http
.
StatusOK
,
gin
.
H
{
"success"
:
false
,
"message"
:
err
.
Error
(),
})
return
}
c
.
JSON
(
http
.
StatusOK
,
gin
.
H
{
"success"
:
true
,
"message"
:
""
,
"data"
:
count
,
})
}
model/token.go
View file @
20e233ea
...
...
@@ -327,3 +327,37 @@ func CountUserTokens(userId int) (int64, error) {
err
:=
DB
.
Model
(
&
Token
{})
.
Where
(
"user_id = ?"
,
userId
)
.
Count
(
&
total
)
.
Error
return
total
,
err
}
// BatchDeleteTokens 删除指定用户的一组令牌,返回成功删除数量
func
BatchDeleteTokens
(
ids
[]
int
,
userId
int
)
(
int
,
error
)
{
if
len
(
ids
)
==
0
{
return
0
,
errors
.
New
(
"ids 不能为空!"
)
}
tx
:=
DB
.
Begin
()
var
tokens
[]
Token
if
err
:=
tx
.
Where
(
"user_id = ? AND id IN (?)"
,
userId
,
ids
)
.
Find
(
&
tokens
)
.
Error
;
err
!=
nil
{
tx
.
Rollback
()
return
0
,
err
}
if
err
:=
tx
.
Where
(
"user_id = ? AND id IN (?)"
,
userId
,
ids
)
.
Delete
(
&
Token
{})
.
Error
;
err
!=
nil
{
tx
.
Rollback
()
return
0
,
err
}
if
err
:=
tx
.
Commit
()
.
Error
;
err
!=
nil
{
return
0
,
err
}
if
common
.
RedisEnabled
{
gopool
.
Go
(
func
()
{
for
_
,
t
:=
range
tokens
{
_
=
cacheDeleteToken
(
t
.
Key
)
}
})
}
return
len
(
tokens
),
nil
}
router/api-router.go
View file @
20e233ea
...
...
@@ -125,6 +125,7 @@ func SetApiRouter(router *gin.Engine) {
tokenRoute
.
POST
(
"/"
,
controller
.
AddToken
)
tokenRoute
.
PUT
(
"/"
,
controller
.
UpdateToken
)
tokenRoute
.
DELETE
(
"/:id"
,
controller
.
DeleteToken
)
tokenRoute
.
POST
(
"/batch"
,
controller
.
DeleteTokenBatch
)
}
redemptionRoute
:=
apiRouter
.
Group
(
"/redemption"
)
redemptionRoute
.
Use
(
middleware
.
AdminAuth
())
...
...
web/src/components/table/TokensTable.js
View file @
20e233ea
...
...
@@ -435,6 +435,7 @@ const TokensTable = () => {
const
refresh
=
async
()
=>
{
await
loadTokens
(
1
);
setSelectedKeys
([]);
};
const
copyText
=
async
(
text
)
=>
{
...
...
@@ -583,6 +584,29 @@ const TokensTable = () => {
}
};
const
batchDeleteTokens
=
async
()
=>
{
if
(
selectedKeys
.
length
===
0
)
{
showError
(
t
(
'请先选择要删除的令牌!'
));
return
;
}
setLoading
(
true
);
try
{
const
ids
=
selectedKeys
.
map
((
token
)
=>
token
.
id
);
const
res
=
await
API
.
post
(
'/api/token/batch'
,
{
ids
});
if
(
res
?.
data
?.
success
)
{
const
count
=
res
.
data
.
data
||
0
;
showSuccess
(
t
(
'已删除 {{count}} 个令牌!'
,
{
count
}));
await
refresh
();
}
else
{
showError
(
res
?.
data
?.
message
||
t
(
'删除失败'
));
}
}
catch
(
error
)
{
showError
(
error
.
message
);
}
finally
{
setLoading
(
false
);
}
};
const
renderHeader
=
()
=>
(
<
div
className
=
"flex flex-col w-full"
>
<
div
className
=
"mb-2"
>
...
...
@@ -595,12 +619,12 @@ const TokensTable = () => {
<
Divider
margin
=
"12px"
/>
<
div
className
=
"flex flex-col md:flex-row justify-between items-center gap-4 w-full"
>
<
div
className
=
"flex gap-2 w-full md:w-auto order-2 md:order-1"
>
<
div
className
=
"flex
flex-wrap
gap-2 w-full md:w-auto order-2 md:order-1"
>
<
Button
theme
=
"light"
type
=
"primary"
icon
=
{
<
IconPlus
/>
}
className
=
"!rounded-full
w-full md:w-auto
"
className
=
"!rounded-full
flex-1 md:flex-initial
"
onClick
=
{()
=>
{
setEditingToken
({
id
:
undefined
,
...
...
@@ -614,21 +638,77 @@ const TokensTable = () => {
theme
=
"light"
type
=
"warning"
icon
=
{
<
IconCopy
/>
}
className
=
"!rounded-full
w-full md:w-auto
"
onClick
=
{
async
()
=>
{
className
=
"!rounded-full
flex-1 md:flex-initial
"
onClick
=
{()
=>
{
if
(
selectedKeys
.
length
===
0
)
{
showError
(
t
(
'请至少选择一个令牌!'
));
return
;
}
let
keys
=
''
;
for
(
let
i
=
0
;
i
<
selectedKeys
.
length
;
i
++
)
{
keys
+=
selectedKeys
[
i
].
name
+
' sk-'
+
selectedKeys
[
i
].
key
+
'\n'
;
Modal
.
info
({
title
:
t
(
'复制令牌'
),
icon
:
null
,
content
:
t
(
'请选择你的复制方式'
),
footer
:
(
<
Space
>
<
Button
type
=
"primary"
theme
=
"solid"
icon
=
{
<
IconCopy
/>
}
onClick
=
{
async
()
=>
{
let
content
=
''
;
for
(
let
i
=
0
;
i
<
selectedKeys
.
length
;
i
++
)
{
content
+=
selectedKeys
[
i
].
name
+
' sk-'
+
selectedKeys
[
i
].
key
+
'\n'
;
}
await
copyText
(
content
);
Modal
.
destroyAll
();
}}
>
{
t
(
'名称+密钥'
)}
<
/Button
>
<
Button
theme
=
"light"
icon
=
{
<
IconCopy
/>
}
onClick
=
{
async
()
=>
{
let
content
=
''
;
for
(
let
i
=
0
;
i
<
selectedKeys
.
length
;
i
++
)
{
content
+=
'sk-'
+
selectedKeys
[
i
].
key
+
'\n'
;
}
await
copyText
(
content
);
Modal
.
destroyAll
();
}}
>
{
t
(
'仅密钥'
)}
<
/Button
>
<
/Space
>
),
});
}}
>
{
t
(
'复制所选令牌'
)}
<
/Button
>
<
div
className
=
"w-full md:hidden"
><
/div
>
<
Button
theme
=
"light"
type
=
"danger"
className
=
"!rounded-full w-full md:w-auto"
onClick
=
{()
=>
{
if
(
selectedKeys
.
length
===
0
)
{
showError
(
t
(
'请至少选择一个令牌!'
));
return
;
}
await
copyText
(
keys
);
Modal
.
confirm
({
title
:
t
(
'批量删除令牌'
),
content
:
(
<
div
>
{
t
(
'确定要删除所选的 {{count}} 个令牌吗?'
,
{
count
:
selectedKeys
.
length
})}
<
/div
>
),
onOk
:
()
=>
batchDeleteTokens
(),
});
}}
>
{
t
(
'
复制所选令牌到剪贴板
'
)}
{
t
(
'
删除所选令牌
'
)}
<
/Button
>
<
/div
>
...
...
web/src/i18n/locales/en.json
View file @
20e233ea
...
...
@@ -813,7 +813,16 @@
"复制所选令牌"
:
"Copy selected token"
,
"请至少选择一个令牌!"
:
"Please select at least one token!"
,
"管理员未设置查询页链接"
:
"The administrator has not set the query page link"
,
"复制所选令牌到剪贴板"
:
"Copy selected token to clipboard"
,
"批量删除令牌"
:
"Batch delete token"
,
"确定要删除所选的 {{count}} 个令牌吗?"
:
"Are you sure you want to delete the selected {{count}} tokens?"
,
"删除所选令牌"
:
"Delete selected token"
,
"请先选择要删除的令牌!"
:
"Please select the token to be deleted!"
,
"已删除 {{count}} 个令牌!"
:
"Deleted {{count}} tokens!"
,
"删除失败"
:
"Delete failed"
,
"复制令牌"
:
"Copy token"
,
"请选择你的复制方式"
:
"Please select your copy method"
,
"名称+密钥"
:
"Name + key"
,
"仅密钥"
:
"Only key"
,
"查看API地址"
:
"View API address"
,
"打开查询页"
:
"Open query page"
,
"时间(仅显示近3天)"
:
"Time (only displays the last 3 days)"
,
...
...
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