聚合数据API查询快递数据-短信验证码-企业核名

前端之家收集整理的这篇文章主要介绍了聚合数据API查询快递数据-短信验证码-企业核名前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

有位朋友让我给他新开的网站帮忙做几个小功能,如下:

  1. 输入快递公司、快递单号,查询出这个快件的所有动态(从哪里出发,到了哪里)
  2. 注册登录等场景下的手机验证码(要求有一定的防刷策略)
  3. 通过输入公司名的关键词,查看这个公司是否已经注册、法人信息、有类似名称的公司等等

并且可以用的接口、文档都提供给我了。
其中需求 1、2,都通过聚合数据这家网站提供的接口实现;需求 3 通过云聚数据来实现。

本项目的文件

因为朋友的网站是用 ThinkPHP 写的,为了保持将来代码的兼容,这三个功能也用 ThinkPHP 写成。

项目的所有文件都放在了 GitHub 上,部分敏感数据已经隐藏,你需要自行替换,地址如下:

GitHub 地址:使用聚合数据API查询快递数据-短信验证码-企业核名

因为这三个功能并不是正式产品,将来会需要嵌入到网站的各个功能模块中去,所以为了查看起来方便,三个功能代码都写在一个文件里,你只要重点关注以下几个文件就好:

  • /Home/Conf/config.PHP参数配置文件
  • /Home/Controller/IndexController.class.PHP后端代码、API的请求与处理
  • /Home/View/Index_index.html前端 html

申请 KEY 和阅读开发文档

分别到上面两家网站上找到“快递”、“短信”、“核名”的文档地址,根据里面的说明,把 KEY、URL 等信息放入配置文件Home/Conf/config.PHP,方便后面重复使用。

常用快递API文档

短信API文档

核名-文档

注意短信的 API 服务,要先到网站的后台添加“短信模板”,让管理员审核后才可以正常调用

最后,Home/Conf/config.PHP配置文件里的内容如下

  1. return array(
  2. // 快递查询
  3. 'EXPRESS_APP_KEY' => '你的快递 APPKEY','EXPRESS_QUERY_URL' => 'http://v.juhe.cn/exp/index',//快递单号查询
  4. 'EXPRESS_COM_URL' => 'http://v.juhe.cn/exp/com',//快递公司查询
  5. // 发短信
  6. 'SEND_SMS_KEY' => '你的短信接口 APPKEY','SEND_SMS_URL' => 'http://v.juhe.cn/sms/send',// 核名
  7. 'COMPANY_KEY' => '核名 APPKEY','COMPANY_URL' => 'http://eci.yjapi.com/ECIFast/Search',//数据库配置信息
  8. 'DB_TYPE' => 'MysqL',// 数据库类型
  9. 'DB_HOST' => 'localhost',// 服务器地址
  10. 'DB_NAME' => '你的数据库名',// 数据库
  11. 'DB_USER' => '你的数据库用户名',// 用户名
  12. 'DB_PWD' => '密码',// 密码
  13. 'DB_PORT' => 3306,// 端口
  14. 'DB_PREFIX' => 'pre_',// 数据库表前缀
  15. 'DB_CHARSET'=> 'utf8',// 字符集
  16. );

设置数据库

为了防止恶意用户利用暴露在外的短信接口捣乱,需要对每个手机号码的行为进行记录。例如:

  • 向某个手机号码发送短信验证码后, 60 秒内不能再次发送
  • 必须在 1 小时内填写短信验证码,否则会过期
  • 避免 ajax 请求地址被机器人程序利用,在表单里要使用图片验证码才能提交数据

关于“表单验证码”我们后面代码会说明,这里先创建表结构如下,用于记录短信发送记录:

  1. CREATE TABLE `pre_smsrecord` (
  2. `id` int(11) unsigned NOT NULL AUTO_INCREMENT,`mobile` varchar(11) NOT NULL DEFAULT '',`tpl_id` int(11) NOT NULL,`code` int(6) NOT NULL,`time` int(11) NOT NULL,PRIMARY KEY (`id`)
  3. ) ENGINE=M

  • mobile是手机号码
  • tpl_id是在网站后台添加并通过审核的短信模板
  • code是发送的验证码(一般是4位或6位)
  • time是发送时间戳

