主要变更: - RDP 远程桌面:新增 backend/pkg/rdp(RDP 引擎 + credssp/display/rle/trace)、 backend/controller/rdp*(连接/凭证/文件/会话/实时)、frontend RDP 组件与 API - Windows SMB 文件传输:backend/pkg/winsmb - 云账单同步:backend/pkg/billingsync + billing_sync/aliyun_billing,前端账单详情 - Agent CLI Kubernetes 工具:kubernetes_action/get、pod_execute/logs - 服务器导入助手:server_import_helpers、ImportCredentialSettings - 国际化与组件重构(cloud/deploy/monitor/server/system 等多模块) - 清理编译产物二进制:移除 backend/server、backend/agent/bin/aiops-agent-* - .gitignore:新增 backend/agent/ 与 .cache/(避免 334MB go-build 缓存误入库) Co-Authored-By: Claude <noreply@anthropic.com>
682 lines
17 KiB
Go
682 lines
17 KiB
Go
// 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)),
|
|
}
|
|
}
|