package main import ( "database/sql" "electricity_bill_calc/cache" "electricity_bill_calc/config" "electricity_bill_calc/global" "electricity_bill_calc/logger" "electricity_bill_calc/migration" "electricity_bill_calc/model" "electricity_bill_calc/router" "electricity_bill_calc/service" "encoding/csv" "fmt" "io" "os" "strconv" "time" "github.com/gin-gonic/gin" jsontime "github.com/liamylian/jsontime/v2/v2" "github.com/samber/lo" "github.com/shopspring/decimal" "github.com/uptrace/bun/migrate" "go.uber.org/zap" ) func init() { l := logger.Named("Init") err := config.SetupSetting() if err != nil { l.Fatal("Configuration load failed.", zap.Error(err)) } l.Info("Configuration loaded!") err = global.SetupDatabaseConnection() if err != nil { l.Fatal("Main Database connect failed.", zap.Error(err)) } l.Info("Main Database connected!") migrator := migrate.NewMigrator(global.DB, migration.Migrations) ctx, cancel := global.TimeoutContext(12) defer cancel() err = migrator.Init(ctx) if err != nil { l.Fatal("Database migration unable to initialized.", zap.Error(err)) } group, err := migrator.Migrate(ctx) if err != nil { l.Fatal("Database migrate failed.", zap.Error(err)) } if group.IsZero() { l.Info("There are no new migrations to run (database is up to date)") } err = global.SetupRedisConnection() if err != nil { l.Fatal("Main Cache Database connect failed.", zap.Error(err)) } l.Info("Main Cache Database connected!") err = initializeRegions() if err != nil { l.Fatal("Regions initialize failed.", zap.Error(err)) } l.Info("Regions synchronized.") err = intializeSingularity() if err != nil { l.Fatal("Singularity account intialize failed.", zap.Error(err)) } l.Info("Singularity account intialized.") timeZoneShanghai, _ := time.LoadLocation("Asia/Shanghai") jsontime.AddTimeFormatAlias("simple_datetime", "2006-01-02 15:04:05") jsontime.AddTimeFormatAlias("simple_date", "2006-01-02") jsontime.AddLocaleAlias("shanghai", timeZoneShanghai) } func initializeRegions() error { ctx, cancel := global.TimeoutContext() defer cancel() logger.Info("Synchronize regions...") regionCsvFile, err := os.Open("regions.csv") if err != nil { return fmt.Errorf("region initialize file is not found: %w", err) } defer regionCsvFile.Close() var existRegions = make([]string, 0) err = global.DB.NewSelect().Model((*model.Region)(nil)). Column("code"). Scan(ctx, &existRegions) if err != nil { return fmt.Errorf("unable to retreive regions from database: %w", err) } regionCsv := csv.NewReader(regionCsvFile) transaction, err := global.DB.BeginTx(ctx, &sql.TxOptions{}) if err != nil { return fmt.Errorf("unable to intiate database transaction: %w", err) } for { record, err := regionCsv.Read() if err == io.EOF { break } if lo.Contains(existRegions, record[0]) { continue } level, err := strconv.Atoi(record[2]) if err != nil { continue } if _, err := transaction.NewInsert().Model(&model.Region{ Code: record[0], Name: record[1], Level: level, Parent: record[3], }).Exec(ctx); err != nil { return fmt.Errorf("region synchronize in failed: %v, %w", record, err) } } if err = transaction.Commit(); err != nil { return fmt.Errorf("synchronize regions to database failed: %w", err) } return nil } func intializeSingularity() error { singularityExists, err := service.UserService.IsUserExists("000") if err != nil { return fmt.Errorf("singularity detect failed: %w", err) } if singularityExists { return nil } singularity := &model.User{ Id: "000", Username: "singularity", Type: 2, Enabled: true, } singularityName := "Singularity" singularityExpires, err := model.ParseDate("2099-12-31") if err != nil { return fmt.Errorf("singularity expires time parse failed: %w", err) } singularityDetail := &model.UserDetail{ Name: &singularityName, UnitServiceFee: decimal.Zero, ServiceExpiration: singularityExpires, } verifyCode, err := service.UserService.CreateUser(singularity, singularityDetail) if err != nil { return fmt.Errorf("singularity account failed to create: %w", err) } logger.Info( fmt.Sprintf("Singularity account created, use %s as verify code to reset password.", verifyCode), zap.String("account", "singularity"), zap.String("verifyCode", verifyCode), ) return nil } func DBConnectionKeepLive() { for range time.Tick(30 * time.Second) { err := global.DB.Ping() if err != nil { continue } } } func RedisOrphanCleanup() { cleanLogger := logger.Named("Cache").With(zap.String("function", "Cleanup")) for range time.Tick(2 * time.Minute) { cleanLogger.Info("Proceeding cleanup orphan keys.") err := cache.ClearOrphanRelationItems() if err != nil { cleanLogger.Error("Orphan keys clear failed.") continue } } } func main() { // 本次停用检测的原因是:使用Ping来保持数据库链接看起来没有什么用处。 // go DBConnectionKeepLive() go RedisOrphanCleanup() gin.SetMode(config.ServerSettings.RunMode) r := router.Router() r.Run(fmt.Sprintf(":%d", config.ServerSettings.HttpPort)) }