hikvisionOpenAPIGo.go 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320
  1. package hikvisionOpenAPIGo
  2. import (
  3. "bytes"
  4. "crypto/hmac"
  5. "crypto/md5"
  6. "crypto/sha256"
  7. "crypto/tls"
  8. "encoding/base64"
  9. "encoding/hex"
  10. "encoding/json"
  11. "fmt"
  12. "github.com/gofrs/uuid"
  13. "io/ioutil"
  14. "net/http"
  15. "net/url"
  16. "sort"
  17. "strconv"
  18. "strings"
  19. "time"
  20. )
  21. // HKConfig 海康OpenAPI配置参数
  22. type HKConfig struct {
  23. Ip string //平台ip
  24. Port int //平台端口
  25. AppKey string //平台APPKey
  26. Secret string //平台APPSecret
  27. IsHttps bool //是否使用HTTPS协议
  28. }
  29. // 返回结果
  30. type Result struct {
  31. Code string `json:"code"`
  32. Msg string `json:"msg"`
  33. Data interface{} `json:"data"`
  34. }
  35. // 返回值data
  36. type Data struct {
  37. Total int `json:"total"`
  38. PageSize int `json:"pageSize"`
  39. PageNo int `json:"pageNo"`
  40. List []map[string]interface{} `json:"list"`
  41. }
  42. // @title HTTP Post请求
  43. // @url HTTP接口Url string HTTP接口Url,不带协议和端口
  44. // @body 请求参数 map[string]any 支持数组、结构体等
  45. // @return 请求结果 Result
  46. func (hk HKConfig) HttpPost(url string, body map[string]any, timeout int) (result Result, err error) {
  47. var header = make(map[string]string)
  48. bodyJson, err := json.Marshal(body)
  49. if err != nil {
  50. return result, err
  51. }
  52. err = hk.initRequest(header, url, string(bodyJson), true)
  53. if err != nil {
  54. return Result{}, err
  55. }
  56. var sb strings.Builder
  57. if hk.IsHttps {
  58. sb.WriteString("https://")
  59. } else {
  60. sb.WriteString("http://")
  61. }
  62. sb.WriteString(fmt.Sprintf("%s:%d%s", hk.Ip, hk.Port, url))
  63. client := &http.Client{
  64. Timeout: time.Duration(timeout) * time.Second,
  65. CheckRedirect: func(req *http.Request, via []*http.Request) error {
  66. return http.ErrUseLastResponse
  67. },
  68. }
  69. if hk.IsHttps {
  70. tr := &http.Transport{
  71. TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
  72. }
  73. client.Transport = tr
  74. }
  75. req, err := http.NewRequest("POST", sb.String(), bytes.NewReader(bodyJson))
  76. if err != nil {
  77. return result, err
  78. }
  79. req.Header.Set("Accept", header["Accept"])
  80. req.Header.Set("Content-Type", header["Content-Type"])
  81. for k, v := range header {
  82. if strings.HasPrefix(k, "x-ca-") {
  83. req.Header.Set(k, v)
  84. }
  85. }
  86. resp, err := client.Do(req)
  87. if err != nil {
  88. return result, err
  89. }
  90. defer resp.Body.Close()
  91. if resp.StatusCode == http.StatusOK {
  92. resBody, _ := ioutil.ReadAll(resp.Body)
  93. err = json.Unmarshal(resBody, &result)
  94. return result, err
  95. } else if resp.StatusCode == http.StatusFound || resp.StatusCode == http.StatusMovedPermanently {
  96. reqUrl := resp.Header.Get("Location")
  97. err = fmt.Errorf("HttpPost Response StatusCode:%d,Location:%s", resp.StatusCode, reqUrl)
  98. } else {
  99. err = fmt.Errorf("HttpPost Response StatusCode:%d", resp.StatusCode)
  100. }
  101. return result, err
  102. }
  103. // @title HTTP Get请求
  104. // @url HTTP接口Url string HTTP接口Url,不带协议和端口
  105. // @params 请求参数 map[string]any 支持数组、结构体等
  106. // @timeout 超时时间 int 单位秒
  107. // @return 请求结果 Result
  108. func (hk HKConfig) HttpGet(url string, params map[string]any, timeout int) (result Result, err error) {
  109. var header = make(map[string]string)
  110. // 构造查询参数
  111. queryParams := buildQueryParams(params)
  112. u := fmt.Sprintf("%s:%d%s?%s", hk.Ip, hk.Port, url, queryParams)
  113. var sb strings.Builder
  114. if hk.IsHttps {
  115. sb.WriteString("https://")
  116. } else {
  117. sb.WriteString("http://")
  118. }
  119. sb.WriteString(u)
  120. client := &http.Client{
  121. Timeout: time.Duration(timeout) * time.Second,
  122. CheckRedirect: func(req *http.Request, via []*http.Request) error {
  123. return http.ErrUseLastResponse
  124. },
  125. }
  126. if hk.IsHttps {
  127. tr := &http.Transport{
  128. TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
  129. }
  130. client.Transport = tr
  131. }
  132. req, err := http.NewRequest("GET", sb.String(), nil)
  133. if err != nil {
  134. return result, err
  135. }
  136. // 初始化签名头
  137. err = hk.initRequest(header, url, "", false)
  138. if err != nil {
  139. return result, err
  140. }
  141. req.Header.Set("Accept", header["Accept"])
  142. req.Header.Set("Content-Type", header["Content-Type"])
  143. for k, v := range header {
  144. if strings.HasPrefix(k, "x-ca-") {
  145. req.Header.Set(k, v)
  146. }
  147. }
  148. resp, err := client.Do(req)
  149. if err != nil {
  150. return result, err
  151. }
  152. defer resp.Body.Close()
  153. if resp.StatusCode == http.StatusOK {
  154. resBody, _ := ioutil.ReadAll(resp.Body)
  155. err = json.Unmarshal(resBody, &result)
  156. return result, err
  157. } else if resp.StatusCode == http.StatusFound || resp.StatusCode == http.StatusMovedPermanently {
  158. reqUrl := resp.Header.Get("Location")
  159. err = fmt.Errorf("HttpGet Response StatusCode:%d,Location:%s", resp.StatusCode, reqUrl)
  160. } else {
  161. err = fmt.Errorf("HttpGet Response StatusCode:%d", resp.StatusCode)
  162. }
  163. return result, err
  164. }
  165. // initRequest 初始化请求头
  166. func (hk HKConfig) initRequest(header map[string]string, url, body string, isPost bool) error {
  167. header["Accept"] = "application/json"
  168. header["Content-Type"] = "application/json"
  169. if isPost {
  170. var err error
  171. header["content-md5"], err = computeContentMd5(body)
  172. if err != nil {
  173. return err
  174. }
  175. }
  176. header["x-ca-timestamp"] = strconv.FormatInt(time.Now().UnixMilli(), 10)
  177. uid, err := uuid.NewV4()
  178. if err != nil {
  179. return err
  180. }
  181. header["x-ca-nonce"] = uid.String()
  182. header["x-ca-key"] = hk.AppKey
  183. var strToSign string
  184. if isPost {
  185. strToSign = buildSignString(header, url, "POST")
  186. } else {
  187. strToSign = buildSignString(header, url, "GET")
  188. }
  189. signedStr, err := computeForHMACSHA256(strToSign, hk.Secret)
  190. if err != nil {
  191. return err
  192. }
  193. header["x-ca-signature"] = signedStr
  194. return nil
  195. }
  196. // computeContentMd5 计算content-md5
  197. func computeContentMd5(body string) (string, error) {
  198. h := md5.New()
  199. _, err := h.Write([]byte(body))
  200. if err != nil {
  201. return "", err
  202. }
  203. md5Str := hex.EncodeToString(h.Sum(nil))
  204. return base64.StdEncoding.EncodeToString([]byte(md5Str)), nil
  205. }
  206. // computeForHMACSHA256 计算HMACSHA265
  207. func computeForHMACSHA256(str, secret string) (string, error) {
  208. mac := hmac.New(sha256.New, []byte(secret))
  209. _, err := mac.Write([]byte(str))
  210. if err != nil {
  211. return "", err
  212. }
  213. return base64.StdEncoding.EncodeToString(mac.Sum(nil)), nil
  214. }
  215. // buildSignString 计算签名字符串
  216. func buildSignString(header map[string]string, url, method string) string {
  217. var sb []string
  218. sb = append(sb, strings.ToUpper(method))
  219. sb = append(sb, "\n")
  220. if header != nil {
  221. if _, ok := header["Accept"]; ok {
  222. sb = append(sb, header["Accept"])
  223. sb = append(sb, "\n")
  224. }
  225. if _, ok := header["Content-MD5"]; ok {
  226. sb = append(sb, header["Content-MD5"])
  227. sb = append(sb, "\n")
  228. }
  229. if _, ok := header["Content-Type"]; ok {
  230. sb = append(sb, header["Content-Type"])
  231. sb = append(sb, "\n")
  232. }
  233. }
  234. sb = append(sb, buildSignHeader(header))
  235. sb = append(sb, url)
  236. return strings.Join(sb, "")
  237. }
  238. // buildSignHeader 计算签名头
  239. func buildSignHeader(header map[string]string) string {
  240. var sslice []string
  241. for key := range header {
  242. if strings.Contains(key, "x-ca-") {
  243. sslice = append(sslice, key)
  244. }
  245. }
  246. sort.Strings(sslice)
  247. var sbSignHeader []string
  248. var sb []string
  249. for _, k := range sslice {
  250. sb = append(sb, k+":")
  251. if header[k] != "" {
  252. sb = append(sb, header[k])
  253. }
  254. sb = append(sb, "\n")
  255. sbSignHeader = append(sbSignHeader, k)
  256. }
  257. header["x-ca-signature-headers"] = strings.Join(sbSignHeader, ",")
  258. return strings.Join(sb, "")
  259. }
  260. // buildQueryParams 构建查询参数字符串,支持数组、基本类型
  261. func buildQueryParams(params map[string]any) string {
  262. values := make(url.Values)
  263. for key, value := range params {
  264. switch v := value.(type) {
  265. case string:
  266. values.Add(key, v)
  267. case int:
  268. values.Add(key, strconv.Itoa(v))
  269. case bool:
  270. values.Add(key, strconv.FormatBool(v))
  271. case []string:
  272. for _, item := range v {
  273. values.Add(key+"[]", item)
  274. }
  275. case []int:
  276. for _, item := range v {
  277. values.Add(key+"[]", strconv.Itoa(item))
  278. }
  279. case []interface{}:
  280. for _, item := range v {
  281. values.Add(key+"[]", fmt.Sprintf("%v", item))
  282. }
  283. default:
  284. values.Add(key, fmt.Sprintf("%v", v))
  285. }
  286. }
  287. return values.Encode()
  288. }