挪车码微信小程序开发(隐私保护通话) 您所在的位置:网站首页 下载114移车小程序 挪车码微信小程序开发(隐私保护通话)

挪车码微信小程序开发(隐私保护通话)

2024-01-30 20:43| 来源: 网络整理| 查看: 265

前言

最近,在公司停车场看到很多车玻璃上都贴着挪车码,微信扫码之后会打开微信小程序或者网页,进而拨打电话呼叫车主挪车。相对于传统纸质版挪车牌,挪车码有以下几大优势:

美观,不占用过多空间。挪车码一般都是直接贴到车窗,和年检、保险标志一样,美观且撕下不留痕迹;隐私保护车主手机号。部分挪车码具备隐私保护机制,也就是说车主和呼叫方之间通话采用的是虚拟号码,并不会暴露车主手机号。这个机制可以防止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 实验室设备网 版权所有