直接下载sql进行还原:在本项目的 GitHub 地址上也可以直接从/Pubic目录找到 sql 文件,你可以直接把它还原你的 MysqL 上。

表单验证码的实现

最终效果如下:

因为三个功能都需要表单验证码,所以首先实现它。

打开/Home/View/Index_index.html,注意里面图片验证码 img 标签,以及对应的 javascript

  1. <p>
  2. (通用)输入验证码 <input type="text" name="verify" id="verify">
  3. <img id="verify-img" src="/?m=Home&c=Index&a=verify" alt="">
  4. <a id="btn-refresh-verify" href="javascript:;" title="">刷新</a>
  5. </p>
  6. <script type="text/javascript" charset="utf-8">
  7. jQuery(document).ready(function($) {
  8. // 刷新验证码按钮
  9. $("#btn-refresh-verify").click(function(event) {
  10. $("#verify-img").attr('src','/?m=Home&c=Index&a=verify' + "&time=" + new Date().getTime());
  11. });
  12. });
  13. </script>

对应的后端代码/Home/Controller/IndexController.class.PHP

  1. /**
  2. * +--------------------------------------------------------------------------
  3. * 生成验证码
  4. *
  5. * +--------------------------------------------------------------------------
  6. */
  7. public function verify(){
  8. $Verify = new \Think\Verify();
  9. $Verify->entry();
  10. }
  11. /**
  12. * +--------------------------------------------------------------------------
  13. * 检查验证码
  14. *
  15. * @param string $code 输入的验证码
  16. * @return boolean
  17. * +--------------------------------------------------------------------------
  18. */
  19. protected function check_verify($code){
  20. $verify = new \Think\Verify();
  21. return $verify->check($code);
  22. }

通用的 Curl 方法,用来向第三方网站的 API 发起请求并获取返回值

因为 3 个功能实际上都是通过网络来请求一个第三方网站的 API 接口地址,因此可以统一成一个通用的方法代码如下,可以传入三个变量,分别为 :url、参数数组、请求方式(是否是post,默认为0),返回一个 json 格式的数据。

  1. /**
  2. * +--------------------------------------------------------------------------
  3. * 通用的“聚合数据”请求接口,返回JSON数据
  4. *
  5. * @param string $url 接口地址
  6. * @param array $params 传递的参数
  7. * @param int $ispost 是否以POST提交,默认GET
  8. * @return json
  9. * +--------------------------------------------------------------------------
  10. */
  11. public function juhecurl($url,$params=false,$ispost=0){
  12. $httpInfo = array();
  13. $ch = curl_init();
  14. curl_setopt( $ch,CURLOPT_HTTP_VERSION,CURL_HTTP_VERSION_1_1 );
  15. curl_setopt( $ch,CURLOPT_USERAGENT,'Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.22 (KHTML,like Gecko) Chrome/25.0.1364.172 Safari/537.22' );
  16. curl_setopt( $ch,CURLOPT_CONNECTTIMEOUT,30 );
  17. curl_setopt( $ch,CURLOPT_TIMEOUT,30);
  18. curl_setopt( $ch,CURLOPT_RETURNTRANSFER,true );
  19. if( $ispost )
  20. {
  21. curl_setopt( $ch,CURLOPT_POST,true );
  22. curl_setopt( $ch,CURLOPT_POSTFIELDS,$params );
  23. curl_setopt( $ch,CURLOPT_URL,$url );
  24. }
  25. else
  26. {
  27. if($params){
  28. curl_setopt( $ch,$url.'?'.$params );
  29. }else{
  30. curl_setopt( $ch,$url);
  31. }
  32. }
  33. $response = curl_exec( $ch );
  34. if ($response === FALSE) {
  35. //echo "cURL Error: " . curl_error($ch);
  36. return false;
  37. }
  38. $httpCode = curl_getinfo( $ch,CURLINFO_HTTP_CODE );
  39. $httpInfo = array_merge( $httpInfo,curl_getinfo( $ch ) );
  40. curl_close( $ch );
  41. return $response;
  42. }

后面我们获取快递数据、发送短信、查询企业名称,都可以调用这个通用的方法

