| package wsclient |
| |
| import ( |
| "bytes" |
| "crypto/hmac" |
| "crypto/sha256" |
| "crypto/tls" |
| "encoding/hex" |
| "encoding/json" |
| "errors" |
| "fmt" |
| "net" |
| "net/http" |
| "strconv" |
| "strings" |
| "sync" |
| "time" |
| |
| "crypto/x509" |
| |
| log "github.com/sirupsen/logrus" |
| "gopkg.in/resty.v1" |
| ) |
| |
| var ( |
| // ErrBadCAPEM 错误的 CA 文件 |
| ErrBadCAPEM = errors.New("不正确的PEM文件") |
| ) |
| |
| var connectionTimeout = time.Duration(500) * time.Millisecond |
| |
| // WebSession web session object |
| type WebSession struct { |
| // AppId app id |
| AppID string |
| // TermId term id |
| TermID string |
| // Appsecret secret |
| AppSecret string |
| // BaseUrl base url |
| BaseURL string |
| // DefaultTimeout default time |
| DefaultTimeout int |
| sessionKey string |
| jwt string |
| expiredAt int64 |
| sslVerify bool |
| startTime int64 |
| httpConnectionPool sync.Pool |
| } |
| |
| func safeGetJSONInt(value interface{}) int { |
| if value == nil { |
| return 0 |
| } |
| s := fmt.Sprintf("%v", value) |
| i, err := strconv.Atoi(s) |
| if err != nil { |
| return 0 |
| } |
| return i |
| } |
| |
| // ServiceResponse service response object |
| type ServiceResponse struct { |
| // RetCode return code |
| RetCode int |
| // RetMsg return message |
| RetMsg string |
| // Result return data |
| Result map[string]interface{} |
| } |
| |
| // NewServiceResponseFromJSON parse json response data |
| func NewServiceResponseFromJSON(jsonData interface{}) *ServiceResponse { |
| if jsonData == nil { |
| return nil |
| } |
| res := &ServiceResponse{} |
| res.Result = jsonData.(map[string]interface{}) |
| res.RetCode = safeGetJSONInt(res.Result["retcode"]) |
| res.RetMsg = res.GetStrValue("retmsg") |
| return res |
| } |
| |
| // GetIntValue get int value |
| func (r *ServiceResponse) GetIntValue(name string) int { |
| return safeGetJSONInt(r.Result[name]) |
| } |
| |
| // GetStrValue get string value |
| func (r *ServiceResponse) GetStrValue(name string) string { |
| s, ok := r.Result[name] |
| if ok { |
| return fmt.Sprintf("%v", s) |
| } |
| return "" |
| } |
| |
| // GetInterfaceValue get value as interface |
| func (r *ServiceResponse) GetInterfaceValue(name string) interface{} { |
| s, ok := r.Result[name] |
| if ok { |
| return s |
| } |
| return nil |
| } |
| |
| // GetFloatValue get float value |
| func (r *ServiceResponse) GetFloatValue(name string) float64 { |
| if s, ok := r.Result[name]; ok { |
| t := fmt.Sprintf("%v", s) |
| f, err := strconv.ParseFloat(t, 64) |
| if err != nil { |
| return 0.0 |
| } |
| return f |
| } |
| return 0.0 |
| } |
| |
| // doGet send GET request |
| func (w *WebSession) doGet(uri string, params map[string]string, |
| timeout int) (*resty.Response, error) { |
| |
| fullURL := w.BaseURL + uri |
| resty.SetTimeout(time.Duration(timeout) * time.Second) |
| resp, err := resty.R(). |
| SetQueryParams(params). |
| Get(fullURL) |
| return resp, err |
| } |
| |
| // GetTimestamp get time stamp format 20160103133455 |
| func (w *WebSession) GetTimestamp() string { |
| t := time.Now() |
| return fmt.Sprintf("%04d%02d%02d%02d%02d%02d", t.Year(), t.Month(), t.Day(), |
| t.Hour(), t.Minute(), t.Second()) |
| } |
| |
| // SignWithKey sign with key |
| func (w *WebSession) SignWithKey(key, message string) string { |
| mac := hmac.New(sha256.New, []byte(key)) |
| mac.Write([]byte(message)) |
| res := mac.Sum(nil) |
| return hex.EncodeToString(res) |
| } |
| |
| // Sign sign data |
| func (w *WebSession) Sign(message string) string { |
| return w.SignWithKey(w.AppSecret, message) |
| } |
| |
| func newTransport(baseurl string, sslVerify bool) *http.Transport { |
| var transport http.Transport |
| if strings.HasPrefix(baseurl, "https://") { |
| var b bool |
| if sslVerify { |
| b = false |
| } else { |
| b = true |
| } |
| transport = http.Transport{MaxIdleConnsPerHost: 0, |
| TLSClientConfig: &tls.Config{InsecureSkipVerify: b}, |
| TLSHandshakeTimeout: time.Duration(1) * time.Second, |
| Dial: func(network, addr string) (net.Conn, error) { |
| defaultTimeout := time.Duration(1) * time.Second |
| return net.DialTimeout(network, addr, defaultTimeout) |
| }} |
| } else if strings.HasPrefix(baseurl, "http://") { |
| transport = http.Transport{MaxIdleConnsPerHost: 0, |
| Dial: func(network, addr string) (net.Conn, error) { |
| defaultTimeout := time.Duration(1) * time.Second |
| return net.DialTimeout(network, addr, defaultTimeout) |
| }} |
| } |
| log.Debugf("创建新连接") |
| return &transport |
| } |
| |
| // doPost send POST request |
| func (w *WebSession) doPost(uri string, param map[string]string) (*resty.Response, error) { |
| param["app_id"] = w.AppID |
| param["term_id"] = w.TermID |
| param["sign_method"] = "HMAC" |
| param["session_key"] = w.sessionKey |
| ts := w.GetTimestamp() |
| param["timestamp"] = ts |
| param["sign"] = w.Sign(w.AppID + w.TermID + w.sessionKey + ts) |
| |
| fullURL := w.BaseURL + uri |
| r, err := resty.R(). |
| SetHeader("Content-Type", "application/json"). |
| SetBody(param). |
| Post(fullURL) |
| if err != nil || r.StatusCode() != 200 { |
| log.Errorf("Status=%v, err=%v", r.StatusCode(), err) |
| } |
| return r, err |
| } |
| |
| // Auth authorization |
| func (w *WebSession) Auth() error { |
| token, err := w.getAuthToken() |
| if err != nil { |
| return err |
| } |
| err = w.getAppAccessKey(token) |
| if err != nil { |
| return err |
| } |
| return nil |
| } |
| |
| func (w *WebSession) updateAuth() { |
| for { |
| log.Debugf("设备: %v 正在尝试重新连接登录验证!", w.AppID) |
| err := w.Auth() |
| if err == nil { |
| return |
| } |
| time.Sleep(time.Second * 10) |
| } |
| } |
| |
| // NewSession new session object |
| func NewSession(appid, appsecret, termid, baseurl string, timeout int, sslVerify bool) *WebSession { |
| return &WebSession{ |
| AppID: appid, |
| AppSecret: appsecret, |
| TermID: termid, |
| BaseURL: baseurl, |
| DefaultTimeout: timeout, |
| sslVerify: sslVerify, |
| } |
| } |
| |
| // NewSessionWithCA new session ca |
| func NewSessionWithCA(appID, appSecret, termID, baseURL string, |
| timeout int, ca []byte) (*WebSession, error) { |
| certs := x509.NewCertPool() |
| if !certs.AppendCertsFromPEM(ca) { |
| return nil, ErrBadCAPEM |
| } |
| resty.SetTLSClientConfig(&tls.Config{ |
| InsecureSkipVerify: true, |
| RootCAs: certs, |
| }) |
| |
| return &WebSession{ |
| AppID: appID, |
| AppSecret: appSecret, |
| TermID: termID, |
| BaseURL: baseURL, |
| DefaultTimeout: timeout, |
| sslVerify: true, |
| }, nil |
| } |
| |
| func (w *WebSession) getAuthToken() (string, error) { |
| type FormJSON struct { |
| AppID string `json:"app_id"` |
| TermID string `json:"term_id"` |
| AccessToken string `json:"token"` |
| } |
| |
| uri := "/auth/gettoken" |
| |
| params := make(map[string]string) |
| params["appid"] = w.AppID |
| r, err := w.doGet(uri, params, 10) |
| |
| if err != nil { |
| return "", err |
| } |
| if r.StatusCode() != 200 { |
| return "", errors.New("请求失败") |
| } |
| |
| body := r.Body() |
| s := &FormJSON{} |
| err = json.Unmarshal(body, &s) |
| if err != nil { |
| log.Errorf("json unmarshal err %v", err) |
| return "", errors.New("解析失败") |
| } |
| return s.AccessToken, nil |
| } |
| |
| func (w *WebSession) getAppAccessKey(token string) error { |
| type FormJSON struct { |
| AppID string `json:"app_id"` |
| TermID string `json:"term_id"` |
| SessionKey string `json:"session_key"` |
| CardKey string `json:"card_key"` |
| Jwt string `json:"jwt"` |
| ExpiredAt string `json:"expiredAt"` |
| } |
| |
| uri := "/auth/authentication" |
| |
| params := make(map[string]string) |
| params["appid"] = w.AppID |
| params["timestamp"] = w.GetTimestamp() |
| params["sign"] = w.Sign(token) |
| params["sign_method"] = "HMAC-SHA256" |
| |
| r, err := w.doGet(uri, params, 10) |
| if err != nil { |
| log.Errorf("err = %v\n", err) |
| return err |
| } |
| |
| if r.StatusCode() != 200 { |
| log.Errorf(" errcode = %v\n", r.StatusCode()) |
| return fmt.Errorf("code %v", r.StatusCode()) |
| } |
| |
| body := r.Body() |
| |
| s := &FormJSON{} |
| err = json.Unmarshal(body, &s) |
| if err != nil { |
| log.Errorf("json unmarshal err %v", err) |
| return err |
| } |
| w.jwt = s.Jwt |
| //JWT is valid for 20 minutes |
| w.expiredAt = 20 * 60 |
| w.startTime = time.Now().Unix() |
| return nil |
| } |
| |
| func (w *WebSession) updateJwt() error { |
| type FormJSON struct { |
| AppID string `json:"app_id"` |
| Jwt string `json:"jwt"` |
| } |
| nowTime := time.Now().Unix() |
| if nowTime-w.startTime >= w.expiredAt { |
| uri := "/auth/refresh" |
| |
| params := make(map[string]string) |
| params["appid"] = w.AppID |
| |
| fullURL := w.BaseURL + uri |
| log.Debugf("CallService: %v", fullURL) |
| r, err := resty.R(). |
| SetHeader("Authorization", "Bearer "+w.jwt). |
| Get(fullURL) |
| if err != nil || r.StatusCode() != 200 { |
| log.Errorf("Status=%v, err=%v", r.StatusCode(), err) |
| return err |
| } |
| |
| body := r.Body() |
| |
| s := &FormJSON{} |
| err = json.Unmarshal(body, &s) |
| if err != nil { |
| log.Errorf("json unmarshal err %v", err) |
| return err |
| } |
| w.jwt = s.Jwt |
| w.startTime = nowTime |
| } |
| return nil |
| } |
| |
| // CallYKTApi call ykt api function |
| func (w *WebSession) CallYKTApi(request *MessageWriter) (*MessageReader, error) { |
| callData := request.Serialize() |
| params := make(map[string]string) |
| params["funcdata"] = callData |
| |
| r, err := w.doPost("/ecardservice/ecardapi", params) |
| if err != nil { |
| log.Errorf(" err = %v\n", err) |
| return nil, err |
| } |
| |
| if r.StatusCode() != 200 { |
| return nil, fmt.Errorf("Request StatusCode:%v", r.StatusCode()) |
| } |
| |
| return NewMessageReader(r.Body()), nil |
| } |
| |
| // CallService call epay service |
| func (w *WebSession) CallService(path string, params map[string]interface{}, |
| signField []string, timeout int) (response *ServiceResponse, err error) { |
| |
| return w.CallService2(path, params, timeout, signField...) |
| } |
| |
| // SetConnectionTimeout set global connection timeout |
| func SetConnectionTimeout(ms int) { |
| connectionTimeout = time.Duration(ms) * time.Millisecond |
| resty.SetTimeout(connectionTimeout) |
| } |
| |
| // CallService2 call epay service |
| func (w *WebSession) CallService2(path string, params map[string]interface{}, |
| timeout int, signField ...string) (response *ServiceResponse, err error) { |
| err = nil |
| |
| if err = w.updateJwt(); err != nil { |
| log.Errorf("updateJwt err = %v", err) |
| return |
| } |
| formData := make(map[string]string) |
| if params != nil { |
| for k, v := range params { |
| formData[k] = fmt.Sprintf("%v", v) |
| } |
| } |
| formData["app_id"] = w.AppID |
| formData["term_id"] = w.TermID |
| ts := w.GetTimestamp() |
| formData["timestamp"] = ts |
| |
| fullURL := w.BaseURL + path |
| log.Debugf("CallService: %v", fullURL) |
| r, err := resty.R(). |
| SetHeader("Authorization", "Bearer "+w.jwt). |
| SetHeader("Accept", "application/json"). |
| SetFormData(formData). |
| Post(fullURL) |
| if err != nil { |
| log.Errorf("Status=%v, err=%v", r, err) |
| return |
| } |
| |
| if r.StatusCode() == 403 || r.StatusCode() == 401 { |
| w.updateAuth() |
| } |
| |
| if r.StatusCode() != 200 { |
| log.Errorf("Request Error %v", r.StatusCode()) |
| err = fmt.Errorf("Request Error, StatusCode : %v", r.StatusCode()) |
| return |
| } |
| |
| var s interface{} |
| decoder := json.NewDecoder(bytes.NewBuffer(r.Body())) |
| decoder.UseNumber() // 此处能够保证bigint的精度 |
| err = decoder.Decode(&s) |
| // err = json.Unmarshal(r.Body(), &s) |
| if err != nil { |
| log.Errorf("json unmarshal err %v", err) |
| return |
| } |
| response = NewServiceResponseFromJSON(s) |
| return |
| } |