FeatureService.php 12 KB

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