Files
SmyWorkCollect/SproutWorkCollect-Backend-Golang/internal/handler/public.go
2026-03-18 22:09:43 +08:00

232 lines
5.4 KiB
Go

package handler
import (
"net/http"
"strconv"
"github.com/gin-gonic/gin"
"sproutworkcollect-backend/internal/model"
"sproutworkcollect-backend/internal/service"
)
// PublicHandler handles all publicly accessible API endpoints.
type PublicHandler struct {
workSvc *service.WorkService
settingsSvc *service.SettingsService
rateLimiter *service.RateLimiter
}
// NewPublicHandler wires up a PublicHandler with its dependencies.
func NewPublicHandler(
workSvc *service.WorkService,
settingsSvc *service.SettingsService,
rateLimiter *service.RateLimiter,
) *PublicHandler {
return &PublicHandler{
workSvc: workSvc,
settingsSvc: settingsSvc,
rateLimiter: rateLimiter,
}
}
// GetSettings handles GET /api/settings
func (h *PublicHandler) GetSettings(c *gin.Context) {
settings, err := h.settingsSvc.Load()
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, settings)
}
// GetWorks handles GET /api/works
func (h *PublicHandler) GetWorks(c *gin.Context) {
works, err := h.workSvc.LoadAllWorks()
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"success": false, "message": err.Error()})
return
}
page, pageSize, paged := resolvePageParams(c)
total := len(works)
worksPage := paginateWorks(works, page, pageSize)
ids := make([]string, len(worksPage))
for i, w := range worksPage {
ids[i] = w.WorkID
}
resp := gin.H{
"success": true,
"data": ids,
"total": total,
}
if paged {
resp["page"] = page
resp["page_size"] = pageSize
resp["total_pages"] = calcTotalPages(total, pageSize)
}
c.JSON(http.StatusOK, resp)
}
// GetWorkDetail handles GET /api/works/:work_id
func (h *PublicHandler) GetWorkDetail(c *gin.Context) {
workID := c.Param("work_id")
work, err := h.workSvc.LoadWork(workID)
if err != nil {
c.JSON(http.StatusNotFound, gin.H{"success": false, "message": "作品不存在"})
return
}
fp := service.Fingerprint(c.ClientIP(), c.GetHeader("User-Agent"))
if h.rateLimiter.CanPerform(fp, "view", workID) {
_ = h.workSvc.UpdateStats(workID, "view")
// Reload to return the updated view count.
if fresh, err2 := h.workSvc.LoadWork(workID); err2 == nil {
work = fresh
}
}
c.JSON(http.StatusOK, gin.H{
"success": true,
"data": h.workSvc.BuildResponse(work),
})
}
// SearchWorks handles GET /api/search?q=...&category=...
func (h *PublicHandler) SearchWorks(c *gin.Context) {
works, err := h.workSvc.SearchWorks(c.Query("q"), c.Query("category"))
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"success": false, "message": err.Error()})
return
}
page, pageSize, paged := resolvePageParams(c)
total := len(works)
worksPage := paginateWorks(works, page, pageSize)
ids := make([]string, len(worksPage))
for i, w := range worksPage {
ids[i] = w.WorkID
}
resp := gin.H{
"success": true,
"data": ids,
"total": total,
}
if paged {
resp["page"] = page
resp["page_size"] = pageSize
resp["total_pages"] = calcTotalPages(total, pageSize)
}
c.JSON(http.StatusOK, resp)
}
func resolvePageParams(c *gin.Context) (int, int, bool) {
page, hasPage := parsePositiveInt(c, "page")
pageSize, hasSize := parsePositiveInt(c, "page_size")
if !hasSize {
pageSize, hasSize = parsePositiveInt(c, "pageSize")
}
if !hasSize {
pageSize, hasSize = parsePositiveInt(c, "limit")
}
if !hasPage && !hasSize {
return 0, 0, false
}
if page <= 0 {
page = 1
}
if pageSize <= 0 {
pageSize = 12
}
if pageSize > 200 {
pageSize = 200
}
return page, pageSize, true
}
func parsePositiveInt(c *gin.Context, key string) (int, bool) {
raw, ok := c.GetQuery(key)
if !ok {
return 0, false
}
val, err := strconv.Atoi(raw)
if err != nil {
return 0, false
}
return val, true
}
func paginateWorks(works []*model.WorkConfig, page, pageSize int) []*model.WorkConfig {
if pageSize <= 0 {
return works
}
if page <= 0 {
page = 1
}
start := (page - 1) * pageSize
if start >= len(works) {
return []*model.WorkConfig{}
}
end := start + pageSize
if end > len(works) {
end = len(works)
}
return works[start:end]
}
func calcTotalPages(total, pageSize int) int {
if pageSize <= 0 {
return 1
}
if total == 0 {
return 0
}
return (total + pageSize - 1) / pageSize
}
// GetCategories handles GET /api/categories
func (h *PublicHandler) GetCategories(c *gin.Context) {
cats, err := h.workSvc.AllCategories()
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"success": false, "message": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"success": true, "data": cats})
}
// LikeWork handles POST /api/like/:work_id
func (h *PublicHandler) LikeWork(c *gin.Context) {
workID := c.Param("work_id")
if _, err := h.workSvc.LoadWork(workID); err != nil {
c.JSON(http.StatusNotFound, gin.H{"success": false, "message": "作品不存在"})
return
}
fp := service.Fingerprint(c.ClientIP(), c.GetHeader("User-Agent"))
if !h.rateLimiter.CanPerform(fp, "like", workID) {
c.JSON(http.StatusTooManyRequests, gin.H{
"success": false,
"message": "操作太频繁,请稍后再试",
})
return
}
if err := h.workSvc.UpdateStats(workID, "like"); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"success": false, "message": "点赞失败"})
return
}
work, _ := h.workSvc.LoadWork(workID)
c.JSON(http.StatusOK, gin.H{
"success": true,
"message": "点赞成功",
"likes": work.Likes,
})
}