功能开发完成

This commit is contained in:
2025-12-31 16:23:40 +08:00
parent 2b51050ca8
commit 6f0598a859
28 changed files with 5463 additions and 118 deletions

View File

@@ -5,9 +5,9 @@ import (
"net/http"
"strconv"
"github.com/go-chi/chi/v5"
"github.com/gitcodestatic/gitcodestatic/internal/logger"
"github.com/gitcodestatic/gitcodestatic/internal/service"
"github.com/go-chi/chi/v5"
)
// RepoHandler 仓库API处理器
@@ -23,6 +23,15 @@ func NewRepoHandler(repoService *service.RepoService) *RepoHandler {
}
// AddBatch 批量添加仓库
// @Summary 批量添加仓库
// @Description 批量添加多个Git仓库异步克隆到本地
// @Tags 仓库管理
// @Accept json
// @Produce json
// @Param request body service.AddReposRequest true "仓库URL列表"
// @Success 200 {object} Response{data=service.AddReposResponse}
// @Failure 400 {object} Response
// @Router /repos/batch [post]
func (h *RepoHandler) AddBatch(w http.ResponseWriter, r *http.Request) {
var req service.AddReposRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
@@ -30,8 +39,8 @@ func (h *RepoHandler) AddBatch(w http.ResponseWriter, r *http.Request) {
return
}
if len(req.URLs) == 0 {
respondError(w, http.StatusBadRequest, 40001, "urls cannot be empty")
if len(req.Repos) == 0 {
respondError(w, http.StatusBadRequest, 40001, "repos cannot be empty")
return
}
@@ -46,6 +55,16 @@ func (h *RepoHandler) AddBatch(w http.ResponseWriter, r *http.Request) {
}
// List 获取仓库列表
// @Summary 获取仓库列表
// @Description 分页查询仓库列表,支持按状态筛选
// @Tags 仓库管理
// @Accept json
// @Produce json
// @Param status query string false "状态筛选(pending/cloning/ready/failed)"
// @Param page query int false "页码" default(1)
// @Param page_size query int false "每页数量" default(20)
// @Success 200 {object} Response
// @Router /repos [get]
func (h *RepoHandler) List(w http.ResponseWriter, r *http.Request) {
status := r.URL.Query().Get("status")
page, _ := strconv.Atoi(r.URL.Query().Get("page"))
@@ -76,6 +95,15 @@ func (h *RepoHandler) List(w http.ResponseWriter, r *http.Request) {
}
// Get 获取仓库详情
// @Summary 获取仓库详情
// @Description 根据ID获取仓库详细信息
// @Tags 仓库管理
// @Accept json
// @Produce json
// @Param id path int true "仓库ID"
// @Success 200 {object} Response{data=models.Repository}
// @Failure 404 {object} Response
// @Router /repos/{id} [get]
func (h *RepoHandler) Get(w http.ResponseWriter, r *http.Request) {
id, err := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64)
if err != nil {
@@ -93,6 +121,16 @@ func (h *RepoHandler) Get(w http.ResponseWriter, r *http.Request) {
}
// SwitchBranch 切换分支
// @Summary 切换仓库分支
// @Description 异步切换仓库到指定分支
// @Tags 仓库管理
// @Accept json
// @Produce json
// @Param id path int true "仓库ID"
// @Param request body object{branch=string} true "分支名称"
// @Success 200 {object} Response{data=models.Task}
// @Failure 400 {object} Response
// @Router /repos/{id}/switch-branch [post]
func (h *RepoHandler) SwitchBranch(w http.ResponseWriter, r *http.Request) {
id, err := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64)
if err != nil {
@@ -124,6 +162,15 @@ func (h *RepoHandler) SwitchBranch(w http.ResponseWriter, r *http.Request) {
}
// Update 更新仓库
// @Summary 更新仓库
// @Description 异步拉取仓库最新代码(git pull)
// @Tags 仓库管理
// @Accept json
// @Produce json
// @Param id path int true "仓库ID"
// @Success 200 {object} Response{data=models.Task}
// @Failure 400 {object} Response
// @Router /repos/{id}/update [post]
func (h *RepoHandler) Update(w http.ResponseWriter, r *http.Request) {
id, err := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64)
if err != nil {
@@ -142,6 +189,14 @@ func (h *RepoHandler) Update(w http.ResponseWriter, r *http.Request) {
}
// Reset 重置仓库
// @Summary 重置仓库
// @Description 异步重置仓库到最新状态
// @Tags 仓库管理
// @Produce json
// @Param id path int true "仓库ID"
// @Success 200 {object} Response{data=models.Task}
// @Failure 400 {object} Response
// @Router /repos/{id}/reset [post]
func (h *RepoHandler) Reset(w http.ResponseWriter, r *http.Request) {
id, err := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64)
if err != nil {
@@ -160,6 +215,14 @@ func (h *RepoHandler) Reset(w http.ResponseWriter, r *http.Request) {
}
// Delete 删除仓库
// @Summary 删除仓库
// @Description 删除指定仓库
// @Tags 仓库管理
// @Produce json
// @Param id path int true "仓库ID"
// @Success 200 {object} Response
// @Failure 400 {object} Response
// @Router /repos/{id} [delete]
func (h *RepoHandler) Delete(w http.ResponseWriter, r *http.Request) {
id, err := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64)
if err != nil {
@@ -175,3 +238,35 @@ func (h *RepoHandler) Delete(w http.ResponseWriter, r *http.Request) {
respondJSON(w, http.StatusOK, 0, "repository deleted successfully", nil)
}
// GetBranches 获取仓库分支列表
// @Summary 获取仓库分支列表
// @Description 获取指定仓库的所有分支
// @Tags 仓库管理
// @Produce json
// @Param id path int true "仓库ID"
// @Success 200 {object} Response{data=object}
// @Failure 400 {object} Response
// @Failure 404 {object} Response
// @Router /repos/{id}/branches [get]
func (h *RepoHandler) GetBranches(w http.ResponseWriter, r *http.Request) {
id, err := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64)
if err != nil {
respondError(w, http.StatusBadRequest, 40001, "invalid repository id")
return
}
branches, err := h.repoService.GetBranches(r.Context(), id)
if err != nil {
logger.Logger.Error().Err(err).Int64("repo_id", id).Msg("failed to get branches")
respondError(w, http.StatusInternalServerError, 50000, err.Error())
return
}
data := map[string]interface{}{
"branches": branches,
"count": len(branches),
}
respondJSON(w, http.StatusOK, 0, "success", data)
}

View File

@@ -7,21 +7,33 @@ import (
"github.com/gitcodestatic/gitcodestatic/internal/logger"
"github.com/gitcodestatic/gitcodestatic/internal/service"
"github.com/gitcodestatic/gitcodestatic/internal/storage"
)
// StatsHandler 统计API处理器
type StatsHandler struct {
statsService *service.StatsService
store storage.Store
}
// NewStatsHandler 创建统计处理器
func NewStatsHandler(statsService *service.StatsService) *StatsHandler {
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 {
@@ -56,6 +68,19 @@ func (h *StatsHandler) Calculate(w http.ResponseWriter, r *http.Request) {
}
// 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")
@@ -98,6 +123,16 @@ func (h *StatsHandler) QueryResult(w http.ResponseWriter, r *http.Request) {
}
// 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")
@@ -128,3 +163,62 @@ func (h *StatsHandler) CountCommits(w http.ResponseWriter, r *http.Request) {
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)
}

View File

@@ -0,0 +1,99 @@
package handlers
import (
"net/http"
"strconv"
"github.com/gitcodestatic/gitcodestatic/internal/logger"
"github.com/gitcodestatic/gitcodestatic/internal/storage"
)
// TaskHandler 任务API处理器
type TaskHandler struct {
store storage.Store
}
// NewTaskHandler 创建任务处理器
func NewTaskHandler(store storage.Store) *TaskHandler {
return &TaskHandler{
store: store,
}
}
// List 查询任务列表
// @Summary 查询任务列表
// @Description 查询任务列表,可按状态过滤
// @Tags 任务管理
// @Produce json
// @Param status query string false "任务状态"
// @Param limit query int false "返回数量限制" default(50)
// @Success 200 {object} Response{data=object}
// @Failure 500 {object} Response
// @Router /tasks [get]
func (h *TaskHandler) List(w http.ResponseWriter, r *http.Request) {
status := r.URL.Query().Get("status")
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
}
}
}
// 使用 List 方法repoID=0 表示不过滤仓库
tasks, total, err := h.store.Tasks().List(r.Context(), 0, status, 1, limit)
if err != nil {
logger.Logger.Error().Err(err).Msg("failed to list tasks")
respondError(w, http.StatusInternalServerError, 50000, "failed to list tasks")
return
}
data := map[string]interface{}{
"tasks": tasks,
"total": total,
}
respondJSON(w, http.StatusOK, 0, "success", data)
}
// ClearAllTasks 清除所有任务记录
// @Summary 清除所有任务记录
// @Description 删除所有任务记录(包括进行中的)
// @Tags 任务管理
// @Produce json
// @Success 200 {object} Response
// @Failure 500 {object} Response
// @Router /tasks/clear [delete]
func (h *TaskHandler) ClearAllTasks(w http.ResponseWriter, r *http.Request) {
if err := h.store.Tasks().DeleteAll(r.Context()); err != nil {
logger.Logger.Error().Err(err).Msg("failed to clear all tasks")
respondError(w, http.StatusInternalServerError, 50000, "failed to clear tasks")
return
}
logger.Logger.Info().Msg("all tasks cleared")
respondJSON(w, http.StatusOK, 0, "所有任务记录已清除", nil)
}
// ClearCompletedTasks 清除已完成的任务记录
// @Summary 清除已完成的任务记录
// @Description 删除已完成、失败或取消的任务记录
// @Tags 任务管理
// @Produce json
// @Success 200 {object} Response
// @Failure 500 {object} Response
// @Router /tasks/clear-completed [delete]
func (h *TaskHandler) ClearCompletedTasks(w http.ResponseWriter, r *http.Request) {
if err := h.store.Tasks().DeleteCompleted(r.Context()); err != nil {
logger.Logger.Error().Err(err).Msg("failed to clear completed tasks")
respondError(w, http.StatusInternalServerError, 50000, "failed to clear completed tasks")
return
}
logger.Logger.Info().Msg("completed tasks cleared")
respondJSON(w, http.StatusOK, 0, "已完成的任务记录已清除", nil)
}

View File

@@ -3,23 +3,32 @@ package api
import (
"net/http"
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
_ "github.com/gitcodestatic/gitcodestatic/docs"
"github.com/gitcodestatic/gitcodestatic/internal/api/handlers"
"github.com/gitcodestatic/gitcodestatic/internal/service"
"github.com/gitcodestatic/gitcodestatic/internal/storage"
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
httpSwagger "github.com/swaggo/http-swagger"
)
// Router 路由配置
type Router struct {
repoHandler *handlers.RepoHandler
statsHandler *handlers.StatsHandler
taskHandler *handlers.TaskHandler
webDir string
webEnabled bool
}
// NewRouter 创建路由
func NewRouter(repoService *service.RepoService, statsService *service.StatsService) *Router {
func NewRouter(repoService *service.RepoService, statsService *service.StatsService, store storage.Store, webDir string, webEnabled bool) *Router {
return &Router{
repoHandler: handlers.NewRepoHandler(repoService),
statsHandler: handlers.NewStatsHandler(statsService),
statsHandler: handlers.NewStatsHandler(statsService, store),
taskHandler: handlers.NewTaskHandler(store),
webDir: webDir,
webEnabled: webEnabled,
}
}
@@ -40,6 +49,19 @@ func (rt *Router) Setup() http.Handler {
w.Write([]byte(`{"status":"healthy"}`))
})
// Swagger documentation
r.Get("/swagger/*", httpSwagger.Handler(
httpSwagger.URL("/swagger/doc.json"),
))
// Web UI static files
if rt.webEnabled {
fileServer := http.FileServer(http.Dir(rt.webDir))
r.Get("/*", func(w http.ResponseWriter, r *http.Request) {
fileServer.ServeHTTP(w, r)
})
}
// API routes
r.Route("/api/v1", func(r chi.Router) {
// 仓库管理
@@ -47,6 +69,7 @@ func (rt *Router) Setup() http.Handler {
r.Post("/batch", rt.repoHandler.AddBatch)
r.Get("/", rt.repoHandler.List)
r.Get("/{id}", rt.repoHandler.Get)
r.Get("/{id}/branches", rt.repoHandler.GetBranches)
r.Post("/{id}/switch-branch", rt.repoHandler.SwitchBranch)
r.Post("/{id}/update", rt.repoHandler.Update)
r.Post("/{id}/reset", rt.repoHandler.Reset)
@@ -58,6 +81,15 @@ func (rt *Router) Setup() http.Handler {
r.Post("/calculate", rt.statsHandler.Calculate)
r.Get("/result", rt.statsHandler.QueryResult)
r.Get("/commit-count", rt.statsHandler.CountCommits)
r.Get("/caches", rt.statsHandler.ListCaches)
r.Delete("/caches/clear", rt.statsHandler.ClearAllCaches)
})
// 任务
r.Route("/tasks", func(r chi.Router) {
r.Get("/", rt.taskHandler.List)
r.Delete("/clear", rt.taskHandler.ClearAllTasks)
r.Delete("/clear-completed", rt.taskHandler.ClearCompletedTasks)
})
})