使用聚合数据API查询快递数据-短信验证码-企业核名
有位朋友让我给他新开的网站帮忙做几个小功能,如下:
- 输入快递公司、快递单号,查询出这个快件的所有动态(从哪里出发,到了哪里)
- 在注册、登录等场景下的手机验证码(要求有一定的防刷策略)
- 通过输入公司名的关键词,查看这个公司是否已经注册、法人信息、有类似名称的公司等等
并且可以用的接口、文档都提供给我了。
其中需求 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
配置文件里的内容如下
return array(// 快递查询'EXPRESS_APP_KEY' => '你的快递 APPKEY','EXPRESS_QUERY_URL' => 'http://v.juhe.cn/exp/index', //快递单号查询'EXPRESS_COM_URL' => 'http://v.juhe.cn/exp/com', //快递公司查询// 发短信'SEND_SMS_KEY' => '你的短信接口 APPKEY','SEND_SMS_URL' => 'http://v.juhe.cn/sms/send',// 核名'COMPANY_KEY' => '核名 APPKEY','COMPANY_URL' => 'http://eci.yjapi.com/ECIFast/Search',//数据库配置信息'DB_TYPE' => 'mysql', // 数据库类型'DB_HOST' => 'localhost', // 服务器地址'DB_NAME' => '你的数据库名', // 数据库名'DB_USER' => '你的数据库用户名', // 用户名'DB_PWD' => '密码', // 密码'DB_PORT' => 3306, // 端口'DB_PREFIX' => 'pre_', // 数据库表前缀 'DB_CHARSET'=> 'utf8', // 字符集 );
设置数据库
为了防止恶意用户利用暴露在外的短信接口捣乱,需要对每个手机号码的行为进行记录。例如:
- 向某个手机号码发送短信验证码后, 60 秒内不能再次发送
- 必须在 1 小时内填写短信验证码,否则会过期
- 避免 ajax 请求地址被机器人程序利用,在表单里要使用图片验证码才能提交数据
关于“表单验证码”我们后面代码会说明,这里先创建表结构如下,用于记录短信发送记录:
CREATE TABLE `pre_smsrecord` (`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`) ) ENGINE=M
mobile
是手机号码tpl_id
是在网站后台添加并通过审核的短信模板code
是发送的验证码(一般是4位或6位)time
是发送时间戳
直接下载sql进行还原:在本项目的 GitHub 地址上也可以直接从 /Pubic
目录找到 sql 文件,你可以直接把它还原你的 MySQL 上。
表单验证码的实现
最终效果如下:
因为三个功能都需要表单验证码,所以首先实现它。
打开 /Home/View/Index_index.html
,注意里面图片验证码 img 标签,以及对应的 javascript
<p>(通用)输入验证码 <input type="text" name="verify" id="verify"><img id="verify-img" src="/?m=Home&c=Index&a=verify" alt=""><a id="btn-refresh-verify" href="javascript:;" title="">刷新</a> </p> <script type="text/javascript" charset="utf-8"> jQuery(document).ready(function($) {// 刷新验证码按钮$("#btn-refresh-verify").click(function(event) {$("#verify-img").attr('src', '/?m=Home&c=Index&a=verify' + "&time=" + new Date().getTime());}); }); </script>
对应的后端代码在 /Home/Controller/IndexController.class.php
中
/** * +-------------------------------------------------------------------------- * 生成验证码 * * +-------------------------------------------------------------------------- */ public function verify(){$Verify = new \Think\Verify();$Verify->entry(); } /** * +-------------------------------------------------------------------------- * 检查验证码 * * @param string $code 输入的验证码 * @return boolean * +-------------------------------------------------------------------------- */ protected function check_verify($code){$verify = new \Think\Verify();return $verify->check($code); }
通用的 Curl 方法,用来向第三方网站的 API 发起请求并获取返回值
因为 3 个功能实际上都是通过网络来请求一个第三方网站的 API 接口地址,因此可以统一成一个通用的方法。代码如下,可以传入三个变量,分别为 :url、参数数组、请求方式(是否是post,默认为0),返回一个 json 格式的数据。
/** * +-------------------------------------------------------------------------- * 通用的“聚合数据”请求接口,返回JSON数据 * * @param string $url 接口地址 * @param array $params 传递的参数 * @param int $ispost 是否以POST提交,默认GET * @return json * +-------------------------------------------------------------------------- */ public function juhecurl($url,$params=false,$ispost=0){$httpInfo = array();$ch = curl_init();curl_setopt( $ch, CURLOPT_HTTP_VERSION , CURL_HTTP_VERSION_1_1 );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' );curl_setopt( $ch, CURLOPT_CONNECTTIMEOUT , 30 );curl_setopt( $ch, CURLOPT_TIMEOUT , 30);curl_setopt( $ch, CURLOPT_RETURNTRANSFER , true );if( $ispost ){curl_setopt( $ch , CURLOPT_POST , true );curl_setopt( $ch , CURLOPT_POSTFIELDS , $params );curl_setopt( $ch , CURLOPT_URL , $url );}else{if($params){curl_setopt( $ch , CURLOPT_URL , $url.'?'.$params );}else{curl_setopt( $ch , CURLOPT_URL , $url);}}$response = curl_exec( $ch );if ($response === FALSE) {//echo "cURL Error: " . curl_error($ch);return false;}$httpCode = curl_getinfo( $ch , CURLINFO_HTTP_CODE );$httpInfo = array_merge( $httpInfo , curl_getinfo( $ch ) );curl_close( $ch );return $response; }
后面我们获取快递数据、发送短信、查询企业名称,都可以调用这个通用的方法。
获取快递数据列表的实现
最终效果如下:
打开 /Home/View/Index_index.html
<h1>获取快递数据</h1> <div id="express-module"><p>选择公司<select name="express-company" id="express-company"><option value="sf">顺丰</option><option value="sto">申通</option><option value="yt">圆通</option><option value="yd">韵达</option><option value="tt">天天</option><option value="ems">EMS</option><option value="zto">中通</option><option value="ht">汇通</option></select></p><p> 输入单号 <input type="text" name="express-number" id="express-number"> </p><p><button id="btn-query-express" type="button" class="btn btn-default">查询快递</button> </p><p>返回结果:</p><p id="reason" style="color:#FF0000"></p><p>快件动态:</p><ul id="express-result"></ul> </div> <!-- 引入jquery库 --> <script src="__PUBLIC__/jquery.min.js" type="text/javascript"></script> <script type="text/javascript" charset="utf-8"> jQuery(document).ready(function($) {//点击快递查询按钮 $("#btn-query-express").click(function(event) {$("#reason").html("");// 更新验证码 $("#verify-img").attr('src', '/?m=Home&c=Index&a=verify' + "&time=" + new Date().getTime());$.getJSON('/?m=Home&c=Index&a=getExpressData', {company: $("#express-company").val(),number: $("#express-number").val(),verify: $("#verify").val(),}, function(json, textStatus) {if(json['resultcode'] == 200){var result_list = json['result']['list'];$("#express-result").html("");for(var i = 0, l = result_list.length; i < l; i++) {$("#express-result").append("<li>" + result_list[i]['datetime'] + "," + result_list[i]['remark'] + "," +result_list[i]['zone'] + "</li>");}}$("#reason").html(json['reason']);});}); }); </script>
对应的后端代码为如下,特别注意,你要把 $content = $this->juhecurl(C("EXPRESS_QUERY_URL"), $params, 1);
这段的注释去掉(因为我开发的时候查询余额不足,所以使用了一个写死的数组来让程序能正常运行)
/** * +-------------------------------------------------------------------------- * 获取快递数据 * * @param string $get.company 快递公司代码 * @param string $get.number 快递单号 * @return json * +-------------------------------------------------------------------------- */ public function getExpressData(){// 传入 get 参数,包括公司代号、快递单号、验证码$com = I("get.company");$no = I("get.number");$verify = I("get.verify");// 处理验证码if ( !$this->check_verify($verify) ) {$content = array('resultcode'=>1000, 'reason'=>'验证码填写错误');echo json_encode($content);exit();}// 处理机器人程序刷接口(目前通过IP判断)$ip = get_client_ip(0, true);$Record = M("expressrecord");$express_record = $Record->where("ip='" . $ip . "'")->find();if( $express_record && ( (time() - $express_record['time']) < 60 ) ){echo json_encode(array('reason'=>'60秒内只能查询一次'));exit();}if ( $com && $no ) {$params = array('key' => C("EXPRESS_APP_KEY"),'com' => $com,'no' => $no);// 开发测试阶段直接返回值,不请求 API// $content = $this->juhecurl(C("EXPRESS_QUERY_URL"), $params, 1);$content = array('resultcode'=>200, 'reason'=>'查询成功', 'result'=>array('list'=>array()));// 删除旧记录(如果有),然后添加新的记录$Record->where("ip='" . $ip . "'")->delete();$data = array('ip' => $ip,'time'=>time());$Record->add($data);//$returnArray = json_decode($content,true);echo json_encode($content, true);} }
短信验证码的发送和检验
废话不多说,前端html直接上代码。这里实际上有两个动作,一个是“给我手机号码 xxxxx 发个验证码”,另一个是“我已经收到了,并填写了,请看我填写的验证码对不对”。
<h1>发送短信验证码与检查</h1> <div id="sms-module"><p>短信模板:<select name="send-sms-tplid" id="send-sms-tplid"><option value="5596">申请注册</option><option value="5602">申请找回密码</option><option value="5603">在线核名</option></select></p><p>手机号码:<input type="text" name="send-sms-mobile" id="send-sms-mobile"></p><p>输入手机验证码:<input type="text" name="send-sms-code" id="send-sms-code"><span id="sms-count-down"></span><button id="btn-send-sms" type="button" class="btn btn-default">发送验证码</button></p><p><button id="btn-check-sms" type="button" class="btn btn-default">提交手机验证码</button></p> </div> <script type="text/javascript" charset="utf-8"> /** * 发送短信验证码后,60秒倒计时 */ var seconds_left = 60; function sms_count_down(){if(seconds_left > 0){seconds_left = seconds_left-1;//var b=new Date(); document.getElementById("sms-count-down").innerHTML = seconds_left + "秒";setTimeout("sms_count_down()",1000);} else {//根据 http://stackoverflow.com/questions/7526601/setattributedisabled-false-changes-editable-attribute-to-false// 不能为 setAttribute 设置任何值,都会变成 ”disabled“,要使用 removeAttribute document.getElementById("btn-send-sms").removeAttribute("disabled");document.getElementById("btn-send-sms").innerHTML = "重新发送";document.getElementById("sms-count-down").innerHTML = "";} } jQuery(document).ready(function($) { //发送短信 $("#btn-send-sms").click(function(event) {$("#reason").html("");$.getJSON('/?m=Home&c=Index&a=sendSMS', {tplid: $("#send-sms-tplid").val(),mobile: $("#send-sms-mobile").val(),}, function(json, textStatus) {$("#reason").html(json['reason']);var error_code = json['error_code'];if(error_code == 0){// 测试阶段,不禁用,可检查 60 秒重复发送//$("#btn-send-sms").attr("disabled", true);//开始倒计时 sms_count_down();}else{$("#btn-send-sms").html("重新发送");}});});//检查短信验证码 $("#btn-check-sms").click(function(event) {$("#reason").html("");// 更新验证码 $("#verify-img").attr('src', '/?m=Home&c=Index&a=verify' + "&time=" + new Date().getTime());$.getJSON('/?m=Home&c=Index&a=checkSmsCode', {tplid: $("#send-sms-tplid").val(),mobile: $("#send-sms-mobile").val(),code: $("#send-sms-code").val(),verify: $("#verify").val(),}, function(json, textStatus) {$("#reason").html(json['reason']);});});});
同样因为有两个动作,后端会稍微复杂一点点
/** * +-------------------------------------------------------------------------- * 请求发送短信接口,60秒后才能重新发送 * * @param int $get.tplid 短信模板id * @param string $get.mobile 手机号码 * @return json * +-------------------------------------------------------------------------- */ public function sendSMS(){$tpl_id = I("get.tplid"); // 短信模板id:注册 5596 找回密码 5602 在线核名 5603$mobile = I("get.mobile"); // 手机号码// 检查数据库记录 ,是否在 60 秒内已经发送过一次$Record = M("smsrecord");$where = array('mobile' => $mobile,'tpl_id' => $tpl_id,);$sms_record = $Record->where($where)->find();if( $sms_record && ( (time() - $sms_record['time']) < 60 ) ){echo json_encode(array('reason'=>'60秒内不能多次发送'));exit();}// 如果60秒内没有发过,则发送验证码短信(6位随机数字)$code = mt_rand(100000, 999999);$smsConf = array('key' => C("SEND_SMS_KEY"), //您申请的APPKEY'mobile' => $mobile, //接受短信的用户手机号码'tpl_id' => $tpl_id, //您申请的短信模板ID,根据实际情况修改'tpl_value' =>'#code#=' . $code //您设置的模板变量,根据实际情况修改 '#code#=1234&#company#=聚合数据');//测试阶段,不发短信,直接设置一个“发送成功” json 字符串$content = $this->juhecurl(C("SEND_SMS_URL") ,$smsConf, 1); //请求发送短信//$content = json_encode(array('error_code'=>0, 'reason'=>'发送成功'));if($content){$result = json_decode($content,true);$error_code = $result['error_code'];if($error_code == 0){// 状态为0,说明短信发送成功// 数据库存储发送记录,用于处理倒计时和输入验证,首先要删除旧记录$Record->where("mobile=" . $mobile)->delete();$data = array('mobile' => $mobile,'tpl_id'=> $tpl_id,'code'=>$code,'time'=>time());$Record->data($data)->add();//echo "短信发送成功,短信ID:".$result['result']['sid'];}else{//状态非0,说明失败//echo "短信发送失败(".$error_code."):".$msg;}}else{//返回内容异常,以下可根据业务逻辑自行修改//$result['reason'] = '短信发送失败';}echo $content; } /** * +-------------------------------------------------------------------------- * 检查填写的手机验证码是否填写正确 * 可以添加更多字段改造成注册、登录等表单 * * @param string $get.verify 验证码 * @param string $get.mobile 手机号码 * @param int $get.tplid 短信模板ID * @param int $get.code 手机接收到的验证码 * +-------------------------------------------------------------------------- */ public function checkSmsCode(){$verify = I("get.verify");$tpl_id = I("get.tplid"); // 短信模板id:注册 5596 找回密码 5602 在线核名 5603$mobile = I("get.mobile"); // 手机号码$code = I("get.code"); // 手机收到的验证码if(!$this->check_verify($verify)){$content = array('resultcode'=>1000, 'reason'=>'验证码填写错误');echo json_encode($content);exit();}// 检查数据库记录,输入的手机验证码是否和之前通过短信 API 发送到手机的一致$Record = M("smsrecord");$where = array('mobile' => $mobile,'tpl_id' => $tpl_id,'code' => $code,);$sms_record = $Record->where($where)->find();if($sms_record){echo json_encode(array('reason'=>'短信验证码核对成功'));// 处理后面的程序(如继续登录、注册等)}else{echo json_encode(array('reason'=>'短信验证码错误'));} }
最后是企业核名
因为这个接口本来只提供了每次查询一个关键词,而朋友的网站要求能每次查询用“,”分隔的多个关键词,因此需要对提交的数据和返回到前端的进行一番处理(循环处理多个列表)。
前端代码比较简单
<h1>企业核名</h1> <div id="company-module"><p>省份:上海<input type="hidden" name="company-province" id="company-province" value="SH"></p><p><input type="text" name="company-name" id="company-name"></p> </div> <button id="btn-company" type="button" class="btn btn-default">企业核名</button> <p>公司查询列表:</p> <ul id="company-result"></ul> <javascript 略…… //企业核名(可输入多个以逗号分隔的名称来多次查询) $("#btn-company").click(function(event) {$("#reason").html("");$("#company-result").html("");// 更新验证码$("#verify-img").attr('src', '/?m=Home&c=Index&a=verify' + "&time=" + new Date().getTime());$.getJSON('/?m=Home&c=Index&a=getCompanyName', {province: $("#company-province").val(),companyName: $("#company-name").val(),verify: $("#verify").val(),}, function(json, textStatus) {if(json['reason']){$("#reason").html(json['reason']);} else {//console.log(json);var json_list = JSON.parse(json);if (json_list.length > 0){for(i in json_list){var result_list = json_list[i].Result;if (result_list.length > 0){for(j in result_list){//console.log(result_list[j]);$("#company-result").append("<li> 名称:" + result_list[j].Name + ", 法人:" + result_list[j].OperName + ", 状态:" + result_list[j].Status + "</li>");}}}}}}); }); </javascript>
后端代码
/** * +-------------------------------------------------------------------------- * 企业核名 * 该接口 http://eci.yjapi.com/ECIFast/Search 仅支持 GET 方式,因此 $param 为字符串形式 * * @param string $get.province 省份 * @param string $get.companyName 公司名 * @return json * +-------------------------------------------------------------------------- */ public function getCompanyName(){// 传入 get 参数$province = I("get.province");$companyName = I("get.companyName");$verify = I("get.verify");// 处理验证码if ( !$this->check_verify($verify) ) {$content = array('resultcode'=>1000, 'reason'=>'验证码填写错误');echo json_encode($content);exit();}// 处理机器人程序刷接口(目前通过IP判断)$ip = get_client_ip(0, true);$Record = M("companyrecord");$company_record = $Record->where("ip='" . $ip . "'")->find();// if( $company_record && ( (time() - $company_record['time']) < 60 ) ){// echo json_encode(array('reason'=>'60秒内只能查询一次企业名称'));// exit();// }$resultArray = array();if ( $province && $companyName ) {// 删除旧记录(如果有),然后添加新的记录$Record->where("ip='" . $ip . "'")->delete();// 循环处理多个公司名$companies = explode(",", $companyName);foreach ($companies as $key => $value) {$params = "key=" . C("COMPANY_KEY") . "&province={$province}&companyName={$value}";// 开发测试阶段直接返回值,不请求 API$content = $this->juhecurl(C("COMPANY_URL"), $params, 0);$returnArray = json_decode($content,true);// 循环插入新的结果$resultArray[] = $returnArray;//$content = array('resultcode'=>200, 'reason'=>'查询成功', 'result'=>array('list'=>array())); }$data = array('ip' => $ip,'time'=>time());$Record->add($data);$content = json_encode($resultArray, true);echo json_encode($content, true);} }
结束
至此三个功能都已经完成了,但是在防止恶意用户利用接口干坏事的策略上,还可以更进一步。例如
- 必须要注册用户才能发送请求
- 手机号码每天只能限制发送有限的验证码(因为目前即使每分钟发送一次,一天下来也比较可观了)
- 手机号码注册后,再次请求发送验证码时,只能发给“已有”的手机号码数据表里的手机号码,而不能把“找回密码”发给一个还没注册的人。
转载于:https://www.cnblogs.com/annie00/p/5810326.html
使用聚合数据API查询快递数据-短信验证码-企业核名相关推荐
- 通过聚合数据API实现快递数据查询-短信验证码-企业核名
有位朋友让我给他新开的网站帮忙做几个小功能,如下: 输入快递公司.快递单号,查询出这个快件的所有动态(从哪里出发,到了哪里) 在注册.登录等场景下的手机验证码(要求有一定的防刷策略) 通过输入公司名的 ...
- 【django】用户注册时短信验证码校验、避免频繁发送短信验证码【17】
注册时短信验证后端逻辑 class CheckSMScodeView(View):def get(self,request,phone):'''用户注册时短信验证码的校验功能:param reques ...
- 利用聚合数据API进行Android开发之短信验证码
在说Android中的短信验证码这个知识点前,我们首先来了解下聚合数据 聚合数据介绍 聚合数据是一家国内最大的基础数据API提供商,专业从事互联网数据服务.免费提供从天气查询.空气质量.地图坐标到金融 ...
- 聚合数据短信验证码接口实现 Android开发短信验证码
在说Android中的短信验证码这个知识点前,我们首先来了解下聚合数据 聚合数据介绍 聚合数据是一家国内最大的基础数据API提供商,专业从事互联网数据服务.免费提供从天气查询.空气质量.地图坐标到金融 ...
- 发送短信验证码 API数据接口
1.前言 此接口发送短信验证码 短信模板 参数说明: 1449978 验证码为:{1}为您的登录验证码,请于{2}分钟内填写,如非本人操作,请忽略本短信. 1449979 验证码为:{1},您正在登录 ...
- 【项目优化01】使用Git管理项目及使用redis缓存短信验证码,菜品以及套餐数据
文章目录 1. 使用Git管理项目 2. redis缓存 2.1 使用redis缓存短信验证码 2.2 使用redis缓存菜品数据 2.3 使用Spring Cache缓存套餐数据 1. 使用Git管 ...
- android can为啥能发收不到数据_拼多多登录时手机收不到短信验证码怎么办
遇到拼多多登录时收不到短信验证码的情况,该怎么办? 使用无痕方式 进入到软件所在的文件夹,以v2.6.6版本为例 右键编辑webcrawl-v2.6.6\root_crawl\setting.js 打 ...
- 使用Spring Boot JPA Specification实现使用JSON数据来查询实体数据
文章目录 使用Spring Boot JPA Specification实现使用JSON数据来查询实体数据 需求概要 JSON 结构的设计 使用策略模式执行不同的查询条件 构造查询条件 主逻辑具体的代 ...
- 使用阿里云短信验证码API发送短信验证码(配置,获取短信验证码,注册,登录,密码重置)
获取阿里云短信验证码需要的配置信息. 如果是新用户,可以免费领取3个月,老用户的话就只能购买了,但是也不贵. 申请短信签名 申请短信模板 编写发送短信验证码的工具类 代码中我已经进行了详细的注释,也写 ...
最新文章
- myeclipse中安装svn插件
- Windows Azure ISV博客系列:ReedRex 的sociobridge
- ie与firefox兼容文档
- 数据结构与算法之贪心算法 C++实现
- monocross 环境搭建:MonoTouch Mono for Android
- Centos 7.X安装DB2 10.5
- 【Clickhouse】rsyslog服务器使用clickhouse列数据库存储日志
- php 正则表达式 取所有内容,php正则表达式获取内容所有链接
- 解决linux下内网域名的ping结果和nslookup结果不一致
- springnbsp;security总结nbsp;太有用了!!
- 令人眼睛一亮的履历表
- RestTemplate发送请求并携带header信息
- 仅需1秒!搞定100万行数据:超强Python数据分析利器
- 视觉SLAM笔记(14) Eigen几何模块
- MongoDB入门(一)——数据库概述
- 苹果Mac矢量图形设计工具:Affinity Designer Beta
- java json-rpc_JSON-RPC(jsonrpc4j)使用demo
- MDM数据分析设计方案
- 游戏数值策划入门介绍
- 根据excel列动态创建mysql表_根据数据库字段动态生成excel模版下载,上传模版获取数据存入数据库(poi 反射)...
热门文章
- hihoCoder挑战赛16 王胖浩与三角形
- 教你用Python开发的一款迷你跑步游戏
- 公交车信息查询管理系统(Java实现)
- linux txt file busy,linux使用cp报错 Text file busy
- [leetcode] 229. Majority Element II
- vi之列操作——步步为营+实例
- Java中抽象类与方法的重写
- mysql连接异常:The server time zone value '�й���ʱ��' is unrecognized or represents more than one time zo
- autojs教程:找图函数
- Hetian lab Day 10:工欲善其事必先利其器