2016-11-08 22:33:32 u013248535 阅读数 5740

微信开发中,涉及到微信支付流程较为繁琐。其中以微信红包为例,参考官方文档:https://pay.weixin.qq.com/wiki/doc/api/tools/cash_coupon.php?chapter=13_4&index=3
调用微信支付链接中,有以下三个字段,需要依据相关算法要求生成。分别是签名(sign),订单号(mch_billno)以及随机字符串(nonce_str)。下面用Java依据算法流程,实现了微信支付工具类,用于生成上述三个字段。
代码如下,已经过测试:


/**
 * Created by Song on 2016/11/8.
 * mail: 1147649695@qq.com
 * 微信支付相关工具类
 */
public class PayWxUtil {
    //上一次订单请求日期
    private static Date preDay = new Date();
    //当前订单日期
    private static Date curDay = new Date();
    //用于记录已产生的订单号
    private static Set<Long> numPoul = new HashSet<Long>();
    /**
     * 获得签名
     * @param params 待编码参数,参数值为空不传入
     * @param key key设置路径:微信商户平台(pay.weixin.qq.com)-->账户设置-->API安全-->密钥设置
     * @return
     */
    public static String getSign(Map<String,String> params,String key){
        List<String> list = new ArrayList<String>(params.keySet());
        Collections.sort(list,new DictionaryCompare());
        StringBuffer sb = new StringBuffer();
        for(String keyVal:list){
            sb.append(keyVal+"="+params.get(keyVal)+"&");
        }
        sb.append("key="+key);
        return DigestUtils.md5Hex(sb.toString()).toUpperCase();
    }

    /**
     * 获得随机字符串
     * @return
     */
    public static String getNonceStr(){
        Random random = new Random();
        long val = random.nextLong();
        String res = DigestUtils.md5Hex(val+"yzx").toUpperCase();
        if(32<res.length()) return res.substring(0,32);
        else return res;
    }

    /**
     * 获取订单号
     * 商户订单号(每个订单号必须唯一)
     * 组成:mch_id+yyyymmdd+10位一天内不能重复的数字。
     * @param mchId
     * @return
     */
    public static String getMchBillno(String mchId){
        Random random = new Random();
        long val = random.nextLong()%10000000000L;//获得0-9999999999内的数字
        curDay = new Date();
        //隔天清空
        if(curDay.after(preDay)) numPoul.clear();
        while(numPoul.contains(val)){
            val = random.nextLong()%10000000000L;
        }
        numPoul.add(val);
        preDay = curDay;
        //按要求,日期格式化输出
        DateFormat df = new SimpleDateFormat("yyyymmdd");
        return mchId+df.format(curDay)+format(val+"",10);
    }

    /**
     * 将字符串str按长度在前面添0补齐
     * @param str
     * @param length
     * @return
     */
    private static String format(String str,int length){
        String pre = "0000000000";
        int len = str.length();
        if(10<=len) return str.substring(0,10);
        else return pre.substring(0,10-len).concat(str);
    }

}

/**
 * 按字典序排序
 */
class DictionaryCompare implements Comparator<String>{
    public int compare(String o1, String o2) {
        return o1.compareTo(o2);
    }
}

