前言
可能看過我博客的朋友知道我主要是做的支付這一塊的測試工作。而我們都知道現在比較流行的支付方式就是微信支付和支付寶支付,當然最近在使用低手續費大力推廣的京東金融(已改名為京東數科)以后也可能站到第一隊列,但是要在中國市場走到和財付通、螞蟻金服一個層級就任重而道遠了。
廢話不多說,我們一起來看看微信支付簽名的官方文檔。搜索微信支付--點擊支付開發文檔--接口規則--安全規范。

我們會看的以下的內容:

簽名生成的方法文檔已經說的很清晰,下面我們一起來看看怎么使用golang來實現它,以及怎么使用一些更高級的特性來優化。
初始方式
最開始的方式比較直接,能實現這個需求就行:
func GetSign(sourceMap map[string]string, bizKey string) string {
orderedString := orderParam(sourceMap, bizKey)
md5Ctx := md5.New()
md5Ctx.Write([]byte(orderedString))
signString := md5Ctx.Sum(nil)
//fmt.Print(hex.EncodeToString(cipherStr))
return hex.EncodeToString(signString)
}
func orderParam(source map[string]string, bizKey string) string {
var tempArr []string
i := 0
for k, v := range source {
tempArr = append(tempArr, k+"="+v)
i++
}
sort.Strings(tempArr)
temString := ""
for n, v := range tempArr {
if n+1 < len(tempArr) {
temString = temString + v + "&"
} else {
temString = temString + v + bizKey
}
}
fmt.Println(temString)
return temString
}
代碼說明
- orderParam主要用來把傳遞的參數轉化為鍵值對的格式(即key1=value1&key2=value2…)並在最后拼接上key
- GetSign 獲取orderParam拼接之后字符串進行md5加密
后來發現這樣的方式有很多的弊端,比如無法處理可能某個參數是數字的情況,無法處理某個參數的value值是map或數組的情況,所以就進行了兼容性和性能上的優化。
優化
這一次的優化主要就是添加了格式的兼容,將傳入的參數變成了可以存儲任何類型數據的interface{},另外就是優化了拼接字符串的操作。優化后的代碼如下
func betterOne(srcmap map[string]interface{}, bizkey string) string {
md5ctx := md5.New()
keys := make([]string, 0, len(srcmap))
for k := range srcmap {
if k == "sign" {
continue
}
keys = append(keys, k)
}
sort.Strings(keys)
var buf bytes.Buffer
for _, k := range keys {
vs := srcmap[k]
if vs == "" {
continue
}
if buf.Len() > 0 {
buf.WriteByte('&')
}
buf.WriteString(k)
buf.WriteByte('=')
switch vv := vs.(type) {
case string:
buf.WriteString(vv)
case int:
buf.WriteString(strconv.FormatInt(int64(vv), 10))
default:
panic("params type not supported")
}
}
buf.WriteString(bizkey)
md5ctx.Write([]byte(buf.String()))
return hex.EncodeToString(md5ctx.Sum(nil))
}
buf.WriteString使用buffer來替代循環的字符串操作,來自於基礎庫http庫中的url.encode方法,在此前 【Golang 接口自動化01】使用標准庫net/http發送Get請求 提到過這個方法。理論上來說性能會有一個比較大的提升,實際測試結果如下:

其中ns/op、B/op、allocs/op分別代表每個操作的耗時、分配內存、分配對象次數。可以看的三者都有較大的提升。
當時在知道這個差距之后,我放下了手上的已經構建得七七八八的自動化代碼,專心的研究了一段時間開源項目的源碼,所以有了下面這個兼容性更好和性能更平衡的版本。
最終方法
這個版本主要考慮的是不同數據兼容性,兼容了直接傳遞struct時進行簽名的計算(后面會學習到直接把struct當作json發送的方法),並把map[string]string、map[string]interface{}的數據都進行了對應處理。
性能對比
下面是三者對比的性能測試結果

可以看到最終版比優化版損耗的時間還要略短。
參考代碼
// Getsign get the sign info
func Getsign(srcdata interface{}, bizkey string) string {
md5ctx := md5.New()
switch v := reflect.ValueOf(srcdata); v.Kind() {
case reflect.String:
md5ctx.Write([]byte(v.String() + bizkey))
return hex.EncodeToString(md5ctx.Sum(nil))
case reflect.Map:
orderStr := orderParam(v.Interface(), bizkey)
md5ctx.Write([]byte(orderStr))
return hex.EncodeToString(md5ctx.Sum(nil))
case reflect.Struct:
orderStr := Struct2map(v.Interface(), bizkey)
md5ctx.Write([]byte(orderStr))
return hex.EncodeToString(md5ctx.Sum(nil))
default:
return ""
}
}
func orderParam(source interface{}, bizKey string) (returnStr string) {
switch v := source.(type) {
case map[string]string:
keys := make([]string, 0, len(v))
for k := range v {
if k == "sign" {
continue
}
keys = append(keys, k)
}
sort.Strings(keys)
var buf bytes.Buffer
for _, k := range keys {
if v[k] == "" {
continue
}
if buf.Len() > 0 {
buf.WriteByte('&')
}
buf.WriteString(k)
buf.WriteByte('=')
buf.WriteString(v[k])
}
buf.WriteString(bizKey)
returnStr = buf.String()
case map[string]interface{}:
keys := make([]string, 0, len(v))
for k := range v {
if k == "sign" {
continue
}
keys = append(keys, k)
}
sort.Strings(keys)
var buf bytes.Buffer
for _, k := range keys {
if v[k] == "" {
continue
}
if buf.Len() > 0 {
buf.WriteByte('&')
}
buf.WriteString(k)
buf.WriteByte('=')
// buf.WriteString(srcmap[k])
switch vv := v[k].(type) {
case string:
buf.WriteString(vv)
case int:
buf.WriteString(strconv.FormatInt(int64(vv), 10))
default:
panic("params type not supported")
}
}
buf.WriteString(bizKey)
returnStr = buf.String()
}
// fmt.Println(returnStr)
return
}
func Struct2map(content interface{}, bizKey string) string {
var tempArr []string
temString := ""
var val map[string]string
if marshalContent, err := json.Marshal(content); err != nil {
fmt.Println(err)
} else {
d := json.NewDecoder(bytes.NewBuffer(marshalContent))
d.UseNumber()
if err := d.Decode(&val); err != nil {
fmt.Println(err)
} else {
for k, v := range val {
val[k] = v
}
}
}
i := 0
for k, v := range val {
// 去除冗余未賦值struct
if v == "" {
continue
}
i++
tempArr = append(tempArr, k+"="+v)
}
sort.Strings(tempArr)
for n, v := range tempArr {
if n+1 < len(tempArr) {
temString = temString + v + "&"
} else {
temString = temString + v + bizKey
}
}
return temString
}
總結
- 微信簽名
- buffer
- 簡單性能測試
