Skip to content

huangzhichong/SmartTool

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

4 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Next.js 全栈项目配置方案

我将为您配置一个完整的企业级全栈开发环境,包含您要求的所有功能。

📁 项目结构

nextjs-fullstack-docker/
├── app/                    # Next.js 13+ App Router
├── components/            # 可复用UI组件
├── lib/                   # 工具函数和共享代码
├── prisma/               # Prisma ORM配置
├── queue/                # 异步任务队列
├── scripts/              # 部署和构建脚本
├── public/               # 静态资源
├── docker/               # Docker配置文件
├── docker-compose.yml    # 开发环境配置
├── docker-compose.prod.yml # 生产环境配置
├── Dockerfile            # 应用镜像配置
├── Dockerfile.dev        # 开发环境镜像
└── package.json

📦 package.json 配置

{
  "name": "nextjs-fullstack",
  "version": "1.0.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint",
    "docker:dev": "docker-compose up --build",
    "docker:prod": "docker-compose -f docker-compose.prod.yml up --build",
    "docker:stop": "docker-compose down",
    "prisma:generate": "prisma generate",
    "prisma:migrate": "prisma migrate dev",
    "prisma:studio": "prisma studio",
    "prisma:push": "prisma db push",
    "prisma:seed": "tsx prisma/seed.ts",
    "queue:worker": "tsx queue/worker.ts"
  },
  "dependencies": {
    "@prisma/client": "^5.0.0",
    "@types/bull": "^4.10.4",
    "bcryptjs": "^2.4.3",
    "bull": "^4.11.5",
    "clsx": "^2.0.0",
    "cookie": "^0.6.0",
    "date-fns": "^2.30.0",
    "express": "^4.18.2",
    "ioredis": "^5.3.2",
    "jsonwebtoken": "^9.0.2",
    "next": "^13.4.0",
    "next-auth": "^4.23.0",
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "react-hook-form": "^7.45.4",
    "tailwind-merge": "^1.14.0",
    "tailwindcss-animate": "^1.0.7",
    "zod": "^3.21.4",
    "zustand": "^4.4.0"
  },
  "devDependencies": {
    "@types/bcryptjs": "^2.4.6",
    "@types/express": "^4.17.20",
    "@types/jsonwebtoken": "^9.0.4",
    "@types/node": "^20.4.0",
    "@types/react": "^18.2.14",
    "@types/react-dom": "^18.2.6",
    "@typescript-eslint/eslint-plugin": "^6.0.0",
    "@typescript-eslint/parser": "^6.0.0",
    "autoprefixer": "^10.4.14",
    "eslint": "^8.44.0",
    "eslint-config-next": "^13.4.0",
    "postcss": "^8.4.27",
    "prisma": "^5.0.0",
    "tailwindcss": "^3.3.3",
    "tsx": "^3.12.7",
    "typescript": "^5.1.6"
  }
}

🐳 Docker 配置

docker-compose.yml (开发环境)

version: '3.8'

services:
  postgres:
    image: postgres:15-alpine
    container_name: nextjs-postgres
    environment:
      POSTGRES_USER: ${DB_USER:-postgres}
      POSTGRES_PASSWORD: ${DB_PASSWORD:-postgres}
      POSTGRES_DB: ${DB_NAME:-nextjs_app}
    ports:
      - "5432:5432"
    volumes:
      - postgres_data:/var/lib/postgresql/data
      - ./docker/postgres/init.sql:/docker-entrypoint-initdb.d/init.sql
    networks:
      - app-network
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 10s
      timeout: 5s
      retries: 5

  redis:
    image: redis:7-alpine
    container_name: nextjs-redis
    ports:
      - "6379:6379"
    volumes:
      - redis_data:/data
    networks:
      - app-network
    command: redis-server --appendonly yes
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 5s
      retries: 5

  app:
    build:
      context: .
      dockerfile: Dockerfile.dev
    container_name: nextjs-app
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=development
      - DATABASE_URL=postgresql://${DB_USER:-postgres}:${DB_PASSWORD:-postgres}@postgres:5432/${DB_NAME:-nextjs_app}
      - REDIS_URL=redis://redis:6379
      - JWT_SECRET=${JWT_SECRET:-your-secret-key-change-this}
      - NEXTAUTH_SECRET=${NEXTAUTH_SECRET:-your-nextauth-secret-change-this}
      - NEXTAUTH_URL=http://localhost:3000
    volumes:
      - .:/app
      - /app/node_modules
      - /app/.next
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy
    networks:
      - app-network
    command: sh -c "npm install && npx prisma generate && npx prisma db push && npm run dev"

