get(); $map = []; foreach ($features as $feature){ $map[$feature->id] = ["type"=>$feature->type,"logic"=>$feature->logic,"describe"=>$feature->describe]; } return $map; } /** * 将特征翻译为数组显示 * $str : "1&2|(3&4)" * array:[ * "strategyGroupStartSign" => false, //是否为策略组起点,这将在解析时解析为 ( "calculation" => "", //运算规则,目前仅有 &,| 翻译后填入 "type"=>"", //特征类型 "id"=>"", //特征ID "logic"=>"", //特征逻辑 "describe"=>"", //特征信息 "strategyGroupEndSign" => false, //是否为策略组终点,这将在解析时解析为 ) * ] * * @param string $str * @return array */ public function translationFeature($str) { if (!$str)return null; $result = []; preg_match_all('/\d+|[\&\|\(\)]/',$str,$result); //初次匹配以数字&|()为分隔符生成数组 $sign = 0; //为第二次切割做起点标记 $model = [];//第二次切割数组 $ids = [];//记录出现的特征ID,统一查询 foreach ($result[0] as $index => &$item){ if (is_numeric($item)){ $model[] = array_slice($result[0],$sign,($index-$sign)+1); $sign = $index+1; $ids[] = $item; continue; } if ($index == count($result[0])-1 && $item == ')'){ $model[] = [")"]; } }//以数字为标准切割策略组 $features = Feature::query()->find($ids);//查询出现的特征 $featureMap = []; foreach ($features as $index => $feature){ $featureMap[$feature->id] = $index; }//为查询的特征重组为key-val形式数组留做引用 foreach ($model as $index => &$m){ $arr = [ "strategyGroupStartSign" => false,//是否为策略组起点,这将在解析时解析为 ( "calculation" => "",//运算规则,目前仅有 &,| 翻译后填入 "type"=>"", //特征类型 "id"=>"", //特征ID "logic"=>"", //特征逻辑 "describe"=>"", //特征信息 "strategyGroupEndSign" => false,//是否为策略组终点,这将在解析时解析为 ) ];//最终对象组模型,策略组将几组特征组合引用 foreach ($m as $string){ if (is_numeric($string)){//填入特征信息 if (isset($featureMap[$string])){ $arr["type"] = Feature::TYPE[$features[$featureMap[$string]]->type]; $arr["id"] = $features[$featureMap[$string]]->id; $arr["logic"] = $features[$featureMap[$string]]->logic; $arr["describe"] = $features[$featureMap[$string]]->describe; } continue; } switch ($string){//特殊字符的翻译 case "(": $arr["strategyGroupStartSign"] = true; break; case ")": $model[$index-1]["strategyGroupEndSign"] = true; break; case "&": $arr["calculation"] = "并且"; break; case "|": $arr["calculation"] = "或"; break; } } if (!$arr["id"]){ unset($model[$index]); continue; } $m = $arr;//变更当前指针为翻译结果 } return $model; } /** * 与translationFeature相反,将一组与translationFeature参数解析为简述 * $features:[ * "strategyGroupStartSign" => false, //是否为策略组起点,这将在解析时解析为 ( "calculation" => "", //运算规则,目前仅有 &,| 翻译后填入 "type"=>"", //特征类型 "id"=>"", //特征ID "logic"=>"", //特征逻辑 "describe"=>"", //特征信息 "strategyGroupEndSign" => false, //是否为策略组终点,这将在解析时解析为 ) * ] * array:[ * "feature" //简述结果 * "map" //简述中出现的Map特征对象重组为key-val数组 * ] * @param array $features * @return array */ public function analysisFeature($features) { $str = ""; $map = []; foreach ($features as &$feature){ if (!$feature["type"] || !$feature["logic"] || !$feature["describe"])continue; $typeMap = array_flip(Feature::TYPE); $f = Feature::query()->firstOrCreate([ "type"=>$typeMap[$feature["type"]], //特征类型 "logic"=>$feature["logic"], //特征逻辑 "describe"=>$feature["describe"], //特征信息 ]); $feature["id"] = $f->id; $map[$feature["id"]] = $f; if ($feature["calculation"] == '并且')$str .= '&'; if ($feature["calculation"] == '或') $str .= '|'; if ($feature["strategyGroupStartSign"])$str .= "("; $str .= $feature["id"]; if ($feature["strategyGroupEndSign"])$str .= ")"; } return ['feature'=>$str,"map"=>$map]; } /** * 格式化简述特征为可阅读显示 * $features : 特征对象集 重组为key-val数组数据,key为ID * $value : 简述特征 例: "1&2|(3&4)" * $columnMapping : 特征type属性字段映射 例:["商品名称"=>"commodity_name"],指定该参数会使特征格式化为SQL段 * string : 例:"商品名称 包含 衣服 并且(订单类型 等于 创建订单 或者 承运商 不包含 顺丰)" * * @param array $features * @param string $value * @param array $columnMapping * @return string */ public function formatFeature(array $features, $value, $columnMapping = null) { if (!$features)return $value; preg_match_all('/\d+|[\&\|\(\)]/',$value,$result); foreach ($result[0] as &$str){ if (is_numeric($str) && isset($features[$str])){ $column = Feature::TYPE[$features[$str]["type"]]; $logic = $features[$str]["logic"]; $describe = $features[$str]["describe"]; if ($columnMapping){ $column = $columnMapping[$column] ?? $column; switch ($logic){ case "包含": $logic = " like "; $describe = "%".$describe."%"; break; case "不包含": $logic = " not like "; $describe = "%".$describe."%"; break; case "等于": $logic = " = "; break; case "小于": $logic = " < "; break; case "大于": $logic = " > "; break; case "小于等于": $logic = " <= "; break; case "大于等于": $logic = " >= "; break; } $str = $column.$logic."'".$describe."'"; }else $str = $column.' '.$logic.' '.$describe; } if ($str == "&"){ if ($columnMapping) $str = " and "; else $str = " 并且 "; } if ($str == "|"){ if ($columnMapping) $str = " or "; else $str = " 或 "; } } return implode("",$result[0]); } /** * 匹配特征 true匹配成功 false匹配失败 * * @param string $value * @param array $columnMapping * @param array $matchObject * * @return bool */ public function matchFeature($value, $columnMapping, $matchObject) :bool { if (!$value)return true; preg_match_all('/\d+|[\&\|\(\)]/',$value,$result); if (implode("",$result[0]) != $value)return false; foreach ($result[0] as &$str) { switch ($str){ case "&": $str = "&&"; break; case "|": $str = "||"; break; default: $str = $this->match($str, $value, $columnMapping, $matchObject); } } $is = implode("",$result[0]); return eval("return $is;"); } /** * 匹配实例 * * @param string|integer $id * @param string $value * @param array $columnMapping * @param array $matchObject * * @return string */ private function match($id, $value, $columnMapping, $matchObject):string { if (!is_numeric($id))return $id; //非特征返回本身 $features = app(CacheService::class)->getOrExecute("feature:".$value,function ()use($value){ preg_match_all('/\d+/',$value,$ids); if ($ids[0])$fs = Feature::query()->whereIn("id",$ids[0])->get(); else return false; $features = []; foreach ($fs as $f)$features[$f->id] = $f; return $features; }); if (!isset($features[$id]))return 'false'; //特征不存在返回 false if (!($columnMapping[$features[$id]["type"]] ?? false))return 'false'; return $this->getMatchBool($features[$id],$matchObject,explode(".",$columnMapping[$features[$id]["type"]])); } /** * 获取匹配结果 * * @param array $feature * @param array|\stdClass $matchObject * @param array $column * * @return string */ private function getMatchBool($feature, $matchObject, $column) { /*递归: 1.列数组为空 对象不存在列 返回false 2.为数组时,递归数组内对象,任意一个符合返回true,全部不符合返回false 3.为正常值时,进入核心匹配,必然返回一个值 4.为对象时,向下递归,splice列数组 */ if (!$column)return 'false'; if (!isset($matchObject[$column[0]]))return 'false'; if (is_array($matchObject)){ foreach ($matchObject as $object){ if ($this->getMatchBool($feature,$object,$column) === 'true')return 'true'; } return 'false'; } $value = $matchObject[$column[0]]; if (count($column)==1 && (is_string($value) || is_numeric($value) || is_null($value))){ $logic = $feature["logic"]; $describe = $feature["describe"]; switch ($logic) { case "包含": if (mb_strpos($value,$describe) !== false)return 'true'; break; case "不包含": if (mb_strpos($value,$describe) === false)return 'true'; break; case "等于": if ($value == $describe)return 'true'; break; case "小于": if ($value < $describe)return 'true'; break; case "大于": if ($value > $describe)return 'true'; break; case "小于等于": if ($value <= $describe)return 'true'; break; case "大于等于": if ($value >= $describe)return 'true'; break; } return 'false'; }; array_splice($column,0,1); return $this->getMatchBool($feature, $value, $column); } }