其中,签名(sign)的算法流程参考官方说明签名算法其具体实现过程摘录如下:
(1)设所有发送或者接收到的数据为集合M,将集合M内非空参数值的参数按照参数名ASCII码从小到大排序(字典序),使用URL键值对的格式(即key1=value1&key2=value2…)拼接成字符串stringA
(2)在stringA最后拼接上key得到stringSignTemp字符串,并对stringSignTemp进行MD5运算,再将得到的字符串所有字符转换为大写,得到sign值signValue。
关于订单号生成,要求订单号后10位为当天不可重复的10位数。这里用静态Set存储了当天已产生的数字。需要说明的是,这种方式下,如果项目在服务器下重新部署了,该静态值将被清空,从而有可能导致错误;当一天内访问量很高,接近上限值9999999999时,获得该随机数的时间将显著增长,且将耗费JVM中永久区较大内存资源,若JVM相关参数配置不当,可能导致PermSize溢出异常,导致系统崩溃。这两种情况下,建议还是采用基于数据库字段自增来获取该值为好。当然,目前这种方式基于本次项目日红包发送量较低的情况,所以不需考虑以上情况。具体实现方式是:
(1)产生0-9999999999类的随机数
(2)判断当前日期curDay是否在上一次请求日期preDay之后,是则执行(3),否则执行(4)
(3)清空随机数静态缓存numPoul
(4)判断该随机数是否在静态缓存资源numPoul中,存在则执行(1),否则执行(5)
(5)将该随机数写入numPoul中,更新preDay为当前日期
(6)将该随机数添0补齐为10位,并按规则组合,得到订单号并返回。
部分细节方面,可能还有疏漏,若有问题,发现将及时改正更新,也欢迎大家提出存在的问题和看法。

2020-01-20 09:33:40 qq_41525524 阅读数 8

第一 微信服务与本服务的校验

校验传参
signature 微信加密签名
timestamp 时间戳
nonce 随机数
echostr 随机字符串

请求方法接口
``
import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet(name = “CheckSignature”, urlPatterns = { “/CheckSignature/Mobile” })
public class CheckSignature extends HttpServlet{

private static final long serialVersionUID = 1L;

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
	// Response返回
	resp.setContentType("text/html;charset=UTF-8");
	//微信加密签名
	String signature = req.getParameter("signature");
	//时间戳
    String timestamp = req.getParameter("timestamp");
    //随机数
    String nonce = req.getParameter("nonce");
    //随机字符串
    String echostr = req.getParameter("echostr");
    
	System.out.println(signature);
	System.out.println(timestamp);
	System.out.println(nonce);
	System.out.println(echostr);
	
    PrintWriter out =resp.getWriter();
    
    if (CheckUitl.checkSignature(signature, timestamp, nonce)) {
        System.err.println("验证成功");
        out.println(echostr);
    }		
    out.close();
	out = null;
}

}
工具类

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

public class CheckUitl {
	    // 与接口配置信息中的Token要一致
		private static String token = "icecdevchinaedunet";
	    
		public static boolean checkSignature(String signature, String timestamp,
				String nonce) {
			// 从请求中(也就是微信服务器传过来的)拿到的token, timestamp, nonce
			String[] arr = new String[] { token, timestamp, nonce };
			// 将token、timestamp、nonce三个参数进行字典序排序
			sort(arr);
			StringBuilder content = new StringBuilder();
			for (int i = 0; i < arr.length; i++) {
				content.append(arr[i]);
			}
			MessageDigest md = null;
			String tmpStr = null;
	 
			try {
				md = MessageDigest.getInstance("SHA-1");
				// 将三个参数字符串拼接成一个字符串进行sha1加密
				byte[] digest = md.digest(content.toString().getBytes());
				// 将字节数组转成字符串
				tmpStr = byteToStr(digest);
			} catch (NoSuchAlgorithmException e) {
				e.printStackTrace();
			}
	 
			content = null;
			// 将sha1加密后的字符串可与signature对比,标识该请求来源于微信
			return tmpStr != null ? tmpStr.equals(signature.toUpperCase()) : false;
		}
	 
		// 将加密后的字节数组变成字符串
		private static String byteToStr(byte[] byteArray) {
			String strDigest = "";
			for (int i = 0; i < byteArray.length; i++) {
				strDigest += byteToHexStr(byteArray[i]);
			}
			return strDigest;
		}
	 
		private static String byteToHexStr(byte mByte) {
			char[] Digit = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A',
					'B', 'C', 'D', 'E', 'F' };
			char[] tempArr = new char[2];
			tempArr[0] = Digit[(mByte >>> 4) & 0X0F];
			tempArr[1] = Digit[mByte & 0X0F];
	 
