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
+224 -32
View File
@@ -12,17 +12,19 @@ import (
)
type coreHandlers struct {
core service.Core
core service.Core
enforceAuthorization bool
}
func newCoreHandlers(core service.Core) *coreHandlers {
return &coreHandlers{core: core}
func newCoreHandlers(core service.Core, enforceAuthorization bool) *coreHandlers {
return &coreHandlers{core: core, enforceAuthorization: enforceAuthorization}
}
func (h *coreHandlers) register(mux *http.ServeMux) {
mux.HandleFunc("/api/v1/auth/register", h.authRegister)
mux.HandleFunc("/api/v1/auth/login", h.authLogin)
mux.HandleFunc("/api/v1/auth/logout", h.authLogout)
mux.HandleFunc("/api/v1/auth/rotate", h.authRotate)
mux.HandleFunc("/api/v1/users/current", h.currentUser)
mux.HandleFunc("/api/v1/users/current/profile", h.currentUserProfile)
mux.HandleFunc("/api/v1/users/current/theme", h.currentUserTheme)
@@ -45,11 +47,18 @@ func (h *coreHandlers) register(mux *http.ServeMux) {
mux.HandleFunc("/api/v1/game-plugins/{id}", h.gamePluginDetail)
mux.HandleFunc("/api/v1/metrics/platform", h.platformMetrics)
mux.HandleFunc("/api/v1/metrics/server-instances", h.serverInstanceMetrics)
mux.HandleFunc("/api/v1/metrics/server-instances/history", h.metricHistory)
mux.HandleFunc("/api/v1/run/metrics/batches", h.requireRunSignature(h.runMetricBatchIngest))
mux.HandleFunc("/api/v1/backups", h.backups)
mux.HandleFunc("/api/v1/backups/{id}", h.backupDetail)
mux.HandleFunc("/api/v1/server-instances", h.serverInstances)
mux.HandleFunc("/api/v1/server-instances/workflows/create", h.serverInstanceCreateWorkflow)
mux.HandleFunc("/api/v1/server-instances/{id}/start", h.serverInstanceStart)
mux.HandleFunc("/api/v1/server-instances/{id}/stop", h.serverInstanceStop)
mux.HandleFunc("/api/v1/server-instances/{id}/process/status", h.serverInstanceProcessStatus)
mux.HandleFunc("/api/v1/server-instances/{id}/runtime/actions", h.serverRuntimeActions)
mux.HandleFunc("/api/v1/server-instances/{id}/runtime-binding", h.serverRuntimeBinding)
mux.HandleFunc("/api/v1/server-instances/{id}/remote-adapters", h.remoteAdapters)
mux.HandleFunc("/api/v1/server-instances/{id}/run/generate", h.serverRunGenerate)
mux.HandleFunc("/api/v1/server-instances/{id}/run/download", h.serverRunDownload)
mux.HandleFunc("/api/v1/server-instances/{id}/run/key/reset", h.serverRunKeyReset)
@@ -57,8 +66,17 @@ func (h *coreHandlers) register(mux *http.ServeMux) {
mux.HandleFunc("/api/v1/server-instances/{id}/client-managers/generate", h.serverClientManagerGenerate)
mux.HandleFunc("/api/v1/server-instances/{id}/client-managers/download", h.serverClientManagerDownload)
mux.HandleFunc("/api/v1/server-instances/{id}/client-managers/key/reset", h.serverClientManagerKeyReset)
mux.HandleFunc("/api/v1/server-instances/{id}/client-managers", h.serverClientManagerLifecycles)
mux.HandleFunc("/api/v1/server-instances/{id}/client-managers/{profileKey}", h.serverClientManagerLifecycleDetail)
mux.HandleFunc("/api/v1/server-instances/{id}/client-managers/deploy", h.serverClientManagerDeploy)
mux.HandleFunc("/api/v1/server-instances/{id}/client-managers/control", h.serverClientManagerControl)
mux.HandleFunc("/api/v1/server-instances/{id}/client-managers/update", h.serverClientManagerUpdateLifecycle)
mux.HandleFunc("/api/v1/server-instances/{id}/client-managers/retry", h.serverClientManagerRetry)
mux.HandleFunc("/api/v1/server-instances/{id}/client-managers/revoke-session", h.serverClientManagerRevokeSession)
mux.HandleFunc("/api/v1/server-instances/{id}/client-managers/uninstall", h.serverClientManagerUninstall)
mux.HandleFunc("/api/v1/server-instances/{id}/dependencies/check", h.serverDependenciesCheck)
mux.HandleFunc("/api/v1/server-instances/{id}/dependencies/install", h.serverDependenciesInstall)
mux.HandleFunc("/api/v1/server-instances/{id}/dependencies", h.serverDependencies)
mux.HandleFunc("/api/v1/server-instances/{id}/logs/live", h.serverLiveLogs)
mux.HandleFunc("/api/v1/server-instances/{id}/logs/backfill", h.serverLogsBackfill)
mux.HandleFunc("/api/v1/server-instances/{id}/config/diff", h.serverInstanceConfigDiff)
@@ -69,19 +87,25 @@ func (h *coreHandlers) register(mux *http.ServeMux) {
mux.HandleFunc("/api/v1/server-instances/{id}/administrators/{userId}", h.serverAdministratorDetail)
mux.HandleFunc("/api/v1/server-instances/{id}", h.serverInstanceDetail)
mux.HandleFunc("/api/v1/run/control/hello", h.runControlHello)
mux.HandleFunc("/api/v1/run/control/heartbeat", h.runControlHeartbeat)
mux.HandleFunc("/api/v1/run/jobs/claim", h.runJobClaim)
mux.HandleFunc("/api/v1/run/jobs/ack", h.runJobAck)
mux.HandleFunc("/api/v1/run/jobs/progress", h.runJobProgress)
mux.HandleFunc("/api/v1/run/jobs/result", h.runJobResult)
mux.HandleFunc("/api/v1/run/jobs/build-input", h.runJobBuildInput)
mux.HandleFunc("/api/v1/run/jobs/cancel", h.runJobCancelPoll)
mux.HandleFunc("/api/v1/run/jobs/reconcile", h.runJobReconcile)
mux.HandleFunc("/api/v1/run/logs/batches", h.runLogBatchIngest)
mux.HandleFunc("/api/v1/run/artifacts/open", h.runArtifactOpen)
mux.HandleFunc("/api/v1/run/artifacts/chunks", h.runArtifactChunkUpload)
mux.HandleFunc("/api/v1/run/artifacts/status", h.runArtifactStatus)
mux.HandleFunc("/api/v1/run/artifacts/complete", h.runArtifactComplete)
mux.HandleFunc("/api/v1/run/control/heartbeat", h.requireRunSignature(h.runControlHeartbeat))
mux.HandleFunc("/api/v1/run/jobs/claim", h.requireRunSignature(h.runJobClaim))
mux.HandleFunc("/api/v1/run/jobs/ack", h.requireRunSignature(h.runJobAck))
mux.HandleFunc("/api/v1/run/jobs/progress", h.requireRunSignature(h.runJobProgress))
mux.HandleFunc("/api/v1/run/jobs/result", h.requireRunSignature(h.runJobResult))
mux.HandleFunc("/api/v1/run/jobs/build-input", h.requireRunSignature(h.runJobBuildInput))
mux.HandleFunc("/api/v1/run/jobs/dependency-input", h.requireRunSignature(h.runJobDependencyInput))
mux.HandleFunc("/api/v1/run/jobs/update-input", h.requireRunSignature(h.runJobUpdateInput))
mux.HandleFunc("/api/v1/run/jobs/update-chunk", h.requireRunSignature(h.runJobUpdateChunk))
mux.HandleFunc("/api/v1/run/jobs/update-health", h.requireRunSignature(h.runJobUpdateHealth))
mux.HandleFunc("/api/v1/run/jobs/client-manager-input", h.requireRunSignature(h.runClientManagerLifecycleInput))
mux.HandleFunc("/api/v1/run/jobs/client-manager-chunk", h.requireRunSignature(h.runClientManagerLifecycleChunk))
mux.HandleFunc("/api/v1/run/jobs/cancel", h.requireRunSignature(h.runJobCancelPoll))
mux.HandleFunc("/api/v1/run/jobs/reconcile", h.requireRunSignature(h.runJobReconcile))
mux.HandleFunc("/api/v1/run/logs/batches", h.requireRunSignature(h.runLogBatchIngest))
mux.HandleFunc("/api/v1/run/artifacts/open", h.requireRunSignature(h.runArtifactOpen))
mux.HandleFunc("/api/v1/run/artifacts/chunks", h.requireRunSignature(h.runArtifactChunkUpload))
mux.HandleFunc("/api/v1/run/artifacts/status", h.requireRunSignature(h.runArtifactStatus))
mux.HandleFunc("/api/v1/run/artifacts/complete", h.requireRunSignature(h.runArtifactComplete))
mux.HandleFunc("/api/v1/run/endpoints", h.runEndpoints)
mux.HandleFunc("/api/v1/run/endpoints/{id}", h.runEndpointDetail)
mux.HandleFunc("/api/v1/jobs", h.jobs)
@@ -97,6 +121,8 @@ func (h *coreHandlers) register(mux *http.ServeMux) {
mux.HandleFunc("/api/v1/log-streams/{id}", h.logStreamDetail)
mux.HandleFunc("/api/v1/audit-events", h.auditEvents)
mux.HandleFunc("/api/v1/audit-events/{id}", h.auditEventDetail)
mux.HandleFunc("/api/v1/client-managers/register", h.clientManagerRegister)
mux.HandleFunc("/api/v1/client-managers/heartbeat", h.clientManagerHeartbeat)
}
// authRegister godoc
@@ -126,7 +152,7 @@ func (h *coreHandlers) authRegister(w http.ResponseWriter, r *http.Request) {
writeServiceError(w, err)
return
}
writeJSON(w, http.StatusOK, dto.AuthSessionFromDomain(session))
h.writeAuthSession(w, r, session)
}
// authLogin godoc
@@ -157,7 +183,7 @@ func (h *coreHandlers) authLogin(w http.ResponseWriter, r *http.Request) {
writeServiceError(w, err)
return
}
writeJSON(w, http.StatusOK, dto.AuthSessionFromDomain(session))
h.writeAuthSession(w, r, session)
}
// authLogout godoc
@@ -178,9 +204,32 @@ func (h *coreHandlers) authLogout(w http.ResponseWriter, r *http.Request) {
writeServiceError(w, err)
return
}
h.clearSessionCookie(w, r)
w.WriteHeader(http.StatusNoContent)
}
// authRotate godoc
// @Summary Rotate the active platform session
// @Description Revokes the current bearer token and returns a new bounded session token.
// @Tags auth
// @Produce json
// @Success 200 {object} dto.AuthSessionResponse
// @Failure 401 {object} dto.ErrorResponse
// @Failure 405 {object} dto.ErrorResponse
// @Router /api/v1/auth/rotate [post]
func (h *coreHandlers) authRotate(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
writeMethodNotAllowed(w, http.MethodPost)
return
}
session, err := h.core.RotateUserSession(bearerToken(r))
if err != nil {
writeServiceError(w, err)
return
}
h.writeAuthSession(w, r, session)
}
// currentUser godoc
// @Summary Get current platform user
// @Description Returns the authenticated current user's bounded identity, roles, profile, and theme preference.
@@ -274,10 +323,14 @@ func (h *coreHandlers) currentUserTheme(w http.ResponseWriter, r *http.Request)
func bearerToken(r *http.Request) string {
const prefix = "Bearer "
header := r.Header.Get("Authorization")
if len(header) < len(prefix) || header[:len(prefix)] != prefix {
if len(header) >= len(prefix) && header[:len(prefix)] == prefix {
return header[len(prefix):]
}
cookie, err := r.Cookie(platformSessionCookieName)
if err != nil {
return ""
}
return header[len(prefix):]
return cookie.Value
}
// pluginBridgeAuthorize godoc
@@ -302,7 +355,12 @@ func (h *coreHandlers) pluginBridgeAuthorize(w http.ResponseWriter, r *http.Requ
writeDecodeError(w, err)
return
}
result, err := h.core.AuthorizePluginBridgeAction(request.ToDomain())
var result domain.PluginBridgeAuthorization
if h.enforceAuthorization {
result, err = h.core.AuthorizePluginBridgeActionForSession(bearerToken(r), request.ToDomain())
} else {
result, err = h.core.AuthorizePluginBridgeAction(request.ToDomain())
}
if err != nil {
writeServiceError(w, err)
return
@@ -530,7 +588,11 @@ func (h *coreHandlers) aiProviderDetail(w http.ResponseWriter, r *http.Request)
writeDecodeError(w, err)
return
}
provider, err := h.core.UpdateAIProvider(r.PathValue("id"), request.ToDomain(r.PathValue("id"), existing.Status))
update := request.ToDomain(r.PathValue("id"), existing.Status)
if strings.TrimSpace(update.APIKeyRef) == "" {
update.APIKeyRef = existing.APIKeyRef
}
provider, err := h.core.UpdateAIProvider(r.PathValue("id"), update)
if err != nil {
writeServiceError(w, err)
return
@@ -1418,6 +1480,93 @@ func (h *coreHandlers) runJobBuildInput(w http.ResponseWriter, r *http.Request)
writeJSON(w, http.StatusOK, dto.DistributionBuildInputFromDomain(result))
}
// runJobDependencyInput returns declared and resolved dependency input only to the active fenced Run attempt.
func (h *coreHandlers) runJobDependencyInput(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
writeMethodNotAllowed(w, http.MethodPost)
return
}
request, err := decodeJSON[dto.DependencyExecutionInputRequest](r)
if err != nil {
writeDecodeError(w, err)
return
}
result, err := h.core.GetDependencyExecutionInput(request.ToDomain())
if err != nil {
writeServiceError(w, err)
return
}
writeJSON(w, http.StatusOK, dto.DependencyExecutionInputFromDomain(result))
}
// runJobUpdateInput returns update metadata only to the active fenced Run attempt.
func (h *coreHandlers) runJobUpdateInput(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
writeMethodNotAllowed(w, http.MethodPost)
return
}
request, err := decodeJSON[dto.RunUpdateInputRequest](r)
if err != nil {
writeDecodeError(w, err)
return
}
result, err := h.core.GetRunUpdateInput(request.ToDomain())
if err != nil {
writeServiceError(w, err)
return
}
writeJSON(w, http.StatusOK, dto.RunUpdateInputFromDomain(result))
}
// runJobUpdateChunk serves one bounded update range only to the active fenced Run attempt.
func (h *coreHandlers) runJobUpdateChunk(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
writeMethodNotAllowed(w, http.MethodPost)
return
}
request, err := decodeJSON[dto.RunUpdateChunkRequest](r)
if err != nil {
writeDecodeError(w, err)
return
}
result, err := h.core.ReadRunUpdateChunk(request.ToDomain())
if err != nil {
writeServiceError(w, err)
return
}
writeJSON(w, http.StatusOK, dto.RunUpdateChunkFromDomain(result))
}
// runJobUpdateHealth godoc
// @Summary Confirm a reconciled Run self-update outcome
// @Description Accepts a signed current-session health or rollback report fenced to the terminal update job attempt.
// @Tags run-jobs
// @Accept json
// @Produce json
// @Param body body dto.RunUpdateHealthRequest true "Run update health report"
// @Success 200 {object} dto.RunUpdateHealthResponse
// @Failure 400 {object} dto.ErrorResponse
// @Failure 401 {object} dto.ErrorResponse
// @Failure 405 {object} dto.ErrorResponse
// @Router /api/v1/run/jobs/update-health [post]
func (h *coreHandlers) runJobUpdateHealth(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
writeMethodNotAllowed(w, http.MethodPost)
return
}
request, err := decodeJSON[dto.RunUpdateHealthRequest](r)
if err != nil {
writeDecodeError(w, err)
return
}
result, err := h.core.ReportRunUpdateHealth(request.ToDomain())
if err != nil {
writeServiceError(w, err)
return
}
writeJSON(w, http.StatusOK, dto.RunUpdateHealthFromDomain(result))
}
// runJobCancelPoll godoc
// @Summary Poll run job cancellation
// @Description Lets a registered run endpoint poll for cancellation requests on active leased jobs.
@@ -1704,11 +1853,18 @@ func (h *coreHandlers) runEndpointDetail(w http.ResponseWriter, r *http.Request)
func (h *coreHandlers) jobs(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet:
jobs, err := h.core.ListJobs(domain.JobFilter{
filter := domain.JobFilter{
ServerInstanceID: r.URL.Query().Get("serverInstanceId"),
RunEndpointID: r.URL.Query().Get("runEndpointId"),
State: domain.JobState(r.URL.Query().Get("state")),
})
}
var jobs []domain.Job
var err error
if h.enforceAuthorization {
jobs, err = h.core.ListJobsForSession(bearerToken(r), filter)
} else {
jobs, err = h.core.ListJobs(filter)
}
if err != nil {
writeServiceError(w, err)
return
@@ -1755,7 +1911,12 @@ func (h *coreHandlers) jobCancel(w http.ResponseWriter, r *http.Request) {
return
}
request.JobID = r.PathValue("id")
result, err := h.core.RequestRunJobCancel(request.ToDomain())
var result domain.RunJobCancelRequestResult
if h.enforceAuthorization {
result, err = h.core.RequestRunJobCancelForSession(bearerToken(r), request.ToDomain())
} else {
result, err = h.core.RequestRunJobCancel(request.ToDomain())
}
if err != nil {
writeServiceError(w, err)
return
@@ -1778,7 +1939,13 @@ func (h *coreHandlers) jobDetail(w http.ResponseWriter, r *http.Request) {
writeMethodNotAllowed(w, http.MethodGet)
return
}
job, err := h.core.GetJob(r.PathValue("id"))
var job domain.Job
var err error
if h.enforceAuthorization {
job, err = h.core.GetJobForSession(bearerToken(r), r.PathValue("id"))
} else {
job, err = h.core.GetJob(r.PathValue("id"))
}
if err != nil {
writeServiceError(w, err)
return
@@ -1802,11 +1969,18 @@ func (h *coreHandlers) jobDetail(w http.ResponseWriter, r *http.Request) {
func (h *coreHandlers) artifacts(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet:
artifacts, err := h.core.ListArtifacts(domain.ArtifactFilter{
filter := domain.ArtifactFilter{
OwnerKind: domain.ArtifactOwnerKind(r.URL.Query().Get("ownerKind")),
OwnerID: r.URL.Query().Get("ownerId"),
State: domain.ArtifactState(r.URL.Query().Get("state")),
})
}
var artifacts []domain.Artifact
var err error
if h.enforceAuthorization {
artifacts, err = h.core.ListArtifactsForSession(bearerToken(r), filter)
} else {
artifacts, err = h.core.ListArtifacts(filter)
}
if err != nil {
writeServiceError(w, err)
return
@@ -1995,10 +2169,17 @@ func parseByteRange(header string) (int64, int, bool) {
func (h *coreHandlers) logStreams(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet:
streams, err := h.core.ListLogStreams(domain.LogStreamFilter{
filter := domain.LogStreamFilter{
ServerInstanceID: r.URL.Query().Get("serverInstanceId"),
StreamKey: r.URL.Query().Get("streamKey"),
})
}
var streams []domain.LogStream
var err error
if h.enforceAuthorization {
streams, err = h.core.ListLogStreamsForSession(bearerToken(r), filter)
} else {
streams, err = h.core.ListLogStreams(filter)
}
if err != nil {
writeServiceError(w, err)
return
@@ -2043,7 +2224,12 @@ func (h *coreHandlers) logStreamQuery(w http.ResponseWriter, r *http.Request) {
writeDecodeError(w, err)
return
}
result, err := h.core.QueryLogStream(request.ToDomain())
var result domain.LogStreamCursorResult
if h.enforceAuthorization {
result, err = h.core.QueryLogStreamForSession(bearerToken(r), request.ToDomain())
} else {
result, err = h.core.QueryLogStream(request.ToDomain())
}
if err != nil {
writeServiceError(w, err)
return
@@ -2066,7 +2252,13 @@ func (h *coreHandlers) logStreamDetail(w http.ResponseWriter, r *http.Request) {
writeMethodNotAllowed(w, http.MethodGet)
return
}
stream, err := h.core.GetLogStream(r.PathValue("id"))
var stream domain.LogStream
var err error
if h.enforceAuthorization {
stream, err = h.core.GetLogStreamForSession(bearerToken(r), r.PathValue("id"))
} else {
stream, err = h.core.GetLogStream(r.PathValue("id"))
}
if err != nil {
writeServiceError(w, err)
return