feat: AI预测分析接入真实后端数据 + 修复CloudBilling文件损坏
- AIPredictiveAnalytics: 替换3组硬编码mock为实时数据驱动预测 - 指标预测: 基于dashboard metrics时序数据(cpu/memory/disk)线性回归计算斜率与R² - 容量规划: 基于日均增长率推算阈值触及时间(天),动态生成AI扩容建议 - 异常预测: 基于告警规则阈值+当前趋势+事件频率计算异常触发概率 - 概览卡片数据驱动化(置信度基于R²拟合度,高风险/容量预警计数实时计算) - CloudBilling.tsx: 修复read_file导致的行号前缀损坏(501行全部含N|前缀)
This commit is contained in:
1 parent
0ebe22d142
commit
2da4f52209
17 files changed
+2299
-322
No files matched your search
@@ -0,0 +1,476 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"ezops/internal/models"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// BillingHandler 云账单处理器
|
||||
type BillingHandler struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewBillingHandler(db *gorm.DB) *BillingHandler {
|
||||
return &BillingHandler{db: db}
|
||||
}
|
||||
|
||||
// ListBillings 获取账单列表
|
||||
func (h *BillingHandler) ListBillings(c *gin.Context) {
|
||||
var billings []models.CloudBilling
|
||||
|
||||
query := h.db.Model(&models.CloudBilling{})
|
||||
|
||||
// 筛选
|
||||
if provider := c.Query("provider"); provider != "" {
|
||||
query = query.Where("provider = ?", provider)
|
||||
}
|
||||
if accountName := c.Query("account_name"); accountName != "" {
|
||||
query = query.Where("account_name LIKE ?", "%"+accountName+"%")
|
||||
}
|
||||
if startDate := c.Query("start_date"); startDate != "" {
|
||||
query = query.Where("billing_date >= ?", startDate)
|
||||
}
|
||||
if endDate := c.Query("end_date"); endDate != "" {
|
||||
query = query.Where("billing_date <= ?", endDate)
|
||||
}
|
||||
|
||||
// 排序
|
||||
query = query.Order("billing_date DESC, id DESC")
|
||||
|
||||
// 分页
|
||||
page := 1
|
||||
pageSize := 50
|
||||
if c.Query("page") != "" {
|
||||
fmt.Sscanf(c.Query("page"), "%d", &page)
|
||||
}
|
||||
if c.Query("page_size") != "" {
|
||||
fmt.Sscanf(c.Query("page_size"), "%d", &pageSize)
|
||||
}
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
if pageSize < 1 || pageSize > 500 {
|
||||
pageSize = 50
|
||||
}
|
||||
|
||||
var total int64
|
||||
query.Count(&total)
|
||||
query.Offset((page - 1) * pageSize).Limit(pageSize).Find(&billings)
|
||||
|
||||
c.JSON(http.StatusOK, models.Response{
|
||||
Code: 200,
|
||||
Message: "success",
|
||||
Data: models.ListResponse{
|
||||
List: billings,
|
||||
Total: total,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// CreateBilling 创建账单记录
|
||||
func (h *BillingHandler) CreateBilling(c *gin.Context) {
|
||||
var req models.CreateBillingRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, models.Response{Code: 400, Message: err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
billing := models.CloudBilling{
|
||||
CloudAccountID: req.CloudAccountID,
|
||||
AccountName: req.AccountName,
|
||||
Provider: req.Provider,
|
||||
BillingDate: req.BillingDate,
|
||||
Cost: req.Cost,
|
||||
Currency: req.Currency,
|
||||
Service: req.Service,
|
||||
Region: req.Region,
|
||||
Description: req.Description,
|
||||
}
|
||||
if billing.Currency == "" {
|
||||
billing.Currency = "CNY"
|
||||
}
|
||||
|
||||
if err := h.db.Create(&billing).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, models.Response{Code: 500, Message: "创建失败: " + err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, models.Response{
|
||||
Code: 200,
|
||||
Message: "创建成功",
|
||||
Data: billing,
|
||||
})
|
||||
}
|
||||
|
||||
// BatchCreateBillings 批量创建账单
|
||||
func (h *BillingHandler) BatchCreateBillings(c *gin.Context) {
|
||||
var req struct {
|
||||
Records []models.CreateBillingRequest `json:"records" binding:"required"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, models.Response{Code: 400, Message: err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
created := 0
|
||||
for _, r := range req.Records {
|
||||
billing := models.CloudBilling{
|
||||
CloudAccountID: r.CloudAccountID,
|
||||
AccountName: r.AccountName,
|
||||
Provider: r.Provider,
|
||||
BillingDate: r.BillingDate,
|
||||
Cost: r.Cost,
|
||||
Currency: r.Currency,
|
||||
Service: r.Service,
|
||||
Region: r.Region,
|
||||
Description: r.Description,
|
||||
}
|
||||
if billing.Currency == "" {
|
||||
billing.Currency = "CNY"
|
||||
}
|
||||
if err := h.db.Create(&billing).Error; err == nil {
|
||||
created++
|
||||
}
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, models.Response{
|
||||
Code: 200,
|
||||
Message: fmt.Sprintf("成功导入 %d 条记录", created),
|
||||
Data: gin.H{"created": created, "total": len(req.Records)},
|
||||
})
|
||||
}
|
||||
|
||||
// DeleteBilling 删除账单记录
|
||||
func (h *BillingHandler) DeleteBilling(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
if err := h.db.Delete(&models.CloudBilling{}, id).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, models.Response{Code: 500, Message: "删除失败"})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, models.Response{Code: 200, Message: "删除成功"})
|
||||
}
|
||||
|
||||
// GetBillingStats 获取账单统计
|
||||
func (h *BillingHandler) GetBillingStats(c *gin.Context) {
|
||||
now := time.Now()
|
||||
today := now.Format("2006-01-02")
|
||||
yesterday := now.AddDate(0, 0, -1).Format("2006-01-02")
|
||||
monthStart := now.Format("2006-01") + "-01"
|
||||
yearStart := fmt.Sprintf("%d-01-01", now.Year())
|
||||
|
||||
stats := models.BillingStats{
|
||||
MonthTrend: []models.DailyCost{},
|
||||
ProviderDist: []models.ProviderCost{},
|
||||
Alerts: []models.BillingAlert{},
|
||||
}
|
||||
|
||||
// 今日成本
|
||||
h.db.Model(&models.CloudBilling{}).Where("billing_date = ?", today).
|
||||
Select("COALESCE(SUM(cost), 0)").Scan(&stats.TodayCost)
|
||||
|
||||
// 昨日成本
|
||||
h.db.Model(&models.CloudBilling{}).Where("billing_date = ?", yesterday).
|
||||
Select("COALESCE(SUM(cost), 0)").Scan(&stats.YesterdayCost)
|
||||
|
||||
// 本月成本
|
||||
h.db.Model(&models.CloudBilling{}).Where("billing_date >= ?", monthStart).
|
||||
Select("COALESCE(SUM(cost), 0)").Scan(&stats.MonthCost)
|
||||
|
||||
// 本年成本
|
||||
h.db.Model(&models.CloudBilling{}).Where("billing_date >= ?", yearStart).
|
||||
Select("COALESCE(SUM(cost), 0)").Scan(&stats.YearCost)
|
||||
|
||||
// 近30天趋势
|
||||
type dayCost struct {
|
||||
BillingDate string
|
||||
Total float64
|
||||
}
|
||||
var trends []dayCost
|
||||
h.db.Model(&models.CloudBilling{}).
|
||||
Select("billing_date, SUM(cost) as total").
|
||||
Where("billing_date >= ?", now.AddDate(0, 0, -29).Format("2006-01-02")).
|
||||
Group("billing_date").
|
||||
Order("billing_date ASC").
|
||||
Scan(&trends)
|
||||
|
||||
for _, t := range trends {
|
||||
stats.MonthTrend = append(stats.MonthTrend, models.DailyCost{Date: t.BillingDate, Cost: t.Total})
|
||||
}
|
||||
|
||||
// 本月厂商成本分布
|
||||
type provCost struct {
|
||||
Provider string
|
||||
Total float64
|
||||
}
|
||||
var provCosts []provCost
|
||||
h.db.Model(&models.CloudBilling{}).
|
||||
Select("provider, SUM(cost) as total").
|
||||
Where("billing_date >= ?", monthStart).
|
||||
Group("provider").
|
||||
Scan(&provCosts)
|
||||
|
||||
var totalProviderCost float64
|
||||
for _, p := range provCosts {
|
||||
totalProviderCost += p.Total
|
||||
}
|
||||
for _, p := range provCosts {
|
||||
percent := 0.0
|
||||
if totalProviderCost > 0 {
|
||||
percent = p.Total / totalProviderCost * 100
|
||||
}
|
||||
stats.ProviderDist = append(stats.ProviderDist, models.ProviderCost{
|
||||
Provider: p.Provider,
|
||||
Cost: p.Total,
|
||||
Percent: percent,
|
||||
})
|
||||
}
|
||||
sort.Slice(stats.ProviderDist, func(i, j int) bool {
|
||||
return stats.ProviderDist[i].Cost > stats.ProviderDist[j].Cost
|
||||
})
|
||||
|
||||
// 检查预警规则
|
||||
h.checkBillingAlerts(&stats, now)
|
||||
|
||||
c.JSON(http.StatusOK, models.Response{
|
||||
Code: 200,
|
||||
Message: "success",
|
||||
Data: stats,
|
||||
})
|
||||
}
|
||||
|
||||
// checkBillingAlerts 检查预警规则是否触发
|
||||
func (h *BillingHandler) checkBillingAlerts(stats *models.BillingStats, now time.Time) {
|
||||
var rules []models.BillingAlertRule
|
||||
h.db.Where("enabled = ?", true).Find(&rules)
|
||||
|
||||
for _, rule := range rules {
|
||||
var current float64
|
||||
|
||||
if rule.Metric == "daily" {
|
||||
// 查今日该厂商/账号的成本
|
||||
q := h.db.Model(&models.CloudBilling{}).Where("billing_date = ?", now.Format("2006-01-02"))
|
||||
if rule.Provider != "" {
|
||||
q = q.Where("provider = ?", rule.Provider)
|
||||
}
|
||||
if rule.CloudAccountID != nil {
|
||||
q = q.Where("cloud_account_id = ?", *rule.CloudAccountID)
|
||||
}
|
||||
q.Select("COALESCE(SUM(cost), 0)").Scan(¤t)
|
||||
} else {
|
||||
// monthly - 本月累计
|
||||
q := h.db.Model(&models.CloudBilling{}).Where("billing_date >= ?", now.Format("2006-01")+"-01")
|
||||
if rule.Provider != "" {
|
||||
q = q.Where("provider = ?", rule.Provider)
|
||||
}
|
||||
if rule.CloudAccountID != nil {
|
||||
q = q.Where("cloud_account_id = ?", *rule.CloudAccountID)
|
||||
}
|
||||
q.Select("COALESCE(SUM(cost), 0)").Scan(¤t)
|
||||
}
|
||||
|
||||
// 检查阈值
|
||||
triggered := false
|
||||
switch rule.Operator {
|
||||
case ">":
|
||||
triggered = current > rule.Threshold
|
||||
case ">=":
|
||||
triggered = current >= rule.Threshold
|
||||
case "<":
|
||||
triggered = current < rule.Threshold
|
||||
case "<=":
|
||||
triggered = current <= rule.Threshold
|
||||
}
|
||||
|
||||
if triggered {
|
||||
providerLabel := rule.Provider
|
||||
if providerLabel == "" {
|
||||
providerLabel = "全部"
|
||||
}
|
||||
stats.Alerts = append(stats.Alerts, models.BillingAlert{
|
||||
RuleID: rule.ID,
|
||||
RuleName: rule.Name,
|
||||
Provider: providerLabel,
|
||||
Current: current,
|
||||
Threshold: rule.Threshold,
|
||||
Metric: rule.Metric,
|
||||
Operator: rule.Operator,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ListAlertRules 获取预警规则列表
|
||||
func (h *BillingHandler) ListAlertRules(c *gin.Context) {
|
||||
var rules []models.BillingAlertRule
|
||||
h.db.Order("created_at DESC").Find(&rules)
|
||||
|
||||
c.JSON(http.StatusOK, models.Response{
|
||||
Code: 200,
|
||||
Message: "success",
|
||||
Data: models.ListResponse{
|
||||
List: rules,
|
||||
Total: int64(len(rules)),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// CreateAlertRule 创建预警规则
|
||||
func (h *BillingHandler) CreateAlertRule(c *gin.Context) {
|
||||
var req models.CreateBillingAlertRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, models.Response{Code: 400, Message: err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
rule := models.BillingAlertRule{
|
||||
Name: req.Name,
|
||||
Provider: req.Provider,
|
||||
CloudAccountID: req.CloudAccountID,
|
||||
Metric: req.Metric,
|
||||
Threshold: req.Threshold,
|
||||
Operator: req.Operator,
|
||||
Enabled: req.Enabled,
|
||||
}
|
||||
if rule.Metric == "" {
|
||||
rule.Metric = "daily"
|
||||
}
|
||||
if rule.Operator == "" {
|
||||
rule.Operator = ">"
|
||||
}
|
||||
|
||||
if err := h.db.Create(&rule).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, models.Response{Code: 500, Message: "创建失败"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, models.Response{
|
||||
Code: 200,
|
||||
Message: "创建成功",
|
||||
Data: rule,
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateAlertRule 更新预警规则
|
||||
func (h *BillingHandler) UpdateAlertRule(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
var rule models.BillingAlertRule
|
||||
if err := h.db.First(&rule, id).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, models.Response{Code: 404, Message: "规则不存在"})
|
||||
return
|
||||
}
|
||||
|
||||
var req models.CreateBillingAlertRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, models.Response{Code: 400, Message: err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
rule.Name = req.Name
|
||||
rule.Provider = req.Provider
|
||||
rule.CloudAccountID = req.CloudAccountID
|
||||
rule.Metric = req.Metric
|
||||
rule.Threshold = req.Threshold
|
||||
rule.Operator = req.Operator
|
||||
rule.Enabled = req.Enabled
|
||||
if rule.Metric == "" {
|
||||
rule.Metric = "daily"
|
||||
}
|
||||
if rule.Operator == "" {
|
||||
rule.Operator = ">"
|
||||
}
|
||||
|
||||
h.db.Save(&rule)
|
||||
|
||||
c.JSON(http.StatusOK, models.Response{
|
||||
Code: 200,
|
||||
Message: "更新成功",
|
||||
Data: rule,
|
||||
})
|
||||
}
|
||||
|
||||
// DeleteAlertRule 删除预警规则
|
||||
func (h *BillingHandler) DeleteAlertRule(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
if err := h.db.Delete(&models.BillingAlertRule{}, id).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, models.Response{Code: 500, Message: "删除失败"})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, models.Response{Code: 200, Message: "删除成功"})
|
||||
}
|
||||
|
||||
// ToggleAlertRule 启用/禁用预警规则
|
||||
func (h *BillingHandler) ToggleAlertRule(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
var rule models.BillingAlertRule
|
||||
if err := h.db.First(&rule, id).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, models.Response{Code: 404, Message: "规则不存在"})
|
||||
return
|
||||
}
|
||||
|
||||
rule.Enabled = !rule.Enabled
|
||||
h.db.Save(&rule)
|
||||
|
||||
status := "已启用"
|
||||
if !rule.Enabled {
|
||||
status = "已禁用"
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, models.Response{
|
||||
Code: 200,
|
||||
Message: status,
|
||||
Data: rule,
|
||||
})
|
||||
}
|
||||
|
||||
// GetProviderOptions 获取厂商选项(用于筛选下拉)
|
||||
func (h *BillingHandler) GetProviderOptions(c *gin.Context) {
|
||||
var providers []string
|
||||
h.db.Model(&models.CloudBilling{}).Distinct("provider").Where("provider != ''").Pluck("provider", &providers)
|
||||
|
||||
result := make([]gin.H, 0, len(providers))
|
||||
for _, p := range providers {
|
||||
label := p
|
||||
switch p {
|
||||
case "aliyun":
|
||||
label = "阿里云"
|
||||
case "tencent":
|
||||
label = "腾讯云"
|
||||
case "aws":
|
||||
label = "AWS"
|
||||
case "huawei":
|
||||
label = "华为云"
|
||||
case "azure":
|
||||
label = "Azure"
|
||||
}
|
||||
result = append(result, gin.H{"value": p, "label": label})
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, models.Response{
|
||||
Code: 200,
|
||||
Message: "success",
|
||||
Data: result,
|
||||
})
|
||||
}
|
||||
|
||||
// formatProvider 格式化厂商名称
|
||||
func formatProvider(provider string) string {
|
||||
names := map[string]string{
|
||||
"aliyun": "阿里云",
|
||||
"tencent": "腾讯云",
|
||||
"aws": "AWS",
|
||||
"huawei": "华为云",
|
||||
"azure": "Azure",
|
||||
}
|
||||
if name, ok := names[strings.ToLower(provider)]; ok {
|
||||
return name
|
||||
}
|
||||
return provider
|
||||
}
|
||||
@@ -363,7 +363,7 @@ func (h *LDAPHandler) ListLDAPUsers(c *gin.Context) {
|
||||
sr, err := conn.Search(ldap.NewSearchRequest(
|
||||
searchBase, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases,
|
||||
200, 0, false, baseFilter,
|
||||
[]string{"uid", "cn", "mail", "dn"},
|
||||
[]string{"uid", "cn", "mail", "dn", "telephoneNumber", "title", "department"},
|
||||
nil,
|
||||
))
|
||||
if err != nil {
|
||||
@@ -372,18 +372,24 @@ func (h *LDAPHandler) ListLDAPUsers(c *gin.Context) {
|
||||
}
|
||||
|
||||
type LdapUser struct {
|
||||
DN string `json:"dn"`
|
||||
UID string `json:"uid"`
|
||||
CN string `json:"cn"`
|
||||
Mail string `json:"mail"`
|
||||
DN string `json:"dn"`
|
||||
UID string `json:"uid"`
|
||||
CN string `json:"cn"`
|
||||
Mail string `json:"mail"`
|
||||
Phone string `json:"phone"`
|
||||
Title string `json:"title"`
|
||||
Department string `json:"department"`
|
||||
}
|
||||
var users []LdapUser
|
||||
for _, e := range sr.Entries {
|
||||
users = append(users, LdapUser{
|
||||
DN: e.DN,
|
||||
UID: e.GetAttributeValue("uid"),
|
||||
CN: e.GetAttributeValue("cn"),
|
||||
Mail: e.GetAttributeValue("mail"),
|
||||
DN: e.DN,
|
||||
UID: e.GetAttributeValue("uid"),
|
||||
CN: e.GetAttributeValue("cn"),
|
||||
Mail: e.GetAttributeValue("mail"),
|
||||
Phone: e.GetAttributeValue("telephoneNumber"),
|
||||
Title: e.GetAttributeValue("title"),
|
||||
Department: e.GetAttributeValue("department"),
|
||||
})
|
||||
}
|
||||
c.JSON(http.StatusOK, models.Response{Code: 200, Data: users})
|
||||
@@ -701,3 +707,109 @@ func (h *LDAPHandler) DeleteLDAPUser(c *gin.Context) {
|
||||
|
||||
c.JSON(http.StatusOK, models.Response{Code: 200, Message: "删除成功"})
|
||||
}
|
||||
|
||||
// UpdateLDAPUser 修改 LDAP 用户属性
|
||||
// POST /api/v1/admin/ldap-config/update-user
|
||||
func (h *LDAPHandler) UpdateLDAPUser(c *gin.Context) {
|
||||
var req struct {
|
||||
DN string `json:"dn"`
|
||||
CN string `json:"cn"`
|
||||
Mail string `json:"mail"`
|
||||
Phone string `json:"phone"`
|
||||
Title string `json:"title"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil || req.DN == "" {
|
||||
c.JSON(http.StatusBadRequest, models.Response{Code: 400, Message: "DN不能为空"})
|
||||
return
|
||||
}
|
||||
|
||||
var configs []models.LDAPConfig
|
||||
if err := h.db.Order("id").Find(&configs).Error; err != nil || len(configs) == 0 {
|
||||
c.JSON(http.StatusBadRequest, models.Response{Code: 400, Message: "LDAP未配置"})
|
||||
return
|
||||
}
|
||||
cfg := configs[0]
|
||||
port := 389
|
||||
if cfg.LDAPPort != nil {
|
||||
port = *cfg.LDAPPort
|
||||
}
|
||||
|
||||
conn, err := ldap.DialURL(fmt.Sprintf("ldap://%s:%d", cfg.LDAPHost, port))
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, models.Response{Code: 500, Message: "LDAP连接失败: " + err.Error()})
|
||||
return
|
||||
}
|
||||
defer conn.Close()
|
||||
conn.SetTimeout(5 * time.Second)
|
||||
if err := conn.Bind(cfg.AdminDN, cfg.AdminPassword); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, models.Response{Code: 500, Message: "管理员认证失败: " + err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// 先查当前值,做 diff
|
||||
sr, err := conn.Search(ldap.NewSearchRequest(
|
||||
req.DN, ldap.ScopeBaseObject, ldap.NeverDerefAliases,
|
||||
1, 0, false, "(objectClass=*)",
|
||||
[]string{"cn", "mail", "telephoneNumber", "title"},
|
||||
nil,
|
||||
))
|
||||
if err != nil || len(sr.Entries) == 0 {
|
||||
c.JSON(http.StatusInternalServerError, models.Response{Code: 500, Message: "用户不存在"})
|
||||
return
|
||||
}
|
||||
entry := sr.Entries[0]
|
||||
|
||||
modReq := ldap.NewModifyRequest(req.DN, nil)
|
||||
|
||||
// cn
|
||||
oldCN := entry.GetAttributeValue("cn")
|
||||
if req.CN != "" && req.CN != oldCN {
|
||||
modReq.Replace("cn", []string{req.CN})
|
||||
modReq.Replace("sn", []string{req.CN}) // sn 同步更新
|
||||
}
|
||||
// mail
|
||||
oldMail := entry.GetAttributeValue("mail")
|
||||
if req.Mail != oldMail {
|
||||
if req.Mail == "" {
|
||||
oldVals := entry.GetAttributeValues("mail")
|
||||
if len(oldVals) > 0 {
|
||||
modReq.Delete("mail", oldVals)
|
||||
}
|
||||
} else {
|
||||
modReq.Replace("mail", []string{req.Mail})
|
||||
}
|
||||
}
|
||||
// telephoneNumber
|
||||
oldPhone := entry.GetAttributeValue("telephoneNumber")
|
||||
if req.Phone != oldPhone {
|
||||
if req.Phone == "" {
|
||||
oldVals := entry.GetAttributeValues("telephoneNumber")
|
||||
if len(oldVals) > 0 {
|
||||
modReq.Delete("telephoneNumber", oldVals)
|
||||
}
|
||||
} else {
|
||||
modReq.Replace("telephoneNumber", []string{req.Phone})
|
||||
}
|
||||
}
|
||||
// title
|
||||
oldTitle := entry.GetAttributeValue("title")
|
||||
if req.Title != oldTitle {
|
||||
if req.Title == "" {
|
||||
oldVals := entry.GetAttributeValues("title")
|
||||
if len(oldVals) > 0 {
|
||||
modReq.Delete("title", oldVals)
|
||||
}
|
||||
} else {
|
||||
modReq.Replace("title", []string{req.Title})
|
||||
}
|
||||
}
|
||||
|
||||
if len(modReq.Changes) > 0 {
|
||||
if err := conn.Modify(modReq); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, models.Response{Code: 500, Message: "修改失败: " + err.Error()})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, models.Response{Code: 200, Message: "修改成功"})
|
||||
}
|
||||
@@ -68,6 +68,7 @@ func Register(r *gin.Engine, db *gorm.DB, log *zap.Logger, cfg *config.Config) {
|
||||
ldapGroup.POST("/create-ou", ldapHandler.CreateLDAPOU)
|
||||
ldapGroup.POST("/create-user", ldapHandler.CreateLDAPUser)
|
||||
ldapGroup.POST("/delete-user", ldapHandler.DeleteLDAPUser)
|
||||
ldapGroup.POST("/update-user", ldapHandler.UpdateLDAPUser)
|
||||
}
|
||||
|
||||
// 仪表盘路由
|
||||
@@ -133,6 +134,26 @@ func Register(r *gin.Engine, db *gorm.DB, log *zap.Logger, cfg *config.Config) {
|
||||
regions.DELETE("/:id", regionHandler.DeleteRegion)
|
||||
}
|
||||
|
||||
// 云账单路由
|
||||
billingHandler := handlers.NewBillingHandler(db)
|
||||
billings := authorized.Group("/cloud-billings")
|
||||
{
|
||||
billings.GET("", billingHandler.ListBillings)
|
||||
billings.POST("", billingHandler.CreateBilling)
|
||||
billings.POST("/batch", billingHandler.BatchCreateBillings)
|
||||
billings.DELETE("/:id", billingHandler.DeleteBilling)
|
||||
billings.GET("/stats", billingHandler.GetBillingStats)
|
||||
billings.GET("/providers", billingHandler.GetProviderOptions)
|
||||
}
|
||||
billingAlerts := authorized.Group("/billing-alerts")
|
||||
{
|
||||
billingAlerts.GET("", billingHandler.ListAlertRules)
|
||||
billingAlerts.POST("", billingHandler.CreateAlertRule)
|
||||
billingAlerts.PUT("/:id", billingHandler.UpdateAlertRule)
|
||||
billingAlerts.DELETE("/:id", billingHandler.DeleteAlertRule)
|
||||
billingAlerts.POST("/:id/toggle", billingHandler.ToggleAlertRule)
|
||||
}
|
||||
|
||||
// 资源模型路由
|
||||
resourceModelHandler := handlers.NewResourceModelHandler(db)
|
||||
resourceModels := authorized.Group("/resource-models")
|
||||
|
||||
Reference in new issue
Block a user