我将为您配置一个完整的企业级全栈开发环境,包含您要求的所有功能。
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
{
"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"
}
}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: bridgeversion: '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: bridgeFROM 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"]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"]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")
}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()
})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(),
])
}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}`)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
}
}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 = prismaimport 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 }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# 数据库配置
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=""#!/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"# 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- 运行安装脚本
chmod +x scripts/setup.sh
./scripts/setup.sh- 启动开发环境
npm run docker:dev应用将在 http://localhost:3000 启动
- 访问服务
- 应用: http://localhost:3000
- PostgreSQL: localhost:5432
- Redis: localhost:6379
├── 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-compose.yml: 开发环境配置docker-compose.prod.yml: 生产环境配置Dockerfile: 生产环境镜像构建Dockerfile.dev: 开发环境镜像构建
// 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"
}# 构建并启动
docker-compose -f docker-compose.prod.yml up -d
# 查看日志
docker-compose -f docker-compose.prod.yml logs -f# 安装依赖
npm ci --only=production
# 构建应用
npm run build
# 启动应用
npm start- 生产环境务必修改所有默认密码和密钥
- Redis 和 PostgreSQL 数据会持久化在 Docker volumes 中
- 队列worker需要单独启动或使用PM2管理
- 定期备份数据库数据
-
端口冲突
- 修改
docker-compose.yml中的端口映射
- 修改
-
数据库连接失败
- 检查 PostgreSQL 容器是否正常运行
- 验证 DATABASE_URL 配置
-
Redis 连接失败
- 检查 Redis 容器状态
- 验证 REDIS_URL 配置
-
队列任务未执行
- 确保队列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- 首次启动:
# 克隆项目后
chmod +x scripts/setup.sh scripts/start.sh
./scripts/setup.sh
./start.sh dev- 访问服务:
- 应用: http://localhost:3000
- Prisma Studio: http://localhost:5555 (运行
npx prisma studio) - PostgreSQL: 使用 pgAdmin 或 psql 连接 localhost:5432
- Redis: 使用 redis-cli 连接 localhost:6379
- 数据库操作:
# 在容器内执行
docker exec -it nextjs-app sh
npm run prisma:migrate
npm run prisma:seed