Files
90CNServer/server/internal/platform/servergroup/merge_archive.go
T
hanfuit 645b64fe21 DNF90 服务端与启动器完整源码(含 GM 工具功能增强)
服务端(Go):
- GM 塞背包/带属性装备发放:在线角色推送 op14 增量物品行,背包实时刷新,无需回角色选择界面
- GM 改金币/改等级:去掉在线拒绝,落库后推送 op14 钱包/op37 等级状态,在线实时刷新
- 等级端点统一切换到新版 GMSetCharacterLevel(自带在线刷新)
- 无限/锁死疲劳模式 + 燃烧疲劳:每房间消耗 2 点、打怪与通关经验双倍
- 燃烧疲劳开关时给所有在线角色推送系统提示(op12 mode 0)
- 深渊史诗掉率:GM 全局倍率 + 城镇增益药剂窗口倍率,真实接入掉落结算

启动器(Python/JS):
- 标题栏自绘、窗口 1204x717、禁止拖拽改大小、仅最小化/关闭
- GM 工具:疲劳与燃烧、深渊史诗掉率、拍卖行上架(单一定价对齐)
- 拍卖行上架前端对齐服务端单一价格语义,修复 400 报错
2026-11-04 14:31:55 +08:00

335 lines
11 KiB
Go

