Files
bj_power/bj_power_mes/internal/hikrcs/client.go
T

176 lines
4.6 KiB
Go
Raw Normal View History

2026-08-27 19:50:57 +08:00
package hikrcs
import (
"bytes"
"crypto/hmac"
"crypto/md5"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"sort"
"strings"
"time"
"github.com/google/uuid"
)
/*
海康 RCS-2000(调度系统)HTTP API 客户端。
签名规则(《AGV海康.md》数字签名授权):
1. 请求头需带:AUTHORIZATION / HOST / X-LR-APPKEY / X-LR-REQUEST-ID / X-LR-VERSION
(存在其他以 X-LR- 开头的头时也一并按 key 排序参与拼接)
2. 待签名字符串 = "METHOD PATH HTTP/1.1\n" + 各 HEADER 行按 key 升序逐行 (KEY: VALUE \n)
3. 签名 = md5( HMAC-SHA256( AppSecret, 待签字符串 ) ) 小写 hex
正式对接前若厂商样例算法细节略有出入,只需调整 buildSignature() 一处。
*/
type Client struct {
BaseURL string // 如 http://192.168.1.210:8099
AppKey string
AppSecret string
Version string // X-LR-VERSION,如 v1.0
HTTP *http.Client
}
func New(baseURL, appKey, appSecret, version string) *Client {
return &Client{
BaseURL: strings.TrimRight(baseURL, "/"),
AppKey: appKey,
AppSecret: appSecret,
Version: version,
HTTP: &http.Client{Timeout: 15 * time.Second},
}
}
// SubmitTaskBody 提交任务请求体
type SubmitTaskBody struct {
TaskCode string `json:"taskCode"` // 任务号(唯一)
PrjCode string `json:"prjCode"` // 项目编码(多项目调度需要)
Pri int `json:"pri"` // 优先级 0~14,默认 4
Task any `json:"task"` // 任务类型定义(子任务/模板ID等),暂放由调用方传
}
func (c *Client) sign(method, path string, headers map[string]string) string {
keys := make([]string, 0, len(headers))
for k := range headers {
keys = append(keys, k)
}
sort.Strings(keys)
var b strings.Builder
b.WriteString(strings.ToUpper(method))
b.WriteString(" ")
b.WriteString(path)
b.WriteString(" HTTP/1.1\n")
for _, k := range keys {
b.WriteString(k)
b.WriteString(": ")
b.WriteString(headers[k])
b.WriteString("\n")
}
content := []byte(b.String())
mac := hmac.New(sha256.New, []byte(c.AppSecret))
mac.Write(content)
sum := mac.Sum(nil)
digest := md5.Sum(sum)
return hex.EncodeToString(digest[:])
}
func (c *Client) do(method, path string, body any) (json.RawMessage, error) {
u := c.BaseURL + path
host := ""
if p, err := url.Parse(c.BaseURL); err == nil {
host = p.Host
}
requestID := strings.ToUpper(uuid.NewString())
headers := map[string]string{
"AUTHORIZATION": "Bearer " + c.AppKey,
"HOST": host,
"X-LR-APPKEY": c.AppKey,
"X-LR-REQUEST-ID": requestID,
"X-LR-VERSION": c.Version,
}
signature := c.sign(method, path, headers)
var rd io.Reader
if body != nil {
buf, err := json.Marshal(body)
if err != nil {
return nil, err
}
rd = bytes.NewReader(buf)
}
req, err := http.NewRequest(method, u, rd)
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/json")
for k, v := range headers {
http.CanonicalHeaderKey(k)
req.Header.Set(canonical(k), v)
}
req.Header.Set("X-LR-SIGNATURE", signature)
resp, err := c.HTTP.Do(req)
if err != nil {
return nil, fmt.Errorf("RCS 不可达(%s): %w", path, err)
}
defer resp.Body.Close()
raw, _ := io.ReadAll(resp.Body)
var out struct {
Code string `json:"code"`
Message string `json:"message"`
ErrorMessage string `json:"errorMessage"`
Data json.RawMessage `json:"data"`
}
_ = json.Unmarshal(raw, &out)
if resp.StatusCode >= 400 || (out.Code != "" && out.Code != "0") {
msg := out.Message
if msg == "" {
msg = out.ErrorMessage
}
return out.Data, fmt.Errorf("RCS 返回错误(%s): code=%s %s", path, out.Code, truncateStr(msg, 200))
}
return out.Data, nil
}
// canonical 将全大写签名头名转成常规首字母大写形式
func canonical(s string) string {
s = strings.ToLower(s)
parts := strings.Split(s, "-")
for i, p := range parts {
if p != "" {
parts[i] = strings.ToUpper(p[:1]) + p[1:]
}
}
return strings.Join(parts, "-")
}
func truncateStr(s string, n int) string {
if len(s) > n {
return s[:n]
}
return s
}
// Submit 提交搬运任务
func (c *Client) Submit(taskCode, prjCode string, pri int, task any) (json.RawMessage, error) {
body := map[string]any{"taskCode": taskCode, "pri": pri, "task": task}
if prjCode != "" {
body["prjCode"] = prjCode
}
return c.do(http.MethodPost, "/rcs/rtas/api/robot/controller/task/submit", body)
}
// QueryTask 查询任务状态
func (c *Client) QueryTask(taskCode string) (json.RawMessage, error) {
path := fmt.Sprintf("/rcs/rtas/api/robot/controller/task/query?taskCode=%s", url.QueryEscape(taskCode))
return c.do(http.MethodGet, path, nil)
}