blob: 5d0c7500e039224eb8922e24b9d92d0b77d1c227 [file] [log] [blame]
package swservice
import (
"bytes"
"crypto/hmac"
"crypto/sha1"
"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
sslVerify bool
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(sha1.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
}
// 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:"access_token"`
}
uri := fmt.Sprintf("/authservice/getauth/%v/getaccesstoken", w.AppID)
params := make(map[string]string)
params["term_id"] = w.TermID
r, err := w.doGet(uri, params, 5)
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"`
}
uri := fmt.Sprintf("/authservice/getauth/%v", w.AppID)
params := make(map[string]string)
params["term_id"] = w.TermID
params["access_token"] = token
params["timestamp"] = w.GetTimestamp()
params["v"] = "1"
params["sign"] = w.Sign(token + params["timestamp"])
params["sign_method"] = "HMAC"
r, err := w.doGet(uri, params, 5)
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.sessionKey = s.SessionKey
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
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
signData := ""
for _, k := range signField {
if v, ok := formData[k]; ok {
signData += v
}
}
signData += ts + w.sessionKey
log.Debugf("Sign: key[%v] data[%v]\n", w.sessionKey, signData)
formData["sign_method"] = "HMAC"
formData["sign"] = w.SignWithKey(w.AppSecret, signData)
fullURL := w.BaseURL + path
log.Debugf("CallService: %v\n", fullURL)
r, err := resty.R().
SetHeader("Accept", "application/json").
SetFormData(formData).
Post(fullURL)
if err != nil {
log.Errorf("Status=%v, err=%v", r, err)
return
}
if r.StatusCode() != 200 {
log.Errorf("Request Error %v\n", 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
}