二、用户注册与登录模块的搭建

在开发一个无代码平台时,用户注册与登录模块是最基础且关键的部分。它不仅是用户进入平台的入口,还为后续的功能模块(如页面设计、协作、模板市场等)提供了身份验证和权限管理的基础。本文将详细介绍如何在 Next.js 框架中搭建用户注册与登录模块,包括数据库设计、后端 API 实现、前端页面集成以及身份验证机制。


1. 数据库设计

首先,我们需要在数据库中设计用户表(User 表),用于存储用户的基本信息和登录凭证。使用 Prisma 作为 ORM 工具,可以方便地定义和管理数据库模型。

schema.prisma 文件中定义 User 表:

kotlin 复制代码
model User {
  id        Int      @id @default(autoincrement())
  email     String   @unique
  password  String   // 存储加密后的密码
  name      String?
  role      String   @default("USER") // 用户角色,如 USER、ADMIN
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
}

运行 Prisma Migrate 同步数据库:

css 复制代码
npx prisma migrate dev --name init_user

2. 实现注册功能

2.1 注册页面

app/register/page.tsx 中创建一个注册页面,包含邮箱、密码和用户名输入框。

ini 复制代码
'use client';

import { useState } from 'react';
import { useRouter } from 'next/navigation';
import { FaUser, FaEnvelope, FaLock, FaEye, FaEyeSlash } from 'react-icons/fa';

export default function Register() {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  const [name, setName] = useState('');
  const [error, setError] = useState('');
  const [showPassword, setShowPassword] = useState(false); // 控制密码显示
  const router = useRouter();

  const validateForm = () => {
    if (!email || !password || !name) {
      setError('All fields are required');
      return false;
    }
    if (!/^[^\s@]+@[^\s@]+.[^\s@]+$/.test(email)) {
      setError('Invalid email format');
      return false;
    }
    if (password.length < 6) {
      setError('Password must be at least 6 characters');
      return false;
    }
    setError('');
    return true;
  };

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    if (!validateForm()) return;

    const response = await fetch('/api/register', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ email, password, name }),
    });
    if (response.ok) {
      router.push('/login');
    } else {
      const errorData = await response.json();
      setError(errorData.message || 'Registration failed');
    }
  };

  return (
    <div className="min-h-screen flex items-center justify-center bg-gradient-to-r from-blue-500 to-purple-600">
      <div className="bg-white p-8 rounded-lg shadow-lg w-96">
        <h1 className="text-2xl font-bold mb-6 text-center text-gray-800">Register</h1>
        {error && <p className="text-red-500 text-sm mb-4">{error}</p>}
        <form onSubmit={handleSubmit}>
          <div className="mb-4">
            <label className="block text-gray-700 text-sm font-bold mb-2" htmlFor="name">
              Name
            </label>
            <div className="relative">
              <FaUser className="absolute left-3 top-3 text-gray-400" />
              <input
                type="text"
                id="name"
                placeholder="Your Name"
                value={name}
                onChange={(e) => setName(e.target.value)}
                className="w-full pl-10 pr-4 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
              />
            </div>
          </div>
          <div className="mb-4">
            <label className="block text-gray-700 text-sm font-bold mb-2" htmlFor="email">
              Email
            </label>
            <div className="relative">
              <FaEnvelope className="absolute left-3 top-3 text-gray-400" />
              <input
                type="email"
                id="email"
                placeholder="Your Email"
                value={email}
                onChange={(e) => setEmail(e.target.value)}
                className="w-full pl-10 pr-4 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
              />
            </div>
          </div>
          <div className="mb-6">
            <label className="block text-gray-700 text-sm font-bold mb-2" htmlFor="password">
              Password
            </label>
            <div className="relative">
              <FaLock className="absolute left-3 top-3 text-gray-400" />
              <input
                type={showPassword ? 'text' : 'password'} // 切换输入框类型
                id="password"
                placeholder="Your Password"
                value={password}
                onChange={(e) => setPassword(e.target.value)}
                className="w-full pl-10 pr-10 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
              />
              <button
                type="button"
                onClick={() => setShowPassword(!showPassword)} // 切换显示状态
                className="absolute right-3 top-3 text-gray-400 hover:text-gray-600"
              >
                {showPassword ? <FaEyeSlash /> : <FaEye />} {/* 切换图标 */}
              </button>
            </div>
          </div>
          <button
            type="submit"
            className="w-full bg-blue-500 text-white py-2 px-4 rounded-lg hover:bg-blue-600 transition duration-300"
          >
            Register
          </button>
        </form>
        <p className="text-center text-gray-600 mt-4">
          Already have an account?{' '}
          <a href="/login" className="text-blue-500 hover:underline">
            Login here
          </a>
        </p>
      </div>
    </div>
  );
}

