zoukankan      html  css  js  c++  java
  • 【Golang 接口自动化06】微信支付md5签名计算及其优化

    前言

    可能看过我博客的朋友知道我主要是做的支付这一块的测试工作。而我们都知道现在比较流行的支付方式就是微信支付和支付宝支付,当然最近在使用低手续费大力推广的京东金融(已改名为京东数科)以后也可能站到第一队列,但是要在中国市场走到和财付通、蚂蚁金服一个层级就任重而道远了。

    废话不多说,我们一起来看看微信支付签名的官方文档。搜索微信支付--点击支付开发文档--接口规则--安全规范。

    我们会看的以下的内容:

    签名生成的方法文档已经说的很清晰,下面我们一起来看看怎么使用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/opB/opallocs/op分别代表每个操作的耗时、分配内存、分配对象次数。可以看的三者都有较大的提升。

    当时在知道这个差距之后,我放下了手上的已经构建得七七八八的自动化代码,专心的研究了一段时间开源项目的源码,所以有了下面这个兼容性更好和性能更平衡的版本。

    最终方法

    这个版本主要考虑的是不同数据兼容性,兼容了直接传递struct时进行签名的计算(后面会学习到直接把struct当作json发送的方法),并把map[string]stringmap[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
    • 简单性能测试
  • 相关阅读:
    python学习笔记二--列表
    python学习笔记一--字符串
    写点什么呢
    nagios&pnp4nagios--yum 安装
    敏捷开发的思路
    Foreman--管理PuppetClient
    url编码解码的问题(urlencode/quote)
    json数据的处理和转化(loads/load/dump/dumps)
    http和https的区别
    python中requests的用法总结
  • 原文地址:https://www.cnblogs.com/Detector/p/9741189.html
Copyright © 2011-2022 走看看