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 ...
随机推荐
- mysql数据的行列转换
动态,适用于列不确定情况,第一种: SET @EE=''; SELECT GROUP_CONCAT('SUM(IF(C2=\'',C2,'\'',',C3,0)) AS ',C2)INTO @EE F ...
- Topcoder SRM558 1000 SurroundingGame
题意:给定一个网格,每个网格有选取代价和占据收益.每个点被占据,需要满足以下两个条件至少一个条件:1.被选取 2.邻近方格都被选取(有公共边被称为邻近) 不一定要占据所有方格,求最大收益. 第一直 ...
- maven_spring mvc_mina_dome(实体,文件,批传)(spring mina 初学dome)
看我们群里经常有人在问mina心跳问题,虽然俺是菜鸟可是觉得挺简单的啊,就写了个dome,希望大家多多提意见. 俺做过一段时间网络协议.所以觉得挺简单的吧.哎呀,反正技术就那样了没啥难的. 废话不多说 ...
- Fragment的startActivityForResult和Activity的startActivityForResult的区别
2016-08-30 18:22:33 前提:我们的APP要兼容Api level 11以前的,所以必须用FragmentActivity 1.对于Fragment的,我们很多时候都会在Activit ...
- hdu 1712, multiple-choice knapsack, 分类: hdoj 2015-07-18 13:25 152人阅读 评论(0) 收藏
reference: 6.4 knapsack in Algorithms(算法概论), Sanjoy Dasgupta University of California, San Diego Chr ...
- mysql启动报错The server quit without updating PID file
现网mysql无法启动是很让人头疼的,数据很有可能恢复不了,解决方法如下: 查看mysql目录下的日志,根据日志来锁定错误原因(mysql的错误日志很抽象) a.如果日志不能提供任何帮助则可进行以下步 ...
- ssh无密钥登陆的简单配置
主要目的是把本地的公钥放到远端被登陆的host上. 本地操作: # ssh-keygen # ssh-copy-id user@host 远端sshd_config配置: RSAAuthenticat ...
- 用Window Authentication的方式去连接SQLServer
用Window Authentication的方式去连接SQLServer Connection String: jdbc:sqlserver://${serverName};databaseName ...
- 例子:Bluetooth app to device sample
本例子演示了: 判断蓝牙是否打开,是通过一个HRsult值为0x8007048F的异常来判断的 catch (Exception ex) { if ((uint)ex.HResult == 0x800 ...
- 设计模式之observer and visitor
很长时间一直对observer(观察者)与visitor(访问者)有些分不清晰. 今天有时间进行一下梳理: 1.observer模式 这基本就是一个通知模式,当被观察者发生改变时,通知所有监听此变化的 ...