volumes:
  postgres_data:
  redis_data:

networks:
  app-network:
    driver: bridge

docker-compose.prod.yml (生产环境)

version: '3.8'

services:
  postgres:
    image: postgres:15-alpine
    container_name: nextjs-postgres-prod
    environment:
      POSTGRES_USER: ${DB_USER}
      POSTGRES_PASSWORD: ${DB_PASSWORD}
      POSTGRES_DB: ${DB_NAME}
    ports:
      - "5432:5432"
    volumes:
      - postgres_data_prod:/var/lib/postgresql/data
    networks:
      - app-network-prod
    restart: unless-stopped

  redis:
    image: redis:7-alpine
    container_name: nextjs-redis-prod
    ports:
      - "6379:6379"
    volumes:
      - redis_data_prod:/data
    networks:
      - app-network-prod
    command: redis-server --appendonly yes
    restart: unless-stopped

  app:
    build:
      context: .
      dockerfile: Dockerfile
    container_name: nextjs-app-prod
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=production
      - DATABASE_URL=postgresql://${DB_USER}:${DB_PASSWORD}@postgres:5432/${DB_NAME}
      - REDIS_URL=redis://redis:6379
      - JWT_SECRET=${JWT_SECRET}
      - NEXTAUTH_SECRET=${NEXTAUTH_SECRET}
      - NEXTAUTH_URL=${APP_URL}
    depends_on:
      - postgres
      - redis
    networks:
      - app-network-prod
    restart: unless-stopped

  queue-worker:
    build:
      context: .
      dockerfile: Dockerfile
    container_name: nextjs-queue-worker
    environment:
      - NODE_ENV=production
      - DATABASE_URL=postgresql://${DB_USER}:${DB_PASSWORD}@postgres:5432/${DB_NAME}
      - REDIS_URL=redis://redis:6379
    depends_on:
      - postgres
      - redis
    networks:
      - app-network-prod
    command: npm run queue:worker
    restart: unless-stopped

volumes:
  postgres_data_prod:
  redis_data_prod:

networks:
  app-network-prod:
    driver: bridge

Dockerfile (生产环境)

FROM node:18-alpine AS base

# 安装依赖
FROM base AS deps
RUN apk add --no-cache libc6-compat openssl
WORKDIR /app

COPY package.json package-lock.json* ./
RUN npm ci --only=production

# 构建应用
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .

RUN npm run prisma:generate
ENV NEXT_TELEMETRY_DISABLED 1
RUN npm run build

# 运行应用
FROM base AS runner
WORKDIR /app

ENV NODE_ENV production
ENV NEXT_TELEMETRY_DISABLED 1

RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs

COPY --from=builder /app/public ./public
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static

USER nextjs

EXPOSE 3000

ENV PORT 3000
ENV HOSTNAME "0.0.0.0"

CMD ["node", "server.js"]

Dockerfile.dev (开发环境)

FROM node:18-alpine

WORKDIR /app

# 安装系统依赖
RUN apk add --no-cache \
    openssl \
    git \
    bash

# 安装 pnpm (可选)
RUN npm install -g pnpm

# 复制 package 文件
COPY package*.json ./

# 安装应用依赖
RUN npm install

# 复制应用代码
COPY . .

# 暴露端口
EXPOSE 3000

# 开发命令
CMD ["npm", "run", "dev"]

🗄️ 数据库配置 (Prisma)

prisma/schema.prisma

generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

model User {
  id        String   @id @default(cuid())
  email     String   @unique
  name      String?
  password  String
  role      UserRole @default(USER)
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
  
  posts     Post[]
  comments  Comment[]
  sessions  Session[]
  tasks     Task[]
  
  @@map("users")
}

enum UserRole {
  USER
  ADMIN
}

model Session {
  id        String   @id @default(cuid())
  userId    String
  token     String   @unique
  expiresAt DateTime
  createdAt DateTime @default(now())
  
  user      User     @relation(fields: [userId], references: [id], onDelete: Cascade)
  
  @@map("sessions")
}

model Post {
  id        String   @id @default(cuid())
  title     String
  content   String?
  published Boolean  @default(false)
  authorId  String
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
  
  author    User     @relation(fields: [authorId], references: [id])
  comments  Comment[]
  tags      Tag[]
  
  @@map("posts")
}

model Comment {
  id        String   @id @default(cuid())
  content   String
  postId    String
  authorId  String
  createdAt DateTime @default(now())
  
  post      Post     @relation(fields: [postId], references: [id], onDelete: Cascade)
  author    User     @relation(fields: [authorId], references: [id])
  
  @@map("comments")
}

model Tag {
  id        String   @id @default(cuid())
  name      String   @unique
  posts     Post[]
  
  @@map("tags")
}

// 异步任务队列相关表
model Task {
  id          String    @id @default(cuid())
  name        String
  data        Json
  status      TaskStatus @default(PENDING)
  result      Json?
  error       String?
  attempts    Int       @default(0)
  maxAttempts Int       @default(3)
  runAt       DateTime  @default(now())
  startedAt   DateTime?
  completedAt DateTime?
  createdAt   DateTime  @default(now())
  updatedAt   DateTime  @updatedAt
  
  userId      String?
  user        User?     @relation(fields: [userId], references: [id])
  
  @@index([status])
  @@index([runAt])
  @@map("tasks")
}

enum TaskStatus {
  PENDING
  PROCESSING
  COMPLETED
  FAILED
}

// 队列日志
model QueueLog {
  id        String   @id @default(cuid())
  queue     String
  jobId     String?
  message   String
  data      Json?
  level     String   @default("info")
  createdAt DateTime @default(now())
  
  @@index([queue])
  @@index([createdAt])
  @@map("queue_logs")
}

prisma/seed.ts

import { PrismaClient } from '@prisma/client'
import { hash } from 'bcryptjs'

const prisma = new PrismaClient()

async function main() {
  // 创建管理员用户
  const adminPassword = await hash('admin123', 12)
  const admin = await prisma.user.upsert({
    where: { email: 'admin@example.com' },
    update: {},
    create: {
      email: 'admin@example.com',
      name: 'Administrator',
      password: adminPassword,
      role: 'ADMIN',
    },
  })

  // 创建普通用户
  const userPassword = await hash('user123', 12)
  const user = await prisma.user.upsert({
    where: { email: 'user@example.com' },
    update: {},
    create: {
      email: 'user@example.com',
      name: 'John Doe',
      password: userPassword,
    },
  })

  // 创建标签
  const tags = await Promise.all(
    ['JavaScript', 'TypeScript', 'React', 'Next.js', 'Node.js'].map(name =>
      prisma.tag.upsert({
        where: { name },
        update: {},
        create: { name },
      })
    )
  )

  console.log({ admin, user, tags })
}

main()
  .catch(e => {
    console.error(e)
    process.exit(1)
  })
  .finally(async () => {
    await prisma.$disconnect()
  })

🔧 异步任务队列 (Bull)

queue/queue.ts

import Queue from 'bull'
import Redis from 'ioredis'
import { prisma } from '@/lib/prisma'

const redisConfig = {
  host: process.env.REDIS_HOST || 'localhost',
  port: parseInt(process.env.REDIS_PORT || '6379'),
  password: process.env.REDIS_PASSWORD,
  maxRetriesPerRequest: null,
}

