java工厂-积木系列
这里记录一个例子,工厂模式的理论就不扯淡了。
遇到的问题:支付方式有很多种,比如微信支付 支付宝支付 银联支付 等等。我们在在实现的时候发现他么的流程上是相似的,以及每个方式都有大量的个性配置,在实例化时需要加载他们,以及为了清晰的讲调用方和实现方进行分离,就有来下面的小设计。
以银联为例,(通过测试)。
共用接口:
public interface CommonPay { /**
* <pre>
* 功能描述:
* 支付预处理:生成支付URL或提供给支付平台的数据
*
* @param param 预支付参数
* @return 支付URL或提供给支付平台的数据
* @throws Exception
*/
<T> PrepayDto<T> prePay(PrepayParam param) throws Exception;
}
工厂类:
public class CommonPayFactory implements InitializingBean { /** 银联支付 */
private CommonPay unionPay; @Override public void afterPropertiesSet() throws Exception {
if (unionPay == null) {
throw new RuntimeException("未配置任何支付实例");
}
} public CommonPay getUnionPay() {
return unionPay;
} public void setUnionPay(CommonPay unionPay) {
this.unionPay = unionPay;
}
}
我们利用spring,将bean初始化信息配置好:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="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"> <bean id="unionPayConfig" class="com.xxxxxx.cashier.web.union.UnionPayConfig">
<property name="payUrl" value="${pay.unionpay.payUrl}" />
<property name="queryUrl" value="${pay.unionpay.queryUrl}" />
<property name="merId" value="${pay.unionpay.merId}" />
<property name="signPfxFile" value="${pay.unionpay.signPfxFile}" />
<property name="signPfxPwd" value="${pay.unionpay.signPfxPwd}" />
<property name="verifySignCertFile" value="${pay.unionpay.verifySignCertFile}" />
<property name="payNotifyFrontUrl" value="${pay.unionpay.payNotifyFrontUrl}" />
<property name="payNotifyUrl" value="${pay.unionpay.payNotifyUrl}" />
</bean> <!-- 通用支付工厂实例 -->
<bean id="commonPayFactory" class="com.xxxxx.cashier.service.pay.CommonPayFactory">
<property name="unionPay">
<bean class="com.xxxxxx.cashier.service.pay.UnionPay">
<property name="UnionPayConfig" ref="unionPayConfig" />
</bean>
</property>
</bean>
</beans>
如此在项目启动时,银联实现会根据配置信息实例化,工厂类会自动加载入银联实例以备获取。
以下是银联实现:
public class UnionPay implements CommonPay { /** Logger */
private static final Logger LOGGER = LoggerFactory.getLogger(UnionPay.class); /** 日期时间格式 */
private static final String DATE_FORMAT_YYYYMMDDHHMMSS = "yyyyMMddHHmmss"; /** 银联支付配置 */
private UnionPayConfig unionPayConfig; @Override public <T> PrepayDto<T> prePay(PrepayParam param) throws Exception { PrepayDto<String> prepayDto = new PrepayDto<>(); // 构造预下单签名参数
Map<String, String> params = new TreeMap<>(); /** 银联全渠道系统,产品参数,除了encoding自行选择外其他不需修改 */
// 版本号,全渠道默认值
params.put("version", UnionPayConfig.VERSION);
// 字符集编码,可以使用UTF-8,GBK两种方式
params.put("encoding", UnionPayConfig.CHARSET);
// 签名方法,只支持 01:RSA方式证书加密
params.put("signMethod", "01");
// 交易类型 ,01:消费
params.put("txnType", "01");
// 交易子类型, 01:自助消费
params.put("txnSubType", "01");
// 业务类型,B2C网关支付,手机wap支付
params.put("bizType", "000201");
// 渠道类型,这个字段区分B2C网关支付和手机wap支付;07:PC,平板 08:手机
params.put("channelType", "07"); /** 商户接入参数 */
// 商户号码,请改成自己申请的正式商户号或者open上注册得来的777测试商户号
params.put("merId", unionPayConfig.getMerId());
// 接入类型,0:直连商户
params.put("accessType", "0");
// 商户订单号,8-40位数字字母,不能含“-”或“_”,可以自行定制规则
params.put("orderId", param.getOutTradeNo());
// 订单发送时间,取系统时间,格式为YYYYMMDDhhmmss,必须取当前时间,否则会报txnTime无效
SimpleDateFormat sdf = new SimpleDateFormat(DATE_FORMAT_YYYYMMDDHHMMSS);
Date orderAddTime = new Date();
String txtTime = sdf.format(orderAddTime);
params.put("txnTime", txtTime);
// 账号
// 1、 后台类消费交易时上送全卡号或卡号后4位
// 2、 跨行收单且收单机构收集银行卡信息时上送、
// 3、前台类交易可通过配置后返回,卡号可选上送
params.put("accNo", param.getCardNumber());
// 交易币种(境内商户一般是156 人民币)
params.put("currencyCode", "156");
// 交易金额,单位分,不要带小数点
params.put("txnAmt", param.getTotalFee());
// 请求方保留域,如需使用请启用即可;透传字段(可以实现商户自定义参数的追踪)本交易的后台通知,
// 对本交易的交易状态查询交易、对账文件中均会原样返回,商户可以按需上传,长度为1-1024个字节
params.put("reserved", "{cardNumberLock=1}"); // TODO
// 前台通知地址 (需设置为外网能访问 http https均可),支付成功后的页面 点击“返回商户”按钮的时候将异步通知报文post到该地址
// 如果想要实现过几秒中自动跳转回商户页面权限,需联系银联业务申请开通自动返回商户权限
params.put("frontUrl", unionPayConfig.getPayNotifyFrontUrl());
// 后台通知地址(需设置为【外网】能访问 http https均可),支付成功后银联会自动将异步通知报文post到商户上送的该地址,失败的交易银联不会发送后台通知
// 注意:1.需设置为外网能访问,否则收不到通知 2.http https均可
// 3.收单后台通知后需要10秒内返回http200或302状态码
// 4.如果银联通知服务器发送通知后10秒内未收到返回状态码或者应答码非http200,那么银联会间隔一段时间再次发送。总共发送5次,每次的间隔时间为0,1,2,4分钟。
// 5.后台通知地址如果上送了带有?的参数,例如:http://abc/web?a=b&c=d 在后台通知处理程序验证签名之前需要编写逻辑将这些字段去掉再验签,否则将会验签失败
params.put("backUrl", unionPayConfig.getPayNotifyUrl()); // 对请求参数拼接后进行消息摘要(SHA-1),然后用商户私钥进行签名
sign(params); // 生成表单HTML文档
String html = createAutoFormHtml(unionPayConfig.getPayUrl(), params, UnionPayConfig.CHARSET); prepayDto.setOrderId(param.getOrderId());
prepayDto.setOrderPaymentMethod(1);//TODO
prepayDto.setOutTradeNo(param.getOutTradeNo());
prepayDto.setResp(html);
return (PrepayDto<T>) prepayDto;
} private void sign(Map<String, String> params) {
// 证书ID 填写签名私钥证书的Serial Number,该值可通过银联提供的SDK获取
params.put("certId", unionPayConfig.getCertId()); // 对请求参数进行签名
String reqStr = PayUtil.mapToQueryStr(params, UnionPayConfig.CHARSET, false, true); // 将Map信息转换成key1=value1&key2=value2的形式
byte[] signDigest = SHA1Util.sha1X16(reqStr, UnionPayConfig.CHARSET); // SHA-1消息摘要
byte[] byteSign = RSA.signBySoft(unionPayConfig.getPrivateKey(), signDigest); // 用商户私钥签名
String sign = com.xiaoka.freework.utils.encrypt.Base64.encode(byteSign); // Base64编码
// 报文摘要的签名
params.put("signature", sign);
} private static String createAutoFormHtml(String reqUrl, Map<String, String> hiddens, String encoding) {
StringBuilder sb = new StringBuilder();
sb.append("<html><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=").append(encoding).append("\"/></head><body>");
sb.append("<form id = \"pay_form\" action=\"").append(reqUrl).append("\" method=\"post\">");
if (null != hiddens && 0 != hiddens.size()) {
Set<Map.Entry<String, String>> set = hiddens.entrySet();
for (Map.Entry<String, String> ey : set) {
String key = ey.getKey();
String value = ey.getValue();
sb.append("<input type=\"hidden\" name=\"").append(key).append("\" id=\"")
.append(key).append("\" value=\"").append(value).append("\"/>");
}
}
sb.append("</form>");
sb.append("</body>");
sb.append("<script type=\"text/javascript\">");
sb.append("document.all.pay_form.submit();");
sb.append("</script>");
sb.append("</html>");
return sb.toString();
} public UnionPayConfig getUnionPayConfig() {
return unionPayConfig;
} public void setUnionPayConfig(UnionPayConfig unionPayConfig) {
this.unionPayConfig = unionPayConfig;
}
}
调用方只需获取银联实例调用方法即可:
CommonPay unionPay = commonPayFactory.getUnionPay(); PrepayParam prepayParam = new PrepayParam();
prepayParam.setOrderId(orderId);
prepayParam.setOutTradeNo(tradeNo);
prepayParam.setCardNumber(cardNo);
prepayParam.setTotalFee(amount.toString());
PrepayDto prepayDto = null;
try {
prepayDto = unionPay.prePay(prepayParam);
} catch (Exception e) {
logger.error("unionPay.prePay has error", e);
}
耐心点~
java工厂-积木系列的更多相关文章
- Java Web学习系列——Maven Web项目中集成使用Spring、MyBatis实现对MySQL的数据访问
本篇内容还是建立在上一篇Java Web学习系列——Maven Web项目中集成使用Spring基础之上,对之前的Maven Web项目进行升级改造,实现对MySQL的数据访问. 添加依赖Jar包 这 ...
- [转]Java多线程干货系列—(一)Java多线程基础
Java多线程干货系列—(一)Java多线程基础 字数7618 阅读1875 评论21 喜欢86 前言 多线程并发编程是Java编程中重要的一块内容,也是面试重点覆盖区域,所以学好多线程并发编程对我们 ...
- 推荐:Java性能优化系列集锦
Java性能问题一直困扰着广大程序员,由于平台复杂性,要定位问题,找出其根源确实很难.随着10多年Java平台的改进以及新出现的多核多处理器,Java软件的性能和扩展性已经今非昔比了.现代JVM持续演 ...
- java高并发系列 - 第5天:深入理解进程和线程
进程 进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础.程序是指令.数据及其组织形式的描述,进程是程序的实体. 进程具有的 ...
- Java 数据持久化系列之池化技术
在上一篇文章<Java 数据持久化系列之JDBC>中,我们了解到使用 JDBC 创建 Connection 可以执行对应的SQL,但是创建 Connection 会消耗很多资源,所以 Ja ...
- Java 数据持久化系列之 HikariCP (一)
在上一篇<Java 数据持久化系列之池化技术>中,我们了解了池化技术,并使用 Apache-common-Pool2 实现了一个简单连接池,实验对比了它和 HikariCP.Druid 等 ...
- Java高并发系列——检视阅读
Java高并发系列--检视阅读 参考 java高并发系列 liaoxuefeng Java教程 CompletableFuture AQS原理没讲,需要找资料补充. JUC中常见的集合原来没讲,比如C ...
- java工厂模式
(1)概念大白话:java工厂模式就是客户端(main函数)要创建对象觉得麻烦就让另外一个叫工厂的类帮它创建,然后自己每次要创建对象就叫工厂帮它弄,举个例子,在没有工厂这个"手下" ...
- Java总结篇系列:Java String
String作为Java中最常用的引用类型,相对来说基本上都比较熟悉,无论在平时的编码过程中还是在笔试面试中,String都很受到青睐,然而,在使用String过程中,又有较多需要注意的细节之处. 1 ...
- Java总结篇系列:Java多线程(三)
本文主要接着前面多线程的两篇文章总结Java多线程中的线程安全问题. 一.一个典型的Java线程安全例子 public class ThreadTest { public static void ma ...
随机推荐
- hibernate入门实例
1. 环境配置 1.1 hiberante环境配置 hibernate可实现面向对象的数据存储.hibernate的官网:http://hibernate.org/ 官网上选择hibernate OR ...
- ObReferenceObjectByName函数调用WIN7下的解决
<寒江独钓 Windows内核安全编程>第4章键盘的过滤ctrl2cap代码中,ObReferenceObjectByName函数调用: [1]extern POBJECT_TYPE Io ...
- 关于Xcode6创建的工程在Xcode5打开
Xcode6创建的工程在Xcode5打开- 4.0只显示3.5大小的问题 只需要在工程里添加Default-568h@2x.png,即可以解决
- 如何隐藏DOM元素
在CSS中,要隐藏DOM元素常见的方法有: 设置元素的opacity值为0 设置元素的visibility值为hidden 设置元素的display值为none 设置元素的position值为abso ...
- Django的单元测试
1.单元测试框架 django-unittest https://docs.djangoproject.com/en/1.9/topics/testing/overview/#speeding-up- ...
- 让powershell同时只能运行一个脚本(进程互斥例子)
powershell,mutex,互斥,进程互斥,脚本互斥 powershell脚本互斥例子,在powershell类别文章中,声明原创唯一. powershell 传教士 原创文章 2016-07- ...
- is a 、like a、has a
has a 代表的是对象和它的成员的从属关系.可以分为组合与聚合两种. 组合:表示两个对象是整体与部分的关系为强关系 聚合:表示两个对象是整体与部分的关系为弱关系 is a 它是类继承关系,只是覆盖了 ...
- local认证
文件路径 用途 示例 备注 #gedit /usr/local/etc/raddb/sites-available/default #gedit /usr/local/etc/raddb/sites- ...
- Spearman秩相关系数和Pearson皮尔森相关系数
1.Pearson皮尔森相关系数 皮尔森相关系数也叫皮尔森积差相关系数,用来反映两个变量之间相似程度的统计量.或者说用来表示两个向量的相似度. 皮尔森相关系数计算公式如下:
- 关于linq to sql类线程同步问题
例如,下面一段代码,当两个线程同时访问时会出现各种由于线程不同步而导致的问题,比如什么DataReader已打开未关闭啊,已经添加了重复的键啊等等. /// <summary> /// 当 ...