feat(sdk-go): initial version

This commit is contained in:
Nex Zhu
2024-11-26 18:54:32 +08:00
commit edfed1bcf2
6 changed files with 510 additions and 0 deletions

192
sdk-go/client/client.go Normal file
View File

@@ -0,0 +1,192 @@
package client
import (
"bytes"
"crypto/rand"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"net/url"
"strings"
"time"
"github.com/tjfoc/gmsm/sm2"
"go.fusiongalaxy.cn/bdware/bdcontract-client/sm2util"
)
type HttpCOptions struct {
MaxRetries int
}
type Client struct {
baseUrl string
priv *sm2.PrivateKey
pub *sm2.PublicKey
pubHex string
httpc *http.Client
maxRetry int
}
func NewClient(
baseUrl string,
priv *sm2.PrivateKey,
pub *sm2.PublicKey,
opt HttpCOptions,
) (*Client, error) {
pubHex, err := sm2util.CheckSm2KeyPair(priv, pub)
if err != nil {
return nil, err
}
if opt.MaxRetries == 0 {
opt.MaxRetries = 3
}
return &Client{
baseUrl: baseUrl,
pub: pub,
pubHex: pubHex,
priv: priv,
httpc: &http.Client{Timeout: 10 * time.Second},
maxRetry: opt.MaxRetries,
}, nil
}
func (c *Client) RequestWithSignature(
path string,
method string,
body map[string]interface{},
priv *sm2.PrivateKey,
pub *sm2.PublicKey,
) (statusCode int, respBody io.ReadCloser, err error) {
var pubHex string
if priv != nil {
var err error
pubHex, err = sm2util.CheckSm2KeyPair(priv, pub)
if err != nil {
return 0, nil, err
}
} else {
priv = c.priv
pubHex = c.pubHex
}
rawUrl := c.baseUrl + path
u := fmt.Sprintf("%s%spubKey=%s",
rawUrl,
func() string {
if strings.Contains(path, "?") {
return "&"
}
return "?"
}(),
pubHex,
)
switch strings.ToUpper(method) {
case "POST":
body["sign"] = c.Sign(u[strings.Index(u, "?")+1:], priv)
bodyJson, err := json.Marshal(body)
if err != nil {
return 0, nil, err
}
resp, err := c.httpc.Post(rawUrl, "application/json", bytes.NewBuffer(bodyJson))
if err != nil {
return 0, nil, err
}
respBody = resp.Body
}
return statusCode, respBody, nil
}
func (c *Client) Sign(data string, priv *sm2.PrivateKey) string {
if priv == nil {
priv = c.priv
}
sig, err := priv.Sign(rand.Reader, []byte(data), nil)
if err != nil {
log.Fatal(err)
}
return hex.EncodeToString(sig)
}
func genUrlParamsFromObject(obj map[string]interface{}) string {
params := make([]string, 0)
for key, value := range obj {
params = append(params, fmt.Sprintf("%s=%v", key, value))
}
return strings.Join(params, "&")
}
func retry[T any](fn func() (int, T, error)) (int, T, error) {
var lastErr error
for attempt := 0; attempt < 3; attempt++ {
resp, statusCode, err := fn()
if err == nil {
return resp, statusCode, nil
}
lastErr = err
time.Sleep(100 * time.Millisecond * time.Duration(attempt+1))
}
var zero T
return 0, zero, lastErr
}
func (c *Client) Ping() (statusCode int, resp *PingResponse, err error) {
return retry(func() (statusCode int, resp *PingResponse, err error) {
statusCode, respBody, err := c.RequestWithSignature(
"/SCManager?action=ping",
"GET",
nil,
nil,
nil,
)
if err != nil {
return 0, nil, err
}
defer respBody.Close()
decoder := json.NewDecoder(respBody)
if err := decoder.Decode(&resp); err != nil {
return 0, nil, err
}
return statusCode, resp, nil
})
}
func (c *Client) StartContract(code string) (statusCode int, resp string, err error) {
params := url.Values{
"action": {"startContract"},
"script": {code},
}
path := fmt.Sprintf("/SCManager?%s", params.Encode())
return retry(func() (int, string, error) {
statusCode, respBody, err := c.RequestWithSignature(
path,
"GET",
nil,
nil,
nil,
)
if err != nil {
return 0, "", err
}
var resp string
defer respBody.Close()
buf := new(strings.Builder)
if _, err := io.Copy(buf, respBody); err != nil {
return 0, "", err
}
return statusCode, resp, nil
})
}

