初次提交
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
import { apiKeyPermissions, apiKeys, db } from '~/drizzle/db'
|
||||
import { eq } from 'drizzle-orm'
|
||||
|
||||
/**
|
||||
* 删除API Key
|
||||
* DELETE /api/admin/api-keys/[id]
|
||||
*/
|
||||
export default defineEventHandler(async (event) => {
|
||||
// 检查用户权限 - 只有超级管理员可以管理API Key
|
||||
const user = event.context.user
|
||||
if (!user || user.role !== 'SUPER_ADMIN') {
|
||||
throw createError({
|
||||
statusCode: 403,
|
||||
statusMessage: '只有超级管理员可以管理API Key'
|
||||
})
|
||||
}
|
||||
|
||||
const apiKeyId = getRouterParam(event, 'id')
|
||||
|
||||
if (!apiKeyId) {
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: 'API Key ID不能为空'
|
||||
})
|
||||
}
|
||||
|
||||
try {
|
||||
// 检查API Key是否存在
|
||||
const existingApiKey = await db
|
||||
.select({
|
||||
id: apiKeys.id,
|
||||
name: apiKeys.name
|
||||
})
|
||||
.from(apiKeys)
|
||||
.where(eq(apiKeys.id, apiKeyId))
|
||||
.limit(1)
|
||||
|
||||
if (existingApiKey.length === 0) {
|
||||
throw createError({
|
||||
statusCode: 404,
|
||||
statusMessage: 'API Key不存在'
|
||||
})
|
||||
}
|
||||
|
||||
const apiKeyName = existingApiKey[0].name
|
||||
|
||||
// 开始事务删除
|
||||
await db.transaction(async (tx) => {
|
||||
// 删除API Key权限
|
||||
await tx.delete(apiKeyPermissions).where(eq(apiKeyPermissions.apiKeyId, apiKeyId))
|
||||
|
||||
// 删除API访问日志(可选,根据业务需求决定是否保留历史日志)
|
||||
// 这里我们选择保留日志记录,只删除API Key本身
|
||||
// await tx.delete(apiLogs)
|
||||
// .where(eq(apiLogs.apiKeyId, apiKeyId))
|
||||
|
||||
// 删除API Key
|
||||
await tx.delete(apiKeys).where(eq(apiKeys.id, apiKeyId))
|
||||
})
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: `API Key "${apiKeyName}" 删除成功`,
|
||||
data: {
|
||||
id: apiKeyId,
|
||||
name: apiKeyName
|
||||
}
|
||||
}
|
||||
} catch (error: any) {
|
||||
if (error.statusCode) {
|
||||
throw error
|
||||
}
|
||||
|
||||
throw createError({
|
||||
statusCode: 500,
|
||||
statusMessage: `删除API Key失败: ${error.message}`
|
||||
})
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,92 @@
|
||||
import { apiKeyPermissions, apiKeys, db, users } from '~/drizzle/db'
|
||||
import { eq } from 'drizzle-orm'
|
||||
|
||||
/**
|
||||
* 获取API Key详情
|
||||
* GET /api/admin/api-keys/[id]
|
||||
*/
|
||||
export default defineEventHandler(async (event) => {
|
||||
// 检查用户权限 - 只有超级管理员可以管理API Key
|
||||
const user = event.context.user
|
||||
if (!user || user.role !== 'SUPER_ADMIN') {
|
||||
throw createError({
|
||||
statusCode: 403,
|
||||
statusMessage: '只有超级管理员可以管理API Key'
|
||||
})
|
||||
}
|
||||
|
||||
const apiKeyId = getRouterParam(event, 'id')
|
||||
|
||||
if (!apiKeyId) {
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: 'API Key ID不能为空'
|
||||
})
|
||||
}
|
||||
|
||||
try {
|
||||
// 获取API Key基本信息
|
||||
const apiKeyResult = await db
|
||||
.select({
|
||||
id: apiKeys.id,
|
||||
name: apiKeys.name,
|
||||
description: apiKeys.description,
|
||||
keyPrefix: apiKeys.keyPrefix,
|
||||
isActive: apiKeys.isActive,
|
||||
expiresAt: apiKeys.expiresAt,
|
||||
|
||||
usageCount: apiKeys.usageCount,
|
||||
lastUsedAt: apiKeys.lastUsedAt,
|
||||
createdAt: apiKeys.createdAt,
|
||||
updatedAt: apiKeys.updatedAt,
|
||||
createdBy: apiKeys.createdByUserId,
|
||||
creatorName: users.name
|
||||
})
|
||||
.from(apiKeys)
|
||||
.leftJoin(users, eq(apiKeys.createdByUserId, users.id))
|
||||
.where(eq(apiKeys.id, apiKeyId))
|
||||
.limit(1)
|
||||
|
||||
const apiKey = apiKeyResult[0]
|
||||
|
||||
if (!apiKey) {
|
||||
throw createError({
|
||||
statusCode: 404,
|
||||
statusMessage: 'API Key不存在'
|
||||
})
|
||||
}
|
||||
|
||||
// 获取API Key权限
|
||||
const permissionsResult = await db
|
||||
.select({
|
||||
permission: apiKeyPermissions.permission
|
||||
})
|
||||
.from(apiKeyPermissions)
|
||||
.where(eq(apiKeyPermissions.apiKeyId, apiKeyId))
|
||||
|
||||
const permissions = permissionsResult.map((p) => p.permission)
|
||||
|
||||
// 计算状态
|
||||
const isExpired = apiKey.expiresAt ? new Date() > apiKey.expiresAt : false
|
||||
const status = !apiKey.isActive ? 'inactive' : isExpired ? 'expired' : 'active'
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
...apiKey,
|
||||
permissions,
|
||||
isExpired,
|
||||
status
|
||||
}
|
||||
}
|
||||
} catch (error: any) {
|
||||
if (error.statusCode) {
|
||||
throw error
|
||||
}
|
||||
|
||||
throw createError({
|
||||
statusCode: 500,
|
||||
statusMessage: `获取API Key详情失败: ${error.message}`
|
||||
})
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,163 @@
|
||||
import { apiKeyPermissions, apiKeys, db } from '~/drizzle/db'
|
||||
import { eq } from 'drizzle-orm'
|
||||
import { z } from 'zod'
|
||||
import { getBeijingTime } from '~/utils/timeUtils'
|
||||
|
||||
/**
|
||||
* 更新API Key
|
||||
* PUT /api/admin/api-keys/[id]
|
||||
*/
|
||||
|
||||
// 请求体验证schema
|
||||
const updateApiKeySchema = z.object({
|
||||
name: z
|
||||
.string()
|
||||
.min(1, 'API Key名称不能为空')
|
||||
.max(100, 'API Key名称不能超过100个字符')
|
||||
.optional(),
|
||||
description: z.string().max(500, '描述不能超过500个字符').optional().nullable(),
|
||||
isActive: z.boolean().optional(),
|
||||
expiresAt: z.string().optional().nullable(),
|
||||
|
||||
permissions: z
|
||||
.array(z.enum(['schedules:read', 'songs:read']))
|
||||
.min(1, '至少需要选择一个权限')
|
||||
.optional()
|
||||
})
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
// 检查用户权限 - 只有超级管理员可以管理API Key
|
||||
const user = event.context.user
|
||||
if (!user || user.role !== 'SUPER_ADMIN') {
|
||||
throw createError({
|
||||
statusCode: 403,
|
||||
statusMessage: '只有超级管理员可以管理API Key'
|
||||
})
|
||||
}
|
||||
|
||||
const apiKeyId = getRouterParam(event, 'id')
|
||||
|
||||
if (!apiKeyId) {
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: 'API Key ID不能为空'
|
||||
})
|
||||
}
|
||||
|
||||
try {
|
||||
// 验证请求体
|
||||
const body = await readBody(event)
|
||||
const validatedData = updateApiKeySchema.parse(body)
|
||||
|
||||
// 检查API Key是否存在
|
||||
const existingApiKey = await db
|
||||
.select({ id: apiKeys.id })
|
||||
.from(apiKeys)
|
||||
.where(eq(apiKeys.id, apiKeyId))
|
||||
.limit(1)
|
||||
|
||||
if (existingApiKey.length === 0) {
|
||||
throw createError({
|
||||
statusCode: 404,
|
||||
statusMessage: 'API Key不存在'
|
||||
})
|
||||
}
|
||||
|
||||
// 处理过期时间
|
||||
let expiresAt = undefined
|
||||
if ('expiresAt' in validatedData) {
|
||||
if (validatedData.expiresAt === null) {
|
||||
expiresAt = null
|
||||
} else if (validatedData.expiresAt) {
|
||||
try {
|
||||
// 处理预设选项格式 (3d, 7d, 30d, 60d, 90d)
|
||||
if (validatedData.expiresAt.match(/^\d+d$/)) {
|
||||
const days = parseInt(validatedData.expiresAt.replace('d', ''))
|
||||
expiresAt = getBeijingTime()
|
||||
expiresAt.setDate(expiresAt.getDate() + days)
|
||||
} else {
|
||||
// 处理传统的日期时间格式(向后兼容)
|
||||
expiresAt = new Date(validatedData.expiresAt)
|
||||
// 验证日期是否有效
|
||||
if (isNaN(expiresAt.getTime())) {
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: '无效的过期时间格式'
|
||||
})
|
||||
}
|
||||
// 验证过期时间不能是过去的时间
|
||||
if (expiresAt <= getBeijingTime()) {
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: '过期时间不能是过去的时间'
|
||||
})
|
||||
}
|
||||
}
|
||||
} catch (error: any) {
|
||||
if (error.statusCode) {
|
||||
throw error
|
||||
}
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: '无效的过期时间格式'
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 开始事务
|
||||
const result = await db.transaction(async (tx) => {
|
||||
// 准备更新数据
|
||||
const updateData: any = {}
|
||||
|
||||
if (validatedData.name !== undefined) updateData.name = validatedData.name
|
||||
if ('description' in validatedData) updateData.description = validatedData.description
|
||||
if (validatedData.isActive !== undefined) updateData.isActive = validatedData.isActive
|
||||
if ('expiresAt' in validatedData) updateData.expiresAt = expiresAt
|
||||
|
||||
// 更新API Key基本信息
|
||||
await tx.update(apiKeys).set(updateData).where(eq(apiKeys.id, apiKeyId))
|
||||
|
||||
// 更新权限(如果提供了权限数据)
|
||||
if (validatedData.permissions) {
|
||||
// 删除现有权限
|
||||
await tx.delete(apiKeyPermissions).where(eq(apiKeyPermissions.apiKeyId, apiKeyId))
|
||||
|
||||
// 插入新权限
|
||||
const permissionValues = validatedData.permissions.map((permission) => ({
|
||||
apiKeyId,
|
||||
permission
|
||||
}))
|
||||
|
||||
await tx.insert(apiKeyPermissions).values(permissionValues)
|
||||
}
|
||||
|
||||
return { apiKeyId }
|
||||
})
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: 'API Key更新成功',
|
||||
data: {
|
||||
id: result.apiKeyId
|
||||
}
|
||||
}
|
||||
} catch (error: any) {
|
||||
if (error.statusCode) {
|
||||
throw error
|
||||
}
|
||||
|
||||
// 处理Zod验证错误
|
||||
if (error.name === 'ZodError') {
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: `请求参数验证失败: ${error.errors.map((e: any) => e.message).join(', ')}`
|
||||
})
|
||||
}
|
||||
|
||||
throw createError({
|
||||
statusCode: 500,
|
||||
statusMessage: `更新API Key失败: ${error.message}`
|
||||
})
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,199 @@
|
||||
import { apiKeyPermissions, apiKeys, db, users } from '~/drizzle/db'
|
||||
import { and, count, desc, eq, inArray, sql } from 'drizzle-orm'
|
||||
|
||||
/**
|
||||
* 获取API Key列表
|
||||
* GET /api/admin/api-keys
|
||||
*/
|
||||
export default defineEventHandler(async (event) => {
|
||||
// 检查用户权限 - 只有超级管理员可以管理API Key
|
||||
const user = event.context.user
|
||||
if (!user || user.role !== 'SUPER_ADMIN') {
|
||||
throw createError({
|
||||
statusCode: 403,
|
||||
statusMessage: '只有超级管理员可以管理API Key'
|
||||
})
|
||||
}
|
||||
|
||||
const query = getQuery(event)
|
||||
const page = parseInt(query.page as string) || 1
|
||||
const limit = parseInt(query.limit as string) || 20
|
||||
const search = (query.search as string) || ''
|
||||
const status = query.status as string // 'active', 'inactive', 'expired'
|
||||
|
||||
const offset = (page - 1) * limit
|
||||
|
||||
try {
|
||||
// 构建查询条件
|
||||
const whereConditions = []
|
||||
|
||||
if (search) {
|
||||
whereConditions.push(
|
||||
sql`${apiKeys.name} ILIKE ${`%${search}%`} OR ${apiKeys.description} ILIKE ${`%${search}%`}`
|
||||
)
|
||||
}
|
||||
|
||||
if (status === 'active') {
|
||||
whereConditions.push(
|
||||
and(
|
||||
eq(apiKeys.isActive, true),
|
||||
sql`(${apiKeys.expiresAt} IS NULL OR ${apiKeys.expiresAt} > NOW())`
|
||||
)
|
||||
)
|
||||
} else if (status === 'inactive') {
|
||||
whereConditions.push(eq(apiKeys.isActive, false))
|
||||
} else if (status === 'expired') {
|
||||
whereConditions.push(
|
||||
and(
|
||||
eq(apiKeys.isActive, true),
|
||||
sql`${apiKeys.expiresAt} IS NOT NULL AND ${apiKeys.expiresAt} <= NOW()`
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
const whereClause =
|
||||
whereConditions.length > 0
|
||||
? whereConditions.length === 1
|
||||
? whereConditions[0]
|
||||
: and(...whereConditions)
|
||||
: undefined
|
||||
|
||||
// 获取API Key列表
|
||||
const apiKeysList = await db
|
||||
.select({
|
||||
id: apiKeys.id,
|
||||
name: apiKeys.name,
|
||||
description: apiKeys.description,
|
||||
keyPrefix: apiKeys.keyPrefix,
|
||||
isActive: apiKeys.isActive,
|
||||
expiresAt: apiKeys.expiresAt,
|
||||
|
||||
usageCount: apiKeys.usageCount,
|
||||
lastUsedAt: apiKeys.lastUsedAt,
|
||||
createdAt: apiKeys.createdAt,
|
||||
updatedAt: apiKeys.updatedAt,
|
||||
createdByUserId: apiKeys.createdByUserId,
|
||||
creatorName: users.name
|
||||
})
|
||||
.from(apiKeys)
|
||||
.leftJoin(users, eq(apiKeys.createdByUserId, users.id))
|
||||
.where(whereClause)
|
||||
.orderBy(desc(apiKeys.createdAt))
|
||||
.limit(limit)
|
||||
.offset(offset)
|
||||
|
||||
// 获取总数
|
||||
const totalResult = await db.select({ count: count() }).from(apiKeys).where(whereClause)
|
||||
|
||||
const total = totalResult[0]?.count || 0
|
||||
|
||||
// 获取每个API Key的权限
|
||||
const apiKeyIds = apiKeysList.map((key) => key.id).filter((id) => id != null)
|
||||
let permissions = []
|
||||
if (apiKeyIds.length > 0) {
|
||||
try {
|
||||
permissions = await db
|
||||
.select({
|
||||
apiKeyId: apiKeyPermissions.apiKeyId,
|
||||
permission: apiKeyPermissions.permission
|
||||
})
|
||||
.from(apiKeyPermissions)
|
||||
.where(inArray(apiKeyPermissions.apiKeyId, apiKeyIds))
|
||||
|
||||
// 确保permissions是数组
|
||||
if (!Array.isArray(permissions)) {
|
||||
permissions = []
|
||||
}
|
||||
} catch (permError) {
|
||||
console.error('获取权限数据失败:', permError)
|
||||
permissions = []
|
||||
}
|
||||
}
|
||||
|
||||
// 组织权限数据
|
||||
const permissionsMap: Record<string, string[]> = {}
|
||||
try {
|
||||
if (permissions && Array.isArray(permissions) && permissions.length > 0) {
|
||||
for (const perm of permissions) {
|
||||
if (
|
||||
perm &&
|
||||
typeof perm === 'object' &&
|
||||
perm.hasOwnProperty('apiKeyId') &&
|
||||
perm.hasOwnProperty('permission') &&
|
||||
perm.apiKeyId != null &&
|
||||
perm.permission != null
|
||||
) {
|
||||
const apiKeyId = String(perm.apiKeyId)
|
||||
const permission = String(perm.permission)
|
||||
|
||||
if (apiKeyId && permission) {
|
||||
if (!permissionsMap[apiKeyId]) {
|
||||
permissionsMap[apiKeyId] = []
|
||||
}
|
||||
permissionsMap[apiKeyId].push(permission)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (mapError) {
|
||||
console.error('组织权限数据失败:', mapError)
|
||||
// permissionsMap已经初始化为空对象,继续执行
|
||||
}
|
||||
|
||||
// 组合结果
|
||||
let result = []
|
||||
try {
|
||||
if (Array.isArray(apiKeysList)) {
|
||||
result = apiKeysList
|
||||
.map((key) => {
|
||||
// 确保key对象存在且有必要的属性
|
||||
if (!key || typeof key !== 'object' || key.id == null) {
|
||||
return null
|
||||
}
|
||||
|
||||
try {
|
||||
const keyId = String(key.id)
|
||||
const expiresAt = key.expiresAt ? new Date(key.expiresAt) : null
|
||||
const now = new Date()
|
||||
|
||||
return {
|
||||
...key,
|
||||
permissions: permissionsMap[keyId] || [],
|
||||
isExpired: expiresAt ? now > expiresAt : false,
|
||||
status: !key.isActive
|
||||
? 'inactive'
|
||||
: expiresAt && now > expiresAt
|
||||
? 'expired'
|
||||
: 'active'
|
||||
}
|
||||
} catch (keyError) {
|
||||
console.error('处理API Key数据失败:', keyError, key)
|
||||
return null
|
||||
}
|
||||
})
|
||||
.filter(Boolean) // 过滤掉null值
|
||||
}
|
||||
} catch (resultError) {
|
||||
console.error('组合结果失败:', resultError)
|
||||
result = []
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
items: result,
|
||||
pagination: {
|
||||
page,
|
||||
limit,
|
||||
total,
|
||||
totalPages: Math.ceil(total / limit)
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error: any) {
|
||||
throw createError({
|
||||
statusCode: 500,
|
||||
statusMessage: `获取API Key列表失败: ${error.message}`
|
||||
})
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,161 @@
|
||||
import { apiKeyPermissions, apiKeys, db } from '~/drizzle/db'
|
||||
import crypto from 'crypto'
|
||||
import { z } from 'zod'
|
||||
import { getBeijingTime } from '~/utils/timeUtils'
|
||||
|
||||
/**
|
||||
* 创建API Key
|
||||
* POST /api/admin/api-keys
|
||||
*/
|
||||
|
||||
// 请求体验证schema
|
||||
const createApiKeySchema = z.object({
|
||||
name: z.string().min(1, 'API Key名称不能为空').max(100, 'API Key名称不能超过100个字符'),
|
||||
description: z
|
||||
.union([z.string().max(500, '描述不能超过500个字符'), z.null(), z.undefined()])
|
||||
.optional(),
|
||||
expiresAt: z.union([z.string(), z.null(), z.undefined()]).optional(),
|
||||
|
||||
permissions: z.array(z.enum(['schedules:read', 'songs:read'])).min(1, '至少需要选择一个权限')
|
||||
})
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
// 检查用户权限 - 只有超级管理员可以管理API Key
|
||||
const user = event.context.user
|
||||
if (!user || user.role !== 'SUPER_ADMIN') {
|
||||
throw createError({
|
||||
statusCode: 403,
|
||||
statusMessage: '只有超级管理员可以管理API Key'
|
||||
})
|
||||
}
|
||||
|
||||
try {
|
||||
// 验证请求体
|
||||
const body = await readBody(event)
|
||||
const validatedData = createApiKeySchema.parse(body)
|
||||
|
||||
// 生成API Key
|
||||
const apiKey = generateApiKey()
|
||||
const keyPrefix = apiKey.substring(0, 10) // vhub_xxxxx
|
||||
const keyHash = crypto.createHash('sha256').update(apiKey).digest('hex')
|
||||
|
||||
// 处理过期时间
|
||||
let expiresAt: Date | null = null
|
||||
if (
|
||||
validatedData.expiresAt &&
|
||||
typeof validatedData.expiresAt === 'string' &&
|
||||
validatedData.expiresAt.trim() !== ''
|
||||
) {
|
||||
try {
|
||||
// 处理预设选项格式 (3d, 7d, 30d, 60d, 90d)
|
||||
if (validatedData.expiresAt.match(/^\d+d$/)) {
|
||||
const days = parseInt(validatedData.expiresAt.replace('d', ''))
|
||||
expiresAt = getBeijingTime()
|
||||
expiresAt.setDate(expiresAt.getDate() + days)
|
||||
} else {
|
||||
// 处理传统的日期时间格式(向后兼容)
|
||||
expiresAt = new Date(validatedData.expiresAt)
|
||||
// 验证日期是否有效
|
||||
if (isNaN(expiresAt.getTime())) {
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: '无效的过期时间格式'
|
||||
})
|
||||
}
|
||||
// 验证过期时间不能是过去的时间
|
||||
if (expiresAt <= getBeijingTime()) {
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: '过期时间不能是过去的时间'
|
||||
})
|
||||
}
|
||||
}
|
||||
} catch (error: any) {
|
||||
if (error.statusCode) {
|
||||
throw error
|
||||
}
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: '无效的过期时间格式'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 开始事务
|
||||
const result = await db.transaction(async (tx) => {
|
||||
// 插入API Key记录
|
||||
const apiKeyResult = await tx
|
||||
.insert(apiKeys)
|
||||
.values({
|
||||
name: validatedData.name,
|
||||
description: validatedData.description || null,
|
||||
keyPrefix,
|
||||
keyHash,
|
||||
isActive: true,
|
||||
expiresAt,
|
||||
|
||||
usageCount: 0,
|
||||
createdByUserId: user.id
|
||||
})
|
||||
.returning({ id: apiKeys.id })
|
||||
|
||||
const apiKeyId = apiKeyResult[0].id
|
||||
|
||||
// 插入权限记录
|
||||
const permissionValues = validatedData.permissions.map((permission) => ({
|
||||
apiKeyId,
|
||||
permission
|
||||
}))
|
||||
|
||||
await tx.insert(apiKeyPermissions).values(permissionValues)
|
||||
|
||||
return {
|
||||
id: apiKeyId,
|
||||
apiKey, // 只在创建时返回完整的API Key
|
||||
name: validatedData.name,
|
||||
description: validatedData.description,
|
||||
keyPrefix,
|
||||
isActive: true,
|
||||
expiresAt,
|
||||
|
||||
permissions: validatedData.permissions,
|
||||
usageCount: 0,
|
||||
createdBy: user.id,
|
||||
creatorName: user.name
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: 'API Key创建成功',
|
||||
data: result
|
||||
}
|
||||
} catch (error: any) {
|
||||
if (error.statusCode) {
|
||||
throw error
|
||||
}
|
||||
|
||||
// 处理Zod验证错误
|
||||
if (error.name === 'ZodError') {
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: `请求参数验证失败: ${error.errors.map((e: any) => e.message).join(', ')}`
|
||||
})
|
||||
}
|
||||
|
||||
throw createError({
|
||||
statusCode: 500,
|
||||
statusMessage: `创建API Key失败: ${error.message}`
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* 生成API Key
|
||||
* 格式: vhub_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx (37字符)
|
||||
*/
|
||||
function generateApiKey(): string {
|
||||
const prefix = 'vhub_'
|
||||
const randomBytes = crypto.randomBytes(16).toString('hex') // 32字符
|
||||
return prefix + randomBytes
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
import { ApiLogService } from '~~/server/services/apiLogService'
|
||||
|
||||
/**
|
||||
* 获取API访问日志
|
||||
* GET /api/admin/api-keys/logs
|
||||
*/
|
||||
export default defineEventHandler(async (event) => {
|
||||
// 检查用户权限 - 只有超级管理员可以查看API日志
|
||||
const user = event.context.user
|
||||
if (!user || user.role !== 'SUPER_ADMIN') {
|
||||
throw createError({
|
||||
statusCode: 403,
|
||||
statusMessage: '只有超级管理员可以查看API日志'
|
||||
})
|
||||
}
|
||||
|
||||
const query = getQuery(event)
|
||||
const page = parseInt(query.page as string) || 1
|
||||
const limit = parseInt(query.limit as string) || 50
|
||||
const apiKeyId = query.apiKeyId as string
|
||||
const endpoint = query.endpoint as string
|
||||
const method = query.method as string
|
||||
const statusCode = query.statusCode ? parseInt(query.statusCode as string) : undefined
|
||||
const startDate = query.startDate as string
|
||||
const endDate = query.endDate as string
|
||||
const ipAddress = query.ipAddress as string
|
||||
const sortBy = (query.sortBy as string) || 'createdAt'
|
||||
const sortOrder = (query.sortOrder as string) || 'desc'
|
||||
|
||||
const offset = (page - 1) * limit
|
||||
|
||||
try {
|
||||
// 使用ApiLogService获取日志
|
||||
const result = await ApiLogService.getLogs({
|
||||
apiKeyId,
|
||||
endpoint,
|
||||
method: method?.toUpperCase(),
|
||||
statusCode,
|
||||
ipAddress,
|
||||
startDate: startDate ? new Date(startDate) : undefined,
|
||||
endDate: endDate ? new Date(endDate) : undefined,
|
||||
page,
|
||||
limit,
|
||||
sortBy,
|
||||
sortOrder
|
||||
})
|
||||
|
||||
// 获取统计信息
|
||||
const stats = await ApiLogService.getStats({
|
||||
apiKeyId,
|
||||
endpoint,
|
||||
method: method?.toUpperCase(),
|
||||
statusCode,
|
||||
ipAddress,
|
||||
startDate: startDate ? new Date(startDate) : undefined,
|
||||
endDate: endDate ? new Date(endDate) : undefined
|
||||
})
|
||||
|
||||
return {
|
||||
logs: result.logs,
|
||||
pagination: result.pagination,
|
||||
stats: stats.basic
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取API访问日志失败:', error)
|
||||
throw createError({
|
||||
statusCode: 500,
|
||||
statusMessage: '获取API访问日志失败'
|
||||
})
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user