feat: 完整游戏运维功能

This commit is contained in:
npc0-hue
2026-07-18 09:04:01 +08:00
parent f3b14b7945
commit 48b8ad8d6c
187 changed files with 16607 additions and 1140 deletions
+73
View File
@@ -36,9 +36,13 @@ func (svc *CoreService) CreateServerInstanceWorkflow(create domain.ServerLifecyc
OwnerUserID: create.OwnerUserID,
State: domain.ServerInstanceStateInstalling,
ConfigVersion: 1,
ConfigKey: "server.properties",
CreatedAt: stamp,
UpdatedAt: stamp,
}
instance.ConfigContent = buildLogicalServerConfig(instance)
instance.ConfigChecksum = validator.BytesChecksum([]byte(instance.ConfigContent))
instance.ConfigUpdatedAt = stamp
if err := validator.ValidateServerInstance(instance); err != nil {
return domain.ServerLifecycleResult{}, err
}
@@ -51,9 +55,16 @@ func (svc *CoreService) CreateServerInstanceWorkflow(create domain.ServerLifecyc
if err := svc.validateLifecycleIdempotency(instance.RunEndpointID, create.IdempotencyKey, instance.ID, domain.LifecycleCapabilityForAction(domain.ServerLifecycleActionCreate)); err != nil {
return domain.ServerLifecycleResult{}, err
}
binding, err := svc.buildRuntimeBinding(instance, plugin, domain.RuntimeBindingUpdate{ProfileKey: create.ProfileKey, Bindings: create.Bindings}, true)
if err != nil {
return domain.ServerLifecycleResult{}, err
}
if err := svc.store.ServerInstances().Create(instance); err != nil {
return domain.ServerLifecycleResult{}, err
}
if err := svc.store.RuntimeBindings().Create(binding); err != nil {
return domain.ServerLifecycleResult{}, err
}
job, err := svc.dispatchLifecycleJob(instance, domain.ServerLifecycleActionCreate, create.IdempotencyKey)
if err != nil {
@@ -108,6 +119,18 @@ func (svc *CoreService) StopServerInstanceForSession(sessionID string, command d
return svc.StopServerInstance(command)
}
func (svc *CoreService) QueryServerInstanceProcessForSession(sessionID string, command domain.ServerLifecycleCommand) (domain.ServerLifecycleResult, error) {
if err := svc.authorizeServerLifecycle(sessionID, command.ServerInstanceID); err != nil {
return domain.ServerLifecycleResult{}, err
}
return svc.dispatchExistingServerLifecycle(command, domain.ServerLifecycleActionStatus, []domain.ServerInstanceState{
domain.ServerInstanceStateReady,
domain.ServerInstanceStateStopped,
domain.ServerInstanceStateRunning,
domain.ServerInstanceStateFailed,
})
}
func (svc *CoreService) dispatchExistingServerLifecycle(command domain.ServerLifecycleCommand, action domain.ServerLifecycleAction, allowedStates []domain.ServerInstanceState) (domain.ServerLifecycleResult, error) {
command = domain.CopyServerLifecycleCommand(command)
if err := validator.ValidateServerLifecycleCommand(command); err != nil {
@@ -138,6 +161,9 @@ func (svc *CoreService) dispatchExistingServerLifecycle(command domain.ServerLif
if err := validator.ValidateServerInstanceDependencies(instance, plugin, endpoint); err != nil {
return domain.ServerLifecycleResult{}, err
}
if err := svc.requireCompleteRuntimeBindings(instance.OwnerUserID, instance.ID, "server.lifecycle."+string(action)+".denied"); err != nil {
return domain.ServerLifecycleResult{}, err
}
if err := validateRunnableEndpoint(endpoint, domain.LifecycleCapabilityForAction(action)); err != nil {
return domain.ServerLifecycleResult{}, err
}
@@ -168,12 +194,33 @@ func (svc *CoreService) lifecycleDependencies(pluginID string, runEndpointID str
func (svc *CoreService) dispatchLifecycleJob(instance domain.ServerInstance, action domain.ServerLifecycleAction, idempotencyKey string) (domain.Job, error) {
capability := domain.LifecycleCapabilityForAction(action)
binding, err := svc.runtimeBindingForServer(instance.ID)
if err != nil {
return domain.Job{}, err
}
plugin, err := svc.store.GamePlugins().Get(instance.PluginID)
if err != nil {
return domain.Job{}, err
}
actionRef := binding.ProfileKey
if profile, ok := runtimeLifecycleProfileForKey(plugin.RuntimeProfiles, binding.ProfileKey); ok {
if ref := runtimeProfileActionRef(profile.ActionRefs, action); ref != "" {
actionRef = ref
}
} else if ref := lifecycleActionRef(plugin, action); ref != "" {
actionRef = ref
}
if strings.TrimSpace(actionRef) == "" {
return domain.Job{}, validationError(fmt.Sprintf("plugin %s lifecycle action is required", action))
}
job, err := svc.CreateJob(domain.Job{
ID: lifecycleJobID(instance.ID, action, idempotencyKey),
ServerInstanceID: instance.ID,
RunEndpointID: instance.RunEndpointID,
Capability: capability,
TargetKey: actionRef,
IdempotencyKey: idempotencyKey,
ExecutionInput: domain.JobExecutionInput{WorkspaceScope: binding.ProfileKey},
})
if err != nil {
return domain.Job{}, err
@@ -184,6 +231,30 @@ func (svc *CoreService) dispatchLifecycleJob(instance domain.ServerInstance, act
return job, nil
}
func runtimeProfileActionRef(actions domain.PluginLifecycleActions, action domain.ServerLifecycleAction) string {
switch action {
case domain.ServerLifecycleActionCreate:
return actions.Install
case domain.ServerLifecycleActionStart:
return actions.Start
case domain.ServerLifecycleActionStop:
return actions.Stop
case domain.ServerLifecycleActionStatus:
return actions.Status
default:
return ""
}
}
func runtimeLifecycleProfileForKey(profiles domain.GamePluginRuntimeProfiles, key string) (domain.RuntimeLifecycleProfile, bool) {
for _, profile := range profiles.LifecycleProfiles {
if profile.Key == key {
return profile, true
}
}
return domain.RuntimeLifecycleProfile{}, false
}
func (svc *CoreService) validateLifecycleIdempotency(runEndpointID string, idempotencyKey string, serverInstanceID string, capability string) error {
existing, err := svc.store.Jobs().GetByIdempotency(runEndpointID, idempotencyKey)
if errors.Is(err, repo.ErrNotFound) {
@@ -213,6 +284,8 @@ func lifecycleActionRef(plugin domain.GamePlugin, action domain.ServerLifecycleA
return plugin.LifecycleActions.Start
case domain.ServerLifecycleActionStop:
return plugin.LifecycleActions.Stop
case domain.ServerLifecycleActionStatus:
return plugin.LifecycleActions.Status
default:
return ""
}