join('store_order o', 'o.id=ci.oid') ->join('store_product p', 'ci.product_id=p.id') ->join('user u', 'u.uid = o.uid') ->where('o.paid', 1) ->where('o.status', '>=', 0) ->where('o.refund_status', 0) ->where('o.is_del', 0) ->where('o.is_system_del', 0) ->where('p.cate_id', $cate_id) ->where('ci.activity', '') ->field('o.id, o.order_id, o.pay_price, ci.product_id, ci.cart_info, u.uid, u.now_money, u.pay_count, p.store_name') ->select(); $orders = $orders ? $orders->toArray() : []; return $orders; } /** * 用统一的架构来计算输赢 */ public function calc() { // 活动分类ID $cate_id = $this->getId(); $products = self::getOrders($cate_id); $pNum = count($products); $minNum = $this->getMinimalProductNum(); if ($pNum < $minNum) { warnlog("not enough order. product num: $pNum, $minNum needed, stop activity:" . $this->getNameCN()); SystemAwardHistory::create([ 'activity' => $this->getName(), 'result' => $this->getResult(mt_rand(0, 10) < 5 ? true : false), 'ts' => time(), 'order_num' => $pNum, 'winner_num' => 0, 'total_paid' => 0, 'diff_paid' => 0, 'profit' => 0, 'rate' => $this->repRate(), ]); return; } // 按商品处理订单 $counter = 0; // 订单中商品计数器 $left = []; // 左侧商品 $right = []; // 右侧商品 $left_spent = 0; // 左侧总价 $right_spent = 0; // 右侧总价 $left_cost = 0; // 左侧总成本 $right_cost = 0; // 右侧总成本 // 打乱商品顺序 shuffle($products); shuffle($products); // 标记有多件商品的订单, 再次统计到属于某个订单的商品时, 订单会重复 $orders = []; foreach ($products as &$p) { $counter += 1; $ci = json_decode($p['cart_info'], true); $num = $ci['cart_num']; // 购买个数 $productInfo = isset($ci['productInfo']) ? $ci['productInfo'] : []; $attrInfo = isset($productInfo['attrInfo']) ? $productInfo['attrInfo'] : []; // 价格, 成本 $price = isset($attrInfo['price']) ? $attrInfo['price'] : 0.0; $cost = isset($attrInfo['cost']) ? $attrInfo['cost'] : 0.0; // 付款总额, 总成本 $ppaid = bcmul($num, $price, 2); // product paid $pcost = bcmul($num, $cost, 2); // orderId $oid = $p['id']; // 标记付款和成本 $p['paid'] = $ppaid; $p['cost'] = $pcost; $p['image'] = isset($attrInfo['image']) ? $attrInfo['image'] : ''; // 处理一个订单多件商品的情况 if (!isset($orders[$oid])) { $orders[$oid] = 1; } else { $orders[$oid] += 1; } // if ($ppaid <= 1.0) { // Log::warning("impossible price, order: ". $p['id']); // continue; // } // if ($pcost <= 0.1) { // Log::warning("impossible cost, order: ". $p['id']); // continue; // } // 按一个规则, 把商品分为两份, 其中一份中奖 if ($this->leaningJudge($counter, $p, $attrInfo)) { $left[] = $p; $left_spent = bcadd($left_spent, $ppaid, 2); $left_cost = bcadd($left_cost, $pcost, 2); } else { $right[] = $p; $right_spent = bcadd($right_spent, $ppaid, 2); $right_cost = bcadd($right_cost, $pcost, 2); } } // foreach // 利润 $left_profit = bcsub($left_spent, $left_cost, 2); $right_profit = bcsub($right_spent, $right_cost, 2); // 定输赢 $diff_money = abs(bcsub($left_spent, $right_spent, 2)); // if ($diff_money < 0.0) { // $diff_money = -$diff_money; // } // 假定左边胜利 $profit = $right_profit; $winners = $left; $losers = $right; // 左边利润高则右边胜利 if ($left_profit > $right_profit) { $winners = $right; $losers = $left; $profit = $left_profit; } // 如果只有 1 件商品 if ($pNum == 1) { // 此时,订单必为 loser // 47% 概率胜 $itWin = false; // 这单胜了 ? $dice = mt_rand(0, 100); if ($dice < 60) { $itWin = true; } $poolBalance = floatval((new ActivityPool)->get('', $this->getName())); // pool 中金额 if (!$itWin) { // 如果为新用户,且池子有钱,让他胜 if ($poolBalance > $profit && $products[0]['pay_count'] <= 1) { $itWin = true; } } if ($itWin) { $profit = -$profit; list($winners, $losers) = [$losers, $winners]; // swap } (new ActivityPool)->hincr_by_float('', $this->getName(), floatval($profit)); warnlog($this->getName() . ": single order[product]. dice:$dice, remain:$poolBalance, pay_count:" . $products[0]['pay_count']); } $result = $this->getResult($winners == $left); warnlog('products:' . json_encode($products) . ' left:' . json_encode($left) . ' left_spent:' . $left_spent . ' left_cost:' . $left_cost . ' left_profit:' . $left_profit . ' right' . json_encode($right) . ' right_spent:' . $right_spent . ' right_cost:' . $right_cost . ' right_profit:' . $right_profit . ' diff_money:' . $diff_money . ' profit:' . $profit . ' result:' . $result); // 保存开奖结果 SystemAwardHistory::create([ 'activity' => $this->getName(), 'result' => $result, 'ts' => time(), 'order_num' => count($left) + count($right), 'winner_num' => count($winners), 'total_paid' => $left_spent + $right_spent, 'diff_paid' => $diff_money, 'profit' => $profit, 'rate' => $this->repRate(), ]); // 结果处置 胜方退款,败方发货 foreach ($winners as $p) { $single = $orders[$p['id']] == 1; $this->execute($p, $single, true); } foreach ($losers as $p) { $single = $orders[$p['id']] == 1; $this->execute($p, $single, false); } warnlog("activity" . $this->getNameCN() . " calc finished. result: $result"); } protected function execute($order, $single = true, $win = true) { BaseModel::beginTrans(); $result = 0; $reparation = 0.0; $product_name = $order['store_name']; $order_str = $order['order_id']; $paid = $order['paid']; $cost = $order['cost']; if ($win) { $result = 1; $reparation = bcmul(bcsub($paid, $cost, 2), $this->repRate(), 2); } if ($reparation < 0.0) { warnlog("reparation = $reparation, orderId=" . $order_str); $reparation = 0.0; } // 标记商品为已参与活动 $r1 = StoreOrderCartInfo::where('oid', $order['id']) ->where('product_id', $order['product_id']) ->update([ 'activity' => $this->getName(), 'result' => $result, 'reparation' => $reparation, ]); // 没胜利,退出 if (!$win) { BaseModel::checkTrans($r1); return; } $refund_price = bcadd($paid, $reparation, 2); // 订单全部商品都中奖的退单 $r2 = true; if ($single) { $r2 = StoreOrder::where('id', $order['id'])->update([ 'refund_price' => $refund_price, 'refund_status' => 2, ]); } // 中奖的 退款并赔款返回到佣金 $r3 = UserBill::income( '活动' . $this->getNameCN() . "[$order_str]", $order['uid'], 'now_money', 'brokerage', $refund_price, $order['id'], bcadd($order['now_money'], $refund_price, 2), '订单退款' . $order['paid'] . '+' . $reparation . '元' ); $r4 = User::bcInc($order['uid'], 'brokerage_price', $refund_price, 'uid'); $ok = $r1 && $r2 && $r3 && $r4; BaseModel::checkTrans($ok); if ($ok) { // 中奖用户发送中奖消息. UserNotice::sendNoticeTo( $order['uid'], '您的订单已处理 点击查看', "您好,由于活动商品库存有限,您的订单 $product_name(订单号 $order_str) 未能成功发货。 该商品付款 $paid 元已如数退还,并赔付您 $reparation 元作为补偿,请在”我的佣金”中核实。对您造成的不便我们深感抱歉。感谢您对我们的信任和喜爱,美天旺祝您生活愉快。", $order['image'] ); } } // execute // 活动所属的 $categoryId abstract protected function getId(); // 活动英文名, 长度 <8 abstract protected function getName(); // 活动中文名 abstract protected function getNameCN(); // 赔款所占商品利润的百分比 abstract protected function repRate(); // 满足开奖的最小订单数 abstract protected function getMinimalProductNum(); // 获得开奖结果 int abstract protected function getResult($leftwin = true); /** * 根据此函数返回值来划分 getOrders() 返回的订单为两份, 其中一份中奖, 当本函数返回 true, $product 加入"左边" * * @param $index: $product 所在序号, 从 1 开始 * @param $product: getOrders() 返回列表中的单个元素 * @param $attr: 商品属性 * @return boolean true 加入左边 */ abstract protected function leaningJudge($index, $product, $attr); }