196 lines
5.1 KiB
Go
196 lines
5.1 KiB
Go
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))
|
||
}
|