mirror of
https://github.com/ctwj/urldb.git
synced 2025-11-25 11:29:37 +08:00
update: 更新批量添加接口,支持一个资源多个链接
This commit is contained in:
@@ -195,23 +195,23 @@ func ToReadyResourceResponseList(resources []entity.ReadyResource) []dto.ReadyRe
|
|||||||
}
|
}
|
||||||
|
|
||||||
// RequestToReadyResource 将ReadyResourceRequest转换为ReadyResource实体
|
// RequestToReadyResource 将ReadyResourceRequest转换为ReadyResource实体
|
||||||
func RequestToReadyResource(req *dto.ReadyResourceRequest) *entity.ReadyResource {
|
// func RequestToReadyResource(req *dto.ReadyResourceRequest) *entity.ReadyResource {
|
||||||
if req == nil {
|
// if req == nil {
|
||||||
return nil
|
// return nil
|
||||||
}
|
// }
|
||||||
|
|
||||||
return &entity.ReadyResource{
|
// return &entity.ReadyResource{
|
||||||
Title: &req.Title,
|
// Title: &req.Title,
|
||||||
Description: req.Description,
|
// Description: req.Description,
|
||||||
URL: req.Url,
|
// URL: req.Url,
|
||||||
Category: req.Category,
|
// Category: req.Category,
|
||||||
Tags: req.Tags,
|
// Tags: req.Tags,
|
||||||
Img: req.Img,
|
// Img: req.Img,
|
||||||
Source: req.Source,
|
// Source: req.Source,
|
||||||
Extra: req.Extra,
|
// Extra: req.Extra,
|
||||||
Key: req.Key,
|
// Key: req.Key,
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
// SystemConfigToPublicResponse 返回不含 api_token 的系统配置响应
|
// SystemConfigToPublicResponse 返回不含 api_token 的系统配置响应
|
||||||
func SystemConfigToPublicResponse(config *entity.SystemConfig) gin.H {
|
func SystemConfigToPublicResponse(config *entity.SystemConfig) gin.H {
|
||||||
|
|||||||
@@ -2,19 +2,17 @@ package dto
|
|||||||
|
|
||||||
// ReadyResourceRequest 待处理资源请求
|
// ReadyResourceRequest 待处理资源请求
|
||||||
type ReadyResourceRequest struct {
|
type ReadyResourceRequest struct {
|
||||||
Title string `json:"title" validate:"required" example:"示例资源标题"`
|
Title string `json:"title" validate:"required" example:"示例资源标题"`
|
||||||
Description string `json:"description" example:"这是一个示例资源描述"`
|
Description string `json:"description" example:"这是一个示例资源描述"`
|
||||||
Url string `json:"url" validate:"required" example:"https://example.com/resource"`
|
Url []string `json:"url" validate:"required" example:"https://example.com/resource"`
|
||||||
Category string `json:"category" example:"示例分类"`
|
Category string `json:"category" example:"示例分类"`
|
||||||
Tags string `json:"tags" example:"标签1,标签2"`
|
Tags string `json:"tags" example:"标签1,标签2"`
|
||||||
Img string `json:"img" example:"https://example.com/image.jpg"`
|
Img string `json:"img" example:"https://example.com/image.jpg"`
|
||||||
Source string `json:"source" example:"数据来源"`
|
Source string `json:"source" example:"数据来源"`
|
||||||
Extra string `json:"extra" example:"额外信息"`
|
Extra string `json:"extra" example:"额外信息"`
|
||||||
Key string `json:"key" example:"资源组标识,可选,不提供则自动生成"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// BatchReadyResourceRequest 批量待处理资源请求
|
// BatchReadyResourceRequest 批量待处理资源请求
|
||||||
type BatchReadyResourceRequest struct {
|
type BatchReadyResourceRequest struct {
|
||||||
Resources []ReadyResourceRequest `json:"resources" validate:"required"`
|
Resources []ReadyResourceRequest `json:"resources" validate:"required"`
|
||||||
Key string `json:"key" example:"批量资源的组标识,可选,不提供则自动生成"`
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -108,22 +108,21 @@ type UpdateTagRequest struct {
|
|||||||
|
|
||||||
// CreateReadyResourceRequest 创建待处理资源请求
|
// CreateReadyResourceRequest 创建待处理资源请求
|
||||||
type CreateReadyResourceRequest struct {
|
type CreateReadyResourceRequest struct {
|
||||||
Title *string `json:"title"`
|
Title *string `json:"title"`
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
URL string `json:"url" binding:"required"`
|
URL []string `json:"url" binding:"required"`
|
||||||
Category string `json:"category"`
|
Category string `json:"category"`
|
||||||
Tags string `json:"tags"`
|
Tags string `json:"tags"`
|
||||||
Img string `json:"img"`
|
Img string `json:"img"`
|
||||||
Source string `json:"source"`
|
Source string `json:"source"`
|
||||||
Extra string `json:"extra"`
|
Extra string `json:"extra"`
|
||||||
IP *string `json:"ip"`
|
IP *string `json:"ip"`
|
||||||
Key string `json:"key"`
|
Key string `json:"key"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// BatchCreateReadyResourceRequest 批量创建待处理资源请求
|
// BatchCreateReadyResourceRequest 批量创建待处理资源请求
|
||||||
type BatchCreateReadyResourceRequest struct {
|
type BatchCreateReadyResourceRequest struct {
|
||||||
Resources []CreateReadyResourceRequest `json:"resources" binding:"required"`
|
Resources []CreateReadyResourceRequest `json:"resources" binding:"required"`
|
||||||
Key string `json:"key"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SearchRequest 搜索请求
|
// SearchRequest 搜索请求
|
||||||
|
|||||||
@@ -324,7 +324,7 @@ func RefreshCapacity(c *gin.Context) {
|
|||||||
cks.UsedSpace = userInfo.UsedSpace
|
cks.UsedSpace = userInfo.UsedSpace
|
||||||
cks.IsValid = userInfo.VIPStatus // 根据VIP状态更新有效性
|
cks.IsValid = userInfo.VIPStatus // 根据VIP状态更新有效性
|
||||||
|
|
||||||
err = repoManager.CksRepository.Update(cks)
|
err = repoManager.CksRepository.UpdateWithAllFields(cks)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ErrorResponse(c, err.Error(), http.StatusInternalServerError)
|
ErrorResponse(c, err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ package handlers
|
|||||||
import (
|
import (
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/ctwj/urldb/db/converter"
|
|
||||||
"github.com/ctwj/urldb/db/dto"
|
"github.com/ctwj/urldb/db/dto"
|
||||||
|
"github.com/ctwj/urldb/db/entity"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
@@ -17,70 +17,6 @@ func NewPublicAPIHandler() *PublicAPIHandler {
|
|||||||
return &PublicAPIHandler{}
|
return &PublicAPIHandler{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddSingleResource godoc
|
|
||||||
// @Summary 单个添加资源
|
|
||||||
// @Description 通过公开API添加单个资源到待处理列表
|
|
||||||
// @Tags PublicAPI
|
|
||||||
// @Accept json
|
|
||||||
// @Produce json
|
|
||||||
// @Param X-API-Token header string true "API访问令牌"
|
|
||||||
// @Param data body dto.ReadyResourceRequest 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/add [post]
|
|
||||||
func (h *PublicAPIHandler) AddSingleResource(c *gin.Context) {
|
|
||||||
var req dto.ReadyResourceRequest
|
|
||||||
if err := c.ShouldBindJSON(&req); err != nil {
|
|
||||||
ErrorResponse(c, "请求参数错误: "+err.Error(), 400)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 验证必填字段
|
|
||||||
if req.Title == "" {
|
|
||||||
ErrorResponse(c, "标题不能为空", 400)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if req.Url == "" {
|
|
||||||
ErrorResponse(c, "URL不能为空", 400)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 转换为实体
|
|
||||||
readyResource := converter.RequestToReadyResource(&req)
|
|
||||||
if readyResource == nil {
|
|
||||||
ErrorResponse(c, "数据转换失败", 500)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 设置来源
|
|
||||||
readyResource.Source = "公开API"
|
|
||||||
|
|
||||||
// 如果没有提供key,则自动生成
|
|
||||||
if readyResource.Key == "" {
|
|
||||||
key, err := repoManager.ReadyResourceRepository.GenerateUniqueKey()
|
|
||||||
if err != nil {
|
|
||||||
ErrorResponse(c, "生成资源组标识失败: "+err.Error(), 500)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
readyResource.Key = key
|
|
||||||
}
|
|
||||||
|
|
||||||
// 保存到数据库
|
|
||||||
err := repoManager.ReadyResourceRepository.Create(readyResource)
|
|
||||||
if err != nil {
|
|
||||||
ErrorResponse(c, "添加资源失败: "+err.Error(), 500)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
SuccessResponse(c, gin.H{
|
|
||||||
"id": readyResource.ID,
|
|
||||||
"key": readyResource.Key,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddBatchResources godoc
|
// AddBatchResources godoc
|
||||||
// @Summary 批量添加资源
|
// @Summary 批量添加资源
|
||||||
// @Description 通过公开API批量添加多个资源到待处理列表
|
// @Description 通过公开API批量添加多个资源到待处理列表
|
||||||
@@ -106,26 +42,62 @@ func (h *PublicAPIHandler) AddBatchResources(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 验证每个资源
|
// 收集所有待提交的URL,去重
|
||||||
for i, resource := range req.Resources {
|
urlSet := make(map[string]struct{})
|
||||||
if resource.Title == "" {
|
for _, resource := range req.Resources {
|
||||||
ErrorResponse(c, "第"+strconv.Itoa(i+1)+"个资源标题不能为空", 400)
|
for _, u := range resource.Url {
|
||||||
return
|
if u != "" {
|
||||||
}
|
urlSet[u] = struct{}{}
|
||||||
|
}
|
||||||
if resource.Url == "" {
|
|
||||||
ErrorResponse(c, "第"+strconv.Itoa(i+1)+"个资源URL不能为空", 400)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
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
|
var createdResources []uint
|
||||||
for _, resourceReq := range req.Resources {
|
for _, resourceReq := range req.Resources {
|
||||||
readyResource := converter.RequestToReadyResource(&resourceReq)
|
// 生成 key(每组同一个 key)
|
||||||
if readyResource != nil {
|
key, err := repoManager.ReadyResourceRepository.GenerateUniqueKey()
|
||||||
readyResource.Source = "公开API批量添加"
|
if err != nil {
|
||||||
err := repoManager.ReadyResourceRepository.Create(readyResource)
|
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 {
|
if err == nil {
|
||||||
createdResources = append(createdResources, readyResource.ID)
|
createdResources = append(createdResources, readyResource.ID)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,65 +46,6 @@ func GetReadyResources(c *gin.Context) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateReadyResource 创建待处理资源
|
|
||||||
func CreateReadyResource(c *gin.Context) {
|
|
||||||
var req dto.CreateReadyResourceRequest
|
|
||||||
if err := c.ShouldBindJSON(&req); err != nil {
|
|
||||||
ErrorResponse(c, err.Error(), http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if req.URL != "" {
|
|
||||||
// 检查待处理资源表
|
|
||||||
readyList, _ := repoManager.ReadyResourceRepository.BatchFindByURLs([]string{req.URL})
|
|
||||||
if len(readyList) > 0 {
|
|
||||||
ErrorResponse(c, "该URL已存在于待处理资源列表", http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// 检查资源表
|
|
||||||
resourceList, _ := repoManager.ResourceRepository.BatchFindByURLs([]string{req.URL})
|
|
||||||
if len(resourceList) > 0 {
|
|
||||||
ErrorResponse(c, "该URL已存在于资源列表", http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果没有提供key,则自动生成
|
|
||||||
if req.Key == "" {
|
|
||||||
key, err := repoManager.ReadyResourceRepository.GenerateUniqueKey()
|
|
||||||
if err != nil {
|
|
||||||
ErrorResponse(c, "生成资源组标识失败: "+err.Error(), http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
req.Key = key
|
|
||||||
}
|
|
||||||
|
|
||||||
resource := &entity.ReadyResource{
|
|
||||||
Title: req.Title,
|
|
||||||
Description: req.Description,
|
|
||||||
URL: req.URL,
|
|
||||||
Category: req.Category,
|
|
||||||
Tags: req.Tags,
|
|
||||||
Img: req.Img,
|
|
||||||
Source: req.Source,
|
|
||||||
Extra: req.Extra,
|
|
||||||
IP: req.IP,
|
|
||||||
Key: req.Key,
|
|
||||||
}
|
|
||||||
|
|
||||||
err := repoManager.ReadyResourceRepository.Create(resource)
|
|
||||||
if err != nil {
|
|
||||||
ErrorResponse(c, err.Error(), http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
SuccessResponse(c, gin.H{
|
|
||||||
"id": resource.ID,
|
|
||||||
"key": resource.Key,
|
|
||||||
"message": "待处理资源创建成功",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// BatchCreateReadyResources 批量创建待处理资源
|
// BatchCreateReadyResources 批量创建待处理资源
|
||||||
func BatchCreateReadyResources(c *gin.Context) {
|
func BatchCreateReadyResources(c *gin.Context) {
|
||||||
var req dto.BatchCreateReadyResourceRequest
|
var req dto.BatchCreateReadyResourceRequest
|
||||||
@@ -116,10 +57,14 @@ func BatchCreateReadyResources(c *gin.Context) {
|
|||||||
// 1. 先收集所有待提交的URL,去重
|
// 1. 先收集所有待提交的URL,去重
|
||||||
urlSet := make(map[string]struct{})
|
urlSet := make(map[string]struct{})
|
||||||
for _, reqResource := range req.Resources {
|
for _, reqResource := range req.Resources {
|
||||||
if reqResource.URL == "" {
|
if len(reqResource.URL) == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
urlSet[reqResource.URL] = struct{}{}
|
for _, u := range reqResource.URL {
|
||||||
|
if u != "" {
|
||||||
|
urlSet[u] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
uniqueUrls := make([]string, 0, len(urlSet))
|
uniqueUrls := make([]string, 0, len(urlSet))
|
||||||
for url := range urlSet {
|
for url := range urlSet {
|
||||||
@@ -144,50 +89,42 @@ func BatchCreateReadyResources(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. 生成批量key(如果请求中没有提供)
|
// 5. 过滤掉已存在的URL
|
||||||
batchKey := req.Key
|
var resources []entity.ReadyResource
|
||||||
if batchKey == "" {
|
for _, reqResource := range req.Resources {
|
||||||
|
if len(reqResource.URL) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
key, err := repoManager.ReadyResourceRepository.GenerateUniqueKey()
|
key, err := repoManager.ReadyResourceRepository.GenerateUniqueKey()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ErrorResponse(c, "生成批量资源组标识失败: "+err.Error(), http.StatusInternalServerError)
|
ErrorResponse(c, "生成批量资源组标识失败: "+err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
batchKey = key
|
for _, url := range reqResource.URL {
|
||||||
}
|
if url == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, ok := existReadyUrls[url]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, ok := existResourceUrls[url]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
// 5. 过滤掉已存在的URL
|
resource := entity.ReadyResource{
|
||||||
var resources []entity.ReadyResource
|
Title: reqResource.Title,
|
||||||
for _, reqResource := range req.Resources {
|
Description: reqResource.Description,
|
||||||
url := reqResource.URL
|
URL: url,
|
||||||
if url == "" {
|
Category: reqResource.Category,
|
||||||
continue
|
Tags: reqResource.Tags,
|
||||||
|
Img: reqResource.Img,
|
||||||
|
Source: reqResource.Source,
|
||||||
|
Extra: reqResource.Extra,
|
||||||
|
IP: reqResource.IP,
|
||||||
|
Key: key,
|
||||||
|
}
|
||||||
|
resources = append(resources, resource)
|
||||||
}
|
}
|
||||||
if _, ok := existReadyUrls[url]; ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if _, ok := existResourceUrls[url]; ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// 使用批量key或单个key
|
|
||||||
resourceKey := batchKey
|
|
||||||
if reqResource.Key != "" {
|
|
||||||
resourceKey = reqResource.Key
|
|
||||||
}
|
|
||||||
|
|
||||||
resource := entity.ReadyResource{
|
|
||||||
Title: reqResource.Title,
|
|
||||||
Description: reqResource.Description,
|
|
||||||
URL: reqResource.URL,
|
|
||||||
Category: reqResource.Category,
|
|
||||||
Tags: reqResource.Tags,
|
|
||||||
Img: reqResource.Img,
|
|
||||||
Source: reqResource.Source,
|
|
||||||
Extra: reqResource.Extra,
|
|
||||||
IP: reqResource.IP,
|
|
||||||
Key: resourceKey,
|
|
||||||
}
|
|
||||||
resources = append(resources, resource)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(resources) == 0 {
|
if len(resources) == 0 {
|
||||||
@@ -206,7 +143,6 @@ func BatchCreateReadyResources(c *gin.Context) {
|
|||||||
|
|
||||||
SuccessResponse(c, gin.H{
|
SuccessResponse(c, gin.H{
|
||||||
"count": len(resources),
|
"count": len(resources),
|
||||||
"key": batchKey,
|
|
||||||
"message": "批量创建成功",
|
"message": "批量创建成功",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
3
main.go
3
main.go
@@ -102,8 +102,6 @@ func main() {
|
|||||||
publicAPI := api.Group("/public")
|
publicAPI := api.Group("/public")
|
||||||
publicAPI.Use(middleware.PublicAPIAuth())
|
publicAPI.Use(middleware.PublicAPIAuth())
|
||||||
{
|
{
|
||||||
// 单个添加资源
|
|
||||||
publicAPI.POST("/resources/add", publicAPIHandler.AddSingleResource)
|
|
||||||
// 批量添加资源
|
// 批量添加资源
|
||||||
publicAPI.POST("/resources/batch-add", publicAPIHandler.AddBatchResources)
|
publicAPI.POST("/resources/batch-add", publicAPIHandler.AddBatchResources)
|
||||||
// 资源搜索
|
// 资源搜索
|
||||||
@@ -166,7 +164,6 @@ func main() {
|
|||||||
|
|
||||||
// 待处理资源管理
|
// 待处理资源管理
|
||||||
api.GET("/ready-resources", middleware.AuthMiddleware(), middleware.AdminMiddleware(), handlers.GetReadyResources)
|
api.GET("/ready-resources", middleware.AuthMiddleware(), middleware.AdminMiddleware(), handlers.GetReadyResources)
|
||||||
api.POST("/ready-resources", middleware.AuthMiddleware(), middleware.AdminMiddleware(), handlers.CreateReadyResource)
|
|
||||||
api.POST("/ready-resources/batch", middleware.AuthMiddleware(), middleware.AdminMiddleware(), handlers.BatchCreateReadyResources)
|
api.POST("/ready-resources/batch", middleware.AuthMiddleware(), middleware.AdminMiddleware(), handlers.BatchCreateReadyResources)
|
||||||
api.POST("/ready-resources/text", middleware.AuthMiddleware(), middleware.AdminMiddleware(), handlers.CreateReadyResourcesFromText)
|
api.POST("/ready-resources/text", middleware.AuthMiddleware(), middleware.AdminMiddleware(), handlers.CreateReadyResourcesFromText)
|
||||||
api.DELETE("/ready-resources/:id", middleware.AuthMiddleware(), middleware.AdminMiddleware(), handlers.DeleteReadyResource)
|
api.DELETE("/ready-resources/:id", middleware.AuthMiddleware(), middleware.AdminMiddleware(), handlers.DeleteReadyResource)
|
||||||
|
|||||||
@@ -4,10 +4,11 @@
|
|||||||
<div class="mb-4 flex-1 w-1">
|
<div class="mb-4 flex-1 w-1">
|
||||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">输入格式说明:</label>
|
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">输入格式说明:</label>
|
||||||
<div class="bg-gray-50 dark:bg-gray-800 p-3 rounded text-sm text-gray-600 dark:text-gray-300 mb-4">
|
<div class="bg-gray-50 dark:bg-gray-800 p-3 rounded text-sm text-gray-600 dark:text-gray-300 mb-4">
|
||||||
<p class="mb-2"><strong>格式要求:</strong>标题和URL两行为一组,标题为必填项</p>
|
<p class="mb-2"><strong>格式要求:</strong>标题和URL为一组,标题必填, 同一标题URL支持多行</p>
|
||||||
<pre class="bg-white dark:bg-gray-800 p-2 rounded border text-xs">
|
<pre class="bg-white dark:bg-gray-800 p-2 rounded border text-xs">
|
||||||
电影标题1
|
电影1
|
||||||
https://pan.baidu.com/s/123456
|
https://pan.baidu.com/s/123456 # 百度网盘 电影1
|
||||||
|
https://pan.quark.com/s/123456 # 夸克网盘 电影1
|
||||||
电影标题2
|
电影标题2
|
||||||
https://pan.baidu.com/s/789012
|
https://pan.baidu.com/s/789012
|
||||||
电视剧标题3
|
电视剧标题3
|
||||||
@@ -59,30 +60,11 @@ const validateInput = () => {
|
|||||||
throw new Error('请输入有效的资源内容')
|
throw new Error('请输入有效的资源内容')
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查是否为偶数行(标题+URL为一组)
|
// 首行必须为标题
|
||||||
if (lines.length % 2 !== 0) {
|
if (/^https?:\/\//i.test(lines[0])) {
|
||||||
throw new Error('资源格式错误:标题和URL必须成对出现,请检查是否缺少标题或URL')
|
// 你可以用 alert、ElMessage 或其它方式提示
|
||||||
}
|
alert('首行必须为标题,不能为链接!')
|
||||||
|
return
|
||||||
// 检查每组的标题是否为空
|
|
||||||
for (let i = 0; i < lines.length; i += 2) {
|
|
||||||
const title = lines[i]
|
|
||||||
const url = lines[i + 1]
|
|
||||||
|
|
||||||
if (!title) {
|
|
||||||
throw new Error(`第${i + 1}行标题不能为空`)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!url) {
|
|
||||||
throw new Error(`第${i + 2}行URL不能为空`)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 验证URL格式
|
|
||||||
try {
|
|
||||||
new URL(url)
|
|
||||||
} catch {
|
|
||||||
throw new Error(`第${i + 2}行URL格式无效: ${url}`)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,14 +78,30 @@ const handleSubmit = async () => {
|
|||||||
const lines = batchInput.value.split(/\r?\n/).map(line => line.trim()).filter(Boolean)
|
const lines = batchInput.value.split(/\r?\n/).map(line => line.trim()).filter(Boolean)
|
||||||
const resources = []
|
const resources = []
|
||||||
|
|
||||||
for (let i = 0; i < lines.length; i += 2) {
|
let currentTitle = ''
|
||||||
const title = lines[i]
|
let currentUrls = []
|
||||||
const url = lines[i + 1]
|
|
||||||
|
|
||||||
|
for (const line of lines) {
|
||||||
|
// 判断是否为 url(以 http/https 开头)
|
||||||
|
if (/^https?:\/\//i.test(line)) {
|
||||||
|
currentUrls.push(line)
|
||||||
|
} else {
|
||||||
|
// 新标题,先保存上一个
|
||||||
|
if (currentTitle && currentUrls.length) {
|
||||||
|
resources.push({
|
||||||
|
title: currentTitle,
|
||||||
|
url: currentUrls.slice()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
currentTitle = line
|
||||||
|
currentUrls = []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 处理最后一组
|
||||||
|
if (currentTitle && currentUrls.length) {
|
||||||
resources.push({
|
resources.push({
|
||||||
title: title,
|
title: currentTitle,
|
||||||
url: url,
|
url: currentUrls.slice()
|
||||||
source: '批量添加'
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,12 +22,27 @@ export function useApiFetch<T = any>(
|
|||||||
...options,
|
...options,
|
||||||
headers,
|
headers,
|
||||||
onResponse({ response }) {
|
onResponse({ response }) {
|
||||||
|
if (response.status === 401 ||
|
||||||
|
(response._data && (response._data.code === 401 || response._data.error === '无效的令牌'))
|
||||||
|
) {
|
||||||
|
userStore.logout()
|
||||||
|
if (process.client) {
|
||||||
|
window.location.href = '/login'
|
||||||
|
}
|
||||||
|
// 触发 onResponseError 逻辑
|
||||||
|
throw Object.assign(new Error('登录已过期,请重新登录'), {
|
||||||
|
data: response._data,
|
||||||
|
status: response.status,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// 统一处理 code/message
|
// 统一处理 code/message
|
||||||
if (response._data && response._data.code && response._data.code !== 200) {
|
if (response._data && response._data.code && response._data.code !== 200) {
|
||||||
throw new Error(response._data.message || '请求失败')
|
throw new Error(response._data.message || '请求失败')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onResponseError({ error }: { error: any }) {
|
onResponseError({ error }: { error: any }) {
|
||||||
|
console.log('error', error)
|
||||||
// 检查是否为"无效的令牌"错误
|
// 检查是否为"无效的令牌"错误
|
||||||
if (error?.data?.error === '无效的令牌') {
|
if (error?.data?.error === '无效的令牌') {
|
||||||
// 清除用户状态
|
// 清除用户状态
|
||||||
|
|||||||
@@ -296,7 +296,6 @@
|
|||||||
API使用说明
|
API使用说明
|
||||||
</h3>
|
</h3>
|
||||||
<div class="text-xs text-blue-700 dark:text-blue-300 space-y-1">
|
<div class="text-xs text-blue-700 dark:text-blue-300 space-y-1">
|
||||||
<p>• 单个添加资源: POST /api/public/resources/add</p>
|
|
||||||
<p>• 批量添加资源: POST /api/public/resources/batch-add</p>
|
<p>• 批量添加资源: POST /api/public/resources/batch-add</p>
|
||||||
<p>• 资源搜索: GET /api/public/resources/search</p>
|
<p>• 资源搜索: GET /api/public/resources/search</p>
|
||||||
<p>• 热门剧: GET /api/public/hot-dramas</p>
|
<p>• 热门剧: GET /api/public/hot-dramas</p>
|
||||||
|
|||||||
@@ -46,57 +46,6 @@
|
|||||||
|
|
||||||
<!-- API接口列表 -->
|
<!-- API接口列表 -->
|
||||||
<div class="space-y-8">
|
<div class="space-y-8">
|
||||||
<!-- 单个添加资源 -->
|
|
||||||
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-lg overflow-hidden">
|
|
||||||
<div class="bg-green-600 text-white px-6 py-4">
|
|
||||||
<h3 class="text-xl font-semibold flex items-center">
|
|
||||||
<i class="fas fa-plus-circle mr-2"></i>
|
|
||||||
单个添加资源
|
|
||||||
</h3>
|
|
||||||
<p class="text-green-100 mt-1">添加单个资源到待处理列表</p>
|
|
||||||
</div>
|
|
||||||
<div class="p-6">
|
|
||||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
|
||||||
<div>
|
|
||||||
<h4 class="font-semibold text-gray-900 dark:text-white mb-3">请求信息</h4>
|
|
||||||
<div class="space-y-2 text-sm">
|
|
||||||
<p><strong>方法:</strong><span class="bg-green-100 dark:bg-green-800 text-green-800 dark:text-green-200 px-2 py-1 rounded">POST</span></p>
|
|
||||||
<p><strong>路径:</strong><code class="bg-gray-100 dark:bg-gray-700 px-2 py-1 rounded">/api/public/resources/add</code></p>
|
|
||||||
<p><strong>认证:</strong><span class="text-red-600 dark:text-red-400">必需</span></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<h4 class="font-semibold text-gray-900 dark:text-white mb-3">请求参数</h4>
|
|
||||||
<div class="bg-gray-50 dark:bg-gray-700 rounded p-4">
|
|
||||||
<pre class="text-sm overflow-x-auto"><code>{
|
|
||||||
"title": "资源标题",
|
|
||||||
"description": "资源描述",
|
|
||||||
"url": "资源链接",
|
|
||||||
"category": "分类名称",
|
|
||||||
"tags": "标签1,标签2",
|
|
||||||
"img": "封面图片链接",
|
|
||||||
"source": "数据来源",
|
|
||||||
"extra": "额外信息"
|
|
||||||
}</code></pre>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="mt-6">
|
|
||||||
<h4 class="font-semibold text-gray-900 dark:text-white mb-3">响应示例</h4>
|
|
||||||
<div class="bg-gray-50 dark:bg-gray-700 rounded p-4">
|
|
||||||
<pre class="text-sm overflow-x-auto"><code>{
|
|
||||||
"success": true,
|
|
||||||
"message": "资源添加成功,已进入待处理列表",
|
|
||||||
"data": {
|
|
||||||
"id": 123
|
|
||||||
},
|
|
||||||
"code": 200
|
|
||||||
}</code></pre>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 批量添加资源 -->
|
<!-- 批量添加资源 -->
|
||||||
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-lg overflow-hidden">
|
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-lg overflow-hidden">
|
||||||
<div class="bg-purple-600 text-white px-6 py-4">
|
<div class="bg-purple-600 text-white px-6 py-4">
|
||||||
@@ -104,7 +53,7 @@
|
|||||||
<i class="fas fa-layer-group mr-2"></i>
|
<i class="fas fa-layer-group mr-2"></i>
|
||||||
批量添加资源
|
批量添加资源
|
||||||
</h3>
|
</h3>
|
||||||
<p class="text-purple-100 mt-1">批量添加多个资源到待处理列表</p>
|
<p class="text-purple-100 mt-1">批量添加多个资源到待处理列表,每个资源可包含多个链接(url为数组),标题和url为必填项</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="p-6">
|
<div class="p-6">
|
||||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||||
@@ -113,22 +62,28 @@
|
|||||||
<div class="space-y-2 text-sm">
|
<div class="space-y-2 text-sm">
|
||||||
<p><strong>方法:</strong><span class="bg-purple-100 dark:bg-purple-800 text-purple-800 dark:text-purple-200 px-2 py-1 rounded">POST</span></p>
|
<p><strong>方法:</strong><span class="bg-purple-100 dark:bg-purple-800 text-purple-800 dark:text-purple-200 px-2 py-1 rounded">POST</span></p>
|
||||||
<p><strong>路径:</strong><code class="bg-gray-100 dark:bg-gray-700 px-2 py-1 rounded">/api/public/resources/batch-add</code></p>
|
<p><strong>路径:</strong><code class="bg-gray-100 dark:bg-gray-700 px-2 py-1 rounded">/api/public/resources/batch-add</code></p>
|
||||||
<p><strong>认证:</strong><span class="text-red-600 dark:text-red-400">必需</span></p>
|
<p><strong>认证:</strong><span class="text-red-600 dark:text-red-400">必需</span>(X-API-Token)</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h4 class="font-semibold text-gray-900 dark:text-white mb-3">请求参数</h4>
|
<h4 class="font-semibold text-gray-900 dark:text-white mb-3">请求参数</h4>
|
||||||
|
<p class="text-xs text-gray-500 dark:text-gray-400 mb-2">title 和 url 是必填项,其他字段均为选填</p>
|
||||||
<div class="bg-gray-50 dark:bg-gray-700 rounded p-4">
|
<div class="bg-gray-50 dark:bg-gray-700 rounded p-4">
|
||||||
<pre class="text-sm overflow-x-auto"><code>{
|
<pre class="text-sm overflow-x-auto"><code>{
|
||||||
"resources": [
|
"resources": [
|
||||||
{
|
{
|
||||||
"title": "资源1",
|
"title": "资源1",
|
||||||
"url": "链接1",
|
"description": "描述1",
|
||||||
"description": "描述1"
|
"url": ["链接1", "链接2"],
|
||||||
|
"category": "分类",
|
||||||
|
"tags": "标签1,标签2",
|
||||||
|
"img": "图片链接",
|
||||||
|
"source": "数据来源",
|
||||||
|
"extra": "额外信息"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "资源2",
|
"title": "资源2",
|
||||||
"url": "链接2",
|
"url": ["链接3"],
|
||||||
"description": "描述2"
|
"description": "描述2"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -141,7 +96,7 @@
|
|||||||
<div class="bg-gray-50 dark:bg-gray-700 rounded p-4">
|
<div class="bg-gray-50 dark:bg-gray-700 rounded p-4">
|
||||||
<pre class="text-sm overflow-x-auto"><code>{
|
<pre class="text-sm overflow-x-auto"><code>{
|
||||||
"success": true,
|
"success": true,
|
||||||
"message": "批量添加成功,共添加 2 个资源",
|
"message": "批量添加成功",
|
||||||
"data": {
|
"data": {
|
||||||
"created_count": 2,
|
"created_count": 2,
|
||||||
"created_ids": [123, 124]
|
"created_ids": [123, 124]
|
||||||
@@ -323,14 +278,15 @@
|
|||||||
<pre class="text-sm overflow-x-auto"><code># 设置API Token
|
<pre class="text-sm overflow-x-auto"><code># 设置API Token
|
||||||
API_TOKEN="your_api_token_here"
|
API_TOKEN="your_api_token_here"
|
||||||
|
|
||||||
# 单个添加资源
|
# 批量添加资源
|
||||||
curl -X POST "http://localhost:8080/api/public/resources/add" \
|
curl -X POST "http://localhost:8080/api/public/resources/batch-add" \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
-H "X-API-Token: $API_TOKEN" \
|
-H "X-API-Token: $API_TOKEN" \
|
||||||
-d '{
|
-d '{
|
||||||
"title": "测试资源",
|
"resources": [
|
||||||
"url": "https://example.com/resource",
|
{ "title": "测试资源1", "url": ["https://example.com/resource1"], "description": "描述1" },
|
||||||
"description": "测试描述"
|
{ "title": "测试资源2", "url": ["https://example.com/resource2", "https://example.com/resource3"], "description": "描述2" }
|
||||||
|
]
|
||||||
}'
|
}'
|
||||||
|
|
||||||
# 搜索资源
|
# 搜索资源
|
||||||
@@ -355,16 +311,21 @@ fetch('/api/public/resources/search?q=测试', { headers: { 'X-API-Token': 'your
|
|||||||
alert(res.message)
|
alert(res.message)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
// 单个添加资源
|
// 批量添加资源
|
||||||
fetch('/api/public/resources/add', {
|
fetch('/api/public/resources/batch-add', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json', 'X-API-Token': 'your_token' },
|
headers: { 'Content-Type': 'application/json', 'X-API-Token': 'your_token' },
|
||||||
body: JSON.stringify({ title: 'xxx', url: 'xxx' })
|
body: JSON.stringify({
|
||||||
|
resources: [
|
||||||
|
{ title: 'xxx', url: ['xxx'], description: 'xxx' },
|
||||||
|
{ title: 'yyy', url: ['yyy', 'zzz'], description: 'yyy' }
|
||||||
|
]
|
||||||
|
})
|
||||||
})
|
})
|
||||||
.then(res => res.json())
|
.then(res => res.json())
|
||||||
.then(res => {
|
.then(res => {
|
||||||
if (res.success) {
|
if (res.success) {
|
||||||
alert('添加成功,ID:' + res.data.id)
|
alert('添加成功,ID: ' + res.data.created_ids.join(', '))
|
||||||
} else {
|
} else {
|
||||||
alert(res.message)
|
alert(res.message)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user