add: 任务列表

This commit is contained in:
ctwj
2025-08-09 23:47:30 +08:00
parent 76eb9c689b
commit 14f22f9128
18 changed files with 2256 additions and 334 deletions

258
task/transfer_processor.go Normal file
View File

@@ -0,0 +1,258 @@
package task
import (
"context"
"encoding/json"
"fmt"
"regexp"
"strings"
"time"
"github.com/ctwj/urldb/db/entity"
"github.com/ctwj/urldb/db/repo"
"github.com/ctwj/urldb/utils"
)
// TransferProcessor 转存任务处理器
type TransferProcessor struct {
repoMgr *repo.RepositoryManager
}
// NewTransferProcessor 创建转存任务处理器
func NewTransferProcessor(repoMgr *repo.RepositoryManager) *TransferProcessor {
return &TransferProcessor{
repoMgr: repoMgr,
}
}
// GetTaskType 获取任务类型
func (tp *TransferProcessor) GetTaskType() string {
return "transfer"
}
// TransferInput 转存任务输入数据结构
type TransferInput struct {
Title string `json:"title"`
URL string `json:"url"`
CategoryID uint `json:"category_id"`
Tags []uint `json:"tags"`
}
// TransferOutput 转存任务输出数据结构
type TransferOutput struct {
ResourceID uint `json:"resource_id,omitempty"`
SaveURL string `json:"save_url,omitempty"`
Error string `json:"error,omitempty"`
Success bool `json:"success"`
Time string `json:"time"`
}
// Process 处理转存任务项
func (tp *TransferProcessor) Process(ctx context.Context, taskID uint, item *entity.TaskItem) error {
utils.Info("开始处理转存任务项: %d", item.ID)
// 解析输入数据
var input TransferInput
if err := json.Unmarshal([]byte(item.InputData), &input); err != nil {
return fmt.Errorf("解析输入数据失败: %v", err)
}
// 验证输入数据
if err := tp.validateInput(&input); err != nil {
return fmt.Errorf("输入数据验证失败: %v", err)
}
// 检查资源是否已存在
exists, existingResource, err := tp.checkResourceExists(input.URL)
if err != nil {
utils.Error("检查资源是否存在失败: %v", err)
}
if exists {
// 资源已存在,更新输出数据
output := TransferOutput{
ResourceID: existingResource.ID,
SaveURL: existingResource.SaveURL,
Success: true,
Time: time.Now().Format("2006-01-02 15:04:05"),
}
outputJSON, _ := json.Marshal(output)
item.OutputData = string(outputJSON)
utils.Info("资源已存在,跳过转存: %s", input.Title)
return nil
}
// 执行转存操作
resourceID, saveURL, err := tp.performTransfer(ctx, &input)
if err != nil {
return fmt.Errorf("转存失败: %v", err)
}
// 更新输出数据
output := TransferOutput{
ResourceID: resourceID,
SaveURL: saveURL,
Success: true,
Time: time.Now().Format("2006-01-02 15:04:05"),
}
outputJSON, _ := json.Marshal(output)
item.OutputData = string(outputJSON)
utils.Info("转存任务项处理完成: %d, 资源ID: %d", item.ID, resourceID)
return nil
}
// validateInput 验证输入数据
func (tp *TransferProcessor) validateInput(input *TransferInput) error {
if strings.TrimSpace(input.Title) == "" {
return fmt.Errorf("标题不能为空")
}
if strings.TrimSpace(input.URL) == "" {
return fmt.Errorf("链接不能为空")
}
// 验证URL格式
if !tp.isValidURL(input.URL) {
return fmt.Errorf("链接格式不正确")
}
return nil
}
// isValidURL 验证URL格式
func (tp *TransferProcessor) isValidURL(url string) bool {
// 简单的URL验证可以根据需要扩展
quarkPattern := `https://pan\.quark\.cn/s/[a-zA-Z0-9]+`
matched, _ := regexp.MatchString(quarkPattern, url)
return matched
}
// checkResourceExists 检查资源是否已存在
func (tp *TransferProcessor) checkResourceExists(url string) (bool, *entity.Resource, error) {
// 根据URL查找资源
resource, err := tp.repoMgr.ResourceRepository.GetByURL(url)
if err != nil {
// 如果是未找到记录的错误,则表示资源不存在
if strings.Contains(err.Error(), "record not found") {
return false, nil, nil
}
return false, nil, err
}
return true, resource, nil
}
// performTransfer 执行转存操作
func (tp *TransferProcessor) performTransfer(ctx context.Context, input *TransferInput) (uint, string, error) {
// 解析URL获取分享信息
shareInfo, err := tp.parseShareURL(input.URL)
if err != nil {
return 0, "", fmt.Errorf("解析分享链接失败: %v", err)
}
// 创建资源记录
var categoryID *uint
if input.CategoryID != 0 {
categoryID = &input.CategoryID
}
resource := &entity.Resource{
Title: input.Title,
URL: input.URL,
CategoryID: categoryID,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}
// 保存资源到数据库
err = tp.repoMgr.ResourceRepository.Create(resource)
if err != nil {
return 0, "", fmt.Errorf("保存资源失败: %v", err)
}
// 添加标签关联
if len(input.Tags) > 0 {
err = tp.addResourceTags(resource.ID, input.Tags)
if err != nil {
utils.Error("添加资源标签失败: %v", err)
}
}
// 执行实际转存操作
saveURL, err := tp.transferToCloud(ctx, shareInfo)
if err != nil {
utils.Error("云端转存失败: %v", err)
// 转存失败但资源已创建返回原始URL
return resource.ID, input.URL, nil
}
// 更新资源的转存链接
if saveURL != "" {
err = tp.repoMgr.ResourceRepository.UpdateSaveURL(resource.ID, saveURL)
if err != nil {
utils.Error("更新转存链接失败: %v", err)
}
}
return resource.ID, saveURL, nil
}
// ShareInfo 分享信息结构
type ShareInfo struct {
PanType string
ShareID string
URL string
}
// parseShareURL 解析分享链接
func (tp *TransferProcessor) parseShareURL(url string) (*ShareInfo, error) {
// 解析夸克网盘链接
quarkPattern := `https://pan\.quark\.cn/s/([a-zA-Z0-9]+)`
re := regexp.MustCompile(quarkPattern)
matches := re.FindStringSubmatch(url)
if len(matches) >= 2 {
return &ShareInfo{
PanType: "quark",
ShareID: matches[1],
URL: url,
}, nil
}
return nil, fmt.Errorf("不支持的分享链接格式: %s", url)
}
// addResourceTags 添加资源标签
func (tp *TransferProcessor) addResourceTags(resourceID uint, tagIDs []uint) error {
for _, tagID := range tagIDs {
// 创建资源标签关联
resourceTag := &entity.ResourceTag{
ResourceID: resourceID,
TagID: tagID,
}
err := tp.repoMgr.ResourceRepository.CreateResourceTag(resourceTag)
if err != nil {
return fmt.Errorf("创建资源标签关联失败: %v", err)
}
}
return nil
}
// transferToCloud 执行云端转存
func (tp *TransferProcessor) transferToCloud(ctx context.Context, shareInfo *ShareInfo) (string, error) {
// 检查是否启用自动转存
autoTransferEnabled, err := tp.repoMgr.SystemConfigRepository.GetConfigBool("auto_transfer")
if err != nil || !autoTransferEnabled {
utils.Info("自动转存未启用,跳过云端转存")
return "", nil
}
// TODO: 实现云端转存逻辑
utils.Info("云端转存功能暂未实现,跳过转存: %s", shareInfo.ShareID)
return "", nil
}