Files
s3-balance/internal/api/handler.go
DullJZ 37b6adb6de first
2025-08-22 21:15:56 +08:00

393 lines
10 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package api
import (
"context"
"encoding/json"
"log"
"net/http"
"strconv"
"time"
"github.com/DullJZ/s3-balance/internal/balancer"
"github.com/DullJZ/s3-balance/internal/bucket"
"github.com/DullJZ/s3-balance/internal/storage"
"github.com/DullJZ/s3-balance/pkg/presigner"
"github.com/gorilla/mux"
)
// Handler API处理器
type Handler struct {
bucketManager *bucket.Manager
balancer *balancer.Balancer
presigner *presigner.Presigner
storage *storage.Service
}
// NewHandler 创建新的API处理器
func NewHandler(
bucketManager *bucket.Manager,
balancer *balancer.Balancer,
presigner *presigner.Presigner,
storage *storage.Service,
) *Handler {
return &Handler{
bucketManager: bucketManager,
balancer: balancer,
presigner: presigner,
storage: storage,
}
}
// RegisterRoutes 注册路由
func (h *Handler) RegisterRoutes(router *mux.Router) {
// 健康检查
router.HandleFunc("/health", h.handleHealth).Methods("GET")
// 存储桶状态
router.HandleFunc("/api/v1/buckets", h.handleListBuckets).Methods("GET")
router.HandleFunc("/api/v1/buckets/{bucket}/stats", h.handleBucketStats).Methods("GET")
// 预签名URL生成
router.HandleFunc("/api/v1/presign/upload", h.handlePresignUpload).Methods("POST")
router.HandleFunc("/api/v1/presign/download", h.handlePresignDownload).Methods("POST")
router.HandleFunc("/api/v1/presign/delete", h.handlePresignDelete).Methods("POST")
router.HandleFunc("/api/v1/presign/multipart", h.handlePresignMultipart).Methods("POST")
// 对象操作(记录元数据)
router.HandleFunc("/api/v1/objects", h.handleListObjects).Methods("GET")
router.HandleFunc("/api/v1/objects/{key:.*}", h.handleGetObjectInfo).Methods("GET")
router.HandleFunc("/api/v1/objects/{key:.*}", h.handleDeleteObject).Methods("DELETE")
}
// 健康检查
func (h *Handler) handleHealth(w http.ResponseWriter, r *http.Request) {
response := map[string]interface{}{
"status": "healthy",
"time": time.Now().Unix(),
}
h.sendJSON(w, http.StatusOK, response)
}
// 列出所有存储桶状态
func (h *Handler) handleListBuckets(w http.ResponseWriter, r *http.Request) {
buckets := h.bucketManager.GetAllBuckets()
var bucketList []map[string]interface{}
for _, b := range buckets {
bucketList = append(bucketList, map[string]interface{}{
"name": b.Config.Name,
"endpoint": b.Config.Endpoint,
"region": b.Config.Region,
"max_size": b.Config.MaxSize,
"max_size_bytes": b.Config.MaxSizeBytes,
"used_size": b.GetUsedSize(),
"available": b.IsAvailable(),
"weight": b.Config.Weight,
"enabled": b.Config.Enabled,
})
}
h.sendJSON(w, http.StatusOK, map[string]interface{}{
"buckets": bucketList,
"strategy": h.balancer.GetStrategy(),
})
}
// 获取单个存储桶统计
func (h *Handler) handleBucketStats(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
bucketName := vars["bucket"]
bucket, ok := h.bucketManager.GetBucket(bucketName)
if !ok {
h.sendError(w, http.StatusNotFound, "bucket not found")
return
}
stats := map[string]interface{}{
"name": bucket.Config.Name,
"max_size_bytes": bucket.Config.MaxSizeBytes,
"used_size": bucket.GetUsedSize(),
"available_space": bucket.GetAvailableSpace(),
"available": bucket.IsAvailable(),
"last_checked": bucket.LastChecked,
}
h.sendJSON(w, http.StatusOK, stats)
}
// PresignUploadRequest 上传预签名请求
type PresignUploadRequest struct {
Key string `json:"key"`
Size int64 `json:"size"`
ContentType string `json:"content_type,omitempty"`
Metadata map[string]string `json:"metadata,omitempty"`
}
// 生成上传预签名URL
func (h *Handler) handlePresignUpload(w http.ResponseWriter, r *http.Request) {
var req PresignUploadRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
h.sendError(w, http.StatusBadRequest, "invalid request body")
return
}
if req.Key == "" {
h.sendError(w, http.StatusBadRequest, "key is required")
return
}
// 选择存储桶
bucket, err := h.balancer.SelectBucket(req.Key, req.Size)
if err != nil {
h.sendError(w, http.StatusServiceUnavailable, err.Error())
return
}
// 生成预签名URL
uploadURL, err := h.presigner.GenerateUploadURL(
context.Background(),
bucket,
req.Key,
req.ContentType,
req.Metadata,
)
if err != nil {
h.sendError(w, http.StatusInternalServerError, "failed to generate upload URL")
return
}
// 记录对象元数据
if err := h.storage.RecordObject(req.Key, bucket.Config.Name, req.Size, req.Metadata); err != nil {
log.Printf("Failed to record object metadata: %v", err)
}
// 更新存储桶使用量(预估)
bucket.UpdateUsedSize(req.Size)
h.sendJSON(w, http.StatusOK, uploadURL)
}
// PresignDownloadRequest 下载预签名请求
type PresignDownloadRequest struct {
Key string `json:"key"`
}
// 生成下载预签名URL
func (h *Handler) handlePresignDownload(w http.ResponseWriter, r *http.Request) {
var req PresignDownloadRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
h.sendError(w, http.StatusBadRequest, "invalid request body")
return
}
if req.Key == "" {
h.sendError(w, http.StatusBadRequest, "key is required")
return
}
// 查找对象所在的存储桶
bucketName, err := h.storage.FindObjectBucket(req.Key)
if err != nil {
h.sendError(w, http.StatusNotFound, "object not found")
return
}
bucket, ok := h.bucketManager.GetBucket(bucketName)
if !ok {
h.sendError(w, http.StatusNotFound, "bucket not found")
return
}
// 生成预签名URL
downloadURL, err := h.presigner.GenerateDownloadURL(
context.Background(),
bucket,
req.Key,
)
if err != nil {
h.sendError(w, http.StatusInternalServerError, "failed to generate download URL")
return
}
h.sendJSON(w, http.StatusOK, downloadURL)
}
// PresignDeleteRequest 删除预签名请求
type PresignDeleteRequest struct {
Key string `json:"key"`
}
// 生成删除预签名URL
func (h *Handler) handlePresignDelete(w http.ResponseWriter, r *http.Request) {
var req PresignDeleteRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
h.sendError(w, http.StatusBadRequest, "invalid request body")
return
}
if req.Key == "" {
h.sendError(w, http.StatusBadRequest, "key is required")
return
}
// 查找对象所在的存储桶
bucketName, err := h.storage.FindObjectBucket(req.Key)
if err != nil {
h.sendError(w, http.StatusNotFound, "object not found")
return
}
bucket, ok := h.bucketManager.GetBucket(bucketName)
if !ok {
h.sendError(w, http.StatusNotFound, "bucket not found")
return
}
// 生成预签名URL
deleteURL, err := h.presigner.GenerateDeleteURL(
context.Background(),
bucket,
req.Key,
)
if err != nil {
h.sendError(w, http.StatusInternalServerError, "failed to generate delete URL")
return
}
h.sendJSON(w, http.StatusOK, deleteURL)
}
// PresignMultipartRequest 分片上传预签名请求
type PresignMultipartRequest struct {
Key string `json:"key"`
PartCount int `json:"part_count"`
Size int64 `json:"size"`
}
// 生成分片上传预签名URLs
func (h *Handler) handlePresignMultipart(w http.ResponseWriter, r *http.Request) {
var req PresignMultipartRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
h.sendError(w, http.StatusBadRequest, "invalid request body")
return
}
if req.Key == "" || req.PartCount <= 0 {
h.sendError(w, http.StatusBadRequest, "invalid parameters")
return
}
// 选择存储桶
bucket, err := h.balancer.SelectBucket(req.Key, req.Size)
if err != nil {
h.sendError(w, http.StatusServiceUnavailable, err.Error())
return
}
// 生成预签名URLs
multipartURLs, err := h.presigner.GenerateMultipartUploadURLs(
context.Background(),
bucket,
req.Key,
req.PartCount,
)
if err != nil {
h.sendError(w, http.StatusInternalServerError, "failed to generate multipart URLs")
return
}
// 记录对象元数据
if err := h.storage.RecordObject(req.Key, bucket.Config.Name, req.Size, nil); err != nil {
log.Printf("Failed to record object metadata: %v", err)
}
// 更新存储桶使用量(预估)
bucket.UpdateUsedSize(req.Size)
h.sendJSON(w, http.StatusOK, multipartURLs)
}
// 列出对象
func (h *Handler) handleListObjects(w http.ResponseWriter, r *http.Request) {
prefix := r.URL.Query().Get("prefix")
bucketName := r.URL.Query().Get("bucket")
marker := r.URL.Query().Get("marker")
limitStr := r.URL.Query().Get("limit")
limit := 100
if limitStr != "" {
if l, err := strconv.Atoi(limitStr); err == nil && l > 0 {
limit = l
}
}
// 调用更新后的ListObjects方法传入所有必需的参数
objects, err := h.storage.ListObjects(bucketName, prefix, marker, limit)
if err != nil {
h.sendError(w, http.StatusInternalServerError, "failed to list objects")
return
}
h.sendJSON(w, http.StatusOK, map[string]interface{}{
"objects": objects,
"count": len(objects),
})
}
// 获取对象信息
func (h *Handler) handleGetObjectInfo(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
key := vars["key"]
info, err := h.storage.GetObjectInfo(key)
if err != nil {
h.sendError(w, http.StatusNotFound, "object not found")
return
}
h.sendJSON(w, http.StatusOK, info)
}
// 删除对象(只删除元数据记录)
func (h *Handler) handleDeleteObject(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
key := vars["key"]
// 获取对象信息以更新存储桶使用量
info, err := h.storage.GetObjectInfo(key)
if err != nil {
h.sendError(w, http.StatusNotFound, "object not found")
return
}
// 更新存储桶使用量
if bucket, ok := h.bucketManager.GetBucket(info.BucketName); ok {
bucket.UpdateUsedSize(-info.Size)
}
// 删除元数据记录
if err := h.storage.DeleteObject(key); err != nil {
h.sendError(w, http.StatusInternalServerError, "failed to delete object")
return
}
h.sendJSON(w, http.StatusOK, map[string]string{
"message": "object deleted successfully",
})
}
// 发送JSON响应
func (h *Handler) sendJSON(w http.ResponseWriter, status int, data interface{}) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
json.NewEncoder(w).Encode(data)
}
// 发送错误响应
func (h *Handler) sendError(w http.ResponseWriter, status int, message string) {
h.sendJSON(w, status, map[string]string{
"error": message,
})
}