2.2 注册 API

app/api/register/route.ts 中处理注册逻辑:

javascript 复制代码
import { PrismaClient } from '@prisma/client';
import bcrypt from 'bcryptjs'; // 使用 bcryptjs
import { NextResponse } from 'next/server';

const prisma = new PrismaClient();

export async function POST(req: Request) {
  const { email, password, name } = await req.json();

  // 检查邮箱是否已注册
  const existingUser = await prisma.user.findUnique({ where: { email } });
  if (existingUser) {
    return NextResponse.json(
      { message: 'Email already registered' },
      { status: 400 }
    );
  }

  // 加密密码
  const hashedPassword = bcrypt.hashSync(password, 10);

  // 创建用户
  const user = await prisma.user.create({
    data: {
      email,
      password: hashedPassword,
      name,
    },
  });

  return NextResponse.json(
    { message: 'User registered successfully', user },
    { status: 201 }
  );
}

3. 实现登录功能

3.1 登录页面

app/login/page.tsx 中创建一个登录页面,包含邮箱和密码输入框。

ini 复制代码
'use client';

import { useState } from 'react';
import { useRouter } from 'next/navigation';
import { FaEnvelope, FaLock, FaEye, FaEyeSlash } from 'react-icons/fa';

export default function Login() {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  const [error, setError] = useState('');
  const [showPassword, setShowPassword] = useState(false); // 控制密码显示
  const router = useRouter();

  const validateForm = () => {
    if (!email || !password) {
      setError('All fields are required');
      return false;
    }
    if (!/^[^\s@]+@[^\s@]+.[^\s@]+$/.test(email)) {
      setError('Invalid email format');
      return false;
    }
    setError('');
    return true;
  };

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    if (!validateForm()) return;

    const response = await fetch('/api/login', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ email, password }),
    });
    if (response.ok) {
      const { token } = await response.json();
      localStorage.setItem('token', token); // 存储 JWT
      router.push('/');
    } else {
      const errorData = await response.json();
      setError(errorData.message || 'Login failed');
    }
  };

  return (
    <div className="min-h-screen flex items-center justify-center bg-gradient-to-r from-blue-500 to-purple-600">
      <div className="bg-white p-8 rounded-lg shadow-lg w-96">
        <h1 className="text-2xl font-bold mb-6 text-center text-gray-800">Login</h1>
        {error && <p className="text-red-500 text-sm mb-4">{error}</p>}
        <form onSubmit={handleSubmit}>
          <div className="mb-4">
            <label className="block text-gray-700 text-sm font-bold mb-2" htmlFor="email">
              Email
            </label>
            <div className="relative">
              <FaEnvelope className="absolute left-3 top-3 text-gray-400" />
              <input
                type="email"
                id="email"
                placeholder="Your Email"
                value={email}
                onChange={(e) => setEmail(e.target.value)}
                className="w-full pl-10 pr-4 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
              />
            </div>
          </div>
          <div className="mb-6">
            <label className="block text-gray-700 text-sm font-bold mb-2" htmlFor="password">
              Password
            </label>
            <div className="relative">
              <FaLock className="absolute left-3 top-3 text-gray-400" />
              <input
                type={showPassword ? 'text' : 'password'} // 切换输入框类型
                id="password"
                placeholder="Your Password"
                value={password}
                onChange={(e) => setPassword(e.target.value)}
                className="w-full pl-10 pr-10 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
              />
              <button
                type="button"
                onClick={() => setShowPassword(!showPassword)} // 切换显示状态
                className="absolute right-3 top-3 text-gray-400 hover:text-gray-600"
              >
                {showPassword ? <FaEyeSlash /> : <FaEye />} {/* 切换图标 */}
              </button>
            </div>
          </div>
          <button
            type="submit"
            className="w-full bg-blue-500 text-white py-2 px-4 rounded-lg hover:bg-blue-600 transition duration-300"
          >
            Login
          </button>
        </form>
        <p className="text-center text-gray-600 mt-4">
          Don&apos;t have an account?{' '}
          <a href="/register" className="text-blue-500 hover:underline">
            Register here
          </a>
        </p>
      </div>
    </div>
  );
}

