169 lines
5.2 KiB
Go
169 lines
5.2 KiB
Go
package quota
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
"gorm.io/gorm"
|
|
"gorm.io/gorm/clause"
|
|
)
|
|
|
|
var (
|
|
ErrUnavailable = errors.New("配额配置不可读取")
|
|
ErrExceeded = errors.New("当前配额已满")
|
|
ErrInvalid = errors.New("配额配置不符合要求")
|
|
ErrVersionConflict = errors.New("配额已被其他用户修改,请刷新后重试")
|
|
)
|
|
|
|
type Service struct{ DB *gorm.DB }
|
|
|
|
func (s Service) Overview(request PageRequest) (Overview, error) {
|
|
if s.DB == nil {
|
|
return Overview{}, ErrUnavailable
|
|
}
|
|
request.Keyword = strings.TrimSpace(request.Keyword)
|
|
if request.Status != "" && request.Status != "active" && request.Status != "pending" && request.Status != "disabled" {
|
|
return Overview{}, ErrInvalid
|
|
}
|
|
used, err := occupiedCount(s.DB)
|
|
if err != nil {
|
|
return Overview{}, fmt.Errorf("count quota occupancy: %w", err)
|
|
}
|
|
now := time.Now().UTC()
|
|
summary := Summary{Used: int(used), ReadStatus: ReadStatusUnreadable, LastReadAt: now}
|
|
var setting Setting
|
|
if err = s.DB.First(&setting, "id = ?", SettingID).Error; err == nil && validLimit(setting.Limit) {
|
|
summary.Limit = setting.Limit
|
|
summary.Remaining = setting.Limit - int(used)
|
|
if summary.Remaining < 0 {
|
|
summary.Remaining = 0
|
|
}
|
|
summary.ReadStatus = ReadStatusReadable
|
|
summary.Source = setting.Source
|
|
summary.Version = setting.Version
|
|
summary.LastChanged = setting.UpdatedAt
|
|
}
|
|
|
|
query := s.DB.Table("sense_devices").Select("id, name, location, status, version, updated_at")
|
|
if request.Keyword != "" {
|
|
pattern := "%" + strings.ToLower(request.Keyword) + "%"
|
|
query = query.Where("LOWER(name) LIKE ? OR LOWER(location) LIKE ?", pattern, pattern)
|
|
}
|
|
if request.Status != "" {
|
|
query = query.Where("status = ?", request.Status)
|
|
}
|
|
var count int64
|
|
if err = query.Count(&count).Error; err != nil {
|
|
return Overview{}, fmt.Errorf("count quota devices: %w", err)
|
|
}
|
|
pageSize := request.GetPageSize()
|
|
if pageSize > 100 {
|
|
pageSize = 100
|
|
}
|
|
var rows []DeviceOccupancy
|
|
if err = query.Order("updated_at DESC").Limit(pageSize).Offset((request.GetPageIndex() - 1) * pageSize).Scan(&rows).Error; err != nil {
|
|
return Overview{}, fmt.Errorf("list quota devices: %w", err)
|
|
}
|
|
for index := range rows {
|
|
rows[index].Occupied = rows[index].Status != "disabled"
|
|
}
|
|
return Overview{Summary: summary, List: rows, Count: count, Tiers: deliveryTiers(summary.Limit)}, nil
|
|
}
|
|
|
|
func (s Service) Update(request UpdateRequest) (Summary, error) {
|
|
request.Reason = strings.TrimSpace(request.Reason)
|
|
if s.DB == nil || !validLimit(request.Limit) || request.Version < 1 || request.Reason == "" || len([]rune(request.Reason)) > 512 {
|
|
return Summary{}, ErrInvalid
|
|
}
|
|
err := s.DB.Transaction(func(tx *gorm.DB) error {
|
|
setting, err := lockSetting(tx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if setting.Version != request.Version {
|
|
return ErrVersionConflict
|
|
}
|
|
now := time.Now().UTC()
|
|
result := tx.Model(&Setting{}).Where("id = ? AND version = ?", SettingID, request.Version).Updates(map[string]any{
|
|
"limit": request.Limit, "source": "database", "version": request.Version + 1,
|
|
"update_by": request.UpdateBy, "updated_at": now,
|
|
})
|
|
if result.Error != nil {
|
|
return result.Error
|
|
}
|
|
if result.RowsAffected == 0 {
|
|
return ErrVersionConflict
|
|
}
|
|
return tx.Create(&Change{ID: uuid.NewString(), OldLimit: setting.Limit, NewLimit: request.Limit, Reason: request.Reason, ChangedBy: request.UpdateBy, CreatedAt: now}).Error
|
|
})
|
|
if err != nil {
|
|
return Summary{}, err
|
|
}
|
|
overview, err := s.Overview(PageRequest{})
|
|
return overview.Summary, err
|
|
}
|
|
|
|
func ReadLimit(db *gorm.DB) (int, error) {
|
|
if db == nil {
|
|
return 0, ErrUnavailable
|
|
}
|
|
var setting Setting
|
|
if err := db.First(&setting, "id = ?", SettingID).Error; err != nil || !validLimit(setting.Limit) {
|
|
return 0, ErrUnavailable
|
|
}
|
|
return setting.Limit, nil
|
|
}
|
|
|
|
func WithAvailableSlot(db *gorm.DB, write func(*gorm.DB) error) error {
|
|
if db == nil {
|
|
return ErrUnavailable
|
|
}
|
|
return db.Transaction(func(tx *gorm.DB) error {
|
|
setting, err := lockSetting(tx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
used, err := occupiedCount(tx)
|
|
if err != nil {
|
|
return fmt.Errorf("count quota occupancy: %w", err)
|
|
}
|
|
if used >= int64(setting.Limit) {
|
|
return ErrExceeded
|
|
}
|
|
return write(tx)
|
|
})
|
|
}
|
|
|
|
func lockSetting(tx *gorm.DB) (Setting, error) {
|
|
var setting Setting
|
|
err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&setting, "id = ?", SettingID).Error
|
|
if err != nil || !validLimit(setting.Limit) {
|
|
return Setting{}, ErrUnavailable
|
|
}
|
|
return setting, nil
|
|
}
|
|
|
|
func occupiedCount(db *gorm.DB) (int64, error) {
|
|
var count int64
|
|
err := db.Table("sense_devices").Where("status <> ?", "disabled").Count(&count).Error
|
|
return count, err
|
|
}
|
|
|
|
func validLimit(limit int) bool { return limit >= 1 && limit <= 100000 }
|
|
|
|
func deliveryTiers(configured int) []Tier {
|
|
tiers := []Tier{
|
|
{Limit: 16, Validation: "verified", DeliveryStatus: "默认学校试点交付档位"},
|
|
{Limit: 32, Validation: "unverified", DeliveryStatus: "需完成目标硬件压测后启用"},
|
|
{Limit: 64, Validation: "unverified", DeliveryStatus: "不作单机容量承诺"},
|
|
{Limit: 128, Validation: "unverified", DeliveryStatus: "当前不在交付承诺范围"},
|
|
}
|
|
for index := range tiers {
|
|
tiers[index].Configured = tiers[index].Limit == configured
|
|
}
|
|
return tiers
|
|
}
|