微信小程序授权登录绑定手机号接口小程序
微信小程序授权登录、绑定手机号(接口+小程序)
微信小程序授权登录、绑定手机号含接口和小程序
本篇博客主要为了记录小程序授权登录和绑定手机号功能的实现,包含小程序端和API(java语言开发)。
1、小程序授权登录
1.1、 微信公众平台小程序登录服务端api地址:
1.2、 登录流程:
登录凭证校验。通过 wx.login 接口(小程序操作)获得临时登录凭证 code 后传到开发者服务器调用此接口完成登录流程。
1.3、 请求地址(GET 请求):
1.4、 请求参数:
属性 | 类型 | 默认值 | 必填 | 说明 |
---|---|---|---|---|
appid | string | 是 | 小程序 appId | |
secret | string | 是 | 小程序 appSecret | |
js_code | string | 是 | 登录时获取的 code | |
grant_type | string | 是 | 授权类型,此处只需填写 authorization_code |
1.5、 返回的 JSON 数据包
属性 | 类型 | 说明 |
---|---|---|
openid | string | 用户唯一标识 |
session_key | string | 会话密钥 |
unionid | string | 用户在开放平台的唯一标识符,在满足 UnionID 下发条件的情况下会返回。 |
errcode | number | 错误码 |
errmsg | string | 错误信息 |
1.6、 表结构
1.7、 后台接口编写
@ApiOperation("微信授权登陆")
@RequestMapping(value = "/wxUserLogin", method = RequestMethod.POST)
@ResponseBody
@ApiImplicitParams({
@ApiImplicitParam(name = "code", value = "CODE", required = true, dataType = "String", paramType = "query"),
@ApiImplicitParam(name = "avatar", value = "avatarUrl", required = true, dataType = "String", paramType = "query"),
@ApiImplicitParam(name="nickName",value="昵称",required = true,dataType = "String",paramType = "query")
})
public CommonResult wxUserLogin(String code, String avatar, String nickName) throws Exception {
Map<String, Object> map = new HashMap<>();
StringBuffer buffer= new StringBuffer("https://api.weixin.qq.com/sns/jscode2session?appid=")
.append(appId).append("&secret=").append(appIdSecret).append("&js_code=").append(code)
.append("&grant_type=authorization_code");
String jsonString = HttpRequest.sendGet(buffer.toString());
JSONObject jsStrs = JSONObject.parseObject(jsonString);
String openId = jsStrs.getString("openid");
String sessionKey = jsStrs.getString("session_key");
map.put("openId", openId);
map.put("sessionKey", sessionKey);
map.put("mobileFlag",false);
if (openId != null) {
//查询用户信息
User user =userService.getUserByOpenId(openId);
if(user==null){
user=new User();
user.setNickName(nickName);
user.setOpenId(openId);
user.setHeadPortrait(avatar);
userService.insertUser(user);
}else{
if(user.getMobile()!=null){
map.put("mobileFlag",true);
}
}
map.put("user",user);
}
return new CommonResult(200,"操作成功",map);
}
@EnableAutoConfiguration
public class HttpRequest {
public static String sendGet(String url) throws Exception {
String result = "";
BufferedReader in = null;
try {
URL realUrl = new URL(url);
// 打开和URL之间的连接
URLConnection connection = realUrl.openConnection();
// 设置通用的请求属性
connection.setRequestProperty("accept", "*/*");
connection.setRequestProperty("Accept-Charset", "UTF-8");
connection.setRequestProperty("contentType", "UTF-8");
connection.setRequestProperty("connection", "Keep-Alive");
connection.setRequestProperty("Content-type", "application/x-www-form-urlencoded;charset=UTF-8");
connection.setRequestProperty("user-agent",
"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
// 建立实际的连接
connection.connect();
// 获取所有响应头字段
Map<String, List<String>> map = connection.getHeaderFields();
// 遍历所有的响应头字段
for (String key : map.keySet()) {
System.out.println(key + "--->" + map.get(key));
}
// 定义 BufferedReader输入流来读取URL的响应
in = new BufferedReader(new InputStreamReader(connection.getInputStream(), "UTF-8"));
String line;
while ((line = in.readLine()) != null) {
result += line;
}
} catch (Exception e) {
System.out.println("发送GET请求出现异常!" + e);
e.printStackTrace();
throw new Exception();
}
// 使用finally块来关闭输入流
finally {
try {
if (in != null) {
in.close();
}
} catch (Exception e2) {
e2.printStackTrace();
}
}
return result;
}
/**
* 向指定 URL 发送POST方法的请求
*
* @param url
* 发送请求的 URL
* @param param
* 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。
* @return 所代表远程资源的响应结果
*/
public static String sendPost(String url, String param) {
OutputStreamWriter out = null;
BufferedReader in = null;
String result = "";
try {
URL realUrl = new URL(url);
// 打开和URL之间的连接
URLConnection conn = realUrl.openConnection();
// 设置通用的请求属性
conn.setRequestProperty("accept", "*/*");
conn.setRequestProperty("connection", "Keep-Alive");
conn.setRequestProperty("user-agent",
"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
//设置请求编码格式
conn.setRequestProperty("Accept-Charset", "UTF-8");
conn.setRequestProperty("contentType", "UTF-8");
// 发送POST请求必须设置如下两行
conn.setDoOutput(true);
conn.setDoInput(true);
// 获取URLConnection对象对应的输出流
out = new OutputStreamWriter(conn.getOutputStream(), "UTF-8");
// 发送请求参数
out.write(param);
// flush输出流的缓冲
out.flush();
// 定义BufferedReader输入流来读取URL的响应
in = new BufferedReader(new InputStreamReader(conn.getInputStream(),"UTF-8"));
String line;
while ((line = in.readLine()) != null) {
result += line;
}
} catch (Exception e) {
System.out.println("发送 POST 请求出现异常!"+e);
e.printStackTrace();
}
//使用finally块来关闭输出流、输入流
finally{
try{
if(out!=null){
out.close();
}
if(in!=null){
in.close();
}
}
catch(IOException ex){
ex.printStackTrace();
}
}
return result;
}
public static String sendPost1(String url, String dataparam) {
String result = "";
BufferedReader in = null;
try {
/*"/GPServer/"+ method +*/
String urlNameString = url;
URL realUrl = new URL(urlNameString);
URLConnection connection = realUrl.openConnection();
//设置网络请求时间最多为5秒;
connection.setConnectTimeout(5000);
//读取网页请求结果时间为15秒
connection.setReadTimeout(25000);
//设置网络请求时间最多为5秒;
// 设置通用的请求属性
connection.setRequestProperty("accept", "*/*");
connection.setRequestProperty("connection", "Keep-Alive");
connection.setRequestProperty("user-agent",
"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
connection.setDoOutput(true);
connection.setDoInput(true);
//设置请求编码格式
connection.setRequestProperty("Accept-Charset", "UTF-8");
connection.setRequestProperty("contentType", "UTF-8");
// 获取URLConnection对象对应的输出流
// String dataparam="f=pjson&name=张三";
//文件流编码设置
OutputStreamWriter out = new OutputStreamWriter(connection.getOutputStream(), "UTF-8");
out.write(dataparam);
//错误方式,这种方式容易出现乱码
// PrintWriter out = new PrintWriter(connection.getOutputStream());
// flush输出流的缓冲
out.flush();
in = new BufferedReader(new InputStreamReader(connection.getInputStream(),"UTF-8"));
String line;
while ((line = in.readLine()) != null) {
result += line;
}
} catch (Exception e) {
e.printStackTrace();
}
// 使用finally块来关闭输入流
finally {
try {
if (in != null) {
in.close();
}
} catch (Exception e2) {
e2.printStackTrace();
}
}
return result;
}
}
1.8、 小程序扫码
wx.getSetting({
success: res => {
if (res.authSetting['scope.userInfo']) {
var that = this
// 登录
wx.login({
success: res => {
// 发送 res.code 到后台换取 openId, sessionKey
wx.getUserInfo({
withCredentials: true,
success: function (res_user) {
that.globalData.userInfo = res_user.userInfo;
debugger
wx.request({
url: getApp().globalData.servsers + '/custom/api/wxUserLogin',
data: { code: res.code, avatar: res_user.userInfo.avatarUrl,nickName: res_user.userInfo.nickName },
method: 'POST',
header: { "Content-Type": "application/x-www-form-urlencoded" },
success: function (res) {
console.log(JSON.stringify(res.data));
that.globalData.openId = res.data.openId;
that.globalData.sessionKey = res.data.sessionKey;
that.globalData.userInfo = res.data.user;
wx.hideLoading();
}, fail: function (e) {
wx.hideLoading();
}
})
// });
}, fail: function (e) {
debugger;
}
})
// }
}, fail: function (e) {
debugger;
wx.hideLoading();
}
})
// }
// })
} else {
// wx.redirectTo({
// url: '/pages/getuser/index',
// })
}
}
})
2、 获取手机号
2.1、 微信公众平台小程序获取手机号说明地址:
2.2、 获取手机号
获取微信用户绑定的手机号,需先调用wx.login接口。
因为需要用户主动触发才能发起获取手机号接口,所以该功能不由 API 来调用,需用 button 组件的点击来触发。
注意 目前该接口针对非个人开发者,且完成了认证的小程序开放(不包含海外主体)。需谨慎使用,若用户举报较多或被发现在不必要场景下使用,微信有权永久回收该小程序的该接口权限。
使用方法
需要将 button 组件 open-type 的值设置为 getPhoneNumber,当用户点击并同意之后,可以通过 bindgetphonenumber 事件回调获取到微信服务器返回的加密数据, 然后在第三方服务端结合 session_key 以及 app_id 进行解密获取手机号。
注意
在回调中调用 wx.login 登录,可能会刷新登录态。此时服务器使用 code 换取的 sessionKey 不是加密时使用的 sessionKey,导致解密失败。建议开发者提前进行 login;或者在回调中先使用 checkSession 进行登录态检查,避免 login 刷新登录态。
2.3、 后台接口编写
//Controller层
@ApiOperation("绑定手机号码")
@RequestMapping(value = "/bindMobile", method = RequestMethod.POST)
@ResponseBody
@ApiImplicitParams({
@ApiImplicitParam(name = "openId", value = "OPENID", required = true, dataType = "String", paramType = "query"),
@ApiImplicitParam(name = "id", value = "用户id", required = true, dataType = "Long", paramType = "query"),
@ApiImplicitParam(name = "encrypData", value = "encrypData", required = true, dataType = "String", paramType = "query"),
@ApiImplicitParam(name = "iv", value = "iv", required = true, dataType = "String", paramType = "query"),
@ApiImplicitParam(name = "sessionKey", value = "sessionKey", required = true, dataType = "String", paramType = "query")
})
public CommonResult bindMobile(String openId, Long id, String encrypData, String iv, String sessionKey ) {
try {
User user = userService.getUserByIdAndOpenId(id,openId);
if(user==null){
return new CommonResult(203,"用户校验失败");
}
logger.info("Bind_RequestJson==================>>>> openId="+openId+",encrypData="+encrypData+",iv="+iv+",sessionKey:"+sessionKey);
return userService.bind(user,encrypData, iv, sessionKey);
} catch (Exception e) {
e.printStackTrace();
logger.info("BindError===>"+e.getMessage());
return new CommonResult(203,"操作失败",e.getMessage());
}
}
//Service层
@Override
public CommonResult bind(User user, String encrypData, String iv, String sessionKey) {
String str = "";
Map map = new HashMap();
try {
str = AESDecodeUtils.decrypt(encrypData, iv, sessionKey);
} catch (Exception e) {
e.printStackTrace();
return new CommonResult(203,"获取手机号码失败");
}
JSONObject jsStrs = JSONObject.parseObject(str);
String phoneNumber = jsStrs.getString("phoneNumber");
if (!StringUtils.isNotBlank(phoneNumber)) {
return new CommonResult(203,"获取手机号码失败");
}
user.setMobile(phoneNumber);
this.updateUser(user);
map.put("mobileFlag", true);
map.put("user",user);
return new CommonResult(200,"绑定手机号成功", map);
}
public int updateUser(User user) {
return userMapper.updateUser(user);
}
2.4、 小程序端
<button open-type="getPhoneNumber" style="background-color: #4c5870;color: #ffffff;margin: 20px;border-radius:0px" bindgetphonenumber="getPhoneNumber">手机号授权</button>
getPhoneNumber: function (e) {
var encryptedData = e.detail.encryptedData;
var iv = e.detail.iv;
var thatss = this;
if (e.detail.errMsg == 'getPhoneNumber:fail user deny') { //用户点击拒绝
wx.showModal({
title: '手机号未授权',
content: '如需正常使用小程序功能,请按确定后并重新点击【手机号授权】按钮,然后选择【允许】',
showCancel: false,
success: function (res) {
if (res.confirm) {
}
}
})
return false;
} else {
wx.showLoading({
title: 'Loading...',
})
wx.login({
success: function (e) {
wx.getUserInfo({
success: function (res) {
let promise = new Promise(function (resolve, reject) {
getApp().globalData.userInfo = res.userInfo;
wx.request({
url: getApp().globalData.servsers + '/api/wechat/custom/wxUserLogin',
data: { code: res.code, avatar: res_user.userInfo.avatarUrl, nickName: res_user.userInfo.nickName },
method: 'POST',
header: { "Content-Type": "application/x-www-form-urlencoded" },
success: function (res) {
console.log(JSON.stringify(res.data));
that.globalData.openId = res.data.openId;
that.globalData.sessionKey = res.data.sessionKey;
that.globalData.userInfo = res.data.user;
wx.hideLoading();
}, fail: function (e) {
wx.hideLoading();
}
})
})
},
fail: function (e) {
wx.hideLoading();
debugger;
}
})
}, fail: function () {
wx.hideLoading();
debugger;
}
})
}
}
3、 充值(小程序支付)
3.1、微信支付官方地址:
参数
属性 | 类型 | 默认值 | 必填 | 说明 |
---|---|---|---|---|
timeStamp | string | 是 | 时间戳,从 1970 年 1 月 1 日 00:00:00 至今的秒数,即当前的时间 | |
nonceStr | string | 是 | 随机字符串,长度为32个字符以下 | |
package | string | 是 | 统一下单接口返回的 prepay_id 参数值,提交格式如:prepay_id=*** | |
signType | string | MD5 | 否 | 签名算法 |
paySign | string | 是 | 签名,具体签名方案参见 | |
success | function | 否 | 接口调用成功的回调函数 | |
fail | function | 否 | 接口调用失败的回调函数 | |
complete | function | 否 | 接口调用结束的回调函数(调用成功、失败都会执行) |
3.2、业务流程时序图
1、用户进入小程序
2、用户使用小程序进行支付(商户系统,就是我们所开发的程序)
3、调用小程序登录API(目的是为了获取用户的openId,这一步,我们可以在授权登录的时候做)
4、验证身份成功后异步返回openId
5、生成商户订单(在我们的数据库添加一条待支付状态的订单记录)
6、调用支付统一下单API(目的是为了获取预支付单号)
7、返回预付单信息(prepay_id即预支付单号),实际需要先执行6、7再执行5把预支付单号存到订单记录中,是为了解决当下未付款订单从未支付订单列表再次支付的问题。
8、组合appId、nonceStr、prepay_id、signType、timeStamp、key得到支付签名paySign
9、返回支付所需要的5个参数和sign
10、用户确认支付
11、鉴权调用微信后台支付
12、异步返回给微信小程序支付结果
13、小程序展示支付结果
14、同步推送支付结果给我们所开发的程序(我们设置的notify_url通知地址的路径)。
15、根据微信后台返回给我们的微信状态更新订单状态成功返回成功,失败返回失败并添加支付失败记录。
3.3、 后台接口
3.4、支付我采用sdk进行实现,地址:
由于支付需要商户号和商户秘钥,这里就不在把此部分代码放入GitHub,下面是我工作中实现的支付接口代码:
@Log(title = "统一下单", businessType = BusinessType.INSERT)
@ApiOperation("微信支付统一下单")
@ApiImplicitParams({
@ApiImplicitParam(name = "openId", value = "openId", required = true, dataType = "String", paramType = "query"),
@ApiImplicitParam(name = "employeeId", value = "员工id", required = true, dataType = "Long", paramType = "query"),
@ApiImplicitParam(name = "amount", value = "金额", required = true, dataType = "Double", paramType = "query")
})
@PostMapping("/placeOrder")
public RequestResult add(String openId, Long employeeId, Double amount,HttpServletRequest request) throws WxPayException {
ZjEmployee zjEmployee = zjEmployeeService.selectZjEmployeeDetailById(employeeId);
if (zjEmployee == null) {
return RequestResult.employee_error("无效用户!");
}
if(amount>0){
ZjRechargeRecord zjRechargeRecord = new ZjRechargeRecord();
int cent = (new Double(amount * 100).intValue());
String formatStr = new SimpleDateFormat("yyyyMMddHHmmssSSS").format(new Date());
zjRechargeRecord.setTransactionNum("ZJ" + formatStr + new DecimalFormat("0").format(amount).toString());
zjRechargeRecord.setRechargeTime(new Date());
zjRechargeRecord.setEmployeeId(employeeId);
zjRechargeRecord.setAmount(amount);
zjRechargeRecord.setPayStatus("1");
//设置充值类型为个人充值
zjRechargeRecord.setRechargeType("2");
WxPayUnifiedOrderRequest wxPayRequest= WxPayUnifiedOrderRequest.newBuilder().build();
//设置随机字符串
wxPayRequest.setNonceStr(RandomUtil.getRandomString(32));
//设置body
wxPayRequest.setBody("充值餐费金额");
//设置商户订单号
wxPayRequest.setOutTradeNo(zjRechargeRecord.getTransactionNum());
//设置币种
wxPayRequest.setFeeType("CNY");
//设置标价金额
wxPayRequest.setTotalFee(cent);
//设置终端ip
wxPayRequest.setSpbillCreateIp(getIp(request));
//设置通知地址
wxPayRequest.setNotifyUrl(AppletConfig.IMGURLPREFIX+"/api/wechat/notify/order");
//设置交易类型
wxPayRequest.setTradeType("JSAPI");
//设置用户标识
wxPayRequest.setOpenid(openId);
//设置签名方式
wxPayRequest.setSignType("MD5");
WxPayUnifiedOrderResult wxPayUnifiedOrderResult= this.wxService.unifiedOrder(wxPayRequest);
boolean addOrderFlag=true;
if("OK".equals(wxPayUnifiedOrderResult.getReturnMsg())){
zjRechargeRecord.setWxOrderNum(wxPayUnifiedOrderResult.getPrepayId());
addOrderFlag=zjRechargeRecordService.insertZjRechargeRecord(zjRechargeRecord)>0?true:false;
}
if(addOrderFlag){
StringBuffer buffer = new StringBuffer();
String timeStamp =String.valueOf(System.currentTimeMillis() / 1000);
buffer.append("appId=").append(wxPayUnifiedOrderResult.getAppid()).append("&nonceStr=").append(wxPayUnifiedOrderResult.getNonceStr())
.append("&package=prepay_id=").append(wxPayUnifiedOrderResult.getPrepayId()).append("&signType=MD5&timeStamp=").append(timeStamp)
.append("&key=").append(AppletConfig.APIKEY);
String paySign= Md5Utils.MD5Encode(buffer.toString(), "UTF-8").toUpperCase();
Map map = new HashMap();
map.put("paySign",paySign);
map.put("timeStamp",timeStamp);
map.put("nonceStr",wxPayUnifiedOrderResult.getNonceStr());
map.put("signType","MD5");
map.put("package","prepay_id="+wxPayUnifiedOrderResult.getPrepayId());
return RequestResult.success(map);
}else{
return RequestResult.employee_error("下单失败请稍后再试!");
}}else{
return RequestResult.employee_error("充值金额必须大于0!");
}
}
/**
* 支付回调通知处理
* @param xmlData
* @return
* @throws WxPayException
*/
@Log(title = "充值支付", businessType = BusinessType.INSERT)
@PostMapping("/notify/order")
public String parseOrderNotifyResult(@RequestBody String xmlData) throws WxPayException {
final WxPayOrderNotifyResult notifyResult = this.wxService.parseOrderNotifyResult(xmlData);
if("SUCCESS".equals(notifyResult.getResultCode())){
boolean addMountFlag = true;
ZjRechargeRecord zjRechargeRecord = zjRechargeRecordService.getZjRechargeRecordByTransactionNum(notifyResult.getOutTradeNo());
if(zjRechargeRecord==null){
WxPayNotifyResponse.fail("充值失败请联系管理员");
}else{
zjRechargeRecord.setPayStatus("2");
zjRechargeRecordService.updateZjRechargeRecord(zjRechargeRecord);
addMountFlag = zjEmployeeService.rechargeByPriceAndId(zjRechargeRecord.getAmount(), zjRechargeRecord.getEmployeeId()) > 0 ? true : false;
if(!addMountFlag){
ZjRechargeFailLog zjRechargeFailLog=new ZjRechargeFailLog();
zjRechargeFailLog.setTransactionNum(zjRechargeRecord.getTransactionNum());
zjRechargeFailLog.setWxOrderNum(zjRechargeRecord.getWxOrderNum());
zjRechargeFailLog.setEmployeeId(zjRechargeRecord.getEmployeeId());
zjRechargeFailLog.setAmount(zjRechargeRecord.getAmount());
zjRechargeFailLog.setRechargeTime(zjRechargeRecord.getRechargeTime());
zjRechargeFailLogService.insertZjRechargeFailLog(zjRechargeFailLog);
WxPayNotifyResponse.fail("充值失败请联系管理员");
}
return WxPayNotifyResponse.success("成功");
}else{
return WxPayNotifyResponse.fail(notifyResult.getReturnMsg());
}
}
public static String getIp(HttpServletRequest request) {
String ip = request.getHeader("X-Forwarded-For");
if (StringUtils.isNotEmpty(ip) && !"unKnown".equalsIgnoreCase(ip)) {
//多次反向代理后会有多个ip值,第一个ip才是真实ip
int index = ip.indexOf(",");
if (index != -1) {
return ip.substring(0, index);
} else {
return ip;
}
}
ip = request.getHeader("X-Real-IP");
if (StringUtils.isNotEmpty(ip) && !"unKnown".equalsIgnoreCase(ip)) {
return ip;
}
try {
return InetAddress.getLocalHost().getHostAddress();
} catch (Exception e) {
return "127.0.0.1";
}
}
3.5、不用SDK,手动编写,需要自己对参数值进行XML转义,提供工具类如下:
参数值转义后的结果示例:
代码:
public class PayCommonUtil {
private final static Logger logger = LoggerFactory.getLogger(PayCommonUtil.class);
public static Map<String, String> weixinPrePay(String openid, String sn,int totalAmount,
String body, String request) {
SortedMap<String, Object> parameterMap = new TreeMap<String, Object>();
parameterMap.put("appid", AppletConfig.APPID);
parameterMap.put("mch_id", AppletConfig.MCHID);
//设置32位随机字符串
parameterMap.put("nonce_str", RandomUtil.getRandomString(32));
parameterMap.put("body", body);
parameterMap.put("out_trade_no", sn);
parameterMap.put("fee_type", "CNY");
parameterMap.put("total_fee", totalAmount+"");
parameterMap.put("spbill_create_ip", request);
parameterMap.put("notify_url", "自己设置的接收通知结果的url");
parameterMap.put("trade_type", "JSAPI");
parameterMap.put("openid", openid);
String sign = PayCommonUtil.createSign("UTF-8", parameterMap,AppletConfig.APIKEY);
parameterMap.put("sign", sign);
String requestXML = PayCommonUtil.getRequestXml(parameterMap);
System.out.println(requestXML);
String result = PayCommonUtil.httpsRequest(
"https://api.mch.weixin.qq.com/pay/unifiedorder", "POST",
requestXML);
Map<String, String> map = null;
try {
map = PayCommonUtil.doXMLParse(result);
} catch (JDOMException e) {
logger.info("生成微信支付失败=============================>>>>weixinPrePay-JDOMException:"+e.getMessage());
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
logger.info("生成微信支付失败=============================>>>>weixinPrePay-IOException:"+e.getMessage());
// TODO Auto-generated catch block
e.printStackTrace();
}
return map;
}
//拼接xml
public static String getRequestXml(SortedMap<String, Object> parameters) {
StringBuffer sb = new StringBuffer();
sb.append("<xml>");
Set es = parameters.entrySet();
Iterator it = es.iterator();
while (it.hasNext()) {
Map.Entry entry = (Map.Entry) it.next();
String key = (String) entry.getKey();
String value = (String) entry.getValue();
if ("attach".equalsIgnoreCase(key) || "body".equalsIgnoreCase(key) || "sign".equalsIgnoreCase(key)) {
sb.append("<" + key + ">" + "<![CDATA[" + value + "]]></" + key + ">");
} else {
sb.append("<" + key + ">" + value + "</" + key + ">");
}
}
sb.append("</xml>");
return sb.toString();
}
//通过签名算法算出签名值
public static String createSign(String characterEncoding, SortedMap<String, Object> parameters, String apiKey) {
StringBuffer sb = new StringBuffer();
Set es = parameters.entrySet();
Iterator it = es.iterator();
while (it.hasNext()) {
Map.Entry entry = (Map.Entry) it.next();
String k = (String) entry.getKey();
Object v = entry.getValue();
if (null != v && !"".equals(v) && !"sign".equals(k) && !"key".equals(k)) {
sb.append(k + "=" + v + "&");
}
}
sb.append("key=" + apiKey);
System.out.println(sb.toString());
String sign = Md5Utils.MD5Encode(sb.toString(), characterEncoding).toUpperCase();
return sign;
}
public static String httpsRequest(String requestUrl, String requestMethod, String outputStr) {
try {
URL url = new URL(requestUrl);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setDoOutput(true);
conn.setDoInput(true);
conn.setUseCaches(false);
conn.setRequestMethod(requestMethod);
conn.setRequestProperty("content-type", "application/x-www-form-urlencoded");
if (null != outputStr) {
OutputStream outputStream = conn.getOutputStream();
outputStream.write(outputStr.getBytes("UTF-8"));
outputStream.close();
}
InputStream inputStream = conn.getInputStream();
InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "utf-8");
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
String str = null;
StringBuffer buffer = new StringBuffer();
while ((str = bufferedReader.readLine()) != null) {
buffer.append(str);
}
bufferedReader.close();
inputStreamReader.close();
inputStream.close();
conn.disconnect();
return buffer.toString();
} catch (ConnectException ce) {
System.out.println("连接异常" + ce);
} catch (Exception e) {
System.out.println("请求" + e);
}
return null;
}
//xml解析
public static Map doXMLParse(String strxml) throws IOException, JDOMException {
strxml = strxml.replaceFirst("encoding=\".*\"", "encoding=\"UTF-8\"");
if (null == strxml || "".equals(strxml)) {
return null;
}
Map m = new HashMap();
InputStream in = new ByteArrayInputStream(strxml.getBytes("UTF-8"));
SAXBuilder builder = new SAXBuilder();
Document doc = builder.build(in);
Element root = doc.getRootElement();
List list = root.getChildren();
Iterator it = list.iterator();
while (it.hasNext()) {
Element e = (Element) it.next();
String k = e.getName();
String v = "";
List children = e.getChildren();
if (children.isEmpty()) {
v = e.getTextNormalize();
} else {
v = getChildrenText(children);
}
System.out.println(k+"-"+v);
m.put(k, v);
}
in.close();
return m;
}
public static String getChildrenText(List children) {
StringBuffer sb = new StringBuffer();
if (!children.isEmpty()) {
Iterator it = children.iterator();
while (it.hasNext()) {
Element e = (Element) it.next();
String name = e.getName();
String value = e.getTextNormalize();
List list = e.getChildren();
sb.append("<" + name + ">");
if (!list.isEmpty()) {
sb.append(getChildrenText(list));
}
sb.append(value);
sb.append("</" + name + ">");
}
}
return sb.toString();
}
}
需要在pom中加入jdom依赖,用于解析xml
<dependency>
<groupId>org.jdom</groupId>
<artifactId>jdom</artifactId>
<version>1.1.3</version>
</dependency>
3.6、小程序端调用
需要先调用我们的统一下单接口得到参数,再调用wx.wx.requestPayment进行扣款操作
let openId = wx.getStorageSync('openId');
let { amount, userData } =this.data;
Req({
url:'/api/wechat/placeOrder',
method:"POST",
data:{ employeeId:userData.id, openId, amount }
}).then((res)=> {
let { data } =res.data;
wx.requestPayment({
timeStamp: data.timeStamp,//时间戳
nonceStr: data.nonceStr,//随机字符串
package: data.package,//prepay_id
signType: 'MD5',//签名算法
paySign: data.paySign,//签名
success (res) {
let { errMsg } =res;
if(errMsg == "requestPayment:ok") {
Req({
url:'/api/wechat/getPersonalInfo',
method:"GET",
data:{ employeeId:userData.id }
}).then((res)=> {
let { data } =res.data;
wx.setStorageSync('userData', JSON.stringify(data));
wx.switchTab({
url: '/pages/index/index',
})
},(err)=>{
})
}
},
fail (res) {
}
})
},(err)=>{
})
4、GitHub地址:
GitHub上SpringBoot工程中仅提供授权登录、绑定手机号的完整代码,微信支付青参照博客充值(小程序支付)这一章节,另会附上user表sql、小程序部分代码,由于小程序是使用我个人申请注册的,故只可以正常演示登录授权功能、获取手机号和充值功能小程序秘钥等需要在开发时替换成项目中申请的小程序的秘钥。amy_demo文件夹下为小程序代码,wechat-applet文件夹下为后台接口代码。
附上GitHub地址:
如有不足之处,欢迎交流,提醒更正O(∩_∩)O~!