feat: API Key 静默认证 + 多邮箱支持
核心改动: - V3 迁移: accounts 加 api_key/api_secret + account_emails 多邮箱关联表 - HMAC 验签改用 api_secret 替代密码哈希(改密码不影响集成) - findByAnyEmail 支持主邮箱 + 关联邮箱查找 - AdminController: API Key 生成/重置 + 邮箱绑定/解绑/列表 - 前端 Users.vue: API Key 列 + 生成按钮 + Secret 复制弹窗 - 现有邮箱自动迁移到 account_emails 表 认证流程: 旧: X-Email → account.email → passwordHash 作 HMAC key 新: X-Email → account_emails ∪ account.email → api_secret 作 HMAC key
This commit is contained in:
@@ -44,7 +44,8 @@ export default {
|
||||
},
|
||||
users: {
|
||||
list: () => api.get('/admin/accounts'),
|
||||
setTrustLevel: (id, level) => api.post(`/admin/accounts/${id}/trust-level`, null, { params: { level } })
|
||||
setTrustLevel: (id, level) => api.post(`/admin/accounts/${id}/trust-level`, null, { params: { level } }),
|
||||
genApiKey: (id) => api.post(`/admin/accounts/${id}/api-key`)
|
||||
},
|
||||
health: () => api.get('/health', { baseURL: '' })
|
||||
}
|
||||
|
||||
@@ -3,37 +3,69 @@
|
||||
<h2>用户管理</h2>
|
||||
<el-card style="margin-top: 20px">
|
||||
<el-table :data="users" v-loading="loading" stripe>
|
||||
<el-table-column prop="id" label="ID" width="80" />
|
||||
<el-table-column prop="email" label="邮箱" />
|
||||
<el-table-column prop="displayName" label="昵称" width="150" />
|
||||
<el-table-column prop="trustLevel" label="信任等级" width="100">
|
||||
<el-table-column prop="id" label="ID" width="60" />
|
||||
<el-table-column prop="email" label="邮箱" min-width="180" />
|
||||
<el-table-column prop="displayName" label="昵称" width="120" />
|
||||
<el-table-column label="API Key" min-width="180">
|
||||
<template #default="scope">
|
||||
<span v-if="scope.row.apiKey" style="font-family:monospace;font-size:12px">{{ scope.row.apiKey }}</span>
|
||||
<el-tag v-else size="small" type="info">未生成</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="trustLevel" label="信任等级" width="90">
|
||||
<template #default="scope">
|
||||
<el-tag :type="trustLevelType(scope.row.trustLevel)">
|
||||
{{ trustLevelLabel(scope.row.trustLevel) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="isActive" label="状态" width="80">
|
||||
<el-table-column prop="isActive" label="状态" width="70">
|
||||
<template #default="scope">
|
||||
<el-tag :type="scope.row.isActive ? 'success' : 'danger'">
|
||||
{{ scope.row.isActive ? '启用' : '禁用' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="createdAt" label="注册时间" width="180" />
|
||||
<el-table-column label="操作" width="120">
|
||||
<el-table-column prop="createdAt" label="注册时间" width="160" />
|
||||
<el-table-column label="操作" width="200" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-button size="small" @click="handleGenKey(scope.row)">生成密钥</el-button>
|
||||
<el-button
|
||||
v-if="scope.row.trustLevel !== 'ADMIN'"
|
||||
size="small"
|
||||
@click="handleSetLevel(scope.row)"
|
||||
>设置等级</el-button>
|
||||
<span v-else style="color:#999;font-size:12px">管理员</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-card>
|
||||
|
||||
<!-- API Key 弹窗 -->
|
||||
<el-dialog v-model="keyDialogVisible" title="API Key" width="500px" :close-on-click-modal="false">
|
||||
<el-alert type="warning" :closable="false" style="margin-bottom:16px">
|
||||
Secret 仅在生成时显示一次,请立即复制保存!
|
||||
</el-alert>
|
||||
<el-form label-width="80px">
|
||||
<el-form-item label="API Key">
|
||||
<el-input v-model="keyResult.apiKey" readonly>
|
||||
<template #append>
|
||||
<el-button @click="copyText(keyResult.apiKey)">复制</el-button>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="API Secret">
|
||||
<el-input v-model="keyResult.apiSecret" readonly type="textarea" :rows="2">
|
||||
<template #append>
|
||||
<el-button @click="copyText(keyResult.apiSecret)">复制</el-button>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="keyDialogVisible = false">关闭</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 等级设置弹窗 -->
|
||||
<el-dialog v-model="dialogVisible" title="设置信任等级" width="400px">
|
||||
<el-form :model="levelForm">
|
||||
@@ -63,6 +95,8 @@ const loading = ref(false)
|
||||
const dialogVisible = ref(false)
|
||||
const saving = ref(false)
|
||||
const levelForm = reactive({ id: null, email: '', level: '' })
|
||||
const keyDialogVisible = ref(false)
|
||||
const keyResult = reactive({ apiKey: '', apiSecret: '' })
|
||||
|
||||
const trustLevelType = (level) => {
|
||||
const map = { MEMBER: '', TRUSTED: 'warning', ADMIN: 'danger' }
|
||||
@@ -86,6 +120,22 @@ const fetchUsers = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
const handleGenKey = async (row) => {
|
||||
try {
|
||||
const res = await api.users.genApiKey(row.id)
|
||||
keyResult.apiKey = res.data.apiKey
|
||||
keyResult.apiSecret = res.data.apiSecret
|
||||
keyDialogVisible.value = true
|
||||
fetchUsers()
|
||||
} catch (e) {
|
||||
ElMessage.error(e.response?.data?.message || '生成失败')
|
||||
}
|
||||
}
|
||||
|
||||
const copyText = (text) => {
|
||||
navigator.clipboard.writeText(text).then(() => ElMessage.success('已复制'))
|
||||
}
|
||||
|
||||
const handleSetLevel = (row) => {
|
||||
levelForm.id = row.id
|
||||
levelForm.email = row.email
|
||||
|
||||
Reference in New Issue
Block a user