find(); if (!$coinInfo) { return app('json')->failed('活动临时关闭,请联系客服'); } $coinInfo = $coinInfo->toArray(); Cache::set($symbol, $coinInfo); } // print_r($coinInfo); // $defStatus = [ 'boot' => 0, 'stop' => 0, 'progress' => 0, 'symbol' => $symbol, 'icon' => $coinInfo['icon'], 'price' => $coinInfo['price'], 'total' => 0, 'step' => 0, 'ts' => 0, ]; // 沒配置挖什麼 if (!$symbol) { warnlog('mining disabled: $symbol'); return app('json')->successful($defStatus); } // 獲取用戶進度 $uid = $request->uid(); $rds = new UserRds(); $mymining = json_decode($rds->get($uid, UserRds::FIELD_MINING), true); if (!$mymining) { $mymining = $defStatus; return app('json')->successful($defStatus); } // if ($mymining['progress'] > 0.0) { $mymining = $this->calcMining($uid, $coinInfo, $mymining); $rds->set($uid, UserRds::FIELD_MINING, json_encode($mymining)); } else { // 本次挖矿已经结束 $mymining['total'] = UserCoin::where('uid', $uid)->where('symbol', $symbol)->value('balance') ?? 0.0; $mymining['symbol'] = $symbol; $mymining['icon'] = $coinInfo['icon']; $mymining['price'] = $coinInfo['price']; $mymining['step'] = self::get_step(); } return app('json')->successful([ 'symbol' => $mymining['symbol'], 'icon' => $mymining['icon'], 'price' => $mymining['price'], 'step' => $mymining['step'], 'progress' => $mymining['progress'], 'total' => $mymining['total'], ]); } /** * 根据 $step 返回一个新 $step * 新的 $step 值为围绕 旧 $step 周围的随机值,上下波动幅度为 10% */ protected function floatStep($step) { $amp = twdiv($step, 10, 8); $min = twsub($step, $amp, 8); $max = twadd($step, $amp, 8); $distance = twsub($max, $min, 8); $section = twdiv($distance, 10, 8); return twmul($section, mt_rand(0, 10), 8) + $min; } /** * 根据上次挖矿状态, 和过去的时长, 计算当前的状态 * @param $p * @return mixed */ protected function calcMining($uid, $coinInfo, $p) { if ( !isset($p['ts']) || !isset($p['boot']) || !isset($p['stop']) || !isset($p['step']) || !isset($p['progress']) ) { warnlog('error format.'); return $p; } if ($p['progress'] <= 0) { return $p; } $step = $this->floatStep($p['step']); $now = time(); $secs_passed = $now - $p['ts']; // 从上次到现在 // if ($now >= $p['stop']) { // 挖矿结束 $secs_remain = $p['stop'] - $p['ts']; if ($secs_remain < 0) { $secs_remain = 0; } // 本次个数 $count = floatval(twmul($step, $secs_remain, 8)); $p['progress'] += floatval(twadd($p['progress'], $count, 8)); // save to db UserCoinTransfer::beginTrans(); $r1 = UserCoinTransfer::addMining($uid, $p['order_id'], $p['symbol'], $p['progress']); $r2 = UserCoin::upsertCoin($uid, $p['symbol'], $p['progress']); UserCoinTransfer::checkTrans($r1 && $r2); if (!$r1 || !$r2) { $amount = $p['progress']; errlog("user<$uid> save transfer failed, amount<$amount>"); return $p; } // -- save to db $p['ts'] = $now; $p['total'] = floatval(twadd($p['total'], $p['progress'], 8)); $p['progress'] = 0; // 更换为可能的新的币种 $p['symbol'] = $coinInfo['symbol']; $p['icon'] = $coinInfo['icon']; $p['price'] = $coinInfo['price']; $p['step'] = self::get_step(); return $p; } // 进行中 $count = floatval(twmul($step, $secs_passed, 8)); $p['progress'] = floatval(twadd($p['progress'], $count, 8)); $p['ts'] = $now; return $p; } public static function get_step(): float { $secs_unit = Config::get('app.mining_sec_unit'); $reward_unit = Config::get('app.mining_num_per_unit'); return twdiv($reward_unit, $secs_unit, 8); } /** * @api {post} /coin/boot 启动挖矿 * @apiName PostCoinBoot * @apiGroup User.Coin * */ public function boot(Request $request) { $uid = $request->uid(); $now = time(); // 是否停止 if (Config::get('activity.mining_stopped')) { return app('json')->fail('活动已暂停,稍后开启'); } // 是否开启活动 $symbol = Config::get('app.mining_symbo'); if (!$symbol) { return app('json')->fail('本活动未开启'); } // 是否已经开启 $rds = new UserRds(); $mining = json_decode($rds->get($uid, UserRds::FIELD_MINING), true); if ($mining) { if ( isset($mining['progress']) && $mining['progress'] > 0.0 && isset($mining['boot']) && isset($mining['stop']) && isset($mining['ts']) ) { return app('json')->fail('已启动'); } } // 是否有订单 $orderId = StoreOrderCartInfo::getMiningOrderId($uid); if (!$orderId) { return app('json')->fail('参加活动下单后可启动,请参看 首页->官方资讯->新手教程'); } // 标记订单 if (!StoreOrderCartInfo::setMining($orderId)) { return app('json')->fail('启动失败'); } // 开启 $step = self::get_step(); $progress = $step; $icon = DictCoin::getIcon($symbol); // 已挖总额 $balance = UserCoin::where('uid', $uid)->where('symbol', $symbol)->value('balance') ?? 0.0; // 单次活动时长 $hours = Config::get('app.mining_time'); if (count($hours) != 2 || $hours[0] > $hours[1]) { warnlog('app.mining_time config error.'); return app('json')->fail('启动失败,请联系客服');; } $hour = random_int($hours[0], $hours[1]); $stop = $now + $hour * 60 * 60; $suc = $rds->set($uid, UserRds::FIELD_MINING, json_encode([ 'boot' => $now, 'step' => $step, 'stop' => $stop, 'symbol' => $symbol, 'icon' => $icon, 'price' => 0, 'progress' => $progress, 'total' => $balance, 'ts' => $now + 1, 'order_id' => $orderId, ])); if ($suc != 0 && $suc != 1) { StoreOrderCartInfo::setMining($orderId, 0); return app('json')->fail('未成功启动'); } return app('json')->successful([ 'symbol' => $symbol, 'icon' => $icon, 'price' => 0, 'step' => $progress, 'progress' => $progress, 'total' => $balance, ]); } /** * @api {get} /coin/history 请求转账记录 * @apiName GetCoinHistory * @apiGroup User.Coin * */ public function history(Request $request) { [$page, $limit] = UtilService::getMore([ ['page', 1], ['limit', 20], ], $request, true); $uid = $request->uid(); $rows = UserCoinTransfer::getUserTransferred($uid, $page, $limit); return app('json')->successful($rows); } /** * @api {post} /coin/addr 更新钱包地址 * @apiName PostCoinAddr * @apiGroup User.Coin * */ public function updateAddr(Request $request) { list($symbol, $addr) = UtilService::postMore([ ['symbol', ''], ['addr', ''], ], $request, true); if (!$symbol || !$addr) { return app('json')->fail('参数不可为空'); } if (!DictCoin::where('symbol', $symbol)->select()->toArray()) { return app('json')->fail('未找到目标'); } $uid = $request->uid(); $suc = UserCoin::upsertAddr($uid, $symbol, $addr); if (!$suc) { return app('json')->fail('执行失败'); } return app('json')->successful(); } /** * @api {post} /coin/transfer 提现 * @apiName PostCoinTransfer * @apiGroup User.Coin * */ public function transfer(Request $request) { list($symbol, $amount) = UtilService::postMore([ ['symbol', ''], ['amount', 0.0] ], $request, true); if (!$symbol) { return app('json')->fail('参数不可为空'); } $meta = DictCoin::where('symbol', $symbol)->find(); if (!$meta) { return app('json')->fail('未找到目标'); } $meta = $meta->toArray(); if ($amount < $meta['min_withdrawal']) { return app('json')->fail('未达到最低限额'); } $uid = $request->uid(); $userCoin = UserCoin::where(['uid' => $uid, 'symbol' => $symbol])->find(); if (!$userCoin) { return app('json')->fail('不适用的用户'); } $userCoin = $userCoin->toArray(); if (!$userCoin['addr']) { return app('json')->fail('地址未设置'); } if ($userCoin['balance'] < $amount) { return app('json')->fail('余额不足'); } $transferred = UserCoinTransfer::hasTransferred($uid, $symbol, $userCoin['addr']); if (!$transferred) { return app('json')->fail('首次操作需联系客服进行'); } // transfer UserCoin::beginTrans(); $left = twsub($userCoin['balance'], $amount, 8); $res1 = UserCoin::where(['uid' => $uid, 'symbol' => $symbol])->update(['balance' => $left]); $res2 = UserCoinTransfer::withdrawal($uid, $symbol, $userCoin['addr'], $amount); $ok = $res1 && $res2; UserCoin::checkTrans($ok); if (!$ok) { return app('json')->fail('执行失败'); } return app('json')->successful('成功,已开始审核'); } }