Files
ai_site/platform/internal/svc/servicecontext.go
2026-07-31 10:31:17 +08:00

214 lines
6.0 KiB
Go

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/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
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
}
}
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)
}
}
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()
}