feat(sdk-go): initial version
This commit is contained in:
192
sdk-go/client/client.go
Normal file
192
sdk-go/client/client.go
Normal 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
119
sdk-go/client/dto.go
Normal 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:"-"`
|
||||
}
|
||||
Reference in New Issue
Block a user