			String s = new String(tempArr);
			return s;
		}
	 
		// 用于字典排序
		public static void sort(String a[]) {
			for (int i = 0; i < a.length - 1; i++) {
				for (int j = i + 1; j < a.length; j++) {
					if (a[j].compareTo(a[i]) < 0) {
						String temp = a[i];
						a[i] = a[j];
						a[j] = temp;
					}
				}
			}
		}
}
2018-01-12 20:02:26 hua1017177499 阅读数 6247

第二步:验证消息的确来自微信服务器
开发者提交信息后,微信服务器将发送GET请求到填写的服务器地址URL上,GET请求携带参数如下表所示:
参数 描述
signature 微信加密签名,signature结合了开发者填写的token参数和请求中的timestamp参数、nonce参数。
timestamp 时间戳
nonce 随机数
echostr 随机字符串
开发者通过检验signature对请求进行校验(下面有校验方式)。若确认此次GET请求来自微信服务器,请原样返回echostr参数内容,则接入生效,成为开发者成功,否则接入失败。加密/校验流程如下:
1)将token、timestamp、nonce三个参数进行字典序排序
2)将三个参数字符串拼接成一个字符串进行sha1加密
3)开发者获得加密后的字符串可与signature对比,标识该请求来源于微信

第一步将token,timestamp和nonce这三个参数进行字典序排序,其实就是整体的用Arrays.sort()

// 1.将token、timestamp、nonce三个参数进行字典序排序
String[] arr = new String[] { token, timestamp, nonce };
Arrays.sort(arr);

第二部拼成字符串并进行sha1加密

// 2. 将三个参数字符串
StringBuilder content = new StringBuilder();
    for (int i = 0; i < arr.length; i++) {
        content.append(arr[i]);
    }

//sha1加密
MessageDigest md = MessageDigest.getInstance("SHA-1");
byte[] digest = md.digest(content.toString().getBytes());
tmpStr = byteToStr(digest);

byteToStr()方法作用:将字节数组转换为十六进制字符串

private static String byteToStr(byte[] byteArray) {
        StringBuilder strDigest = new StringBuilder();
        for (int i = 0; i < byteArray.length; i++) {
            strDigest.append(byteToHexStr(byteArray[i]));
        }
        return strDigest.toString();
    }

byteToHexStr()方法作用:将字节转换为十六进制字符串

private static String byteToHexStr(byte mByte) {
        char[] Digit = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A',
                'B', 'C', 'D', 'E', 'F' };
        char[] tempArr = new char[2];
        tempArr[0] = Digit[(mByte >>> 4) & 0X0F];
        tempArr[1] = Digit[mByte & 0X0F];
        String s = new String(tempArr);
        return s;
    }

下面是完整的验证消息来自微信的方法:

/**
     * 方法名:checkSignature</br> 
     * 详述:验证签名</br> 
     * 开发人员:sony</br> 
     * 创建时间:2017-12-29 </br>
     * 
     * @param signature
     * @param timestamp
     * @param nonce
     * @return
     * @throws
     */
    public static boolean checkSignature(String signature, String timestamp,
            String nonce) {
        // 1.将token、timestamp、nonce三个参数进行字典序排序
        String[] arr = new String[] { token, timestamp, nonce };
        Arrays.sort(arr);

        // 2. 将三个参数字符串拼接成一个字符串进行sha1加密
        StringBuilder content = new StringBuilder();
        for (int i = 0; i < arr.length; i++) {
            content.append(arr[i]);
        }
        MessageDigest md = null;
        String tmpStr = null;
        try {
            md = MessageDigest.getInstance("SHA-1");
            // 将三个参数字符串拼接成一个字符串进行sha1加密
            byte[] digest = md.digest(content.toString().getBytes());
            tmpStr = byteToStr(digest);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }

        content = null;
        // 3.将sha1加密后的字符串可与signature对比,标识该请求来源于微信
        return tmpStr != null ? tmpStr.equals(signature.toUpperCase()) : false;
    }