119
sdk-go/client/dto.go Normal file
View File

@@ -0,0 +1,119 @@
package client
// ClientResponse represents a generic response structure
type ClientResponse[T any] struct {
Data *T `json:"data,omitempty"`
}
// PingResponse is a specific response type for ping operations
type PingResponse struct {
ClientResponse[string] // will contain "pong"
}
// SaveFileRequest represents the request structure for saving files
type SaveFileRequest struct {
Content string `json:"content"`
IsAppend bool `json:"isAppend"`
IsPrivate bool `json:"isPrivate"`
Path string `json:"path"`
}
// ListProjectPermissionRequest represents the request for listing project permissions
type ListProjectPermissionRequest struct {
IsPrivate bool `json:"isPrivate"`
Path string `json:"path"`
}
// ListProjectPermissionResponseData contains permission response data
type ListProjectPermissionResponseData struct {
Permissions []string `json:"permissions"`
YPK string `json:"ypk"`
}
// StartContractByYpkRequest represents the request for starting a contract
type StartContractByYpkRequest struct {
IsPrivate bool `json:"isPrivate"`
Path string `json:"path"`
Script string `json:"script"`
}
// ListAllUsersResponseDataListItem represents a key-value pair
type ListAllUsersResponseDataListItem struct {
Key string `json:"key"`
Value string `json:"value"`
}
// ListAllUsersResponseData contains user listing response data
type ListAllUsersResponseData struct {
KV []ListAllUsersResponseDataListItem `json:"kv"`
Time []ListAllUsersResponseDataListItem `json:"time"`
}
// OnlineContractsItem represents an online contract
type OnlineContractsItem struct {
ContractID string `json:"contractID"`
ContractName string `json:"contractName"`
IsMaster bool `json:"isMaster"`
Type string `json:"type"`
YjsType string `json:"yjsType"`
Extra map[string]interface{} `json:"-"`
}
// OnlineItem represents an online node
type OnlineItem struct {
CIManager string `json:"cimanager"`
ContractVersion int `json:"contractVersion"`
Events int `json:"events"`
IPPort string `json:"ipPort"`
MasterAddress string `json:"masterAddress"`
NodeName string `json:"nodeName"`
PeerID string `json:"peerID"`
PubKey string `json:"pubKey"`
Contracts []OnlineContractsItem `json:"contracts"`
}
// ListNodesResponse represents the response for listing nodes
type ListNodesResponse struct {
Action string `json:"action"`
Offline []string `json:"offline"`
Online []OnlineItem `json:"online"`
}
// DistributeContractResponse represents the response for contract distribution
type DistributeContractResponse struct {
Action string `json:"action"`
Progress string `json:"progress"`
}
// ExecuteContractArgs represents arguments for contract execution
type ExecuteContractArgs struct {
Method string `json:"method,omitempty"`
WithSignature bool `json:"withSignature,omitempty"`
WithDynamicAnalysis bool `json:"withDynamicAnalysis,omitempty"`
}
// ExecuteContractResponse represents the response from contract execution
type ExecuteContractResponse[T any] struct {
Status *bool `json:"status,omitempty"`
Data *T `json:"data,omitempty"`
ExecuteTime *float64 `json:"executeTime,omitempty"`
CID *string `json:"cid,omitempty"`
IsPrivate *bool `json:"isPrivate,omitempty"`
AdditionalData map[string]interface{} `json:"-"`
}
// ConfigNodeArgs represents configuration arguments for a node
type ConfigNodeArgs struct {
NodeName string `json:"nodeName,omitempty"`
DataChain string `json:"dataChain,omitempty"`
MasterAddress string `json:"masterAddress,omitempty"`
NodeCenter string `json:"nodeCenter,omitempty"`
LHSProxyAddress string `json:"LHSProxyAddress,omitempty"`
ExtraConfig map[string]string `json:"-"`
}
// LoadNodeConfigResponseData represents the response data for node configuration
type LoadNodeConfigResponseData struct {
DoipConfig string `json:"doipConfig"`
ExtraData map[string]string `json:"-"`
}