// 创建Redis连接
const redis = new Redis(redisConfig)

// 定义队列
export enum QueueName {
  EMAIL = 'email',
  PROCESSING = 'processing',
  NOTIFICATION = 'notification',
}

// 创建队列实例
const createQueue = (name: QueueName) => {
  return new Queue(name, {
    redis: redisConfig,
    defaultJobOptions: {
      removeOnComplete: 100, // 保留最近100个完成的任务
      removeOnFail: 1000,    // 保留最近1000个失败的任务
      attempts: 3,           // 最大重试次数
      backoff: {
        type: 'exponential', // 指数退避
        delay: 1000,         // 初始延迟
      },
    },
  })
}

// 初始化队列
export const emailQueue = createQueue(QueueName.EMAIL)
export const processingQueue = createQueue(QueueName.PROCESSING)
export const notificationQueue = createQueue(QueueName.NOTIFICATION)

// 队列事件监听器
const setupQueueListeners = (queue: Queue.Queue) => {
  queue.on('completed', async (job, result) => {
    console.log(`Job ${job.id} completed with result:`, result)
    
    // 更新数据库任务状态
    await prisma.task.updateMany({
      where: { id: job.id },
      data: {
        status: 'COMPLETED',
        result: result,
        completedAt: new Date(),
      },
    })
  })

  queue.on('failed', async (job, error) => {
    console.error(`Job ${job.id} failed with error:`, error)
    
    // 更新数据库任务状态
    await prisma.task.updateMany({
      where: { id: job.id },
      data: {
        status: 'FAILED',
        error: error.message,
        attempts: job.attemptsMade,
      },
    })
  })

  queue.on('active', async (job) => {
    console.log(`Job ${job.id} is now active`)
    
    await prisma.task.updateMany({
      where: { id: job.id },
      data: {
        status: 'PROCESSING',
        startedAt: new Date(),
        attempts: job.attemptsMade,
      },
    })
  })
}

// 设置所有队列的监听器
[emailQueue, processingQueue, notificationQueue].forEach(setupQueueListeners)

// 添加任务到队列
export async function addJobToQueue<T>(
  queue: QueueName,
  jobName: string,
  data: T,
  options?: Queue.JobOptions
): Promise<Queue.Job> {
  const queueMap = {
    [QueueName.EMAIL]: emailQueue,
    [QueueName.PROCESSING]: processingQueue,
    [QueueName.NOTIFICATION]: notificationQueue,
  }

  const selectedQueue = queueMap[queue]
  
  // 创建数据库记录
  const task = await prisma.task.create({
    data: {
      name: jobName,
      data: data as any,
      status: 'PENDING',
      runAt: options?.delay ? new Date(Date.now() + options.delay) : new Date(),
    },
  })

  // 添加到队列
  const job = await selectedQueue.add(jobName, data, {
    ...options,
    jobId: task.id,
  })

  return job
}

// 关闭队列连接
export async function closeQueues() {
  await Promise.all([
    emailQueue.close(),
    processingQueue.close(),
    notificationQueue.close(),
    redis.quit(),
  ])
}

queue/worker.ts

import { emailQueue, processingQueue, notificationQueue } from './queue'
import { QueueName, addJobToQueue } from './queue'
import { sendEmail } from '@/lib/email'
import { processData } from '@/lib/processing'

// 邮件队列处理器
emailQueue.process(async (job) => {
  const { to, subject, template, data } = job.data
  
  console.log(`Processing email job ${job.id} to ${to}`)
  
  try {
    const result = await sendEmail({
      to,
      subject,
      template,
      data,
    })
    
    return { success: true, messageId: result.messageId }
  } catch (error) {
    throw new Error(`Failed to send email: ${error.message}`)
  }
})

// 数据处理队列处理器
processingQueue.process(5, async (job) => { // 5个并发
  const { task, data } = job.data
  
  console.log(`Processing data job ${job.id}: ${task}`)
  
  try {
    const result = await processData(task, data)
    return { success: true, result }
  } catch (error) {
    throw new Error(`Failed to process data: ${error.message}`)
  }
})

