挪车码微信小程序开发(隐私保护通话) | 您所在的位置:网站首页 › 下载114移车小程序 › 挪车码微信小程序开发(隐私保护通话) |
前言
最近,在公司停车场看到很多车玻璃上都贴着挪车码,微信扫码之后会打开微信小程序或者网页,进而拨打电话呼叫车主挪车。相对于传统纸质版挪车牌,挪车码有以下几大优势: 美观,不占用过多空间。挪车码一般都是直接贴到车窗,和年检、保险标志一样,美观且撕下不留痕迹;隐私保护车主手机号。部分挪车码具备隐私保护机制,也就是说车主和呼叫方之间通话采用的是虚拟号码,并不会暴露车主手机号。这个机制可以防止4s店销售、保险销售等通过真实手机号骚扰车主的可能;成本极低。传统挪车牌一般都是几元、十几元甚至几十元,而挪车码成本不到1元,所以对车主和商家来讲成本都大幅降低;运营空间极大。如果你的小程序或者公众号拥有大量的车主信息,那么流量带来的收益是非常可观的。 经典案例分析下面分析一个扫码挪车经典案例,它是采用的微信小程序模式,之所以经典,完全是因为它是我自己写的😂。 上图是一张二维码,这是一个空码,空码的意思是它没被人绑定过,扫码之后界面如下: 绑定完成之后界面如下: 这时候车主注册过程已经完成了,下面就是呼叫车主的界面: 呼叫方扫码之后,会显示车主车牌号,点击电话通知,可以直接拨打电话: 可以看到,呼叫方和车主都不是171开头的号码,但是双方手机上显示的是171开头的号码,这就是隐私通话,而且10分钟之后双方再拨打这个号码就不通了,当然失效时间开发者可以根据自己的需要修改。这样彻底实现保护号码隐私。 华为云隐私保护通话开发之前,首先需要了解下隐私保护通话机制。该机制通俗的来讲就是A和B通话,双方都不暴露真实号码,双方屏显号码为C。笔者结合市面上常见的几大云市场,权衡利弊,最后锁定使用华为云隐私保护通话。 进入华为云官网,在产品---企业应用---云通信,找到隐私保护通话,点击后查看产品介绍。 可以看到,该产品有5种模式,AXB模式、AX模式、X模式等。读者可以自己查看了解,我在这里就不一一介绍了。下面分析下我们扫码挪车的场景。 扫码挪车场景呼叫方扫码挪车会有两种场景,一种是呼叫方本身就是我们的车主,也就是说呼叫方之前在我们这里注册过,我们知道他的手机号,这样A和B我们都知道手机号,所以在这种场景下我们采用AXB模式;另一种场景是呼叫方不是我们的车主,也就是说他并没有在我们这里注册过,我们不知道他的真实号码,在这种场景下我们采取X模式。 有读者会问,这样我们在呼叫之前还需要去判断是不是我们的用户,岂不是麻烦?其实当你真正的了解这两种模式之后你就不会有这种疑问了,AXB模式一个X号码可以绑定1000组关系,在挪车场景中X模式一个X号码在一定时间内只能是“冻结”状态。号码是有月租的,一个月5块钱,当你的用户量大的时候成本还是很高的,所以需要区分。这一问题后续也会讲到。 虚拟号码申请既然是隐私通话,不暴露真实号码,那么中间肯定是需要虚拟号码的,虚拟号码需要申请开通,如果是前期测试阶段,建议申请一个AXB模式号码,2个X模式号码。账号注册、创建隐私保护通话应用、号码申请等流程在这里就不赘述了,按步骤来就可以。 开发微信小程序开发我这里不再赘述,读者如有问题可以随时联系我,在这里主要讲的是对接华为云的开发细节,按上面讲的两个场景区分。 场景一:呼叫方是我们的用户呼叫方既然是我们的用户,那么我们自然知道他的手机号,果断采取AXB模式。首先看一下AXB模式接口文档: AXB模式绑定接口可以看到,文档上写的很清晰,其实就是一个post请求,很简单,只需要注意以下几点: 请求Headers参数要严格按照文档上的来,否则请求失败;callerNum和calleeNum要求是全局号码格式,以”+86开头“;relationNum和areaCode是二选一关系,但建议使用areaCode,华为云接口会自动分配可用的虚拟号码。否则开发者还需要自己筛选空闲的relationNum;recordHintTone和lastMinVoice参数如果非空的话,需要提前上传放音文件。示例: /** * AXB模式号码绑定 * @param callerNum 主叫 * @param calleeNum 被叫 * @param relationNum 虚拟号码X * @return */ public static Map bindAXB(String callerNum,String calleeNum,String relationNum){ Map resultMap = new HashMap(); if (StringUtils.isEmpty(callerNum) || StringUtils.isEmpty(calleeNum)) { resultMap.put("status", false); resultMap.put("errorCode", "40001"); resultMap.put("msg", "参数错误"); return resultMap; } // 封装JOSN请求 JSONObject json = new JSONObject(); json.put("relationNum", relationNum); // X号码(关系号码) json.put("callerNum", callerNum); // A方真实号码(手机或固话) json.put("calleeNum", calleeNum); // B方真实号码(手机或固话) /** * 选填,各参数要求请参考"AXB模式绑定接口" */ json.put("areaCode", "0531"); //城市码 json.put("callDirection", 0); //允许呼叫的方向--互相呼叫 //json.put("duration", SystemContant.CallDuration); //绑定关系保持时间 // json.put("recordFlag", "false"); //是否通话录音 // json.put("recordHintTone", "recordHintTone.wav"); //录音提示音 json.put("maxDuration", SystemContant.MaxDuration); //单次通话最长时间 json.put("lastMinVoice", "lastMinVoice.wav"); //通话最后一分钟提示音 json.put("privateSms", "false"); //是否支持短信功能 JSONObject callerHintTone = new JSONObject(); callerHintTone.put("callerHintTone", "callerHintTone.wav"); callerHintTone.put("calleeHintTone", "callerHintTone.wav"); json.put("preVoice", callerHintTone); //个性化通话前等待音*/ /*JSONArray preVoiceArr = new JSONArray(); JSONObject callerHintTone = new JSONObject(); callerHintTone.put("callerHintTone", "callerHintTone.wav"); preVoiceArr.add(callerHintTone); //设置A拨打X号码时的通话前等待音 JSONObject calleeHintTone = new JSONObject(); calleeHintTone.put("calleeHintTone", "callerHintTone.wav"); preVoiceArr.add(calleeHintTone); //设置B拨打X号码时的通话前等待音 json.put("preVoice", preVoiceArr); //个性化通话前等待音*/ String result = HttpUtil.sendPost(SystemContant.HuaWeiAppKey, SystemContant.HuaWeiAppSecret, SystemContant.HuaWeiHttpServer+SystemContant.VirtualCallInteface, json.toString()); System.out.println(result); JSONObject jsonResult = JSONObject.fromObject(result); String resultcode = jsonResult.getString("resultcode"); if(StringUtils.equals("0", resultcode)){ resultMap.put("status", true); resultMap.put("relationNum", jsonResult.getString("relationNum")); resultMap.put("subscriptionId", jsonResult.getString("subscriptionId")); }else{ resultMap.put("status", false); resultMap.put("relationNum", ""); } resultMap.put("errorCode", resultcode); resultMap.put("msg", jsonResult.getString("resultdesc")); //绑定sessionId,唯一标识一组绑定关系 //subscriptionId return resultMap; }
public static String sendPost(String appKey, String appSecret, String url, String jsonBody) { DataOutputStream out = null; BufferedReader in = null; StringBuffer result = new StringBuffer(); HttpsURLConnection connection = null; InputStream is = null; HostnameVerifier hv = new HostnameVerifier() { @Override public boolean verify(String hostname, SSLSession session) { return true; } }; try { trustAllHttpsCertificates(); } catch (Exception e1) { e1.printStackTrace(); } try { URL realUrl = new URL(url); connection = (HttpsURLConnection) realUrl.openConnection(); connection.setHostnameVerifier(hv); connection.setDoOutput(true); connection.setDoInput(true); connection.setRequestMethod("POST"); connection.setRequestProperty("Accept", "application/json"); connection.setRequestProperty("Content-Type", "application/json;charset=UTF-8"); connection.setRequestProperty("Authorization", "WSSE realm=\"SDP\", profile=\"UsernameToken\", type=\"Appkey\""); connection.setRequestProperty("X-WSSE", buildWsseHeader(appKey, appSecret)); logger.info("RequestBody is : " + jsonBody); connection.setConnectTimeout(3000); connection.connect(); out = new DataOutputStream(connection.getOutputStream()); out.writeBytes(jsonBody); out.flush(); out.close(); int status = connection.getResponseCode(); if (HTTP_STATUS_OK == status) { is = connection.getInputStream(); } else { is = connection.getErrorStream(); } in = new BufferedReader(new InputStreamReader(is, "UTF-8")); String line = ""; while ((line = in.readLine()) != null) { result.append(line); } } catch (Exception e) { e.printStackTrace(); logger.info("Send Post request catch exception: " + e.toString()); } finally { IOUtils.closeQuietly(out); IOUtils.closeQuietly(is); IOUtils.closeQuietly(in); if (null != connection) { IOUtils.close(connection); } } return result.toString(); } 上面示例只是一个post请求,读者仅可作为参考,如果有问题,随时可以与我沟通。 AXB模式解绑接口同样,解绑接口也是一个post请求,这里也不再赘述。针对挪车码的开发,这两个接口就足够了。 场景二:呼叫方不是我们的用户呼叫方不是我们的用户,那我们不知道他的号码,这个时候我采用的是X模式。当然读者也可以研究下其他的模式,有可能也适用,我这边只是针对我自己的需求选择X模式。 同样,首先查看接口文档: 接口有很多,我只用到了其中的呼叫事件通知接口。下面介绍该场景下我程序的逻辑: 首先有一个号码池,这个号码池用于存储空闲X号码,呼叫方点击”电话通知“按钮,这时需要从号码池找出一个空闲号码(要加锁,避免并发情况下出问题),将这个号码返回给呼叫方,同时保存记录到数据库。当呼叫方真正呼叫这个虚拟号码时,华为云会通过呼叫事件通知接口通知开发者服务器对应的接口,传参包括主叫号码、被叫号码等信息。被叫号码即虚拟号码,我们这时就可以通过虚拟号码查找数据库记录,返回真实的被叫号码给华为云,这样呼叫方和被叫方就可以正常通话了。一般挪车电话通话时间并不长,我们可以设置10分钟完成挪车操作,这样过期之后虚拟号码回收到号码池。这里只讲到了主体流程,里面的细节问题读者可以自由处理。下面是开发者服务器需要开发的接口示例,该接口用于接收华为云呼叫事件通知。 /** * X模式下接收华为传过来的数据 * @return */ @RequestMapping(value="/billCallBack",method= RequestMethod.POST) @ResponseBody public Map billCallBack(@RequestBody XCallBackPo xCallBackPo){ log.error(JsonUtil.toJSON(xCallBackPo)); Map resultMap = new HashMap(); // 封装JOSN请求 String eventType = xCallBackPo.getEventType(); // 通知的事件类型 CallStatusInfo statusInfo = xCallBackPo.getStatusInfo(); // 呼叫状态事件的信息 if(statusInfo!=null){ String timestamp = statusInfo.getTimestamp(); // 呼叫事件发生时隐私保护通话平台的UNIX时间戳 String callUserData = statusInfo.getUserData(); // 用户附属信息 String callSessionId = statusInfo.getSessionId(); // 通话链路的标识ID String caller = statusInfo.getCaller(); // 主叫号码 String called = statusInfo.getCalled(); // 被叫号码 Integer stateCode = statusInfo.getStateCode(); // 通话挂机的原因值 String stateDesc = statusInfo.getStateDesc(); // 通话挂机的原因值的描述 String origCalleeNum = statusInfo.getOrigCalleeNum(); // 呼叫的原始被叫号码,即隐私号码(X) String displayCallerNum = statusInfo.getDisplayCallerNum(); // 主显号码 String digitInfo = statusInfo.getDigitInfo(); // 预留字段,用于在放音收号场景中携带收号结果(即用户输入的数字) String callSubscriptionId = statusInfo.getSubscriptionId(); // 绑定关系ID String callNotifyMode = statusInfo.getNotifyMode(); // 通知模式,仅X模式场景携带 if (StringUtils.isNotEmpty(callNotifyMode)&&"Block".equalsIgnoreCase(callNotifyMode)) { resultMap.put("operation", "connect"); // 用于指示平台的呼叫操作 // resp.put("operation", "close"); JSONObject connectInfo = new JSONObject(); connectInfo.put("displayCalleeNum", called); // 被叫端的来显号码 //查询真实被叫号码 String calleeNum = ""; UserXRecord userXRecord = userCarService.getUserXRecordByRelationNum(called); if(userXRecord!=null){ if(StringUtils.isEmpty(userXRecord.getCaller())){ //第一次通话 userXRecord.setCaller(caller); userCarService.updateUserXRecord(userXRecord); //保存openId和电话 // userCarService.inserOrUpdateUserCarInfo("",caller,userXRecord.getReserve1(),"","","",""); } calleeNum = userXRecord.getCallee(); if(StringUtils.equals(calleeNum,caller)){ calleeNum = userXRecord.getCaller(); } } if(StringUtils.isEmpty(calleeNum)){ messageProducer.sendSystemErrorMessage("", "miniProgram", "UserCarController", "billCallBack", "XCallBackPo:"+xCallBackPo, "{status:false}", "", "X模式下返回华为云被叫号码为空", "saveErrorLog"); } connectInfo.put("calleeNum", calleeNum); // 真实被叫号码 connectInfo.put("maxDuration", SystemContant.CallDuration/60); // 单次通话最长时间 connectInfo.put("waitVoice", "callerHintTone.wav"); // 个性化通话前等待音 connectInfo.put("lastMinVoice", "lastMinVoice.wav"); // 通话最后一分钟提示音 /* connectInfo.put("recordFlag", "true"); // 是否通话录音 connectInfo.put("recordHintTone", "recordHintTone001.wav"); // 录音提示音 connectInfo.put("lastMinVoice", "lastMinVoice001.wav"); // 通话最后一分钟提示音 connectInfo.put("userData", "userflag001");*/ // 用户自定义数据 resultMap.put("connectInfo", connectInfo); // 指示平台接续被叫通话的参数列表 /*JSONArray closeInfoArr = new JSONArray(); JSONObject closeInfo = new JSONObject(); closeInfo.put("closeHintTone", "closeHintTone001.wav"); // 挂机提示音 closeInfo.put("userData", "userflag001"); // 用户自定义数据 closeInfoArr.add(closeInfo); resultMap.put("closeInfo", closeInfoArr);*/ // 指示平台结束会话的参数列表 } } return resultMap; } 以上即为挪车码隐私保护通话相关内容。如有任何问题,欢迎在评论区留言,我会尽快回复~~~ 联系作者
|
CopyRight 2018-2019 实验室设备网 版权所有 |