获取快递数据列表的实现

最终效果如下:

打开/Home/View/Index_index.html

  1. <h1>获取快递数据</h1>
  2. <div id="express-module">
  3. <p>
  4. 选择公司
  5. <select name="express-company" id="express-company">
  6. <option value="sf">顺丰</option>
  7. <option value="sto">申通</option>
  8. <option value="yt">圆通</option>
  9. <option value="yd">韵达</option>
  10. <option value="tt">天天</option>
  11. <option value="ems">EMS</option>
  12. <option value="zto">中通</option>
  13. <option value="ht">汇通</option>
  14. </select>
  15. </p>
  16. <p>
  17. 输入单号 <input type="text" name="express-number" id="express-number">
  18. </p>
  19. <p>
  20. <button id="btn-query-express" type="button" class="btn btn-default">查询快递</button>
  21. </p>
  22. <p>返回结果:</p>
  23. <p id="reason" style="color:#FF0000"></p>
  24. <p>快件动态:</p>
  25. <ul id="express-result">
  26. </ul>
  27. </div>
  28. <!-- 引入jquery库 -->
  29. <script src="__PUBLIC__/jquery.min.js" type="text/javascript"></script>
  30. <script type="text/javascript" charset="utf-8">
  31. jQuery(document).ready(function($) {
  32. //点击快递查询按钮
  33. $("#btn-query-express").click(function(event) {
  34. $("#reason").html("");
  35. // 更新验证码
  36. $("#verify-img").attr('src','/?m=Home&c=Index&a=verify' + "&time=" + new Date().getTime());
  37. $.getJSON(
  38. '/?m=Home&c=Index&a=getExpressData',{
  39. company: $("#express-company").val(),number: $("#express-number").val(),verify: $("#verify").val(),},function(json,textStatus) {
  40. if(json['resultcode'] == 200){
  41. var result_list = json['result']['list'];
  42. $("#express-result").html("");
  43. for(var i = 0,l = result_list.length; i < l; i++) {
  44. $("#express-result").append("<li>" + result_list[i]['datetime'] + "," + result_list[i]['remark'] + "," +result_list[i]['zone'] + "</li>");
  45. }
  46. }
  47. $("#reason").html(json['reason']);
  48. });
  49. });
  50. });
  51. </script>

对应的后端代码为如下,特别注意,你要把$content = $this->juhecurl(C("EXPRESS_QUERY_URL"),$params,1);这段的注释去掉(因为我开发的时候查询余额不足,所以使用了一个写死的数组来让程序能正常运行)

  1. /**
  2. * +--------------------------------------------------------------------------
  3. * 获取快递数据
  4. *
  5. * @param string $get.company 快递公司代码
  6. * @param string $get.number 快递单号
  7. * @return json
  8. * +--------------------------------------------------------------------------
  9. */
  10. public function getExpressData(){
  11. // 传入 get 参数,包括公司代号、快递单号、验证码
  12. $com = I("get.company");
  13. $no = I("get.number");
  14. $verify = I("get.verify");
  15. // 处理验证码
  16. if ( !$this->check_verify($verify) ) {
  17. $content = array('resultcode'=>1000,'reason'=>'验证码填写错误');
  18. echo json_encode($content);
  19. exit();
  20. }
  21. // 处理机器人程序刷接口(目前通过IP判断)
  22. $ip = get_client_ip(0,true);
  23. $Record = M("expressrecord");
  24. $express_record = $Record->where("ip='" . $ip . "'")->find();
  25. if( $express_record && ( (time() - $express_record['time']) < 60 ) ){
  26. echo json_encode(array('reason'=>'60秒内只能查询一次'));
  27. exit();
  28. }
  29. if ( $com && $no ) {
  30. $params = array(
  31. 'key' => C("EXPRESS_APP_KEY"),'com' => $com,'no' => $no
  32. );
  33. // 开发测试阶段直接返回值,不请求 API
  34. // $content = $this->juhecurl(C("EXPRESS_QUERY_URL"),1);
  35. $content = array('resultcode'=>200,'reason'=>'查询成功','result'=>array('list'=>array()));
  36. // 删除旧记录(如果有),然后添加新的记录
  37. $Record->where("ip='" . $ip . "'")->delete();
  38. $data = array(
  39. 'ip' => $ip,'time'=>time()
  40. );
  41. $Record->add($data);
  42. //$returnArray = json_decode($content,true);
  43. echo json_encode($content,true);
  44. }
  45. }

