本文档总结了在 PromptHub 项目中配置 Supabase 的完整步骤,包括项目创建、数据库配置、认证设置和前端集成。
第一步:创建 Supabase 项目
1.1 注册和登录
- 访问 supabase.com
- 使用 GitHub 或邮箱注册账号
- 登录到 Supabase 控制台
1.2 创建新项目
-
点击 "New Project"
-
选择组织(或创建新组织)
-
填写项目信息:
- Name: PromptHub
- Database Password: 设置强密码
- Region: 选择最近的区域
-
点击 "Create new project"
-
等待项目初始化完成(约 2-3 分钟)
1.3 获取项目配置信息
-
进入项目后,点击左侧 "Settings" → "API"
-
记录以下信息:
- Project URL:
- anon public key:
第二步:配置数据库表
2.1 进入 SQL 编辑器
- 左侧导航栏点击 "SQL Editor"
- 点击 "New query"
2.2 创建用户相关表
执行以下 SQL 语句:
-- 用户收藏表
CREATE TABLE user_favorites (
id SERIAL PRIMARY KEY,
user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE,
prompt_id INTEGER NOT NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
UNIQUE(user_id, prompt_id)
);
-- 用户使用历史表
CREATE TABLE user_history (
id SERIAL PRIMARY KEY,
user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE,
prompt_id INTEGER NOT NULL,
used_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
2.3 启用 Row Level Security (RLS)
-- 启用RLS
ALTER TABLE user_favorites ENABLE ROW LEVEL SECURITY;
ALTER TABLE user_history ENABLE ROW LEVEL SECURITY;
2.4 创建 RLS 策略
-- 用户收藏表策略
CREATE POLICY "Users can view their own favorites" ON user_favorites
FOR SELECT USING (auth.uid() = user_id);
CREATE POLICY "Users can insert their own favorites" ON user_favorites
FOR INSERT WITH CHECK (auth.uid() = user_id);
CREATE POLICY "Users can delete their own favorites" ON user_favorites
FOR DELETE USING (auth.uid() = user_id);
-- 用户历史表策略
CREATE POLICY "Users can view their own history" ON user_history
FOR SELECT USING (auth.uid() = user_id);
CREATE POLICY "Users can insert their own history" ON user_history
FOR INSERT WITH CHECK (auth.uid() = user_id);
第三步:配置认证设置
3.1 进入认证设置
- 左侧导航栏点击 "Authentication"
- 点击 "Settings" 标签页
3.2 配置用户注册
在 "User Registration" 部分:
✅ Enable email confirmations: 启用
✅ Enable phone confirmations: 可选
✅ Enable manual linking: 可选
✅ Secure email change: 启用
✅ Double confirm changes: 启用
3.3 配置站点 URL
在 "Site URL" 部分:
Site URL: https://xagwviyrnlupojyarvxl.supabase.co
Redirect URLs:
- http://localhost:3000
- http://localhost:3001
- https://your-production-domain.com
3.4 配置 SMTP 设置(可选)
在 "SMTP Settings" 部分:
方案 A:使用 Supabase 默认 SMTP(推荐测试)
- 无需额外配置
方案 B:使用 Gmail SMTP
SMTP Host: smtp.gmail.com
SMTP Port: 587
SMTP User: your-email@gmail.com
SMTP Pass: your-app-password
SMTP Admin Email: your-email@gmail.com
第四步:配置邮件模板
4.1 进入邮件模板设置
- "Authentication" → "Settings"
- 找到 "Email Templates" 部分
4.2 自定义确认邮件模板
点击 "Confirm signup" 模板,使用以下模板:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>确认你的 PromptHub 账号</title>
<style>
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; line-height: 1.6; color: #333; }
.container { max-width: 600px; margin: 0 auto; padding: 20px; }
.header { text-align: center; padding: 30px 0; background: linear-gradient(135deg, #3b82f6, #8b5cf6); color: white; border-radius: 12px; margin-bottom: 30px; }
.button { display: inline-block; background: #3b82f6; color: white; padding: 15px 30px; text-decoration: none; border-radius: 8px; font-weight: 600; margin: 20px 0; }
.footer { margin-top: 40px; padding-top: 20px; border-top: 1px solid #eee; font-size: 14px; color: #666; }
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>🎉 欢迎加入 PromptHub!</h1>
<p>精选的编程和学习提示词平台</p>
</div>
<h2>确认你的邮箱地址</h2>
<p>你好!感谢你注册 PromptHub。为了确保账号安全,请确认你的邮箱地址。</p>
<div style="text-align: center;">
<a href="{{ .ConfirmationURL }}" class="button">确认邮箱地址</a>
</div>
<p><strong>如果按钮无法点击</strong>,请复制以下链接到浏览器:</p>
<p style="word-break: break-all; background: #f1f5f9; padding: 15px; border-radius: 6px; font-family: monospace; font-size: 14px;">
{{ .ConfirmationURL }}
</p>
<div class="footer">
<p>如果你没有注册 PromptHub 账号,请忽略这封邮件。</p>
<p>此链接将在24小时后失效。</p>
<p>祝好,<br>PromptHub 团队</p>
</div>
</div>
</body>
</html>
第五步:前端项目配置
5.1 安装依赖
npm install @supabase/supabase-js
5.2 创建环境变量文件
创建 .env.local 文件:
VITE_SUPABASE_URL=
VITE_SUPABASE_ANON_KEY=
5.3 创建 Supabase 客户端
创建 src/utils/supabase.js:
import { createClient } from '@supabase/supabase-js'
const supabaseUrl = import.meta.env.VITE_SUPABASE_URL
const supabaseAnonKey = import.meta.env.VITE_SUPABASE_ANON_KEY
export const supabase = createClient(supabaseUrl, supabaseAnonKey)
5.4 创建认证上下文
创建 src/contexts/AuthContext.jsx:
import React, { createContext, useContext, useEffect, useState } from 'react'
import { supabase } from '../utils/supabase'
const AuthContext = createContext({})
export const useAuth = () => {
return useContext(AuthContext)
}
export const AuthProvider = ({ children }) => {
const [user, setUser] = useState(null)
const [loading, setLoading] = useState(true)
useEffect(() => {
const getUser = async () => {
const { data: { user } } = await supabase.auth.getUser()
setUser(user)
setLoading(false)
}
getUser()
const { data: { subscription } } = supabase.auth.onAuthStateChange(
async (event, session) => {
setUser(session?.user ?? null)
setLoading(false)
}
)
return () => subscription.unsubscribe()
}, [])
const signUp = async (email, password) => {
const { data, error } = await supabase.auth.signUp({
email,
password,
})
return { data, error }
}
const signIn = async (email, password) => {
const { data, error } = await supabase.auth.signInWithPassword({
email,
password,
})
return { data, error }
}
const signOut = async () => {
const { error } = await supabase.auth.signOut()
return { error }
}
const value = {
user,
loading,
signUp,
signIn,
signOut,
}
return (
<AuthContext.Provider value={value}>
{children}
</AuthContext.Provider>
)
}
5.5 创建认证模态框组件
创建 src/components/AuthModal.jsx:
import React, { useState, useEffect } from 'react'
import { useAuth } from '../contexts/AuthContext'
const AuthModal = ({ isOpen, onClose, mode = 'login' }) => {
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const [loading, setLoading] = useState(false)
const [error, setError] = useState('')
const [message, setMessage] = useState('')
const [showEmailSent, setShowEmailSent] = useState(false)
const { signIn, signUp } = useAuth()
useEffect(() => {
if (isOpen) {
document.body.classList.add('modal-open')
} else {
document.body.classList.remove('modal-open')
}
return () => {
document.body.classList.remove('modal-open')
}
}, [isOpen])
const handleSubmit = async (e) => {
e.preventDefault()
setLoading(true)
setError('')
setMessage('')
try {
if (mode === 'login') {
const { error } = await signIn(email, password)
if (error) throw error
onClose()
} else {
const { error } = await signUp(email, password)
if (error) throw error
setShowEmailSent(true)
setMessage('🎉 注册成功!')
}
} catch (error) {
setError(error.message)
} finally {
setLoading(false)
}
}
const handleClose = () => {
setShowEmailSent(false)
setEmail('')
setPassword('')
setError('')
setMessage('')
onClose()
}
useEffect(() => {
const handleEscape = (e) => {
if (e.key === 'Escape' && isOpen) {
handleClose()
}
}
if (isOpen) {
document.addEventListener('keydown', handleEscape)
}
return () => {
document.removeEventListener('keydown', handleEscape)
}
}, [isOpen])
const handleOverlayClick = (e) => {
if (e.target === e.currentTarget) {
handleClose()
}
}
if (!isOpen) return null
if (showEmailSent) {
return (
<div className="auth-modal-overlay" onClick={handleOverlayClick}>
<div className="auth-modal">
<div className="auth-modal-header">
<h2>📧 验证邮箱</h2>
<button className="close-btn" onClick={handleClose}>
<i className="fas fa-times"></i>
</button>
</div>
<div className="email-verification-content">
<div className="verification-icon">
<i className="fas fa-envelope-open-text"></i>
</div>
<h3>检查你的邮箱</h3>
<p>我们已向 <strong>{email}</strong> 发送了验证邮件。</p>
<div className="verification-steps">
<div className="step">
<span className="step-number">1</span>
<span>打开你的邮箱</span>
</div>
<div className="step">
<span className="step-number">2</span>
<span>点击验证链接</span>
</div>
<div className="step">
<span className="step-number">3</span>
<span>开始使用 PromptHub</span>
</div>
</div>
<div className="verification-tips">
<p><i className="fas fa-info-circle"></i> 没有收到邮件?请检查垃圾邮件文件夹</p>
<p><i className="fas fa-clock"></i> 验证邮件可能需要几分钟才能到达</p>
</div>
<button
className="auth-btn secondary-btn"
onClick={handleClose}
>
返回登录
</button>
</div>
</div>
</div>
)
}
return (
<div className="auth-modal-overlay" onClick={handleOverlayClick}>
<div className="auth-modal">
<div className="auth-modal-header">
<h2>{mode === 'login' ? '登录' : '注册'}</h2>
<button className="close-btn" onClick={handleClose}>
<i className="fas fa-times"></i>
</button>
</div>
<form onSubmit={handleSubmit} className="auth-form">
<div className="form-group">
<label htmlFor="email">邮箱</label>
<input
type="email"
id="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
/>
</div>
<div className="form-group">
<label htmlFor="password">密码</label>
<input
type="password"
id="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
/>
</div>
{error && <div className="error-message">{error}</div>}
{message && <div className="success-message">{message}</div>}
<button type="submit" className="auth-btn" disabled={loading}>
{loading ? '处理中...' : (mode === 'login' ? '登录' : '注册')}
</button>
</form>
</div>
</div>
)
}
export default AuthModal
5.6 更新 Header 组件
在 src/components/Header.jsx 中添加认证功能:
import React, { useState, useEffect } from 'react';
import { ThemeManager } from '../utils/theme';
import { useAuth } from '../contexts/AuthContext';
import AuthModal from './AuthModal';
const Header = ({ onSearch }) => {
const [theme, setTheme] = useState('light');
const [searchValue, setSearchValue] = useState('');
const [showAuthModal, setShowAuthModal] = useState(false);
const [authMode, setAuthMode] = useState('login');
const { user, signOut } = useAuth();
// ... 其他代码 ...
const handleAuthClick = (mode) => {
setAuthMode(mode);
setShowAuthModal(true);
};
const handleSignOut = async () => {
await signOut();
};
return (
<header className="header">
{/* ... 现有代码 ... */}
<div className="auth-section">
{user ? (
<div className="user-menu">
<span className="user-email" title={user.email}>
{user.email}
</span>
<button className="sign-out-btn" onClick={handleSignOut}>
退出登录
</button>
</div>
) : (
<div className="auth-buttons">
<button
className="auth-btn login-btn"
onClick={() => handleAuthClick('login')}
>
登录
</button>
<button
className="auth-btn signup-btn"
onClick={() => handleAuthClick('signup')}
>
注册
</button>
</div>
)}
</div>
<AuthModal
isOpen={showAuthModal}
onClose={() => setShowAuthModal(false)}
mode={authMode}
/>
</header>
);
};
export default Header;
5.7 更新 App.jsx
在 src/App.jsx 中包装 AuthProvider:
import React, { useState, useEffect, useMemo } from 'react';
import { AuthProvider } from './contexts/AuthContext';
// ... 其他导入 ...
const App = () => {
// ... 现有代码 ...
return (
<AuthProvider>
<div className="app">
{/* ... 现有内容 ... */}
</div>
</AuthProvider>
);
};
export default App;
第六步:添加样式
6.1 认证模态框样式
在 src/styles/components.css 中添加:
/* 认证模态框样式 */
.auth-modal-overlay {
position: fixed !important;
top: 0 !important;
left: 0 !important;
right: 0 !important;
bottom: 0 !important;
background: rgba(0, 0, 0, 0.5) !important;
display: flex !important;
align-items: center !important;
justify-content: center !important;
z-index: 9999 !important;
backdrop-filter: blur(4px);
width: 100vw !important;
height: 100vh !important;
margin: 0 !important;
padding: 0 !important;
}
.auth-modal {
position: relative !important;
background: var(--bg-white) !important;
border-radius: var(--radius-lg) !important;
padding: 2rem !important;
width: 100% !important;
max-width: 400px !important;
box-shadow: var(--shadow-xl) !important;
animation: modalSlideIn 0.3s ease-out !important;
margin: 0 !important;
transform: none !important;
}
[data-theme="dark"] .auth-modal {
background: var(--bg-gray-800) !important;
border: 1px solid var(--bg-gray-700) !important;
}
/* ... 更多样式 ... */
第七步:测试配置
7.1 启动开发服务器
npm run dev
7.2 测试功能
- 访问 http://localhost:3001
- 点击"注册"按钮
- 输入邮箱和密码
- 检查是否收到验证邮件
- 点击验证链接
- 测试登录功能
第八步:生产环境部署
8.1 更新环境变量
在生产环境中,更新 .env.local:
VITE_SUPABASE_URL=
VITE_SUPABASE_ANON_KEY=your-production-anon-key
8.2 更新 Redirect URLs
在 Supabase 控制台中添加生产域名:
Redirect URLs:
- https://your-production-domain.com
- https://your-production-domain.com/auth/callback
8.3 配置自定义域名(可选)
- 在 Supabase 控制台中配置自定义域名
- 更新 DNS 记录
- 配置 SSL 证书
常见问题解决
1. 邮件发送失败
- 检查 SMTP 配置
- 确认邮箱地址格式
- 查看 Supabase 日志
2. 验证链接无效
- 检查 Site URL 配置
- 确认 Redirect URLs
- 验证链接是否过期
3. 用户无法登录
- 检查用户状态
- 确认密码正确
- 查看浏览器控制台
安全建议
-
环境变量安全
- 不要提交
.env.local到版本控制 - 使用不同的密钥用于开发和生产
- 不要提交
-
RLS 策略
- 确保所有用户表都启用了 RLS
- 定期审查 RLS 策略
-
API 密钥管理
- 定期轮换 API 密钥
- 使用最小权限原则
监控和维护
-
日志监控
- 定期检查 Supabase 日志
- 监控认证失败率
-
性能优化
- 监控数据库查询性能
- 优化 RLS 策略
-
备份策略
- 定期备份用户数据
- 测试恢复流程
总结
通过以上步骤,你已经成功配置了:
- ✅ Supabase 项目创建
- ✅ 数据库表结构
- ✅ 用户认证系统
- ✅ 邮箱验证功能
- ✅ 前端集成
- ✅ 安全策略
- ✅ 生产环境配置
现在你的 PromptHub 项目具备了完整的用户认证系统!