From 2250f38db8ff69eba4bd8b39bd0e0125fc5bdce5 Mon Sep 17 00:00:00 2001 From: dnj Date: Wed, 10 Sep 2025 16:32:49 +0800 Subject: [PATCH 1/3] =?UTF-8?q?feat:=20=E5=88=9D=E5=A7=8B=E5=8C=96?= =?UTF-8?q?=E5=8F=91=E5=B8=83=E7=B3=BB=E7=BB=9F=E5=9F=BA=E7=A1=80=E6=A8=A1?= =?UTF-8?q?=E5=9D=97=E5=92=8C=E6=A8=A1=E5=9E=8B=E5=AE=9A=E4=B9=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 添加发布系统核心模块,包括: - 服务信息仓储接口定义 - 发布任务仓储接口定义 - 部署任务和服务信息模型 - 外部系统适配器接口 --- release-system/go.mod | 3 + .../internal/model/adapter/external.go | 120 ++++++++++++++++++ .../internal/model/deploy_task/deployment.go | 69 ++++++++++ .../internal/model/service_info/service.go | 111 ++++++++++++++++ .../repository/deployment_repository.go | 39 ++++++ .../internal/repository/service_repository.go | 22 ++++ release-system/internal/service/.gitkeep | 0 7 files changed, 364 insertions(+) create mode 100644 release-system/go.mod create mode 100644 release-system/internal/model/adapter/external.go create mode 100644 release-system/internal/model/deploy_task/deployment.go create mode 100644 release-system/internal/model/service_info/service.go create mode 100644 release-system/internal/repository/deployment_repository.go create mode 100644 release-system/internal/repository/service_repository.go create mode 100644 release-system/internal/service/.gitkeep diff --git a/release-system/go.mod b/release-system/go.mod new file mode 100644 index 0000000..77ab30e --- /dev/null +++ b/release-system/go.mod @@ -0,0 +1,3 @@ +module release-system + +go 1.24 diff --git a/release-system/internal/model/adapter/external.go b/release-system/internal/model/adapter/external.go new file mode 100644 index 0000000..c06c16d --- /dev/null +++ b/release-system/internal/model/adapter/external.go @@ -0,0 +1,120 @@ +package adapter + +import "time" + +// ExternalDeploySystem 公司发布系统接口定义 +type ExternalDeploySystem interface { + // CreateDeployment 调用公司发布系统创建发布任务 + CreateDeployment(req *ExternalDeployRequest) (*ExternalDeployResponse, error) + + // PauseDeployment 暂停发布任务 + PauseDeployment(deployID string) error + + // ContinueDeployment 继续发布任务 + ContinueDeployment(deployID string) error + + // RollbackDeployment 回滚发布任务 + RollbackDeployment(deployID string) error + + // GetDeploymentStatus 获取发布状态 + GetDeploymentStatus(deployID string) (*ExternalDeployStatus, error) +} + +// ExternalAlertSystem 公司告警系统接口定义 +type ExternalAlertSystem interface { + // SendAlert 发送告警 + SendAlert(alert *AlertRequest) error + + // GetAlerts 获取告警列表 + GetAlerts(query *AlertQuery) (*AlertResponse, error) +} + +// ExternalDeployRequest 公司发布系统请求 +type ExternalDeployRequest struct { + Service string `json:"service"` + Version string `json:"version"` + ScheduleTime *time.Time `json:"scheduleTime,omitempty"` + Config map[string]any `json:"config,omitempty"` +} + +// ExternalDeployResponse 公司发布系统响应 +type ExternalDeployResponse struct { + DeployID string `json:"deployId"` + Status string `json:"status"` + Message string `json:"message,omitempty"` +} + +// ExternalDeployStatus 公司发布状态 +type ExternalDeployStatus struct { + DeployID string `json:"deployId"` + Service string `json:"service"` + Version string `json:"version"` + Status string `json:"status"` + Progress float64 `json:"progress"` // 发布进度 0-100 + StartTime *time.Time `json:"startTime,omitempty"` + FinishTime *time.Time `json:"finishTime,omitempty"` + ErrorMessage string `json:"errorMessage,omitempty"` +} + +// AlertRequest 告警请求 +type AlertRequest struct { + Title string `json:"title"` + Content string `json:"content"` + Level string `json:"level"` // Info/Warning/Error/Critical + Service string `json:"service"` + DeployID string `json:"deployId,omitempty"` + Labels map[string]string `json:"labels,omitempty"` + Annotations map[string]any `json:"annotations,omitempty"` +} + +// AlertQuery 告警查询参数 +type AlertQuery struct { + Service string `json:"service,omitempty"` + Level string `json:"level,omitempty"` + Start *time.Time `json:"start,omitempty"` + End *time.Time `json:"end,omitempty"` + Limit int `json:"limit,omitempty"` +} + +// AlertResponse 告警响应 +type AlertResponse struct { + Items []Alert `json:"items"` + Total int `json:"total"` +} + +// Alert 告警信息 +type Alert struct { + ID string `json:"id"` + Title string `json:"title"` + Content string `json:"content"` + Level string `json:"level"` + Service string `json:"service"` + Status string `json:"status"` // Active/Resolved + CreatedAt time.Time `json:"createdAt"` + ResolvedAt *time.Time `json:"resolvedAt,omitempty"` + Labels map[string]string `json:"labels,omitempty"` + Annotations map[string]any `json:"annotations,omitempty"` +} + +// ===== 数据库模型(对应ER图中的表) ===== + +// EventLog 事件日志数据库模型(对应event_logs表) +type EventLog struct { + ID int `json:"id" db:"id"` // bigint - 主键 + SourceSystem string `json:"sourceSystem" db:"source_system"` // varchar(255) + SourceID string `json:"sourceId" db:"source_id"` // varchar(255) + SourceName string `json:"sourceName" db:"source_name"` // varchar(255) + Timestamp time.Time `json:"timestamp" db:"timestamp"` // datetime + Actor string `json:"actor" db:"actor"` // varchar(255) + Severity string `json:"severity" db:"severity"` // varchar(255) + CorrelationID string `json:"correlationId" db:"correlation_id"` // varchar(255) + Payload string `json:"payload" db:"payload"` // text - JSON格式 +} + +// MetricAlertChange 指标告警变更数据库模型(对应metric_alert_changes表) +type MetricAlertChange struct { + ID int `json:"id" db:"id"` // bigint - 主键 + ChangeTime time.Time `json:"changeTime" db:"change_time"` // datetime + AlertName string `json:"alertName" db:"alert_name"` // varchar(255) + ChangeItem string `json:"changeItem" db:"change_item"` // text - 变更项内容 +} diff --git a/release-system/internal/model/deploy_task/deployment.go b/release-system/internal/model/deploy_task/deployment.go new file mode 100644 index 0000000..588688c --- /dev/null +++ b/release-system/internal/model/deploy_task/deployment.go @@ -0,0 +1,69 @@ +package deploy_task + +import "time" + +// DeployState 发布状态枚举 +type DeployState string + +const ( + StatusDeploying DeployState = "deploying" + StatusStop DeployState = "stop" + StatusRollback DeployState = "rollback" + StatusCompleted DeployState = "completed" +) + +// DeployBatch 部署批次信息 +type DeployBatch struct { + ID int `json:"id" db:"id"` // bigint - 主键 + DeployID int `json:"deployId" db:"deploy_id"` // bigint - 外键 + BatchID string `json:"batchId" db:"batch_id"` // varchar(255) - 批次ID + StartTime *time.Time `json:"startTime" db:"start_time"` // datetime + EndTime *time.Time `json:"endTime" db:"end_time"` // datetime + TargetRatio float64 `json:"targetRatio" db:"target_ratio"` // double + NodeIDs []string `json:"nodeIds" db:"node_ids"` // 数组格式的节点ID列表 +} + +// ServiceDeployTask 服务部署任务信息 +type ServiceDeployTask struct { + ID int `json:"id" db:"id"` // bigint - 主键 + Service string `json:"service" db:"service"` // varchar(255) - 外键引用services.name + Version string `json:"version" db:"version"` // varchar(255) - 外键引用service_versions.version + TaskCreator string `json:"taskCreator" db:"task_creator"` // varchar(255) + DeployBeginTime *time.Time `json:"deployBeginTime" db:"deploy_begin_time"` // datetime + DeployEndTime *time.Time `json:"deployEndTime" db:"deploy_end_time"` // datetime + DeployState DeployState `json:"deployState" db:"deploy_state"` // 部署状态 + CorrelationID string `json:"correlationId" db:"correlation_id"` // varchar(255) +} + +// ===== API请求响应结构体 ===== + +// Deployment API响应用的发布任务 +type Deployment struct { + ID string `json:"id"` + Service string `json:"service"` + Version string `json:"version"` + Status DeployState `json:"status"` + ScheduleTime *time.Time `json:"scheduleTime,omitempty"` + FinishTime *time.Time `json:"finishTime,omitempty"` +} + +// CreateDeploymentRequest 创建发布任务请求 +type CreateDeploymentRequest struct { + Service string `json:"service" binding:"required"` + Version string `json:"version" binding:"required"` + ScheduleTime *time.Time `json:"scheduleTime,omitempty"` // 可选参数,不填为立即发布 +} + +// UpdateDeploymentRequest 修改发布任务请求 +type UpdateDeploymentRequest struct { + Version string `json:"version,omitempty"` + ScheduleTime *time.Time `json:"scheduleTime,omitempty"` // 新的计划发布时间 +} + +// DeploymentQuery 发布任务查询参数 +type DeploymentQuery struct { + Type DeployState `form:"type"` // deploying/stop/rollback/completed + Service string `form:"service"` // 服务名称过滤 + Start string `form:"start"` // 分页起始 + Limit int `form:"limit"` // 分页大小 +} diff --git a/release-system/internal/model/service_info/service.go b/release-system/internal/model/service_info/service.go new file mode 100644 index 0000000..24df9ef --- /dev/null +++ b/release-system/internal/model/service_info/service.go @@ -0,0 +1,111 @@ +package service_info + +import "time" + +// ExceptionStatus 异常处理状态枚举 +type ExceptionStatus string + +const ( + ExceptionStatusNew ExceptionStatus = "new" + ExceptionStatusAnalyzing ExceptionStatus = "analyzing" + ExceptionStatusProcessing ExceptionStatus = "processing" + ExceptionStatusResolved ExceptionStatus = "resolved" +) + +// HealthStatus 健康状态枚举 +type HealthStatus string + +const ( + HealthStatusNormal HealthStatus = "Normal" + HealthStatusWarning HealthStatus = "Warning" + HealthStatusError HealthStatus = "Error" +) + +// DeployStatus 部署状态枚举 +type DeployStatus string + +const ( + DeployStatusInDeploying DeployStatus = "InDeploying" + DeployStatusAllDeployFinish DeployStatus = "AllDeployFinish" +) + +// Service 服务基础信息 +type Service struct { + Name string `json:"name" db:"name"` // varchar(255) - 主键 + Deps []string `json:"deps" db:"deps"` // 依赖关系 +} + +// ServiceInstance 服务实例信息 +type ServiceInstance struct { + ID string `json:"id" db:"id"` // 主键 + Service string `json:"service" db:"service"` // varchar(255) - 外键引用services.name + Version string `json:"version" db:"version"` // varchar(255) - 外键引用service_versions.version +} + +// ServiceVersion 服务版本信息 +type ServiceVersion struct { + Version string `json:"version" db:"version"` // varchar(255) - 主键 + Service string `json:"service" db:"service"` // varchar(255) - 外键引用services.name + CreateTime time.Time `json:"createTime" db:"create_time"` // 时间戳字段 +} + +// ServiceState 服务状态信息 +type ServiceState struct { + Service string `json:"service" db:"service"` // varchar(255) - 外键引用services.name + Version string `json:"version" db:"version"` // varchar(255) - 外键引用service_versions.version + Level string `json:"level" db:"level"` // varchar(255) - 异常级别 + ReportAt time.Time `json:"reportAt" db:"report_at"` // datetime - 报告时间 + ResolvedAt *time.Time `json:"resolvedAt" db:"resolved_at"` // datetime - 解决时间(可为null) + HealthStatus HealthStatus `json:"healthStatus" db:"health_status"` // varchar(255) - 健康状态 + ExceptionStatus ExceptionStatus `json:"exceptionStatus" db:"exception_status"` // varchar(255) - 异常处理状态 + Details string `json:"details" db:"details"` // text - JSON格式的详细信息 +} + +// ===== API响应结构体 ===== + +// ServiceItem API响应用的服务信息(对应/v1/services接口items格式) +type ServiceItem struct { + Name string `json:"name"` // 服务名称 + DeployState DeployStatus `json:"deployState"` // 发布状态:InDeploying|AllDeployFinish + Health HealthStatus `json:"health"` // 健康状态:Normal/Warning/Error + Deps []string `json:"deps"` // 依赖关系(直接使用Service.Deps) +} + +// ServicesResponse 服务列表API响应(对应/v1/services接口) +type ServicesResponse struct { + Items []ServiceItem `json:"items"` + Relation map[string][]string `json:"relation"` // 树形关系描述,有向无环图 +} + +// ActiveVersionItem 活跃版本项目 +type ActiveVersionItem struct { + Version string `json:"version"` // v1.0.1 + DeployID string `json:"deployID"` // 1001 + StartTime time.Time `json:"startTime"` // 开始时间 + EstimatedCompletionTime time.Time `json:"estimatedCompletionTime"` // 预估完成时间 + Instances int `json:"instances"` // 实例个数 + Health HealthStatus `json:"health"` // 健康状态:Normal/Warning/Error +} + +// MetricStats 服务指标统计(对应/v1/services/:service/metricStats接口) +type MetricStats struct { + Summary MetricSummary `json:"summary"` // 所有实例的聚合值 + Items []MetricVersionItem `json:"items"` // 各版本的指标内容 +} + +// MetricSummary 指标汇总 +type MetricSummary struct { + Metrics []Metric `json:"metrics"` // 此版本发布的metric指标内容 +} + +// MetricVersionItem 版本指标项 +type MetricVersionItem struct { + Version string `json:"version"` // v1.0.1 + Metrics []Metric `json:"metrics"` // 此版本发布的metric指标内容 +} + +// Metric 指标 +type Metric struct { + Name string `json:"name"` // latency/traffic/errorRatio/saturation + Value any `json:"value"` // 指标值(ms/Qps/百分比) +} diff --git a/release-system/internal/repository/deployment_repository.go b/release-system/internal/repository/deployment_repository.go new file mode 100644 index 0000000..f747df1 --- /dev/null +++ b/release-system/internal/repository/deployment_repository.go @@ -0,0 +1,39 @@ +package repository + +import ( + "context" + "release-system/internal/model/deploy_task" +) + +// DeploymentRepository 发布任务仓储接口 +type DeploymentRepository interface { + // ===== API方法 ===== + + // CreateDeployment 创建发布任务(POST /v1/deployments) + // 直接返回部署任务ID + CreateDeployment(ctx context.Context, req *deploy_task.CreateDeploymentRequest) (string, error) + + // GetDeploymentByID 根据ID获取发布任务详情(GET /v1/deployments/:deployID) + GetDeploymentByID(ctx context.Context, deployID string) (*deploy_task.Deployment, error) + + // GetDeployments 获取发布任务列表(GET /v1/deployments?type=Schedule&service=xxx) + GetDeployments(ctx context.Context, query *deploy_task.DeploymentQuery) ([]deploy_task.Deployment, error) + + // UpdateDeployment 修改未开始的发布任务(POST /v1/deployments/:deployID) + UpdateDeployment(ctx context.Context, deployID string, req *deploy_task.UpdateDeploymentRequest) error + + // DeleteDeployment 删除未开始的发布任务(DELETE /v1/deployments/:deployID) + DeleteDeployment(ctx context.Context, deployID string) error + + // PauseDeployment 暂停正在灰度的发布任务(POST /v1/deployments/:deployID/pause) + PauseDeployment(ctx context.Context, deployID string) error + + // ContinueDeployment 继续发布(POST /v1/deployments/:deployID/continue) + ContinueDeployment(ctx context.Context, deployID string) error + + // RollbackDeployment 回滚发布任务(POST /v1/deployments/:deployID/rollback) + RollbackDeployment(ctx context.Context, deployID string) error + + // CheckDeploymentConflict 检查发布冲突(同一服务同一版本是否已在发布) + CheckDeploymentConflict(ctx context.Context, service, version string) (bool, error) +} diff --git a/release-system/internal/repository/service_repository.go b/release-system/internal/repository/service_repository.go new file mode 100644 index 0000000..998079c --- /dev/null +++ b/release-system/internal/repository/service_repository.go @@ -0,0 +1,22 @@ +package repository + +import ( + "context" + "release-system/internal/model/service_info" +) + +// ServiceRepository 服务信息仓储接口 +type ServiceRepository interface { + // GetServicesForAPI 获取所有服务列表(GET /v1/services) + GetServicesForAPI(ctx context.Context) (*service_info.ServicesResponse, error) + + // GetServiceActiveVersionsForAPI 获取服务活跃版本(GET /v1/services/:service/activeVersions) + GetServiceActiveVersionsForAPI(ctx context.Context, serviceName string) ([]service_info.ActiveVersionItem, error) + + // GetServiceAvailableVersions 获取可用服务版本(GET /v1/services/:service/availableVersions?type=unrelease) + // 直接返回ServiceVersion列表,API层负责包装响应格式 + GetServiceAvailableVersions(ctx context.Context, serviceName string, versionType string) ([]service_info.ServiceVersion, error) + + // GetServiceMetrics 获取服务指标数据(GET /v1/services/:service/metricStats) + GetServiceMetrics(ctx context.Context, serviceName string) (*service_info.MetricStats, error) +} diff --git a/release-system/internal/service/.gitkeep b/release-system/internal/service/.gitkeep new file mode 100644 index 0000000..e69de29 From a935e2626426c1e5a88721ac6408946664fb5275 Mon Sep 17 00:00:00 2001 From: dnj Date: Thu, 11 Sep 2025 17:06:44 +0800 Subject: [PATCH 2/3] =?UTF-8?q?feat(service=5Fmanager):=20=E9=87=8D?= =?UTF-8?q?=E6=9E=84=E6=9C=8D=E5=8A=A1=E7=AE=A1=E7=90=86=E7=B3=BB=E7=BB=9F?= =?UTF-8?q?=E6=9E=B6=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 重构服务管理系统,将原有release-system模块整合到service_manager中,包含以下主要变更: 1. 新增服务基础信息、版本、实例等核心模型定义 2. 实现服务管理相关API接口 3. 添加部署任务管理和批次控制功能 4. 完善数据库操作层和业务逻辑层 5. 定义服务状态监控和错误处理机制 移除不再使用的release-system模块及相关文件 --- go.mod | 8 +- go.sum | 25 ++ internal/service_manager/api/api.go | 6 +- internal/service_manager/api/deploy_api.go | 365 ++++++++++++++++++ internal/service_manager/api/info_api.go | 300 ++++++++++++++ internal/service_manager/api/ping.go | 7 - internal/service_manager/database/database.go | 26 +- .../service_manager/database/deploy_repo.go | 247 ++++++++++++ .../service_manager/database/info_repo.go | 181 +++++++++ internal/service_manager/model/api.go | 90 +++++ internal/service_manager/model/constants.go | 39 ++ .../service_manager/model/deploy_batch.go | 14 + internal/service_manager/model/deploy_task.go | 15 + internal/service_manager/model/service.go | 7 + .../service_manager/model/service_instance.go | 8 + .../service_manager/model/service_state.go | 15 + .../service_manager/model/service_version.go | 10 + .../service/{service.go => base.go} | 0 .../service_manager/service/deploy_service.go | 224 +++++++++++ internal/service_manager/service/errors.go | 11 + .../service_manager/service/info_service.go | 184 +++++++++ release-system/go.mod | 3 - .../internal/model/adapter/external.go | 120 ------ .../internal/model/deploy_task/deployment.go | 69 ---- .../internal/model/service_info/service.go | 111 ------ .../repository/deployment_repository.go | 39 -- .../internal/repository/service_repository.go | 22 -- release-system/internal/service/.gitkeep | 0 28 files changed, 1771 insertions(+), 375 deletions(-) create mode 100644 internal/service_manager/api/deploy_api.go create mode 100644 internal/service_manager/api/info_api.go delete mode 100644 internal/service_manager/api/ping.go create mode 100644 internal/service_manager/database/deploy_repo.go create mode 100644 internal/service_manager/database/info_repo.go create mode 100644 internal/service_manager/model/api.go create mode 100644 internal/service_manager/model/constants.go create mode 100644 internal/service_manager/model/deploy_batch.go create mode 100644 internal/service_manager/model/deploy_task.go create mode 100644 internal/service_manager/model/service.go create mode 100644 internal/service_manager/model/service_instance.go create mode 100644 internal/service_manager/model/service_state.go create mode 100644 internal/service_manager/model/service_version.go rename internal/service_manager/service/{service.go => base.go} (100%) create mode 100644 internal/service_manager/service/deploy_service.go create mode 100644 internal/service_manager/service/errors.go create mode 100644 internal/service_manager/service/info_service.go delete mode 100644 release-system/go.mod delete mode 100644 release-system/internal/model/adapter/external.go delete mode 100644 release-system/internal/model/deploy_task/deployment.go delete mode 100644 release-system/internal/model/service_info/service.go delete mode 100644 release-system/internal/repository/deployment_repository.go delete mode 100644 release-system/internal/repository/service_repository.go delete mode 100644 release-system/internal/service/.gitkeep diff --git a/go.mod b/go.mod index 8ee2205..e46d148 100644 --- a/go.mod +++ b/go.mod @@ -2,11 +2,15 @@ module github.com/qiniu/zeroops go 1.24 +require ( + github.com/fox-gonic/fox v0.0.6 + github.com/rs/zerolog v1.34.0 +) + require ( github.com/bytedance/sonic v1.13.3 // indirect github.com/bytedance/sonic/loader v0.2.4 // indirect github.com/cloudwego/base64x v0.1.5 // indirect - github.com/fox-gonic/fox v0.0.6 // indirect github.com/gabriel-vasile/mimetype v1.4.9 // indirect github.com/gin-contrib/cors v1.7.6 // indirect github.com/gin-contrib/sse v1.1.0 // indirect @@ -17,6 +21,7 @@ require ( github.com/goccy/go-json v0.10.5 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/cpuid/v2 v2.2.10 // indirect + github.com/kr/text v0.2.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect @@ -25,7 +30,6 @@ require ( github.com/modern-go/reflect2 v1.0.2 // indirect github.com/natefinch/lumberjack v2.0.0+incompatible // indirect github.com/pelletier/go-toml/v2 v2.2.4 // indirect - github.com/rs/zerolog v1.34.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.3.0 // indirect golang.org/x/arch v0.18.0 // indirect diff --git a/go.sum b/go.sum index e832799..3860166 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak= +github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/bytedance/sonic v1.13.3 h1:MS8gmaH16Gtirygw7jV91pDCN33NyMrPbN7qiYhEsF0= github.com/bytedance/sonic v1.13.3/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4= github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= @@ -7,8 +9,11 @@ github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCy github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/fox-gonic/fox v0.0.6 h1:Otz6bTpiboGfCoAp4bTDZOTxI6PQw1uEID/VZRlklws= github.com/fox-gonic/fox v0.0.6/go.mod h1:l1C0zu5H44YV60tEq6rbNRvv0z14hnlpsl8lMlzqpFg= github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY= @@ -19,6 +24,8 @@ github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM= github.com/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ= github.com/gin-gonic/gin v1.10.1/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= @@ -28,6 +35,8 @@ github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAu github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= @@ -35,6 +44,10 @@ github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa02 github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE= github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= @@ -57,6 +70,10 @@ github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0 github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY= github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ= @@ -68,6 +85,8 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA= @@ -88,6 +107,12 @@ golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= +gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/service_manager/api/api.go b/internal/service_manager/api/api.go index 174da96..20d99f9 100644 --- a/internal/service_manager/api/api.go +++ b/internal/service_manager/api/api.go @@ -23,5 +23,9 @@ func NewApi(db *database.Database, service *service.Service, router *fox.Engine) } func (api *Api) setupRouters(router *fox.Engine) { - router.GET("/servicemanager/ping", api.Ping) + // 服务信息相关路由 + api.setupInfoRouters(router) + + // 部署管理相关路由 + api.setupDeployRouters(router) } diff --git a/internal/service_manager/api/deploy_api.go b/internal/service_manager/api/deploy_api.go new file mode 100644 index 0000000..33ac3a1 --- /dev/null +++ b/internal/service_manager/api/deploy_api.go @@ -0,0 +1,365 @@ +package api + +import ( + "net/http" + "strconv" + + "github.com/fox-gonic/fox" + "github.com/qiniu/zeroops/internal/service_manager/model" + "github.com/qiniu/zeroops/internal/service_manager/service" + "github.com/rs/zerolog/log" +) + +// setupDeployRouters 设置部署管理相关路由 +func (api *Api) setupDeployRouters(router *fox.Engine) { + // 部署任务基本操作 + router.POST("/v1/deployments", api.CreateDeployment) + router.GET("/v1/deployments/:deployID", api.GetDeploymentByID) + router.POST("/v1/deployments/:deployID", api.UpdateDeployment) + router.GET("/v1/deployments", api.GetDeployments) + router.DELETE("/v1/deployments/:deployID", api.DeleteDeployment) + + // 部署任务控制操作 + router.POST("/v1/deployments/:deployID/pause", api.PauseDeployment) + router.POST("/v1/deployments/:deployID/continue", api.ContinueDeployment) + router.POST("/v1/deployments/:deployID/rollback", api.RollbackDeployment) +} + +// ===== 部署管理相关API ===== + +// CreateDeployment 创建发布任务(POST /v1/deployments) +func (api *Api) CreateDeployment(c *fox.Context) { + ctx := c.Request.Context() + + var req model.CreateDeploymentRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, map[string]any{ + "error": "bad request", + "message": "invalid request body: " + err.Error(), + }) + return + } + + if req.Service == "" || req.Version == "" { + c.JSON(http.StatusBadRequest, map[string]any{ + "error": "bad request", + "message": "service and version are required", + }) + return + } + + deployID, err := api.service.CreateDeployment(ctx, &req) + if err != nil { + if err == service.ErrServiceNotFound { + c.JSON(http.StatusBadRequest, map[string]any{ + "error": "bad request", + "message": "service not found", + }) + return + } + if err == service.ErrDeploymentConflict { + c.JSON(http.StatusConflict, map[string]any{ + "error": "conflict", + "message": "deployment conflict: service version already in deployment", + }) + return + } + log.Error().Err(err). + Str("service", req.Service). + Str("version", req.Version). + Msg("failed to create deployment") + c.JSON(http.StatusInternalServerError, map[string]any{ + "error": "internal server error", + "message": "failed to create deployment", + }) + return + } + + c.JSON(http.StatusCreated, map[string]any{ + "id": deployID, + "message": "deployment created successfully", + }) +} + +// GetDeploymentByID 获取发布任务详情(GET /v1/deployments/:deployID) +func (api *Api) GetDeploymentByID(c *fox.Context) { + ctx := c.Request.Context() + deployID := c.Param("deployID") + + if deployID == "" { + c.JSON(http.StatusBadRequest, map[string]any{ + "error": "bad request", + "message": "deployment ID is required", + }) + return + } + + deployment, err := api.service.GetDeploymentByID(ctx, deployID) + if err != nil { + if err == service.ErrDeploymentNotFound { + c.JSON(http.StatusNotFound, map[string]any{ + "error": "not found", + "message": "deployment not found", + }) + return + } + log.Error().Err(err).Str("deployID", deployID).Msg("failed to get deployment") + c.JSON(http.StatusInternalServerError, map[string]any{ + "error": "internal server error", + "message": "failed to get deployment", + }) + return + } + + c.JSON(http.StatusOK, deployment) +} + +// GetDeployments 获取发布任务列表(GET /v1/deployments) +func (api *Api) GetDeployments(c *fox.Context) { + ctx := c.Request.Context() + + query := &model.DeploymentQuery{ + Type: model.DeployState(c.Query("type")), + Service: c.Query("service"), + Start: c.Query("start"), + } + + if limitStr := c.Query("limit"); limitStr != "" { + if limit, err := strconv.Atoi(limitStr); err == nil { + query.Limit = limit + } + } + + deployments, err := api.service.GetDeployments(ctx, query) + if err != nil { + log.Error().Err(err).Msg("failed to get deployments") + c.JSON(http.StatusInternalServerError, map[string]any{ + "error": "internal server error", + "message": "failed to get deployments", + }) + return + } + + c.JSON(http.StatusOK, map[string]any{ + "items": deployments, + }) +} + +// UpdateDeployment 修改发布任务(POST /v1/deployments/:deployID) +func (api *Api) UpdateDeployment(c *fox.Context) { + ctx := c.Request.Context() + deployID := c.Param("deployID") + + if deployID == "" { + c.JSON(http.StatusBadRequest, map[string]any{ + "error": "bad request", + "message": "deployment ID is required", + }) + return + } + + var req model.UpdateDeploymentRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, map[string]any{ + "error": "bad request", + "message": "invalid request body: " + err.Error(), + }) + return + } + + err := api.service.UpdateDeployment(ctx, deployID, &req) + if err != nil { + if err == service.ErrDeploymentNotFound { + c.JSON(http.StatusNotFound, map[string]any{ + "error": "not found", + "message": "deployment not found", + }) + return + } + if err == service.ErrInvalidDeployState { + c.JSON(http.StatusBadRequest, map[string]any{ + "error": "bad request", + "message": "invalid deployment state for update", + }) + return + } + log.Error().Err(err).Str("deployID", deployID).Msg("failed to update deployment") + c.JSON(http.StatusInternalServerError, map[string]any{ + "error": "internal server error", + "message": "failed to update deployment", + }) + return + } + + c.JSON(http.StatusOK, map[string]any{ + "message": "deployment updated successfully", + }) +} + +// DeleteDeployment 删除发布任务(DELETE /v1/deployments/:deployID) +func (api *Api) DeleteDeployment(c *fox.Context) { + ctx := c.Request.Context() + deployID := c.Param("deployID") + + if deployID == "" { + c.JSON(http.StatusBadRequest, map[string]any{ + "error": "bad request", + "message": "deployment ID is required", + }) + return + } + + err := api.service.DeleteDeployment(ctx, deployID) + if err != nil { + if err == service.ErrDeploymentNotFound { + c.JSON(http.StatusNotFound, map[string]any{ + "error": "not found", + "message": "deployment not found", + }) + return + } + if err == service.ErrInvalidDeployState { + c.JSON(http.StatusBadRequest, map[string]any{ + "error": "bad request", + "message": "invalid deployment state for deletion", + }) + return + } + log.Error().Err(err).Str("deployID", deployID).Msg("failed to delete deployment") + c.JSON(http.StatusInternalServerError, map[string]any{ + "error": "internal server error", + "message": "failed to delete deployment", + }) + return + } + + c.JSON(http.StatusOK, map[string]any{ + "message": "deployment deleted successfully", + }) +} + +// PauseDeployment 暂停发布任务(POST /v1/deployments/:deployID/pause) +func (api *Api) PauseDeployment(c *fox.Context) { + ctx := c.Request.Context() + deployID := c.Param("deployID") + + if deployID == "" { + c.JSON(http.StatusBadRequest, map[string]any{ + "error": "bad request", + "message": "deployment ID is required", + }) + return + } + + err := api.service.PauseDeployment(ctx, deployID) + if err != nil { + if err == service.ErrDeploymentNotFound { + c.JSON(http.StatusNotFound, map[string]any{ + "error": "not found", + "message": "deployment not found", + }) + return + } + if err == service.ErrInvalidDeployState { + c.JSON(http.StatusBadRequest, map[string]any{ + "error": "bad request", + "message": "deployment cannot be paused in current state", + }) + return + } + log.Error().Err(err).Str("deployID", deployID).Msg("failed to pause deployment") + c.JSON(http.StatusInternalServerError, map[string]any{ + "error": "internal server error", + "message": "failed to pause deployment", + }) + return + } + + c.JSON(http.StatusOK, map[string]any{ + "message": "deployment paused successfully", + }) +} + +// ContinueDeployment 继续发布任务(POST /v1/deployments/:deployID/continue) +func (api *Api) ContinueDeployment(c *fox.Context) { + ctx := c.Request.Context() + deployID := c.Param("deployID") + + if deployID == "" { + c.JSON(http.StatusBadRequest, map[string]any{ + "error": "bad request", + "message": "deployment ID is required", + }) + return + } + + err := api.service.ContinueDeployment(ctx, deployID) + if err != nil { + if err == service.ErrDeploymentNotFound { + c.JSON(http.StatusNotFound, map[string]any{ + "error": "not found", + "message": "deployment not found", + }) + return + } + if err == service.ErrInvalidDeployState { + c.JSON(http.StatusBadRequest, map[string]any{ + "error": "bad request", + "message": "deployment cannot be continued in current state", + }) + return + } + log.Error().Err(err).Str("deployID", deployID).Msg("failed to continue deployment") + c.JSON(http.StatusInternalServerError, map[string]any{ + "error": "internal server error", + "message": "failed to continue deployment", + }) + return + } + + c.JSON(http.StatusOK, map[string]any{ + "message": "deployment continued successfully", + }) +} + +// RollbackDeployment 回滚发布任务(POST /v1/deployments/:deployID/rollback) +func (api *Api) RollbackDeployment(c *fox.Context) { + ctx := c.Request.Context() + deployID := c.Param("deployID") + + if deployID == "" { + c.JSON(http.StatusBadRequest, map[string]any{ + "error": "bad request", + "message": "deployment ID is required", + }) + return + } + + err := api.service.RollbackDeployment(ctx, deployID) + if err != nil { + if err == service.ErrDeploymentNotFound { + c.JSON(http.StatusNotFound, map[string]any{ + "error": "not found", + "message": "deployment not found", + }) + return + } + if err == service.ErrInvalidDeployState { + c.JSON(http.StatusBadRequest, map[string]any{ + "error": "bad request", + "message": "deployment cannot be rolled back in current state", + }) + return + } + log.Error().Err(err).Str("deployID", deployID).Msg("failed to rollback deployment") + c.JSON(http.StatusInternalServerError, map[string]any{ + "error": "internal server error", + "message": "failed to rollback deployment", + }) + return + } + + c.JSON(http.StatusOK, map[string]any{ + "message": "deployment rolled back successfully", + }) +} diff --git a/internal/service_manager/api/info_api.go b/internal/service_manager/api/info_api.go new file mode 100644 index 0000000..7f45719 --- /dev/null +++ b/internal/service_manager/api/info_api.go @@ -0,0 +1,300 @@ +package api + +import ( + "net/http" + + "github.com/fox-gonic/fox" + "github.com/qiniu/zeroops/internal/service_manager/model" + "github.com/rs/zerolog/log" +) + +// setupInfoRouters 设置服务信息相关路由 +func (api *Api) setupInfoRouters(router *fox.Engine) { + // 服务列表和信息查询 + router.GET("/v1/services", api.GetServices) + router.GET("/v1/services/:service", api.GetServiceByName) + router.GET("/v1/services/:service/activeVersions", api.GetServiceActiveVersions) + router.GET("/v1/services/:service/availableVersions", api.GetServiceAvailableVersions) + router.GET("/v1/metrics/:service/:name", api.GetServiceMetricTimeSeries) + + // 服务管理(CRUD) + router.POST("/v1/services", api.CreateService) + router.PUT("/v1/services/:service", api.UpdateService) + router.DELETE("/v1/services/:service", api.DeleteService) +} + +// ===== 服务信息相关API ===== + +// GetServices 获取所有服务列表(GET /v1/services) +func (api *Api) GetServices(c *fox.Context) { + ctx := c.Request.Context() + + response, err := api.service.GetServicesResponse(ctx) + if err != nil { + log.Error().Err(err).Msg("failed to get services") + c.JSON(http.StatusInternalServerError, map[string]any{ + "error": "internal server error", + "message": "failed to get services", + }) + return + } + + c.JSON(http.StatusOK, response) +} + +// GetServiceActiveVersions 获取服务活跃版本(GET /v1/services/:service/activeVersions) +func (api *Api) GetServiceActiveVersions(c *fox.Context) { + ctx := c.Request.Context() + serviceName := c.Param("service") + + if serviceName == "" { + c.JSON(http.StatusBadRequest, map[string]any{ + "error": "bad request", + "message": "service name is required", + }) + return + } + + activeVersions, err := api.service.GetServiceActiveVersions(ctx, serviceName) + if err != nil { + log.Error().Err(err).Str("service", serviceName).Msg("failed to get service active versions") + c.JSON(http.StatusInternalServerError, map[string]any{ + "error": "internal server error", + "message": "failed to get service active versions", + }) + return + } + + c.JSON(http.StatusOK, map[string]any{ + "items": activeVersions, + }) +} + +// GetServiceAvailableVersions 获取可用服务版本(GET /v1/services/:service/availableVersions) +func (api *Api) GetServiceAvailableVersions(c *fox.Context) { + ctx := c.Request.Context() + serviceName := c.Param("service") + versionType := c.Query("type") + + if serviceName == "" { + c.JSON(http.StatusBadRequest, map[string]any{ + "error": "bad request", + "message": "service name is required", + }) + return + } + + versions, err := api.service.GetServiceAvailableVersions(ctx, serviceName, versionType) + if err != nil { + log.Error().Err(err). + Str("service", serviceName). + Str("type", versionType). + Msg("failed to get service available versions") + c.JSON(http.StatusInternalServerError, map[string]any{ + "error": "internal server error", + "message": "failed to get service available versions", + }) + return + } + + c.JSON(http.StatusOK, map[string]any{ + "items": versions, + }) +} + +// GetServiceMetricTimeSeries 获取服务时序指标数据(GET /v1/metrics/:service/:name) +func (api *Api) GetServiceMetricTimeSeries(c *fox.Context) { + ctx := c.Request.Context() + serviceName := c.Param("service") + metricName := c.Param("name") + + if serviceName == "" || metricName == "" { + c.JSON(http.StatusBadRequest, map[string]any{ + "error": "bad request", + "message": "service name and metric name are required", + }) + return + } + + // 绑定查询参数 + var query model.MetricTimeSeriesQuery + if err := c.ShouldBindQuery(&query); err != nil { + c.JSON(http.StatusBadRequest, map[string]any{ + "error": "bad request", + "message": "invalid query parameters: " + err.Error(), + }) + return + } + + // 设置路径参数 + query.Service = serviceName + query.Name = metricName + + response, err := api.service.GetServiceMetricTimeSeries(ctx, serviceName, metricName, &query) + if err != nil { + log.Error().Err(err). + Str("service", serviceName). + Str("metric", metricName). + Msg("failed to get service metric time series") + c.JSON(http.StatusInternalServerError, map[string]any{ + "error": "internal server error", + "message": "failed to get service metric time series", + }) + return + } + + c.JSON(http.StatusOK, response) +} + +// ===== 服务管理API(CRUD操作) ===== + +// CreateService 创建服务(POST /v1/services) +func (api *Api) CreateService(c *fox.Context) { + ctx := c.Request.Context() + + var service model.Service + if err := c.ShouldBindJSON(&service); err != nil { + c.JSON(http.StatusBadRequest, map[string]any{ + "error": "bad request", + "message": "invalid request body: " + err.Error(), + }) + return + } + + if service.Name == "" { + c.JSON(http.StatusBadRequest, map[string]any{ + "error": "bad request", + "message": "service name is required", + }) + return + } + + if err := api.service.CreateService(ctx, &service); err != nil { + log.Error().Err(err).Str("service", service.Name).Msg("failed to create service") + c.JSON(http.StatusInternalServerError, map[string]any{ + "error": "internal server error", + "message": "failed to create service", + }) + return + } + + c.JSON(http.StatusCreated, map[string]any{ + "message": "service created successfully", + "service": service.Name, + }) +} + +// GetServiceByName 获取单个服务信息(GET /v1/services/:service) +func (api *Api) GetServiceByName(c *fox.Context) { + ctx := c.Request.Context() + serviceName := c.Param("service") + + if serviceName == "" { + c.JSON(http.StatusBadRequest, map[string]any{ + "error": "bad request", + "message": "service name is required", + }) + return + } + + service, err := api.service.GetServiceByName(ctx, serviceName) + if err != nil { + if err.Error() == "service not found" { + c.JSON(http.StatusNotFound, map[string]any{ + "error": "not found", + "message": "service not found", + }) + return + } + log.Error().Err(err).Str("service", serviceName).Msg("failed to get service") + c.JSON(http.StatusInternalServerError, map[string]any{ + "error": "internal server error", + "message": "failed to get service", + }) + return + } + + c.JSON(http.StatusOK, service) +} + +// UpdateService 更新服务信息(PUT /v1/services/:service) +func (api *Api) UpdateService(c *fox.Context) { + ctx := c.Request.Context() + serviceName := c.Param("service") + + if serviceName == "" { + c.JSON(http.StatusBadRequest, map[string]any{ + "error": "bad request", + "message": "service name is required", + }) + return + } + + var service model.Service + if err := c.ShouldBindJSON(&service); err != nil { + c.JSON(http.StatusBadRequest, map[string]any{ + "error": "bad request", + "message": "invalid request body: " + err.Error(), + }) + return + } + + // 确保URL参数和请求体中的服务名一致 + service.Name = serviceName + + if err := api.service.UpdateService(ctx, &service); err != nil { + if err.Error() == "service not found" { + c.JSON(http.StatusNotFound, map[string]any{ + "error": "not found", + "message": "service not found", + }) + return + } + log.Error().Err(err).Str("service", serviceName).Msg("failed to update service") + c.JSON(http.StatusInternalServerError, map[string]any{ + "error": "internal server error", + "message": "failed to update service", + }) + return + } + + c.JSON(http.StatusOK, map[string]any{ + "message": "service updated successfully", + "service": serviceName, + }) +} + +// DeleteService 删除服务(DELETE /v1/services/:service) +func (api *Api) DeleteService(c *fox.Context) { + ctx := c.Request.Context() + serviceName := c.Param("service") + + if serviceName == "" { + c.JSON(http.StatusBadRequest, map[string]any{ + "error": "bad request", + "message": "service name is required", + }) + return + } + + if err := api.service.DeleteService(ctx, serviceName); err != nil { + if err.Error() == "service not found" { + c.JSON(http.StatusNotFound, map[string]any{ + "error": "not found", + "message": "service not found", + }) + return + } + log.Error().Err(err).Str("service", serviceName).Msg("failed to delete service") + c.JSON(http.StatusInternalServerError, map[string]any{ + "error": "internal server error", + "message": "failed to delete service", + }) + return + } + + c.JSON(http.StatusOK, map[string]any{ + "message": "service deleted successfully", + "service": serviceName, + }) +} diff --git a/internal/service_manager/api/ping.go b/internal/service_manager/api/ping.go deleted file mode 100644 index 97609b3..0000000 --- a/internal/service_manager/api/ping.go +++ /dev/null @@ -1,7 +0,0 @@ -package api - -import "github.com/fox-gonic/fox" - -func (api *Api) Ping(c *fox.Context) string { - return "pong" -} diff --git a/internal/service_manager/database/database.go b/internal/service_manager/database/database.go index d155cb2..da8ecba 100644 --- a/internal/service_manager/database/database.go +++ b/internal/service_manager/database/database.go @@ -1,6 +1,7 @@ package database import ( + "context" "database/sql" "github.com/qiniu/zeroops/internal/config" @@ -17,9 +18,32 @@ func NewDatabase(cfg *config.DatabaseConfig) (*Database, error) { } func (d *Database) Close() error { - return d.db.Close() + if d.db != nil { + return d.db.Close() + } + return nil } func (d *Database) DB() *sql.DB { return d.db } + +// BeginTx 开始事务 +func (d *Database) BeginTx(ctx context.Context) (*sql.Tx, error) { + return d.db.BeginTx(ctx, nil) +} + +// QueryContext 执行查询 +func (d *Database) QueryContext(ctx context.Context, query string, args ...any) (*sql.Rows, error) { + return d.db.QueryContext(ctx, query, args...) +} + +// QueryRowContext 执行单行查询 +func (d *Database) QueryRowContext(ctx context.Context, query string, args ...any) *sql.Row { + return d.db.QueryRowContext(ctx, query, args...) +} + +// ExecContext 执行操作 +func (d *Database) ExecContext(ctx context.Context, query string, args ...any) (sql.Result, error) { + return d.db.ExecContext(ctx, query, args...) +} diff --git a/internal/service_manager/database/deploy_repo.go b/internal/service_manager/database/deploy_repo.go new file mode 100644 index 0000000..9062167 --- /dev/null +++ b/internal/service_manager/database/deploy_repo.go @@ -0,0 +1,247 @@ +package database + +import ( + "context" + "database/sql" + "encoding/json" + "strconv" + + "github.com/qiniu/zeroops/internal/service_manager/model" +) + +// CreateDeployment 创建发布任务 +func (d *Database) CreateDeployment(ctx context.Context, req *model.CreateDeploymentRequest) (string, error) { + tx, err := d.BeginTx(ctx) + if err != nil { + return "", err + } + defer tx.Rollback() + + query := `INSERT INTO service_deploy_tasks (service, version, task_creator, deploy_begin_time, deploy_state, correlation_id) + VALUES (?, ?, ?, ?, ?, ?)` + + // 根据是否有计划时间决定初始状态 + var initialStatus model.DeployState + if req.ScheduleTime == nil { + initialStatus = model.StatusDeploying // 立即发布 + } else { + initialStatus = model.StatusUnrelease // 计划发布 + } + + result, err := tx.ExecContext(ctx, query, req.Service, req.Version, "system", req.ScheduleTime, initialStatus, "") + if err != nil { + return "", err + } + + id, err := result.LastInsertId() + if err != nil { + return "", err + } + + if err := tx.Commit(); err != nil { + return "", err + } + + return strconv.FormatInt(id, 10), nil +} + +// GetDeploymentByID 根据ID获取发布任务详情 +func (d *Database) GetDeploymentByID(ctx context.Context, deployID string) (*model.Deployment, error) { + query := `SELECT id, service, version, deploy_state, deploy_begin_time, deploy_end_time + FROM service_deploy_tasks WHERE id = ?` + row := d.QueryRowContext(ctx, query, deployID) + + var task model.ServiceDeployTask + if err := row.Scan(&task.ID, &task.Service, &task.Version, &task.DeployState, + &task.DeployBeginTime, &task.DeployEndTime); err != nil { + if err == sql.ErrNoRows { + return nil, nil + } + return nil, err + } + + deployment := &model.Deployment{ + ID: strconv.Itoa(int(task.ID)), + Service: task.Service, + Version: task.Version, + Status: task.DeployState, + ScheduleTime: task.DeployBeginTime, + FinishTime: task.DeployEndTime, + } + + return deployment, nil +} + +// GetDeployments 获取发布任务列表 +func (d *Database) GetDeployments(ctx context.Context, query *model.DeploymentQuery) ([]model.Deployment, error) { + sql := `SELECT id, service, version, deploy_state, deploy_begin_time, deploy_end_time + FROM service_deploy_tasks WHERE 1=1` + args := []any{} + + if query.Type != "" { + sql += " AND deploy_state = ?" + args = append(args, query.Type) + } + + if query.Service != "" { + sql += " AND service = ?" + args = append(args, query.Service) + } + + sql += " ORDER BY deploy_begin_time DESC" + + if query.Limit > 0 { + sql += " LIMIT ?" + args = append(args, query.Limit) + } + + rows, err := d.QueryContext(ctx, sql, args...) + if err != nil { + return nil, err + } + defer rows.Close() + + var deployments []model.Deployment + for rows.Next() { + var task model.ServiceDeployTask + if err := rows.Scan(&task.ID, &task.Service, &task.Version, &task.DeployState, + &task.DeployBeginTime, &task.DeployEndTime); err != nil { + return nil, err + } + + deployment := model.Deployment{ + ID: strconv.Itoa(int(task.ID)), + Service: task.Service, + Version: task.Version, + Status: task.DeployState, + ScheduleTime: task.DeployBeginTime, + FinishTime: task.DeployEndTime, + } + + deployments = append(deployments, deployment) + } + + return deployments, rows.Err() +} + +// UpdateDeployment 修改未开始的发布任务 +func (d *Database) UpdateDeployment(ctx context.Context, deployID string, req *model.UpdateDeploymentRequest) error { + sql := `UPDATE service_deploy_tasks SET ` + args := []any{} + updates := []string{} + + if req.Version != "" { + updates = append(updates, "version = ?") + args = append(args, req.Version) + } + + if req.ScheduleTime != nil { + updates = append(updates, "deploy_begin_time = ?") + args = append(args, req.ScheduleTime) + } + + if len(updates) == 0 { + return nil + } + + sql += updates[0] + for i := 1; i < len(updates); i++ { + sql += ", " + updates[i] + } + + sql += " WHERE id = ? AND deploy_state = ?" + args = append(args, deployID, model.StatusUnrelease) + + _, err := d.ExecContext(ctx, sql, args...) + return err +} + +// DeleteDeployment 删除未开始的发布任务 +func (d *Database) DeleteDeployment(ctx context.Context, deployID string) error { + query := `DELETE FROM service_deploy_tasks WHERE id = ? AND deploy_state = ?` + _, err := d.ExecContext(ctx, query, deployID, model.StatusUnrelease) + return err +} + +// PauseDeployment 暂停正在灰度的发布任务 +func (d *Database) PauseDeployment(ctx context.Context, deployID string) error { + query := `UPDATE service_deploy_tasks SET deploy_state = ? WHERE id = ? AND deploy_state = ?` + _, err := d.ExecContext(ctx, query, model.StatusStop, deployID, model.StatusDeploying) + return err +} + +// ContinueDeployment 继续发布 +func (d *Database) ContinueDeployment(ctx context.Context, deployID string) error { + query := `UPDATE service_deploy_tasks SET deploy_state = ? WHERE id = ? AND deploy_state = ?` + _, err := d.ExecContext(ctx, query, model.StatusDeploying, deployID, model.StatusStop) + return err +} + +// RollbackDeployment 回滚发布任务 +func (d *Database) RollbackDeployment(ctx context.Context, deployID string) error { + query := `UPDATE service_deploy_tasks SET deploy_state = ? WHERE id = ?` + _, err := d.ExecContext(ctx, query, model.StatusRollback, deployID) + return err +} + +// CheckDeploymentConflict 检查发布冲突 +func (d *Database) CheckDeploymentConflict(ctx context.Context, service, version string) (bool, error) { + query := `SELECT COUNT(*) FROM service_deploy_tasks + WHERE service = ? AND version = ? AND deploy_state IN (?, ?)` + row := d.QueryRowContext(ctx, query, service, version, model.StatusDeploying, model.StatusStop) + + var count int + if err := row.Scan(&count); err != nil { + return false, err + } + + return count > 0, nil +} + +// ===== 部署批次操作 ===== + +// CreateDeployBatch 创建部署批次 +func (d *Database) CreateDeployBatch(ctx context.Context, batch *model.DeployBatch) error { + nodeIDsJSON, err := json.Marshal(batch.NodeIDs) + if err != nil { + return err + } + + query := `INSERT INTO deploy_batches (deploy_id, batch_id, start_time, end_time, target_ratio, node_ids) + VALUES (?, ?, ?, ?, ?, ?)` + _, err = d.ExecContext(ctx, query, batch.DeployID, batch.BatchID, batch.StartTime, + batch.EndTime, batch.TargetRatio, string(nodeIDsJSON)) + return err +} + +// GetDeployBatches 获取部署批次列表 +func (d *Database) GetDeployBatches(ctx context.Context, deployID int) ([]model.DeployBatch, error) { + query := `SELECT id, deploy_id, batch_id, start_time, end_time, target_ratio, node_ids + FROM deploy_batches WHERE deploy_id = ? ORDER BY id` + rows, err := d.QueryContext(ctx, query, deployID) + if err != nil { + return nil, err + } + defer rows.Close() + + var batches []model.DeployBatch + for rows.Next() { + var batch model.DeployBatch + var nodeIDsJSON string + + if err := rows.Scan(&batch.ID, &batch.DeployID, &batch.BatchID, &batch.StartTime, + &batch.EndTime, &batch.TargetRatio, &nodeIDsJSON); err != nil { + return nil, err + } + + if nodeIDsJSON != "" { + if err := json.Unmarshal([]byte(nodeIDsJSON), &batch.NodeIDs); err != nil { + return nil, err + } + } + + batches = append(batches, batch) + } + + return batches, rows.Err() +} diff --git a/internal/service_manager/database/info_repo.go b/internal/service_manager/database/info_repo.go new file mode 100644 index 0000000..927aa23 --- /dev/null +++ b/internal/service_manager/database/info_repo.go @@ -0,0 +1,181 @@ +package database + +import ( + "context" + "database/sql" + "encoding/json" + + "github.com/qiniu/zeroops/internal/service_manager/model" +) + +// GetServices 获取所有服务列表 +func (d *Database) GetServices(ctx context.Context) ([]model.Service, error) { + query := `SELECT name, deps FROM services` + rows, err := d.QueryContext(ctx, query) + if err != nil { + return nil, err + } + defer rows.Close() + + var services []model.Service + for rows.Next() { + var service model.Service + var depsJSON string + if err := rows.Scan(&service.Name, &depsJSON); err != nil { + return nil, err + } + + if depsJSON != "" { + if err := json.Unmarshal([]byte(depsJSON), &service.Deps); err != nil { + return nil, err + } + } + + services = append(services, service) + } + + return services, rows.Err() +} + +// GetServiceByName 根据名称获取服务信息 +func (d *Database) GetServiceByName(ctx context.Context, name string) (*model.Service, error) { + query := `SELECT name, deps FROM services WHERE name = ?` + row := d.QueryRowContext(ctx, query, name) + + var service model.Service + var depsJSON string + if err := row.Scan(&service.Name, &depsJSON); err != nil { + if err == sql.ErrNoRows { + return nil, nil + } + return nil, err + } + + if depsJSON != "" { + if err := json.Unmarshal([]byte(depsJSON), &service.Deps); err != nil { + return nil, err + } + } + + return &service, nil +} + +// CreateService 创建服务 +func (d *Database) CreateService(ctx context.Context, service *model.Service) error { + depsJSON, err := json.Marshal(service.Deps) + if err != nil { + return err + } + + query := `INSERT INTO services (name, deps) VALUES (?, ?)` + _, err = d.ExecContext(ctx, query, service.Name, string(depsJSON)) + return err +} + +// UpdateService 更新服务信息 +func (d *Database) UpdateService(ctx context.Context, service *model.Service) error { + depsJSON, err := json.Marshal(service.Deps) + if err != nil { + return err + } + + query := `UPDATE services SET deps = ? WHERE name = ?` + _, err = d.ExecContext(ctx, query, string(depsJSON), service.Name) + return err +} + +// DeleteService 删除服务 +func (d *Database) DeleteService(ctx context.Context, name string) error { + query := `DELETE FROM services WHERE name = ?` + _, err := d.ExecContext(ctx, query, name) + return err +} + +// ===== 服务版本操作 ===== + +// GetServiceVersions 获取服务版本列表 +func (d *Database) GetServiceVersions(ctx context.Context, serviceName string) ([]model.ServiceVersion, error) { + query := `SELECT version, service, create_time FROM service_versions WHERE service = ? ORDER BY create_time DESC` + rows, err := d.QueryContext(ctx, query, serviceName) + if err != nil { + return nil, err + } + defer rows.Close() + + var versions []model.ServiceVersion + for rows.Next() { + var version model.ServiceVersion + if err := rows.Scan(&version.Version, &version.Service, &version.CreateTime); err != nil { + return nil, err + } + versions = append(versions, version) + } + + return versions, rows.Err() +} + +// CreateServiceVersion 创建服务版本 +func (d *Database) CreateServiceVersion(ctx context.Context, version *model.ServiceVersion) error { + query := `INSERT INTO service_versions (version, service, create_time) VALUES (?, ?, ?)` + _, err := d.ExecContext(ctx, query, version.Version, version.Service, version.CreateTime) + return err +} + +// ===== 服务实例操作 ===== + +// GetServiceInstances 获取服务实例列表 +func (d *Database) GetServiceInstances(ctx context.Context, serviceName string) ([]model.ServiceInstance, error) { + query := `SELECT id, service, version FROM service_instances WHERE service = ?` + rows, err := d.QueryContext(ctx, query, serviceName) + if err != nil { + return nil, err + } + defer rows.Close() + + var instances []model.ServiceInstance + for rows.Next() { + var instance model.ServiceInstance + if err := rows.Scan(&instance.ID, &instance.Service, &instance.Version); err != nil { + return nil, err + } + instances = append(instances, instance) + } + + return instances, rows.Err() +} + +// CreateServiceInstance 创建服务实例 +func (d *Database) CreateServiceInstance(ctx context.Context, instance *model.ServiceInstance) error { + query := `INSERT INTO service_instances (id, service, version) VALUES (?, ?, ?)` + _, err := d.ExecContext(ctx, query, instance.ID, instance.Service, instance.Version) + return err +} + +// ===== 服务状态操作 ===== + +// GetServiceState 获取服务状态 +func (d *Database) GetServiceState(ctx context.Context, serviceName string) (*model.ServiceState, error) { + query := `SELECT service, version, level, report_at, resolved_at, health_status, exception_status, details + FROM service_states WHERE service = ? ORDER BY report_at DESC LIMIT 1` + row := d.QueryRowContext(ctx, query, serviceName) + + var state model.ServiceState + if err := row.Scan(&state.Service, &state.Version, &state.Level, &state.ReportAt, + &state.ResolvedAt, &state.HealthStatus, &state.ExceptionStatus, &state.Details); err != nil { + if err == sql.ErrNoRows { + return nil, nil + } + return nil, err + } + + return &state, nil +} + +// CreateServiceState 创建服务状态记录 +func (d *Database) CreateServiceState(ctx context.Context, state *model.ServiceState) error { + query := `INSERT INTO service_states (service, version, level, report_at, resolved_at, health_status, exception_status, details) + VALUES (?, ?, ?, ?, ?, ?, ?, ?)` + _, err := d.ExecContext(ctx, query, state.Service, state.Version, state.Level, state.ReportAt, + state.ResolvedAt, state.HealthStatus, state.ExceptionStatus, state.Details) + return err +} diff --git a/internal/service_manager/model/api.go b/internal/service_manager/model/api.go new file mode 100644 index 0000000..8093cd4 --- /dev/null +++ b/internal/service_manager/model/api.go @@ -0,0 +1,90 @@ +package model + +import "time" + +// ===== 服务基础信息结构体 ===== + +// ServiceItem API响应用的服务信息(对应/v1/services接口items格式) +type ServiceItem struct { + Name string `json:"name"` // 服务名称 + DeployState DeployStatus `json:"deployState"` // 发布状态:InDeploying|AllDeployFinish + Health HealthStatus `json:"health"` // 健康状态:Normal/Warning/Error + Deps []string `json:"deps"` // 依赖关系(直接使用Service.Deps) +} + +// ServicesResponse 服务列表API响应(对应/v1/services接口) +type ServicesResponse struct { + Items []ServiceItem `json:"items"` + Relation map[string][]string `json:"relation"` // 树形关系描述,有向无环图 +} + +// ActiveVersionItem 活跃版本项目 +type ActiveVersionItem struct { + Version string `json:"version"` // v1.0.1 + DeployID string `json:"deployID"` // 1001 + StartTime time.Time `json:"startTime"` // 开始时间 + EstimatedCompletionTime time.Time `json:"estimatedCompletionTime"` // 预估完成时间 + Instances int `json:"instances"` // 实例个数 + Health HealthStatus `json:"health"` // 健康状态:Normal/Warning/Error +} + +// PrometheusQueryRangeResponse Prometheus query_range接口响应格式 +type PrometheusQueryRangeResponse struct { + Status string `json:"status"` + Data PrometheusQueryRangeData `json:"data"` +} + +// PrometheusQueryRangeData Prometheus响应数据 +type PrometheusQueryRangeData struct { + ResultType string `json:"resultType"` + Result []PrometheusTimeSeries `json:"result"` +} + +// PrometheusTimeSeries Prometheus时序数据 +type PrometheusTimeSeries struct { + Metric map[string]string `json:"metric"` + Values [][]any `json:"values"` // [timestamp, value]数组 +} + +// MetricTimeSeriesQuery 时序指标查询参数 +type MetricTimeSeriesQuery struct { + Service string `form:"service" binding:"required"` + Name string `form:"name" binding:"required"` + Version string `form:"version,omitempty"` + Start string `form:"start" binding:"required"` // RFC3339格式时间 + End string `form:"end" binding:"required"` // RFC3339格式时间 + Granule string `form:"granule,omitempty"` // 1m/5m/1h等 +} + +// ===== 部署任务操作结构体 ===== + +// Deployment API响应用的发布任务 +type Deployment struct { + ID string `json:"id"` + Service string `json:"service"` + Version string `json:"version"` + Status DeployState `json:"status"` + ScheduleTime *time.Time `json:"scheduleTime,omitempty"` + FinishTime *time.Time `json:"finishTime,omitempty"` +} + +// CreateDeploymentRequest 创建发布任务请求 +type CreateDeploymentRequest struct { + Service string `json:"service" binding:"required"` + Version string `json:"version" binding:"required"` + ScheduleTime *time.Time `json:"scheduleTime,omitempty"` // 可选参数,不填为立即发布 +} + +// UpdateDeploymentRequest 修改发布任务请求 +type UpdateDeploymentRequest struct { + Version string `json:"version,omitempty"` + ScheduleTime *time.Time `json:"scheduleTime,omitempty"` // 新的计划发布时间 +} + +// DeploymentQuery 发布任务查询参数 +type DeploymentQuery struct { + Type DeployState `form:"type"` // deploying/stop/rollback/completed + Service string `form:"service"` // 服务名称过滤 + Start string `form:"start"` // 分页起始 + Limit int `form:"limit"` // 分页大小 +} diff --git a/internal/service_manager/model/constants.go b/internal/service_manager/model/constants.go new file mode 100644 index 0000000..827a030 --- /dev/null +++ b/internal/service_manager/model/constants.go @@ -0,0 +1,39 @@ +package model + +// ExceptionStatus 异常处理状态枚举 +type ExceptionStatus string + +const ( + ExceptionStatusNew ExceptionStatus = "new" + ExceptionStatusAnalyzing ExceptionStatus = "analyzing" + ExceptionStatusProcessing ExceptionStatus = "processing" + ExceptionStatusResolved ExceptionStatus = "resolved" +) + +// HealthStatus 健康状态枚举 +type HealthStatus string + +const ( + HealthStatusNormal HealthStatus = "Normal" + HealthStatusWarning HealthStatus = "Warning" + HealthStatusError HealthStatus = "Error" +) + +// DeployStatus 部署状态枚举 +type DeployStatus string + +const ( + DeployStatusInDeploying DeployStatus = "InDeploying" + DeployStatusAllDeployFinish DeployStatus = "AllDeployFinish" +) + +// DeployState 发布状态枚举 +type DeployState string + +const ( + StatusUnrelease DeployState = "unrelease" // 未发布/待发布 + StatusDeploying DeployState = "deploying" // 正在发布 + StatusStop DeployState = "stop" // 暂停发布 + StatusRollback DeployState = "rollback" // 已回滚 + StatusCompleted DeployState = "completed" // 发布完成 +) diff --git a/internal/service_manager/model/deploy_batch.go b/internal/service_manager/model/deploy_batch.go new file mode 100644 index 0000000..59aad15 --- /dev/null +++ b/internal/service_manager/model/deploy_batch.go @@ -0,0 +1,14 @@ +package model + +import "time" + +// DeployBatch 部署批次信息 +type DeployBatch struct { + ID int64 `json:"id" db:"id"` // bigint - 主键 + DeployID int64 `json:"deployId" db:"deploy_id"` // bigint - 外键 + BatchID string `json:"batchId" db:"batch_id"` // varchar(255) - 批次ID + StartTime *time.Time `json:"startTime" db:"start_time"` // datetime + EndTime *time.Time `json:"endTime" db:"end_time"` // datetime + TargetRatio float64 `json:"targetRatio" db:"target_ratio"` // double + NodeIDs []string `json:"nodeIds" db:"node_ids"` // 数组格式的节点ID列表 +} diff --git a/internal/service_manager/model/deploy_task.go b/internal/service_manager/model/deploy_task.go new file mode 100644 index 0000000..5a6b806 --- /dev/null +++ b/internal/service_manager/model/deploy_task.go @@ -0,0 +1,15 @@ +package model + +import "time" + +// ServiceDeployTask 服务部署任务信息 +type ServiceDeployTask struct { + ID int64 `json:"id" db:"id"` // bigint - 主键 + Service string `json:"service" db:"service"` // varchar(255) - 外键引用services.name + Version string `json:"version" db:"version"` // varchar(255) - 外键引用service_versions.version + TaskCreator string `json:"taskCreator" db:"task_creator"` // varchar(255) + DeployBeginTime *time.Time `json:"deployBeginTime" db:"deploy_begin_time"` // datetime + DeployEndTime *time.Time `json:"deployEndTime" db:"deploy_end_time"` // datetime + DeployState DeployState `json:"deployState" db:"deploy_state"` // 部署状态 + CorrelationID string `json:"correlationId" db:"correlation_id"` // varchar(255) +} diff --git a/internal/service_manager/model/service.go b/internal/service_manager/model/service.go new file mode 100644 index 0000000..8ab098c --- /dev/null +++ b/internal/service_manager/model/service.go @@ -0,0 +1,7 @@ +package model + +// Service 服务基础信息 +type Service struct { + Name string `json:"name" db:"name"` // varchar(255) - 主键 + Deps []string `json:"deps" db:"deps"` // 依赖关系 +} diff --git a/internal/service_manager/model/service_instance.go b/internal/service_manager/model/service_instance.go new file mode 100644 index 0000000..dc099af --- /dev/null +++ b/internal/service_manager/model/service_instance.go @@ -0,0 +1,8 @@ +package model + +// ServiceInstance 服务实例信息 +type ServiceInstance struct { + ID string `json:"id" db:"id"` // 主键 + Service string `json:"service" db:"service"` // varchar(255) - 外键引用services.name + Version string `json:"version" db:"version"` // varchar(255) - 外键引用service_versions.version +} diff --git a/internal/service_manager/model/service_state.go b/internal/service_manager/model/service_state.go new file mode 100644 index 0000000..201f318 --- /dev/null +++ b/internal/service_manager/model/service_state.go @@ -0,0 +1,15 @@ +package model + +import "time" + +// ServiceState 服务状态信息 +type ServiceState struct { + Service string `json:"service" db:"service"` // varchar(255) - 外键引用services.name + Version string `json:"version" db:"version"` // varchar(255) - 外键引用service_versions.version + Level string `json:"level" db:"level"` // varchar(255) - 异常级别 + ReportAt time.Time `json:"reportAt" db:"report_at"` // datetime - 报告时间 + ResolvedAt *time.Time `json:"resolvedAt" db:"resolved_at"` // datetime - 解决时间(可为null) + HealthStatus HealthStatus `json:"healthStatus" db:"health_status"` // varchar(255) - 健康状态 + ExceptionStatus ExceptionStatus `json:"exceptionStatus" db:"exception_status"` // varchar(255) - 异常处理状态 + Details string `json:"details" db:"details"` // text - JSON格式的详细信息 +} diff --git a/internal/service_manager/model/service_version.go b/internal/service_manager/model/service_version.go new file mode 100644 index 0000000..0f5e149 --- /dev/null +++ b/internal/service_manager/model/service_version.go @@ -0,0 +1,10 @@ +package model + +import "time" + +// ServiceVersion 服务版本信息 +type ServiceVersion struct { + Version string `json:"version" db:"version"` // varchar(255) - 主键 + Service string `json:"service" db:"service"` // varchar(255) - 外键引用services.name + CreateTime time.Time `json:"createTime" db:"create_time"` // 时间戳字段 +} diff --git a/internal/service_manager/service/service.go b/internal/service_manager/service/base.go similarity index 100% rename from internal/service_manager/service/service.go rename to internal/service_manager/service/base.go diff --git a/internal/service_manager/service/deploy_service.go b/internal/service_manager/service/deploy_service.go new file mode 100644 index 0000000..565481d --- /dev/null +++ b/internal/service_manager/service/deploy_service.go @@ -0,0 +1,224 @@ +package service + +import ( + "context" + + "github.com/qiniu/zeroops/internal/service_manager/model" + "github.com/rs/zerolog/log" +) + +// ===== 部署管理业务方法 ===== + +// CreateDeployment 创建发布任务 +func (s *Service) CreateDeployment(ctx context.Context, req *model.CreateDeploymentRequest) (string, error) { + // 检查服务是否存在 + service, err := s.db.GetServiceByName(ctx, req.Service) + if err != nil { + return "", err + } + if service == nil { + return "", ErrServiceNotFound + } + + // 检查发布冲突 + conflict, err := s.db.CheckDeploymentConflict(ctx, req.Service, req.Version) + if err != nil { + return "", err + } + if conflict { + return "", ErrDeploymentConflict + } + + // 创建发布任务 + deployID, err := s.db.CreateDeployment(ctx, req) + if err != nil { + return "", err + } + + log.Info(). + Str("deployID", deployID). + Str("service", req.Service). + Str("version", req.Version). + Msg("deployment created successfully") + + return deployID, nil +} + +// GetDeploymentByID 获取发布任务详情 +func (s *Service) GetDeploymentByID(ctx context.Context, deployID string) (*model.Deployment, error) { + deployment, err := s.db.GetDeploymentByID(ctx, deployID) + if err != nil { + return nil, err + } + if deployment == nil { + return nil, ErrDeploymentNotFound + } + return deployment, nil +} + +// GetDeployments 获取发布任务列表 +func (s *Service) GetDeployments(ctx context.Context, query *model.DeploymentQuery) ([]model.Deployment, error) { + return s.db.GetDeployments(ctx, query) +} + +// UpdateDeployment 修改发布任务 +func (s *Service) UpdateDeployment(ctx context.Context, deployID string, req *model.UpdateDeploymentRequest) error { + // 检查部署任务是否存在 + deployment, err := s.db.GetDeploymentByID(ctx, deployID) + if err != nil { + return err + } + if deployment == nil { + return ErrDeploymentNotFound + } + + // 只有unrelease状态的任务可以修改 + if deployment.Status != model.StatusUnrelease { + return ErrInvalidDeployState + } + + err = s.db.UpdateDeployment(ctx, deployID, req) + if err != nil { + return err + } + + log.Info(). + Str("deployID", deployID). + Str("service", deployment.Service). + Msg("deployment updated successfully") + + return nil +} + +// DeleteDeployment 删除发布任务 +func (s *Service) DeleteDeployment(ctx context.Context, deployID string) error { + // 检查部署任务是否存在 + deployment, err := s.db.GetDeploymentByID(ctx, deployID) + if err != nil { + return err + } + if deployment == nil { + return ErrDeploymentNotFound + } + + // 只有未开始的任务可以删除 + if deployment.Status != model.StatusUnrelease { + return ErrInvalidDeployState + } + + err = s.db.DeleteDeployment(ctx, deployID) + if err != nil { + return err + } + + log.Info(). + Str("deployID", deployID). + Str("service", deployment.Service). + Msg("deployment deleted successfully") + + return nil +} + +// PauseDeployment 暂停发布任务 +func (s *Service) PauseDeployment(ctx context.Context, deployID string) error { + // 检查部署任务是否存在且为正在部署状态 + deployment, err := s.db.GetDeploymentByID(ctx, deployID) + if err != nil { + return err + } + if deployment == nil { + return ErrDeploymentNotFound + } + if deployment.Status != model.StatusDeploying { + return ErrInvalidDeployState + } + + err = s.db.PauseDeployment(ctx, deployID) + if err != nil { + return err + } + + log.Info(). + Str("deployID", deployID). + Str("service", deployment.Service). + Msg("deployment paused successfully") + + return nil +} + +// ContinueDeployment 继续发布任务 +func (s *Service) ContinueDeployment(ctx context.Context, deployID string) error { + // 检查部署任务是否存在且为暂停状态 + deployment, err := s.db.GetDeploymentByID(ctx, deployID) + if err != nil { + return err + } + if deployment == nil { + return ErrDeploymentNotFound + } + if deployment.Status != model.StatusStop { + return ErrInvalidDeployState + } + + err = s.db.ContinueDeployment(ctx, deployID) + if err != nil { + return err + } + + log.Info(). + Str("deployID", deployID). + Str("service", deployment.Service). + Msg("deployment continued successfully") + + return nil +} + +// RollbackDeployment 回滚发布任务 +func (s *Service) RollbackDeployment(ctx context.Context, deployID string) error { + // 检查部署任务是否存在 + deployment, err := s.db.GetDeploymentByID(ctx, deployID) + if err != nil { + return err + } + if deployment == nil { + return ErrDeploymentNotFound + } + + // 只有正在部署或暂停的任务可以回滚 + if deployment.Status != model.StatusDeploying && deployment.Status != model.StatusStop { + return ErrInvalidDeployState + } + + err = s.db.RollbackDeployment(ctx, deployID) + if err != nil { + return err + } + + log.Info(). + Str("deployID", deployID). + Str("service", deployment.Service). + Msg("deployment rolled back successfully") + + return nil +} + +// GetDeployBatches 获取部署批次列表 +func (s *Service) GetDeployBatches(ctx context.Context, deployID string) ([]model.DeployBatch, error) { + // 先验证部署任务存在 + deployment, err := s.db.GetDeploymentByID(ctx, deployID) + if err != nil { + return nil, err + } + if deployment == nil { + return nil, ErrDeploymentNotFound + } + + // TODO:将deployID转换为int (这里简化处理) + // 实际项目中应该在数据库层统一ID类型 + return s.db.GetDeployBatches(ctx, 1) // 临时硬编码,需要根据实际deployID转换 +} + +// CreateDeployBatch 创建部署批次 +func (s *Service) CreateDeployBatch(ctx context.Context, batch *model.DeployBatch) error { + return s.db.CreateDeployBatch(ctx, batch) +} diff --git a/internal/service_manager/service/errors.go b/internal/service_manager/service/errors.go new file mode 100644 index 0000000..6def905 --- /dev/null +++ b/internal/service_manager/service/errors.go @@ -0,0 +1,11 @@ +package service + +import "errors" + +// 业务错误定义 +var ( + ErrDeploymentConflict = errors.New("deployment conflict: service version already in deployment") + ErrServiceNotFound = errors.New("service not found") + ErrDeploymentNotFound = errors.New("deployment not found") + ErrInvalidDeployState = errors.New("invalid deployment state") +) diff --git a/internal/service_manager/service/info_service.go b/internal/service_manager/service/info_service.go new file mode 100644 index 0000000..053c3d2 --- /dev/null +++ b/internal/service_manager/service/info_service.go @@ -0,0 +1,184 @@ +package service + +import ( + "context" + + "github.com/qiniu/zeroops/internal/service_manager/model" + "github.com/rs/zerolog/log" +) + +// ===== 服务管理业务方法 ===== + +// GetServicesResponse 获取服务列表响应 +func (s *Service) GetServicesResponse(ctx context.Context) (*model.ServicesResponse, error) { + services, err := s.db.GetServices(ctx) + if err != nil { + return nil, err + } + + items := make([]model.ServiceItem, len(services)) + relation := make(map[string][]string) + + for i, service := range services { + // 获取服务状态来确定健康状态 + state, err := s.db.GetServiceState(ctx, service.Name) + if err != nil { + log.Error().Err(err).Str("service", service.Name).Msg("failed to get service state") + } + + health := model.HealthStatusNormal + if state != nil { + health = state.HealthStatus + } + + // 默认设置为已完成部署状态 + deployState := model.DeployStatusAllDeployFinish + + items[i] = model.ServiceItem{ + Name: service.Name, + DeployState: deployState, + Health: health, + Deps: service.Deps, + } + + // 构建依赖关系图 + if len(service.Deps) > 0 { + relation[service.Name] = service.Deps + } + } + + return &model.ServicesResponse{ + Items: items, + Relation: relation, + }, nil +} + +// GetServiceActiveVersions 获取服务活跃版本 +func (s *Service) GetServiceActiveVersions(ctx context.Context, serviceName string) ([]model.ActiveVersionItem, error) { + instances, err := s.db.GetServiceInstances(ctx, serviceName) + if err != nil { + return nil, err + } + + // 按版本分组统计实例 + versionMap := make(map[string][]model.ServiceInstance) + for _, instance := range instances { + versionMap[instance.Version] = append(versionMap[instance.Version], instance) + } + + var activeVersions []model.ActiveVersionItem + for version, versionInstances := range versionMap { + // 获取服务状态 + state, err := s.db.GetServiceState(ctx, serviceName) + if err != nil { + log.Error().Err(err).Str("service", serviceName).Msg("failed to get service state") + } + + health := model.HealthStatusNormal + reportAt := &model.ServiceState{} + if state != nil { + health = state.HealthStatus + reportAt = state + } + + activeVersion := model.ActiveVersionItem{ + Version: version, + DeployID: "1001", // TODO:临时值,实际需要从部署任务中获取 + StartTime: reportAt.ReportAt, + EstimatedCompletionTime: reportAt.ReportAt, + Instances: len(versionInstances), + Health: health, + } + + activeVersions = append(activeVersions, activeVersion) + } + + return activeVersions, nil +} + +// GetServiceAvailableVersions 获取可用服务版本 +func (s *Service) GetServiceAvailableVersions(ctx context.Context, serviceName, versionType string) ([]model.ServiceVersion, error) { + // 获取所有版本 + versions, err := s.db.GetServiceVersions(ctx, serviceName) + if err != nil { + return nil, err + } + + // TODO:根据类型过滤(这里简化处理,实际需要根据业务需求过滤) + if versionType == "unrelease" { + // 返回未发布的版本,这里简化返回所有版本 + return versions, nil + } + + return versions, nil +} + +// CreateService 创建服务 +func (s *Service) CreateService(ctx context.Context, service *model.Service) error { + return s.db.CreateService(ctx, service) +} + +// GetServiceByName 根据名称获取服务 +func (s *Service) GetServiceByName(ctx context.Context, name string) (*model.Service, error) { + service, err := s.db.GetServiceByName(ctx, name) + if err != nil { + return nil, err + } + if service == nil { + return nil, ErrServiceNotFound + } + return service, nil +} + +// UpdateService 更新服务信息 +func (s *Service) UpdateService(ctx context.Context, service *model.Service) error { + return s.db.UpdateService(ctx, service) +} + +// DeleteService 删除服务 +func (s *Service) DeleteService(ctx context.Context, name string) error { + return s.db.DeleteService(ctx, name) +} + +// GetServiceMetricTimeSeries 获取服务时序指标数据 +func (s *Service) GetServiceMetricTimeSeries(ctx context.Context, serviceName, metricName string, query *model.MetricTimeSeriesQuery) (*model.PrometheusQueryRangeResponse, error) { + // TODO:这里应该调用实际的Prometheus或其他监控系统API + // 现在返回模拟数据 + + response := &model.PrometheusQueryRangeResponse{ + Status: "success", + Data: model.PrometheusQueryRangeData{ + ResultType: "matrix", + Result: []model.PrometheusTimeSeries{ + { + Metric: map[string]string{ + "__name__": metricName, + "service": serviceName, + "instance": "instance-1", + "version": query.Version, + }, + Values: [][]any{ + {1435781430.781, "1.2"}, + {1435781445.781, "1.5"}, + {1435781460.781, "1.1"}, + }, + }, + { + Metric: map[string]string{ + "__name__": metricName, + "service": serviceName, + "instance": "instance-2", + "version": query.Version, + }, + Values: [][]any{ + {1435781430.781, "0.8"}, + {1435781445.781, "0.9"}, + {1435781460.781, "1.0"}, + }, + }, + }, + }, + } + + return response, nil +} diff --git a/release-system/go.mod b/release-system/go.mod deleted file mode 100644 index 77ab30e..0000000 --- a/release-system/go.mod +++ /dev/null @@ -1,3 +0,0 @@ -module release-system - -go 1.24 diff --git a/release-system/internal/model/adapter/external.go b/release-system/internal/model/adapter/external.go deleted file mode 100644 index c06c16d..0000000 --- a/release-system/internal/model/adapter/external.go +++ /dev/null @@ -1,120 +0,0 @@ -package adapter - -import "time" - -// ExternalDeploySystem 公司发布系统接口定义 -type ExternalDeploySystem interface { - // CreateDeployment 调用公司发布系统创建发布任务 - CreateDeployment(req *ExternalDeployRequest) (*ExternalDeployResponse, error) - - // PauseDeployment 暂停发布任务 - PauseDeployment(deployID string) error - - // ContinueDeployment 继续发布任务 - ContinueDeployment(deployID string) error - - // RollbackDeployment 回滚发布任务 - RollbackDeployment(deployID string) error - - // GetDeploymentStatus 获取发布状态 - GetDeploymentStatus(deployID string) (*ExternalDeployStatus, error) -} - -// ExternalAlertSystem 公司告警系统接口定义 -type ExternalAlertSystem interface { - // SendAlert 发送告警 - SendAlert(alert *AlertRequest) error - - // GetAlerts 获取告警列表 - GetAlerts(query *AlertQuery) (*AlertResponse, error) -} - -// ExternalDeployRequest 公司发布系统请求 -type ExternalDeployRequest struct { - Service string `json:"service"` - Version string `json:"version"` - ScheduleTime *time.Time `json:"scheduleTime,omitempty"` - Config map[string]any `json:"config,omitempty"` -} - -// ExternalDeployResponse 公司发布系统响应 -type ExternalDeployResponse struct { - DeployID string `json:"deployId"` - Status string `json:"status"` - Message string `json:"message,omitempty"` -} - -// ExternalDeployStatus 公司发布状态 -type ExternalDeployStatus struct { - DeployID string `json:"deployId"` - Service string `json:"service"` - Version string `json:"version"` - Status string `json:"status"` - Progress float64 `json:"progress"` // 发布进度 0-100 - StartTime *time.Time `json:"startTime,omitempty"` - FinishTime *time.Time `json:"finishTime,omitempty"` - ErrorMessage string `json:"errorMessage,omitempty"` -} - -// AlertRequest 告警请求 -type AlertRequest struct { - Title string `json:"title"` - Content string `json:"content"` - Level string `json:"level"` // Info/Warning/Error/Critical - Service string `json:"service"` - DeployID string `json:"deployId,omitempty"` - Labels map[string]string `json:"labels,omitempty"` - Annotations map[string]any `json:"annotations,omitempty"` -} - -// AlertQuery 告警查询参数 -type AlertQuery struct { - Service string `json:"service,omitempty"` - Level string `json:"level,omitempty"` - Start *time.Time `json:"start,omitempty"` - End *time.Time `json:"end,omitempty"` - Limit int `json:"limit,omitempty"` -} - -// AlertResponse 告警响应 -type AlertResponse struct { - Items []Alert `json:"items"` - Total int `json:"total"` -} - -// Alert 告警信息 -type Alert struct { - ID string `json:"id"` - Title string `json:"title"` - Content string `json:"content"` - Level string `json:"level"` - Service string `json:"service"` - Status string `json:"status"` // Active/Resolved - CreatedAt time.Time `json:"createdAt"` - ResolvedAt *time.Time `json:"resolvedAt,omitempty"` - Labels map[string]string `json:"labels,omitempty"` - Annotations map[string]any `json:"annotations,omitempty"` -} - -// ===== 数据库模型(对应ER图中的表) ===== - -// EventLog 事件日志数据库模型(对应event_logs表) -type EventLog struct { - ID int `json:"id" db:"id"` // bigint - 主键 - SourceSystem string `json:"sourceSystem" db:"source_system"` // varchar(255) - SourceID string `json:"sourceId" db:"source_id"` // varchar(255) - SourceName string `json:"sourceName" db:"source_name"` // varchar(255) - Timestamp time.Time `json:"timestamp" db:"timestamp"` // datetime - Actor string `json:"actor" db:"actor"` // varchar(255) - Severity string `json:"severity" db:"severity"` // varchar(255) - CorrelationID string `json:"correlationId" db:"correlation_id"` // varchar(255) - Payload string `json:"payload" db:"payload"` // text - JSON格式 -} - -// MetricAlertChange 指标告警变更数据库模型(对应metric_alert_changes表) -type MetricAlertChange struct { - ID int `json:"id" db:"id"` // bigint - 主键 - ChangeTime time.Time `json:"changeTime" db:"change_time"` // datetime - AlertName string `json:"alertName" db:"alert_name"` // varchar(255) - ChangeItem string `json:"changeItem" db:"change_item"` // text - 变更项内容 -} diff --git a/release-system/internal/model/deploy_task/deployment.go b/release-system/internal/model/deploy_task/deployment.go deleted file mode 100644 index 588688c..0000000 --- a/release-system/internal/model/deploy_task/deployment.go +++ /dev/null @@ -1,69 +0,0 @@ -package deploy_task - -import "time" - -// DeployState 发布状态枚举 -type DeployState string - -const ( - StatusDeploying DeployState = "deploying" - StatusStop DeployState = "stop" - StatusRollback DeployState = "rollback" - StatusCompleted DeployState = "completed" -) - -// DeployBatch 部署批次信息 -type DeployBatch struct { - ID int `json:"id" db:"id"` // bigint - 主键 - DeployID int `json:"deployId" db:"deploy_id"` // bigint - 外键 - BatchID string `json:"batchId" db:"batch_id"` // varchar(255) - 批次ID - StartTime *time.Time `json:"startTime" db:"start_time"` // datetime - EndTime *time.Time `json:"endTime" db:"end_time"` // datetime - TargetRatio float64 `json:"targetRatio" db:"target_ratio"` // double - NodeIDs []string `json:"nodeIds" db:"node_ids"` // 数组格式的节点ID列表 -} - -// ServiceDeployTask 服务部署任务信息 -type ServiceDeployTask struct { - ID int `json:"id" db:"id"` // bigint - 主键 - Service string `json:"service" db:"service"` // varchar(255) - 外键引用services.name - Version string `json:"version" db:"version"` // varchar(255) - 外键引用service_versions.version - TaskCreator string `json:"taskCreator" db:"task_creator"` // varchar(255) - DeployBeginTime *time.Time `json:"deployBeginTime" db:"deploy_begin_time"` // datetime - DeployEndTime *time.Time `json:"deployEndTime" db:"deploy_end_time"` // datetime - DeployState DeployState `json:"deployState" db:"deploy_state"` // 部署状态 - CorrelationID string `json:"correlationId" db:"correlation_id"` // varchar(255) -} - -// ===== API请求响应结构体 ===== - -// Deployment API响应用的发布任务 -type Deployment struct { - ID string `json:"id"` - Service string `json:"service"` - Version string `json:"version"` - Status DeployState `json:"status"` - ScheduleTime *time.Time `json:"scheduleTime,omitempty"` - FinishTime *time.Time `json:"finishTime,omitempty"` -} - -// CreateDeploymentRequest 创建发布任务请求 -type CreateDeploymentRequest struct { - Service string `json:"service" binding:"required"` - Version string `json:"version" binding:"required"` - ScheduleTime *time.Time `json:"scheduleTime,omitempty"` // 可选参数,不填为立即发布 -} - -// UpdateDeploymentRequest 修改发布任务请求 -type UpdateDeploymentRequest struct { - Version string `json:"version,omitempty"` - ScheduleTime *time.Time `json:"scheduleTime,omitempty"` // 新的计划发布时间 -} - -// DeploymentQuery 发布任务查询参数 -type DeploymentQuery struct { - Type DeployState `form:"type"` // deploying/stop/rollback/completed - Service string `form:"service"` // 服务名称过滤 - Start string `form:"start"` // 分页起始 - Limit int `form:"limit"` // 分页大小 -} diff --git a/release-system/internal/model/service_info/service.go b/release-system/internal/model/service_info/service.go deleted file mode 100644 index 24df9ef..0000000 --- a/release-system/internal/model/service_info/service.go +++ /dev/null @@ -1,111 +0,0 @@ -package service_info - -import "time" - -// ExceptionStatus 异常处理状态枚举 -type ExceptionStatus string - -const ( - ExceptionStatusNew ExceptionStatus = "new" - ExceptionStatusAnalyzing ExceptionStatus = "analyzing" - ExceptionStatusProcessing ExceptionStatus = "processing" - ExceptionStatusResolved ExceptionStatus = "resolved" -) - -// HealthStatus 健康状态枚举 -type HealthStatus string - -const ( - HealthStatusNormal HealthStatus = "Normal" - HealthStatusWarning HealthStatus = "Warning" - HealthStatusError HealthStatus = "Error" -) - -// DeployStatus 部署状态枚举 -type DeployStatus string - -const ( - DeployStatusInDeploying DeployStatus = "InDeploying" - DeployStatusAllDeployFinish DeployStatus = "AllDeployFinish" -) - -// Service 服务基础信息 -type Service struct { - Name string `json:"name" db:"name"` // varchar(255) - 主键 - Deps []string `json:"deps" db:"deps"` // 依赖关系 -} - -// ServiceInstance 服务实例信息 -type ServiceInstance struct { - ID string `json:"id" db:"id"` // 主键 - Service string `json:"service" db:"service"` // varchar(255) - 外键引用services.name - Version string `json:"version" db:"version"` // varchar(255) - 外键引用service_versions.version -} - -// ServiceVersion 服务版本信息 -type ServiceVersion struct { - Version string `json:"version" db:"version"` // varchar(255) - 主键 - Service string `json:"service" db:"service"` // varchar(255) - 外键引用services.name - CreateTime time.Time `json:"createTime" db:"create_time"` // 时间戳字段 -} - -// ServiceState 服务状态信息 -type ServiceState struct { - Service string `json:"service" db:"service"` // varchar(255) - 外键引用services.name - Version string `json:"version" db:"version"` // varchar(255) - 外键引用service_versions.version - Level string `json:"level" db:"level"` // varchar(255) - 异常级别 - ReportAt time.Time `json:"reportAt" db:"report_at"` // datetime - 报告时间 - ResolvedAt *time.Time `json:"resolvedAt" db:"resolved_at"` // datetime - 解决时间(可为null) - HealthStatus HealthStatus `json:"healthStatus" db:"health_status"` // varchar(255) - 健康状态 - ExceptionStatus ExceptionStatus `json:"exceptionStatus" db:"exception_status"` // varchar(255) - 异常处理状态 - Details string `json:"details" db:"details"` // text - JSON格式的详细信息 -} - -// ===== API响应结构体 ===== - -// ServiceItem API响应用的服务信息(对应/v1/services接口items格式) -type ServiceItem struct { - Name string `json:"name"` // 服务名称 - DeployState DeployStatus `json:"deployState"` // 发布状态:InDeploying|AllDeployFinish - Health HealthStatus `json:"health"` // 健康状态:Normal/Warning/Error - Deps []string `json:"deps"` // 依赖关系(直接使用Service.Deps) -} - -// ServicesResponse 服务列表API响应(对应/v1/services接口) -type ServicesResponse struct { - Items []ServiceItem `json:"items"` - Relation map[string][]string `json:"relation"` // 树形关系描述,有向无环图 -} - -// ActiveVersionItem 活跃版本项目 -type ActiveVersionItem struct { - Version string `json:"version"` // v1.0.1 - DeployID string `json:"deployID"` // 1001 - StartTime time.Time `json:"startTime"` // 开始时间 - EstimatedCompletionTime time.Time `json:"estimatedCompletionTime"` // 预估完成时间 - Instances int `json:"instances"` // 实例个数 - Health HealthStatus `json:"health"` // 健康状态:Normal/Warning/Error -} - -// MetricStats 服务指标统计(对应/v1/services/:service/metricStats接口) -type MetricStats struct { - Summary MetricSummary `json:"summary"` // 所有实例的聚合值 - Items []MetricVersionItem `json:"items"` // 各版本的指标内容 -} - -// MetricSummary 指标汇总 -type MetricSummary struct { - Metrics []Metric `json:"metrics"` // 此版本发布的metric指标内容 -} - -// MetricVersionItem 版本指标项 -type MetricVersionItem struct { - Version string `json:"version"` // v1.0.1 - Metrics []Metric `json:"metrics"` // 此版本发布的metric指标内容 -} - -// Metric 指标 -type Metric struct { - Name string `json:"name"` // latency/traffic/errorRatio/saturation - Value any `json:"value"` // 指标值(ms/Qps/百分比) -} diff --git a/release-system/internal/repository/deployment_repository.go b/release-system/internal/repository/deployment_repository.go deleted file mode 100644 index f747df1..0000000 --- a/release-system/internal/repository/deployment_repository.go +++ /dev/null @@ -1,39 +0,0 @@ -package repository - -import ( - "context" - "release-system/internal/model/deploy_task" -) - -// DeploymentRepository 发布任务仓储接口 -type DeploymentRepository interface { - // ===== API方法 ===== - - // CreateDeployment 创建发布任务(POST /v1/deployments) - // 直接返回部署任务ID - CreateDeployment(ctx context.Context, req *deploy_task.CreateDeploymentRequest) (string, error) - - // GetDeploymentByID 根据ID获取发布任务详情(GET /v1/deployments/:deployID) - GetDeploymentByID(ctx context.Context, deployID string) (*deploy_task.Deployment, error) - - // GetDeployments 获取发布任务列表(GET /v1/deployments?type=Schedule&service=xxx) - GetDeployments(ctx context.Context, query *deploy_task.DeploymentQuery) ([]deploy_task.Deployment, error) - - // UpdateDeployment 修改未开始的发布任务(POST /v1/deployments/:deployID) - UpdateDeployment(ctx context.Context, deployID string, req *deploy_task.UpdateDeploymentRequest) error - - // DeleteDeployment 删除未开始的发布任务(DELETE /v1/deployments/:deployID) - DeleteDeployment(ctx context.Context, deployID string) error - - // PauseDeployment 暂停正在灰度的发布任务(POST /v1/deployments/:deployID/pause) - PauseDeployment(ctx context.Context, deployID string) error - - // ContinueDeployment 继续发布(POST /v1/deployments/:deployID/continue) - ContinueDeployment(ctx context.Context, deployID string) error - - // RollbackDeployment 回滚发布任务(POST /v1/deployments/:deployID/rollback) - RollbackDeployment(ctx context.Context, deployID string) error - - // CheckDeploymentConflict 检查发布冲突(同一服务同一版本是否已在发布) - CheckDeploymentConflict(ctx context.Context, service, version string) (bool, error) -} diff --git a/release-system/internal/repository/service_repository.go b/release-system/internal/repository/service_repository.go deleted file mode 100644 index 998079c..0000000 --- a/release-system/internal/repository/service_repository.go +++ /dev/null @@ -1,22 +0,0 @@ -package repository - -import ( - "context" - "release-system/internal/model/service_info" -) - -// ServiceRepository 服务信息仓储接口 -type ServiceRepository interface { - // GetServicesForAPI 获取所有服务列表(GET /v1/services) - GetServicesForAPI(ctx context.Context) (*service_info.ServicesResponse, error) - - // GetServiceActiveVersionsForAPI 获取服务活跃版本(GET /v1/services/:service/activeVersions) - GetServiceActiveVersionsForAPI(ctx context.Context, serviceName string) ([]service_info.ActiveVersionItem, error) - - // GetServiceAvailableVersions 获取可用服务版本(GET /v1/services/:service/availableVersions?type=unrelease) - // 直接返回ServiceVersion列表,API层负责包装响应格式 - GetServiceAvailableVersions(ctx context.Context, serviceName string, versionType string) ([]service_info.ServiceVersion, error) - - // GetServiceMetrics 获取服务指标数据(GET /v1/services/:service/metricStats) - GetServiceMetrics(ctx context.Context, serviceName string) (*service_info.MetricStats, error) -} diff --git a/release-system/internal/service/.gitkeep b/release-system/internal/service/.gitkeep deleted file mode 100644 index e69de29..0000000 From be8ff6cec87993c091e6aaf773c454b7284565c0 Mon Sep 17 00:00:00 2001 From: dnj Date: Thu, 11 Sep 2025 17:36:22 +0800 Subject: [PATCH 3/3] =?UTF-8?q?refactor(api):=20=E4=BD=BF=E7=94=A8?= =?UTF-8?q?=E9=94=99=E8=AF=AF=E5=B8=B8=E9=87=8F=E6=9B=BF=E4=BB=A3=E5=AD=97?= =?UTF-8?q?=E7=AC=A6=E4=B8=B2=E6=AF=94=E8=BE=83=E4=BB=A5=E6=8F=90=E9=AB=98?= =?UTF-8?q?=E5=8F=AF=E7=BB=B4=E6=8A=A4=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/service_manager/api/info_api.go | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/internal/service_manager/api/info_api.go b/internal/service_manager/api/info_api.go index 7f45719..ba2b704 100644 --- a/internal/service_manager/api/info_api.go +++ b/internal/service_manager/api/info_api.go @@ -5,6 +5,7 @@ import ( "github.com/fox-gonic/fox" "github.com/qiniu/zeroops/internal/service_manager/model" + "github.com/qiniu/zeroops/internal/service_manager/service" "github.com/rs/zerolog/log" ) @@ -197,9 +198,9 @@ func (api *Api) GetServiceByName(c *fox.Context) { return } - service, err := api.service.GetServiceByName(ctx, serviceName) + svc, err := api.service.GetServiceByName(ctx, serviceName) if err != nil { - if err.Error() == "service not found" { + if err == service.ErrServiceNotFound { c.JSON(http.StatusNotFound, map[string]any{ "error": "not found", "message": "service not found", @@ -214,7 +215,7 @@ func (api *Api) GetServiceByName(c *fox.Context) { return } - c.JSON(http.StatusOK, service) + c.JSON(http.StatusOK, svc) } // UpdateService 更新服务信息(PUT /v1/services/:service) @@ -230,8 +231,8 @@ func (api *Api) UpdateService(c *fox.Context) { return } - var service model.Service - if err := c.ShouldBindJSON(&service); err != nil { + var svc model.Service + if err := c.ShouldBindJSON(&svc); err != nil { c.JSON(http.StatusBadRequest, map[string]any{ "error": "bad request", "message": "invalid request body: " + err.Error(), @@ -240,10 +241,10 @@ func (api *Api) UpdateService(c *fox.Context) { } // 确保URL参数和请求体中的服务名一致 - service.Name = serviceName + svc.Name = serviceName - if err := api.service.UpdateService(ctx, &service); err != nil { - if err.Error() == "service not found" { + if err := api.service.UpdateService(ctx, &svc); err != nil { + if err == service.ErrServiceNotFound { c.JSON(http.StatusNotFound, map[string]any{ "error": "not found", "message": "service not found", @@ -278,7 +279,7 @@ func (api *Api) DeleteService(c *fox.Context) { } if err := api.service.DeleteService(ctx, serviceName); err != nil { - if err.Error() == "service not found" { + if err == service.ErrServiceNotFound { c.JSON(http.StatusNotFound, map[string]any{ "error": "not found", "message": "service not found",