FeatureService.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298
  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. $column = Feature::type[$features[$str]["type"]];
  159. $logic = $features[$str]["logic"];
  160. $describe = $features[$str]["describe"];
  161. if (is_numeric($str) && isset($features[$str])){
  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. }
  177. $str = $column.$logic."'".$describe."'";
  178. }else $str = $column.' '.$logic.' '.$describe;
  179. }
  180. if ($str == "&"){
  181. if ($columnMapping) $str = " and ";
  182. else $str = " 并且 ";
  183. }
  184. if ($str == "|"){
  185. if ($columnMapping) $str = " or ";
  186. else $str = " 或 ";
  187. }
  188. }
  189. return implode("",$result[0]);
  190. }
  191. /**
  192. * 匹配特征
  193. * $vale : 特征简述 例: "1&2|(3|4)"
  194. * $columnMapping : 列映射 例:["商品名称"=>"commodity_name"]
  195. * $matchObject : 被匹配对象,必须存在列映射所指定字段 例:["commodity_name"=>"衣服"]
  196. * $isMultiMatching 是否开启多重匹配 开启将匹配对象视为二维数组深层寻找商品(入库需要)
  197. * bool true匹配成功 false匹配失败
  198. *
  199. * @param string $value
  200. * @param array $columnMapping
  201. * @param array $matchObject
  202. * @param bool $isMultiMatching
  203. * @return bool
  204. */
  205. public function matchFeature($value, $columnMapping, $matchObject, $isMultiMatching = false) :bool
  206. {
  207. preg_match_all('/\d+|[\&\|\(\)]/',$value,$result);
  208. if (implode("",$result[0]) != $value)return false;
  209. $features = app(CacheService::class)->getOrExecute($value,function ()use($value){
  210. preg_match_all('/\d+/',$value,$ids);
  211. if ($ids[0])$fs = Feature::query()->whereIn("id",$ids[0])->get();
  212. else return false;
  213. $features = [];
  214. foreach ($fs as $f){
  215. $features[$f->id] = $f;
  216. }
  217. return $features;
  218. });
  219. foreach ($result[0] as &$str) {
  220. if (is_numeric($str) && isset($features[$str])) {
  221. $column = array_flip(Feature::type)[$features[$str]["type"]];
  222. $logic = $features[$str]["logic"];
  223. $describe = $features[$str]["describe"];
  224. if ($column == '商品名称' && $isMultiMatching){
  225. $packageColumn = $columnMapping["packages"] ?? "packages";
  226. $packages = $matchObject[$packageColumn] ?? [];
  227. $str = $this->multiMatching($packages,$logic,$describe,$columnMapping[$column] ?? '');
  228. continue;
  229. }
  230. $value = isset($columnMapping[$column]) ? ($matchObject[$columnMapping[$column]] ?? '') : '';
  231. switch ($logic) {
  232. case "包含":
  233. $str = mb_strpos($value,$describe) === false ? 'false' : 'true';
  234. break;
  235. case "不包含":
  236. $str = mb_strpos($value,$describe) === false ? 'true' : 'false';
  237. break;
  238. case "等于":
  239. $str = $value == $describe ? 'true' : 'false';
  240. break;
  241. default:
  242. $str = 'false';
  243. }
  244. continue;
  245. }
  246. if ($str == "&") {
  247. $str = '&&';
  248. continue;
  249. }
  250. if ($str == "|") {
  251. $str = '||';
  252. }
  253. }
  254. $is = implode("",$result[0]);
  255. return eval("return $is;");
  256. }
  257. /**
  258. * 多重子项匹配
  259. *
  260. * @param array $packages
  261. * @param string $logic
  262. * @param string $describe
  263. * @param string $column
  264. * @return string //"true" || "false"
  265. */
  266. private function multiMatching($packages, $logic, $describe, $column):string
  267. {
  268. if(!$column)return 'false';
  269. foreach ($packages as $package){
  270. $value = $package[$column] ?? '';
  271. switch ($logic) {
  272. case "包含":
  273. if (mb_strpos($value,$describe)!==false)return 'true';
  274. break;
  275. case "不包含":
  276. if (mb_strpos($value,$describe) === false)return 'true';
  277. break;
  278. case "等于":
  279. if ($value == $describe)return 'true';
  280. break;
  281. }
  282. }
  283. return "false";
  284. }
  285. }