Files
GitCodeStatic/internal/api/handlers/stats.go
2025-12-31 16:41:14 +08:00

225 lines
6.7 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 handlers
import (
"encoding/json"
"net/http"
"strconv"
"github.com/hanxuanyu/gitcodestatic/internal/logger"
"github.com/hanxuanyu/gitcodestatic/internal/service"
"github.com/hanxuanyu/gitcodestatic/internal/storage"
)
// StatsHandler 统计API处理器
type StatsHandler struct {
statsService *service.StatsService
store storage.Store
}
// NewStatsHandler 创建统计处理器
func NewStatsHandler(statsService *service.StatsService, store storage.Store) *StatsHandler {
return &StatsHandler{
statsService: statsService,
store: store,
}
}
// Calculate 触发统计计算
// @Summary 触发统计任务
// @Description 异步触发统计计算任务
// @Tags 统计管理
// @Accept json
// @Produce json
// @Param request body service.CalculateRequest true "统计请求"
// @Success 200 {object} Response{data=models.Task}
// @Failure 400 {object} Response
// @Router /stats/calculate [post]
func (h *StatsHandler) Calculate(w http.ResponseWriter, r *http.Request) {
var req service.CalculateRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
respondError(w, http.StatusBadRequest, 40001, "invalid request body")
return
}
if req.RepoID == 0 {
respondError(w, http.StatusBadRequest, 40001, "repo_id is required")
return
}
if req.Branch == "" {
respondError(w, http.StatusBadRequest, 40001, "branch is required")
return
}
// 校验约束参数
if err := service.ValidateStatsConstraint(req.Constraint); err != nil {
respondError(w, http.StatusBadRequest, 40001, err.Error())
return
}
task, err := h.statsService.Calculate(r.Context(), &req)
if err != nil {
logger.Logger.Error().Err(err).Msg("failed to submit stats task")
respondError(w, http.StatusInternalServerError, 50000, err.Error())
return
}
respondJSON(w, http.StatusOK, 0, "statistics task submitted", task)
}
// QueryResult 查询统计结果
// @Summary 查询统计结果
// @Description 查询统计计算结果
// @Tags 统计管理
// @Produce json
// @Param repo_id query int true "仓库ID"
// @Param branch query string true "分支名称"
// @Param constraint_type query string false "约束类型"
// @Param from query string false "开始日期"
// @Param to query string false "结束日期"
// @Param limit query int false "提交数限制"
// @Success 200 {object} Response{data=models.StatsResult}
// @Failure 400 {object} Response
// @Router /stats/query [get]
func (h *StatsHandler) QueryResult(w http.ResponseWriter, r *http.Request) {
repoID, _ := strconv.ParseInt(r.URL.Query().Get("repo_id"), 10, 64)
branch := r.URL.Query().Get("branch")
constraintType := r.URL.Query().Get("constraint_type")
from := r.URL.Query().Get("from")
to := r.URL.Query().Get("to")
limit, _ := strconv.Atoi(r.URL.Query().Get("limit"))
if repoID == 0 {
respondError(w, http.StatusBadRequest, 40001, "repo_id is required")
return
}
if branch == "" {
respondError(w, http.StatusBadRequest, 40001, "branch is required")
return
}
req := &service.QueryResultRequest{
RepoID: repoID,
Branch: branch,
ConstraintType: constraintType,
From: from,
To: to,
Limit: limit,
}
result, err := h.statsService.QueryResult(r.Context(), req)
if err != nil {
if err.Error() == "statistics not found, please submit calculation task first" {
respondError(w, http.StatusNotFound, 40400, err.Error())
return
}
logger.Logger.Error().Err(err).Msg("failed to query stats result")
respondError(w, http.StatusInternalServerError, 50000, err.Error())
return
}
respondJSON(w, http.StatusOK, 0, "success", result)
}
// CountCommits 统计提交次数
// @Summary 统计提交次数
// @Description 统计指定条件下的提交次数
// @Tags 统计管理
// @Produce json
// @Param repo_id query int true "仓库ID"
// @Param branch query string true "分支名称"
// @Param from query string false "开始日期"
// @Success 200 {object} Response{data=service.CountCommitsResponse}
// @Failure 400 {object} Response
// @Router /stats/commits/count [get]
func (h *StatsHandler) CountCommits(w http.ResponseWriter, r *http.Request) {
repoID, _ := strconv.ParseInt(r.URL.Query().Get("repo_id"), 10, 64)
branch := r.URL.Query().Get("branch")
from := r.URL.Query().Get("from")
if repoID == 0 {
respondError(w, http.StatusBadRequest, 40001, "repo_id is required")
return
}
if branch == "" {
respondError(w, http.StatusBadRequest, 40001, "branch is required")
return
}
req := &service.CountCommitsRequest{
RepoID: repoID,
Branch: branch,
From: from,
}
result, err := h.statsService.CountCommits(r.Context(), req)
if err != nil {
logger.Logger.Error().Err(err).Msg("failed to count commits")
respondError(w, http.StatusInternalServerError, 50000, err.Error())
return
}
respondJSON(w, http.StatusOK, 0, "success", result)
}
// ListCaches 获取统计缓存列表
// @Summary 获取统计缓存列表
// @Description 获取已计算的统计缓存列表
// @Tags 统计管理
// @Produce json
// @Param repo_id query int false "仓库ID可选不传则返回所有"
// @Param limit query int false "返回数量限制" default(50)
// @Success 200 {object} Response{data=object}
// @Failure 500 {object} Response
// @Router /stats/caches [get]
func (h *StatsHandler) ListCaches(w http.ResponseWriter, r *http.Request) {
repoID, _ := strconv.ParseInt(r.URL.Query().Get("repo_id"), 10, 64)
limitStr := r.URL.Query().Get("limit")
limit := 50
if limitStr != "" {
if l, err := strconv.Atoi(limitStr); err == nil && l > 0 {
limit = l
if limit > 200 {
limit = 200
}
}
}
caches, total, err := h.store.StatsCache().List(r.Context(), repoID, limit)
if err != nil {
logger.Logger.Error().Err(err).Msg("failed to list stats caches")
respondError(w, http.StatusInternalServerError, 50000, "failed to list stats caches")
return
}
data := map[string]interface{}{
"caches": caches,
"total": total,
}
respondJSON(w, http.StatusOK, 0, "success", data)
}
// ClearAllCaches 清除所有统计缓存
// @Summary 清除所有统计缓存
// @Description 删除所有统计缓存记录和文件
// @Tags 统计管理
// @Produce json
// @Success 200 {object} Response
// @Failure 500 {object} Response
// @Router /stats/caches/clear [delete]
func (h *StatsHandler) ClearAllCaches(w http.ResponseWriter, r *http.Request) {
// 删除数据库中的缓存记录
if err := h.store.StatsCache().DeleteAll(r.Context()); err != nil {
logger.Logger.Error().Err(err).Msg("failed to clear all caches")
respondError(w, http.StatusInternalServerError, 50000, "failed to clear caches")
return
}
logger.Logger.Info().Msg("all stats caches cleared")
respondJSON(w, http.StatusOK, 0, "所有统计缓存已清除", nil)
}