初次提交
Build VoiceHub FPK / build-fpk (push) Failing after 56m5s
Build and Push Docker Image / build-and-push (push) Canceled after 1h9m35s

This commit is contained in:
2026-09-06 08:19:02 +08:00
commit 4ce4649aaf
1061 changed files with 337258 additions and 0 deletions
+150
View File
@@ -0,0 +1,150 @@
import jwt from 'jsonwebtoken'
import { db } from '~/drizzle/db'
import { users } from '~/drizzle/schema'
import { executeRedisCommand, isRedisReady } from '../../utils/redis'
import { eq } from 'drizzle-orm'
// 用户认证缓存(永久缓存,登出或权限变更时主动失效)
export default defineEventHandler(async (event) => {
try {
const token =
getCookie(event, 'auth-token') || getHeader(event, 'authorization')?.replace('Bearer ', '')
if (!token) {
throw createError({
statusCode: 401,
statusMessage: '未提供认证令牌'
})
}
// 验证JWT令牌
const decoded = jwt.verify(token, process.env.JWT_SECRET!) as { userId: number }
const userId = decoded.userId
// 优先从Redis缓存获取用户认证状态
if (isRedisReady()) {
const cachedUser = await executeRedisCommand(async () => {
const client = (await import('../../utils/redis')).getRedisClient()
if (!client) return null
const cacheKey = `auth:user:${userId}`
const userData = await client.get(cacheKey)
if (userData) {
console.log(`[API] 用户认证缓存命中: ${userId}`)
return JSON.parse(userData)
}
return null
})
if (cachedUser) {
// 为缓存的用户数据添加字段
const userWithDetails = {
id: cachedUser.id,
username: cachedUser.username,
name: cachedUser.name,
grade: cachedUser.grade,
class: cachedUser.class,
studentId: cachedUser.studentId,
email: cachedUser.email,
emailVerified: cachedUser.emailVerified,
role: cachedUser.role,
requirePasswordChange: cachedUser.forcePasswordChange || !cachedUser.passwordChangedAt,
has2FA: cachedUser.identities?.some((id: any) => id.provider === 'totp') || false,
avatar: cachedUser.identities?.find((id: any) => id.provider === 'github')?.providerUsername
? `https://github.com/${cachedUser.identities.find((id: any) => id.provider === 'github').providerUsername}.png`
: null
}
return {
user: userWithDetails,
valid: true
}
}
}
// 缓存未命中或Redis不可用,从数据库获取用户信息
// 同时查询所有身份绑定信息
const userResult = await db.query.users.findFirst({
where: eq(users.id, userId),
columns: {
id: true,
username: true,
name: true,
grade: true,
class: true,
studentId: true,
email: true,
emailVerified: true,
role: true,
forcePasswordChange: true,
passwordChangedAt: true
},
with: {
identities: {
columns: {
provider: true,
providerUsername: true
}
}
}
})
const dbUser = userResult || null
if (!dbUser) {
throw createError({
statusCode: 401,
statusMessage: '用户不存在'
})
}
// 构建返回的用户对象,只包含需要的字段
const githubIdentity = dbUser.identities?.find((id: any) => id.provider === 'github')
const user = {
id: dbUser.id,
username: dbUser.username,
name: dbUser.name,
grade: dbUser.grade,
class: dbUser.class,
studentId: dbUser.studentId,
email: dbUser.email,
emailVerified: dbUser.emailVerified,
role: dbUser.role,
requirePasswordChange: dbUser.forcePasswordChange || !dbUser.passwordChangedAt,
has2FA: dbUser.identities?.some((id: any) => id.provider === 'totp') || false,
// 动态生成 GitHub 头像 URL
avatar: githubIdentity?.providerUsername
? `https://github.com/${githubIdentity.providerUsername}.png`
: null
}
// 将用户认证状态缓存到Redis(如果可用)- 永久缓存
if (isRedisReady()) {
await executeRedisCommand(async () => {
const client = (await import('../../utils/redis')).getRedisClient()
if (!client) return
const cacheKey = `auth:user:${userId}`
// 缓存完整的数据库用户信息,用于后续验证
await client.set(cacheKey, JSON.stringify(dbUser))
console.log(`[API] 用户认证状态已缓存: ${userId}`)
})
}
return {
user,
valid: true
}
} catch (error) {
if (error.name === 'JsonWebTokenError' || error.name === 'TokenExpiredError') {
throw createError({
statusCode: 401,
statusMessage: '令牌无效或已过期'
})
}
throw error
}
})