diff --git a/app/README.md b/app/README.md new file mode 100644 index 0000000..e69de29 diff --git a/backend/.env.example b/backend/.env.example deleted file mode 100644 index fd0eefb..0000000 --- a/backend/.env.example +++ /dev/null @@ -1,17 +0,0 @@ -# Application -APP_ENV=development -APP_PORT=8080 - -# Database -DB_HOST=localhost -DB_PORT=5432 -DB_USER=postgres -DB_PASSWORD=your_secure_password -DB_NAME=appdb -DB_SSL_MODE=disable - -# Redis -REDIS_HOST=localhost -REDIS_PORT=6379 -REDIS_PASSWORD= -REDIS_DB=0 diff --git a/backend/Makefile b/backend/Makefile deleted file mode 100644 index 4fd4e3f..0000000 --- a/backend/Makefile +++ /dev/null @@ -1,86 +0,0 @@ -# Makefile - -.PHONY: all build up down restart logs clean tidy test help - -# 默认目标 -all: build up - -# 构建镜像 -build: - docker-compose build - -# 启动服务 -up: - docker-compose up -d - -# 停止服务 -down: - docker-compose down - -# 完全清理(包括数据卷) -clean: - docker-compose down -v - docker system prune -f - -# 重启服务 -restart: - docker-compose restart - -# 查看日志 -logs: - docker-compose logs -f - -# 查看 API 日志 -logs-api: - docker-compose logs -f api - -# 查看数据库日志 -logs-db: - docker-compose logs -f postgres - -# 查看 Redis 日志 -logs-redis: - docker-compose logs -f redis - -# 进入 API 容器 -shell-api: - docker-compose exec api sh - -# 进入数据库容器 -shell-db: - docker-compose exec postgres psql -U postgres -d appdb - -# 进入 Redis 容器 -shell-redis: - docker-compose exec redis redis-cli - -# 下载依赖 -tidy: - cd api && go mod tidy - -# 运行测试 -test: - cd api && go test -v ./... - -# 本地运行 -run: - cd api && go run main.go - -# 帮助 -help: - @echo "Available targets:" - @echo " make build - 构建 Docker 镜像" - @echo " make up - 启动所有服务" - @echo " make down - 停止所有服务" - @echo " make restart - 重启所有服务" - @echo " make logs - 查看所有服务日志" - @echo " make logs-api - 查看 API 服务日志" - @echo " make logs-db - 查看数据库日志" - @echo " make logs-redis - 查看 Redis 日志" - @echo " make shell-api - 进入 API 容器" - @echo " make shell-db - 进入数据库容器" - @echo " make shell-redis- 进入 Redis 容器" - @echo " make clean - 清理所有容器和数据卷" - @echo " make tidy - 整理 Go 依赖" - @echo " make test - 运行测试" - @echo " make run - 本地运行 API 服务" diff --git a/backend/README.md b/backend/README.md index 210a13b..e69de29 100644 --- a/backend/README.md +++ b/backend/README.md @@ -1,163 +0,0 @@ -# RESTful API 工程 - -基于 Golang + Redis + Postgres 的单机多服务 RESTful API 工程。 - -## 技术栈 - -- **Golang**: 1.25.8-alpine3.23 - API 服务 -- **Postgres**: 18.3-alpine3.23 - 关系型数据库 -- **Redis**: 8.6.2-alpine - 缓存服务 - -## 项目结构 - -``` -. -├── api/ # API 服务代码 -│ ├── config/ # 配置管理 -│ ├── handlers/ # HTTP 处理器 -│ ├── models/ # 数据模型 -│ ├── router/ # 路由配置 -│ ├── Dockerfile # API 服务镜像 -│ ├── go.mod # Go 模块定义 -│ └── main.go # 入口文件 -├── migrations/ # 数据库迁移脚本 -├── docker-compose.yml # Docker Compose 配置 -├── Makefile # 常用命令 -├── .env # 环境变量(本地开发) -└── .env.example # 环境变量示例 -``` - -## 快速开始 - -### 1. 克隆项目并进入目录 - -```bash -cd api -``` - -### 2. 复制环境变量文件 - -```bash -cp .env.example .env -# 根据需要编辑 .env 文件 -``` - -### 3. 启动服务 - -```bash -make up -# 或者 -docker-compose up -d -``` - -### 4. 验证服务 - -- API: http://localhost:8080 -- Health Check: http://localhost:8080/health -- API Docs: http://localhost:8080/api/v1/ping - -### 5. 查看日志 - -```bash -make logs -# 或者单独查看 -make logs-api -make logs-db -make logs-redis -``` - -## API 接口 - -### 健康检查 - -```bash -curl http://localhost:8080/health -``` - -### 用户接口 - -| 方法 | 路径 | 描述 | -|------|------|------| -| GET | /api/v1/ping | 测试接口 | -| GET | /api/v1/users | 获取用户列表 | -| POST | /api/v1/users | 创建用户 | -| GET | /api/v1/users/:id | 获取单个用户 | -| PUT | /api/v1/users/:id | 更新用户 | -| DELETE | /api/v1/users/:id | 删除用户 | - -### 示例请求 - -```bash -# 创建用户 -curl -X POST http://localhost:8080/api/v1/users \ - -H "Content-Type: application/json" \ - -d '{"name":"王五","email":"wangwu@example.com"}' - -# 获取用户列表 -curl http://localhost:8080/api/v1/users - -# 获取单个用户 -curl http://localhost:8080/api/v1/users/1 -``` - -## 常用命令 - -```bash -# 构建镜像 -make build - -# 启动服务 -make up - -# 停止服务 -make down - -# 重启服务 -make restart - -# 查看日志 -make logs - -# 进入容器 -make shell-api # 进入 API 容器 -make shell-db # 进入数据库 -make shell-redis # 进入 Redis - -# 本地开发 -make tidy # 整理依赖 -make test # 运行测试 -make run # 本地运行 - -# 清理环境 -make clean # 清理所有容器和数据 -``` - -## 环境变量 - -| 变量名 | 默认值 | 描述 | -|--------|--------|------| -| APP_ENV | development | 应用环境 | -| APP_PORT | 8080 | API 端口 | -| DB_HOST | localhost | 数据库主机 | -| DB_PORT | 5432 | 数据库端口 | -| DB_USER | postgres | 数据库用户 | -| DB_PASSWORD | postgres | 数据库密码 | -| DB_NAME | appdb | 数据库名 | -| REDIS_HOST | localhost | Redis 主机 | -| REDIS_PORT | 6379 | Redis 端口 | -| REDIS_PASSWORD | | Redis 密码 | - -## 开发说明 - -1. **添加新接口**: 在 `api/handlers/` 下添加处理器,在 `api/router/router.go` 中注册路由 -2. **添加模型**: 在 `api/models/` 下添加数据模型 -3. **数据库迁移**: 在 `migrations/` 下添加 SQL 文件 - -## 部署 - -生产环境部署前请确保: - -1. 修改 `.env` 中的敏感信息(密码等) -2. 设置 `APP_ENV=production` -3. 使用 HTTPS -4. 配置适当的日志和监控 diff --git a/backend/api/Dockerfile b/backend/api/Dockerfile deleted file mode 100644 index 3775062..0000000 --- a/backend/api/Dockerfile +++ /dev/null @@ -1,54 +0,0 @@ -FROM golang:1.25.8-alpine3.23 AS builder - -# 安装构建依赖 -RUN apk add --no-cache git ca-certificates tzdata - -# 设置工作目录 -WORKDIR /app - -# 复制依赖文件 -COPY go.mod go.sum ./ - -# 下载依赖 -RUN go mod download - -# 复制源代码 -COPY . . - -# 构建应用 -RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build \ - -ldflags="-w -s" \ - -o /app/server \ - main.go - -# 使用更小的基础镜像 -FROM alpine:3.23 - -# 安装运行时依赖 -RUN apk --no-cache add ca-certificates tzdata - -# 创建非 root 用户 -RUN addgroup -g 1000 -S appgroup && \ - adduser -u 1000 -S appuser -G appgroup - -# 设置工作目录 -WORKDIR /app - -# 从 builder 复制二进制文件 -COPY --from=builder /app/server /app/server - -# 更改文件所有者 -RUN chown -R appuser:appgroup /app - -# 切换到非 root 用户 -USER appuser - -# 暴露端口 -EXPOSE 8080 - -# 健康检查 -HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ - CMD wget --no-verbose --tries=1 --spider http://localhost:8080/health || exit 1 - -# 启动应用 -ENTRYPOINT ["/app/server"] diff --git a/backend/api/config/config.go b/backend/api/config/config.go deleted file mode 100644 index 0988f55..0000000 --- a/backend/api/config/config.go +++ /dev/null @@ -1,117 +0,0 @@ -package config - -import ( - "context" - "fmt" - "os" - "time" - - "github.com/redis/go-redis/v9" - "gorm.io/driver/postgres" - "gorm.io/gorm" - "gorm.io/gorm/logger" -) - -// Config 应用配置 -type Config struct { - DB *gorm.DB - Redis *redis.Client -} - -// New 创建配置实例 -func New() *Config { - return &Config{} -} - -// InitDB 初始化数据库连接 -func (c *Config) InitDB() error { - dsn := fmt.Sprintf("host=%s user=%s password=%s dbname=%s port=%s sslmode=%s", - getEnv("DB_HOST", "localhost"), - getEnv("DB_USER", "postgres"), - getEnv("DB_PASSWORD", "postgres"), - getEnv("DB_NAME", "appdb"), - getEnv("DB_PORT", "5432"), - getEnv("DB_SSL_MODE", "disable"), - ) - - // 配置 GORM - config := &gorm.Config{ - Logger: logger.Default.LogMode(logger.Info), - } - - if os.Getenv("APP_ENV") == "production" { - config.Logger = logger.Default.LogMode(logger.Silent) - } - - db, err := gorm.Open(postgres.Open(dsn), config) - if err != nil { - return fmt.Errorf("failed to connect to database: %w", err) - } - - // 获取底层的 sqlDB - sqlDB, err := db.DB() - if err != nil { - return fmt.Errorf("failed to get sqlDB: %w", err) - } - - // 设置连接池 - sqlDB.SetMaxIdleConns(10) - sqlDB.SetMaxOpenConns(100) - sqlDB.SetConnMaxLifetime(time.Hour) - - c.DB = db - return nil -} - -// CloseDB 关闭数据库连接 -func (c *Config) CloseDB() error { - if c.DB != nil { - sqlDB, err := c.DB.DB() - if err != nil { - return err - } - return sqlDB.Close() - } - return nil -} - -// InitRedis 初始化 Redis 连接 -func (c *Config) InitRedis() error { - opt := &redis.Options{ - Addr: fmt.Sprintf("%s:%s", - getEnv("REDIS_HOST", "localhost"), - getEnv("REDIS_PORT", "6379"), - ), - Password: getEnv("REDIS_PASSWORD", ""), - DB: 0, - } - - client := redis.NewClient(opt) - - // 测试连接 - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() - - if err := client.Ping(ctx).Err(); err != nil { - return fmt.Errorf("failed to connect to redis: %w", err) - } - - c.Redis = client - return nil -} - -// CloseRedis 关闭 Redis 连接 -func (c *Config) CloseRedis() error { - if c.Redis != nil { - return c.Redis.Close() - } - return nil -} - -// getEnv 获取环境变量,如果不存在则返回默认值 -func getEnv(key, defaultValue string) string { - if value := os.Getenv(key); value != "" { - return value - } - return defaultValue -} diff --git a/backend/api/go.mod b/backend/api/go.mod deleted file mode 100644 index e62be2a..0000000 --- a/backend/api/go.mod +++ /dev/null @@ -1,11 +0,0 @@ -module api - -go 1.25 - -require ( - github.com/gin-gonic/gin v1.10.0 - github.com/redis/go-redis/v9 v9.7.3 - github.com/joho/godotenv v1.5.1 - gorm.io/driver/postgres v1.5.11 - gorm.io/gorm v1.25.12 -) diff --git a/backend/api/handlers/handler.go b/backend/api/handlers/handler.go deleted file mode 100644 index 1b7ac60..0000000 --- a/backend/api/handlers/handler.go +++ /dev/null @@ -1,17 +0,0 @@ -package handlers - -import ( - "api/config" -) - -// Handler 处理器 -type Handler struct { - cfg *config.Config -} - -// New 创建处理器实例 -func New(cfg *config.Config) *Handler { - return &Handler{ - cfg: cfg, - } -} diff --git a/backend/api/handlers/health.go b/backend/api/handlers/health.go deleted file mode 100644 index 9b2163a..0000000 --- a/backend/api/handlers/health.go +++ /dev/null @@ -1,64 +0,0 @@ -package handlers - -import ( - "context" - "net/http" - "time" - - "github.com/gin-gonic/gin" -) - -// HealthResponse 健康检查响应 -type HealthResponse struct { - Status string `json:"status"` - Time string `json:"time"` - Services map[string]string `json:"services"` -} - -// HealthCheck 健康检查 -func (h *Handler) HealthCheck(c *gin.Context) { - ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) - defer cancel() - - response := HealthResponse{ - Status: "ok", - Time: time.Now().Format(time.RFC3339), - Services: make(map[string]string), - } - - // 检查数据库连接 - sqlDB, err := h.cfg.DB.DB() - if err != nil { - response.Services["database"] = "error: " + err.Error() - response.Status = "degraded" - } else { - if err := sqlDB.PingContext(ctx); err != nil { - response.Services["database"] = "error: " + err.Error() - response.Status = "degraded" - } else { - response.Services["database"] = "ok" - } - } - - // 检查 Redis 连接 - if err := h.cfg.Redis.Ping(ctx).Err(); err != nil { - response.Services["redis"] = "error: " + err.Error() - response.Status = "degraded" - } else { - response.Services["redis"] = "ok" - } - - if response.Status == "ok" { - c.JSON(http.StatusOK, response) - } else { - c.JSON(http.StatusServiceUnavailable, response) - } -} - -// Ping 简单的 ping 测试 -func (h *Handler) Ping(c *gin.Context) { - c.JSON(http.StatusOK, gin.H{ - "message": "pong", - "time": time.Now().Format(time.RFC3339), - }) -} diff --git a/backend/api/handlers/user.go b/backend/api/handlers/user.go deleted file mode 100644 index 7c5edba..0000000 --- a/backend/api/handlers/user.go +++ /dev/null @@ -1,154 +0,0 @@ -package handlers - -import ( - "context" - "net/http" - "time" - - "github.com/gin-gonic/gin" -) - -// UserRequest 用户请求 -type UserRequest struct { - Name string `json:"name" binding:"required"` - Email string `json:"email" binding:"required,email"` -} - -// UserResponse 用户响应 -type UserResponse struct { - ID uint `json:"id"` - Name string `json:"name"` - Email string `json:"email"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` -} - -// ListUsers 获取用户列表 -func (h *Handler) ListUsers(c *gin.Context) { - // TODO: 实现从数据库获取用户列表 - users := []UserResponse{ - { - ID: 1, - Name: "张三", - Email: "zhangsan@example.com", - CreatedAt: time.Now(), - UpdatedAt: time.Now(), - }, - { - ID: 2, - Name: "李四", - Email: "lisi@example.com", - CreatedAt: time.Now(), - UpdatedAt: time.Now(), - }, - } - - c.JSON(http.StatusOK, gin.H{ - "code": 0, - "data": users, - }) -} - -// GetUser 获取单个用户 -func (h *Handler) GetUser(c *gin.Context) { - id := c.Param("id") - - // TODO: 实现从数据库获取用户 - // 尝试从缓存获取 - ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) - defer cancel() - - cached, err := h.cfg.Redis.Get(ctx, "user:"+id).Result() - if err == nil && cached != "" { - c.JSON(http.StatusOK, gin.H{ - "code": 0, - "data": cached, - "cached": true, - }) - return - } - - // 模拟从数据库获取 - user := UserResponse{ - ID: 1, - Name: "张三", - Email: "zhangsan@example.com", - CreatedAt: time.Now(), - UpdatedAt: time.Now(), - } - - c.JSON(http.StatusOK, gin.H{ - "code": 0, - "data": user, - }) -} - -// CreateUser 创建用户 -func (h *Handler) CreateUser(c *gin.Context) { - var req UserRequest - if err := c.ShouldBindJSON(&req); err != nil { - c.JSON(http.StatusBadRequest, gin.H{ - "code": 400, - "message": err.Error(), - }) - return - } - - // TODO: 实现保存到数据库 - - user := UserResponse{ - ID: 1, - Name: req.Name, - Email: req.Email, - CreatedAt: time.Now(), - UpdatedAt: time.Now(), - } - - c.JSON(http.StatusCreated, gin.H{ - "code": 0, - "data": user, - }) -} - -// UpdateUser 更新用户 -func (h *Handler) UpdateUser(c *gin.Context) { - id := c.Param("id") - - var req UserRequest - if err := c.ShouldBindJSON(&req); err != nil { - c.JSON(http.StatusBadRequest, gin.H{ - "code": 400, - "message": err.Error(), - }) - return - } - - // TODO: 实现更新数据库 - - _ = id - user := UserResponse{ - ID: 1, - Name: req.Name, - Email: req.Email, - CreatedAt: time.Now(), - UpdatedAt: time.Now(), - } - - c.JSON(http.StatusOK, gin.H{ - "code": 0, - "data": user, - }) -} - -// DeleteUser 删除用户 -func (h *Handler) DeleteUser(c *gin.Context) { - id := c.Param("id") - - // TODO: 实现从数据库删除 - _ = id - - c.JSON(http.StatusOK, gin.H{ - "code": 0, - "message": "用户已删除", - }) -} diff --git a/backend/api/main.go b/backend/api/main.go deleted file mode 100644 index e3ca66e..0000000 --- a/backend/api/main.go +++ /dev/null @@ -1,94 +0,0 @@ -package main - -import ( - "context" - "fmt" - "log" - "net/http" - "os" - "os/signal" - "syscall" - "time" - - "api/config" - "api/router" - - "github.com/gin-gonic/gin" - "github.com/joho/godotenv" -) - -func main() { - // 加载环境变量 - if err := godotenv.Load(); err != nil { - log.Println("No .env file found, using environment variables") - } - - // 设置运行模式 - if os.Getenv("APP_ENV") == "production" { - gin.SetMode(gin.ReleaseMode) - } else { - gin.SetMode(gin.DebugMode) - } - - // 初始化配置 - cfg := config.New() - - // 初始化数据库连接 - if err := cfg.InitDB(); err != nil { - log.Fatalf("Failed to initialize database: %v", err) - } - defer cfg.CloseDB() - - // 初始化 Redis 连接 - if err := cfg.InitRedis(); err != nil { - log.Fatalf("Failed to initialize redis: %v", err) - } - defer cfg.CloseRedis() - - // 创建 Gin 引擎 - r := gin.New() - - // 使用中间件 - r.Use(gin.Logger()) - r.Use(gin.Recovery()) - - // 注册路由 - router.RegisterRoutes(r, cfg) - - // 获取端口 - port := os.Getenv("APP_PORT") - if port == "" { - port = "8080" - } - - // 创建 HTTP 服务器 - srv := &http.Server{ - Addr: fmt.Sprintf(":%s", port), - Handler: r, - } - - // 优雅启停 - go func() { - log.Printf("Server starting on port %s", port) - if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { - log.Fatalf("Failed to start server: %v", err) - } - }() - - // 等待中断信号 - quit := make(chan os.Signal, 1) - signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) - <-quit - - log.Println("Shutting down server...") - - // 设置关闭超时 - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() - - if err := srv.Shutdown(ctx); err != nil { - log.Fatalf("Server forced to shutdown: %v", err) - } - - log.Println("Server exited") -} diff --git a/backend/api/models/user.go b/backend/api/models/user.go deleted file mode 100644 index 54c3206..0000000 --- a/backend/api/models/user.go +++ /dev/null @@ -1,22 +0,0 @@ -package models - -import ( - "time" - - "gorm.io/gorm" -) - -// User 用户模型 -type User struct { - ID uint `gorm:"primaryKey" json:"id"` - Name string `gorm:"size:100;not null" json:"name"` - Email string `gorm:"size:100;uniqueIndex;not null" json:"email"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` - DeletedAt gorm.DeletedAt `gorm:"index" json:"-"` -} - -// TableName 指定表名 -func (User) TableName() string { - return "users" -} diff --git a/backend/api/router/router.go b/backend/api/router/router.go deleted file mode 100644 index be5fc86..0000000 --- a/backend/api/router/router.go +++ /dev/null @@ -1,44 +0,0 @@ -package router - -import ( - "net/http" - - "api/config" - "api/handlers" - - "github.com/gin-gonic/gin" -) - -// RegisterRoutes 注册路由 -func RegisterRoutes(r *gin.Engine, cfg *config.Config) { - // 创建处理器 - h := handlers.New(cfg) - - // 健康检查 - r.GET("/health", h.HealthCheck) - - // API v1 路由组 - v1 := r.Group("/api/v1") - { - // 示例路由 - v1.GET("/ping", h.Ping) - - // 用户相关路由 - user := v1.Group("/users") - { - user.GET("", h.ListUsers) - user.POST("", h.CreateUser) - user.GET("/:id", h.GetUser) - user.PUT("/:id", h.UpdateUser) - user.DELETE("/:id", h.DeleteUser) - } - } - - // 404 处理 - r.NoRoute(func(c *gin.Context) { - c.JSON(http.StatusNotFound, gin.H{ - "code": 404, - "message": "Not Found", - }) - }) -} diff --git a/backend/docker-compose.yml b/backend/docker-compose.yml deleted file mode 100644 index f4ff6ec..0000000 --- a/backend/docker-compose.yml +++ /dev/null @@ -1,80 +0,0 @@ -version: "3.9" - -services: - # API 服务 - api: - build: - context: ./api - dockerfile: Dockerfile - container_name: api_service - ports: - - "8080:8080" - environment: - - APP_ENV=development - - APP_PORT=8080 - - DB_HOST=postgres - - DB_PORT=5432 - - DB_USER=${DB_USER:-postgres} - - DB_PASSWORD=${DB_PASSWORD:-postgres} - - DB_NAME=${DB_NAME:-appdb} - - DB_SSL_MODE=disable - - REDIS_HOST=redis - - REDIS_PORT=6379 - - REDIS_PASSWORD=${REDIS_PASSWORD:-} - - REDIS_DB=0 - depends_on: - postgres: - condition: service_healthy - redis: - condition: service_healthy - networks: - - app-network - restart: unless-stopped - - # PostgreSQL 数据库 - postgres: - image: postgres:18.3-alpine3.23 - container_name: postgres_db - ports: - - "5432:5432" - environment: - - POSTGRES_USER=${DB_USER:-postgres} - - POSTGRES_PASSWORD=${DB_PASSWORD:-postgres} - - POSTGRES_DB=${DB_NAME:-appdb} - volumes: - - postgres_data:/var/lib/postgresql/data - - ./migrations:/docker-entrypoint-initdb.d - networks: - - app-network - healthcheck: - test: ["CMD-SHELL", "pg_isready -U ${DB_USER:-postgres} -d ${DB_NAME:-appdb}"] - interval: 5s - timeout: 5s - retries: 5 - restart: unless-stopped - - # Redis 缓存 - redis: - image: redis:8.6.2-alpine - container_name: redis_cache - ports: - - "6379:6379" - command: redis-server --appendonly yes - volumes: - - redis_data:/data - networks: - - app-network - healthcheck: - test: ["CMD", "redis-cli", "ping"] - interval: 5s - timeout: 5s - retries: 5 - restart: unless-stopped - -volumes: - postgres_data: - redis_data: - -networks: - app-network: - driver: bridge diff --git a/backend/migrations/001_init.sql b/backend/migrations/001_init.sql deleted file mode 100644 index 663d730..0000000 --- a/backend/migrations/001_init.sql +++ /dev/null @@ -1,20 +0,0 @@ --- 初始化数据库 --- 创建用户表 -CREATE TABLE IF NOT EXISTS users ( - id SERIAL PRIMARY KEY, - name VARCHAR(100) NOT NULL, - email VARCHAR(100) UNIQUE NOT NULL, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - deleted_at TIMESTAMP -); - --- 创建索引 -CREATE INDEX IF NOT EXISTS idx_users_email ON users(email); -CREATE INDEX IF NOT EXISTS idx_users_deleted_at ON users(deleted_at); - --- 插入测试数据 -INSERT INTO users (name, email) VALUES - ('张三', 'zhangsan@example.com'), - ('李四', 'lisi@example.com') -ON CONFLICT (email) DO NOTHING; diff --git a/frontend/README.md b/frontend/README.md new file mode 100644 index 0000000..e69de29