mirror of
				https://github.com/cloudreve/cloudreve.git
				synced 2025-11-01 00:57:15 +08:00 
			
		
		
		
	Init V4 community edition (#2265)
* Init V4 community edition * Init V4 community edition
This commit is contained in:
		| @ -1,159 +1,251 @@ | ||||
| package admin | ||||
|  | ||||
| import ( | ||||
| 	"strings" | ||||
| 	"context" | ||||
| 	"strconv" | ||||
|  | ||||
| 	model "github.com/cloudreve/Cloudreve/v3/models" | ||||
| 	"github.com/cloudreve/Cloudreve/v3/pkg/serializer" | ||||
| 	"github.com/cloudreve/Cloudreve/v3/pkg/task" | ||||
| 	"github.com/cloudreve/Cloudreve/v4/application/dependency" | ||||
| 	"github.com/cloudreve/Cloudreve/v4/ent" | ||||
| 	"github.com/cloudreve/Cloudreve/v4/ent/task" | ||||
| 	"github.com/cloudreve/Cloudreve/v4/inventory" | ||||
| 	"github.com/cloudreve/Cloudreve/v4/pkg/hashid" | ||||
| 	"github.com/cloudreve/Cloudreve/v4/pkg/queue" | ||||
| 	"github.com/cloudreve/Cloudreve/v4/pkg/serializer" | ||||
| 	"github.com/cloudreve/Cloudreve/v4/pkg/setting" | ||||
| 	"github.com/gin-gonic/gin" | ||||
| 	"github.com/gofrs/uuid" | ||||
| 	"github.com/samber/lo" | ||||
| ) | ||||
|  | ||||
| // TaskBatchService 任务批量操作服务 | ||||
| type TaskBatchService struct { | ||||
| 	ID []uint `json:"id" binding:"min=1"` | ||||
| func GetQueueMetrics(c *gin.Context) ([]QueueMetric, error) { | ||||
| 	res := []QueueMetric{} | ||||
| 	dep := dependency.FromContext(c) | ||||
|  | ||||
| 	mediaMeta := dep.MediaMetaQueue(c) | ||||
| 	entityRecycle := dep.EntityRecycleQueue(c) | ||||
| 	ioIntense := dep.IoIntenseQueue(c) | ||||
| 	remoteDownload := dep.RemoteDownloadQueue(c) | ||||
| 	thumb := dep.ThumbQueue(c) | ||||
|  | ||||
| 	res = append(res, QueueMetric{ | ||||
| 		Name:            setting.QueueTypeMediaMeta, | ||||
| 		BusyWorkers:     mediaMeta.BusyWorkers(), | ||||
| 		SuccessTasks:    mediaMeta.SuccessTasks(), | ||||
| 		FailureTasks:    mediaMeta.FailureTasks(), | ||||
| 		SubmittedTasks:  mediaMeta.SubmittedTasks(), | ||||
| 		SuspendingTasks: mediaMeta.SuspendingTasks(), | ||||
| 	}) | ||||
| 	res = append(res, QueueMetric{ | ||||
| 		Name:            setting.QueueTypeEntityRecycle, | ||||
| 		BusyWorkers:     entityRecycle.BusyWorkers(), | ||||
| 		SuccessTasks:    entityRecycle.SuccessTasks(), | ||||
| 		FailureTasks:    entityRecycle.FailureTasks(), | ||||
| 		SubmittedTasks:  entityRecycle.SubmittedTasks(), | ||||
| 		SuspendingTasks: entityRecycle.SuspendingTasks(), | ||||
| 	}) | ||||
| 	res = append(res, QueueMetric{ | ||||
| 		Name:            setting.QueueTypeIOIntense, | ||||
| 		BusyWorkers:     ioIntense.BusyWorkers(), | ||||
| 		SuccessTasks:    ioIntense.SuccessTasks(), | ||||
| 		FailureTasks:    ioIntense.FailureTasks(), | ||||
| 		SubmittedTasks:  ioIntense.SubmittedTasks(), | ||||
| 		SuspendingTasks: ioIntense.SuspendingTasks(), | ||||
| 	}) | ||||
| 	res = append(res, QueueMetric{ | ||||
| 		Name:            setting.QueueTypeRemoteDownload, | ||||
| 		BusyWorkers:     remoteDownload.BusyWorkers(), | ||||
| 		SuccessTasks:    remoteDownload.SuccessTasks(), | ||||
| 		FailureTasks:    remoteDownload.FailureTasks(), | ||||
| 		SubmittedTasks:  remoteDownload.SubmittedTasks(), | ||||
| 		SuspendingTasks: remoteDownload.SuspendingTasks(), | ||||
| 	}) | ||||
| 	res = append(res, QueueMetric{ | ||||
| 		Name:            setting.QueueTypeThumb, | ||||
| 		BusyWorkers:     thumb.BusyWorkers(), | ||||
| 		SuccessTasks:    thumb.SuccessTasks(), | ||||
| 		FailureTasks:    thumb.FailureTasks(), | ||||
| 		SubmittedTasks:  thumb.SubmittedTasks(), | ||||
| 		SuspendingTasks: thumb.SuspendingTasks(), | ||||
| 	}) | ||||
|  | ||||
| 	return res, nil | ||||
| } | ||||
|  | ||||
| // ImportTaskService 导入任务 | ||||
| type ImportTaskService struct { | ||||
| 	UID       uint   `json:"uid" binding:"required"` | ||||
| 	PolicyID  uint   `json:"policy_id" binding:"required"` | ||||
| 	Src       string `json:"src" binding:"required,min=1,max=65535"` | ||||
| 	Dst       string `json:"dst" binding:"required,min=1,max=65535"` | ||||
| 	Recursive bool   `json:"recursive"` | ||||
| } | ||||
| const ( | ||||
| 	taskTypeCondition          = "task_type" | ||||
| 	taskStatusCondition        = "task_status" | ||||
| 	taskCorrelationIDCondition = "task_correlation_id" | ||||
| 	taskUserIDCondition        = "task_user_id" | ||||
| ) | ||||
|  | ||||
| func (s *AdminListService) Tasks(c *gin.Context) (*ListTaskResponse, error) { | ||||
| 	dep := dependency.FromContext(c) | ||||
| 	taskClient := dep.TaskClient() | ||||
| 	hasher := dep.HashIDEncoder() | ||||
| 	var ( | ||||
| 		err           error | ||||
| 		userID        int | ||||
| 		correlationID *uuid.UUID | ||||
| 		status        []task.Status | ||||
| 		taskType      []string | ||||
| 	) | ||||
|  | ||||
| 	if s.Conditions[taskTypeCondition] != "" { | ||||
| 		taskType = []string{s.Conditions[taskTypeCondition]} | ||||
| 	} | ||||
|  | ||||
| 	if s.Conditions[taskStatusCondition] != "" { | ||||
| 		status = []task.Status{task.Status(s.Conditions[taskStatusCondition])} | ||||
| 	} | ||||
|  | ||||
| 	if s.Conditions[taskCorrelationIDCondition] != "" { | ||||
| 		cid, err := uuid.FromString(s.Conditions[taskCorrelationIDCondition]) | ||||
| 		if err != nil { | ||||
| 			return nil, serializer.NewError(serializer.CodeParamErr, "Invalid task correlation ID", err) | ||||
| 		} | ||||
| 		correlationID = &cid | ||||
| 	} | ||||
|  | ||||
| 	if s.Conditions[taskUserIDCondition] != "" { | ||||
| 		userID, err = strconv.Atoi(s.Conditions[taskUserIDCondition]) | ||||
| 		if err != nil { | ||||
| 			return nil, serializer.NewError(serializer.CodeParamErr, "Invalid task user ID", err) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	ctx := context.WithValue(c, inventory.LoadTaskUser{}, true) | ||||
| 	res, err := taskClient.List(ctx, &inventory.ListTaskArgs{ | ||||
| 		PaginationArgs: &inventory.PaginationArgs{ | ||||
| 			Page:     s.Page - 1, | ||||
| 			PageSize: s.PageSize, | ||||
| 			OrderBy:  s.OrderBy, | ||||
| 			Order:    inventory.OrderDirection(s.OrderDirection), | ||||
| 		}, | ||||
| 		UserID:        userID, | ||||
| 		CorrelationID: correlationID, | ||||
| 		Types:         taskType, | ||||
| 		Status:        status, | ||||
| 	}) | ||||
|  | ||||
| // Create 新建导入任务 | ||||
| func (service *ImportTaskService) Create(c *gin.Context, user *model.User) serializer.Response { | ||||
| 	// 创建任务 | ||||
| 	job, err := task.NewImportTask(service.UID, service.PolicyID, service.Src, service.Dst, service.Recursive) | ||||
| 	if err != nil { | ||||
| 		return serializer.DBErr("Failed to create task record.", err) | ||||
| 	} | ||||
| 	task.TaskPoll.Submit(job) | ||||
| 	return serializer.Response{} | ||||
| } | ||||
|  | ||||
| // Delete 删除任务 | ||||
| func (service *TaskBatchService) Delete(c *gin.Context) serializer.Response { | ||||
| 	if err := model.DB.Where("id in (?)", service.ID).Delete(&model.Download{}).Error; err != nil { | ||||
| 		return serializer.DBErr("Failed to delete task records", err) | ||||
| 	} | ||||
| 	return serializer.Response{} | ||||
| } | ||||
|  | ||||
| // DeleteGeneral 删除常规任务 | ||||
| func (service *TaskBatchService) DeleteGeneral(c *gin.Context) serializer.Response { | ||||
| 	if err := model.DB.Where("id in (?)", service.ID).Delete(&model.Task{}).Error; err != nil { | ||||
| 		return serializer.DBErr("Failed to delete task records", err) | ||||
| 	} | ||||
| 	return serializer.Response{} | ||||
| } | ||||
|  | ||||
| // Tasks 列出常规任务 | ||||
| func (service *AdminListService) Tasks() serializer.Response { | ||||
| 	var res []model.Task | ||||
| 	total := 0 | ||||
|  | ||||
| 	tx := model.DB.Model(&model.Task{}) | ||||
| 	if service.OrderBy != "" { | ||||
| 		tx = tx.Order(service.OrderBy) | ||||
| 		return nil, serializer.NewError(serializer.CodeDBError, "Failed to list tasks", err) | ||||
| 	} | ||||
|  | ||||
| 	for k, v := range service.Conditions { | ||||
| 		tx = tx.Where(k+" = ?", v) | ||||
| 	} | ||||
|  | ||||
| 	if len(service.Searches) > 0 { | ||||
| 		search := "" | ||||
| 		for k, v := range service.Searches { | ||||
| 			search += k + " like '%" + v + "%' OR " | ||||
| 	tasks := make([]queue.Task, 0, len(res.Tasks)) | ||||
| 	nodeMap := make(map[int]*ent.Node) | ||||
| 	for _, t := range res.Tasks { | ||||
| 		task, err := queue.NewTaskFromModel(t) | ||||
| 		if err != nil { | ||||
| 			return nil, serializer.NewError(serializer.CodeDBError, "Failed to parse task", err) | ||||
| 		} | ||||
| 		search = strings.TrimSuffix(search, " OR ") | ||||
| 		tx = tx.Where(search) | ||||
| 	} | ||||
|  | ||||
| 	// 计算总数用于分页 | ||||
| 	tx.Count(&total) | ||||
|  | ||||
| 	// 查询记录 | ||||
| 	tx.Limit(service.PageSize).Offset((service.Page - 1) * service.PageSize).Find(&res) | ||||
|  | ||||
| 	// 查询对应用户,同时计算HashID | ||||
| 	users := make(map[uint]model.User) | ||||
| 	for _, file := range res { | ||||
| 		users[file.UserID] = model.User{} | ||||
| 	} | ||||
|  | ||||
| 	userIDs := make([]uint, 0, len(users)) | ||||
| 	for k := range users { | ||||
| 		userIDs = append(userIDs, k) | ||||
| 	} | ||||
|  | ||||
| 	var userList []model.User | ||||
| 	model.DB.Where("id in (?)", userIDs).Find(&userList) | ||||
|  | ||||
| 	for _, v := range userList { | ||||
| 		users[v.ID] = v | ||||
| 	} | ||||
|  | ||||
| 	return serializer.Response{Data: map[string]interface{}{ | ||||
| 		"total": total, | ||||
| 		"items": res, | ||||
| 		"users": users, | ||||
| 	}} | ||||
| } | ||||
|  | ||||
| // Downloads 列出离线下载任务 | ||||
| func (service *AdminListService) Downloads() serializer.Response { | ||||
| 	var res []model.Download | ||||
| 	total := 0 | ||||
|  | ||||
| 	tx := model.DB.Model(&model.Download{}) | ||||
| 	if service.OrderBy != "" { | ||||
| 		tx = tx.Order(service.OrderBy) | ||||
| 	} | ||||
|  | ||||
| 	for k, v := range service.Conditions { | ||||
| 		tx = tx.Where(k+" = ?", v) | ||||
| 	} | ||||
|  | ||||
| 	if len(service.Searches) > 0 { | ||||
| 		search := "" | ||||
| 		for k, v := range service.Searches { | ||||
| 			search += k + " like '%" + v + "%' OR " | ||||
| 		summary := task.Summarize(hasher) | ||||
| 		if summary != nil && summary.NodeID > 0 { | ||||
| 			if _, ok := nodeMap[summary.NodeID]; !ok { | ||||
| 				nodeMap[summary.NodeID] = nil | ||||
| 			} | ||||
| 		} | ||||
| 		search = strings.TrimSuffix(search, " OR ") | ||||
| 		tx = tx.Where(search) | ||||
| 		tasks = append(tasks, task) | ||||
| 	} | ||||
|  | ||||
| 	// 计算总数用于分页 | ||||
| 	tx.Count(&total) | ||||
|  | ||||
| 	// 查询记录 | ||||
| 	tx.Limit(service.PageSize).Offset((service.Page - 1) * service.PageSize).Find(&res) | ||||
|  | ||||
| 	// 查询对应用户,同时计算HashID | ||||
| 	users := make(map[uint]model.User) | ||||
| 	for _, file := range res { | ||||
| 		users[file.UserID] = model.User{} | ||||
| 	// Get nodes | ||||
| 	nodes, err := dep.NodeClient().GetNodeByIds(c, lo.Keys(nodeMap)) | ||||
| 	if err != nil { | ||||
| 		return nil, serializer.NewError(serializer.CodeDBError, "Failed to query nodes", err) | ||||
| 	} | ||||
| 	for _, n := range nodes { | ||||
| 		nodeMap[n.ID] = n | ||||
| 	} | ||||
|  | ||||
| 	userIDs := make([]uint, 0, len(users)) | ||||
| 	for k := range users { | ||||
| 		userIDs = append(userIDs, k) | ||||
| 	} | ||||
| 	return &ListTaskResponse{ | ||||
| 		Pagination: res.PaginationResults, | ||||
| 		Tasks: lo.Map(res.Tasks, func(task *ent.Task, i int) GetTaskResponse { | ||||
| 			var ( | ||||
| 				uid     string | ||||
| 				node    *ent.Node | ||||
| 				summary *queue.Summary | ||||
| 			) | ||||
|  | ||||
| 	var userList []model.User | ||||
| 	model.DB.Where("id in (?)", userIDs).Find(&userList) | ||||
| 			if task.Edges.User != nil { | ||||
| 				uid = hashid.EncodeUserID(hasher, task.Edges.User.ID) | ||||
| 			} | ||||
|  | ||||
| 	for _, v := range userList { | ||||
| 		users[v.ID] = v | ||||
| 	} | ||||
| 			t := tasks[i] | ||||
| 			summary = t.Summarize(hasher) | ||||
| 			if summary != nil && summary.NodeID > 0 { | ||||
| 				node = nodeMap[summary.NodeID] | ||||
| 			} | ||||
|  | ||||
| 	return serializer.Response{Data: map[string]interface{}{ | ||||
| 		"total": total, | ||||
| 		"items": res, | ||||
| 		"users": users, | ||||
| 	}} | ||||
| 			return GetTaskResponse{ | ||||
| 				Task:       task, | ||||
| 				UserHashID: uid, | ||||
| 				Node:       node, | ||||
| 				Summary:    summary, | ||||
| 			} | ||||
| 		}), | ||||
| 	}, nil | ||||
| } | ||||
|  | ||||
| type ( | ||||
| 	SingleTaskService struct { | ||||
| 		ID int `uri:"id" json:"id" binding:"required"` | ||||
| 	} | ||||
| 	SingleTaskParamCtx struct{} | ||||
| ) | ||||
|  | ||||
| func (s *SingleTaskService) Get(c *gin.Context) (*GetTaskResponse, error) { | ||||
| 	dep := dependency.FromContext(c) | ||||
| 	taskClient := dep.TaskClient() | ||||
| 	hasher := dep.HashIDEncoder() | ||||
|  | ||||
| 	ctx := context.WithValue(c, inventory.LoadTaskUser{}, true) | ||||
| 	task, err := taskClient.GetTaskByID(ctx, s.ID) | ||||
| 	if err != nil { | ||||
| 		return nil, serializer.NewError(serializer.CodeDBError, "Failed to get task", err) | ||||
| 	} | ||||
|  | ||||
| 	t, err := queue.NewTaskFromModel(task) | ||||
| 	if err != nil { | ||||
| 		return nil, serializer.NewError(serializer.CodeDBError, "Failed to parse task", err) | ||||
| 	} | ||||
|  | ||||
| 	summary := t.Summarize(hasher) | ||||
| 	var ( | ||||
| 		node       *ent.Node | ||||
| 		userHashID string | ||||
| 	) | ||||
|  | ||||
| 	if summary != nil && summary.NodeID > 0 { | ||||
| 		node, _ = dep.NodeClient().GetNodeById(c, summary.NodeID) | ||||
| 	} | ||||
|  | ||||
| 	if task.Edges.User != nil { | ||||
| 		userHashID = hashid.EncodeUserID(hasher, task.Edges.User.ID) | ||||
| 	} | ||||
|  | ||||
| 	return &GetTaskResponse{ | ||||
| 		Task:       task, | ||||
| 		Summary:    summary, | ||||
| 		Node:       node, | ||||
| 		UserHashID: userHashID, | ||||
| 	}, nil | ||||
| } | ||||
|  | ||||
| type ( | ||||
| 	BatchTaskService struct { | ||||
| 		IDs []int `json:"ids" binding:"required"` | ||||
| 	} | ||||
| 	BatchTaskParamCtx struct{} | ||||
| ) | ||||
|  | ||||
| func (s *BatchTaskService) Delete(c *gin.Context) error { | ||||
| 	dep := dependency.FromContext(c) | ||||
| 	taskClient := dep.TaskClient() | ||||
|  | ||||
| 	err := taskClient.DeleteByIDs(c, s.IDs...) | ||||
| 	if err != nil { | ||||
| 		return serializer.NewError(serializer.CodeDBError, "Failed to delete tasks", err) | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
		Reference in New Issue
	
	Block a user
	 AaronLiu
					AaronLiu