package main import ( "context" "database/sql" "errors" "log" "net" "net/http" "os" "os/signal" "syscall" "time" _ "github.com/go-sql-driver/mysql" "google.golang.org/grpc" "google.golang.org/grpc/reflection" "git.5iot.com/iotlcm-public/iotlcm-sms/api/pb" "git.5iot.com/iotlcm-public/iotlcm-sms/internal/grpcapi" "git.5iot.com/iotlcm-public/iotlcm-sms/internal/httpapi" "git.5iot.com/iotlcm-public/iotlcm-sms/internal/sms" "git.5iot.com/iotlcm-public/iotlcm-sms/internal/sms/aliyun" "git.5iot.com/iotlcm-public/iotlcm-sms/internal/sms/tencent" ) // 构建时通过 -ldflags 注入。 var ( BuildVersion = "0.0" BuildTime = "" CommitID = "" ) func main() { ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) defer stop() log.Printf("iotlcm-sms starting, version=%s buildTime=%s commit=%s", BuildVersion, BuildTime, CommitID) // 默认全局配置:config.yaml 提供默认值,SMS_* 环境变量可覆盖。 defaultConfig, source, err := sms.LoadDefaultConfig() if err != nil { log.Fatalf("load sms default config: %v", err) } log.Printf("sms default config loaded: %s", source.Describe()) // 场景模板配置来自数据库(表 t_sms_template_config):改库即生效,无需改代码或重启。 // 连接参数与凭证同源于 config.yaml(SMS_DB_DSN 等环境变量可覆盖);DSN 为空时能力关闭。 var scenes sms.SceneProvider var sceneStore *sms.SceneStore sceneDb, sceneDbSrc, err := sms.LoadSceneDbConfig() if err != nil { log.Printf("load scene db config: %v(场景模板能力关闭)", err) } else if sceneDb.DSN == "" { log.Printf("scene db DSN not set, scene-based template config disabled") } else { db, err := sql.Open("mysql", sceneDb.DSN) if err != nil { log.Fatalf("open sms config database: %v", err) } db.SetMaxOpenConns(4) db.SetMaxIdleConns(2) db.SetConnMaxLifetime(time.Hour) ttl := sceneDb.TTL() sceneStore = sms.NewSceneStore(db, ttl) defer func() { _ = sceneStore.Close() }() scenes = sceneStore log.Printf("sms scene config store enabled (source: %s), cache ttl=%s", sceneDbSrc.Describe(), ttl) } providers := sms.NewRegistryWithDefault(defaultConfig, scenes) providers.Register(sms.ServiceAliyun, aliyun.New()) providers.Register(sms.ServiceTencent, tencent.New()) httpAddr := envOrDefault("HTTP_ADDR", ":8080") httpServer := &http.Server{ Addr: httpAddr, Handler: httpapi.NewHandler(providers), ReadHeaderTimeout: 5 * time.Second, ReadTimeout: 10 * time.Second, WriteTimeout: 30 * time.Second, IdleTimeout: 60 * time.Second, } grpcAddr := envOrDefault("GRPC_ADDR", ":11200") grpcListener, err := net.Listen("tcp", grpcAddr) if err != nil { log.Fatalf("listen on gRPC address %s: %v", grpcAddr, err) } grpcServer := grpc.NewServer() pb.RegisterNoticeServer(grpcServer, grpcapi.NewServer(providers)) reflection.Register(grpcServer) errCh := make(chan error, 2) go func() { log.Printf("sms http server listening on %s", httpAddr) if err := httpServer.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { errCh <- err } }() go func() { log.Printf("sms gRPC server listening on %s", grpcAddr) if err := grpcServer.Serve(grpcListener); err != nil { errCh <- err } }() select { case err := <-errCh: log.Printf("server stopped unexpectedly: %v", err) stop() case <-ctx.Done(): } shutdownCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() if err := httpServer.Shutdown(shutdownCtx); err != nil { log.Printf("shutdown HTTP server: %v", err) } grpcServer.GracefulStop() } func envOrDefault(name, fallback string) string { if value := os.Getenv(name); value != "" { return value } return fallback }