微信小程序授权登录、绑定手机号(接口+小程序)


本篇博客主要为了记录小程序授权登录和绑定手机号功能的实现,包含小程序端和API(java语言开发)。

1、小程序授权登录

1.1、 微信公众平台小程序登录服务端api地址:

https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/login/auth.code2Session.html

1.2、 登录流程:

登录凭证校验。通过 wx.login 接口(小程序操作)获得临时登录凭证 code 后传到开发者服务器调用此接口完成登录流程。

1.3、 请求地址(GET 请求):

https://api.weixin.qq.com/sns/jscode2session?appid=APPID&secret=SECRET&js_code=JSCODE&grant_type=authorization_code

1.4、 请求参数:

属性类型默认值必填说明
appidstring小程序 appId
secretstring小程序 appSecret
js_codestring登录时获取的 code
grant_typestring授权类型,此处只需填写 authorization_code

1.5、 返回的 JSON 数据包

属性类型说明
openidstring用户唯一标识
session_keystring会话密钥
unionidstring用户在开放平台的唯一标识符,在满足 UnionID 下发条件的情况下会返回。
errcodenumber错误码
errmsgstring错误信息

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、 微信公众平台小程序获取手机号说明地址:

https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/getPhoneNumber.html

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、微信支付官方地址:

https://developers.weixin.qq.com/miniprogram/dev/api/open-api/payment/wx.requestPayment.html
参数

属性类型默认值必填说明
timeStampstring时间戳,从 1970 年 1 月 1 日 00:00:00 至今的秒数,即当前的时间
nonceStrstring随机字符串,长度为32个字符以下
packagestring统一下单接口返回的 prepay_id 参数值,提交格式如:prepay_id=***
signTypestringMD5签名算法
paySignstring签名,具体签名方案参见 小程序支付接口文档
successfunction接口调用成功的回调函数
failfunction接口调用失败的回调函数
completefunction接口调用结束的回调函数(调用成功、失败都会执行)

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进行实现,地址:

https://github.com/Wechat-Group/WxJava
由于支付需要商户号和商户秘钥,这里就不在把此部分代码放入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地址:
https://github.com/Amywang1996/wechat
如有不足之处,欢迎交流,提醒更正O(∩_∩)O~!

  • 11
    点赞
  • 84
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
要在PHP中实现微信小程序授权获取用户信息并绑定手机号登录,可以按照以下步骤进行操作: 1. 在微信小程序端,通过`wx.login`获取到用户的临时登录凭证`code`。 2. 将获取到的`code`发送到服务器端,使用`https`接口调用`code2Session`接口,获取到`openid`和`session_key`。 3. 将`openid`和`session_key`保存至服务器端数据库或缓存中。 4. 在小程序端,使用`wx.getUserInfo`获取用户信息,包括`nickName`、`avatarUrl`等,并将用户信息传输到服务器端。 5. 在服务器端,接收到用户信息后,将用户信息保存到服务器数据库中,可以使用`openid`作为用户的唯一标识。 6. 在小程序端,点击绑定手机号的按钮,调用`wx.request`向服务器发送请求,请求获取手机号的能力。 7. 在服务器端,接收到手机号请求后,可以返回一个包含手机号获取能力的`code`给小程序端。 8. 小程序端收到`code`后,调用`wx.request`向服务器发送请求,请求获取手机号。 9. 服务器端接收到获取手机号的请求后,可以通过调用微信开放平台提供的解密能力,解密包含手机号的数据,并将解密得到的手机号与用户信息进行绑定。 10. 绑定成功后,返回相应的状态给小程序端。 总结:通过以上步骤,我们可以在PHP中实现微信小程序授权获取用户信息并绑定手机号登录的功能。在小程序端,用户使用微信授权登录后,将用户信息传输到服务器端保存,并通过绑定手机号使用户能够更便捷地登录和使用小程序。同时,在服务器端需要进行数据加密和解密的操作,确保用户的信息安全。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值