UserCoinController.php 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289
  1. <?php
  2. namespace app\api\controller\coin;
  3. use app\models\coin\UserCoinTransfer;
  4. use app\models\redis\UserHash;
  5. use app\models\store\StoreOrderCartInfo;
  6. use app\models\system\DictCoin;
  7. use app\models\user\UserCoin;
  8. use app\Request;
  9. use crmeb\services\{UtilService, JsonService};
  10. use think\facade\Config;
  11. use think\facade\Cache;
  12. use think\facade\Log;
  13. class UserCoinController {
  14. public function status(Request $request) {
  15. // 是否开启挖矿
  16. $symbol = Config::get('app.mining_symbo');
  17. //
  18. $icon = Cache::get($symbol);
  19. if (!$icon) {
  20. $icon = DictCoin::where('symbol', $symbol)->value('icon');
  21. Cache::set($symbol, $icon);
  22. }
  23. //
  24. $defStatus = [
  25. 'boot' => 0,
  26. 'stop' => 0,
  27. 'progress'=> 0,
  28. 'symbol'=>$symbol,
  29. 'icon'=>$icon,
  30. 'price'=> 0.00,
  31. 'total'=> 0,
  32. 'step' => 0,
  33. 'ts' => 0,
  34. ];
  35. if (!$symbol) {
  36. Log::warning('mining disabled: $symbol');
  37. return app('json')->successful($defStatus);
  38. }
  39. $uid = $request->uid();
  40. $mymining = UserHash::mining_get($uid);
  41. if (!$mymining) {
  42. $mymining = $defStatus;
  43. }
  44. if ($mymining['progress'] > 0.0) {
  45. $mymining = $this->calcMining($uid, $mymining);
  46. UserHash::mining_set($uid, $mymining);
  47. } else {
  48. $mymining['total'] = UserCoin::where('uid', $uid)->where('symbol', $symbol)->value('balance') ?? 0.0;
  49. }
  50. return app('json')->successful([
  51. 'symbol' => $mymining['symbol'],
  52. 'icon' => $mymining['icon'],
  53. 'price' => $mymining['price'],
  54. 'step' => $mymining['step'],
  55. 'progress' => $mymining['progress'],
  56. 'total' => $mymining['total'],
  57. ]);
  58. }
  59. /**
  60. * 根据 $step 返回一个新 $step
  61. * 新的 $step 值为围绕 旧 $step 周围的随机值,上下波动幅度为 10%
  62. */
  63. protected function floatStep($step)
  64. {
  65. $amp = bcdiv($step, 10, 8);
  66. $min = bcsub($step, $amp, 8);
  67. $max = bcadd($step, $amp, 8);
  68. $distance = bcsub($max, $min, 8);
  69. $section = bcdiv($distance, 10, 8);
  70. return bcmul($section, mt_rand(0, 10), 8) + $min;
  71. }
  72. /**
  73. * 根据上次挖矿状态, 和过去的时长, 计算当前的状态
  74. * @param $p
  75. * @return mixed
  76. */
  77. protected function calcMining($uid, $p) {
  78. if (!isset($p['ts']) ||
  79. !isset($p['boot']) ||
  80. !isset($p['stop']) ||
  81. !isset($p['step']) ||
  82. !isset($p['progress'])) {
  83. Log::warning('error format.');
  84. return $p;
  85. }
  86. if ($p['progress'] <= 0) {
  87. return $p;
  88. }
  89. $step = $this->floatStep($p['step']);
  90. $now = time();
  91. $secs_passed = $now - $p['ts']; // 从上次到现在
  92. //
  93. if ($now >= $p['stop']) { // 挖矿结束
  94. $secs_remain = $p['stop'] - $p['ts'];
  95. if ($secs_remain < 0) {
  96. $secs_remain = 0;
  97. }
  98. // 本次个数
  99. $count = floatval(bcmul($step, $secs_remain, 8));
  100. $p['progress'] += floatval(bcadd($p['progress'], $count, 8));
  101. // save to db
  102. UserCoinTransfer::beginTrans();
  103. $r1 = UserCoinTransfer::addMining($uid, $p['order_id'], $p['symbol'], $p['progress']);
  104. $r2 = UserCoin::upsertCoin($uid, $p['symbol'], $p['progress']);
  105. UserCoinTransfer::checkTrans($r1 && $r2);
  106. if (!$r1 || !$r2) {
  107. $amount = $p['progress'];
  108. Log::error("user<$uid> save transfer failed, amount<$amount>");
  109. return $p;
  110. }
  111. // -- save to db
  112. $p['ts'] = $now;
  113. $p['total'] = floatval(bcadd($p['total'], $p['progress'], 8));
  114. $p['progress'] = 0;
  115. return $p;
  116. }
  117. // 进行中
  118. $count = floatval(bcmul($step, $secs_passed, 8));
  119. $p['progress'] = floatval(bcadd($p['progress'], $count, 8));
  120. $p['ts'] = $now;
  121. return $p;
  122. }
  123. /**
  124. * 启动
  125. */
  126. public function boot(Request $request) {
  127. $uid = $request->uid();
  128. $now = time();
  129. // 是否开启活动
  130. $symbol = Config::get('app.mining_symbo');
  131. if (!$symbol) {
  132. return app('json')->fail('本活动未开启');
  133. }
  134. // 是否已经开启
  135. $mining = UserHash::mining_get($uid);
  136. if ($mining) {
  137. if (isset($mining['progress']) && $mining['progress'] > 0.0
  138. && isset($mining['boot']) && isset($mining['stop']) && isset($mining['ts'])) {
  139. return app('json')->fail('已启动');
  140. }
  141. }
  142. // 是否有订单
  143. $orderId = StoreOrderCartInfo::getMiningOrderId($uid);
  144. if (!$orderId) {
  145. return app('json')->fail('参加活动后可启动,请参看新手教程');
  146. }
  147. // 标记订单
  148. if (!StoreOrderCartInfo::setMining($orderId)) {
  149. return app('json')->fail('启动失败');
  150. }
  151. // 开启
  152. $secs_unit = Config::get('app.mining_sec_unit');
  153. $reward_unit = Config::get('app.mining_num_per_unit');
  154. $step = floatval(bcdiv($reward_unit, $secs_unit, 8));
  155. $progress = $step;
  156. $icon = DictCoin::getIcon($symbol);
  157. $balance = UserCoin::where('uid', $uid)->where('symbol', $symbol)->value('balance') ?? 0.0;
  158. $hours = Config::get('app.mining_time');
  159. if (count($hours) != 2 || $hours[0] > $hours[1]) {
  160. Log::warning('app.mining_time config error.');
  161. return app('json')->fail('启动失败,请联系客服');;
  162. }
  163. $hour = random_int($hours[0], $hours[1]);
  164. $stop = $now + $hour * 60 * 60;
  165. $suc = UserHash::mining_set($uid, [
  166. 'boot' => $now,
  167. 'step' => $step,
  168. 'stop' => $stop,
  169. 'symbol'=> $symbol,
  170. 'icon' => $icon,
  171. 'price' => 0,
  172. 'progress' => $progress,
  173. 'total' => $balance,
  174. 'ts' => $now + 1,
  175. 'order_id' => $orderId,
  176. ]);
  177. if ($suc != 0 && $suc != 1) {
  178. StoreOrderCartInfo::setMining($orderId, 0);
  179. return app('json')->fail('未成功执行');
  180. }
  181. return app('json')->successful([
  182. 'symbol' => $symbol,
  183. 'icon' => $icon,
  184. 'price' => 0,
  185. 'step' => $progress,
  186. 'progress' => $progress,
  187. 'total' => $balance,
  188. ]);
  189. }
  190. /**
  191. * 请求转账记录
  192. */
  193. public function history(Request $request) {
  194. [$page, $limit] = UtilService::getMore([
  195. ['page', 1],
  196. ['limit', 20],
  197. ], $request, true);
  198. $uid = $request->uid();
  199. $rows = UserCoinTransfer::getUserTransferred($uid, $page, $limit);
  200. return app('json')->successful($rows);
  201. }
  202. /*
  203. * 更新钱包地址
  204. */
  205. public function updateAddr(Request $request) {
  206. list($symbol, $addr) = UtilService::postMore([
  207. ['symbol', ''],
  208. ['addr', ''],
  209. ], $request, true);
  210. if (!$symbol || !$addr) {
  211. return app('json')->fail('参数不可为空');
  212. }
  213. if (!DictCoin::where('symbol', $symbol)->select()->toArray()) {
  214. return app('json')->fail('未找到目标');
  215. }
  216. $uid = $request->uid();
  217. $suc = UserCoin::upsertAddr($uid, $symbol, $addr);
  218. if (!$suc) {
  219. return app('json')->fail('执行失败');
  220. }
  221. return app('json')->successful();
  222. }
  223. /*
  224. * 提现
  225. */
  226. public function transfer(Request $request) {
  227. list($symbol, $amount) = UtilService::postMore([
  228. ['symbol', ''],
  229. ['amount', 0.0]
  230. ], $request, true);
  231. if(!$symbol) {
  232. return app('json')->fail('参数不可为空');
  233. }
  234. $meta = DictCoin::where('symbol', $symbol)->find();
  235. if (!$meta) {
  236. return app('json')->fail('未找到目标');
  237. }
  238. $meta = $meta->toArray();
  239. if ($amount < $meta['min_withdrawal']){
  240. return app('json')->fail('未达到最低限额');
  241. }
  242. $uid = $request->uid();
  243. $userCoin = UserCoin::where(['uid'=>$uid, 'symbol'=>$symbol])->find();
  244. if (!$userCoin) {
  245. return app('json')->fail('不适用的用户');
  246. }
  247. $userCoin = $userCoin->toArray();
  248. if(!$userCoin['addr']) {
  249. return app('json')->fail('地址未设置');
  250. }
  251. if ($userCoin['balance'] < $amount) {
  252. return app('json')->fail('余额不足');
  253. }
  254. $transferred = UserCoinTransfer::hasTransferred($uid, $symbol, $userCoin['addr']);
  255. if (!$transferred) {
  256. return app('json')->fail('首次操作需联系客服进行');
  257. }
  258. // transfer
  259. UserCoin::beginTrans();
  260. $left = bcsub($userCoin['balance'], $amount, 8);
  261. $res1 = UserCoin::where(['uid'=>$uid, 'symbol'=>$symbol])->update(['balance'=>$left]);
  262. $res2 = UserCoinTransfer::withdrawal($uid, $symbol, $userCoin['addr'], $amount);
  263. $ok = $res1 && $res2;
  264. UserCoin::checkTrans($ok);
  265. if (!$ok) {
  266. return app('json')->fail('执行失败');
  267. }
  268. return app('json')->successful('成功,已开始审核');
  269. }
  270. }