FeatureService.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297
  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"] = $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. $f = Feature::query()->firstOrCreate([
  126. "type"=>$feature["type"], //特征类型
  127. "logic"=>$feature["logic"], //特征逻辑
  128. "describe"=>$feature["describe"], //特征信息
  129. ]);
  130. $feature["id"] = $f->id;
  131. $map[$feature["id"]] = $f;
  132. if ($feature["calculation"] == '并且')$str .= '&';
  133. if ($feature["calculation"] == '或') $str .= '|';
  134. if ($feature["strategyGroupStartSign"])$str .= "(";
  135. $str .= $feature["id"];
  136. if ($feature["strategyGroupEndSign"])$str .= ")";
  137. }
  138. return ['feature'=>$str,"map"=>$map];
  139. }
  140. /**
  141. * 格式化简述特征为可阅读显示
  142. * $features : 特征对象集 重组为key-val数组数据,key为ID
  143. * $value : 简述特征 例: "1&2|(3&4)"
  144. * $columnMapping : 特征type属性字段映射 例:["商品名称"=>"commodity_name"],指定该参数会使特征格式化为SQL段
  145. * string : 例:"商品名称 包含 衣服 并且(订单类型 等于 创建订单 或者 承运商 不包含 顺丰)"
  146. *
  147. * @param array $features
  148. * @param string $value
  149. * @param array $columnMapping
  150. * @return string
  151. */
  152. public function formatFeature(array $features, $value, $columnMapping = null)
  153. {
  154. if (!$features)return $value;
  155. preg_match_all('/\d+|[\&\|\(\)]/',$value,$result);
  156. foreach ($result[0] as &$str){
  157. if (is_numeric($str) && isset($features[$str])){
  158. $column = $features[$str]["type"];
  159. $logic = $features[$str]["logic"];
  160. $describe = $features[$str]["describe"];
  161. if ($columnMapping){
  162. $column = $columnMapping[$column] ?? $column;
  163. switch ($logic){
  164. case "包含":
  165. $logic = " like ";
  166. $describe = "%".$describe."%";
  167. break;
  168. case "不包含":
  169. $logic = " not like ";
  170. $describe = "%".$describe."%";
  171. break;
  172. case "等于":
  173. $logic = " = ";
  174. break;
  175. }
  176. $str = $column.$logic."'".$describe."'";
  177. }else $str = $features[$str]["type"].' '.$features[$str]["logic"].' '.$features[$str]["describe"];
  178. }
  179. if ($str == "&"){
  180. if ($columnMapping) $str = " and ";
  181. else $str = " 并且 ";
  182. }
  183. if ($str == "|"){
  184. if ($columnMapping) $str = " or ";
  185. else $str = " 或 ";
  186. }
  187. }
  188. return implode("",$result[0]);
  189. }
  190. /**
  191. * 匹配特征
  192. * $vale : 特征简述 例: "1&2|(3|4)"
  193. * $columnMapping : 列映射 例:["商品名称"=>"commodity_name"]
  194. * $matchObject : 被匹配对象,必须存在列映射所指定字段 例:["commodity_name"=>"衣服"]
  195. * $isMultiMatching 是否开启多重匹配 开启将匹配对象视为二维数组深层寻找商品(入库需要)
  196. * bool true匹配成功 false匹配失败
  197. *
  198. * @param string $value
  199. * @param array $columnMapping
  200. * @param array $matchObject
  201. * @param bool $isMultiMatching
  202. * @return bool
  203. */
  204. public function matchFeature($value, $columnMapping, $matchObject, $isMultiMatching = false) :bool
  205. {
  206. preg_match_all('/\d+|[\&\|\(\)]/',$value,$result);
  207. if (implode("",$result[0]) != $value)return false;
  208. $features = app(CacheService::class)->getOrExecute($value,function ()use($value){
  209. preg_match_all('/\d+/',$value,$ids);
  210. if ($ids[0])$fs = Feature::query()->whereIn("id",$ids[0])->get();
  211. else return false;
  212. $features = [];
  213. foreach ($fs as $f){
  214. $features[$f->id] = $f;
  215. }
  216. return $features;
  217. });
  218. foreach ($result[0] as &$str) {
  219. if (is_numeric($str) && isset($features[$str])) {
  220. $column = $features[$str]["type"];
  221. $logic = $features[$str]["logic"];
  222. $describe = $features[$str]["describe"];
  223. if ($column == '商品名称' && $isMultiMatching){
  224. $packageColumn = $columnMapping["packages"] ?? "packages";
  225. $packages = $matchObject[$packageColumn] ?? [];
  226. $str = $this->multiMatching($packages,$logic,$describe,$columnMapping[$column] ?? '');
  227. continue;
  228. }
  229. $value = isset($columnMapping[$column]) ? ($matchObject[$columnMapping[$column]] ?? '') : '';
  230. switch ($logic) {
  231. case "包含":
  232. $str = mb_strpos($value,$describe) === false ? 'false' : 'true';
  233. break;
  234. case "不包含":
  235. $str = mb_strpos($value,$describe) === false ? 'true' : 'false';
  236. break;
  237. case "等于":
  238. $str = $value == $describe ? 'true' : 'false';
  239. break;
  240. default:
  241. $str = 'false';
  242. }
  243. continue;
  244. }
  245. if ($str == "&") {
  246. $str = '&&';
  247. continue;
  248. }
  249. if ($str == "|") {
  250. $str = '||';
  251. }
  252. }
  253. $is = implode("",$result[0]);
  254. return eval("return $is;");
  255. }
  256. /**
  257. * 多重子项匹配
  258. *
  259. * @param array $packages
  260. * @param string $logic
  261. * @param string $describe
  262. * @param string $column
  263. * @return string //"true" || "false"
  264. */
  265. private function multiMatching($packages, $logic, $describe, $column):string
  266. {
  267. if(!$column)return 'false';
  268. foreach ($packages as $package){
  269. $value = $package[$column] ?? '';
  270. switch ($logic) {
  271. case "包含":
  272. if (mb_strpos($value,$describe)!==false)return 'true';
  273. break;
  274. case "不包含":
  275. if (mb_strpos($value,$describe) === false)return 'true';
  276. break;
  277. case "等于":
  278. if ($value == $describe)return 'true';
  279. break;
  280. }
  281. }
  282. return "false";
  283. }
  284. }