package main import ( "fmt" "log" "github.com/gin-gonic/gin" "gorm.io/driver/mysql" "gorm.io/gorm" "gorm.io/gorm/logger" "github.com/wangjia/jiu/backend/config" "github.com/wangjia/jiu/backend/internal/model" "github.com/wangjia/jiu/backend/internal/router" "github.com/wangjia/jiu/backend/internal/util" ) func main() { // 加载配置 config.Load() // 初始化数据库 db := initDB() // ALTER 已有数据库的 enum,使 superadmin 在已运行实例上也生效(忽略错误) db.Exec("ALTER TABLE users MODIFY COLUMN role ENUM('admin','operator','readonly','superadmin') NOT NULL DEFAULT 'operator'") // 自动迁移(GORM AutoMigrate 只增不删,生产安全) autoMigrate(db) // 回填存量商品的拼音索引(一次性,已有值的跳过) backfillPinyin(db) // 启动 Gin gin.SetMode(config.C.Server.Mode) r := gin.New() r.Use(gin.Logger(), gin.Recovery()) // CORS corsOrigin := config.C.Server.CORSOrigin r.Use(func(c *gin.Context) { c.Header("Access-Control-Allow-Origin", corsOrigin) c.Header("Access-Control-Allow-Methods", "GET,POST,PUT,DELETE,OPTIONS") c.Header("Access-Control-Allow-Headers", "Authorization,Content-Type") if c.Request.Method == "OPTIONS" { c.AbortWithStatus(204) return } c.Next() }) // Serve uploaded images (in production, Nginx handles /images/) r.Static("/images", config.C.Storage.UploadDir) router.Setup(r, db) addr := fmt.Sprintf(":%s", config.C.Server.Port) log.Printf("Server starting on %s (mode: %s)", addr, config.C.Server.Mode) if err := r.Run(addr); err != nil { log.Fatalf("Server failed: %v", err) } } func initDB() *gorm.DB { dsn := config.C.Database.DSN if dsn == "" { log.Fatal("database.dsn is required in config") } logLevel := logger.Silent if config.C.Server.Mode == "debug" { logLevel = logger.Info } db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{ Logger: logger.Default.LogMode(logLevel), }) if err != nil { log.Fatalf("failed to connect database: %v", err) } sqlDB, _ := db.DB() sqlDB.SetMaxIdleConns(10) sqlDB.SetMaxOpenConns(100) return db } func autoMigrate(db *gorm.DB) { err := db.AutoMigrate( &model.Shop{}, &model.User{}, &model.License{}, &model.ProductCategory{}, &model.Product{}, &model.Warehouse{}, &model.Partner{}, &model.StockInOrder{}, &model.StockInItem{}, &model.StockOutOrder{}, &model.StockOutItem{}, &model.Inventory{}, &model.InventoryLog{}, &model.InventoryCheck{}, &model.InventoryCheckItem{}, &model.FinanceRecord{}, &model.NumberRule{}, &model.ProductNameOption{}, &model.ProductSeriesOption{}, &model.ProductSpecOption{}, &model.ProductOriginOption{}, &model.ProductShelfLifeOption{}, &model.ProductStorageOption{}, &model.ProductDescriptionDoc{}, &model.ProductImage{}, &model.ErrorReport{}, &model.Feedback{}, ) if err != nil { log.Fatalf("auto migrate failed: %v", err) } log.Println("AutoMigrate completed") } func backfillPinyin(db *gorm.DB) { var products []model.Product db.Where("name_pinyin = '' OR name_pinyin IS NULL").Find(&products) if len(products) == 0 { return } for i := range products { full, initials := util.ToPinyin(products[i].Name) db.Model(&products[i]).Updates(map[string]interface{}{ "name_pinyin": full, "name_initials": initials, }) } log.Printf("backfillPinyin: updated %d products", len(products)) }