diff --git a/.gitignore b/.gitignore index eca5bc7..792ac77 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,6 @@ docs/ app.db docker/server -server __debug_bin # Editor directories and files diff --git a/cmd/server/main.go b/cmd/server/main.go new file mode 100644 index 0000000..03f3d33 --- /dev/null +++ b/cmd/server/main.go @@ -0,0 +1,188 @@ +package main + +import ( + "flag" + "fmt" + "os" + "os/signal" + "time" + "wpw-common/internal" + "wpw-common/pkg/gen/biz/core" + + "context" + + coresvc "wpw-common/internal/biz/core" + "wpw-common/internal/http" + "wpw-common/internal/system" + "wpw-common/pkg/gormlog" + + _ "github.com/joho/godotenv/autoload" + "go.uber.org/zap" + + "gorm.io/driver/mysql" + _ "gorm.io/driver/mysql" + "gorm.io/driver/sqlite" + _ "gorm.io/driver/sqlite" + "gorm.io/gorm" + "gorm.io/gorm/logger" +) + +var ( + appCtx = (internal.AppContext{}).Init() + ctx context.Context + + appSecret string + taskWaitDuration = time.Second * 10 + serverListenAddr = "0.0.0.0:8000" + dataSourceType = "sqlite" + dsn = "./app.db" + fallabackFlagValue func() +) + +func osEnvFallback(s string, key string) string { + if s != "" { + return s + } + return os.Getenv(key) +} + +func gormSetup(driver string, dsn string, config *gorm.Config) (*gorm.DB, error) { + if driver == "" { + driver = "sqlite" + if dsn == "" { + dsn = "./app.db" + } + } + switch driver { + case "sqlite": + return gorm.Open(sqlite.Open(dsn), config) + case "mysql": + return gorm.Open(mysql.Open(dsn), config) + default: + panic(fmt.Sprintf("unknown driver %s", driver)) + } +} + +func init() { + var err error + + // flags + flag.DurationVar(&taskWaitDuration, "grace-duration", + taskWaitDuration, "graceful wait duration") + flag.StringVar(&appSecret, "secret", appSecret, "Application secret") + flag.StringVar(&serverListenAddr, "addr", + serverListenAddr, "Server listen address") + flag.StringVar(&dataSourceType, "dst", dataSourceType, "Data source") + flag.StringVar(&dsn, "dsn", dsn, "DSN") + fallabackFlagValue = func() { + appSecret = osEnvFallback(appSecret, "SECRET") + serverListenAddr = osEnvFallback(serverListenAddr, "ADDR") + dataSourceType = osEnvFallback(dataSourceType, "DRIVER") + dsn = osEnvFallback(dsn, "DSN") + } + + appCtx.Logger, err = zap.NewDevelopment() + if err != nil { + panic(err) + } + + // initalize application context + ctx, appCtx.Cancel = context.WithCancel(context.Background()) + ctx = internal.WithAppContext(ctx, &appCtx) +} + +func main() { + var err error + flag.Parse() + fallabackFlagValue() + + defer appCtx.Logger.Sync() + sugar := appCtx.Logger.Sugar().Named("main") + sugar.Info("starting app") + + sig := make(chan os.Signal, 1) + signal.Notify(sig, os.Interrupt) + + // push secret to context + if appSecret == "" { + sugar.Fatal("application secret is empty") + } + appCtx.Data["secret"] = appSecret + + // initialize db + if appCtx.Data["db"], err = gormSetup(dataSourceType, dsn, &gorm.Config{ + Logger: gormlog.New(gormlog.GormLogParams{ + LogLevel: logger.Info, + SlowThreshold: time.Second * 2, + IgnoreRecordNotFoundError: true, + Logger: appCtx.Logger, + }), + }); err != nil { + sugar.Fatalw("db error", + "driver", dataSourceType, + "err", err) + } + + // system init + system.Init(&appCtx) + + // register services + http.RegisterService("/api/core/v1", core.NewCoreServiceProcessor( + coresvc.NewCoreServiceFromAppContext(&appCtx))) + + // + + appCtx.DoTask(func(ctx context.Context) { + sugar.Info("task start") + defer sugar.Info("task done") + for { + select { + case <-time.After(time.Second * 40): + return + case <-ctx.Done(): + sugar.Info("task notify cancel") + <-time.After(time.Second * 5) + return + } + } + }) + + // start server + appCtx.DoTask(func(ctx context.Context) { + errChan := make(chan error, 1) + go func() { errChan <- http.Run(&appCtx, serverListenAddr) }() + if err := <-errChan; err != nil { + sugar.Errorw("server error", + "err", err) + } + }) + +spin: + for { + select { + case <-ctx.Done(): + sugar.Info("waiting task done", + "cnt", appCtx.TaskCount()) + done := make(chan struct{}, 1) + go func() { appCtx.WaitTask(); done <- struct{}{} }() + select { + case <-done: + break spin + case sig := <-sig: + // cancel the wait + sugar.Fatalw("waiting task cancelled", + "sig", sig, + "remaining", appCtx.TaskRemaining(), + ) + case <-time.After(taskWaitDuration): + sugar.Fatalw("timeout waiting task done", + "remaining", appCtx.TaskRemaining()) + } + case sig := <-sig: + sugar.Infow("received signal", + "sig", sig, + ) + appCtx.Cancel() + } + } +}