mirror of
https://github.com/DullJZ/s3-balance.git
synced 2026-07-04 09:31:21 +08:00
237 lines
7.2 KiB
Go
237 lines
7.2 KiB
Go
package api
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
"time"
|
|
|
|
"github.com/DullJZ/s3-balance/internal/balancer"
|
|
"github.com/DullJZ/s3-balance/internal/bucket"
|
|
"github.com/DullJZ/s3-balance/internal/config"
|
|
"github.com/gorilla/mux"
|
|
)
|
|
|
|
// AdminHandler 管理API处理器
|
|
type AdminHandler struct {
|
|
bucketManager *bucket.Manager
|
|
balancer *balancer.Balancer
|
|
config *config.Config
|
|
configManager *config.Manager
|
|
}
|
|
|
|
// NewAdminHandler 创建新的管理API处理器
|
|
func NewAdminHandler(
|
|
bucketManager *bucket.Manager,
|
|
balancer *balancer.Balancer,
|
|
cfg *config.Config,
|
|
configManager *config.Manager,
|
|
) *AdminHandler {
|
|
return &AdminHandler{
|
|
bucketManager: bucketManager,
|
|
balancer: balancer,
|
|
config: cfg,
|
|
configManager: configManager,
|
|
}
|
|
}
|
|
|
|
// BucketResponse 存储桶响应结构
|
|
type BucketResponse struct {
|
|
Name string `json:"name"`
|
|
Endpoint string `json:"endpoint"`
|
|
Region string `json:"region"`
|
|
MaxSize string `json:"max_size"`
|
|
MaxSizeBytes int64 `json:"max_size_bytes"`
|
|
UsedSize int64 `json:"used_size"`
|
|
AvailableSize int64 `json:"available_size"`
|
|
UsagePercent float64 `json:"usage_percent"`
|
|
ObjectCount int64 `json:"object_count"`
|
|
Weight int `json:"weight"`
|
|
Enabled bool `json:"enabled"`
|
|
Available bool `json:"available"`
|
|
Virtual bool `json:"virtual"`
|
|
LastChecked time.Time `json:"last_checked"`
|
|
OperationCountA int64 `json:"operation_count_a"`
|
|
OperationCountB int64 `json:"operation_count_b"`
|
|
OperationLimits struct {
|
|
TypeA int `json:"type_a"`
|
|
TypeB int `json:"type_b"`
|
|
} `json:"operation_limits"`
|
|
}
|
|
|
|
// BucketsListResponse 存储桶列表响应结构
|
|
type BucketsListResponse struct {
|
|
Total int `json:"total"`
|
|
Buckets []BucketResponse `json:"buckets"`
|
|
}
|
|
|
|
// HealthResponse 健康状态响应结构
|
|
type HealthResponse struct {
|
|
Status string `json:"status"`
|
|
Timestamp time.Time `json:"timestamp"`
|
|
LoadBalancer string `json:"load_balancer_strategy"`
|
|
TotalBuckets int `json:"total_buckets"`
|
|
AvailableBuckets int `json:"available_buckets"`
|
|
Database string `json:"database_type"`
|
|
}
|
|
|
|
// RegisterRoutes 注册管理API路由
|
|
// 注意: router 参数应该是已经带有 /api 前缀的子路由器
|
|
func (h *AdminHandler) RegisterRoutes(router *mux.Router) {
|
|
// 注册路由,同时支持 OPTIONS 方法用于 CORS 预检
|
|
router.HandleFunc("/buckets", h.ListBuckets).Methods(http.MethodGet, http.MethodOptions)
|
|
router.HandleFunc("/buckets/{name}", h.GetBucketDetail).Methods(http.MethodGet, http.MethodOptions)
|
|
router.HandleFunc("/health", h.GetHealth).Methods(http.MethodGet, http.MethodOptions)
|
|
router.HandleFunc("/config", h.GetConfig).Methods(http.MethodGet, http.MethodOptions)
|
|
router.HandleFunc("/config", h.UpdateConfig).Methods(http.MethodPost, http.MethodOptions)
|
|
}
|
|
|
|
// ListBuckets 获取存储桶列表
|
|
func (h *AdminHandler) ListBuckets(w http.ResponseWriter, r *http.Request) {
|
|
buckets := h.bucketManager.GetAllBuckets()
|
|
|
|
response := BucketsListResponse{
|
|
Total: len(buckets),
|
|
Buckets: make([]BucketResponse, 0, len(buckets)),
|
|
}
|
|
|
|
for _, b := range buckets {
|
|
bucketResp := h.convertBucketInfo(b)
|
|
response.Buckets = append(response.Buckets, bucketResp)
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(response)
|
|
}
|
|
|
|
// GetBucketDetail 获取存储桶详情
|
|
func (h *AdminHandler) GetBucketDetail(w http.ResponseWriter, r *http.Request) {
|
|
vars := mux.Vars(r)
|
|
name := vars["name"]
|
|
|
|
bucketInfo, exists := h.bucketManager.GetBucket(name)
|
|
if !exists {
|
|
http.Error(w, `{"error": "bucket not found"}`, http.StatusNotFound)
|
|
return
|
|
}
|
|
|
|
response := h.convertBucketInfo(bucketInfo)
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(response)
|
|
}
|
|
|
|
// GetHealth 获取系统健康状态
|
|
func (h *AdminHandler) GetHealth(w http.ResponseWriter, r *http.Request) {
|
|
buckets := h.bucketManager.GetAllBuckets()
|
|
availableBuckets := h.bucketManager.GetAvailableBuckets()
|
|
|
|
status := "healthy"
|
|
if len(availableBuckets) == 0 {
|
|
status = "unhealthy"
|
|
} else if len(availableBuckets) < len(buckets)/2 {
|
|
status = "degraded"
|
|
}
|
|
|
|
response := HealthResponse{
|
|
Status: status,
|
|
Timestamp: time.Now(),
|
|
LoadBalancer: h.config.Balancer.Strategy,
|
|
TotalBuckets: len(buckets),
|
|
AvailableBuckets: len(availableBuckets),
|
|
Database: h.config.Database.Type,
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(response)
|
|
}
|
|
|
|
// GetConfig 获取当前配置
|
|
func (h *AdminHandler) GetConfig(w http.ResponseWriter, r *http.Request) {
|
|
if h.configManager == nil {
|
|
http.Error(w, `{"error": "config manager not available"}`, http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
currentConfig := h.configManager.GetConfig()
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(currentConfig)
|
|
}
|
|
|
|
// UpdateConfig 更新配置
|
|
func (h *AdminHandler) UpdateConfig(w http.ResponseWriter, r *http.Request) {
|
|
if h.configManager == nil {
|
|
http.Error(w, `{"error": "config manager not available"}`, http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
// 解析请求体
|
|
var newConfig config.Config
|
|
if err := json.NewDecoder(r.Body).Decode(&newConfig); err != nil {
|
|
http.Error(w, `{"error": "invalid JSON format: `+err.Error()+`"}`, http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
// 设置默认值
|
|
newConfig.SetDefaults()
|
|
|
|
// 更新配置
|
|
if err := h.configManager.UpdateConfig(&newConfig); err != nil {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
json.NewEncoder(w).Encode(map[string]string{
|
|
"error": "validation failed",
|
|
"message": err.Error(),
|
|
})
|
|
return
|
|
}
|
|
|
|
// 返回成功响应
|
|
response := map[string]interface{}{
|
|
"success": true,
|
|
"message": "Configuration updated successfully. Changes will take effect automatically.",
|
|
"config": &newConfig,
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(response)
|
|
}
|
|
|
|
// convertBucketInfo 转换BucketInfo为BucketResponse
|
|
func (h *AdminHandler) convertBucketInfo(b *bucket.BucketInfo) BucketResponse {
|
|
resp := BucketResponse{
|
|
Name: b.Config.Name,
|
|
Endpoint: b.Config.Endpoint,
|
|
Region: b.Config.Region,
|
|
MaxSize: b.Config.MaxSize,
|
|
MaxSizeBytes: b.Config.MaxSizeBytes,
|
|
UsedSize: b.UsedSize,
|
|
ObjectCount: b.GetObjectCount(),
|
|
Weight: b.Config.Weight,
|
|
Enabled: b.Config.Enabled,
|
|
Available: b.Available,
|
|
Virtual: b.Config.Virtual,
|
|
LastChecked: b.LastChecked,
|
|
OperationCountA: b.GetOperationCount(bucket.OperationTypeA),
|
|
OperationCountB: b.GetOperationCount(bucket.OperationTypeB),
|
|
}
|
|
|
|
resp.OperationLimits.TypeA = b.Config.OperationLimits.TypeA
|
|
resp.OperationLimits.TypeB = b.Config.OperationLimits.TypeB
|
|
|
|
// 计算可用空间
|
|
if b.Config.MaxSizeBytes > 0 {
|
|
resp.AvailableSize = b.Config.MaxSizeBytes - b.UsedSize
|
|
if resp.AvailableSize < 0 {
|
|
resp.AvailableSize = 0
|
|
}
|
|
// 计算使用百分比
|
|
resp.UsagePercent = float64(b.UsedSize) / float64(b.Config.MaxSizeBytes) * 100
|
|
} else {
|
|
resp.AvailableSize = -1 // -1 表示无限制
|
|
resp.UsagePercent = 0
|
|
}
|
|
|
|
return resp
|
|
}
|