Init V4 community edition (#2265)

* Init V4 community edition

* Init V4 community edition
This commit is contained in:
AaronLiu
2025-04-20 17:31:25 +08:00
committed by GitHub
parent da4e44b77a
commit 21d158db07
597 changed files with 119415 additions and 41692 deletions

View File

@@ -1,76 +0,0 @@
package node
import (
"encoding/gob"
model "github.com/cloudreve/Cloudreve/v3/models"
"github.com/cloudreve/Cloudreve/v3/pkg/cluster"
"github.com/cloudreve/Cloudreve/v3/pkg/conf"
"github.com/cloudreve/Cloudreve/v3/pkg/filesystem/driver/googledrive"
"github.com/cloudreve/Cloudreve/v3/pkg/filesystem/driver/onedrive"
"github.com/cloudreve/Cloudreve/v3/pkg/filesystem/oauth"
"github.com/cloudreve/Cloudreve/v3/pkg/mq"
"github.com/cloudreve/Cloudreve/v3/pkg/serializer"
"github.com/gin-gonic/gin"
)
type SlaveNotificationService struct {
Subject string `uri:"subject" binding:"required"`
}
type OauthCredentialService struct {
PolicyID uint `uri:"id" binding:"required"`
}
func HandleMasterHeartbeat(req *serializer.NodePingReq) serializer.Response {
res, err := cluster.DefaultController.HandleHeartBeat(req)
if err != nil {
return serializer.Err(serializer.CodeInternalSetting, "Cannot initialize slave controller", err)
}
return serializer.Response{
Code: 0,
Data: res,
}
}
// HandleSlaveNotificationPush 转发从机的消息通知到本机消息队列
func (s *SlaveNotificationService) HandleSlaveNotificationPush(c *gin.Context) serializer.Response {
var msg mq.Message
dec := gob.NewDecoder(c.Request.Body)
if err := dec.Decode(&msg); err != nil {
return serializer.ParamErr("Cannot parse notification message", err)
}
mq.GlobalMQ.Publish(s.Subject, msg)
return serializer.Response{}
}
// Get 获取主机Oauth策略的AccessToken
func (s *OauthCredentialService) Get(c *gin.Context) serializer.Response {
policy, err := model.GetPolicyByID(s.PolicyID)
if err != nil {
return serializer.Err(serializer.CodePolicyNotExist, "", err)
}
var client oauth.TokenProvider
switch policy.Type {
case "onedrive":
client, err = onedrive.NewClient(&policy)
if err != nil {
return serializer.Err(serializer.CodeInternalSetting, "Cannot initialize OneDrive client", err)
}
case "googledrive":
client, err = googledrive.NewClient(&policy)
if err != nil {
return serializer.Err(serializer.CodeInternalSetting, "Cannot initialize Google Drive client", err)
}
default:
return serializer.Err(serializer.CodePolicyNotExist, "", nil)
}
if err := client.UpdateCredential(c, conf.SystemConfig.Mode == "slave"); err != nil {
return serializer.Err(serializer.CodeInternalSetting, "Cannot refresh OneDrive credential", err)
}
return serializer.Response{Data: client.AccessToken()}
}

1
service/node/response.go Normal file
View File

@@ -0,0 +1 @@
package node

120
service/node/rpc.go Normal file
View File

@@ -0,0 +1,120 @@
package node
import (
"context"
"github.com/cloudreve/Cloudreve/v4/application/dependency"
"github.com/cloudreve/Cloudreve/v4/inventory"
"github.com/cloudreve/Cloudreve/v4/pkg/credmanager"
"github.com/cloudreve/Cloudreve/v4/pkg/filemanager/fs"
"github.com/cloudreve/Cloudreve/v4/pkg/filemanager/manager"
"github.com/cloudreve/Cloudreve/v4/pkg/serializer"
"github.com/cloudreve/Cloudreve/v4/pkg/util"
"github.com/gin-gonic/gin"
)
type SlaveNotificationService struct {
Subject string `uri:"subject" binding:"required"`
}
type (
OauthCredentialParamCtx struct{}
OauthCredentialService struct {
ID string `uri:"id" binding:"required"`
}
)
// Get 获取主机Oauth策略的AccessToken
func (s *OauthCredentialService) Get(c *gin.Context) (*credmanager.CredentialResponse, error) {
dep := dependency.FromContext(c)
credManager := dep.CredManager()
cred, err := credManager.Obtain(c, s.ID)
if cred == nil || err != nil {
return nil, serializer.NewError(serializer.CodeNotFound, "Credential not found", err)
}
return &credmanager.CredentialResponse{
Token: cred.String(),
ExpireAt: cred.Expiry(),
}, nil
}
type (
StatelessPrepareUploadParamCtx struct{}
)
func StatelessPrepareUpload(s *fs.StatelessPrepareUploadService, c *gin.Context) (*fs.StatelessPrepareUploadResponse, error) {
dep := dependency.FromContext(c)
userClient := dep.UserClient()
user, err := userClient.GetLoginUserByID(c, s.UserID)
if err != nil {
return nil, err
}
ctx := context.WithValue(c.Request.Context(), inventory.UserCtx{}, user)
fm := manager.NewFileManager(dep, user)
uploadSession, err := fm.PrepareUpload(ctx, s.UploadRequest)
if err != nil {
return nil, err
}
return &fs.StatelessPrepareUploadResponse{
Session: uploadSession,
Req: s.UploadRequest,
}, nil
}
type (
StatelessCompleteUploadParamCtx struct{}
)
func StatelessCompleteUpload(s *fs.StatelessCompleteUploadService, c *gin.Context) (fs.File, error) {
dep := dependency.FromContext(c)
userClient := dep.UserClient()
user, err := userClient.GetLoginUserByID(c, s.UserID)
if err != nil {
return nil, err
}
util.WithValue(c, inventory.UserCtx{}, user)
fm := manager.NewFileManager(dep, user)
return fm.CompleteUpload(c, s.UploadSession)
}
type (
StatelessOnUploadFailedParamCtx struct{}
)
func StatelessOnUploadFailed(s *fs.StatelessOnUploadFailedService, c *gin.Context) error {
dep := dependency.FromContext(c)
userClient := dep.UserClient()
user, err := userClient.GetLoginUserByID(c, s.UserID)
if err != nil {
return err
}
util.WithValue(c, inventory.UserCtx{}, user)
fm := manager.NewFileManager(dep, user)
fm.OnUploadFailed(c, s.UploadSession)
return nil
}
type StatelessCreateFileParamCtx struct{}
func StatelessCreateFile(s *fs.StatelessCreateFileService, c *gin.Context) error {
dep := dependency.FromContext(c)
userClient := dep.UserClient()
user, err := userClient.GetLoginUserByID(c, s.UserID)
if err != nil {
return err
}
uri, err := fs.NewUriFromString(s.Path)
if err != nil {
return err
}
util.WithValue(c, inventory.UserCtx{}, user)
fm := manager.NewFileManager(dep, user)
_, err = fm.Create(c, uri, s.Type)
return err
}

150
service/node/task.go Normal file
View File

@@ -0,0 +1,150 @@
package node
import (
"context"
"fmt"
"os"
"strconv"
"github.com/cloudreve/Cloudreve/v4/application/dependency"
"github.com/cloudreve/Cloudreve/v4/ent/task"
"github.com/cloudreve/Cloudreve/v4/inventory/types"
"github.com/cloudreve/Cloudreve/v4/pkg/cluster"
"github.com/cloudreve/Cloudreve/v4/pkg/cluster/routes"
"github.com/cloudreve/Cloudreve/v4/pkg/filemanager/workflows"
"github.com/cloudreve/Cloudreve/v4/pkg/logging"
"github.com/cloudreve/Cloudreve/v4/pkg/queue"
"github.com/cloudreve/Cloudreve/v4/pkg/serializer"
"github.com/gin-gonic/gin"
)
type (
CreateSlaveTaskParamCtx struct{}
)
func CreateTaskInSlave(s *cluster.CreateSlaveTask, c *gin.Context) (int, error) {
dep := dependency.FromContext(c)
registry := dep.TaskRegistry()
props, err := slaveTaskPropsFromContext(c)
if err != nil {
return 0, serializer.NewError(serializer.CodeParamErr, "failed to get master props from header", err)
}
var t queue.Task
switch s.Type {
case queue.SlaveUploadTaskType:
t = workflows.NewSlaveUploadTask(c, props, registry.NextID(), s.State)
case queue.SlaveCreateArchiveTaskType:
t = workflows.NewSlaveCreateArchiveTask(c, props, registry.NextID(), s.State)
case queue.SlaveExtractArchiveType:
t = workflows.NewSlaveExtractArchiveTask(c, props, registry.NextID(), s.State)
default:
return 0, serializer.NewError(serializer.CodeParamErr, "type not supported", nil)
}
if err := dep.SlaveQueue(c).QueueTask(c, t); err != nil {
return 0, serializer.NewError(serializer.CodeInternalSetting, "failed to queue task", err)
}
registry.Set(t.ID(), t)
return t.ID(), nil
}
type (
GetSlaveTaskParamCtx struct{}
GetSlaveTaskService struct {
ID int `uri:"id" binding:"required"`
}
)
func (s *GetSlaveTaskService) Get(c *gin.Context) (*cluster.SlaveTaskSummary, error) {
dep := dependency.FromContext(c)
registry := dep.TaskRegistry()
t, ok := registry.Get(s.ID)
if !ok {
return nil, serializer.NewError(serializer.CodeNotFound, "task not found", nil)
}
status := t.Status()
_, clearOnComplete := c.GetQuery(routes.SlaveClearTaskRegistryQuery)
if clearOnComplete && status == task.StatusCompleted ||
status == task.StatusError ||
status == task.StatusCanceled {
registry.Delete(s.ID)
}
res := &cluster.SlaveTaskSummary{
Status: status,
PrivateState: t.State(),
Progress: t.Progress(c),
}
err := t.Error()
if err != nil {
res.Error = err.Error()
}
return res, nil
}
func slaveTaskPropsFromContext(ctx context.Context) (*types.SlaveTaskProps, error) {
nodeIdStr, ok := ctx.Value(cluster.SlaveNodeIDCtx{}).(string)
if !ok {
return nil, fmt.Errorf("failed to get node ID from context")
}
nodeId, err := strconv.Atoi(nodeIdStr)
if err != nil {
return nil, fmt.Errorf("failed to convert node ID to int: %w", err)
}
masterSiteUrl := cluster.MasterSiteUrlFromContext(ctx)
if masterSiteUrl == "" {
return nil, fmt.Errorf("failed to get master site URL from context")
}
masterSiteVersion, ok := ctx.Value(cluster.MasterSiteVersionCtx{}).(string)
if !ok {
return nil, fmt.Errorf("failed to get master site version from context")
}
masterSiteId, ok := ctx.Value(cluster.MasterSiteIDCtx{}).(string)
if !ok {
return nil, fmt.Errorf("failed to convert master site ID to int: %w", err)
}
props := &types.SlaveTaskProps{
NodeID: nodeId,
MasterSiteID: masterSiteId,
MasterSiteURl: masterSiteUrl,
MasterSiteVersion: masterSiteVersion,
}
return props, nil
}
type (
FolderCleanupParamCtx struct{}
)
func Cleanup(args *cluster.FolderCleanup, c *gin.Context) error {
l := logging.FromContext(c)
ae := serializer.NewAggregateError()
for _, p := range args.Path {
l.Info("Cleaning up folder %q", p)
if err := os.RemoveAll(p); err != nil {
l.Warning("Failed to clean up folder %q: %s", p, err)
ae.Add(p, err)
}
}
return ae.Aggregate()
}
type (
CreateSlaveDownloadTaskParamCtx struct{}
GetSlaveDownloadTaskParamCtx struct{}
CancelSlaveDownloadTaskParamCtx struct{}
SelectSlaveDownloadFilesParamCtx struct{}
TestSlaveDownloadParamCtx struct{}
)