mTimesConfig = []; $this->mInterceptConfig = []; $this->mGrossRatios = []; $this->mDetailRatios = []; $this->mMixedPrices = []; } public function load() { $this->load_retry(); $this->load_intercept(); } private function load_retry() { $isDay = functional::isDay(); $mch_configs = function ($isDay) { $result = []; $i = 0; while (true) { $start = $i * 100; $items = Model()->table('merchant')->where(['mchid' => ['gt', 0], 'merchant_state' => 1])->field('mchid,retry_times_cfg')->order('mchid asc')->limit("{$start},100")->select(); if(empty($items)) { break; } $i++; foreach ($items as $item) { $mchid = intval($item['mchid']); if($mchid <= 0) continue; $retry_times_cfg = unserialize($item['retry_times_cfg']); if(empty($retry_times_cfg)) continue; $qualities = &$retry_times_cfg['qualities']; foreach ($qualities as $quality => $cfg) { if ($isDay) { $qualities[$quality]['secs'] = $cfg['day_secs']; } else { $qualities[$quality]['secs'] = $cfg['night_secs']; } } $result[$mchid] = $retry_times_cfg; } } return $result; }; $this->mTimesConfig = $mch_configs($isDay); } private function load_intercept() { $mch_configs = function () { $result = []; $i = 0; while (true) { $start = $i * 100; $items = Model()->table('merchant')->where(['mchid' => ['gt', 0], 'merchant_state' => 1])->field('mchid,intercept_cfg')->order('mchid asc')->limit("{$start},100")->select(); if(empty($items)) { break; } $i++; foreach ($items as $item) { $mchid = intval($item['mchid']); if($mchid <= 0) continue; $cfg = unserialize($item['intercept_cfg']); if(empty($cfg)) continue; if(!empty($cfg['segment'])) { $segment = $cfg['segment']; $sitems = explode(',',$segment); $tmp = []; foreach ($sitems as $sitem) { $sitem = trim($sitem); if(!empty($sitem)) { $tmp[] = $sitem; } } if(!empty($tmp)) { $cfg['segment'] = implode('|',$tmp); } else { $cfg['segment'] = ''; } } $result[$mchid] = $cfg; } } return $result; }; $this->mInterceptConfig = $mch_configs(); } public function update($gross_ratios, $detail_ratios) { if (!empty($gross_ratios)) { $this->mGrossRatios = $gross_ratios; } if (!empty($detail_ratios)) { $this->mDetailRatios = $detail_ratios; } $this->mMchQTS = []; foreach ($detail_ratios as $key=>$val) { [$mchid,$quality,$card_type,$spec] = explode('-',$key); $mchid = intval($mchid); $quality = intval($quality); $card_type = intval($card_type); $spec = intval($spec); $this->mMchQTS[$mchid][] = [$quality,$card_type,$spec]; } } public function setMixedPrice($mchid, $mixed_quality, $card_type, $spec, $prices) { $key = "{$card_type}-{$spec}"; $this->mMixedPrices[$mchid][$mixed_quality][$key] = $prices; } //获取售价和进价 private function getPrice($mchid, $mixed_quality, $card_type, $spec) { $key = "{$card_type}-{$spec}"; if (array_key_exists($mchid, $this->mMixedPrices)) { $prices = $this->mMixedPrices[$mchid]; if (array_key_exists($mixed_quality, $prices)) { $qprices = $prices[$mixed_quality]; if (array_key_exists($key, $qprices)) { $price = $qprices[$key]; return $price; } } } return false; } public function total($mchid,$qualities) { if (array_key_exists($mchid, $this->mTimesConfig)) { $items = $this->mTimesConfig[$mchid]['qualities']; $times = 0; $secs = 0; foreach ($items as $quality => $val) { if (!in_array($quality,$qualities,true)) { continue; } $times += $val['times'] ?? 1; $secs += $val['secs'] ?? 180; } return [true, $times, $secs]; } else { return [false, 0, 0]; } } public function times($mchid, $quality) { if (array_key_exists($mchid, $this->mTimesConfig)) { $items = $this->mTimesConfig[$mchid]['qualities'] ?? []; if (array_key_exists($quality, $items)) { return $items[$quality]['times']; } } return false; } public function seconds($mchid, $quality) { if (array_key_exists($mchid, $this->mTimesConfig)) { $items = $this->mTimesConfig[$mchid]['qualities'] ?? []; if (array_key_exists($quality, $items)) { return $items[$quality]['secs']; } } return false; } public function exist($mchid) { if (array_key_exists($mchid, $this->mTimesConfig)) { return true; } else { return false; } } private function lower_ratio($mchid) { $lower_ratio = $this->mTimesConfig[$mchid]['lower_ratio'] ?? []; if (empty($lower_ratio)) { return [0.0, 3600]; } else { return [$lower_ratio['ratio'], $lower_ratio['period']]; } } private function profit_ratio($mchid) { $profit_ratio = $this->mTimesConfig[$mchid]['profit_ratio'] ?? 0.0; return $profit_ratio; } private function gross_ratio($mchid, $period) { $mratios = $this->mGrossRatios; if (array_key_exists($mchid, $mratios)) { $mratios = $mratios[$mchid]; if (array_key_exists($period, $mratios)) { return $mratios[$period]; } } return [0, 0, 1.0]; } private function detail_ratio($mchid, $quality, $card_type, $spec, $period) { $key = "{$mchid}-{$quality}-{$card_type}-{$spec}"; if(array_key_exists($key,$this->mDetailRatios)) { $ratios = $this->mDetailRatios[$key]; if(array_key_exists($period,$ratios)) { return $ratios[$period]; } } return [0, 0, 1.0]; } private function detail_ratios($mchid,$qualities,$card_type, $spec,$period) { $result = []; foreach ($qualities as $quality) { $ret = $this->detail_ratio($mchid,$quality,$card_type,$spec,$period); $result[$quality] = $ret; } return $result; } private function getMerchantSuppleType($mchid) { return $this->mTimesConfig[$mchid]['profit_formula'] ?? 'qts'; } //return true 表示当前质量满足条件。 public function ratio_match($mchid, $org_quality, $cur_quality, $card_type, $spec, $qualities) { if(count($qualities) <= 1) { return true; } $header = __METHOD__ . " mchid={$mchid} card_type={$card_type} spec={$spec}"; [$ratio, $period] = $this->lower_ratio($mchid); [$_succ, $_fail, $gross_ratio] = $this->gross_ratio($mchid, $period); Log::record("{$header} gross_ratio = {$gross_ratio},lower_ratio={$ratio}",Log::DEBUG); if($gross_ratio > $ratio) return true; if (!PolicyUtil::mixed_quality($org_quality)) { return false; } $type = $this->getMerchantSuppleType($mchid); Log::record("type = {$type}",Log::DEBUG); if($type === 'all') { return $this->all_checker($mchid, $org_quality, $spec, $qualities, $period, $header, $ratio); } elseif($type === 'qt') { return $this->qts_checker($mchid, $org_quality,$cur_quality, $card_type, $spec, $qualities,$period,$header,$ratio); } else { return $this->qts_checker($mchid, $org_quality,$cur_quality, $card_type, $spec, $qualities,$period,$header,$ratio); } } private function all_checker($mchid, $org_quality, $spec, $qualities, $period, $header, $low_ratio) { $qtses = $this->mMchQTS[$mchid]; $profit_judger = function ($mchid, $qtses, $org_quality, $period, $profit_ratio, $header) { $profit = 0; $amount = 0; foreach ($qtses as $qts) { [$quality,$card_type,$spec] = $qts; if($quality == $org_quality) { continue; } // Log::record("quality={$quality},card_type ={$card_type},spec={$spec}",Log::DEBUG); $prices = $this->getPrice($mchid, $org_quality, $card_type, $spec); $out_price = $prices[$org_quality]; $in_price = $prices[$quality] ?? false; if ($in_price === false || $in_price <= 0) { continue; } [$succ, $fail, $ratio] = $this->detail_ratio($mchid, $quality,$card_type, $spec, $period); $profit += $succ * ($out_price - $in_price); $amount += $succ * $spec; // Log::record("out_price = {$out_price},in_price={$in_price} profit={$profit} amount={$amount}",Log::DEBUG); } $cur_pratio = round(($profit + 0.00001) / ($amount + 0.00001),4); $profit_ratio = round($profit_ratio,4); Log::record("{$header} all_checker cur_pratio = {$cur_pratio},profit_ratio={$profit_ratio}",Log::DEBUG); return $cur_pratio > $profit_ratio; }; $spec_max = function ($mchid, $qtsex, $org_quality, $qualities, $low_ratio, $period) { $ratio_calc = function ($mchid, $specs, $spec_qt, $max_spec, $org_quality, $qualities, $period) { $all_succ = 0; $all_fail = 0; foreach ($specs as $spec) { $qts = $spec_qt[$spec]; if($max_spec >= $spec) { $fHighRatio = true; //此时采用面额中最高成功率计算 } else { $fHighRatio = false; } $card_types = []; foreach ($qts as $qt) { [$quality,$card_type] = $qt; if($quality === $org_quality) { continue; } $card_types[] = $card_type; } $detail_ratios = $this->detail_ratios($mchid, $qualities,$card_type, $spec, $period); if($fHighRatio) { $all = 0; $ratios = []; foreach ($detail_ratios as $quality => $sfr) { [$succ,$fail,$ratio] = $sfr; Log::record("high succ={$succ},fail={$fail},ratio = {$ratio}",Log::DEBUG); $ratios[] = $ratio; $all += $succ; $all += $fail; } $max_ratios = max($ratios); $succs = $max_ratios * $all; $fails = $all - $succs; $all_succ += $succs; $all_fail += $fails; } else { foreach ($detail_ratios as $quality => $sfr) { [$succ,$fail,$ratio] = $sfr; Log::record("normal succ={$succ},fail={$fail},ratio = {$ratio}",Log::DEBUG); $all_succ += $succ; $all_fail += $fail; } } } $ratio = ($all_succ + 0.00001) / ($all_succ + $all_fail + 0.00001); return round($ratio,4); }; //////////////////////////////////////////////////////////////////////////////////////////////////////////// $specs = []; $spec_qt = []; foreach ($qtsex as $qts) { [$quality, $card_type, $spec] = $qts; $specs[] = $spec; $spec_qt[$spec][] = [$quality,$card_type]; } $specs = array_unique($specs); sort($specs); for ($i = 0; $i < count($specs); $i++) { $max_spec = $specs[$i]; $ratio = $ratio_calc($mchid, $specs, $spec_qt, $max_spec, $org_quality, $qualities, $period); Log::record("spec={$max_spec},ratio={$ratio},low_ratio={$low_ratio}",Log::DEBUG); if($ratio >= $low_ratio) { break; } } return $max_spec; }; $profit_ratio = $this->profit_ratio($mchid); $can_next = $profit_judger($mchid, $qtses, $org_quality, $period, $profit_ratio, $header); if($can_next) { $max_spec = $spec_max($mchid,$qtses, $org_quality, $qualities, $low_ratio,$period); Log::record("mchid={$mchid} max_spec={$max_spec}",Log::DEBUG); if($max_spec >= $spec) { return false; } else { return true; } } else { return true; } } private function qts_checker($mchid, $org_quality,$cur_quality, $card_type, $spec, $qualities,$period,$header,$ratio) { $ratio_calc = function ($mchid, $card_type, $spec, $period, $org_quality, $qualities) { $succs = 0; $fails = 0; foreach ($qualities as $quality) { if ($quality === $org_quality) continue; [$_succ, $_fail, $cur_ratio] = $this->detail_ratio($mchid, $quality, $card_type, $spec, $period); $succs += $_succ; $fails += $_fail; } $ratio = round($succs / ($succs + $fails + 0.000001),4); return $ratio; }; $cur_ratio = $ratio_calc($mchid, $card_type, $spec, $period, $org_quality, $qualities); Log::record("{$header} cur_ratio = {$cur_ratio},lower_ratio={$ratio}",Log::DEBUG); if($cur_ratio >= $ratio) return true; $prices = $this->getPrice($mchid, $org_quality, $card_type, $spec); if ($prices === false) { return false; } $dratios = $this->detail_ratios($mchid, $qualities,$card_type, $spec, $period); $profit_ratio = $this->profit_ratio($mchid); $profit_judger = function ($org_quality,$qualities,$prices,$dratios,$profit_ratio) use ($header) { $sale = $prices[$org_quality]; $profit = 0; $amount = 0; $all_succs = 0; foreach ($qualities as $quality) { $inprice = $prices[$quality] ?? false; if ($inprice === false) continue; [$succ, $fail, $ratio] = $dratios[$quality]; if($succ === 0) continue; $profit += $succ * ($sale - $inprice); $amount += $succ * $sale; $all_succs += $succ; } if($all_succs === 0) { Log::record("{$header} qts_checker all_succs = {$all_succs} can next quality",Log::DEBUG); return true; } else { $cur_pratio = round($profit / ($amount + 0.000001),4); $profit_ratio = round($profit_ratio,4); Log::record("{$header} qts_checker cur_pratio = {$cur_pratio},profit_ratio={$profit_ratio}",Log::DEBUG); return $cur_pratio > $profit_ratio; } }; $can_next = $profit_judger($org_quality,$qualities,$prices,$dratios,$profit_ratio); return !$can_next; } public function need_intercept($mchid,$card_type,$card_state,$is_transfer,$card_no) : bool { $start_with = function ($card_no,$segment) { $reg = "/^(?:{$segment})\d*$/"; if(preg_match($reg, $card_no, $matches)) { return true; } else { return false; } }; $mintercepts = $this->mInterceptConfig; if(array_key_exists($mchid,$mintercepts)) { $mintercepts = $mintercepts[$mchid]; if(!empty($mintercepts['card_states']) && in_array($card_state,$mintercepts['card_states'],true)) { return true; } if(!empty($mintercepts['card_types']) && in_array($card_type,$mintercepts['card_types'],true)) { return true; } if($mintercepts['is_transfer'] && $is_transfer) { return true; } if (!empty($mintercepts['segment']) && $start_with($card_no,$mintercepts['segment'])) { return true; } } return false; } }