文章目录结构
一、引言
上一章节介绍有关于SDK内容,以及项目搭建。
那么本章就来说说,在开始动手写代码之前,我们先要确认好,这个SDK你想做成什么样子,想怎么去设计。
郑重声明:小编并非专业架构设计师,但小编至少认为该项目的架构设计是很容易理解,所以才用文章的形式分享出来,也可以更加一层的理解熟悉对接支付内容,不是有句话是这么说:知其然知其所以然。
那我们这个SDK具体需要做成什么样子呢?
在开发任何功能之前,作为程序员的我们来说,肯定需要确认一下具体需求是什么,要做成什么样子。
假设现在小编公司需要开发一个专门来用支付的项目,然后领导噼里啪啦说要求一周内搞定,那这个时候如果之前小伙伴没有对接过支付,那么肯定就会去乖乖的看微信、支付宝等对接文档对吧。
对于稍微有经验的老司机来说,就可能会去找相对应的开源第三方支付SDK,那么重点来了,这些第三方支付的SDK他帮你实现了对接微信和支付宝,不需要小伙伴跑过去看文档了,只需要了解这个第三方支付SDK的用法就行啦。
需求就很清晰明了,我们需要在底层对接支付宝和微信,并且对外提供配置,最终能够快速实现支付的功能的效果,那么本次小编所要给大家分享的支付SDK,其中包含了微信和支付宝,当然第一版只实现了基本的支付功能。
二、整体结构设计
小伙伴可以先思考一下 ~~~~
其实这个SDK说起来并不是很复杂,无非就是让用户设置支付相关信息,然后调用相对应功能的方法就行啦。
当当当当当然,小编画图也不是专业的,请各位谅解、谅解、谅解。
base-pay-sdk : 就是我们这个项目的名字啦。
WxPayConfig:这个是需要用户来进行配置的,比如说appid、商户号、商户密钥等等,配置好之后我们需要交给BasePayService里面去。
AliPayConfig:这个是支付宝支付所需要的配置,appid、商户私钥、支付宝公钥等等,最后也是交给BasePayService。
BasePayService:这个对象相当于一个桥梁,它负责接收用户配置和对外提供方法调用,以及与下面的实现层进行交互。
实现层就主要就是分支付宝和微信了,这个项目主要编写代码时间都在这个实现层里面了。实现层会各自使用到WxPayConfig、AliPayConfig,拿到相对应的基本数据之后,会对每一个功能进行不同的代码实现,
这里可能就会有小伙伴想问,小编这个图是怎么想出来的呢?
这个是根据实际情况出想来的,小伙伴们可以这样想:
用户层:这个SDK肯定是提供给公司或者其他人所使用,那么对于用户来说肯定是使用越方便越好。比如说发起支付,用户先设置对应的基本信息(appid、商户号等等),在发起支付的时候,肯定还需要对应的订单信息(订单号、订单名称、金额等等),剩下的发起支付功能都交给底层具体实现执行。
实现层:会定义支付宝、微信所请求的接口信息、请求参数、响应参数、状态等等等,把具体实现代码封转起来,不像外界爆露。
三、项目代码结构
项目总体分层大概就是这个样子了,那么接下来就是项目目录结构了,小伙伴们可以先建立空目录,后续具体代码实现都会一一所使用到的。
config:配置包,存放相关信息配置。 (微信、支付宝参数配置)
constants:常量包,存放项目公用常量,
enums:枚举包,小编在实际公司开发中,发现很多都不喜欢写枚举,而是直接代码里面写,其实这种习惯一点都不好。枚举可以存放一些状态或者说数据标识。最常见的就是成功的状态是什么,失败的状态又是什么。
model:实体包,存放基本字段数据,请求实体、响应实体等等。
service:服务包,定义相关api接口,以及对应的接口实现。 (重要代码就都在这个里面了)
utils:通用包,一些通用的类都可以存放到这,比如MapUtil 等等。
以上包名小伙伴可以自由发挥,没有什么特殊爱好,以上只是偏向小编个人习惯。
四、项目通用工具类
MapUtil :
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.lang3.StringUtils;
import org.simpleframework.xml.Element;
import java.io.IOException;
import java.lang.reflect.Field;
import java.net.URLEncoder;
import java.util.*;
/**
* @Auther: IT贱男
* @Date: 2019/12/19 14:05
* @Description: Map 工具类
*/
public class MapUtil {
final static ObjectMapper objectMapper;
static {
objectMapper = new ObjectMapper();
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
}
/**
* 对象转map
*
* @param obj
* @return
*/
public static Map<String, String> buildMap(Object obj) {
Map<String, String> map = new HashMap<>();
try {
Class<?> clazz = obj.getClass();
for (Field field : clazz.getDeclaredFields()) {
field.setAccessible(true);
String fieldName = field.getName();
//如果 element 注解 name 字段设置了内容, 使用其当成字段名
Element element = field.getAnnotation(Element.class);
if (element != null && StringUtils.isNotEmpty(element.name())) {
fieldName = element.name();
}
String value = field.get(obj) == null ? "" : String.valueOf(field.get(obj));
map.put(fieldName, value);
}
} catch (Exception e) {
e.printStackTrace();
}
return map;
}
/**
* map转为url
* 结果类似 token=abccdssx&sign=ccsacccss
*
* @return
*/
public static String toUrl(Map<String, String> map) {
String url = "";
for (Map.Entry<String, String> entry : map.entrySet()) {
url += entry.getKey() + "=" + entry.getValue() + "&";
}
//移除最后一个&
url = StringUtils.substringBeforeLast(url, "&");
return url;
}
/**
* map转url 排序后转
*
* @param map
* @return
*/
public static String toUrlWithSort(Map<String, String> map) {
List<String> keys = new ArrayList<>(map.keySet());
Collections.sort(keys);
String prestr = "";
for (int i = 0; i < keys.size(); i++) {
String key = keys.get(i);
String value = map.get(key);
if (i == keys.size() - 1) {//拼接时,不包括最后一个&字符
prestr = prestr + key + "=" + value;
} else {
prestr = prestr + key + "=" + value + "&";
}
}
return prestr;
}
/**
* 移除map中空的key和value
*
* @param map
* @return
*/
public static Map<String, String> removeEmptyKeyAndValue(Map<String, String> map) {
Iterator<Map.Entry<String, String>> it = map.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<String, String> entry = it.next();
String key = entry.getKey();
String value = entry.getValue();
if (StringUtils.isBlank(key) || StringUtils.isBlank(value)) {
it.remove();
}
}
return map;
}
/**
* 将map中的key转换成小写
*
* @param map
* @return
*/
public static Map<String, String> keyToLowerCase(Map<String, String> map) {
Map<String, String> responseMap = new HashMap<>();
Iterator<Map.Entry<String, String>> it = map.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<String, String> entry = it.next();
String key = entry.getKey();
String value = entry.getValue();
responseMap.put(key.toLowerCase(), value);
}
return responseMap;
}
/**
* map转url 排序后转
*
* @param map
* @return
*/
public static String toUrlWithSortAndEncode(Map<String, String> map) {
List<String> keys = new ArrayList<>(map.keySet());
Collections.sort(keys);
String prestr = "";
for (int i = 0; i < keys.size(); i++) {
String key = keys.get(i);
String value = map.get(key);
if (value == null) {
break;
}
if (i == keys.size() - 1) {//拼接时,不包括最后一个&字符
prestr = prestr + key + "=" + URLEncoder.encode(value);
} else {
prestr = prestr + key + "=" + URLEncoder.encode(value) + "&";
}
}
return prestr;
}
/**
* 表单字符串转化成 hashMap
*
* @param orderinfo
* @return
*/
public static HashMap<String, String> form2Map(String orderinfo) {
String listinfo[];
HashMap<String, String> map = new HashMap<String, String>();
listinfo = orderinfo.split("&");
for (String s : listinfo) {
String list[] = s.split("=");
if (list.length > 1) {
map.put(list[0], list[1]);
}
}
return map;
}
/**
* 对象转map,将字段转换为下划线形式
*
* @param obj
* @return
*/
public static Map<String, String> object2MapWithUnderline(Object obj) {
Map<String, String> map = new HashMap<>();
try {
Class<?> clazz = obj.getClass();
for (Field field : clazz.getDeclaredFields()) {
field.setAccessible(true);
String fieldName = field.getName();
fieldName = CamelCaseUtil.toUnderlineName(fieldName);
String value = field.get(obj) == null ? "" : String.valueOf(field.get(obj));
map.put(fieldName, value);
}
} catch (Exception e) {
e.printStackTrace();
}
return map;
}
/**
* 表单字符串转化成 hashMap,将具有下划线的key转换为小驼峰
* @param orderinfo,
* @return
*/
public static HashMap<String, String> form2MapWithCamelCase( String orderinfo) {
String listinfo[];
HashMap<String, String> map = new HashMap<String, String>();
listinfo = orderinfo.split("&");
for(String s : listinfo)
{
String list[] = s.split("=");
if(list.length>1)
{
map.put(CamelCaseUtil.toCamelCase(list[0]),list[1]);
}
}
return map;
}
/**
* map转对象
*
* @param obj
* @param clazz
* @param <T>
* @return
*/
public static <T> T mapToObject(Object obj, Class<T> clazz) {
try {
return objectMapper.readValue(serialize(obj), clazz);
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
public static String serialize(Object obj) {
if (obj == null) {
return null;
}
if (obj.getClass() == String.class) {
return (String) obj;
}
try {
return objectMapper.writeValueAsString(obj);
} catch (JsonProcessingException e) {
return null;
}
}
}
MoneyUtil :
import java.math.BigDecimal;
/**
* @Auther: IT贱男
* @Date: 2019/12/19 14:19
* @Description: 订单金额处理
*/
public class MoneyUtil {
/**
* 元转分 符合ISO 4217标准的三位字母代码
*
* @param yuan
* @return
*/
public static Integer YuanToFen(Double yuan) {
return BigDecimal.valueOf(yuan).movePointRight(2).intValue();
}
/**
* 分转元
*
* @param fen
* @return
*/
public static Double FenToYuan(Integer fen) {
return new BigDecimal(fen).movePointLeft(2).doubleValue();
}
}
RandomUtil :
/**
* @Auther: IT贱男
* @Date: 2019/12/19 13:49
* @Description: 生成随机数
*/
public class RandomUtil {
// 主要保证签名不可预测。我们推荐生成随机数算法如下:调用随机数函数生成,将得到的值转换为字符串。
private static final String RANDOM_STR = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
private static final java.util.Random RANDOM = new java.util.Random();
public static String getRandomStr() {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 16; i++) {
sb.append(RANDOM_STR.charAt(RANDOM.nextInt(RANDOM_STR.length())));
}
return sb.toString();
}
}
XmlUtil :
mport org.simpleframework.xml.Serializer;
import org.simpleframework.xml.core.Persister;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.StringWriter;
import java.util.HashMap;
import java.util.Map;
/**
* @Auther: IT贱男
* @Date: 2019/12/19 15:20
* @Description: xml处理工具类
*/
public class XmlUtil {
/**
* xml转对象
*
* @param xml
* @param objClass
* @return
*/
public static Object toObject(String xml, Class objClass) {
Serializer serializer = new Persister();
try {
return serializer.read(objClass, xml);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* xml 转 字符
*
* @param obj
* @return
*/
public static String toString(Object obj) {
Serializer serializer = new Persister();
StringWriter output = new StringWriter();
try {
serializer.write(obj, output);
} catch (Exception e) {
e.printStackTrace();
}
return output.toString();
}
/**
* xml 转 map
*
* @param strXML XML字符串
* @return XML数据转换后的Map
* @throws Exception
*/
public static Map<String, String> toMap(String strXML) {
try {
Map<String, String> data = new HashMap<>();
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
InputStream stream = new ByteArrayInputStream(strXML.getBytes("UTF-8"));
org.w3c.dom.Document doc = documentBuilder.parse(stream);
doc.getDocumentElement().normalize();
NodeList nodeList = doc.getDocumentElement().getChildNodes();
for (int idx = 0; idx < nodeList.getLength(); ++idx) {
Node node = nodeList.item(idx);
if (node.getNodeType() == Node.ELEMENT_NODE) {
org.w3c.dom.Element element = (org.w3c.dom.Element) node;
data.put(element.getNodeName(), element.getTextContent());
}
}
try {
stream.close();
} catch (Exception ex) {
// do nothing
}
return data;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}