mirror of
https://github.com/DullJZ/s3-balance.git
synced 2026-06-28 22:41:23 +08:00
Merge branch 'dev' into add-api
This commit is contained in:
@@ -76,7 +76,7 @@ func (h *S3Handler) RegisterS3Routes(router *mux.Router) {
|
||||
|
||||
// 带认证/虚拟主机的路由
|
||||
protected := router.NewRoute().PathPrefix("/{bucket}").Subrouter()
|
||||
protected.StrictSlash(true)
|
||||
// 注意:不使用 StrictSlash(true) 以避免 301 重定向,兼容WinSCP
|
||||
|
||||
// Bucket operations
|
||||
protected.HandleFunc("", h.handleBucketOperations).Methods("GET", "HEAD", "PUT", "DELETE")
|
||||
|
||||
193
internal/api/stats_handler.go
Normal file
193
internal/api/stats_handler.go
Normal file
@@ -0,0 +1,193 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/DullJZ/s3-balance/internal/storage"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
// StatsHandler 统计数据处理器
|
||||
type StatsHandler struct {
|
||||
storage *storage.Service
|
||||
}
|
||||
|
||||
// NewStatsHandler 创建统计处理器
|
||||
func NewStatsHandler(storage *storage.Service) *StatsHandler {
|
||||
return &StatsHandler{
|
||||
storage: storage,
|
||||
}
|
||||
}
|
||||
|
||||
// RegisterRoutes 注册统计API路由
|
||||
func (h *StatsHandler) RegisterRoutes(router *mux.Router) {
|
||||
router.HandleFunc("/api/stats/monthly", h.GetCurrentMonthStats).Methods("GET")
|
||||
router.HandleFunc("/api/stats/monthly/{year}/{month}", h.GetMonthlyStats).Methods("GET")
|
||||
router.HandleFunc("/api/stats/monthly/range", h.GetMonthlyStatsRange).Methods("GET")
|
||||
router.HandleFunc("/api/stats/bucket/{bucket}/history", h.GetBucketHistory).Methods("GET")
|
||||
}
|
||||
|
||||
// MonthlyStatsResponse 月度统计响应
|
||||
type MonthlyStatsResponse struct {
|
||||
Year int `json:"year"`
|
||||
Month int `json:"month"`
|
||||
Bucket string `json:"bucket"`
|
||||
Stats BucketOperationCounts `json:"stats"`
|
||||
}
|
||||
|
||||
// BucketOperationCounts 存储桶操作计数
|
||||
type BucketOperationCounts struct {
|
||||
OperationCountA int64 `json:"operation_count_a"`
|
||||
OperationCountB int64 `json:"operation_count_b"`
|
||||
Total int64 `json:"total"`
|
||||
}
|
||||
|
||||
// GetCurrentMonthStats 获取当前月份的统计
|
||||
func (h *StatsHandler) GetCurrentMonthStats(w http.ResponseWriter, r *http.Request) {
|
||||
stats, err := h.storage.GetCurrentMonthStats()
|
||||
if err != nil {
|
||||
log.Printf("Failed to get current month stats: %v", err)
|
||||
http.Error(w, "Failed to fetch statistics", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
response := h.formatMonthlyStats(stats)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(response)
|
||||
}
|
||||
|
||||
// GetMonthlyStats 获取指定月份的统计
|
||||
func (h *StatsHandler) GetMonthlyStats(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
year, err := strconv.Atoi(vars["year"])
|
||||
if err != nil {
|
||||
http.Error(w, "Invalid year", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
month, err := strconv.Atoi(vars["month"])
|
||||
if err != nil || month < 1 || month > 12 {
|
||||
http.Error(w, "Invalid month", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
stats, err := h.storage.GetMonthlyStats(year, month)
|
||||
if err != nil {
|
||||
log.Printf("Failed to get monthly stats: %v", err)
|
||||
http.Error(w, "Failed to fetch statistics", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
response := h.formatMonthlyStats(stats)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(response)
|
||||
}
|
||||
|
||||
// GetMonthlyStatsRange 获取时间范围内的统计
|
||||
func (h *StatsHandler) GetMonthlyStatsRange(w http.ResponseWriter, r *http.Request) {
|
||||
query := r.URL.Query()
|
||||
|
||||
startYear, err := strconv.Atoi(query.Get("start_year"))
|
||||
if err != nil {
|
||||
http.Error(w, "Invalid start_year", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
startMonth, err := strconv.Atoi(query.Get("start_month"))
|
||||
if err != nil || startMonth < 1 || startMonth > 12 {
|
||||
http.Error(w, "Invalid start_month", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
endYear, err := strconv.Atoi(query.Get("end_year"))
|
||||
if err != nil {
|
||||
http.Error(w, "Invalid end_year", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
endMonth, err := strconv.Atoi(query.Get("end_month"))
|
||||
if err != nil || endMonth < 1 || endMonth > 12 {
|
||||
http.Error(w, "Invalid end_month", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
stats, err := h.storage.GetMonthlyStatsRange(startYear, startMonth, endYear, endMonth)
|
||||
if err != nil {
|
||||
log.Printf("Failed to get monthly stats range: %v", err)
|
||||
http.Error(w, "Failed to fetch statistics", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
response := h.formatMonthlyStats(stats)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(response)
|
||||
}
|
||||
|
||||
// GetBucketHistory 获取指定存储桶的历史统计
|
||||
func (h *StatsHandler) GetBucketHistory(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
bucket := vars["bucket"]
|
||||
|
||||
// 获取查询参数中的月份数,默认12个月
|
||||
months := 12
|
||||
if monthsStr := r.URL.Query().Get("months"); monthsStr != "" {
|
||||
if m, err := strconv.Atoi(monthsStr); err == nil && m > 0 {
|
||||
months = m
|
||||
}
|
||||
}
|
||||
|
||||
stats, err := h.storage.GetBucketMonthlyHistory(bucket, months)
|
||||
if err != nil {
|
||||
log.Printf("Failed to get bucket history: %v", err)
|
||||
http.Error(w, "Failed to fetch statistics", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
response := h.formatMonthlyStats(stats)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(response)
|
||||
}
|
||||
|
||||
// formatMonthlyStats 格式化月度统计数据
|
||||
func (h *StatsHandler) formatMonthlyStats(stats []storage.BucketMonthlyStats) []MonthlyStatsResponse {
|
||||
result := make([]MonthlyStatsResponse, 0, len(stats))
|
||||
|
||||
for _, stat := range stats {
|
||||
result = append(result, MonthlyStatsResponse{
|
||||
Year: stat.Year,
|
||||
Month: stat.Month,
|
||||
Bucket: stat.BucketName,
|
||||
Stats: BucketOperationCounts{
|
||||
OperationCountA: stat.OperationCountA,
|
||||
OperationCountB: stat.OperationCountB,
|
||||
Total: stat.OperationCountA + stat.OperationCountB,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// ArchiveCurrentMonth 手动触发归档当前月份(管理API)
|
||||
func (h *StatsHandler) ArchiveCurrentMonth(w http.ResponseWriter, r *http.Request) {
|
||||
now := time.Now()
|
||||
year, month := now.Year(), int(now.Month())
|
||||
|
||||
if err := h.storage.ArchiveMonthlyStats(year, month); err != nil {
|
||||
log.Printf("Failed to archive monthly stats: %v", err)
|
||||
http.Error(w, "Failed to archive statistics", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(map[string]string{
|
||||
"status": "success",
|
||||
"message": "Monthly statistics archived successfully",
|
||||
"year": strconv.Itoa(year),
|
||||
"month": strconv.Itoa(month),
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user