// 通知队列处理器
notificationQueue.process(async (job) => {
  const { userId, type, message } = job.data
  
  console.log(`Processing notification job ${job.id} for user ${userId}`)
  
  // 这里可以实现WebSocket推送或数据库存储
  return { success: true, delivered: true }
})

// 示例:添加任务到队列
export async function sendWelcomeEmail(email: string, name: string) {
  return addJobToQueue(QueueName.EMAIL, 'welcome-email', {
    to: email,
    subject: 'Welcome to Our Platform',
    template: 'welcome',
    data: { name },
  })
}

export async function processUserUpload(userId: string, fileData: any) {
  return addJobToQueue(QueueName.PROCESSING, 'user-upload', {
    task: 'process_upload',
    data: { userId, ...fileData },
  }, {
    priority: 1, // 高优先级
  })
}

// 启动worker
console.log('Queue worker started...')
console.log(`Environment: ${process.env.NODE_ENV}`)

🔐 身份验证配置

lib/auth.ts

import { compare, hash } from 'bcryptjs'
import { SignJWT, jwtVerify } from 'jose'
import { cookies } from 'next/headers'
import { prisma } from './prisma'

const secretKey = process.env.JWT_SECRET!
const key = new TextEncoder().encode(secretKey)

export async function encrypt(payload: any) {
  return new SignJWT(payload)
    .setProtectedHeader({ alg: 'HS256' })
    .setIssuedAt()
    .setExpirationTime('7d')
    .sign(key)
}

export async function decrypt(input: string): Promise<any> {
  const { payload } = await jwtVerify(input, key, {
    algorithms: ['HS256'],
  })
  return payload
}

export async function login(email: string, password: string) {
  const user = await prisma.user.findUnique({
    where: { email },
  })

  if (!user || !(await compare(password, user.password))) {
    throw new Error('Invalid credentials')
  }

  const expires = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000)
  const session = await encrypt({ userId: user.id, expires })

  // 存储session到数据库
  await prisma.session.create({
    data: {
      userId: user.id,
      token: session,
      expiresAt: expires,
    },
  })

  return session
}

export async function logout() {
  const cookieStore = cookies()
  const session = cookieStore.get('session')?.value

  if (session) {
    await prisma.session.deleteMany({
      where: { token: session },
    })
  }

  cookieStore.delete('session')
}

export async function getCurrentUser() {
  const cookieStore = cookies()
  const session = cookieStore.get('session')?.value

  if (!session) return null

  try {
    const payload = await decrypt(session)
    
    // 验证session是否有效
    const validSession = await prisma.session.findFirst({
      where: {
        token: session,
        userId: payload.userId,
        expiresAt: { gt: new Date() },
      },
      include: { user: true },
    })

    if (!validSession) return null

    // 移除密码字段
    const { password, ...userWithoutPassword } = validSession.user
    return userWithoutPassword
  } catch (error) {
    console.error('Failed to verify session:', error)
    return null
  }
}

📁 核心工具文件

lib/prisma.ts

import { PrismaClient } from '@prisma/client'

const globalForPrisma = globalThis as unknown as {
  prisma: PrismaClient | undefined
}

export const prisma = globalForPrisma.prisma ?? new PrismaClient()

if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma

lib/redis.ts

import Redis from 'ioredis'

const redisUrl = process.env.REDIS_URL || 'redis://localhost:6379'

const redis = new Redis(redisUrl, {
  maxRetriesPerRequest: null,
  enableReadyCheck: false,
})

redis.on('error', (err) => {
  console.error('Redis error:', err)
})

redis.on('connect', () => {
  console.log('Connected to Redis')
})

export { redis }

tailwind.config.ts

import type { Config } from 'tailwindcss'