Servlet中的doGet方法中验证:

/**
  * 确认请求来自微信服务器
  */
    @Override
    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        // 微信加密签名
        String signature = request.getParameter("signature");
        // 时间戳
        String timestamp = request.getParameter("timestamp");
        // 随机数
        String nonce = request.getParameter("nonce");
        // 随机字符串
        String echostr = request.getParameter("echostr");

        PrintWriter out = response.getWriter();

        // 通过检验signature对请求进行校验,若校验成功则原样返回echostr,表示接入成功,否则接入失败
        if (SignUtil.checkSignature(signature, timestamp, nonce)) {
            System.out.print("echostr=" + echostr);
            out.print(echostr);
        }

        out.close();
        out = null;
    }

记录在这边防止忘记,希望能给需要的人带来帮助!

2018-04-04 11:38:36 Y__nice 阅读数 417

本文实现的是将微信服务器连接到处理服务器

开发者提交信息后,微信服务器将发送GET请求到填写的服务器地址URL上,参数有四个:

signature微信加密签名,signature结合了开发者填写的token参数和请求中的timestamp参数、nonce参数。
timestamp时间戳
nonce随机数
echostr随机字符串

将token、timestamp、nonce三个参数进行字典序排序 ,将三个参数字符串拼接成一个字符串进行sha1加密 ,开发者获得加密后的字符串可与signature对比,标识该请求来源于微信。

通过signature检查请求是否来自微信服务器,若是,返回$echostr,则接入成功。


接通两个服务器

define("TOKEN", "weixin");
$wechatObj = new wechatCallbackapiTest();
 $wechatObj->run();

class wechatCallbackapiTest
{
    public function run(){
        $echoStr = $_GET["echostr"];
        //valid signature , option
        if($this->checkSignature()){
            echo $echoStr;
            exit;
        }
    }
//返回$echostr  接入成功
public function responseMsg()
    {
        //get post data, May be due to the different environments
        $postStr = $GLOBALS["HTTP_RAW_POST_DATA"];
        file_put_contents('msg.txt', $postStr,FILE_APPEND);
        //extract post data
        if (!empty($postStr)){
                
                $postObj = simplexml_load_string($postStr, 'SimpleXMLElement', LIBXML_NOCDATA);
                $fromUsername = $postObj->FromUserName;
                $toUsername = $postObj->ToUserName;
                $keyword = trim($postObj->Content);
                $time = time();
                $textTpl = "<xml>
                            <ToUserName><![CDATA[%s]]></ToUserName>
                            <FromUserName><![CDATA[%s]]></FromUserName>
                            <CreateTime>%s</CreateTime>
                            <MsgType><![CDATA[%s]]></MsgType>
                            <Content><![CDATA[%s]]></Content>
                            <FuncFlag>0</FuncFlag>
                            </xml>";    
                        
                if(!empty( $keyword ))
                {


                    $msgType = "text";
                    $contentStr = "Welcome to wechat world!";
                    $resultStr = sprintf($textTpl, $fromUsername, $toUsername, $time, $msgType, $contentStr);
                    echo $resultStr;
                    file_put_contents('msg.txt', $resultStr,FILE_APPEND);
                }else{
                    echo "Input something...";
                }


        }else {
            echo "";
            exit;
        }
    }
//判断请求是否来自微信服务器   (签名是否一致) 

private function checkSignature()
    {
        $signature = $_GET["signature"];
        $timestamp = $_GET["timestamp"];
        $nonce = $_GET["nonce"];    
                
        $token = TOKEN;
        $tmpArr = array($token, $timestamp, $nonce);
        sort($tmpArr);
        $tmpStr = implode( $tmpArr );
        $tmpStr = sha1( $tmpStr );
        
        if( $tmpStr == $signature ){
            return true;
        }else{
            return false;
        }
    }



2016-12-23 17:50:29 LeoFitz 阅读数 389

微信开发者模式一些参数简介

1.开发者提交信息后,微信服务器将发送GET请求到填写的服务器地址URL上,GET请求携带参数如下表所示:

signature 微信加密签名,signature结合了开发者填写的token参数和请求中的timestamp参数、nonce参数。
timestamp 时间戳
nonce 随机数
echostr 随机字符串

2.开发者通过检验signature对请求进行校验(下面有校验方式)。若确认此次GET请求来自微信服务器,请原样返回echostr参数内容,则接入生效,成为开发者成功,否则接入失败。加密/校验流程如下:

1)将token、timestamp、nonce三个参数进行字典序排序

