'; const OP_LESS = '<'; /** * @var AdminKlUpdateService */ private static $self; /** * @var OrderObject */ private $order; /** * @var UserObject */ private $user; /** * @return $this|AdminKlUpdateService */ public static function instance() { if (!self::$self) { self::$self = new self(); } return self::$self; } /** * 绑定订单 * @param OrderObject $order * @return OrderObject */ public function setOrder(OrderObject $order) { return $this->order = $order; } /** * @return UserObject */ public function getUser() { return $this->user; } /** * 绑定用户 * @param UserObject $user * @return UserObject */ public function setUser(UserObject $user) { return $this->user = $user; } /** * 获取KL渠道 * @param string $channelId * @param array $order_data * @param array $rule_config * @param string $business_line * @return \app\main\model\object\ReturnObject */ public function getKlAdmin($channelId, $order_data, $rule_config, $business_line = PayConstants::BUSINESS_WECHAT) { LogService::debug(json_encode($rule_config, JSON_UNESCAPED_UNICODE)); $oOrder = (new OrderObject())->bind($order_data); $this->setOrder($oOrder); $this->setUser(UserService::instance()->getUserInfo()); if ($this->user->is_black != UserConstants::USER_BLACK_NO) { LogService::info('用户黑名单,K'); return $this->getKlAdminInfo($business_line); } $adminConfig = model('AdminConfig')->getAdminInfoAll($channelId); $orderDeduct = new OrderDeduction(); //检测是否到达扣量比例 if (!$orderDeduct->checkIsDeductThreshold($channelId, $adminConfig, $this->user->id)) { LogService::info('KL渠道:扣量比例超过阈值,!K'); return $this->setData(self::KL_NO)->getReturn(); } //检查是否开启扣量 if (!$adminConfig['is_blacklist']) { LogService::info('KL渠道:渠道未开启扣量,!K'); return $this->setData(self::KL_NO)->getReturn(); } //用户白名单 if ($this->user->is_white) { LogService::info('KL渠道:用户白名单,!K'); return $this->setData(self::KL_NO)->getReturn(); } //IP检测 if (WhiteList::checkedIpWhite(Ip::ip())) { WhiteList::addUserIdWhite($this->user->id); LogService::info('KL渠道:用户与渠道同IP,!K'); return $this->setData(self::KL_NO)->getReturn(); } if ($this->checkKl($rule_config)->data) { return $this->getKlAdminInfo($business_line); } else { LogService::info('KL渠道:扣量规则检测结果,!K'); return $this->setData(self::KL_NO)->getReturn(); } } /** * 获取渠道 * @param $business_line * @return \app\main\model\object\ReturnObject */ public function getKlAdminInfo($business_line) { $klAdmin = self::KL_NO; $suffix = ''; if ($business_line != PayConstants::BUSINESS_WECHAT) { $suffix = '_app'; } $channel_ids = Config::get('site.change_ids' . $suffix); if (!$channel_ids) { LogService::info('KL渠道:未查询到KL渠道配置,!K'); } else { $channel_id_arr = explode(',', $channel_ids); shuffle($channel_id_arr); $admin_id = array_pop($channel_id_arr); if (!$klAdmin = model('admin_extend')->where('admin_id', $admin_id)->find()) { LogService::info('KL渠道:未查询到KL渠道,!K'); } else { //用户正常情况才进行黑灰名单配置 if ($this->user->is_black == UserConstants::USER_BLACK_NO) { $id = OrderService::instance()->getOrderModel() ->where('user_id', $this->user->id) ->value('id'); //首单加黑 if (!$id) { UserService::instance()->update(['is_black' => UserConstants::USER_BLACK_YES], ['id' => $this->user->id]); } else { UserService::instance()->update(['is_black' => UserConstants::USER_BLACK_HALF], ['id' => $this->user->id]); } } LogService::info('KL渠道:扣量规则检测结果,K:' . $klAdmin['admin_id']); } } return $this->setData($klAdmin)->getReturn(); } /** * 检测是否需要KL * @param array $ruleConfig * @return \app\main\model\object\ReturnObject */ public function checkKl(array $ruleConfig) { //户筛选规则 if ($this->checkUserNew()->data) { $screen_field = KlConstants::KL_SELECTOR_NEW_USER; } else { $screen_field = KlConstants::KL_SELECTOR_OLD_USER; } if (array_key_exists($screen_field, $ruleConfig[KlConstants::KL_SELECTOR])) { list($city, $new, $pay, $op) = $this->getScreenConfig($ruleConfig[KlConstants::KL_SELECTOR][$screen_field])->data; $klStatus = $this->checkScreen($city, $new, $pay, $op)->data; if ($klStatus) { LogService::info('检测:用户筛选通过,!K'); return $this->setData(self::KL_NO)->getReturn(); } else { LogService::info('检测:用户筛选不通过,GO ON'); } } else { LogService::info('检测:用户筛选条件不匹配,GO ON'); } //过滤规则 $kl = true; //注册时间与下单时间 if (array_key_exists(KlConstants::KL_FILTER_REGISTER, $ruleConfig[KlConstants::KL_FILTER])) { $kl = $kl && $this->checkFilterOrderAndRegister($ruleConfig[KlConstants::KL_FILTER][KlConstants::KL_FILTER_REGISTER][KlConstants::KL_FILTER_REGISTER_SPACE]); } else { LogService::info('检测:无注册时间与下单时间规则,GO ON'); } //用户行为 if (array_key_exists(KlConstants::KL_FILTER_BEHAVOR, $ruleConfig[KlConstants::KL_FILTER])) { list($pay_time_span, $pay_type, $read_chapter_count, $shubi_left, $order_count, $op) = $this->getFilterConfig($ruleConfig[KlConstants::KL_FILTER][KlConstants::KL_FILTER_BEHAVOR])->data; $kl = $kl && $this->checkFilterUser($pay_time_span, $pay_type, $read_chapter_count, $shubi_left, $order_count, $op)->data; } if ($kl) { LogService::info('检测:用户行为通过,!K'); return $this->setData(self::KL_NO)->getReturn(); } else { LogService::info('检测:用户行为不通过,GO ON'); } //vip订单 if ($this->order->day) { LogService::info('检测:VIP订单'); $field = KlConstants::KL_DEDUCT_VIP; //活动订单订单 } else if ($this->order->activity_id) { LogService::info('检测:活动订单'); $field = KlConstants::KL_DEDUCT_ACTIVITY; //普通订单 } else { LogService::info('检测:普通订单'); $field = KlConstants::KL_DEDUCT_NORMAL; } if (array_key_exists($field, $ruleConfig[KlConstants::KL_DEDUCT])) { list($double_pay, $new, $old) = $this->getKlOrderConfig($ruleConfig[KlConstants::KL_DEDUCT][$field])->data; $klStatus = $this->checkOrderKL($double_pay, $new, $old)->data; if ($klStatus) { return $this->setData(self::KL_YES)->getReturn(); } } else { LogService::info('检测:订单扣减类型不匹配,!K'); } return $this->setData(self::KL_NO)->getReturn(); } /** * 获取筛选参数 * @param array $screen * @return \app\main\model\object\ReturnObject */ public function getScreenConfig(array $screen) { $city = false; $new = false; $pay = false; $op = self::OP_AND; if (array_key_exists(KlConstants::KL_SELECTOR_NEW_CITY, $screen)) { $city = $screen[KlConstants::KL_SELECTOR_NEW_CITY]; } if (array_key_exists(KlConstants::KL_SELECTOR_NEW_ADD, $screen)) { $new = $screen[KlConstants::KL_SELECTOR_NEW_ADD]; } if (array_key_exists(KlConstants::KL_SELECTOR_NEW_RECHARGE, $screen)) { $pay = $screen[KlConstants::KL_SELECTOR_NEW_RECHARGE]; } if (array_key_exists(KlConstants::KL_SELECTOR_NEW_RELATION, $screen)) { $op = $screen[KlConstants::KL_SELECTOR_NEW_RELATION]; } return $this->setData([$city, $new, $pay, $op])->getReturn(); } /** * 获取过滤规则 * @param array $filter * @return \app\main\model\object\ReturnObject */ public function getFilterConfig(array $filter) { $pay_time_span = false; $pay_type = false; $read_chapter_count = false; $shubi_left = false; $order_count = false; $op = self::OP_AND; if (array_key_exists(KlConstants::KL_FILTER_BEHAVOR_SPACE, $filter)) { $pay_time_span = $filter[KlConstants::KL_FILTER_BEHAVOR_SPACE]; } if (array_key_exists(KlConstants::KL_FILTER_BEHAVOR_RECHARGE, $filter)) { $pay_type = $filter[KlConstants::KL_FILTER_BEHAVOR_RECHARGE]; } if (array_key_exists(KlConstants::KL_FILTER_BEHAVOR_READ, $filter)) { $read_chapter_count = $filter[KlConstants::KL_FILTER_BEHAVOR_READ]; } if (array_key_exists(KlConstants::KL_FILTER_BEHAVOR_KANDIAN, $filter)) { $shubi_left = $filter[KlConstants::KL_FILTER_BEHAVOR_KANDIAN]; } if (array_key_exists(KlConstants::KL_FILTER_BEHAVOR_ORDERS, $filter)) { $order_count = $filter[KlConstants::KL_FILTER_BEHAVOR_ORDERS]; } if (array_key_exists(KlConstants::KL_FILTER_BEHAVOR_RELATION, $filter)) { $op = $filter[KlConstants::KL_FILTER_BEHAVOR_RELATION]; } return $this->setData([$pay_time_span, $pay_type, $read_chapter_count, $shubi_left, $order_count, $op])->getReturn(); } /** * 获取KL order配置 * @param array $order * @return \app\main\model\object\ReturnObject */ public function getKlOrderConfig(array $order) { $double_pay = false; $new = false; $old = false; if (array_key_exists(KlConstants::KL_DEDUCT_NORMAL_NEW, $order)) { $new = $order[KlConstants::KL_DEDUCT_NORMAL_NEW]; } if (array_key_exists(KlConstants::KL_DEDUCT_NORMAL_OLD, $order)) { $old = $order[KlConstants::KL_DEDUCT_NORMAL_OLD]; } if (array_key_exists(KlConstants::KL_DEDUCT_NORMAL_MANY, $order)) { $double_pay = $order[KlConstants::KL_DEDUCT_NORMAL_MANY]; } return $this->setData([$double_pay, $new, $old])->getReturn(); } /** * 筛选 * @param bool $city * @param bool $new * @param bool $pay * @param string $op * @return \app\main\model\object\ReturnObject */ public function checkScreen($city = false, $new = false, $pay = false, $op = self::OP_AND) { //默认满足条件,不扣 $checkList = []; $channel_id = AdminService::instance()->getAdminExtendModel()->getChannelId($this->user->channel_id); if ($city) { $currentCity = Ip::citycode(); $cities = explode(',', $city); //渠道城市 $cacheCity = CacheConstants::getChannelWhiteCityCacheKey($channel_id); $channel_city = Redis::instance()->sMembers($cacheCity); if ($channel_city) { $cities = array_merge($cities, $channel_city); $cities = array_unique($cities); } $filterCity = in_array($currentCity, $cities); if ($filterCity) { LogService::info('筛选:城市白名单,满足:' . $currentCity . ':' . $city); } else { LogService::info('筛选:城市白名单,不满足' . $currentCity . ':' . $city); } $checkList[] = (int)$filterCity; } //当日新用户、活跃用户 if ($new) { if ($this->checkUserNew()->data) { $key = CacheConstants::getNewUserCacheKey($channel_id); $currentValue = (int)Redis::instance()->get($key); $msg = '新增用户'; }else{ $key = CacheConstants::getOldUserActiveCacheKey($channel_id); $currentValue = (int)Redis::instance()->pfCount($key); $msg = '活跃用户'; } list($first_letter, $value) = $this->getOpValue($new)->data; $filterNew = $this->checkRelation($currentValue, $first_letter, $value)->data; if ($filterNew) { LogService::info("筛选:${msg}满足判定:$currentValue$new"); }else{ LogService::info("筛选:${msg}不满足判定:$currentValue$new"); } $checkList[] = (int)$filterNew; } //当日起充人数 if ($pay) { $key = CacheConstants::getPayUserLimitCacheKey($channel_id); $currentValue = (int)Redis::instance()->pfCount($key); list($first_letter, $value) = $this->getOpValue($pay)->data; $filterPay = $this->checkRelation($currentValue, $first_letter, $value)->data; if ($filterPay) { LogService::info("筛选:起充用户满足判定:$currentValue$pay"); }else{ LogService::info("筛选:起充用户不满足判定:$currentValue$pay"); } $checkList[] = (int)$filterPay; } if ($op == self::OP_AND) { $result = count($checkList) == array_sum($checkList); } else { $result = max($checkList); } if ($result) { LogService::info("筛选:满足条件,!K"); }else{ LogService::info("筛选:不满足条件,GO ON"); } return $this->setData($result)->getReturn(); } /** * 用户行为判定 * @param bool $pay_time_span * @param bool $pay_type * @param bool $read_chapter_count * @param bool $shubi_left * @param bool $order_count * @param string $op * @return \app\main\model\object\ReturnObject */ public function checkFilterUser($pay_time_span = false, $pay_type = false, $read_chapter_count = false, $shubi_left = false, $order_count = false, $op = self::OP_AND) { $checkList = []; //支付间隔 if ($pay_time_span) { $cachePayTime = CacheConstants::getUserPayTimeCacheKey($this->user->id); $finish_time = (int)Redis::instance()->get($cachePayTime); if ($finish_time) { $current = round((time() - $finish_time) / 60, 2); $filterPayTimeSpan = $this->checkRelation($current, '<', $pay_time_span)->data; if ($filterPayTimeSpan) { LogService::info('用户行为:支付时间间隔检测通过:' . $current . '<' . $pay_time_span); } else { LogService::info('用户行为:支付时间间隔检测不通过:' . $current . '<' . $pay_time_span); } }else{ $filterPayTimeSpan = 0; LogService::info('用户行为:支付时间间隔检测不通过:未找到上一单'); } $checkList[] = (int)$filterPayTimeSpan; } //支付类型 if ($pay_type) { $pay_types = explode(',', $pay_type); $compare_types = []; if ((bool)$this->order->activity_id) { $compare_types[] = 'H'; LogService::info('用户行为:活动订单'); } if (!$this->order->book_id && !$this->order->activity_id) { $compare_types[] = 'Z'; LogService::info('用户行为:直充订单'); } $compare_type = implode(',', $compare_types); if (array_intersect($pay_types, $compare_types)) { $filterPayType = 1; LogService::info('用户行为:订单类型通过.' . $compare_type . ':' . $pay_type); } else { $filterPayType = 0; LogService::info('用户行为:订单类型不通过.' . $compare_type . ':' . $pay_type); } $checkList[] = $filterPayType; } //阅读章节 if ($read_chapter_count) { $cacheChapter = CacheConstants::getUserChapterCacheKey($this->user->id); $count = Redis::instance()->pfCount($cacheChapter); list($first_letter, $value) = $this->getOpValue($read_chapter_count)->data; $filterReadChapterCount = $this->checkRelation($count, $first_letter, $value)->data; if ($filterReadChapterCount) { LogService::info('用户行为:阅读章节检测通过:' . $count . $read_chapter_count); } else { LogService::info('用户行为:阅读章节检测不通过:' . $count . $read_chapter_count); } $checkList[] = (int)$filterReadChapterCount; } //剩余书币 if ($shubi_left) { list($first_letter, $value) = $this->getOpValue($shubi_left)->data; $kandian = FinancialService::instance()->getTotalKandian($this->user->id)->data; $filterShubiLeft = $this->checkRelation($kandian, $first_letter, $value)->data; if ($filterShubiLeft) { LogService::info('用户行为:书币剩余检测通过:' . $kandian . $shubi_left); } else { LogService::info('用户行为:书币剩余检测不通过:' . $kandian . $shubi_left); } $checkList[] = (int)$filterShubiLeft; } //订单完成数 if ($order_count) { $cacheOrderCount = CacheConstants::getUserOrderCompletedCountCacheKey($this->user->id); $count = (int)Redis::instance()->get($cacheOrderCount); list($first_letter, $value) = $this->getOpValue($order_count)->data; $filterOrderCount = $this->checkRelation($count, $first_letter, $value)->data; if ($filterOrderCount) { LogService::info('用户行为:订单数检测通过:' . $count . $order_count); } else { LogService::info('用户行为:订单数检测不通过:' . $count . $order_count); } $checkList[] = (int)$filterOrderCount; } if ($op == self::OP_AND) { $result = count($checkList) == array_sum($checkList); } else { $result = max($checkList); } if ($result) { LogService::info('用户行为:满足条件,!K'); } else { LogService::info('用户行为:不满足条件,GO ON'); } return $this->setData($result)->getReturn(); } /** * 注册与下单时间之差 * @param bool $time * @return \app\main\model\object\ReturnObject */ public function checkFilterOrderAndRegister($time = false) { $filter = true; if ($time) { list($first_letter, $value) = $this->getOpValue($time)->data; $diff = round((time() - $this->user->createtime) / 60, 2); $filter = $this->checkRelation($diff, $first_letter, $value)->data; if ($filter) { LogService::info('注册与下单时间之差:满足条件,!K:' . $diff . $first_letter . $value); } else { LogService::info('注册与下单时间之差:不满足条件,GO ON:' . $diff . $first_letter . $value); } } else { LogService::info('注册与下单时间之差:无规则,GO ON'); } return $this->setData($filter)->getReturn(); } /** * 检查KL比例规则 * @param bool $double_pay * @param bool $new * @param bool $old * @return \app\main\model\object\ReturnObject */ public function checkOrderKL($double_pay = false, $new = false, $old = false) { $rate = rand(1, 100); if ($double_pay) { $cacheOrderCount = CacheConstants::getUserOrderCompletedCountCacheKey($this->user->id); $count = Redis::instance()->get($cacheOrderCount); if ($count > 2) { LogService::info('KL比例:订单扣量比例规则,满足二充:' . $count); if ($rate <= $double_pay) { LogService::info('KL比例:扣量生效,二充:' . $rate . ':' . $double_pay); return $this->setData(self::KL_YES)->getReturn(); } else { LogService::info('KL比例:扣量未命中,二充:' . $rate . ':' . $double_pay); return $this->setData(self::KL_NO)->getReturn(); } } else { LogService::info('KL比例:非二充用户,订单数:' . $count . ',GO ON'); } } if ($new && $this->checkUserNew()->data) { LogService::info('KL比例:订单扣量比例规则,满足新用户'); if ($rate <= $new) { LogService::info('KL比例:扣量生效,新用户:' . $rate . ':' . $new); return $this->setData(self::KL_YES)->getReturn(); } else { LogService::info('KL比例:扣量未命中,新用户:' . $rate . ':' . $new); return $this->setData(self::KL_NO)->getReturn(); } } if ($old && !$this->checkUserNew()->data) { LogService::info('KL比例:订单扣量比例规则,满足老用户'); if ($rate <= $old) { LogService::info('KL比例:扣量生效,老用户:' . $rate . ':' . $old); return $this->setData(self::KL_YES)->getReturn(); } else { LogService::info('KL比例:扣量未命中,老用户:' . $rate . ':' . $old); return $this->setData(self::KL_NO)->getReturn(); } } LogService::info('KL比例:扣量未命中,END'); return $this->setData(self::KL_NO)->getReturn(); } /** * 获取操作符与值 * @param $opValue * @return \app\main\model\object\ReturnObject */ public function getOpValue($opValue) { $first_letter = substr($opValue, 0, 1); $value = (int)substr($opValue, 1); return $this->setData([$first_letter, $value])->getReturn(); } /** * 获取比较结果 * @param $v1 * @param $op * @param $v2 * @return \app\main\model\object\ReturnObject */ public function checkRelation($v1, $op, $v2) { if ($op == self::OP_BIGGER) { return $this->setData($v1 > $v2)->getReturn(); } else if ($op == self::OP_LESS) { return $this->setData($v1 < $v2)->getReturn(); } else { return $this->setData($v1 == $v2)->getReturn(); } } /** * 检测新用户 * @return \app\main\model\object\ReturnObject */ public function checkUserNew() { return $this->setData(date('Ymd') == date('Ymd', $this->user->createtime))->getReturn(); } /** * 设置KL规则修改信息 * @param $ruleId * @param $adminId * @param $msg * @return \app\main\model\object\ReturnObject */ public function updateConfigUserCache($ruleId, $adminId, $msg) { $admin = AdminService::instance()->getAdminModel()->where('id', $adminId)->find(); if ($admin) { $cache = CacheConstants::getKlRuleModifyInfo($ruleId); $data = [ 'updatetime' => time(), 'opuser_name' => $admin['username'], 'msg' => $msg, ]; Redis::instance()->hMset($cache, $data); LogService::info(json_encode($data, true)); KlService::instance()->delKlRuleManageCache(); } return $this->setData(true)->getReturn(); } }