服务端(Go): - GM 塞背包/带属性装备发放:在线角色推送 op14 增量物品行,背包实时刷新,无需回角色选择界面 - GM 改金币/改等级:去掉在线拒绝,落库后推送 op14 钱包/op37 等级状态,在线实时刷新 - 等级端点统一切换到新版 GMSetCharacterLevel(自带在线刷新) - 无限/锁死疲劳模式 + 燃烧疲劳:每房间消耗 2 点、打怪与通关经验双倍 - 燃烧疲劳开关时给所有在线角色推送系统提示(op12 mode 0) - 深渊史诗掉率:GM 全局倍率 + 城镇增益药剂窗口倍率,真实接入掉落结算 启动器(Python/JS): - 标题栏自绘、窗口 1204x717、禁止拖拽改大小、仅最小化/关闭 - GM 工具:疲劳与燃烧、深渊史诗掉率、拍卖行上架(单一定价对齐) - 拍卖行上架前端对齐服务端单一价格语义,修复 400 报错
372 lines
13 KiB
Go
372 lines
13 KiB
Go
package app
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/http"
|
|
"time"
|
|
|
|
"longheng.io/server/internal/platform/admincmdqueue"
|
|
"longheng.io/server/internal/platform/cluster"
|
|
"longheng.io/server/internal/platform/config"
|
|
"longheng.io/server/internal/platform/discovery"
|
|
"longheng.io/server/internal/platform/reload"
|
|
rpckit "longheng.io/server/internal/platform/rpc"
|
|
"longheng.io/server/internal/platform/servergroup"
|
|
"longheng.io/server/internal/platform/topology"
|
|
"longheng.io/server/internal/platform/workerpool"
|
|
)
|
|
|
|
// New 创建只装配不运行的 Application,适合测试或上层进程自行托管生命周期。
|
|
func New(serviceName, configPath string, builder Builder) (*Application, error) {
|
|
return NewWithManifest(serviceName, configPath, ServiceManifest{Components: builder})
|
|
}
|
|
|
|
// Run 创建并运行基于 Builder 的服务,直到传入 context 或系统信号触发停服。
|
|
func Run(ctx context.Context, serviceName, configPath string, builder Builder) error {
|
|
return RunWithManifest(ctx, serviceName, configPath, ServiceManifest{Components: builder})
|
|
}
|
|
|
|
// RunWithManifest 创建并运行基于 ServiceManifest 的服务,统一复用平台基础设施装配。
|
|
func RunWithManifest(ctx context.Context, serviceName, configPath string, manifest ServiceManifest) error {
|
|
if ctx == nil {
|
|
ctx = context.Background()
|
|
}
|
|
application, err := NewWithManifest(serviceName, configPath, manifest)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return application.Run(ctx)
|
|
}
|
|
|
|
// NewWithManifest 创建 Application 并完成平台、runtime、admin 与业务组件的统一装配。
|
|
func NewWithManifest(serviceName, configPath string, manifest ServiceManifest) (*Application, error) {
|
|
foundation, err := newPlatformBase(serviceName, configPath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
cfg := foundation.cfg
|
|
logger := foundation.logger
|
|
otelShutdown := foundation.otelShutdown
|
|
pvfArchive := foundation.pvfArchive
|
|
dataTables := foundation.dataTables
|
|
dataTableViews := foundation.dataTableViews
|
|
stateSyncStore := foundation.stateSyncStore
|
|
serverGroupManager := foundation.serverGroupManager
|
|
serverGroupStore := foundation.serverGroupStore
|
|
auditLogger := foundation.auditLogger
|
|
logicLogger := foundation.logicLogger
|
|
biLogger := foundation.biLogger
|
|
healthSvc := foundation.healthSvc
|
|
metricsRegistry := foundation.metricsRegistry
|
|
tracer := foundation.tracer
|
|
rateLimiter := foundation.rateLimiter
|
|
adminTimeouts := foundation.adminTimeouts
|
|
adminServer := foundation.adminServer
|
|
schedulerSvc := foundation.schedulerSvc
|
|
var application *Application
|
|
configProvider := func() config.ServiceConfig {
|
|
if application != nil {
|
|
return application.configSnapshot()
|
|
}
|
|
return cfg
|
|
}
|
|
adminGuard := newAdminSecurity(configProvider, auditLogger, logger, rateLimiter)
|
|
storageObjects, err := newStorageObjSvc(cfg, adminServer.Mux(), adminGuard.wrap, adminGuard.wrapDangerous)
|
|
if err != nil {
|
|
_ = foundation.close(context.Background())
|
|
return nil, err
|
|
}
|
|
accountCenter, err := newAccountCenter(cfg, adminServer.Mux(), adminGuard.wrap, adminGuard.wrapDangerous)
|
|
if err != nil {
|
|
_ = storageObjects.Close()
|
|
_ = foundation.close(context.Background())
|
|
return nil, err
|
|
}
|
|
|
|
reg, err := newRegistry(cfg, logger)
|
|
if err != nil {
|
|
_ = foundation.close(context.Background())
|
|
return nil, err
|
|
}
|
|
eventBus, err := newBus(cfg, logger)
|
|
if err != nil {
|
|
_ = reg.Close()
|
|
_ = foundation.close(context.Background())
|
|
return nil, err
|
|
}
|
|
attachBusMetrics(eventBus, metricsRegistry, cfg)
|
|
cacheStore, err := newCache(cfg, metricsRegistry)
|
|
if err != nil {
|
|
_ = eventBus.Close()
|
|
_ = reg.Close()
|
|
_ = foundation.close(context.Background())
|
|
return nil, err
|
|
}
|
|
lockManager, err := newLockManager(cfg, metricsRegistry)
|
|
if err != nil {
|
|
_ = cacheStore.Close()
|
|
_ = eventBus.Close()
|
|
_ = reg.Close()
|
|
_ = foundation.close(context.Background())
|
|
return nil, err
|
|
}
|
|
presenceRuntime, err := newPresenceRuntime(cfg)
|
|
if err != nil {
|
|
_ = lockManager.Close()
|
|
_ = cacheStore.Close()
|
|
_ = eventBus.Close()
|
|
_ = reg.Close()
|
|
_ = foundation.close(context.Background())
|
|
return nil, err
|
|
}
|
|
regPresenceMetrics(metricsRegistry, presenceRuntime, cfg)
|
|
onlinePush, err := newOnlinePushService(eventBus, presenceRuntime, adminServer.Mux(), adminGuard.wrap, adminGuard.wrapDangerous)
|
|
if err != nil {
|
|
if closer, ok := presenceRuntime.(interface{ Close() error }); ok {
|
|
_ = closer.Close()
|
|
}
|
|
_ = lockManager.Close()
|
|
_ = cacheStore.Close()
|
|
_ = eventBus.Close()
|
|
_ = reg.Close()
|
|
_ = foundation.close(context.Background())
|
|
return nil, err
|
|
}
|
|
eventLog, eventLogWorker, err := newEventLog(cfg, eventBus, adminServer.Mux(), adminGuard.wrap, logger)
|
|
if err != nil {
|
|
if closer, ok := presenceRuntime.(interface{ Close() error }); ok {
|
|
_ = closer.Close()
|
|
}
|
|
_ = lockManager.Close()
|
|
_ = cacheStore.Close()
|
|
_ = eventBus.Close()
|
|
_ = reg.Close()
|
|
_ = foundation.close(context.Background())
|
|
return nil, err
|
|
}
|
|
var adminCommands *admincmdqueue.Executor
|
|
if eventLog != nil {
|
|
adminCommands, err = admincmdqueue.NewExecutor(admincmdqueue.ExecutorOptions{Log: eventLog})
|
|
if err != nil {
|
|
_ = eventLog.Close()
|
|
if closer, ok := presenceRuntime.(interface{ Close() error }); ok {
|
|
_ = closer.Close()
|
|
}
|
|
_ = lockManager.Close()
|
|
_ = cacheStore.Close()
|
|
_ = eventBus.Close()
|
|
_ = reg.Close()
|
|
_ = foundation.close(context.Background())
|
|
return nil, err
|
|
}
|
|
}
|
|
hotUpdateManager, err := newHotUpdateManager(cfg, dataTables, dataTableViews, eventLog, logger)
|
|
if err != nil {
|
|
if eventLog != nil {
|
|
_ = eventLog.Close()
|
|
}
|
|
if closer, ok := presenceRuntime.(interface{ Close() error }); ok {
|
|
_ = closer.Close()
|
|
}
|
|
_ = lockManager.Close()
|
|
_ = cacheStore.Close()
|
|
_ = eventBus.Close()
|
|
_ = reg.Close()
|
|
_ = foundation.close(context.Background())
|
|
return nil, err
|
|
}
|
|
workers := workerpool.New("worker-pool", cfg.Worker.Size, cfg.Worker.Queue, logger)
|
|
rpcEndpoint := rpckit.NewEndpoint(cfg.Service.Name, cfg.Service.NodeID, eventBus, logger)
|
|
rpcEndpoint.SetOptionsExact(rpckit.Options{
|
|
CallTimeout: time.Duration(cfg.RPC.CallTimeoutSeconds) * time.Second,
|
|
MaxPending: cfg.RPC.MaxPending,
|
|
MaxPayloadBytes: cfg.RPC.MaxPayloadBytes,
|
|
})
|
|
discoveryResolver := discovery.NewResolver(
|
|
reg,
|
|
time.Duration(cfg.Discovery.CacheTTLSeconds)*time.Second,
|
|
time.Duration(cfg.Discovery.FailureTTLSeconds)*time.Second,
|
|
!cfg.Discovery.AllowMaintaining,
|
|
)
|
|
discoveryResolver.SetFailureThreshold(cfg.Discovery.FailureThreshold)
|
|
discoveryResolver.SetStrategy(discovery.Strategy(cfg.Discovery.Strategy))
|
|
rpcEndpoint.SetDiscoveryResolver(discoveryResolver)
|
|
rpcEndpoint.SetTracer(tracer)
|
|
registerRPCMetrics(metricsRegistry, rpcEndpoint, cfg)
|
|
topologyManager := topology.New(topologyConfig(cfg), reg, logger)
|
|
clusterCoord := cluster.New(cfg, reg, eventBus, healthSvc, adminServer.Mux(), cfg.Admin.Token, logger, func(next http.HandlerFunc) http.HandlerFunc {
|
|
return adminGuard.wrap("cluster", next)
|
|
})
|
|
application = &Application{
|
|
cfg: cfg,
|
|
configPath: configPath,
|
|
logger: logger,
|
|
bus: eventBus,
|
|
cache: cacheStore,
|
|
lockManager: lockManager,
|
|
presence: presenceRuntime,
|
|
registry: reg,
|
|
cluster: clusterCoord,
|
|
health: healthSvc,
|
|
metrics: metricsRegistry,
|
|
rateLimiter: rateLimiter,
|
|
discovery: discoveryResolver,
|
|
rpc: rpcEndpoint,
|
|
topology: topologyManager,
|
|
scheduler: schedulerSvc,
|
|
tracer: tracer,
|
|
pvfArchive: pvfArchive,
|
|
dataTables: dataTables,
|
|
dataTableViews: dataTableViews,
|
|
audit: auditLogger,
|
|
logicLog: logicLogger,
|
|
biLog: biLogger,
|
|
eventLog: eventLog,
|
|
adminCommands: adminCommands,
|
|
accountCenter: accountCenter,
|
|
onlinePush: onlinePush,
|
|
otelShutdown: otelShutdown,
|
|
workers: workers,
|
|
admin: adminServer,
|
|
hotUpdate: hotUpdateManager,
|
|
stateSync: stateSyncStore,
|
|
storageObjects: storageObjects,
|
|
serverGroup: serverGroupManager,
|
|
serverGroupStore: serverGroupStore,
|
|
serverGroupArchives: foundation.serverGroupArchives,
|
|
}
|
|
reloadManager := reload.New("config-reloader", reloadConfig(cfg), application.applyRuntimeReload, logger)
|
|
application.reloader = reloadManager
|
|
registerCommonRoutes(commonRouteContext{
|
|
Mux: adminServer.Mux(),
|
|
ConfigProvider: application.configSnapshot,
|
|
Health: healthSvc,
|
|
Metrics: metricsRegistry,
|
|
RateLimiter: rateLimiter,
|
|
Discovery: discoveryResolver,
|
|
RPC: rpcEndpoint,
|
|
Topology: topologyManager,
|
|
Scheduler: schedulerSvc,
|
|
Tracer: tracer,
|
|
PVF: pvfArchive,
|
|
DataTables: dataTables,
|
|
DataTableViews: dataTableViews,
|
|
Audit: auditLogger,
|
|
LogicLog: logicLogger,
|
|
BILog: biLogger,
|
|
Reload: reloadManager,
|
|
Logger: logger,
|
|
})
|
|
regUpdateStateRoutes(adminServer.Mux(), adminGuard.wrapDangerous, adminCommands, hotUpdateManager, stateSyncStore, time.Duration(cfg.StateSync.TTLSeconds)*time.Second)
|
|
if serverGroupManager != nil {
|
|
servergroup.RegisterAdminRoutes(adminServer.Mux(), serverGroupManager, servergroup.AdminOptions{
|
|
Prefix: cfg.ServerGroup.AdminPrefix,
|
|
Store: serverGroupStore,
|
|
Archives: foundation.serverGroupArchives,
|
|
WarZoneInZoneDelaySecs: cfg.ServerGroup.InZoneDelaySeconds,
|
|
WarZoneNoticeLeadSecs: cfg.ServerGroup.NoticeLeadSeconds,
|
|
Wrap: adminGuard.wrap,
|
|
WrapDangerous: adminGuard.wrapDangerous,
|
|
})
|
|
}
|
|
|
|
env := &Env{
|
|
Config: cfg,
|
|
ConfigPath: configPath,
|
|
Logger: logger,
|
|
Bus: eventBus,
|
|
Cache: cacheStore,
|
|
Lock: lockManager,
|
|
Presence: presenceRuntime,
|
|
Cluster: clusterCoord,
|
|
Registry: reg,
|
|
Health: healthSvc,
|
|
Metrics: metricsRegistry,
|
|
RateLimiter: rateLimiter,
|
|
Discovery: discoveryResolver,
|
|
RPC: rpcEndpoint,
|
|
Topology: topologyManager,
|
|
Scheduler: schedulerSvc,
|
|
Tracer: tracer,
|
|
PVF: pvfArchive,
|
|
DataTables: dataTables,
|
|
DataTableViews: dataTableViews,
|
|
Audit: auditLogger,
|
|
LogicLog: logicLogger,
|
|
BILog: biLogger,
|
|
EventLog: eventLog,
|
|
AdminCommands: adminCommands,
|
|
AccountCenter: accountCenter,
|
|
OnlinePush: onlinePush,
|
|
Reload: reloadManager,
|
|
HotUpdate: hotUpdateManager,
|
|
StateSync: stateSyncStore,
|
|
StorageObjects: storageObjects,
|
|
ServerGroup: serverGroupManager,
|
|
ServerGroupStore: serverGroupStore,
|
|
ServerGroupArchives: foundation.serverGroupArchives,
|
|
Workers: workers,
|
|
AdminMux: adminServer.Mux(),
|
|
AdminTimeouts: adminTimeouts,
|
|
AdminAuth: adminGuard.wrap,
|
|
AdminDangerous: adminGuard.wrapDangerous,
|
|
ConfigProvider: application.configSnapshot,
|
|
}
|
|
|
|
components := []Component{workers, schedulerSvc, rpcEndpoint, reloadManager}
|
|
if hotUpdateManager != nil {
|
|
components = append(components, hotUpdateManager)
|
|
}
|
|
if stateSyncSweeper := newStateSyncSweeper(stateSyncStore, time.Duration(cfg.StateSync.CleanupIntervalSeconds)*time.Second, logger); stateSyncSweeper != nil {
|
|
components = append(components, stateSyncSweeper)
|
|
}
|
|
if eventLogWorker != nil {
|
|
components = append(components, eventLogWorker)
|
|
}
|
|
extra, err := manifest.Build(env)
|
|
if err != nil {
|
|
_ = cleanupComponents(context.Background(), healthSvc, extra)
|
|
closeHotCtl(hotUpdateManager)
|
|
if closer, ok := presenceRuntime.(interface{ Close() error }); ok {
|
|
_ = closer.Close()
|
|
}
|
|
_ = lockManager.Close()
|
|
_ = cacheStore.Close()
|
|
_ = eventBus.Close()
|
|
_ = reg.Close()
|
|
if eventLog != nil {
|
|
_ = eventLog.Close()
|
|
}
|
|
_ = foundation.close(context.Background())
|
|
return nil, err
|
|
}
|
|
application.i18nCatalog = env.I18N
|
|
if cfg.DataTables.Enabled {
|
|
if _, err := dataTableViews.RefreshAll(context.Background(), dataTables); err != nil {
|
|
_ = cleanupComponents(context.Background(), healthSvc, extra)
|
|
closeHotCtl(hotUpdateManager)
|
|
if closer, ok := presenceRuntime.(interface{ Close() error }); ok {
|
|
_ = closer.Close()
|
|
}
|
|
_ = lockManager.Close()
|
|
_ = cacheStore.Close()
|
|
_ = eventBus.Close()
|
|
_ = reg.Close()
|
|
if eventLog != nil {
|
|
_ = eventLog.Close()
|
|
}
|
|
_ = foundation.close(context.Background())
|
|
return nil, fmt.Errorf("refresh data table views: %w", err)
|
|
}
|
|
}
|
|
components = append(components, extra...)
|
|
components = append(components, adminServer)
|
|
components = append(components, clusterCoord)
|
|
components = append(components, topologyManager)
|
|
|
|
application.components = components
|
|
application.constructed = extra
|
|
return application, nil
|
|
}
|