2025-09-10-Supabase配置完整指南

AI前端产品
返回博客列表

本文档总结了在 PromptHub 项目中配置 Supabase 的完整步骤,包括项目创建、数据库配置、认证设置和前端集成。

第一步:创建 Supabase 项目

1.1 注册和登录

  1. 访问 supabase.com
  2. 使用 GitHub 或邮箱注册账号
  3. 登录到 Supabase 控制台

1.2 创建新项目

  1. 点击 "New Project"

  2. 选择组织(或创建新组织)

  3. 填写项目信息:

    • Name: PromptHub
    • Database Password: 设置强密码
    • Region: 选择最近的区域
  4. 点击 "Create new project"

  5. 等待项目初始化完成(约 2-3 分钟)

1.3 获取项目配置信息

  1. 进入项目后,点击左侧 "Settings""API"

  2. 记录以下信息:

    • Project URL:
    • anon public key:

第二步:配置数据库表

2.1 进入 SQL 编辑器

  1. 左侧导航栏点击 "SQL Editor"
  2. 点击 "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 进入认证设置

  1. 左侧导航栏点击 "Authentication"
  2. 点击 "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 进入邮件模板设置

  1. "Authentication""Settings"
  2. 找到 "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 测试功能

  1. 访问 http://localhost:3001
  2. 点击"注册"按钮
  3. 输入邮箱和密码
  4. 检查是否收到验证邮件
  5. 点击验证链接
  6. 测试登录功能

第八步:生产环境部署

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 配置自定义域名(可选)

  1. 在 Supabase 控制台中配置自定义域名
  2. 更新 DNS 记录
  3. 配置 SSL 证书

常见问题解决

1. 邮件发送失败

  • 检查 SMTP 配置
  • 确认邮箱地址格式
  • 查看 Supabase 日志

2. 验证链接无效

  • 检查 Site URL 配置
  • 确认 Redirect URLs
  • 验证链接是否过期

3. 用户无法登录

  • 检查用户状态
  • 确认密码正确
  • 查看浏览器控制台

安全建议

  1. 环境变量安全

    • 不要提交 .env.local 到版本控制
    • 使用不同的密钥用于开发和生产
  2. RLS 策略

    • 确保所有用户表都启用了 RLS
    • 定期审查 RLS 策略
  3. API 密钥管理

    • 定期轮换 API 密钥
    • 使用最小权限原则

监控和维护

  1. 日志监控

    • 定期检查 Supabase 日志
    • 监控认证失败率
  2. 性能优化

    • 监控数据库查询性能
    • 优化 RLS 策略
  3. 备份策略

    • 定期备份用户数据
    • 测试恢复流程

总结

通过以上步骤,你已经成功配置了:

  • ✅ Supabase 项目创建
  • ✅ 数据库表结构
  • ✅ 用户认证系统
  • ✅ 邮箱验证功能
  • ✅ 前端集成
  • ✅ 安全策略
  • ✅ 生产环境配置

现在你的 PromptHub 项目具备了完整的用户认证系统!