Prechádzať zdrojové kódy

add: 实现 coin 接口协议

joe 4 rokov pred
rodič
commit
f55b943116

+ 126 - 5
app/api/controller/coin/UserCoinController.php

@@ -1,21 +1,142 @@
 <?php
 namespace app\api\controller\coin;
 
+use app\models\coin\UserCoinTransfer;
+use app\models\redis\UserHash;
+use app\models\store\StoreOrderCartInfo;
+use app\models\system\DictCoin;
+use app\models\user\UserCoin;
+use app\Request;
+use crmeb\services\{UtilService, JsonService};
+use think\facade\Config;
+
 class UserCoinController {
 
-    public function boot() {
+    /**
+     * 启动
+     */
+    public function boot(Request $request) {
+        $uid = 1000; //TODO
+        // 是否开启
+        $symbol = Config::get('app.mining_symbo');
+        if (!$symbol) {
+            return JsonService::fail('本活动未开启');
+        }
+        // 是否有订单
+        $orderId = StoreOrderCartInfo::getMiningOrderId($uid);
+        if (!$orderId) {
+            return JsonService::fail('下单后可启动');
+        }
+        // 标记订单
+        if (!StoreOrderCartInfo::setMining($orderId)) {
+            return JsonService::fail('执行失败');
+        }
+        // 开启
+        $secs_unit = Config::get('app.mining_sec_unit');
+        $reward_unit = Config::get('app.mining_num_per_unit');
+        $progress = floatval(bcdiv($reward_unit, $secs_unit, 8));
+        $total = UserCoin::where(['uid' => $uid, 'symbol' => $symbol])->value('balance');
+        $icon = DictCoin::where('symbol', $symbol)->value('icon');
+        $suc = UserHash::mining_set($uid, [
+            'symbol'=> $symbol,
+            'icon' => $icon,
+            'price' => 0,
+            'progress' => $progress,
+            'total' => $total,
+            'ts' => time(),
+            'order_id' => $orderId,
+        ]);
 
-    }
+        if (!$suc) {
+            return JsonService::fail('未成功执行');
+        }
 
-    public function history() {
+        return JsonService::successful();
+    }
 
+    /**
+     * 请求转账记录
+     */
+    public function history(Request $request) {
+        [$page, $limit] = UtilService::getMore([
+            ['page', 1],
+            ['limit', 20],
+        ], $request, true);
+        $uid = 1; // TODO
+        $rows = UserCoinTransfer::getUserTransferred($uid, $page, $limit);
+        return JsonService::successful($rows);
     }
 
-    public function updateAddr() {
+    /*
+     * 更新钱包地址
+     */
+    public function updateAddr(Request $request) {
+        list($symbol, $addr) = UtilService::postMore([
+            ['symbol', ''],
+            ['addr', ''],
+        ], $request, true);
+        if (!$symbol || !$addr) {
+            return JsonService::fail('参数不可为空');
+        }
+        if (!DictCoin::where('symbol', $symbol)->select()) {
+            return JsonService::fail('未找到目标');
+        }
 
+        $uid = $request->uid();
+        $suc = UserCoin::upsertAddr($uid, $symbol, $addr);
+        if (!$suc) {
+            return JsonService::fail('执行失败');
+        }
+        return JsonService::successful();
     }
 
-    public function transfer() {
+    /*
+     * 提现
+     */
+    public function transfer(Request $request) {
+        list($symbol, $amount) = UtilService::postMore([
+            ['symbol', ''],
+            ['amount', 0.0]
+        ], $request, true);
+
+        if(!$symbol) {
+            return JsonService::fail('参数不可为空');
+        }
+        $meta = DictCoin::where('symbol', $symbol)->select();
+        if (!$meta) {
+            return JsonService::fail('未找到目标');
+        }
+        $meta = $meta->toArray();
+        if ($amount < $meta['min_withdrawal']){
+            return JsonService::fail('未达到最低限额');
+        }
+        $uid = $request->uid();
+        $userCoin = UserCoin::where(['uid'=>$uid, 'symbol'=>$symbol])->select();
+        if (!$userCoin) {
+            return JsonService::fail('不适用的用户');
+        }
+        $userCoin = $userCoin->toArray();
+        if(!$userCoin['addr']) {
+            return JsonService::fail('地址未设置');
+        }
+        if ($userCoin['balance'] < $amount) {
+            return JsonService::fail('余额不足');
+        }
+        $transferred = UserCoinTransfer::hasTransferred($uid, $symbol, $userCoin['addr']);
+        if (!$transferred) {
+            return JsonService::fail('首次操作需联系客服进行');
+        }
+        // transfer
+        UserCoin::beginTrans();
+        $left = bcsub($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 JsonService::fail('执行失败');
+        }
+        return JsonService::successful('成功,已开始审核');
     }
 }

+ 34 - 52
app/api/controller/user/UserNotificationController.php

@@ -1,12 +1,15 @@
 <?php
 namespace app\api\controller\user;
 
+use app\models\coin\UserCoinTransfer;
+use app\models\redis\SystemCarousel;
+use app\models\redis\UserHash;
+use app\models\system\DictCoin;
 use app\Request;
-use crmeb\services\UtilService;
-use crmeb\utils\Redis;
+use crmeb\services\JsonService;
 use think\facade\Config;
 use think\facade\Log;
-
+use think\facade\Cache;
 /**
  *
  * activities json 配置:
@@ -27,61 +30,35 @@ class UserNotificationController {
      */
     public function snapshot(Request $request) {
         $uid = 1;
-        $redis = Redis::instance();
         // 未读消息
-        $unread = intval(Redis::hGet('user:'.$uid, 'unread')??0);
+        $unread = UserHash::unread_get($uid);
 
         // 是否开启挖矿
-        $symbo = Config::get('app.mining_symbo');
-        $icon = Config::get('app.mining_symbo_icon');
-
-        /*
-         最近挖矿状态
-         hash key  user:<uid> field: mining
-
-         格式:
-            {
-                "progress": 10,
-                "symbol": "etc",
-                "icon": "http://x.png",
-                "price": 20,
-                "total": 20
-            }
-         */
-
+        $symbol = Config::get('app.mining_symbo');
+        $icon = Cache::get($symbol);
+        if (!$icon) {
+            $icon = DictCoin::where('symbol', $symbol)->value('icon');
+            Cache::set($symbol, $icon);
+        }
+        
         $defStatus = [
             'progress'=> 0,
-            'symbol'=>$symbo,
+            'symbol'=>$symbol,
             'icon'=>$icon,
             'total'=> 0,
             'ts' => time(),
         ];
-        if ($symbo) {
-            $smymining = Redis::hGet('user:'.$uid, 'mining');
-            $mymining = json_decode($smymining, true) ?? $defStatus;
+        if ($symbol) {
+            $mymining = UserHash::mining_get($uid) ?? $defStatus;
             if ($mymining['progress'] > 0) {
-                $mymining = $this->calcMining($mymining);
+                $mining = $this->calcMining($uid, $mymining);
+                UserHash::mining_set($uid, $mining);
             }
         }
+        // 跑马灯
+        $carousel = SystemCarousel::getFirst(20);
 
-        /*
-        跑马灯
-        列表: 键 sys:carousel
-
-        格式:
-            {
-                 "text": "<span style=\"color:2343;\"></span>",
-                 "uri": "page/boards",
-            }
-
-         */
-        $arrStr = Redis::lRange('sys:carousel', 0, 10)??[];
-        $carousel = [];
-        foreach ($arrStr as $str) {
-            array_push($carousel, json_decode($str, true));
-        }
-
-        return app('json')->successful(compact('unread', 'mymining', 'carousel'));
+        return JsonService::successful(compact('unread', 'mining', 'carousel'));
     }
 
     /**
@@ -89,7 +66,7 @@ class UserNotificationController {
      * @param $p
      * @return mixed
      */
-    protected function calcMining(&$p) {
+    protected function calcMining($uid, $p) {
         if ($p['progress'] <= 0) {
             return $p;
         }
@@ -98,26 +75,31 @@ class UserNotificationController {
         }
         $now = time();
         $secs_passed = $now - $p['ts'];
-        $reward_unit = Config::get('app.mining_num_per_5_sec');
+        $secs_unit = Config::get('app.mining_sec_unit');
+        $reward_unit = Config::get('app.mining_num_per_unit');
         $hours = Config::get('app.mining_time');
-        if (count($hours) != 2 || $hours[1] < $hours[0]) {
+        if (count($hours) != 2 || $hours[0] > $hours[1]) {
             Log::warning('app.mining_time config error.');
             return $p;
         }
-        $hour = random_int($hours[1], $hours[0]);
+        $hour = random_int($hours[0], $hours[1]);
         $secs = $hour * 60 * 60;
         //
         if ($secs_passed >= $secs) { // 挖矿结束
             $p['progress'] = 0;
 
             // 本次个数
-            $count = floatval(bcmul(bcdiv($secs, 5.0, 2), $reward_unit, 2));
-            // TODO save to database
+            $count = floatval(bcmul(bcdiv($secs, $secs_unit, 8), $reward_unit, 8));
+            // save to database
+            if (!UserCoinTransfer::addMining($uid, $p['order_id'], $p['symbol'], $count)) {
+                Log::error("user<$uid> save transfer failed, amount<$count>");
+            }
+
             $p['total'] += $count;
             return $p;
         }
 
-        $count = floatval(bcmul(bcdiv($secs_passed, 5.0, 2), $reward_unit, 2));
+        $count = floatval(bcmul(bcdiv($secs_passed, $secs_unit, 8), $reward_unit, 8));
         $p['progress'] = $count;
 
         return $p;

+ 92 - 0
app/models/coin/UserCoinTransfer.php

@@ -0,0 +1,92 @@
+<?php
+namespace app\models\coin;
+
+use crmeb\traits\ModelTrait;
+use crmeb\basic\BaseModel;
+
+class UserCoinTransfer extends BaseModel {
+
+    use ModelTrait;
+
+    /**
+     * 增加一条挖矿记录
+     *
+     * @param $uid
+     * @param $orderId
+     * @param $symbol
+     * @param $amount
+     * @return UserCoinTransfer|\think\Model
+     */
+    public static function addMining($uid, $orderId, $symbol, $amount) {
+        $data = [
+            'uid' => $uid,
+            'order_id' => $orderId,
+            'symbol' => $symbol,
+            'amount' => $amount,
+            'ts' => time(),
+        ];
+
+        return self::create($data);
+    }
+
+    /**
+     * 增加一条提现记录
+     *
+     * @param $uid
+     * @param $symbol
+     * @param $amount
+     */
+    public static function withdrawal($uid, $symbol, $to, $amount) {
+        return self::create([
+            'uid' => $uid,
+            'symbol' => $symbol,
+            'to' => $to,
+            'amount' => $amount,
+            'ts' => time(),
+            'out' => 1,
+            'status' => 1,
+        ]);
+    }
+
+    /**
+     * 确认提币操作
+     *
+     * @param $id
+     * @return UserCoinTransfer
+     */
+    public static function confirmWithdrawal($id) {
+        return self::where('id', $id)->update(['status'=>0]);
+    }
+
+    /**
+     * 取得用户记录
+     *
+     * @param $uid
+     * @param $page
+     * @param int $limit
+     * @return array
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\DbException
+     * @throws \think\db\exception\ModelNotFoundException
+     */
+    public static function getUserTransferred($uid, $page, $limit=20) {
+        $res = self::where('uid', $uid)->order('ts desc')
+            ->alias('t')->join('dict_coin c', 'c.symbol=t.symbol')
+            ->field('t.symbol, c.icon, t.from, t.to, t.amount, t.out, t.ts')
+            ->limit(($page-1) * $limit, $limit)->select();
+        return $res ? $res->toArray() : [];
+    }
+
+    /**
+     * @param $uid
+     * @param $symbol
+     * @return \think\Collection
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\DbException
+     * @throws \think\db\exception\ModelNotFoundException
+     */
+    public static function hasTransferred($uid, $symbol, $addr) {
+        $row = self::where(['uid'=>$uid, 'symbol'=>$symbol, 'to'=>$addr, 'out'=>1])->limit(1)->select();
+        return $row;
+    }
+}

+ 46 - 0
app/models/redis/SystemCarousel.php

@@ -0,0 +1,46 @@
+<?php
+namespace app\models\redis;
+
+use crmeb\utils\Redis;
+
+/**
+ * Class SystemCarousel
+ * @package app\models\redis
+ */
+class SystemCarousel {
+
+    protected static $KEY = 'sys:carousel';
+
+    /*
+     格式:
+            {
+                 "text": "<span style=\"color:2343;\"></span>",
+                 "uri": "page/boards",
+            }
+     uri 为客户端路由, 用于客户端跳转
+     */
+    public static function add($text, $uri='') {
+        $val = json_encode([
+            'text'=> $text,
+            'uri' => $uri,
+        ]);
+
+        Redis::instance();
+        return Redis::lPush(self::$KEY, $val);
+    }
+
+    public static function getFirst($n) {
+        Redis::instance();
+        $arrStr = Redis::lRange(self::$KEY, 0, $n) ?? '[]';
+        $rv = [];
+        foreach ($arrStr as $str) {
+            $rv[] = json_decode($str);
+        }
+        return $rv;
+    }
+
+    public static function removeTrash($start) {
+        Redis::instance();
+        return Redis::lTrim(self::$KEY, 0, $start);
+    }
+}

+ 60 - 0
app/models/redis/UserHash.php

@@ -0,0 +1,60 @@
+<?php
+namespace app\models\redis;
+
+use crmeb\utils\Redis;
+use think\Log;
+
+/**
+ * Class UserHash
+ * @package app\models\redis
+ */
+class UserHash {
+    protected static $FIELD_UNREAD = 'unread';
+    protected static $FIELD_MINING = 'mining';
+
+    public static function key($uid) {
+        return 'user:'.$uid;
+    }
+
+    public static function unread_get($uid) {
+        Redis::instance();
+        return intval(Redis::hGet(self::key($uid), self::$FIELD_UNREAD)??0);
+    }
+
+    public static function unread_incr($uid, $by=1) {
+        Redis::instance();
+        return Redis::hIncrBy(self::key($uid), self::$FIELD_UNREAD, $by);
+    }
+
+    /*
+     格式:
+            {
+                "progress": 10,
+                "symbol": "etc",
+                "icon": "http://x.png",
+                "price": 20,
+                "ts": 1234,
+                "order_id": 12343,
+                "total": 20
+            }
+     */
+    public static function mining_get($uid) {
+        Redis::instance();
+        $sval = Redis::hGet(self::key($uid), self::$FIELD_MINING);
+        if ($sval) {
+            return json_decode($sval, true);
+        }
+        return $sval;
+    }
+
+    public static function mining_set($uid, $mining) {
+        if (!isset($mining['ts']) || !isset($mining['symbol']) ||
+        !isset($mining['progress']) || !isset($mining['order_id'])) {
+            Log::error('invalid mining value');
+            return false;
+        }
+
+        Redis::instance();
+        return Redis::hSet(self::key($uid), self::$FIELD_MINING, json_encode($mining));
+    }
+}

+ 21 - 14
app/models/store/StoreOrderCartInfo.php

@@ -25,11 +25,6 @@ class StoreOrderCartInfo extends BaseModel
 
     use ModelTrait;
 
-    public static function getCartInfoAttr($value)
-    {
-        return json_decode($value,true)?:[];
-    }
-
     public static function setCartInfo($oid,array $cartInfo)
     {
         $group = [];
@@ -45,15 +40,27 @@ class StoreOrderCartInfo extends BaseModel
         return self::setAll($group);
     }
 
-    public static function getProductNameList($oid)
-    {
-        $cartInfo = self::where('oid',$oid)->select();
-        $goodsName = [];
-        foreach ($cartInfo as $cart){
-            $suk = isset($cart['cart_info']['productInfo']['attrInfo']) ? '('.$cart['cart_info']['productInfo']['attrInfo']['suk'].')' : '';
-            $goodsName[] = $cart['cart_info']['productInfo']['store_name'].$suk;
-        }
-        return $goodsName;
+    /**
+     * 得到还没参加挖矿的一个订单号
+     *
+     * @param $uid
+     * @return mixed
+     */
+    public static function getMiningOrderId($uid) {
+        $row = self::where('so.uid', $uid)
+            ->where('so.paid', 1)
+            ->where('ci.mine', 0)
+            ->alias('ci')->join('store_order so', 'ci.oid=so.id')->field('ci.oid')->limit(1)->value('oid');
+        return $row;
     }
 
+    /**
+     * 标记为已参加挖矿
+     *
+     * @param $orderId
+     * @return StoreOrderCartInfo
+     */
+    public static function setMining($orderId) {
+        return self::where('oid', $orderId)->update(['mine'=>1]);
+    }
 }

+ 10 - 0
app/models/system/DictCoin.php

@@ -0,0 +1,10 @@
+<?php
+namespace app\models\system;
+
+use crmeb\traits\ModelTrait;
+use crmeb\basic\BaseModel;
+
+class DictCoin extends BaseModel {
+
+    use ModelTrait;
+}

+ 24 - 0
app/models/user/UserCoin.php

@@ -0,0 +1,24 @@
+<?php
+namespace app\models\user;
+
+use crmeb\traits\ModelTrait;
+use crmeb\basic\BaseModel;
+
+class UserCoin extends BaseModel {
+
+    use ModelTrait;
+
+    public static function upsertAddr($uid, $symbol, $addr) {
+        $cond = ['uid'=>$uid, 'symbol'=>$symbol];
+        $row = self::where($cond)->select();
+        if ($row) {
+            return self::where($cond)->update(['addr'=>$addr]);
+        }else{
+            return self::create([
+                'uid'=>$uid,
+                'symbol'=>$symbol,
+                'addr'=>$addr,
+            ]);
+        }
+    }
+}

+ 10 - 0
app/models/user/UserExt.php

@@ -0,0 +1,10 @@
+<?php
+namespace app\models\user;
+
+use crmeb\traits\ModelTrait;
+use crmeb\basic\BaseModel;
+
+class UserExt extends BaseModel {
+
+    use ModelTrait;
+}

+ 11 - 9
app/models/user/UserNotice.php

@@ -4,7 +4,6 @@
  * @author: xaboy<365615158@qq.com>
  * @day: 2017/11/11
  */
-
 namespace app\models\user;
 
 use app\admin\model\user\UserNoticeSee;
@@ -102,15 +101,18 @@ class UserNotice extends BaseModel
      * @param $nids
      */
     public static function seeNotice($uid, $nids) {
+        $rows = [];
         foreach ($nids as $nid) {
-            try {
-                $data["nid"] = $nid;
-                $data["uid"] = $uid;
-                $data["add_time"] = time();
-                UserNoticeSee::create($data);
-            } catch(\Exception $e) {
-                Log::warning("seeNotice exception.sql=".UserNoticeSee::getLastSql());
-            }
+            $rows[] = [
+                'nid' => $nid,
+                'uid' => $uid,
+                'add_time' => time(),
+            ];
+        }
+        try {
+            UserNoticeSee::setAll($rows);
+        } catch(\Exception $e) {
+            Log::warning("seeNotice exception.sql=".UserNoticeSee::getLastSql());
         }
     }
 

+ 5 - 2
config/app.php

@@ -53,6 +53,9 @@ return [
     'mining_symbo' => 'doge',
     'mining_symbo_icon' => '',
     'mining_time' => [12, 24],
-    // 每 5 sec 挖币个数
-    'mining_num_per_5_sec' => 0.01,
+    // 每 @mining_sec_unit 挖币 @mining_num_per_unit
+    'mining_sec_unit' => 5,
+    'mining_num_per_unit' => 0.01,
+
+    //
 ];

+ 3 - 3
config/queue.php

@@ -10,7 +10,7 @@
 // +----------------------------------------------------------------------
 
 return [
-    'default'     => 'database',
+    'default'     => 'redis',
     'connections' => [
         'sync'     => [
             'driver' => 'sync',
@@ -25,8 +25,8 @@ return [
             'queue'      => 'default',
             'host'       => '127.0.0.1',
             'port'       => 6379,
-            'password'   => '',
-            'select'     => 0,
+            'password'   => '123456',
+            'select'     => 3,
             'timeout'    => 0,
             'persistent' => false,
         ],