Files
urldb/handlers/ready_resource_handler.go

468 lines
13 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 (
"net/http"
"strconv"
"strings"
"github.com/ctwj/urldb/db/converter"
"github.com/ctwj/urldb/db/dto"
"github.com/ctwj/urldb/db/entity"
"github.com/gin-gonic/gin"
)
// GetReadyResources 获取待处理资源列表
func GetReadyResources(c *gin.Context) {
// 获取分页参数
pageStr := c.DefaultQuery("page", "1")
pageSizeStr := c.DefaultQuery("page_size", "100")
page, err := strconv.Atoi(pageStr)
if err != nil || page < 1 {
page = 1
}
pageSize, err := strconv.Atoi(pageSizeStr)
if err != nil || pageSize < 1 || pageSize > 1000 {
pageSize = 100
}
// 获取分页数据
resources, total, err := repoManager.ReadyResourceRepository.FindWithPagination(page, pageSize)
if err != nil {
ErrorResponse(c, err.Error(), http.StatusInternalServerError)
return
}
responses := converter.ToReadyResourceResponseList(resources)
// 使用标准化的分页响应格式
SuccessResponse(c, gin.H{
"data": responses,
"page": page,
"page_size": pageSize,
"total": total,
})
}
// BatchCreateReadyResources 批量创建待处理资源
func BatchCreateReadyResources(c *gin.Context) {
var req dto.BatchCreateReadyResourceRequest
if err := c.ShouldBindJSON(&req); err != nil {
ErrorResponse(c, err.Error(), http.StatusBadRequest)
return
}
// 1. 先收集所有待提交的URL去重
urlSet := make(map[string]struct{})
for _, reqResource := range req.Resources {
if len(reqResource.URL) == 0 {
continue
}
for _, u := range reqResource.URL {
if u != "" {
urlSet[u] = struct{}{}
}
}
}
uniqueUrls := make([]string, 0, len(urlSet))
for url := range urlSet {
uniqueUrls = append(uniqueUrls, url)
}
// 2. 批量查询待处理资源表中已存在的URL
existReadyUrls := make(map[string]struct{})
if len(uniqueUrls) > 0 {
readyList, _ := repoManager.ReadyResourceRepository.BatchFindByURLs(uniqueUrls)
for _, r := range readyList {
existReadyUrls[r.URL] = struct{}{}
}
}
// 3. 批量查询资源表中已存在的URL
existResourceUrls := make(map[string]struct{})
if len(uniqueUrls) > 0 {
resourceList, _ := repoManager.ResourceRepository.BatchFindByURLs(uniqueUrls)
for _, r := range resourceList {
existResourceUrls[r.URL] = struct{}{}
}
}
// 5. 过滤掉已存在的URL
var resources []entity.ReadyResource
for _, reqResource := range req.Resources {
if len(reqResource.URL) == 0 {
continue
}
key, err := repoManager.ReadyResourceRepository.GenerateUniqueKey()
if err != nil {
ErrorResponse(c, "生成批量资源组标识失败: "+err.Error(), http.StatusInternalServerError)
return
}
for _, url := range reqResource.URL {
if url == "" {
continue
}
if _, ok := existReadyUrls[url]; ok {
continue
}
if _, ok := existResourceUrls[url]; ok {
continue
}
resource := entity.ReadyResource{
Title: reqResource.Title,
Description: reqResource.Description,
URL: url,
Category: reqResource.Category,
Tags: reqResource.Tags,
Img: reqResource.Img,
Source: reqResource.Source,
Extra: reqResource.Extra,
IP: reqResource.IP,
Key: key,
}
resources = append(resources, resource)
}
}
if len(resources) == 0 {
SuccessResponse(c, gin.H{
"count": 0,
"message": "无新增资源所有URL均已存在",
})
return
}
err := repoManager.ReadyResourceRepository.BatchCreate(resources)
if err != nil {
ErrorResponse(c, err.Error(), http.StatusInternalServerError)
return
}
SuccessResponse(c, gin.H{
"count": len(resources),
"message": "批量创建成功",
})
}
// CreateReadyResourcesFromText 从文本创建待处理资源
func CreateReadyResourcesFromText(c *gin.Context) {
text := c.PostForm("text")
if text == "" {
ErrorResponse(c, "文本内容不能为空", http.StatusBadRequest)
return
}
lines := strings.Split(text, "\n")
var resources []entity.ReadyResource
for _, line := range lines {
line = strings.TrimSpace(line)
if line == "" {
continue
}
// 简单的URL提取逻辑
if strings.Contains(line, "http") {
resource := entity.ReadyResource{
URL: line,
}
resources = append(resources, resource)
}
}
if len(resources) == 0 {
ErrorResponse(c, "未找到有效的URL", http.StatusBadRequest)
return
}
err := repoManager.ReadyResourceRepository.BatchCreate(resources)
if err != nil {
ErrorResponse(c, err.Error(), http.StatusInternalServerError)
return
}
SuccessResponse(c, gin.H{
"count": len(resources),
"message": "从文本创建成功",
})
}
// DeleteReadyResource 删除待处理资源
func DeleteReadyResource(c *gin.Context) {
idStr := c.Param("id")
id, err := strconv.ParseUint(idStr, 10, 32)
if err != nil {
ErrorResponse(c, "无效的ID", http.StatusBadRequest)
return
}
err = repoManager.ReadyResourceRepository.Delete(uint(id))
if err != nil {
ErrorResponse(c, err.Error(), http.StatusInternalServerError)
return
}
SuccessResponse(c, gin.H{"message": "待处理资源删除成功"})
}
// ClearReadyResources 清空所有待处理资源
func ClearReadyResources(c *gin.Context) {
resources, err := repoManager.ReadyResourceRepository.FindAll()
if err != nil {
ErrorResponse(c, err.Error(), http.StatusInternalServerError)
return
}
for _, resource := range resources {
err = repoManager.ReadyResourceRepository.Delete(resource.ID)
if err != nil {
ErrorResponse(c, err.Error(), http.StatusInternalServerError)
return
}
}
SuccessResponse(c, gin.H{
"deleted_count": len(resources),
"message": "所有待处理资源已清空",
})
}
// GetReadyResourcesByKey 根据key获取待处理资源
func GetReadyResourcesByKey(c *gin.Context) {
key := c.Param("key")
if key == "" {
ErrorResponse(c, "key参数不能为空", http.StatusBadRequest)
return
}
resources, err := repoManager.ReadyResourceRepository.FindByKey(key)
if err != nil {
ErrorResponse(c, err.Error(), http.StatusInternalServerError)
return
}
responses := converter.ToReadyResourceResponseList(resources)
SuccessResponse(c, gin.H{
"data": responses,
"key": key,
"count": len(resources),
})
}
// DeleteReadyResourcesByKey 根据key删除待处理资源
func DeleteReadyResourcesByKey(c *gin.Context) {
key := c.Param("key")
if key == "" {
ErrorResponse(c, "key参数不能为空", http.StatusBadRequest)
return
}
// 先查询要删除的资源数量
resources, err := repoManager.ReadyResourceRepository.FindByKey(key)
if err != nil {
ErrorResponse(c, err.Error(), http.StatusInternalServerError)
return
}
if len(resources) == 0 {
ErrorResponse(c, "未找到指定key的资源", http.StatusNotFound)
return
}
// 删除所有具有相同key的资源
err = repoManager.ReadyResourceRepository.DeleteByKey(key)
if err != nil {
ErrorResponse(c, err.Error(), http.StatusInternalServerError)
return
}
SuccessResponse(c, gin.H{
"deleted_count": len(resources),
"key": key,
"message": "资源组删除成功",
})
}
// getRetryableErrorCount 统计可重试的错误数量
func getRetryableErrorCount(resources []entity.ReadyResource) int {
count := 0
for _, resource := range resources {
if resource.ErrorMsg != "" {
errorMsg := strings.ToUpper(resource.ErrorMsg)
// 检查错误类型标记
if strings.Contains(resource.ErrorMsg, "[NO_ACCOUNT]") ||
strings.Contains(resource.ErrorMsg, "[NO_VALID_ACCOUNT]") ||
strings.Contains(resource.ErrorMsg, "[TRANSFER_FAILED]") ||
strings.Contains(resource.ErrorMsg, "[LINK_CHECK_FAILED]") {
count++
} else if strings.Contains(errorMsg, "没有可用的网盘账号") ||
strings.Contains(errorMsg, "没有有效的网盘账号") ||
strings.Contains(errorMsg, "网盘信息获取失败") ||
strings.Contains(errorMsg, "链接检查失败") {
count++
}
}
}
return count
}
// GetReadyResourcesWithErrors 获取有错误信息的待处理资源
func GetReadyResourcesWithErrors(c *gin.Context) {
// 获取分页参数
pageStr := c.DefaultQuery("page", "1")
pageSizeStr := c.DefaultQuery("page_size", "100")
page, err := strconv.Atoi(pageStr)
if err != nil || page < 1 {
page = 1
}
pageSize, err := strconv.Atoi(pageSizeStr)
if err != nil || pageSize < 1 || pageSize > 1000 {
pageSize = 100
}
// 获取有错误的资源
resources, err := repoManager.ReadyResourceRepository.FindWithErrors()
if err != nil {
ErrorResponse(c, err.Error(), http.StatusInternalServerError)
return
}
responses := converter.ToReadyResourceResponseList(resources)
// 统计错误类型
errorTypeStats := make(map[string]int)
for _, resource := range resources {
if resource.ErrorMsg != "" {
// 尝试从错误信息中提取错误类型
if len(resource.ErrorMsg) > 0 && resource.ErrorMsg[0] == '[' {
endIndex := strings.Index(resource.ErrorMsg, "]")
if endIndex > 0 {
errorType := resource.ErrorMsg[1:endIndex]
errorTypeStats[errorType]++
} else {
errorTypeStats["UNKNOWN"]++
}
} else {
// 如果没有错误类型标记,尝试从错误信息中推断
errorMsg := strings.ToUpper(resource.ErrorMsg)
if strings.Contains(errorMsg, "不支持的链接") {
errorTypeStats["UNSUPPORTED_LINK"]++
} else if strings.Contains(errorMsg, "链接无效") {
errorTypeStats["INVALID_LINK"]++
} else if strings.Contains(errorMsg, "没有可用的网盘账号") {
errorTypeStats["NO_ACCOUNT"]++
} else if strings.Contains(errorMsg, "没有有效的网盘账号") {
errorTypeStats["NO_VALID_ACCOUNT"]++
} else if strings.Contains(errorMsg, "网盘信息获取失败") {
errorTypeStats["TRANSFER_FAILED"]++
} else if strings.Contains(errorMsg, "创建网盘服务失败") {
errorTypeStats["SERVICE_CREATION_FAILED"]++
} else if strings.Contains(errorMsg, "处理标签失败") {
errorTypeStats["TAG_PROCESSING_FAILED"]++
} else if strings.Contains(errorMsg, "处理分类失败") {
errorTypeStats["CATEGORY_PROCESSING_FAILED"]++
} else if strings.Contains(errorMsg, "资源保存失败") {
errorTypeStats["RESOURCE_SAVE_FAILED"]++
} else if strings.Contains(errorMsg, "未找到对应的平台ID") {
errorTypeStats["PLATFORM_NOT_FOUND"]++
} else if strings.Contains(errorMsg, "链接检查失败") {
errorTypeStats["LINK_CHECK_FAILED"]++
} else {
errorTypeStats["UNKNOWN"]++
}
}
}
}
SuccessResponse(c, gin.H{
"data": responses,
"page": page,
"page_size": pageSize,
"total": len(resources),
"count": len(resources),
"error_stats": errorTypeStats,
"retryable_count": getRetryableErrorCount(resources),
})
}
// ClearErrorMsg 清除指定资源的错误信息
func ClearErrorMsg(c *gin.Context) {
idStr := c.Param("id")
id, err := strconv.ParseUint(idStr, 10, 32)
if err != nil {
ErrorResponse(c, "无效的ID", http.StatusBadRequest)
return
}
err = repoManager.ReadyResourceRepository.ClearErrorMsg(uint(id))
if err != nil {
ErrorResponse(c, err.Error(), http.StatusInternalServerError)
return
}
SuccessResponse(c, gin.H{"message": "错误信息已清除"})
}
// RetryFailedResources 重试失败的资源
func RetryFailedResources(c *gin.Context) {
// 获取有错误的资源
resources, err := repoManager.ReadyResourceRepository.FindWithErrors()
if err != nil {
ErrorResponse(c, err.Error(), http.StatusInternalServerError)
return
}
if len(resources) == 0 {
SuccessResponse(c, gin.H{
"message": "没有需要重试的资源",
"count": 0,
})
return
}
// 只重试可重试的错误
clearedCount := 0
skippedCount := 0
for _, resource := range resources {
isRetryable := false
errorMsg := strings.ToUpper(resource.ErrorMsg)
// 检查错误类型标记
if strings.Contains(resource.ErrorMsg, "[NO_ACCOUNT]") ||
strings.Contains(resource.ErrorMsg, "[NO_VALID_ACCOUNT]") ||
strings.Contains(resource.ErrorMsg, "[TRANSFER_FAILED]") ||
strings.Contains(resource.ErrorMsg, "[LINK_CHECK_FAILED]") {
isRetryable = true
} else if strings.Contains(errorMsg, "没有可用的网盘账号") ||
strings.Contains(errorMsg, "没有有效的网盘账号") ||
strings.Contains(errorMsg, "网盘信息获取失败") ||
strings.Contains(errorMsg, "链接检查失败") {
isRetryable = true
}
if isRetryable {
if err := repoManager.ReadyResourceRepository.ClearErrorMsg(resource.ID); err == nil {
clearedCount++
}
} else {
skippedCount++
}
}
SuccessResponse(c, gin.H{
"message": "已清除可重试资源的错误信息,资源将在下次调度时重新处理",
"total_count": len(resources),
"cleared_count": clearedCount,
"skipped_count": skippedCount,
"retryable_count": getRetryableErrorCount(resources),
})
}