FeatureService.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328
  1. <?php
  2. namespace App\Services;
  3. use App\Feature;
  4. use App\Traits\ServiceAppAop;
  5. class FeatureService
  6. {
  7. use ServiceAppAop;
  8. protected $modelClass=Feature::class;
  9. public function getMapArray()
  10. {
  11. $features = Feature::query()->get();
  12. $map = [];
  13. foreach ($features as $feature){
  14. $map[$feature->id] = ["type"=>$feature->type,"logic"=>$feature->logic,"describe"=>$feature->describe];
  15. }
  16. return $map;
  17. }
  18. /**
  19. * 将特征翻译为数组显示
  20. * $str : "1&2|(3&4)"
  21. * array:[
  22. * "strategyGroupStartSign" => false, //是否为策略组起点,这将在解析时解析为 (
  23. "calculation" => "", //运算规则,目前仅有 &,| 翻译后填入
  24. "type"=>"", //特征类型
  25. "id"=>"", //特征ID
  26. "logic"=>"", //特征逻辑
  27. "describe"=>"", //特征信息
  28. "strategyGroupEndSign" => false, //是否为策略组终点,这将在解析时解析为 )
  29. * ]
  30. *
  31. * @param string $str
  32. * @return array
  33. */
  34. public function translationFeature($str)
  35. {
  36. if (!$str)return null;
  37. $result = [];
  38. preg_match_all('/\d+|[\&\|\(\)]/',$str,$result); //初次匹配以数字&|()为分隔符生成数组
  39. $sign = 0; //为第二次切割做起点标记
  40. $model = [];//第二次切割数组
  41. $ids = [];//记录出现的特征ID,统一查询
  42. foreach ($result[0] as $index => &$item){
  43. if (is_numeric($item)){
  44. $model[] = array_slice($result[0],$sign,($index-$sign)+1);
  45. $sign = $index+1;
  46. $ids[] = $item;
  47. continue;
  48. }
  49. if ($index == count($result[0])-1 && $item == ')'){
  50. $model[] = [")"];
  51. }
  52. }//以数字为标准切割策略组
  53. $features = Feature::query()->find($ids);//查询出现的特征
  54. $featureMap = [];
  55. foreach ($features as $index => $feature){
  56. $featureMap[$feature->id] = $index;
  57. }//为查询的特征重组为key-val形式数组留做引用
  58. foreach ($model as $index => &$m){
  59. $arr = [
  60. "strategyGroupStartSign" => false,//是否为策略组起点,这将在解析时解析为 (
  61. "calculation" => "",//运算规则,目前仅有 &,| 翻译后填入
  62. "type"=>"", //特征类型
  63. "id"=>"", //特征ID
  64. "logic"=>"", //特征逻辑
  65. "describe"=>"", //特征信息
  66. "strategyGroupEndSign" => false,//是否为策略组终点,这将在解析时解析为 )
  67. ];//最终对象组模型,策略组将几组特征组合引用
  68. foreach ($m as $string){
  69. if (is_numeric($string)){//填入特征信息
  70. if (isset($featureMap[$string])){
  71. $arr["type"] = Feature::TYPE[$features[$featureMap[$string]]->type];
  72. $arr["id"] = $features[$featureMap[$string]]->id;
  73. $arr["logic"] = $features[$featureMap[$string]]->logic;
  74. $arr["describe"] = $features[$featureMap[$string]]->describe;
  75. }
  76. continue;
  77. }
  78. switch ($string){//特殊字符的翻译
  79. case "(":
  80. $arr["strategyGroupStartSign"] = true;
  81. break;
  82. case ")":
  83. $model[$index-1]["strategyGroupEndSign"] = true;
  84. break;
  85. case "&":
  86. $arr["calculation"] = "并且";
  87. break;
  88. case "|":
  89. $arr["calculation"] = "或";
  90. break;
  91. }
  92. }
  93. if (!$arr["id"]){
  94. unset($model[$index]);
  95. continue;
  96. }
  97. $m = $arr;//变更当前指针为翻译结果
  98. }
  99. return $model;
  100. }
  101. /**
  102. * 与translationFeature相反,将一组与translationFeature参数解析为简述
  103. * $features:[
  104. * "strategyGroupStartSign" => false, //是否为策略组起点,这将在解析时解析为 (
  105. "calculation" => "", //运算规则,目前仅有 &,| 翻译后填入
  106. "type"=>"", //特征类型
  107. "id"=>"", //特征ID
  108. "logic"=>"", //特征逻辑
  109. "describe"=>"", //特征信息
  110. "strategyGroupEndSign" => false, //是否为策略组终点,这将在解析时解析为 )
  111. * ]
  112. * array:[
  113. * "feature" //简述结果
  114. * "map" //简述中出现的Map特征对象重组为key-val数组
  115. * ]
  116. * @param array $features
  117. * @return array
  118. */
  119. public function analysisFeature($features)
  120. {
  121. $str = "";
  122. $map = [];
  123. foreach ($features as &$feature){
  124. if (!$feature["type"] || !$feature["logic"] || !$feature["describe"])continue;
  125. $typeMap = array_flip(Feature::TYPE);
  126. $f = Feature::query()->firstOrCreate([
  127. "type"=>$typeMap[$feature["type"]], //特征类型
  128. "logic"=>$feature["logic"], //特征逻辑
  129. "describe"=>$feature["describe"], //特征信息
  130. ]);
  131. $feature["id"] = $f->id;
  132. $map[$feature["id"]] = $f;
  133. if ($feature["calculation"] == '并且')$str .= '&';
  134. if ($feature["calculation"] == '或') $str .= '|';
  135. if ($feature["strategyGroupStartSign"])$str .= "(";
  136. $str .= $feature["id"];
  137. if ($feature["strategyGroupEndSign"])$str .= ")";
  138. }
  139. return ['feature'=>$str,"map"=>$map];
  140. }
  141. /**
  142. * 格式化简述特征为可阅读显示
  143. * $features : 特征对象集 重组为key-val数组数据,key为ID
  144. * $value : 简述特征 例: "1&2|(3&4)"
  145. * $columnMapping : 特征type属性字段映射 例:["商品名称"=>"commodity_name"],指定该参数会使特征格式化为SQL段
  146. * string : 例:"商品名称 包含 衣服 并且(订单类型 等于 创建订单 或者 承运商 不包含 顺丰)"
  147. *
  148. * @param array $features
  149. * @param string $value
  150. * @param array $columnMapping
  151. * @return string
  152. */
  153. public function formatFeature(array $features, $value, $columnMapping = null)
  154. {
  155. if (!$features)return $value;
  156. preg_match_all('/\d+|[\&\|\(\)]/',$value,$result);
  157. foreach ($result[0] as &$str){
  158. if (is_numeric($str) && isset($features[$str])){
  159. $column = Feature::TYPE[$features[$str]["type"]];
  160. $logic = $features[$str]["logic"];
  161. $describe = $features[$str]["describe"];
  162. if ($columnMapping){
  163. $column = $columnMapping[$column] ?? $column;
  164. switch ($logic){
  165. case "包含":
  166. $logic = " like ";
  167. $describe = "%".$describe."%";
  168. break;
  169. case "不包含":
  170. $logic = " not like ";
  171. $describe = "%".$describe."%";
  172. break;
  173. case "等于":
  174. $logic = " = ";
  175. break;
  176. case "小于":
  177. $logic = " < ";
  178. break;
  179. case "大于":
  180. $logic = " > ";
  181. break;
  182. case "小于等于":
  183. $logic = " <= ";
  184. break;
  185. case "大于等于":
  186. $logic = " >= ";
  187. break;
  188. }
  189. $str = $column.$logic."'".$describe."'";
  190. }else $str = $column.' '.$logic.' '.$describe;
  191. }
  192. if ($str == "&"){
  193. if ($columnMapping) $str = " and ";
  194. else $str = " 并且 ";
  195. }
  196. if ($str == "|"){
  197. if ($columnMapping) $str = " or ";
  198. else $str = " 或 ";
  199. }
  200. }
  201. return implode("",$result[0]);
  202. }
  203. /**
  204. * 匹配特征 true匹配成功 false匹配失败
  205. *
  206. * @param string $value
  207. * @param array $columnMapping
  208. * @param array $matchObject
  209. *
  210. * @return bool
  211. */
  212. public function matchFeature($value, $columnMapping, $matchObject) :bool
  213. {
  214. if (!$value)return true;
  215. preg_match_all('/\d+|[\&\|\(\)]/',$value,$result);
  216. if (implode("",$result[0]) != $value)return false;
  217. foreach ($result[0] as &$str) {
  218. switch ($str){
  219. case "&":
  220. $str = "&&";
  221. break;
  222. case "|":
  223. $str = "||";
  224. break;
  225. default:
  226. $str = $this->match($str, $value, $columnMapping, $matchObject);
  227. }
  228. }
  229. $is = implode("",$result[0]);
  230. return eval("return $is;");
  231. }
  232. /**
  233. * 匹配实例
  234. *
  235. * @param string|integer $id
  236. * @param string $value
  237. * @param array $columnMapping
  238. * @param array $matchObject
  239. *
  240. * @return string
  241. */
  242. private function match($id, $value, $columnMapping, $matchObject):string
  243. {
  244. if (!is_numeric($id))return $id; //非特征返回本身
  245. $features = app(CacheService::class)->getOrExecute("feature:".$value,function ()use($value){
  246. preg_match_all('/\d+/',$value,$ids);
  247. if ($ids[0])$fs = Feature::query()->whereIn("id",$ids[0])->get();
  248. else return false;
  249. $features = [];
  250. foreach ($fs as $f)$features[$f->id] = $f;
  251. return $features;
  252. });
  253. if (!isset($features[$id]))return 'false'; //特征不存在返回 false
  254. if (!($columnMapping[$features[$id]["type"]] ?? false))return 'false';
  255. return $this->getMatchBool($features[$id],$matchObject,explode(".",$columnMapping[$features[$id]["type"]]));
  256. }
  257. /**
  258. * 获取匹配结果
  259. *
  260. * @param array $feature
  261. * @param array|\stdClass $matchObject
  262. * @param array $column
  263. *
  264. * @return string
  265. */
  266. private function getMatchBool($feature, $matchObject, $column)
  267. {
  268. /*递归: 1.列数组为空 对象不存在列 返回false
  269. 2.为数组时,递归数组内对象,任意一个符合返回true,全部不符合返回false
  270. 3.为正常值时,进入核心匹配,必然返回一个值
  271. 4.为对象时,向下递归,splice列数组
  272. */
  273. if (!$column)return 'false';
  274. if (!isset($matchObject[$column[0]]))return 'false';
  275. if (is_array($matchObject)){
  276. foreach ($matchObject as $object){
  277. if ($this->getMatchBool($feature,$object,$column) === 'true')return 'true';
  278. }
  279. return 'false';
  280. }
  281. $value = $matchObject[$column[0]];
  282. if (count($column)==1 && (is_string($value) || is_numeric($value) || is_null($value))){
  283. $logic = $feature["logic"];
  284. $describe = $feature["describe"];
  285. switch ($logic) {
  286. case "包含":
  287. if (mb_strpos($value,$describe) !== false)return 'true';
  288. break;
  289. case "不包含":
  290. if (mb_strpos($value,$describe) === false)return 'true';
  291. break;
  292. case "等于":
  293. if ($value == $describe)return 'true';
  294. break;
  295. case "小于":
  296. if ($value < $describe)return 'true';
  297. break;
  298. case "大于":
  299. if ($value > $describe)return 'true';
  300. break;
  301. case "小于等于":
  302. if ($value <= $describe)return 'true';
  303. break;
  304. case "大于等于":
  305. if ($value >= $describe)return 'true';
  306. break;
  307. }
  308. return 'false';
  309. };
  310. array_splice($column,0,1);
  311. return $this->getMatchBool($feature, $value, $column);
  312. }
  313. }