短信验证码的发送和检验

废话不多说,前端html直接上代码。这里实际上有两个动作,一个是“给我手机号码 xxxxx 发个验证码”,另一个是“我已经收到了,并填写了,请看我填写的验证码对不对”。

  1. <h1>发送短信验证码与检查</h1>
  2. <div id="sms-module">
  3. <p>
  4. 短信模板:
  5. <select name="send-sms-tplid" id="send-sms-tplid">
  6. <option value="5596">申请注册</option>
  7. <option value="5602">申请找回密码</option>
  8. <option value="5603">在线核名</option>
  9. </select>
  10. </p>
  11. <p>
  12. 手机号码:<input type="text" name="send-sms-mobile" id="send-sms-mobile">
  13. </p>
  14. <p>
  15. 输入手机验证码:<input type="text" name="send-sms-code" id="send-sms-code">
  16. <span id="sms-count-down"></span>
  17. <button id="btn-send-sms" type="button" class="btn btn-default">发送验证码</button>
  18. </p>
  19. <p>
  20. <button id="btn-check-sms" type="button" class="btn btn-default">提交手机验证码</button>
  21. </p>
  22. </div>
  23. <script type="text/javascript" charset="utf-8">
  24. /**
  25. * 发送短信验证码后,60秒倒计时
  26. */
  27. var seconds_left = 60;
  28. function sms_count_down(){
  29. if(seconds_left > 0){
  30. seconds_left = seconds_left-1;
  31. //var b=new Date();
  32. document.getElementById("sms-count-down").innerHTML = seconds_left + "秒";
  33. setTimeout("sms_count_down()",1000);
  34. } else {
  35. //根据 http://stackoverflow.com/questions/7526601/setattributedisabled-false-changes-editable-attribute-to-false
  36. // 不能为 setAttribute 设置任何值,都会变成 ”disabled“,要使用 removeAttribute
  37. document.getElementById("btn-send-sms").removeAttribute("disabled");
  38. document.getElementById("btn-send-sms").innerHTML = "重新发送";
  39. document.getElementById("sms-count-down").innerHTML = "";
  40. }
  41. }
  42. jQuery(document).ready(function($) {
  43. //发送短信
  44. $("#btn-send-sms").click(function(event) {
  45. $("#reason").html("");
  46. $.getJSON(
  47. '/?m=Home&c=Index&a=sendSMS',{
  48. tplid: $("#send-sms-tplid").val(),mobile: $("#send-sms-mobile").val(),textStatus) {
  49. $("#reason").html(json['reason']);
  50. var error_code = json['error_code'];
  51. if(error_code == 0){
  52. // 测试阶段,不禁用,可检查 60 秒重复发送
  53. //$("#btn-send-sms").attr("disabled",true);
  54. //开始倒计时
  55. sms_count_down();
  56. }else{
  57. $("#btn-send-sms").html("重新发送");
  58. }
  59. });
  60. });
  61. //检查短信验证码
  62. $("#btn-check-sms").click(function(event) {
  63. $("#reason").html("");
  64. // 更新验证码
  65. $("#verify-img").attr('src','/?m=Home&c=Index&a=verify' + "&time=" + new Date().getTime());
  66. $.getJSON(
  67. '/?m=Home&c=Index&a=checkSmsCode',code: $("#send-sms-code").val(),textStatus) {
  68. $("#reason").html(json['reason']);
  69. });
  70. });
  71. });

