Files
ai_site/platform/internal/svc/servicecontext.go
whm cb56e6847e feat: add Z12/Z13 bind APIs, stock import, and sync docs
Enable auto default sync channels on agent activate, bind-code/phone confirm flows, publish ALTER, and align admin/yuheng docs with the production bind path.
2026-08-05 11:47:20 +08:00

227 lines
6.5 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package svc
import (
"context"
"database/sql"
"log"
"net/url"
"strings"
"time"
"aijianzhan/platform/internal/agentstore"
"aijianzhan/platform/internal/audit"
"aijianzhan/platform/internal/authx"
"aijianzhan/platform/internal/bindcodestore"
"aijianzhan/platform/internal/config"
"aijianzhan/platform/internal/crud"
"aijianzhan/platform/internal/dbsync"
"aijianzhan/platform/internal/invitestore"
"aijianzhan/platform/internal/license"
"aijianzhan/platform/internal/meta"
"aijianzhan/platform/internal/orgunitstore"
"aijianzhan/platform/internal/ratelimit"
"aijianzhan/platform/internal/rolestore"
"aijianzhan/platform/internal/schema"
"aijianzhan/platform/internal/smsstore"
"aijianzhan/platform/internal/storage"
"aijianzhan/platform/internal/tenantperm"
"aijianzhan/platform/internal/userstore"
_ "github.com/lib/pq"
)
type ServiceContext struct {
Config config.Config
Meta meta.Store
Schema schema.Runner
CRUD crud.Engine
Users userstore.Store
Agents agentstore.Store
Roles rolestore.Store
Invites invitestore.Store
OrgUnits orgunitstore.Store
Audit audit.Store
Objects storage.Store
Limiter *ratelimit.Limiter
DBPool *crud.DBPool
MemoryMode bool
DB *sql.DB
JWT authx.JWTConfig
DBSync *dbsync.Manager
BindCodes bindcodestore.Store
TenantPerm tenantperm.Store
SMS *smsstore.Store
License *license.Manager
}
func NewServiceContext(c config.Config) *ServiceContext {
users := userstore.NewMemoryStore()
aud := audit.NewMemoryStore()
agents := agentstore.NewMemoryStore()
roles := rolestore.NewMemoryStore()
invites := invitestore.NewMemoryStore()
orgUnits := orgunitstore.NewMemoryStore()
pubBase := strings.TrimRight(c.PublicBaseURL, "/")
if pubBase == "" {
pubBase = "http://127.0.0.1:8180"
}
storageBase := c.Storage.PublicBase
if storageBase == "" {
storageBase = pubBase + "/api/v1/storage"
}
obj, err := storage.NewLocalStore(c.Storage.LocalRoot, storageBase)
if err != nil {
log.Printf("storage init: %v", err)
obj, _ = storage.NewLocalStore("./data/uploads", storageBase)
}
ctx := &ServiceContext{
Config: c,
Meta: meta.NewMemoryStore(),
Schema: schema.NoopRunner{},
CRUD: crud.NewMemoryEngine(),
Users: users,
Agents: agents,
Roles: roles,
Invites: invites,
OrgUnits: orgUnits,
Audit: aud,
TenantPerm: tenantperm.NewMemoryStore(),
Objects: obj,
Limiter: ratelimit.New(c.RateLimitPerMin),
MemoryMode: true,
SMS: smsstore.New(
time.Duration(c.SMS.CodeTTLSeconds)*time.Second,
time.Duration(c.SMS.ResendSeconds)*time.Second,
c.SMS.DevFixedCode,
),
JWT: authx.JWTConfig{
AccessSecret: c.Auth.AccessSecret,
AccessExpire: c.Auth.AccessExpire,
},
}
if ctx.JWT.AccessSecret == "" {
ctx.JWT.AccessSecret = "dev-only-change-me"
}
if c.DataSource != "" && !c.DryRun {
db, err := sql.Open("postgres", c.DataSource)
if err != nil {
log.Printf("postgres open failed, fallback memory: %v", err)
} else if err := db.Ping(); err != nil {
log.Printf("postgres ping failed, fallback memory: %v", err)
_ = db.Close()
} else {
db.SetMaxOpenConns(20)
db.SetMaxIdleConns(5)
migCtx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
if err := meta.EnsureSchema(migCtx, db); err != nil {
log.Printf("meta migrate failed: %v", err)
ctx.DB = db
ctx.Schema = &schema.PostgresRunner{DB: db, AdminDSN: adminDSN(c.DataSource)}
pool := crud.NewDBPool(db, c.DataSource)
ctx.DBPool = pool
ctx.CRUD = crud.NewPostgresEngine(db, pool)
ctx.MemoryMode = false
} else {
ctx.DB = db
ctx.Schema = &schema.PostgresRunner{DB: db, AdminDSN: adminDSN(c.DataSource)}
pool := crud.NewDBPool(db, c.DataSource)
ctx.DBPool = pool
ctx.CRUD = crud.NewPostgresEngine(db, pool)
ctx.Meta = meta.NewPostgresStore(db)
ctx.Users = userstore.NewPostgresStore(db)
ctx.Agents = agentstore.NewPostgresStore(db)
ctx.Roles = rolestore.NewPostgresStore(db)
ctx.Invites = invitestore.NewPostgresStore(db)
ctx.OrgUnits = orgunitstore.NewPostgresStore(db)
ctx.Audit = audit.NewPostgresStore(db)
ctx.TenantPerm = tenantperm.NewPostgresStore(db)
ctx.MemoryMode = false
log.Printf("postgres engine + meta + users + agents + roles + invites + org_units + audit + tenant_perm enabled")
}
}
}
userstore.EnsureDemoUser(context.Background(), ctx.Users)
userstore.EnsurePlatformAdminUser(context.Background(), ctx.Users)
if ctx.Users != nil {
if err := ctx.Users.EnsureTenantSlugs(context.Background()); err != nil {
log.Printf("ensure tenant slugs: %v", err)
}
}
_ = ctx.Roles.EnsureDefaults(context.Background(), 1)
if ctx.TenantPerm != nil {
_ = ctx.TenantPerm.EnsureDefault(context.Background(), 1)
authx.EntitlementChecker = func(c context.Context, tenantID int64, perm string) bool {
ok, err := ctx.TenantPerm.Allows(c, tenantID, perm)
if err != nil {
log.Printf("entitlement check: %v", err)
return false
}
return ok
}
}
// Z9e存量已发布模块补齐 import/export不依赖用户再发布
if ctx.Meta != nil && !ctx.MemoryMode {
bfCtx, bfCancel := context.WithTimeout(context.Background(), 30*time.Second)
meta.RunBackfillDefaultImportExportOnBoot(bfCtx, ctx.Meta)
bfCancel()
}
if c.DryRun {
ctx.Schema = schema.NoopRunner{}
}
// 跨库同步中间件(默认启用)
if c.DBSync.Enabled {
dir := c.DBSync.DataDir
if dir == "" {
dir = "./data/dbsync"
}
store, err := dbsync.NewFileStore(dir)
if err != nil {
log.Printf("dbsync store: %v", err)
} else {
ctx.DBSync = dbsync.NewManager(store)
log.Printf("dbsync middleware enabled (dir=%s)", dir)
if bc, err := bindcodestore.NewFileStore(dir); err != nil {
log.Printf("bind code store: %v", err)
} else {
ctx.BindCodes = bc
}
}
}
licCfg := c.License
if licCfg.Enabled {
if strings.TrimSpace(licCfg.ControlSecret) == "" {
licCfg.ControlSecret = c.Auth.IssueSecret
}
if strings.TrimSpace(licCfg.SignSecret) == "" {
licCfg.SignSecret = licCfg.ControlSecret
}
}
lic, err := license.NewManager(licCfg)
if err != nil {
log.Printf("license manager: %v", err)
} else {
ctx.License = lic
if lic.Enabled() {
if ctx.DB != nil {
lic.AttachMirror(license.NewPGMirror(ctx.DB))
}
log.Printf("license lease enabled (leases=%s state=%s)", lic.Path(), lic.StatePath())
}
}
return ctx
}
func adminDSN(dsn string) string {
u, err := url.Parse(dsn)
if err != nil {
return dsn
}
u.Path = "/postgres"
return u.String()
}