const config: Config = {
  darkMode: ["class"],
  content: [
    './pages/**/*.{ts,tsx}',
    './components/**/*.{ts,tsx}',
    './app/**/*.{ts,tsx}',
    './src/**/*.{ts,tsx}',
  ],
  theme: {
    container: {
      center: true,
      padding: "2rem",
      screens: {
        "2xl": "1400px",
      },
    },
    extend: {
      colors: {
        border: "hsl(var(--border))",
        input: "hsl(var(--input))",
        ring: "hsl(var(--ring))",
        background: "hsl(var(--background))",
        foreground: "hsl(var(--foreground))",
        primary: {
          DEFAULT: "hsl(var(--primary))",
          foreground: "hsl(var(--primary-foreground))",
        },
        secondary: {
          DEFAULT: "hsl(var(--secondary))",
          foreground: "hsl(var(--secondary-foreground))",
        },
        destructive: {
          DEFAULT: "hsl(var(--destructive))",
          foreground: "hsl(var(--destructive-foreground))",
        },
        muted: {
          DEFAULT: "hsl(var(--muted))",
          foreground: "hsl(var(--muted-foreground))",
        },
        accent: {
          DEFAULT: "hsl(var(--accent))",
          foreground: "hsl(var(--accent-foreground))",
        },
        card: {
          DEFAULT: "hsl(var(--card))",
          foreground: "hsl(var(--card-foreground))",
        },
      },
      borderRadius: {
        lg: "var(--radius)",
        md: "calc(var(--radius) - 2px)",
        sm: "calc(var(--radius) - 4px)",
      },
    },
  },
  plugins: [require("tailwindcss-animate")],
}

export default config

📋 环境变量配置

.env.example

# 数据库配置
DATABASE_URL="postgresql://postgres:postgres@localhost:5432/nextjs_app"

# Redis配置
REDIS_URL="redis://localhost:6379"
REDIS_HOST="localhost"
REDIS_PORT="6379"
REDIS_PASSWORD=""

# 应用配置
APP_URL="http://localhost:3000"
NODE_ENV="development"
PORT="3000"

# 安全配置
JWT_SECRET="your-jwt-secret-change-in-production"
NEXTAUTH_SECRET="your-nextauth-secret-change-in-production"
NEXTAUTH_URL="http://localhost:3000"

# 数据库连接池配置
DATABASE_POOL_SIZE="10"
DATABASE_CONNECTION_TIMEOUT="30"

# 邮件配置 (可选)
SMTP_HOST=""
SMTP_PORT=""
SMTP_USER=""
SMTP_PASSWORD=""
SMTP_FROM=""

🚀 启动脚本

scripts/setup.sh

#!/bin/bash

echo "Setting up Next.js Fullstack Project..."

# 创建必要的目录
mkdir -p docker/postgres

# 检查是否已安装Docker
if ! command -v docker &> /dev/null; then
    echo "Docker is not installed. Please install Docker first."
    exit 1
fi

# 检查是否已安装Docker Compose
if ! command -v docker-compose &> /dev/null; then
    echo "Docker Compose is not installed. Please install Docker Compose first."
    exit 1
fi

# 复制环境变量文件
if [ ! -f .env ]; then
    cp .env.example .env
    echo "Created .env file from example."
    echo "Please update .env with your actual configuration."
fi

# 创建PostgreSQL初始化脚本
cat > docker/postgres/init.sql << EOF
-- 创建扩展
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
CREATE EXTENSION IF NOT EXISTS "pgcrypto";
EOF

echo "Setup complete!"
echo ""
echo "To start the development environment:"
echo "  npm run docker:dev"
echo ""
echo "To start the production environment:"
echo "  npm run docker:prod"
echo ""
echo "To stop all containers:"
echo "  npm run docker:stop"

📝 README.md

# Next.js Fullstack Docker 项目

这是一个基于 Next.js 13+ 的全栈开发环境,包含:
- PostgreSQL 数据库
- Redis 缓存和消息队列
- Docker 容器化部署
- 异步任务队列 (Bull)
- 完整的身份验证系统

## 快速开始

### 前置要求
- Node.js 18+
- Docker & Docker Compose
- Git

### 安装步骤

