Files
BYW-VoiceHub/server/api/meow/bind.post.ts
T
Henry_Du cccaf36990 feat(brand): 站点品牌名统一为「宝一外校园点歌系统」
站长要求全站一致(小程序端已单独提交)。本次只改**用户可见文案与默认值**:

- 服务端默认值/兜底:system-settings-defaults、admin/system-settings(GET/POST)、
  site-config.get、utils/siteUtils —— 原来是 'VoiceHub' / '校园广播站点歌系统'
- 用户可见通知文案:MeoW 绑定验证码与标题、MeoW 测试通知、微信订阅测试通知
- 2FA 应用名(authenticator.keyuri)、WebAuthn rpName(出现在系统密钥弹窗里)
- 网页版界面:首页副标题、后台侧栏品牌名、登录/注册/找回/改密标题与文案、
  年度回顾(IntroSlide / ShareCard / 页面标题)、用户教程、通知发送预览、
  API 密钥与数据库导入说明、酷狗 sidecar 说明、打印页脚与歌单卡片页脚、Logo alt

**刻意不动**:localStorage/sessionStorage 的 `voicehub_*` 键、`voicehub:` 前缀的
STORAGE_KEY 与缓存/Redis 前缀、`window.voiceHubPlayer` 之类代码标识、MeoW 的
User-Agent、以及上游项目链接 `github.com/laoshuikaixue/VoiceHub`(那是对原项目的署名)。

注意:站点名**数据**(systemSettings.siteTitle)仍是你后台里设置的「宝一外校园点歌」,
代码只保证默认值与文案一致;要让全站显示完整名称,请在后台「站点配置 → 站点标题」
改成「宝一外校园点歌系统」。
2026-09-30 20:24:55 +08:00

195 lines
5.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { createError, defineEventHandler, readBody } from 'h3'
import { db } from '~/drizzle/db'
import { users } from '~/drizzle/schema'
import { and, eq, ne } from 'drizzle-orm'
// 生成6位数字验证码
function generateVerificationCode(): string {
return Math.floor(100000 + Math.random() * 900000).toString()
}
// 验证码存储(生产环境应使用Redis等持久化存储)
const verificationCodes = new Map<string, { code: string; userId: number; expiresAt: number }>()
export default defineEventHandler(async (event) => {
try {
// 从认证中间件获取用户信息
const user = event.context.user
if (!user) {
throw createError({
statusCode: 401,
statusMessage: '请先登录'
})
}
const userId = user.id
const body = await readBody(event)
const { action, meowId, verificationCode } = body
// 如果是验证并绑定操作
if (action === 'verify_and_bind' && verificationCode) {
const storedData = verificationCodes.get(meowId)
if (!storedData) {
throw createError({
statusCode: 400,
statusMessage: '验证码已过期,请重新发送'
})
}
if (storedData.userId !== userId) {
throw createError({
statusCode: 400,
statusMessage: '验证码不匹配'
})
}
if (Date.now() > storedData.expiresAt) {
verificationCodes.delete(meowId)
throw createError({
statusCode: 400,
statusMessage: '验证码已过期,请重新发送'
})
}
if (storedData.code !== verificationCode) {
throw createError({
statusCode: 400,
statusMessage: '验证码错误'
})
}
// 验证通过,绑定账号
await db
.update(users)
.set({
meowNickname: meowId,
meowBoundAt: new Date()
})
.where(eq(users.id, userId))
// 清除验证码
verificationCodes.delete(meowId)
return {
success: true,
message: 'MeoW 账号绑定成功!'
}
}
// 如果是发送验证码操作
if (action === 'send_verification') {
if (!meowId || !meowId.trim()) {
throw createError({
statusCode: 400,
statusMessage: '请输入 MeoW 用户 ID'
})
}
// 验证用户ID格式
if (meowId.includes('/')) {
throw createError({
statusCode: 400,
statusMessage: '用户 ID 不能包含斜杠'
})
}
// 检查用户是否存在
const userResult = await db.select().from(users).where(eq(users.id, userId)).limit(1)
const userExists = userResult[0]
if (!userExists) {
throw createError({
statusCode: 404,
statusMessage: '用户不存在'
})
}
// 检查是否有其他用户绑定了相同的 MeoW ID(仅用于记录,不阻止绑定)
const existingUsers = await db
.select({
name: users.name,
username: users.username
})
.from(users)
.where(and(eq(users.meowNickname, meowId), ne(users.id, userId)))
if (existingUsers.length > 0) {
console.log(
`MeoW ID "${meowId}" 已被 ${existingUsers.length} 个其他用户绑定:`,
existingUsers.map((u) => `${u.name}(${u.username})`).join(', ')
)
}
// 生成验证码
const code = generateVerificationCode()
const expiresAt = Date.now() + 5 * 60 * 1000 // 5分钟过期
// 存储验证码
verificationCodes.set(meowId, {
code,
userId,
expiresAt
})
// 发送验证码到 MeoW
const message = `宝一外校园点歌系统账号绑定验证码:${code}(5分钟内有效)`
const title = '宝一外校园点歌系统账号绑定'
try {
// 使用 GET 请求发送验证码
const meowUrl = `https://api.chuckfang.com/${encodeURIComponent(meowId)}/${encodeURIComponent(title)}/${encodeURIComponent(message)}`
const response = await fetch(meowUrl, {
method: 'GET',
headers: {
'User-Agent': 'VoiceHub/1.0'
}
})
if (!response.ok) {
throw new Error(`HTTP ${response.status}`)
}
const result = await response.json()
if (result.status !== 200) {
throw new Error(result.message || '发送失败')
}
return {
success: true,
message: '验证码已发送到您的 MeoW,请查收并输入验证码完成绑定',
requiresVerification: true
}
} catch (error) {
// 清除验证码
verificationCodes.delete(meowId)
console.error('发送 MeoW 验证码失败:', error)
throw createError({
statusCode: 500,
statusMessage: '发送验证码失败,请检查 MeoW ID 是否正确或稍后重试'
})
}
}
// 如果不是有效的操作
throw createError({
statusCode: 400,
statusMessage: '无效的操作'
})
} catch (error) {
console.error('MeoW 绑定失败:', error)
if (error.statusCode) {
throw error
}
throw createError({
statusCode: 500,
statusMessage: '绑定失败,请稍后重试'
})
}
})