Files
urldb/handlers/public_api_handler.go
2025-08-03 11:18:40 +08:00

322 lines
9.2 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 (
"strconv"
"strings"
"github.com/ctwj/urldb/db/dto"
"github.com/ctwj/urldb/db/entity"
"github.com/gin-gonic/gin"
)
// PublicAPIHandler 公开API处理器
type PublicAPIHandler struct{}
// NewPublicAPIHandler 创建公开API处理器
func NewPublicAPIHandler() *PublicAPIHandler {
return &PublicAPIHandler{}
}
// filterForbiddenWords 过滤包含违禁词的资源
func (h *PublicAPIHandler) filterForbiddenWords(resources []entity.Resource) ([]entity.Resource, []string) {
// 获取违禁词配置
forbiddenWords, err := repoManager.SystemConfigRepository.GetConfigValue(entity.ConfigKeyForbiddenWords)
if err != nil {
// 如果获取失败,返回原资源列表
return resources, nil
}
if forbiddenWords == "" {
return resources, nil
}
// 分割违禁词
words := strings.Split(forbiddenWords, ",")
var filteredResources []entity.Resource
var foundForbiddenWords []string
for _, resource := range resources {
shouldSkip := false
title := strings.ToLower(resource.Title)
description := strings.ToLower(resource.Description)
for _, word := range words {
word = strings.TrimSpace(word)
if word != "" && (strings.Contains(title, strings.ToLower(word)) || strings.Contains(description, strings.ToLower(word))) {
foundForbiddenWords = append(foundForbiddenWords, word)
shouldSkip = true
break
}
}
if !shouldSkip {
filteredResources = append(filteredResources, resource)
}
}
// 去重违禁词
uniqueForbiddenWords := make([]string, 0)
wordMap := make(map[string]bool)
for _, word := range foundForbiddenWords {
if !wordMap[word] {
wordMap[word] = true
uniqueForbiddenWords = append(uniqueForbiddenWords, word)
}
}
return filteredResources, uniqueForbiddenWords
}
// AddBatchResources godoc
// @Summary 批量添加资源
// @Description 通过公开API批量添加多个资源到待处理列表
// @Tags PublicAPI
// @Accept json
// @Produce json
// @Param X-API-Token header string true "API访问令牌"
// @Param data body dto.BatchReadyResourceRequest true "批量资源信息"
// @Success 200 {object} map[string]interface{} "批量添加成功"
// @Failure 400 {object} map[string]interface{} "请求参数错误"
// @Failure 401 {object} map[string]interface{} "认证失败"
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
// @Router /api/public/resources/batch-add [post]
func (h *PublicAPIHandler) AddBatchResources(c *gin.Context) {
var req dto.BatchReadyResourceRequest
if err := c.ShouldBindJSON(&req); err != nil {
ErrorResponse(c, "请求参数错误: "+err.Error(), 400)
return
}
if len(req.Resources) == 0 {
ErrorResponse(c, "资源列表不能为空", 400)
return
}
// 收集所有待提交的URL去重
urlSet := make(map[string]struct{})
for _, resource := range req.Resources {
for _, u := range resource.Url {
if u != "" {
urlSet[u] = struct{}{}
}
}
}
uniqueUrls := make([]string, 0, len(urlSet))
for url := range urlSet {
uniqueUrls = append(uniqueUrls, url)
}
// 批量查重
readyList, _ := repoManager.ReadyResourceRepository.BatchFindByURLs(uniqueUrls)
existReadyUrls := make(map[string]struct{})
for _, r := range readyList {
existReadyUrls[r.URL] = struct{}{}
}
resourceList, _ := repoManager.ResourceRepository.BatchFindByURLs(uniqueUrls)
existResourceUrls := make(map[string]struct{})
for _, r := range resourceList {
existResourceUrls[r.URL] = struct{}{}
}
var createdResources []uint
for _, resourceReq := range req.Resources {
// 生成 key每组同一个 key
key, err := repoManager.ReadyResourceRepository.GenerateUniqueKey()
if err != nil {
ErrorResponse(c, "生成资源组标识失败: "+err.Error(), 500)
return
}
for _, url := range resourceReq.Url {
if url == "" {
continue
}
if _, ok := existReadyUrls[url]; ok {
continue
}
if _, ok := existResourceUrls[url]; ok {
continue
}
readyResource := entity.ReadyResource{
Title: &resourceReq.Title,
Description: resourceReq.Description,
URL: url,
Category: resourceReq.Category,
Tags: resourceReq.Tags,
Img: resourceReq.Img,
Source: "api",
Extra: resourceReq.Extra,
Key: key,
}
err := repoManager.ReadyResourceRepository.Create(&readyResource)
if err == nil {
createdResources = append(createdResources, readyResource.ID)
}
}
}
SuccessResponse(c, gin.H{
"created_count": len(createdResources),
"created_ids": createdResources,
})
}
// SearchResources godoc
// @Summary 资源搜索
// @Description 搜索资源,支持关键词、标签、分类过滤,自动过滤包含违禁词的资源
// @Tags PublicAPI
// @Accept json
// @Produce json
// @Param X-API-Token header string true "API访问令牌"
// @Param keyword query string false "搜索关键词"
// @Param tag query string false "标签过滤"
// @Param category query string false "分类过滤"
// @Param page query int false "页码" default(1)
// @Param page_size query int false "每页数量" default(20) maximum(100)
// @Success 200 {object} map[string]interface{} "搜索成功如果存在违禁词过滤会返回forbidden_words_filtered字段"
// @Failure 401 {object} map[string]interface{} "认证失败"
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
// @Router /api/public/resources/search [get]
func (h *PublicAPIHandler) SearchResources(c *gin.Context) {
// 获取查询参数
keyword := c.Query("keyword")
tag := c.Query("tag")
category := c.Query("category")
pageStr := c.DefaultQuery("page", "1")
pageSizeStr := c.DefaultQuery("page_size", "20")
page, err := strconv.Atoi(pageStr)
if err != nil || page < 1 {
page = 1
}
pageSize, err := strconv.Atoi(pageSizeStr)
if err != nil || pageSize < 1 || pageSize > 100 {
pageSize = 20
}
// 构建搜索条件
params := map[string]interface{}{
"page": page,
"page_size": pageSize,
}
if keyword != "" {
params["search"] = keyword
}
if tag != "" {
params["tag"] = tag
}
if category != "" {
params["category"] = category
}
// 执行搜索
resources, total, err := repoManager.ResourceRepository.SearchWithFilters(params)
if err != nil {
ErrorResponse(c, "搜索失败: "+err.Error(), 500)
return
}
// 过滤违禁词
filteredResources, foundForbiddenWords := h.filterForbiddenWords(resources)
// 计算过滤后的总数
filteredTotal := len(filteredResources)
// 转换为响应格式
var resourceResponses []gin.H
for _, resource := range filteredResources {
resourceResponses = append(resourceResponses, gin.H{
"id": resource.ID,
"title": resource.Title,
"url": resource.URL,
"description": resource.Description,
"view_count": resource.ViewCount,
"created_at": resource.CreatedAt.Format("2006-01-02 15:04:05"),
"updated_at": resource.UpdatedAt.Format("2006-01-02 15:04:05"),
})
}
// 构建响应数据
responseData := gin.H{
"list": resourceResponses,
"total": filteredTotal,
"page": page,
"limit": pageSize,
}
// 如果存在违禁词过滤,添加提醒字段
if len(foundForbiddenWords) > 0 {
responseData["forbidden_words_filtered"] = true
responseData["filtered_forbidden_words"] = foundForbiddenWords
responseData["original_total"] = total
responseData["filtered_count"] = total - int64(filteredTotal)
}
SuccessResponse(c, responseData)
}
// GetHotDramas godoc
// @Summary 获取热门剧列表
// @Description 获取热门剧列表,支持分页
// @Tags PublicAPI
// @Accept json
// @Produce json
// @Param X-API-Token header string true "API访问令牌"
// @Param page query int false "页码" default(1)
// @Param page_size query int false "每页数量" default(20) maximum(100)
// @Success 200 {object} map[string]interface{} "获取成功"
// @Failure 401 {object} map[string]interface{} "认证失败"
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
// @Router /api/public/hot-dramas [get]
func (h *PublicAPIHandler) GetHotDramas(c *gin.Context) {
pageStr := c.DefaultQuery("page", "1")
pageSizeStr := c.DefaultQuery("page_size", "20")
page, err := strconv.Atoi(pageStr)
if err != nil || page < 1 {
page = 1
}
pageSize, err := strconv.Atoi(pageSizeStr)
if err != nil || pageSize < 1 || pageSize > 100 {
pageSize = 20
}
// 获取热门剧
hotDramas, total, err := repoManager.HotDramaRepository.FindAll(page, pageSize)
if err != nil {
ErrorResponse(c, "获取热门剧失败: "+err.Error(), 500)
return
}
// 转换为响应格式
var hotDramaResponses []gin.H
for _, drama := range hotDramas {
hotDramaResponses = append(hotDramaResponses, gin.H{
"id": drama.ID,
"title": drama.Title,
"description": drama.CardSubtitle, // 使用副标题作为描述
"img": drama.PosterURL, // 使用海报URL作为图片
"url": drama.DoubanURI, // 使用豆瓣链接作为URL
"rating": drama.Rating,
"year": drama.Year,
"region": drama.Region,
"genres": drama.Genres,
"category": drama.Category,
"created_at": drama.CreatedAt.Format("2006-01-02 15:04:05"),
"updated_at": drama.UpdatedAt.Format("2006-01-02 15:04:05"),
})
}
SuccessResponse(c, gin.H{
"hot_dramas": hotDramaResponses,
"total": total,
"page": page,
"page_size": pageSize,
})
}