1. **克隆项目**
```bash
git clone <your-repo-url>
cd nextjs-fullstack-docker
  1. 运行安装脚本
chmod +x scripts/setup.sh
./scripts/setup.sh
  1. 启动开发环境
npm run docker:dev

应用将在 http://localhost:3000 启动

  1. 访问服务

项目结构

├── app/              # Next.js 13 App Router
├── components/       # React 组件
├── lib/             # 工具函数和配置
├── prisma/          # 数据库 ORM
├── queue/           # 异步任务队列
├── docker/          # Docker 配置
└── scripts/         # 部署脚本

常用命令

# 开发环境
npm run docker:dev     # 启动开发环境
npm run dev           # 仅启动Next.js开发服务器

# 生产环境
npm run docker:prod   # 启动生产环境
npm run build         # 构建生产版本
npm start             # 运行生产版本

# 数据库操作
npm run prisma:generate  # 生成Prisma客户端
npm run prisma:migrate   # 运行数据库迁移
npm run prisma:studio    # 打开Prisma Studio
npm run prisma:seed      # 填充种子数据

# 队列
npm run queue:worker     # 启动队列worker

配置说明

环境变量

复制 .env.example.env 并修改配置:

# 数据库
DATABASE_URL="postgresql://user:password@postgres:5432/dbname"

# Redis
REDIS_URL="redis://redis:6379"

# 安全
JWT_SECRET="your-secure-secret"

Docker 配置

  • docker-compose.yml: 开发环境配置
  • docker-compose.prod.yml: 生产环境配置
  • Dockerfile: 生产环境镜像构建
  • Dockerfile.dev: 开发环境镜像构建

API 示例

用户认证

// POST /api/auth/login
{
  "email": "user@example.com",
  "password": "password123"
}

// GET /api/auth/session
// 返回当前用户信息

队列任务

// POST /api/queue/email
{
  "to": "user@example.com",
  "subject": "Welcome",
  "template": "welcome"
}

部署

1. Docker 部署

# 构建并启动
docker-compose -f docker-compose.prod.yml up -d

# 查看日志
docker-compose -f docker-compose.prod.yml logs -f

2. 手动部署

# 安装依赖
npm ci --only=production

# 构建应用
npm run build

# 启动应用
npm start

注意事项

  1. 生产环境务必修改所有默认密码和密钥
  2. Redis 和 PostgreSQL 数据会持久化在 Docker volumes 中
  3. 队列worker需要单独启动或使用PM2管理
  4. 定期备份数据库数据

故障排除

常见问题

  1. 端口冲突

    • 修改 docker-compose.yml 中的端口映射
  2. 数据库连接失败

    • 检查 PostgreSQL 容器是否正常运行
    • 验证 DATABASE_URL 配置
  3. Redis 连接失败

    • 检查 Redis 容器状态
    • 验证 REDIS_URL 配置
  4. 队列任务未执行

    • 确保队列worker正在运行
    • 检查Redis连接状态

🚀 一键启动命令

创建一个 start.sh 脚本来简化启动:

#!/bin/bash

echo "🚀 Next.js Fullstack Docker 项目启动器"

case "$1" in
    "dev")
        echo "启动开发环境..."
        docker-compose up --build
        ;;
    "prod")
        echo "启动生产环境..."
        docker-compose -f docker-compose.prod.yml up --build -d
        ;;
    "stop")
        echo "停止所有服务..."
        docker-compose down
        ;;
    "logs")
        echo "查看日志..."
        docker-compose logs -f
        ;;
    "clean")
        echo "清理Docker资源..."
        docker-compose down -v
        docker system prune -f
        ;;
    *)
        echo "使用方法: ./start.sh [dev|prod|stop|logs|clean]"
        ;;
esac

💡 使用说明

  1. 首次启动
# 克隆项目后
chmod +x scripts/setup.sh scripts/start.sh
./scripts/setup.sh
./start.sh dev
  1. 访问服务
  1. 数据库操作
# 在容器内执行
docker exec -it nextjs-app sh
npm run prisma:migrate
npm run prisma:seed

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages