diff --git a/memora-api/internal/app/app.go b/memora-api/internal/app/app.go new file mode 100644 index 0000000..b5a89d4 --- /dev/null +++ b/memora-api/internal/app/app.go @@ -0,0 +1,50 @@ +package app + +import ( + "fmt" + + "memora-api/internal/bootstrap" + "memora-api/internal/handler" + "memora-api/internal/router" + "memora-api/internal/service" + + "github.com/gin-gonic/gin" +) + +type App struct { + engine *gin.Engine + addr string +} + +func New(configPath string) (*App, error) { + cfg, err := bootstrap.LoadConfig(configPath) + if err != nil { + return nil, fmt.Errorf("加载配置失败: %w", err) + } + + db, err := bootstrap.InitDB(cfg) + if err != nil { + return nil, fmt.Errorf("连接数据库失败: %w", err) + } + + if err := bootstrap.AutoMigrate(db); err != nil { + return nil, fmt.Errorf("数据库迁移失败: %w", err) + } + + wordService := service.NewWordService(db) + wordHandler := handler.NewWordHandler(wordService) + engine := router.New(wordHandler) + + return &App{ + engine: engine, + addr: fmt.Sprintf("%s:%d", cfg.Server.Host, cfg.Server.Port), + }, nil +} + +func (a *App) Run() error { + return a.engine.Run(a.addr) +} + +func (a *App) Addr() string { + return a.addr +} diff --git a/memora-api/internal/bootstrap/config.go b/memora-api/internal/bootstrap/config.go new file mode 100644 index 0000000..6fe96c0 --- /dev/null +++ b/memora-api/internal/bootstrap/config.go @@ -0,0 +1,7 @@ +package bootstrap + +import "memora-api/internal/config" + +func LoadConfig(path string) (*config.Config, error) { + return config.LoadConfig(path) +} diff --git a/memora-api/internal/bootstrap/database.go b/memora-api/internal/bootstrap/database.go new file mode 100644 index 0000000..c7e6333 --- /dev/null +++ b/memora-api/internal/bootstrap/database.go @@ -0,0 +1,17 @@ +package bootstrap + +import ( + "memora-api/internal/config" + "memora-api/internal/model" + + "gorm.io/driver/mysql" + "gorm.io/gorm" +) + +func InitDB(cfg *config.Config) (*gorm.DB, error) { + return gorm.Open(mysql.Open(cfg.Database.DSN()), &gorm.Config{}) +} + +func AutoMigrate(db *gorm.DB) error { + return db.AutoMigrate(&model.Word{}, &model.MemoryRecord{}) +} diff --git a/memora-api/internal/middleware/cors.go b/memora-api/internal/middleware/cors.go new file mode 100644 index 0000000..3da5427 --- /dev/null +++ b/memora-api/internal/middleware/cors.go @@ -0,0 +1,17 @@ +package middleware + +import "github.com/gin-gonic/gin" + +func CORS() gin.HandlerFunc { + return func(c *gin.Context) { + c.Writer.Header().Set("Access-Control-Allow-Origin", "*") + c.Writer.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS") + c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization") + + if c.Request.Method == "OPTIONS" { + c.AbortWithStatus(204) + return + } + c.Next() + } +} diff --git a/memora-api/internal/router/router.go b/memora-api/internal/router/router.go new file mode 100644 index 0000000..71fa9c5 --- /dev/null +++ b/memora-api/internal/router/router.go @@ -0,0 +1,25 @@ +package router + +import ( + "memora-api/internal/handler" + "memora-api/internal/middleware" + + "github.com/gin-gonic/gin" +) + +func New(wordHandler *handler.WordHandler) *gin.Engine { + r := gin.Default() + r.Use(middleware.CORS()) + + api := r.Group("/api") + { + api.POST("/words", wordHandler.AddWord) + api.GET("/words", wordHandler.GetWords) + api.GET("/review", wordHandler.GetReviewWords) + api.POST("/review", wordHandler.SubmitReview) + api.GET("/stats", wordHandler.GetStatistics) + api.GET("/audio", wordHandler.GetAudio) + } + + return r +} diff --git a/memora-api/main.go b/memora-api/main.go index cb96041..5d518ca 100644 --- a/memora-api/main.go +++ b/memora-api/main.go @@ -1,78 +1,19 @@ package main import ( - "fmt" "log" - "memora-api/internal/config" - "memora-api/internal/handler" - "memora-api/internal/model" - "memora-api/internal/service" - - "github.com/gin-gonic/gin" - "gorm.io/driver/mysql" - "gorm.io/gorm" + "memora-api/internal/app" ) func main() { - // 加载配置 - cfg, err := config.LoadConfig("config.yaml") + application, err := app.New("config.yaml") if err != nil { - log.Fatalf("加载配置失败: %v", err) + log.Fatal(err) } - // 连接数据库 - db, err := gorm.Open(mysql.Open(cfg.Database.DSN()), &gorm.Config{}) - if err != nil { - log.Fatalf("连接数据库失败: %v", err) - } - - // 自动迁移表 - if err := db.AutoMigrate(&model.Word{}, &model.MemoryRecord{}); err != nil { - log.Fatalf("数据库迁移失败: %v", err) - } - - // 初始化服务 - wordService := service.NewWordService(db) - wordHandler := handler.NewWordHandler(wordService) - - // 启动 Gin - r := gin.Default() - - // CORS 中间件 - r.Use(func(c *gin.Context) { - c.Writer.Header().Set("Access-Control-Allow-Origin", "*") - c.Writer.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS") - c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization") - if c.Request.Method == "OPTIONS" { - c.AbortWithStatus(204) - return - } - c.Next() - }) - - // 路由 - api := r.Group("/api") - { - // 记忆模式 - api.POST("/words", wordHandler.AddWord) - api.GET("/words", wordHandler.GetWords) - - // 复习模式 - api.GET("/review", wordHandler.GetReviewWords) - api.POST("/review", wordHandler.SubmitReview) - - // 统计 - api.GET("/stats", wordHandler.GetStatistics) - - // 音频 - api.GET("/audio", wordHandler.GetAudio) - } - - // 启动服务器 - addr := fmt.Sprintf("%s:%d", cfg.Server.Host, cfg.Server.Port) - log.Printf("服务器启动在: %s", addr) - if err := r.Run(addr); err != nil { + log.Printf("服务器启动在: %s", application.Addr()) + if err := application.Run(); err != nil { log.Fatalf("启动服务器失败: %v", err) } }