同样因为有两个动作,后端会稍微复杂一点点

  1. /**
  2. * +--------------------------------------------------------------------------
  3. * 请求发送短信接口,60秒后才能重新发送
  4. *
  5. * @param int $get.tplid 短信模板id
  6. * @param string $get.mobile 手机号码
  7. * @return json
  8. * +--------------------------------------------------------------------------
  9. */
  10. public function sendSMS(){
  11. $tpl_id = I("get.tplid"); // 短信模板id:注册 5596 找回密码 5602 在线核名 5603
  12. $mobile = I("get.mobile"); // 手机号码
  13. // 检查数据库记录,是否在 60 秒内已经发送过一次
  14. $Record = M("smsrecord");
  15. $where = array(
  16. 'mobile' => $mobile,'tpl_id' => $tpl_id,);
  17. $sms_record = $Record->where($where)->find();
  18. if( $sms_record && ( (time() - $sms_record['time']) < 60 ) ){
  19. echo json_encode(array('reason'=>'60秒内不能多次发送'));
  20. exit();
  21. }
  22. // 如果60秒内没有发过,则发送验证码短信(6位随机数字)
  23. $code = mt_rand(100000,999999);
  24. $smsConf = array(
  25. 'key' => C("SEND_SMS_KEY"),//您申请的APPKEY
  26. 'mobile' => $mobile,//接受短信的用户手机号码
  27. 'tpl_id' => $tpl_id,//您申请的短信模板ID,根据实际情况修改
  28. 'tpl_value' =>'#code#=' . $code //您设置的模板变量,根据实际情况修改 '#code#=1234&#company#=聚合数据'
  29. );
  30. //测试阶段,不发短信,直接设置一个“发送成功” json 字符串
  31. $content = $this->juhecurl(C("SEND_SMS_URL"),$smsConf,1); //请求发送短信
  32. //$content = json_encode(array('error_code'=>0,'reason'=>'发送成功'));
  33. if($content){
  34. $result = json_decode($content,true);
  35. $error_code = $result['error_code'];
  36. if($error_code == 0){
  37. // 状态为0,说明短信发送成功
  38. // 数据库存储发送记录,用于处理倒计时和输入验证,首先要删除旧记录
  39. $Record->where("mobile=" . $mobile)->delete();
  40. $data = array(
  41. 'mobile' => $mobile,'tpl_id'=> $tpl_id,'code'=>$code,'time'=>time()
  42. );
  43. $Record->data($data)->add();
  44. //echo "短信发送成功,短信ID:".$result['result']['sid'];
  45. }else{
  46. //状态非0,说明失败
  47. //echo "短信发送失败(".$error_code."):".$msg;
  48. }
  49. }else{
  50. //返回内容异常,以下可根据业务逻辑自行修改
  51. //$result['reason'] = '短信发送失败';
  52. }
  53. echo $content;
  54. }
  55. /**
  56. * +--------------------------------------------------------------------------
  57. * 检查填写的手机验证码是否填写正确
  58. * 可以添加更多字段改造成注册登录等表单
  59. *
  60. * @param string $get.verify 验证码
  61. * @param string $get.mobile 手机号码
  62. * @param int $get.tplid 短信模板ID
  63. * @param int $get.code 手机接收到的验证码
  64. * +--------------------------------------------------------------------------
  65. */
  66. public function checkSmsCode(){
  67. $verify = I("get.verify");
  68. $tpl_id = I("get.tplid"); // 短信模板id:注册 5596 找回密码 5602 在线核名 5603
  69. $mobile = I("get.mobile"); // 手机号码
  70. $code = I("get.code"); // 手机收到的验证码
  71. if(!$this->check_verify($verify)){
  72. $content = array('resultcode'=>1000,'reason'=>'验证码填写错误');
  73. echo json_encode($content);
  74. exit();
  75. }
  76. // 检查数据库记录,输入的手机验证码是否和之前通过短信 API 发送到手机的一致
  77. $Record = M("smsrecord");
  78. $where = array(
  79. 'mobile' => $mobile,'code' => $code,);
  80. $sms_record = $Record->where($where)->find();
  81. if($sms_record){
  82. echo json_encode(array('reason'=>'短信验证码核对成功'));
  83. // 处理后面的程序(如继续登录注册等)
  84. }else{
  85. echo json_encode(array('reason'=>'短信验证码错误'));
  86. }
  87. }

最后是企业核名

因为这个接口本来只提供了每次查询一个关键词,而朋友的网站要求能每次查询用“,”分隔的多个关键词,因此需要对提交的数据和返回到前端的进行一番处理(循环处理多个列表)。

