完善多赛道联调与全局产品架构

This commit is contained in:
2026-04-02 18:11:43 +08:00
parent 6964e26ec9
commit 0e28f70bad
45 changed files with 4819 additions and 282 deletions

View File

@@ -0,0 +1,189 @@
package service
import (
"crypto/rand"
"encoding/json"
"fmt"
"math/big"
"net/http"
"strings"
"cmr-backend/internal/apperr"
)
const (
AssignmentModeManual = "manual"
AssignmentModeRandom = "random"
AssignmentModeServerAssigned = "server-assigned"
)
type CourseVariantView struct {
ID string `json:"id"`
Name string `json:"name"`
Description *string `json:"description,omitempty"`
RouteCode *string `json:"routeCode,omitempty"`
Selectable bool `json:"selectable"`
}
type VariantBindingView struct {
ID string `json:"id"`
Name string `json:"name"`
RouteCode *string `json:"routeCode,omitempty"`
AssignmentMode string `json:"assignmentMode"`
}
type VariantPlan struct {
AssignmentMode *string
CourseVariants []CourseVariantView
}
func resolveVariantPlan(payloadJSON *string) VariantPlan {
if payloadJSON == nil || strings.TrimSpace(*payloadJSON) == "" {
return VariantPlan{}
}
var payload map[string]any
if err := json.Unmarshal([]byte(*payloadJSON), &payload); err != nil {
return VariantPlan{}
}
play, _ := payload["play"].(map[string]any)
if len(play) == 0 {
return VariantPlan{}
}
result := VariantPlan{}
if rawMode, ok := play["assignmentMode"].(string); ok {
if normalized := normalizeAssignmentMode(rawMode); normalized != nil {
result.AssignmentMode = normalized
}
}
rawVariants, _ := play["courseVariants"].([]any)
if len(rawVariants) == 0 {
return result
}
for _, raw := range rawVariants {
item, ok := raw.(map[string]any)
if !ok {
continue
}
id, _ := item["id"].(string)
name, _ := item["name"].(string)
id = strings.TrimSpace(id)
name = strings.TrimSpace(name)
if id == "" || name == "" {
continue
}
var description *string
if value, ok := item["description"].(string); ok && strings.TrimSpace(value) != "" {
trimmed := strings.TrimSpace(value)
description = &trimmed
}
var routeCode *string
if value, ok := item["routeCode"].(string); ok && strings.TrimSpace(value) != "" {
trimmed := strings.TrimSpace(value)
routeCode = &trimmed
}
selectable := true
if value, ok := item["selectable"].(bool); ok {
selectable = value
}
result.CourseVariants = append(result.CourseVariants, CourseVariantView{
ID: id,
Name: name,
Description: description,
RouteCode: routeCode,
Selectable: selectable,
})
}
return result
}
func resolveLaunchVariant(plan VariantPlan, requestedVariantID string) (*VariantBindingView, error) {
requestedVariantID = strings.TrimSpace(requestedVariantID)
if len(plan.CourseVariants) == 0 {
return nil, nil
}
mode := AssignmentModeManual
if plan.AssignmentMode != nil {
mode = *plan.AssignmentMode
}
if requestedVariantID != "" {
for _, item := range plan.CourseVariants {
if item.ID == requestedVariantID {
if !item.Selectable && mode == AssignmentModeManual {
return nil, apperr.New(http.StatusBadRequest, "variant_not_selectable", "requested variant is not selectable")
}
return &VariantBindingView{
ID: item.ID,
Name: item.Name,
RouteCode: item.RouteCode,
AssignmentMode: mode,
}, nil
}
}
return nil, apperr.New(http.StatusBadRequest, "variant_not_found", "requested variant does not exist")
}
selected, err := selectDefaultVariant(plan.CourseVariants, mode)
if err != nil {
return nil, err
}
return &VariantBindingView{
ID: selected.ID,
Name: selected.Name,
RouteCode: selected.RouteCode,
AssignmentMode: mode,
}, nil
}
func normalizeAssignmentMode(value string) *string {
switch strings.TrimSpace(value) {
case AssignmentModeManual:
mode := AssignmentModeManual
return &mode
case AssignmentModeRandom:
mode := AssignmentModeRandom
return &mode
case AssignmentModeServerAssigned:
mode := AssignmentModeServerAssigned
return &mode
default:
return nil
}
}
func selectDefaultVariant(items []CourseVariantView, mode string) (*CourseVariantView, error) {
candidates := make([]CourseVariantView, 0, len(items))
for _, item := range items {
if item.Selectable {
candidates = append(candidates, item)
}
}
if len(candidates) == 0 {
candidates = append(candidates, items...)
}
if len(candidates) == 0 {
return nil, apperr.New(http.StatusBadRequest, "variant_not_found", "course variants are empty")
}
switch mode {
case AssignmentModeRandom:
index, err := rand.Int(rand.Reader, big.NewInt(int64(len(candidates))))
if err != nil {
return nil, apperr.New(http.StatusInternalServerError, "variant_select_failed", fmt.Sprintf("failed to select random variant: %v", err))
}
selected := candidates[int(index.Int64())]
return &selected, nil
case AssignmentModeServerAssigned, AssignmentModeManual:
fallthrough
default:
selected := candidates[0]
return &selected, nil
}
}