Files
MAcode/tcs-iptv/cmd/api-svc/main.go
T
selfrelease 599ccfe1c8 feat(chain): PostgreSQL 持久化链(最小改动·写穿+启动水合)
- internal/chain/persistent.go: PersistentChain 装饰 MemoryChain
  - 复用 MemoryChain 全部业务规则(权限/1:1绑定/防换壳),读走内存
  - 写路径在内存变更成功后写穿 PG 镜像表(content_registry/hash_binding/identity_mapping/version_history/chain_tx)
  - 启动从 PG 水合恢复内存状态,重启不丢数据;PG 为非权威镜像,写穿失败仅记日志
- deploy/migrations/0004_binding_revoked.sql: hash_binding 增加 revoked/revoked_reason 列(集级下架镜像)
- cmd/api-svc/main.go: 共享一个 *sql.DB,PG 可用时启用 PersistentChain+PostgresStore,否则回退内存
- 验证: seed_demo 后内容/映射/哈希落库;重启水合3条内容,resolve/mappings 正常恢复
- 面向未来: 接真实 ChainMaker 时整体替换 chain.Client,业务层零改动
2026-06-14 19:24:38 +08:00

96 lines
3.1 KiB
Go

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 优先使用 PostgreSQL 持久化链(写穿 + 启动水合),不可用时回退纯内存。
func newChain(db *sql.DB) chain.Client {
if db != nil {
if pc, err := chain.NewPersistentChain(db); err == nil {
log.Printf("chain: 使用 PostgreSQL 持久化链(写穿+水合,重启不丢数据)")
return pc
} else {
log.Printf("chain: PG 持久化链初始化失败(%v),回退内存链", err)
}
}
log.Printf("chain: 使用内存链(仅开发用,重启丢数据)")
return chain.NewMemoryChain()
}
func main() {
cfg := config.Load()
// 装配依赖:共享一个 PG 连接给链持久化与号段存储
db := openDB(cfg.PostgresDSN)
ch := newChain(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)
}
}