前端代码比较简单

  1. <h1>企业核名</h1>
  2. <div id="company-module">
  3. <p>
  4. 省份:上海
  5. <input type="hidden" name="company-province" id="company-province" value="SH">
  6. </p>
  7. <p>
  8. <input type="text" name="company-name" id="company-name">
  9. </p>
  10. </div>
  11. <button id="btn-company" type="button" class="btn btn-default">企业核名</button>
  12. <p>公司查询列表:</p>
  13. <ul id="company-result">
  14. </ul>
  15. <javascript 略……
  16. //企业核名(可输入多个以逗号分隔的名称来多次查询)
  17. $("#btn-company").click(function(event) {
  18. $("#reason").html("");
  19. $("#company-result").html("");
  20. // 更新验证码
  21. $("#verify-img").attr('src','/?m=Home&c=Index&a=verify' + "&time=" + new Date().getTime());
  22. $.getJSON(
  23. '/?m=Home&c=Index&a=getCompanyName',{
  24. province: $("#company-province").val(),companyName: $("#company-name").val(),textStatus) {
  25. if(json['reason']){
  26. $("#reason").html(json['reason']);
  27. } else {
  28. //console.log(json);
  29. var json_list = JSON.parse(json);
  30. if (json_list.length > 0){
  31. for(i in json_list){
  32. var result_list = json_list[i].Result;
  33. if (result_list.length > 0){
  34. for(j in result_list){
  35. //console.log(result_list[j]);
  36. $("#company-result").append("<li> 名称:" + result_list[j].Name + ",法人:" + result_list[j].OperName + ",状态:" + result_list[j].Status + "</li>");
  37. }
  38. }
  39. }
  40. }
  41. }
  42. });
  43. });
  44. </javascript>

后端代码

  1. /**
  2. * +--------------------------------------------------------------------------
  3. * 企业核名
  4. * 该接口 http://eci.yjapi.com/ECIFast/Search 仅支持 GET 方式,因此 $param 为字符串形式
  5. *
  6. * @param string $get.province 省份
  7. * @param string $get.companyName 公司名
  8. * @return json
  9. * +--------------------------------------------------------------------------
  10. */
  11. public function getCompanyName(){
  12. // 传入 get 参数
  13. $province = I("get.province");
  14. $companyName = I("get.companyName");
  15. $verify = I("get.verify");
  16. // 处理验证码
  17. if ( !$this->check_verify($verify) ) {
  18. $content = array('resultcode'=>1000,true);
  19. $Record = M("companyrecord");
  20. $company_record = $Record->where("ip='" . $ip . "'")->find();
  21. // if( $company_record && ( (time() - $company_record['time']) < 60 ) ){
  22. // echo json_encode(array('reason'=>'60秒内只能查询一次企业名称'));
  23. // exit();
  24. // }
  25. $resultArray = array();
  26. if ( $province && $companyName ) {
  27. // 删除旧记录(如果有),然后添加新的记录
  28. $Record->where("ip='" . $ip . "'")->delete();
  29. // 循环处理多个公司名
  30. $companies = explode(",",$companyName);
  31. foreach ($companies as $key => $value) {
  32. $params = "key=" . C("COMPANY_KEY") . "&province={$province}&companyName={$value}";
  33. // 开发测试阶段直接返回值,不请求 API
  34. $content = $this->juhecurl(C("COMPANY_URL"),0);
  35. $returnArray = json_decode($content,true);
  36. // 循环插入新的结果
  37. $resultArray[] = $returnArray;
  38. //$content = array('resultcode'=>200,'result'=>array('list'=>array()));
  39. }
  40. $data = array(
  41. 'ip' => $ip,'time'=>time()
  42. );
  43. $Record->add($data);
  44. $content = json_encode($resultArray,true);
  45. }
  46. }

结束

至此三个功能都已经完成了,但是在防止恶意用户利用接口干坏事的策略上,还可以更进一步。例如

  • 必须要注册用户才能发送请求
  • 手机号码每天只能限制发送有限的验证码(因为目前即使每分钟发送一次,一天下来也比较可观了)
  • 手机号码注册后,再次请求发送验证码时,只能发给“已有”的手机号码数据表里的手机号码,而不能把“找回密码”发给一个还没注册的人。

猜你在找的设计模式相关文章