// Package service 提供业务逻辑层 — 编排 repository、处理加密/校验/转换 // Handler 不直接调用 repository,通过 service 调用 package service import ( "encoding/json" "fmt" "math" "ezops/dto" "ezops/model" "ezops/pkg/crypto" ezredis "ezops/pkg/redis" "ezops/repository" "gorm.io/gorm" ) // ━━━ Server Service ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ type ServerService struct { repo *repository.ServerRepository cache ezredis.Cache crypt *crypto.SecretBox } func NewServerService(repo *repository.ServerRepository, cache ezredis.Cache, crypt *crypto.SecretBox) *ServerService { return &ServerService{repo: repo, cache: cache, crypt: crypt} } // ListServers 获取服务器列表 func (s *ServerService) ListServers(keyword, status, regionID string, page, pageSize int) ([]model.Server, int64, error) { list, total, err := s.repo.List(keyword, status, regionID, page, pageSize) if err != nil { return nil, 0, err } // 填充计算字段 for i := range list { list[i].HasAuth = list[i].Password != "" || list[i].SSHKey != "" } return list, total, nil } // GetServer 获取服务器详情 func (s *ServerService) GetServer(id uint) (*model.Server, error) { server, err := s.repo.GetByID(id) if err != nil { return nil, err } server.HasAuth = server.Password != "" || server.SSHKey != "" return server, nil } // CreateServer 创建服务器 — 加密凭证 func (s *ServerService) CreateServer(req *dto.CreateServerRequest) (*model.Server, error) { srv := &model.Server{ Hostname: req.Hostname, IP: req.IP, PublicIP: req.PublicIP, Port: req.Port, Username: req.Username, AuthType: req.AuthType, OSType: req.OSType, Protocol: req.Protocol, RDPPort: req.RDPPort, OS: req.OS, OSVersion: req.OSVersion, Arch: req.Arch, CPU: req.CPU, Memory: req.Memory, Disk: req.Disk, RegionID: req.RegionID, CloudID: req.CloudID, Tags: req.Tags, Description: req.Description, } if req.Port == 0 { srv.Port = 22 } if req.Username == "" { srv.Username = "root" } if req.AuthType == "" { srv.AuthType = "password" } if srv.OSType == "" { srv.OSType = "linux" } if srv.Protocol == "" { srv.Protocol = "ssh" } if srv.Protocol == "rdp" { srv.OSType = "windows" if srv.RDPPort == 0 { srv.RDPPort = 3389 } } // 加密密码和 SSH 密钥 if req.Password != "" { enc, err := s.crypt.Encrypt(req.Password) if err != nil { return nil, fmt.Errorf("加密密码失败: %w", err) } srv.Password = enc } if req.SSHKey != "" { enc, err := s.crypt.Encrypt(req.SSHKey) if err != nil { return nil, fmt.Errorf("加密 SSH 密钥失败: %w", err) } srv.SSHKey = enc } if req.CredentialID != nil && *req.CredentialID > 0 { if err := s.applyCredentialToServer(*req.CredentialID, srv); err != nil { return nil, err } } if err := s.repo.Create(srv); err != nil { return nil, err } return srv, nil } // UpdateServer 部分更新服务器 func (s *ServerService) UpdateServer(id uint, req *dto.UpdateServerRequest) error { updates := make(map[string]interface{}) if req.Hostname != nil { updates["hostname"] = *req.Hostname } if req.IP != nil { updates["ip"] = *req.IP } if req.PublicIP != nil { updates["public_ip"] = *req.PublicIP } if req.Port != nil { updates["port"] = *req.Port } if req.Username != nil { updates["username"] = *req.Username } if req.AuthType != nil { updates["auth_type"] = *req.AuthType } if req.OSType != nil { updates["os_type"] = *req.OSType } if req.Protocol != nil { updates["protocol"] = *req.Protocol } if req.RDPPort != nil { updates["rdp_port"] = *req.RDPPort } if req.OS != nil { updates["os"] = *req.OS } if req.OSVersion != nil { updates["os_version"] = *req.OSVersion } if req.Arch != nil { updates["arch"] = *req.Arch } if req.CPU != nil { updates["cpu"] = *req.CPU } if req.Memory != nil { updates["memory"] = *req.Memory } if req.Disk != nil { updates["disk"] = *req.Disk } if req.Status != nil { updates["status"] = *req.Status } if req.RegionID != nil { updates["region_id"] = *req.RegionID } if req.CloudID != nil { updates["cloud_id"] = *req.CloudID } if req.Tags != nil { updates["tags"] = *req.Tags } if req.Description != nil { updates["description"] = *req.Description } // 加密凭据字段 if req.Password != nil && *req.Password != "" { enc, err := s.crypt.Encrypt(*req.Password) if err != nil { return fmt.Errorf("加密密码失败: %w", err) } updates["password"] = enc } if req.SSHKey != nil && *req.SSHKey != "" { enc, err := s.crypt.Encrypt(*req.SSHKey) if err != nil { return fmt.Errorf("加密 SSH 密钥失败: %w", err) } updates["ssh_key"] = enc } if req.CredentialID != nil && *req.CredentialID > 0 { srv, err := s.repo.GetByID(id) if err != nil { return err } if err := s.applyCredentialToServer(*req.CredentialID, srv); err != nil { return err } updates["username"] = srv.Username updates["password"] = srv.Password updates["ssh_key"] = srv.SSHKey updates["auth_type"] = srv.AuthType updates["port"] = srv.Port updates["rdp_port"] = srv.RDPPort updates["protocol"] = srv.Protocol updates["os_type"] = srv.OSType } if len(updates) == 0 { return nil } return s.repo.UpdateFields(id, updates) } func (s *ServerService) applyCredentialToServer(credentialID uint, srv *model.Server) error { cred, err := s.credentialByID(credentialID) if err != nil { return err } plain, err := s.crypt.Decrypt(cred.Content) if err != nil { return fmt.Errorf("凭据解密失败: %w", err) } switch cred.SecretType { case "ssh": var sshc model.SSHSecretContent if json.Unmarshal([]byte(plain), &sshc) == nil { if sshc.SSHUser != "" { srv.Username = sshc.SSHUser } if sshc.SSHPort > 0 { srv.Port = sshc.SSHPort } if sshc.PrivateKey != "" { srv.SSHKey = sshc.PrivateKey srv.Password = "" srv.AuthType = "key" } else if sshc.Password != "" { srv.Password = sshc.Password srv.SSHKey = "" srv.AuthType = "password" } } srv.Protocol = "ssh" if srv.OSType == "" { srv.OSType = "linux" } case "rdp": var rdpc model.RDPSecretContent if err := json.Unmarshal([]byte(plain), &rdpc); err != nil { return fmt.Errorf("RDP 凭据格式错误: %w", err) } srv.Username = rdpc.Username srv.Password = plain srv.SSHKey = "" srv.AuthType = "password" srv.Protocol = "rdp" srv.OSType = "windows" if srv.RDPPort == 0 { srv.RDPPort = 3389 } default: return fmt.Errorf("服务器仅支持关联 ssh/rdp 凭据") } return nil } func (s *ServerService) credentialByID(id uint) (*model.Credential, error) { var cred model.Credential if err := s.repo.DB().First(&cred, id).Error; err != nil { return nil, err } return &cred, nil } // DeleteServer 删除服务器 func (s *ServerService) DeleteServer(id uint) error { return s.repo.Delete(id) } // GetDashboardStats 获取仪表盘统计 func (s *ServerService) GetDashboardStats() (*model.DashboardStats, error) { var stats model.DashboardStats stats.ServerStats.Total = s.repo.CountByStatus("") stats.ServerStats.Running = s.repo.CountByStatus("running") stats.ServerStats.Stopped = s.repo.CountByStatus("stopped") stats.ServerStats.Maintenance = s.repo.CountByStatus("maintenance") stats.ServerStats.Error = s.repo.CountByStatus("error") stats.ServerStats.AvgCPU = math.Round(s.repo.AvgMetric("cpu_usage")*100) / 100 stats.ServerStats.AvgMemory = math.Round(s.repo.AvgMetric("memory_usage")*100) / 100 stats.ServerStats.AvgDisk = math.Round(s.repo.AvgMetric("disk_usage")*100) / 100 return &stats, nil } // ━━━ Credential Service ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ type CredentialService struct { repo *repository.CredentialRepository crypt *crypto.SecretBox } func NewCredentialService(repo *repository.CredentialRepository, crypt *crypto.SecretBox) *CredentialService { return &CredentialService{repo: repo, crypt: crypt} } // List 分页查询 func (s *CredentialService) List(keyword, secretType string, page, pageSize int) ([]model.Credential, int64, error) { return s.repo.List(keyword, secretType, page, pageSize) } // GetByID 获取详情 func (s *CredentialService) GetByID(id uint) (*model.Credential, error) { return s.repo.GetByID(id) } // buildCredentialContent 根据请求构建加密内容 func (s *CredentialService) buildCredentialContent(req *dto.CreateCredentialRequest) (string, string, error) { switch req.SecretType { case "ssh": data := map[string]interface{}{ "private_key": req.SSHPrivateKey, "public_key": req.SSHPublicKey, "ssh_user": req.SSHUser, "ssh_port": req.SSHPort, "password": req.SSHPassword, } b, _ := json.Marshal(data) return string(b), fmt.Sprintf("SSH %s@%d", req.SSHUser, req.SSHPort), nil case "rdp": security := req.RDPSecurity if security == "" { security = "nla" } data := map[string]string{ "username": req.RDPUsername, "password": req.RDPPassword, "domain": req.RDPDomain, "security": security, } b, _ := json.Marshal(data) return string(b), fmt.Sprintf("RDP %s", req.RDPUsername), nil case "docker": data := map[string]string{ "server": req.DockerServer, "username": req.DockerUsername, "password": req.DockerPassword, } b, _ := json.Marshal(data) return string(b), req.DockerServer, nil case "tls": data := map[string]string{"cert": req.TLSCert, "key": req.TLSKey, "ca": req.TLSCA} b, _ := json.Marshal(data) return string(b), "TLS 证书已配置", nil case "password": return req.PlainPassword, "密码", nil case "env": return req.EnvVars, "环境变量", nil default: return "", "", fmt.Errorf("不支持的凭据类型: %s", req.SecretType) } } // Create 创建凭据 func (s *CredentialService) Create(req *dto.CreateCredentialRequest) (*model.Credential, error) { content, usage, err := s.buildCredentialContent(req) if err != nil { return nil, err } encContent, err := s.crypt.Encrypt(content) if err != nil { return nil, fmt.Errorf("加密失败: %w", err) } cred := &model.Credential{ Name: req.Name, SecretType: req.SecretType, Content: encContent, Summary: usage, Description: req.Description, } if err := s.repo.Create(cred); err != nil { return nil, err } return cred, nil } // buildUpdateContent 构建更新凭据的加密内容 func (s *CredentialService) buildUpdateContent(req *dto.UpdateCredentialRequest, existingSecretType string) (string, string, error) { st := existingSecretType switch st { case "ssh": data := map[string]interface{}{} if req.SSHPrivateKey != nil { data["private_key"] = *req.SSHPrivateKey } if req.SSHPublicKey != nil { data["public_key"] = *req.SSHPublicKey } if req.SSHUser != nil { data["ssh_user"] = *req.SSHUser } if req.SSHPort != nil { data["ssh_port"] = *req.SSHPort } if req.SSHPassword != nil { data["password"] = *req.SSHPassword } if len(data) == 0 { return "", "", nil } b, _ := json.Marshal(data) return string(b), "", nil case "rdp": data := map[string]string{} if req.RDPUsername != nil { data["username"] = *req.RDPUsername } if req.RDPPassword != nil { data["password"] = *req.RDPPassword } if req.RDPDomain != nil { data["domain"] = *req.RDPDomain } if req.RDPSecurity != nil { data["security"] = *req.RDPSecurity } if len(data) == 0 { return "", "", nil } b, _ := json.Marshal(data) return string(b), "", nil case "docker": data := map[string]string{} if req.DockerServer != nil { data["server"] = *req.DockerServer } if req.DockerUsername != nil { data["username"] = *req.DockerUsername } if req.DockerPassword != nil { data["password"] = *req.DockerPassword } b, _ := json.Marshal(data) return string(b), "", nil case "tls": data := map[string]string{} if req.TLSCert != nil { data["cert"] = *req.TLSCert } if req.TLSKey != nil { data["key"] = *req.TLSKey } if req.TLSCA != nil { data["ca"] = *req.TLSCA } b, _ := json.Marshal(data) return string(b), "", nil case "password": if req.PlainPassword != nil { return *req.PlainPassword, "", nil } return "", "", nil case "env": if req.EnvVars != nil { return *req.EnvVars, "", nil } return "", "", nil } return "", "", nil } // Update 更新凭据 func (s *CredentialService) Update(id uint, req *dto.UpdateCredentialRequest) error { existing, err := s.repo.GetByID(id) if err != nil { return err } content, _, err := s.buildUpdateContent(req, existing.SecretType) if err != nil { return err } updates := make(map[string]interface{}) if req.Name != nil { updates["name"] = *req.Name } if req.Description != nil { updates["description"] = *req.Description } if content != "" { enc, err := s.crypt.Encrypt(content) if err != nil { return fmt.Errorf("加密失败: %w", err) } updates["secret_data"] = enc } if len(updates) == 0 { return nil } return s.repo.UpdateFields(id, updates) } // Delete 删除凭据 func (s *CredentialService) Delete(id uint) error { return s.repo.Delete(id) } // ━━━ Alert Service ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ type AlertContactService struct { repo *repository.AlertContactRepository } func NewAlertContactService(repo *repository.AlertContactRepository) *AlertContactService { return &AlertContactService{repo: repo} } func (s *AlertContactService) List(keyword string, page, pageSize int) ([]model.AlertContact, int64, error) { return s.repo.List(keyword, page, pageSize) } func (s *AlertContactService) GetByID(id uint) (*model.AlertContact, error) { return s.repo.GetByID(id) } func (s *AlertContactService) Create(req *dto.CreateAlertContactRequest) (*model.AlertContact, error) { c := &model.AlertContact{ Name: req.Name, ChannelType: req.ChannelType, WebhookURL: req.WebhookURL, Phone: req.Phone, Email: req.Email, } if err := s.repo.Create(c); err != nil { return nil, err } return c, nil } func (s *AlertContactService) Update(id uint, req *dto.UpdateAlertContactRequest) error { updates := make(map[string]interface{}) if req.Name != nil { updates["name"] = *req.Name } if req.ChannelType != nil { updates["channel_type"] = *req.ChannelType } if req.WebhookURL != nil { updates["webhook_url"] = *req.WebhookURL } if req.Phone != nil { updates["phone"] = *req.Phone } if req.Email != nil { updates["email"] = *req.Email } if len(updates) == 0 { return nil } // AlertContact 没有 UpdateFields,直接 Save c, err := s.repo.GetByID(id) if err != nil { return err } if req.Name != nil { c.Name = *req.Name } if req.ChannelType != nil { c.ChannelType = *req.ChannelType } if req.WebhookURL != nil { c.WebhookURL = *req.WebhookURL } if req.Phone != nil { c.Phone = *req.Phone } if req.Email != nil { c.Email = *req.Email } return s.repo.Update(c) } func (s *AlertContactService) Delete(id uint) error { return s.repo.Delete(id) } // AlertContactGroup Service type AlertContactGroupService struct { repo *repository.AlertContactGroupRepository } func NewAlertContactGroupService(repo *repository.AlertContactGroupRepository) *AlertContactGroupService { return &AlertContactGroupService{repo: repo} } func (s *AlertContactGroupService) List(keyword string, page, pageSize int) ([]model.AlertContactGroup, int64, error) { return s.repo.List(keyword, page, pageSize) } func (s *AlertContactGroupService) GetByID(id uint) (*model.AlertContactGroup, error) { return s.repo.GetByID(id) } func (s *AlertContactGroupService) Create(req *dto.CreateAlertContactGroupRequest) (*model.AlertContactGroup, error) { g := &model.AlertContactGroup{GroupName: req.GroupName, Notes: req.Notes} if len(req.ContactIDs) > 0 { g.ContactIDs = toJSONString(req.ContactIDs) } if err := s.repo.Create(g); err != nil { return nil, err } return g, nil } func (s *AlertContactGroupService) Update(id uint, req *dto.UpdateAlertContactGroupRequest) error { updates := make(map[string]interface{}) if req.GroupName != nil { updates["group_name"] = *req.GroupName } if req.Notes != nil { updates["notes"] = *req.Notes } if req.ContactIDs != nil { updates["contact_ids"] = toJSONString(*req.ContactIDs) } if len(updates) == 0 { return nil } return s.repo.UpdateFields(id, updates) } func (s *AlertContactGroupService) Delete(id uint) error { return s.repo.Delete(id) } func toJSONString(v interface{}) string { b, _ := json.Marshal(v) return string(b) } // ━━━ Config Helper — 管理所有 Service 的依赖注入 ━━━━ type Services struct { Server *ServerService Credential *CredentialService AlertContact *AlertContactService AlertContactGroup *AlertContactGroupService } // NewServices 初始化所有服务 func NewServices(db *gorm.DB, cache ezredis.Cache, masterKey string) *Services { crypt := crypto.NewSecretBox(masterKey) return &Services{ Server: NewServerService(repository.NewServerRepository(db), cache, crypt), Credential: NewCredentialService(repository.NewCredentialRepository(db), crypt), AlertContact: NewAlertContactService(repository.NewAlertContactRepository(db)), AlertContactGroup: NewAlertContactGroupService(repository.NewAlertContactGroupRepository(db)), } }