登录注册接入数据库
This commit is contained in:
@@ -166,6 +166,62 @@ const captcha = ref<string>('')
|
||||
|
||||
const isLoading = ref<boolean>(false)
|
||||
|
||||
/**
|
||||
* 【核心函数】:登录成功后,多条件校验是否为商家角色
|
||||
* 优先级: session_uid (auth_id) -> id -> normalized email
|
||||
*/
|
||||
const checkMerchantAccess = async (uid: string, rawEmail: string) : Promise<boolean> => {
|
||||
const email = rawEmail.trim().toLowerCase()
|
||||
console.log(`🔍 开始校验商家端角色 -> UID: ${uid}, Email: ${email}`)
|
||||
|
||||
try {
|
||||
// 1. 尝试按 auth_id 查询
|
||||
let res = await supa.from('ak_users').select('role').eq('auth_id', uid).execute()
|
||||
let dataArray = res.data
|
||||
if (Array.isArray(dataArray) && dataArray.length > 0) {
|
||||
const role = (dataArray[0] as UTSJSONObject).getString('role')
|
||||
console.log('✅ 按 auth_id 匹配成功,role:', role)
|
||||
return role === 'merchant'
|
||||
}
|
||||
|
||||
// 2. 尝试按 id 查询 (兼容老数据)
|
||||
res = await supa.from('ak_users').select('role').eq('id', uid).execute()
|
||||
dataArray = res.data
|
||||
if (Array.isArray(dataArray) && dataArray.length > 0) {
|
||||
const role = (dataArray[0] as UTSJSONObject).getString('role')
|
||||
console.log('✅ 按 id 匹配成功,role:', role)
|
||||
return role === 'merchant'
|
||||
}
|
||||
|
||||
// 3. 尝试按 email 兜底查询
|
||||
if (email !== '') {
|
||||
res = await supa.from('ak_users').select('role').eq('email', email).execute()
|
||||
dataArray = res.data
|
||||
|
||||
if (Array.isArray(dataArray) && dataArray.length > 0) {
|
||||
// 如果按邮箱查出来多条,可能存在脏数据,只取第一条并记录日志
|
||||
if (dataArray.length > 1) {
|
||||
console.error('⚠️ 警告: 按 email 查到多条 ak_users 记录,取第一条校验。Email:', email)
|
||||
}
|
||||
const role = (dataArray[0] as UTSJSONObject).getString('role')
|
||||
console.log('✅ 按 email 匹配成功,role:', role)
|
||||
return role === 'merchant'
|
||||
}
|
||||
}
|
||||
|
||||
console.error('❌ 未能在 ak_users 中找到该用户的任何记录')
|
||||
// 查无此人,跑出自定义错误以与普通系统报错区分
|
||||
throw new Error('NOT_REGISTERED')
|
||||
} catch (e) {
|
||||
console.error('❌ 查询角色过程异常:', e)
|
||||
if (e instanceof Error && e.message === 'NOT_REGISTERED') {
|
||||
throw new Error('您还没有注册商家端账户,快去注册一个')
|
||||
}
|
||||
// 真实的查询异常/RLS异常抛出,防止误会为"未注册"
|
||||
throw new Error('商家身份校验失败,请联系管理员检查用户数据')
|
||||
}
|
||||
}
|
||||
|
||||
const codeDisabled = ref<boolean>(false)
|
||||
const codeText = ref<string>('获取验证码')
|
||||
let codeTimer: number | null = null
|
||||
@@ -260,8 +316,8 @@ const getCode = async () => {
|
||||
const handleLogin = async () => {
|
||||
if (!validateAccount()) return
|
||||
|
||||
// 特殊账号处理:admin/admin 直接跳转
|
||||
if (account.value === 'admin' && password.value === 'admin') {
|
||||
// 特殊账号处理:仅在测试模式下保留直登逻辑,生产环境强制校验角色
|
||||
if (IS_TEST_MODE && account.value === 'admin' && password.value === 'admin') {
|
||||
setIsLoggedIn(true)
|
||||
const adminProfile = {
|
||||
id: 'admin',
|
||||
@@ -301,13 +357,12 @@ const handleLogin = async () => {
|
||||
if (loginType.value === 0) {
|
||||
const isEmail = account.value.includes('@')
|
||||
if (isEmail) {
|
||||
// 邮箱 + 密码登录(Supabase Auth)
|
||||
// 1) 调用 Supabase Auth 登录
|
||||
const result = await supa.signIn(account.value.trim(), password.value)
|
||||
console.log('signIn result:', result)
|
||||
|
||||
// 检查登录是否失败
|
||||
if (result.user == null) {
|
||||
// 检查是否是邮箱未确认的错误
|
||||
const rawData = result.raw as UTSJSONObject
|
||||
const errorMsg = rawData?.getString('msg') ?? ''
|
||||
const errorCode = rawData?.getString('error_code') ?? ''
|
||||
@@ -317,12 +372,25 @@ const handleLogin = async () => {
|
||||
errorMsg.includes('邮箱') && errorMsg.includes('确认')) {
|
||||
throw new Error('邮箱未确认,请先检查邮箱并点击确认链接')
|
||||
} else if (errorMsg.includes('Invalid login credentials') ||
|
||||
errorCode === 'invalid_credentials') {
|
||||
errorCode === 'invalid_credentials' ||
|
||||
errorMsg.includes('Invalid credentials')) {
|
||||
throw new Error('邮箱或密码错误')
|
||||
} else {
|
||||
throw new Error(errorMsg || '登录失败,请重试')
|
||||
}
|
||||
}
|
||||
|
||||
// 2) 【核心逻辑】:执行商家端角色准入校验
|
||||
// 优先使用 session user id 来查询数据库里的真实 user 数据兜底校验
|
||||
const sessionUser = result.user
|
||||
let sessionUid = sessionUser?.getString('id') ?? ''
|
||||
|
||||
const isMerchant = await checkMerchantAccess(sessionUid, account.value)
|
||||
if (!isMerchant) {
|
||||
await supa.signOut()
|
||||
logout()
|
||||
throw new Error('您还没有注册商家端账户,快去注册一个')
|
||||
}
|
||||
} else {
|
||||
uni.showToast({ title: '手机号密码登录功能开发中', icon: 'none' })
|
||||
return
|
||||
@@ -332,27 +400,26 @@ const handleLogin = async () => {
|
||||
return
|
||||
}
|
||||
|
||||
// 尝试获取/补全用户资料,但失败时不再阻塞登录
|
||||
// 更新 store 中的用户资料
|
||||
try {
|
||||
const profile = await getCurrentUser()
|
||||
console.log('current user profile:', profile)
|
||||
console.log('fetch profile success:', profile)
|
||||
} catch (e) {
|
||||
console.error('获取用户信息失败(忽略,不阻塞登录):', e)
|
||||
console.error('获取用户信息失败(忽略):', e)
|
||||
}
|
||||
|
||||
// 显式保存用户ID到本地存储,确保页面刷新或重启后 SupabaseService 能恢复身份
|
||||
// 显式保存用户ID到本地存储
|
||||
const currentSession = supa.getSession()
|
||||
if (currentSession.user != null) {
|
||||
const uid = currentSession.user?.getString('id')
|
||||
if (uid != null) {
|
||||
uni.setStorageSync('user_id', uid)
|
||||
console.log('用户ID已保存到本地存储:', uid)
|
||||
}
|
||||
}
|
||||
|
||||
uni.showToast({ title: '登录成功', icon: 'success' })
|
||||
|
||||
// 即使在测试模式下,点击登录后也执行跳转,确保进入首页
|
||||
// 成功跳转逻辑
|
||||
setTimeout(() => {
|
||||
const pages = getCurrentPages() as any[]
|
||||
const currentPage = pages.length > 0 ? pages[pages.length - 1] : null
|
||||
|
||||
@@ -93,7 +93,6 @@
|
||||
<script setup lang="uts">
|
||||
import { ref } from 'vue'
|
||||
import supa from '@/components/supadb/aksupainstance.uts'
|
||||
import { ensureUserProfile } from '@/utils/sapi.uts'
|
||||
|
||||
// 响应式数据
|
||||
const email = ref<string>('')
|
||||
@@ -202,95 +201,55 @@
|
||||
|
||||
try {
|
||||
// 使用 Supabase Auth:邮箱 + 密码注册
|
||||
const result = await supa.signUp(email.value.trim(), password.value)
|
||||
const options = new UTSJSONObject()
|
||||
const metaData = new UTSJSONObject()
|
||||
// 【核心修改】:商家端注册时,固定声明 user_role 为 'merchant'
|
||||
// 该元数据会被 Supabase Auth 存储,并由数据库触发器自动同步到 ak_users 业务表的 role 字段
|
||||
metaData.set('user_role', 'merchant')
|
||||
options.set('data', metaData)
|
||||
|
||||
const result = await supa.signUp(email.value.trim(), password.value, options)
|
||||
|
||||
console.log('📝 注册返回结果:', result)
|
||||
console.log('📝 注册返回结果(JSON):', JSON.stringify(result))
|
||||
|
||||
// 检查是否有错误(邮件发送失败等)
|
||||
// 检查是否有错误
|
||||
const errorCode = result?.getString('error_code') ?? ''
|
||||
const errorMsg = result?.getString('msg') ?? ''
|
||||
const code = result?.getNumber('code') ?? 0
|
||||
|
||||
console.log('📝 错误代码:', errorCode, '错误信息:', errorMsg, '状态码:', code)
|
||||
|
||||
// 如果返回 500 错误且是邮件发送失败,但用户可能已创建
|
||||
if (code === 500 && (errorCode === 'unexpected_failure' || errorMsg.includes('confirmation email'))) {
|
||||
console.warn('⚠️ 邮件发送失败,但用户可能已创建,尝试获取用户信息')
|
||||
// 即使邮件发送失败,用户可能已经在 auth.users 中创建
|
||||
// 这里我们仍然尝试创建用户资料
|
||||
}
|
||||
|
||||
// signUp 返回的是 UTSJSONObject,Supabase signup API 返回结构:
|
||||
// { user: {...}, session: {...} } - 如果邮箱验证未开启
|
||||
// { user: {...} } - 如果邮箱验证已开启(需要验证邮箱后才能登录)
|
||||
// { code: 500, error_code: ..., msg: ... } - 如果发生错误(但用户可能已创建)
|
||||
let user: UTSJSONObject | null = null
|
||||
let hasSession = false
|
||||
|
||||
if (result != null) {
|
||||
// 尝试获取 user 字段
|
||||
const userField = result.getJSON('user')
|
||||
if (userField != null) {
|
||||
user = userField
|
||||
console.log('✅ 找到 user 字段:', user.getString('id'), user.getString('email'))
|
||||
} else {
|
||||
// 如果没有 user 字段,可能 result 本身就是 user 对象
|
||||
const id = result.getString('id')
|
||||
if (id != null && id !== '') {
|
||||
user = result
|
||||
console.log('✅ result 本身就是 user 对象:', id)
|
||||
} else {
|
||||
console.warn('⚠️ 未找到 user 信息,检查所有字段:', Object.keys(result.toMap() || {}))
|
||||
}
|
||||
}
|
||||
|
||||
// 检查是否有 session(表示注册后自动登录成功)
|
||||
const sessionField = result.getJSON('session')
|
||||
if (sessionField != null) {
|
||||
hasSession = true
|
||||
console.log('✅ 找到 session,已自动登录')
|
||||
// 如果有 session,说明已经自动登录,token 应该已经设置
|
||||
// 此时可以直接创建用户资料
|
||||
} else {
|
||||
console.log('ℹ️ 未找到 session,可能需要邮箱验证')
|
||||
}
|
||||
}
|
||||
|
||||
// 如果返回错误且没有用户信息,说明注册失败
|
||||
if (user == null && code !== 0 && code !== 200) {
|
||||
// 如果是邮件发送失败,给出明确的错误提示
|
||||
if (code === 500 && errorMsg.includes('confirmation email')) {
|
||||
throw new Error('注册失败:邮件服务配置错误,请联系管理员或修改 Supabase 配置(设置 ENABLE_EMAIL_AUTOCONFIRM=true)')
|
||||
throw new Error('注册失败:邮件服务配置错误')
|
||||
} else {
|
||||
throw new Error(errorMsg || '注册失败,请重试')
|
||||
}
|
||||
}
|
||||
|
||||
// 如果获取到 user,尝试创建业务侧用户资料(ak_users)
|
||||
// 【核心修改】:移除手动调用 ensureUserProfile 逻辑
|
||||
// 既然已经设置了数据库触发器 (Trigger),用户信息会自动从 auth.users 同步到 ak_users
|
||||
// 前端不再执行二次插入,避免并发冲突或重复写入
|
||||
if (user != null) {
|
||||
try {
|
||||
const profileResult = await ensureUserProfile(user)
|
||||
if (profileResult != null) {
|
||||
console.log('✅ 用户资料创建成功:', profileResult.id)
|
||||
} else {
|
||||
console.warn('⚠️ 用户资料创建失败,但注册已成功')
|
||||
// 如果创建失败,可能是因为 RLS 策略限制
|
||||
// 建议用户登录后再自动创建(在 getCurrentUser 中处理)
|
||||
}
|
||||
} catch (profileError) {
|
||||
console.error('❌ 创建用户资料异常:', profileError)
|
||||
// 即使创建资料失败,也不阻止注册流程
|
||||
// 用户登录时会自动创建(见 utils/store.uts 的 getCurrentUser)
|
||||
// 注册后立即登出,确保用户必须通过登录流程(且经过角色校验)才能进入首页
|
||||
await supa.signOut()
|
||||
} catch (signOutError) {
|
||||
console.error('❌ 登出异常:', signOutError)
|
||||
}
|
||||
} else {
|
||||
console.warn('⚠️ 注册成功但未获取到用户信息')
|
||||
// 可能需要邮箱验证,用户验证邮箱后登录时会自动创建资料
|
||||
}
|
||||
|
||||
// 如果注册后没有自动登录(需要邮箱验证),提示用户
|
||||
if (!hasSession && user != null) {
|
||||
console.log('ℹ️ 需要邮箱验证,验证后登录时会自动创建用户资料')
|
||||
}
|
||||
|
||||
uni.showToast({
|
||||
|
||||
Reference in New Issue
Block a user