课程目标
1. 掌握二维码生成插件qrious的使用
2. 能够说出微信支付开发的整体思路
3. 能够调用微信支付接口(统一下单)生成支付二维码
4. 能够调用微信支付接口(查询订单)查询支付状态
5. 实现支付日志的生成与订单状态的修改
二维码
-
什么是二维码
-
二维码又称QR Code,QR全称Quick Response,是一个近几年来移动设备上超流行的一种编码方式,它比传统的Bar Code条形码能存更多的信息,也能表示更多的数据类型。
-
二维条码/二维码(2-dimensional bar code)是用某种特定的几何图形按一定规律在平面(二维方向上)分布的黑白相间的图形记录数据符号信息的;在代码编制上巧妙地利用构成计算机内部逻辑基础的“0”、“1”比特流的概念,使用若干个与二进制相对应的几何形体来表示文字数值信息,通过图象输入设备或光电扫描设备自动识读以实现信息自动处理:它具有条码技术的一些共性:每种码制有其特定的字符集;每个字符占有一定的宽度;具有一定的校验功能等。同时还具有对不同行的信息自动识别功能、及处理图形旋转变化点。
-
图示:
-
-
二维码的优势:
- 信息容量大, 可以容纳多达1850个大写字母或2710个数字或500多个汉字
- 应用范围广, 支持文字,声音,图片,指纹等等…
- 容错能力强, 即使图片出现部分破损也能使用
- 成本低, 容易制作
-
二维码的容错级别:
L级(低) 7%的码字可以被恢复。
M级(中) 的码字的15%可以被恢复。
Q级(四分)的码字的25%可以被恢复。
H级(高) 的码字的30%可以被恢复。
二维码生成插件qrious:
-
qrious是一款基于HTML5 Canvas的纯JS二维码生成插件。通过qrious.js可以快速生成各种二维码,你可以控制二维码的尺寸颜色,还可以将生成的二维码进行Base64编码。
-
qrious.js二维码插件的可用配置参数如下:
-
释义:
扫描二维码关注公众号,回复: 4525577 查看本文章- 方块确定方位,黑色代表1,白色代表0
微信扫码支付
1. 介绍:
* 微信扫码支付是商户系统按微信支付协议生成支付二维码,用户再用微信“扫一扫”完成支付的模式。该模式适用于 PC 网站支付、实体店单品或订单支付、媒体广告支付等场景。
2. 申请步骤:(了解)
1. 第一步:注册公众号(类型须为:服务号)
请根据营业执照类型选择以下主体注册:个体工商户| 企业/公司| 政府| 媒体| 其他类型。
2. 第二步:认证公众号
公众号认证后才可申请微信支付,认证费:300 元/次。
3. 第三步:提交资料申请微信支付
登录公众平台,点击左侧菜单【微信支付】,开始填写资料等待审核,审核时间为 1-5个工作日内。
4. 第四步:开户成功,登录商户平台进行验证
资料审核通过后,请登录联系人邮箱查收商户号和密码,并登录商户平台填写财付通备付金打的小额资金数额,完成账户验证。
5. 第五步:在线签署协议
本协议为线上电子协议,签署后方可进行交易及资金结算,签署完立即生效。
微信支付SDK
1. 微信支付接口调用的整体思路:
* 按 API 要求组装参数,以 XML 方式发送(POST)给微信支付接口(URL),微信支付接口也是以 XML 方式给予响应。程序根据返回的结果(其中包括支付 URL)生成二维码或判断订单状态
* 简单来讲:通过URL将一些参数告诉给微信,然后接受返回值生成二维码
2. 释义:
1. appid:微信公众账号或开放平台 APP 的唯一标识
2. mch_id:商户号 (配置文件中的 partner)
3. partnerkey:商户密钥
4. sign:数字签名, 根据微信官方提供的密钥和一套算法生成的一个加密信息, 就是为了保证交易的安全性【每次都不同】
3. 微信支付SDK
* 微信支付提供了 SDK, 下载后打开源码,install 到本地仓库;
* 导入依赖:
<dependency>
<groupId>com.github.wxpay</groupId>
<artifactId>wxpay-sdk</artifactId>
<version>0.0.3</version>
</dependency>
4. 我们主要会用到微信支付 SDK 的以下功能:
1. 获取随机字符串
WXPayUtil.generateNonceStr()
2. MAP 转换为 XML 字符串(自动添加签名)
WXPayUtil.generateSignedXml(param, partnerkey)
3. XML 字符串转换为 MAP
WXPayUtil.xmlToMap(result)
5. HttpClient工具类
1. 介绍:
1. HttpClient 是 Apache Jakarta Common 下的子项目,用来提供高效的、最新的、功能丰富的支持 HTTP 协议的客户端编程工具包,并且它支持 HTTP 协议最新的版本和建议。HttpClient 已经应用在很多的项目中,比如 Apache Jakarta 上很著名的另外两个开源项目Cactus 和 HTMLUnit 都使用了 HttpClient。HttpClient
2. 通俗的讲就是模拟了浏览器的行为,如果我们需要在后端向某一地址提交数据获取结果,就可以使用 HttpClient.
3. 关于 HttpClient(原生)具体的使用不属于我们本章的学习内容,我们这里这里为了简化 HttpClient 的使用,提供了工具类 HttpClient(对原生 HttpClient 进行了封装)
2. HttpClient工具类使用的步骤:
HttpClient client=new HttpClient(请求的 url 地址);
client.setHttps(true);//是否是 https 协议
client.setXmlParam(xmlParam);//发送的 xml 数据
client.post();//执行 post 请求
String result = client.getContent(); //获取结果
查询状态
思路:
* 前端循环调用后端
* 后端循环调用微信支付查询结果,前端调用后端
2. 交易状态类型:
* SUCCESS: 支付成功
* REFUND : 转入退款
* NOTPAY: 未支付
* CLOSED: 已关闭
* REVOKED: 已撤销
* USERPAYING: 用户支付中
* PAYERROR: 支付失败(其他原因,银行返回失败)
* 支付状态机等可以查看相关文档
支付日志需求
* 思路:
* 在用户下订单时,判断如果为微信支付,就向支付日志表添加一条记录,信息包括支付总金额,订单id(多个),用户id,下单时间等信息,支付状态为0(未支付);
* 生成的支付日志对象放入reids中,以用户id作为key,这样在生成支付二维码时就可以从redis中提取支付日志对象中的金额和订单号;
* 当用户支付成功后,修改支付日志的支付状态为1(已支付),并记录微信传递给我们的交易流水号。根据订单id(多个)修改订单的状态为2(已付款).
微信支付准备:
1. 业务流程:
1. 选择商品以及相应的规格和数量后点击"加入购物车"。
2. 当加入购物车后跳转到购物车列表页面,对收货地址,商品添加或者删除等操作后点击"结算"
3. 当带着参数进入结算页的时候,首先对使用微信支付和货到付款等方式进行判断,然后跳转到相应的页面
4. 【今日】页面通过与微信官方交互然后自动生成二维码,用户扫码之后显示"付款成功"或者"付款失败"等.
2. 技术分析[准备阶段]:
1. 微信支付功能必须为服务号才能申请微信付款功能,这个由公司完成并提供给我们相关的账户和密码;
2. 查看微信支付SDK文档,获取大概的流程;
3. 首先应该在本地仓库中配好微信支付SDK的jar包,并引入微信支付SDK的依赖
4. 使用HttpClient工具类,方便向微信官方发送请求和接收请求;
5. 搭建工程,分为消费方和服务方,消费方应该直接放入用户加入购物车的消费项目里,服务方加入服务方,服务方分成interface和service解耦合;
6. 应该引入HttpClient.java工具类和添加依赖,添加properties配置文件[这里的配置文件应该为商户的信息,如appid,partner,partnerkey...等]
支付流程图
1. 首先生成微信二维码:
1. 准备:
1. 通过HttpClient工具类实现对远程支付接口的调用
2. 接口连接:https://api.mch.weixin.qq.com/pay/unifiedorder
1. 参考文档:统一下单API
2. 开始:
1. interface:接口服务层应该建立一个生成微信二维码的接口
2. serviceimpl:注入properties中的配置文件,创建一个生成二维码的方法createNative
* 创建一个map集合,将需要发送给微信的数据存入map集合内
* 通过WXPayUtil工具类将集合内的数据转为XML格式数据
* 通过HttpClient发送给微信
* 获取XML结果后调用WXPayUtil工具类将它转为Map格式的数据
* 创建一个新的Map集合,将获取的结果存入新建的map集合内 【这里的数据是要展现给前端的,所以只存一些需要展示的,不必所有都存入,否则安全性有影响】
* 将集合返回
3. Controller:
1. 通过@Reference注解注入Service的类:WeixinPayService
2. 建立一个生成微信二维码的方法,调用雪花算法将订单号和支付类型作为方法参数调用Serivec的createNative,将得到的map集合返回上去;
4. payService.js:
1. 建立一个本地支付的方法,指向pay/createNative.do [Controller.java类的生成二维码方法]
5. payController.js:
1. 建立一个本地生成二维码的方法,前端页面调用此方法进行查询后端,获取结果后得到订单编号和金额大小,同时在这里生成二维码,将订单编号和金额大小都返回到前端页面;
6. pay.html:
1. 首先引入js相关文件,qrious.min.js创建二维码的js文件,以及绑定ng-app,ng-controller,初始调用ng-init生成二维码功能
2. 将放置二维码图片的地方<img id="qrious">
3. 显示订单:订单号:{{out_trade_no}} 显示金额:<em class="orange money">¥{{money}}</em>元
2. 然后,检测支付状态:
1. 准备:
1. 需求:
* 当用户支付成功的时候我们应该跳转到成功页面,提示:恭喜您,付款成功啦!,支付方式:微信支付 支付金额:xxxx元
* 当用户支付失败的时候我们应该跳转到失败页面,提示:支付失败,请稍后再试! 失败原因:... 提供一个重新支付按钮以及跳转到品优购首页的功能
2. 实现思路:
1. 我们可以通过HttpClient工具类实现对远程支付接口的调用
2. 微信接口链接:
https://api.mch.weixin.qq.com/pay/orderquery
3. 参考文档: 查询订单API
4. 思路:
1. 我们可以在后端建立查询状态方法,方法内通过循环查询,如果获取到结果则进行判断:
* 如果结果为Null,则返回空map集合,
* 如果有值,则返回给controller,再返回给前端
2. 前端进行循环调用查询状态方法。前端controller.js对返回的结果进行判断:
1. 如果map==null,则提示支付出错,跳出循环判断,
2. 如果map.get("trade_state").equals("SUCCESS"),那么则返回支付成功,
3. 同时程序可以使用Thread进行睡眠,Thread.sleep(3000),此方法可以使循环查询每三秒执行一次
2. 开始:
1. interface:
* 创建查询状态方法,接收参数订单编号
2. serviceImpl:
* 实现查询状态方法,新建一个HashMap,然后存入公众号id,商户号id,订单号,随机字符串,以及查询订单状态的url地址;【URL地址是微信提供的,其他通过配置文件注入和参数接收】
* 通过WXPayUtil将Map集合转为XML格式的字符串,然后通过HttpClient传入url地址发送获取状态;
* 接收状态并将其转为Map集合,然后返回结果,如果获取失败则打印异常;
3. controller:
* 创建queryPayStatus
1. 先定义一个实体类Result resutl=null;
2. 循环调用Service层的查询订单状态方法并获取返回值;
3. 对返回值进行判断,如果==null,则result:false,出错
4. 如果map.get("trade_state").equals("SUCCESS"),则result:true,"支付成功"
5. 线程等待每3秒执行一次
4. payService.js:
* 创建查询状态方法并指向查询状态的地址和方法,传给它订单编号;
5. payController.js:
* controller.js中创建查询状态方法,方法中对返回结果进行判断,
1. 如果返回结果为true,则跳转到支付成功页面
2. 如果返回结果为false,则跳转到失败页面
* 将该方法在createNative生成二维码的时候进行调用,这样在二维码生成的时候就完成了查询订单状态的功能;因为二维码是初始化init就进行调用,所以生成二维码开始起就可以对二维码状态进行查询;
再然后,我们需要加一个查询时间限制
1. 准备:
1. 需求:
* 如果用户到了二维码页面一直未支付,或是关掉了支付页面,我们的代码会一直循环调用微信接口,这样会对程序造成很大的压力。所以我们要加一个时间限制或是循环次数限制,当超过时间或次数时,跳出循环。
2. 实现思路:
1. 可以在Contrller.java中的查询状态方法内定义一个计时器,int x=0; 然后再while(true){}循环查询状态的时候每次x++;如果x>=100时[可以自定义时间],然后result=new Result(false,"二维码超时");
2. 在 前端中payController.js中再添加一个判断,如果成功则跳转,如果失败则进行判断,如果返回的结果是二维码超时,则重新生成二维码,否则就跳转到失败页面;
、
再然后,支付成功页面我们应该显示金额
1. 准备:
1. 需求:现在我们支付成功页面显示的是固定的值,怎么显示真正的支付金额呢?我们这里可以使用 angularJS 的页面传参来解决
2. 实现思路:
* payController.js中的查询支付状态方法内,如果返回的result是true,支付成功,那么我们在跳转到支付成功页面的时候应该传入参数:$scope.money [付款金额]
2. 开始:
1. payController.js:
1. 跳转页面传参,当支付成功后跳转到成功页面的时候传入付款金额
* if(response.success){
location.href="paysuccess.html#?money="+$scope.money;}
2. 引入$location服务,新增获取金额的方法;
return $location.search()['money'];
2. paysuccess.html:
1. 引入相关的js,并在body添加指令:ng-app,ng-controller
2. 用表达式显示金额: <p>支付金额:¥{{getMoney()}}元</p>
最后,我们应该添加一个支付日志的功能
1. 准备:
1. 需求:
1. 系统中无法查询到支付记录
2. 支付后订单状态没有改变
2. 实现思路:
1. 在用户下订单时,判断如果为微信支付,就想支付日志表添加一条记录,信息包括支付总金额、订单 ID(多个)、用户 ID 、下单时间等信息,支付状态为 0(未支付)
2. 生成的支付日志对象放入 redis 中,以用户 ID 作为 key,这样在生成支付二维码时就可以从 redis 中提取支付日志对象中的金额和订单号。
3. 当用户支付成功后,修改支付日志的支付状态为 1(已支付),并记录微信传递给我们的交易流水号。根据订单 ID(多个)修改订单的状态为 2(已付款)。
4. 分析表结构:根据需求我们应该独立建立一个订单支付日志表,它应该包含:支付订单号,创建事件,支付完成时间,支付金额,交易流水,交易状态,支付类型,订单表id串
2. 具体思路[解决系统中无法查询到支付记录]:
1. interface:
* 首先我们在接口中创建一个通过用户id查询redis中订单的办法。
2. service:
1. 然后在实现类中的生成二维码方法[createNative]中先获取当前用户,
2. 通过当前用户userid查询redis返回一个payLog日志对象,
3. 然后对其进行判断如果该日志不存在则返回一个new HashMap(); 【即这个值为null】
4. 如果该日志存在,则将日志中的订单编号和商品金额作为参数调用weixinPayService.createNative(..)返回;
3. 具体思路[解决支付后订单状态没有改变]
1. interface:
* 创建一个修改订单状态的方法,并传入订单编号,和交易号的的方法;
2. serviceimpl:
1. 首先要明确做的三件事情:
1. 修改支付日志状态
2. 修改关联的订单的状态
3. 清除缓存中的支付日志对象
2. 实现接口新建的修改订单状态方法updateOrderStatus(String out_trade_no,String transaction_id):
1. 通过日志操作类通过订单编号查询到payLog对象,然后修改payLog对象内的属性,如交易状态,交易号等
2. 通过日志类payLog.getOrderList()获取订单号列表然后查询到orderList值,
3. orderList的值为"xxx,xxx,xxx",我们可以通过spilt(",")方法获取到订单号数组。
4. 遍历订单编号,通过订单操作类orderMapper.selectByPrimaryKey方法查询id值获得订单,如果订单存在则修改状态,并orderMapper.updateByPrimaryKey(order)覆盖订单;
5. 通过redisTemplate查找"payLog".delete删除日志id;
3. PayController.java:
1. 在微信支付接口有成功返回状态时,调用修改状态的方法
if(map.get("trade_state").equals("SUCCESS")){//如果成功
result=new Result(true, "支付成功");
//修改订单状态
orderService.updateOrderStatus(out_trade_no,
map.get("transaction_id"));
break;
}
interface:
1. pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>pinyougou-parent</artifactId>
<groupId>com.pinyougou</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<packaging>jar</packaging>
<artifactId>pinyougou-pay-interface</artifactId>
</project>
2. WeixinPayService.java [接口]
package com.pinyougou.pay.service;
import java.util.Map;
public interface WeixinPayService {
/*
*
* 生成二维码
* */
public Map createNative(String out_trade_no ,String total_fee);
/*
*
* 查询支付订单状态
* */
public Map queryPayStatus(String out_trade_no);
}
serviceImpl
-
pom.xml
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>pinyougou-parent</artifactId> <groupId>com.pinyougou</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <packaging>war</packaging> <artifactId>pinyougou-pay-service</artifactId> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.7</maven.compiler.source> <maven.compiler.target>1.7</maven.compiler.target> </properties> <dependencies> <!-- Spring --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jms</artifactId> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> </dependency> <!-- dubbo相关 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>dubbo</artifactId> </dependency> <dependency> <groupId>org.apache.zookeeper</groupId> <artifactId>zookeeper</artifactId> </dependency> <dependency> <groupId>com.github.sgroschupf</groupId> <artifactId>zkclient</artifactId> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> </dependency> <dependency> <groupId>javassist</groupId> <artifactId>javassist</artifactId> </dependency> <dependency> <groupId>commons-codec</groupId> <artifactId>commons-codec</artifactId> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> <scope>provided</scope> </dependency> <dependency> <groupId>com.pinyougou</groupId> <artifactId>pinyougou-pay-interface</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <dependency> <groupId>com.pinyougou</groupId> <artifactId>pinyougou-common</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <dependency> <groupId>com.github.wxpay</groupId> <artifactId>wxpay-sdk</artifactId> <version>0.0.3</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.tomcat.maven</groupId> <artifactId>tomcat7-maven-plugin</artifactId> <version>2.2</version> <configuration> <!-- 指定端口 --> <port>9000</port> <!-- 请求路径 --> <path>/</path> </configuration> </plugin> </plugins> </build> </project>
-
WeinxinPayServiceImpl.java:
package com.pinyougou.pay.service.impl; import com.alibaba.dubbo.config.annotation.Service; import com.github.wxpay.sdk.WXPayUtil; import com.pinyougou.pay.service.WeixinPayService; import org.springframework.beans.factory.annotation.Value; import util.HttpClient; import java.util.HashMap; import java.util.Map; /** * WeixinPayServiceImpl * hasee * 2018/12/14 * 15:13 * * @Version 1.0 **/ @Service public class WeixinPayServiceImpl implements WeixinPayService { @Value("${appid}") private String appid; @Value("${partner}") private String partner; @Value("${partnerkey}") private String partnerkey; @Override public Map createNative(String out_trade_no, String total_fee) { //1. 参数封装 Map param=new HashMap(); param.put("appid",appid); //公众号id param.put("mch_id",partner); //商户key值 param.put("nonce_str", WXPayUtil.generateNonceStr()); //商户密码,随机字符串 param.put("body","品优购"); //主体 param.put("out_trade_no",out_trade_no); //交易订单 param.put("total_fee",total_fee); //金额(分) param.put("spbill_create_ip","127.0.0.1"); //终端ip param.put("notify_url","http://www.itcast.cn"); //回调地址 param.put("trade_type","NATIVE"); //交易类型 try { String xmlParam=WXPayUtil.generateSignedXml(param,partnerkey); System.out.println("请求的参数:"+xmlParam); //2. 发送请求 HttpClient httpClient=new HttpClient("https://api.mch.weixin.qq.com/pay/unifiedorder"); httpClient.setHttps(true); httpClient.setXmlParam(xmlParam); httpClient.post(); //获取结果 String xmlResult=httpClient.getContent(); Map<String, String> mapResult=WXPayUtil.xmlToMap(xmlResult); System.out.println("接收到的结果"+mapResult); Map map=new HashMap(); map.put("code_url",mapResult.get("code_url")); //生成支付二维码连接 map.put("out_trade_no",mapResult.get("out_trade_no")); map.put("total_fee",mapResult.get("total_fee")); return map; } catch (Exception e) { e.printStackTrace(); return new HashMap(); } } @Override public Map queryPayStatus(String out_trade_no) { //1. 封装参数 Map param=new HashMap(); param.put("appid",appid); param.put("mch_id",partner); param.put("out_trade_no",out_trade_no); param.put("nonce_str",WXPayUtil.generateNonceStr()); try { String xmlParam=WXPayUtil.generateSignedXml(param,partnerkey); //2. 发送请求 HttpClient httpClient=new HttpClient("https://api.mch.weixin.qq.com/pay/orderquery"); httpClient.setHttps(true); httpClient.setXmlParam(xmlParam); httpClient.post(); //3. 获取结果 String xmlResult=httpClient.getContent(); Map<String, String> map=WXPayUtil.xmlToMap(xmlResult); return map; } catch (Exception e) { e.printStackTrace(); return new HashMap(); } } }
-
resources/sping/applicationContext-service.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xmlns:dubbo="http://code.alibabatech.com/schema/dubbo" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <dubbo:protocol name="dubbo" port="20880"></dubbo:protocol> <dubbo:application name="pinyougou-pay-service"/> <dubbo:registry address="zookeeper://192.168.25.128:2181"/> <dubbo:annotation package="com.pinyougou.pay.service.impl" /> </beans>
-
webapp/WEB-INF/web.xml:
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5"> <!-- 加载spring容器 --> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath*:spring/applicationContext*.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> </web-app>
Controller:
-
controller:
package com.pinyougou.cart.controller; import com.alibaba.dubbo.config.annotation.Reference; import com.pinyougou.order.service.OrderService; import com.pinyougou.pay.service.WeixinPayService; import com.pinyougou.pojo.TbPayLog; import entity.Result; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.HashMap; import java.util.Map; /** * PayController * hasee * 2018/12/14 * 15:55 * * @Version 1.0 **/ @RestController @RequestMapping("/pay") public class PayController { @Reference private WeixinPayService weixinPayService; @Reference private OrderService orderService; @RequestMapping("/createNative") public Map createNative(){ //获取当前登录用户 String username=SecurityContextHolder.getContext().getAuthentication().getName(); //从日志中提取缓存 TbPayLog payLog=orderService.searchPayLogFromRedis(username); //调用微信支付接口 if (payLog!=null){ return weixinPayService.createNative(payLog.getOutTradeNo(),payLog.getTotalFee()+""); }else{ return new HashMap(); } } @RequestMapping("/queryPayStatus") public Result queryPayStatus(String out_trade_no){ Result result=null; int x=0; while (true){ Map<String,String> map=weixinPayService.queryPayStatus(out_trade_no); //调用查询 if (map==null){ result=new Result(false,"支付发生错误!"); break; } if (map.get("trade_state").equals("SUCCESS")){ result=new Result(true,"支付成功"); orderService.updateOrderStatus(out_trade_no,map.get("transaction_id")); //修改订单状态 break; } try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } x++; //平时x>100[三秒执行一次,每分钟执行20次,五分钟执行100次,大概5分钟左右二维码超时],这里方便测试我们可以直接大于4 if (x>=4){ result=new Result(false,"二维码超时"); break; } } return result; } }
-
pom.xml:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>pinyougou-parent</artifactId> <groupId>com.pinyougou</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <packaging>war</packaging> <artifactId>pinyougou-cart-web</artifactId> <dependencies> <!-- Spring --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jms</artifactId> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> </dependency> <!-- dubbo相关 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>dubbo</artifactId> </dependency> <dependency> <groupId>org.apache.zookeeper</groupId> <artifactId>zookeeper</artifactId> </dependency> <dependency> <groupId>com.github.sgroschupf</groupId> <artifactId>zkclient</artifactId> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> </dependency> <dependency> <groupId>javassist</groupId> <artifactId>javassist</artifactId> </dependency> <dependency> <groupId>commons-codec</groupId> <artifactId>commons-codec</artifactId> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> <scope>provided</scope> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-web</artifactId> <version>4.1.0.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-config</artifactId> <version>4.1.0.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-cas</artifactId> <version>4.1.0.RELEASE</version> </dependency> <dependency> <groupId>org.jasig.cas.client</groupId> <artifactId>cas-client-core</artifactId> <version>3.3.3</version> <exclusions> <exclusion> <groupId>org.slf4j</groupId> <artifactId>log4j-over-slf4j</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>com.pinyougou</groupId> <artifactId>pinyougou-common</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <dependency> <groupId>com.pinyougou</groupId> <artifactId>pinyougou-cart-interface</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <dependency> <groupId>com.pinyougou</groupId> <artifactId>pinyougou-user-interface</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <dependency> <groupId>com.pinyougou</groupId> <artifactId>pinyougou-order-interface</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <dependency> <groupId>com.pinyougou</groupId> <artifactId>pinyougou-pay-interface</artifactId> <version>1.0-SNAPSHOT</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.tomcat.maven</groupId> <artifactId>tomcat7-maven-plugin</artifactId> <version>2.2</version> <configuration> <!-- 指定端口 --> <port>9107</port> <!-- 请求路径 --> <path>/</path> </configuration> </plugin> </plugins> </build> </project>
-
resources/sping/spring-security.xml
<?xml version="1.0" encoding="UTF-8"?> <beans:beans xmlns="http://www.springframework.org/schema/security" xmlns:beans="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd"> <http pattern="/css/**" security="none"></http> <http pattern="/img/**" security="none"></http> <http pattern="/js/**" security="none"></http> <http pattern="/plugins/**" security="none"></http> <http pattern="/cart.html" security="none"></http> <!-- entry-point-ref 入口点引用 --> <!--登录不在自己本身的系统中进行登录,所以需要设置入口点--> <http use-expressions="false" entry-point-ref="casProcessingFilterEntryPoint"> <!--匿名角色IS_AUTHENTICATED_ANONYMOUSLY--> <!--如果用户登录了,则显示登录名,如果未登录则登录名:anonymousUser,必须要放在ROLE_USER前面--> <intercept-url pattern="/cart/*.do" access="IS_AUTHENTICATED_ANONYMOUSLY"></intercept-url> <intercept-url pattern="/**" access="ROLE_USER"/> <csrf disabled="true"/> <!-- custom-filter为过滤器, position 表示将过滤器放在指定的位置上,before表示放在指定位置之前 ,after表示放在指定的位置之后 --> <custom-filter ref="casAuthenticationFilter" position="CAS_FILTER" /> <custom-filter ref="requestSingleLogoutFilter" before="LOGOUT_FILTER"/> <custom-filter ref="singleLogoutFilter" before="CAS_FILTER"/> </http> <!--CAS只是单点登录的一种解决方案,所以这里不一定一定要接入CAS,也可以接入其他的;--> <!-- CAS入口点 开始 --> <beans:bean id="casProcessingFilterEntryPoint" class="org.springframework.security.cas.web.CasAuthenticationEntryPoint"> <!-- 单点登录服务器登录URL --> <beans:property name="loginUrl" value="http://localhost:9100/cas/login"/> <beans:property name="serviceProperties" ref="serviceProperties"/> </beans:bean> <beans:bean id="serviceProperties" class="org.springframework.security.cas.ServiceProperties"> <!--service 配置自身工程的根地址+/login/cas --> <beans:property name="service" value="http://localhost:9107/login/cas"/> </beans:bean> <!-- CAS入口点 结束 --> <!-- 认证过滤器 开始 --> <beans:bean id="casAuthenticationFilter" class="org.springframework.security.cas.web.CasAuthenticationFilter"> <beans:property name="authenticationManager" ref="authenticationManager"/> </beans:bean> <!-- 认证管理器 --> <authentication-manager alias="authenticationManager"> <authentication-provider ref="casAuthenticationProvider"> </authentication-provider> </authentication-manager> <!-- 认证提供者 --> <beans:bean id="casAuthenticationProvider" class="org.springframework.security.cas.authentication.CasAuthenticationProvider"> <beans:property name="authenticationUserDetailsService"> <beans:bean class="org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper"> <beans:constructor-arg ref="userDetailsService" /> </beans:bean> </beans:property> <beans:property name="serviceProperties" ref="serviceProperties"/> <!-- ticketValidator 为票据验证器 --> <beans:property name="ticketValidator"> <beans:bean class="org.jasig.cas.client.validation.Cas20ServiceTicketValidator"> <beans:constructor-arg index="0" value="http://localhost:9100/cas"/> </beans:bean> </beans:property> <beans:property name="key" value="an_id_for_this_auth_provider_only"/> </beans:bean> <!-- 认证类 --> <beans:bean id="userDetailsService" class="com.pinyougou.user.service.UserDetailServiceImpl"/> <!-- 认证过滤器 结束 --> <!-- 单点登出 开始 --> <beans:bean id="singleLogoutFilter" class="org.jasig.cas.client.session.SingleSignOutFilter"/> <!-- 经过此配置,当用户在地址栏输入本地工程 /logout/cas --> <beans:bean id="requestSingleLogoutFilter" class="org.springframework.security.web.authentication.logout.LogoutFilter"> <beans:constructor-arg value="http://localhost:9100/cas/logout?service=http://localhost:9103"/> <beans:constructor-arg> <beans:bean class="org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler"/> </beans:constructor-arg> <beans:property name="filterProcessesUrl" value="/logout/cas"/> </beans:bean> <!-- 单点登出 结束 --> </beans:beans>
-
resources/springmvc.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xmlns:dubbo="http://code.alibabatech.com/schema/dubbo" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <context:property-placeholder location="classpath:config/application.properties" /> <mvc:annotation-driven> <mvc:message-converters register-defaults="true"> <bean class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter"> <property name="supportedMediaTypes" value="application/json"/> <property name="features"> <array> <value>WriteMapNullValue</value> <value>WriteDateUseDateFormat</value> </array> </property> </bean> </mvc:message-converters> </mvc:annotation-driven> <!-- 引用dubbo 服务 --> <dubbo:application name="pinyougou-cart-web" /> <dubbo:registry address="zookeeper://192.168.25.128:2181"/> <dubbo:annotation package="com.pinyougou.cart.controller" /> </beans>
-
js/controller.js
app.controller('payController',function ($scope,$location,payService) { $scope.createNative=function () { payService.createNative().success( function (response) { //显示订单号和金额 //toFixed是保留两位小数,total_fee单位是分除以100变为元 $scope.money=(response.total_fee/100).toFixed(2); $scope.out_trade_no=response.out_trade_no; //生成二维码 var qr=new QRious({ element:document.getElementById('qrious'), size:250, value:response.code_url, level:'H' }); queryPayStatus(); //调用查询 }) }; //调用查询 queryPayStatus=function () { payService.queryPayStatus($scope.out_trade_no).success( function (response) { if (response.success){ location.href="paysuccess.html#?money="+$scope.money; }else{ if (response.message=='二维码超时'){ //这里调用重新生成二维码功能,当关闭浏览器,后端循环遍历查询订单状态即停止,如果不关闭浏览器和本支付页面的话,就可以实现每隔几秒重新生成一次二维码功能; $scope.createNative(); //重新生成二维码 }else{ location.href="payfail.html"; } }})} // 获取金额 $scope.getMoney=function () { return $location.search()['money']; } });
-
js/base.js:
var app=angular.module('pinyougou',[]);
-
js/service.js:
app.controller('payController',function ($scope,$location,payService) { $scope.createNative=function () { payService.createNative().success( function (response) { //显示订单号和金额 //toFixed是保留两位小数,total_fee单位是分除以100变为元 $scope.money=(response.total_fee/100).toFixed(2); $scope.out_trade_no=response.out_trade_no; //生成二维码 var qr=new QRious({ element:document.getElementById('qrious'), size:250, value:response.code_url, level:'H' }); queryPayStatus(); //调用查询 }) }; //调用查询 queryPayStatus=function () { payService.queryPayStatus($scope.out_trade_no).success( function (response) { if (response.success){ location.href="paysuccess.html#?money="+$scope.money; }else{ if (response.message=='二维码超时'){ //这里调用重新生成二维码功能,当关闭浏览器,后端循环遍历查询订单状态即停止,如果不关闭浏览器和本支付页面的话,就可以实现每隔几秒重新生成一次二维码功能; $scope.createNative(); //重新生成二维码 }else{ location.href="payfail.html"; } }})} // 获取金额 $scope.getMoney=function () { return $location.search()['money']; } });
-
webapp/WEB-INF/web.xml
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5"> <!-- 解决post乱码 --> <filter> <filter-name>CharacterEncodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>utf-8</param-value> </init-param> <init-param> <param-name>forceEncoding</param-name> <param-value>true</param-value> </init-param> </filter> <filter-mapping> <filter-name>CharacterEncodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring/spring-security.xml</param-value> </context-param> <listener> <listener-class> org.springframework.web.context.ContextLoaderListener </listener-class> </listener> <filter> <filter-name>springSecurityFilterChain</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> </filter> <filter-mapping> <filter-name>springSecurityFilterChain</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <servlet> <servlet-name>springmvc</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <!-- 指定加载的配置文件 ,通过参数contextConfigLocation加载--> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring/springmvc.xml</param-value> </init-param> </servlet> <servlet-mapping> <servlet-name>springmvc</servlet-name> <url-pattern>*.do</url-pattern> </servlet-mapping> <welcome-file-list> <welcome-file>cart.html</welcome-file> </welcome-file-list> </web-app>
HttpClient工具类:
package util;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.text.ParseException;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import org.apache.http.Consts;
import org.apache.http.HttpEntity;
import org.apache.http.NameValuePair;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLContextBuilder;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.conn.ssl.TrustStrategy;
import org.apache.http.conn.ssl.X509HostnameVerifier;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
/**
* http请求客户端
*
* @author Administrator
*
*/
public class HttpClient {
private String url;
private Map<String, String> param;
private int statusCode;
private String content;
private String xmlParam;
private boolean isHttps;
public boolean isHttps() {
return isHttps;
}
public void setHttps(boolean isHttps) {
this.isHttps = isHttps;
}
public String getXmlParam() {
return xmlParam;
}
public void setXmlParam(String xmlParam) {
this.xmlParam = xmlParam;
}
public HttpClient(String url, Map<String, String> param) {
this.url = url;
this.param = param;
}
public HttpClient(String url) {
this.url = url;
}
public void setParameter(Map<String, String> map) {
param = map;
}
public void addParameter(String key, String value) {
if (param == null)
param = new HashMap<String, String>();
param.put(key, value);
}
public void post() throws ClientProtocolException, IOException {
HttpPost http = new HttpPost(url);
setEntity(http);
execute(http);
}
public void put() throws ClientProtocolException, IOException {
HttpPut http = new HttpPut(url);
setEntity(http);
execute(http);
}
public void get() throws ClientProtocolException, IOException {
if (param != null) {
StringBuilder url = new StringBuilder(this.url);
boolean isFirst = true;
for (String key : param.keySet()) {
if (isFirst)
url.append("?");
else
url.append("&");
url.append(key).append("=").append(param.get(key));
}
this.url = url.toString();
}
HttpGet http = new HttpGet(url);
execute(http);
}
/**
* set http post,put param
*/
private void setEntity(HttpEntityEnclosingRequestBase http) {
if (param != null) {
List<NameValuePair> nvps = new LinkedList<NameValuePair>();
for (String key : param.keySet())
nvps.add(new BasicNameValuePair(key, param.get(key))); // 参数
http.setEntity(new UrlEncodedFormEntity(nvps, Consts.UTF_8)); // 设置参数
}
if (xmlParam != null) {
http.setEntity(new StringEntity(xmlParam, Consts.UTF_8));
}
}
private void execute(HttpUriRequest http) throws ClientProtocolException,
IOException {
CloseableHttpClient httpClient = null;
try {
if (isHttps) {
SSLContext sslContext = new SSLContextBuilder()
.loadTrustMaterial(null, new TrustStrategy() {
// 信任所有
public boolean isTrusted(X509Certificate[] chain,
String authType)
throws CertificateException {
return true;
}
}).build();
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
sslContext);
httpClient = HttpClients.custom().setSSLSocketFactory(sslsf)
.build();
} else {
httpClient = HttpClients.createDefault();
}
CloseableHttpResponse response = httpClient.execute(http);
try {
if (response != null) {
if (response.getStatusLine() != null)
statusCode = response.getStatusLine().getStatusCode();
HttpEntity entity = response.getEntity();
// 响应内容
content = EntityUtils.toString(entity, Consts.UTF_8);
}
} finally {
response.close();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
httpClient.close();
}
}
public int getStatusCode() {
return statusCode;
}
public String getContent() throws ParseException, IOException {
return content;
}
}
雪花算法工具类
package util;
import java.lang.management.ManagementFactory;
import java.net.InetAddress;
import java.net.NetworkInterface;
/**
* <p>名称:IdWorker.java</p>
* <p>描述:分布式自增长ID</p>
* <pre>
* Twitter的 Snowflake JAVA实现方案
* </pre>
* 核心代码为其IdWorker这个类实现,其原理结构如下,我分别用一个0表示一位,用—分割开部分的作用:
* 1||0---0000000000 0000000000 0000000000 0000000000 0 --- 00000 ---00000 ---000000000000
* 在上面的字符串中,第一位为未使用(实际上也可作为long的符号位),接下来的41位为毫秒级时间,
* 然后5位datacenter标识位,5位机器ID(并不算标识符,实际是为线程标识),
* 然后12位该毫秒内的当前毫秒内的计数,加起来刚好64位,为一个Long型。
* 这样的好处是,整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞(由datacenter和机器ID作区分),
* 并且效率较高,经测试,snowflake每秒能够产生26万ID左右,完全满足需要。
* <p>
* 64位ID (42(毫秒)+5(机器ID)+5(业务编码)+12(重复累加))
*
* @author Polim
*/
public class IdWorker {
// 时间起始标记点,作为基准,一般取系统的最近时间(一旦确定不能变动)
private final static long twepoch = 1288834974657L;
// 机器标识位数
private final static long workerIdBits = 5L;
// 数据中心标识位数
private final static long datacenterIdBits = 5L;
// 机器ID最大值
private final static long maxWorkerId = -1L ^ (-1L << workerIdBits);
// 数据中心ID最大值
private final static long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
// 毫秒内自增位
private final static long sequenceBits = 12L;
// 机器ID偏左移12位
private final static long workerIdShift = sequenceBits;
// 数据中心ID左移17位
private final static long datacenterIdShift = sequenceBits + workerIdBits;
// 时间毫秒左移22位
private final static long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
private final static long sequenceMask = -1L ^ (-1L << sequenceBits);
/* 上次生产id时间戳 */
private static long lastTimestamp = -1L;
// 0,并发控制
private long sequence = 0L;
private final long workerId;
// 数据标识id部分
private final long datacenterId;
public IdWorker(){
this.datacenterId = getDatacenterId(maxDatacenterId);
this.workerId = getMaxWorkerId(datacenterId, maxWorkerId);
}
/**
* @param workerId
* 工作机器ID
* @param datacenterId
* 序列号
*/
public IdWorker(long workerId, long datacenterId) {
if (workerId > maxWorkerId || workerId < 0) {
throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
}
if (datacenterId > maxDatacenterId || datacenterId < 0) {
throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));
}
this.workerId = workerId;
this.datacenterId = datacenterId;
}
/**
* 获取下一个ID
*
* @return
*/
public synchronized long nextId() {
long timestamp = timeGen();
if (timestamp < lastTimestamp) {
throw new RuntimeException(String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
}
if (lastTimestamp == timestamp) {
// 当前毫秒内,则+1
sequence = (sequence + 1) & sequenceMask;
if (sequence == 0) {
// 当前毫秒内计数满了,则等待下一秒
timestamp = tilNextMillis(lastTimestamp);
}
} else {
sequence = 0L;
}
lastTimestamp = timestamp;
// ID偏移组合生成最终的ID,并返回ID
long nextId = ((timestamp - twepoch) << timestampLeftShift)
| (datacenterId << datacenterIdShift)
| (workerId << workerIdShift) | sequence;
return nextId;
}
private long tilNextMillis(final long lastTimestamp) {
long timestamp = this.timeGen();
while (timestamp <= lastTimestamp) {
timestamp = this.timeGen();
}
return timestamp;
}
private long timeGen() {
return System.currentTimeMillis();
}
/**
* <p>
* 获取 maxWorkerId
* </p>
*/
protected static long getMaxWorkerId(long datacenterId, long maxWorkerId) {
StringBuffer mpid = new StringBuffer();
mpid.append(datacenterId);
String name = ManagementFactory.getRuntimeMXBean().getName();
if (!name.isEmpty()) {
/*
* GET jvmPid
*/
mpid.append(name.split("@")[0]);
}
/*
* MAC + PID 的 hashcode 获取16个低位
*/
return (mpid.toString().hashCode() & 0xffff) % (maxWorkerId + 1);
}
/**
* <p>
* 数据标识id部分
* </p>
*/
protected static long getDatacenterId(long maxDatacenterId) {
long id = 0L;
try {
InetAddress ip = InetAddress.getLocalHost();
NetworkInterface network = NetworkInterface.getByInetAddress(ip);
if (network == null) {
id = 1L;
} else {
byte[] mac = network.getHardwareAddress();
id = ((0x000000FF & (long) mac[mac.length - 1])
| (0x0000FF00 & (((long) mac[mac.length - 2]) << 8))) >> 6;
id = id % (maxDatacenterId + 1);
}
} catch (Exception e) {
System.out.println(" getDatacenterId: " + e.getMessage());
}
return id;
}
/*
//测试
public static void main(String[] args) {
IdWorker idWorker=new IdWorker(0,0);
for (int i=0; i < 100; i++) {
long nextId=idWorker.nextId();
System.out.println(nextId);
}
}
*/
}