| @@ -27,7 +27,7 @@ func Test_ServerClient(t *testing.T) { | |||||
| }) | }) | ||||
| So(err, ShouldBeNil) | So(err, ShouldBeNil) | ||||
| go svr.Serve() | |||||
| //go svr.Start() | |||||
| cli, err := NewRabbitMQTransport(rabbitURL, testQueue, "") | cli, err := NewRabbitMQTransport(rabbitURL, testQueue, "") | ||||
| So(err, ShouldBeNil) | So(err, ShouldBeNil) | ||||
| @@ -2,6 +2,7 @@ package mq | |||||
| import ( | import ( | ||||
| "fmt" | "fmt" | ||||
| "gitlink.org.cn/cloudream/common/utils/sync2" | |||||
| "net" | "net" | ||||
| "time" | "time" | ||||
| @@ -72,12 +73,18 @@ type RabbitMQServer struct { | |||||
| connection *amqp.Connection | connection *amqp.Connection | ||||
| channel *amqp.Channel | channel *amqp.Channel | ||||
| closed chan any | closed chan any | ||||
| config RabbitMQParam | |||||
| OnMessage MessageHandlerFn | OnMessage MessageHandlerFn | ||||
| OnError func(err error) | OnError func(err error) | ||||
| } | } | ||||
| func NewRabbitMQServer(url string, queueName string, onMessage MessageHandlerFn) (*RabbitMQServer, error) { | |||||
| type RabbitMQParam struct { | |||||
| RetryNum int `json:"retryNum"` | |||||
| RetryInterval int `json:"retryInterval"` | |||||
| } | |||||
| func NewRabbitMQServer(url string, queueName string, onMessage MessageHandlerFn, cfg RabbitMQParam) (*RabbitMQServer, error) { | |||||
| config := amqp.Config{ | config := amqp.Config{ | ||||
| Dial: func(network, addr string) (net.Conn, error) { | Dial: func(network, addr string) (net.Conn, error) { | ||||
| return net.DialTimeout(network, addr, 60*time.Second) // 设置连接超时时间为 60 秒 | return net.DialTimeout(network, addr, 60*time.Second) // 设置连接超时时间为 60 秒 | ||||
| @@ -102,12 +109,56 @@ func NewRabbitMQServer(url string, queueName string, onMessage MessageHandlerFn) | |||||
| queueName: queueName, | queueName: queueName, | ||||
| closed: make(chan any), | closed: make(chan any), | ||||
| OnMessage: onMessage, | OnMessage: onMessage, | ||||
| config: cfg, | |||||
| } | } | ||||
| return srv, nil | return srv, nil | ||||
| } | } | ||||
| func (s *RabbitMQServer) Serve() error { | |||||
| type RabbitMQLogEvent interface{} | |||||
| func (s *RabbitMQServer) Start() *sync2.UnboundChannel[RabbitMQLogEvent] { | |||||
| ch := sync2.NewUnboundChannel[RabbitMQLogEvent]() | |||||
| channel := s.openChannel(ch) | |||||
| if channel == nil { | |||||
| ch.Send(1) | |||||
| return ch | |||||
| } | |||||
| retryNum := 0 | |||||
| for { | |||||
| select { | |||||
| case rawReq, ok := <-channel: | |||||
| if !ok { | |||||
| if retryNum > s.config.RetryNum { | |||||
| ch.Send(fmt.Errorf("maximum number of retries exceeded")) | |||||
| ch.Send(1) | |||||
| return ch | |||||
| } | |||||
| retryNum++ | |||||
| time.Sleep(time.Duration(s.config.RetryInterval) * time.Millisecond) | |||||
| channel = s.openChannel(ch) | |||||
| continue | |||||
| } | |||||
| reqMsg, err := Deserialize(rawReq.Body) | |||||
| if err != nil { | |||||
| ch.Send(NewDeserializeError(err)) | |||||
| continue | |||||
| } | |||||
| go s.handleMessage(ch, reqMsg, rawReq) | |||||
| case <-s.closed: | |||||
| return nil | |||||
| } | |||||
| } | |||||
| } | |||||
| func (s *RabbitMQServer) openChannel(ch *sync2.UnboundChannel[RabbitMQLogEvent]) <-chan amqp.Delivery { | |||||
| _, err := s.channel.QueueDeclare( | _, err := s.channel.QueueDeclare( | ||||
| s.queueName, | s.queueName, | ||||
| false, | false, | ||||
| @@ -117,7 +168,8 @@ func (s *RabbitMQServer) Serve() error { | |||||
| nil, | nil, | ||||
| ) | ) | ||||
| if err != nil { | if err != nil { | ||||
| return fmt.Errorf("declare queue failed, err: %w", err) | |||||
| ch.Send(fmt.Errorf("declare queue failed, err: %w", err)) | |||||
| return nil | |||||
| } | } | ||||
| channel, err := s.channel.Consume( | channel, err := s.channel.Consume( | ||||
| @@ -131,32 +183,14 @@ func (s *RabbitMQServer) Serve() error { | |||||
| ) | ) | ||||
| if err != nil { | if err != nil { | ||||
| return fmt.Errorf("open consume channel failed, err: %w", err) | |||||
| ch.Send(fmt.Errorf("get rabbitmq channel failed, err: %w", err)) | |||||
| return nil | |||||
| } | } | ||||
| for { | |||||
| select { | |||||
| case rawReq, ok := <-channel: | |||||
| if !ok { | |||||
| s.onError(NewDeserializeError(fmt.Errorf("channel is closed"))) | |||||
| return NewReceiveMessageError(fmt.Errorf("channel is closed")) | |||||
| } | |||||
| reqMsg, err := Deserialize(rawReq.Body) | |||||
| if err != nil { | |||||
| s.onError(NewDeserializeError(err)) | |||||
| continue | |||||
| } | |||||
| go s.handleMessage(reqMsg, rawReq) | |||||
| case <-s.closed: | |||||
| return nil | |||||
| } | |||||
| } | |||||
| return channel | |||||
| } | } | ||||
| func (s *RabbitMQServer) handleMessage(reqMsg *Message, rawReq amqp.Delivery) { | |||||
| func (s *RabbitMQServer) handleMessage(ch *sync2.UnboundChannel[RabbitMQLogEvent], reqMsg *Message, rawReq amqp.Delivery) { | |||||
| replyed := make(chan bool) | replyed := make(chan bool) | ||||
| defer close(replyed) | defer close(replyed) | ||||
| @@ -167,7 +201,7 @@ func (s *RabbitMQServer) handleMessage(reqMsg *Message, rawReq amqp.Delivery) { | |||||
| reply, err := s.OnMessage(reqMsg) | reply, err := s.OnMessage(reqMsg) | ||||
| if err != nil { | if err != nil { | ||||
| s.onError(NewDispatchError(err)) | |||||
| ch.Send(NewDispatchError(err)) | |||||
| return | return | ||||
| } | } | ||||
| @@ -175,7 +209,7 @@ func (s *RabbitMQServer) handleMessage(reqMsg *Message, rawReq amqp.Delivery) { | |||||
| reply.SetRequestID(reqMsg.GetRequestID()) | reply.SetRequestID(reqMsg.GetRequestID()) | ||||
| err := s.replyToClient(*reply, &rawReq) | err := s.replyToClient(*reply, &rawReq) | ||||
| if err != nil { | if err != nil { | ||||
| s.onError(NewReplyError(err)) | |||||
| ch.Send(NewReplyError(err)) | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| @@ -1,25 +0,0 @@ | |||||
| package cloudstorage | |||||
| import "fmt" | |||||
| //type ObjectStorageInfo interface { | |||||
| // NewClient() (ObjectStorageClient, error) | |||||
| //} | |||||
| type ObjectStorageClient interface { | |||||
| InitiateMultipartUpload(objectName string) (string, error) | |||||
| UploadPart() | |||||
| CompleteMultipartUpload() (string, error) | |||||
| AbortMultipartUpload() | |||||
| Close() | |||||
| } | |||||
| func NewObjectStorageClient(info ObjectStorage) (ObjectStorageClient, error) { | |||||
| switch info.Manufacturer { | |||||
| case AliCloud: | |||||
| return NewOSSClient(info), nil | |||||
| case HuaweiCloud: | |||||
| return &OBSClient{}, nil | |||||
| } | |||||
| return nil, fmt.Errorf("unknown cloud storage manufacturer %s", info.Manufacturer) | |||||
| } | |||||
| @@ -1,16 +0,0 @@ | |||||
| package cloudstorage | |||||
| type ObjectStorage struct { | |||||
| Manufacturer string `json:"manufacturer"` | |||||
| Region string `json:"region"` | |||||
| AK string `json:"access_key_id"` | |||||
| SK string `json:"secret_access_key"` | |||||
| Endpoint string `json:"endpoint"` | |||||
| Bucket string `json:"bucket"` | |||||
| } | |||||
| const ( | |||||
| HuaweiCloud = "HuaweiCloud" | |||||
| AliCloud = "AliCloud" | |||||
| SugonCloud = "SugonCloud" | |||||
| ) | |||||
| @@ -1,24 +0,0 @@ | |||||
| package cloudstorage | |||||
| type OBSClient struct { | |||||
| } | |||||
| func (c *OBSClient) InitiateMultipartUpload(objectName string) (string, error) { | |||||
| return "", nil | |||||
| } | |||||
| func (c *OBSClient) UploadPart() { | |||||
| } | |||||
| func (c *OBSClient) CompleteMultipartUpload() (string, error) { | |||||
| return "", nil | |||||
| } | |||||
| func (c *OBSClient) AbortMultipartUpload() { | |||||
| } | |||||
| func (c *OBSClient) Close() { | |||||
| } | |||||
| @@ -1,55 +0,0 @@ | |||||
| package cloudstorage | |||||
| import ( | |||||
| "fmt" | |||||
| "github.com/aliyun/aliyun-oss-go-sdk/oss" | |||||
| "log" | |||||
| ) | |||||
| type OSSClient struct { | |||||
| client *oss.Client | |||||
| bucket *oss.Bucket | |||||
| } | |||||
| func (c *OSSClient) InitiateMultipartUpload(objectName string) (string, error) { | |||||
| imur, err := c.bucket.InitiateMultipartUpload(objectName) | |||||
| if err != nil { | |||||
| return "", fmt.Errorf("failed to initiate multipart upload: %w", err) | |||||
| } | |||||
| return imur.UploadID, nil | |||||
| } | |||||
| func NewOSSClient(obs ObjectStorage) *OSSClient { | |||||
| // 创建OSSClient实例。 | |||||
| client, err := oss.New(obs.Endpoint, obs.AK, obs.SK) | |||||
| if err != nil { | |||||
| log.Fatalf("Error: %v", err) | |||||
| } | |||||
| bucket, err := client.Bucket(obs.Bucket) | |||||
| if err != nil { | |||||
| log.Fatalf("Error: %v", err) | |||||
| } | |||||
| return &OSSClient{ | |||||
| client: client, | |||||
| bucket: bucket, | |||||
| } | |||||
| } | |||||
| func (c *OSSClient) UploadPart() { | |||||
| } | |||||
| func (c *OSSClient) CompleteMultipartUpload() (string, error) { | |||||
| return "", nil | |||||
| } | |||||
| func (c *OSSClient) AbortMultipartUpload() { | |||||
| } | |||||
| func (c *OSSClient) Close() { | |||||
| // 关闭client | |||||
| } | |||||
| @@ -7,6 +7,38 @@ import ( | |||||
| "gitlink.org.cn/cloudream/common/utils/serder" | "gitlink.org.cn/cloudream/common/utils/serder" | ||||
| ) | ) | ||||
| type Storage struct { | |||||
| StorageID StorageID `json:"storageID" gorm:"column:StorageID; primaryKey; type:bigint; autoIncrement;"` | |||||
| Name string `json:"name" gorm:"column:Name; type:varchar(256); not null"` | |||||
| // 完全管理此存储服务的Hub的ID | |||||
| MasterHub NodeID `json:"masterHub" gorm:"column:MasterHub; type:bigint; not null"` | |||||
| // 存储服务的地址,包含鉴权所需数据 | |||||
| Address StorageAddress `json:"address" gorm:"column:Address; type:json; not null; serializer:union"` | |||||
| // 存储服务拥有的特别功能 | |||||
| Features []StorageFeature `json:"features" gorm:"column:Features; type:json; serializer:union"` | |||||
| } | |||||
| func (Storage) TableName() string { | |||||
| return "Storage" | |||||
| } | |||||
| func (s *Storage) String() string { | |||||
| return fmt.Sprintf("%v(%v)", s.Name, s.StorageID) | |||||
| } | |||||
| // 共享存储服务的配置数据 | |||||
| type SharedStorage struct { | |||||
| StorageID StorageID `json:"storageID" gorm:"column:StorageID; primaryKey; type:bigint"` | |||||
| // 调度文件时保存文件的根路径 | |||||
| LoadBase string `json:"loadBase" gorm:"column:LoadBase; type:varchar(1024); not null"` | |||||
| // 回源数据时数据存放位置的根路径 | |||||
| DataReturnBase string `json:"dataReturnBase" gorm:"column:DataReturnBase; type:varchar(1024); not null"` | |||||
| } | |||||
| func (SharedStorage) TableName() string { | |||||
| return "SharedStorage" | |||||
| } | |||||
| // 存储服务地址 | // 存储服务地址 | ||||
| type StorageAddress interface { | type StorageAddress interface { | ||||
| GetType() string | GetType() string | ||||
| @@ -31,34 +63,53 @@ func (a *LocalStorageAddress) String() string { | |||||
| return "Local" | return "Local" | ||||
| } | } | ||||
| type Storage struct { | |||||
| StorageID StorageID `json:"storageID" gorm:"column:StorageID; primaryKey; type:bigint; autoIncrement;"` | |||||
| Name string `json:"name" gorm:"column:Name; type:varchar(256); not null"` | |||||
| // 完全管理此存储服务的Hub的ID | |||||
| MasterHub NodeID `json:"masterHub" gorm:"column:MasterHub; type:bigint; not null"` | |||||
| // 存储服务的地址,包含鉴权所需数据 | |||||
| Address StorageAddress `json:"address" gorm:"column:Address; type:json; not null; serializer:union"` | |||||
| // 存储服务拥有的特别功能 | |||||
| Features []StorageFeature `json:"features" gorm:"column:Features; type:json; serializer:union"` | |||||
| type OSSAddress struct { | |||||
| serder.Metadata `union:"Local"` | |||||
| Region string `json:"region"` | |||||
| AK string `json:"accessKeyId"` | |||||
| SK string `json:"secretAccessKey"` | |||||
| Endpoint string `json:"endpoint"` | |||||
| Bucket string `json:"bucket"` | |||||
| } | } | ||||
| func (Storage) TableName() string { | |||||
| return "Storage" | |||||
| func (a *OSSAddress) GetType() string { | |||||
| return "OSSAddress" | |||||
| } | } | ||||
| func (s *Storage) String() string { | |||||
| return fmt.Sprintf("%v(%v)", s.Name, s.StorageID) | |||||
| func (a *OSSAddress) String() string { | |||||
| return "OSSAddress" | |||||
| } | } | ||||
| // 共享存储服务的配置数据 | |||||
| type SharedStorage struct { | |||||
| StorageID StorageID `json:"storageID" gorm:"column:StorageID; primaryKey; type:bigint"` | |||||
| // 调度文件时保存文件的根路径 | |||||
| LoadBase string `json:"loadBase" gorm:"column:LoadBase; type:varchar(1024); not null"` | |||||
| // 回源数据时数据存放位置的根路径 | |||||
| DataReturnBase string `json:"dataReturnBase" gorm:"column:DataReturnBase; type:varchar(1024); not null"` | |||||
| type OBSAddress struct { | |||||
| serder.Metadata `union:"Local"` | |||||
| Region string `json:"region"` | |||||
| AK string `json:"accessKeyId"` | |||||
| SK string `json:"secretAccessKey"` | |||||
| Endpoint string `json:"endpoint"` | |||||
| Bucket string `json:"bucket"` | |||||
| } | } | ||||
| func (SharedStorage) TableName() string { | |||||
| return "SharedStorage" | |||||
| func (a *OBSAddress) GetType() string { | |||||
| return "OBSAddress" | |||||
| } | |||||
| func (a *OBSAddress) String() string { | |||||
| return "OBSAddress" | |||||
| } | |||||
| type COSAddress struct { | |||||
| serder.Metadata `union:"Local"` | |||||
| Region string `json:"region"` | |||||
| AK string `json:"accessKeyId"` | |||||
| SK string `json:"secretAccessKey"` | |||||
| Endpoint string `json:"endpoint"` | |||||
| Bucket string `json:"bucket"` | |||||
| } | |||||
| func (a *COSAddress) GetType() string { | |||||
| return "COSAddress" | |||||
| } | |||||
| func (a *COSAddress) String() string { | |||||
| return "COSAddress" | |||||
| } | } | ||||