FeatureService.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336
  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. * 匹配特征
  205. * $vale : 特征简述 例: "1&2|(3|4)"
  206. * $columnMapping : 列映射 例:["商品名称"=>"commodity_name"]
  207. * $matchObject : 被匹配对象,必须存在列映射所指定字段 例:["commodity_name"=>"衣服"]
  208. * $isMultiMatching 是否开启多重匹配 开启将匹配对象视为二维数组深层寻找商品(入库需要)
  209. * bool true匹配成功 false匹配失败
  210. *
  211. * @param string $value
  212. * @param array $columnMapping
  213. * @param array $matchObject
  214. * @param bool $isMultiMatching
  215. * @return bool
  216. */
  217. public function matchFeature($value, $columnMapping, $matchObject, $isMultiMatching = false) :bool
  218. {
  219. if (!$value)return true;
  220. preg_match_all('/\d+|[\&\|\(\)]/',$value,$result);
  221. if (implode("",$result[0]) != $value)return false;
  222. $features = app(CacheService::class)->getOrExecute($value,function ()use($value){
  223. preg_match_all('/\d+/',$value,$ids);
  224. if ($ids[0])$fs = Feature::query()->whereIn("id",$ids[0])->get();
  225. else return false;
  226. $features = [];
  227. foreach ($fs as $f){
  228. $features[$f->id] = $f;
  229. }
  230. return $features;
  231. });
  232. foreach ($result[0] as &$str) {
  233. if (is_numeric($str) && isset($features[$str])) {
  234. $column = Feature::type[$features[$str]["type"]];
  235. $logic = $features[$str]["logic"];
  236. $describe = $features[$str]["describe"];
  237. if (($column == '商品名称' || $column == '商品备注' || $column == '长') && $isMultiMatching){
  238. $packageColumn = $columnMapping["packages"] ?? "packages";
  239. $packages = $matchObject[$packageColumn] ?? [];
  240. $str = $this->multiMatching($packages,$logic,$describe,$columnMapping[$column] ?? '');
  241. continue;
  242. }
  243. $value = isset($columnMapping[$column]) ? ($matchObject[$columnMapping[$column]] ?? '') : '';
  244. switch ($logic) {
  245. case "包含":
  246. $str = mb_strpos($value,$describe) === false ? 'false' : 'true';
  247. break;
  248. case "不包含":
  249. $str = mb_strpos($value,$describe) === false ? 'true' : 'false';
  250. break;
  251. case "等于":
  252. $str = $value == $describe ? 'true' : 'false';
  253. break;
  254. case "小于":
  255. $str = $value < $describe ? 'true' : 'false';
  256. break;
  257. case "大于":
  258. $str = $value > $describe ? 'true' : 'false';
  259. break;
  260. case "小于等于":
  261. $str = $value <= $describe ? 'true' : 'false';
  262. break;
  263. case "大于等于":
  264. $str = $value >= $describe ? 'true' : 'false';
  265. break;
  266. default:
  267. $str = 'false';
  268. }
  269. continue;
  270. }
  271. if ($str == "&") {
  272. $str = '&&';
  273. continue;
  274. }
  275. if ($str == "|") {
  276. $str = '||';
  277. }
  278. }
  279. $is = implode("",$result[0]);
  280. return eval("return $is;");
  281. }
  282. /**
  283. * 多重子项匹配
  284. *
  285. * @param array $packages
  286. * @param string $logic
  287. * @param string $describe
  288. * @param string $column
  289. * @return string //"true" || "false"
  290. */
  291. private function multiMatching($packages, $logic, $describe, $column):string
  292. {
  293. if(!$column)return 'false';
  294. foreach ($packages as $package){
  295. $value = $package[$column] ?? '';
  296. switch ($logic) {
  297. case "包含":
  298. if (mb_strpos($value,$describe)!==false)return 'true';
  299. break;
  300. case "不包含":
  301. if (mb_strpos($value,$describe) === false)return 'true';
  302. break;
  303. case "等于":
  304. if ($value == $describe)return 'true';
  305. break;
  306. case "小于":
  307. if ($value < $describe)return 'true';
  308. break;
  309. case "大于":
  310. if ($value > $describe)return 'true';
  311. break;
  312. case "小于等于":
  313. if ($value <= $describe)return 'true';
  314. break;
  315. case "大于等于":
  316. if ($value >= $describe)return 'true';
  317. break;
  318. }
  319. }
  320. return "false";
  321. }
  322. }