UserCoinController.php 9.9 KB

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