package main import ( "database/sql" "log" _ "github.com/lib/pq" "github.com/gin-gonic/gin" "github.com/tcs-iptv/tcs/internal/api" "github.com/tcs-iptv/tcs/internal/chain" "github.com/tcs-iptv/tcs/internal/config" "github.com/tcs-iptv/tcs/internal/httpx" "github.com/tcs-iptv/tcs/internal/macode" "github.com/tcs-iptv/tcs/internal/service" ) // openDB 尝试连接 PostgreSQL,连通则返回 *sql.DB,否则返回 nil(回退内存)。 func openDB(dsn string) *sql.DB { db, err := sql.Open("postgres", dsn) if err != nil { return nil } if err := db.Ping(); err != nil { return nil } return db } // newAllocationStore 优先使用 PostgreSQL(持久、防重号),不可用时回退内存。 func newAllocationStore(db *sql.DB) macode.AllocationStore { if db != nil { log.Printf("macode: 使用 PostgreSQL 号段存储") return macode.NewPostgresStore(db) } log.Printf("macode: PostgreSQL 不可用,回退内存号段存储(仅开发用)") return macode.NewMemoryStore() } // newChain 按配置选择链后端: // - chainmaker:真实链(需 -tags chainmaker 构建并配置 SDK),失败回退 pg/内存 // - pg:PG 持久化链(写穿+水合,重启不丢数据) // - memory:纯内存(仅开发) // // PG 不可用时自动降级到内存。 func newChain(backend, sdkConf string, db *sql.DB) chain.Client { switch backend { case "chainmaker": cm, err := chain.NewChainMakerClient(sdkConf, db) if err == nil { log.Printf("chain: 使用 ChainMaker 真实链后端") return cm } log.Printf("chain: ChainMaker 后端不可用(%v),降级", err) fallthrough case "pg": if db != nil { if pc, err := chain.NewPersistentChain(db); err == nil { log.Printf("chain: 使用 PostgreSQL 持久化链(写穿+水合,重启不丢数据)") return pc } else { log.Printf("chain: PG 持久化链初始化失败(%v),回退内存链", err) } } fallthrough default: log.Printf("chain: 使用内存链(仅开发用,重启丢数据)") return chain.NewMemoryChain() } } func main() { cfg := config.Load() // 装配依赖:共享一个 PG 连接给链持久化与号段存储 db := openDB(cfg.PostgresDSN) ch := newChain(cfg.ChainBackend, cfg.ChainMakerSDKConf, db) gen := macode.NewGenerator(newAllocationStore(db)) // 示例号段(生产由与发码机构对接后配置) // 机构节点 6101 = 陕西(管理方:陕西IPTV运营公司);行业节点 8531 = IPTV视听内容 _ = gen.RegisterSegment(macode.Segment{ IndustryNode: "8531", OrgNode: "6101", Category: macode.CategoryMicroDrama, Start: 1, End: 9999999, SeqWidth: 7, }) _ = gen.RegisterSegment(macode.Segment{ IndustryNode: "8531", OrgNode: "6101", Category: macode.CategoryWebSeries, Start: 1, End: 9999999, SeqWidth: 7, }) _ = gen.RegisterSegment(macode.Segment{ IndustryNode: "8531", OrgNode: "6101", Category: macode.CategoryWebMovie, Start: 1, End: 9999999, SeqWidth: 7, }) svc := service.New(ch, gen) h := api.NewHandler(svc) // 鉴权密钥库(MVP 预置四角色示例密钥;生产从 Vault/DB 加载) keys := httpx.NewMemoryKeyStore() keys.Add("ak-regulator", "sk-regulator", string(chain.RoleRegulator)) keys.Add("ak-reviewer", "sk-reviewer", string(chain.RoleReviewer)) keys.Add("ak-cp", "sk-cp", string(chain.RoleCP)) keys.Add("ak-operator", "sk-operator", string(chain.RoleOperator)) r := gin.Default() httpx.Health(r, "api-svc") v1 := r.Group("/api/v1", httpx.AuthMiddleware(keys)) h.Register(v1) log.Printf("api-svc listening on %s", cfg.APIAddr) if err := r.Run(cfg.APIAddr); err != nil { log.Fatalf("api-svc failed: %v", err) } }