优化api密钥

This commit is contained in:
yml2213
2026-08-03 10:38:06 +08:00
parent 9a3471cc61
commit 7fe4a05ed7
6 changed files with 71 additions and 15 deletions
+10
View File
@@ -302,6 +302,16 @@ func (h *MerchantHandler) UpdateAPIClientStatus(c *gin.Context) {
response.OK(c, nil) response.OK(c, nil)
} }
// DeleteAPIClient DELETE /api/merchant/api-clients/:id
func (h *MerchantHandler) DeleteAPIClient(c *gin.Context) {
id, _ := strconv.ParseUint(c.Param("id"), 10, 64)
if err := h.merchantSvc.DeleteAPIClient(middleware.GetMerchantID(c), uint(id), middleware.GetUserID(c)); err != nil {
response.BadRequest(c, err.Error())
return
}
response.OK(c, nil)
}
func (h *MerchantHandler) ListCallbacks(c *gin.Context) { func (h *MerchantHandler) ListCallbacks(c *gin.Context) {
list, err := h.callbackSvc.ListSubscriptions(middleware.GetMerchantID(c)) list, err := h.callbackSvc.ListSubscriptions(middleware.GetMerchantID(c))
if err != nil { if err != nil {
+2 -1
View File
@@ -113,9 +113,10 @@ func Setup(h *Handlers) *gin.Engine {
merchant.GET("/wallet", middleware.RequireMerchantFeature(h.OpenDB, model.MerchantFeatureWallet), middleware.RequireMerchantRole(model.MemberRoleOwner, model.MemberRoleOperator, model.MemberRoleFinance), h.Merchant.GetWallet) merchant.GET("/wallet", middleware.RequireMerchantFeature(h.OpenDB, model.MerchantFeatureWallet), middleware.RequireMerchantRole(model.MemberRoleOwner, model.MemberRoleOperator, model.MemberRoleFinance), h.Merchant.GetWallet)
merchant.GET("/wallet/ledger", middleware.RequireMerchantFeature(h.OpenDB, model.MerchantFeatureWallet), middleware.RequireMerchantRole(model.MemberRoleOwner, model.MemberRoleFinance), h.Merchant.ListWalletLedger) merchant.GET("/wallet/ledger", middleware.RequireMerchantFeature(h.OpenDB, model.MerchantFeatureWallet), middleware.RequireMerchantRole(model.MemberRoleOwner, model.MemberRoleFinance), h.Merchant.ListWalletLedger)
merchant.POST("/wallet/adjust", middleware.RequireMerchantFeature(h.OpenDB, model.MerchantFeatureWallet), middleware.RequireMerchantRole(model.MemberRoleOwner, model.MemberRoleFinance), h.Merchant.AdjustWallet) merchant.POST("/wallet/adjust", middleware.RequireMerchantFeature(h.OpenDB, model.MerchantFeatureWallet), middleware.RequireMerchantRole(model.MemberRoleOwner, model.MemberRoleFinance), h.Merchant.AdjustWallet)
merchant.GET("/api-clients", middleware.RequireMerchantFeature(h.OpenDB, model.MerchantFeatureAPI), middleware.RequireMerchantRole(model.MemberRoleOwner, model.MemberRoleOperator), h.Merchant.ListAPIClients) merchant.GET("/api-clients", middleware.RequireMerchantFeature(h.OpenDB, model.MerchantFeatureAPI), h.Merchant.ListAPIClients)
merchant.POST("/api-clients", middleware.RequireMerchantFeature(h.OpenDB, model.MerchantFeatureAPI), middleware.RequireMerchantRole(model.MemberRoleOwner, model.MemberRoleOperator), h.Merchant.CreateAPIClient) merchant.POST("/api-clients", middleware.RequireMerchantFeature(h.OpenDB, model.MerchantFeatureAPI), middleware.RequireMerchantRole(model.MemberRoleOwner, model.MemberRoleOperator), h.Merchant.CreateAPIClient)
merchant.PATCH("/api-clients/:id/status", middleware.RequireMerchantFeature(h.OpenDB, model.MerchantFeatureAPI), middleware.RequireMerchantRole(model.MemberRoleOwner, model.MemberRoleOperator), h.Merchant.UpdateAPIClientStatus) merchant.PATCH("/api-clients/:id/status", middleware.RequireMerchantFeature(h.OpenDB, model.MerchantFeatureAPI), middleware.RequireMerchantRole(model.MemberRoleOwner, model.MemberRoleOperator), h.Merchant.UpdateAPIClientStatus)
merchant.DELETE("/api-clients/:id", middleware.RequireMerchantFeature(h.OpenDB, model.MerchantFeatureAPI), middleware.RequireMerchantRole(model.MemberRoleOwner, model.MemberRoleOperator), h.Merchant.DeleteAPIClient)
merchant.GET("/callbacks", middleware.RequireMerchantFeature(h.OpenDB, model.MerchantFeatureCallbacks), middleware.RequireMerchantRole(model.MemberRoleOwner, model.MemberRoleOperator), h.Merchant.ListCallbacks) merchant.GET("/callbacks", middleware.RequireMerchantFeature(h.OpenDB, model.MerchantFeatureCallbacks), middleware.RequireMerchantRole(model.MemberRoleOwner, model.MemberRoleOperator), h.Merchant.ListCallbacks)
merchant.POST("/callbacks", middleware.RequireMerchantFeature(h.OpenDB, model.MerchantFeatureCallbacks), middleware.RequireMerchantRole(model.MemberRoleOwner, model.MemberRoleOperator), h.Merchant.CreateCallback) merchant.POST("/callbacks", middleware.RequireMerchantFeature(h.OpenDB, model.MerchantFeatureCallbacks), middleware.RequireMerchantRole(model.MemberRoleOwner, model.MemberRoleOperator), h.Merchant.CreateCallback)
merchant.PATCH("/callbacks/:id/status", middleware.RequireMerchantFeature(h.OpenDB, model.MerchantFeatureCallbacks), middleware.RequireMerchantRole(model.MemberRoleOwner, model.MemberRoleOperator), h.Merchant.UpdateCallbackStatus) merchant.PATCH("/callbacks/:id/status", middleware.RequireMerchantFeature(h.OpenDB, model.MerchantFeatureCallbacks), middleware.RequireMerchantRole(model.MemberRoleOwner, model.MemberRoleOperator), h.Merchant.UpdateCallbackStatus)
+24 -1
View File
@@ -506,7 +506,9 @@ func (s *MerchantService) CreateAPIClient(merchantID uint, in CreateAPIClientInp
return errors.New("商户不存在或已禁用") return errors.New("商户不存在或已禁用")
} }
var clientCount int64 var clientCount int64
if err := tx.Model(&model.APIClient{}).Where("merchant_id = ?", merchantID).Count(&clientCount).Error; err != nil { if err := tx.Model(&model.APIClient{}).
Where("merchant_id = ? AND status = ?", merchantID, model.APIClientStatusActive).
Count(&clientCount).Error; err != nil {
return err return err
} }
if clientCount >= MaxAPIClientsPerMerchant { if clientCount >= MaxAPIClientsPerMerchant {
@@ -545,6 +547,27 @@ func (s *MerchantService) UpdateAPIClientStatus(merchantID, id uint, status stri
}) })
} }
// DeleteAPIClient 删除商户的 API 密钥(物理删除,不可恢复)。
// 删除前要求先停用,避免在用的密钥被误删。
func (s *MerchantService) DeleteAPIClient(merchantID, id uint, actorUserID uint) error {
return s.db.Transaction(func(tx *gorm.DB) error {
var client model.APIClient
if err := tx.Where("id = ? AND merchant_id = ?", id, merchantID).First(&client).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return errors.New("API 客户端不存在")
}
return err
}
if client.Status == model.APIClientStatusActive {
return errors.New("请先停用该密钥再删除")
}
if err := tx.Delete(&client).Error; err != nil {
return err
}
return writeAudit(tx, &merchantID, &actorUserID, nil, "api_client.delete", "api_client", fmt.Sprint(id), map[string]string{"name": client.Name, "app_key": client.AppKey})
})
}
// ProductCatalogItem 是商品目录(自营商户可售商品)的展示项,供平台管理员分配商品时勾选。 // ProductCatalogItem 是商品目录(自营商户可售商品)的展示项,供平台管理员分配商品时勾选。
type ProductCatalogItem struct { type ProductCatalogItem struct {
ID uint `json:"id"` ID uint `json:"id"`
+2
View File
@@ -91,6 +91,8 @@ export const merchantApi = {
}) => request.post('/merchant/api-clients', data).then((r) => r.data.data as ApiCredential), }) => request.post('/merchant/api-clients', data).then((r) => r.data.data as ApiCredential),
updateApiClientStatus: (id: number, status: ApiClient['status']) => updateApiClientStatus: (id: number, status: ApiClient['status']) =>
request.patch(`/merchant/api-clients/${id}/status`, { status }).then((r) => r.data.data), request.patch(`/merchant/api-clients/${id}/status`, { status }).then((r) => r.data.data),
deleteApiClient: (id: number) =>
request.delete(`/merchant/api-clients/${id}`).then((r) => r.data.data),
callbacks: () => callbacks: () =>
request.get('/merchant/callbacks').then((r) => r.data.data as CallbackSubscription[]), request.get('/merchant/callbacks').then((r) => r.data.data as CallbackSubscription[]),
createCallback: (data: { name: string; url: string; events: string }) => createCallback: (data: { name: string; url: string; events: string }) =>
+2 -2
View File
@@ -76,7 +76,7 @@ const adminSections: SidebarSection[] = [
}, },
{ {
key: 'funds', key: 'funds',
label: '资金', label: '积分',
icon: <WalletOutlined />, icon: <WalletOutlined />,
children: [{ key: 'merchant-wallet', label: '积分明细', path: '/merchant-wallet' }], children: [{ key: 'merchant-wallet', label: '积分明细', path: '/merchant-wallet' }],
}, },
@@ -114,7 +114,7 @@ const merchantSections: SidebarSection[] = [
}, },
{ {
key: 'funds', key: 'funds',
label: '资金', label: '积分',
icon: <WalletOutlined />, icon: <WalletOutlined />,
children: [{ key: 'merchant-wallet', label: '积分明细', path: '/merchant-wallet' }], children: [{ key: 'merchant-wallet', label: '积分明细', path: '/merchant-wallet' }],
}, },
+31 -11
View File
@@ -8,6 +8,7 @@ import {
Input, Input,
InputNumber, InputNumber,
Modal, Modal,
Popconfirm,
Select, Select,
Space, Space,
Table, Table,
@@ -206,14 +207,10 @@ export default function MerchantCenter({ fixedTab, title = '商户中心' }: Mer
} }
}, [ledger.page, ledger.size, merchantRole]) }, [ledger.page, ledger.size, merchantRole])
const loadAPIClients = useCallback(async (role = merchantRole) => { const loadAPIClients = useCallback(async () => {
if (role !== 'owner' && role !== 'operator') {
setApiClients([])
return
}
const clientData = await merchantApi.apiClients() const clientData = await merchantApi.apiClients()
setApiClients(clientData || []) setApiClients(clientData || [])
}, [merchantRole]) }, [])
const loadCallbacks = useCallback(async (role = merchantRole) => { const loadCallbacks = useCallback(async (role = merchantRole) => {
if (role !== 'owner' && role !== 'operator') { if (role !== 'owner' && role !== 'operator') {
@@ -245,7 +242,7 @@ export default function MerchantCenter({ fixedTab, title = '商户中心' }: Mer
await loadWallet(undefined, undefined, role) await loadWallet(undefined, undefined, role)
return return
case 'api': case 'api':
await loadAPIClients(role) await loadAPIClients()
return return
case 'callbacks': case 'callbacks':
await loadCallbacks(role) await loadCallbacks(role)
@@ -457,6 +454,16 @@ export default function MerchantCenter({ fixedTab, title = '商户中心' }: Mer
} }
} }
const deleteAPIClient = async (record: ApiClient) => {
try {
await merchantApi.deleteApiClient(record.id)
message.success('密钥已删除')
loadAPIClients()
} catch (e) {
message.error(e instanceof Error ? e.message : '删除失败')
}
}
const toggleCallback = async (record: CallbackSubscription) => { const toggleCallback = async (record: CallbackSubscription) => {
try { try {
await merchantApi.updateCallbackStatus(record.id, record.status === 'active' ? 'disabled' : 'active') await merchantApi.updateCallbackStatus(record.id, record.status === 'active' ? 'disabled' : 'active')
@@ -550,11 +557,24 @@ export default function MerchantCenter({ fixedTab, title = '商户中心' }: Mer
{ {
title: '操作', title: '操作',
key: 'action', key: 'action',
width: 100, width: 150,
render: (_, record) => canManage ? ( render: (_, record) => canManage ? (
<Button type="link" size="small" onClick={() => toggleAPIClient(record)}> <Space size={0}>
{record.status === 'active' ? '禁用' : '启用'} <Button type="link" size="small" onClick={() => toggleAPIClient(record)}>
</Button> {record.status === 'active' ? '禁用' : '启用'}
</Button>
<Popconfirm
title="删除该密钥?"
description={record.status === 'active' ? '请先停用该密钥再删除' : '删除后不可恢复,使用该密钥的对接方将无法调用开放接口。'}
okText="删除"
okButtonProps={{ danger: true }}
onConfirm={() => deleteAPIClient(record)}
>
<Button type="link" size="small" danger disabled={record.status === 'active'}>
</Button>
</Popconfirm>
</Space>
) : '-', ) : '-',
}, },
] ]