package servergroup
import "time"
const (
// MergeWorkflowName 是合服运营工作流归档名。
MergeWorkflowName = "servergroup_merge"
)
const (
// MergeStageDryRun 表示合服工作流处于预演阶段。
MergeStageDryRun = "dry_run"
// MergeStageApply 表示合服工作流处于正式应用阶段。
MergeStageApply = "apply"
// MergeStageRollbackDry 表示合服工作流处于回滚预演阶段。
MergeStageRollbackDry = "rollback_dry_run"
// MergeStageRollback 表示合服工作流处于正式回滚阶段。
MergeStageRollback = "rollback"
)
// MergeArchiveOptions 描述一次合服运营归档需要汇总的输入。
type MergeArchiveOptions struct {
// Stage 是归档阶段,取值为 dry_run、apply、rollback_dry_run 或 rollback。
Stage string
// ArchiveID 是外部工作流或工单系统传入的归档 ID,空值会自动推导。
ArchiveID string
// WorkflowID 是 adminworkflow 记录 ID,便于从归档反查审批记录。
WorkflowID string
// ApprovalID 是审批单或工单 ID。
ApprovalID string
// IdempotencyKey 是执行侧幂等键。
IdempotencyKey string
// OperatorID 是触发本次归档的操作者。
OperatorID string
// Reason 是本次合服或回滚的原因。
Reason string
// Request 是合服请求,空值时会从迁移计划、执行结果或模块报告中推导。
Request MergeRequest
// GeneratedAt 是归档生成时间,空值时使用当前 UTC 时间。
GeneratedAt time.Time
// Migration 是 dry-run 迁移计划。
Migration *MigrationPlan
// Apply 是平台路由 apply 结果。
Apply *MergeApplyResult
// RollbackApply 是平台路由回滚预检或 apply 结果。
RollbackApply *RollbackApplyResult
// ModuleReports 是项目侧模块 GenDB、Merge、Rollback 阶段报告。
ModuleReports []MergeModuleRunReport
// Meta 是项目侧附加元数据。
Meta map[string]string
}
// MergeArchive 是合服运营工作流可落库、可审计、可导出的归档包。
type MergeArchive struct {
// ArchiveID 是归档主键,通常等于 adminworkflow ID、幂等键或合服请求 ID。
ArchiveID string `json:"archive_id"`
// Workflow 是运营工作流名,固定为 servergroup_merge。
Workflow string `json:"workflow"`
// Stage 是归档阶段。
Stage string `json:"stage"`
// WorkflowID 是 adminworkflow 记录 ID。
WorkflowID string `json:"workflow_id,omitempty"`
// ApprovalID 是审批单或工单 ID。
ApprovalID string `json:"approval_id,omitempty"`
// IdempotencyKey 是执行侧幂等键。
IdempotencyKey string `json:"idempotency_key,omitempty"`
// OperatorID 是操作者。
OperatorID string `json:"operator_id,omitempty"`
// Reason 是操作原因。
Reason string `json:"reason,omitempty"`
// Request 是合服请求快照。
Request MergeRequest `json:"request"`
// GeneratedAt 是归档生成时间,统一保存为 UTC。
GeneratedAt time.Time `json:"generated_at"`
// Ready 表示 dry-run 迁移计划是否允许进入执行窗口。
Ready bool `json:"ready"`
// OK 表示迁移计划、平台执行和模块报告都没有 blocker 或失败。
OK bool `json:"ok"`
// Applied 表示平台路由 apply 或 rollback apply 已真正执行。
Applied bool `json:"applied,omitempty"`
// RollbackPoint 是本次操作可用于恢复平台路由的回滚锚点。
RollbackPoint RollbackPoint `json:"rollback_point,omitempty"`
// Migration 是 dry-run 迁移计划快照。
Migration *MigrationPlan `json:"migration,omitempty"`
// Apply 是平台路由 apply 结果。
Apply *MergeApplyResult `json:"apply,omitempty"`
// RollbackApply 是平台路由回滚预检或 apply 结果。
RollbackApply *RollbackApplyResult `json:"rollback_apply,omitempty"`
// ModuleReports 是项目侧模块阶段报告。
ModuleReports []MergeModuleRunReport `json:"module_reports,omitempty"`
// Findings 汇总迁移计划和模块报告中的风险项。
Findings []ConflictFinding `json:"findings,omitempty"`
// Warnings 汇总 dry-run 警告。
Warnings []string `json:"warnings,omitempty"`
// Evidence 汇总模块报告中的证据引用。
Evidence []string `json:"evidence,omitempty"`
// Rollback 汇总模块报告中的业务回滚动作。
Rollback []string `json:"rollback,omitempty"`
// Meta 是项目侧附加元数据。
Meta map[string]string `json:"meta,omitempty"`
}
// NewMergeArchive 把合服计划、平台执行结果和模块报告聚合成运营归档包。
func NewMergeArchive(options MergeArchiveOptions) (MergeArchive, error) {
stage := normalizeID(options.Stage)
if stage == "" {
stage = MergeStageDryRun
}
if !validMergeStage(stage) {
return MergeArchive{}, ErrInvalidMigration
}
generatedAt := normalizeTime(options.GeneratedAt)
if generatedAt.IsZero() {
generatedAt = time.Now().UTC()
}
request := mergeArchiveRequest(options)
archive := MergeArchive{
ArchiveID: mergeArchiveID(options, request),
Workflow: MergeWorkflowName,
Stage: stage,
WorkflowID: firstNonEmpty(options.WorkflowID),
ApprovalID: firstNonEmpty(options.ApprovalID),
IdempotencyKey: firstNonEmpty(options.IdempotencyKey),
OperatorID: firstNonEmpty(options.OperatorID, request.OperatorID),
Reason: firstNonEmpty(options.Reason, request.Reason),
Request: request,
GeneratedAt: generatedAt,
Ready: true,
OK: true,
Meta: normalizeStringMap(options.Meta),
}
if options.Migration != nil {
migration := cloneMigrationPlan(*options.Migration)
archive.Migration = &migration
archive.Ready = migration.Ready
archive.Findings = append(archive.Findings, migration.Findings...)
archive.Warnings = append(archive.Warnings, migration.DryRun.Warnings...)
archive.RollbackPoint = cloneRollbackPoint(migration.RollbackPoint)
if !migration.Ready || hasBlockerFinding(migration.Findings) {
archive.OK = false
}
}
if options.Apply != nil {
apply := cloneMergeResult(*options.Apply)
archive.Apply = &apply
archive.Applied = apply.Applied
if archive.RollbackPoint.ID == "" {
archive.RollbackPoint = cloneRollbackPoint(apply.RollbackPoint)
}
if stage == MergeStageApply && !apply.Applied {
archive.OK = false
}
}
if options.RollbackApply != nil {
rollback := cloneRollbackApply(*options.RollbackApply)
archive.RollbackApply = &rollback
archive.Applied = rollback.Applied
if archive.RollbackPoint.ID == "" {
archive.RollbackPoint = cloneRollbackPoint(rollback.RollbackPoint)
}
if stage == MergeStageRollback && !rollback.Applied {
archive.OK = false
}
}
archive.ModuleReports = cloneMergeReports(options.ModuleReports)
for _, report := range archive.ModuleReports {
archive.Findings = append(archive.Findings, report.Findings...)
archive.Evidence = append(archive.Evidence, report.Evidence...)
archive.Rollback = append(archive.Rollback, report.Rollback...)
if !report.OK {
archive.OK = false
}
}
archive.Findings = cloneClashFindings(archive.Findings)
archive.Warnings = cloneMergeStrings(archive.Warnings)
archive.Evidence = cloneMergeStrings(archive.Evidence)
archive.Rollback = cloneMergeStrings(archive.Rollback)
if hasBlockerFinding(archive.Findings) {
archive.OK = false
}
if archive.ArchiveID == "" {
return MergeArchive{}, ErrInvalidMigration
}
return archive, nil
}
func validMergeStage(stage string) bool {
switch stage {
case MergeStageDryRun, MergeStageApply, MergeStageRollbackDry, MergeStageRollback:
return true
default:
return false
}
}
func mergeArchiveRequest(options MergeArchiveOptions) MergeRequest {
request := normMergeReq(options.Request)
if request.ID != "" {
return request
}
if options.Migration != nil {
request = normMergeReq(options.Migration.Request)
if request.ID != "" {
return request
}
}
if options.Apply != nil {
request = normMergeReq(options.Apply.Request)
if request.ID != "" {
return request
}
}
for _, report := range options.ModuleReports {
request = normMergeReq(report.Request)
if request.ID != "" {
return request
}
}
return request
}
func mergeArchiveID(options MergeArchiveOptions, request MergeRequest) string {
for _, value := range []string{
options.ArchiveID,
options.WorkflowID,
options.IdempotencyKey,
request.ID,
} {
if value = normalizeID(value); value != "" {
return value
}
}
if options.RollbackApply != nil {
return normalizeID(options.RollbackApply.RollbackPoint.ID)
}
return ""
}
func cloneMergeArchive(archive MergeArchive) MergeArchive {
archive.Request = normMergeReq(archive.Request)
archive.RollbackPoint = cloneRollbackPoint(archive.RollbackPoint)
if archive.Migration != nil {
migration := cloneMigrationPlan(*archive.Migration)
archive.Migration = &migration
}
if archive.Apply != nil {
apply := cloneMergeResult(*archive.Apply)
archive.Apply = &apply
}
if archive.RollbackApply != nil {
rollback := cloneRollbackApply(*archive.RollbackApply)
archive.RollbackApply = &rollback
}
archive.ModuleReports = cloneMergeReports(archive.ModuleReports)
archive.Findings = cloneClashFindings(archive.Findings)
archive.Warnings = cloneMergeStrings(archive.Warnings)
archive.Evidence = cloneMergeStrings(archive.Evidence)
archive.Rollback = cloneMergeStrings(archive.Rollback)
archive.Meta = cloneStringMap(archive.Meta)
return archive
}
func cloneMigrationPlan(plan MigrationPlan) MigrationPlan {
return MigrationPlan{
Request: normMergeReq(plan.Request),
DryRun: cloneMergeDryRun(plan.DryRun),
Steps: cloneMigrationSteps(plan.Steps),
Findings: cloneClashFindings(plan.Findings),
Ready: plan.Ready,
Rollback: clonePlan(plan.Rollback),
RollbackPoint: cloneRollbackPoint(plan.RollbackPoint),
}
}
func cloneMergeResult(result MergeApplyResult) MergeApplyResult {
return MergeApplyResult{
Request: normMergeReq(result.Request),
Applied: result.Applied,
DryRun: cloneMergeDryRun(result.DryRun),
Change: result.Change,
Execution: cloneMergePlan(result.Execution),
Rollback: clonePlan(result.Rollback),
RollbackPoint: cloneRollbackPoint(result.RollbackPoint),
Warnings: cloneMergeStrings(result.Warnings),
}
}
func cloneMergePlan(plan MergeExecutionPlan) MergeExecutionPlan {
plan.Shards = append([]string(nil), plan.Shards...)
if len(plan.Steps) == 0 {
return plan
}
steps := make([]MergeExecutionStep, len(plan.Steps))
for i, step := range plan.Steps {
step.Checks = append([]string(nil), step.Checks...)
steps[i] = step
}
plan.Steps = steps
return plan
}
func cloneRollbackApply(result RollbackApplyResult) RollbackApplyResult {
result.Previous = clonePlan(result.Previous)
result.Restored = clonePlan(result.Restored)
result.RollbackPoint = cloneRollbackPoint(result.RollbackPoint)
result.Request = cloneRollbackReq(result.Request)
return result
}
func cloneRollbackReq(request RollbackApplyRequest) RollbackApplyRequest {
request.RollbackPoint = cloneRollbackPoint(request.RollbackPoint)
return request
}
func cloneMergeReports(reports []MergeModuleRunReport) []MergeModuleRunReport {
if len(reports) == 0 {
return nil
}
out := make([]MergeModuleRunReport, len(reports))
for i, report := range reports {
out[i] = MergeModuleRunReport{
Phase: normalizeID(report.Phase),
Request: normMergeReq(report.Request),
GeneratedAt: normalizeTime(report.GeneratedAt),
OK: report.OK,
Results: cloneMergeResults(report.Results),
Findings: cloneClashFindings(report.Findings),
Evidence: cloneMergeStrings(report.Evidence),
Rollback: cloneMergeStrings(report.Rollback),
Meta: cloneStringMap(report.Meta),
}
}
return out
}