OrderDeduction.php 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380
  1. <?php
  2. namespace app\api\library;
  3. use app\common\library\Ip;
  4. use app\common\library\Redis;
  5. use app\common\utility\WhiteList;
  6. use app\main\constants\UserConstants;
  7. use app\main\service\FinancialService;
  8. use app\main\service\UserService;
  9. use think\Config;
  10. use think\Log;
  11. use think\Model;
  12. class OrderDeduction{
  13. protected $redis = null;
  14. public function __construct(){
  15. $this->redis = Redis::instance();
  16. }
  17. /**
  18. * 订单扣量
  19. * @param integer $admin_id 后台用户ID
  20. * @param integer $channel_id 渠道ID
  21. * @param array $order_data 订单数据
  22. * @param bool $is_activity 是否是活动订单
  23. * @return array|bool|false|\PDOStatement|string|Model
  24. * @throws \think\db\exception\DataNotFoundException
  25. * @throws \think\db\exception\ModelNotFoundException
  26. * @throws \think\exception\DbException
  27. */
  28. public function orderKL($admin_id, $channel_id, $order_data, $is_activity = false)
  29. {
  30. if (UserService::instance()->getUserInfo()->is_black == UserConstants::USER_BLACK_NO) {
  31. $log_is_vip = $order_data['day'] ? '会员' : '普通';
  32. $adminConfig = model('AdminConfig')->getAdminInfoAll($channel_id);
  33. //检查渠道是否开启扣量
  34. if (!$adminConfig['is_blacklist']) {
  35. Log::info("KL逻辑-{$log_is_vip} 渠道商:" . $channel_id . ' 用户ID:' . $order_data['user_id'] . ' IP:' . Ip::ip() . ' 处理:已执行 结果:未命中 原因:渠道未开启KL');
  36. return false;
  37. }
  38. //检查代理商ID白名单
  39. $kl_agent_blacklist = explode(',', Config::get('site.kl_agent_blacklist') ?? '');
  40. if (in_array($admin_id, $kl_agent_blacklist)) {
  41. Log::info("KL逻辑-{$log_is_vip} 代理商: {$admin_id} 渠道商:" . $channel_id . ' 用户ID:' . $order_data['user_id'] . ' IP:' . Ip::ip() . ' 处理:已执行 结果:未命中 原因:代理商ID白名单');
  42. return false;
  43. }
  44. //检查扣量基数
  45. $kl_base = $this->getDeductionBase($adminConfig, $order_data['day'], false);
  46. if (!$kl_base || $kl_base < 0) {
  47. Log::info("KL逻辑-{$log_is_vip} 渠道商:" . $channel_id . ' 用户ID:' . $order_data['user_id'] . ' IP:' . Ip::ip() . ' 处理:已执行 结果:未命中 原因:渠道未开启KL');
  48. return false;
  49. }
  50. //获取计数器,未到扣量基数时累加,到扣量基数时暂停,直到扣量后继续累加
  51. $kl_index = $this->getDeductionCounter($channel_id, $order_data['day'], false, false);
  52. if ($kl_index % $kl_base != 0) {
  53. Log::info("KL逻辑-{$log_is_vip} 渠道商:" . $channel_id . ' 用户ID:' . $order_data['user_id'] . ' IP:' . Ip::ip() . ' 处理:已执行 结果:未命中 基数:' . $kl_base . ' 单数:' . $kl_index . ' 取模:' . $kl_index % $kl_base);
  54. $this->getDeductionCounter($channel_id, $order_data['day'], false);
  55. }
  56. //检测是否到达扣量比例
  57. if (!$this->checkIsDeductThreshold($channel_id, $adminConfig, $order_data['user_id'])) {
  58. return false;
  59. }
  60. //检查是否扣量白名单
  61. if (!$this->checkIgnore($admin_id, $channel_id, $order_data, 'KL逻辑', $is_activity)) {
  62. return false;
  63. }
  64. //扣量计数器判断
  65. if ($kl_index % $kl_base != 0) {
  66. return false;
  67. } else {
  68. $this->getDeductionCounter($channel_id, $order_data['day'], false);
  69. }
  70. Log::info("KL逻辑-{$log_is_vip} 渠道商:" . $channel_id . ' 用户ID:' . $order_data['user_id'] . ' IP:' . Ip::ip() . ' 处理:已执行 结果:已命中 基数:' . $kl_base . ' 单数:' . $kl_index . ' 取模:' . $kl_index % $kl_base);
  71. }
  72. //获取要转移的渠道
  73. if (!$change_ids = Config::get('site.change_ids')) {
  74. Log::info("KL逻辑-原因:目标账户为空");
  75. return false;
  76. }
  77. //获取渠道信息
  78. $arrchange = explode(',', $change_ids);
  79. $admin_id = $arrchange[array_rand($arrchange, 1)];
  80. if (!$thisAdminExtend = model('admin_extend')->where('admin_id', $admin_id)->find()) {
  81. Log::info("KL逻辑-原因:获取目标账户信息失败:" . $admin_id);
  82. return false;
  83. }
  84. return $thisAdminExtend;
  85. }
  86. /**
  87. * 订单转移
  88. * @param integer $admin_id 后台用户ID
  89. * @param integer $channel_id 渠道ID
  90. * @param array $order_data 订单数据
  91. * @param bool $is_activity 是否是活动订单
  92. * @return array|bool|false|\PDOStatement|string|Model
  93. * @throws \think\db\exception\DataNotFoundException
  94. * @throws \think\db\exception\ModelNotFoundException
  95. * @throws \think\exception\DbException
  96. */
  97. public function orderTransfer($admin_id,$channel_id,$order_data,$is_activity = false){
  98. $log_is_vip = $order_data['day'] ? '会员' : '普通';
  99. $adminConfig = model('AdminConfig')->getAdminInfoAll($channel_id);
  100. //检查是否开启转移
  101. if(!$adminConfig['agent_is_blacklist']){
  102. Log::info("订单转移-{$log_is_vip} 渠道商:" . $channel_id . ' 用户ID:' . $order_data['user_id'] . ' IP:' . Ip::ip() . ' 处理:已执行 结果:未命中 原因:渠道未开启KL');
  103. return false;
  104. }
  105. //检查扣量基数
  106. $kl_base = $this->getDeductionBase($adminConfig,$order_data['day'],true);
  107. if(!$kl_base || $kl_base < 0){
  108. Log::info("订单转移-{$log_is_vip} 渠道商:" . $channel_id . ' 用户ID:' . $order_data['user_id'] . ' IP:' . Ip::ip() . ' 处理:已执行 结果:未命中 原因:渠道未开启KL');
  109. return false;
  110. }
  111. //检查是否扣量白名单
  112. if(!$this->checkIgnore($admin_id,$channel_id,$order_data,'订单转移',$is_activity)){
  113. return false;
  114. }
  115. //获取计数器
  116. $kl_index = $this->getDeductionCounter($admin_id,$order_data['day'],true);
  117. if($kl_index % $kl_base != 0){
  118. Log::info("订单转移-{$log_is_vip} 渠道商:" . $channel_id . ' 用户ID:' . $order_data['user_id'] . ' IP:' . Ip::ip() . ' 处理:已执行 结果:未命中 基数:' . $kl_base . ' 单数:' . $kl_index . ' 取模:' . $kl_index % $kl_base);
  119. return false;
  120. }
  121. if(!$agentObj = model('admin_extend')->where('admin_id', $admin_id)->find()){
  122. Log::info("订单转移-{$log_is_vip} 渠道商:" . $channel_id . ' 用户ID:' . $order_data['user_id'] . ' IP:' . Ip::ip() . ' 处理:已执行 结果:未命中 基数:' . $kl_base . ' 单数:' . $kl_index . ' 取模:' . $kl_index % $kl_base.' 原因:获取代理信息失败');
  123. return false;
  124. }
  125. if(!$thisAdminExtend = model('admin_extend')->where('admin_id', $agentObj->create_by)->find()){
  126. Log::info("订单转移-{$log_is_vip} 渠道商:" . $channel_id . ' 用户ID:' . $order_data['user_id'] . ' IP:' . Ip::ip() . ' 处理:已执行 结果:未命中 基数:' . $kl_base . ' 单数:' . $kl_index . ' 取模:' . $kl_index % $kl_base.' 原因:获取代理上级渠道信息失败');
  127. return false;
  128. }
  129. Log::info("订单转移-{$log_is_vip} 渠道商:" . $channel_id . ' 用户ID:' . $order_data['user_id'] . ' IP:' . Ip::ip() . ' 处理:已执行 结果:已命中 基数:' . $kl_base . ' 单数:' . $kl_index . ' 取模:' . $kl_index % $kl_base);
  130. return $thisAdminExtend;
  131. }
  132. /**
  133. * 获取计数信息
  134. * @param int $channel_id 渠道ID
  135. * @param int $is_vip 是否是VIP
  136. * @param bool $is_transfer 是否是转移订单
  137. * @param bool $is_incr 是否自增
  138. * @return int
  139. */
  140. public function getDeductionCounter($channel_id,$is_vip,$is_transfer = false,$is_incr = true){
  141. if($is_transfer){
  142. $vip_key = 'KAV:'.$channel_id;
  143. $nor_key = 'KAN:'.$channel_id;
  144. }else{
  145. $vip_key = 'KCV:'.$channel_id;
  146. $nor_key = 'KCN:'.$channel_id;
  147. }
  148. if($is_vip){
  149. //vip
  150. if($this->redis->exists($vip_key)){
  151. if($is_incr){
  152. return $this->redis->incr($vip_key);
  153. }else{
  154. return $this->redis->get($vip_key);
  155. }
  156. }else{
  157. $this->redis->set($vip_key,1);
  158. return 1;
  159. }
  160. }else{
  161. //普通
  162. if($this->redis->exists($nor_key)){
  163. if($is_incr){
  164. return $this->redis->incr($nor_key);
  165. }else{
  166. return $this->redis->get($nor_key);
  167. }
  168. }else{
  169. $this->redis->set($nor_key,1);
  170. return 1;
  171. }
  172. }
  173. }
  174. /**
  175. * 获取扣量基数
  176. * @param int $adminConfig 后台用户信息
  177. * @param int $is_vip 是否是vip
  178. * @param bool $is_transfer 是否是转移订单
  179. * @return int
  180. */
  181. public function getDeductionBase($adminConfig,$is_vip,$is_transfer = false){
  182. if($is_transfer){
  183. $vip_key = 'agent_vip_num';
  184. $nor_key = 'agent_normal_num';
  185. }else{
  186. $vip_key = 'vip_num';
  187. $nor_key = 'normal_num';
  188. }
  189. //扣量基数,默认取渠道商,渠道商没有配置取系统配置
  190. if($is_vip){
  191. if (intval($adminConfig[$vip_key]) > 0) {
  192. return intval($adminConfig[$vip_key]);
  193. } else {
  194. return intval(Config::get('site.'.$vip_key));
  195. }
  196. }else{
  197. if (intval($adminConfig[$nor_key]) > 0) {
  198. return intval($adminConfig[$nor_key]);
  199. } else {
  200. return intval(Config::get('site.'.$nor_key));
  201. }
  202. }
  203. }
  204. /**
  205. * 检测渠道扣量是否达到比例
  206. * @param $admin_id
  207. * @param $adminConfig
  208. * @return bool
  209. */
  210. public function checkIsDeductThreshold($admin_id,$adminConfig,$user_id){
  211. try{
  212. if(isset($adminConfig['kl_rate'])){
  213. $sys_rate = floatval($adminConfig['kl_rate']) ? $adminConfig['kl_rate'] : Config::get('site.kl_rate');
  214. }else{
  215. $sys_rate = Config::get('site.kl_rate');
  216. }
  217. $all_money = $this->getChannelOrderPayMoney($admin_id);
  218. $deduct_money = $this->getChannelOrderPayMoney($admin_id,true);
  219. $deduct_rate = $all_money ? strval(round($deduct_money/$all_money,2)) : 0;
  220. if(floatval($deduct_rate) < floatval($sys_rate) ){
  221. Log::info("KL逻辑-阈值 渠道商:" .$admin_id. ' 用户ID:' . $user_id . ' IP:' . Ip::ip() . ' 处理:已执行 日志: 命中'.' 已付款金额:'.strval(intval($all_money)/100).' 已扣量金额:'.strval(intval($deduct_money)/100).' 扣量比例:'.$deduct_rate.' 配置比例:'.$sys_rate.' 未到阈值');
  222. return true;
  223. }else{
  224. Log::info("KL逻辑-阈值 渠道商:" .$admin_id. ' 用户ID:' . $user_id . ' IP:' . Ip::ip() . ' 处理:已执行 日志: 命中'.' 已付款金额:'.strval(intval($all_money)/100).' 已扣量金额:'.strval(intval($deduct_money)/100).' 扣量比例:'.$deduct_rate.' 配置比例:'.$sys_rate.' 已到阈值');
  225. return false;
  226. }
  227. }catch (\Exception $e){
  228. Log::error('OrderDeduct->Info: 检测扣量金额阈值错误:'.$e->getMessage());
  229. return true;
  230. }
  231. }
  232. /**
  233. * 获取当前时间的前24小时,包含当前小时
  234. * @return array
  235. */
  236. public function getBeforeTwentyFourTime(){
  237. $before_array = [];
  238. $current_time = time();
  239. $first_time = strtotime('-1 day');
  240. for ($time=$first_time; $time<=$current_time; $time+=3600){
  241. array_push($before_array,$time);
  242. }
  243. return $before_array;
  244. }
  245. /**
  246. * 获取当前渠道支付成功的订单金额
  247. * @param $admin_id
  248. * @param bool $is_deduct
  249. * @return float|int
  250. */
  251. public function getChannelOrderPayMoney($admin_id,$is_deduct = false){
  252. $count_money = 0;
  253. $before_time = $this->getBeforeTwentyFourTime();
  254. foreach($before_time as $val){
  255. if($is_deduct){
  256. $key = 'KDM:'.$admin_id.':'.date('dH',$val);
  257. }else{
  258. $key = 'KAM:'.$admin_id.':'.date('dH',$val);
  259. }
  260. if($this->redis->exists($key)){
  261. $count_money += intval($this->redis->get($key));
  262. }else{
  263. // $orderModel = model('Orders');
  264. // if($is_deduct){
  265. // $orderModel->where('deduct','=',1);
  266. // }
  267. // $money = $orderModel->where('state','=',1)
  268. // ->whereTime('finishtime', '>=', strtotime(date('Y-m-d H:00:00',$val)))
  269. // ->whereTime('finishtime', '<', strtotime(date('Y-m-d H:59:59',$val)))
  270. // ->sum('money');
  271. // $this->redis->set($key,intval($money*100));
  272. // $this->redis->expire($key,86400);
  273. // $count_money += intval($money)*100;
  274. // $count_money += 0;
  275. }
  276. }
  277. return $count_money;
  278. }
  279. /**
  280. * 添加渠道订单支付金额统计
  281. * @param $admin_id
  282. * @param $money
  283. * @param $order_no
  284. * @param bool $is_deduct 是否是扣量订单
  285. */
  286. public function addChannelOrderMoneyCache($admin_id,$money,$order_no,$is_deduct = false){
  287. try{
  288. $group_id = model('AuthGroupAccess')->getGroupId($admin_id);
  289. //代理商
  290. if($group_id == 4){
  291. $adminExtend = model('AdminExtend')->getInfo($admin_id);
  292. $admin_id = $adminExtend['create_by'];
  293. }
  294. if($is_deduct){
  295. $key = 'KDM:'.$admin_id.':'.date('dH');
  296. }else{
  297. $key = 'KAM:'.$admin_id.':'.date('dH');
  298. }
  299. if($this->redis->exists($key)){
  300. $this->redis->incrBy($key,intval($money*100));
  301. }else{
  302. $this->redis->set($key,intval($money*100));
  303. $this->redis->expire($key,86400);
  304. }
  305. }catch (\Exception $e){
  306. Log::error('OrderDeduct->Info: 添加渠道订单金额错误 IsDeduct:'.$is_deduct.' ChannelID:'.$admin_id.' OrderNo:'.$order_no.' Monery:'.$money);
  307. }
  308. }
  309. /**
  310. * 检查扣量白名单
  311. * @param int $admin_id 后台用户ID
  312. * @param int $channel_id 渠道ID
  313. * @param array $order_data 订单数据
  314. * @param string $msgTitle 消息表头
  315. * @param bool $is_activity 是否是活动订单
  316. * @return bool
  317. */
  318. public function checkIgnore($admin_id,$channel_id,$order_data,$msgTitle,$is_activity){
  319. $user_info = model('User')->getUserInfo($order_data['user_id']);
  320. $log_is_vip = $order_data['day'] ? '会员' : '普通';
  321. //用户上一次充值成功时间小于两小时
  322. if($this->redis->exists('UPT:'.$order_data['user_id'])){
  323. WhiteList::addUserIdWhite($order_data['user_id']);
  324. Log::info("白名单:" . ' 用户ID:' . $order_data['user_id'] . '添加时间: '.date('Y-m-d H:i:s').' 原因:用户上一次充值成功时间小于两小时');
  325. Log::info("{$msgTitle}-{$log_is_vip} 渠道商:" . $channel_id . ' 用户ID:' . $order_data['user_id'] .' City:'.Ip::province(). ' IP:' . Ip::ip() . ' 处理:已执行 结果:未命中 原因:用户上一次充值成功小于'.Config::get('site.pay_time'));
  326. return false;
  327. }
  328. //用户创建时间与支付时间小于指定时间时加入用户白名单
  329. if($this->redis->exists('UCPT:'.$order_data['user_id'])){
  330. WhiteList::addUserIdWhite($order_data['user_id']);
  331. Log::info("白名单:" . ' 用户ID:' . $order_data['user_id'] . '添加时间: '.date('Y-m-d H:i:s').' 原因:用户创建时间与支付时间小于指定时间时加入用户白名单');
  332. Log::info("{$msgTitle}-{$log_is_vip} 渠道商:" . $channel_id . ' 用户ID:' . $order_data['user_id'] .' City:'.Ip::province(). ' IP:' . Ip::ip() . ' 处理:已执行 结果:未命中 原因:用户创建时间与下单间隔时间小于'.Config::get('site.create_pay_time'));
  333. return false;
  334. }
  335. //判断客户在不在ip白名单
  336. if (WhiteList::checkedIpWhite(Ip::ip())) {
  337. WhiteList::addUserIdWhite($order_data['user_id']);
  338. Log::info("{$msgTitle}-{$log_is_vip} 渠道商:" . $channel_id . ' 用户ID:' . $order_data['user_id'] . ' IP:' . Ip::ip() . ' 处理:已执行 结果:未命中 原因:渠道IP白名单');
  339. Log::info("白名单:" . ' 用户ID:' . $order_data['user_id'] . '添加时间: '.date('Y-m-d H:i:s').' 原因:用户IP在IP白名单内');
  340. return false;
  341. }
  342. //判断是不是用户白名单
  343. if (($user_info['is_white'] ?? 0)) {
  344. Log::info("{$msgTitle}-{$log_is_vip} 渠道商:" . $channel_id . ' 用户ID:' . $order_data['user_id'] . ' IP:' . Ip::ip() . ' 处理:已执行 结果:未命中 原因:用户ID白名单');
  345. return false;
  346. }
  347. //判断客户是否在同一城市
  348. if(WhiteList::checkedCityWhite($admin_id,Ip::province())){
  349. Log::info("{$msgTitle}-{$log_is_vip} 渠道商:" . $channel_id . ' 用户ID:' . $order_data['user_id'] .' City:'.Ip::province(). ' IP:' . Ip::ip() . ' 处理:已执行 结果:未命中 原因:渠道City白名单');
  350. return false;
  351. }
  352. //不是活动订单时,直冲不处理
  353. if(!$is_activity && empty($order_data['book_id']) ){
  354. Log::info("{$msgTitle}-{$log_is_vip} 渠道商:" . $channel_id . ' 用户ID:' . $order_data['user_id'] .' City:'.Ip::province(). ' IP:' . Ip::ip() . ' 处理:已执行 结果:未命中 原因:不是活动时,直冲用户不扣量');
  355. return false;
  356. }
  357. //不是活动订单时,充值看点大于100不处理
  358. if(!$is_activity && $user_info){
  359. if(FinancialService::instance()->getTotalKandian($user_info['id'])->data > 100){
  360. Log::info("{$msgTitle}-{$log_is_vip} 渠道商:" . $channel_id . ' 用户ID:' . $order_data['user_id'] .' City:'.Ip::province(). ' IP:' . Ip::ip() . ' 处理:已执行 结果:未命中 原因:不是活动时,充值看点大于100不扣量');
  361. return false;
  362. }
  363. }
  364. return true;
  365. }
  366. }