package hikvisionOpenAPIGo import ( "bytes" "crypto/hmac" "crypto/md5" "crypto/sha256" "crypto/tls" "encoding/base64" "encoding/hex" "encoding/json" "fmt" "github.com/gofrs/uuid" "io/ioutil" "net/http" "net/url" "sort" "strconv" "strings" "time" ) // HKConfig 海康OpenAPI配置参数 type HKConfig struct { Ip string //平台ip Port int //平台端口 AppKey string //平台APPKey Secret string //平台APPSecret IsHttps bool //是否使用HTTPS协议 } // 返回结果 type Result struct { Code string `json:"code"` Msg string `json:"msg"` Data interface{} `json:"data"` } // 返回值data type Data struct { Total int `json:"total"` PageSize int `json:"pageSize"` PageNo int `json:"pageNo"` List []map[string]interface{} `json:"list"` } // @title HTTP Post请求 // @url HTTP接口Url string HTTP接口Url,不带协议和端口 // @body 请求参数 map[string]any 支持数组、结构体等 // @return 请求结果 Result func (hk HKConfig) HttpPost(url string, body map[string]any, timeout int) (result Result, err error) { var header = make(map[string]string) bodyJson, err := json.Marshal(body) if err != nil { return result, err } err = hk.initRequest(header, url, string(bodyJson), true) if err != nil { return Result{}, err } var sb strings.Builder if hk.IsHttps { sb.WriteString("https://") } else { sb.WriteString("http://") } sb.WriteString(fmt.Sprintf("%s:%d%s", hk.Ip, hk.Port, url)) client := &http.Client{ Timeout: time.Duration(timeout) * time.Second, CheckRedirect: func(req *http.Request, via []*http.Request) error { return http.ErrUseLastResponse }, } if hk.IsHttps { tr := &http.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, } client.Transport = tr } req, err := http.NewRequest("POST", sb.String(), bytes.NewReader(bodyJson)) if err != nil { return result, err } req.Header.Set("Accept", header["Accept"]) req.Header.Set("Content-Type", header["Content-Type"]) for k, v := range header { if strings.HasPrefix(k, "x-ca-") { req.Header.Set(k, v) } } resp, err := client.Do(req) if err != nil { return result, err } defer resp.Body.Close() if resp.StatusCode == http.StatusOK { resBody, _ := ioutil.ReadAll(resp.Body) err = json.Unmarshal(resBody, &result) return result, err } else if resp.StatusCode == http.StatusFound || resp.StatusCode == http.StatusMovedPermanently { reqUrl := resp.Header.Get("Location") err = fmt.Errorf("HttpPost Response StatusCode:%d,Location:%s", resp.StatusCode, reqUrl) } else { err = fmt.Errorf("HttpPost Response StatusCode:%d", resp.StatusCode) } return result, err } // @title HTTP Get请求 // @url HTTP接口Url string HTTP接口Url,不带协议和端口 // @params 请求参数 map[string]any 支持数组、结构体等 // @timeout 超时时间 int 单位秒 // @return 请求结果 Result func (hk HKConfig) HttpGet(url string, params map[string]any, timeout int) (result Result, err error) { var header = make(map[string]string) // 构造查询参数 queryParams := buildQueryParams(params) u := fmt.Sprintf("%s:%d%s?%s", hk.Ip, hk.Port, url, queryParams) var sb strings.Builder if hk.IsHttps { sb.WriteString("https://") } else { sb.WriteString("http://") } sb.WriteString(u) client := &http.Client{ Timeout: time.Duration(timeout) * time.Second, CheckRedirect: func(req *http.Request, via []*http.Request) error { return http.ErrUseLastResponse }, } if hk.IsHttps { tr := &http.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, } client.Transport = tr } req, err := http.NewRequest("GET", sb.String(), nil) if err != nil { return result, err } // 初始化签名头 err = hk.initRequest(header, url, "", false) if err != nil { return result, err } req.Header.Set("Accept", header["Accept"]) req.Header.Set("Content-Type", header["Content-Type"]) for k, v := range header { if strings.HasPrefix(k, "x-ca-") { req.Header.Set(k, v) } } resp, err := client.Do(req) if err != nil { return result, err } defer resp.Body.Close() if resp.StatusCode == http.StatusOK { resBody, _ := ioutil.ReadAll(resp.Body) err = json.Unmarshal(resBody, &result) return result, err } else if resp.StatusCode == http.StatusFound || resp.StatusCode == http.StatusMovedPermanently { reqUrl := resp.Header.Get("Location") err = fmt.Errorf("HttpGet Response StatusCode:%d,Location:%s", resp.StatusCode, reqUrl) } else { err = fmt.Errorf("HttpGet Response StatusCode:%d", resp.StatusCode) } return result, err } // initRequest 初始化请求头 func (hk HKConfig) initRequest(header map[string]string, url, body string, isPost bool) error { header["Accept"] = "application/json" header["Content-Type"] = "application/json" if isPost { var err error header["content-md5"], err = computeContentMd5(body) if err != nil { return err } } header["x-ca-timestamp"] = strconv.FormatInt(time.Now().UnixMilli(), 10) uid, err := uuid.NewV4() if err != nil { return err } header["x-ca-nonce"] = uid.String() header["x-ca-key"] = hk.AppKey var strToSign string if isPost { strToSign = buildSignString(header, url, "POST") } else { strToSign = buildSignString(header, url, "GET") } signedStr, err := computeForHMACSHA256(strToSign, hk.Secret) if err != nil { return err } header["x-ca-signature"] = signedStr return nil } // computeContentMd5 计算content-md5 func computeContentMd5(body string) (string, error) { h := md5.New() _, err := h.Write([]byte(body)) if err != nil { return "", err } md5Str := hex.EncodeToString(h.Sum(nil)) return base64.StdEncoding.EncodeToString([]byte(md5Str)), nil } // computeForHMACSHA256 计算HMACSHA265 func computeForHMACSHA256(str, secret string) (string, error) { mac := hmac.New(sha256.New, []byte(secret)) _, err := mac.Write([]byte(str)) if err != nil { return "", err } return base64.StdEncoding.EncodeToString(mac.Sum(nil)), nil } // buildSignString 计算签名字符串 func buildSignString(header map[string]string, url, method string) string { var sb []string sb = append(sb, strings.ToUpper(method)) sb = append(sb, "\n") if header != nil { if _, ok := header["Accept"]; ok { sb = append(sb, header["Accept"]) sb = append(sb, "\n") } if _, ok := header["Content-MD5"]; ok { sb = append(sb, header["Content-MD5"]) sb = append(sb, "\n") } if _, ok := header["Content-Type"]; ok { sb = append(sb, header["Content-Type"]) sb = append(sb, "\n") } } sb = append(sb, buildSignHeader(header)) sb = append(sb, url) return strings.Join(sb, "") } // buildSignHeader 计算签名头 func buildSignHeader(header map[string]string) string { var sslice []string for key := range header { if strings.Contains(key, "x-ca-") { sslice = append(sslice, key) } } sort.Strings(sslice) var sbSignHeader []string var sb []string for _, k := range sslice { sb = append(sb, k+":") if header[k] != "" { sb = append(sb, header[k]) } sb = append(sb, "\n") sbSignHeader = append(sbSignHeader, k) } header["x-ca-signature-headers"] = strings.Join(sbSignHeader, ",") return strings.Join(sb, "") } // buildQueryParams 构建查询参数字符串,支持数组、基本类型 func buildQueryParams(params map[string]any) string { values := make(url.Values) for key, value := range params { switch v := value.(type) { case string: values.Add(key, v) case int: values.Add(key, strconv.Itoa(v)) case bool: values.Add(key, strconv.FormatBool(v)) case []string: for _, item := range v { values.Add(key+"[]", item) } case []int: for _, item := range v { values.Add(key+"[]", strconv.Itoa(item)) } case []interface{}: for _, item := range v { values.Add(key+"[]", fmt.Sprintf("%v", item)) } default: values.Add(key, fmt.Sprintf("%v", v)) } } return values.Encode() }