• PHP实现省市区关键词搜索邮编


    1. 前两天做了一个项目, 其中有一个需求是根据用户输入的关键词查询邮编. 最开始设计的数据库结构是省市区分为三个字段, province, city, area, 但是在写代码实现的过程中发现, 用户只输入省或者市或区县, 通过mysql的like模糊查询没问题. 但是如果用户输入的是"广东省深圳", "广东省南山区", "深圳市南山"等等, 显然只使用like无法实现, 我打算使用正则先将用户输入的关键词进行分割, 拆分成三个关键词, 然后再根据拆分后的每个关键词进行模糊查询, 我尝试使用`or like`, 想着使用一条SQL语句进行多个条件的查询, 但是没有成功, 下面的就是根据三个关键词进行查询的部分代码(正则的部分没有贴出), 当你输入"深圳市南山区" 的时候, 查询不到数据. 只有输入"广东省深圳市南山区" 才有数据. 因为正则的分隔是优先分隔省出来, 也就是你输入的是深圳市南山区, 他会把深圳市当做省来查询, 为什么这样呢, 因为要考虑到直辖市, 北京市就是省级的, 所以不好处理正则分隔. 还有内蒙古自治区, 也是省级的. 
       1 if(!empty($pro)){
       2                 $where = ['or like', 'province', $pro[0]];
       3             }
       4             if(!empty($city)){
       5                 //$where = ['and', ['like', 'city', $city[0]]];
       6                 $where = ['or like', 'city', $city[0]];
       7             }
       8             if(!empty($area)){
       9                 //$where = ['and', ['like', 'area', $area]];
      10                 $where = ['or like', 'area', $area];
      11             }
      12             $result = Postcode::find()->select('province, city, area, post_code')->where($where)->asArray()->all();
      View Code
    2. 上面的数据库结构似乎不太行, 我决定改变数据库的结构, 把省市区三个字段合并为一个字段, 使用逗号进行分隔区分. 这样做好处是 不管用户输入的是什么, 我都可以在一个字段里进行模糊查询. 这样的话, 数据库的字段主要就是两个, place(省市区合并后的字符串). postcode(邮编), 其他自行添加. 事实证明这种方案确实可行, 最后成功了. 接着说, 数据库的省市区之间是有逗号的, 所以在查询的时候,也需要加上逗号, 否则本来能查到数据的也查不到了. 
    3. 对用户输入的关键词进行正则分割,  正则分割这里, 最开始我才用的策略是进行一次性分割, 如下
      //一次性分隔, 根本无法确认第几个元素有值, 不可行
      $pattern = '/(.*市)(.*市).*?|(.*省)(.*市).*?|(.*区)(.*盟).*?|(.*区)(.*市).*?|(.*省)(.*州).*?|(.*省)(.*区).*?|(.*省)(.*县).*?|(.*区)(.*区).*?|(.*区)(.*州).*?/';
      preg_match_all($pattern, $q, $matches);
      //但是有个问题就是匹配到的数据在matches数组的第几个元素里, 这是无法确定的. 除非把matches数组遍历, 判断哪个元素有值. 

      我觉得这个不太可行, 我才用第二个策略, 先把关键词按照省来分隔, 再将匹配到的省从关键词截掉, 在剩下的关键词里面匹配市, 最后剩下的就是区县, 正则代码如下

       1 $pattPro = '/.*省|.*市|.*区/U';    //必须加上U, 在这里使用?是不能防止贪婪匹配的, e.g: 北京市北京市, 正则表达式顺序市在前省在后就会先匹配市
       2             preg_match($pattPro, $q, $pro);   //e.g: 辽宁省沈阳市,匹配"省"的结果就是辽宁省沈阳市,而不是辽宁省和沈阳市
       3 
       4             /* 把匹配到的省份从关键词中去除, 方便之后市区的匹配, 并且注意是中文, 所以需要mb_string扩展 */
       5             $lenPro = mb_strlen($pro[0]);    //由于mb_strpos获取一个字符串首次出现的位置是按照第一个字符的位置, 而不是最后一个字符的位置, 所以先获取字符串的最后一个字符
       6             /* 特别注意: 获取字符串长度的时候,没有使用mb_开头的函数, 截取的开始位置不同的 */
       7             $q = mb_substr($q, $lenPro);    //从最后一个字符出现的位置+1开始截取就是省后面的城市名了,
       8 
       9             $pattCity = '/.*市|.*盟|.*州|.*区|.*县/U';
      10             preg_match($pattCity, $q, $city);
      11             $lenCity = mb_strlen($city[0]);
      12             $area = mb_substr($q, $lenCity);
      正则

      分隔完再添加逗号, 去数据库查询. 

      1 $info = $pro[0].','.$city[0].','.$area;
      2                 $info = trim($info, ',');    //去掉逗号, 那么无论省市区哪个为空, 最终字符串info里左右都只会剩下一个",", 这样总是可以在数据库中查询到
      3                 $where = ['or like', 'place', $info];
      4                 $count = post_code::find()->where($where)->count();
      5                 $p = new Pagination(['totalCount'=>$count, 'pageSize'=>$pagesize]);
      6                 $result = post_code::find()->select('place, postcode')->where($where)->offset(($page-1)*$pagesize)->limit($p->limit)->asArray()->all();
      查询数据库

      经过测试, 发现了一个问题, 就是用户输入"江西" 不加"省", 那么正则匹配后会在江西后面加上逗号, 这显然查不到数据的, 因为数据库里存的是"江西省," , 还有输入"江西省吉安", 也查不到,

    4. 为了解决这些问题, 又想到了一种新的方案, 先试想一下, 用户输入习惯, 用户一般肯定是比较喜欢输入"江西", 而不是"江西省", 输入"内蒙古" 而不是"内蒙古自治区", 所以能够发现我只要在用户输入的时候, 先判断一下关键词的字数, 只要是<=3, 那么就不需要进行正则匹配, 直接拿到数据库进行查询只有在关键词字数大于 3 的时候, 才进行正则等一系列的操作, 发现这个解决了之前的问题. 不管用户输入"广东省深圳", 还是"深圳", 都可以查到.

    5. 最后附上完整代码, (注: 这里因为数据比较多, 所以使用了分页查询, 以及对数组进行分页等)

        1 /* 获取邮编号码 */
        2     public function actionPostcode($page=1, $pagesize=20, $q=null)
        3     {
        4         if($q==null){
        5             $count = post_code::find()->count();
        6             $p = new Pagination(['totalCount'=>$count, 'pageSize'=>$pagesize]);
        7             $result = post_code::find()->select('place, postcode')->offset(($page-1)*$pagesize)->limit($p->limit)->asArray()->all();
        8             if(!$result){
        9                 return [
       10                     'code' => 1,
       11                     'msg' => '没有数据',
       12                 ];
       13             }
       14             $data = [];
       15             foreach($result as $k=>$v){
       16                 $data[$k]['province'] = explode(',', $v['place'])[0];
       17                 $data[$k]['city'] = explode(',', $v['place'])[1];
       18                 $data[$k]['area'] = explode(',', $v['place'])[2];
       19                 $data[$k]['postcode'] = $v['postcode'];
       20             }
       21             return [
       22                 'code' => 0,
       23                 'msg' => 'success',
       24                 'data' => $data,
       25             ];
       26         }
       27         /* 使用一个折中方案处理用户输入"石家庄"也能搜索的情况, 就是判断用户输入的字数, 如果<=3, 则直接去数据库查询, 只有字数超过3才进行分隔 */
       28         if($q != null && !is_numeric($q) && mb_strlen($q)<=3){
       29             $where = ['like', 'place', $q];
       30             $count = post_code::find()->where($where)->count();
       31             $p = new Pagination(['totalCount'=>$count, 'pageSize'=>$pagesize]);
       32             $result = post_code::find()->select('place, postcode')->where($where)->offset(($page-1)*$pagesize)->limit($p->limit)->asArray()->all();
       33             if(!$result){
       34                 return [
       35                     'code' => 1,
       36                     'msg' => '没有数据',
       37                 ];
       38             }
       39             $data = [];
       40             foreach($result as $k=>$v){
       41                 $data[$k]['province'] = explode(',', $v['place'])[0];
       42                 $data[$k]['city'] = explode(',', $v['place'])[1];
       43                 $data[$k]['area'] = explode(',', $v['place'])[2];
       44                 $data[$k]['postcode'] = $v['postcode'];
       45             }
       46             return [
       47                 'code' => 0,
       48                 'msg' => 'success',
       49                 'data' => $data,
       50             ];
       51         }
       52 
       53         /* 按照省市区关键词查询, 并且关键词字数大于3 */
       54         if($q != null && !is_numeric($q) && mb_strlen($q)>3){
       55             $pattPro = '/.*省|.*市|.*区/U';    //必须加上U, 在这里使用?是不能防止贪婪匹配的, e.g: 北京市北京市, 正则表达式顺序市在前省在后就会先匹配市
       56             preg_match($pattPro, $q, $pro);   //e.g: 辽宁省沈阳市,匹配"省"的结果就是辽宁省沈阳市,而不是辽宁省和沈阳市
       57 
       58             /* 把匹配到的省份从关键词中去除, 方便之后市区的匹配, 并且注意是中文, 所以需要mb_string扩展 */
       59             $lenPro = mb_strlen($pro[0]);    //由于mb_strpos获取一个字符串首次出现的位置是按照第一个字符的位置, 而不是最后一个字符的位置, 所以先获取字符串的最后一个字符
       60             /* 特别注意: 获取字符串长度的时候,没有使用mb_开头的函数, 截取的开始位置不同的 */
       61             $q = mb_substr($q, $lenPro);    //从最后一个字符出现的位置+1开始截取就是省后面的城市名了,
       62 
       63             $pattCity = '/.*市|.*盟|.*州|.*区|.*县/U';
       64             preg_match($pattCity, $q, $city);
       65             $lenCity = mb_strlen($city[0]);
       66             $area = mb_substr($q, $lenCity);
       67 
       68 
       69             /* 将关键词使用逗号拼接起来, 再去数据库模糊查询 */
       70 //            if(!empty($pro) && empty($city) && empty($area)){
       71 //                $info = $pro[0];
       72 //            }elseif (!empty($pro) && !empty($city) && empty($area)){
       73 //                $info = $pro[0].','.$city[0];
       74 //            }else{
       75 //                $info = $pro[0].','.$city[0].','.$area;
       76 //            }
       77 
       78             /* 因为县,区既可以理解为市级也可以理解为区级, 所以正则会默认按照市级进行分割 */
       79             /* 只有一种情况需要单独考虑, 就是关键词是省区, 中间跨过市的情况下, 直接查询数据库是查询不到结果的 */
       80             $status = false;   //如果用户输入"江西省吉安", 分隔后city为空, area不为空,如果是这种情况, 将status置为true
       81             if(empty($city[0]) && !empty($area)){
       82                 $info = [$pro[0], $area];    //参考or like 的使用方法
       83                 $status = true;
       84                 $where = ['or like', 'place', $info];
       85                 $res = post_code::find()->select('place, postcode')->where($where)->asArray()->all();
       86             }else{
       87                 $info = $pro[0].','.$city[0].','.$area;
       88                 $info = trim($info, ',');    //去掉逗号, 那么无论省市区哪个为空, 最终字符串info里左右都只会剩下一个",", 这样总是可以在数据库中查询到
       89                 $where = ['or like', 'place', $info];
       90                 $count = post_code::find()->where($where)->count();
       91                 $p = new Pagination(['totalCount'=>$count, 'pageSize'=>$pagesize]);
       92                 $result = post_code::find()->select('place, postcode')->where($where)->offset(($page-1)*$pagesize)->limit($p->limit)->asArray()->all();
       93             }
       94 
       95             if($status){
       96                 /* 在返回的结果中进行二次过滤, 因为江西省吉安的这种情况将返回的是江西省的数据, 而不是北京, 所以可以进行二次过滤 */
       97                 $new_res = [];   //用来接收二次过滤后的数据,
       98                 foreach($res as $k=>$v){
       99                     if(strstr($v['place'], $area) !== false){     //也可以使用正则匹配数组, 这里使用 strstr模拟模糊匹配
      100                         /* 匹配成功 TODO */
      101                         array_push($new_res, $v);
      102                     }
      103                 }
      104                 /* 做分页处理 */
      105                 $pnum = ceil(count($new_res) / $pagesize);
      106                 if($page > $pnum){
      107                     $page = $pnum;
      108                 }
      109                 $result = array_slice($new_res, ($page-1)*$pagesize, $pagesize);
      110             }
      111             if(!$result){
      112                 return [
      113                     'code' => 1,
      114                     'msg' => '没有数据',
      115                 ];
      116             }
      117             /* 处理数据结构, 分隔省市区为三个字段 */
      118             $data = [];
      119             foreach($result as $k=>$v){
      120                 $data[$k]['province'] = explode(',', $v['place'])[0];
      121                 $data[$k]['city'] = explode(',', $v['place'])[1];
      122                 $data[$k]['area'] = explode(',', $v['place'])[2];
      123                 $data[$k]['postcode'] = $v['postcode'];
      124             }
      125             return [
      126                 'code' => 0,
      127                 'msg' => 'success',
      128                 'data' => $data,
      129             ];
      130         }
      131         /* 按照邮编查询 */
      132         if($q != null && is_numeric($q)){
      133             $where = ['like', 'postcode', $q];
      134             $count = post_code::find()->where($where)->count();
      135             $p = new Pagination(['totalCount'=>$count, 'pageSize'=>$pagesize]);
      136             $result = post_code::find()->select('place, postcode')->where($where)->offset(($page-1)*$pagesize)->limit($p->limit)->asArray()->all();  //防止用户输入类似0返回所有数据的情况
      137             if(!$result){
      138                 return [
      139                     'code' => 1,
      140                     'msg' => '没有数据',
      141                 ];
      142             }
      143             $data = [];
      144             foreach($result as $k=>$v){
      145                 $data[$k]['province'] = explode(',', $v['place'])[0];
      146                 $data[$k]['city'] = explode(',', $v['place'])[1];
      147                 $data[$k]['area'] = explode(',', $v['place'])[2];
      148                 $data[$k]['postcode'] = $v['postcode'];
      149             }
      150             return [
      151                 'code' => 0,
      152                 'msg' => 'success',
      153                 'data' => $data,
      154             ];
      155         }
      156     }
      完整代码

      对数组进行模糊匹配可以使用正则preg_grep, 还可以使用strstr函数模拟实现

    6. 这里对数组分页参考了https://www.cnblogs.com/vip-deng-vip/p/8005434.html
  • 相关阅读:
    java操作Redis
    Redis安装和基本操作
    IDEA使用教程+JRebel破解
    java环境配置
    qtp10安装步骤(比较完整)
    c++第一章1.6
    软件测试第二章作业
    c++作业22题
    c++第二周阶段小测2
    oracle12c数据库第一周小测验
  • 原文地址:https://www.cnblogs.com/bneglect/p/11770138.html
Copyright © 2020-2023  润新知