昨天看了支付宝的登录接口代码,觉得有些东西还是对以后的开发有帮助的。下面就记录自己的感想。
首先是AlipayCore.java这个类,该类是请求、通知返回两个文件所调用的公用函数核心处理文件,不需要修改。方法主要是对签名和请求参数进行拼接:
/**
* 生成签名结果
* @param sArray 要签名的数组
* @return 签名结果字符串
*/
public static String buildMysign(Map sArray) {
String prestr = createLinkString(sArray); //把数组所有元素,按照“参数=参数值”的模式用“&”字符拼接成字符串
prestr = prestr + AlipayConfig.key; //把拼接后的字符串再与安全校验码直接连接起来
String mysign = AlipayMd5Encrypt.md5(prestr);
return mysign;
}
/**
* 除去数组中的空值和签名参数
* @param sArray 签名参数组
* @return 去掉空值与签名参数后的新签名参数组
*/
public static Map paraFilter(Map sArray) {
Map result = new HashMap();
if (sArray == null || sArray.size() <= 0) {
return result;
}
for (String key : sArray.keySet()) {
String value = sArray.get(key);
if (value == null || value.equals("") || key.equalsIgnoreCase("sign")
|| key.equalsIgnoreCase("sign_type")) {
continue;
}
result.put(key, value);
}
return result;
}
/**
* 把数组所有元素排序,并按照“参数=参数值”的模式用“&”字符拼接成字符串
* @param params 需要排序并参与字符拼接的参数组
* @return 拼接后字符串
*/
public static String createLinkString(Map params) {
List keys = new ArrayList(params.keySet());
Collections.sort(keys);
String prestr = "";
for (int i = 0; i < keys.size(); i++) {
String key = keys.get(i);
String value = params.get(key);
if (i == keys.size() - 1) {//拼接时,不包括最后一个&字符
prestr = prestr + key + "=" + value;
} else {
prestr = prestr + key + "=" + value + "&";
}
}
return prestr;
}
//↓↓↓↓↓↓↓↓↓↓请在这里配置您的基本信息↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
// 合作身份者ID,以2088开头由16位纯数字组成的字符串
public static String partner = "";
// 交易安全检验码,由数字和字母组成的32位字符串
public static String key = "";
// 当前页面跳转后的页面 要用 http://格式的完整路径,不允许加?id=123这类自定义参数
// 域名不能写成http://localhost/alipay.auth.authorize_jsp_utf8/return_url.jsp ,否则会导致return_url执行无效
public static String return_url = "http://127.0.0.1:8080/alipay.auth.authorize_jsp_utf8/return_url.jsp";
//↑↑↑↑↑↑↑↑↑↑请在这里配置您的基本信息↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
// 调试用,创建TXT日志路径
public static String log_path = "D:\\alipay_log_" + System.currentTimeMillis()+".txt";
// 字符编码格式 目前支持 gbk 或 utf-8
public static String input_charset = "UTF-8";
// 签名方式 不需修改
public static String sign_type = "MD5";
//访问模式,根据自己的服务器是否支持ssl访问,若支持请选择https;若不支持请选择http
public static String transport = "http";
AlipayMd5Encrypt.java这个类是对签名进行加密的工具类,不需要修改即可用:
/**
* 对字符串进行MD5签名
*
* @param text
* 明文
*
* @return 密文
*/
public static String md5(String text) {
return DigestUtils.md5Hex(getContentBytes(text, AlipayConfig.input_charset));
}
/**
* @param content
* @param charset
* @return
* @throws SignatureException
* @throws UnsupportedEncodingException
*/
private static byte[] getContentBytes(String content, String charset) {
if (charset == null || "".equals(charset)) {
return content.getBytes();
}
try {
return content.getBytes(charset);
} catch (UnsupportedEncodingException e) {
throw new RuntimeException("MD5签名过程中出现错误,指定的编码集不对,您目前指定的编码集是:" + charset);
}
}
接下来是AlipayNotify.java这个类,这个类主要是处理支付宝各接口通知返回:
/**
* HTTPS形式消息验证地址
*/
private static final String HTTPS_VERIFY_URL = "https://www.alipay.com/cooperate/gateway.do?service=notify_verify&";
/**
* HTTP形式消息验证地址
*/
private static final String HTTP_VERIFY_URL = "http://notify.alipay.com/trade/notify_query.do?";
/**
* 验证消息是否是支付宝发出的合法消息
* @param params 通知返回来的参数数组
* @return 验证结果
*/
public static boolean verify(Map params) {
String mysign = getMysign(params);
String responseTxt = "true";
if(params.get("notify_id") != null) {responseTxt = verifyResponse(params.get("notify_id"));}
String sign = "";
if(params.get("sign") != null) {sign = params.get("sign");}
//写日志记录(若要调试,请取消下面两行注释)
//String sWord = "responseTxt=" + responseTxt + "\n notify_url_log:sign=" + sign + "&mysign="
// + mysign + "\n notify回来的参数:" + AlipayCore.createLinkString(params);
//AlipayCore.logResult(sWord);
//验证
//responsetTxt的结果不是true,与服务器设置问题、合作身份者ID、notify_id一分钟失效有关
//mysign与sign不等,与安全校验码、请求时的参数格式(如:带自定义参数等)、编码格式有关
if (mysign.equals(sign) && responseTxt.equals("true")) {
return true;
} else {
return false;
}
}
/**
* 根据反馈回来的信息,生成签名结果
* @param Params 通知返回来的参数数组
* @return 生成的签名结果
*/
private static String getMysign(Map Params) {
Map sParaNew = AlipayCore.paraFilter(Params);//过滤空值、sign与sign_type参数
String mysign = AlipayCore.buildMysign(sParaNew);//获得签名结果
return mysign;
}
/**
* 获取远程服务器ATN结果,验证返回URL
* @param notify_id 通知校验ID
* @return 服务器ATN结果
* 验证结果集:
* invalid命令参数不对 出现这个错误,请检测返回处理中partner和key是否为空
* true 返回正确信息
* false 请检查防火墙或者是服务器阻止端口问题以及验证时间是否超过一分钟
*/
private static String verifyResponse(String notify_id) {
//获取远程服务器ATN结果,验证是否是支付宝服务器发来的请求
String transport = AlipayConfig.transport;
String partner = AlipayConfig.partner;
String veryfy_url = "";
if (transport.equalsIgnoreCase("https")) {
veryfy_url = HTTPS_VERIFY_URL;
} else {
veryfy_url = HTTP_VERIFY_URL;
}
veryfy_url = veryfy_url + "partner=" + partner + "¬ify_id=" + notify_id;
return checkUrl(veryfy_url);
}
/**
* 获取远程服务器ATN结果
* @param urlvalue 指定URL路径地址
* @return 服务器ATN结果
* 验证结果集:
* invalid命令参数不对 出现这个错误,请检测返回处理中partner和key是否为空
* true 返回正确信息
* false 请检查防火墙或者是服务器阻止端口问题以及验证时间是否超过一分钟
*/
private static String checkUrl(String urlvalue) {
String inputLine = "";
try {
URL url = new URL(urlvalue);
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
BufferedReader in = new BufferedReader(new InputStreamReader(urlConnection
.getInputStream()));
inputLine = in.readLine().toString();
} catch (Exception e) {
e.printStackTrace();
inputLine = "";
}
return inputLine;
}
下面我们就需要进行alipay的访问请求了,在请求过程中我们要有请求参数数组,提交的表单,有了这些条件之后我们就可以进行模拟HTTP请求获得返回的数据了。生成请求参数数组的代码如下:
/**
* 生成要请求给支付宝的参数数组
* @param sParaTemp 请求前的参数数组
* @return 要请求的参数数组
*/
private static Map buildRequestPara(Map sParaTemp) {
//除去数组中的空值和签名参数
Map sPara = AlipayCore.paraFilter(sParaTemp);
//生成签名结果
String mysign = AlipayCore.buildMysign(sPara);
//签名结果与签名方式加入请求提交参数组中
sPara.put("sign", mysign);
sPara.put("sign_type", AlipayConfig.sign_type);
return sPara;
}
构造提交表单代码:
/**
* 构造提交表单HTML数据
* @param sParaTemp 请求参数数组
* @param gateway 网关地址
* @param strMethod 提交方式。两个值可选:post、get
* @param strButtonName 确认按钮显示文字
* @return 提交表单HTML文本
*/
public static String buildForm(Map sParaTemp, String gateway, String strMethod,
String strButtonName) {
//待请求参数数组
Map sPara = buildRequestPara(sParaTemp);
List keys = new ArrayList(sPara.keySet());
StringBuffer sbHtml = new StringBuffer();
sbHtml.append("");
sbHtml.append("");
return sbHtml.toString();
}
构造模拟远程HTTP的POST请求,获取支付宝的返回XML处理结果:
public static String sendPostInfo(Map sParaTemp, String gateway)
throws Exception {
//待请求参数数组
Map sPara = buildRequestPara(sParaTemp);
HttpProtocolHandler httpProtocolHandler = HttpProtocolHandler.getInstance();
HttpRequest request = new HttpRequest(HttpResultType.BYTES);
//设置编码集
request.setCharset(AlipayConfig.input_charset);
request.setParameters(generatNameValuePair(sPara));
request.setUrl(gateway+"_input_charset="+AlipayConfig.input_charset);
HttpResponse response = httpProtocolHandler.execute(request);
if (response == null) {
return null;
}
String strResult = response.getStringResult();
return strResult;
}
generatNameValuePair方法:
/**
* MAP类型数组转换成NameValuePair类型
* @param properties MAP类型数组
* @return NameValuePair类型数组
*/
private static NameValuePair[] generatNameValuePair(Map properties) {
NameValuePair[] nameValuePair = new NameValuePair[properties.size()];
int i = 0;
for (Map.Entry entry : properties.entrySet()) {
nameValuePair[i++] = new NameValuePair(entry.getKey(), entry.getValue());
}
return nameValuePair;
}
接下来要看看HttpProtocolHandler.java这个类了,这个类是模拟HTTP请求的核心类,最主要的是execute这个执行http请求的方法:
public HttpResponse execute(HttpRequest request) {
HttpClient httpclient = new HttpClient(connectionManager);
// 设置连接超时
int connectionTimeout = defaultConnectionTimeout;
if (request.getConnectionTimeout() > 0) {
connectionTimeout = request.getConnectionTimeout();
}
httpclient.getHttpConnectionManager().getParams().setConnectionTimeout(connectionTimeout);
// 设置回应超时
int soTimeout = defaultSoTimeout;
if (request.getTimeout() > 0) {
soTimeout = request.getTimeout();
}
httpclient.getHttpConnectionManager().getParams().setSoTimeout(soTimeout);
// 设置等待ConnectionManager释放connection的时间
httpclient.getParams().setConnectionManagerTimeout(defaultHttpConnectionManagerTimeout);
String charset = request.getCharset();
charset = charset == null ? DEFAULT_CHARSET : charset;
HttpMethod method = null;
if (request.getMethod().equals(HttpRequest.METHOD_GET)) {
method = new GetMethod(request.getUrl());
method.getParams().setCredentialCharset(charset);
// parseNotifyConfig会保证使用GET方法时,request一定使用QueryString
method.setQueryString(request.getQueryString());
} else {
method = new PostMethod(request.getUrl());
((PostMethod) method).addParameters(request.getParameters());
method.addRequestHeader("Content-Type",
"application/x-www-form-urlencoded; text/html; charset=" + charset);
}
// 设置Http Header中的User-Agent属性
method.addRequestHeader("User-Agent", "Mozilla/4.0");
HttpResponse response = new HttpResponse();
try {
httpclient.executeMethod(method);
if (request.getResultType().equals(HttpResultType.STRING)) {
response.setStringResult(method.getResponseBodyAsString());
} else if (request.getResultType().equals(HttpResultType.BYTES)) {
response.setByteResult(method.getResponseBody());
}
response.setResponseHeaders(method.getResponseHeaders());
} catch (UnknownHostException ex) {
return null;
} catch (IOException ex) {
return null;
} catch (Exception ex) {
return null;
} finally {
method.releaseConnection();
}
return response;
}
当然我们需要在new HttpProtocolHandler这个类的时候创建一个线程安全的HTTP连接池,为什么要线程安全,下文会有提示。我们把这个步骤放到私有的构造方法当中:
private HttpProtocolHandler() {
connectionManager = new MultiThreadedHttpConnectionManager();
connectionManager.getParams().setDefaultMaxConnectionsPerHost(defaultMaxConnPerHost);
connectionManager.getParams().setMaxTotalConnections(defaultMaxTotalConn);
IdleConnectionTimeoutThread ict = new IdleConnectionTimeoutThread();
ict.addConnectionManager(connectionManager);
ict.setConnectionTimeout(defaultIdleConnTimeout);
ict.start();
}
然后使用如下方法来使其他类能使用getInstance()方法就获得HttpProtocolHandler这个类的实例对象,并且是线程安全的:
private static HttpProtocolHandler httpProtocolHandler = new HttpProtocolHandler();
/**
* 工厂方法
*
* @return
*/
public static HttpProtocolHandler getInstance() {
return httpProtocolHandler;
}
此类当中还有一些属性,用来设置http连接的超时时间,连接数等参数:
private static String DEFAULT_CHARSET = "GBK";
/** 连接超时时间,由bean factory设置,缺省为8秒钟 */
private int defaultConnectionTimeout = 8000;
/** 回应超时时间, 由bean factory设置,缺省为30秒钟 */
private int defaultSoTimeout = 30000;
/** 闲置连接超时时间, 由bean factory设置,缺省为60秒钟 */
private int defaultIdleConnTimeout = 60000;
private int defaultMaxConnPerHost = 30;
private int defaultMaxTotalConn = 80;
/** 默认等待HttpConnectionManager返回连接超时(只有在达到最大连接数时起作用):1秒*/
private static final long defaultHttpConnectionManagerTimeout = 3 * 1000;
/**
* HTTP连接管理器,该连接管理器必须是线程安全的.(如何设置线程安全上文已经写了)
*/
private HttpConnectionManager connectionManager;
此类当中还有HttpClient和HttpResponse这两个类,这两个类主要是对HTTP请求的封装和返回http的相应消息。这两个类就不在这里讲了,就是两个pojo类,HttpClient里面封装了请求需要的属性比如字符集,请求method,超时时间等,HttpResponse里面是响应头和返回结构的封装。
/**
* 支付宝提供给商户的服务接入网关URL(新)
*/
private static final String ALIPAY_GATEWAY_NEW = "https://mapi.alipay.com/gateway.do?";
/**
* 构造快捷登录接口
* @param sParaTemp 请求参数集合
* @return 表单提交HTML信息
*/
public static String alipay_auth_authorize(Map sParaTemp) {
//增加基本配置
sParaTemp.put("service", "alipay.auth.authorize");
sParaTemp.put("target_service", "user.auth.quick.login");
sParaTemp.put("partner", AlipayConfig.partner);
sParaTemp.put("return_url", AlipayConfig.return_url);
sParaTemp.put("_input_charset", AlipayConfig.input_charset);
String strButtonName = "确认";
return AlipaySubmit.buildForm(sParaTemp, ALIPAY_GATEWAY_NEW, "get", strButtonName);
}
/**
* 用于防钓鱼,调用接口query_timestamp来获取时间戳的处理函数
* 注意:远程解析XML出错,与服务器是否支持SSL等配置有关
* @return 时间戳字符串
* @throws IOException
* @throws DocumentException
* @throws MalformedURLException
*/
public static String query_timestamp() throws MalformedURLException,
DocumentException, IOException {
//构造访问query_timestamp接口的URL串
String strUrl = ALIPAY_GATEWAY_NEW + "service=query_timestamp&partner=" + AlipayConfig.partner;
StringBuffer result = new StringBuffer();
SAXReader reader = new SAXReader();
Document doc = reader.read(new URL(strUrl).openStream());
List nodeList = doc.selectNodes("//alipay/*");
for (Node node : nodeList) {
// 截取部分不需要解析的信息
if (node.getName().equals("is_success") && node.getText().equals("T")) {
// 判断是否有成功标示
List nodeList1 = doc.selectNodes("//response/timestamp/*");
for (Node node1 : nodeList1) {
result.append(node1.getText());
}
}
}
return result.toString();
}
到这里支付宝的登录接口基本上已经结束了,讲的不是很详细,具体流程和代码在这里下载: http://download.csdn.net/detail/uohzoaix/4009777。好了,吃饭去了。