145 lines
4.6 KiB
Go
145 lines
4.6 KiB
Go
package main
|
||
|
||
import (
|
||
"flag"
|
||
"fmt"
|
||
"log/slog"
|
||
"os"
|
||
|
||
robfigcron "github.com/robfig/cron/v3"
|
||
"gorm.io/gorm"
|
||
|
||
"ws_usernode/internal/api"
|
||
"ws_usernode/internal/auth"
|
||
"ws_usernode/internal/config"
|
||
"ws_usernode/internal/cron"
|
||
"ws_usernode/internal/mail"
|
||
"ws_usernode/internal/model"
|
||
"ws_usernode/internal/router"
|
||
"ws_usernode/internal/server"
|
||
"ws_usernode/internal/service"
|
||
"ws_usernode/internal/system"
|
||
)
|
||
|
||
// bootstrap 加载配置、打开数据库、构建日志。
|
||
// cfgPath 为配置文件路径,debug 开启调试日志与 GORM SQL 日志。
|
||
func bootstrap(cfgPath string, debug bool) (*config.Config, *slog.Logger, *gorm.DB, error) {
|
||
cfg, err := config.Load(cfgPath)
|
||
if err != nil {
|
||
return nil, nil, nil, err
|
||
}
|
||
if debug {
|
||
cfg.Log.Level = "debug"
|
||
}
|
||
log := newLogger(cfg.Log.Level, cfg.Log.Format)
|
||
|
||
db, err := model.Open(cfg.Database.Driver, cfg.Database.DSN, debug)
|
||
if err != nil {
|
||
return nil, nil, nil, fmt.Errorf("打开数据库: %w", err)
|
||
}
|
||
return cfg, log, db, nil
|
||
}
|
||
|
||
// commonFlags 定义所有子命令共享的 --config / --debug。
|
||
func commonFlags(fs *flag.FlagSet) (cfgPath *string, debug *bool) {
|
||
cfgPath = fs.String("config", "config.toml", "配置文件路径(默认 config.toml)")
|
||
debug = fs.Bool("debug", false, "调试模式(debug 日志 + GORM SQL 日志)")
|
||
return cfgPath, debug
|
||
}
|
||
|
||
// newLogger 按配置构建 slog 输出器。
|
||
func newLogger(level, format string) *slog.Logger {
|
||
var lvl slog.Level
|
||
switch level {
|
||
case "debug":
|
||
lvl = slog.LevelDebug
|
||
case "warn":
|
||
lvl = slog.LevelWarn
|
||
case "error":
|
||
lvl = slog.LevelError
|
||
default:
|
||
lvl = slog.LevelInfo
|
||
}
|
||
opts := &slog.HandlerOptions{Level: lvl}
|
||
var h slog.Handler
|
||
if format == "json" {
|
||
h = slog.NewJSONHandler(os.Stderr, opts)
|
||
} else {
|
||
h = slog.NewTextHandler(os.Stderr, opts)
|
||
}
|
||
return slog.New(h)
|
||
}
|
||
|
||
// serve 子命令:启动 HTTP 服务(自动迁移 + 定时任务)。
|
||
func cmdServe(args []string) error {
|
||
fs := flag.NewFlagSet("serve", flag.ContinueOnError)
|
||
cfgPath, debug := commonFlags(fs)
|
||
if err := fs.Parse(args); err != nil {
|
||
return err
|
||
}
|
||
cfg, log, db, err := bootstrap(*cfgPath, *debug)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
sys := system.New(cfg.System, log)
|
||
adminSvc := service.NewAdminService(db)
|
||
userSvc := service.NewUserService(db, sys, cfg)
|
||
keySvc := service.NewKeyService(db, sys, cfg)
|
||
auditSvc := service.NewAuditService(db, log)
|
||
settingsSvc := service.NewSettingService(db, cfg)
|
||
|
||
// 认证依赖:DB OTP/会话/重置令牌存储 + 内存图形验证码/登录限速器 + 邮件
|
||
captchas := auth.NewMemoryCaptchaStore(cfg.Auth.CaptchaTTL)
|
||
otps := auth.NewDBOTPStore(db, auth.DefaultMaxFailures, auth.DefaultFailureWin)
|
||
sessions := auth.NewDBSessionStore(db)
|
||
resets := auth.NewDBResetTokenStore(db)
|
||
limiter := auth.NewRateLimiter(cfg.Auth.MaxLoginFailures, cfg.Auth.LockDuration)
|
||
// M3:所有邮件经队列(mail_logs 落库 + 失败重试),OTP 双通道不受邮件故障影响
|
||
mailer := mail.NewQueuedMailer(mail.New(cfg.SMTP, log), db, log)
|
||
authSvc := service.NewAuthService(db, cfg, otps, captchas, sessions, resets, limiter, mailer, userSvc, adminSvc, auditSvc, log)
|
||
approvalSvc := service.NewApprovalService(db, cfg, userSvc, mailer, log)
|
||
// M4:设置覆盖默认 TTL(settings 表);生命周期每日维护
|
||
userSvc.WithSettings(settingsSvc)
|
||
lifecycleSvc := service.NewLifecycleService(db, cfg, sys, userSvc, settingsSvc, mailer, auditSvc, log)
|
||
|
||
// 启动前自动迁移(骨架阶段保证表结构就绪;M5 部署建议显式 migrate)
|
||
if err := model.Migrate(db); err != nil {
|
||
return fmt.Errorf("数据库迁移: %w", err)
|
||
}
|
||
log.Info("db: migrated", "driver", cfg.Database.Driver, "dsn", cfg.Database.DSN)
|
||
|
||
// 定时任务(M3:邮件重试;M4:过期扫描/回收/审计归档)
|
||
sched := robfigcron.New()
|
||
cron.New(cfg, mailer, lifecycleSvc, auditSvc, settingsSvc, log).Register(sched)
|
||
sched.Start()
|
||
defer sched.Stop()
|
||
|
||
h := api.New(cfg, authSvc, userSvc, keySvc, approvalSvc, auditSvc, settingsSvc)
|
||
r := router.New(cfg, h, sessions, log)
|
||
|
||
srv := server.New(cfg.Server.Listen, r, log)
|
||
if err := srv.Run(); err != nil {
|
||
return fmt.Errorf("服务异常退出: %w", err)
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// migrate 子命令:执行数据库迁移。
|
||
func cmdMigrate(args []string) error {
|
||
fs := flag.NewFlagSet("migrate", flag.ContinueOnError)
|
||
cfgPath, debug := commonFlags(fs)
|
||
if err := fs.Parse(args); err != nil {
|
||
return err
|
||
}
|
||
cfg, log, db, err := bootstrap(*cfgPath, *debug)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
if err := model.Migrate(db); err != nil {
|
||
return fmt.Errorf("数据库迁移: %w", err)
|
||
}
|
||
log.Info("db: migration complete", "driver", cfg.Database.Driver)
|
||
return nil
|
||
}
|