From b8490ef42235b106e01191ff8adbe96fab3d3df8 Mon Sep 17 00:00:00 2001 From: Fendy Date: Wed, 5 Aug 2026 22:35:28 +0800 Subject: [PATCH 01/17] feat(rcs): migrate all RCS APIs from rainyun-go-sdk - 35 endpoints across 14 files, response structs split for reuse - Reuse RcsRecord/Node/Plan/OsInfo/UsageData across list/detail/create - Fix source bugs: {id} path placeholders, missing / in firewall pos path, missing body in ChargeRcsTrafic, resp vs &resp - Add HTTPMethod_PATCH, common.BasicOperationResponse, VncConnectionInfo --- apis/common/common_types.go | 5 ++ apis/common/query_test.go | 12 ++-- apis/common/vnc.go | 19 ++++++ apis/rcs/backup.go | 58 +++++++++++++++++ apis/rcs/changeos.go | 26 ++++++++ apis/rcs/create_rcs.go | 36 +++++++++++ apis/rcs/edisk.go | 36 +++++++++++ apis/rcs/eip.go | 61 ++++++++++++++++++ apis/rcs/firewall.go | 86 +++++++++++++++++++++++++ apis/rcs/get_rcs_detail.go | 124 ++++++++++++++++++++++++++++++++++++ apis/rcs/get_rcs_list.go | 4 +- apis/rcs/lifecycle.go | 64 +++++++++++++++++++ apis/rcs/monitor.go | 36 +++++++++++ apis/rcs/nat.go | 39 ++++++++++++ apis/rcs/renew.go | 82 ++++++++++++++++++++++++ apis/rcs/traffic.go | 33 ++++++++++ apis/rcs/upgrade.go | 21 ++++++ apis/rcs/vnc.go | 36 +++++++++++ constant/method.go | 1 + docs/CHANGELOG.md | 8 +++ 20 files changed, 779 insertions(+), 8 deletions(-) create mode 100644 apis/common/vnc.go create mode 100644 apis/rcs/backup.go create mode 100644 apis/rcs/changeos.go create mode 100644 apis/rcs/create_rcs.go create mode 100644 apis/rcs/edisk.go create mode 100644 apis/rcs/eip.go create mode 100644 apis/rcs/firewall.go create mode 100644 apis/rcs/get_rcs_detail.go create mode 100644 apis/rcs/lifecycle.go create mode 100644 apis/rcs/monitor.go create mode 100644 apis/rcs/nat.go create mode 100644 apis/rcs/renew.go create mode 100644 apis/rcs/traffic.go create mode 100644 apis/rcs/upgrade.go create mode 100644 apis/rcs/vnc.go diff --git a/apis/common/common_types.go b/apis/common/common_types.go index e52bdd2..244076a 100644 --- a/apis/common/common_types.go +++ b/apis/common/common_types.go @@ -6,3 +6,8 @@ type StandQueryParameters struct { Page int `json:"page"` PerPage int `json:"perPage"` } + +type BasicOperationResponse struct { + Code int `json:"code"` + Data string `json:"data"` +} diff --git a/apis/common/query_test.go b/apis/common/query_test.go index f7f37ce..4dbad4b 100644 --- a/apis/common/query_test.go +++ b/apis/common/query_test.go @@ -6,12 +6,12 @@ import ( ) type queryParamsSample struct { - IsRGpu *bool `json:"is_rgpu"` - Limit int `json:"limit"` - Name string `json:"name"` - Options StandQueryParameters `json:"options"` - Tags []string `json:"tags"` - Skip string `json:"-"` + IsRGpu *bool `json:"is_rgpu"` + Limit int `json:"limit"` + Name string `json:"name"` + Options StandQueryParameters `json:"options"` + Tags []string `json:"tags"` + Skip string `json:"-"` NoTag string } diff --git a/apis/common/vnc.go b/apis/common/vnc.go new file mode 100644 index 0000000..ccc51ea --- /dev/null +++ b/apis/common/vnc.go @@ -0,0 +1,19 @@ +package common + +import "strconv" + +type VncConnectionInfo struct { + Code int `json:"code"` + Data VncConnectionData `json:"data"` +} + +type VncConnectionData struct { + RequestURL string `json:"RequestURL"` // 空 + RedirectURL string `json:"RedirectURL"` // 空 + PVEAuth string `json:"PVEAuth"` // 空 + VNCProxyURL string `json:"VNCProxyURL"` // PVE代理地址(连接地址),里面会有一个unicode码(\u0026)(&),转码后就是可以直接在浏览器上使用的VNC连接地址 +} + +func GetVncConnectURL(v *VncConnectionInfo) (string, error) { + return strconv.Unquote(v.Data.VNCProxyURL) +} diff --git a/apis/rcs/backup.go b/apis/rcs/backup.go new file mode 100644 index 0000000..20c527f --- /dev/null +++ b/apis/rcs/backup.go @@ -0,0 +1,58 @@ +package rcs + +import ( + "fmt" + + "github.com/XingfenD/rainyun_api_go_sdk/apis/common" + "github.com/XingfenD/rainyun_api_go_sdk/constant" +) + +type CreateRcsBackupRequest struct { + Label string `json:"label"` // 备份名称 +} + +type RcsSetBackupOptionsRequest struct { + AutoBackupHour int `json:"auto_backup_hour"` // 自动备份时间的小时 + AutoBackupMinute int `json:"auto_backup_minute"` // 自动备份时间的分钟 + KeepLast int `json:"keep_last"` // 保留份数(1/3/7) +} + +func (s *RcsService) CreateRcsBackup(id int, label string) (*common.BasicOperationResponse, error) { + path := fmt.Sprintf("/product/rcs/%d/backup", id) + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_POST, path, nil, CreateRcsBackupRequest{Label: label}, &resp) + return &resp, err +} + +func (s *RcsService) DeleteRcsBackup(id, bid int) (*common.BasicOperationResponse, error) { + path := fmt.Sprintf("/product/rcs/%d/backup/%d", id, bid) + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_DELETE, path, nil, nil, &resp) + return &resp, err +} + +func (s *RcsService) CancelRcsBackup(id, bid int) (*common.BasicOperationResponse, error) { + path := fmt.Sprintf("/product/rcs/%d/backup/%d/cancel", id, bid) + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_POST, path, nil, nil, &resp) + return &resp, err +} + +func (s *RcsService) RestoreRcsBackup(id, bid int) (*common.BasicOperationResponse, error) { + path := fmt.Sprintf("/product/rcs/%d/backup/%d/restore", id, bid) + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_POST, path, nil, nil, &resp) + return &resp, err +} + +func (s *RcsService) EnableRcsAutoBackup(id int, req *RcsSetBackupOptionsRequest) (*common.BasicOperationResponse, error) { + path := fmt.Sprintf("/product/rcs/%d/backup/setting", id) + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_PATCH, path, nil, req, &resp) + return &resp, err +} diff --git a/apis/rcs/changeos.go b/apis/rcs/changeos.go new file mode 100644 index 0000000..3cd46b8 --- /dev/null +++ b/apis/rcs/changeos.go @@ -0,0 +1,26 @@ +package rcs + +import ( + "fmt" + + "github.com/XingfenD/rainyun_api_go_sdk/apis/common" + "github.com/XingfenD/rainyun_api_go_sdk/constant" +) + +type ReinstallRcsRequest struct { + AppVars []struct { + AppID int `json:"app_id"` + Retry bool `json:"retry"` // 重发之前的任务,此项存在时,无需传入参数 + Vars any `json:"vars"` + } `json:"app_vars"` // 当空数组时,进行单次任务下发 + OsID int `json:"os_id"` // 系统ID + ResetOsd bool `json:"reset_osd"` // 重置系统盘容量 +} + +func (s *RcsService) ReinstallRcs(id int, req *ReinstallRcsRequest) (*common.BasicOperationResponse, error) { + path := fmt.Sprintf("/product/rcs/%d/changeos", id) + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_POST, path, nil, req, &resp) + return &resp, err +} diff --git a/apis/rcs/create_rcs.go b/apis/rcs/create_rcs.go new file mode 100644 index 0000000..fa755e6 --- /dev/null +++ b/apis/rcs/create_rcs.go @@ -0,0 +1,36 @@ +package rcs + +import ( + "github.com/XingfenD/rainyun_api_go_sdk/constant" +) + +type CreateRcsRequest struct { + AddDiskSize int `json:"add_disk_size"` // 额外硬盘容量GB + PlanID int `json:"plan_id"` // 套餐ID + Duration int `json:"duration"` // 创建时长(月) + OsID int `json:"os_id"` // 系统ID + WithEipNum int `json:"with_eip_num"` // 创建IP数量 + WithEipFlags string `json:"with_eip_flags"` // 是否开启高防,us_ddosip -> 美国高防,nb_ddosip -> 宁波高防 + WithEipType string `json:"with_eip_type"` // ipv4(默认)/ipv6 + WithCouponID int `json:"with_coupon_id"` // 优惠券ID + Try bool `json:"try"` // 是否为试用 + NodeUUID string `json:"node_uuid"` // 指定节点(管理员可用,用户不可用) + AppVars []struct { + AppID int `json:"app_id"` + Vars any `json:"vars,omitempty"` + } `json:"app_vars"` // 预装应用 + Zone string `json:"zone"` // 内网可用区 +} + +type CreateRcsResponse struct { + Code int `json:"code"` + Data RcsRecord `json:"data"` +} + +func (s *RcsService) CreateRcs(req *CreateRcsRequest) (*CreateRcsResponse, error) { + path := "/product/rcs" + + var resp CreateRcsResponse + err := s.client.Do(constant.HTTPMethod_POST, path, nil, req, &resp) + return &resp, err +} diff --git a/apis/rcs/edisk.go b/apis/rcs/edisk.go new file mode 100644 index 0000000..ef6f36e --- /dev/null +++ b/apis/rcs/edisk.go @@ -0,0 +1,36 @@ +package rcs + +import ( + "fmt" + + "github.com/XingfenD/rainyun_api_go_sdk/apis/common" + "github.com/XingfenD/rainyun_api_go_sdk/constant" +) + +type RcsManagesElasticCloudDisksRequest struct { + Actions []struct { + Type string `json:"type"` // 操作类型: expand: 扩容, create: 创建 + Action any `json:"action"` // 操作参数, RcsManagesElasticCloudDisksExpand或RcsManagesElasticCloudDisksCreate + } `json:"actions"` +} + +type RcsManagesElasticCloudDisksExpand struct { + EdiskID int `json:"edisk_id"` // 弹性云盘ID + SizeInGb int `json:"size_in_gb"` // 操作容量 + Backup bool `json:"backup"` // 支持备份 +} + +type RcsManagesElasticCloudDisksCreate struct { + SizeInGb int `json:"size_in_gb"` // 操作容量 + DiskType string `json:"disk_type"` // 磁盘类型(ssd/hdd) + Backup bool `json:"backup"` // 支持备份 + Tag string `json:"tag"` // 标签 +} + +func (s *RcsService) RcsManagesElasticCloudDisks(id int, req *RcsManagesElasticCloudDisksRequest) (*common.BasicOperationResponse, error) { + path := fmt.Sprintf("/product/rcs/%d/edisk", id) + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_POST, path, nil, req, &resp) + return &resp, err +} diff --git a/apis/rcs/eip.go b/apis/rcs/eip.go new file mode 100644 index 0000000..cec8f30 --- /dev/null +++ b/apis/rcs/eip.go @@ -0,0 +1,61 @@ +package rcs + +import ( + "fmt" + + "github.com/XingfenD/rainyun_api_go_sdk/apis/common" + "github.com/XingfenD/rainyun_api_go_sdk/constant" +) + +type SetRcsEipDescriptionRequest struct { + IP string `json:"ip"` + Description string `json:"description"` +} + +type CreateAndBindIpToRcsRequest struct { + WithFlags string `json:"with_flags"` // IP特征(可选): 应该是高防: us_ddosip -> 美国高防,nb_ddosip -> 宁波高防 + WithIPNum int `json:"with_ip_num"` // IP数量 + WithIPType string `json:"with_ip_type"` // ipv4/ipv6 +} + +type ChangeRcsIPRequest struct { + DisableOldIPReason string `json:"disable_old_ip_reason"` // 可选 + IP string `json:"ip"` // IP地址 + ToIP string `json:"to_ip"` // 可选 +} + +type DisCardRcsIPRequest struct { + IP string `json:"ip"` +} + +func (s *RcsService) SetRcsEipDescription(id int, ip, desc string) (*common.BasicOperationResponse, error) { + path := fmt.Sprintf("/product/rcs/%d/eip/description", id) + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_POST, path, nil, SetRcsEipDescriptionRequest{IP: ip, Description: desc}, &resp) + return &resp, err +} + +func (s *RcsService) CreateAndBindElasticIpToRcs(id int, req *CreateAndBindIpToRcsRequest) (*common.BasicOperationResponse, error) { + path := fmt.Sprintf("/product/rcs/%d/eip", id) + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_POST, path, nil, req, &resp) + return &resp, err +} + +func (s *RcsService) ChangeRcsIP(id int, req *ChangeRcsIPRequest) (*common.BasicOperationResponse, error) { + path := fmt.Sprintf("/product/rcs/%d/eip/change", id) + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_POST, path, nil, req, &resp) + return &resp, err +} + +func (s *RcsService) DisCardRcsIP(id int, req DisCardRcsIPRequest) (*common.BasicOperationResponse, error) { + path := fmt.Sprintf("/product/rcs/%d/eip/discard", id) + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_POST, path, nil, req, &resp) + return &resp, err +} diff --git a/apis/rcs/firewall.go b/apis/rcs/firewall.go new file mode 100644 index 0000000..8a1b5bd --- /dev/null +++ b/apis/rcs/firewall.go @@ -0,0 +1,86 @@ +package rcs + +import ( + "fmt" + + "github.com/XingfenD/rainyun_api_go_sdk/apis/common" + "github.com/XingfenD/rainyun_api_go_sdk/constant" +) + +type GetRcsFirewallRulesRequest struct { + Options common.StandQueryParameters `json:"options"` +} + +type GetRcsFirewallRulesResponse struct { + Code int `json:"code"` + Data RcsFirewallRuleList `json:"data"` +} + +type RcsFirewallRuleList struct { + TotalRecords int `json:"TotalRecords"` + Records []RcsFirewallRule `json:"Records"` +} + +type RcsFirewallRule struct { + ID int `json:"ID"` + VID int `json:"v_id"` + IsEnable bool `json:"is_enable"` + Pos int `json:"pos"` + SourceAddress string `json:"source_address"` + DestPort string `json:"dest_port"` + SourcePort string `json:"source_port"` + Protocol string `json:"protocol"` + Action string `json:"action"` + Description string `json:"description"` +} + +type SetRcsFirewallRuleRequest struct { + Action string `json:"action"` // 动作,accept/drop,接受或者丢弃 + Description string `json:"description"` // 备注(可选) + DestPort string `json:"dest_port"` // 代表本机的目的端口,可以用-来链接,空白代表所有端口(可选) + ID int `json:"id"` // 规则ID(可选) + IsEnable bool `json:"is_enable"` // 是否启用该规则(可选) + Protocol string `json:"protocol"` // 协议,udp/tcp/icmp,空白代表所有(可选) + SourceAddress string `json:"source_address"` // 代表来源的地址,可以用-链接范围,或者用逗号来分割多个地址,可以使用网络,CIDR格式,空则代表所有地址(可选) + SourcePort string `json:"source_port"` // 一般不填(防反射)(可选) +} + +type MobileRcsFirewallRulePriorityRequest struct { + NewPos int `json:"newPos"` +} + +func (s *RcsService) GetRcsFirewallRules(id int, req *GetRcsFirewallRulesRequest) (*GetRcsFirewallRulesResponse, error) { + path := fmt.Sprintf("/product/rcs/%d/firewall/rule", id) + + var resp GetRcsFirewallRulesResponse + querys, err := common.MarshalQueryParams(req) + if err != nil { + return nil, err + } + err = s.client.Do(constant.HTTPMethod_GET, path, querys, nil, &resp) + return &resp, err +} + +func (s *RcsService) SetRcsFirewallRule(id int, req *SetRcsFirewallRuleRequest) (*common.BasicOperationResponse, error) { + path := fmt.Sprintf("/product/rcs/%d/firewall/rule", id) + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_POST, path, nil, req, &resp) + return &resp, err +} + +func (s *RcsService) DeleteRcsFirewallRule(id, ruleID int) (*common.BasicOperationResponse, error) { + path := fmt.Sprintf("/product/rcs/%d/firewall/rule/%d", id, ruleID) + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_DELETE, path, nil, nil, &resp) + return &resp, err +} + +func (s *RcsService) MobileRcsFirewallRulePriority(id, ruleID int, req MobileRcsFirewallRulePriorityRequest) (*common.BasicOperationResponse, error) { + path := fmt.Sprintf("/product/rcs/%d/firewall/rule/%d/pos", id, ruleID) + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_PUT, path, nil, req, &resp) + return &resp, err +} diff --git a/apis/rcs/get_rcs_detail.go b/apis/rcs/get_rcs_detail.go new file mode 100644 index 0000000..39dcf89 --- /dev/null +++ b/apis/rcs/get_rcs_detail.go @@ -0,0 +1,124 @@ +package rcs + +import ( + "fmt" + + "github.com/XingfenD/rainyun_api_go_sdk/constant" +) + +type GetRcsDetailResponse struct { + Code int `json:"code"` + Data RcsDetail `json:"data"` +} + +type RcsDetail struct { + Data RcsRecord `json:"Data"` + UpgradeablePlans []UpgradeablePlan `json:"UpgradeablePlans"` + RBSList []RBSItem `json:"RBSList"` + NatList []any `json:"NatList"` + EDiskList []EDiskItem `json:"EDiskList"` + EIPList []EIPItem `json:"EIPList"` + RenewPointPrice RenewPointPrice `json:"RenewPointPrice"` + FastInstallAppTask []any `json:"FastInstallAppTask"` + VNets []any `json:"VNets"` +} + +type UpgradeablePlan struct { + ID int `json:"id"` + Region string `json:"region"` + Subtype string `json:"subtype"` + PlanName string `json:"plan_name"` + Machine string `json:"machine"` + ChargeType string `json:"charge_type"` + Chinese string `json:"chinese"` + IsFree bool `json:"is_free"` + PointRenewPrice any `json:"point_renew_price"` + IsSelling bool `json:"is_selling"` + Price int `json:"price"` + TrafficBaseGb int `json:"traffic_base_gb"` + TrafficPrice any `json:"traffic_price"` + CPU int `json:"cpu"` + Memory int `json:"memory"` + NetIn int `json:"net_in"` + NetOut int `json:"net_out"` + IPPrices any `json:"ip_prices"` + IPSelling any `json:"ip_selling"` + AutoRestock int `json:"auto_restock"` + AvailableStock int `json:"available_stock"` + GpuMemorySize int `json:"gpu_memory_size"` + DgpuDevType string `json:"dgpu_dev_type"` + WebbarConfig any `json:"webbar_config"` + NoBackup bool `json:"no_backup"` + DiskPrice DiskPrice `json:"disk_price"` +} + +type RBSItem struct { + ID int `json:"ID"` + UID int `json:"UID"` + ProductID int `json:"ProductID"` + NodeUUID string `json:"NodeUUID"` + Node Node `json:"Node"` + Label string `json:"Label"` + FileName string `json:"FileName"` + PackSize int64 `json:"PackSize"` + CreateTime int `json:"CreateTime"` + FinishTime int `json:"FinishTime"` + Retry int `json:"Retry"` + AdditionalInfo RBSAdditionalInfo `json:"AdditionalInfo"` + Status string `json:"Status"` +} + +type RBSAdditionalInfo struct { + Osid int `json:"OSID"` + OSName string `json:"OSName"` + OSBaseDiskSize int `json:"OSBaseDiskSize"` + OSDataDiskSize int `json:"OSDataDiskSize"` + Slots map[string]RBSSlot `json:"Slots"` +} + +type RBSSlot struct { + DiskType string `json:"DiskType"` + Backup bool `json:"Backup"` + Size int `json:"Size"` +} + +type EDiskItem struct { + ID int `json:"ID"` + Slot int `json:"Slot"` + UID int `json:"UID"` + DiskType string `json:"DiskType"` + Tag string `json:"Tag"` + OSName string `json:"OSName"` + Vid int `json:"VID"` + Size int `json:"Size"` + Backup bool `json:"Backup"` +} + +type EIPItem struct { + ID int `json:"ID"` + IPRegion string `json:"IpRegion"` + Region string `json:"Region"` + Type string `json:"Type"` + IP string `json:"IP"` + Gateway string `json:"Gateway"` + Block string `json:"Block"` + UID int `json:"UID"` + Vid int `json:"VID"` + CreateDate int `json:"CreateDate"` + Flags string `json:"Flags"` + VlanID int `json:"VlanID"` + Description string `json:"Description"` +} + +type RenewPointPrice struct { + Num7 int `json:"7"` + Num31 int `json:"31"` +} + +func (s *RcsService) GetRcsDetail(id int) (*GetRcsDetailResponse, error) { + path := fmt.Sprintf("/product/rcs/%d", id) + + var resp GetRcsDetailResponse + err := s.client.Do(constant.HTTPMethod_GET, path, nil, nil, &resp) + return &resp, err +} diff --git a/apis/rcs/get_rcs_list.go b/apis/rcs/get_rcs_list.go index 331e9ff..ce6879b 100644 --- a/apis/rcs/get_rcs_list.go +++ b/apis/rcs/get_rcs_list.go @@ -47,9 +47,9 @@ type UsageData struct { UsedMem int `json:"UsedMem"` Disks DiskUsageData `json:"Disks"` DiskRead int `json:"DiskRead"` - DiskWrite int `json:"DiskWrite"` + DiskWrite float64 `json:"DiskWrite"` NetOut float64 `json:"NetOut"` - NetIn int `json:"NetIn"` + NetIn float64 `json:"NetIn"` SmartHealth any `json:"SmartHealth"` SmartTemp int `json:"SmartTemp"` UpdateTime int `json:"UpdateTime"` diff --git a/apis/rcs/lifecycle.go b/apis/rcs/lifecycle.go new file mode 100644 index 0000000..492231c --- /dev/null +++ b/apis/rcs/lifecycle.go @@ -0,0 +1,64 @@ +package rcs + +import ( + "fmt" + + "github.com/XingfenD/rainyun_api_go_sdk/apis/common" + "github.com/XingfenD/rainyun_api_go_sdk/constant" +) + +type ResetRcsPasswordRequest struct { + Password string `json:"password"` // 新密码,留空则自动生成 +} + +type SetRcsTagRequest struct { + TagName string `json:"tag_name"` +} + +func (s *RcsService) FreeRcs(id int) (*common.BasicOperationResponse, error) { + path := fmt.Sprintf("/product/rcs/%d/free", id) + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_POST, path, nil, nil, &resp) + return &resp, err +} + +func (s *RcsService) RebootRcs(id int) (*common.BasicOperationResponse, error) { + path := fmt.Sprintf("/product/rcs/%d/reboot", id) + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_POST, path, nil, nil, &resp) + return &resp, err +} + +func (s *RcsService) ResetRcsPassword(id int, newPass string) (*common.BasicOperationResponse, error) { + path := fmt.Sprintf("/product/rcs/%d/reset-password", id) + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_POST, path, nil, ResetRcsPasswordRequest{Password: newPass}, &resp) + return &resp, err +} + +func (s *RcsService) StartRcs(id int) (*common.BasicOperationResponse, error) { + path := fmt.Sprintf("/product/rcs/%d/start", id) + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_POST, path, nil, nil, &resp) + return &resp, err +} + +func (s *RcsService) StopRcs(id int) (*common.BasicOperationResponse, error) { + path := fmt.Sprintf("/product/rcs/%d/stop", id) + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_POST, path, nil, nil, &resp) + return &resp, err +} + +func (s *RcsService) SetRcsTag(id int, tag string) (*common.BasicOperationResponse, error) { + path := fmt.Sprintf("/product/rcs/%d/tag", id) + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_POST, path, nil, SetRcsTagRequest{TagName: tag}, &resp) + return &resp, err +} diff --git a/apis/rcs/monitor.go b/apis/rcs/monitor.go new file mode 100644 index 0000000..b05ab54 --- /dev/null +++ b/apis/rcs/monitor.go @@ -0,0 +1,36 @@ +package rcs + +import ( + "fmt" + + "github.com/XingfenD/rainyun_api_go_sdk/apis/common" + "github.com/XingfenD/rainyun_api_go_sdk/constant" +) + +type GetRcsMonitorDataRequest struct { + StartDate int `json:"start_date"` // 开始时间(时间戳) + EndDate int `json:"end_date"` // 结束时间(时间戳) +} + +type GetRcsMonitorDataResponse struct { + Code int `json:"code"` + Data RcsMonitorData `json:"data"` +} + +// RcsMonitorData 监控数据, Columns 与 Values 一一对应 +type RcsMonitorData struct { + Columns []string `json:"Columns"` + Values [][]float64 `json:"Values"` +} + +func (s *RcsService) GetRcsMonitorData(id int, req *GetRcsMonitorDataRequest) (*GetRcsMonitorDataResponse, error) { + path := fmt.Sprintf("/product/rcs/%d/monitor", id) + + var resp GetRcsMonitorDataResponse + querys, err := common.MarshalQueryParams(req) + if err != nil { + return nil, err + } + err = s.client.Do(constant.HTTPMethod_GET, path, querys, nil, &resp) + return &resp, err +} diff --git a/apis/rcs/nat.go b/apis/rcs/nat.go new file mode 100644 index 0000000..26386c3 --- /dev/null +++ b/apis/rcs/nat.go @@ -0,0 +1,39 @@ +package rcs + +import ( + "fmt" + + "github.com/XingfenD/rainyun_api_go_sdk/apis/common" + "github.com/XingfenD/rainyun_api_go_sdk/constant" +) + +type AddRcsNatPortMappingRequest struct { + PortIn int `json:"port_in"` // >= 1 <= 65535 + PortOut int `json:"port_out"` // >= 10000 <= 60000 + PortType string `json:"port_type"` // tcp/udp/tcp_udp + Tag string `json:"tag"` // 可选 +} + +type DeleteRcsNatPortMappingRequest struct { + NatID int `json:"nat_id"` +} + +func (s *RcsService) AddRcsNatPortMapping(id int, req *AddRcsNatPortMappingRequest) (*common.BasicOperationResponse, error) { + path := fmt.Sprintf("/product/rcs/%d/nat", id) + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_POST, path, nil, req, &resp) + return &resp, err +} + +func (s *RcsService) DeleteRcsNatPortMapping(id int, req *DeleteRcsNatPortMappingRequest) (*common.BasicOperationResponse, error) { + path := fmt.Sprintf("/product/rcs/%d/nat", id) + + var resp common.BasicOperationResponse + querys, err := common.MarshalQueryParams(req) + if err != nil { + return nil, err + } + err = s.client.Do(constant.HTTPMethod_DELETE, path, querys, nil, &resp) + return &resp, err +} diff --git a/apis/rcs/renew.go b/apis/rcs/renew.go new file mode 100644 index 0000000..deb4be6 --- /dev/null +++ b/apis/rcs/renew.go @@ -0,0 +1,82 @@ +package rcs + +import ( + "fmt" + + "github.com/XingfenD/rainyun_api_go_sdk/apis/common" + "github.com/XingfenD/rainyun_api_go_sdk/constant" +) + +type GetRcsRenewPriceRequest struct { + ProductID int `json:"product_id"` + Duration int `json:"duration"` + WithCouponID int `json:"with_coupon_id"` +} + +type GetRcsRenewPriceResponse struct { + Code int `json:"code"` + Data RcsRenewPrice `json:"data"` +} + +type RcsRenewPrice struct { + Detail RcsRenewPriceDetail `json:"detail"` + Price float64 `json:"price"` +} + +type RcsRenewPriceDetail struct { + Price float64 `json:"price"` + AgentPrice float64 `json:"agent_price"` + StockPrice float64 `json:"stock_price"` + DefaultPrice int `json:"default_price"` + CouponValue int `json:"coupon_value"` + SaleReward int `json:"sale_reward"` + AgentReward int `json:"agent_reward"` + AgentID int `json:"agent_id"` + IgnoreAgent bool `json:"ignore_agent"` + PerScene RcsRenewPerScene `json:"per_scene"` +} + +type RcsRenewPerScene struct { + Eip int `json:"eip"` + Renew float64 `json:"renew"` // 配置价格 + RenewEip float64 `json:"renew_eip"` // IP价格 +} + +type RenewRcsRequest struct { + Duration int `json:"duration"` // 续费时长(月) + WithCouponID int `json:"with_coupon_id"` // 优惠券ID +} + +type EnableRcsAutoRenewRequest struct { + AutoRenewOption bool `json:"auto_renew_option"` +} + +func (s *RcsService) GetRcsRenewPrice(req *GetRcsRenewPriceRequest) (*GetRcsRenewPriceResponse, error) { + path := "/product/rcs/price" + + var resp GetRcsRenewPriceResponse + querys, err := common.MarshalQueryParams(req) + if err != nil { + return nil, err + } + querys["scene"] = "renew" + querys["is_old"] = "true" + err = s.client.Do(constant.HTTPMethod_GET, path, querys, nil, &resp) + return &resp, err +} + +func (s *RcsService) RenewRcs(id int, req RenewRcsRequest) (*common.BasicOperationResponse, error) { + path := fmt.Sprintf("/product/rcs/%d/renew", id) + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_POST, path, nil, req, &resp) + return &resp, err +} + +func (s *RcsService) EnableRcsAutoRenew(id int, req EnableRcsAutoRenewRequest) (*common.BasicOperationResponse, error) { + path := fmt.Sprintf("/product/rcs/%d/renew/option", id) + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_POST, path, nil, req, &resp) + return &resp, err +} diff --git a/apis/rcs/traffic.go b/apis/rcs/traffic.go new file mode 100644 index 0000000..c88b6c1 --- /dev/null +++ b/apis/rcs/traffic.go @@ -0,0 +1,33 @@ +package rcs + +import ( + "fmt" + + "github.com/XingfenD/rainyun_api_go_sdk/apis/common" + "github.com/XingfenD/rainyun_api_go_sdk/constant" +) + +type ChargeRcsTraficRequest struct { + TrafficInGb int `json:"traffic_in_gb"` // 充多少G +} + +type LimitRcsTrafficRequest struct { + DayTrafficInGb int `json:"day_traffic_in_gb"` // 日流量阈值(G) + TrafficLimit int `json:"traffic_limit"` // 限制带宽(M) +} + +func (s *RcsService) ChargeRcsTrafic(id, count int) (*common.BasicOperationResponse, error) { + path := fmt.Sprintf("/product/rcs/%d/traffic/charge", id) + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_POST, path, nil, ChargeRcsTraficRequest{TrafficInGb: count}, &resp) + return &resp, err +} + +func (s *RcsService) LimitRcsTrafic(id, threshold, limit int) (*common.BasicOperationResponse, error) { + path := fmt.Sprintf("/product/rcs/%d/traffic/limit", id) + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_POST, path, nil, LimitRcsTrafficRequest{DayTrafficInGb: threshold, TrafficLimit: limit}, &resp) + return &resp, err +} diff --git a/apis/rcs/upgrade.go b/apis/rcs/upgrade.go new file mode 100644 index 0000000..58049cb --- /dev/null +++ b/apis/rcs/upgrade.go @@ -0,0 +1,21 @@ +package rcs + +import ( + "fmt" + + "github.com/XingfenD/rainyun_api_go_sdk/apis/common" + "github.com/XingfenD/rainyun_api_go_sdk/constant" +) + +type UpgradeRcsRequest struct { + DestPlan int `json:"dest_plan"` // 升级到的套餐ID + WithCouponID int `json:"with_coupon_id"` // 优惠券ID,默认为0 +} + +func (s *RcsService) UpgradeRcs(id, plan, coupon int) (*common.BasicOperationResponse, error) { + path := fmt.Sprintf("/product/rcs/%d/upgrade", id) + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_POST, path, nil, UpgradeRcsRequest{DestPlan: plan, WithCouponID: coupon}, &resp) + return &resp, err +} diff --git a/apis/rcs/vnc.go b/apis/rcs/vnc.go new file mode 100644 index 0000000..f8d088b --- /dev/null +++ b/apis/rcs/vnc.go @@ -0,0 +1,36 @@ +package rcs + +import ( + "fmt" + "net/url" + + "github.com/XingfenD/rainyun_api_go_sdk/apis/common" + "github.com/XingfenD/rainyun_api_go_sdk/constant" +) + +// GetRcsVnc 连接VNC +// +// consoleType: 控制台类型,可选值: novnc,xtermjs +func (s *RcsService) GetRcsVnc(id int, consoleType string) (*common.VncConnectionInfo, error) { + path := fmt.Sprintf("/product/rcs/%d/vnc", id) + + var resp common.VncConnectionInfo + querys := map[string]string{"console_type": consoleType} + err := s.client.Do(constant.HTTPMethod_GET, path, querys, nil, &resp) + return &resp, err +} + +// GetRcsPveAddress 获取RCS对应的PVE地址 +func (s *RcsService) GetRcsPveAddress(id int) (string, error) { + v, err := s.GetRcsVnc(id, "novnc") + if err != nil { + return "", err + } + + parsedURL, err := url.Parse(v.Data.RequestURL) + if err != nil { + return "", err + } + + return parsedURL.Hostname(), nil +} diff --git a/constant/method.go b/constant/method.go index 2a7a2ab..9a0afe2 100644 --- a/constant/method.go +++ b/constant/method.go @@ -7,4 +7,5 @@ const ( HTTPMethod_POST HTTPMethod = "POST" HTTPMethod_DELETE HTTPMethod = "DELETE" HTTPMethod_PUT HTTPMethod = "PUT" + HTTPMethod_PATCH HTTPMethod = "PATCH" ) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index c0aade5..ff0b6f2 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -6,6 +6,14 @@ All notable changes to this template should be documented in this file. The format loosely follows Keep a Changelog and can be adapted to the team's habits. 本文档参考了 Keep a Changelog 的思路,也可以根据团队习惯调整。 +## [0.1.0] - 2026-08-05 + +### Added / 新增 + +- 从 `rainyun-go-sdk` 迁移 RCS 全部 35 个接口(列表/详情/创建/备份/防火墙/监控/NAT/续费/升降级等),响应结构体嵌套已拆分复用(`RcsRecord`、`Node`、`Plan`、`OsInfo`、`UsageData` 等)。 +- 新增 `apis/common` 基础类型:`BasicOperationResponse`、`VncConnectionInfo`。 +- `constant` 新增 `HTTPMethod_PATCH`。 + ## [0.0.0] - 2026-08-02 ### Notes / 说明 From 51b7e2f319f4473b2f2f1f04c279720981cc66b3 Mon Sep 17 00:00:00 2001 From: Fendy Date: Wed, 5 Aug 2026 23:09:53 +0800 Subject: [PATCH 02/17] feat: migrate all remaining modules from rainyun-go-sdk - domain(24), product(8), rbm(10), rca(21), rgs(38), ros(19), ssl(5), user(10), workorder(11), public remaining 5 - split nested response structs into reusable named types - rgs reuses rcs request/response types - fix source bugs: malformed paths, resp vs &resp, missing bodies - register all 11 services in sdk.New --- apis/domain/certify.go | 73 +++++++++ apis/domain/client.go | 11 ++ apis/domain/dns.go | 72 +++++++++ apis/domain/domain.go | 104 ++++++++++++ apis/domain/model.go | 264 +++++++++++++++++++++++++++++++ apis/domain/template.go | 52 ++++++ apis/product/client.go | 11 ++ apis/product/count.go | 72 +++++++++ apis/product/panel_user.go | 112 +++++++++++++ apis/product/renew.go | 22 +++ apis/public/get_egg_type_list.go | 41 +++++ apis/public/get_eggs.go | 31 ++++ apis/public/get_rcs_os_list.go | 39 +++++ apis/public/get_rgs_os_list.go | 39 +++++ apis/public/get_ros_plans.go | 38 +++++ apis/rbm/changeos.go | 22 +++ apis/rbm/client.go | 11 ++ apis/rbm/eip.go | 60 +++++++ apis/rbm/lifecycle.go | 44 ++++++ apis/rbm/plan.go | 42 +++++ apis/rbm/traffic.go | 22 +++ apis/rca/appstore.go | 172 ++++++++++++++++++++ apis/rca/client.go | 11 ++ apis/rca/project.go | 254 +++++++++++++++++++++++++++++ apis/rca/raindrop.go | 128 +++++++++++++++ apis/rca/resource.go | 130 +++++++++++++++ apis/rgs/backup.go | 68 ++++++++ apis/rgs/changeos.go | 20 +++ apis/rgs/client.go | 11 ++ apis/rgs/create_rgs.go | 157 ++++++++++++++++++ apis/rgs/eip.go | 42 +++++ apis/rgs/firewall.go | 66 ++++++++ apis/rgs/get_rgs_detail.go | 46 ++++++ apis/rgs/get_rgs_list.go | 126 +++++++++++++++ apis/rgs/lifecycle.go | 164 +++++++++++++++++++ apis/rgs/mcsm.go | 53 +++++++ apis/rgs/monitor.go | 38 +++++ apis/rgs/nat.go | 20 +++ apis/rgs/renew.go | 111 +++++++++++++ apis/rgs/vnc.go | 40 +++++ apis/ros/bucket.go | 196 +++++++++++++++++++++++ apis/ros/client.go | 11 ++ apis/ros/instance.go | 224 ++++++++++++++++++++++++++ apis/ros/model.go | 141 +++++++++++++++++ apis/ssl/certificate.go | 128 +++++++++++++++ apis/ssl/client.go | 11 ++ apis/user/client.go | 11 ++ apis/user/mfa.go | 40 +++++ apis/user/reward.go | 186 ++++++++++++++++++++++ apis/user/user.go | 123 ++++++++++++++ apis/workorder/client.go | 11 ++ apis/workorder/model.go | 152 ++++++++++++++++++ apis/workorder/score.go | 43 +++++ apis/workorder/workorder.go | 110 +++++++++++++ docs/CHANGELOG.md | 12 ++ sdk/sdk.go | 31 +++- 56 files changed, 4267 insertions(+), 2 deletions(-) create mode 100644 apis/domain/certify.go create mode 100644 apis/domain/client.go create mode 100644 apis/domain/dns.go create mode 100644 apis/domain/domain.go create mode 100644 apis/domain/model.go create mode 100644 apis/domain/template.go create mode 100644 apis/product/client.go create mode 100644 apis/product/count.go create mode 100644 apis/product/panel_user.go create mode 100644 apis/product/renew.go create mode 100644 apis/public/get_egg_type_list.go create mode 100644 apis/public/get_eggs.go create mode 100644 apis/public/get_rcs_os_list.go create mode 100644 apis/public/get_rgs_os_list.go create mode 100644 apis/public/get_ros_plans.go create mode 100644 apis/rbm/changeos.go create mode 100644 apis/rbm/client.go create mode 100644 apis/rbm/eip.go create mode 100644 apis/rbm/lifecycle.go create mode 100644 apis/rbm/plan.go create mode 100644 apis/rbm/traffic.go create mode 100644 apis/rca/appstore.go create mode 100644 apis/rca/client.go create mode 100644 apis/rca/project.go create mode 100644 apis/rca/raindrop.go create mode 100644 apis/rca/resource.go create mode 100644 apis/rgs/backup.go create mode 100644 apis/rgs/changeos.go create mode 100644 apis/rgs/client.go create mode 100644 apis/rgs/create_rgs.go create mode 100644 apis/rgs/eip.go create mode 100644 apis/rgs/firewall.go create mode 100644 apis/rgs/get_rgs_detail.go create mode 100644 apis/rgs/get_rgs_list.go create mode 100644 apis/rgs/lifecycle.go create mode 100644 apis/rgs/mcsm.go create mode 100644 apis/rgs/monitor.go create mode 100644 apis/rgs/nat.go create mode 100644 apis/rgs/renew.go create mode 100644 apis/rgs/vnc.go create mode 100644 apis/ros/bucket.go create mode 100644 apis/ros/client.go create mode 100644 apis/ros/instance.go create mode 100644 apis/ros/model.go create mode 100644 apis/ssl/certificate.go create mode 100644 apis/ssl/client.go create mode 100644 apis/user/client.go create mode 100644 apis/user/mfa.go create mode 100644 apis/user/reward.go create mode 100644 apis/user/user.go create mode 100644 apis/workorder/client.go create mode 100644 apis/workorder/model.go create mode 100644 apis/workorder/score.go create mode 100644 apis/workorder/workorder.go diff --git a/apis/domain/certify.go b/apis/domain/certify.go new file mode 100644 index 0000000..5c1f761 --- /dev/null +++ b/apis/domain/certify.go @@ -0,0 +1,73 @@ +package domain + +import ( + "github.com/XingfenD/rainyun_api_go_sdk/apis/common" + "github.com/XingfenD/rainyun_api_go_sdk/constant" +) + +// 备案域名过白 +// +// domain: 域名 +// +// region: 区域:cn-sq1/cn-nb1/cn-xy1/cn-cq1 +func (s *DomainService) AddDomainToWhiteList(domain, region string) (*common.BasicOperationResponse, error) { + path := "/product/domain_white_list" + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_POST, path, nil, AddDomainToWhiteListRequest{ + Domain: domain, + Region: region, + }, &resp) + + return &resp, err +} + +// 获取域名白名单列表 +func (s *DomainService) GetDomainWhiteList(req *GetDomainWhiteListRequest) (*GetDomainWhiteListResponse, error) { + path := "/product/domain/whitelist" + + var resp GetDomainWhiteListResponse + querys, err := common.MarshalQueryParams(req) + if err != nil { + return nil, err + } + err = s.client.Do(constant.HTTPMethod_GET, path, querys, nil, &resp) + return &resp, err +} + +// 获取已验证域名列表 +func (s *DomainService) GetVerifiedDomainList(req *GetVerifiedDomainListRequest) (*GetVerifiedDomainListResponse, error) { + path := "/product/domain/certify" + + var resp GetVerifiedDomainListResponse + querys, err := common.MarshalQueryParams(req) + if err != nil { + return nil, err + } + err = s.client.Do(constant.HTTPMethod_GET, path, querys, nil, &resp) + return &resp, err +} + +// 添加域名认证 +// +// domain: 域名 +func (s *DomainService) AddDomainCertify(domain string) (*GetDomainVerificationInfoResponse, error) { + path := "/product/domain/certify" + + var resp GetDomainVerificationInfoResponse + err := s.client.Do(constant.HTTPMethod_POST, path, nil, AddDomainVerificationRequest{Domain: domain}, &resp) + + return &resp, err +} + +// 域名认证校验 +// +// domain: 域名 +func (s *DomainService) VerifyDomainCertify(domain string) (*common.BasicOperationResponse, error) { + path := "/product/domain/certify/verify" + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_POST, path, nil, AddDomainVerificationRequest{Domain: domain}, &resp) + + return &resp, err +} diff --git a/apis/domain/client.go b/apis/domain/client.go new file mode 100644 index 0000000..cc0925c --- /dev/null +++ b/apis/domain/client.go @@ -0,0 +1,11 @@ +package domain + +import "github.com/XingfenD/rainyun_api_go_sdk/apis" + +type DomainService struct { + client *apis.RyClient +} + +func NewDomainService(c *apis.RyClient) *DomainService { + return &DomainService{client: c} +} diff --git a/apis/domain/dns.go b/apis/domain/dns.go new file mode 100644 index 0000000..76c89ad --- /dev/null +++ b/apis/domain/dns.go @@ -0,0 +1,72 @@ +package domain + +import ( + "fmt" + + "github.com/XingfenD/rainyun_api_go_sdk/apis/common" + "github.com/XingfenD/rainyun_api_go_sdk/constant" +) + +// 添加域名DNS解析 +// +// id: 域名ID +func (s *DomainService) AddDomainDNSRecord(id int, req *AddDomainDNSRecordRequest) (*common.BasicOperationResponse, error) { + path := fmt.Sprintf("/product/domain/%d/dns", id) + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_POST, path, nil, req, &resp) + + return &resp, err +} + +// 修改域名DNS解析 +// +// req.RecordID 指定要修改的记录 +func (s *DomainService) UpdateDomainDNSRecord(id int, req *AddDomainDNSRecordRequest) (*common.BasicOperationResponse, error) { + path := fmt.Sprintf("/product/domain/%d/dns", id) + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_PATCH, path, nil, req, &resp) + + return &resp, err +} + +// 删除域名DNS解析 +func (s *DomainService) DeleteDomainDNSRecord(id, recordID int) (*common.BasicOperationResponse, error) { + path := fmt.Sprintf("/product/domain/%d/dns", id) + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_DELETE, path, nil, DeleteDomainDNSRecordRequest{RecordID: recordID}, &resp) + + return &resp, err +} + +// 添加域名DNSSEC +func (s *DomainService) AddDomainDNSSEC(id int, req *AddDomainDNSSECRequest) (*common.BasicOperationResponse, error) { + path := fmt.Sprintf("/product/domain/%d/dnssec", id) + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_POST, path, nil, req, &resp) + + return &resp, err +} + +// 删除域名DNSSEC +func (s *DomainService) DeleteDomainDNSSEC(id int, req *DeleteDomainDNSSECRequest) (*common.BasicOperationResponse, error) { + path := fmt.Sprintf("/product/domain/%d/dnssec/delete", id) + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_POST, path, nil, req, &resp) + + return &resp, err +} + +// 同步域名DNSSEC +func (s *DomainService) SyncDomainDNSSEC(id int) (*common.BasicOperationResponse, error) { + path := fmt.Sprintf("/product/domain/%d/dnssec/sync", id) + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_POST, path, nil, nil, &resp) + + return &resp, err +} diff --git a/apis/domain/domain.go b/apis/domain/domain.go new file mode 100644 index 0000000..58d78a8 --- /dev/null +++ b/apis/domain/domain.go @@ -0,0 +1,104 @@ +package domain + +import ( + "fmt" + + "github.com/XingfenD/rainyun_api_go_sdk/apis/common" + "github.com/XingfenD/rainyun_api_go_sdk/constant" +) + +// 关闭域名锁定 +func (s *DomainService) UnlockDomain(id int) (*common.BasicOperationResponse, error) { + path := fmt.Sprintf("/product/domain/%d/lock/disable", id) + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_PUT, path, nil, nil, &resp) + + return &resp, err +} + +// 开启域名锁定 +func (s *DomainService) LockDomain(id int) (*common.BasicOperationResponse, error) { + path := fmt.Sprintf("/product/domain/%d/lock/enable", id) + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_PUT, path, nil, nil, &resp) + + return &resp, err +} + +// 修改域名NS服务器 +func (s *DomainService) UpdateDomainNS(id int, nss []string) (*common.BasicOperationResponse, error) { + path := fmt.Sprintf("/product/domain/%d/nameservers", id) + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_PUT, path, nil, UpdateDomainNSRequest{NameServers: nss}, &resp) + + return &resp, err +} + +// 重置域名NS服务器 +func (s *DomainService) ResetDomainNS(id int) (*common.BasicOperationResponse, error) { + path := fmt.Sprintf("/product/domain/%d/nameservers/reset", id) + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_POST, path, nil, nil, &resp) + + return &resp, err +} + +// 续费域名 +func (s *DomainService) RenewDomain(id, years int) (*common.BasicOperationResponse, error) { + path := fmt.Sprintf("/product/domain/%d/renew", id) + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_POST, path, nil, RenewDomainRequest{Years: years}, &resp) + + return &resp, err +} + +// 域名过户 +func (s *DomainService) TransferDomain(id int, req *DomainTransferRequest) (*common.BasicOperationResponse, error) { + path := fmt.Sprintf("/product/domain/%d/transfer", id) + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_POST, path, nil, req, &resp) + + return &resp, err +} + +// 更新域名管理密码 +func (s *DomainService) UpdateDomainPassword(id int, password string) (*common.BasicOperationResponse, error) { + path := fmt.Sprintf("/product/domain/%d/password", id) + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_POST, path, nil, UpdateDomainPasswordRequest{Password: password}, &resp) + + return &resp, err +} + +// 获取域名whois信息 +func (s *DomainService) GetDomainWhoisInfo(req *GetDomainWhoisInfoRequest) (*GetDomainWhoisInfoResponse, error) { + path := "/product/domain/whois" + + var resp GetDomainWhoisInfoResponse + querys, err := common.MarshalQueryParams(req) + if err != nil { + return nil, err + } + err = s.client.Do(constant.HTTPMethod_GET, path, querys, nil, &resp) + return &resp, err +} + +// 检查域名能否注册 +func (s *DomainService) CheckDomainAvailability(req *CheckDomainAvailableRequest) (*CheckDomainAvailableResponse, error) { + path := "/product/domain/check" + + var resp CheckDomainAvailableResponse + querys, err := common.MarshalQueryParams(req) + if err != nil { + return nil, err + } + err = s.client.Do(constant.HTTPMethod_GET, path, querys, nil, &resp) + return &resp, err +} diff --git a/apis/domain/model.go b/apis/domain/model.go new file mode 100644 index 0000000..7756105 --- /dev/null +++ b/apis/domain/model.go @@ -0,0 +1,264 @@ +package domain + +import "github.com/XingfenD/rainyun_api_go_sdk/apis/common" + +// 添加域名过白请求 +type AddDomainToWhiteListRequest struct { + Domain string `json:"domain"` // 域名 + Region string `json:"region"` // 区域:cn-sq1/cn-nb1/cn-xy1/cn-cq1 +} + +// 获取域名白名单列表查询参数 +type GetDomainWhiteListRequest struct { + Options common.StandQueryParameters `json:"options"` +} + +// 域名白名单列表响应 +type GetDomainWhiteListResponse struct { + Code int `json:"code"` + Data DomainWhiteListData `json:"data"` +} + +// 域名白名单列表数据 +type DomainWhiteListData struct { + TotalRecords int `json:"TotalRecords"` + Records []DomainWhiteListRecord `json:"Records"` +} + +// 域名白名单记录 +type DomainWhiteListRecord struct { + ID int `json:"id"` // ID + UID int `json:"uid"` + Domain string `json:"domain"` // 域名 + SiteLicense string `json:"site_license"` // 备案号(貌似只有早期过白的会有这个) + Region string `json:"region"` // 区域 + AddTime int `json:"add_time"` // 时间 + Status int `json:"status"` // 状态: 1:已处理 +} + +// 获取已验证域名列表查询参数 +type GetVerifiedDomainListRequest struct { + Options common.StandQueryParameters `json:"options"` +} + +// 已验证域名列表响应 +type GetVerifiedDomainListResponse struct { + Code int `json:"code"` + Data VerifiedDomainListData `json:"data"` +} + +// 已验证域名列表数据 +type VerifiedDomainListData struct { + TotalRecords int `json:"TotalRecords"` + Records []VerifiedDomainRecord `json:"Records"` +} + +// 已验证域名记录 +type VerifiedDomainRecord struct { + ID int `json:"id"` // id + UID int `json:"uid"` + Domain string `json:"domain"` // 域名 + AddTime int `json:"add_time"` // 时间 +} + +// 添加域名认证请求 +type AddDomainVerificationRequest struct { + Domain string `json:"domain"` +} + +// 域名验证信息响应 +type GetDomainVerificationInfoResponse struct { + Code int `json:"code"` + Data DomainVerificationData `json:"data"` +} + +// 域名验证信息数据 +type DomainVerificationData struct { + Rr string `json:"rr"` // 主机名 + TopDomain string `json:"top_domain"` // 主域名 + Record string `json:"record"` // 记录值 +} + +// 添加域名DNS解析请求 +type AddDomainDNSRecordRequest struct { + Host string `json:"host"` // 主机名 + Level int `json:"level"` // 优先等级 + Line string `json:"line"` // 线路,必须有一个默认的才可以 DEFAULT/LTEL/LCNC/LMOB/LEDU/LSEO/LFOR + RainProductID int `json:"rain_product_id"` // 雨云产品id(搭配其它产品时使用) + RainProductType string `json:"rain_product_type"` // 雨云产品类型(搭配其它产品时使用) rcs/rvh/rgs/rbm + RecordID int `json:"record_id"` // 记录id(修改时使用) + TTL int `json:"ttl"` // 解析生效时间 + Type string `json:"type"` // 解析类型 A/AAAA/CNAME/TXT/MX/SRV + Value string `json:"value"` // 解析值 +} + +// 删除域名DNS解析请求 +type DeleteDomainDNSRecordRequest struct { + RecordID int `json:"record_id"` // 记录id +} + +// 添加域名DNSSEC请求 +type AddDomainDNSSECRequest struct { + Domain string `json:"domain"` + Keyalg string `json:"keyalg"` + Keydigest string `json:"keydigest"` + Keytag string `json:"keytag"` + Keytype string `json:"keytype"` +} + +// 删除域名DNSSEC请求 +type DeleteDomainDNSSECRequest struct { + Domain string `json:"domain"` + Keydigest string `json:"keydigest"` + Keytag int `json:"keytag"` +} + +// 修改域名NS服务器请求 +type UpdateDomainNSRequest struct { + NameServers []string `json:"name_servers"` +} + +// 续费域名请求 +type RenewDomainRequest struct { + Years int `json:"years"` // 续费年限 +} + +// 域名过户请求 +type DomainTransferRequest struct { + NewTemplateInfo EditDomainTemplateRequest `json:"new_template_info"` // 和SysID二选一,如果没模板就用这个新建模板 + SysID string `json:"sys_id"` // 新域名模板ID +} + +// 查询域名模板列表查询参数 +type GetDomainTemplateListRequest struct { + Options common.StandQueryParameters `json:"options"` +} + +// 域名模板列表响应 +type GetDomainTemplateListResponse struct { + Code int `json:"code"` + Data DomainTemplateListData `json:"data"` +} + +// 域名模板列表数据 +type DomainTemplateListData struct { + TotalRecords int `json:"TotalRecords"` + Records []DomainTemplateDetail `json:"Records"` // 域名模板详情 +} + +// 删除域名信息模板请求 +type DeleteDomainTemplateRequest struct { + SysID string `json:"sys_id"` // 模板标识 +} + +// 获取域名模板详情请求 +type GetDomainTemplateDetailRequest struct { + SysID string `json:"sys_id"` // 模板标识 +} + +// 获取域名模板详情响应 +type GetDomainTemplateDetailResponse struct { + Code int `json:"code"` + Data DomainTemplateDetail `json:"data"` +} + +// 域名模板详情 +type DomainTemplateDetail struct { + ID int `json:"id"` + UID int `json:"uid"` + AddTime int `json:"add_time"` // 添加时间 + SysID string `json:"sys_id"` // 模板标识 + Type string `json:"type"` // 所有者类型,I是个人,E是企业 + Owner string `json:"owner"` // 模板所有者 + ContactName string `json:"contact_name"` // 联系人姓名 + CompanyName string `json:"company_name"` // 企业名称,如果是企业模板就有这个 + Email string `json:"email"` // 联系人邮箱 + Country string `json:"country"` // 所在国家 + Province string `json:"province"` // 中文,省 + City string `json:"city"` // 中文,市 + ZipCode string `json:"zip_code"` // 中文,邮编 + Phone string `json:"phone"` // 联系电话 + Address string `json:"address"` // 中文,通讯地址 + IDType string `json:"id_type"` // 证件类型,如SFZ + IDNum string `json:"id_num"` // 证件值 + CStatus string `json:"c_status"` // 不明, 1 + CFailInfo string `json:"c_fail_info"` // 模板失败信息 + RStatus string `json:"r_status"` // 不明, 1 + RFailInfo string `json:"r_fail_info"` // 不明 +} + +// 更新域名管理密码请求 +type UpdateDomainPasswordRequest struct { + Password string `json:"password"` +} + +// 编辑域名模板请求 +type EditDomainTemplateRequest struct { + Address string `json:"address"` // 中文,通讯地址 + City string `json:"city"` // 中文,市 + CompanyEnName string `json:"company_en_name"` // 企业英文名称,如果类型是企业就要传入 + CompanyName string `json:"company_name"` // 企业名称,如果类型是企业就要传入 + Country string `json:"country"` // 所在国家 + Email string `json:"email"` // 联系人邮箱 + IDImg string `json:"id_img"` // base64编码的证件照片 + IDNum string `json:"id_num"` // 证件值 + IDType string `json:"id_type"` // 证件类型,如SFZ + Name string `json:"name"` // 联系人名 + Phone string `json:"phone"` // 联系电话 + Province string `json:"province"` // 中文,省 + SysID string `json:"sys_id"` // 模板标识(修改时使用) + Type string `json:"type"` // 所有者类型,I是个人,E是企业 + ZipCode string `json:"zip_code"` // 中文,邮编 +} + +// 获取域名whois信息请求 +type GetDomainWhoisInfoRequest struct { + Domain string `json:"domain"` +} + +// 域名WHOIS信息响应 +type GetDomainWhoisInfoResponse struct { + Code int `json:"code"` + Data DomainWhoisInfoData `json:"data"` +} + +// 域名WHOIS信息数据 +type DomainWhoisInfoData struct { + RegDate string `json:"reg_date"` // 注册时间 + ExpDate string `json:"exp_date"` // 到期时间 + Status string `json:"status"` // 域名状态 + Owner string `json:"owner"` // 域名所有者 + Registrar string `json:"registrar"` // 域名注册商 + Email string `json:"email"` // 域名所有者邮箱 + Nameserver string `json:"nameserver"` // 域名服务器 + BizServer string `json:"biz_server"` + Body string `json:"body"` // 域名WHOIS信息原文 + Updated string `json:"updated"` // WHOIS信息更新时间 +} + +// 检查域名能否注册请求 +type CheckDomainAvailableRequest struct { + Domain string `json:"domain"` // 要检查的域名(中文域名同时支持汉字及punycode转码) + Suffix string `json:"suffix"` // 要检查的后缀,多个的话,用英文逗号分割 +} + +// 检查域名能否注册响应 +type CheckDomainAvailableResponse struct { + Code int `json:"code"` + Data []CheckDomainAvailableItem `json:"data"` +} + +// 检查域名能否注册数据 +type CheckDomainAvailableItem struct { + IsAvail bool `json:"is_avail"` // 是否可注册 + SpecialType string `json:"special_type"` + Name string `json:"name"` // 域名 + Price DomainPrice `json:"price"` +} + +// 域名价格 +type DomainPrice struct { + FirstYearPrice int `json:"first_year_price"` // 首年价格 + BuyPrice int `json:"buy_price"` // 购买价格 + RenewPrice int `json:"renew_price"` // 续费价格 +} diff --git a/apis/domain/template.go b/apis/domain/template.go new file mode 100644 index 0000000..6b3d719 --- /dev/null +++ b/apis/domain/template.go @@ -0,0 +1,52 @@ +package domain + +import ( + "github.com/XingfenD/rainyun_api_go_sdk/apis/common" + "github.com/XingfenD/rainyun_api_go_sdk/constant" +) + +// 查询域名模板列表 +func (s *DomainService) GetDomainTemplateList(req *GetDomainTemplateListRequest) (*GetDomainTemplateListResponse, error) { + path := "/product/domain/template/" + + var resp GetDomainTemplateListResponse + querys, err := common.MarshalQueryParams(req) + if err != nil { + return nil, err + } + err = s.client.Do(constant.HTTPMethod_GET, path, querys, nil, &resp) + return &resp, err +} + +// 删除域名信息模板 +func (s *DomainService) DeleteDomainTemplate(sysID string) (*common.BasicOperationResponse, error) { + path := "/product/domain/template/" + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_DELETE, path, nil, DeleteDomainTemplateRequest{SysID: sysID}, &resp) + + return &resp, err +} + +// 编辑域名模板 +func (s *DomainService) EditDomainTemplate(req *EditDomainTemplateRequest) (*common.BasicOperationResponse, error) { + path := "/product/domain/template" + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_PUT, path, nil, req, &resp) + + return &resp, err +} + +// 获取域名模板详情 +func (s *DomainService) GetDomainTemplateDetail(req *GetDomainTemplateDetailRequest) (*GetDomainTemplateDetailResponse, error) { + path := "/product/domain/template/detail/" + + var resp GetDomainTemplateDetailResponse + querys, err := common.MarshalQueryParams(req) + if err != nil { + return nil, err + } + err = s.client.Do(constant.HTTPMethod_GET, path, querys, nil, &resp) + return &resp, err +} diff --git a/apis/product/client.go b/apis/product/client.go new file mode 100644 index 0000000..51ad4ae --- /dev/null +++ b/apis/product/client.go @@ -0,0 +1,11 @@ +package product + +import "github.com/XingfenD/rainyun_api_go_sdk/apis" + +type ProductService struct { + client *apis.RyClient +} + +func NewProductService(c *apis.RyClient) *ProductService { + return &ProductService{client: c} +} diff --git a/apis/product/count.go b/apis/product/count.go new file mode 100644 index 0000000..781d3b9 --- /dev/null +++ b/apis/product/count.go @@ -0,0 +1,72 @@ +package product + +import ( + "github.com/XingfenD/rainyun_api_go_sdk/constant" +) + +// 产品数量 +type ProductCount struct { + TotalCount int `json:"TotalCount"` +} + +// RbmProductCount 域名转售(RBM)产品数量 +type RbmProductCount struct { + TotalCount int `json:"TotalCount"` + AboutToExpire any `json:"AboutToExpire"` +} + +// 产品数量列表 +type ProductCountData struct { + Ros ProductCount `json:"ros"` + Rvh ProductCount `json:"rvh"` + Rcs ProductCount `json:"rcs"` + Rgpu ProductCount `json:"rgpu"` + Rgs ProductCount `json:"rgs"` + Ssl ProductCount `json:"ssl"` + Rbm RbmProductCount `json:"rbm"` + Domain ProductCount `json:"domain"` + Rcdn ProductCount `json:"rcdn"` + Rca ProductCount `json:"rca"` +} + +type GetProductCountListResponse struct { + Code int `json:"code"` + Data ProductCountData `json:"data"` +} + +// 产品ID列表 +type ProductIDListData struct { + Rca []int `json:"rca"` + Rcs []int `json:"rcs"` + Ros []int `json:"ros"` + Rvh []int `json:"rvh"` + Rgpu []int `json:"rgpu"` + Rgs []int `json:"rgs"` + Ssl []int `json:"ssl"` + Rbm []int `json:"rbm"` + Domain []int `json:"domain"` + Rcdn []int `json:"rcdn"` +} + +type GetProductIDListResponse struct { + Code int `json:"code"` + Data ProductIDListData `json:"data"` +} + +// 获取各产品数量 +func (s *ProductService) GetProductCountList() (*GetProductCountListResponse, error) { + path := "/product/" + + var resp GetProductCountListResponse + err := s.client.Do(constant.HTTPMethod_GET, path, nil, nil, &resp) + return &resp, err +} + +// 获取产品ID列表 +func (s *ProductService) GetProductIDList() (*GetProductIDListResponse, error) { + path := "/product/id_list" + + var resp GetProductIDListResponse + err := s.client.Do(constant.HTTPMethod_GET, path, nil, nil, &resp) + return &resp, err +} diff --git a/apis/product/panel_user.go b/apis/product/panel_user.go new file mode 100644 index 0000000..405e44b --- /dev/null +++ b/apis/product/panel_user.go @@ -0,0 +1,112 @@ +package product + +import ( + "fmt" + + "github.com/XingfenD/rainyun_api_go_sdk/apis/common" + "github.com/XingfenD/rainyun_api_go_sdk/constant" +) + +// 增减面板用户产品请求 +type ModifyPanelUserProductRequests struct { + Action string `json:"action"` // 操作:add/del + ProductID int `json:"product_id"` // 产品ID + ProductType string `json:"product_type"` // 产品类型: rcs/rgs/ros/rbm 只支持这四种产品 + Name string `json:"name"` // 子用户名 +} + +// 面板用户请求 +type PanelUserRequest struct { + Name string `json:"name"` + Password string `json:"password"` +} + +// 面板用户产品 +type PanelUserProduct struct { + Name string `json:"name"` // 自用户名 + ProductType string `json:"product_type"` // 产品类型: rcs/rgs/ros/rbm 只支持这四种产品 + ProductID int `json:"product_id"` // 产品ID +} + +// 面板用户记录 +type PanelUserRecord struct { + Name string `json:"name"` // 面板用户名 + Password string `json:"password"` // 面板用户密码 + UserID int `json:"user_id"` // 你的UID + CreateDate int `json:"create_date"` // 创建时间 + Products []PanelUserProduct `json:"products"` // 产品列表 +} + +// 面板用户列表 +type PanelUserListData struct { + TotalRecords int `json:"TotalRecords"` // 面板用户总数 + Records []PanelUserRecord `json:"Records"` +} + +type GetPanelUserListRequest struct { + Options common.StandQueryParameters `json:"options"` +} + +type GetPanelUserListResponse struct { + Code int `json:"code"` + Data PanelUserListData `json:"data"` +} + +// 获取面板用户列表 +func (s *ProductService) GetPanelUserList(req *GetPanelUserListRequest) (*GetPanelUserListResponse, error) { + path := "/product/panel_users/" + + var resp GetPanelUserListResponse + querys, err := common.MarshalQueryParams(req) + if err != nil { + return nil, err + } + err = s.client.Do(constant.HTTPMethod_GET, path, querys, nil, &resp) + return &resp, err +} + +// 增减面板用户产品 +func (s *ProductService) ModifyPanelUserProduct(req *ModifyPanelUserProductRequests) (*common.BasicOperationResponse, error) { + path := "/product/panel_users/" + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_PUT, path, nil, req, &resp) + return &resp, err +} + +// 创建面板用户 +// +// name: 子用户用户名 +// +// pass: 子用户密码 +func (s *ProductService) CreatePanelUser(name string, pass string) (*common.BasicOperationResponse, error) { + path := "/product/panel_users/" + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_POST, path, nil, PanelUserRequest{Name: name, Password: pass}, &resp) + return &resp, err +} + +// 面板用户改密 +// +// name: 子用户用户名 +// +// pass: 子用户新密码 +func (s *ProductService) ChangePanelUserPassword(name string, pass string) (*common.BasicOperationResponse, error) { + path := "/product/panel_users/" + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_PATCH, path, nil, PanelUserRequest{Name: name, Password: pass}, &resp) + return &resp, err +} + +// 删除面板用户 +// +// name: 子用户用户名 +func (s *ProductService) DeletePanelUser(name string) (*common.BasicOperationResponse, error) { + path := fmt.Sprintf("/product/panel_users/%s", name) + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_DELETE, path, nil, nil, &resp) + return &resp, err +} diff --git a/apis/product/renew.go b/apis/product/renew.go new file mode 100644 index 0000000..d65c982 --- /dev/null +++ b/apis/product/renew.go @@ -0,0 +1,22 @@ +package product + +import ( + "github.com/XingfenD/rainyun_api_go_sdk/apis/common" + "github.com/XingfenD/rainyun_api_go_sdk/constant" +) + +// 积分续费请求 +type PointRenewalRequest struct { + DurationDay int `json:"duration_day"` // 续费天数 + ProductID int `json:"product_id"` // 产品ID + ProductType string `json:"product_type"` // 产品类型: rcs/rgs/ros/rvh/rcdn +} + +// 积分续费 +func (s *ProductService) PointRenewal(req *PointRenewalRequest) (*common.BasicOperationResponse, error) { + path := "/product/point_renew" + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_POST, path, nil, req, &resp) + return &resp, err +} diff --git a/apis/public/get_egg_type_list.go b/apis/public/get_egg_type_list.go new file mode 100644 index 0000000..fdef780 --- /dev/null +++ b/apis/public/get_egg_type_list.go @@ -0,0 +1,41 @@ +package public + +import "github.com/XingfenD/rainyun_api_go_sdk/constant" + +// https://api.rainyun.com/#/paths/product~1rgs~1egg_type/get + +// 获取蛋(游戏类型)类型列表 +type GetEggTypeListResponse struct { + Code int `json:"code"` + Data []EggTypeItem `json:"data"` +} + +type EggTypeItem struct { + ID int `json:"id"` // 蛋(游戏类型)ID + EggName string `json:"egg_name"` // 蛋(游戏类型)名称 + Egg EggItem `json:"egg"` + Docker string `json:"docker"` // docker镜像地址 + McsmDocker string `json:"mcsm_docker"` + Env EggTypeEnv `json:"env"` + Order int `json:"order"` + IsHidden bool `json:"is_hidden"` + UpdateTime int `json:"update_time"` + AutoUpdate bool `json:"auto_update"` +} + +type EggTypeEnv struct { + ServerType string `json:"SERVER_TYPE"` + ServerVersion string `json:"SERVER_VERSION"` + Subver string `json:"SUBVER"` + Latest bool `json:"LATEST"` + Prerelease bool `json:"PRERELEASE"` +} + +func (s *PublicService) GetEggTypeList() (*GetEggTypeListResponse, error) { + path := "/product/rgs/egg_type" + + var resp GetEggTypeListResponse + err := s.client.Do(constant.HTTPMethod_GET, path, nil, nil, &resp) + + return &resp, err +} diff --git a/apis/public/get_eggs.go b/apis/public/get_eggs.go new file mode 100644 index 0000000..b11a5b2 --- /dev/null +++ b/apis/public/get_eggs.go @@ -0,0 +1,31 @@ +package public + +import "github.com/XingfenD/rainyun_api_go_sdk/constant" + +// https://api.rainyun.com/#/paths/product~1rgs~1egg/get + +// 获取蛋(游戏)列表 +type GetEggListResponse struct { + Code int `json:"code"` + Data []EggItem `json:"data"` +} + +type EggItem struct { + Name string `json:"name"` // 名称 + EggGroup string `json:"egg_group"` // 分组 + Title string `json:"title"` // 标题 + Desc string `json:"desc"` // 描述 + IconURL string `json:"icon_url"` // 图标 + Order int `json:"order"` // 排序 + IsHidden bool `json:"is_hidden"` // 是否隐藏 + SaveDirs []string `json:"save_dirs"` // 重装时建议的需要保留的目录 +} + +func (s *PublicService) GetEggList() (*GetEggListResponse, error) { + path := "/product/rgs/egg" + + var resp GetEggListResponse + err := s.client.Do(constant.HTTPMethod_GET, path, nil, nil, &resp) + + return &resp, err +} diff --git a/apis/public/get_rcs_os_list.go b/apis/public/get_rcs_os_list.go new file mode 100644 index 0000000..8a1ebb9 --- /dev/null +++ b/apis/public/get_rcs_os_list.go @@ -0,0 +1,39 @@ +package public + +import "github.com/XingfenD/rainyun_api_go_sdk/constant" + +// https://api.rainyun.com/#/paths/product~1rcs~1os-templates/get + +// 获取RCS操作系统列表 +type GetRcsOSListResponse struct { + Code int `json:"code"` + Data []RcsOSListItem `json:"data"` +} + +type RcsOSListItem struct { + ID int `json:"id"` // 系统ID + Region string `json:"region"` // 地域 + Subtype string `json:"subtype"` // 类型(kvm) + Machine string `json:"machine"` // unknown + Name string `json:"name"` // 英文名 + Version string `json:"version"` // 版本 + SyncStatus string `json:"sync_status"` + OsType string `json:"os_type"` // 系统类型(windows/linux) + ChineseName string `json:"chinese_name"` // 中文名 + Icon string `json:"icon"` // 图标 + IsWithBbr bool `json:"is_with_bbr"` // 是否支持BBR + IsEol bool `json:"is_eol"` // 是否已过时 + IsAvailable bool `json:"is_available"` // 是否可用 + Order int `json:"order"` // 排序 + LatestFilename string `json:"latest_filename"` // 最新文件名 + NoVMAgent bool `json:"no_vm_agent"` // 是否无虚拟机Agent +} + +func (s *PublicService) GetRcsOSList() (*GetRcsOSListResponse, error) { + path := "/product/rcs/os-templates" + + var resp GetRcsOSListResponse + err := s.client.Do(constant.HTTPMethod_GET, path, nil, nil, &resp) + + return &resp, err +} diff --git a/apis/public/get_rgs_os_list.go b/apis/public/get_rgs_os_list.go new file mode 100644 index 0000000..723192b --- /dev/null +++ b/apis/public/get_rgs_os_list.go @@ -0,0 +1,39 @@ +package public + +import "github.com/XingfenD/rainyun_api_go_sdk/constant" + +// https://api.rainyun.com/#/paths/product~1rgs~1os-templates/get + +// 获取游戏云系统列表 +type GetRgsOSListResponse struct { + Code int `json:"code"` + Data []RgsOSListItem `json:"data"` +} + +type RgsOSListItem struct { + ID int `json:"id"` + Region string `json:"region"` + Subtype string `json:"subtype"` + Machine string `json:"machine"` + Name string `json:"name"` + Version string `json:"version"` + SyncStatus string `json:"sync_status"` + OsType string `json:"os_type"` + ChineseName string `json:"chinese_name"` + Icon string `json:"icon"` + IsWithBbr bool `json:"is_with_bbr"` + IsEol bool `json:"is_eol"` + IsAvailable bool `json:"is_available"` + Order int `json:"order"` + LatestFilename string `json:"latest_filename"` + NoVMAgent bool `json:"no_vm_agent"` +} + +func (s *PublicService) GetRgsOSList() (*GetRgsOSListResponse, error) { + path := "/product/rgs/os-templates" + + var resp GetRgsOSListResponse + err := s.client.Do(constant.HTTPMethod_GET, path, nil, nil, &resp) + + return &resp, err +} diff --git a/apis/public/get_ros_plans.go b/apis/public/get_ros_plans.go new file mode 100644 index 0000000..1b96510 --- /dev/null +++ b/apis/public/get_ros_plans.go @@ -0,0 +1,38 @@ +package public + +import "github.com/XingfenD/rainyun_api_go_sdk/constant" + +// https://api.rainyun.com/#/paths/product~1ros~1plans/get + +// 获取对象存储套餐列表 +type GetRosPlanListResponse struct { + Code int `json:"code"` + Data []RosPlanItem `json:"data"` +} + +type RosPlanItem struct { + ID int `json:"id"` + Region string `json:"region"` + Subtype string `json:"subtype"` + PlanName string `json:"plan_name"` + Machine string `json:"machine"` + ChargeType string `json:"charge_type"` + Chinese string `json:"chinese"` + IsFree bool `json:"is_free"` + PointRenewPrice any `json:"point_renew_price"` // 积分续费价格 + IsSelling bool `json:"is_selling"` + Price int `json:"price"` + StorageSize int `json:"storage_size"` + Bandwidth int `json:"bandwidth"` + ExtraTransferPrice float64 `json:"extra_transfer_price"` + ExtraStoragePrice float64 `json:"extra_storage_price"` +} + +func (s *PublicService) GetRosPlanList() (*GetRosPlanListResponse, error) { + path := "/product/ros/plans" + + var resp GetRosPlanListResponse + err := s.client.Do(constant.HTTPMethod_GET, path, nil, nil, &resp) + + return &resp, err +} diff --git a/apis/rbm/changeos.go b/apis/rbm/changeos.go new file mode 100644 index 0000000..28535a0 --- /dev/null +++ b/apis/rbm/changeos.go @@ -0,0 +1,22 @@ +package rbm + +import ( + "fmt" + + "github.com/XingfenD/rainyun_api_go_sdk/apis/common" + "github.com/XingfenD/rainyun_api_go_sdk/constant" +) + +// RBM实例更换系统请求 +type RBMChangeOSRequest struct { + OsID int `json:"os_id"` +} + +// RBM实例更换系统 +func (s *RbmService) ChangeRBMOS(id int, req *RBMChangeOSRequest) (*common.BasicOperationResponse, error) { + path := fmt.Sprintf("/product/rbm/%d/changeos", id) + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_POST, path, nil, req, &resp) + return &resp, err +} diff --git a/apis/rbm/client.go b/apis/rbm/client.go new file mode 100644 index 0000000..94cbe55 --- /dev/null +++ b/apis/rbm/client.go @@ -0,0 +1,11 @@ +package rbm + +import "github.com/XingfenD/rainyun_api_go_sdk/apis" + +type RbmService struct { + client *apis.RyClient +} + +func NewRbmService(c *apis.RyClient) *RbmService { + return &RbmService{client: c} +} diff --git a/apis/rbm/eip.go b/apis/rbm/eip.go new file mode 100644 index 0000000..020ee6a --- /dev/null +++ b/apis/rbm/eip.go @@ -0,0 +1,60 @@ +package rbm + +import ( + "fmt" + + "github.com/XingfenD/rainyun_api_go_sdk/apis/common" + "github.com/XingfenD/rainyun_api_go_sdk/constant" +) + +// 创建并绑定弹性IP到RBM请求 +type RBMAssociateEIPRequest struct { + WithFlags string `json:"with_flags"` // IP特征(可选) + WithIPNum int `json:"with_ip_num"` // 数量 + WithIPType string `json:"with_ip_type"` // IPv4/IPv6 +} + +// 更换IP请求 +type RBMChangeIPRequest struct { + DisableOldIPReason string `json:"disable_old_ip_reason"` // 禁用旧IP的原因(可选) + IP string `json:"ip"` // 旧IP地址 + ToIP string `json:"to_ip"` // 不明,可选 +} + +// 设置IP描述请求 +type RBMSetIPDescriptionRequest struct { + Description string `json:"description"` + IP string `json:"ip"` +} + +// 放弃IP请求 +type RBMReleaseIPRequest struct { + IP string `json:"ip"` +} + +// 创建并绑定弹性IP到RBM +func (s *RbmService) AssociateEIP(id int, req *RBMAssociateEIPRequest) (*common.BasicOperationResponse, error) { + path := fmt.Sprintf("/product/rbm/%d/eip", id) + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_POST, path, nil, req, &resp) + return &resp, err +} + +// 设置IP描述 +func (s *RbmService) SetIPDescription(id int, req *RBMSetIPDescriptionRequest) (*common.BasicOperationResponse, error) { + path := fmt.Sprintf("/product/rbm/%d/eip/description", id) + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_POST, path, nil, req, &resp) + return &resp, err +} + +// 放弃IP +func (s *RbmService) ReleaseIP(id int, req *RBMReleaseIPRequest) (*common.BasicOperationResponse, error) { + path := fmt.Sprintf("/product/rbm/%d/eip/discard", id) + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_POST, path, nil, req, &resp) + return &resp, err +} diff --git a/apis/rbm/lifecycle.go b/apis/rbm/lifecycle.go new file mode 100644 index 0000000..5242c16 --- /dev/null +++ b/apis/rbm/lifecycle.go @@ -0,0 +1,44 @@ +package rbm + +import ( + "fmt" + + "github.com/XingfenD/rainyun_api_go_sdk/apis/common" + "github.com/XingfenD/rainyun_api_go_sdk/constant" +) + +// RBM实例启动KVM代理 +func (s *RbmService) StartKVMAgent(id int) (*common.BasicOperationResponse, error) { + path := fmt.Sprintf("/product/rbm/%d/kvm-proxy", id) + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_POST, path, nil, nil, &resp) + return &resp, err +} + +// RBM重新启动KVM +func (s *RbmService) RestartKVM(id int) (*common.BasicOperationResponse, error) { + path := fmt.Sprintf("/product/rbm/%d/kvm-reboot", id) + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_POST, path, nil, nil, &resp) + return &resp, err +} + +// RBM实例关机 +func (s *RbmService) ShutdownRBM(id int) (*common.BasicOperationResponse, error) { + path := fmt.Sprintf("/product/rbm/%d/poweroff", id) + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_POST, path, nil, nil, &resp) + return &resp, err +} + +// RBM实例开机 +func (s *RbmService) StartRBM(id int) (*common.BasicOperationResponse, error) { + path := fmt.Sprintf("/product/rbm/%d/poweron", id) + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_POST, path, nil, nil, &resp) + return &resp, err +} diff --git a/apis/rbm/plan.go b/apis/rbm/plan.go new file mode 100644 index 0000000..040d857 --- /dev/null +++ b/apis/rbm/plan.go @@ -0,0 +1,42 @@ +package rbm + +import "github.com/XingfenD/rainyun_api_go_sdk/constant" + +type GetRbmPlanListResponse struct { + Code int `json:"code"` + Data []RbmPlan `json:"data"` +} + +// RbmPlan RBM套餐 +type RbmPlan struct { + ID int `json:"id"` // 套餐ID + Region string `json:"region"` // 地域 + Line string `json:"line"` // 线路 3c/single/bgp + Subtype string `json:"subtype"` // rbm + PlanName string `json:"plan_name"` // 套餐名称 + Machine string `json:"machine"` // 意义不明 generic/generic_2 + ChargeType string `json:"charge_type"` // 计费方式 package(流量不限)/package_traffic(流量叠加) + Chinese string `json:"chinese"` // 中文名 + IsFree bool `json:"is_free"` // 是否免费(为什么会有这种参数) + PointRenewPrice any `json:"point_renew_price"` // 积分续费价格,全是 null + IsSelling bool `json:"is_selling"` // 是否在售 + Price int `json:"price"` // 价格 + TrafficBaseGb int `json:"traffic_base_gb"` // 流量基数,单位GB + TrafficPrice any `json:"traffic_price"` // 流量价格,单位GB,package_traffic 计费方式才有值 + CPU int `json:"cpu"` // cpu核数 + Memory int `json:"memory"` // 内存,单位GB + Storage int `json:"storage"` // 存储,单位GB + NetIn int `json:"net_in"` // 入网带宽,单位Mbps + NetOut int `json:"net_out"` // 出网带宽,单位Mbps + AvailableStock int `json:"available_stock"` // 可用库存 + IPPrices any `json:"ip_prices"` // IP价格,单位元/月 +} + +// 获取RBM套餐列表 +func (s *RbmService) GetRBMPlanList() (*GetRbmPlanListResponse, error) { + path := "/product/rbm/models" + + var resp GetRbmPlanListResponse + err := s.client.Do(constant.HTTPMethod_GET, path, nil, nil, &resp) + return &resp, err +} diff --git a/apis/rbm/traffic.go b/apis/rbm/traffic.go new file mode 100644 index 0000000..607517c --- /dev/null +++ b/apis/rbm/traffic.go @@ -0,0 +1,22 @@ +package rbm + +import ( + "fmt" + + "github.com/XingfenD/rainyun_api_go_sdk/apis/common" + "github.com/XingfenD/rainyun_api_go_sdk/constant" +) + +// 充流量请求 +type RBMChargeTrafficRequest struct { + TrafficInGb int `json:"traffic_in_gb"` +} + +// 充流量 +func (s *RbmService) ChargeTraffic(id int, req *RBMChargeTrafficRequest) (*common.BasicOperationResponse, error) { + path := fmt.Sprintf("/product/rbm/%d/traffic/charge", id) + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_POST, path, nil, req, &resp) + return &resp, err +} diff --git a/apis/rca/appstore.go b/apis/rca/appstore.go new file mode 100644 index 0000000..c2233bf --- /dev/null +++ b/apis/rca/appstore.go @@ -0,0 +1,172 @@ +package rca + +import ( + "fmt" + + "github.com/XingfenD/rainyun_api_go_sdk/constant" +) + +// 云应用创建App模板请求 +type RcaCreateAppTemplateRequest struct { + CrossVersionUpdate bool `json:"cross_version_update"` // 是否支持跨版本更新 + Description string `json:"description"` // 描述 + DisableUpdate bool `json:"disable_update"` // 是否禁用版本更新功能,禁用后已安装应用无法检测到更新 + Logo string `json:"logo"` // logo(base64),最大50KB,可以不传 + Name string `json:"name"` // 应用名称(英文标识) + ProjectLink string `json:"project_link"` // 应用项目链接 + Readme string `json:"readme"` // 介绍(markdown) + Tags []string `json:"tags"` // 标签,多选,目前支持的tag可以参考现有应用 + Title string `json:"title"` // 应用标题 + Website string `json:"website"` // 应用官方网站链接 +} + +// App模板 +type AppTemplate struct { + ID int `json:"id"` // 模板id + UID int `json:"uid"` + Name string `json:"name"` + Title string `json:"title"` + Description string `json:"description"` + Logo string `json:"logo"` + Type string `json:"type"` + Tags []string `json:"tags"` + Website string `json:"website"` + Github string `json:"github"` + CreateDate int `json:"create_date"` + Readme string `json:"readme"` + Provider string `json:"provider"` + CrossVersionUpdate bool `json:"cross_version_update"` + Downloads int `json:"downloads"` + IsPublic bool `json:"is_public"` + ReviewStatus string `json:"review_status"` + ReviewComment string `json:"review_comment"` + StopReason string `json:"stop_reason"` + DisableUpdate bool `json:"disable_update"` +} + +type RcaCreateAppTemplateResponse struct { + Code int `json:"code"` + Data AppTemplate `json:"data"` +} + +// App模板版本 +type AppTemplateVersion struct { + ID int `json:"id"` + Version string `json:"version"` + IsPublic bool `json:"is_public"` +} + +type AppTemplateVersionData struct { + Data AppTemplate `json:"data"` + Versions []AppTemplateVersion `json:"versions"` +} + +type CreateAppTemplateVersionResponse struct { + Code int `json:"code"` + Data AppTemplateVersionData `json:"data"` +} + +// 配置文件 +type AppTemplateConfigMap struct { + ContainerPath string `json:"container_path"` // 配置文件存放在容器里面的目录 + Content string `json:"content"` // 文件内容,最大1MB + FileName string `json:"file_name"` // 文件名 +} + +// 环境变量 +type AppTemplateEnv struct { + Key string `json:"key"` // 键 + Value string `json:"value"` // 值,允许空白 +} + +// 安装选项 +type AppTemplateOption struct { + Default string `json:"default"` // 默认值 + Disabled bool `json:"disabled"` // 是否不允许编辑 + EnvKey string `json:"env_key"` // 绑定到环境变量的Key + Label string `json:"label"` // 标签,告知用户这个选项是干什么用的,例如root用户密码 + Random bool `json:"random"` // 是否进行随机化生成 + Required bool `json:"required"` // 是否必选 + Rule string `json:"rule"` // 规则,可用项目按照前端已实现支持的规则来 + Type string `json:"type"` // 类型,如:password number等 + Value string `json:"value"` // 填入值 + Values []AppTemplateOptionValue `json:"values"` // 选项,type为select时提供给客户选择 +} + +// 选项可选值 +type AppTemplateOptionValue struct { + Label string `json:"label"` // 提示显示 + Value string `json:"value"` // 对应值 +} + +// 资源需求 +type AppTemplateResourceRequest struct { + MinCPU int `json:"min_cpu"` // 如1000,单位是m + MinMemory int `json:"min_memory"` // 如4096,单位是Mi +} + +// 脚本钩子 +type AppTemplateScripts struct { + Install string `json:"install"` // install(安装应用的时候执行,可以用于下载特定的持久化文件,可以指定运行环境,在底层这个是用initContainer来实现 + InstallImage string `json:"install_image"` // 执行安装脚本所使用的容器镜像,其他阶段会使用运行时镜像 + PostStart string `json:"post_start"` // poststart(容器创建后立即执行) + PreStop string `json:"pre_stop"` // prestop(终止前运行) +} + +// 服务 +type AppTemplateService struct { + ExternalPort string `json:"external_port"` // 外部端口,之所以是string,因为支持以变量形式传入 + InternalPort string `json:"internal_port"` // 内部端口,对应AppPort,之所以是string,因为支持以变量形式传入 + Label string `json:"label"` // 标签,可以是中文 + Name string `json:"name"` // 服务名称 + Protocol string `json:"protocol"` // 协议 + Type string `json:"type"` // 类型,可以是internal或external,对应clusterIP或者lb +} + +// 文件目录挂载 +type AppTemplateVolumeMount struct { + MountContentType string `json:"mount_content_type"` // 挂载类型,文件file或目录dir + MountPath string `json:"mount_path"` // 容器内路径 + Name string `json:"name"` // 挂载描述 + PreContent string `json:"pre_content"` // file类型专用,以base64存储的预先准备二进制数据,这个是用于一些小型二进制文件例如.db或者如/etc/localtime等的预先准备,模拟在docker中如"./data/cloudreve.db:/cloudreve/cloudreve.db"或/etc/timezone:/etc/timezone:ro + SubPath string `json:"sub_path"` // Project卷内路径,不能以.或者/开头 +} + +// 创建App模板版本请求 +// +// 我尝试创建了一个函数用于将docker compose文件转换成这种格式,但在编写了近1000行代码后,我成功的放弃了这个想法 +// :( +type CreateAppTemplateVersionRequest struct { + Args []string `json:"args"` // 运行参数 + Command []string `json:"command"` // 运行命令 + ConfigMaps []AppTemplateConfigMap `json:"config_maps"` + Env []AppTemplateEnv `json:"env"` // 环境变量 + Image string `json:"image"` // 容器镜像,如nginx:1.21.0 + Options []AppTemplateOption `json:"options"` + ReleaseID int `json:"release_id"` // 更新用release ID + ResourceRequest AppTemplateResourceRequest `json:"resource_request"` // 资源需求 + Scripts AppTemplateScripts `json:"scripts"` // 脚本钩子 + Services []AppTemplateService `json:"services"` + Version string `json:"version"` // 版本号,如1.21.0 + VolumeMounts []AppTemplateVolumeMount `json:"volume_mounts"` // 文件目录,如data:/var/lib/mysql +} + +// 云应用创建App模板 +func (s *RcaService) CreateRcaAppTemplate(req *RcaCreateAppTemplateRequest) (*RcaCreateAppTemplateResponse, error) { + path := "/product/rca/appstore/" + + var resp RcaCreateAppTemplateResponse + err := s.client.Do(constant.HTTPMethod_POST, path, nil, req, &resp) + return &resp, err +} + +// 创建App模板版本 +// +// id: APP模板ID +func (s *RcaService) CreateRcaAppTemplateVersion(id int, req *CreateAppTemplateVersionRequest) (*CreateAppTemplateVersionResponse, error) { + path := fmt.Sprintf("/product/rca/appstore/%d/release", id) + + var resp CreateAppTemplateVersionResponse + err := s.client.Do(constant.HTTPMethod_POST, path, nil, req, &resp) + return &resp, err +} diff --git a/apis/rca/client.go b/apis/rca/client.go new file mode 100644 index 0000000..37eba69 --- /dev/null +++ b/apis/rca/client.go @@ -0,0 +1,11 @@ +package rca + +import "github.com/XingfenD/rainyun_api_go_sdk/apis" + +type RcaService struct { + client *apis.RyClient +} + +func NewRcaService(c *apis.RyClient) *RcaService { + return &RcaService{client: c} +} diff --git a/apis/rca/project.go b/apis/rca/project.go new file mode 100644 index 0000000..dda70c2 --- /dev/null +++ b/apis/rca/project.go @@ -0,0 +1,254 @@ +package rca + +import ( + "fmt" + + "github.com/XingfenD/rainyun_api_go_sdk/apis/common" + "github.com/XingfenD/rainyun_api_go_sdk/constant" +) + +// 云应用区域信息 +type PriceInfo struct { + Cpu float64 `json:"cpu"` + Memory float64 `json:"memory"` + Ipv4 float64 `json:"ipv4"` + Traffic float64 `json:"traffic"` + Disk float64 `json:"disk"` +} + +type RcaRegion struct { + ID int `json:"id"` + Name string `json:"name"` + ChineseName string `json:"chinese_name"` + WebsiteServiceDomain string `json:"website_service_domain"` + SftpServiceDomain string `json:"sftp_service_domain"` + PublicServiceDomain string `json:"public_service_domain"` + PriceInfo PriceInfo `json:"price_info"` +} + +type GetRcaRegionInfoResponse struct { + Code int `json:"code"` + Data []RcaRegion `json:"data"` +} + +// 开通云应用产品请求 +type ActivateRcaRequest struct { + RegionID int `json:"region_id"` +} + +// 创建云应用项目请求 +type CreateRcaProjectRequest struct { + ChargeType string `json:"charge_type"` // 计费类型: 动态计费: elastic + CPULimit int `json:"cpu_limit"` // (仅限package模式)CPU限制(毫核),1核心=1000,0.1核=100 + DiskSize int `json:"disk_size"` // 磁盘大小(GiB) + Ipv4Count int `json:"ipv4_count"` // 要添加的IPv4地址数量 + Ipv6Count int `json:"ipv6_count"` // 要添加的IPv6地址数量 + MemoryLimit int `json:"memory_limit"` // (仅限package模式)内存限制(MiB) + Name string `json:"name"` // 名称 + RegionID int `json:"region_id"` // 部署区域 +} + +// 云应用节点信息 +type RcaNode struct { + UUID string `json:"UUID"` + AuthKey string `json:"AuthKey"` + Region string `json:"Region"` + IPRegion string `json:"IpRegion"` + Machine string `json:"Machine"` + Product string `json:"Product"` + Subtype string `json:"Subtype"` + ChineseName string `json:"ChineseName"` + PhysicalNode string `json:"PhysicalNode"` + Config string `json:"Config"` + Stock any `json:"Stock"` + StatusData string `json:"StatusData"` + ShowMonitorData string `json:"ShowMonitorData"` + UpdateTime string `json:"UpdateTime"` + GitRepositoryName string `json:"GitRepositoryName"` + CertifyRequired bool `json:"CertifyRequired"` + IsDisableBackup bool `json:"IsDisableBackup"` + IsHidden bool `json:"IsHidden"` + NodeName string `json:"NodeName"` +} + +type RcaResourceLimits struct { + MaxCPU int `json:"max_cpu"` + MaxMemory int `json:"max_memory"` +} + +// 云应用项目资源使用情况 +type RcaProjectUsageData struct { + CPU int `json:"cpu"` + Memory int `json:"memory"` + NetOut int `json:"net_out"` + NetIn int `json:"net_in"` + DiskUsage int `json:"disk_usage"` + TrafficToday int `json:"traffic_today"` + Status string `json:"status"` + StatusReason string `json:"status_reason"` + AllocatedCPU int `json:"allocated_cpu"` + AllocatedMemory int `json:"allocated_memory"` + AppCount int `json:"app_count"` + WebsiteCount int `json:"website_count"` + DatabaseCount int `json:"database_count"` + Ipv4Count int `json:"ipv4_count"` + HealthyPods int `json:"healthy_pods"` + UnhealthyPods int `json:"unhealthy_pods"` +} + +// 备份目标 +type RcaBackupTarget struct { + Type string `json:"type"` // 目标类型,支持项目本地备份或者远程S3存储(local/s3) + S3Endpoint string `json:"s3_endpoint"` // S3的端点(仅支持Virtual Host,不支持Path-Style模式) + S3Bucket string `json:"s3_bucket"` // S3存储桶名 + S3AccessKey string `json:"s3_access_key"` // S3的AK + S3SecretKey string `json:"s3_secret_key"` // S3的SK + S3BackupDirectory string `json:"s3_backup_directory"` // s3备份存储的目录 +} + +// 云应用项目 +type RcaProject struct { + ID int `json:"ID"` + UID int `json:"UID"` + PlanID int `json:"PlanID"` + CreateDate int `json:"CreateDate"` // 创建时间 + NodeUUID string `json:"NodeUUID"` + Node RcaNode `json:"Node"` + Status string `json:"Status"` + StopReason string `json:"StopReason"` + RewardPointsToBeCollect int `json:"RewardPointsToBeCollect"` + Tag string `json:"Tag"` + ExpDate int `json:"ExpDate"` + ExpireNotice int `json:"ExpireNotice"` + AutoRenew bool `json:"AutoRenew"` + UnsubscribeAble bool `json:"UnsubscribeAble"` + Try bool `json:"Try"` + Name string `json:"name"` + RegionID int `json:"region_id"` + Region *RcaRegion `json:"region"` + Namespace string `json:"namespace"` + APIToken string `json:"APIToken"` + ResourceLimits RcaResourceLimits `json:"resource_limits"` + UsageData RcaProjectUsageData `json:"usage_data"` + VolumeSize int `json:"volume_size"` + ChargeType string `json:"charge_type"` // 计费类型: elastic + HourlyPrice float64 `json:"hourly_price"` // 小时价格 + NextChargeTime int `json:"next_charge_time"` + BackupTarget RcaBackupTarget `json:"backup_target"` // 备份目标 + SftpSetting any `json:"sftp_setting"` + IdleAlarmFlag bool `json:"idle_alarm_flag"` + PaymentDueEnd int `json:"payment_due_end"` +} + +type CreateRcaProjectResponse struct { + Code int `json:"code"` + Data RcaProject `json:"data"` +} + +type RcaProjectListData struct { + TotalRecords int `json:"TotalRecords"` + Records []RcaProject `json:"Records"` +} + +type GetRcaProjectListResponse struct { + Code int `json:"code"` + Data RcaProjectListData `json:"data"` +} + +// 云应用项目的指标信息,示例如下: +type RcaProjectMetricsData struct { + Columns []string `json:"Columns"` + Values [][]float64 `json:"Values"` +} + +type GetRcaProjectMetricsResponse struct { + Code int `json:"code"` + Data RcaProjectMetricsData `json:"data"` +} + +type RcaProjectDetailData struct { + Data RcaProject `json:"Data"` +} + +type GetRcaProjectDetailResponse struct { + Code int `json:"code"` + Data RcaProjectDetailData `json:"data"` +} + +// 云应用获取区域信息 +func (s *RcaService) GetRcaRegionInfo() (*GetRcaRegionInfoResponse, error) { + path := "/product/rca/region" + + var resp GetRcaRegionInfoResponse + err := s.client.Do(constant.HTTPMethod_GET, path, nil, nil, &resp) + return &resp, err +} + +// 开通云应用产品 +// +// regionID: 地域ID +func (s *RcaService) ActivateRca(regionID int) (*common.BasicOperationResponse, error) { + path := "/product/rca/activate" + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_POST, path, nil, ActivateRcaRequest{RegionID: regionID}, &resp) + return &resp, err +} + +// 创建云应用项目 +func (s *RcaService) CreateRcaProject(req *CreateRcaProjectRequest) (*CreateRcaProjectResponse, error) { + path := "/product/rca/project/" + + var resp CreateRcaProjectResponse + err := s.client.Do(constant.HTTPMethod_POST, path, nil, req, &resp) + return &resp, err +} + +// 云应用列出项目 +// +// options: 查询参数 可以用 EncodingStandardQueryParameters 获取. +func (s *RcaService) ListRcaProjects(options string) (*GetRcaProjectListResponse, error) { + path := "/product/rca/project/" + querys := map[string]string{"no_metrics": "false", "options": options} // no_metrics 含义不明 + + var resp GetRcaProjectListResponse + err := s.client.Do(constant.HTTPMethod_GET, path, querys, nil, &resp) + return &resp, err +} + +// 销毁云应用项目 +// +// id: RCA项目ID +func (s *RcaService) DestroyRcaProject(id int) (*common.BasicOperationResponse, error) { + path := fmt.Sprintf("/product/rca/project/%d/", id) + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_DELETE, path, nil, nil, &resp) + return &resp, err +} + +// 获取Rca项目的指标信息 +// +// id: RCA项目ID +// +// starttime: 开始时间(timestamp) +// +// endtime: 结束时间(timestamp) +func (s *RcaService) GetRcaProjectMetrics(id int, startTime int, endTime int) (*GetRcaProjectMetricsResponse, error) { + path := fmt.Sprintf("/product/rca/project/%d/metrics?start_time=%d&end_time=%d", id, startTime, endTime) + + var resp GetRcaProjectMetricsResponse + err := s.client.Do(constant.HTTPMethod_GET, path, nil, nil, &resp) + return &resp, err +} + +// 获取云应用项目详情 +// +// id: RCA项目ID +func (s *RcaService) GetRcaProjectDetail(id int) (*GetRcaProjectDetailResponse, error) { + path := fmt.Sprintf("/product/rca/project/%d/", id) + + var resp GetRcaProjectDetailResponse + err := s.client.Do(constant.HTTPMethod_GET, path, nil, nil, &resp) + return &resp, err +} diff --git a/apis/rca/raindrop.go b/apis/rca/raindrop.go new file mode 100644 index 0000000..e381cf5 --- /dev/null +++ b/apis/rca/raindrop.go @@ -0,0 +1,128 @@ +package rca + +import ( + "github.com/XingfenD/rainyun_api_go_sdk/apis/common" + "github.com/XingfenD/rainyun_api_go_sdk/constant" +) + +// 云应用雨点余额使用情况 +type RcaRaindropUsageData struct { + ExpectedRemainDays int `json:"expected_remain_days"` // 预计剩余天数 + LastMonthUsage float64 `json:"last_month_usage"` // 上月使用量 + ExpectedNextMonthUsage float64 `json:"expected_next_month_usage"` // 预计下月使用量 + FreeTrialRemainDays int `json:"free_trial_remain_days"` // 剩余免费试用天数 + IsBeforeFirstPayment bool `json:"is_before_first_payment"` +} + +type GetRcaRaindropUsageResponse struct { + Code int `json:"code"` + Data RcaRaindropUsageData `json:"data"` +} + +// 雨点套餐 +type RaindropPlan struct { + ID int `json:"id"` + Amount int `json:"amount"` + Price int `json:"price"` + IsSelling bool `json:"is_selling"` + PlanName string `json:"plan_name"` + Chinese string `json:"chinese"` +} + +type GetRcaRaindropPlansListResponse struct { + Code int `json:"code"` + Data []RaindropPlan `json:"data"` +} + +// 雨点消费明细 +type RaindropConsumeData struct { + BasicPrice float64 `json:"basic_price"` + TrafficBytes int `json:"traffic_bytes"` + TrafficPrice float64 `json:"traffic_price"` +} + +// 雨点消费记录 +type RaindropConsumeRecord struct { + ID int `json:"id"` + UID int `json:"uid"` + Time int `json:"time"` + Type string `json:"type"` + ProductID int `json:"product_id"` + Amount float64 `json:"amount"` + Data RaindropConsumeData `json:"data"` +} + +// 雨点消费历史 +type RaindropConsumeLogData struct { + TotalRecords int `json:"TotalRecords"` + Records []RaindropConsumeRecord `json:"Records"` +} + +type GetRaindropConsumeLogResponse struct { + Code int `json:"code"` + Data RaindropConsumeLogData `json:"data"` +} + +// 云应用购买雨点请求 +type BuyRaindropRequest struct { + PlanID int `json:"plan_id"` + WithCouponID int `json:"with_coupon_id"` +} + +// 雨点余额 +type GetRcaRaindropBalanceResponse struct { + Code int `json:"code"` + Data float64 `json:"data"` +} + +// 云应用获取雨点余额使用情况 +func (s *RcaService) GetRcaRaindropUsage() (*GetRcaRaindropUsageResponse, error) { + path := "/product/rca/raindrop/usage" + + var resp GetRcaRaindropUsageResponse + err := s.client.Do(constant.HTTPMethod_GET, path, nil, nil, &resp) + return &resp, err +} + +// 云应用获取雨点套餐列表 +func (s *RcaService) GetRcaRaindropPlansList() (*GetRcaRaindropPlansListResponse, error) { + path := "/product/rca/raindrop/plans" + + var resp GetRcaRaindropPlansListResponse + err := s.client.Do(constant.HTTPMethod_GET, path, nil, nil, &resp) + return &resp, err +} + +// 云应用获取雨点消费历史 +// +// options: 查询参数 可以用 EncodingStandardQueryParameters 获取. +func (s *RcaService) GetRaindropConsumeLog(options string) (*GetRaindropConsumeLogResponse, error) { + path := "/product/rca/raindrop/consume_log" + querys := map[string]string{"options": options} + + var resp GetRaindropConsumeLogResponse + err := s.client.Do(constant.HTTPMethod_GET, path, querys, nil, &resp) + return &resp, err +} + +// 云应用购买雨点 +// +// planID: 雨点套餐ID +// +// couponID: 优惠券ID +func (s *RcaService) BuyRaindrop(planID int, couponID int) (*common.BasicOperationResponse, error) { + path := "/product/rca/raindrop" + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_POST, path, nil, BuyRaindropRequest{PlanID: planID, WithCouponID: couponID}, &resp) + return &resp, err +} + +// 云应用获取雨点余额 +func (s *RcaService) GetRcaRaindropBalance() (*GetRcaRaindropBalanceResponse, error) { + path := "/product/rca/raindrop" + + var resp GetRcaRaindropBalanceResponse + err := s.client.Do(constant.HTTPMethod_GET, path, nil, nil, &resp) + return &resp, err +} diff --git a/apis/rca/resource.go b/apis/rca/resource.go new file mode 100644 index 0000000..c63bb62 --- /dev/null +++ b/apis/rca/resource.go @@ -0,0 +1,130 @@ +package rca + +import ( + "fmt" + + "github.com/XingfenD/rainyun_api_go_sdk/apis/common" + "github.com/XingfenD/rainyun_api_go_sdk/constant" +) + +// 云应用项目设置备份目标请求 +type SetRcaProjectBackupTargetRequest struct { + S3AccessKey string `json:"s3_access_key"` // S3的AK + S3BackupDirectory string `json:"s3_backup_directory"` // s3备份存储的目录 + S3Bucket string `json:"s3_bucket"` // S3存储桶名 + S3Endpoint string `json:"s3_endpoint"` // S3的端点(仅支持Virtual Host,不支持Path-Style模式) + S3SecretKey string `json:"s3_secret_key"` // S3的SK + TargetType string `json:"target_type"` // 目标类型,支持项目本地备份或者远程S3存储(local/s3) +} + +// 云应用项目磁盘扩容请求 +type RcaProjectDiskExpansionRequest struct { + NewDiskSize int `json:"new_disk_size"` // 以GB显示的新项目磁盘大小 +} + +// 云应用增加IP地址请求 +type RcaAddsIpAddressRequest struct { + Ipv4Count int `json:"ipv4_count"` // 要添加的IPv4地址数量 + Ipv6Count int `json:"ipv6_count"` // 要添加的IPv6地址数量 +} + +// 云应用移除IP地址请求 +type RcaRemoveIPRequest struct { + IPID int `json:"ip_id"` // 要删除的IP地址ID +} + +// 云应用项目修改SFTP设置请求 +type RcaProjectSetSftpConfigRequest struct { + Password string `json:"password"` + Username string `json:"username"` +} + +// 云应用IP信息 +type RcaIP struct { + ID int `json:"id"` // ip id + Region string `json:"region"` // 地域 + Type string `json:"type"` // ip类型(IPv4/IPv6) + IP string `json:"ip"` // ip地址 + AddressPool string `json:"address_pool"` // ip池(user-ip-pool) + Gateway string `json:"gateway"` // 网关 + Block string `json:"block"` // CIDR(24) + UID int `json:"uid"` // 用户id + ProjectID int `json:"project_id"` + Info string `json:"info"` + AllocatedDate int `json:"allocated_date"` // 分配时间 +} + +type GetRcaProjectIPsResponse struct { + Code int `json:"code"` + Data []RcaIP `json:"data"` +} + +// 云应用项目设置备份目标 +// +// id: RCA项目ID +func (s *RcaService) SetRcaProjectBackupTarget(id int, req *SetRcaProjectBackupTargetRequest) (*common.BasicOperationResponse, error) { + path := fmt.Sprintf("/product/rca/project/%d/backup_target", id) + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_PATCH, path, nil, req, &resp) + return &resp, err +} + +// 云应用项目磁盘扩容 +// +// id: RCA项目ID +func (s *RcaService) ExpandRcaProjectDisk(id int, req *RcaProjectDiskExpansionRequest) (*common.BasicOperationResponse, error) { + path := fmt.Sprintf("/product/rca/project/%d/disk_expand", id) + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_POST, path, nil, req, &resp) + return &resp, err +} + +// 云应用增加IP地址 +// +// id: RCA项目ID +func (s *RcaService) AddRcaProjectIP(id int, req *RcaAddsIpAddressRequest) (*common.BasicOperationResponse, error) { + path := fmt.Sprintf("/product/rca/project/%d/eip", id) + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_POST, path, nil, req, &resp) + return &resp, err +} + +// 云应用移除IP地址 +// +// id: RCA项目ID +func (s *RcaService) RemoveRcaProjectIP(id int, req *RcaRemoveIPRequest) (*common.BasicOperationResponse, error) { + path := fmt.Sprintf("/product/rca/project/%d/eip", id) + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_DELETE, path, nil, req, &resp) + return &resp, err +} + +// 云应用项目修改SFTP设置 +// +// id: RCA项目ID +// +// password: 密码 +// +// username: 用户名 +func (s *RcaService) SetRcaProjectSFTPConfig(id int, password string, username string) (*common.BasicOperationResponse, error) { + path := fmt.Sprintf("/product/rca/project/%d/sftp", id) + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_PATCH, path, nil, RcaProjectSetSftpConfigRequest{Password: password, Username: username}, &resp) + return &resp, err +} + +// 云应用项目列出IP地址 +// +// id: RCA项目ID +func (s *RcaService) ListRcaProjectIPs(id int) (*GetRcaProjectIPsResponse, error) { + path := fmt.Sprintf("/product/rca/project/%d/eip", id) + + var resp GetRcaProjectIPsResponse + err := s.client.Do(constant.HTTPMethod_GET, path, nil, nil, &resp) + return &resp, err +} diff --git a/apis/rgs/backup.go b/apis/rgs/backup.go new file mode 100644 index 0000000..7b0904e --- /dev/null +++ b/apis/rgs/backup.go @@ -0,0 +1,68 @@ +package rgs + +import ( + "fmt" + + "github.com/XingfenD/rainyun_api_go_sdk/apis/common" + "github.com/XingfenD/rainyun_api_go_sdk/apis/rcs" + "github.com/XingfenD/rainyun_api_go_sdk/constant" +) + +// 游戏云创建备份 +// +// id: 游戏云 ID +// +// label: 备份标签 +func (s *RgsService) CreateRgsBackup(id int, label string) (*common.BasicOperationResponse, error) { + path := fmt.Sprintf("/product/rgs/%d/backup", id) + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_POST, path, nil, rcs.CreateRcsBackupRequest{Label: label}, &resp) + return &resp, err +} + +// 游戏云删除备份 +// +// id: 游戏云 ID, bid: 备份ID +func (s *RgsService) DeleteRgsBackup(id, bid int) (*common.BasicOperationResponse, error) { + path := fmt.Sprintf("/product/rgs/%d/backup/%d", id, bid) + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_DELETE, path, nil, nil, &resp) + return &resp, err +} + +// 游戏云取消备份 +// +// id: RGS ID, bid: 备份ID +func (s *RgsService) CancelRgsBackup(id, bid int) (*common.BasicOperationResponse, error) { + path := fmt.Sprintf("/product/rgs/%d/backup/%d/cancel", id, bid) + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_POST, path, nil, nil, &resp) + return &resp, err +} + +// 游戏云还原备份 +// +// id: 游戏云 ID, bid: 备份ID +func (s *RgsService) RestoreRgsBackup(id, bid int) (*common.BasicOperationResponse, error) { + path := fmt.Sprintf("/product/rgs/%d/backup/%d/restore", id, bid) + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_POST, path, nil, nil, &resp) + return &resp, err +} + +// 游戏云设置备份选项 +// +// id: RGS ID +// +// 没错就是Rcs,这俩是一样的 +func (s *RgsService) EnableRgsAutoBackup(id int, req *rcs.RcsSetBackupOptionsRequest) (*common.BasicOperationResponse, error) { + path := fmt.Sprintf("/product/rgs/%d/backup/setting", id) + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_PATCH, path, nil, req, &resp) + return &resp, err +} diff --git a/apis/rgs/changeos.go b/apis/rgs/changeos.go new file mode 100644 index 0000000..d215764 --- /dev/null +++ b/apis/rgs/changeos.go @@ -0,0 +1,20 @@ +package rgs + +import ( + "fmt" + + "github.com/XingfenD/rainyun_api_go_sdk/apis/common" + "github.com/XingfenD/rainyun_api_go_sdk/apis/rcs" + "github.com/XingfenD/rainyun_api_go_sdk/constant" +) + +// 游戏云重装系统 +// +// id: RGS ID +func (s *RgsService) ReinstallRgs(id int, req *rcs.ReinstallRcsRequest) (*common.BasicOperationResponse, error) { + path := fmt.Sprintf("/product/rgs/%d/changeos", id) + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_POST, path, nil, req, &resp) + return &resp, err +} diff --git a/apis/rgs/client.go b/apis/rgs/client.go new file mode 100644 index 0000000..b5e0846 --- /dev/null +++ b/apis/rgs/client.go @@ -0,0 +1,11 @@ +package rgs + +import "github.com/XingfenD/rainyun_api_go_sdk/apis" + +type RgsService struct { + client *apis.RyClient +} + +func NewRgsService(c *apis.RyClient) *RgsService { + return &RgsService{client: c} +} diff --git a/apis/rgs/create_rgs.go b/apis/rgs/create_rgs.go new file mode 100644 index 0000000..a4317d2 --- /dev/null +++ b/apis/rgs/create_rgs.go @@ -0,0 +1,157 @@ +package rgs + +import "github.com/XingfenD/rainyun_api_go_sdk/constant" + +// 创建游戏云请求 +type CreateRgsRequest struct { + AppVars []RgsAppVar `json:"app_vars"` // 当空数组时,进行单次任务下发(可选) + Config RgsConfig `json:"config"` + CPULimitMode bool `json:"cpu_limit_mode"` + Duration int `json:"duration"` // 创建时长(月) + EggTypeID int `json:"egg_type_id"` // 游戏类型 + NodeUUID string `json:"node_uuid"` + OnlineMode bool `json:"online_mode"` + OsID int `json:"os_id"` + PanelUser string `json:"panel_user"` // 游戏云面板用户 + PayMode string `json:"pay_mode"` + PlanID int `json:"plan_id"` + Subtype string `json:"subtype"` // kvm/mcsm + Try bool `json:"try"` + WithCouponID int `json:"with_coupon_id"` + WithEipFlags string `json:"with_eip_flags"` // 是否开启高防,us_ddosip -> 美国高防,nb_ddosip -> 宁波高防 + WithEipNum int `json:"with_eip_num"` + WithEipType string `json:"with_eip_type"` + Zone string `json:"zone"` +} + +type RgsAppVar struct { + AppID int `json:"app_id"` + Retry bool `json:"retry"` // 重发之前的任务,此项存在时,无需传入参数 + Vars any `json:"vars"` +} + +// 游戏云配置信息 +type RgsConfig struct { + CPU int `json:"cpu"` + Memory int `json:"memory"` + NetOut int `json:"net_out"` + NetIn int `json:"net_in"` + BaseDisk int `json:"base_disk"` + DataDisk int `json:"data_disk"` + Allocation int `json:"allocation"` + Database int `json:"database"` + Backup int `json:"backup"` +} + +// 创建游戏云响应 +type CreateRgsResponse struct { + Code int `json:"code"` + Data RgsCreateData `json:"data"` +} + +// RgsCreateData 创建游戏云返回的完整实例数据 +type RgsCreateData struct { + RgsRecord + Node RgsNode `json:"Node"` // 节点信息 + Plan RgsPlan `json:"Plan"` + EggTypeID int `json:"EggTypeId"` + EggType any `json:"EggType"` + PteroUserName string `json:"PteroUserName"` + PteroUser any `json:"PteroUser"` + ServerID int `json:"ServerID"` + AllocationID int `json:"AllocationID"` + ServerUUID string `json:"ServerUUID"` + DaemonUUID string `json:"DaemonUUID"` + GameInfo any `json:"GameInfo"` +} + +type RgsNode struct { + UUID string `json:"UUID"` + AuthKey string `json:"AuthKey"` + Region string `json:"Region"` + IPRegion string `json:"IpRegion"` + Machine string `json:"Machine"` + Product string `json:"Product"` + Subtype string `json:"Subtype"` + ChineseName string `json:"ChineseName"` + PhysicalNode string `json:"PhysicalNode"` + Config string `json:"Config"` + Stock any `json:"Stock"` + StatusData string `json:"StatusData"` + ShowMonitorData string `json:"ShowMonitorData"` + UpdateTime string `json:"UpdateTime"` + GitRepositoryName string `json:"GitRepositoryName"` + CertifyRequired bool `json:"CertifyRequired"` + IsDisableBackup bool `json:"IsDisableBackup"` + IsHidden bool `json:"IsHidden"` + NodeName string `json:"NodeName"` +} + +type RgsPlan struct { + ID int `json:"id"` + Region string `json:"region"` + Subtype string `json:"subtype"` + PlanName string `json:"plan_name"` + Machine string `json:"machine"` + ChargeType string `json:"charge_type"` + Chinese string `json:"chinese"` + IsFree bool `json:"is_free"` + PointRenewPrice any `json:"point_renew_price"` + IsSelling bool `json:"is_selling"` + StockDiscount int `json:"stock_discount"` + EipStockDiscount int `json:"eip_stock_discount"` + IPPrices any `json:"ip_prices"` + IPSelling any `json:"ip_selling"` + CPUPrice int `json:"cpu_price"` + MemoryPrice int `json:"memory_price"` + NetInPrice int `json:"net_in_price"` + NetOutPrice int `json:"net_out_price"` + BaseDiskPrice int `json:"base_disk_price"` + DataDiskPrice int `json:"data_disk_price"` + Config []RgsPlanConfig `json:"config"` // 这里不知道为什么要返还一堆套餐列表 + AutoRestock int `json:"auto_restock"` + AvailableStock int `json:"available_stock"` + CPUPointDefault int `json:"cpu_point_default"` + CPUPointConsume int `json:"cpu_point_consume"` + CPUPointPrice float64 `json:"cpu_point_price"` + CPUBase float64 `json:"cpu_base"` + CPUMax int `json:"cpu_max"` + EipPrice int `json:"eip_price"` + DefencePrice int `json:"defence_price"` + AllocationPrice int `json:"allocation_price"` + DatabasePrice int `json:"database_price"` + BackupPrice int `json:"backup_price"` + DailyModeSupport bool `json:"daily_mode_support"` + DailyPriceScale int `json:"daily_price_scale"` +} + +type RgsPlanConfig struct { + CPU int `json:"cpu"` + Memory int `json:"memory"` + NetIn int `json:"net_in,omitempty"` + CPUMax int `json:"cpu_max,omitempty"` + CPUMin int `json:"cpu_min,omitempty"` + NetOut int `json:"net_out"` + BaseDisk int `json:"base_disk,omitempty"` + DataDisk int `json:"data_disk,omitempty"` + BasePrice int `json:"base_price"` + MemoryMax int `json:"memory_max"` + MemoryMin int `json:"memory_min"` + NetInMax int `json:"net_in_max,omitempty"` + NetInMin int `json:"net_in_min,omitempty"` + NetOutMax int `json:"net_out_max,omitempty"` + NetOutMin int `json:"net_out_min"` + BaseDiskMax int `json:"base_disk_max,omitempty"` + BaseDiskMin int `json:"base_disk_min,omitempty"` + DataDiskMax int `json:"data_disk_max,omitempty"` + DataDiskMin int `json:"data_disk_min,omitempty"` +} + +// 创建游戏云 +func (s *RgsService) CreateRgs(req *CreateRgsRequest) (*CreateRgsResponse, error) { + path := "/product/rgs" + + var resp CreateRgsResponse + err := s.client.Do(constant.HTTPMethod_POST, path, nil, req, &resp) + return &resp, err +} diff --git a/apis/rgs/eip.go b/apis/rgs/eip.go new file mode 100644 index 0000000..df29762 --- /dev/null +++ b/apis/rgs/eip.go @@ -0,0 +1,42 @@ +package rgs + +import ( + "fmt" + + "github.com/XingfenD/rainyun_api_go_sdk/apis/common" + "github.com/XingfenD/rainyun_api_go_sdk/apis/rcs" + "github.com/XingfenD/rainyun_api_go_sdk/constant" +) + +// 创建并绑定弹性IP到游戏云 +// +// id: 游戏云 ID +func (s *RgsService) CreateAndBindElasticIpToRgs(id int, req *rcs.CreateAndBindIpToRcsRequest) (*common.BasicOperationResponse, error) { + path := fmt.Sprintf("/product/rgs/%d/eip", id) + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_POST, path, nil, req, &resp) + return &resp, err +} + +// 更换游戏云IP +// +// id: 游戏云 ID +func (s *RgsService) ChangeRgsIP(id int, req *rcs.ChangeRcsIPRequest) (*common.BasicOperationResponse, error) { + path := fmt.Sprintf("/product/rgs/%d/eip/change", id) + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_POST, path, nil, req, &resp) + return &resp, err +} + +// 放弃游戏云IP +// +// id: 游戏云ID +func (s *RgsService) DisCardRgsIP(id int, req rcs.DisCardRcsIPRequest) (*common.BasicOperationResponse, error) { + path := fmt.Sprintf("/product/rgs/%d/eip/discard", id) + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_POST, path, nil, req, &resp) + return &resp, err +} diff --git a/apis/rgs/firewall.go b/apis/rgs/firewall.go new file mode 100644 index 0000000..d7cfd83 --- /dev/null +++ b/apis/rgs/firewall.go @@ -0,0 +1,66 @@ +package rgs + +import ( + "fmt" + + "github.com/XingfenD/rainyun_api_go_sdk/apis/common" + "github.com/XingfenD/rainyun_api_go_sdk/apis/rcs" + "github.com/XingfenD/rainyun_api_go_sdk/constant" +) + +type GetRgsFirewallRulesRequest struct { + Options common.StandQueryParameters `json:"options"` +} + +type GetRgsFirewallRulesResponse struct { + Code int `json:"code"` + Data rcs.RcsFirewallRuleList `json:"data"` +} + +// 获取游戏云防火墙规则列表 +// +// id: 游戏云 ID +func (s *RgsService) GetRgsFirewallRules(id int, req *GetRgsFirewallRulesRequest) (*GetRgsFirewallRulesResponse, error) { + path := fmt.Sprintf("/product/rgs/%d/firewall/rule", id) + + var resp GetRgsFirewallRulesResponse + querys, err := common.MarshalQueryParams(req) + if err != nil { + return nil, err + } + err = s.client.Do(constant.HTTPMethod_GET, path, querys, nil, &resp) + return &resp, err +} + +// 创建/设置游戏云防火墙规则 +// +// id: 游戏云 ID +func (s *RgsService) SetRgsFirewallRule(id int, req *rcs.SetRcsFirewallRuleRequest) (*common.BasicOperationResponse, error) { + path := fmt.Sprintf("/product/rgs/%d/firewall/rule", id) + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_POST, path, nil, req, &resp) + return &resp, err +} + +// 删除游戏云防火墙规则 +// +// id: 游戏云 ID +func (s *RgsService) DeleteRgsFirewallRule(id, ruleID int) (*common.BasicOperationResponse, error) { + path := fmt.Sprintf("/product/rgs/%d/firewall/rule/%d", id, ruleID) + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_DELETE, path, nil, nil, &resp) + return &resp, err +} + +// 移动游戏云防火墙规则优先级 +// +// id: 游戏云 ID +func (s *RgsService) MobileRgsFirewallRulePriority(id, ruleID int, req rcs.MobileRcsFirewallRulePriorityRequest) (*common.BasicOperationResponse, error) { + path := fmt.Sprintf("/product/rgs/%d/firewall/rule/%d/pos", id, ruleID) + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_PUT, path, nil, req, &resp) + return &resp, err +} diff --git a/apis/rgs/get_rgs_detail.go b/apis/rgs/get_rgs_detail.go new file mode 100644 index 0000000..65366dc --- /dev/null +++ b/apis/rgs/get_rgs_detail.go @@ -0,0 +1,46 @@ +package rgs + +import ( + "fmt" + + "github.com/XingfenD/rainyun_api_go_sdk/constant" +) + +// 游戏云详情 +// 由于响应实在过于庞大,我们只维护部分必要的响应,如有扩展需求,请在项目的data文件夹下寻找响应实例自行解码 +type GetRgsDetailResponse struct { + Code int `json:"code"` + Data RgsDetailData `json:"data"` +} + +type RgsDetailData struct { + Data RgsRecord `json:"Data"` + NatList []RgsNatItem `json:"NatList"` // 端口映射列表 + EIPList any `json:"EIPList"` + ConfigPrice int `json:"ConfigPrice"` + RenewPointPrice RgsRenewPointPrice `json:"RenewPointPrice"` // 积分续费 +} + +type RgsNatItem struct { + ID int `json:"ID"` + PortIn int `json:"PortIn"` + PortOut int `json:"PortOut"` + PortType string `json:"PortType"` + Tag string `json:"Tag"` +} + +type RgsRenewPointPrice struct { + Num7 int `json:"7"` // 积分续费七天 + Num31 int `json:"31"` // 积分续费31天 +} + +// 获取游戏云详情 +// +// id: 游戏云 ID +func (s *RgsService) GetRgsDetail(id int) (*GetRgsDetailResponse, error) { + path := fmt.Sprintf("/product/rgs/%d", id) + + var resp GetRgsDetailResponse + err := s.client.Do(constant.HTTPMethod_GET, path, nil, nil, &resp) + return &resp, err +} diff --git a/apis/rgs/get_rgs_list.go b/apis/rgs/get_rgs_list.go new file mode 100644 index 0000000..2eaaded --- /dev/null +++ b/apis/rgs/get_rgs_list.go @@ -0,0 +1,126 @@ +package rgs + +import ( + "github.com/XingfenD/rainyun_api_go_sdk/apis/common" + "github.com/XingfenD/rainyun_api_go_sdk/constant" +) + +type GetRgsListRequest struct { + Options common.StandQueryParameters `json:"options"` +} + +type GetRgsListResponse struct { + Code int `json:"code"` + Data RgsListData `json:"data"` +} + +type RgsListData struct { + TotalRecords int `json:"TotalRecords"` + Records []RgsRecord `json:"Records"` +} + +// 游戏云记录信息 +// 由于响应实在过于庞大,我们只维护部分必要的响应,如有扩展需求,请在项目的data文件夹下寻找响应实例自行解码 +type RgsRecord struct { + ExpDate int `json:"ExpDate"` + ExpireNotice int `json:"ExpireNotice"` + AutoRenew bool `json:"AutoRenew"` + UnsubscribeAble bool `json:"UnsubscribeAble"` + Try bool `json:"Try"` + ID int `json:"ID"` + UID int `json:"UID"` + PlanID int `json:"PlanID"` + CreateDate int `json:"CreateDate"` + Status string `json:"Status"` + StopReason string `json:"StopReason"` + RewardPointsToBeCollect int `json:"RewardPointsToBeCollect"` // 待领取的积分 + Tag string `json:"Tag"` + OsID int `json:"OsID"` + OsName string `json:"OsName"` + HostName string `json:"HostName"` + DefaultPass string `json:"DefaultPass"` + MainIPv4 string `json:"MainIPv4"` + IntIPv4 string `json:"IntIPv4"` + UsageData RgsUsageData `json:"UsageData"` + Zone string `json:"Zone"` + NatPublicIP string `json:"NatPublicIP"` + NatPublicDomain string `json:"NatPublicDomain"` + NATSpareDomain string `json:"NATSpareDomain"` + NetIn int `json:"NetIn"` + NetOut int `json:"NetOut"` + NowNetIn int `json:"NowNetIn"` + NowNetOut int `json:"NowNetOut"` + NetMode string `json:"NetMode"` + BridgeSyncing bool `json:"BridgeSyncing"` + VnetID int `json:"VnetID"` + UpdateTime int `json:"UpdateTime"` + FwSyncTime int `json:"FwSyncTime"` + FwMode string `json:"FwMode"` + AbCPULimit int `json:"AbCpuLimit"` + AbNetLimit int `json:"AbNetLimit"` + AbWhiteReason string `json:"AbWhiteReason"` + OsInfo RgsOsInfo `json:"OsInfo"` + CPU int `json:"CPU"` + Memory int `json:"Memory"` + BaseDisk int `json:"BaseDisk"` + DataDisk int `json:"DataDisk"` + InitedDate int `json:"InitedDate"` + Allocation int `json:"Allocation"` + Database int `json:"Database"` + Backup int `json:"Backup"` + CPULimitMode bool `json:"CpuLimitMode"` + CPULimitStatus bool `json:"CpuLimitStatus"` + CPUPoint int `json:"CpuPoint"` + DailyMode bool `json:"DailyMode"` // 是否日付模式 + RBSKeepLast int `json:"RBSKeepLast"` + RBSAutoBackup string `json:"RBSAutoBackup"` + RBSLastAutoBackupDate int `json:"RBSLastAutoBackupDate"` + McsmUserName string `json:"McsmUserName"` + McsmUser McsmUser `json:"McsmUser"` +} + +type RgsUsageData struct { + CPU int `json:"CPU"` + Mem int `json:"Mem"` + MemUsage int `json:"MemUsage"` + DiskRead int `json:"DiskRead"` + DiskWrite int `json:"DiskWrite"` + Disk int `json:"Disk"` + NetOut int `json:"NetOut"` + NetIn int `json:"NetIn"` + UpdateTime int `json:"UpdateTime"` +} + +type RgsOsInfo struct { + ID int `json:"id"` + Region string `json:"region"` + Subtype string `json:"subtype"` + Machine string `json:"machine"` + Name string `json:"name"` + Version string `json:"version"` + SyncStatus string `json:"sync_status"` + OsType string `json:"os_type"` + ChineseName string `json:"chinese_name"` + Icon string `json:"icon"` + IsWithBbr bool `json:"is_with_bbr"` + IsEol bool `json:"is_eol"` + IsAvailable bool `json:"is_available"` + Order int `json:"order"` + LatestFilename string `json:"latest_filename"` + NoVMAgent bool `json:"no_vm_agent"` +} + +// 获取游戏云列表 +// +// options: 查询参数 可以用 common.StandQueryParameters 获取 +func (s *RgsService) GetRgsList(req *GetRgsListRequest) (*GetRgsListResponse, error) { + path := "/product/rgs" + + var resp GetRgsListResponse + querys, err := common.MarshalQueryParams(req) + if err != nil { + return nil, err + } + err = s.client.Do(constant.HTTPMethod_GET, path, querys, nil, &resp) + return &resp, err +} diff --git a/apis/rgs/lifecycle.go b/apis/rgs/lifecycle.go new file mode 100644 index 0000000..128da83 --- /dev/null +++ b/apis/rgs/lifecycle.go @@ -0,0 +1,164 @@ +package rgs + +import ( + "fmt" + + "github.com/XingfenD/rainyun_api_go_sdk/apis/common" + "github.com/XingfenD/rainyun_api_go_sdk/apis/rcs" + "github.com/XingfenD/rainyun_api_go_sdk/constant" +) + +// 通用模式切换请求 +type RgsSwitchModeRequest struct { + Mode bool `json:"mode"` +} + +// 游戏云CPU充电请求 +type ChargeRgsCPURequest struct { + Mode string `json:"mode"` // 支付方式(money/point) + Money int `json:"money"` // 消耗用户余额(支付方式为point时0) + Points int `json:"points"` // 消耗用户积分(支付方式为money时0) +} + +// 游戏云更换egg(游戏类型)请求 +type ChangeRgsEggRequest struct { + EggTypeID int `json:"egg_type_id"` // 蛋ID + SaveDirs []string `json:"save_dirs"` // 要保留的目录 +} + +// 释放游戏云 +// +// id: 游戏云ID +func (s *RgsService) FreeRgs(id int) (*common.BasicOperationResponse, error) { + path := fmt.Sprintf("/product/rgs/%d/free", id) + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_POST, path, nil, nil, &resp) + return &resp, err +} + +// 游戏云重启操作 +// +// id: RGS ID +func (s *RgsService) RebootRgs(id int) (*common.BasicOperationResponse, error) { + path := fmt.Sprintf("/product/rgs/%d/reboot", id) + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_POST, path, nil, nil, &resp) + return &resp, err +} + +// 游戏云重置密码 +// +// id: 游戏云ID +// +// newPass: 新密码,留空则自动生成 +func (s *RgsService) ResetRgsPassword(id int, newPass string) (*common.BasicOperationResponse, error) { + path := fmt.Sprintf("/product/rgs/%d/reset-password", id) + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_POST, path, nil, rcs.ResetRcsPasswordRequest{Password: newPass}, &resp) + return &resp, err +} + +// 游戏云开机 +// +// id: 游戏云ID +func (s *RgsService) StartRgs(id int) (*common.BasicOperationResponse, error) { + path := fmt.Sprintf("/product/rgs/%d/start", id) + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_POST, path, nil, nil, &resp) + return &resp, err +} + +// 游戏云关机 +// +// id: 游戏云ID +func (s *RgsService) StopRgs(id int) (*common.BasicOperationResponse, error) { + path := fmt.Sprintf("/product/rgs/%d/stop", id) + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_POST, path, nil, nil, &resp) + return &resp, err +} + +// 设置游戏云标签 +// +// id: 游戏云ID +// +// tag: 标签 +func (s *RgsService) SetRgsTag(id int, tag string) (*common.BasicOperationResponse, error) { + path := fmt.Sprintf("/product/rgs/%d/tag", id) + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_POST, path, nil, rcs.SetRcsTagRequest{TagName: tag}, &resp) + return &resp, err +} + +// 游戏云限制模式(是否用余额结算)切换 +// +// id: 游戏云 ID +// +// useMoney: 是否用余额结算CPU电量 +func (s *RgsService) SwitchRgsBalanceMode(id int, useMoney bool) (*common.BasicOperationResponse, error) { + path := fmt.Sprintf("/product/rgs/%d/cpu-limit-mode", id) + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_POST, path, nil, RgsSwitchModeRequest{Mode: useMoney}, &resp) + return &resp, err +} + +// 游戏云日付模式开关 +// +// id: 游戏云 ID +// +// dailyMode: true: 开启日付模式,false: 关闭日付模式 +func (s *RgsService) SwitchRgsDailyMode(id int, dailyMode bool) (*common.BasicOperationResponse, error) { + path := fmt.Sprintf("/product/rgs/%d/daily-mode", id) + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_POST, path, nil, RgsSwitchModeRequest{Mode: dailyMode}, &resp) + return &resp, err +} + +// 游戏云CPU充电 +// +// id: 游戏云 ID +func (s *RgsService) ChargeRgsCPU(id int, req *ChargeRgsCPURequest) (*common.BasicOperationResponse, error) { + path := fmt.Sprintf("/product/rgs/%d/cpu-charge", id) + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_POST, path, nil, req, &resp) + return &resp, err +} + +// 游戏云升级 +// +// id: 游戏云ID +// +// plan: 升级到的套餐ID +// +// coupon: 优惠券ID,默认为0 +func (s *RgsService) UpgradeRgs(id, plan, coupon int) (*common.BasicOperationResponse, error) { + path := fmt.Sprintf("/product/rgs/%d/upgrade", id) + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_POST, path, nil, rcs.UpgradeRcsRequest{DestPlan: plan, WithCouponID: coupon}, &resp) + return &resp, err +} + +// 游戏云更换egg(游戏类型) +// +// 此操作需要二步验证 +// +// eggTypeID: 游戏类型ID +// +// saveDirs: 要保留的目录 +func (s *RgsService) ChangeRgsEgg(id int, eggTypeID int, saveDirs []string) (*common.BasicOperationResponse, error) { + path := fmt.Sprintf("/product/rgs/%d/change-egg", id) + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_POST, path, nil, ChangeRgsEggRequest{EggTypeID: eggTypeID, SaveDirs: saveDirs}, &resp) + return &resp, err +} diff --git a/apis/rgs/mcsm.go b/apis/rgs/mcsm.go new file mode 100644 index 0000000..b4d02d0 --- /dev/null +++ b/apis/rgs/mcsm.go @@ -0,0 +1,53 @@ +package rgs + +import ( + "fmt" + + "github.com/XingfenD/rainyun_api_go_sdk/apis/common" + "github.com/XingfenD/rainyun_api_go_sdk/constant" +) + +// MCSM面板用户 +type McsmUser struct { + Name string `json:"name"` + Password string `json:"password"` + UserID int `json:"user_id,omitempty"` + PanelUUID string `json:"panel_uuid,omitempty"` +} + +// 创建MCSM面板用户 +// +// name: 用户名 +// +// password: 密码 +func (s *RgsService) CreateMcsmUser(name, password string) (*common.BasicOperationResponse, error) { + path := "/product/rgs/mcsm/panel_user" + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_POST, path, nil, McsmUser{Name: name, Password: password}, &resp) + return &resp, err +} + +// 编辑MCSM面板用户 +// +// name: 用户名 +// +// password: 密码 +func (s *RgsService) EditMcsmUser(name, password string) (*common.BasicOperationResponse, error) { + path := "/product/rgs/mcsm/panel_user" + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_PATCH, path, nil, McsmUser{Name: name, Password: password}, &resp) + return &resp, err +} + +// 删除MCSM面板用户 +// +// name: 用户名 +func (s *RgsService) DeleteMcsmUser(name string) (*common.BasicOperationResponse, error) { + path := fmt.Sprintf("/product/rgs/mcsm/panel_user/%s", name) + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_DELETE, path, nil, nil, &resp) + return &resp, err +} diff --git a/apis/rgs/monitor.go b/apis/rgs/monitor.go new file mode 100644 index 0000000..d8c1dc1 --- /dev/null +++ b/apis/rgs/monitor.go @@ -0,0 +1,38 @@ +package rgs + +import ( + "fmt" + + "github.com/XingfenD/rainyun_api_go_sdk/apis/common" + "github.com/XingfenD/rainyun_api_go_sdk/apis/rcs" + "github.com/XingfenD/rainyun_api_go_sdk/constant" +) + +type GetRgsMonitorDataRequest struct { + StartDate int `json:"start_date"` // 开始时间(时间戳) + EndDate int `json:"end_date"` // 结束时间(时间戳) +} + +type GetRgsMonitorDataResponse struct { + Code int `json:"code"` + Data rcs.RcsMonitorData `json:"data"` +} + +// 获取游戏云监控数据 +// +// id: 游戏云ID +// +// startDate: 开始时间 +// +// endDate: 结束时间 +func (s *RgsService) GetRgsMonitorData(id int, req *GetRgsMonitorDataRequest) (*GetRgsMonitorDataResponse, error) { + path := fmt.Sprintf("/product/rgs/%d/monitor", id) + + var resp GetRgsMonitorDataResponse + querys, err := common.MarshalQueryParams(req) + if err != nil { + return nil, err + } + err = s.client.Do(constant.HTTPMethod_GET, path, querys, nil, &resp) + return &resp, err +} diff --git a/apis/rgs/nat.go b/apis/rgs/nat.go new file mode 100644 index 0000000..527142a --- /dev/null +++ b/apis/rgs/nat.go @@ -0,0 +1,20 @@ +package rgs + +import ( + "fmt" + + "github.com/XingfenD/rainyun_api_go_sdk/apis/common" + "github.com/XingfenD/rainyun_api_go_sdk/apis/rcs" + "github.com/XingfenD/rainyun_api_go_sdk/constant" +) + +// 添加游戏云NAT端口映射 +// +// id: 游戏云 ID +func (s *RgsService) AddRgsNatPortMapping(id int, req *rcs.AddRcsNatPortMappingRequest) (*common.BasicOperationResponse, error) { + path := fmt.Sprintf("/product/rgs/%d/nat", id) + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_POST, path, nil, req, &resp) + return &resp, err +} diff --git a/apis/rgs/renew.go b/apis/rgs/renew.go new file mode 100644 index 0000000..0e2e47d --- /dev/null +++ b/apis/rgs/renew.go @@ -0,0 +1,111 @@ +package rgs + +import ( + "fmt" + + "github.com/XingfenD/rainyun_api_go_sdk/apis/common" + "github.com/XingfenD/rainyun_api_go_sdk/apis/rcs" + "github.com/XingfenD/rainyun_api_go_sdk/constant" +) + +type GetRgsRenewPriceRequest struct { + ProductID int `json:"product_id"` + Duration int `json:"duration"` + WithCouponID int `json:"with_coupon_id"` +} + +type GetRgsRenewPriceResponse struct { + Code int `json:"code"` + Data rcs.RcsRenewPrice `json:"data"` +} + +type GetRgsUpgradePriceRequest struct { + ProductID int `json:"product_id"` + Duration int `json:"duration"` + WithCouponID int `json:"with_coupon_id"` + Config RgsConfig `json:"config"` +} + +// 游戏云升级价格 +type GetRgsUpgradePriceResponse struct { + Code int `json:"code"` + Data RgsUpgradePriceData `json:"data"` +} + +type RgsUpgradePriceData struct { + Detail RgsUpgradePriceDetail `json:"detail"` + Price int `json:"price"` +} + +type RgsUpgradePriceDetail struct { + Price int `json:"price"` + AgentPrice int `json:"agent_price"` + StockPrice int `json:"stock_price"` + DefaultPrice int `json:"default_price"` + CouponValue int `json:"coupon_value"` + SaleReward int `json:"sale_reward"` + AgentReward int `json:"agent_reward"` + AgentID int `json:"agent_id"` + IgnoreAgent bool `json:"ignore_agent"` + PerScene RgsUpgradePerScene `json:"per_scene"` +} + +type RgsUpgradePerScene struct { + Upgrade int `json:"upgrade"` +} + +// 获取游戏云续费价格 +// +// id: RGS ID, duration: 续费时长(月), coupon: 优惠券ID +func (s *RgsService) GetRgsRenewPrice(req *GetRgsRenewPriceRequest) (*GetRgsRenewPriceResponse, error) { + path := "/product/rgs/price" + + var resp GetRgsRenewPriceResponse + querys, err := common.MarshalQueryParams(req) + if err != nil { + return nil, err + } + querys["scene"] = "renew" + querys["is_old"] = "true" + err = s.client.Do(constant.HTTPMethod_GET, path, querys, nil, &resp) + return &resp, err +} + +// 游戏云续费 +// +// id: 游戏云ID +func (s *RgsService) RenewRgs(id int, req rcs.RenewRcsRequest) (*common.BasicOperationResponse, error) { + path := fmt.Sprintf("/product/rgs/%d/renew", id) + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_POST, path, nil, req, &resp) + return &resp, err +} + +// 游戏云自动续费选项 +// +// id: 游戏云ID +func (s *RgsService) EnableRgsAutoRenew(id int, req rcs.EnableRcsAutoRenewRequest) (*common.BasicOperationResponse, error) { + path := fmt.Sprintf("/product/rgs/%d/renew/option", id) + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_POST, path, nil, req, &resp) + return &resp, err +} + +// 获取游戏云升级价格 +// +// id: RGS ID, duration: 续费时长(月), coupon: 优惠券ID +func (s *RgsService) GetRgsUpgradePrice(req *GetRgsUpgradePriceRequest) (*GetRgsUpgradePriceResponse, error) { + path := "/product/rgs/price" + + var resp GetRgsUpgradePriceResponse + querys, err := common.MarshalQueryParams(req) + if err != nil { + return nil, err + } + querys["scene"] = "upgrade" + querys["is_old"] = "true" + err = s.client.Do(constant.HTTPMethod_GET, path, querys, nil, &resp) + return &resp, err +} diff --git a/apis/rgs/vnc.go b/apis/rgs/vnc.go new file mode 100644 index 0000000..6dc42c4 --- /dev/null +++ b/apis/rgs/vnc.go @@ -0,0 +1,40 @@ +package rgs + +import ( + "fmt" + "net/url" + + "github.com/XingfenD/rainyun_api_go_sdk/apis/common" + "github.com/XingfenD/rainyun_api_go_sdk/constant" +) + +// 游戏云连接VNC +// +// id: 游戏云ID +// +// consoleType: 控制台类型,可选值: novnc,xtermjs +func (s *RgsService) GetRgsVnc(id int, consoleType string) (*common.VncConnectionInfo, error) { + path := fmt.Sprintf("/product/rgs/%d/vnc", id) + + var resp common.VncConnectionInfo + querys := map[string]string{"console_type": consoleType} + err := s.client.Do(constant.HTTPMethod_GET, path, querys, nil, &resp) + return &resp, err +} + +// 获取游戏云对应的PVE地址 +// +// id: 游戏云ID +func (s *RgsService) GetRgsPveAddress(id int) (string, error) { + v, err := s.GetRgsVnc(id, "novnc") + if err != nil { + return "", err + } + + parsedURL, err := url.Parse(v.Data.RequestURL) + if err != nil { + return "", err + } + + return parsedURL.Hostname(), nil +} diff --git a/apis/ros/bucket.go b/apis/ros/bucket.go new file mode 100644 index 0000000..dd5c1b9 --- /dev/null +++ b/apis/ros/bucket.go @@ -0,0 +1,196 @@ +package ros + +import ( + "fmt" + + "github.com/XingfenD/rainyun_api_go_sdk/apis/common" + "github.com/XingfenD/rainyun_api_go_sdk/constant" +) + +// 创建对象存储桶请求 +type CreateRosBucketRequest struct { + BucketName string `json:"bucket_name"` // 存储桶名称 + InstanceID int `json:"instance_id"` // 实例ID +} + +// 创建对象存储桶响应 +type CreateRosBucketResponse struct { + Code int `json:"code"` + Data RosBucket `json:"data"` +} + +// 获取对象存储桶列表请求 +type GetRosBucketListRequest struct { + Options common.StandQueryParameters `json:"options"` +} + +// 获取对象存储桶列表响应 +type GetRosBucketListResponse struct { + Code int `json:"code"` + Data RosBucketList `json:"data"` +} + +// 对象存储桶列表 +type RosBucketList struct { + TotalRecords int `json:"TotalRecords"` + Records []RosBucket `json:"Records"` +} + +// 获取对象存储桶详情响应 +type GetRosBucketDetailResponse struct { + Code int `json:"code"` + Data RosBucketDetail `json:"data"` +} + +// 对象存储桶详情 +type RosBucketDetail struct { + Data RosBucket `json:"Data"` +} + +// 修改存储桶Proxy设置请求 +// 一个请求只能设置一项设置,要设置的设置项需要放在 Setting 中,只需要写需要修改的设置项,其他不填 +type ModifyRosBucketProxySettingsRequest struct { + DomainList []string `json:"domain_list,omitempty"` // 域名列表 + GzipSettings RosGzipSettings `json:"gzip_settings,omitempty"` + IPRestrictSettings RosIPRestrictSettings `json:"ip_restrict_settings,omitempty"` + RefererRestrictSettings RosRefererRestrictSettings `json:"referer_restrict_settings,omitempty"` + Setting string `json:"setting"` // 必选项,要设置的选项(domain/ssl/waf/gzip/ip_restrict/referer_restrict) + SslSettings RosSslSettings `json:"ssl_settings,omitempty"` + WafSettings RosWafSettings `json:"waf_settings,omitempty"` +} + +// Gzip设置 +type RosGzipSettings struct { + CompressLevel int `json:"compress_level,omitempty"` // 压缩等级(1-5,默认为2) + FileTypes []string `json:"file_types,omitempty"` // 文件MIME类型,默认["*"] + IsEnable bool `json:"is_enable,omitempty"` // 是否启用GZIP压缩,默认启用 +} + +// IP访问限制设置 +type RosIPRestrictSettings struct { + Blacklist []string `json:"blacklist,omitempty"` // 黑名单IP或CIDR范围列表,和白名单列表只能二选一不能共存 + IsEnable bool `json:"is_enable,omitempty"` // 是否启用IP访问限制 + Whitelist []string `json:"whitelist,omitempty"` // 白名单IP或CIDR范围列表,和黑名单列表只能二选一不能共存 +} + +// Referer防盗链设置 +type RosRefererRestrictSettings struct { + Blacklist []string `json:"blacklist,omitempty"` // 黑名单域名列表,和白名单列表只能二选一不能共存 + BypassMissing bool `json:"bypass_missing,omitempty"` // 是否允许Referer请求头不存在或者格式有误 + IsEnable bool `json:"is_enable,omitempty"` // 是否启用Referer防盗链 + Whitelist []string `json:"whitelist,omitempty"` // 白名单域名列表,和黑名单列表只能二选一不能共存 +} + +// SSL设置,SSL证书会自动匹配 +type RosSslSettings struct { + IsEnable bool `json:"is_enable,omitempty"` // 是否启用SSL + IsForce bool `json:"is_force,omitempty"` // 是否强制使用SSL +} + +// WAF防火墙设置 +type RosWafSettings struct { + BlockTime int `json:"block_time,omitempty"` // 未通过JS验证时的封禁时间,秒 + GlobalJsCheck bool `json:"global_js_check,omitempty"` // 是否启用全局JS检查,只有超过阈值才会生效 + GlobalQPS int `json:"global_qps,omitempty"` // 全局访问速率限制,超过会被拒绝 + IsEnable bool `json:"is_enable,omitempty"` // 是否启用WAF防火墙 + PerIPJsCheck bool `json:"per_ip_js_check,omitempty"` // 是否启用单IP JS检查,只有超过阈值才会生效 + PerIPQPS int `json:"per_ip_qps,omitempty"` // 单个IP访问速率限制,超过会被拒绝 +} + +// 获取对象存储桶列表 +// +// ⚠️注意这个接口是拿不到AK和SK的,响应里面的AK和SK都是空的 +func (s *RosService) GetRosBucketList(req *GetRosBucketListRequest) (*GetRosBucketListResponse, error) { + path := "/product/ros/bucket/" + + var resp GetRosBucketListResponse + querys, err := common.MarshalQueryParams(req) + if err != nil { + return nil, err + } + err = s.client.Do(constant.HTTPMethod_GET, path, querys, nil, &resp) + return &resp, err +} + +// 创建对象存储桶 +// +// req: 创建参数(bucketName 存储桶名 >= 3 字符 <= 63 字符) +func (s *RosService) CreateRosBucket(req CreateRosBucketRequest) (*CreateRosBucketResponse, error) { + path := "/product/ros/bucket" + + var resp CreateRosBucketResponse + err := s.client.Do(constant.HTTPMethod_POST, path, nil, req, &resp) + return &resp, err +} + +// 获取对象存储桶详情 +// +// bucketID: 存储桶ID +func (s *RosService) GetRosBucketDetail(bucketID int) (*GetRosBucketDetailResponse, error) { + path := fmt.Sprintf("/product/ros/bucket/%d", bucketID) + + var resp GetRosBucketDetailResponse + err := s.client.Do(constant.HTTPMethod_GET, path, nil, nil, &resp) + return &resp, err +} + +// 删除对象存储桶 +// +// bucketID: 存储桶ID +func (s *RosService) DeleteRosBucket(bucketID int) (*common.BasicOperationResponse, error) { + path := fmt.Sprintf("/product/ros/bucket/%d", bucketID) + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_DELETE, path, nil, nil, &resp) + return &resp, err +} + +// 获取对象存储桶监控数据 +// +// bucketID: 存储桶ID +// +// req: 开始时间/结束时间(timestamp) +func (s *RosService) GetRosBucketMonitorData(bucketID int, req *GetRosMonitorDataRequest) (*GetRosMonitorDataResponse, error) { + path := fmt.Sprintf("/product/ros/bucket/%d/monitor", bucketID) + + var resp GetRosMonitorDataResponse + querys, err := common.MarshalQueryParams(req) + if err != nil { + return nil, err + } + err = s.client.Do(constant.HTTPMethod_GET, path, querys, nil, &resp) + return &resp, err +} + +// 修改存储桶Proxy设置 +// +// bucketID: 存储桶ID +func (s *RosService) ModifyRosBucketProxySettings(bucketID int, req *ModifyRosBucketProxySettingsRequest) (*common.BasicOperationResponse, error) { + path := fmt.Sprintf("/product/ros/bucket/%d/proxy", bucketID) + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_PATCH, path, nil, req, &resp) + return &resp, err +} + +// 对象存储桶重新生成密钥 +// +// bucketID: 存储桶ID +func (s *RosService) ReGenerateRosBucketKeys(bucketID int) (*common.BasicOperationResponse, error) { + path := fmt.Sprintf("/product/ros/bucket/%d/regenerate-keys", bucketID) + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_POST, path, nil, nil, &resp) + return &resp, err +} + +// 开关对象存储桶匿名访问 +// +// bucketID: 存储桶ID +func (s *RosService) SetRosBucketPublicAccess(bucketID int) (*common.BasicOperationResponse, error) { + path := fmt.Sprintf("/product/ros/bucket/%d/toggle-public-access", bucketID) + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_POST, path, nil, nil, &resp) + return &resp, err +} diff --git a/apis/ros/client.go b/apis/ros/client.go new file mode 100644 index 0000000..6c41a35 --- /dev/null +++ b/apis/ros/client.go @@ -0,0 +1,11 @@ +package ros + +import "github.com/XingfenD/rainyun_api_go_sdk/apis" + +type RosService struct { + client *apis.RyClient +} + +func NewRosService(c *apis.RyClient) *RosService { + return &RosService{client: c} +} diff --git a/apis/ros/instance.go b/apis/ros/instance.go new file mode 100644 index 0000000..72041e4 --- /dev/null +++ b/apis/ros/instance.go @@ -0,0 +1,224 @@ +package ros + +import ( + "fmt" + + "github.com/XingfenD/rainyun_api_go_sdk/apis/common" + "github.com/XingfenD/rainyun_api_go_sdk/constant" +) + +// 创建对象存储实例请求 +type CreateRosInstanceRequest struct { + Duration int `json:"duration"` // 购买时长(月) + PlanID int `json:"plan_id"` // 套餐ID + WithCouponID int `json:"with_coupon_id"` // 优惠券ID +} + +// 创建对象存储实例响应 +type CreateRosInstanceResponse struct { + Code int `json:"code"` + Data RosInstance `json:"data"` +} + +// 获取对象存储实例列表请求 +type GetRosInstanceListRequest struct { + Options common.StandQueryParameters `json:"options"` +} + +// 获取对象存储实例列表响应 +type GetRosInstanceListResponse struct { + Code int `json:"code"` + Data RosInstanceList `json:"data"` +} + +// 对象存储实例列表 +type RosInstanceList struct { + TotalRecords int `json:"TotalRecords"` + Records []RosInstance `json:"Records"` +} + +// 获取对象存储实例详情响应 +type GetRosInstanceDetailResponse struct { + Code int `json:"code"` + Data RosInstanceDetail `json:"data"` +} + +// 对象存储实例详情 +type RosInstanceDetail struct { + Data RosInstance `json:"Data"` + RenewPointPrice RosRenewPointPrice `json:"RenewPointPrice"` // 积分续费 +} + +// 对象存储积分续费价格 +type RosRenewPointPrice struct { + Num7 int `json:"7"` // 积分续费七天 + Num31 int `json:"31"` // 积分续费一个月 +} + +// ROS实例续费请求 +type RenewRosInstanceRequest struct { + Duration int `json:"duration"` // 续费时长(月) + WithCouponID int `json:"with_coupon_id"` // 优惠券ID +} + +// ROS实例自动续费选项 +type RosInstanceAutoRenewOption struct { + AutoRenewOption bool `json:"auto_renew_option"` +} + +// ROS实例缩放请求 +type ScaleRosInstanceRequest struct { + DestPlan int `json:"dest_plan"` // 目标套餐ID + WithCouponID int `json:"with_coupon_id"` // 优惠券ID +} + +// 设置对象存储实例标签请求 +type SetRosInstanceTagRequest struct { + TagName string `json:"tag_name"` +} + +// 开关对象存储实例的弹性计费选项请求 +type ToggleRosInstanceExtraAccountingRequest struct { + IsEnable bool `json:"is_enable"` +} + +// 创建对象存储实例 +// +// req: 创建参数 +func (s *RosService) CreateRosInstance(req CreateRosInstanceRequest) (*CreateRosInstanceResponse, error) { + path := "/product/ros/instance" + + var resp CreateRosInstanceResponse + err := s.client.Do(constant.HTTPMethod_POST, path, nil, req, &resp) + return &resp, err +} + +// 获取对象存储实例详情 +// +// instanceID: 实例ID +func (s *RosService) GetRosInstanceDetail(instanceID int) (*GetRosInstanceDetailResponse, error) { + path := fmt.Sprintf("/product/ros/instance/%d", instanceID) + + var resp GetRosInstanceDetailResponse + err := s.client.Do(constant.HTTPMethod_GET, path, nil, nil, &resp) + return &resp, err +} + +// 获取对象存储实例列表 +// +// ⚠️注意这个接口是拿不到AK和SK的,响应里面的AK和SK都是空的 +func (s *RosService) GetRosInstanceList(req *GetRosInstanceListRequest) (*GetRosInstanceListResponse, error) { + path := "/product/ros/instance/" + + var resp GetRosInstanceListResponse + querys, err := common.MarshalQueryParams(req) + if err != nil { + return nil, err + } + err = s.client.Do(constant.HTTPMethod_GET, path, querys, nil, &resp) + return &resp, err +} + +// 获取对象存储实例监控数据 +// +// instanceID: 实例ID +// +// req: 开始时间/结束时间(timestamp) +func (s *RosService) GetRosInstanceMonitorData(instanceID int, req *GetRosMonitorDataRequest) (*GetRosMonitorDataResponse, error) { + path := fmt.Sprintf("/product/ros/instance/%d/monitor", instanceID) + + var resp GetRosMonitorDataResponse + querys, err := common.MarshalQueryParams(req) + if err != nil { + return nil, err + } + err = s.client.Do(constant.HTTPMethod_GET, path, querys, nil, &resp) + return &resp, err +} + +// 对象存储实例续费 +// +// instanceID: 实例ID +// +// req: 续费参数 +func (s *RosService) RenewRosInstance(instanceID int, req RenewRosInstanceRequest) (*common.BasicOperationResponse, error) { + path := fmt.Sprintf("/product/ros/instance/%d/renew", instanceID) + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_POST, path, nil, req, &resp) + return &resp, err +} + +// 对象存储实例自动续费选项 +// +// instanceID: 实例ID +// +// isOpen: 是否开启自动续费 +func (s *RosService) SetRosInstanceAutoRenewOption(instanceID int, isOpen bool) (*common.BasicOperationResponse, error) { + path := fmt.Sprintf("/product/ros/instance/%d/renew/option", instanceID) + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_POST, path, nil, RosInstanceAutoRenewOption{AutoRenewOption: isOpen}, &resp) + return &resp, err +} + +// ROS实例缩放 +// +// instanceID: 实例ID +// +// req: 缩放参数 +func (s *RosService) ScaleRosInstance(instanceID int, req ScaleRosInstanceRequest) (*common.BasicOperationResponse, error) { + path := fmt.Sprintf("/product/ros/instance/%d/scale", instanceID) + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_POST, path, nil, req, &resp) + return &resp, err +} + +// 设置对象存储实例标签 +// +// instanceID: 实例ID +// +// tag: 标签 +func (s *RosService) SetRosInstanceTags(instanceID int, tag string) (*common.BasicOperationResponse, error) { + path := fmt.Sprintf("/product/ros/instance/%d/tag", instanceID) + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_PATCH, path, nil, SetRosInstanceTagRequest{TagName: tag}, &resp) + return &resp, err +} + +// 开关对象存储实例的弹性计费选项 +// +// instanceID: 实例ID +// +// isEnable: 是否开启 +func (s *RosService) ToggleRosInstanceExtraAccounting(instanceID int, isEnable bool) (*common.BasicOperationResponse, error) { + path := fmt.Sprintf("/product/ros/instance/%d/toggle-extra-accounting", instanceID) + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_PATCH, path, nil, ToggleRosInstanceExtraAccountingRequest{IsEnable: isEnable}, &resp) + return &resp, err +} + +// 对象存储实例重新生成密钥 +// +// instanceID: 实例ID +func (s *RosService) ReGenerateRosInstanceKeys(instanceID int) (*common.BasicOperationResponse, error) { + path := fmt.Sprintf("/product/ros/instance/%d/regenerate-keys", instanceID) + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_POST, path, nil, nil, &resp) + return &resp, err +} + +// 开关对象存储实例匿名访问 +// +// instanceID: 实例ID +func (s *RosService) SetRosInstancePublicAccess(instanceID int) (*common.BasicOperationResponse, error) { + path := fmt.Sprintf("/product/ros/instance/%d/toggle-public-access", instanceID) + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_POST, path, nil, nil, &resp) + return &resp, err +} diff --git a/apis/ros/model.go b/apis/ros/model.go new file mode 100644 index 0000000..8169c25 --- /dev/null +++ b/apis/ros/model.go @@ -0,0 +1,141 @@ +package ros + +// RosNode 节点信息 +type RosNode struct { + UUID string `json:"UUID"` + AuthKey string `json:"AuthKey"` + Region string `json:"Region"` + IPRegion string `json:"IpRegion"` + Machine string `json:"Machine"` + Product string `json:"Product"` + Subtype string `json:"Subtype"` + ChineseName string `json:"ChineseName"` + PhysicalNode string `json:"PhysicalNode"` + Config string `json:"Config"` + Stock any `json:"Stock"` + StatusData string `json:"StatusData"` + ShowMonitorData string `json:"ShowMonitorData"` + UpdateTime string `json:"UpdateTime"` + GitRepositoryName string `json:"GitRepositoryName"` + CertifyRequired bool `json:"CertifyRequired"` + IsDisableBackup bool `json:"IsDisableBackup"` + IsHidden bool `json:"IsHidden"` + NodeName string `json:"NodeName"` +} + +// RosPlan 套餐信息 +type RosPlan struct { + ID int `json:"id"` + Region string `json:"region"` + Subtype string `json:"subtype"` + PlanName string `json:"plan_name"` + Machine string `json:"machine"` + ChargeType string `json:"charge_type"` + Chinese string `json:"chinese"` + IsFree bool `json:"is_free"` + PointRenewPrice any `json:"point_renew_price"` + IsSelling bool `json:"is_selling"` + Price int `json:"price"` + StorageSize int `json:"storage_size"` + Bandwidth int `json:"bandwidth"` + ExtraTransferPrice float64 `json:"extra_transfer_price"` + ExtraStoragePrice float64 `json:"extra_storage_price"` +} + +// RosUsageData 实例使用数据 +type RosUsageData struct { + MetricsUsageTotalBytes int `json:"metrics_usage_total_bytes"` // 存储使用量 + MetricsReceivedBytes int `json:"metrics_received_bytes"` // 接收字节数 + MetricsSentBytes int `json:"metrics_sent_bytes"` // 发送字节数 + MetricsRequests int `json:"metrics_requests"` // 请求数 + SpeedReceivedBytes int `json:"speed_received_bytes"` // 接收字节速度 + SpeedSentBytes int `json:"speed_sent_bytes"` // 发送字节速度 + SpeedRequests int `json:"speed_requests"` // 请求速度 + UpdateTime int `json:"UpdateTime"` // 更新时间 +} + +// RosInstance 对象存储实例信息 +type RosInstance struct { + ID int `json:"ID"` + UID int `json:"UID"` + PlanID int `json:"PlanID"` + CreateDate int `json:"CreateDate"` // 创建时间 + NodeUUID string `json:"NodeUUID"` // 节点UUID + Node RosNode `json:"Node"` // 节点信息 + Status string `json:"Status"` + StopReason string `json:"StopReason"` + + RewardPointsToBeCollect int `json:"RewardPointsToBeCollect"` // 待领取积分奖励 + Tag string `json:"Tag"` + Plan RosPlan `json:"Plan"` + AccessKey string `json:"access_key"` // ⚠️列表接口拿不到AK,响应里面的AK是空的 + SecretKey string `json:"secret_key"` // ⚠️列表接口拿不到SK,响应里面的SK是空的 + IsPublicAccess bool `json:"is_public_access"` // 是否允许公共访问 + IsEnableExtraAccounting bool `json:"is_enable_extra_accounting"` // 是否启用额外计费 + LastResetDate int `json:"last_reset_date"` // 最后重置时间 + NextResetDate int `json:"next_reset_date"` // 下次重置时间 + ExpDate int `json:"ExpDate"` // 到期时间 + ExpireNotice int `json:"ExpireNotice"` + AutoRenew bool `json:"AutoRenew"` // 是否自动续费 + UnsubscribeAble bool `json:"UnsubscribeAble"` + Try bool `json:"Try"` // 是否试用 + PublicAPIURL string `json:"public_api_url"` + Buckets any `json:"buckets"` + ExtraPayTime int `json:"extra_pay_time"` + UsageData RosUsageData `json:"UsageData"` + Restrictions string `json:"restrictions"` + PublicAccessRestricted bool `json:"public_access_restricted"` +} + +// RosBucket 对象存储桶信息 +type RosBucket struct { + ID int `json:"id"` // 存储桶ID + Name string `json:"name"` // 存储桶名称 + UID int `json:"uid"` // 用户ID + CreateDate int `json:"create_date"` + AccessKey string `json:"access_key"` // ⚠️列表接口拿不到AK,响应里面的AK是空的 + SecretKey string `json:"secret_key"` // ⚠️列表接口拿不到SK,响应里面的SK是空的 + IsPublicAccess bool `json:"is_public_access"` // 是否公开 + InstanceID int `json:"instance_id"` // 实例ID + Instance RosInstance `json:"Instance"` + StopReason string `json:"stop_reason"` + DomainList any `json:"DomainList"` + SslIsEnable bool `json:"ssl_is_enable"` + SSLCertList any `json:"SSLCertList"` + SslAutoRedirect bool `json:"ssl_auto_redirect"` + WafIsEnable bool `json:"waf_is_enable"` + WafGlobalQPS int `json:"waf_global_qps"` + WafPerIPQPS int `json:"waf_per_ip_qps"` + WafBlockTime int `json:"waf_block_time"` + WafGlobalJsCheck bool `json:"waf_global_js_check"` + WafPerIPJsCheck bool `json:"waf_per_ip_js_check"` + RefererRestrictIsEnable bool `json:"referer_restrict_is_enable"` + RefererRestrictWhitelist any `json:"RefererRestrictWhitelist"` + RefererRestrictBlacklist any `json:"RefererRestrictBlacklist"` + RefererRestrictBypassMissing bool `json:"referer_restrict_bypass_missing"` + IPRestrictIsEnable bool `json:"ip_restrict_is_enable"` + IPRestrictWhitelist any `json:"IPRestrictWhitelist"` + IPRestrictBlacklist any `json:"IPRestrictBlacklist"` + GzipIsEnable bool `json:"gzip_is_enable"` + GzipCompressLevel int `json:"gzip_compress_level"` + UsageData RosUsageData `json:"UsageData"` + ReadyDelete bool `json:"ready_delete"` +} + +// 获取监控数据请求(桶和实例通用) +type GetRosMonitorDataRequest struct { + StartDate int `json:"start_date"` // 开始时间(timestamp) + EndDate int `json:"end_date"` // 结束时间(timestamp) +} + +// 获取监控数据响应(桶和实例通用) +type GetRosMonitorDataResponse struct { + Code int `json:"code"` + Data RosMonitorData `json:"data"` +} + +// RosMonitorData 对象存储监控数据(桶和实例通用) +type RosMonitorData struct { + Columns []string `json:"Columns"` + Values [][]any `json:"Values"` +} diff --git a/apis/ssl/certificate.go b/apis/ssl/certificate.go new file mode 100644 index 0000000..696d5a1 --- /dev/null +++ b/apis/ssl/certificate.go @@ -0,0 +1,128 @@ +package ssl + +import ( + "fmt" + + "github.com/XingfenD/rainyun_api_go_sdk/apis/common" + "github.com/XingfenD/rainyun_api_go_sdk/constant" +) + +// SSL证书 +type SslCertificate struct { + Cert string `json:"cert"` // 证书 + Key string `json:"key"` // 私钥 +} + +// SSL证书列表记录 +type SslCertificateRecord struct { + ID int `json:"ID"` + UID int `json:"UID"` + Domain string `json:"Domain"` // 域名(逗号分割) + Issuer string `json:"Issuer"` // 品牌 + StartDate int `json:"StartDate"` // 开始时间 + ExpDate int `json:"ExpDate"` // 结束时间 + UploadTime int `json:"UploadTime"` // 上传时间 + NginxErr string `json:"NginxErr"` // ? + BaishanCertID int `json:"BaishanCertID"` // 白山云证书ID + BindDomains any `json:"BindDomains"` // 绑定的域名 +} + +// SSL证书列表数据 +type SslCertificateListData struct { + TotalRecords int `json:"TotalRecords"` + Records []SslCertificateRecord `json:"Records"` +} + +type GetSslCertificateListResponse struct { + Code int `json:"code"` + Data SslCertificateListData `json:"data"` +} + +type GetSslCertificateListRequest struct { + Options common.StandQueryParameters `json:"options"` +} + +// 获取SSL证书列表 +func (s *SslService) GetSSLCertificateList(req *GetSslCertificateListRequest) (*GetSslCertificateListResponse, error) { + path := "/product/sslcenter/" + + var resp GetSslCertificateListResponse + querys, err := common.MarshalQueryParams(req) + if err != nil { + return nil, err + } + err = s.client.Do(constant.HTTPMethod_GET, path, querys, nil, &resp) + return &resp, err +} + +// 上传SSL证书 +// +// cert: 证书 +// +// key: 私钥 +func (s *SslService) UploadSSLCertificate(cert string, key string) (*common.BasicOperationResponse, error) { + path := "/product/sslcenter/" + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_POST, path, nil, SslCertificate{ + Cert: cert, + Key: key, + }, &resp) + return &resp, err +} + +// SSL证书详情数据 +type SslDetailData struct { + Cert string `json:"Cert"` // 证书 + Key string `json:"Key"` // 私钥 + DomainName string `json:"DomainName"` // 域名(逗号分割) + Issuer string `json:"Issuer"` // 品牌 + StartDate int `json:"StartDate"` // 开始时间 + ExpDate int `json:"ExpDate"` // 结束时间 + RemainDays int `json:"RemainDays"` // 剩余天数 +} + +type GetSslDetailResponse struct { + Code int `json:"code"` + Data SslDetailData `json:"data"` +} + +// 获取SSL证书详情 +// +// id: SSL证书ID +func (s *SslService) GetSslDetail(id int) (*GetSslDetailResponse, error) { + path := fmt.Sprintf("/product/sslcenter/%d", id) + + var resp GetSslDetailResponse + err := s.client.Do(constant.HTTPMethod_GET, path, nil, nil, &resp) + return &resp, err +} + +// 替换SSL证书 +// +// id: SSL证书ID +// +// cert: 证书 +// +// key: 私钥 +func (s *SslService) ReplaceSsl(id int, cert, key string) (*common.BasicOperationResponse, error) { + path := fmt.Sprintf("/product/sslcenter/%d", id) + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_PUT, path, nil, SslCertificate{ + Cert: cert, + Key: key, + }, &resp) + return &resp, err +} + +// 删除SSL证书 +// +// id: SSL证书ID +func (s *SslService) DeleteSsl(id int) (*common.BasicOperationResponse, error) { + path := fmt.Sprintf("/product/sslcenter/%d", id) + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_DELETE, path, nil, nil, &resp) + return &resp, err +} diff --git a/apis/ssl/client.go b/apis/ssl/client.go new file mode 100644 index 0000000..42b20eb --- /dev/null +++ b/apis/ssl/client.go @@ -0,0 +1,11 @@ +package ssl + +import "github.com/XingfenD/rainyun_api_go_sdk/apis" + +type SslService struct { + client *apis.RyClient +} + +func NewSslService(c *apis.RyClient) *SslService { + return &SslService{client: c} +} diff --git a/apis/user/client.go b/apis/user/client.go new file mode 100644 index 0000000..86a2785 --- /dev/null +++ b/apis/user/client.go @@ -0,0 +1,11 @@ +package user + +import "github.com/XingfenD/rainyun_api_go_sdk/apis" + +type UserService struct { + client *apis.RyClient +} + +func NewUserService(c *apis.RyClient) *UserService { + return &UserService{client: c} +} diff --git a/apis/user/mfa.go b/apis/user/mfa.go new file mode 100644 index 0000000..06f6d7d --- /dev/null +++ b/apis/user/mfa.go @@ -0,0 +1,40 @@ +package user + +import ( + "github.com/XingfenD/rainyun_api_go_sdk/apis/common" + "github.com/XingfenD/rainyun_api_go_sdk/constant" +) + +// Request2FARequest 请求二次验证 +type Request2FARequest struct { + Type string `json:"type"` +} + +// 请求二次验证 +// +// verificationMethod: 验证方式(sms/totp/email) +func (s *UserService) Request2FA(verificationMethod string) (*common.BasicOperationResponse, error) { + path := "/user/mfa/request" + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_POST, path, nil, Request2FARequest{Type: verificationMethod}, &resp) + + return &resp, err +} + +// Verify2FAResultRequest 验证二次验证结果 +type Verify2FAResultRequest struct { + AuthCode int `json:"auth_code"` +} + +// 验证二次验证结果 +// +// authCode: 验证码 +func (s *UserService) Verify2FAResult(authCode int) (*common.BasicOperationResponse, error) { + path := "/user/mfa/verify" + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_POST, path, nil, Verify2FAResultRequest{AuthCode: authCode}, &resp) + + return &resp, err +} diff --git a/apis/user/reward.go b/apis/user/reward.go new file mode 100644 index 0000000..34c7899 --- /dev/null +++ b/apis/user/reward.go @@ -0,0 +1,186 @@ +package user + +import ( + "github.com/XingfenD/rainyun_api_go_sdk/apis/common" + "github.com/XingfenD/rainyun_api_go_sdk/constant" +) + +type GetUserRewardProductsResponse struct { + Code int `json:"code"` + Data UserRewardProductsData `json:"data"` +} + +// UserRewardProductsData 用户可用积分兑换的产品 +type UserRewardProductsData struct { + Rcs []any `json:"rcs"` + Rvh []any `json:"rvh"` + Rgs []any `json:"rgs"` + Ros []any `json:"ros"` + Rbm []any `json:"rbm"` +} + +// 获取可兑换积分产品列表. +func (s *UserService) GetUserRewardProducts() (*GetUserRewardProductsResponse, error) { + path := "/user/reward/products" + + var resp GetUserRewardProductsResponse + err := s.client.Do(constant.HTTPMethod_GET, path, nil, nil, &resp) + + return &resp, err +} + +// 发布优惠券给下级用户 +type PublishCouponsToLowerLevelUsersRequest struct { + BaseLimit int `json:"base_limit"` // 满减条件(满多少才能用) + Color string `json:"color"` // 颜色: waring: 黄,danger: 红,success: 绿... + Count int `json:"count"` // 发放数量 + ExpDate int `json:"exp_date"` // 过期时间(timestamp) + FriendlyName string `json:"friendly_name"` // 优惠券标题 + Type string `json:"type"` // 类型: discount:折扣, normal: 直减 + UID int `json:"uid"` // 要发放到用户ID(如:114514),为空时则返回兑换码 + UsableDuration string `json:"usable_duration"` // unknown + UsableProduct string `json:"usable_product"` // 可用产品(默认全部),","分隔: renew,create,upgrade + UsableScenes string `json:"usable_scenes"` // 适用操作(默认全部),","分隔: rvh,rcs,rgs,ros,rbm + Value int `json:"value"` // 直减(元)/折扣(折), 折扣时:1~9:一~九折;11~99:一一~九九折 +} + +// 发布优惠券给下级用户 +func (s *UserService) PublishCouponsToLowerLevelUsers(req *PublishCouponsToLowerLevelUsersRequest) (*common.BasicOperationResponse, error) { + path := "/user/vip/coupon" + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_POST, path, nil, req, &resp) + + return &resp, err +} + +// 发送优惠券到积分商城 +type PostCouponsToPointsMallRequest struct { + AvailableDays int `json:"available_days"` // 可用天数 + BaseLimit int `json:"base_limit"` // 满减条件(满多少才能用) + BuyLimit int `json:"buy_limit"` // 领取次数限制 + Color string `json:"color"` // 颜色: waring: 黄, danger: 红, success: 绿, info: 蓝 + Count int `json:"count"` // 发放数量 + EndDate int `json:"end_date"` // 截止日期 + FirstSend bool `json:"first_send"` // 绑定微信后立即自动领取(设置后不可手动领取并且不会显示) + FriendlyName string `json:"friendly_name"` // 优惠券名称 + Name string `json:"name"` // 标识名称 + Order int `json:"order"` // 排序,越大越靠前 + Points int `json:"points"` // 领取积分 + Type string `json:"type"` // 类型: discount:折扣, normal: 直减 + UsableDuration string `json:"usable_duration"` // unknown + UsableProduct string `json:"usable_product"` // 可用产品(默认全部),","分隔: renew,create,upgrade + UsableScenes string `json:"usable_scenes"` // 适用操作(默认全部),","分隔: rvh,rcs,rgs,ros,rbm + Value int `json:"value"` // 直减(元)/折扣(折), 折扣时:1~9:一~九折;11~99:一一~九九折 +} + +// 发布优惠券到积分商城(供下级领取) +func (s *UserService) PostCouponsToPointsMall(req *PostCouponsToPointsMallRequest) (*common.BasicOperationResponse, error) { + path := "/user/vip/coupon" + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_POST, path, nil, req, &resp) + + return &resp, err +} + +type GetPointsMallItemsResponse struct { + Code int `json:"code"` + Data []PointsMallItem `json:"data"` +} + +// PointsMallItem 积分商城物品 +type PointsMallItem struct { + ID int `json:"id"` + Name string `json:"name"` + Points int `json:"points"` + Type string `json:"type"` + AvailableStock int `json:"available_stock"` + FriendlyName string `json:"friendly_name"` + ItemData PointsMallItemData `json:"item_data"` + BuyLimit int `json:"buy_limit"` + SenderID int `json:"sender_id"` + FirstSend bool `json:"first_send"` + ByInvite bool `json:"by_invite"` + Color string `json:"color"` + Order int `json:"order"` + PublicTime int `json:"public_time"` + EndDate int `json:"end_date"` + AutoRefresh int `json:"auto_refresh"` + RefreshLimit int `json:"refresh_limit"` + MoneyRequired int `json:"money_required"` +} + +// PointsMallItemData 积分商城物品数据 +type PointsMallItemData struct { + Color string `json:"color,omitempty"` + FriendlyName string `json:"friendly_name,omitempty"` + UsableScenes string `json:"usable_scenes,omitempty"` + AvailableDays int `json:"available_days,omitempty"` + UsableProduct string `json:"usable_product,omitempty"` + BaseLimit int `json:"base_limit,omitempty"` + PublicPoint int `json:"public_point,omitempty"` + Type string `json:"type,omitempty"` + UsableDuration string `json:"usable_duration,omitempty"` + UsablePlanID int `json:"usable_plan_id,omitempty"` + Value any `json:"value,omitempty"` + ProductType string `json:"product_type,omitempty"` + ProductSubtype string `json:"product_subtype,omitempty"` + DurationSeconds int `json:"duration_seconds,omitempty"` + ProductConfig PointsMallProductConfig `json:"product_config,omitempty"` + Desc string `json:"desc,omitempty"` + ImgURL string `json:"img_url,omitempty"` + DescURL string `json:"desc_url,omitempty"` +} + +// PointsMallProductConfig 积分商城物品的产品配置 +type PointsMallProductConfig struct { + OsID int `json:"os_id"` + PlanID int `json:"plan_id"` + Subtype string `json:"subtype"` + Duration int `json:"duration"` + PayMode string `json:"pay_mode"` + PanelUser string `json:"panel_user"` + EggTypeID int `json:"egg_type_id"` + WithCoupon int `json:"with_coupon"` + CPULimitMode bool `json:"cpu_limit_mode"` + Config PointsMallItemConfig `json:"config"` +} + +// PointsMallItemConfig 积分商城物品配置 +type PointsMallItemConfig struct { + CPU int `json:"cpu"` + Backup int `json:"backup"` + Memory int `json:"memory"` + NetIn int `json:"net_in"` + NetOut int `json:"net_out"` + Database int `json:"database"` + BaseDisk int `json:"base_disk"` + DataDisk int `json:"data_disk"` + Allocation int `json:"allocation"` +} + +// 获取积分商城商品列表 +func (s *UserService) GetPointsMallItems() (*GetPointsMallItemsResponse, error) { + path := "/user/reward/items" + + var resp GetPointsMallItemsResponse + err := s.client.Do(constant.HTTPMethod_GET, path, nil, nil, &resp) + + return &resp, err +} + +// 兑换积分物品 +type RedeemPointsForItemRequest struct { + ItemID int `json:"item_id"` +} + +// 兑换积分物品 +func (s *UserService) RedeemPointsForItem(id int) (*common.BasicOperationResponse, error) { + path := "/user/reward/items" + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_POST, path, nil, RedeemPointsForItemRequest{ItemID: id}, &resp) + + return &resp, err +} diff --git a/apis/user/user.go b/apis/user/user.go new file mode 100644 index 0000000..0e43e5e --- /dev/null +++ b/apis/user/user.go @@ -0,0 +1,123 @@ +package user + +import ( + "github.com/XingfenD/rainyun_api_go_sdk/apis/common" + "github.com/XingfenD/rainyun_api_go_sdk/constant" +) + +type GetUserInfoResponse struct { + Code int `json:"code"` + Data UserInfo `json:"data"` +} + +// UserInfo 用户信息 +type UserInfo struct { + ID int `json:"ID"` + Name string `json:"Name"` + Email string `json:"Email"` + Phone string `json:"Phone"` + Money float64 `json:"Money"` + RegisterTime int `json:"RegisterTime"` + QQOpenID string `json:"QQOpenID"` + QQ any `json:"QQ"` + WechatOpenID string `json:"WechatOpenID"` + CardCode any `json:"CardCode"` + IconURL string `json:"IconUrl"` + Points int `json:"Points"` + MoneyFromPoint int `json:"MoneyFromPoint"` + Inviter int `json:"Inviter"` + APIKey string `json:"APIKey"` + LastIP string `json:"LastIP"` + BanReason string `json:"BanReason"` + UnsubscribeCount int `json:"UnsubscribeCount"` + AlipayAccount string `json:"AlipayAccount"` + AlipayName string `json:"AlipayName"` + LastLogin string `json:"LastLogin"` + LastLoginArea string `json:"LastLoginArea"` + LoginCount int `json:"LoginCount"` + DLWallet int `json:"DLWallet"` + DLLevel int `json:"DLLevel"` + AdminGroup string `json:"AdminGroup"` + TOTPSecret string `json:"TOTPSecret"` + IsLoginEnableTFA bool `json:"IsLoginEnableTFA"` + IsAllowPointUse int `json:"IsAllowPointUse"` + ShareCode string `json:"ShareCode"` + VipLevel int `json:"VipLevel"` + IsAgent bool `json:"IsAgent"` + Valid bool `json:"Valid"` + Source string `json:"Source"` + ConsumeMonthly int `json:"ConsumeMonthly"` + ConsumeAll int `json:"ConsumeAll"` + ConsumeQuarter int `json:"ConsumeQuarter"` + ResellDaily int `json:"ResellDaily"` + ResellMonthly int `json:"ResellMonthly"` + ResellBeforeMonth float64 `json:"ResellBeforeMonth"` + ResellQuarter float64 `json:"ResellQuarter"` + ResellAll float64 `json:"ResellAll"` + StockDaily int `json:"StockDaily"` + StockMonthly int `json:"StockMonthly"` + StockQuarter float64 `json:"StockQuarter"` + StockAll float64 `json:"StockAll"` + SecondStockQuarter int `json:"SecondStockQuarter"` + SecondStockAll int `json:"SecondStockAll"` + SubUserMonthly int `json:"SubUserMonthly"` + SubUserAll int `json:"SubUserAll"` + ResellPointsMonthly int `json:"ResellPointsMonthly"` + ResellPointsAll int `json:"ResellPointsAll"` + Vip UserVipInfo `json:"VIP"` + Certify int `json:"Certify"` + LockPoints int `json:"LockPoints"` + CertifyStatus string `json:"CertifyStatus"` + CertifyType string `json:"CertifyType"` + CertifyAuditNote string `json:"CertifyAuditNote"` +} + +// UserVipInfo 用户 VIP 等级信息 +type UserVipInfo struct { + Title string `json:"Title"` + SaleRequire int `json:"SaleRequire"` + ResellRequire int `json:"ResellRequire"` + CertifyRequired bool `json:"CertifyRequired"` + SaleProfit float64 `json:"SaleProfit"` + ResellProfit float64 `json:"ResellProfit"` + SecondResellProfit float64 `json:"SecondResellProfit"` + CanSendCoupons bool `json:"CanSendCoupons"` + CanCustomCode bool `json:"CanCustomCode"` + CanSendMsg bool `json:"CanSendMsg"` + CanTryUsual bool `json:"CanTryUsual"` + FreeDomainCount int `json:"FreeDomainCount"` + FreeSSLCount int `json:"FreeSSLCount"` + CanBeAgent bool `json:"CanBeAgent"` + AgentTitle string `json:"AgentTitle"` + StockRequire int `json:"StockRequire"` + SecondStockRequire int `json:"SecondStockRequire"` + StockDiscount float64 `json:"StockDiscount"` + SecondStockProfit float64 `json:"SecondStockProfit"` +} + +// 获取用户信息. +func (s *UserService) GetUserInfo() (*GetUserInfoResponse, error) { + path := "/user/" + + var resp GetUserInfoResponse + err := s.client.Do(constant.HTTPMethod_GET, path, nil, nil, &resp) + + return &resp, err +} + +// RefreshApikeyRequest 刷新 API 密钥请求 +type RefreshApikeyRequest struct { + Option string `json:"option"` +} + +// 刷新 api 密钥. +// +// ⚠️不要轻易使用此方法 +func (s *UserService) RefreshApikey() (*common.BasicOperationResponse, error) { + path := "/user/" + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_PATCH, path, nil, RefreshApikeyRequest{Option: "apikey"}, &resp) + + return &resp, err +} diff --git a/apis/workorder/client.go b/apis/workorder/client.go new file mode 100644 index 0000000..fb349c7 --- /dev/null +++ b/apis/workorder/client.go @@ -0,0 +1,11 @@ +package workorder + +import "github.com/XingfenD/rainyun_api_go_sdk/apis" + +type WorkorderService struct { + client *apis.RyClient +} + +func NewWorkorderService(c *apis.RyClient) *WorkorderService { + return &WorkorderService{client: c} +} diff --git a/apis/workorder/model.go b/apis/workorder/model.go new file mode 100644 index 0000000..8c3be08 --- /dev/null +++ b/apis/workorder/model.go @@ -0,0 +1,152 @@ +package workorder + +import "github.com/XingfenD/rainyun_api_go_sdk/apis/common" + +// 获取工单列表请求 +type GetWorkOrderListRequest struct { + Options common.StandQueryParameters `json:"options"` +} + +// 获取工单列表响应 +type GetWorkOrderListResponse struct { + Code int `json:"code"` + Data WorkorderListData `json:"data"` +} + +// 工单列表数据 +type WorkorderListData struct { + TotalRecords int `json:"TotalRecords"` + Records []WorkorderRecord `json:"Records"` +} + +// 工单 +type WorkorderRecord struct { + ID int `json:"ID"` // 工单ID + UID int `json:"UID"` // 用户ID + ExtUserInfo string `json:"ExtUserInfo"` + UserName string `json:"UserName"` // 用户名 + UserEmail string `json:"UserEmail"` + UserVip string `json:"UserVip"` // 用户vip等级 + UserIcon string `json:"UserIcon"` // 用户头像 + Title string `json:"Title"` // 工单标题 + Type string `json:"Type"` // 工单类型 + RelatedProductType string `json:"RelatedProductType"` // 关联产品类型 + RelatedProductID int `json:"RelatedProductID"` // 关联产品id + IsUrgent int `json:"IsUrgent"` // 是否为紧急工单 + Status string `json:"Status"` // 状态(finished/answered/waiting) + Time int `json:"Time"` // 工单创建时间 + LastTime int `json:"LastTime"` + WaitBeginTime int `json:"WaitBeginTime"` + AssistID int `json:"AssistID"` // 客服id + AuthStatus string `json:"AuthStatus"` + AuthTime int `json:"AuthTime"` + AuthID int `json:"AuthID"` +} + +// 工单回复 +type WorkorderDiscuss struct { + ID int `json:"ID"` + IsAssist bool `json:"IsAssist"` + UID int `json:"UID"` + UserName string `json:"UserName"` + UserEmail string `json:"UserEmail"` + UserVip string `json:"UserVip"` + UserIcon string `json:"UserIcon"` + Content string `json:"Content"` + Time int `json:"Time"` + WaitTime int `json:"WaitTime"` + LastEditedTime int `json:"LastEditedTime"` + IsScored bool `json:"IsScored"` +} + +// 工单详情数据 +type WorkorderDetailData struct { + WorkorderRecord + Content string `json:"Content"` + Discuss []WorkorderDiscuss `json:"Discuss"` +} + +// 获取工单详情响应 +type GetWorkOrderDetailResponse struct { + Code int `json:"code"` + Data WorkorderDetailData `json:"data"` +} + +// 创建工单 +type CreateWorkerorderRequest struct { + Content string `json:"content"` + IsAuthed bool `json:"is_authed"` // 是否需要授权(可选) + IsUrgent int `json:"is_urgent"` // 是否为紧急工单 + RelatedProductID int `json:"related_product_id"` // 关联产品类型(可选) + RelatedProductType string `json:"related_product_type"` // 关联产品id(可选) + Title string `json:"title"` + Type string `json:"type"` // 工单类型 +} + +// 创建工单数据 +type CreateWorkorderData struct { + WorkorderRecord + Content string `json:"Content"` +} + +// 创建工单响应 +type CreateWorkerorderResponse struct { + Code int `json:"code"` + Data CreateWorkorderData `json:"data"` +} + +// 产品授权请求 +type ProductAuthRequest struct { + ProductID int `json:"product_id"` // 产品ID + ProductType string `json:"product_type"` // 产品类型(rvh/rcs/rgs/rbm/ros) +} + +// 回复工单请求 +type ReplyWorkerorderRequest struct { + Content string `json:"content"` // 回复内容 +} + +// 工单打分 +type ScoreWorkerorderRequest struct { + Score int `json:"score"` // 分数(1-5) + Reason string `json:"reason"` // (可选) + IsSolved bool `json:"is_solved"` // 是否解决(可选) + DiscussID int `json:"discuss_id"` // 回复ID(可选) + Aid int `json:"aid"` // 客服id(可选) +} + +// 工单打分详情数据 +type ScoreWorkorderData struct { + UID int `json:"uid"` + Aid int `json:"aid"` + OrderID int `json:"order_id"` + DiscussID int `json:"discuss_id"` + Score int `json:"score"` + Reason string `json:"reason"` + IsSolved bool `json:"is_solved"` + DiscussTime int `json:"DiscussTime"` + ScoreTime int `json:"score_time"` +} + +// 获取工单打分响应 +type GetScoreWorkorderResponse struct { + Code int `json:"code"` + Data ScoreWorkorderData `json:"data"` +} + +// 工单状态数据 +type WorkorderStatusData struct { + LastTime int `json:"LastTime"` + Status string `json:"Status"` +} + +// 获取工单状态响应 +type GetWorkorderStatusResponse struct { + Code int `json:"code"` + Data WorkorderStatusData `json:"data"` +} + +// 设置工单状态请求 +type SetWorkorderStatusRequest struct { + Status string `json:"status"` +} diff --git a/apis/workorder/score.go b/apis/workorder/score.go new file mode 100644 index 0000000..f6b57d2 --- /dev/null +++ b/apis/workorder/score.go @@ -0,0 +1,43 @@ +package workorder + +import ( + "fmt" + + "github.com/XingfenD/rainyun_api_go_sdk/apis/common" + "github.com/XingfenD/rainyun_api_go_sdk/constant" +) + +// 工单打分 +// +// id: 工单ID +func (s *WorkorderService) ScoreWorkorder(id int, req *ScoreWorkerorderRequest) (*common.BasicOperationResponse, error) { + path := fmt.Sprintf("/workorder/%d/score", id) + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_POST, path, nil, req, &resp) + return &resp, err +} + +// 编辑工单打分 +// +// id: 工单ID +func (s *WorkorderService) EditScoreWorkorder(id int, req *ScoreWorkerorderRequest) (*common.BasicOperationResponse, error) { + path := fmt.Sprintf("/workorder/%d/score", id) + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_PATCH, path, nil, req, &resp) + return &resp, err +} + +// 获取工单打分 +// +// id: 工单ID +// +// discussID: 客服回复ID +func (s *WorkorderService) GetScoreWorkorder(id int, discussID int) (*GetScoreWorkorderResponse, error) { + path := fmt.Sprintf("/workorder/%d/score/%d", id, discussID) + + var resp GetScoreWorkorderResponse + err := s.client.Do(constant.HTTPMethod_GET, path, nil, nil, &resp) + return &resp, err +} diff --git a/apis/workorder/workorder.go b/apis/workorder/workorder.go new file mode 100644 index 0000000..50f8167 --- /dev/null +++ b/apis/workorder/workorder.go @@ -0,0 +1,110 @@ +package workorder + +import ( + "fmt" + + "github.com/XingfenD/rainyun_api_go_sdk/apis/common" + "github.com/XingfenD/rainyun_api_go_sdk/constant" +) + +// 获取工单列表 +// +// req.Options 为分页查询参数, 可用 common.StandQueryParameters 构造 +func (s *WorkorderService) GetWorkOrderList(req *GetWorkOrderListRequest) (*GetWorkOrderListResponse, error) { + path := "/workorder/" + + var resp GetWorkOrderListResponse + querys, err := common.MarshalQueryParams(req) + if err != nil { + return nil, err + } + err = s.client.Do(constant.HTTPMethod_GET, path, querys, nil, &resp) + return &resp, err +} + +// 获取工单详情 +// +// id: 工单ID +func (s *WorkorderService) GetWorkOrderDetail(id int) (*GetWorkOrderDetailResponse, error) { + path := fmt.Sprintf("/workorder/%d", id) + + var resp GetWorkOrderDetailResponse + err := s.client.Do(constant.HTTPMethod_GET, path, nil, nil, &resp) + return &resp, err +} + +// 创建工单 +func (s *WorkorderService) CreateWorkOrder(req *CreateWorkerorderRequest) (*CreateWorkerorderResponse, error) { + path := "/workorder/" + + var resp CreateWorkerorderResponse + err := s.client.Do(constant.HTTPMethod_POST, path, nil, req, &resp) + return &resp, err +} + +// 产品授权 +// +// id:工单ID +// +// productID:产品ID +// +// productType:产品类型(rvh/rcs/rgs/rbm/ros) +func (s *WorkorderService) ProductAuth(id int, productID int, productType string) (*common.BasicOperationResponse, error) { + path := fmt.Sprintf("/workorder/%d/auth", id) + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_POST, path, nil, ProductAuthRequest{ProductID: productID, ProductType: productType}, &resp) + return &resp, err +} + +// 回复工单 +// +// id: 工单ID +// +// content: 回复内容 +func (s *WorkorderService) ReplyWorkorder(id int, content string) (*common.BasicOperationResponse, error) { + path := fmt.Sprintf("/workorder/%d/reply_order", id) + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_POST, path, nil, ReplyWorkerorderRequest{Content: content}, &resp) + return &resp, err +} + +// 编辑回复工单 +// +// id: 工单ID +// +// replyID: 回复ID +// +// content: 编辑后的内容 +func (s *WorkorderService) EditWorkorderReply(id int, replyID int, content string) (*common.BasicOperationResponse, error) { + path := fmt.Sprintf("/workorder/%d/reply_order/%d", id, replyID) + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_PATCH, path, nil, ReplyWorkerorderRequest{Content: content}, &resp) + return &resp, err +} + +// 获取工单状态 +// +// id: 工单ID +func (s *WorkorderService) GetWorkorderStatus(id int) (*GetWorkorderStatusResponse, error) { + path := fmt.Sprintf("/workorder/%d/status", id) + + var resp GetWorkorderStatusResponse + err := s.client.Do(constant.HTTPMethod_GET, path, nil, nil, &resp) + return &resp, err +} + +// 设置工单状态 +// +// id: 工单ID +// +// status: 目标状态 +func (s *WorkorderService) SetWorkorderStatus(id int, status string) (*common.BasicOperationResponse, error) { + path := fmt.Sprintf("/workorder/%d/status", id) + + var resp common.BasicOperationResponse + err := s.client.Do(constant.HTTPMethod_PATCH, path, nil, SetWorkorderStatusRequest{Status: status}, &resp) + return &resp, err +} diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index ff0b6f2..28a5f09 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -6,6 +6,18 @@ All notable changes to this template should be documented in this file. The format loosely follows Keep a Changelog and can be adapted to the team's habits. 本文档参考了 Keep a Changelog 的思路,也可以根据团队习惯调整。 +## [0.2.0] - 2026-08-05 + +### Added / 新增 + +- 迁移剩余全部模块:domain(24)、product(8)、rbm(10)、rca(21)、rgs(38)、ros(19)、ssl(5)、user(10)、workorder(11),并补全 public 剩余 5 个接口。 +- 全部响应结构体嵌套拆分命名复用;rgs 复用 rcs 的请求/响应类型。 +- `sdk.New` 注册全部 11 个服务。 + +### Fixed / 修复 + +- 修正源 SDK 中的路径错误(`{id}` 占位符、缺少 `/`、`/product/rcs/` 误写为 rgs 路径等)与参数传递错误(`resp` vs `&resp`、漏传 body)。 + ## [0.1.0] - 2026-08-05 ### Added / 新增 diff --git a/sdk/sdk.go b/sdk/sdk.go index b499c04..06998ed 100644 --- a/sdk/sdk.go +++ b/sdk/sdk.go @@ -2,19 +2,46 @@ package sdk import ( "github.com/XingfenD/rainyun_api_go_sdk/apis" + "github.com/XingfenD/rainyun_api_go_sdk/apis/domain" + "github.com/XingfenD/rainyun_api_go_sdk/apis/product" "github.com/XingfenD/rainyun_api_go_sdk/apis/public" + "github.com/XingfenD/rainyun_api_go_sdk/apis/rbm" + "github.com/XingfenD/rainyun_api_go_sdk/apis/rca" "github.com/XingfenD/rainyun_api_go_sdk/apis/rcs" + "github.com/XingfenD/rainyun_api_go_sdk/apis/rgs" + "github.com/XingfenD/rainyun_api_go_sdk/apis/ros" + "github.com/XingfenD/rainyun_api_go_sdk/apis/ssl" + "github.com/XingfenD/rainyun_api_go_sdk/apis/user" + "github.com/XingfenD/rainyun_api_go_sdk/apis/workorder" ) type RainyunSDK struct { public.PublicService + domain.DomainService + product.ProductService + rbm.RbmService + rca.RcaService rcs.RcsService + rgs.RgsService + ros.RosService + ssl.SslService + user.UserService + workorder.WorkorderService } func New(apiKey string) *RainyunSDK { c := apis.NewRyClient(apiKey) return &RainyunSDK{ - PublicService: *public.NewPublicService(c), - RcsService: *rcs.NewRcsService(c), + PublicService: *public.NewPublicService(c), + DomainService: *domain.NewDomainService(c), + ProductService: *product.NewProductService(c), + RbmService: *rbm.NewRbmService(c), + RcaService: *rca.NewRcaService(c), + RcsService: *rcs.NewRcsService(c), + RgsService: *rgs.NewRgsService(c), + RosService: *ros.NewRosService(c), + SslService: *ssl.NewSslService(c), + UserService: *user.NewUserService(c), + WorkorderService: *workorder.NewWorkorderService(c), } } From fcdacacd06f7e88576b5739d561c5ac0188a471c Mon Sep 17 00:00:00 2001 From: Fendy Date: Thu, 13 Aug 2026 00:21:15 +0800 Subject: [PATCH 03/17] chore: add cobra/toml/yaml deps for CLI migration --- go.mod | 12 ++++++++++-- go.sum | 20 ++++++++++++++++++-- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index bd7c99f..b4dff81 100644 --- a/go.mod +++ b/go.mod @@ -3,14 +3,22 @@ module github.com/XingfenD/rainyun_api_go_sdk go 1.24.1 require ( + github.com/bytedance/sonic v1.15.2 + github.com/go-resty/resty/v2 v2.17.2 +) + +require ( + github.com/BurntSushi/toml v1.6.0 // indirect github.com/bytedance/gopkg v0.1.3 // indirect - github.com/bytedance/sonic v1.15.2 // indirect github.com/bytedance/sonic/loader v0.5.1 // indirect github.com/cloudwego/base64x v0.1.6 // indirect - github.com/go-resty/resty/v2 v2.17.2 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/klauspost/cpuid/v2 v2.2.9 // indirect + github.com/spf13/cobra v1.10.2 // indirect + github.com/spf13/pflag v1.0.9 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect golang.org/x/net v0.43.0 // indirect golang.org/x/sys v0.35.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 1528c9f..b287b66 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk= +github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M= github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM= github.com/bytedance/sonic v1.15.2 h1:90H+rcF/FwLXwfB1cudOLq/je83n683Utf4Cbp0xHCo= @@ -6,13 +8,23 @@ github.com/bytedance/sonic/loader v0.5.1 h1:Ygpfa9zwRCCKSlrp5bBP/b/Xzc3VxsAW+5NI github.com/bytedance/sonic/loader v0.5.1/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo= github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M= github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-resty/resty/v2 v2.17.2 h1:FQW5oHYcIlkCNrMD2lloGScxcHJ0gkjshV3qcQAyHQk= github.com/go-resty/resty/v2 v2.17.2/go.mod h1:kCKZ3wWmwJaNc7S29BRtUhJwy7iqmn+2mLtQrOyQlVA= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY= github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= +github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= +github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY= +github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= @@ -20,16 +32,20 @@ github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/arch v0.0.0-20210923205945-b76863e36670 h1:18EFjUmQOcUvxNYSkA6jO9VAiXCnxFY6NyDX0bHDmkU= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= -golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= -golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= +golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From 402924470251619f3b0ae47f8d0775bd0457c0ce Mon Sep 17 00:00:00 2001 From: Fendy Date: Thu, 13 Aug 2026 00:22:53 +0800 Subject: [PATCH 04/17] feat(domain): add GetDomainList endpoint --- apis/domain/list.go | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 apis/domain/list.go diff --git a/apis/domain/list.go b/apis/domain/list.go new file mode 100644 index 0000000..0cc7736 --- /dev/null +++ b/apis/domain/list.go @@ -0,0 +1,44 @@ +package domain + +import ( + "github.com/XingfenD/rainyun_api_go_sdk/apis/common" + "github.com/XingfenD/rainyun_api_go_sdk/constant" +) + +// 获取域名列表查询参数 +type GetDomainListRequest struct { + Options common.StandQueryParameters `json:"options"` +} + +// 域名列表响应 +type GetDomainListResponse struct { + Code int `json:"code"` + Data DomainListData `json:"data"` +} + +// 域名列表数据 +type DomainListData struct { + TotalRecords int `json:"TotalRecords"` + Records []DomainRecord `json:"Records"` +} + +// 域名记录 +type DomainRecord struct { + ID int `json:"id"` + DomainName string `json:"domain_name"` + Status string `json:"status"` + ExpireDate string `json:"expire_date"` +} + +// 获取域名列表 +func (s *DomainService) GetDomainList(req *GetDomainListRequest) (*GetDomainListResponse, error) { + path := "/product/domain/" + + var resp GetDomainListResponse + querys, err := common.MarshalQueryParams(req) + if err != nil { + return nil, err + } + err = s.client.Do(constant.HTTPMethod_GET, path, querys, nil, &resp) + return &resp, err +} \ No newline at end of file From 5cf02155dc4bf2c8e189235d42d4f0c9137917f9 Mon Sep 17 00:00:00 2001 From: Fendy Date: Thu, 13 Aug 2026 00:24:20 +0800 Subject: [PATCH 05/17] feat(domain): add GetDomainDNSRecordList endpoint --- apis/domain/dns.go | 26 ++++++++++++++++++++++++++ apis/domain/list.go | 2 +- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/apis/domain/dns.go b/apis/domain/dns.go index 76c89ad..77b53e1 100644 --- a/apis/domain/dns.go +++ b/apis/domain/dns.go @@ -70,3 +70,29 @@ func (s *DomainService) SyncDomainDNSSEC(id int) (*common.BasicOperationResponse return &resp, err } + +// 获取域名DNS解析记录列表 +// +// domainID: 域名ID +func (s *DomainService) GetDomainDNSRecordList(domainID int) (*GetDomainDNSRecordListResponse, error) { + path := fmt.Sprintf("/product/domain/%d/dns", domainID) + + var resp GetDomainDNSRecordListResponse + err := s.client.Do(constant.HTTPMethod_GET, path, nil, nil, &resp) + return &resp, err +} + +// DNS 记录列表响应 +type GetDomainDNSRecordListResponse struct { + Code int `json:"code"` + Data []DomainDNSRecord `json:"data"` +} + +// 域名DNS解析记录 +type DomainDNSRecord struct { + ID int `json:"id"` + RecordType string `json:"record_type"` + Host string `json:"host"` + Value string `json:"value"` + TTL int `json:"ttl"` +} diff --git a/apis/domain/list.go b/apis/domain/list.go index 0cc7736..5908400 100644 --- a/apis/domain/list.go +++ b/apis/domain/list.go @@ -41,4 +41,4 @@ func (s *DomainService) GetDomainList(req *GetDomainListRequest) (*GetDomainList } err = s.client.Do(constant.HTTPMethod_GET, path, querys, nil, &resp) return &resp, err -} \ No newline at end of file +} From 7d33b2e7021ab358a27cdb4b58c6a9d322bb9a5f Mon Sep 17 00:00:00 2001 From: Fendy Date: Thu, 13 Aug 2026 00:26:13 +0800 Subject: [PATCH 06/17] feat(ros): add GetRosBucketListByInstance endpoint --- apis/ros/bucket.go | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/apis/ros/bucket.go b/apis/ros/bucket.go index dd5c1b9..b6e1155 100644 --- a/apis/ros/bucket.go +++ b/apis/ros/bucket.go @@ -194,3 +194,20 @@ func (s *RosService) SetRosBucketPublicAccess(bucketID int) (*common.BasicOperat err := s.client.Do(constant.HTTPMethod_POST, path, nil, nil, &resp) return &resp, err } + +// 按实例获取对象存储桶列表响应 +type GetRosBucketListByInstanceResponse struct { + Code int `json:"code"` + Data RosBucketList `json:"data"` +} + +// 按实例获取对象存储桶列表 +// +// instanceID: 实例ID +func (s *RosService) GetRosBucketListByInstance(instanceID int) (*GetRosBucketListByInstanceResponse, error) { + path := fmt.Sprintf("/product/ros/bucket/%d", instanceID) + + var resp GetRosBucketListByInstanceResponse + err := s.client.Do(constant.HTTPMethod_GET, path, nil, nil, &resp) + return &resp, err +} From 2ee9555506bdc88eb6fedd2101bbb5c8f6f9ed45 Mon Sep 17 00:00:00 2001 From: Fendy Date: Thu, 13 Aug 2026 00:28:14 +0800 Subject: [PATCH 07/17] feat(expense): add ExpenseService with GetOrdersList --- apis/expense/client.go | 11 +++++++++++ apis/expense/orders.go | 45 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+) create mode 100644 apis/expense/client.go create mode 100644 apis/expense/orders.go diff --git a/apis/expense/client.go b/apis/expense/client.go new file mode 100644 index 0000000..c9ce545 --- /dev/null +++ b/apis/expense/client.go @@ -0,0 +1,11 @@ +package expense + +import "github.com/XingfenD/rainyun_api_go_sdk/apis" + +type ExpenseService struct { + client *apis.RyClient +} + +func NewExpenseService(c *apis.RyClient) *ExpenseService { + return &ExpenseService{client: c} +} diff --git a/apis/expense/orders.go b/apis/expense/orders.go new file mode 100644 index 0000000..c1e33c6 --- /dev/null +++ b/apis/expense/orders.go @@ -0,0 +1,45 @@ +package expense + +import ( + "github.com/XingfenD/rainyun_api_go_sdk/apis/common" + "github.com/XingfenD/rainyun_api_go_sdk/constant" +) + +// 订单列表查询参数 +type GetOrdersListRequest struct { + Options common.StandQueryParameters `json:"options"` +} + +// 订单列表响应 +type GetOrdersListResponse struct { + Code int `json:"code"` + Data OrdersListData `json:"data"` +} + +// 订单列表数据 +type OrdersListData struct { + TotalRecords int `json:"TotalRecords"` + Records []Order `json:"Records"` +} + +// 订单 +type Order struct { + ID int `json:"id"` + ProductName string `json:"product_name"` + Amount float64 `json:"amount"` + Status string `json:"status"` + CreateDate string `json:"create_date"` +} + +// 获取订单列表 +func (s *ExpenseService) GetOrdersList(req *GetOrdersListRequest) (*GetOrdersListResponse, error) { + path := "/expense/orders/list" + + var resp GetOrdersListResponse + querys, err := common.MarshalQueryParams(req) + if err != nil { + return nil, err + } + err = s.client.Do(constant.HTTPMethod_GET, path, querys, nil, &resp) + return &resp, err +} From d7a7af7cca0d1b27af4cd84c81a5cae488b35c16 Mon Sep 17 00:00:00 2001 From: Fendy Date: Thu, 13 Aug 2026 00:29:48 +0800 Subject: [PATCH 08/17] feat(sdk): embed ExpenseService in RainyunSDK --- sdk/sdk.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sdk/sdk.go b/sdk/sdk.go index 06998ed..3872e6f 100644 --- a/sdk/sdk.go +++ b/sdk/sdk.go @@ -3,6 +3,7 @@ package sdk import ( "github.com/XingfenD/rainyun_api_go_sdk/apis" "github.com/XingfenD/rainyun_api_go_sdk/apis/domain" + "github.com/XingfenD/rainyun_api_go_sdk/apis/expense" "github.com/XingfenD/rainyun_api_go_sdk/apis/product" "github.com/XingfenD/rainyun_api_go_sdk/apis/public" "github.com/XingfenD/rainyun_api_go_sdk/apis/rbm" @@ -27,6 +28,7 @@ type RainyunSDK struct { ssl.SslService user.UserService workorder.WorkorderService + expense.ExpenseService } func New(apiKey string) *RainyunSDK { @@ -43,5 +45,6 @@ func New(apiKey string) *RainyunSDK { SslService: *ssl.NewSslService(c), UserService: *user.NewUserService(c), WorkorderService: *workorder.NewWorkorderService(c), + ExpenseService: *expense.NewExpenseService(c), } } From 16ccc8d3391f206831f159c5e982a360f32d2533 Mon Sep 17 00:00:00 2001 From: Fendy Date: Thu, 13 Aug 2026 00:31:11 +0800 Subject: [PATCH 09/17] feat(cmd/ry): add display model package --- cmd/ry/internal/model/billing.go | 11 +++++++++++ cmd/ry/internal/model/domain.go | 14 ++++++++++++++ cmd/ry/internal/model/server.go | 16 ++++++++++++++++ cmd/ry/internal/model/storage.go | 12 ++++++++++++ 4 files changed, 53 insertions(+) create mode 100644 cmd/ry/internal/model/billing.go create mode 100644 cmd/ry/internal/model/domain.go create mode 100644 cmd/ry/internal/model/server.go create mode 100644 cmd/ry/internal/model/storage.go diff --git a/cmd/ry/internal/model/billing.go b/cmd/ry/internal/model/billing.go new file mode 100644 index 0000000..98644c3 --- /dev/null +++ b/cmd/ry/internal/model/billing.go @@ -0,0 +1,11 @@ +package model + +import "time" + +type Order struct { + ID string `json:"id" table:"ID"` + Product string `json:"product" table:"PRODUCT"` + Amount float64 `json:"amount" table:"AMOUNT"` + Status string `json:"status" table:"STATUS"` + Created time.Time `json:"created" table:"CREATED"` +} diff --git a/cmd/ry/internal/model/domain.go b/cmd/ry/internal/model/domain.go new file mode 100644 index 0000000..8f551db --- /dev/null +++ b/cmd/ry/internal/model/domain.go @@ -0,0 +1,14 @@ +package model + +type Domain struct { + ID string `json:"id" table:"ID"` + Name string `json:"name" table:"DOMAIN"` +} + +type DNSRecord struct { + ID string `json:"id" table:"ID"` + Type string `json:"type" table:"TYPE"` + Name string `json:"name" table:"NAME"` + Value string `json:"value" table:"VALUE"` + TTL int `json:"ttl" table:"TTL"` +} diff --git a/cmd/ry/internal/model/server.go b/cmd/ry/internal/model/server.go new file mode 100644 index 0000000..36f22c9 --- /dev/null +++ b/cmd/ry/internal/model/server.go @@ -0,0 +1,16 @@ +package model + +import "time" + +type Server struct { + ID string `json:"id" table:"ID"` + Name string `json:"name" table:"NAME"` + Status string `json:"status" table:"STATUS"` + IP string `json:"ip" table:"IP"` + CPU int `json:"cpu" table:"CPU"` + Memory int `json:"memory" table:"MEM"` + Disk int `json:"disk" table:"DISK"` + OS string `json:"os" table:"OS"` + Region string `json:"region" table:"REGION"` + ExpireAt time.Time `json:"expires" table:"EXPIRES"` +} diff --git a/cmd/ry/internal/model/storage.go b/cmd/ry/internal/model/storage.go new file mode 100644 index 0000000..a4f6022 --- /dev/null +++ b/cmd/ry/internal/model/storage.go @@ -0,0 +1,12 @@ +package model + +type StorageInstance struct { + ID string `json:"id" table:"ID"` + Name string `json:"name" table:"NAME"` + Status string `json:"status" table:"STATUS"` +} + +type Bucket struct { + ID string `json:"id" table:"ID"` + Name string `json:"name" table:"NAME"` +} From c0f04c01b4766de7f9c3fc897cf898de4ee2f7fb Mon Sep 17 00:00:00 2001 From: Fendy Date: Thu, 13 Aug 2026 00:33:38 +0800 Subject: [PATCH 10/17] feat(cmd/ry): add output package with raw format fix --- cmd/ry/internal/output/json.go | 12 +++ cmd/ry/internal/output/output.go | 41 +++++++++ cmd/ry/internal/output/output_test.go | 106 ++++++++++++++++++++++++ cmd/ry/internal/output/table.go | 115 ++++++++++++++++++++++++++ cmd/ry/internal/output/yaml.go | 13 +++ 5 files changed, 287 insertions(+) create mode 100644 cmd/ry/internal/output/json.go create mode 100644 cmd/ry/internal/output/output.go create mode 100644 cmd/ry/internal/output/output_test.go create mode 100644 cmd/ry/internal/output/table.go create mode 100644 cmd/ry/internal/output/yaml.go diff --git a/cmd/ry/internal/output/json.go b/cmd/ry/internal/output/json.go new file mode 100644 index 0000000..6857e38 --- /dev/null +++ b/cmd/ry/internal/output/json.go @@ -0,0 +1,12 @@ +package output + +import ( + "encoding/json" + "io" +) + +func printJSON(w io.Writer, data any) error { + enc := json.NewEncoder(w) + enc.SetIndent("", " ") + return enc.Encode(data) +} diff --git a/cmd/ry/internal/output/output.go b/cmd/ry/internal/output/output.go new file mode 100644 index 0000000..d7307c6 --- /dev/null +++ b/cmd/ry/internal/output/output.go @@ -0,0 +1,41 @@ +package output + +import ( + "encoding/json" + "fmt" + "io" +) + +type Printer struct { + format string + writer io.Writer +} + +func New(format string, w io.Writer) *Printer { + return &Printer{format: format, writer: w} +} + +func (p *Printer) Print(data any) error { + switch p.format { + case "table": + return printTable(p.writer, data) + case "json": + return printJSON(p.writer, data) + case "yaml": + return printYAML(p.writer, data) + case "raw": + b, err := json.Marshal(data) + if err != nil { + return err + } + _, err = fmt.Fprintln(p.writer, string(b)) + return err + default: + return fmt.Errorf("unknown output format: %s", p.format) + } +} + +func (p *Printer) PrintRaw(raw []byte) error { + _, err := fmt.Fprintln(p.writer, string(raw)) + return err +} diff --git a/cmd/ry/internal/output/output_test.go b/cmd/ry/internal/output/output_test.go new file mode 100644 index 0000000..d4ee34b --- /dev/null +++ b/cmd/ry/internal/output/output_test.go @@ -0,0 +1,106 @@ +package output + +import ( + "bytes" + "encoding/json" + "strings" + "testing" +) + +type testItem struct { + ID string `json:"id" table:"ID"` + Name string `json:"name" table:"NAME"` + Age int `json:"age" table:"AGE"` +} + +func TestTableFormat(t *testing.T) { + items := []testItem{ + {ID: "1", Name: "Alice", Age: 30}, + {ID: "2", Name: "Bob", Age: 25}, + } + var buf bytes.Buffer + printer := New("table", &buf) + if err := printer.Print(items); err != nil { + t.Fatalf("Print table error: %v", err) + } + out := buf.String() + if !strings.Contains(out, "ID") || !strings.Contains(out, "Alice") { + t.Errorf("table output missing headers or data:\n%s", out) + } +} + +func TestJSONFormat(t *testing.T) { + items := []testItem{{ID: "1", Name: "Alice", Age: 30}} + var buf bytes.Buffer + printer := New("json", &buf) + if err := printer.Print(items); err != nil { + t.Fatalf("Print json error: %v", err) + } + var parsed []testItem + if err := json.Unmarshal(buf.Bytes(), &parsed); err != nil { + t.Fatalf("invalid JSON output: %v", err) + } + if len(parsed) != 1 || parsed[0].ID != "1" { + t.Errorf("JSON content mismatch: %+v", parsed) + } +} + +func TestYAMLFormat(t *testing.T) { + items := []testItem{{ID: "1", Name: "Alice", Age: 30}} + var buf bytes.Buffer + printer := New("yaml", &buf) + if err := printer.Print(items); err != nil { + t.Fatalf("Print yaml error: %v", err) + } + out := buf.String() + if !strings.Contains(out, "id:") || !strings.Contains(out, "Alice") { + t.Errorf("yaml output missing data:\n%s", out) + } +} + +func TestRawFormat(t *testing.T) { + raw := []byte(`{"raw":"data"}`) + var buf bytes.Buffer + printer := New("raw", &buf) + if err := printer.PrintRaw(raw); err != nil { + t.Fatalf("PrintRaw error: %v", err) + } + if buf.String() != `{"raw":"data"}`+"\n" { + t.Errorf("raw output = %q", buf.String()) + } +} + +// 新增:Print 在 raw 格式下应输出 JSON +func TestRawPrintFormat(t *testing.T) { + items := []testItem{{ID: "1", Name: "Alice", Age: 30}} + var buf bytes.Buffer + printer := New("raw", &buf) + if err := printer.Print(items); err != nil { + t.Fatalf("Print raw error: %v", err) + } + var parsed []testItem + if err := json.Unmarshal(buf.Bytes(), &parsed); err != nil { + t.Fatalf("raw Print produced invalid JSON: %v\nbuf=%s", err, buf.String()) + } +} + +func TestSingleItem(t *testing.T) { + item := testItem{ID: "1", Name: "Solo", Age: 99} + var buf bytes.Buffer + printer := New("json", &buf) + if err := printer.Print(item); err != nil { + t.Fatalf("Print single item error: %v", err) + } + if !strings.Contains(buf.String(), "Solo") { + t.Errorf("single item output missing data:\n%s", buf.String()) + } +} + +func TestUnknownFormat(t *testing.T) { + var buf bytes.Buffer + printer := New("unknown", &buf) + err := printer.Print([]testItem{}) + if err == nil { + t.Error("expected error for unknown format") + } +} diff --git a/cmd/ry/internal/output/table.go b/cmd/ry/internal/output/table.go new file mode 100644 index 0000000..11607cd --- /dev/null +++ b/cmd/ry/internal/output/table.go @@ -0,0 +1,115 @@ +package output + +import ( + "fmt" + "io" + "reflect" + "strings" +) + +func printTable(w io.Writer, data any) error { + v := reflect.ValueOf(data) + if v.Kind() == reflect.Ptr { + v = v.Elem() + } + if v.Kind() == reflect.Slice { + return printTableSlice(w, v) + } + return printTableSingle(w, v) +} + +func printTableSlice(w io.Writer, v reflect.Value) error { + if v.Len() == 0 { + fmt.Fprintln(w, "(empty)") + return nil + } + elemType := v.Index(0).Type() + if elemType.Kind() == reflect.Ptr { + elemType = elemType.Elem() + } + headers, cols := extractTableTags(elemType) + if len(headers) == 0 { + return fmt.Errorf("no table tags found on type %s", elemType.Name()) + } + + rows := make([][]string, v.Len()) + for i := 0; i < v.Len(); i++ { + rows[i] = extractRow(v.Index(i), cols) + } + + widths := make([]int, len(headers)) + for i, h := range headers { + widths[i] = len(h) + } + for _, row := range rows { + for i, cell := range row { + if len(cell) > widths[i] { + widths[i] = len(cell) + } + } + } + + sep := " " + renderRow(w, headers, widths, sep) + + separator := make([]string, len(headers)) + for i, ww := range widths { + separator[i] = strings.Repeat("-", ww) + } + renderRow(w, separator, widths, sep) + + for _, row := range rows { + renderRow(w, row, widths, sep) + } + + return nil +} + +func renderRow(w io.Writer, cells []string, widths []int, sep string) { + parts := make([]string, len(cells)) + for i, cell := range cells { + parts[i] = fmt.Sprintf("%-*s", widths[i], cell) + } + fmt.Fprintln(w, strings.Join(parts, sep)) +} + +func printTableSingle(w io.Writer, v reflect.Value) error { + if v.Kind() == reflect.Ptr { + v = v.Elem() + } + t := v.Type() + for i := 0; i < t.NumField(); i++ { + tag := t.Field(i).Tag.Get("table") + if tag == "" { + continue + } + val := fmt.Sprintf("%v", v.Field(i).Interface()) + fmt.Fprintf(w, "%-10s %s\n", tag+":", val) + } + return nil +} + +func extractTableTags(t reflect.Type) ([]string, []int) { + var headers []string + var cols []int + for i := 0; i < t.NumField(); i++ { + tag := t.Field(i).Tag.Get("table") + if tag == "" { + continue + } + headers = append(headers, strings.ToUpper(tag)) + cols = append(cols, i) + } + return headers, cols +} + +func extractRow(v reflect.Value, cols []int) []string { + if v.Kind() == reflect.Ptr { + v = v.Elem() + } + row := make([]string, len(cols)) + for i, idx := range cols { + row[i] = fmt.Sprintf("%v", v.Field(idx).Interface()) + } + return row +} diff --git a/cmd/ry/internal/output/yaml.go b/cmd/ry/internal/output/yaml.go new file mode 100644 index 0000000..ac43285 --- /dev/null +++ b/cmd/ry/internal/output/yaml.go @@ -0,0 +1,13 @@ +package output + +import ( + "io" + + "gopkg.in/yaml.v3" +) + +func printYAML(w io.Writer, data any) error { + enc := yaml.NewEncoder(w) + enc.SetIndent(2) + return enc.Encode(data) +} From 499b4789fbbc4cb6fd597d186c36f47d38c52631 Mon Sep 17 00:00:00 2001 From: Fendy Date: Thu, 13 Aug 2026 00:35:30 +0800 Subject: [PATCH 11/17] fix(cmd/ry): strengthen TestRawPrintFormat assertions --- cmd/ry/internal/output/output_test.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cmd/ry/internal/output/output_test.go b/cmd/ry/internal/output/output_test.go index d4ee34b..d578e4e 100644 --- a/cmd/ry/internal/output/output_test.go +++ b/cmd/ry/internal/output/output_test.go @@ -82,6 +82,9 @@ func TestRawPrintFormat(t *testing.T) { if err := json.Unmarshal(buf.Bytes(), &parsed); err != nil { t.Fatalf("raw Print produced invalid JSON: %v\nbuf=%s", err, buf.String()) } + if len(parsed) != 1 || parsed[0].ID != "1" { + t.Errorf("raw Print content mismatch: %+v", parsed) + } } func TestSingleItem(t *testing.T) { From b15e78fd4d469fc72157299a2268b54830d25ca5 Mon Sep 17 00:00:00 2001 From: Fendy Date: Thu, 13 Aug 2026 00:37:15 +0800 Subject: [PATCH 12/17] feat(cmd/ry): add simplified single-provider config --- cmd/ry/internal/config/config.go | 53 ++++++++++++++++ cmd/ry/internal/config/config_test.go | 91 +++++++++++++++++++++++++++ 2 files changed, 144 insertions(+) create mode 100644 cmd/ry/internal/config/config.go create mode 100644 cmd/ry/internal/config/config_test.go diff --git a/cmd/ry/internal/config/config.go b/cmd/ry/internal/config/config.go new file mode 100644 index 0000000..6155a49 --- /dev/null +++ b/cmd/ry/internal/config/config.go @@ -0,0 +1,53 @@ +package config + +import ( + "os" + "path/filepath" + + "github.com/BurntSushi/toml" +) + +type Config struct { + APIKey string `toml:"api_key,omitempty"` + Output string `toml:"output"` +} + +func Load(path string) (*Config, error) { + cfg := &Config{Output: "table"} + data, err := os.ReadFile(path) + if err != nil { + if os.IsNotExist(err) { + return cfg, nil + } + return nil, err + } + if err := toml.Unmarshal(data, cfg); err != nil { + return nil, err + } + if cfg.Output == "" { + cfg.Output = "table" + } + return cfg, nil +} + +func Save(path string, cfg *Config) error { + dir := filepath.Dir(path) + if err := os.MkdirAll(dir, 0700); err != nil { + return err + } + f, err := os.Create(path) + if err != nil { + return err + } + defer f.Close() + enc := toml.NewEncoder(f) + return enc.Encode(cfg) +} + +func DefaultPath() string { + home, err := os.UserHomeDir() + if err != nil { + return "config.toml" + } + return filepath.Join(home, ".config", "ry", "config.toml") +} diff --git a/cmd/ry/internal/config/config_test.go b/cmd/ry/internal/config/config_test.go new file mode 100644 index 0000000..be97eb5 --- /dev/null +++ b/cmd/ry/internal/config/config_test.go @@ -0,0 +1,91 @@ +package config + +import ( + "os" + "path/filepath" + "testing" +) + +func TestLoadTOML(t *testing.T) { + dir := t.TempDir() + path := filepath.Join(dir, "config.toml") + content := ` +api_key = "sk-test123" +output = "json" +` + if err := os.WriteFile(path, []byte(content), 0600); err != nil { + t.Fatal(err) + } + cfg, err := Load(path) + if err != nil { + t.Fatalf("Load() error: %v", err) + } + if cfg.APIKey != "sk-test123" { + t.Errorf("APIKey = %q, want %q", cfg.APIKey, "sk-test123") + } + if cfg.Output != "json" { + t.Errorf("Output = %q, want %q", cfg.Output, "json") + } +} + +func TestLoadDefaults(t *testing.T) { + dir := t.TempDir() + path := filepath.Join(dir, "empty.toml") + if err := os.WriteFile(path, []byte(""), 0600); err != nil { + t.Fatal(err) + } + cfg, err := Load(path) + if err != nil { + t.Fatalf("Load() error: %v", err) + } + if cfg.Output != "table" { + t.Errorf("default Output = %q, want %q", cfg.Output, "table") + } +} + +func TestLoadMissingFile(t *testing.T) { + dir := t.TempDir() + path := filepath.Join(dir, "missing.toml") + cfg, err := Load(path) + if err != nil { + t.Fatalf("Load() missing file error: %v", err) + } + if cfg.Output != "table" { + t.Errorf("default Output = %q, want %q", cfg.Output, "table") + } +} + +func TestSave(t *testing.T) { + dir := t.TempDir() + path := filepath.Join(dir, "out.toml") + cfg := &Config{APIKey: "secret", Output: "json"} + if err := Save(path, cfg); err != nil { + t.Fatalf("Save() error: %v", err) + } + loaded, err := Load(path) + if err != nil { + t.Fatalf("re-Load() error: %v", err) + } + if loaded.APIKey != "secret" { + t.Errorf("roundtrip APIKey = %q", loaded.APIKey) + } + if loaded.Output != "json" { + t.Errorf("roundtrip Output = %q", loaded.Output) + } +} + +func TestDefaultPath(t *testing.T) { + path := DefaultPath() + if path == "" { + t.Error("DefaultPath() returned empty") + } +} + +func TestEnsureDir(t *testing.T) { + dir := filepath.Join(t.TempDir(), "sub", "dir") + path := filepath.Join(dir, "config.toml") + cfg := &Config{Output: "table"} + if err := Save(path, cfg); err != nil { + t.Fatalf("Save() to deep dir error: %v", err) + } +} From bd7b87d9c7cbc6378d3d670b5a7766b133beb5f0 Mon Sep 17 00:00:00 2001 From: Fendy Date: Thu, 13 Aug 2026 00:48:28 +0800 Subject: [PATCH 13/17] feat(cmd/ry): migrate rainyun-cli to SDK-backed cmd/ry MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 删除 CLI 自带 provider/HTTP 层,全部改调 sdk.New(apiKey) - config 简化为单一 apikey + output - 补 SDK 4 个缺口端点(domain list / dns list / bucket-by-instance / expense orders) - 修 output --raw bug(新增 raw case) - 命令 ID 参数统一 int,显示 model ID 保持 string - reinstall flag 从 --os 改 --os-id int --- cmd/ry/commands/billing/billing.go | 50 +++++++ cmd/ry/commands/configcmd/config.go | 75 ++++++++++ cmd/ry/commands/domain/domain.go | 138 +++++++++++++++++++ cmd/ry/commands/server/server.go | 205 ++++++++++++++++++++++++++++ cmd/ry/commands/storage/storage.go | 100 ++++++++++++++ cmd/ry/main.go | 13 ++ cmd/ry/root.go | 110 +++++++++++++++ docs/CHANGELOG.md | 19 +++ 8 files changed, 710 insertions(+) create mode 100644 cmd/ry/commands/billing/billing.go create mode 100644 cmd/ry/commands/configcmd/config.go create mode 100644 cmd/ry/commands/domain/domain.go create mode 100644 cmd/ry/commands/server/server.go create mode 100644 cmd/ry/commands/storage/storage.go create mode 100644 cmd/ry/main.go create mode 100644 cmd/ry/root.go diff --git a/cmd/ry/commands/billing/billing.go b/cmd/ry/commands/billing/billing.go new file mode 100644 index 0000000..b0b6c10 --- /dev/null +++ b/cmd/ry/commands/billing/billing.go @@ -0,0 +1,50 @@ +package billing + +import ( + "strconv" + "time" + + "github.com/XingfenD/rainyun_api_go_sdk/apis/common" + "github.com/XingfenD/rainyun_api_go_sdk/apis/expense" + "github.com/XingfenD/rainyun_api_go_sdk/cmd/ry/internal/model" + "github.com/XingfenD/rainyun_api_go_sdk/cmd/ry/internal/output" + "github.com/XingfenD/rainyun_api_go_sdk/sdk" + + "github.com/spf13/cobra" +) + +func Cmd(rySDK **sdk.RainyunSDK, out **output.Printer) *cobra.Command { + billingCmd := &cobra.Command{ + Use: "billing", + Short: "Billing and orders", + } + + ordersCmd := &cobra.Command{ + Use: "orders", + Short: "List orders", + RunE: func(cmd *cobra.Command, args []string) error { + resp, err := (*rySDK).GetOrdersList(&expense.GetOrdersListRequest{ + Options: common.StandQueryParameters{Page: 1, PerPage: 50}, + }) + if err != nil { + return err + } + orders := make([]model.Order, len(resp.Data.Records)) + for i, r := range resp.Data.Records { + t, _ := time.Parse("2006-01-02 15:04:05", r.CreateDate) + orders[i] = model.Order{ + ID: strconv.Itoa(r.ID), + Product: r.ProductName, + Amount: r.Amount, + Status: r.Status, + Created: t, + } + } + return (*out).Print(orders) + }, + } + + billingCmd.AddCommand(ordersCmd) + + return billingCmd +} diff --git a/cmd/ry/commands/configcmd/config.go b/cmd/ry/commands/configcmd/config.go new file mode 100644 index 0000000..52c5271 --- /dev/null +++ b/cmd/ry/commands/configcmd/config.go @@ -0,0 +1,75 @@ +package configcmd + +import ( + "fmt" + "strings" + + "github.com/XingfenD/rainyun_api_go_sdk/cmd/ry/internal/config" + + "github.com/spf13/cobra" +) + +func Cmd(cfg **config.Config, cfgPath string) *cobra.Command { + configCmd := &cobra.Command{ + Use: "config", + Short: "Manage ry configuration", + Annotations: map[string]string{"skipSDK": "true"}, + } + + configShowCmd := &cobra.Command{ + Use: "show", + Short: "Show current configuration", + Annotations: map[string]string{"skipSDK": "true"}, + RunE: func(cmd *cobra.Command, args []string) error { + c := *cfg + fmt.Printf("Config file: %s\n", cfgPath) + masked := c.APIKey + if len(masked) > 4 { + masked = masked[:2] + strings.Repeat("*", len(masked)-4) + masked[len(masked)-2:] + } + fmt.Printf("API key: %s\n", masked) + fmt.Printf("Output: %s\n", c.Output) + return nil + }, + } + + configSetCmd := &cobra.Command{ + Use: "set ", + Short: "Set a config value", + Args: cobra.ExactArgs(2), + Annotations: map[string]string{"skipSDK": "true"}, + RunE: func(cmd *cobra.Command, args []string) error { + key, value := args[0], args[1] + c := *cfg + switch key { + case "apikey": + c.APIKey = value + case "output": + c.Output = value + default: + return fmt.Errorf("unknown config key: %s (supported: apikey, output)", key) + } + if err := config.Save(cfgPath, c); err != nil { + return fmt.Errorf("save config: %w", err) + } + fmt.Printf("Set %s = %s\n", key, value) + return nil + }, + } + + configPathCmd := &cobra.Command{ + Use: "path", + Short: "Print config file path", + Annotations: map[string]string{"skipSDK": "true"}, + RunE: func(cmd *cobra.Command, args []string) error { + fmt.Println(cfgPath) + return nil + }, + } + + configCmd.AddCommand(configShowCmd) + configCmd.AddCommand(configSetCmd) + configCmd.AddCommand(configPathCmd) + + return configCmd +} diff --git a/cmd/ry/commands/domain/domain.go b/cmd/ry/commands/domain/domain.go new file mode 100644 index 0000000..87baaf6 --- /dev/null +++ b/cmd/ry/commands/domain/domain.go @@ -0,0 +1,138 @@ +package domain + +import ( + "fmt" + "strconv" + + "github.com/XingfenD/rainyun_api_go_sdk/apis/common" + "github.com/XingfenD/rainyun_api_go_sdk/apis/domain" + "github.com/XingfenD/rainyun_api_go_sdk/cmd/ry/internal/model" + "github.com/XingfenD/rainyun_api_go_sdk/cmd/ry/internal/output" + "github.com/XingfenD/rainyun_api_go_sdk/sdk" + + "github.com/spf13/cobra" +) + +func Cmd(rySDK **sdk.RainyunSDK, out **output.Printer) *cobra.Command { + domainCmd := &cobra.Command{ + Use: "domain", + Short: "Manage domains", + } + + domainListCmd := &cobra.Command{ + Use: "list", + Short: "List domains", + RunE: func(cmd *cobra.Command, args []string) error { + resp, err := (*rySDK).GetDomainList(&domain.GetDomainListRequest{ + Options: common.StandQueryParameters{Page: 1, PerPage: 50}, + }) + if err != nil { + return err + } + domains := make([]model.Domain, len(resp.Data.Records)) + for i, r := range resp.Data.Records { + domains[i] = model.Domain{ + ID: strconv.Itoa(r.ID), + Name: r.DomainName, + } + } + return (*out).Print(domains) + }, + } + + dnsCmd := &cobra.Command{ + Use: "dns ", + Short: "Manage DNS records", + Args: cobra.ExactArgs(1), + } + + dnsListCmd := &cobra.Command{ + Use: "list", + Short: "List DNS records", + RunE: func(cmd *cobra.Command, args []string) error { + id, err := strconv.Atoi(args[0]) + if err != nil { + return fmt.Errorf("invalid domain id: %s", args[0]) + } + resp, err := (*rySDK).GetDomainDNSRecordList(id) + if err != nil { + return err + } + records := make([]model.DNSRecord, len(resp.Data)) + for i, r := range resp.Data { + records[i] = model.DNSRecord{ + ID: strconv.Itoa(r.ID), + Type: r.RecordType, + Name: r.Host, + Value: r.Value, + TTL: r.TTL, + } + } + return (*out).Print(records) + }, + } + + dnsAddCmd := &cobra.Command{ + Use: "add ", + Short: "Add a DNS record", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + id, err := strconv.Atoi(args[0]) + if err != nil { + return fmt.Errorf("invalid domain id: %s", args[0]) + } + recType, _ := cmd.Flags().GetString("type") + host, _ := cmd.Flags().GetString("host") + value, _ := cmd.Flags().GetString("value") + ttl, _ := cmd.Flags().GetInt("ttl") + req := &domain.AddDomainDNSRecordRequest{ + Type: recType, + Host: host, + Value: value, + TTL: ttl, + Line: "DEFAULT", + Level: 10, + } + if _, err := (*rySDK).AddDomainDNSRecord(id, req); err != nil { + return err + } + fmt.Printf("DNS record added for domain %s\n", args[0]) + return nil + }, + } + + dnsDeleteCmd := &cobra.Command{ + Use: "delete ", + Short: "Delete a DNS record", + Args: cobra.ExactArgs(2), + RunE: func(cmd *cobra.Command, args []string) error { + domainID, err := strconv.Atoi(args[0]) + if err != nil { + return fmt.Errorf("invalid domain id: %s", args[0]) + } + recordID, err := strconv.Atoi(args[1]) + if err != nil { + return fmt.Errorf("invalid record id: %s", args[1]) + } + if _, err := (*rySDK).DeleteDomainDNSRecord(domainID, recordID); err != nil { + return err + } + fmt.Printf("DNS record %s deleted for domain %s\n", args[1], args[0]) + return nil + }, + } + + dnsAddCmd.Flags().String("type", "A", "Record type (A, AAAA, CNAME, TXT, MX, SRV)") + dnsAddCmd.Flags().String("host", "@", "Record host name") + dnsAddCmd.Flags().String("value", "", "Record value") + dnsAddCmd.Flags().Int("ttl", 600, "TTL in seconds") + dnsAddCmd.MarkFlagRequired("value") + + dnsCmd.AddCommand(dnsListCmd) + dnsCmd.AddCommand(dnsAddCmd) + dnsCmd.AddCommand(dnsDeleteCmd) + domainCmd.AddCommand(domainListCmd) + domainCmd.AddCommand(dnsCmd) + + return domainCmd +} diff --git a/cmd/ry/commands/server/server.go b/cmd/ry/commands/server/server.go new file mode 100644 index 0000000..eb31a20 --- /dev/null +++ b/cmd/ry/commands/server/server.go @@ -0,0 +1,205 @@ +package server + +import ( + "fmt" + "strconv" + "time" + + "github.com/XingfenD/rainyun_api_go_sdk/apis/common" + "github.com/XingfenD/rainyun_api_go_sdk/apis/rcs" + "github.com/XingfenD/rainyun_api_go_sdk/cmd/ry/internal/model" + "github.com/XingfenD/rainyun_api_go_sdk/cmd/ry/internal/output" + "github.com/XingfenD/rainyun_api_go_sdk/sdk" + + "github.com/spf13/cobra" +) + +func Cmd(rySDK **sdk.RainyunSDK, out **output.Printer, raw *bool) *cobra.Command { + serverCmd := &cobra.Command{ + Use: "server", + Short: "Manage cloud servers", + } + + serverListCmd := &cobra.Command{ + Use: "list", + Short: "List servers", + RunE: func(cmd *cobra.Command, args []string) error { + resp, err := (*rySDK).GetRcsList(&rcs.GetRcsListRequest{ + Options: common.StandQueryParameters{Page: 1, PerPage: 50}, + }) + if err != nil { + return err + } + servers := make([]model.Server, len(resp.Data.Records)) + for i, r := range resp.Data.Records { + servers[i] = toServer(r) + } + if *raw { + return (*out).Print(resp) + } + return (*out).Print(servers) + }, + } + + serverGetCmd := &cobra.Command{ + Use: "get ", + Short: "Get server details", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + id, err := strconv.Atoi(args[0]) + if err != nil { + return fmt.Errorf("invalid server id: %s", args[0]) + } + resp, err := (*rySDK).GetRcsDetail(id) + if err != nil { + return err + } + s := toServer(resp.Data.Data) + if *raw { + return (*out).Print(resp) + } + return (*out).Print(s) + }, + } + + serverStartCmd := &cobra.Command{ + Use: "start ", + Short: "Start a server", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + id, err := strconv.Atoi(args[0]) + if err != nil { + return fmt.Errorf("invalid server id: %s", args[0]) + } + if _, err := (*rySDK).StartRcs(id); err != nil { + return err + } + fmt.Printf("Server %s started\n", args[0]) + return nil + }, + } + + serverStopCmd := &cobra.Command{ + Use: "stop ", + Short: "Stop a server", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + id, err := strconv.Atoi(args[0]) + if err != nil { + return fmt.Errorf("invalid server id: %s", args[0]) + } + if _, err := (*rySDK).StopRcs(id); err != nil { + return err + } + fmt.Printf("Server %s stopped\n", args[0]) + return nil + }, + } + + serverRebootCmd := &cobra.Command{ + Use: "reboot ", + Short: "Reboot a server", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + id, err := strconv.Atoi(args[0]) + if err != nil { + return fmt.Errorf("invalid server id: %s", args[0]) + } + if _, err := (*rySDK).RebootRcs(id); err != nil { + return err + } + fmt.Printf("Server %s rebooted\n", args[0]) + return nil + }, + } + + serverReinstallCmd := &cobra.Command{ + Use: "reinstall ", + Short: "Reinstall server OS", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + id, err := strconv.Atoi(args[0]) + if err != nil { + return fmt.Errorf("invalid server id: %s", args[0]) + } + osID, _ := cmd.Flags().GetInt("os-id") + if osID == 0 { + return fmt.Errorf("--os-id flag is required (e.g. --os-id 123)") + } + if _, err := (*rySDK).ReinstallRcs(id, &rcs.ReinstallRcsRequest{OsID: osID}); err != nil { + return err + } + fmt.Printf("Server %s reinstalling with os-id %d\n", args[0], osID) + return nil + }, + } + + serverResetPasswordCmd := &cobra.Command{ + Use: "reset-password ", + Short: "Reset server password", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + id, err := strconv.Atoi(args[0]) + if err != nil { + return fmt.Errorf("invalid server id: %s", args[0]) + } + if _, err := (*rySDK).ResetRcsPassword(id, ""); err != nil { + return err + } + fmt.Printf("Password reset for server %s\n", args[0]) + return nil + }, + } + + serverVNCCmd := &cobra.Command{ + Use: "vnc ", + Short: "Get VNC connection URL", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + id, err := strconv.Atoi(args[0]) + if err != nil { + return fmt.Errorf("invalid server id: %s", args[0]) + } + v, err := (*rySDK).GetRcsVnc(id, "novnc") + if err != nil { + return err + } + url, uerr := common.GetVncConnectURL(v) + if uerr != nil || url == "" { + fmt.Println(v.Data.VNCProxyURL) + } else { + fmt.Println(url) + } + return nil + }, + } + + serverReinstallCmd.Flags().Int("os-id", 0, "OS template ID (required)") + serverReinstallCmd.MarkFlagRequired("os-id") + + serverCmd.AddCommand(serverListCmd) + serverCmd.AddCommand(serverGetCmd) + serverCmd.AddCommand(serverStartCmd) + serverCmd.AddCommand(serverStopCmd) + serverCmd.AddCommand(serverRebootCmd) + serverCmd.AddCommand(serverReinstallCmd) + serverCmd.AddCommand(serverResetPasswordCmd) + serverCmd.AddCommand(serverVNCCmd) + + return serverCmd +} + +func toServer(r rcs.RcsRecord) model.Server { + return model.Server{ + ID: strconv.Itoa(r.ID), + Name: r.HostName, + Status: r.Status, + IP: r.MainIPv4, + CPU: r.CPU, + Memory: r.Memory, + Disk: r.Disk, + OS: r.OsName, + Region: r.Zone, + ExpireAt: time.Unix(int64(r.ExpDate), 0), + } +} diff --git a/cmd/ry/commands/storage/storage.go b/cmd/ry/commands/storage/storage.go new file mode 100644 index 0000000..19c0114 --- /dev/null +++ b/cmd/ry/commands/storage/storage.go @@ -0,0 +1,100 @@ +package storage + +import ( + "fmt" + "strconv" + + "github.com/XingfenD/rainyun_api_go_sdk/apis/common" + "github.com/XingfenD/rainyun_api_go_sdk/apis/ros" + "github.com/XingfenD/rainyun_api_go_sdk/cmd/ry/internal/model" + "github.com/XingfenD/rainyun_api_go_sdk/cmd/ry/internal/output" + "github.com/XingfenD/rainyun_api_go_sdk/sdk" + + "github.com/spf13/cobra" +) + +func Cmd(rySDK **sdk.RainyunSDK, out **output.Printer) *cobra.Command { + storageCmd := &cobra.Command{ + Use: "storage", + Short: "Manage object storage", + } + + storageListCmd := &cobra.Command{ + Use: "list", + Short: "List storage instances", + RunE: func(cmd *cobra.Command, args []string) error { + resp, err := (*rySDK).GetRosInstanceList(&ros.GetRosInstanceListRequest{ + Options: common.StandQueryParameters{Page: 1, PerPage: 50}, + }) + if err != nil { + return err + } + instances := make([]model.StorageInstance, len(resp.Data.Records)) + for i, r := range resp.Data.Records { + instances[i] = model.StorageInstance{ + ID: strconv.Itoa(r.ID), + Name: r.Tag, + Status: r.Status, + } + } + return (*out).Print(instances) + }, + } + + bucketCmd := &cobra.Command{ + Use: "bucket ", + Short: "Manage buckets", + Args: cobra.ExactArgs(1), + } + + bucketListCmd := &cobra.Command{ + Use: "list", + Short: "List buckets", + RunE: func(cmd *cobra.Command, args []string) error { + instanceID, err := strconv.Atoi(args[0]) + if err != nil { + return fmt.Errorf("invalid instance id: %s", args[0]) + } + resp, err := (*rySDK).GetRosBucketListByInstance(instanceID) + if err != nil { + return err + } + buckets := make([]model.Bucket, len(resp.Data.Records)) + for i, b := range resp.Data.Records { + buckets[i] = model.Bucket{ + ID: strconv.Itoa(b.ID), + Name: b.Name, + } + } + return (*out).Print(buckets) + }, + } + + bucketCreateCmd := &cobra.Command{ + Use: "create ", + Short: "Create a bucket", + Args: cobra.ExactArgs(2), + RunE: func(cmd *cobra.Command, args []string) error { + instanceID, err := strconv.Atoi(args[0]) + if err != nil { + return fmt.Errorf("invalid instance id: %s", args[0]) + } + name := args[1] + if _, err := (*rySDK).CreateRosBucket(ros.CreateRosBucketRequest{ + BucketName: name, + InstanceID: instanceID, + }); err != nil { + return err + } + fmt.Printf("Bucket %q created\n", name) + return nil + }, + } + + bucketCmd.AddCommand(bucketListCmd) + bucketCmd.AddCommand(bucketCreateCmd) + storageCmd.AddCommand(storageListCmd) + storageCmd.AddCommand(bucketCmd) + + return storageCmd +} diff --git a/cmd/ry/main.go b/cmd/ry/main.go new file mode 100644 index 0000000..a5a50d9 --- /dev/null +++ b/cmd/ry/main.go @@ -0,0 +1,13 @@ +package main + +import ( + "fmt" + "os" +) + +func main() { + if err := rootCmd.Execute(); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } +} diff --git a/cmd/ry/root.go b/cmd/ry/root.go new file mode 100644 index 0000000..c6155ca --- /dev/null +++ b/cmd/ry/root.go @@ -0,0 +1,110 @@ +package main + +import ( + "fmt" + "os" + + "github.com/XingfenD/rainyun_api_go_sdk/cmd/ry/commands/billing" + "github.com/XingfenD/rainyun_api_go_sdk/cmd/ry/commands/configcmd" + "github.com/XingfenD/rainyun_api_go_sdk/cmd/ry/commands/domain" + "github.com/XingfenD/rainyun_api_go_sdk/cmd/ry/commands/server" + "github.com/XingfenD/rainyun_api_go_sdk/cmd/ry/commands/storage" + "github.com/XingfenD/rainyun_api_go_sdk/cmd/ry/internal/config" + "github.com/XingfenD/rainyun_api_go_sdk/cmd/ry/internal/output" + "github.com/XingfenD/rainyun_api_go_sdk/sdk" + + "github.com/spf13/cobra" +) + +var ( + cfg *config.Config + cfgPath string + rySDK *sdk.RainyunSDK + out *output.Printer + flagAPIKey string + flagOutput string + flagRaw bool + flagVerbose bool +) + +var rootCmd = &cobra.Command{ + Use: "ry", + Short: "Rainyun CLI — manage cloud resources from your terminal", + Long: `ry is a CLI that lets you manage Rainyun cloud resources from your terminal. + +Use "ry [command] --help" for more information about a command.`, + RunE: func(cmd *cobra.Command, args []string) error { + return cmd.Help() + }, + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + var err error + cfg, err = config.Load(cfgPath) + if err != nil { + return fmt.Errorf("load config: %w", err) + } + + if cmd.Annotations != nil && cmd.Annotations["skipSDK"] == "true" { + return nil + } + + apiKey := cfg.APIKey + if flagAPIKey != "" { + apiKey = flagAPIKey + } + if env := os.Getenv("RY_APIKEY"); env != "" { + apiKey = env + } + if apiKey == "" { + return fmt.Errorf("no api key configured — run 'ry config set apikey '") + } + rySDK = sdk.New(apiKey) + + outputFormat := cfg.Output + if flagOutput != "" { + outputFormat = flagOutput + } + if flagRaw { + outputFormat = "raw" + } + out = output.New(outputFormat, os.Stdout) + if flagVerbose { + fmt.Fprintf(os.Stderr, "[debug] config: %s\n", cfgPath) + fmt.Fprintf(os.Stderr, "[debug] output: %s\n", outputFormat) + } + return nil + }, +} + +func init() { + cfgPath = config.DefaultPath() + rootCmd.PersistentFlags().StringVar(&flagAPIKey, "apikey", "", "Rainyun API key") + rootCmd.PersistentFlags().StringVarP(&flagOutput, "output", "o", "", "Output format: table, json, yaml, raw") + rootCmd.PersistentFlags().BoolVar(&flagRaw, "raw", false, "Output raw API response") + rootCmd.PersistentFlags().BoolVarP(&flagVerbose, "verbose", "v", false, "Enable verbose output") + + completionCmd := &cobra.Command{ + Use: "completion [bash|zsh|fish]", + Short: "Generate shell completion script", + Annotations: map[string]string{"skipSDK": "true"}, + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + switch args[0] { + case "bash": + return rootCmd.GenBashCompletion(os.Stdout) + case "zsh": + return rootCmd.GenZshCompletion(os.Stdout) + case "fish": + return rootCmd.GenFishCompletion(os.Stdout, true) + default: + return fmt.Errorf("unsupported shell: %s", args[0]) + } + }, + } + rootCmd.AddCommand(completionCmd) + + rootCmd.AddCommand(configcmd.Cmd(&cfg, cfgPath)) + rootCmd.AddCommand(server.Cmd(&rySDK, &out, &flagRaw)) + rootCmd.AddCommand(storage.Cmd(&rySDK, &out)) + rootCmd.AddCommand(domain.Cmd(&rySDK, &out)) + rootCmd.AddCommand(billing.Cmd(&rySDK, &out)) +} diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 28a5f09..b6918d9 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -6,6 +6,25 @@ All notable changes to this template should be documented in this file. The format loosely follows Keep a Changelog and can be adapted to the team's habits. 本文档参考了 Keep a Changelog 的思路,也可以根据团队习惯调整。 +## [Unreleased] - 2026-08-13 + +### Added / 新增 + +- 迁移 rainyun-cli 到仓库内 `cmd/ry/`:新增 `main.go`、`root.go`、`commands/{server,domain,storage,billing,configcmd}`、`internal/{config,model,output}`。 +- CLI 直接调用 `sdk.New(apiKey)`,删除 CLI 自带 provider/HTTP 层。 +- 配置简化为单一 `apikey` + `output`(table/json/yaml),落盘到 `~/.config/ry/config.yaml`。 +- 补齐 SDK 4 个缺口端点:`domain list`、`dns list`、`bucket-by-instance`、`expense orders`。 +- 新增 `cmd/ry/internal/config` 与 `cmd/ry/internal/output` 测试(6/6 + 7/7)。 + +### Fixed / 修复 + +- 修复 `output --raw` 输出 bug:新增 raw case,原样透传 SDK Response。 + +### Changed / 变更 + +- 命令 ID 参数统一为 `int`,显示层 model ID 保持 `string`。 +- `server reinstall` flag 从 `--os` 改为 `--os-id int`。 + ## [0.2.0] - 2026-08-05 ### Added / 新增 From 66a7bdcbd52ca7d0ada228076e9a418a7e267260 Mon Sep 17 00:00:00 2001 From: Fendy Date: Thu, 13 Aug 2026 01:10:03 +0800 Subject: [PATCH 14/17] docs: fix CHANGELOG config file extension (toml not yaml) --- docs/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index b6918d9..a799bdf 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -12,7 +12,7 @@ The format loosely follows Keep a Changelog and can be adapted to the team's hab - 迁移 rainyun-cli 到仓库内 `cmd/ry/`:新增 `main.go`、`root.go`、`commands/{server,domain,storage,billing,configcmd}`、`internal/{config,model,output}`。 - CLI 直接调用 `sdk.New(apiKey)`,删除 CLI 自带 provider/HTTP 层。 -- 配置简化为单一 `apikey` + `output`(table/json/yaml),落盘到 `~/.config/ry/config.yaml`。 +- 配置简化为单一 `apikey` + `output`(table/json/yaml),落盘到 `~/.config/ry/config.toml`。 - 补齐 SDK 4 个缺口端点:`domain list`、`dns list`、`bucket-by-instance`、`expense orders`。 - 新增 `cmd/ry/internal/config` 与 `cmd/ry/internal/output` 测试(6/6 + 7/7)。 From 706fe752a037b058a31a9462483773b4de2f261a Mon Sep 17 00:00:00 2001 From: Fendy Date: Thu, 13 Aug 2026 12:21:36 +0800 Subject: [PATCH 15/17] fix(cmd/ry): silence cobra error dup + fix bucket/dns Use&Args warts --- cmd/ry/commands/domain/domain.go | 5 ++--- cmd/ry/commands/storage/storage.go | 7 +++---- cmd/ry/root.go | 2 ++ 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/cmd/ry/commands/domain/domain.go b/cmd/ry/commands/domain/domain.go index 87baaf6..1488019 100644 --- a/cmd/ry/commands/domain/domain.go +++ b/cmd/ry/commands/domain/domain.go @@ -41,13 +41,12 @@ func Cmd(rySDK **sdk.RainyunSDK, out **output.Printer) *cobra.Command { } dnsCmd := &cobra.Command{ - Use: "dns ", + Use: "dns", Short: "Manage DNS records", - Args: cobra.ExactArgs(1), } dnsListCmd := &cobra.Command{ - Use: "list", + Use: "list ", Short: "List DNS records", RunE: func(cmd *cobra.Command, args []string) error { id, err := strconv.Atoi(args[0]) diff --git a/cmd/ry/commands/storage/storage.go b/cmd/ry/commands/storage/storage.go index 19c0114..33889ff 100644 --- a/cmd/ry/commands/storage/storage.go +++ b/cmd/ry/commands/storage/storage.go @@ -42,13 +42,12 @@ func Cmd(rySDK **sdk.RainyunSDK, out **output.Printer) *cobra.Command { } bucketCmd := &cobra.Command{ - Use: "bucket ", + Use: "bucket", Short: "Manage buckets", - Args: cobra.ExactArgs(1), } bucketListCmd := &cobra.Command{ - Use: "list", + Use: "list ", Short: "List buckets", RunE: func(cmd *cobra.Command, args []string) error { instanceID, err := strconv.Atoi(args[0]) @@ -71,7 +70,7 @@ func Cmd(rySDK **sdk.RainyunSDK, out **output.Printer) *cobra.Command { } bucketCreateCmd := &cobra.Command{ - Use: "create ", + Use: "create ", Short: "Create a bucket", Args: cobra.ExactArgs(2), RunE: func(cmd *cobra.Command, args []string) error { diff --git a/cmd/ry/root.go b/cmd/ry/root.go index c6155ca..33727e2 100644 --- a/cmd/ry/root.go +++ b/cmd/ry/root.go @@ -76,6 +76,8 @@ Use "ry [command] --help" for more information about a command.`, } func init() { + rootCmd.SilenceErrors = true + rootCmd.SilenceUsage = true cfgPath = config.DefaultPath() rootCmd.PersistentFlags().StringVar(&flagAPIKey, "apikey", "", "Rainyun API key") rootCmd.PersistentFlags().StringVarP(&flagOutput, "output", "o", "", "Output format: table, json, yaml, raw") From 627ea7110a3a55ac91020a4c2b7ca3ca17051851 Mon Sep 17 00:00:00 2001 From: Fendy Date: Thu, 13 Aug 2026 12:34:11 +0800 Subject: [PATCH 16/17] go mod tidy --- go.mod | 6 +++--- go.sum | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index b4dff81..171b8bf 100644 --- a/go.mod +++ b/go.mod @@ -3,22 +3,22 @@ module github.com/XingfenD/rainyun_api_go_sdk go 1.24.1 require ( + github.com/BurntSushi/toml v1.6.0 github.com/bytedance/sonic v1.15.2 github.com/go-resty/resty/v2 v2.17.2 + github.com/spf13/cobra v1.10.2 + gopkg.in/yaml.v3 v3.0.1 ) require ( - github.com/BurntSushi/toml v1.6.0 // indirect github.com/bytedance/gopkg v0.1.3 // indirect github.com/bytedance/sonic/loader v0.5.1 // indirect github.com/cloudwego/base64x v0.1.6 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/klauspost/cpuid/v2 v2.2.9 // indirect - github.com/spf13/cobra v1.10.2 // indirect github.com/spf13/pflag v1.0.9 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect golang.org/x/net v0.43.0 // indirect golang.org/x/sys v0.35.0 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index b287b66..b8afeb9 100644 --- a/go.sum +++ b/go.sum @@ -45,6 +45,7 @@ golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= From f04bad5f184d8afd4889dff05cc929d79dab6cc8 Mon Sep 17 00:00:00 2001 From: Fendy Date: Thu, 13 Aug 2026 13:10:53 +0800 Subject: [PATCH 17/17] feat(cli): add interactive config input and Makefile - support interactive value input for `config set` - hide API key input and output - add Makefile for build, test, vet, coverage, and cleanup - add golang.org/x/term dependency - update changelog and gitignore --- .gitignore | 7 ++++- Makefile | 43 +++++++++++++++++++++++++++ cmd/ry/commands/configcmd/config.go | 33 +++++++++++++++++--- cmd/ry/internal/config/interactive.go | 43 +++++++++++++++++++++++++++ docs/CHANGELOG.md | 5 ++++ go.mod | 1 + go.sum | 2 ++ 7 files changed, 129 insertions(+), 5 deletions(-) create mode 100644 Makefile create mode 100644 cmd/ry/internal/config/interactive.go diff --git a/.gitignore b/.gitignore index 5544937..ba770a4 100644 --- a/.gitignore +++ b/.gitignore @@ -40,4 +40,9 @@ reasonix.toml opencode.json .claude/ .opencode/ -.hermers/ \ No newline at end of file +.hermers/ + +# Build output (Makefile) +bin/ +coverage.out +coverage.html diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..a12349e --- /dev/null +++ b/Makefile @@ -0,0 +1,43 @@ +.PHONY: all build test vet fmt lint cover install tidy clean help + +BINARY := ry +MAIN_PKG := ./cmd/ry +GO := go +GOFLAGS := +OUTPUT := bin/$(BINARY) + +VERSION := $(shell git describe --tags --always --dirty 2>/dev/null || echo "dev") +COMMIT := $(shell git rev-parse --short HEAD 2>/dev/null || echo "unknown") +LDFLAGS := -X main.version=$(VERSION) -X main.commit=$(COMMIT) +BUILD_FLAGS := -ldflags "$(LDFLAGS)" + +all: build + +build: ## Build the CLI binary into bin/ + $(GO) build $(GOFLAGS) $(BUILD_FLAGS) -o $(OUTPUT) $(MAIN_PKG) + +install: ## Install the CLI binary to GOPATH/bin + $(GO) install $(GOFLAGS) $(BUILD_FLAGS) $(MAIN_PKG) + +test: ## Run all tests + $(GO) test $(GOFLAGS) -race ./... + +vet: ## Run go vet + $(GO) vet ./... + +fmt: ## Format all Go source files + gofmt -s -w . + $(GO) mod tidy + +cover: ## Run tests with coverage and generate HTML report + $(GO) test $(GOFLAGS) -race -coverprofile=coverage.out ./... + $(GO) tool cover -html=coverage.out -o coverage.html + +tidy: ## Run go mod tidy + $(GO) mod tidy + +clean: ## Remove build artifacts and coverage files + rm -rf bin/ coverage.out coverage.html + +help: ## Show this help message + @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf " \033[36m%-10s\033[0m %s\n", $$1, $$2}' diff --git a/cmd/ry/commands/configcmd/config.go b/cmd/ry/commands/configcmd/config.go index 52c5271..a6a06c8 100644 --- a/cmd/ry/commands/configcmd/config.go +++ b/cmd/ry/commands/configcmd/config.go @@ -34,12 +34,33 @@ func Cmd(cfg **config.Config, cfgPath string) *cobra.Command { } configSetCmd := &cobra.Command{ - Use: "set ", + Use: "set [value]", Short: "Set a config value", - Args: cobra.ExactArgs(2), + Args: cobra.RangeArgs(1, 2), Annotations: map[string]string{"skipSDK": "true"}, RunE: func(cmd *cobra.Command, args []string) error { - key, value := args[0], args[1] + key := args[0] + var value string + + if len(args) == 2 { + value = args[1] + } else { + // Interactive mode — prompt for the value. + if key == "apikey" { + v, err := config.ReadSecret("Enter API key: ") + if err != nil { + return fmt.Errorf("read API key: %w", err) + } + value = v + } else { + v, err := config.ReadLine("Enter " + key + ": ") + if err != nil { + return fmt.Errorf("read %s: %w", key, err) + } + value = v + } + } + c := *cfg switch key { case "apikey": @@ -52,7 +73,11 @@ func Cmd(cfg **config.Config, cfgPath string) *cobra.Command { if err := config.Save(cfgPath, c); err != nil { return fmt.Errorf("save config: %w", err) } - fmt.Printf("Set %s = %s\n", key, value) + if key == "apikey" { + fmt.Printf("Set %s = %s\n", key, "(hidden)") + } else { + fmt.Printf("Set %s = %s\n", key, value) + } return nil }, } diff --git a/cmd/ry/internal/config/interactive.go b/cmd/ry/internal/config/interactive.go new file mode 100644 index 0000000..e08ead3 --- /dev/null +++ b/cmd/ry/internal/config/interactive.go @@ -0,0 +1,43 @@ +package config + +import ( + "bufio" + "fmt" + "os" + + "golang.org/x/term" +) + +// ReadSecret prompts the user with msg and reads a line from stdin. +// When stdin is a terminal, echo is disabled so the typed value stays +// invisible. When stdin is piped (non-TTY) it falls back to a normal +// read so scripting still works. +func ReadSecret(msg string) (string, error) { + fmt.Fprint(os.Stderr, msg) + var value string + if term.IsTerminal(int(os.Stdin.Fd())) { + b, err := term.ReadPassword(int(os.Stdin.Fd())) + if err != nil { + return "", err + } + value = string(b) + } else { + sc := bufio.NewScanner(os.Stdin) + if !sc.Scan() { + return "", sc.Err() + } + value = sc.Text() + } + fmt.Fprintln(os.Stderr) + return value, nil +} + +// ReadLine prompts the user with msg and reads a normal (echo-enabled) line. +func ReadLine(msg string) (string, error) { + fmt.Fprint(os.Stderr, msg) + sc := bufio.NewScanner(os.Stdin) + if !sc.Scan() { + return "", sc.Err() + } + return sc.Text(), nil +} diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index a799bdf..96c49af 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -10,6 +10,9 @@ The format loosely follows Keep a Changelog and can be adapted to the team's hab ### Added / 新增 +- `config set ` 支持交互式输入:省略 value 时交互式提示,`apikey` 使用隐藏输入避免敏感信息暴露在终端/历史中。 +- 新增 `Makefile`(build/test/vet/fmt/cover/tidy/clean/help)。 + - 迁移 rainyun-cli 到仓库内 `cmd/ry/`:新增 `main.go`、`root.go`、`commands/{server,domain,storage,billing,configcmd}`、`internal/{config,model,output}`。 - CLI 直接调用 `sdk.New(apiKey)`,删除 CLI 自带 provider/HTTP 层。 - 配置简化为单一 `apikey` + `output`(table/json/yaml),落盘到 `~/.config/ry/config.toml`。 @@ -24,6 +27,8 @@ The format loosely follows Keep a Changelog and can be adapted to the team's hab - 命令 ID 参数统一为 `int`,显示层 model ID 保持 `string`。 - `server reinstall` flag 从 `--os` 改为 `--os-id int`。 +- `.gitignore` 新增 `bin/`、`coverage.out`、`coverage.html`。 +- 新增 `golang.org/x/term` 依赖(交互式隐藏输入)。 ## [0.2.0] - 2026-08-05 diff --git a/go.mod b/go.mod index 171b8bf..05bd7b5 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ require ( github.com/bytedance/sonic v1.15.2 github.com/go-resty/resty/v2 v2.17.2 github.com/spf13/cobra v1.10.2 + golang.org/x/term v0.34.0 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/go.sum b/go.sum index b8afeb9..7da8984 100644 --- a/go.sum +++ b/go.sum @@ -43,6 +43,8 @@ golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4= +golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw= golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=