2)将三个参数字符串拼接成一个字符串进行sha1加密

3)开发者获得加密后的字符串可与signature对比,标识该请求来源于微信

对应源代码

①加密/校验方法代码

 1 package com.util;
 2 
 3 import java.security.MessageDigest;
 4 import java.util.Arrays;
 5 
 6 public class CheckUtil {
 7  private static final String token="grantward";
 8   public static boolean checkSignature(String signature,String timestamp,String nonce){
 9       
10       String []arr=new String[]{token,timestamp,nonce};
11       //排序
12       Arrays.sort(arr);
13       //生成字符串
14       StringBuffer content=new StringBuffer();
15       for (int i = 0; i < arr.length; i++) {
16           content.append(arr[i]);
17         
18     }
19       String temp=getSha1(content.toString());
20       System.out.println("x-->"+temp);
21       System.out.println("z>>>"+signature);
22       return temp.equals(signature);
23       
24       
25       
26   }
27  //sha1加密
28   private static String getSha1(String str) {
29       if (str == null || str.length() == 0) {
30       return null;
31       }
32       char hexDigits[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
33       MessageDigest mdTemp;
34       try {
35       mdTemp = MessageDigest.getInstance("SHA1");
36       mdTemp.update(str.getBytes("UTF-8"));
37       byte[] md = mdTemp.digest();
38       int j = md.length;
39       char buf[] = new char[j * 2];
40       int k = 0;
41       for (int i = 0; i < j; i++) {
42       byte b0 = md[i];
43       buf[k++] = hexDigits[b0 >>> 4 & 0xf];
44       buf[k++] = hexDigits[b0 & 0xf];
45       }
46       return new String(buf);
47       } catch (Exception e) {
48       return null;
49       }
50       }
51 }

②servlet代码

 1 package com.servlet;
 2 
 3 import java.io.IOException;
 4 import java.io.PrintWriter;
 5 import java.util.Date;
 6 import java.util.Map;
 7 
 8 import javax.servlet.ServletException;
 9 import javax.servlet.http.HttpServlet;
10 import javax.servlet.http.HttpServletRequest;
11 import javax.servlet.http.HttpServletResponse;
12 
13 import org.dom4j.DocumentException;
14 
15 import com.po.TextMessage;
16 import com.util.MessagesUitl;
17 
18 import sun.misc.MessageUtils;
19 
20 
21 
22 public class WeixinServlet extends HttpServlet{
23 @Override
24 protected void doGet(HttpServletRequest req, HttpServletResponse resp)
25         throws ServletException, IOException {
26     String signature=req.getParameter("signature");
27     String timestamp=req.getParameter("timestamp");
28     String nonce=req.getParameter("nonce");
29     String echostr=req.getParameter("echostr");
30     PrintWriter out=resp.getWriter();
31     if (com.util.CheckUtil.checkSignature(signature, timestamp, nonce)) {
32         System.out.println();
33         out.print(echostr);
34     }else{
35         System.out.println("不匹配");
36     }
37 }
38 74 }

 

接入微信公众平台

阅读数 284

java微信二次开发01

阅读数 356

没有更多推荐了,返回首页