Files
2026-06-10 03:29:17 +00:00

145 lines
3.8 KiB
Go
Raw Permalink 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 handler
import (
"net/http"
"strings"
"sync"
"api-server/internal/scraper"
"github.com/gin-gonic/gin"
)
// ExtractHandler 视频提取 API 处理器
type ExtractHandler struct {
session *scraper.Session
}
// NewExtractHandler 创建提取处理器
func NewExtractHandler() *ExtractHandler {
return &ExtractHandler{
session: scraper.NewSession(),
}
}
// ExtractRequest 提取请求
type ExtractRequest struct {
URL string `json:"url" binding:"required" example:"https://h1014.sol148.com/view_video.php?viewkey=xxx"`
}
// BatchExtractRequest 批量提取请求
type BatchExtractRequest struct {
URLs []string `json:"urls" binding:"required"`
Concurrency int `json:"concurrency" example:"3"`
}
// RegisterRoutes 注册路由
func (h *ExtractHandler) RegisterRoutes(r *gin.RouterGroup) {
r.POST("/extract", h.ExtractSingle)
r.POST("/extract/batch", h.ExtractBatch)
}
// ExtractSingle 提取单个视频源
// @Summary 提取视频源
// @Description 从视频播放页 URL 提取正片源地址与元信息(标题、时长、热度、作者等)
// @Description 原理strencode2 → unescape → 正则提取 <source> 中 .mp4 链接
// @Tags extract
// @Accept json
// @Produce json
// @Param body body ExtractRequest true "视频播放页 URL"
// @Success 200 {object} map[string]interface{}
// @Failure 400 {object} map[string]interface{}
// @Failure 500 {object} map[string]interface{}
// @Router /extract [post]
func (h *ExtractHandler) ExtractSingle(c *gin.Context) {
var req struct {
URL string `json:"url" binding:"required"`
}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"code": 1, "msg": "请提供视频 URL"})
return
}
info, err := scraper.ExtractVideo(h.session, req.URL)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"code": 1,
"msg": "提取失败: " + err.Error(),
})
return
}
c.JSON(http.StatusOK, gin.H{"code": 0, "data": info})
}
// ExtractBatch 批量提取视频源
// @Summary 批量提取视频源
// @Description 并发提取多个视频播放页的源地址支持自定义并发度默认3最大10
// @Tags extract
// @Accept json
// @Produce json
// @Param body body BatchExtractRequest true "URL 列表 + 并发度"
// @Success 200 {object} map[string]interface{}
// @Failure 400 {object} map[string]interface{}
// @Router /extract/batch [post]
func (h *ExtractHandler) ExtractBatch(c *gin.Context) {
var req struct {
URLs []string `json:"urls" binding:"required"`
Concurrency int `json:"concurrency"`
}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"code": 1, "msg": "请提供 URL 列表"})
return
}
if req.Concurrency <= 0 {
req.Concurrency = 3
}
if req.Concurrency > 10 {
req.Concurrency = 10
}
type result struct {
URL string `json:"url"`
Data *scraper.VideoInfo `json:"data,omitempty"`
Err string `json:"error,omitempty"`
}
results := make([]result, len(req.URLs))
sem := make(chan struct{}, req.Concurrency)
var wg sync.WaitGroup
for i, u := range req.URLs {
wg.Add(1)
go func(idx int, urlStr string) {
defer wg.Done()
sem <- struct{}{}
defer func() { <-sem }()
s := scraper.NewSession()
info, err := scraper.ExtractVideo(s, strings.TrimSpace(urlStr))
if err != nil {
results[idx] = result{URL: urlStr, Err: err.Error()}
} else {
results[idx] = result{URL: urlStr, Data: info}
}
}(i, u)
}
wg.Wait()
success := 0
for _, r := range results {
if r.Err == "" {
success++
}
}
c.JSON(http.StatusOK, gin.H{
"code": 0,
"total": len(req.URLs),
"success": success,
"data": results,
})
}