3.2 登录 API

app/api/login/route.ts 中处理登录逻辑:

php 复制代码
import { PrismaClient } from '@prisma/client';
import bcrypt from 'bcryptjs'; // 使用 bcryptjs
import jwt from 'jsonwebtoken';
import { NextResponse } from 'next/server';

const prisma = new PrismaClient();

export async function POST(req: Request) {
  const { email, password } = await req.json();

  // 查找用户
  const user = await prisma.user.findUnique({ where: { email } });
  if (!user) {
    return NextResponse.json(
      { message: 'Invalid email or password' },
      { status: 400 }
    );
  }

  // 验证密码
  const isValidPassword = bcrypt.compareSync(password, user.password);
  if (!isValidPassword) {
    return NextResponse.json(
      { message: 'Invalid email or password' },
      { status: 400 }
    );
  }

  // 生成 JWT
  const token = jwt.sign({ userId: user.id }, process.env.JWT_SECRET!, {
    expiresIn: '1h',
  });

  return NextResponse.json(
    { message: 'Login successful', token },
    { status: 200 }
  );
}

4. 实现身份验证

为了保护需要登录才能访问的页面,我们可以创建一个高阶组件(HOC)来验证用户身份。

typescript 复制代码
// utils/withAuth.tsx
'use client';

import { useEffect } from 'react';
import { useRouter } from 'next/navigation';

export const withAuth = (WrappedComponent: React.ComponentType) => {
  return (props: any) => {
    const router = useRouter();

    useEffect(() => {
      const token = localStorage.getItem('token');
      if (!token) {
        router.push('/login');
      }
    }, []);

    return <WrappedComponent {...props} />;
  };
};

在需要保护的页面中使用 withAuth

javascript 复制代码
// app/dashboard/page.tsx
import { withAuth } from '@/utils/withAuth';

function Dashboard() {
  return <div>Welcome to the Dashboard!</div>;
}

export default withAuth(Dashboard);

5. 提醒:配置 JWT_SECRET

在实现用户注册与登录模块时,JWT_SECRET 是一个非常重要的环境变量。它用于生成和验证 JSON Web Token (JWT) ,确保用户身份验证的安全性。如果未正确配置 JWT_SECRET,可能会导致登录功能无法正常工作或存在安全风险。

如何配置 JWT_SECRET?

  1. 在项目根目录下创建或编辑 .env 文件,添加以下内容:

    ini 复制代码
    JWT_SECRET=your-secret-key

    your-secret-key 替换为一个随机的、足够复杂的字符串。

  2. 确保 .env 文件没有被提交到版本控制(如 Git)。在 .gitignore 中添加以下内容:

    bash 复制代码
    .env
  3. 在代码中通过 process.env.JWT_SECRET 使用该密钥。


6. 总结

通过以上步骤,我们成功搭建了用户注册与登录模块,包括:

  • 数据库设计:使用 Prisma 定义 User 表。
  • 注册功能:实现注册页面和 API。
  • 登录功能:实现登录页面和 API。
  • 身份验证:使用 JWT 和 HOC 保护页面。

接下来,可以在此基础上扩展用户管理、权限控制等功能模块。希望本文对你有所帮助!如果有任何问题,欢迎留言讨论。😊

相关推荐
KAI7 小时前
Next项目中静态资源的压缩、优化
next.js
Java陈序员13 小时前
告别繁琐操作!这款神器用 AI 轻松绘制专业图表!
openai·next.js·deepseek
Mintopia13 小时前
🧭 Next.js 服务器部署摘要
react.js·全栈·next.js
sumAll2 天前
别再手动对齐矩形了!这个开源神器让 AI 帮你画架构图 (Next-AI-Draw-IO 体验)
前端·人工智能·next.js
KAI3 天前
失败的服务端SSR降级CSR
next.js
少卿3 天前
Next.js 国际化实现方案详解
前端·next.js
鹿鹿鹿鹿isNotDefined4 天前
Antd5.x 在 Next.js14.x 项目中,初次渲染样式丢失
前端·react.js·next.js
程序员爱钓鱼7 天前
Next.js SSR 项目生产部署全攻略
前端·next.js·trae
程序员爱钓鱼7 天前
使用Git 实现Hugo热更新部署方案(零停机、自动上线)
前端·next.js·trae