Browse Source

初始化

lh 1 year ago
commit
d029519094
100 changed files with 13707 additions and 0 deletions
  1. 18 0
      .editorconfig
  2. 52 0
      .env.example
  3. 10 0
      .gitattributes
  4. 16 0
      .gitignore
  5. 14 0
      .styleci.yml
  6. 8 0
      README.md
  7. 56 0
      app/Cache/CacheKeys.php
  8. 94 0
      app/Cache/FinanceCache.php
  9. 235 0
      app/Cache/StatisticCache.php
  10. 271 0
      app/Cache/UserCache.php
  11. 171 0
      app/Client/Site.php
  12. 171 0
      app/Console/Book/BookSpiderAfter.php
  13. 107 0
      app/Console/Book/ReadRecordStatsCommand.php
  14. 190 0
      app/Console/Book/SyncBooksFromZwContent.php
  15. 210 0
      app/Console/Book/SyncChaptersFromZwContent.php
  16. 125 0
      app/Console/Channel/ChannelDayStatistics.php
  17. 277 0
      app/Console/Channel/RegisterChannel.php
  18. 44 0
      app/Console/Channel/RegisterChannelUser.php
  19. 54 0
      app/Console/DyReport/ResetSendOrderReportDataCommand.php
  20. 151 0
      app/Console/DyReport/SendOrderDayStatsCommand.php
  21. 128 0
      app/Console/DyReport/SendOrderRechargeDayStatsCommand.php
  22. 77 0
      app/Console/Kernel.php
  23. 104 0
      app/Console/Pay/RefundCommand.php
  24. 196 0
      app/Console/Pay/TransFundCommand.php
  25. 112 0
      app/Console/Subscribe/BookOrderByDayCommand.php
  26. 208 0
      app/Console/Sync/SyncBooks.php
  27. 425 0
      app/Console/Sync/SyncChapters.php
  28. 53 0
      app/Console/Test/ChangeUserChannelId.php
  29. 32 0
      app/Console/Test/TestCache.php
  30. 111 0
      app/Console/Test/TestCommand.php
  31. 44 0
      app/Console/Test/TestOpenApi.php
  32. 42 0
      app/Console/Test/TestSms.php
  33. 56 0
      app/Console/Test/TestZfbTransFundCommand.php
  34. 80 0
      app/Console/TikTok/AccessTokenManage.php
  35. 79 0
      app/Console/TikTok/ClientTokenManage.php
  36. 84 0
      app/Consts/BaseConst.php
  37. 17 0
      app/Consts/BillConst.php
  38. 144 0
      app/Consts/CoinConst.php
  39. 84 0
      app/Consts/ErrorConst.php
  40. 70 0
      app/Consts/FinanceConsts.php
  41. 8 0
      app/Consts/SysConst.php
  42. 65 0
      app/Dao/Account/AccountDao.php
  43. 159 0
      app/Dao/Bank/BankDao.php
  44. 117 0
      app/Dao/Bank/WithdrawDao.php
  45. 40 0
      app/Dao/Channel/ChannelDao.php
  46. 102 0
      app/Dao/Channel/ChannelUserDao.php
  47. 126 0
      app/Dao/Order/OrderDao.php
  48. 39 0
      app/Dao/Report/ReportDao.php
  49. 24 0
      app/Dao/SendOrder/SendOrderDao.php
  50. 86 0
      app/Dao/Settlement/BillDao.php
  51. 17 0
      app/Dao/Settlement/FinancialDao.php
  52. 31 0
      app/Dao/Sms/SmsDao.php
  53. 109 0
      app/Dao/User/UserDao.php
  54. 19 0
      app/Exceptions/ApiException.php
  55. 96 0
      app/Exceptions/Handler.php
  56. 33 0
      app/Facade/Site.php
  57. 91 0
      app/Http/Controllers/Bank/BankController.php
  58. 164 0
      app/Http/Controllers/Book/BookController.php
  59. 200 0
      app/Http/Controllers/Channel/ChannelHomeController.php
  60. 82 0
      app/Http/Controllers/Channel/ChannelUserController.php
  61. 13 0
      app/Http/Controllers/Controller.php
  62. 80 0
      app/Http/Controllers/Home/HomeController.php
  63. 290 0
      app/Http/Controllers/Order/OrderController.php
  64. 213 0
      app/Http/Controllers/Pay/PayController.php
  65. 77 0
      app/Http/Controllers/Pay/ProductController.php
  66. 116 0
      app/Http/Controllers/Settlement/SettlementController.php
  67. 288 0
      app/Http/Controllers/User/UserController.php
  68. 38 0
      app/Http/Controllers/Webhook/WebhookController.php
  69. 72 0
      app/Http/Kernel.php
  70. 21 0
      app/Http/Middleware/Authenticate.php
  71. 30 0
      app/Http/Middleware/BindExportToken.php
  72. 29 0
      app/Http/Middleware/BindToken.php
  73. 92 0
      app/Http/Middleware/CheckBook.php
  74. 28 0
      app/Http/Middleware/CheckLogin.php
  75. 71 0
      app/Http/Middleware/CheckSign.php
  76. 41 0
      app/Http/Middleware/CheckTokenTrait.php
  77. 17 0
      app/Http/Middleware/EncryptCookies.php
  78. 17 0
      app/Http/Middleware/PreventRequestsDuringMaintenance.php
  79. 32 0
      app/Http/Middleware/RedirectIfAuthenticated.php
  80. 19 0
      app/Http/Middleware/TrimStrings.php
  81. 20 0
      app/Http/Middleware/TrustHosts.php
  82. 28 0
      app/Http/Middleware/TrustProxies.php
  83. 17 0
      app/Http/Middleware/VerifyCsrfToken.php
  84. 109 0
      app/Jobs/ReportDy.php
  85. 41 0
      app/Libs/AliOSS.php
  86. 86 0
      app/Libs/AliSMS.php
  87. 185 0
      app/Libs/ApiResponse.php
  88. 60 0
      app/Libs/BatchUpdateTrait.php
  89. 1592 0
      app/Libs/Helpers.php
  90. 195 0
      app/Libs/OSS.php
  91. 183 0
      app/Libs/Pays/AliPaySdk/aop/AlipayConfig.php
  92. 188 0
      app/Libs/Pays/AliPaySdk/aop/AlipayMobilePublicMultiMediaClient.php
  93. 115 0
      app/Libs/Pays/AliPaySdk/aop/AlipayMobilePublicMultiMediaExecute.php
  94. 1303 0
      app/Libs/Pays/AliPaySdk/aop/AopCertClient.php
  95. 527 0
      app/Libs/Pays/AliPaySdk/aop/AopCertification.php
  96. 1334 0
      app/Libs/Pays/AliPaySdk/aop/AopClient.php
  97. 78 0
      app/Libs/Pays/AliPaySdk/aop/AopEncrypt.php
  98. 18 0
      app/Libs/Pays/AliPaySdk/aop/EncryptParseItem.php
  99. 16 0
      app/Libs/Pays/AliPaySdk/aop/EncryptResponseData.php
  100. 0 0
      app/Libs/Pays/AliPaySdk/aop/NewAopEncrypt.php

+ 18 - 0
.editorconfig

@@ -0,0 +1,18 @@
+root = true
+
+[*]
+charset = utf-8
+end_of_line = lf
+insert_final_newline = true
+indent_style = space
+indent_size = 4
+trim_trailing_whitespace = true
+
+[*.md]
+trim_trailing_whitespace = false
+
+[*.{yml,yaml}]
+indent_size = 2
+
+[docker-compose.yml]
+indent_size = 4

+ 52 - 0
.env.example

@@ -0,0 +1,52 @@
+APP_NAME=Laravel
+APP_ENV=local
+APP_KEY=
+APP_DEBUG=true
+APP_URL=http://localhost
+
+LOG_CHANNEL=stack
+LOG_DEPRECATIONS_CHANNEL=null
+LOG_LEVEL=debug
+
+DB_CONNECTION=mysql
+DB_HOST=127.0.0.1
+DB_PORT=3306
+DB_DATABASE=laravel
+DB_USERNAME=root
+DB_PASSWORD=
+
+BROADCAST_DRIVER=log
+CACHE_DRIVER=file
+FILESYSTEM_DRIVER=local
+QUEUE_CONNECTION=sync
+SESSION_DRIVER=file
+SESSION_LIFETIME=120
+
+MEMCACHED_HOST=127.0.0.1
+
+REDIS_HOST=127.0.0.1
+REDIS_PASSWORD=null
+REDIS_PORT=6379
+
+MAIL_MAILER=smtp
+MAIL_HOST=mailhog
+MAIL_PORT=1025
+MAIL_USERNAME=null
+MAIL_PASSWORD=null
+MAIL_ENCRYPTION=null
+MAIL_FROM_ADDRESS=null
+MAIL_FROM_NAME="${APP_NAME}"
+
+AWS_ACCESS_KEY_ID=
+AWS_SECRET_ACCESS_KEY=
+AWS_DEFAULT_REGION=us-east-1
+AWS_BUCKET=
+AWS_USE_PATH_STYLE_ENDPOINT=false
+
+PUSHER_APP_ID=
+PUSHER_APP_KEY=
+PUSHER_APP_SECRET=
+PUSHER_APP_CLUSTER=mt1
+
+MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
+MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"

+ 10 - 0
.gitattributes

@@ -0,0 +1,10 @@
+* text=auto
+
+*.blade.php diff=html
+*.css diff=css
+*.html diff=html
+*.md diff=markdown
+*.php diff=php
+
+/.github export-ignore
+CHANGELOG.md export-ignore

+ 16 - 0
.gitignore

@@ -0,0 +1,16 @@
+/node_modules
+/public/hot
+/public/storage
+/storage/*.key
+/vendor
+composer.lock
+.env
+.env.backup
+.phpunit.result.cache
+docker-compose.override.yml
+Homestead.json
+Homestead.yaml
+npm-debug.log
+yarn-error.log
+/.idea
+/.vscode

+ 14 - 0
.styleci.yml

@@ -0,0 +1,14 @@
+php:
+  preset: laravel
+  version: 8
+  disabled:
+    - no_unused_imports
+  finder:
+    not-name:
+      - index.php
+      - server.php
+js:
+  finder:
+    not-name:
+      - webpack.mix.js
+css: true

+ 8 - 0
README.md

@@ -0,0 +1,8 @@
+### 相关运维sql查询
+```
+查询书籍排序重复的问题
+select bid,count(*) from chapters WHERE sequence = 1 group by bid having count(bid)>1;
+
+查询书籍完本统计
+SELECT COUNT(*), `status` FROM books GROUP BY `status`;
+```

+ 56 - 0
app/Cache/CacheKeys.php

@@ -0,0 +1,56 @@
+<?php
+
+/**
+ * Created by PhpStorm.
+ * User: wangchen
+ * Date: 2019-05-07
+ * Time: 14:48
+ */
+
+namespace App\Cache;
+
+
+class CacheKeys
+{
+    /**
+     * 所有key的管理配置
+     *
+     * key规范:类型+模块+功能+参数
+     *
+     * H:   表示Hash类型
+     * Ss:  表示SortedSet类型
+     * S:   表示Set集合
+     * L:   表示List类型
+     * Str: 表示String类型
+     *
+     * @var array
+     */
+    public static $all = [
+        'user'      => [
+            'token'           => 'u_token_%s',                    // %s为token值
+            'recent_books'    => 'u_recent_books_%s',             // 最近阅读书籍(%s为uid)
+            'current_chapter' => 'u_current_book_and_chapter_%s', // 当前阅读章节(%s为uid)
+            'share_num'       => 'u_share_book_%s',               // 每日分享数(%s为uid)
+        ],
+        'statistic' => [
+            'subscribe_books'           => 's_subscribe_books_%s',  // 书籍订阅统计(%s为日期Ymd)
+            'charge_list_pv'            => 's_charge_list_pv_%s',   // 充值档位的pv(%s为日期Ymd)
+            'charge_list_uv'            => 's_charge_list_uv_%s',   // 充值档位的uv(%s为日期Ymd)
+            'template_uv'               => 's_template_uv_%s',      // 充值模板的uv(%s为模板id)
+            'add_shelf_pv'              => 'add_shelf_pv_%s',       // 加入书架的pv(%s为日期Ymd)
+            'add_shelf_uv'              => 'add_shelf_uv_%s',       // 加入书架的uv(%s为日期Ymd)
+            'set_url_link_pv'           => 'set_url_link_pv_%s',    // 生成分享链接参数的pv(%s为日期Ymd)
+            'set_url_link_uv'           => 'set_url_link_uv_%s',    // 生成分享链接参数的uv(%s为日期Ymd)
+            'set_dy_link_uv'            => 'set_dy_link_uv_%s',     // 生成外网分享链接的uv(%s为日期Ymd)
+            'set_dy_link_pv'            => 'set_dy_link_pv_%s',     // 生成外网分享链接的uv(%s为日期Ymd)
+            'welfare_pv'                => 'welfare_pv_%s',         // 福利页的pv(%s为日期Ymd)
+            'welfare_uv'                => 'welfare_uv_%s',         // 福利页的uv(%s为日期Ymd)
+            'book_list_pv'              => 'book_list_pv_%s',       // 书籍搜索页的pv(%s为日期Ymd)
+            'book_list_uv'              => 'book_list_uv_%s',       // 书籍搜索页的uv(%s为日期Ymd)
+        ],
+		'finance'   => [
+            'sms_code'  => 'dy_sms_%s_%s',        // channelId phone
+            'with_draw' => 'dy_withdraw_cash:%s', // date('Ymd')
+        ],
+    ];
+}

+ 94 - 0
app/Cache/FinanceCache.php

@@ -0,0 +1,94 @@
+<?php
+
+
+namespace App\Cache;
+
+
+use App\Consts\BaseConst;
+use App\Libs\Utils;
+use Illuminate\Support\Facades\Cache;
+use Illuminate\Support\Facades\Redis;
+
+class FinanceCache
+{
+    /**
+     * 获取手机验证码
+     *
+     * @param $channelId
+     * @param $phone
+     * @return mixed
+     */
+    public static function getSmsCode($channelId, $phone)
+    {
+        $cacheKey = Utils::getCacheKey('finance.sms_code', [$channelId, $phone]);
+        return Redis::get($cacheKey);
+    }
+
+    /**
+     * 设置手机验证码
+     *
+     * @param $channelId
+     * @param $phone
+     * @param $code
+     * @return bool
+     */
+    public static function setSmsCode($channelId, $phone, $code)
+    {
+        $cacheKey = Utils::getCacheKey('finance.sms_code', [$channelId, $phone]);
+        Redis::set($cacheKey, $code);
+        $ttl = Redis::ttl($cacheKey);
+        if ((int)$ttl < 1) {
+            // 缓存设置10分钟
+            Redis::expire($cacheKey, 600);
+            Cache::put($cacheKey, $code, 10);
+        }
+
+        return true;
+    }
+
+    /**
+     * 删除验证码
+     *
+     * @param $channelId
+     * @param $phone
+     * @return mixed
+     */
+    public static function delSmsCode($channelId, $phone)
+    {
+        $cacheKey = Utils::getCacheKey('finance.sms_code', [$channelId, $phone]);
+        Cache::forget($cacheKey);
+        return Redis::del($cacheKey);
+    }
+
+    /**
+     * 添加渠道到当天提现队列
+     *
+     * @param $chanelId
+     * @param $ymd
+     * @return bool
+     */
+    public static function sAddChannelIdToWithDraw($chanelId, $ymd)
+    {
+        $cacheKey = Utils::getCacheKey('finance.with_draw', [$ymd]);
+        Redis::sAdd($cacheKey, $chanelId);
+        $ttl = Redis::ttl($cacheKey);
+        if ((int)$ttl < 1) {
+            Redis::expire($cacheKey, BaseConst::ONE_DAY_SECONDS);
+        }
+
+        return true;
+    }
+
+    /**
+     * 判别渠道是否在日期提现队列中
+     *
+     * @param $chanelId
+     * @param $ymd
+     * @return mixed
+     */
+    public static function checkChannelIdIsInWithDraw($chanelId, $ymd)
+    {
+        $cacheKey = Utils::getCacheKey('finance.with_draw', [$ymd]);
+        return Redis::sIsmember($cacheKey, $chanelId);
+    }
+}

+ 235 - 0
app/Cache/StatisticCache.php

@@ -0,0 +1,235 @@
+<?php
+
+
+namespace App\Cache;
+
+
+use App\Consts\ErrorConst;
+use App\Libs\Utils;
+use Illuminate\Support\Facades\Redis;
+
+class StatisticCache
+{
+    /**
+     * 以下传参的通用说明
+     * key_name的具体传参值从CacheKeys.php中获取,以statistic为前缀
+     * day的值默认为当天,可通过传不同日期增删改对应的值,day的格式为date('Ymd')
+     */
+
+
+    /**
+     * 获取pv
+     * @param $key_name
+     * @param $day
+     * @return mixed
+     */
+    public static function getPV($key_name, $day = ''): int
+    {
+        $key_name = 'statistic.' . $key_name . '_pv';
+        if (!$day) $day = date('Ymd');
+        $cacheKey = Utils::getCacheKey($key_name, [$day]);
+        if (!$cacheKey) Utils::throwError(ErrorConst::REDIS_KEY_NOT_EXIST);
+        $result = Redis::get($cacheKey);
+        return $result ? $result : 0;
+    }
+
+    /**
+     * 设置pv
+     *
+     * @param $key_name
+     * @param $day
+     * @return mixed
+     */
+    public static function setPV($key_name, $day = '')
+    {
+        $key_name = 'statistic.' . $key_name . '_pv';
+        if (!$day) $day = date('Ymd');
+        $cacheKey = Utils::getCacheKey($key_name, [$day]);
+        if (!$cacheKey) Utils::throwError(ErrorConst::REDIS_KEY_NOT_EXIST);
+        return Redis::incr($cacheKey);
+    }
+
+    /**
+     * 删除pv
+     *
+     * @param $key_name
+     * @param $day
+     * @return mixed
+     */
+    public static function delPV($key_name, $day = '')
+    {
+        $key_name = 'statistic.' . $key_name . '_pv';
+        if (!$day) $day = date('Ymd');
+        $cacheKey = Utils::getCacheKey($key_name, [$day]);
+        if (!$cacheKey) Utils::throwError(ErrorConst::REDIS_KEY_NOT_EXIST);
+        return Redis::del($cacheKey);
+    }
+
+    /**
+     * 获取uv
+     *
+     * @param $key_name
+     * @param $day
+     * @return mixed
+     */
+    public static function getUV($key_name, $day = ''): int
+    {
+        $key_name = 'statistic.' . $key_name . '_uv';
+        if (!$day) $day = date('Ymd');
+        $cacheKey = Utils::getCacheKey($key_name, [$day]);
+        if (!$cacheKey) Utils::throwError(ErrorConst::REDIS_KEY_NOT_EXIST);
+//        $result = Redis::pfcount($cacheKey);
+        $result = Redis::scard($cacheKey);
+        return $result ? $result : 0;
+    }
+
+    /**
+     * 设置uv
+     *
+     * @param $key_name
+     * @param $day
+     * @param $uid
+     * @return mixed
+     */
+    public static function setUV($key_name, $uid, $day = '')
+    {
+        $key_name = 'statistic.' . $key_name . '_uv';
+        if (!$day) $day = date('Ymd');
+        $cacheKey = Utils::getCacheKey($key_name, [$day]);
+        if (!$cacheKey) Utils::throwError(ErrorConst::REDIS_KEY_NOT_EXIST);
+//        return Redis::pfadd($cacheKey, $uid);
+        return Redis::sadd($cacheKey, $uid);
+    }
+
+    /**
+     * 删除uv
+     *
+     * @param $key_name
+     * @param $day
+     * @return mixed
+     */
+    public static function delUV($key_name, $day = '')
+    {
+        $key_name = 'statistic.' . $key_name . '_uv';
+        if (!$day) $day = date('Ymd');
+        $cacheKey = Utils::getCacheKey($key_name, [$day]);
+        if (!$cacheKey) Utils::throwError(ErrorConst::REDIS_KEY_NOT_EXIST);
+        return Redis::del($cacheKey);
+    }
+
+    /**
+     * 获取模板uv
+     *
+     * @param $template_id
+     * @return mixed
+     */
+    public static function getTemplateUV($template_id): int
+    {
+        $key_name = 'statistic.template_uv';
+        $cacheKey = Utils::getCacheKey($key_name, [$template_id]);
+        if (!$cacheKey) Utils::throwError(ErrorConst::REDIS_KEY_NOT_EXIST);
+//        $result = Redis::pfcount($cacheKey);
+        $result = Redis::scard($cacheKey);
+        return $result ? $result : 0;
+    }
+
+    /**
+     * 设置模板uv
+     *
+     * @param $uid
+     * @param $template_id
+     * @return mixed
+     */
+    public static function setTemplateUV($uid, $template_id = 0)
+    {
+        $key_name = 'statistic.template_uv';
+        $cacheKey = Utils::getCacheKey($key_name, [$template_id]);
+        if (!$cacheKey) Utils::throwError(ErrorConst::REDIS_KEY_NOT_EXIST);
+//        return Redis::pfadd($cacheKey, $uid);
+        return Redis::sadd($cacheKey, $uid);
+    }
+
+    /**
+     * 删除模板uv
+     *
+     * @param $template_id
+     * @return mixed
+     */
+    public static function delTemplateUV($template_id = 0)
+    {
+        $key_name = 'statistic.template_uv';
+        $cacheKey = Utils::getCacheKey($key_name, [$template_id]);
+        if (!$cacheKey) Utils::throwError(ErrorConst::REDIS_KEY_NOT_EXIST);
+        return Redis::del($cacheKey);
+    }
+
+    /**
+     * 获取订阅统计总记录数目(按日)
+     *
+     * @param $day
+     * @return mixed
+     */
+    public static function getSubscribeCount($day = '')
+    {
+        if (!$day) $day = date('Ymd');
+        $cacheKey = Utils::getCacheKey('statistic.subscribe_books', [$day]);
+        if (!$cacheKey) Utils::throwError(ErrorConst::REDIS_KEY_NOT_EXIST);
+        $result = Redis::llen($cacheKey);
+        return $result ? $result : 0;
+    }
+
+    /**
+     * 获取订阅统计(按日)
+     *
+     * @param $day
+     * @param $start_index
+     * @param $end_index
+     * @return mixed
+     */
+    public static function getSubscribe($day = '', $start_index = 0, $end_index = -1)
+    {
+        if (!$day) $day = date('Ymd');
+        $cacheKey = Utils::getCacheKey('statistic.subscribe_books', [$day]);
+        if (!$cacheKey) Utils::throwError(ErrorConst::REDIS_KEY_NOT_EXIST);
+        $result = Redis::lrange($cacheKey, $start_index, $end_index);
+        return $result ? $result : [];
+    }
+
+    /**
+     * 设置订阅统计(按日)
+     *
+     * @param $data
+     * @param $day
+     * @return mixed
+     */
+    public static function setSubscribe(array $data, $day = '')
+    {
+        if (!$day) $day = date('Ymd');
+        $cacheKey = Utils::getCacheKey('statistic.subscribe_books', [$day]);
+        if (!$cacheKey) Utils::throwError(ErrorConst::REDIS_KEY_NOT_EXIST);
+        if (!Redis::exists($cacheKey)) {
+            $result = Redis::lpush($cacheKey, json_encode($data, true));
+            $seven_days_later = date('Y-m-d 23:59:59', strtotime('+7 day'));
+            $expire_at = strtotime($seven_days_later) - time();
+            Redis::expire($cacheKey, $expire_at);
+        } else {
+            $result = Redis::lpush($cacheKey, json_encode($data, true));
+        }
+
+        return $result;
+    }
+
+    /**
+     * 删除订阅统计(按日)
+     *
+     * @param $day
+     * @return mixed
+     */
+    public static function delSubscribe($day = '')
+    {
+        if (!$day) $day = date('Ymd');
+        $cacheKey = Utils::getCacheKey('statistic.subscribe_books', [$day]);
+        if (!$cacheKey) Utils::throwError(ErrorConst::REDIS_KEY_NOT_EXIST);
+        return Redis::del($cacheKey);
+    }
+}

+ 271 - 0
app/Cache/UserCache.php

@@ -0,0 +1,271 @@
+<?php
+
+
+namespace App\Cache;
+
+
+use App\Consts\BaseConst;
+use App\Libs\Utils;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Facades\Redis;
+
+class UserCache
+{
+    /**
+     * 获取token数据
+     * @param $token
+     * @return array|mixed
+     */
+    public static function getTokenData($token)
+    {
+        if (empty($token)) {
+            return [];
+        }
+
+        $cacheKey = Utils::getCacheKey('user.token', [$token]);
+        $result = Redis::get($cacheKey);
+        return $result ? json_decode($result, true) : [];
+    }
+
+    /**
+     * 设置token数据
+     *
+     * @param $token
+     * @param $data
+     * @return mixed
+     */
+    public static function setTokenData($token, $data)
+    {
+        if (empty($token)) {
+            return false;
+        }
+
+        $cacheKey = Utils::getCacheKey('user.token', [$token]);
+        return Redis::set($cacheKey, json_encode($data, JSON_UNESCAPED_UNICODE));
+    }
+
+    /**
+     * 删除token数据
+     *
+     * @param $token
+     * @return mixed
+     */
+    public static function delTokenData($token)
+    {
+        if (empty($token)) {
+            return false;
+        }
+
+        $cacheKey = Utils::getCacheKey('user.token', [$token]);
+        return Redis::del($cacheKey);
+    }
+
+    /**
+     * 设置最近阅读数据
+     * @param $uid
+     * @param $data
+     * @return false
+     */
+    public static function setRecentBooks($uid, $data) {
+        if (empty($uid)) {
+            return false;
+        }
+
+        $cacheKey = Utils::getCacheKey('user.recent_books', [$uid]);
+
+        if (!Redis::exists($cacheKey)) {
+            Redis::hset($cacheKey, $data['bid'], json_encode($data, JSON_UNESCAPED_UNICODE));
+            Redis::expire($cacheKey, BaseConst::ONE_WEEK_SECONDS);
+        }else {
+            Redis::hset($cacheKey, $data['bid'], json_encode($data, JSON_UNESCAPED_UNICODE));
+        }
+
+        return true;
+    }
+
+    /**
+     * 获取最近阅读数据(bid)
+     * @param $uid
+     * @param $bid
+     * @return array|false|mixed
+     */
+    public static function getRecentBooksByBid($uid, $bid) {
+        if (empty($uid)) {
+            return false;
+        }
+        $cacheKey = Utils::getCacheKey('user.recent_books', [$uid]);
+        if (Redis::hExists($cacheKey, $bid)) {
+            $json = Redis::hget($cacheKey, $bid);
+            return json_decode($json, true);
+        }else {
+            return [];
+        }
+    }
+
+    /**
+     * 获取最近阅读数据(keys)
+     * @param $uid
+     * @return array|mixed
+     */
+    public static function getRecentBooksKeys($uid) {
+        if (empty($uid)) {
+            return false;
+        }
+        $cacheKey = Utils::getCacheKey('user.recent_books', [$uid]);
+        $result = Redis::hkeys($cacheKey);
+        return $result ? $result : [];
+    }
+
+    /**
+     * 获取最近阅读数据(values)
+     * @param $uid
+     * @return array|mixed
+     */
+    public static function getRecentBooksValues($uid) {
+        if (empty($uid)) {
+            return false;
+        }
+        $cacheKey = Utils::getCacheKey('user.recent_books', [$uid]);
+        if (Redis::exists($cacheKey)) {
+            $result = Redis::hvals($cacheKey);
+            $data = [];
+            if ($result) {
+                foreach ($result as $item) {
+                    $data[] = json_decode($item, true);
+                }
+            }
+        }else {     // key不存在时取数据库
+            $table = 'record_records'.($uid % 2048);
+            $result = DB::connection('read_record_mysql')->table($table)->where('uid', $uid)->groupBy('bid')
+                ->select('bid',
+                    DB::raw("(select max(created_at) from {$table} as a where a.bid = {$table}.bid) as read_time"),
+                    DB::raw("(select max(sequence) from {$table} as a where a.bid = {$table}.bid) as sequence"))->get();
+            $data = [];
+            foreach ($result as $item) {
+                $book_chapter = DB::table('chapters as c')->leftJoin('books as b', 'b.id', 'c.bid')
+                    ->where(['c.bid'=>getProp($item, 'bid'), 'c.sequence'=>getProp($item, 'sequence'), 'c.is_check'=>1, 'c.is_deleted'=>0])
+                    ->select('c.bid', 'b.name as book_name', 'c.id as cid', 'c.name as chapter_name', 'c.sequence')->first();
+                if ($book_chapter) {
+                    $book_chapter = (array)$book_chapter;
+                    $book_chapter['read_time'] = getProp($item, 'read_time');
+                }
+
+                Redis::hset($cacheKey, $book_chapter['bid'], json_encode($book_chapter, JSON_UNESCAPED_UNICODE));
+
+                $data[] = $book_chapter;
+            }
+
+            Redis::expire($cacheKey, BaseConst::ONE_WEEK_SECONDS);
+        }
+
+        return $data;
+    }
+
+    /**
+     * 获取最近阅读数据(all)
+     * @param $uid
+     * @return mixed|array
+     */
+    public static function getRecentBooksAll($uid) {
+        if (empty($uid)) {
+            return false;
+        }
+        $cacheKey = Utils::getCacheKey('user.recent_books', [$uid]);
+        $result = Redis::hgetall($cacheKey);
+        return $result ? $result : [];
+    }
+
+    /**
+     * 删除最近阅读数据
+     * @param $uid
+     * @return mixed
+     */
+    public static function delRecentBooks($uid)
+    {
+        if (empty($uid)) {
+            return false;
+        }
+
+        $cacheKey = Utils::getCacheKey('user.recent_books', [$uid]);
+        return Redis::del($cacheKey);
+    }
+
+    /**
+     * 设置当前阅读章节
+     * @param $uid
+     * @param $data
+     * @return false
+     */
+    public static function setCurrentChapter($uid, $data) {
+        if (empty($uid)) {
+            return false;
+        }
+        $cacheKey = Utils::getCacheKey('user.current_chapter', [$uid]);
+        return Redis::set($cacheKey, json_encode($data, JSON_UNESCAPED_UNICODE));
+    }
+
+    /**
+     * 获取当前阅读章节
+     * @param $uid
+     * @return array|false|mixed
+     */
+    public static function getCurrentChapter($uid) {
+        if (empty($uid)) {
+            return false;
+        }
+        $cacheKey = Utils::getCacheKey('user.current_chapter', [$uid]);
+        $result = Redis::get($cacheKey);
+        return $result ? json_decode($result, true) : [];
+    }
+
+    /**
+     * 删除当前阅读章节
+     * @param $uid
+     * @return mixed
+     */
+    public static function delCurrentChapter($uid)
+    {
+        if (empty($uid)) {
+            return false;
+        }
+
+        $cacheKey = Utils::getCacheKey('user.current_chapter', [$uid]);
+        return Redis::del($cacheKey);
+    }
+
+    /**
+     * 设置今日好书分享数
+     * @param $uid
+     * @return bool
+     */
+    public static function setTodayShareNum($uid) {
+        if (empty($uid)) {
+            return false;
+        }
+
+        $cacheKey = Utils::getCacheKey('user.share_num', [$uid]);
+        if (Redis::exists($cacheKey)) {
+            Redis::incr($cacheKey);
+        }else {
+            $expire_at = strtotime(date('Y-m-d 23:59:59')) - time();
+            Redis::setex($cacheKey, $expire_at, 1);
+        }
+
+        return true;
+    }
+
+    /**
+     * 获取今日好书分享数
+     * @param $uid
+     * @return int
+     */
+    public static function getTodayShareNum($uid) {
+        if (empty($uid)) {
+            return 0;
+        }
+
+        $cacheKey = Utils::getCacheKey('user.share_num', [$uid]);
+        $share_num = Redis::get($cacheKey);
+        return $share_num ? $share_num : 0;
+    }
+}

+ 171 - 0
app/Client/Site.php

@@ -0,0 +1,171 @@
+<?php
+
+
+namespace App\Client;
+
+
+class Site
+{
+    protected $site;
+
+    public function __construct()
+    {
+        $this->site = app()->make('siteData');
+    }
+
+    /**
+     * 获取用户id
+     *
+     * @return mixed
+     */
+    public function getUid()
+    {
+        return (int)getProp($this->site, 'uid');
+    }
+
+    /**
+     * 获取用户channel_id
+     *
+     * @return mixed
+     */
+    public function getChannelId()
+    {
+        return (int)getProp($this->site, 'channel_id');
+    }
+
+    /**
+     * 获取站点类型
+     *
+     * @return mixed
+     */
+    public function getChannelType()
+    {
+        return (string)getProp($this->site, 'channel_type');
+    }
+
+    /**
+     * 获取站点充值模板id
+     *
+     * @return mixed
+     */
+    public function getChannelPayId()
+    {
+        return (int)getProp($this->site, 'channel_pay_id');
+    }
+
+    /**
+     * 获取用户send_order_id
+     *
+     * @return mixed
+     */
+    public function getSendOrderId()
+    {
+        return (int)getProp($this->site, 'send_order_id');
+    }
+
+    /**
+     * 获取用户from_uid
+     *
+     * @return mixed
+     */
+    public function getFromUid()
+    {
+        return (int)getProp($this->site, 'from_uid');
+    }
+
+    /**
+     * 获取用户token
+     *
+     * @return mixed
+     */
+    public function getToken()
+    {
+        return (string)getProp($this->site, 'token');
+    }
+
+    /**
+     * 获取用户account
+     *
+     * @return mixed
+     */
+    public function getAccount()
+    {
+        return (string)getProp($this->site, 'account');
+    }
+
+    /**
+     * 获取用户phone
+     *
+     * @return mixed
+     */
+    public function getPhone()
+    {
+        return (string)getProp($this->site, 'phone');
+    }
+
+    /**
+     * 获取用户阅读记录
+     *
+     * @return array
+     */
+    public function getRecentBooks()
+    {
+        return (array)getProp($this->site, 'recent_books');
+    }
+
+    /**
+     * 获取当前阅读章节
+     *
+     * @return array
+     */
+    public function getCurrentChapter()
+    {
+        return (array)getProp($this->site, 'current_chapter');
+    }
+
+    /**
+     * 获取当前阅读章节
+     *
+     * @return int
+     */
+    public function getRecentBid()
+    {
+        return (int)getProp($this->site, 'recent_bid');
+    }
+
+    /**
+     * @return int
+     */
+    public function getCurrentChannelId()
+    {
+        return (int)getProp($this->site, 'current_channel_id');
+    }
+
+    /**
+     * @return string
+     */
+    public function getCurrentChannelName()
+    {
+        return getProp($this->site, 'current_channel_name');
+    }
+
+    /**
+     * 设置全局返回报错数据
+     *
+     * @param $data
+     */
+    public function setErrorResponseData($data)
+    {
+        $this->site->errorResponseData = $data;
+    }
+
+    /**
+     * 获取全局返回报错数据
+     *
+     * @return mixed|string
+     */
+    public function getErrorResponseData()
+    {
+        return getProp($this->site, 'errorResponseData', []);
+    }
+}

+ 171 - 0
app/Console/Book/BookSpiderAfter.php

@@ -0,0 +1,171 @@
+<?php
+
+namespace App\Console\Book;
+
+use App\Libs\BatchUpdateTrait;
+use App\Models\Book\Book;
+use App\Models\Book\Chapter;
+use App\Models\Book\BookConfig;
+use Illuminate\Console\Command;
+
+class BookSpiderAfter extends Command
+{
+    use BatchUpdateTrait;
+
+    /**
+     * The name and signature of the console command.
+     *
+     * @var string
+     */
+    protected $signature = 'book:after:spider {--bid=}';
+
+    /**
+     * The console command description.
+     *
+     * @var string
+     */
+    protected $description = '书籍采集后续更新';
+
+    public function handle()
+    {
+        // 传参
+        $bid = (int)$this->option('bid');
+
+        // 获取书籍列表
+        $books = $this->getBooks($bid);
+
+        // 开始更新
+        $this->start($books);
+    }
+
+    /**
+     * @param $books
+     * @return void
+     */
+    private function start($books): void
+    {
+        if ($books->isEmpty()) {
+            return;
+        }
+
+        foreach ($books as $book) {
+            $bid = (int)getProp($book, 'id');
+            dLog('sync')->info('book:after:spider-start', compact('bid'));
+
+            $this->updateChapterPrevAndNextCid($bid);
+            $this->updateBookData($bid);
+
+            dLog('sync')->info('book:after:spider-end', compact('bid'));
+        }
+
+    }
+
+    /**
+     * 获取待更新书籍列表
+     *
+     * @param $bid
+     * @return mixed
+     */
+    private function getBooks($bid)
+    {
+        // 组装查询条件
+        $query = Book::select('id', 'zw_id', 'name')->where('zw_id', '>', '0');
+
+        // 此处bid为原创小程序bid
+        if ($bid) {
+            $query->where('books.id', $bid);
+        }
+
+        // 查询
+        return $query->get();
+    }
+
+    /**
+     * 更新书籍数据
+     *
+     * @param $bid
+     * @return bool
+     */
+    private function updateBookData($bid): bool
+    {
+        if (empty($bid)) {
+            return false;
+        }
+
+        // 基本查询条件
+        $where = ['bid' => $bid, 'is_check' => 1, 'is_draft' => 0, 'is_deleted' => 0];
+
+        // 获取首个章节
+        $firstChapter = Chapter::select('id')->where($where)->orderBy('sequence', 'asc')->first();
+
+        // 获取首个vip章节
+        $vipWhere           = $where;
+        $vipWhere['is_vip'] = 1;
+        $firstVipChapter    = Chapter::select('id', 'sequence')->where($vipWhere)->orderBy('sequence', 'asc')->first();
+
+        // 获取最新章节
+        $lastChapter = Chapter::select('id', 'name')->where($where)->orderBy('sequence', 'desc')->first();
+
+        // 获取章节总数
+        $count = Chapter::where($where)->count();
+
+        // 更新书籍相关数据
+        Book::where('id', $bid)->update([
+            'first_cid'     => (int)getProp($firstChapter, 'id'),
+            'last_cid'      => (int)getProp($lastChapter, 'id'),
+            'last_chapter'  => getProp($lastChapter, 'name'),
+            'chapter_count' => $count,
+            'updated_at'    => date('Y-m-d H:i:s')
+        ]);
+
+        // 更新书籍配置
+        BookConfig::where('bid', $bid)->update([
+            'vip_seq'    => (int)getProp($firstVipChapter, 'sequence'),
+            'updated_at' => date('Y-m-d H:i:s')
+        ]);
+
+        return true;
+    }
+
+    /**
+     * 更新章节上下章节id
+     *
+     * @param $bid
+     * @return bool
+     */
+    private function updateChapterPrevAndNextCid($bid): bool
+    {
+        if (empty($bid)) {
+            return false;
+        }
+
+        // 基本查询条件
+        $where    = ['bid' => $bid, 'is_check' => 1, 'is_draft' => 0, 'is_deleted' => 0];
+        $chapters = Chapter::select('id', 'sequence')->where($where)->orderBy('sequence', 'asc')->get();
+        if (empty($chapters)) {
+            return false;
+        }
+
+        // 组装待更新数据
+        $seqData = [];
+        foreach ($chapters as $chapter) {
+            $sequence = (int)getProp($chapter, 'sequence');
+            $seqData[$sequence] = ['id' => (int)getProp($chapter, 'id')];
+        }
+
+        // 批量更新
+        $updateData = [];
+        foreach ($seqData as $seq => $seqItem) {
+            $prevSeqData = getProp($seqData,$seq - 1, []);
+            $nextSeqData = getProp($seqData,$seq + 1, []);
+            $updateData[] = [
+                'id' => $seqItem['id'],
+                'prev_cid' => (int)getProp($prevSeqData, 'id'),
+                'next_cid' => (int)getProp($nextSeqData, 'id'),
+            ];
+        }
+        $this->batchUpdateDb('chapters', $updateData);
+
+        return true;
+    }
+}

+ 107 - 0
app/Console/Book/ReadRecordStatsCommand.php

@@ -0,0 +1,107 @@
+<?php
+namespace App\Console\Book;
+
+use Illuminate\Console\Command;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Facades\Redis;
+
+class ReadRecordStatsCommand extends Command
+{
+    /**
+     * The name and signature of the console command.
+     *
+     * @var string
+     */
+    protected $signature = 'Book:ReadRecordStats';
+
+    /**
+     * The console command description.
+     *
+     * @var string
+     */
+    protected $description = 'redis临时保存的阅读记录写入数据库';
+
+    /**
+     * Create a new command instance.
+     *
+     * @return void
+     */
+    public function __construct()
+    {
+        parent::__construct();
+    }
+
+    /**
+     * Execute the console command.
+     *
+     * @return mixed
+     */
+    public function handle()
+    {
+        $this->saveDB();
+    }
+
+    private function saveDB()
+    {
+        dLog('command_logs')->info('~~~~~~~~~~~~~~~~~~~~~~~~~~开始统计redis临时保存的阅读记录~~~~~~~~~~~~~~~~~~~~~~~~~~');
+        $executeStart = microtime(true);
+        // 获取数据
+        $data = $this->getRecord();
+        if (!$data) {
+            return false;
+        }
+
+        // 组装数据
+        $records = $bookRecords = [];
+        foreach ($data as $item) {
+            foreach ($item as $v) {
+                $record = json_decode($v, true);
+                if (!is_array($record) || empty($record)) {
+                    continue;
+                }
+
+                // record_records表数据
+                $table1 = $record['uid'] % 2048;
+                $records[$table1][] = $record;
+
+                // book_record_records表数据
+                $table2 = $record['bid'] % 2048;
+                $bookRecords[$table2][] = $record;
+            }
+        }
+
+        // 保存到record_records表
+        if ($records) {
+            foreach ($records as $key => $v) {
+                $table = sprintf('record_records%s', $key);
+                DB::connection('read_record_mysql')->table($table)->insert($v);
+            }
+        }
+
+        // 保存到book_record_records表
+        if ($bookRecords) {
+            foreach ($bookRecords as $key => $v) {
+                $table = sprintf('book_record_records%s', $key);
+                DB::connection('read_record_mysql')->table($table)->insert($v);
+            }
+        }
+        $executeEnd = microtime(true);
+        dLog('command_logs')->info('脚本运行时间: ', ['execute_time'=>round(($executeEnd - $executeStart), 6).'s']);
+        dLog('command_logs')->info('~~~~~~~~~~~~~~~~~~~~~~~~~~结束统计redis临时保存的阅读记录~~~~~~~~~~~~~~~~~~~~~~~~~~');
+    }
+
+    private function getRecord()
+    {
+        $length = Redis::Llen('read_record_stats');
+        if (!$length) return [];
+        $count = ceil($length / 200);
+        $result = [];
+        for ($i = 0; $i < $count; $i++) {
+            $start = $i * 200;
+            $end = $start + 199;
+            $result[] = Redis::Lrange('read_record_stats', $start, $end);
+        }
+        Redis::del('read_record_stats');
+        return $result;
+    }
+}

+ 190 - 0
app/Console/Book/SyncBooksFromZwContent.php

@@ -0,0 +1,190 @@
+<?php
+
+namespace App\Console\Book;
+
+use App\Models\Book\Book;
+use App\Models\Book\BookConfig;
+use App\Models\Book\Chapter;
+use App\Models\Book\ChapterContent;
+use GuzzleHttp\Client;
+use Illuminate\Console\Command;
+use GuzzleHttp\Exception\GuzzleException;
+
+class SyncBooksFromZwContent extends Command
+{
+    /**
+     * The name and signature of the console command.
+     *
+     * @var string
+     */
+    protected $signature = 'book:sync:from:zw {--zw_ids=}';
+
+    /**
+     * The console command description.
+     *
+     * @var string
+     */
+    protected $description = '从内容平台同步并更新图书信息';
+
+    /**
+     * @var Client
+     */
+    private $client;
+
+    // 内容中台域名
+    private $baseUrl = 'http://cp.yqsd.cn/api/book';
+
+    /**
+     * Create a new command instance.
+     *
+     * @return void
+     */
+    public function __construct()
+    {
+        parent::__construct();
+        $this->client = new Client(['timeout' => 20.0, 'allow_redirects' => true]);
+    }
+
+    /**
+     * @return void
+     * @throws GuzzleException
+     */
+    public function handle()
+    {
+        // 传参
+        $zwIds  = $this->filterParams();
+
+        // 获取书籍列表
+        if (empty($zwIds)) {
+            $zwIds = Book::select('id', 'zw_id')->where('zw_id', '>', '0')->plunck('zw_id');
+        }
+
+        // 执行更新
+        $this->syncZwBooks($zwIds);
+    }
+
+    /**
+     * @return array
+     */
+    private function filterParams(): array
+    {
+        // 传参
+        $idsStr = $this->option('zw_ids');
+        $ids    = explode(',', $idsStr);
+        if (empty($ids)) {
+            return [];
+        }
+
+        $result = [];
+        foreach ($ids as $id) {
+            if (in_array($id, $result) || !is_numeric($id) || (int)$id < 1) {
+                continue;
+            }
+
+            $result[] = (int)$id;
+        }
+
+        return $result;
+    }
+
+    /**
+     * @param $zwIds
+     *
+     * @return void
+     * @throws GuzzleException
+     */
+    private function syncZwBooks($zwIds): void
+    {
+        // 判空
+        if (empty($zwIds)) {
+            return;
+        }
+
+        // 循环执行
+        foreach ($zwIds as $zwId) {
+            $this->syncZwBook($zwId);
+        }
+
+    }
+
+    /**
+     * @param $zwId
+     * @return void
+     * @throws GuzzleException
+     */
+    private function syncZwBook($zwId): void
+    {
+        dLog('sync')->info('book:sync-start', compact('zwId'));
+        // 判空
+        if (empty($zwId)) {
+            return;
+        }
+
+        // 获取内容平台章节列表
+        $zwBook = $this->request('/bookInfo/' . $zwId);
+        if (empty($zwBook)) {
+            return;
+        }
+
+        // 获取书籍
+        $book = Book::where('zw_id', $zwId)->first();
+        if (!$book) {
+            $book = new Book();
+            $book->zw_id = $zwBook['bid'];
+        }
+
+        $book->name = $zwBook['name'];
+        $book->author = $zwBook['author'];
+        $book->intro = trim(str_replace("&nbsp;", "", $zwBook['intro']));
+        $book->cover = $zwBook['cover'];
+        $book->keyword = $zwBook['keyword'];
+        $book->gender = $zwBook['gender'];  # 1男 2女 //TODO 是否要调整
+        $book->category_id = $zwBook['category_id'];
+        $book->category_name = $zwBook['category_name'];
+        $book->ncategory_id = $zwBook['ncategory_id'];
+        $book->size = $zwBook['size'];
+        $book->status = $zwBook['status'];
+        $book->chapter_count = $zwBook['chapter_count'];
+        $book->save();
+
+
+        // 获取书籍配置
+        $bookConfig = BookConfig::where('bid', $book->id)->first();
+        if (!$bookConfig) {
+            $bookConfig              = new BookConfig();
+            $bookConfig->bid         = $book->id;
+            $bookConfig->charge_type = 'CHAPTER';
+            $bookConfig->is_on_shelf = 0;
+            $bookConfig->copyright_limit_data = '0000-00-00';
+        }
+        $bookConfig->cover = $zwBook['cover'];
+        $bookConfig->book_name = $zwBook['name'];
+        $bookConfig->source_domain = $zwBook['gender'] == 1 ? 'fanqqe.com' : 'fanqrj.com';
+        $bookConfig->cp_source = $zwBook['cp'];
+        $bookConfig->save();
+
+        dLog('sync')->info('book:sync-end', compact('zwId'));
+    }
+
+    /**
+     * 请求
+     *
+     * @param $url
+     * @return array|mixed|string
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    private function request($url)
+    {
+        $url = $this->baseUrl . $url;
+        try {
+            $result = $this->client->request('get', $url)->getBody()->getContents();
+            $result = json_decode($result, true);
+            $code   = getProp($result, 'code', 0);
+            $data   = getProp($result, 'data', []);
+        } catch (\Exception $e) {
+            return [];
+        }
+
+        return $code ? [] : $data;
+    }
+}

+ 210 - 0
app/Console/Book/SyncChaptersFromZwContent.php

@@ -0,0 +1,210 @@
+<?php
+
+namespace App\Console\Book;
+
+use App\Models\Book\Book;
+use App\Models\Book\Chapter;
+use App\Models\Book\ChapterContent;
+use GuzzleHttp\Client;
+use Illuminate\Console\Command;
+use GuzzleHttp\Exception\GuzzleException;
+
+class SyncChaptersFromZwContent extends Command
+{
+    /**
+     * The name and signature of the console command.
+     *
+     * @var string
+     */
+    protected $signature = 'chapter:content:sync:from:zw {--bid=} {--after=}';
+
+    /**
+     * The console command description.
+     *
+     * @var string
+     */
+    protected $description = '从内容平台同步并更新图书章节';
+
+    /**
+     * @var Client
+     */
+    private $client;
+
+    // 内容中台域名
+    private $baseUrl = 'http://cp.yqsd.cn/api/book';
+
+    /**
+     * Create a new command instance.
+     *
+     * @return void
+     */
+    public function __construct()
+    {
+        parent::__construct();
+        $this->client = new Client(['timeout' => 20.0, 'allow_redirects' => true]);
+    }
+
+    /**
+     * @return void
+     * @throws GuzzleException
+     */
+    public function handle()
+    {
+        // 传参
+        $bid   = (int)$this->option('bid');
+        $after = (int)$this->option('after');
+
+        // 获取书籍列表
+        $books = $this->getBooks($bid, $after);
+
+        // 执行更新
+        $this->syncChapters($books);
+    }
+
+    /**
+     * 获取待更新书籍列表
+     *
+     * @param $bid
+     * @param $after
+     * @return mixed
+     */
+    private function getBooks($bid, $after)
+    {
+        // 组装查询条件
+        $query = Book::select('id', 'zw_id', 'name')->where('zw_id', '>', '0');
+
+        // 此处bid为原创小程序bid
+        if ($bid) {
+            if ($after) {
+                $query->where('books.id', '>=', $bid);
+            }else{
+                $query->where('books.id', $bid);
+            }
+        }
+
+        // 查询
+        return $query->get();
+    }
+
+    /**
+     * @param $books
+     * @return void
+     * @throws GuzzleException
+     */
+    private function syncChapters($books): void
+    {
+        // 判空
+        if ($books->isEmpty()) {
+            return;
+        }
+
+        // 循环执行
+        foreach ($books as $book) {
+            $this->syncBookChapters($book->id, $book->zw_id);
+        }
+
+    }
+
+    /**
+     * @param $bid
+     * @param $zwId
+     * @return void
+     * @throws GuzzleException
+     */
+    private function syncBookChapters($bid, $zwId): void
+    {
+        dLog('sync')->info('chapter:sync-start', compact('bid', 'zwId'));
+        // 判空
+        if (empty($bid) || empty($zwId)) {
+            return;
+        }
+
+        // 获取最新章节信息
+        $lastChapter = Chapter::where('bid', $bid)
+                              ->where('is_check', 1)
+                              ->where('is_draft', 0)
+                              ->where('is_deleted', 0)
+                              ->orderBy('sequence', 'desc')
+                              ->first();
+
+        // 章节相关参数
+        $maxSequence   = (int)getProp($lastChapter, 'sequence', 0);
+        $lastChapterId = (int)getProp($lastChapter, 'id', 0);
+        $zwChapterId   = (int)getProp($lastChapter, 'zw_chapter_id', 0);
+
+        // 获取内容平台章节列表
+        $zwChapters = $this->request('/chapterlist/' . $zwId);
+        if (empty($zwChapters)) {
+            return;
+        }
+
+        // 循环章节内容
+        foreach ($zwChapters as $zwChapter) {
+            $zwCid       = (int)getProp($zwChapter, 'chapter_id', 0);
+            $chapterName = getProp($zwChapter, 'chapter_name');
+
+            // 从最新章节开始新增内容
+            if ($zwChapterId && $zwChapterId !== $zwCid) {
+                continue;
+            }
+
+            // 获取内容平台章节内容
+            $contentData = $this->request('/chapterContent/' . $zwId . '/' . $zwCid);
+            if (empty($contentData)) {
+                continue;
+            }
+
+            // 先保存章节内容
+            $obj                = new ChapterContent();
+            $obj->bid           = $bid;
+            $obj->zw_bid        = $zwId;
+            $obj->zw_chapter_id = $zwCid;
+            $obj->chapter_name  = $chapterName;
+            $obj->content       = getProp($contentData, 'content');
+            $obj->save();
+
+            // 保存章节
+            $obj1                     = new Chapter();
+            $obj1->bid                = $bid;
+            $obj1->name               = $chapterName;
+            $obj1->sequence           = (int)getProp($zwChapter, 'sequence');
+            $obj1->size               = (int)getProp($zwChapter, 'size');
+            $obj1->is_vip             = (int)getProp($zwChapter, 'is_vip');
+            $obj1->is_check           = 1;
+            $obj1->recent_update_at   = date('Y-m-d H:i:s');
+            $obj1->post_time          = date('Y-m-d H:i:s');
+            $obj1->zw_bid             = $zwId;
+            $obj1->zw_chapter_id      = $zwCid;
+            $obj1->chapter_content_id = $obj->id;
+            $obj1->save();
+        }
+
+        dLog('sync')->info('chapter:sync-end', compact('bid'));
+
+        // 执行更新书籍数据脚本
+        $this->call('book:after:spider', ['--bid' => $bid]);
+
+    }
+
+    /**
+     * 请求
+     *
+     * @param $url
+     * @return array|mixed|string
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    private function request($url)
+    {
+        $url = $this->baseUrl . $url;
+        try {
+            $result = $this->client->request('get', $url)->getBody()->getContents();
+            $result = json_decode($result, true);
+            $code   = getProp($result, 'code', 0);
+            $data   = getProp($result, 'data', []);
+        } catch (\Exception $e) {
+            return [];
+        }
+
+        return $code ? [] : $data;
+    }
+}

+ 125 - 0
app/Console/Channel/ChannelDayStatistics.php

@@ -0,0 +1,125 @@
+<?php
+
+namespace App\Console\Channel;
+
+use Illuminate\Console\Command;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Facades\Log;
+
+class ChannelDayStatistics extends Command
+{
+    /**
+     * The name and signature of the console command.
+     *
+     * @var string
+     */
+    protected $signature = 'channelDayStatistics {--channel_id=} {--day=} {--start_day=} {--end_day=}';
+
+    /**
+     * The console command description.
+     *
+     * @var string
+     */
+    protected $description = '站点统计日数据';
+
+    private $channelUserService;
+
+    public function __construct(
+    )
+    {
+        parent::__construct();
+    }
+
+    public function handle()
+    {
+        // 获取全部站点
+        $distribution_channel_ids = DB::table('distribution_channels')->orderBy('id')->select('id')->get()->pluck('id')->toArray();
+        // 传参
+        $option_day = trim($this->option('day'));
+        if (!$option_day) {
+            $day = date('Y-m-d', strtotime('-1 day'));
+        }else {
+            $day = $option_day;
+        }
+        $channel_id = trim($this->option('channel_id'));
+        $start_day = trim($this->option('start_day'));
+        $end_day = trim($this->option('end_day'));
+
+        if ($channel_id) {
+            if (!$start_day && !$option_day) {
+                // 指定站点按站点创建日期开始统计
+                $created_at = DB::table('distribution_channels')->where('id', $channel_id)->value('created_at');
+                if (!$created_at) dd('该站点不存在');
+                $start_day = transDate($created_at, 'Y-m-d');
+            }
+            $distribution_channel_ids = [$channel_id];
+        }
+
+        if ($start_day) {       // 指定开始日期统计
+            if (!$end_day) {    // 未指定结束日期则默认截止到昨天
+                $end_day = date('Y-m-d', strtotime('-1 day'));
+            }
+            $day = $start_day;
+            while (true) {
+                if (strtotime($day) > strtotime($end_day)) break;
+                $this->runStatistics($day, $distribution_channel_ids);
+                $day = date('Y-m-d', strtotime($day.' +1 day'));
+            }
+        }else {
+            $this->runStatistics($day, $distribution_channel_ids);
+        }
+    }
+
+    // 执行统计
+    private function runStatistics($day, $distribution_channel_ids) {
+        dLog('command_logs')->info('~~~~~~~~~~~~~~~~~~~~~~~~~~开始统计站点('.$day.')数据~~~~~~~~~~~~~~~~~~~~~~~~~~');
+        $executeStart = microtime(true);
+        $month = date('Ym', strtotime($day));
+        $day_start = $day.' 00:00:00';
+        $day_end = $day.' 23:59:59';
+
+        foreach ($distribution_channel_ids as $channel_id) {
+            $total_order_num = DB::table('orders')->where('distribution_channel_id', $channel_id)->whereBetween('created_at', [$day_start, $day_end])->count('id');
+            $paid_order = DB::table('orders')->where('distribution_channel_id', $channel_id)->whereBetween('created_at', [$day_start, $day_end])->where('status', 'PAID')
+                ->selectRaw("count(id) as paid_order_num, count(distinct uid) as total_pay_num, sum(price) as total_pay_amount")->first();
+            $paid_order_num = getProp($paid_order, 'paid_order_num', 0);
+            $total_pay_num = getProp($paid_order, 'total_pay_num', 0);
+            $total_pay_amount = getProp($paid_order, 'total_pay_amount', 0);
+
+            $register_uids = DB::table('users')->where('distribution_channel_id', $channel_id)->whereBetween('created_at', [$day_start, $day_end])->select('id')->get()->pluck('id')->toArray();
+            $register_orders = DB::table('orders')->where('distribution_channel_id', $channel_id)->whereBetween('created_at', [$day_start, $day_end])
+                ->whereIn('uid', $register_uids)->where('status', 'PAID')
+                ->selectRaw('count(distinct uid) as register_pay_num, sum(price) as register_pay_amount')->first();
+            $register_num = count($register_uids);
+            $register_pay_num = getProp($register_orders, 'register_pay_num', 0);
+            $register_pay_amount = getProp($register_orders, 'register_pay_amount', 0);
+            $statistics = [
+                'distribution_channel_id'   => $channel_id,
+                'day'                       => $day,
+                'month'                     => $month,
+                'total_order_num'           => $total_order_num,
+                'paid_order_num'            => $paid_order_num,
+                'register_num'              => $register_num,
+                'register_pay_num'          => $register_pay_num,
+                'register_pay_amount'       => $register_pay_amount,
+                'total_pay_num'             => $total_pay_num,
+                'total_pay_amount'          => $total_pay_amount,
+                'created_at'                => date('Y-m-d H:i:s'),
+                'updated_at'                => date('Y-m-d H:i:s'),
+            ];
+
+            $boolen = DB::table('channel_day_statistics')->updateOrInsert([
+                'distribution_channel_id'   => $channel_id,
+                'day'                       => $day,
+            ], $statistics);
+            if (!$boolen) {
+                Log::info('站点日统计失败, 失败记录: '.json_encode($statistics, 256));
+            }
+            $executeEnd = microtime(true);
+            dLog('command_logs')->info('站点('.$channel_id.')--'.$day.'执行时间: ', ['execute_time'=>round(($executeEnd - $executeStart), 6).'s']);
+        }
+        $executeEnd = microtime(true);
+        dLog('command_logs')->info('脚本运行时间: ', ['execute_time'=>round(($executeEnd - $executeStart), 6).'s']);
+        dLog('command_logs')->info('~~~~~~~~~~~~~~~~~~~~~~~~~~结束统计站点('.$day.')数据~~~~~~~~~~~~~~~~~~~~~~~~~~');
+    }
+}

+ 277 - 0
app/Console/Channel/RegisterChannel.php

@@ -0,0 +1,277 @@
+<?php
+
+namespace App\Console\Channel;
+
+use App\Libs\Utils;
+use Illuminate\Console\Command;
+use App\Services\Channel\ChannelUserService;
+use Illuminate\Support\Facades\DB;
+
+class RegisterChannel extends Command
+{
+    /**
+     * The name and signature of the console command.
+     *
+     * @var string
+     */
+    protected $signature = 'channel:register {nickname} {account} {password} {--channel_type=}';
+
+    /**
+     * The console command description.
+     *
+     * @var string
+     */
+    protected $description = '创建站点';
+
+    private $channelUserService;
+
+    public function __construct(
+        ChannelUserService $channelUserService
+    )
+    {
+        parent::__construct();
+        $this->channelUserService = $channelUserService;
+    }
+
+    public function handle()
+    {
+        // 传参
+        $nickname  = trim($this->argument('nickname'));
+        $account  = trim($this->argument('account'));
+        $password = trim($this->argument('password'));
+        $channel_type = trim($this->option('channel_type'));
+
+        if (!$nickname || !$account || !$password) {
+            dd('参数不对,请确认后重试');
+        }
+        if (!$channel_type) $channel_type = 'RECHARGE';
+
+        try {
+            DB::beginTransaction();
+            // 注册站点用户
+            $res = $this->channelUserService->register($account, $password);
+            $channel_user_id = DB::table('channel_users')->where('account', $account)->value('id');
+            if (!$channel_user_id) {
+                DB::rollBack();
+                Utils::throwError('1002:注册站点用户失败');
+            }
+
+            // 获取下一个站点id
+            $distribution_channel_id = DB::table('distribution_channels')->insertGetId([
+                'name'                  => $channel_type == 'RECHARGE' ? '掌维充送制' : '掌维会员制',
+                'channel_type'          => $channel_type,
+                'pay_merchant_id'       => 1,
+                'nickname'              => $nickname,
+                'channel_user_id'       => $channel_user_id,
+                'book_charge_type'      => 'HYBRID',
+                'book_coin'             => 1250,
+                'chapter_coin'          => 60,
+                'book_calculate_price_type' => 'all',
+                'chapter_calculate_price_type'  => 'bywords',
+                'created_at'            => date('Y-m-d H:i:s'),
+                'updated_at'            => date('Y-m-d H:i:s'),
+            ]);
+            if (!$distribution_channel_id) {
+                DB::rollBack();
+                Utils::throwError('1002:站点创建失败');
+            }
+
+            // 创建模板
+            $template_id = DB::table('channel_templates')->insertGetId([
+                'distribution_channel_id'   => $distribution_channel_id,
+                'template_name'             => '默认模板',
+                'template_type'             => 1,
+                'user_scope'                => 1,
+                'orders'                    => 0,
+                'is_enable'                 => 1,
+                'template_cate'             => $channel_type == 'RECHARGE' ? 1 : 2,
+                'created_at'                => date('Y-m-d H:i:s'),
+                'updated_at'                => date('Y-m-d H:i:s'),
+            ]);
+            if (!$template_id) {
+                DB::rollBack();
+                Utils::throwError('1002:默认模版创建失败');
+            }
+
+            $boolen = DB::table('distribution_channels')->where('id', $distribution_channel_id)->update([
+                'pay_id'        => $template_id,
+                'updated_at'    => date('Y-m-d H:i:s')
+            ]);
+            if (!$boolen) {
+                DB::rollBack();
+                Utils::throwError('1002:更新站点模板失败');
+            }
+
+            if ($channel_type == 'PERIOD') {
+                $products = [
+                    [
+                        'type'              => 'WEEK',
+                        'name'              => '周卡',
+                        'name_desc'         => '周卡',
+                        'price'             => '9.9',
+                        'price_desc'        => '',
+                        'angle_sign_text'   => '',
+                        'given'             => 0,
+                        'is_default'        => 0,
+                        'is_enabled'        => 1,
+                        'sequence'          => 1,
+                        'template_type'     => $template_id,
+                        'template_id'       => $template_id,
+                        'created_at'        => date('Y-m-d H:i:s'),
+                    ],
+                    [
+                        'type'              => 'MONTH',
+                        'name'              => '月卡',
+                        'name_desc'         => '月卡(限时优惠)',
+                        'price'             => '18.8',
+                        'price_desc'        => '原价:¥30',
+                        'angle_sign_text'   => '',
+                        'given'             => 0,
+                        'is_default'        => 1,
+                        'is_enabled'        => 1,
+                        'sequence'          => 2,
+                        'template_type'     => $template_id,
+                        'template_id'       => $template_id,
+                        'created_at'        => date('Y-m-d H:i:s'),
+                    ],
+                    [
+                        'type'              => 'QUARTER',
+                        'name'              => '季卡',
+                        'name_desc'         => '季卡(限时优惠)',
+                        'price'             => '25',
+                        'price_desc'        => '原价:¥50',
+                        'angle_sign_text'   => '',
+                        'given'             => 0,
+                        'is_default'        => 0,
+                        'is_enabled'        => 1,
+                        'sequence'          => 3,
+                        'template_type'     => $template_id,
+                        'template_id'       => $template_id,
+                        'created_at'        => date('Y-m-d H:i:s'),
+                    ],
+                    [
+                        'type'              => 'RECHARGE',
+                        'name'              => '年卡',
+                        'name_desc'         => '年卡(限时优惠)',
+                        'price'             => '68',
+                        'price_desc'        => '原价:¥120',
+                        'angle_sign_text'   => '',
+                        'given'             => 0,
+                        'is_default'        => 0,
+                        'is_enabled'        => 1,
+                        'sequence'          => 4,
+                        'template_type'     => $template_id,
+                        'template_id'       => $template_id,
+                        'created_at'        => date('Y-m-d H:i:s'),
+                    ],
+                ];
+            }else {
+                $products = [
+                    [
+                        'type'              => 'RECHARGE',
+                        'name'              => '19.9元',
+                        'name_desc'         => '',
+                        'price'             => '19.9',
+                        'price_desc'        => '',
+                        'angle_sign_text'   => '',
+                        'given'             => 0,
+                        'is_default'        => 1,
+                        'is_enabled'        => 1,
+                        'sequence'          => 1,
+                        'template_type'     => $template_id,
+                        'template_id'       => $template_id,
+                        'created_at'        => date('Y-m-d H:i:s'),
+                    ],
+                    [
+                        'type'              => 'RECHARGE',
+                        'name'              => '39.9元',
+                        'name_desc'         => '',
+                        'price'             => '39.9',
+                        'price_desc'        => '增1900书币',
+                        'angle_sign_text'   => '热',
+                        'given'             => 1900,
+                        'is_default'        => 0,
+                        'is_enabled'        => 1,
+                        'sequence'          => 2,
+                        'template_type'     => $template_id,
+                        'template_id'       => $template_id,
+                        'created_at'        => date('Y-m-d H:i:s'),
+                    ],
+                    [
+                        'type'              => 'RECHARGE',
+                        'name'              => '59.9元',
+                        'name_desc'         => '',
+                        'price'             => '59.9',
+                        'price_desc'        => '增3900书币',
+                        'angle_sign_text'   => '多送39',
+                        'given'             => 3900,
+                        'is_default'        => 0,
+                        'is_enabled'        => 1,
+                        'sequence'          => 3,
+                        'template_type'     => $template_id,
+                        'template_id'       => $template_id,
+                        'created_at'        => date('Y-m-d H:i:s'),
+                    ],
+                    [
+                        'type'              => 'WEEK',
+                        'name'              => '周卡',
+                        'name_desc'         => '周卡',
+                        'price'             => '29.9',
+                        'price_desc'        => '',
+                        'angle_sign_text'   => '一周所有书免费看',
+                        'given'             => 0,
+                        'is_default'        => 0,
+                        'is_enabled'        => 1,
+                        'sequence'          => 4,
+                        'template_type'     => $template_id,
+                        'template_id'       => $template_id,
+                        'created_at'        => date('Y-m-d H:i:s'),
+                    ],
+                    [
+                        'type'              => 'MONTH',
+                        'name'              => '月卡',
+                        'name_desc'         => '月卡',
+                        'price'             => '49.9',
+                        'price_desc'        => '',
+                        'angle_sign_text'   => '一个月所有书免费看',
+                        'given'             => 0,
+                        'is_default'        => 0,
+                        'is_enabled'        => 1,
+                        'sequence'          => 5,
+                        'template_type'     => $template_id,
+                        'template_id'       => $template_id,
+                        'created_at'        => date('Y-m-d H:i:s'),
+                    ],
+                    [
+                        'type'              => 'RECHARGE',
+                        'name'              => '年卡',
+                        'name_desc'         => '年卡',
+                        'price'             => '89.9',
+                        'price_desc'        => '',
+                        'angle_sign_text'   => '一年所有书免费看',
+                        'given'             => 0,
+                        'is_default'        => 0,
+                        'is_enabled'        => 1,
+                        'sequence'          => 6,
+                        'template_type'     => $template_id,
+                        'template_id'       => $template_id,
+                        'created_at'        => date('Y-m-d H:i:s'),
+                    ],
+                ];
+            }
+
+            $boolen2 = DB::table('products')->insert($products);
+            if (!$boolen2) {
+                DB::rollBack();
+                Utils::throwError('1002:充值档位创建失败');
+            }
+
+        }catch (\Exception $e) {
+            DB::rollBack();
+            dd($e->getMessage());
+        }
+
+        DB::commit();
+        dd('创建站点成功!');
+    }
+}

+ 44 - 0
app/Console/Channel/RegisterChannelUser.php

@@ -0,0 +1,44 @@
+<?php
+
+namespace App\Console\Channel;
+
+use Illuminate\Console\Command;
+use App\Services\Channel\ChannelUserService;
+
+class RegisterChannelUser extends Command
+{
+    /**
+     * The name and signature of the console command.
+     *
+     * @var string
+     */
+    protected $signature = 'user:register {account} {password}';
+
+    /**
+     * The console command description.
+     *
+     * @var string
+     */
+    protected $description = '注册用户';
+
+    private $channelUserService;
+
+    public function __construct(
+        ChannelUserService $channelUserService
+    )
+    {
+        parent::__construct();
+        $this->channelUserService = $channelUserService;
+    }
+
+    public function handle()
+    {
+        // 传参
+        $account  = trim($this->argument('account'));
+        $password = trim($this->argument('password'));
+
+        // 注册
+        $res = $this->channelUserService->register($account, $password);
+        dd($account, $password, $res);
+    }
+}

+ 54 - 0
app/Console/DyReport/ResetSendOrderReportDataCommand.php

@@ -0,0 +1,54 @@
+<?php
+namespace App\Console\DyReport;
+
+use App\Cache\StatisticCache;
+use Illuminate\Console\Command;
+use Illuminate\Support\Facades\DB;
+
+class ResetSendOrderReportDataCommand extends Command
+{
+    /**
+     * The name and signature of the console command.
+     *
+     * @var string
+     */
+    protected $signature = 'DyReport:ResetSendOrderReportData';
+
+    /**
+     * The console command description.
+     *
+     * @var string
+     */
+    protected $description = '每日0点重置所有派单链接的回传记录数';
+
+    /**
+     * Create a new command instance.
+     *
+     * @return void
+     */
+    public function __construct()
+    {
+        parent::__construct();
+    }
+
+    /**
+     * Execute the console command.
+     *
+     * @return mixed
+     */
+    public function handle()
+    {
+        dLog('command_logs')->info('~~~~~~~~~~~~~~~~~~~~~~~~~~开始重置所有派单链接的回传记录数~~~~~~~~~~~~~~~~~~~~~~~~~~');
+        $executeStart = microtime(true);
+
+        $result = DB::table('send_orders')->update([
+            'report_receive_num'    => 0,
+            'report_post_num'       => 0
+        ]);
+        dLog('command_logs')->info('执行结果: ', ['result'=>$result]);
+
+        $executeEnd = microtime(true);
+        dLog('command_logs')->info('脚本运行时间: ', ['execute_time'=>round(($executeEnd - $executeStart), 6).'s']);
+        dLog('command_logs')->info('~~~~~~~~~~~~~~~~~~~~~~~~~~结束重置所有派单链接的回传记录数~~~~~~~~~~~~~~~~~~~~~~~~~~');
+    }
+}

+ 151 - 0
app/Console/DyReport/SendOrderDayStatsCommand.php

@@ -0,0 +1,151 @@
+<?php
+namespace App\Console\DyReport;
+
+use Illuminate\Console\Command;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Facades\Redis;
+
+class SendOrderDayStatsCommand extends Command
+{
+    /**
+     * The name and signature of the console command.
+     *
+     * @var string
+     */
+    protected $signature = 'SendOrderDayStats {--day=} {--id=}';
+
+    /**
+     * The console command description.
+     *
+     * @var string
+     */
+    protected $description = '每日0点统计昨日各派单链接相关数据';
+
+    /**
+     * Create a new command instance.
+     *
+     * @return void
+     */
+    public function __construct()
+    {
+        parent::__construct();
+    }
+
+    /**
+     * Execute the console command.
+     *
+     * @return mixed
+     */
+    public function handle()
+    {
+        dLog('command_logs')->info('~~~~~~~~~~~~~~~~~~~~~~~~~~开始统计昨日各派单链接相关数据~~~~~~~~~~~~~~~~~~~~~~~~~~');
+        $executeStart = microtime(true);
+        $date = $this->option('day');
+        $send_order_id = $this->option('id');
+        if ($date) {        // 指定日期
+            $day = $date;
+            $start = date($date.' 00:00:00');
+            $end = date($date.' 23:59:59');
+        }else {             // 未指定日期则默认昨日
+            $day = date('Y-m-d', strtotime('-1 day'));
+            $start = date('Y-m-d 00:00:00', strtotime('-1 day'));
+            $end = date('Y-m-d 23:59:59', strtotime('-1 day'));
+        }
+
+        $query= DB::table('send_orders')->where('is_enable', '1')->where('created_at', '<', $end);
+
+        if ($send_order_id) {
+            $query->where('id', $send_order_id);
+        }
+        $send_orders = $query->select('id', 'uv', 'cost', 'total_register_pay_amount', 'total_profit', 'total_register_num', 'total_register_pay_num')
+            ->get()->map(function ($value) {
+            return (array)$value;
+        })->toArray();
+
+        foreach ($send_orders as $send_order) {
+            $send_order_id = getProp($send_order, 'id');
+            // 获取指定日期成本
+            $cost = DB::table('send_order_day_stats')->where('send_order_id', $send_order_id)->where('day', $day)->value('cost');
+
+            // 新增注册人数
+            $register_num = DB::table('users')->where('send_order_id', $send_order_id)->whereBetween('created_at', [$start, $end])->count('id');
+            // 注册充值人数(只计算首充)
+            $register_pay_num = DB::table('orders')->leftJoin('users', 'orders.uid', 'users.id')->where('users.send_order_id', $send_order_id)
+                ->where('orders.send_order_id', $send_order_id)->whereBetween('users.created_at', [$start, $end])
+                ->where('orders.status', 'PAID')->whereBetween('orders.created_at', [$start, $end])->where('orders.pay_num', 1)->count('orders.id');
+            // 注册用户充值金额(包含多充)
+            $register_pay_amount = DB::table('users')->leftJoin('orders', 'orders.uid', 'users.id')->where('users.send_order_id', $send_order_id)
+                ->where('orders.send_order_id', $send_order_id)->whereBetween('users.created_at', [$start, $end])
+                ->where('orders.status', 'PAID')->whereBetween('orders.created_at', [$start, $end])->sum('orders.price');
+
+            // 获取日数据汇总(指定日期之前的汇总)
+            $total_data = DB::table('send_order_day_stats')->where('send_order_id', $send_order_id)
+                ->where('day', '<', $day)->select(
+                DB::raw('sum(cost) as total_cost'),
+                DB::raw('sum(register_num) as total_register_num'),
+                DB::raw('sum(register_pay_amount) as total_register_pay_amount'),
+                DB::raw('sum(register_pay_num) as total_register_pay_num'),
+            )->first();
+
+            // 累计成本
+            $total_cost = $cost + getProp($total_data, 'total_cost', 0);
+            // 累计注册人数
+            $total_register_num = $register_num + getProp($total_data, 'total_register_num', 0);
+            // 累计充值金额(包含多充)
+            $total_register_pay_amount = $register_pay_amount + getProp($total_data, 'total_register_pay_amount', 0);
+            // 累计充值人数(只计算首充)
+            $total_register_pay_num = $register_pay_num + getProp($total_data, 'total_register_pay_num', 0);
+            // 累计盈利
+            $total_profit = $total_register_pay_amount - $total_cost;
+
+            $data = [
+                'send_order_id'                 => $send_order_id,
+                'day'                           => $day,
+                'register_num'                  => $register_num,
+                'register_pay_num'              => $register_pay_num,
+                'register_pay_amount'           => $register_pay_amount,
+//                'total_cost'                    => $total_cost,
+//                'total_profit'                  => $total_profit,
+//                'total_register_num'            => $total_register_num,
+//                'total_register_pay_num'        => $total_register_pay_num,
+//                'total_register_pay_amount'     => $total_register_pay_amount,
+                'created_at'                    => date('Y-m-d H:i:s'),
+                'updated_at'                    => date('Y-m-d H:i:s'),
+            ];
+
+            // 更新派单数据日表
+            $boolen = DB::table('send_order_day_stats')->updateOrInsert([
+                'send_order_id'     => $send_order_id,
+                'day'               => $day,
+            ], $data);
+            if (!$boolen) {
+                dLog('command_logs')->info('派单链接日表统计失败: ', $data);
+            }
+
+            // 获取uv数据
+            $uv = Redis::scard('send_order_uv_'.$send_order_id);
+            $uv = $uv ? $uv : 0;
+
+            $total_data = [
+                'uv'                            => $uv,
+                'cost'                          => $total_cost,
+                'total_register_pay_amount'     => $total_register_pay_amount,
+                'total_profit'                  => $total_profit,
+                'total_register_num'            => $total_register_num,
+                'total_register_pay_num'        => $total_register_pay_num,
+                'updated_at'                    => date('Y-m-d H:i:s')
+            ];
+
+            // 更新派单数据总表
+            $boolen1 = DB::table('send_orders')->where('id', $send_order_id)->update($total_data);
+            if (!$boolen1) {
+                $total_data['send_order_id'] = $send_order_id;
+                dLog('command_logs')->info('派单链接总表统计失败: ', $total_data);
+            }
+        }
+
+        $executeEnd = microtime(true);
+        dLog('command_logs')->info('脚本运行时间: ', ['execute_time'=>round(($executeEnd - $executeStart), 6).'s']);
+        dLog('command_logs')->info('~~~~~~~~~~~~~~~~~~~~~~~~~~结束统计昨日各派单链接相关数据~~~~~~~~~~~~~~~~~~~~~~~~~~');
+    }
+}

+ 128 - 0
app/Console/DyReport/SendOrderRechargeDayStatsCommand.php

@@ -0,0 +1,128 @@
+<?php
+namespace App\Console\DyReport;
+
+use Illuminate\Console\Command;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Facades\Redis;
+
+class SendOrderRechargeDayStatsCommand extends Command
+{
+    /**
+     * The name and signature of the console command.
+     *
+     * @var string
+     */
+    protected $signature = 'SendOrderRechargeDayStats {--date=} {--begin_date=} {--end_date=}';
+
+    /**
+     * The console command description.
+     *
+     * @var string
+     */
+    protected $description = '注册用户派单数据日统计';
+
+    /**
+     * Create a new command instance.
+     *
+     * @return void
+     */
+    public function __construct()
+    {
+        parent::__construct();
+    }
+
+    /**
+     * Execute the console command.
+     *
+     * @return mixed
+     */
+    public function handle()
+    {
+        dLog('command_logs')->info('~~~~~~~~~~~~~~~~~~~~~~~~~~开始统计注册用户派单日数据~~~~~~~~~~~~~~~~~~~~~~~~~~');
+        $executeStart = microtime(true);
+        $begin_date = $this->option('begin_date');
+        $end_date = $this->option('end_date');
+        if ($begin_date) {
+            $date = $begin_date;
+            if (!$end_date) {   // 未传截止日期则默认截止到今天0点
+                $end_date = date('Y-m-d');
+            }else {
+                if (strtotime($end_date) > strtotime(date('Y-m-d'))) {  // 截止日期如果超过今天0点,则将截止日期重置为今天0点
+                    $end_date = date('Y-m-d');
+                }
+            }
+            while (strtotime($date) < strtotime($end_date)) {
+                dLog('command_logs')->info("【任务执行中】~~~~~~ 日期:" . $date);
+                $this->statistic($date);
+                $date = date('Y-m-d', strtotime('+1 days', strtotime($date)));
+            }
+        } else {
+            $date = $this->option('date');
+            $date = $date ? $date : date('Y-m-d', strtotime('-1 days'));
+            $this->statistic($date);
+        }
+
+        $executeEnd = microtime(true);
+        dLog('command_logs')->info('脚本运行时间: ', ['execute_time'=>round(($executeEnd - $executeStart), 6).'s']);
+        dLog('command_logs')->info('~~~~~~~~~~~~~~~~~~~~~~~~~~结束统计注册用户派单日数据~~~~~~~~~~~~~~~~~~~~~~~~~~');
+    }
+
+    /**
+     * 新用户数据统计
+     * @param string $date 统计日期
+     */
+    public function statistic(string $date)
+    {
+        $statistic_info = $this->runSql($date);
+
+        foreach ($statistic_info as $item) {
+            $this->saveStatistic($item);
+        }
+    }
+
+    public function runSql(string $date)
+    {
+        $end_date =  date('Y-m-d', strtotime('+1 days', strtotime($date)));
+        return DB::connection('mysql::write')->table('orders')->leftJoin('users', 'orders.uid', 'users.id')
+            ->where('orders.created_at', '>=', $date)
+            ->where('orders.created_at', '<', $end_date)
+            ->where('orders.status', 'PAID')
+            ->selectRaw("orders.send_order_id,DATE_FORMAT(users.created_at, '%Y-%m-%d') as register_date,
+            timestampdiff(day,DATE_FORMAT(users.created_at, '%Y-%m-%d'),DATE_FORMAT(orders.created_at, '%Y-%m-%d')) as diff_day,
+            DATE_FORMAT(orders.created_at, '%Y-%m-%d') as pay_date,
+            sum(orders.price) as pay_amount,
+            count(distinct orders.uid) as pay_num,
+            count(orders.id) as order_num,
+            count(orders.pay_num = 1 or null) as first_pay_num")
+            ->groupBy('send_order_id', 'register_date')
+            ->get()->map(function ($value) {
+                return (array)$value;
+            })->toArray();
+    }
+
+    private function saveStatistic($item)
+    {
+
+        try{
+            DB::table('send_order_recharge_day_stats')->updateOrInsert(
+                [
+                    'send_order_id'     => $item['send_order_id'],  // 派单id
+                    'register_date'     => $item['register_date'],  // 注册日期
+                    'day_num'           => $item['diff_day'],       // 第几天
+                ],
+                [
+                    'pay_date'          => $item['pay_date'],       // 支付日期
+                    'pay_amount'        => $item['pay_amount'],     // 支付金额
+                    'pay_num'           => $item['pay_num'],        // 支付人数
+                    'order_num'         => $item['order_num'],      // 订单数
+                    'first_pay_num'     => $item['first_pay_num'],  //首单人数
+                    'created_at'        => date('Y-m-d H:i:s'),
+                    'updated_at'        => date('Y-m-d H:i:s'),
+                ]
+            );
+        }catch (\Exception $e){
+            dLog('command_logs')->info("【任务执行失败】~~~~~~ 错误信息:" . $e->getMessage());
+        }
+
+    }
+}

+ 77 - 0
app/Console/Kernel.php

@@ -0,0 +1,77 @@
+<?php
+
+namespace App\Console;
+
+use App\Console\Book\BookSpiderAfter;
+use App\Console\Book\SyncBooksFromZwContent;
+use App\Console\Book\SyncChaptersFromZwContent;
+use App\Console\Channel\ChannelDayStatistics;
+use App\Console\Channel\RegisterChannel;
+use App\Console\Channel\RegisterChannelUser;
+use App\Console\DyReport\ResetSendOrderReportDataCommand;
+use App\Console\DyReport\SendOrderDayStatsCommand;
+use App\Console\DyReport\SendOrderRechargeDayStatsCommand;
+use App\Console\Subscribe\BookOrderByDayCommand;
+use App\Console\Sync\SyncBooks;
+use App\Console\Sync\SyncChapters;
+use App\Console\Test\ChangeUserChannelId;
+use App\Console\Test\TestCache;
+use App\Console\Test\TestOpenApi;
+use App\Console\Test\TestSms;
+use Illuminate\Console\Scheduling\Schedule;
+use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
+
+class Kernel extends ConsoleKernel
+{
+    protected $commands = [
+        Test\TestZfbTransFundCommand::class,
+        Test\TestCommand::class,
+        TestCache::class,
+        TestOpenApi::class,
+        TestSms::class,
+        ChangeUserChannelId::class,
+        TikTok\AccessTokenManage::class,
+        TikTok\ClientTokenManage::class,
+        Book\ReadRecordStatsCommand::class,
+        Pay\TransFundCommand::class,
+        Pay\RefundCommand::class,
+        SyncChaptersFromZwContent::class,
+        SyncBooksFromZwContent::class,
+        BookSpiderAfter::class,
+        SyncBooks::class,
+        SyncChapters::class,
+        BookOrderByDayCommand::class,
+        ResetSendOrderReportDataCommand::class,
+        RegisterChannelUser::class,
+        SendOrderDayStatsCommand::class,
+        SendOrderRechargeDayStatsCommand::class,
+        RegisterChannel::class,
+        ChannelDayStatistics::class,
+    ];
+
+
+    /**
+     * Define the application's command schedule.
+     *
+     * @param \Illuminate\Console\Scheduling\Schedule $schedule
+     * @return void
+     */
+    protected function schedule(Schedule $schedule)
+    {
+        $schedule->command('channelDayStatistics')->dailyAt('00:01');               // 站点日数据统计
+        $schedule->command('SendOrderDayStats')->dailyAt('00:01');                  // 派单日数据统计
+        $schedule->command('SendOrderRechargeDayStats')->dailyAt('00:05');          // 注册用户派单数据日统计
+    }
+
+    /**
+     * Register the commands for the application.
+     *
+     * @return void
+     */
+    protected function commands()
+    {
+        $this->load(__DIR__ . '/Commands');
+
+        require base_path('routes/console.php');
+    }
+}

+ 104 - 0
app/Console/Pay/RefundCommand.php

@@ -0,0 +1,104 @@
+<?php
+
+namespace App\Console\Pay;
+
+use App\Cache\UserCache;
+use App\Libs\Utils;
+use App\Models\Channel\Channel;
+use App\Models\Pay\PayMerchant;
+use Illuminate\Console\Command;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Facades\Redis;
+use Ycpay\Byte;
+use App\Servicess\WithdrawCashService;
+use App\Services\Order\OrderService;
+use Ycpay\Factory as PayFactory;
+
+
+/**
+ * 退款脚本
+ */
+class RefundCommand extends Command
+{
+    /**
+     * @var string
+     */
+    protected $signature = 'order_refund {--trade_no=}';
+
+    /**
+     * The console command description.
+     *
+     * @var string
+     */
+    protected $description = '订单退款';
+
+
+    /**
+     * handle
+     */
+    public function handle()
+    {
+        // 传参
+        $trade_no = $this->option('trade_no');
+        \Log::info('order_refund_start:'.$trade_no);
+        
+        $order = OrderService::getByTradeNo($trade_no);
+        \Log::info('$order:'.json_encode($order));
+        $distribution_channel_id = $order->distribution_channel_id;
+        
+//         $channel = Channel::find($distribution_channel_id);
+//         \Log::info('$channel:'.$distribution_channel_id.' param:'.json_encode($channel));
+        
+        $pay_merchant = PayMerchant::find($order->pay_merchant_id);
+        if(empty($pay_merchant)){
+            \Log::info('create_order_pay_merchant_null:'.$order->pay_merchant_id);
+            
+            return $this->error('50013:参数异常');
+        }
+        $pay_config_info = $pay_merchant->config_info;
+        $pay_config_infos = json_decode($pay_config_info,true);
+        
+        \Log::info('create_order_pay_merchant:'.json_encode($pay_merchant));
+        \Log::info('$pay_config_infos:');\Log::info($pay_config_infos);
+        
+        $byte = new Byte();
+        
+        $config = [
+            'app_id'=>isset($pay_config_infos['appid'])?$pay_config_infos['appid']:'',
+            'secret'=>isset($pay_config_infos['secret'])?$pay_config_infos['secret']:'',
+            'notify_url'=>isset($pay_config_infos['notify_url'])?$pay_config_infos['notify_url']:'',
+            'refund_notify_url'=>isset($pay_config_infos['refund_notify_url'])?$pay_config_infos['refund_notify_url']:'',
+            'token'=>isset($pay_config_infos['token'])?$pay_config_infos['token']:'',
+            'salt'=>isset($pay_config_infos['salt'])?$pay_config_infos['salt']:'',
+            'store_uid'=>isset($pay_config_infos['store_uid'])?$pay_config_infos['store_uid']:'',// 收款商户号,1个主体下面有多个商户号
+            'merchant_id'=>$order->pay_merchant_id,
+            'title'=>isset($pay_config_infos['title'])?$pay_config_infos['title']:'',
+            'desc'=>isset($pay_config_infos['desc'])?$pay_config_infos['desc']:'',
+        ];
+        \Log::info('$config:'.json_encode($config));
+        
+        $pay_client = PayFactory::getInstance('Byte');
+        
+        $order_param = [
+            'out_order_no'=>$trade_no,
+            'out_refund_no'=>generateOrderSn('refund-'),
+            'reason'=>'退款',
+            'refund_amount'=>$order->price * 100,
+            'cp_extra'=>'',
+            'notify_url'=>$config['refund_notify_url'],
+        ];
+        
+        \Log::info('$order_param:');
+        \Log::info(json_encode($order_param));
+        
+        $order_refund_res = $pay_client->init($config)->applyOrderRefund($order_param);
+        if(isset($order_refund_res->err_no) && $order_refund_res->err_no == 0 &&$order_refund_res->err_tips == 'success'){
+            
+        }
+        \Log::info('order_refund_res:'.json_encode($order_refund_res));
+  
+        
+    }
+
+
+}

File diff suppressed because it is too large
+ 196 - 0
app/Console/Pay/TransFundCommand.php


+ 112 - 0
app/Console/Subscribe/BookOrderByDayCommand.php

@@ -0,0 +1,112 @@
+<?php
+namespace App\Console\Subscribe;
+
+use App\Cache\StatisticCache;
+use Illuminate\Console\Command;
+use Illuminate\Support\Facades\DB;
+
+class BookOrderByDayCommand extends Command
+{
+    /**
+     * The name and signature of the console command.
+     *
+     * @var string
+     */
+    protected $signature = 'Subscribe:BookOrderByDay {--day=}';
+
+    /**
+     * The console command description.
+     *
+     * @var string
+     */
+    protected $description = 'redis临时保存的订阅记录按天汇总写入数据库';
+
+    /**
+     * Create a new command instance.
+     *
+     * @return void
+     */
+    public function __construct()
+    {
+        parent::__construct();
+    }
+
+    /**
+     * Execute the console command.
+     *
+     * @return mixed
+     */
+    public function handle()
+    {
+        $day = $this->option('day');
+        if (!$day) $day = date('Y-m-d', strtotime('-1 day'));
+        $this->saveDB($day);
+    }
+
+    private function saveDB($day)
+    {
+        dLog('command_logs')->info('~~~~~~~~~~~~~~~~~~~~~~~~~~开始统计redis临时保存的订阅记录~~~~~~~~~~~~~~~~~~~~~~~~~~');
+        $executeStart = microtime(true);
+        // 获取数据
+        $data = $this->getRecord($day);
+
+        $fields = ['bid', 'day', 'balance', 'charge_balance', 'reward_balance', 'is_chapter', 'count'];
+
+        // 组装数据
+        $insertData = [];
+        foreach ($data as $item) {
+            foreach ($item as $v) {
+                $arr = json_decode($v, true);
+                if (!is_array($arr) || empty($arr)) {
+                    continue;
+                }
+
+                // 参数不正确就跳过
+                foreach ($fields as $field) {
+                    if (!isset($arr[$field])) continue(2);
+                }
+                if (isset($arr['uid'])) unset($arr['uid']);
+                $arr['created_at'] = $arr['updated_at'] = date('Y-m-d H:i:s');
+
+                // 不是指定日期的数据则跳过
+                if ($arr['day'] != $day) continue;
+
+                // 按书籍id累加
+                if (isset($insertData[$arr['bid']])) {
+                    $insertData[$arr['bid']]['balance'] += $arr['balance'];
+                    $insertData[$arr['bid']]['charge_balance'] += $arr['charge_balance'];
+                    $insertData[$arr['bid']]['reward_balance'] += $arr['reward_balance'];
+                    $insertData[$arr['bid']]['count'] += $arr['count'];
+                }else {
+                    $insertData[$arr['bid']] = $arr;
+                }
+
+            }
+        }
+
+        if ($insertData) {
+            DB::table('book_order_by_day')->where('day', $day)->delete();
+            DB::table('book_order_by_day')->insert($insertData);
+        }
+
+        $executeEnd = microtime(true);
+        dLog('command_logs')->info('脚本运行时间: ', ['execute_time'=>round(($executeEnd - $executeStart), 6).'s']);
+        dLog('command_logs')->info('~~~~~~~~~~~~~~~~~~~~~~~~~~结束统计redis临时保存的订阅记录~~~~~~~~~~~~~~~~~~~~~~~~~~');
+    }
+
+    private function getRecord($day)
+    {
+        if ($day) $day = date('Ymd', strtotime($day));
+        else $day = date('Ymd', strtotime('-1 day'));
+        $length = StatisticCache::getSubscribeCount($day);
+        if (!$length) return [];
+        $count = ceil($length / 1000);
+        $result = [];
+        for ($i = 0; $i < $count; $i++) {
+            $start = $i * 1000;
+            $end = $start + 999;
+            $result[] = StatisticCache::getSubscribe($day, $start, $end);
+        }
+        return $result;
+    }
+}

+ 208 - 0
app/Console/Sync/SyncBooks.php

@@ -0,0 +1,208 @@
+<?php
+
+namespace App\Console\Sync;
+
+use App\Libs\BatchUpdateTrait;
+use App\Models\Book\Book;
+use App\Models\Book\BookConfig;
+use Illuminate\Console\Command;
+use Illuminate\Support\Facades\DB;
+
+class SyncBooks extends Command
+{
+    use BatchUpdateTrait;
+
+    /**
+     * The name and signature of the console command.
+     *
+     * @var string
+     */
+    protected $signature = 'sync:books {--zw_ids=}';
+
+    /**
+     * The console command description.
+     *
+     * @var string
+     */
+    protected $description = '从内容平台同步书籍';
+
+    private $zwBooks = [];
+
+    public function handle()
+    {
+        // 传参
+        $idsStr    = $this->option('zw_ids');
+        $passZwIds = filterValidIds(explode(',', $idsStr));
+
+        // 获取书籍列表
+        $existedZwIds = $this->getAllBookZwIds($passZwIds);
+
+        // 合并
+        $zwIds = array_unique(array_merge($passZwIds, $existedZwIds));
+
+        // 执行更新
+        $this->runSync($zwIds);
+    }
+
+    /**
+     * @param $zwIds
+     * @return array
+     */
+    private function getAllBookZwIds($zwIds): array
+    {
+        // 查询当前所有书
+        $query = Book::select('id', 'zw_id');
+        if ($zwIds) {
+            $query->whereIn('zw_id', $zwIds);
+        } else {
+            $query->where('zw_id', '>', 0);
+        }
+
+        $books = $query->get();
+        if ($books->isEmpty()) {
+            return [];
+        }
+
+        // 标记已存在书籍
+        $result = [];
+        foreach ($books as $book) {
+            $zwId = (int)getProp($book, 'zw_id');
+            if (!isset($this->zwBooks[$zwId])) {
+                $this->zwBooks[$zwId] = (int)getProp($book, 'id');
+            }
+
+            $result[] = $zwId;
+        }
+
+        return $result;
+    }
+
+    /**
+     * @param $zwIds
+     * @return void
+     */
+    private function runSync($zwIds): void
+    {
+        // 判空
+        if (empty($zwIds)) {
+            return;
+        }
+
+        // 获取内容平台书籍
+        $zwBooks = $this->getZwBooksByIds($zwIds);
+        if (empty($zwBooks)) {
+            return;
+        }
+
+        // 组装书籍数据
+        $now   = date('Y-m-d H:i:s');
+        $zwIds = $upBooks = $inBooks = $initConfigs = [];
+        foreach ($zwBooks as $zwBook) {
+            $zwId     = (int)getProp($zwBook, 'id');
+            $cover    = getProp($zwBook, 'cover') ?: getProp($zwBook, 'book_config_cover');
+            $intro    = getProp($zwBook, 'intro');
+            $intro    = str_replace("&nbsp;", "", $intro);
+            $intro    = trim($intro);
+            $zwIds[]  = $zwId;
+            $bookItem = [
+                'zw_id'         => $zwId,
+                'name'          => getProp($zwBook, 'name'),
+                'author'        => getProp($zwBook, 'author'),
+                'intro'         => $intro,
+                'cover'         => $cover,
+                'keyword'       => getProp($zwBook, 'keyword'),
+                'gender'        => getProp($zwBook, 'gender'), # 1男 2女
+                'category_id'   => getProp($zwBook, 'category_id'),
+                'category_name' => getProp($zwBook, 'category_name'),
+                'ncategory_id'  => getProp($zwBook, 'ncategory_id'),
+                'size'          => getProp($zwBook, 'size'),
+                'status'        => getProp($zwBook, 'status'),
+                'chapter_count' => getProp($zwBook, 'chapter_count'),
+                'updated_at'    => $now,
+            ];
+
+            // 书籍配置表
+            $initConfigs[$zwId] = [
+                'cover'         => $cover,
+                'book_name'     => getProp($zwBook, 'name'),
+                'source_domain' => getProp($zwBook, 'gender') == 1 ? 'fanqqe.com' : 'fanqrj.com',
+                'cp_source'     => getProp($zwBook, 'source'),
+                'updated_at'    => $now,
+            ];
+
+            // 区分更新还是写入
+            $bid = (int)getProp($this->zwBooks, $zwId);
+            if ($bid) {
+                // 书籍数据
+                $bookItem['id'] = $bid;
+                $upBooks[]      = $bookItem;
+            } else {
+                $bookItem['created_at'] = $now;
+                $inBooks[]              = $bookItem;
+            }
+
+        }
+
+        Book::insert($inBooks);
+        $this->batchUpdateDb('books', $upBooks);
+
+        // 获取这批书籍
+        $books = Book::select('id', 'zw_id')->whereIn('zw_id', $zwIds)->get();
+        if ($books->isEmpty()) {
+            return;
+        }
+
+        $bids        = collect($books)->pluck('id')->all();
+        $bookConfigs = BookConfig::select('id', 'bid')->whereIn('bid', $bids)->get();
+        $configBids  = collect($bookConfigs)->pluck('bid')->all();
+
+        // 区分更新or新增
+        $inConfigs = $upConfigs = [];
+        foreach ($books as $book) {
+            $bid               = (int)getProp($book, 'id');
+            $zwId              = (int)getProp($book, 'zw_id');
+            $bookConfig        = getProp($initConfigs, $zwId, []);
+            $bookConfig['bid'] = $bid;
+            if ($bookConfig && in_array($bid, $configBids)) {
+                // 书籍数据
+                $upConfigs[] = $bookConfig;
+            } else {
+                $bookConfig['copyright_limit_data'] = '0000-00-00';
+                $bookConfig['charge_type']          = 'CHAPTER';
+                $bookConfig['shelf_time']           = $now;
+                $bookConfig['created_at']           = $now;
+                $inConfigs[]                        = $bookConfig;
+            }
+        }
+
+        BookConfig::insert($inConfigs);
+        $this->batchUpdateDb('book_configs', $upConfigs, 'bid');
+    }
+
+    /**
+     * 获取内容平台书籍
+     *
+     * @param $ids
+     * @return array
+     */
+    private function getZwBooksByIds($ids): array
+    {
+        if (empty($ids)) {
+            return [];
+        }
+
+        $result = DB::connection('zw_content_mysql')
+                    ->table('books')
+                    ->leftjoin('book_configs', 'book_configs.bid', '=', 'books.id')->whereIn('books.id', $ids)
+                    ->select('books.id', 'books.name', 'books.out_bid', 'books.source', 'books.source_name', 'books.author', 'books.source_bid',
+                        'books.intro', 'books.gender', 'books.cover', 'books.size', 'books.keyword', 'books.category_name', 'books.status', 'books.chapter_count',
+                        'books.updated_at', 'books.primary_cover', 'books.category_id', 'books.last_chapter_update_time', 'books.series', 'books.labels',
+                        'books.styles', 'books.man_type', 'books.woman_type', 'books.time_back', 'books.directional', 'books.is_app', 'books.end_time', 'books.post_time',
+                        'books.ncategory_id', 'books.roles', 'books.authorization_status', 'books.is_vip', 'books.is_first', 'books.is_check', 'books.is_contract',
+                        'books.author_id', 'book_configs.is_on_shelf', 'book_configs.cover as book_config_cover', 'books.group_id'
+                    )
+                    ->get();
+
+        return $result ? $result->toArray() : [];
+    }
+}

+ 425 - 0
app/Console/Sync/SyncChapters.php

@@ -0,0 +1,425 @@
+<?php
+
+namespace App\Console\Sync;
+
+use App\Libs\BatchUpdateTrait;
+use App\Models\Book\Book;
+use App\Models\Book\Chapter;
+use App\Models\Book\ChapterContent;
+use Illuminate\Console\Command;
+use Illuminate\Support\Facades\DB;
+
+class SyncChapters extends Command
+{
+    use BatchUpdateTrait;
+
+    /**
+     * The name and signature of the console command.
+     *
+     * @var string
+     */
+    protected $signature = 'sync:chapters {--bids=}';
+
+    /**
+     * The console command description.
+     *
+     * @var string
+     */
+    protected $description = '从内容平台同步章节';
+
+    public function handle()
+    {
+        // 传参
+        $bids = $this->option('bids');
+        $bids = filterValidIds(explode(',', $bids));
+        dLog('sync')->info('sync chapters start...');
+
+        // 获取书籍列表
+        $books = $this->getBooks($bids);
+
+        // 执行更新
+        $this->runSync($books);
+        dLog('sync')->info('sync chapters end...');
+    }
+
+    /**
+     * @param $bids
+     * @return mixed
+     */
+    private function getBooks($bids)
+    {
+        // 组装查询条件
+        $query = Book::select('id', 'zw_id')->where('zw_id', '>', 0);
+        if ($bids) {
+            $query->whereIn('id', $bids);
+        } else {
+            // 连载
+            $query->where('status', 0);
+        }
+
+        // 查询
+        return $query->orderBy('id')->get();
+    }
+
+    /**
+     * @param $books
+     * @return void
+     */
+    private function runSync($books): void
+    {
+        // 判空
+        if ($books->isEmpty()) {
+            return;
+        }
+
+        // 循环执行
+        foreach ($books as $book) {
+            // 这本书有点问题
+            if (in_array($book->zw_id, [141873, 125717])) {
+                continue;
+            }
+
+            dLog('sync')->info('sync book chapters start: ', [$book->id, $book->zw_id]);
+
+            // 同步章节
+            $this->syncChapters($book->id, $book->zw_id);
+
+            dLog('sync')->info('sync book chapters end: ', [$book->id, $book->zw_id]);
+
+            // 执行更新书籍数据脚本
+            $this->call('book:after:spider', ['--bid' => $book->id]);
+        }
+    }
+
+    /**
+     * 制作新增章节的同步
+     *
+     * @param $bid
+     * @param $zwBid
+     * @return void
+     */
+    private function syncChapters($bid, $zwBid)
+    {
+        // 判空
+        if (empty($bid) || empty($zwBid)) {
+            return;
+        }
+
+        // 获取内容平台书籍所有章节列表
+        $zwChapters = $this->getZwChapters($zwBid);
+        if (empty($zwChapters)) {
+            return;
+        }
+
+        // 获取抖音平台书籍所有章节(及内容)列表
+        $chapters        = Chapter::where('bid', $bid)->get();
+        $chapterContents = ChapterContent::select('id', 'bid', 'zw_bid', 'zw_chapter_id')
+                                         ->where('bid', $bid)
+                                         ->where('zw_chapter_id', '>', 0)
+                                         ->get();
+
+        // 校验章节数据
+        $diffData = $this->getDiffChapters($bid, $zwBid, $zwChapters, $chapters, $chapterContents);
+
+        // 写入新章节
+        $chaptersInData = getProp($diffData, 'chaptersInData', []);
+        if ($chaptersInData) {
+            Chapter::insert($chaptersInData);
+        }
+
+        // 更新章节
+        $chaptersUpData = getProp($diffData, 'chaptersUpData', []);
+        if ($chaptersInData) {
+            $this->batchUpdateDb('chapters', $chaptersUpData);
+        }
+
+        // 写入新章节内容(内容数据较大,分批获取)
+        $newZwChaptersContentIds = getProp($diffData, 'newZwChaptersContentIds', []);
+        $zwChaptersContentData   = getProp($diffData, 'zwChaptersContentData', []);
+        $this->saveNewChaptersContent($newZwChaptersContentIds, $bid, $zwBid, $zwChaptersContentData);
+
+        //更新章节的content_chapter_id
+        $this->updateChaptersContentId($bid);
+    }
+
+    /**
+     * 书籍章节比较
+     *
+     * @param $bid
+     * @param $zwBid
+     * @param $zwChapters
+     * @param $chapters
+     * @param $chapterContents
+     * @return array
+     */
+    private function getDiffChapters($bid, $zwBid, $zwChapters, $chapters, $chapterContents): array
+    {
+        if (empty($zwChapters)) {
+            return [];
+        }
+
+        // 循环章节数据
+        $chaptersData = [];
+        if ($chapters) {
+            foreach ($chapters as $chapter) {
+                $id          = (int)getProp($chapter, 'id');
+                $zwChapterId = (int)getProp($chapter, 'zw_chapter_id');
+
+                // 当前已存在的章节
+                $chaptersData[$zwChapterId] = ['id' => $id];
+            }
+        }
+
+        // 循环章节内容表
+        $chapterContentsData = [];
+        if ($chapterContents) {
+            foreach ($chapterContents as $chapterContent) {
+                $bid         = (int)getProp($chapterContent, 'bid');
+                $zwChapterId = (int)getProp($chapterContent, 'zw_chapter_id');
+                $zwBid       = (int)getProp($chapterContent, 'zw_bid');
+
+                // 当前已存在的章节内容
+                $chapterContentsData[$zwChapterId] = ['bid' => $bid, 'zw_bid' => $zwBid];
+            }
+        }
+
+        // 循环内容平台章节列表数据
+        $now            = date('Y-m-d H:i:s');
+        $chaptersUpData = $chaptersInData = $newZwChaptersContentIds = $zwChaptersContentData = [];
+        foreach ($zwChapters as $zwChapter) {
+            // 内容平台章节id
+            $zwChapterId        = (int)getProp($zwChapter, 'id');
+            $zwChapterContentId = (int)getProp($zwChapter, 'chapter_content_id');
+            $name               = getProp($zwChapter, 'name');
+
+            // 章节基本数据,为了同步章节内容时用
+            $zwChaptersContentData[$zwChapterContentId] = [
+                'bid'           => $bid,
+                'chapter_name'  => $name,
+                'zw_bid'        => $zwBid,
+                'zw_chapter_id' => $zwChapterId,
+            ];
+
+            // 基本数据
+            $item = [
+                'bid'              => $bid,
+                'name'             => $name,
+                'sequence'         => (int)getProp($zwChapter, 'sequence'),
+                'size'             => (int)getProp($zwChapter, 'size'),
+                'is_vip'           => (int)getProp($zwChapter, 'is_vip'),
+                'is_check'         => 1,
+                'is_deleted'       => (int)getProp($zwChapter, 'is_deleted'),
+                'zw_bid'           => $zwBid,
+                'zw_chapter_id'    => $zwChapterId,
+                'recent_update_at' => $now,
+                'post_time'        => $now,
+                'check_time'       => $now,
+                'created_at'       => $now,
+                'updated_at'       => $now,
+            ];
+
+            // 区分章节写入还是更新
+            if (!isset($chaptersData[$zwChapterId])) {
+                $chaptersInData[] = $item;
+            } else {
+                // 去掉无需更新的字段
+                unset($item['bid'], $item['zw_bid'], $item['zw_chapter_id'], $item['recent_update_at'],
+                    $item['post_time'], $item['check_time'], $item['created_at']);
+
+                // 加上当前章节id
+                $item['id']       = (int)getProp($chaptersData[$zwChapterId], 'id');
+                $chaptersUpData[] = $item;
+            }
+
+            // 区分章节内容写入还是更新
+            if (!isset($chapterContentsData[$zwChapterId])) {
+                $newZwChaptersContentIds[] = $zwChapterContentId;
+            }
+        }
+
+        return compact('chaptersUpData', 'chaptersInData', 'newZwChaptersContentIds', 'zwChaptersContentData');
+    }
+
+    /**
+     * 写入章节内容
+     *
+     * @param $zwChaptersContentIds
+     * @param $bid
+     * @param $zwBid
+     * @param $zwChaptersContentData
+     * @return void
+     */
+    private function saveNewChaptersContent($zwChaptersContentIds, $bid, $zwBid, $zwChaptersContentData)
+    {
+        if (empty($zwChaptersContentIds)) {
+            return;
+        }
+
+        // 当前时间
+        $now = date('Y-m-d H:i:s');
+
+        // 切分id
+        $zwChapterContentIdsArr = array_chunk($zwChaptersContentIds, 100);
+
+        // 循环切分数据
+        foreach ($zwChapterContentIdsArr as $contentIds) {
+
+            // 获取内容平台章节内容
+            $zwChapterContents = $this->getZwChaptersContent($contentIds);
+            if (empty($zwChapterContents)) {
+                continue;
+            }
+
+            // 组装写入数据
+            $chaptersContentInsertData = [];
+            foreach ($zwChapterContents as $zwChapterContent) {
+                $zwChapterContentId   = (int)getProp($zwChapterContent, 'id');
+                $zwChapterContentData = getProp($zwChaptersContentData, $zwChapterContentId, []);
+
+                // 组装写入数据
+                $chaptersContentInsertData[] = [
+                    'bid'           => $bid,
+                    'zw_bid'        => $zwBid,
+                    'zw_chapter_id' => (int)getProp($zwChapterContentData, 'zw_chapter_id'),
+                    'chapter_name'  => getProp($zwChapterContentData, 'chapter_name'),
+                    'content'       => formatContent(getProp($zwChapterContent, 'content')),
+                    'created_at'    => $now,
+                    'updated_at'    => $now,
+                ];
+            }
+
+            // 执行写入
+            ChapterContent::insert($chaptersContentInsertData);
+        }
+    }
+
+    /**
+     * 更新章节chapter_content_id
+     *
+     * @param $bid
+     * @return void
+     */
+    private function updateChaptersContentId($bid)
+    {
+        if (empty($bid)) {
+            return;
+        }
+
+        // 获取新章节
+        $chapters        = $this->getChaptersByBid($bid);
+        $chapterContents = $this->getChapterContentsByBid($bid);
+        if (empty($chapters) || empty($chapterContents)) {
+            return;
+        }
+
+        $chaptersData        = $this->buildChaptersData($chapters);
+        $chapterContentsData = $this->buildChaptersData($chapterContents);
+
+        // 组装更新数据-更新章节表chapter_content_id
+        $chaptersUpdateData = [];
+        foreach ($chaptersData as $key => $value) {
+            $chapterContent       = getProp($chapterContentsData, $key, []);
+            $chaptersUpdateData[] = [
+                'id'                 => getProp($value, 'id'),
+                'chapter_content_id' => (int)getProp($chapterContent, 'id'),
+            ];
+        }
+
+        // 执行批量更新
+        $this->batchUpdateDb('chapters', $chaptersUpdateData);
+    }
+
+    /**
+     * @param $chapters
+     * @return array
+     */
+    private function buildChaptersData($chapters): array
+    {
+        if (empty($chapters)) {
+            return [];
+        }
+
+        $result = [];
+        foreach ($chapters as $chapter) {
+            $bid         = getProp($chapter, 'bid');
+            $zwBid       = getProp($chapter, 'zw_bid');
+            $zwChapterId = getProp($chapter, 'zw_chapter_id');
+            $key         = $bid . '_' . $zwBid . '_' . $zwChapterId;
+            if (!isset($result[$key])) {
+                $result[$key] = $chapter;
+            }
+        }
+
+        return $result;
+    }
+
+    /**
+     * @param $bid
+     * @return array
+     */
+    private function getChaptersByBid($bid): array
+    {
+        if (empty($bid)) {
+            return [];
+        }
+
+        $chapters = Chapter::select('id', 'bid', 'zw_bid', 'zw_chapter_id')->where('bid', $bid)->get();
+        return $chapters ? $chapters->toArray() : [];
+    }
+
+    /**
+     * @param $bid
+     * @return array
+     */
+    private function getChapterContentsByBid($bid): array
+    {
+        if (empty($bid)) {
+            return [];
+        }
+
+        $chapters = ChapterContent::select('id', 'bid', 'zw_bid', 'zw_chapter_id')->where('bid', $bid)->get();
+        return $chapters ? $chapters->toArray() : [];
+    }
+
+    /**
+     * 查询内容平台章节列表
+     *
+     * @param $bid
+     * @return array
+     */
+    private function getZwChapters($bid): array
+    {
+        if (empty($bid)) {
+            return [];
+        }
+
+        $result = DB::connection('zw_content_mysql')
+                    ->table('chapters')
+                    ->select('id', 'bid', 'name', 'sequence', 'size', 'is_vip', 'chapter_content_id', 'is_deleted', 'is_draft')
+                    ->where('bid', $bid)
+//                    ->where('is_draft', 0)
+//                    ->where('is_check', 1)
+//                    ->where('is_deleted', 0)
+                    ->orderBy('sequence')
+                    ->get();
+        return $result ? $result->toArray() : [];
+    }
+
+    /**
+     * @param $zwChapterContentIds
+     * @return array
+     */
+    private function getZwChaptersContent($zwChapterContentIds): array
+    {
+        if (empty($zwChapterContentIds)) {
+            return [];
+        }
+
+        $result = DB::connection('zw_content_mysql')
+                    ->table('chapter_contents')
+                    ->select('id', 'content')
+                    ->whereIn('id', $zwChapterContentIds)
+                    ->get();
+        return $result ? $result->toArray() : [];
+    }
+}

+ 53 - 0
app/Console/Test/ChangeUserChannelId.php

@@ -0,0 +1,53 @@
+<?php
+
+namespace App\Console\Test;
+
+use App\Cache\UserCache;
+use App\Models\User\User;
+use Illuminate\Console\Command;
+
+class ChangeUserChannelId extends Command
+{
+    /**
+     * @var string
+     */
+    protected $signature = 'user:change:channel:id {uid} {channelId}';
+
+    /**
+     * The console command description.
+     *  php artisan Payment:BasePayment --bid='1'
+     *
+     * @var string
+     */
+    protected $description = '修改用户站点id';
+
+    public function handle()
+    {
+        $uid       = (int)$this->argument('uid');
+        $channelId = (int)$this->argument('channelId');
+        if ($uid < 1 || $channelId < 1) {
+            dd('参数错误');
+        }
+
+        // 获取uid信息
+        $user = User::where('id', $uid)->first();
+        if (!getProp($user, 'id')) {
+            dd('查询不到该用户');
+        }
+
+        // 执行更新
+        $user->distribution_channel_id = $channelId;
+        $user->save();
+
+        // 获取用户缓存信息
+        $token     = getProp($user, 'token');
+        $userCache = UserCache::getTokenData($token);
+        if (empty($userCache)) {
+            dd('查询不到用户token缓存');
+        }
+
+        // 更新用户缓存
+        $userCache['distribution_channel_id'] = $channelId;
+        UserCache::setTokenData($token, $userCache);
+    }
+}

+ 32 - 0
app/Console/Test/TestCache.php

@@ -0,0 +1,32 @@
+<?php
+
+namespace App\Console\Test;
+
+use Illuminate\Console\Command;
+use Illuminate\Support\Facades\Redis;
+
+class TestCache extends Command
+{
+    /**
+     * @var string
+     */
+    protected $signature = 'test:cache';
+
+    /**
+     * The console command description.
+     *  php artisan Payment:BasePayment --bid='1'
+     *
+     * @var string
+     */
+    protected $description = '测试缓存';
+
+    /**
+     * handle
+     */
+    public function handle()
+    {
+        $key = 'FGCOuXrAPi80aWgH';
+        Redis::set($key, 'test');
+    }
+
+}

+ 111 - 0
app/Console/Test/TestCommand.php

@@ -0,0 +1,111 @@
+<?php
+
+namespace App\Console\Test;
+
+use App\Cache\StatisticCache;
+use App\Cache\UserCache;
+use App\Facade\Site;
+use App\Jobs\ReportDy;
+use App\Libs\Utils;
+use App\Models\User\User;
+use App\Models\User\UserEarningsLogs;
+use App\Services\OpenApi\OpenService;
+use App\Services\Pay\PayService;
+use Illuminate\Console\Command;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Facades\Redis;
+use Vinkla\Hashids\Facades\Hashids;
+
+/**
+ * 测试
+ */
+class TestCommand extends Command
+{
+    /**
+     * @var string
+     */
+    protected $signature = 'test {--token=} {--uid=} {--eid=}';
+
+    /**
+     * The console command description.
+     *  php artisan Payment:BasePayment --bid='1'
+     *
+     * @var string
+     */
+    protected $description = '测试';
+
+    private $openService;
+    private $payService;
+
+    public function __construct(
+        OpenService $openService,
+        PayService $payService
+    )
+    {
+        parent::__construct();
+        $this->openService = $openService;
+        $this->payService = $payService;
+    }
+
+
+    /**
+     * handle
+     */
+    public function handle()
+    {
+//        $result = $this->payService->reportPay(18, date('YmdHis').getMillisecond().rand(1000,9999));
+//        dd($result);
+//
+//        dd('exit');
+        $token = $this->option('token');
+        $uid = $this->option('uid');
+        $eid = $this->option('eid');
+        if ($eid) dd(Hashids::decode($eid));
+        if (!$token) $token = '67ecfebf74424f52472cec71769c339f';
+        if (!$uid) $uid = 18;
+
+//        $instance   = $this->openService->getInstance();
+//        $res = $instance->generateShareLink('app/home', json_encode(['promotion_code'=>100001, 'invite_code'=>100023]));
+//        $res = $instance->getShareQueryInfo('https://z.douyin.com/6sJ3LOR');
+//        dd($res);
+//        $path = urlencode('pages/index/index?invite_code=100055');
+//        $res = $instance->createQRCode('pages/index/index');
+
+        // 写入测试用户token
+        UserCache::setTokenData($token, ['uid'=>$uid, 'distribution_channel_id'=>1, 'send_order_id'=>0, 'from_uid'=>0]);
+
+        // 写入测试用户最近阅读记录
+        UserCache::delRecentBooks($uid);
+        $bids = [1,5,6,4,29,59,47,68,53,86,87,58,100,37];
+        foreach ($bids as $bid) {
+            $book_info = DB::table('books')->where('id', $bid)->select('name', 'cover')->first();
+            $random_sequence = mt_rand(1,20);
+            $chapter = DB::table('chapters')->where(['bid'=>$bid, 'is_check'=>1, 'is_deleted'=>0, 'sequence'=>$random_sequence])
+                ->select('id', 'name')->first();
+
+            UserCache::setRecentBooks($uid, [
+                'bid'           => $bid,
+                'book_name'     => getProp($book_info, 'name'),
+                'cover'         => getProp($book_info, 'cover'),
+                'cid'           => getProp($chapter, 'id'),
+                'chapter_name'  => getProp($chapter, 'name'),
+                'read_time'     => date('Y-m-d H:i:s', time()+mt_rand(0,1800)),
+                'sequence'      => $random_sequence,
+                'gender'        => 2
+            ]);
+        }
+
+        // 写入测试用户当前阅读书籍和章节
+        UserCache::delCurrentChapter($uid);
+        UserCache::setCurrentChapter($uid, [
+            'bid'           => 94,
+            'book_name'     => '闪婚后,晏先生被甜妻撩上瘾',
+            'cover'         => 'http://zwcontent.oss-cn-hangzhou.aliyuncs.com/books/cover/ed5Ntjzi3W.jpg',
+            'cid'           => 100000970,
+            'chapter_name'  => '第152章 吃瓜',
+            'read_time'     => date('Y-m-d H:i:s', time()+1800),
+            'sequence'      => 152,
+            'gender'        => 2,
+        ]);
+    }
+}

+ 44 - 0
app/Console/Test/TestOpenApi.php

@@ -0,0 +1,44 @@
+<?php
+
+namespace App\Console\Test;
+
+use App\Dao\Account\AccountDao;
+use App\Dao\User\UserDao;
+use App\Services\OpenApi\OpenService;
+use Illuminate\Console\Command;
+
+class TestOpenApi extends Command
+{
+    /**
+     * @var string
+     */
+    protected $signature = 'test:open:api';
+
+    /**
+     * The console command description.
+     *  php artisan Payment:BasePayment --bid='1'
+     *
+     * @var string
+     */
+    protected $description = '测试open api';
+
+    private $openService;
+
+    public function __construct(
+        OpenService $openService
+    )
+    {
+        parent::__construct();
+        $this->openService = $openService;
+    }
+
+    /**
+     * handle
+     */
+    public function handle()
+    {
+        $this->openService->getOpenInstance()->eventList();
+        $this->openService->getInstance()->permissionList();
+    }
+
+}

+ 42 - 0
app/Console/Test/TestSms.php

@@ -0,0 +1,42 @@
+<?php
+
+namespace App\Console\Test;
+
+use App\Libs\AliSMS;
+use Illuminate\Console\Command;
+
+class TestSms extends Command
+{
+    /**
+     * @var string
+     */
+    protected $signature = 'test:sms';
+
+    /**
+     * The console command description.
+     *  php artisan Payment:BasePayment --bid='1'
+     *
+     * @var string
+     */
+    protected $description = '测试sms';
+
+    /**
+     * handle
+     */
+    public function handle()
+    {
+        $queryRes = AliSMS::querySmsTemplateList();
+        if ($queryRes->statusCode === 200) {
+            foreach ($queryRes->body->smsTemplateList as $item) {
+                var_dump([$item->templateCode, $item->templateName, $item->templateContent]);
+            }
+        }
+
+        /*
+        $sendRes = AliSMS::sendSms('13127819373', 'SMS_165400036', ['code' => 8978], env('SMS_SIGN'));
+        var_dump($sendRes->body);
+        var_dump($sendRes->statusCode);
+        */
+    }
+
+}

+ 56 - 0
app/Console/Test/TestZfbTransFundCommand.php

@@ -0,0 +1,56 @@
+<?php
+
+namespace App\Console\Test;
+
+use App\Cache\UserCache;
+use App\Libs\Utils;
+use Illuminate\Console\Command;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Facades\Redis;
+use Ycpay\Alitrans;
+use Ycpay\Byte;
+use Ycpay\Ali;
+/**
+ * 测试支付宝转账
+ */
+class TestZfbTransFundCommand extends Command
+{
+    /**
+     * @var string
+     */
+    protected $signature = 'test_zfb_trans_fund';
+
+    /**
+     * The console command description.
+     *
+     * @var string
+     */
+    protected $description = '测试支付宝转账';
+
+
+    /**
+     * handle
+     */
+    public function handle()
+    {
+        $uid = 18;
+
+        $config = [
+            'appid'=>'2021003192638805',
+            'secret'=>'2021003192638805',
+            'notify_url'=>'https://zfb.ycsd.cn/pay/receive_zfb_third_notify',
+            'privateKey'=>file_get_contents(storage_path('cert/huomaoxiaoshuohui/应用私钥RSA2048.txt')),
+            'publicKey'=>file_get_contents(storage_path('cert/huomaoxiaoshuohui/应用公钥RSA2048.txt')),
+        ];
+        $object = new Alitrans();
+        
+        \Log::info('$config');
+        \Log::info($config);
+//         Byte::init($config);
+        $object->init($config);
+        $res = $object->queryAccountFund();
+        \Log::info('$res');
+        \Log::info($res);
+
+    }
+}

+ 80 - 0
app/Console/TikTok/AccessTokenManage.php

@@ -0,0 +1,80 @@
+<?php
+
+namespace App\Console\TikTok;
+
+use App\Dao\Account\AccountDao;
+use App\Services\OpenApi\OpenService;
+use App\Libs\TikTok\Kernel\Exceptions;
+use Illuminate\Console\Command;
+
+class AccessTokenManage extends Command
+{
+    /**
+     * @var string
+     */
+    protected $signature = 'tiktok:accessToken:refresh';
+
+    /**
+     * The console command description.
+     *
+     * @var string
+     */
+    protected $description = '抖音access token刷新管理';
+
+    private $accountDao;
+    private $openService;
+
+    public function __construct(
+        AccountDao  $accountDao,
+        OpenService $openService
+    )
+    {
+        parent::__construct();
+        $this->accountDao  = $accountDao;
+        $this->openService = $openService;
+    }
+
+    /**
+     * @return void
+     * @throws Exceptions\HttpException
+     * @throws Exceptions\InvalidArgumentException
+     * @throws Exceptions\InvalidConfigException
+     * @throws Exceptions\RuntimeException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     * @throws \Psr\SimpleCache\InvalidArgumentException
+     */
+    public function handle()
+    {
+        // 获取所有小程序
+        $apps = $this->accountDao->getMiniApps();
+        if (empty($apps)) {
+            return;
+        }
+
+        foreach ($apps as $app) {
+            // 数据库记录的token到期时间
+            $tokenExpiresAt = getProp($app, 'access_token_expires_at');
+
+            // token到期前10分钟开始更新
+            if (strtotime($tokenExpiresAt) - time() > 600) {
+                continue;
+            }
+
+            // 实例化并获取token
+            $tokenData = $this->openService->getInstance([
+                'app_id'  => getProp($app, 'app_id'),
+                'secret'  => getProp($app, 'app_secret'),
+                'sandbox' => (bool)getProp($app, 'is_sandbox'),
+            ])->getAccessToken(true);
+            if ($tokenData) {
+                // 更新数据库
+                $seconds = (int)getProp($tokenData, 'expires_in');
+                $this->accountDao->updateMiniApp(['id' => getProp($app, 'id')], [
+                    'access_token'            => getProp($tokenData, 'access_token'),
+                    'access_token_expires_at' => date('Y-m-d H:i:s', strtotime("+ $seconds second")),
+                ]);
+            }
+        }
+    }
+
+}

+ 79 - 0
app/Console/TikTok/ClientTokenManage.php

@@ -0,0 +1,79 @@
+<?php
+
+namespace App\Console\TikTok;
+
+use App\Dao\Account\AccountDao;
+use App\Services\OpenApi\OpenService;
+use App\Libs\TikTok\Kernel\Exceptions;
+use Illuminate\Console\Command;
+
+class ClientTokenManage extends Command
+{
+    /**
+     * @var string
+     */
+    protected $signature = 'tiktok:clientToken:refresh';
+
+    /**
+     * The console command description.
+     *
+     * @var string
+     */
+    protected $description = '抖音client token刷新管理';
+
+    private $accountDao;
+    private $openService;
+
+    public function __construct(
+        AccountDao  $accountDao,
+        OpenService $openService
+    )
+    {
+        parent::__construct();
+        $this->accountDao  = $accountDao;
+        $this->openService = $openService;
+    }
+
+    /**
+     * @return void
+     * @throws Exceptions\HttpException
+     * @throws Exceptions\InvalidArgumentException
+     * @throws Exceptions\InvalidConfigException
+     * @throws Exceptions\RuntimeException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     * @throws \Psr\SimpleCache\InvalidArgumentException
+     */
+    public function handle()
+    {
+        // 获取所有小程序
+        $apps = $this->accountDao->getMiniApps();
+        if (empty($apps)) {
+            return;
+        }
+
+        foreach ($apps as $app) {
+            // 数据库记录的token到期时间
+            $tokenExpiresAt = getProp($app, 'client_token_expires_at');
+
+            // token到期前10分钟开始更新
+            if (strtotime($tokenExpiresAt) - time() > 600) {
+                continue;
+            }
+
+            // 实例化并获取token
+            $tokenData = $this->openService->getOpenInstance([
+                'app_id'  => getProp($app, 'app_id'),
+                'secret'  => getProp($app, 'app_secret'),
+            ])->getClientToken(true);
+            if ($tokenData) {
+                // 更新数据库
+                $seconds = (int)getProp($tokenData, 'expires_in');
+                $this->accountDao->updateMiniApp(['id' => getProp($app, 'id')], [
+                    'client_token'            => getProp($tokenData, 'access_token'),
+                    'client_token_expires_at' => date('Y-m-d H:i:s', strtotime("+ $seconds second")),
+                ]);
+            }
+        }
+    }
+
+}

+ 84 - 0
app/Consts/BaseConst.php

@@ -0,0 +1,84 @@
+<?php
+
+
+namespace App\Consts;
+
+
+class BaseConst
+{
+    /**
+     * 一天86400秒
+     */
+    const ONE_DAY_SECONDS = 86400;
+
+    /**
+     * 一周 86400 * 7 秒
+     */
+    const ONE_WEEK_SECONDS = 604800;
+
+    /**
+     * 一个月 86400 * 31 秒
+     */
+    const ONE_MONTH_SECONDS = 2678400;
+
+    /**
+     * 一天24*60分钟
+     */
+    const ONE_DAY_MINITUES = 1440;
+
+    /**
+     * 一分钟60秒
+     */
+    const ONE_MINUTE_SECONDS = 60;
+
+    /**
+     * 一小时3600秒
+     */
+    const ONE_HOUR = 3600;
+
+    /**
+     * 一年365天
+     */
+    const ONE_YEAR_DAYS = 365;
+
+    /**
+     * 默认锁时长
+     */
+    const LOCK_DEFAULT_SECONDS = 5;
+    /**
+     * 显示7天以前数据
+     */
+    const DATE_RANGE_DAYS = 7;
+    /**
+     * 默认分页数量
+     */
+    const DEFAULT_LENGTH = 15;
+
+    /**
+     * 每日分享量(达到该数值可获得一天会员)
+     */
+    const DAILY_SHARE_NUM = 5;
+
+    /**
+     * 初级vip等级
+     */
+    const JUNIOR_VIP_LEVEL = 1;
+
+    /**
+     * 高级vip登记等级
+     */
+    const SENIOR_VIP_LEVEL = 2;
+
+    const USER_SCOPE = [
+        '1' => '补充模板未覆盖人群',
+        '2' => '付费用户',
+        '3' => '派单用户'
+    ];
+
+    const PRODUCT_TYPE = [
+        'WEEK'      => '周卡',
+        'MONTH'     => '月卡',
+        'QUARTER'   => '季卡',
+        'YEAR'      => '年卡'
+    ];
+}

+ 17 - 0
app/Consts/BillConst.php

@@ -0,0 +1,17 @@
+<?php
+
+namespace App\Consts;
+
+class BillConst
+{
+    // 服务费比例
+    const SERVICE_RATE = 0.01;
+
+    // 结算比例
+    const SETTLEMENT_RATE = 0.9;
+
+    // 结算类型
+    const SETTLEMENT_TYPE_CHANNEL  = 'channel';  // 渠道结算
+    const SETTLEMENT_TYPE_USER     = 'user';     // 用户提现
+    const SETTLEMENT_TYPE_PLATFORM = 'platform'; // 平台
+}

+ 144 - 0
app/Consts/CoinConst.php

@@ -0,0 +1,144 @@
+<?php
+
+
+namespace App\Consts;
+
+
+class CoinConst
+{
+    /**
+     * 1块钱书对应100书币
+     */
+    const PRICE_COIN_RATIO = 100;
+
+    /**
+     * 首个计费章节书币价格以35书币为基准,低于35提升至35
+     */
+    const BASE_PRICE_COIN = 35;
+
+    /**
+     * 按章节付费,默认千字0.06元(6书币)
+     */
+    const DEFAULT_CHAPTER_PRICE = 0.06;
+
+    /**
+     * 初级vip 6分
+     */
+    const JUNIOR_CHAPTER_PRICE = 0.06;
+
+    /**
+     * 高级vip 6分
+     */
+    const SENIOR_CHAPTER_PRICE = 0.06;
+
+    /**
+     * 初级77折、高级55折
+     */
+    const JUNIOR_CHAPTER_DISCOUNT = 0.77;
+    const SENIOR_CHAPTER_DISCOUNT = 0.55;
+
+    /**
+     * IOS充值
+     */
+    const IOS_COIN_DISCOUNT = 0.65;
+
+    /**
+     * 书币增加类型
+     */
+    const INCREASE = 1;
+
+    /**
+     * 书币减少类型
+     */
+    const DECREASE = 0;
+
+    /**
+     * 永久有效
+     */
+    const FOREVER = 'forever';
+
+    /**
+     * 非永久有效
+     */
+    const NO_FOREVER = 'noForever';
+
+    /**
+     * 原创书币
+     */
+    const TYPE_ORIGIN = 'origin';
+
+    /**
+     * 奖金币
+     */
+    const TYPE_AWARD = 'award';
+
+    /**
+     * 订阅包月扣减书币
+     */
+    const CHANGE_TYPE_DECR_MONTH_BAG = 'sub_month_bag';
+
+    /**
+     * 订阅图书扣减书币
+     */
+    const CHANGE_TYPE_DECR_BOOK = 'sub_book';
+
+    /**
+     * 订阅章节扣减书币
+     */
+    const CHANGE_TYPE_DECR_CHAPTER = 'sub_chapter';
+
+    /**
+     * 送礼物
+     */
+    const CHARGE_TYPE_DECR_GIFT = 'send_gift';
+
+    /**
+     * 充值
+     */
+    const CHANGE_TYPE_INCR_CHARGE = 'recharge';
+
+    /**
+     * 充值补送
+     */
+    const CHANGE_TYPE_INCR_CHARGE_RESEND = 'recharge_resend';
+
+    /**
+     * 签到
+     */
+    const CHANGE_TYPE_INCR_SING_IN = 'sign_in';
+
+    /**
+     * 答题
+     */
+    const CHANGE_TYPE_INCR_QUESTION = 'question';
+
+    /**
+     * 答题奖励(排行榜)
+     */
+    const CHANGE_TYPE_INCR_QUESTION_BONUS = 'question_bonus';
+
+    /**
+     * 活动
+     */
+    const CHANGE_TYPE_INCR_ACTIVITY = 'activity';
+
+    /**
+     * 个人号
+     */
+    const CHANGE_TYPE_INCR_PERSONAL = 'personal';
+
+    /**
+     * 过期类型
+     */
+    const CHANGE_TYPE_EXPIRE = 'expire';
+
+    /**
+     * 数据迁移
+     */
+    const CHANGE_TYPE_MOVE = 'move';
+
+    /**
+     * 系统补送
+     */
+    const CHANGE_TYPE_SYS_SEND = 'sys_send';
+}

+ 84 - 0
app/Consts/ErrorConst.php

@@ -0,0 +1,84 @@
+<?php
+
+namespace App\Consts;
+
+class ErrorConst
+{
+    // 基础错误
+    const RECORD_NOT_EXIST                  = '1001:记录不存在';
+    const DATA_EXCEPTION                    = '1002:数据异常';
+    const PARAM_ERROR_CODE                  = '1003:请确认填写的数据无误';
+    const TASK_DOING                        = '1004:任务执行中';
+    const NOT_ACCESS                        = '1005:没有权限';
+    const SYS_EXCEPTION                     = '1006:系统错误';
+    const FREQUENT_OPERATION                = '1007:操作频繁,请稍后操作';
+    const NOT_LOGIN                         = '1008:请先登录';
+    const NOT_OPENED                        = '1009:功能暂未开放';
+    const DB_INVALID                        = '1010:信息更新失败,请联系管理员';
+    const STOP_TRANSACTION                  = '1011:事务异常,中止事务';
+    const ACCOUNT_NOT_MATCH_DOMAIN          = '1012:账号与域名不匹配';
+    const REDIS_KEY_NOT_EXIST               = '1013:the redis key is not found';
+    const REPORT_FAILED                     = '1014:回传上报错误';
+    const CHANNEL_ID_INVALID                = '1015:确认选择有效站点';
+    const NO_DATA_FOR_EXPORT                = '1016:无有效数据可导出';
+    const FINANCE_SMS_FAILED                = '1017:手机验证码发送失败';
+    const FINANCE_SMS_EXIST                 = '1018:手机验证码已发送';
+    const FINANCE_SMS_CHECK_FAILED          = '1019:手机验证码错误';
+    const FINANCE_SMS_CHECK_EXPIRED         = '1020:手机验证码已过期';
+    const FINANCE_SMS_CHECK_LIMIT           = '1021:手机验证码发送频率过快';
+    const FINANCE_PHONE_ERROR               = '1022:请联系开发者设置有效手机号';
+    const FINANCE_CARD_NUMBER_ERROR         = '1023:银行卡号格式不正确';
+    const DATE_START_GREATER_THAN_END       = '1024:开始时间不能大于结束时间';
+    const DATE_RANGE_LESS_THAN_7DAY         = '1025:只能选择7日前日期范围';
+
+    // 业务错误
+    const CHAPTER_NOT_EXIST                 = '10001:该章节不存在,请重试~';
+    const VIP_VALID                         = '10002:付费章节需开通会员才可阅读';
+    const BOOK_NOT_EXIST                    = '10003:该书籍不存在或已下架,请重试~';
+    const SEND_ORDER_NOT_EXIST              = '10004:该派单链接不存在,请确认后重试~';
+    const CHANNEL_NOT_EXIST                 = '10005:该站点不存在,请确认后重试~';
+    const USER_COIN_NOT_ENOUGH              = '10006:您的书币不足,请先充值~';
+    const DECREASE_COIN_FAILED              = '10007:书币扣减失败,请从目录页重新进入当前章节~';
+    const SEND_ORDER_NAME_INVALID           = '10008:派单名称重复,请修改后重试~';
+    const DATE_RANGE_OVER_MONTH             = '10009:时间范围筛选超过一个月';
+
+	// 用户相关
+    const USER_INI_FAIL                     = '20001:用户创建失败';
+    const USER_DATA_IS_VALID                = '20002:用户数据异常';
+    const USER_IS_NOT_EXIST                 = '20003:用户不存在';
+    const USER_DATA_IS_EMPTY                = '20004:用户数据不完整';
+    const USER_OPENID_NOT_MATCH             = '20005:用户openid不匹配';
+    const USER_APPID_NOT_MATCH              = '20006:用户appid不匹配';
+    const USER_AUTH_FAIL                    = '20007:授权失败,请联系客服';
+    const USER_IS_EXIST                     = '20007:用户已存在';
+    const USER_NO_ACCESS_CHANNEL            = '20008:当前登录用户无此站点权限';
+
+    // Finance
+    const FINANCE_ACCOUNT_EXISTED           = '60001:账户已在使用中';
+    const WITHDRAW_CASH_AMOUNT              = '60002:提现金额必须 >= 100';
+    const WITHDRAW_CASH_AMOUNT_MORE         = '60002:提现金额不能大于20万';
+    const WITHDRAW_CASH_AMOUNT_INSUFFICIEN  = '60003:可提现金额不足';
+    const WITHDRAW_CASH_AMOUNT_FROZEN       = '60004:账号已冻结';
+    const WITHDRAW_CASH_AMOUNT_NO_CHANGE    = '60005:不能修改状态';
+    const WITHDRAW_CASH_AMOUNT_ACCOUNT      = '60006:银行卡账号未设置';
+    const PAYMENT_CHANNEL_AMOUNT_WITHOUT    = '60007:渠道可打款金额不足,请走人工打款';
+    const LIQUIDATED_STAT_AMOUNT_WITHOUT    = '60008:渠道可清算金额不足';
+    const COMMISSION_RATE_WITHOUT           = '60009:结算比例在 0.1 到 0.99 之间';
+    const WITHDRAW_CASH_TODAY_USE           = '60010:您今天已经提现过了!';
+    const PAYMENT_AUTO_NOT_OPEN             = '60007:自动打款暂未开通,请人工打款!';
+    const PAYMENT_WITHDRAW_MONEY_TOO        = '60012:已成功打款!';
+    const PAYMENT_WITHDRAW_MONEY            = '60013:打款金额错误,提现金额 - 手续费 = 打款金额';
+    const PRIVATE_ACCOUNT_NOT_SUFFICIENT    = '60014:当前是对私提现,对私账户余额不足!';
+    const PRIVATE_ACCOUNT_NO_ACCESS         = '60015:当前账号无权限';
+    const CAPTCHA_VERIFY_ERROR              = '10021:验证码错误';
+    const NOT_ALLOWED_PRIVATE_ACCOUNT       = '10018:该渠道只能对公打款';
+    const WITHDRAW_UPDATING                 = '10019:提现升级中,请稍后再试!';
+    const WITHDRAW_PRIVATE_NOT_ENOUGH       = '10020:当前对私账户余额不足!';
+    const WITHDRAW_NOT_ENOUGH               = '10021:账户余额不足!';
+    const CASH_ACCOUNT_NOT_SET              = '10006:提现账户未设置';
+    const CASH_ACCOUNT_EXIST                = '10007:提现账户已存在';
+    const INSUFFICIENT_BALANCE              = '10008:余额不足';
+    const LESS_THAN_LOWEST_WITHDRAW_MONEY   = '10009:提现不能低于100';
+    const WITHDRAW_CASHES_FAILED            = '10012:提现失败';
+    const WITHDRAW_CASH_AMOUNT_NOT_MATCH    = '10013:银行卡账号对公对私与提现选项不匹配';
+}

+ 70 - 0
app/Consts/FinanceConsts.php

@@ -0,0 +1,70 @@
+<?php
+
+namespace App\Consts;
+
+class FinanceConsts
+{
+    // 对私提现最小
+    const WITH_DRAW_PRIVATE_MIN = 100;
+
+    // 对私提现最大
+    const WITH_DRAW_PRIVATE_MAX = 200000;
+
+    // 对公
+    const COMPANY_YES = 1;
+
+    // 对私
+    const COMPANY_NO = 0;
+
+    // 禁止编辑
+    const STATUS_FORBID_UPDATE = -1;
+
+    // 默认
+    const STATUS_DEFAULT = 0;
+
+    // 公司主体
+    const PAY_COMPANIES = [
+        [
+            'id'                  => 1,
+            'name'                => '杭州新阅网络科技有限公司',
+            'pay_channel_id'      => 0,
+            'only_public_account' => 0
+        ]
+    ];
+
+    const CHECK_PENDING = '待审核';
+
+    const AUDITING = '审核中';
+
+    const AUDIT_PASS = '审核通过';
+
+    const AUDIT_FAILED = '审核不通过';
+
+    const WAITTING_PAID = '待打款';
+
+    const AUTO_WAITTING_PAID = '自动待打款';
+
+    const ARTIFICAL_WAITTING_PAID = '人工待打款';
+
+    const PAYING = '打款中';
+
+    const AUTO_PAYING = '自动打款中';
+
+    const ARTIFICAL_PAYING = '人工打款中';
+
+    const PAID_SUCCESS = '打款成功';
+
+    const AUTO_PAID_SUCCESS = '自动打款成功';
+
+    const ARTIFICAL_PAID_SUCCESS = '人工打款成功';
+
+    const PAID_FAILED = '打款失败';
+
+    const AUTO_PAID_FAILED = '自动打款失败';
+
+    const ARTIFICAL_PAID_FAILED = '人工打款失败';
+
+    const UNKNOWN_ERROR = '未知错误码';
+
+    const OTHER_ERROR = '其他错误';
+}

+ 8 - 0
app/Consts/SysConst.php

@@ -0,0 +1,8 @@
+<?php
+
+namespace App\Consts;
+
+class SysConst
+{
+    const DEFAULT_CHANNEL_ID = 2; // 默认站点
+}

+ 65 - 0
app/Dao/Account/AccountDao.php

@@ -0,0 +1,65 @@
+<?php
+
+namespace App\Dao\Account;
+
+use App\Models\Account\OfficialAccount;
+
+class AccountDao
+{
+    /**
+     * 查询小程序列表
+     *
+     * @param array $where
+     * @param int   $limit
+     * @return array
+     */
+    public function getMiniApps(array $where = [], int $limit = 0): array
+    {
+        $fields = ['id', 'name', 'app_id', 'app_secret', 'access_token', 'access_token_expires_at',
+            'client_token', 'client_token_expires_at', 'is_enabled', 'is_sandbox'];
+        $query  = OfficialAccount::select($fields)->where('is_enabled', 1);
+
+        // 查询sandbox应用
+        if (isset($where['is_sandbox']) && in_array($where['is_sandbox'], [0, 1])) {
+            $query->where('is_sandbox', $where['is_sandbox']);
+        }
+
+        // limit查询长度
+        if ($limit) {
+            $query->limit($limit);
+        }
+
+        $result = $query->get();
+        return $result ? $result->toArray() : [];
+    }
+
+    /**
+     * 获取小程序信息
+     *
+     * @param $appId
+     * @return array
+     */
+    public function getMiniAppByAppId($appId): array
+    {
+        if (empty($appId)) {
+            return [];
+        }
+
+        $result = OfficialAccount::where('app_id', $appId)->first();
+        return $result ? $result->toArray() : [];
+    }
+
+    /**
+     * @param $where
+     * @param $data
+     * @return false
+     */
+    public function updateMiniApp($where, $data): bool
+    {
+        if (empty($where) || empty($data)) {
+            return false;
+        }
+
+        return OfficialAccount::where($where)->update($data);
+    }
+}

+ 159 - 0
app/Dao/Bank/BankDao.php

@@ -0,0 +1,159 @@
+<?php
+
+namespace App\Dao\Bank;
+
+use App\Cache\FinanceCache;
+use App\Consts\FinanceConsts;
+use App\Models\Bank\Banks;
+use App\Models\Bank\ChannelUserCashAccount;
+
+class BankDao
+{
+    /**
+     * @return mixed
+     */
+    public function bankList()
+    {
+        return Banks::select('id', 'name', 'code', 'source')
+                    ->where('is_show', 1)
+                    ->where('source', 'LIANLIANPAY')
+                    ->orderBy('id', 'asc')
+                    ->get();
+    }
+
+    /**
+     * @param $bankId
+     * @return mixed
+     */
+    public function getBank($bankId)
+    {
+        return Banks::select('id', 'name', 'code', 'source')
+                    ->where('id', $bankId)
+                    ->where('is_show', 1)
+                    ->first();
+    }
+
+    /**
+     * @param     $channelUserId
+     * @param int $isCompany
+     * @return array
+     */
+    public function getValidCashAccountsByChannelUserId($channelUserId, int $isCompany = -1): array
+    {
+        if (empty($channelUserId)) {
+            return [];
+        }
+
+        $query = ChannelUserCashAccount::where('channel_user_id', $channelUserId)->where('deleted_at', '=', null);
+
+        // 对公对私
+        if (in_array($isCompany, [0, 1])) {
+            $query->where('is_company', $isCompany);
+        } else {
+            $query->orderBy('is_company', 'desc');
+        }
+
+        $result = $query->orderBy('updated_at', 'desc')->get();
+        return $result ? $result->toArray() : [];
+    }
+
+    /**
+     * @param $id
+     * @return array
+     */
+    public function getValidCashAccountById($id): array
+    {
+        if (empty($id)) {
+            return [];
+        }
+
+        $result = ChannelUserCashAccount::where('id', $id)->where('deleted_at', '=', null)->first();
+        return $result ? $result->toArray() : [];
+    }
+
+    /**
+     * 添加银行账号
+     *
+     * @param $param
+     * @return bool|int
+     */
+    public function addCashAccount($param)
+    {
+        $channelUserId = (int)getProp($param, 'channel_user_id');
+        $cardNumber    = trim(getProp($param, 'card_number'));
+        if (empty($channelUserId) || empty($cardNumber) || empty($param)) {
+            return false;
+        }
+
+        $data = [
+            'channel_user_id' => $channelUserId,
+            'account_name'    => trim(getProp($param, 'account_name')),
+            'identity_card'   => trim(getProp($param, 'identity_card')),
+            'card_number'     => $cardNumber,
+            'account_bank'    => trim(getProp($param, 'account_bank')),
+            'bank'            => trim(getProp($param, 'bank')),
+            'province'        => trim(getProp($param, 'province')),
+            'phone'           => trim(getProp($param, 'phone')),
+            'bank_id'         => (int)getProp($param, 'bank_id'),
+            'is_company'      => (int)getProp($param, 'is_company'),
+            'status'          => FinanceConsts::STATUS_FORBID_UPDATE,
+            'created_at'      => date('Y-m-d H:i:s'),
+            'updated_at'      => date('Y-m-d H:i:s'),
+        ];
+
+        return ChannelUserCashAccount::insertGetId($data);
+    }
+
+    /**
+     * 删除账号
+     *
+     * @param $cardId
+     * @return bool
+     */
+    public function deleteCashAccountByCardId($cardId): bool
+    {
+        if (empty($cardId)) {
+            return false;
+        }
+
+        return ChannelUserCashAccount::where('id', $cardId)
+                                     ->update(['deleted_at' => date('Y-m-d H:i:s')]);
+    }
+
+    /**
+     * @param $channelUserId
+     * @return bool
+     */
+    public function deleteCashAccountByChannelUserId($channelUserId): bool
+    {
+        if (empty($channelUserId)) {
+            return false;
+        }
+
+        return ChannelUserCashAccount::where('channel_user_id', $channelUserId)
+                                     ->update(['deleted_at' => date('Y-m-d H:i:s')]);
+    }
+
+    /**
+     * 校验手机验证码
+     *
+     * @param $channelId
+     * @param $phone
+     * @param $code
+     * @return bool
+     */
+    public function checkSmsCode($channelId, $phone, $code): bool
+    {
+        if (empty($code)) {
+            return false;
+        }
+
+        // 对比验证码
+        $phoneCode = FinanceCache::getSmsCode($channelId, $phone);
+        if (empty($phoneCode) || (string)$code !== (string)$phoneCode) {
+            return false;
+        }
+
+        return true;
+    }
+}

+ 117 - 0
app/Dao/Bank/WithdrawDao.php

@@ -0,0 +1,117 @@
+<?php
+
+namespace App\Dao\Bank;
+
+use App\Consts\FinanceConsts;
+use App\Models\Withdraw\ChannelWithDrawCash;
+
+class WithdrawDao
+{
+    /**
+     * 获取多张卡今日提现记录
+     *
+     * @param $cardNumbers
+     * @param $startTime
+     * @param $endTime
+     * @return mixed
+     */
+    public function getWithDrawLogsByBankAccountIds($cardNumbers, $startTime, $endTime)
+    {
+        return ChannelWithDrawCash::whereIn('bank_account', $cardNumbers)
+                                  ->where('created_at', '>=', $startTime)
+                                  ->where('created_at', '<=', $endTime)
+                                  ->get();
+    }
+
+    /**
+     * 提现记录
+     *
+     * @param $param
+     * @return mixed
+     */
+    public function getWithdrawCashes($param)
+    {
+        $query = ChannelWithDrawCash::select('channel_withdraw_cashes.amount', 'channel_withdraw_cashes.status',
+            'bank_account', 'account_name', 'channel_withdraw_cashes.created_at', 'payments.pay_time')
+                                    ->leftJoin('payments', 'payments.withdraw_cash_id', 'channel_withdraw_cashes.id');
+
+        // 站点
+        $channelId = (int)getProp($param, 'channel_id');
+        if ($channelId) {
+            $query->where('distribution_channel_id', $channelId);
+        }
+
+        // 状态
+        $status = getProp($param, 'status');
+        if ($status && in_array($status, [FinanceConsts::CHECK_PENDING, FinanceConsts::AUDIT_FAILED, '待打款', '已打款'])) {
+            if ($status == '待打款') {
+                $query->whereIn('channel_withdraw_cashes.status', [
+                    FinanceConsts::ARTIFICAL_WAITTING_PAID,
+                    FinanceConsts::AUTO_WAITTING_PAID
+                ]);
+            } elseif ($status == '已打款') {
+                $query->whereIn('channel_withdraw_cashes.status', [
+                    FinanceConsts::ARTIFICAL_PAID_SUCCESS,
+                    FinanceConsts::AUTO_PAID_SUCCESS
+                ]);
+            } else {
+                $query->where('channel_withdraw_cashes.status', $status);
+            }
+        }
+
+        // 时间
+        $dateRange = getProp($param, 'date_range');
+        if ($dateRange) {
+            [$startDate, $endDate] = explode(',', $dateRange);
+            if ($startDate && $endDate && $startDate <= $endDate) {
+                $query->where('channel_withdraw_cashes.created_at', '>=', date('Y-m-d 00:00:00', strtotime($startDate)));
+                $query->where('channel_withdraw_cashes.created_at', '<=', date('Y-m-d 23:59:50', strtotime($endDate)));
+            }
+        }
+
+        return $query->orderBy('channel_withdraw_cashes.id', 'desc')->paginate();
+    }
+
+    /**
+     * 判断渠道在日期内是否有提现
+     *
+     * @param $channelId
+     * @param $startTime
+     * @param $endTime
+     * @return mixed
+     */
+    public function checkChannelIdIsInWithDraw($channelId, $startTime, $endTime)
+    {
+        return ChannelWithDrawCash::where('distribution_channel_id', $channelId)
+                                  ->where('created_at', '>=', $startTime)
+                                  ->where('created_at', '<=', $endTime)
+                                  ->exists();
+    }
+
+    /**
+     * 增加提现单
+     *
+     * @param $data
+     */
+    public function addWithDrawCashes($data)
+    {
+        return ChannelWithDrawCash::create($data);
+    }
+
+    /**
+     * 最近一笔提现中
+     *
+     * @param $channelId
+     * @return mixed
+     */
+    public function getLatestWithdrawingCash($channelId)
+    {
+        return ChannelWithDrawCash::where('distribution_channel_id', $channelId)
+                                  ->whereIn('status', [
+                                      FinanceConsts::CHECK_PENDING, FinanceConsts::AUDITING,
+                                      FinanceConsts::AUDIT_PASS, FinanceConsts::WAITTING_PAID
+                                  ])
+                                  ->orderBy('id', 'desc')
+                                  ->first();
+    }
+}

+ 40 - 0
app/Dao/Channel/ChannelDao.php

@@ -0,0 +1,40 @@
+<?php
+
+namespace App\Dao\Channel;
+
+use App\Models\Channel\Channel;
+
+class ChannelDao
+{
+    /**
+     * 获取站点信息
+     *
+     * @param $channelId
+     * @return array
+     */
+    public function getChannelById($channelId): array
+    {
+        if (empty($channelId)) {
+            return [];
+        }
+
+        $result = Channel::where('id', $channelId)->first();
+        return $result ? $result->toArray() : [];
+    }
+
+    /**
+     * @param $channelUserId
+     * @return array
+     */
+    public function getUserChannels($channelUserId): array
+    {
+        if (empty($channelUserId)) {
+            return [];
+        }
+
+        $channels = Channel::where('channel_user_id', $channelUserId)
+                           ->where('is_enabled', 1)
+                           ->get();
+        return $channels ? $channels->toArray() : [];
+    }
+}

+ 102 - 0
app/Dao/Channel/ChannelUserDao.php

@@ -0,0 +1,102 @@
+<?php
+
+namespace App\Dao\Channel;
+
+use App\Models\Channel\ChannelUser;
+
+class ChannelUserDao
+{
+    /**
+     * @param $account
+     * @param $password
+     * @return array
+     */
+    public function getUserByAccountAndPassword($account, $password): array
+    {
+        if (strlen($account) < 1 || strlen($password) < 1) {
+            return [];
+        }
+
+        $result = ChannelUser::where(compact('account', 'password'))->first();
+        return $result ? $result->toArray() : [];
+    }
+
+    /**
+     * @param $account
+     * @return array
+     */
+    public function getUserByAccount($account): array
+    {
+        if (strlen($account) < 1) {
+            return [];
+        }
+
+        $result = ChannelUser::where('account', $account)->first();
+        return $result ? $result->toArray() : [];
+    }
+
+    /**
+     * @param $token
+     * @return array
+     */
+    public function getUserByToken($token): array
+    {
+        if (strlen($token) < 1) {
+            return [];
+        }
+
+        $result = ChannelUser::where(compact('token'))->first();
+        return $result ? $result->toArray() : [];
+    }
+
+    /**
+     * @param $uid
+     * @return array
+     */
+    public function getUserByUid($uid): array
+    {
+        if (empty($uid)) {
+            return [];
+        }
+
+        $result = ChannelUser::where('id', $uid)->first();
+        return $result ? $result->toArray() : [];
+    }
+
+    /**
+     * @param $where
+     * @param $data
+     * @return bool
+     */
+    public function updateUserData($where, $data): bool
+    {
+        if (empty($where) || empty($data)) {
+            return false;
+        }
+
+        return ChannelUser::where($where)->update($data);
+    }
+
+    /**
+     * @param $account
+     * @param $password
+     * @return bool
+     */
+    public function createUser($account, $password): bool
+    {
+        if (strlen($account) < 1 || strlen($password) < 1) {
+            return false;
+        }
+
+        // 创建
+        $user             = new ChannelUser();
+        $user->nickname   = $account;
+        $user->account    = $account;
+        $user->phone      = '';
+        $user->password   = $password;
+        $user->is_enabled = 1;
+        $user->save();
+
+        return (bool)$user->id;
+    }
+}

+ 126 - 0
app/Dao/Order/OrderDao.php

@@ -0,0 +1,126 @@
+<?php
+
+namespace App\Dao\Order;
+
+use App\Models\Order\Order;
+
+class OrderDao
+{
+    /**
+     * @param $param
+     * @return mixed
+     */
+    public function orderList($param)
+    {
+        // 查询字段
+        $fields = [
+            'orders.id', 'orders.distribution_channel_id', 'orders.uid', 'orders.price', 'orders.status', 'orders.trade_no',
+            'orders.from_bid', 'orders.order_type', 'orders.send_order_id', 'send_orders.name as send_order_name',
+            'books.name as book_name', 'orders.created_at', 'orders.updated_at', 'users.created_at as register_time',
+            'users.register_ip', 'channel_users.nickname', 'channel_copy_user_mappings.created_at as bind_send_order_time'
+        ];
+        $query  = Order::select($fields)
+                       ->leftJoin('users', 'users.id', 'orders.uid')
+                       ->leftJoin('books', 'books.id', 'orders.from_bid')
+                       ->leftJoin('send_orders', 'send_orders.id', 'orders.send_order_id')
+                       ->leftJoin('distribution_channels', 'distribution_channels.id', 'orders.distribution_channel_id')
+                       ->leftJoin('channel_users', 'channel_users.id', 'distribution_channels.channel_user_id')
+                       ->leftJoin('channel_copy_user_mappings', 'channel_copy_user_mappings.trade_no', 'orders.trade_no');
+
+        // 站点
+        $channelId = getProp($param, 'channel_id');
+        if ($channelId) {
+            $query->whereIn('orders.distribution_channel_id', $channelId);
+        }
+
+        // 订单编号
+        $tradeNo = trim(getProp($param, 'trade_no'));
+        if ($tradeNo) {
+            $query->where('orders.trade_no', $tradeNo);
+        }
+
+        // 用户uid
+        $uid = (int)getProp($param, 'uid');
+        if ($uid) {
+            $query->where('orders.uid', $uid);
+        }
+
+        // 派单id
+        $sendOrderId = (int)getProp($param, 'send_order_id');
+        if ($sendOrderId) {
+            $query->where('orders.send_order_id', $sendOrderId);
+        }
+
+        // 充值类型
+        $orderType = trim(getProp($param, 'order_type'));
+        if (in_array($orderType, ['RECHARGE', 'YEAR', 'QUARTER', 'MONTH', 'WEEK'])) {
+            $query->where('orders.order_type', $orderType);
+        }
+
+        // 提现状态,状态 PAID:已支付 UNPAID未支付; REFUND 退款;  FAIL失败
+        $status = trim(getProp($param, 'status'));
+        if ($status) {
+            $query->where('orders.status', $status);
+        }
+
+        // 订单创建时间
+        $dateRange = getProp($param, 'date_range');
+        if ($dateRange) {
+            [$startDate, $endDate] = explode(',', $dateRange);
+            if ($startDate && $endDate && $startDate <= $endDate) {
+                $query->where('orders.created_at', '>=', date('Y-m-d 00:00:00', strtotime($startDate)));
+                $query->where('orders.created_at', '<=', date('Y-m-d 23:59:59', strtotime($endDate)));
+            }
+        }
+
+        // 导出用
+        $all = (int)getProp($param, 'all');
+        if ($all) {
+            return $query->orderBy('orders.created_at', 'desc')->get();
+        }
+
+        return $query->orderBy('orders.created_at', 'desc')->paginate(10);
+    }
+
+    /**
+     * 站点充值总额
+     *
+     * @param $channelId
+     * @return mixed
+     */
+    public function getChannelRechargeAmount($channelId)
+    {
+        return Order::where('distribution_channel_id', $channelId)
+                    ->where('status', 'PAID')
+                    ->sum('price');
+    }
+
+    /**
+     * 站点充值总额(不含今日)
+     *
+     * @param $channelId
+     * @return mixed
+     */
+    public function getChannelRechargeAmountWithoutToday($channelId)
+    {
+        return Order::where('distribution_channel_id', $channelId)
+                    ->where('status', 'PAID')
+                    ->where('created_at', '<', date('Y-m-d 00:00:00'))
+                    ->sum('price');
+    }
+
+    /**
+     * 站点月充值总额(不含今日)
+     *
+     * @param $channelId
+     * @return mixed
+     */
+    public function getChannelMonthRechargeAmountWithoutToday($channelId)
+    {
+        return Order::where('distribution_channel_id', $channelId)
+                    ->where('status', 'PAID')
+                    ->where('created_at', '>=', date('Y-m-01 00:00:00'))
+                    ->where('created_at', '<', date('Y-m-d 00:00:00'))
+                    ->sum('price');
+    }
+}

+ 39 - 0
app/Dao/Report/ReportDao.php

@@ -0,0 +1,39 @@
+<?php
+
+namespace App\Dao\Report;
+
+use App\Models\Report\ReportLog;
+
+class ReportDao
+{
+    /**
+     * @param $data
+     * @return false
+     */
+    public function saveReportLog($data): bool
+    {
+        if (empty($data)) {
+            return false;
+        }
+
+        return ReportLog::insert($data);
+    }
+
+    /**
+     * @param $clickId
+     * @param $eventType
+     * @return array
+     */
+    public function getReportLogByClickId($clickId, $eventType): array
+    {
+        if (empty($clickId)) {
+            return [];
+        }
+
+        $result = ReportLog::select('id', 'clickid', 'projectid', 'promotionid', 'uid')
+                           ->where('clickid', $clickId)
+                           ->where('event_type', $eventType)
+                           ->first();
+        return $result ? $result->toArray() : [];
+    }
+}

+ 24 - 0
app/Dao/SendOrder/SendOrderDao.php

@@ -0,0 +1,24 @@
+<?php
+
+namespace App\Dao\SendOrder;
+
+use App\Models\SendOrder\SendOrder as SendOrderModel;
+
+class SendOrderDao
+{
+    /**
+     * 派单信息
+     *
+     * @param $id
+     * @return array
+     */
+    public function getSendOrderById($id): array
+    {
+        if (empty($id)) {
+            return [];
+        }
+
+        $result = SendOrderModel::where('id', $id)->first();
+        return $result ? $result->toArray() : [];
+    }
+}

+ 86 - 0
app/Dao/Settlement/BillDao.php

@@ -0,0 +1,86 @@
+<?php
+
+namespace App\Dao\Settlement;
+
+use App\Consts\BaseConst;
+use App\Consts\BillConst;
+use App\Models\Order\Order;
+use App\Models\Settlement\Bills;
+
+class BillDao
+{
+    /**
+     * 获取渠道结算账单列表
+     *
+     * @param $param
+     * @return mixed
+     */
+    public function getBills($param)
+    {
+        $query = Bills::select('id', 'date', 'recharge_amount', 'rate', 'service_amount', 'settlement_price')
+                      ->where('type', BillConst::SETTLEMENT_TYPE_CHANNEL);
+
+        // 站点
+        $channelId = (int)getProp($param, 'channel_id');
+        if ($channelId) {
+            $query->where('distribution_channel_id', $channelId);
+        }
+
+        $dateRange = getProp($param, 'date_range');
+        if ($dateRange) {
+            [$startDate, $endDate] = explode(',', $dateRange);
+            if ($startDate && $endDate && $startDate <= $endDate) {
+                $query->where('date', '>=', $startDate);
+                $query->where('date', '<=', $endDate);
+            }
+        } else {
+            // 只显示7日前数据
+            $query->where('date', '<=', date('Y-m-d', strtotime('-' . BaseConst::DATE_RANGE_DAYS . ' days')));
+        }
+
+        // 导出用
+        $all = (int)getProp($param, 'export');
+        if ($all) {
+            return $query->orderBy('id', 'desc')->get();
+        }
+
+        return $query->orderBy('id', 'desc')->paginate(10);
+    }
+
+    /**
+     * @param $channelId
+     * @param $date
+     * @return mixed
+     */
+    public function billOrders($channelId, $date)
+    {
+        return Order::select('orders.id', 'price', 'send_order_id', 'send_orders.name as send_order_name', 'orders.created_at')
+                    ->leftJoin('send_orders', 'orders.send_order_id', '=', 'send_orders.id')
+                    ->where('orders.distribution_channel_id', $channelId)
+                    ->where('orders.status', 'PAID')
+                    ->where('orders.send_order_id', '>', 0)
+                    ->where('orders.created_at', '>=', date('Y-m-d 00:00:00', strtotime($date)))
+                    ->where('orders.created_at', '<=', date('Y-m-d 23:59:59', strtotime($date)))
+                    ->orderBy('orders.id', 'desc')
+                    ->paginate();
+    }
+
+    /**
+     * @param $channelId
+     * @param string $startDate
+     * @param string $endDate
+     * @return mixed
+     */
+    public function getBillsRechargeAmount($channelId, string $startDate = '', string $endDate = '')
+    {
+        $query = Bills::where('distribution_channel_id', $channelId)->where('type', 'channel');
+        if ($startDate && $endDate) {
+            $query->whereBetween('date', [$startDate, $endDate]);
+        }
+        if ($endDate && !$startDate) {
+            $query->where('date', '<=', $endDate);
+        }
+
+        return $query->sum('recharge_amount');
+    }
+}

+ 17 - 0
app/Dao/Settlement/FinancialDao.php

@@ -0,0 +1,17 @@
+<?php
+
+namespace App\Dao\Settlement;
+
+use App\Models\Settlement\FinancialStats;
+
+class FinancialDao
+{
+    /**
+     * @param $channelId
+     * @return mixed
+     */
+    public function getChannelFinancialStat($channelId)
+    {
+        return FinancialStats::where('distribution_channel_id', $channelId)->first();
+    }
+}

+ 31 - 0
app/Dao/Sms/SmsDao.php

@@ -0,0 +1,31 @@
+<?php
+
+namespace App\Dao\Sms;
+
+use App\Models\Sms\SmsSendLog;
+use App\Models\Sms\SmsTemplate;
+
+class SmsDao
+{
+    /**
+     * 短信模板
+     *
+     * @param $templateType
+     * @return mixed
+     */
+    public function getSmsTemplate($templateType)
+    {
+        return SmsTemplate::where('template_type', $templateType)->first();
+    }
+
+    /**
+     * 记录短信发送日志
+     *
+     * @param $data
+     * @return mixed
+     */
+    public function recordSmsLog($data)
+    {
+        return SmsSendLog::create($data);
+    }
+}

+ 109 - 0
app/Dao/User/UserDao.php

@@ -0,0 +1,109 @@
+<?php
+
+namespace App\Dao\User;
+
+use App\Models\User\User;
+
+class UserDao
+{
+    /**
+     * @param $uid
+     * @return array
+     */
+    public function getUserById($uid): array
+    {
+        if (empty($uid)) {
+            return [];
+        }
+
+        $result = User::where('id', $uid)->first();
+        return $result ? $result->toArray() : [];
+    }
+
+    /**
+     * 根据openid获取用户数据
+     *
+     * @param string $openId
+     * @return array
+     */
+    public function getUserByOpenId(string $openId): array
+    {
+        if (empty($openId)) {
+            return [];
+        }
+
+        $result = User::where('openid', $openId)->first();
+        return $result ? $result->toArray() : [];
+    }
+
+    /**
+     * 根据openid和unionid获取用户数据
+     *
+     * @param string $openId
+     * @param string $unionId
+     * @return array
+     */
+    public function getUserByOpenIdAndUnionId(string $openId, string $unionId): array
+    {
+        if (empty($openId) || empty($unionId)) {
+            return [];
+        }
+
+        $result = User::where('openid', $openId)->where('unionid', $unionId)->first();
+        return $result ? $result->toArray() : [];
+    }
+
+    /**
+     * 根据unionid获取用户数据(多个)
+     *
+     * @param string $unionId
+     * @return array
+     */
+    public function getUsersByUnionId(string $unionId): array
+    {
+        if (empty($unionId)) {
+            return [];
+        }
+
+        $result = User::where('unionid', $unionId)->get();
+        return $result ? $result->toArray() : [];
+    }
+
+    /**
+     * 设置登录用户信息
+     *
+     * @param $openId
+     * @param $unionId
+     * @param $data
+     * @return mixed
+     */
+    public function setUser($openId, $unionId, $data)
+    {
+        return User::updateOrCreate(['openid' => $openId, 'unionid' => $unionId], $data);
+    }
+
+    /**
+     * @param $data
+     * @return mixed
+     */
+    public function createUser($data)
+    {
+        return User::create($data);
+    }
+
+    /**
+     * 更新用户
+     *
+     * @param $id
+     * @param $data
+     * @return bool
+     */
+    public function updateUserById($id, $data): bool
+    {
+        if (empty($id) || empty($data)) {
+            return false;
+        }
+
+        return User::where('id', $id)->update($data);
+    }
+}

+ 19 - 0
app/Exceptions/ApiException.php

@@ -0,0 +1,19 @@
+<?php
+
+
+namespace App\Exceptions;
+
+use Illuminate\Http\Request;
+
+class ApiException extends \Exception
+{
+    public function __construct($code = 0, $message = '', \Throwable $previous = null)
+    {
+        parent::__construct($message, $code);
+    }
+
+    public function render(Request $request)
+    {
+        return response()->json(['code'=>$this->code, 'msg'=>$this->message, 'data'=>[]])->setEncodingOptions(JSON_UNESCAPED_UNICODE);
+    }
+}

+ 96 - 0
app/Exceptions/Handler.php

@@ -0,0 +1,96 @@
+<?php
+
+namespace App\Exceptions;
+
+use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
+use Throwable;
+use Exception;
+
+class Handler extends ExceptionHandler
+{
+    /**
+     * A list of the exception types that are not reported.
+     *
+     * @var array<int, class-string<Throwable>>
+     */
+    protected $dontReport = [
+        ApiException::class,
+    ];
+
+    /**
+     * A list of the inputs that are never flashed for validation exceptions.
+     *
+     * @var array<int, string>
+     */
+    protected $dontFlash = [
+        'current_password',
+        'password',
+        'password_confirmation',
+    ];
+
+    /**
+     * @param Throwable $e
+     * @return void
+     * @throws Throwable
+     */
+    public function report(Throwable $e)
+    {
+        $appEnv  = env('APP_ENV', 'production');
+        $appName = env('APP_NAME', '未设置项目名');
+        if ($appEnv === 'production' && $this->shouldReport($e)) {
+            $date        = date('Y-m-d H:i:s');
+            $file        = $e->getFile();
+            $line        = $e->getLine();
+            $message     = $e->getMessage();
+            $trace       = $e->getTraceAsString();
+            $traceArr    = explode('#', $trace);
+            $traceSimple = $trace;
+            if (is_array($traceArr)) {
+                $traceSub    = array_slice($traceArr, 0, 3);
+                $traceSimple = implode('#', $traceSub);
+            }
+            $msg = <<<EOF
+项目:$appName [$appEnv]
+报错时间:$date
+报错文件:$file
+报错行数:line $line
+报错信息:$message
+报错跟踪:
+$traceSimple
+EOF;
+            sendNotice($msg);
+        }
+        parent::report($e);
+    }
+
+    /**
+     * Register the exception handling callbacks for the application.
+     *
+     * @return void
+     */
+    public function register()
+    {
+        $this->reportable(function (Throwable $e) {
+            //
+        });
+
+        $this->renderable(function(Exception $e, $request) {
+            return $this->handleException($request, $e);
+        });
+    }
+
+    private function handleException($request, Exception $e)
+    {
+        if ($e instanceof ApiException) {
+            $data = [
+                'code' => $e->getCode(),
+                'msg'  => $e->getMessage(),
+                'data' => (Object)[]
+            ];
+
+            return response()->json($data);
+        }
+
+        return null;
+    }
+}

+ 33 - 0
app/Facade/Site.php

@@ -0,0 +1,33 @@
+<?php
+
+
+namespace App\Facade;
+
+use Illuminate\Support\Facades\Facade;
+
+
+/**
+ * @package App\Facades\Site
+ * @see app/Client/Site.php
+ * @method static getUid()
+ * @method static getChannelId()
+ * @method static getChannelType()
+ * @method static getChannelPayId()
+ * @method static getSendOrderId()
+ * @method static getFromUid()
+ * @method static getToken()
+ * @method static getRecentBooks()
+ * @method static getCurrentChapter()
+ * @method static getRecentBid()
+ * @method static getCurrentChannelId()
+ * @method static getCurrentChannelName()
+ * @method static getAccount()
+ * @method static getPhone()
+ */
+class Site extends Facade
+{
+    protected static function getFacadeAccessor()
+    {
+        return 'Site';
+    }
+}

+ 91 - 0
app/Http/Controllers/Bank/BankController.php

@@ -0,0 +1,91 @@
+<?php
+
+namespace App\Http\Controllers\Bank;
+
+use App\Libs\ApiResponse;
+use App\Services\Bank\BankService;
+use App\Transformer\Bank\BankTransformer;
+use App\Exceptions\ApiException;
+use Illuminate\Http\Request;
+use Illuminate\Routing\Controller as BaseController;
+
+class BankController extends BaseController
+{
+    use ApiResponse;
+
+    protected $bankService;
+
+    public function __construct(
+        BankService $bankService
+    )
+    {
+        $this->bankService = $bankService;
+    }
+
+    /**
+     * 下拉银行卡列表
+     *
+     * @return mixed
+     */
+    public function bankList()
+    {
+        $banks = $this->bankService->bankList();
+        return $this->success($banks);
+    }
+
+    /**
+     * 站点银行卡列表
+     *
+     * @return mixed
+     */
+    public function channelBankAccounts(Request $request)
+    {
+        $all = $request->only('is_company');
+
+        $banks = $this->bankService->channelBankAccounts($all);
+        return $this->success($banks, [new BankTransformer(), 'buildCashAccounts']);
+    }
+
+    /**
+     * 新增银行卡账号
+     *
+     * @param Request $request
+     * @return mixed
+     * @throws ApiException
+     */
+    public function addBankAccount(Request $request)
+    {
+        $all = $request->only('account_name', 'identity_card', 'card_number',
+            'account_bank', 'bank_id', 'is_company', 'province', 'sms_code', 'bank');
+
+        $result = $this->bankService->addBankAccount($all);
+        return $this->success(['result' => $result ? 1 : 0]);
+    }
+
+    /*
+     * 删除银行卡账号
+     *
+     * @param Request $request
+     * @return mixed
+     * @throws ApiException
+     */
+    public function delBankAccount(Request $request)
+    {
+        $all = $request->only('id', 'delete_all');
+
+        $result = $this->bankService->delBankAccount($all);
+        return $this->success(['result' => $result ? 1 : 0]);
+    }
+
+    /**
+     * 发送手机验证码
+     *
+     * @return mixed
+     * @throws ApiException
+     */
+    public function getBankAccountSms()
+    {
+        $result = $this->bankService->getBankAccountSms();
+        return $this->success(['result' => $result ? 1 : 0]);
+    }
+}

+ 164 - 0
app/Http/Controllers/Book/BookController.php

@@ -0,0 +1,164 @@
+<?php
+
+namespace App\Http\Controllers\Book;
+
+
+use App\Libs\ApiResponse;
+use App\Services\Book\BookService;
+use App\Transformer\Book\BookTransformer;
+use Illuminate\Http\Request;
+use Illuminate\Routing\Controller as BaseController;
+
+class BookController extends BaseController
+{
+    use ApiResponse;
+    protected $bookService;
+
+    public function __construct(
+        BookService $bookService
+    )
+    {
+        $this->bookService = $bookService;
+    }
+
+    /**
+     * 书籍列表搜索页
+     * @param Request $request
+     * @return mixed
+     */
+    public function bookList(Request $request) {
+        $data = $request->all();
+
+        $result = $this->bookService->getBookList($data);
+        return $this->success($result, [new BookTransformer(), 'newBuildBookList']);
+    }
+
+    /**
+     * 热门搜索
+     * @param Request $request
+     * @return mixed
+     */
+    public function hotSearches(Request $request) {
+        $result = $this->bookService->getHotSearches();
+        return $this->success($result);
+    }
+
+    /**
+     * 书籍详情
+     * @param Request $request
+     * @return mixed
+     */
+    public function bookDetail(Request $request) {
+        $data = $request->all();
+
+        $result = $this->bookService->getBookDetail($data);
+        return $this->success($result);
+    }
+
+    /**
+     * 获取某本书的阅读记录
+     * @param Request $request
+     * @return mixed
+     */
+    public function recentChapter(Request $request) {
+        $data = $request->all();
+
+        $result = $this->bookService->recentChapter($data);
+        return $this->success($result);
+    }
+
+    /**
+     * 生成分享链接参数(小程序内部调用)
+     * @param Request $request
+     * @return mixed
+     */
+    public function setUrlLink(Request $request) {
+        $data = $request->all();
+
+        $result = $this->bookService->setUrlLink($data);
+        return $this->success($result);
+    }
+
+    /**
+     * 生成分享链接(外网)
+     * @param Request $request
+     * @return mixed
+     */
+    public function setDyLink(Request $request) {
+        $data = $request->all();
+
+        $result = $this->bookService->setDyLink($data);
+        return $this->success($result);
+    }
+
+    /**
+     * 章节目录
+     * @param Request $request
+     * @return mixed
+     */
+    public function chapterList(Request $request) {
+        $data = $request->all();
+
+        $result = $this->bookService->getChapterList($data);
+        return $this->success($result, [new BookTransformer(), 'newBuildChapterList']);
+    }
+
+    /**
+     * 设置书籍收费方式
+     * @param Request $request
+     * @return mixed
+     */
+    public function setBookChargeType(Request $request) {
+        $data = $request->all();
+        $result = $this->bookService->setBookChargeType($data);
+        return $this->success(['success'=>$result ? 1 : 0]);
+    }
+
+    /**
+     * 章节信息
+     * @param Request $request
+     * @return mixed
+     */
+    public function chapterInfo(Request $request) {
+        $data = $request->all();
+
+        $result = $this->bookService->getChapterInfo($data);
+
+        return $this->success($result);
+    }
+
+    /**
+     * 生成派单链接
+     * @param Request $request
+     * @return mixed
+     */
+    public function setSendOrder(Request $request) {
+        $data = $request->all();
+        $result = $this->bookService->setSendOrder($data);
+        return $this->success(['success'=>$result ? 1 : 0]);
+    }
+
+    /**
+     * 一级分类列表
+     * @param Request $request
+     * @return mixed
+     */
+    public function categoryList(Request $request) {
+        $data = $request->all();
+
+        $result = $this->bookService->getCategoryList($data);
+        return $this->success(['list'=>$result]);
+    }
+
+    /**
+     * 分类书籍
+     * @param Request $request
+     * @return mixed
+     */
+    public function categoryBooks(Request $request) {
+        $data = $request->all();
+
+        $result = $this->bookService->getCategoryBooks($data);
+        return $this->success($result, [new BookTransformer(), 'newBuildCategoryBooks']);
+    }
+}

+ 200 - 0
app/Http/Controllers/Channel/ChannelHomeController.php

@@ -0,0 +1,200 @@
+<?php
+
+namespace App\Http\Controllers\Channel;
+
+use App\Exceptions\ApiException;
+use App\Libs\ApiResponse;
+use App\Http\Controllers\Controller;
+use App\Libs\Utils;
+use App\Services\Channel\ChannelHomeService;
+use App\Transformer\Channel\ChannelTransformer;
+use Illuminate\Http\Request;
+use Illuminate\Support\Facades\Validator;
+
+class ChannelHomeController extends Controller
+{
+    use ApiResponse;
+    private $channelHomeService;
+
+    public function __construct(
+        ChannelHomeService $channelHomeService
+    ) {
+        $this->channelHomeService = $channelHomeService;
+    }
+
+    public function index(Request $request)
+    {
+        return view('index');
+    }
+
+    /**
+     * 获取当前站点信息
+     * @return mixed
+     */
+    public function channelInfo() {
+        $result = $this->channelHomeService->getChannelInfo();
+
+        return $this->success($result);
+    }
+
+    /**
+     * 设置全站点按本或按章及价格
+     * @param Request $request
+     * @return mixed
+     */
+    public function setChannelBookChargeType(Request $request) {
+        $data = $request->all();
+        $validator = Validator::make($data, [
+            'book_charge_type'                      => 'required|in:MERGE,HYBRID',
+            'book_calculate_price_type'             => 'required_if:book_charge_type,MERGE,HYBRID|in:bywords,const,all',
+            'book_coin'                             => 'required_if:book_charge_type,MERGE,HYBRID|numeric',
+            'chapter_calculate_price_type'          => 'required_if:book_charge_type,HYBRID|in:bywords,const,all',
+            'chapter_coin'                          => 'required_if:book_charge_type,HYBRID|numeric',
+        ], [
+            'book_charge_type.required'                 => '请选择站点类型',
+            'book_charge_type.in'                       => '站点类型选择范围不正确',
+            'book_calculate_price_type.required_if'     => '请选择短篇书籍收费方式',
+            'book_calculate_price_type.in'              => '短篇书籍收费方式选择范围不正确',
+            'book_coin.required_if'                     => '请填写短篇书籍价格',
+            'book_coin.numeric'                         => '短篇书籍价格格式不正确',
+            'chapter_calculate_price_type.required_if'  => '请选择长篇书籍收费方式',
+            'chapter_calculate_price_type.in'           => '长篇书籍收费方式选择范围不正确',
+            'chapter_coin.required_if'                  => '请填写长篇书籍价格',
+            'chapter_coin.numeric'                      => '长篇书籍价格格式不正确',
+        ]);
+
+        if ($validator->fails()) {
+            $errors = $validator->errors();
+            Utils::throwError('1003:'.$errors->all()[0]);
+        }
+        $result = $this->channelHomeService->setChannelBookChargeType($data);
+
+        return $this->success(['success'=>$result ? 1 : 0]);
+    }
+
+    /**
+     * 获取子账号信息
+     * @return mixed
+     */
+    public function getSubUser() {
+        $result = $this->channelHomeService->getSubUser();
+
+        return $this->success($result);
+    }
+
+    /**
+     * 站点总数据
+     * @param Request $request
+     * @return mixed
+     */
+    public function statisticsByTotal(Request $request) {
+        $data   = $request->all();
+        $result = $this->channelHomeService->statisticsByTotal($data);
+
+        return $this->success($result);
+    }
+
+    /**
+     * 站点数据按日明细
+     * @param Request $request
+     * @return mixed
+     */
+    public function statisticsByDay(Request $request) {
+        $data   = $request->all();
+        $result = $this->channelHomeService->statisticsByDay($data);
+
+        return $this->success($result, [new ChannelTransformer(), 'newBuildStatisticsByDay']);
+    }
+
+    /**
+     * 导出站点统计日明细
+     *
+     * @param Request $request
+     * @return mixed
+     */
+    public function exportStatisticsByDay(Request $request)
+    {
+        $data   = $request->all();
+        $result = $this->channelHomeService->exportStatisticsByDay($data);
+
+        return $this->success(['success' => $result ? 1 : 0]);
+    }
+
+    /**
+     * 站点数据按日明细
+     * @param Request $request
+     * @return mixed
+     */
+    public function statisticsByDayForMaster(Request $request) {
+        $data   = $request->all();
+        $result = $this->channelHomeService->statisticsByDayForMaster($data);
+
+        return $this->success($result, [new ChannelTransformer(), 'newBuildStatisticsByDayForMaster']);
+    }
+
+    /**
+     * 导出站点统计日明细
+     *
+     * @param Request $request
+     * @return mixed
+     */
+    public function exportStatisticsByDayForMaster(Request $request)
+    {
+        $data   = $request->all();
+        $result = $this->channelHomeService->exportStatisticsByDayForMaster($data);
+
+        return $this->success(['success' => $result ? 1 : 0]);
+    }
+
+    /**
+     * 站点数据按月明细
+     * @param Request $request
+     * @return mixed
+     */
+    public function statisticsByMonth(Request $request) {
+        $data   = $request->all();
+        $result = $this->channelHomeService->statisticsByMonth($data);
+
+        return $this->success($result, [new ChannelTransformer(), 'newBuildStatisticsByMonth']);
+    }
+
+    /**
+     * 导出站点统计月明细
+     *
+     * @param Request $request
+     * @return mixed
+     */
+    public function exportStatisticsByMonth(Request $request)
+    {
+        $data   = $request->all();
+        $result = $this->channelHomeService->exportStatisticsByMonth($data);
+
+        return $this->success(['success' => $result ? 1 : 0]);
+    }
+
+    /**
+     * 站点数据按月明细
+     * @param Request $request
+     * @return mixed
+     */
+    public function statisticsByMonthForMaster(Request $request) {
+        $data   = $request->all();
+        $result = $this->channelHomeService->statisticsByMonthForMaster($data);
+
+        return $this->success($result, [new ChannelTransformer(), 'newBuildStatisticsByMonthForMaster']);
+    }
+
+    /**
+     * 导出站点统计月明细
+     *
+     * @param Request $request
+     * @return mixed
+     */
+    public function exportStatisticsByMonthForMaster(Request $request)
+    {
+        $data   = $request->all();
+        $result = $this->channelHomeService->exportStatisticsByMonthForMaster($data);
+
+        return $this->success(['success' => $result ? 1 : 0]);
+    }
+}

+ 82 - 0
app/Http/Controllers/Channel/ChannelUserController.php

@@ -0,0 +1,82 @@
+<?php
+
+namespace App\Http\Controllers\Channel;
+
+use App\Http\Controllers\Controller;
+use App\Services\Channel\ChannelUserService;
+use Illuminate\Http\Request;
+use App\Consts\ErrorConst;
+use App\Libs\Utils;
+use App\Facade\Site;
+use App\Libs\ApiResponse;
+use App\Exceptions\ApiException;
+
+class ChannelUserController extends Controller
+{
+    use ApiResponse;
+
+    private $channelUserService;
+
+    public function __construct(ChannelUserService $channelUserService)
+    {
+        $this->channelUserService = $channelUserService;
+    }
+
+    /**
+     * 登录
+     *
+     * @param Request $request
+     * @return mixed
+     * @throws ApiException
+     */
+    public function login(Request $request)
+    {
+        $all      = $request->all();
+        $account  = trim(getProp($all, 'account'));
+        $password = trim(getProp($all, 'password'));
+        if (strlen($account) < 1 || strlen($password) < 1) {
+            Utils::throwError(ErrorConst::PARAM_ERROR_CODE);
+        }
+
+        // 登录
+        $user = $this->channelUserService->login($account, $password);
+        return $this->success($user);
+    }
+
+    /**
+     * 退出登录
+     *
+     * @return mixed
+     */
+    public function logout()
+    {
+        // 当前登录用户
+        $uid = Site::getUid();
+
+        // 退出
+        // $result = $this->channelUserService->logout($uid);
+        $result = true;
+        return $this->success(compact('result'));
+    }
+
+    /**
+     * 修改密码
+     *
+     * @param Request $request
+     * @return mixed
+     * @throws ApiException
+     */
+    public function resetPassword(Request $request)
+    {
+        // 当前登录用户
+        $uid         = Site::getUid();
+        $newPassword = trim($request->get('new_password', ''));
+        if (strlen($newPassword) < 1) {
+            Utils::throwError(ErrorConst::PARAM_ERROR_CODE);
+        }
+
+        // 退出
+        $result = $this->channelUserService->resetPassword($uid, $newPassword);
+        return $this->success(compact('result'));
+    }
+}

+ 13 - 0
app/Http/Controllers/Controller.php

@@ -0,0 +1,13 @@
+<?php
+
+namespace App\Http\Controllers;
+
+use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
+use Illuminate\Foundation\Bus\DispatchesJobs;
+use Illuminate\Foundation\Validation\ValidatesRequests;
+use Illuminate\Routing\Controller as BaseController;
+
+class Controller extends BaseController
+{
+    use AuthorizesRequests, DispatchesJobs, ValidatesRequests;
+}

+ 80 - 0
app/Http/Controllers/Home/HomeController.php

@@ -0,0 +1,80 @@
+<?php
+
+namespace App\Http\Controllers\Home;
+
+
+use App\Consts\ErrorConst;
+use App\Facade\Site;
+use App\Libs\ApiResponse;
+use App\Libs\Utils;
+use App\Models\User\User;
+use App\Services\Book\BookService;
+use App\Services\User\UserService;
+use App\Transformer\Home\HomeTransformer;
+use Illuminate\Http\Request;
+use Illuminate\Routing\Controller as BaseController;
+use Illuminate\Support\Facades\Log;
+
+class HomeController extends BaseController
+{
+    use ApiResponse;
+    protected $bookService;
+    protected $userService;
+
+    public function __construct(
+        BookService $bookService,
+        UserService $userService
+    )
+    {
+        $this->bookService = $bookService;
+        $this->userService = $userService;
+    }
+
+    /**
+     * 首页
+     * @param Request $request
+     * @return mixed
+     */
+    public function index(Request $request) {
+        $data = $request->all();
+        Log::info('首页入参: '.json_encode($data, 256));
+        $gender = getProp($data, 'book_gender');
+        $result = [];
+        $result['current_book'] = $this->bookService->getCurrentBook();
+        $result['book_gender'] = $result['current_book'] ? $result['current_book']['gender'] : 1;
+        if ($gender) $result['book_gender'] = $gender;
+        $result['banners'] = $this->bookService->getBanners($result['book_gender']);
+        $result['feature_books'] = $this->bookService->getFeatureBooks($result['book_gender']);
+        $result['favorite_books'] = $this->bookService->getFavoriteBooks($result['book_gender']);
+
+        // 获取分享链接中的隐藏参数(如果存在隐藏参数邀请码则绑定)
+        $invite_code = getProp($data, 'invite_code');
+        $uid = Site::getUid();
+        $from_uid = User::where('invite_code', $invite_code)->value('id');
+        if ($from_uid && $uid) {
+            $info = $this->userService->bind($from_uid, $uid);  // 绑定邀请码
+            Log::info('首页邀请码绑定结果-----from_uid: '.$from_uid.';uid: '.$uid.';info: '.$info);
+        }
+
+        // 获取分享链接中的隐藏参数(如果存在隐藏参数派单id则绑定)
+        $send_order_id = getProp($data, 'send_order_id');
+        $uid = Site::getUid();
+        if ($send_order_id && $uid) {
+            $info = $this->userService->bindSendOrder($send_order_id);  // 绑定派单链接
+            Log::info('首页派单链接绑定结果-----send_order_id: '.$send_order_id.';uid: '.$uid.';info: '.$info);
+        }
+
+        return $this->success($result, [new HomeTransformer(), 'newBuildHomeList']);
+    }
+
+    /**
+     * 抖音投放第三方监测链接
+     * @param Request $request
+     * @return mixed
+     */
+    public function listenData(Request $request) {
+        $data = $request->all();
+        dLog('listen_data')->info('监测链接入参: ', $data);
+        return $this->success(['success'=>1]);
+    }
+}

+ 290 - 0
app/Http/Controllers/Order/OrderController.php

@@ -0,0 +1,290 @@
+<?php
+
+namespace App\Http\Controllers\Order;
+
+
+use App\Exceptions\ApiException;
+use App\Libs\ApiResponse;
+use App\Services\Book\BookService;
+use App\Services\Order\OrderService;
+use App\Services\User\UserService;
+use App\Transformer\Order\OrderTransformer;
+use Illuminate\Http\Request;
+use Illuminate\Routing\Controller as BaseController;
+
+class OrderController extends BaseController
+{
+    use ApiResponse;
+
+    protected $bookService;
+    protected $userService;
+    protected $orderService;
+
+    public function __construct(
+        BookService  $bookService,
+        UserService  $userService,
+        OrderService $orderService
+    )
+    {
+        $this->bookService  = $bookService;
+        $this->userService  = $userService;
+        $this->orderService = $orderService;
+    }
+
+    /**
+     * 订单明细
+     *
+     * @param Request $request
+     * @return mixed
+     */
+    public function orderData(Request $request)
+    {
+        $data   = $request->all();
+        $result = $this->orderService->getOrderData($data);
+
+        return $this->success($result, [new OrderTransformer(), 'newBuildOrderData']);
+    }
+
+    /**
+     * 用户信息
+     *
+     * @param Request $request
+     * @return mixed
+     */
+    public function userData(Request $request)
+    {
+        $data   = $request->all();
+        $result = $this->orderService->getUserData($data);
+
+        return $this->success($result, [new OrderTransformer(), 'newBuildUserData']);
+    }
+
+    /**
+     * 回传日志
+     *
+     * @param Request $request
+     * @return mixed
+     */
+    public function reportData(Request $request)
+    {
+        $data   = $request->all();
+        $result = $this->orderService->getReportData($data);
+
+        return $this->success($result, [new OrderTransformer(), 'newBuildReportData']);
+    }
+
+    /**
+     * 导出回传日志
+     *
+     * @param Request $request
+     * @return mixed
+     */
+    public function exportReportData(Request $request)
+    {
+        $data   = $request->all();
+        $result = $this->orderService->exportReportData($data);
+
+        return $this->success(['success' => $result ? 1 : 0]);
+    }
+
+    /**
+     * 派单列表
+     *
+     * @param Request $request
+     * @return mixed
+     */
+    public function sendOrderData(Request $request)
+    {
+        $data   = $request->all();
+        $result = $this->orderService->getSendOrderData($data);
+
+        return $this->success($result, [new OrderTransformer(), 'newBuildSendOrderData']);
+    }
+
+    /**
+     * 导出派单数据
+     *
+     * @param Request $request
+     * @return mixed
+     */
+    public function exportSendOrderData(Request $request)
+    {
+        $data   = $request->all();
+        $result = $this->orderService->exportSendOrderData($data);
+
+        return $this->success(['success' => $result ? 1 : 0]);
+    }
+
+    /**
+     * 获取派单链接适用的模板
+     * @param Request $request
+     * @return mixed
+     */
+    public function sendOrderTemplates(Request $request) {
+        $data   = $request->all();
+        $result = $this->orderService->getSendOrderTemplates($data);
+
+        return $this->success($result);
+    }
+
+    /**
+     * 编辑派单链接
+     *
+     * @param Request $request
+     * @return mixed
+     */
+    public function editSendOrderUrl(Request $request)
+    {
+        $data   = $request->all();
+        $result = $this->orderService->editSendOrderUrl($data);
+
+        return $this->success(['success' => $result ? 1 : 0]);
+    }
+
+    /**
+     * 派单日数据
+     *
+     * @param Request $request
+     * @return mixed
+     */
+    public function sendOrderDayData(Request $request)
+    {
+        $data   = $request->all();
+        $result = $this->orderService->getSendOrderDayData($data);
+
+        return $this->success($result, [new OrderTransformer(), 'newBuildSendOrderDayData']);
+    }
+
+    /**
+     * 导出派单日数据
+     *
+     * @param Request $request
+     * @return mixed
+     */
+    public function exportSendOrderDayData(Request $request)
+    {
+        $data   = $request->all();
+        $result = $this->orderService->exportSendOrderDayData($data);
+
+        return $this->success(['success' => $result ? 1 : 0]);
+    }
+
+    /**
+     * 设置派单日成本
+     *
+     * @param Request $request
+     * @return mixed
+     */
+    public function setDayCost(Request $request)
+    {
+        $data   = $request->all();
+        $result = $this->orderService->setDayCost($data);
+
+        return $this->success(['success' => $result ? 1 : 0]);
+    }
+
+    /**
+     * 充值模板
+     * @param Request $request
+     * @return mixed
+     */
+    public function templateList(Request $request) {
+        $data   = $request->all();
+        $result = $this->orderService->getTemplateList($data);
+
+        return $this->success($result, [new OrderTransformer(), 'newBuildTemplateList']);
+    }
+
+    /**
+     * 设置充值模板启用状态
+     * @param Request $request
+     * @return mixed
+     */
+    public function setTemplateStatus(Request $request)
+    {
+        $data   = $request->all();
+        $result = $this->orderService->setTemplateStatus($data);
+
+        return $this->success(['success' => $result ? 1 : 0]);
+    }
+
+    /**
+     * 新增模版
+     * @param Request $request
+     * @return mixed
+     */
+    public function addTemplate(Request $request) {
+        $data   = $request->all();
+        $result = $this->orderService->addTemplate($data);
+
+        return $this->success(['success' => $result ? 1 : 0]);
+    }
+
+    /**
+     * 编辑模版
+     * @param Request $request
+     * @return mixed
+     */
+    public function editTemplate(Request $request) {
+        $data   = $request->all();
+        $result = $this->orderService->editTemplate($data);
+
+        return $this->success(['success' => $result ? 1 : 0]);
+    }
+
+    /**
+     * 推广数据
+     *
+     * @param Request $request
+     * @return mixed
+     */
+    public function promotionData(Request $request)
+    {
+        $data   = $request->all();
+        $result = $this->orderService->getPromotionData($data);
+
+        return $this->success($result);
+    }
+
+    /**
+     * 补回传
+     *
+     * @param Request $request
+     * @return mixed
+     */
+    public function reportPromotionOrder(Request $request)
+    {
+        $data   = $request->all();
+        $result = $this->orderService->reportPromotionOrder($data);
+
+        return $this->success(['success' => $result ? 1 : 0]);
+    }
+
+    /**
+     * 订单列表
+     *
+     * @param Request $request
+     * @return mixed
+     * @throws ApiException
+     */
+    public function orderList(Request $request)
+    {
+        $data   = $request->all();
+        $result = $this->orderService->orderList($data);
+        return $this->success($result, [new OrderTransformer(), 'orderList']);
+    }
+
+    /**
+     * 订单列表导出
+     *
+     * @param Request $request
+     * @return mixed
+     * @throws ApiException
+     */
+    public function orderExport(Request $request)
+    {
+        $data = $request->all();
+        $result  = $this->orderService->exportOrderList($data);
+        return response($result)->header('Content-type', 'application/vnd.ms-excel');
+    }
+}

+ 213 - 0
app/Http/Controllers/Pay/PayController.php

@@ -0,0 +1,213 @@
+<?php
+
+namespace App\Http\Controllers\Pay;
+
+
+use App\Facade\Site;
+use App\Libs\ApiResponse;
+use App\Models\User\User;
+use App\Services\Pay\PayService;
+use App\Services\Pay\ProductService;
+use App\Transformer\Pay\PayTransformer;
+use Illuminate\Http\Request;
+use Illuminate\Routing\Controller as BaseController;
+use Illuminate\Support\Facades\Redis;
+use Vinkla\Hashids\Facades\Hashids;
+use Log;
+use Ycpay\Byte;
+use App\Services\User\UserService;
+
+class PayController extends BaseController
+{
+    use ApiResponse;
+    protected $payService;
+
+    public function __construct(
+        PayService $payService
+    )
+    {
+        $this->payService = $payService;
+    }
+
+    public function create_order(Request $request) {
+        $uid = Site::getUid();
+        $user = User::find($uid);
+        \Log::info('create_order:'.$uid.' request:'.json_encode($request->all()));
+        $product_id = $request->get('product_id');
+        
+        $phone_type = $request->has('phone_type')?$request->get('phone_type'):'android';
+        if (!in_array($phone_type,['ios','android'])){
+            $phone_type = 'android';
+        }
+        if(empty($product_id) || empty($user)) return $this->error('50013:参数异常');
+        
+//         if(empty($uid)) return $this->error('50012:非法登录');
+        
+        $trade_no = generateOrderSn();
+        
+        $product = ProductService::getProductSingle($product_id);
+        if(empty($product)) return $this->error('50013:参数异常product_id');
+        
+        $price = $product->price;
+        
+        // 内测uid 1分钱
+        if (in_array($uid, explode(',', env('TEST_UID')))) {
+            $price = 0.01;
+            \Log::info('create_order_1fen_uid:'.$uid);
+        }
+
+        if(empty($price)) return $this->error('50013:参数异常price');
+
+        $send_order_id = Site::getSendOrderId();
+        $distribution_channel_id = Site::getChannelId();
+        if(empty($distribution_channel_id)) return $this->error('50013:参数异常channel_id');
+        
+        $from_bid = Site::getRecentBid();
+        $create_ip = getIpAddr();
+        $order_type = $product->type;
+        
+        $result = $create_order_param = [];
+        $order_param = [
+            'price'=>$price,
+            'uid'=>$uid,
+            'order_type'=>$order_type,
+            'distribution_channel_id'=>$distribution_channel_id,
+            'create_ip'=>$create_ip,
+            'from_bid'=>$from_bid,
+            'send_order_id'=>$send_order_id,
+            'trade_no'=>$trade_no,
+            'product_id'=>$product_id,
+            'pay_type'=>'Byte',
+            'openid'=>$user->openid,
+            'phone_type'=>$phone_type,
+        ];
+        \Log::info('order_param:'.json_encode($order_param));
+        $result = $this->payService->create_order($order_param);
+
+
+        //{"err_no":0,"err_tips":"success","data":{"order_id":"N7230729566975953204","order_token":"CgwIARDGJRiuMiABKAESTgpMszqeb0L8DTtnY44geDAfYHAsrZrKRsbwMz8GdAz8fRmJwGJf860xXpHLTPBlka94g6Gsm6NuyakLaHYoTmtg9uHag7rRZVGXrpnDtRoA.L","url":""}}
+        //{"err_no":2008,"err_tips":"\u7b7e\u540d\u6821\u9a8c\u5f02\u5e38\uff0c\u8bf7\u4f7f\u7528\u6b63\u786e\u7684\u7b7e\u540d","data":null}
+
+        if(isset($result['err_no']) && $result['err_no'] == 0){
+            $create_order_param = [
+                'trade_no'=>$trade_no,
+                'order_id'=>$result['data']['order_id'],
+                'order_token'=>$result['data']['order_token'],
+                'pay_url'=>$result['data']['url'],
+                
+            ];
+            
+            // 维护orders的url字段和order_id字段 
+            $order = $this->payService->getByTradeNo($trade_no);
+            $order->tiktok_order_id = $result['data']['order_id'];
+            $order->pay_url = $result['data']['url'];
+            $order->save();
+
+        }else{
+            if(isset($result['err_tips'])){
+                $error_msg = $result['err_tips'];
+            }else{
+                $error_msg = isset($result['msg'])?$result['msg']:'';
+            }
+            \Log::info('create_order_ept:'.$error_msg);
+            return $this->error('20010:创建订单异常');
+        }
+
+        return $this->success($create_order_param, [new PayTransformer(), 'createOrderParams']);
+    }
+    
+    /**
+     * 抖音官方回调
+    {
+    	"msg": "{\"appid\":\"tta48fd2323bb0abbc01\",\"cp_orderno\":\"2023051916413016844856905211716\",
+    	\"cp_extra\":\"\",\"way\":\"2\",\"channel_no\":\"2023051922001465081408495054\",\"channel_gateway_no\":\"\",
+    	\"payment_order_no\":\"DPS2305191641249038630039365653\",\"out_channel_order_no\":\"2023051922001465081408495054\",
+    	\"total_amount\":1,\"status\":\"SUCCESS\",\"seller_uid\":\"72265320820071651950\",\"extra\":\"\",
+    	\"item_id\":\"\",\"paid_at\":1684485701,\"message\":\"\",\"order_id\":\"N7234810920067680567\",
+    	\"ec_pay_trade_no\":\"DTPP2305191641229006646669375653\"}",
+    	"msg_signature": "d4402df600eee04fd3350259c1656a10b6006679",
+    	"nonce": "7346",
+    	"timestamp": "1684485701",
+    	"type": "payment",
+    	"_url": "\/pay\/receive_dy_pay_notify"
+    }
+     * 
+
+     */
+    function dycallback(Request $request)
+    {
+        $callback_params = $request->all();
+        \Log::info('dycallback:'.json_encode($callback_params));
+        $callback_params = json_decode(json_encode($callback_params),true);
+
+        $trade_no = $transaction_id = '';
+        if(isset($callback_params['msg']) && $callback_params['msg']){
+            $callback_params_decode = json_decode($callback_params['msg'],true);
+            
+            $trade_no = $callback_params_decode['cp_orderno'];
+            $order_id = $callback_params_decode['order_id'];
+            $transaction_id = $callback_params_decode['channel_no'];
+        }
+        
+        \Log::info('dycallback:trade_no:'.$trade_no.' transaction_id:'.$transaction_id.' order_id:'.$order_id);
+
+        $calllback =  $this->payService->orderCallBack($trade_no, $transaction_id,$order_id);
+        if($calllback){
+            \Log::info('dycallback:trade_no_success'.$trade_no);
+            exit('{"err_no":0,"err_tips":"success"}');
+        }
+        \Log::info('dycallback:trade_no_fail'.$trade_no);
+        
+        exit('fail');
+    }
+    
+    // 退款回调
+    function refund_dycallback(Request $request)
+    {
+        $callback_params = $request->all();
+        \Log::info('refund_dycallback:'.json_encode($callback_params));
+        
+        exit('{"err_no":0,"err_tips":"success"}');
+    }
+    
+    
+    /**
+     * TODO  加上查询逻辑
+     * @param Request $request
+     * @return mixed
+     */
+    public function query_user_pay(Request $request) {
+        $uid = Site::getUid();
+        
+        \Log::info('query_user_pay:'.$uid.' request:'.json_encode($request->all()));
+        
+        $trade_no = $request->get('trade_no');
+        if(empty($trade_no)) return $this->error('50013:参数异常');
+        
+        return $this->success(['is_paid'=>1]);
+    }
+    
+    /**
+     * 前端支付相关信息回调
+     * @param Request $request
+     * @return mixed
+     */
+    public function pay_info_callback(Request $request) {
+        $uid = Site::getUid();
+        $user = User::find($uid);
+        \Log::info('pay_info_callback:'.$uid.' request:'.json_encode($request->all()));
+        
+        $trade_no = $request->get('trade_no');
+        $order_status = $request->get('order_status');
+        if(empty($order_status) || empty($trade_no)) return $this->error('50013:参数异常');
+        
+        // 当前只支持 取消支付,待支付和成功在其他函数直接执行
+        if(in_array($order_status,[2])){
+            $this->payService->report_to_douyin($trade_no, $user->openid, $order_status);
+        }
+        
+        
+        return $this->success(['is_success'=>1]);
+    }
+
+}

+ 77 - 0
app/Http/Controllers/Pay/ProductController.php

@@ -0,0 +1,77 @@
+<?php
+
+namespace App\Http\Controllers\Pay;
+
+
+use App\Cache\StatisticCache;
+use App\Facade\Site;
+use App\Libs\ApiResponse;
+use App\Services\Pay\ProductService;
+use Illuminate\Http\Request;
+use Illuminate\Routing\Controller as BaseController;
+
+/**
+ * 充值商品
+ *
+ */
+class ProductController extends BaseController
+{
+    use ApiResponse;
+
+    protected $productService;
+
+    public function __construct(
+        ProductService $productService
+    )
+    {
+        $this->productService = $productService;
+    }
+
+    public function charge_list(Request $request)
+    {
+        $template_id = 1;
+
+        // 充值类型站点
+        $channelType  = Site::getChannelType();
+        if ($channelType === 'RECHARGE') {
+            $sendOrderId  = Site::getSendOrderId();
+            $channelPayId = Site::getChannelPayId();
+
+            // 获取派单信息
+            $sendOrder = $this->productService->getSendOrderById($sendOrderId);
+            $template_id = getProp($sendOrder, 'pay_id', $channelPayId);
+        }
+
+        \Log::info('chargeList:template_id:' . $template_id);
+
+        // 获取模板充值列表
+        $charge_list = ProductService::getChargeProduct($template_id);
+        \Log::info('$charge_list:' . json_encode($charge_list));
+        if (!$charge_list) {
+            return response()->error('WAP_SYS_ERROR');
+        }
+
+        $data = [];
+        foreach ($charge_list as $v) {
+            $temp   = [
+                'price'           => $v->price,
+                'price_desc'      => $v->price_desc,
+                'name_desc'       => $v->name_desc,
+                'given'           => $v->given,
+                'is_default'      => $v->is_default,
+                'angle_sign_text' => $v->angle_sign_text,
+                'type'            => $v->type,
+                'product_id'      => $v->id,
+            ];
+            $data[] = $temp;
+        }
+
+        // 统计(uv|pv)
+        StatisticCache::setPV('charge_list');
+        $uid = Site::getUid();
+        if ($uid) StatisticCache::setUV('charge_list', $uid);
+
+        return response()->success($data);
+    }
+
+}

+ 116 - 0
app/Http/Controllers/Settlement/SettlementController.php

@@ -0,0 +1,116 @@
+<?php
+
+namespace App\Http\Controllers\Settlement;
+
+use App\Consts\BaseConst;
+use App\Consts\ErrorConst;
+use App\Libs\ApiResponse;
+use App\Libs\Utils;
+use App\Services\Settlement\SettlementService;
+use App\Transformer\Settlement\SettlementTransformer;
+use App\Exceptions\ApiException;
+use Illuminate\Http\Request;
+use Illuminate\Routing\Controller as BaseController;
+
+class SettlementController extends BaseController
+{
+    use ApiResponse;
+
+    private $settlementService;
+
+    public function __construct(
+        SettlementService $settlementService
+    )
+    {
+        $this->settlementService = $settlementService;
+    }
+
+    /**
+     * 结算信息-汇总数据
+     *
+     * @return mixed
+     */
+    public function billsStat()
+    {
+        $result = $this->settlementService->billsStat();
+        return $this->success($result);
+    }
+
+    /**
+     * 结算列表
+     *
+     * @param Request $request
+     * @return mixed
+     * @throws ApiException
+     */
+    public function bills(Request $request)
+    {
+        $all = $request->only('date_range', 'export');
+
+        // 时间判断
+        $dateRange = getProp($all, 'date_range');
+        if ($dateRange) {
+            [$startDate, $endDate] = explode(',', $dateRange);
+
+            // 开始日期不能大于结束日期
+            if ($startDate > $endDate) {
+                Utils::throwError(ErrorConst::DATE_START_GREATER_THAN_END);
+            }
+
+            // 不能选择近七日时间
+            if ($endDate > date('Y-m-d', strtotime('-' . BaseConst::DATE_RANGE_DAYS . ' days'))) {
+                Utils::throwError(ErrorConst::DATE_RANGE_LESS_THAN_7DAY);
+            }
+        }
+
+        $result = $this->settlementService->bills($all);
+        return $this->success($result, [new SettlementTransformer(), 'buildBills']);
+    }
+
+    /**
+     * 结算单-订单明细
+     *
+     * @param Request $request
+     * @return mixed
+     * @throws ApiException
+     */
+    public function billOrders(Request $request)
+    {
+        $date = trim($request->get('date'));
+        if (empty($date)) {
+            Utils::throwError(ErrorConst::PARAM_ERROR_CODE);
+        }
+
+        $result = $this->settlementService->billOrders($date);
+        return $this->paginate($result, [new SettlementTransformer(), 'buildBillOrders']);
+    }
+
+    /**
+     * 提现列表
+     *
+     * @param Request $request
+     * @return mixed
+     */
+    public function withdrawCashes(Request $request)
+    {
+        $all    = $request->only('date_range', 'status');
+        $result = $this->settlementService->withdrawCashes($all);
+
+        return $this->success($result, [new SettlementTransformer(), 'buildWithdrawCashes']);
+    }
+
+    /**
+     * 提现
+     *
+     * @param Request $request
+     * @return mixed
+     * @throws ApiException
+     */
+    public function applyWithDraw(Request $request)
+    {
+        $all    = $request->only('amount', 'card_id', 'is_company');
+        $result = $this->settlementService->applyWithDraw($all);
+
+        return $this->success($result);
+    }
+}

+ 288 - 0
app/Http/Controllers/User/UserController.php

@@ -0,0 +1,288 @@
+<?php
+
+namespace App\Http\Controllers\User;
+
+
+use App\Consts\ErrorConst;
+use App\Libs\ApiResponse;
+use App\Libs\TikTok\Kernel\Exceptions\Exception;
+use App\Libs\Utils;
+use App\Services\Book\BookService;
+use App\Services\OpenApi\OpenService;
+use App\Services\Report\ReportService;
+use App\Services\User\UserService;
+use App\Transformer\User\UserTransformer;
+use Illuminate\Http\Request;
+use Illuminate\Routing\Controller as BaseController;
+use App\Exceptions\ApiException;
+use GuzzleHttp\Exception\GuzzleException;
+use Illuminate\Support\Facades\Validator;
+
+class UserController extends BaseController
+{
+    use ApiResponse;
+
+    protected $userService;
+    protected $openService;
+    protected $bookService;
+    protected $reportService;
+
+    public function __construct(
+        UserService   $userService,
+        OpenService   $openService,
+        BookService   $bookService,
+        ReportService $reportService
+    )
+    {
+        $this->userService   = $userService;
+        $this->openService   = $openService;
+        $this->bookService   = $bookService;
+        $this->reportService = $reportService;
+    }
+
+    /**
+     * 登录接口
+     *
+     * @param Request $request
+     * @return mixed
+     * @throws ApiException
+     * @throws GuzzleException
+     */
+    public function login(Request $request)
+    {
+        $params        = $request->all();
+        $sendOrderId   = (int)getProp($params, 'send_order_id');
+        $code          = trim(getProp($params, 'code'));
+        $anonymousCode = trim(getProp($params, 'anonymous_code'));
+        if (empty($code)) {
+            Utils::throwError(ErrorConst::PARAM_ERROR_CODE);
+        }
+
+        dLog('login')->info('params', $params);
+
+        // 授权
+        $user = $this->openService->getInstance()->code2Session($sendOrderId, $code, $anonymousCode);
+
+        // 绑定邀请码
+        try {
+            $this->userService->bindUser($params);
+        } catch (\Exception $e) {
+            dLog('exception')->info('login-bind-fail', [
+                'params'        => $params,
+                'user'          => $user,
+                'exception_msg' => $e->getMessage()
+            ]);
+        }
+
+        // 上报注册
+        try {
+            $this->reportService->reportRegister($user, $params);
+        } catch (\Exception $e) {
+            dLog('exception')->info('login-report-register-fail', [
+                'params'        => $params,
+                'user'          => $user,
+                'exception_msg' => $e->getMessage()
+            ]);
+        }
+
+        return $this->success($user);
+    }
+
+    /**
+     * 我的书架
+     *
+     * @param Request $request
+     * @return mixed
+     */
+    public function shelfBooks(Request $request)
+    {
+        $data = $request->all();
+
+        $result = $this->userService->getShelfBooks($data);
+        return $this->success($result, [new UserTransformer(), 'newBuildUserShelfBooks']);
+    }
+
+    /**
+     * 添加书架
+     *
+     * @param Request $request
+     * @return mixed
+     */
+    public function addShelf(Request $request)
+    {
+        $data = $request->all();
+
+        $result = $this->userService->addShelf($data);
+        return $this->success(['success' => $result ? 1 : 0]);
+    }
+
+    /**
+     * 删除书架
+     *
+     * @param Request $request
+     * @return mixed
+     */
+    public function deleteShelf(Request $request)
+    {
+        $data = $request->all();
+
+        $result = $this->userService->batchDeleteShelf($data);
+        return $this->success(['success' => $result ? 1 : 0]);
+    }
+
+    /**
+     * 获取用户阅读记录
+     *
+     * @param Request $request
+     * @return mixed
+     */
+    public function recentBooks(Request $request)
+    {
+        $data = $request->all();
+
+        $result = $this->userService->getRecentBooks($data);
+        return $this->success(['list' => $result]);
+    }
+
+    /**
+     * 用户信息
+     *
+     * @param Request $request
+     * @return mixed
+     */
+    public function userInfo(Request $request)
+    {
+        $data = $request->all();
+
+        $result = $this->userService->getUserInfo($data);
+        return $this->success($result);
+    }
+
+    /**
+     * 绑定用户
+     *
+     * @param Request $request
+     * @return mixed
+     */
+    public function bindUser(Request $request)
+    {
+        $data = $request->all();
+
+        $result = $this->userService->bindUser($data);
+        return $this->success(['success' => $result ? 1 : 0]);
+    }
+
+    /**
+     * 福利页
+     *
+     * @param Request $request
+     * @return mixed
+     */
+    public function welfare(Request $request)
+    {
+        $data = $request->all();
+
+        $result = $this->userService->welfare($data);
+        return $this->success($result);
+    }
+
+    /**
+     * 我的收益
+     *
+     * @param Request $request
+     * @return mixed
+     */
+    public function earnings(Request $request)
+    {
+        $data = $request->all();
+
+        $result = $this->userService->earnings($data);
+        return $this->success($result, [new UserTransformer(), 'newBuildEarnings']);
+    }
+
+    /**
+     * 提现档位
+     *
+     * @param Request $request
+     * @return mixed
+     */
+    public function withdrawConfigs(Request $request)
+    {
+        $data = $request->all();
+
+        $result = $this->userService->withdrawConfigs($data);
+        return $this->success($result);
+    }
+
+    /**
+     * 提现
+     *
+     * @param Request $request
+     * @return mixed
+     */
+    public function withdraw(Request $request)
+    {
+        $data      = $request->all();
+        $validator = Validator::make($data, [
+            'withdraw_type'   => 'required|in:WECHAT,ZHIFUBAO',
+            'amount'          => 'required|numeric',
+            'alipay_username' => 'required_if:withdraw_type,ZHIFUBAO',
+            'alipay_account'  => 'required_if:withdraw_type,ZHIFUBAO',
+        ], [
+            'withdraw_type.required'      => '请选择提现方式',
+            'withdraw_type.in'            => '提现方式不正确',
+            'amount.required'             => '请填写提现额度',
+            'amount.numeric'              => '提现额度格式不正确',
+            'alipay_username.required_if' => '请填写用户名',
+            'alipay_account.required_if'  => '请填写手机号或邮箱',
+        ]);
+        if ($validator->fails()) {
+            Utils::throwError('1002:' . $validator->errors()->all()[0]);
+        }
+
+        $result = $this->userService->withdraw($data);
+        return $this->success(['success' => $result ? 1 : 0]);
+    }
+
+    /**
+     * 当前阅读
+     *
+     * @return mixed
+     */
+    public function currentBook()
+    {
+        $result = $this->bookService->getCurrentBook();
+        return $this->success($result, [new UserTransformer(), 'newBuildCurrentBook']);
+    }
+
+    /**
+     * 同步用户数据
+     *
+     * @param Request $request
+     * @return mixed
+     * @throws ApiException
+     */
+    public function syncUserInfo(Request $request)
+    {
+        $iv            = $request->get('iv', '');
+        $signature     = $request->get('signature', '');
+        $encryptedData = $request->get('encryptedData', '');
+        $rawData       = $request->get('rawData', '');
+        $userInfo      = $request->get('userInfo', []);
+        $result        = $this->userService->syncUserInfo($iv, $signature, $encryptedData, $rawData, $userInfo);
+        return $this->success(compact('result'));
+    }
+
+    /**
+     * 绑定派单链接
+     *
+     * @param Request $request
+     * @return mixed
+     */
+    public function bindSendOrder(Request $request)
+    {
+        $data   = $request->all();
+        $result = $this->userService->bindSendOrder($data);
+        return $this->success(['success' => $result ? 1 : 0]);
+    }
+}

+ 38 - 0
app/Http/Controllers/Webhook/WebhookController.php

@@ -0,0 +1,38 @@
+<?php
+
+namespace App\Http\Controllers\Webhook;
+
+use App\Services\Webhook\WebhookService;
+use Illuminate\Http\Request;
+use Illuminate\Http\JsonResponse;
+use Illuminate\Routing\Controller as BaseController;
+
+class WebhookController extends BaseController
+{
+    private $webhookService;
+
+    public function __construct(
+        WebhookService $webhookService
+    )
+    {
+        $this->webhookService = $webhookService;
+    }
+
+    /**
+     * 抖音im回调
+     *
+     * @param Request $request
+     * @return JsonResponse
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function douYinIm(Request $request)
+    {
+        $data = $request->all();
+        dLog('webhook')->info('douYinIm-params', $data);
+
+        // 根据event事件区分
+        $event  = trim(getProp($data, 'event'));
+        $result = $this->webhookService->handleDouYinEvent($event, $data);
+        return response()->json($result);
+    }
+}

+ 72 - 0
app/Http/Kernel.php

@@ -0,0 +1,72 @@
+<?php
+
+namespace App\Http;
+
+use Illuminate\Foundation\Http\Kernel as HttpKernel;
+
+class Kernel extends HttpKernel
+{
+    /**
+     * The application's global HTTP middleware stack.
+     *
+     * These middleware are run during every request to your application.
+     *
+     * @var array<int, class-string|string>
+     */
+    protected $middleware = [
+        // \App\Http\Middleware\TrustHosts::class,
+        \App\Http\Middleware\TrustProxies::class,
+        \Fruitcake\Cors\HandleCors::class,
+        \App\Http\Middleware\PreventRequestsDuringMaintenance::class,
+        \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
+        \App\Http\Middleware\TrimStrings::class,
+        \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
+    ];
+
+    /**
+     * The application's route middleware groups.
+     *
+     * @var array<string, array<int, class-string|string>>
+     */
+    protected $middlewareGroups = [
+        'web' => [
+            \App\Http\Middleware\EncryptCookies::class,
+            \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
+            \Illuminate\Session\Middleware\StartSession::class,
+            // \Illuminate\Session\Middleware\AuthenticateSession::class,
+            \Illuminate\View\Middleware\ShareErrorsFromSession::class,
+            \App\Http\Middleware\VerifyCsrfToken::class,
+            \Illuminate\Routing\Middleware\SubstituteBindings::class,
+        ],
+
+        'api' => [
+            // \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
+            'throttle:api',
+            \Illuminate\Routing\Middleware\SubstituteBindings::class,
+        ],
+    ];
+
+    /**
+     * The application's route middleware.
+     *
+     * These middleware may be assigned to groups or used individually.
+     *
+     * @var array<string, class-string|string>
+     */
+    protected $routeMiddleware = [
+        'auth'             => \App\Http\Middleware\Authenticate::class,
+        'auth.basic'       => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
+        'cache.headers'    => \Illuminate\Http\Middleware\SetCacheHeaders::class,
+        'can'              => \Illuminate\Auth\Middleware\Authorize::class,
+        'guest'            => \App\Http\Middleware\RedirectIfAuthenticated::class,
+        'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class,
+        'signed'           => \Illuminate\Routing\Middleware\ValidateSignature::class,
+        'throttle'         => \Illuminate\Routing\Middleware\ThrottleRequests::class,
+        'verified'         => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
+        'checkLogin'       => \App\Http\Middleware\CheckLogin::class,
+        'checkBook'        => \App\Http\Middleware\CheckBook::class,
+        'bindToken'        => \App\Http\Middleware\BindToken::class,
+        'bindExportToken'  => \App\Http\Middleware\BindExportToken::class,
+        'checkSign'        => \App\Http\Middleware\CheckSign::class,
+    ];
+}

+ 21 - 0
app/Http/Middleware/Authenticate.php

@@ -0,0 +1,21 @@
+<?php
+
+namespace App\Http\Middleware;
+
+use Illuminate\Auth\Middleware\Authenticate as Middleware;
+
+class Authenticate extends Middleware
+{
+    /**
+     * Get the path the user should be redirected to when they are not authenticated.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @return string|null
+     */
+    protected function redirectTo($request)
+    {
+        if (! $request->expectsJson()) {
+            return route('login');
+        }
+    }
+}

+ 30 - 0
app/Http/Middleware/BindExportToken.php

@@ -0,0 +1,30 @@
+<?php
+
+
+namespace App\Http\Middleware;
+
+use Closure;
+use App\Exceptions\ApiException;
+
+class BindExportToken
+{
+    use CheckTokenTrait;
+
+    /**
+     * @param         $request
+     * @param Closure $next
+     * @return mixed
+     * @throws ApiException
+     */
+    public function handle($request, Closure $next)
+    {
+        // 接口中的token(如有则绑定token到全局)
+        $all       = $request->all();
+        $token     = getProp($all, 'd_token');
+        $channelId = getProp($all, 'd_channel_id');
+        if ($token && $channelId) {
+            $this->checkTokenTrait($token, $channelId);
+        }
+        return $next($request);
+    }
+}

+ 29 - 0
app/Http/Middleware/BindToken.php

@@ -0,0 +1,29 @@
+<?php
+
+
+namespace App\Http\Middleware;
+
+use Closure;
+use App\Exceptions\ApiException;
+
+class BindToken
+{
+    use CheckTokenTrait;
+
+    /**
+     * @param         $request
+     * @param Closure $next
+     * @return mixed
+     * @throws ApiException
+     */
+    public function handle($request, Closure $next)
+    {
+        // 接口中的token(如有则绑定token到全局)
+        $token     = $request->header('d-token', '');
+        $channelId = (int)$request->header('d-channel-id', 0);
+        if ($token && $channelId) {
+            $this->checkTokenTrait($token, $channelId);
+        }
+        return $next($request);
+    }
+}

+ 92 - 0
app/Http/Middleware/CheckBook.php

@@ -0,0 +1,92 @@
+<?php
+
+
+namespace App\Http\Middleware;
+
+use App\Consts\ErrorConst;
+use App\Facade\Site;
+use App\Libs\Utils;
+use Closure;
+use App\Exceptions\ApiException;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Facades\Route;
+use Vinkla\Hashids\Facades\Hashids;
+
+class CheckBook
+{
+    /**
+     * @param         $request
+     * @param Closure $next
+     * @return mixed
+     * @throws ApiException
+     */
+    public function handle($request, Closure $next)
+    {
+        $params = $request->all();
+        $cid = getProp($params, 'cid');
+        $bid = getProp($params, 'bid');
+        if ($bid) {
+            if (mb_strlen($bid) === 32) {
+                $bid = Hashids::decode($bid)[0];
+                if (!$bid) {    // 无法解密的是数据异常
+                    Utils::throwError(ErrorConst::DATA_EXCEPTION);
+                }
+            }
+
+            $isOnShelf = (int)DB::table('book_configs')->where('bid', $bid)->value('is_on_shelf');
+            if (!in_array($isOnShelf, [1, 2])) {
+                Utils::throwError(ErrorConst::BOOK_NOT_EXIST);
+            }
+        }
+
+        if (!$cid) return $next($request);
+        if (mb_strlen($cid) === 32) {
+            $cid = Hashids::decode($cid)[0];
+            if (!$cid) {    // 无法解密的是数据异常
+                Utils::throwError(ErrorConst::DATA_EXCEPTION);
+            }
+        }
+
+        // 获取章节信息
+        $chapter_info = DB::table('chapters')->leftJoin('book_configs', 'chapters.bid', 'book_configs.bid')
+            ->whereIn('book_configs.is_on_shelf', [1, 2])->where('chapters.id', $cid)
+            ->select('chapters.is_vip', 'chapters.sequence')->first();
+        if (!$chapter_info) {
+            Utils::throwError(ErrorConst::DATA_EXCEPTION);
+        }
+
+        $uid = Site::getUid();
+
+        // 获取站点信息
+        $distribution_channel_id = Site::getChannelId();
+        $distribution_channel_info = DB::table('distribution_channels')->where('id', $distribution_channel_id)->first();
+        $channel_type = getProp($distribution_channel_info, 'channel_type');
+
+        switch ($channel_type) {
+            case 'PERIOD':      // 时间周期(会员制)
+                // 用户未登录不可看第10章以后的章节
+                if (!$uid && getProp($chapter_info, 'sequence') > 10) {
+                    Utils::throwError(ErrorConst::NOT_LOGIN);
+                }
+
+                // 验证收费章节是否可以阅读
+                if ($uid && getProp($chapter_info, 'is_vip')) {
+                    $vip_limit_date = DB::table('users')->where('id', $uid)->value('vip_limit_date');
+                    $vip_limit_date = transDate($vip_limit_date, 'Y-m-d');
+                    if (!$vip_limit_date || $vip_limit_date < date('Y-m-d')) {
+                        Utils::throwError(ErrorConst::VIP_VALID);
+                    }
+                }
+                break;
+            case 'FREE':        // 全免费
+                //TODO 全免费站点无需检验章节内容
+                break;
+            case 'RECHARGE':    // 充值
+                //TODO 预留逻辑(后续开发充值类型的章节判断)
+                break;
+            default:
+                break;
+        }
+        return $next($request);
+    }
+}

+ 28 - 0
app/Http/Middleware/CheckLogin.php

@@ -0,0 +1,28 @@
+<?php
+
+
+namespace App\Http\Middleware;
+
+use App\Cache\UserCache;
+use App\Consts\ErrorConst;
+use App\Facade\Site;
+use App\Libs\Utils;
+use Closure;
+use App\Exceptions\ApiException;
+
+class CheckLogin
+{
+    /**
+     * @param         $request
+     * @param Closure $next
+     * @return mixed
+     * @throws ApiException
+     */
+    public function handle($request, Closure $next)
+    {
+        $uid = Site::getUid();
+        if (!$uid)  Utils::throwError(ErrorConst::NOT_LOGIN);
+
+        return $next($request);
+    }
+}

+ 71 - 0
app/Http/Middleware/CheckSign.php

@@ -0,0 +1,71 @@
+<?php
+
+
+namespace App\Http\Middleware;
+
+use App\Cache\UserCache;
+use App\Consts\ErrorConst;
+use App\Libs\Utils;
+use App\Models\Channel\Channel;
+use Closure;
+use App\Exceptions\ApiException;
+use Illuminate\Support\Facades\Log;
+
+class CheckSign
+{
+    /**
+     * @param         $request
+     * @param Closure $next
+     * @return mixed
+     * @throws ApiException
+     */
+    public function handle($request, Closure $next)
+    {
+        $params = $request->all();
+        $token = $request->header('d-token', '');
+        $token_data = UserCache::getTokenData($token);
+        // 未登录跳过验签
+        if (!getProp($token_data, 'uid')) return $next($request);
+        $uid = getProp($token_data, 'uid');
+        // 老用户跳过验签
+        if ($uid <= 479) return $next($request);
+
+        $referer_url = '';
+        if (isset($params['_url'])) {
+            $referer_url = $params['_url'];
+            unset($params['_url']);
+        }
+        // 先验签(非本地模式需要验签)
+        if (env('CHECK_SIGN') && $params) {
+            $param_sign = getProp($params, 'sign');
+            $timestamp = getProp($params, 'timestamp');
+            if (!getProp($params, 'nonce_str') || !$timestamp) {
+                Log::info('验签失败, 请求参数不正确;传参: '.json_encode($params, 256));
+                Utils::throwError('1002:数据异常,请求参数不正确');
+            }
+            if (time() - $timestamp > 300) {
+                Log::info('验签失败, 签名5分钟内有效;传参: '.json_encode($params, 256));
+                Utils::throwError('1002:数据异常,请求参数不正确');
+            }
+
+            foreach ($params as $k=>$v) {
+                if (!$v) unset($params[$k]);
+            }
+
+            unset($params['sign']);
+            ksort($params);
+            $str = strtoupper(http_build_query($params));
+            $sign = md5($str.'&key='.env('SIGN_SALT'));
+            if ($param_sign != $sign) {
+                $params['_url'] = $referer_url;
+                $params['sign'] = $param_sign;
+                $params['check_sign'] = $sign;
+                $params['check_str'] = $str.'&key='.env('SIGN_SALT');
+                Log::info('验签失败, 签名不正确;传参: '.json_encode($params, 256));
+                Utils::throwError('1002:数据异常,请求参数不正确');
+            }
+        }
+
+        return $next($request);
+    }
+}

+ 41 - 0
app/Http/Middleware/CheckTokenTrait.php

@@ -0,0 +1,41 @@
+<?php
+
+namespace App\Http\Middleware;
+
+use App\Consts\ErrorConst;
+use App\Libs\Utils;
+use App\Models\Channel\Channel;
+use App\Models\Channel\ChannelUser;
+
+trait CheckTokenTrait
+{
+    public function checkTokenTrait($token, $channelId)
+    {
+        // 获取用户信息
+        $user = ChannelUser::where('token', $token)->first();
+        $uid  = (int)getProp($user, 'id');
+        if (!$uid) {
+            Utils::throwError(ErrorConst::NOT_LOGIN);
+        }
+
+        // 将数据绑定到全局
+        $site           = app('siteData');
+        $site->uid      = $uid;
+        $site->account  = getProp($user, 'account');
+        $site->phone    = getProp($user, 'phone');
+        $site->nickname = getProp($user, 'nickname');
+        $site->token    = $token;
+
+        // 获取当前站点id
+        $channel       = Channel::getById($channelId);
+        $channelUserId = (int)getProp($channel, 'channel_user_id');
+
+        // 判断站点权限
+        if ($uid != $channelUserId) {
+            Utils::throwError(ErrorConst::USER_NO_ACCESS_CHANNEL);
+        }
+
+        $site->current_channel_id   = $channelId;
+        $site->current_channel_name = getProp($channel, 'name');
+    }
+}

+ 17 - 0
app/Http/Middleware/EncryptCookies.php

@@ -0,0 +1,17 @@
+<?php
+
+namespace App\Http\Middleware;
+
+use Illuminate\Cookie\Middleware\EncryptCookies as Middleware;
+
+class EncryptCookies extends Middleware
+{
+    /**
+     * The names of the cookies that should not be encrypted.
+     *
+     * @var array<int, string>
+     */
+    protected $except = [
+        //
+    ];
+}

+ 17 - 0
app/Http/Middleware/PreventRequestsDuringMaintenance.php

@@ -0,0 +1,17 @@
+<?php
+
+namespace App\Http\Middleware;
+
+use Illuminate\Foundation\Http\Middleware\PreventRequestsDuringMaintenance as Middleware;
+
+class PreventRequestsDuringMaintenance extends Middleware
+{
+    /**
+     * The URIs that should be reachable while maintenance mode is enabled.
+     *
+     * @var array<int, string>
+     */
+    protected $except = [
+        //
+    ];
+}

+ 32 - 0
app/Http/Middleware/RedirectIfAuthenticated.php

@@ -0,0 +1,32 @@
+<?php
+
+namespace App\Http\Middleware;
+
+use App\Providers\RouteServiceProvider;
+use Closure;
+use Illuminate\Http\Request;
+use Illuminate\Support\Facades\Auth;
+
+class RedirectIfAuthenticated
+{
+    /**
+     * Handle an incoming request.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @param  \Closure(\Illuminate\Http\Request): (\Illuminate\Http\Response|\Illuminate\Http\RedirectResponse)  $next
+     * @param  string|null  ...$guards
+     * @return \Illuminate\Http\Response|\Illuminate\Http\RedirectResponse
+     */
+    public function handle(Request $request, Closure $next, ...$guards)
+    {
+        $guards = empty($guards) ? [null] : $guards;
+
+        foreach ($guards as $guard) {
+            if (Auth::guard($guard)->check()) {
+                return redirect(RouteServiceProvider::HOME);
+            }
+        }
+
+        return $next($request);
+    }
+}

+ 19 - 0
app/Http/Middleware/TrimStrings.php

@@ -0,0 +1,19 @@
+<?php
+
+namespace App\Http\Middleware;
+
+use Illuminate\Foundation\Http\Middleware\TrimStrings as Middleware;
+
+class TrimStrings extends Middleware
+{
+    /**
+     * The names of the attributes that should not be trimmed.
+     *
+     * @var array<int, string>
+     */
+    protected $except = [
+        'current_password',
+        'password',
+        'password_confirmation',
+    ];
+}

+ 20 - 0
app/Http/Middleware/TrustHosts.php

@@ -0,0 +1,20 @@
+<?php
+
+namespace App\Http\Middleware;
+
+use Illuminate\Http\Middleware\TrustHosts as Middleware;
+
+class TrustHosts extends Middleware
+{
+    /**
+     * Get the host patterns that should be trusted.
+     *
+     * @return array<int, string|null>
+     */
+    public function hosts()
+    {
+        return [
+            $this->allSubdomainsOfApplicationUrl(),
+        ];
+    }
+}

+ 28 - 0
app/Http/Middleware/TrustProxies.php

@@ -0,0 +1,28 @@
+<?php
+
+namespace App\Http\Middleware;
+
+use Illuminate\Http\Middleware\TrustProxies as Middleware;
+use Illuminate\Http\Request;
+
+class TrustProxies extends Middleware
+{
+    /**
+     * The trusted proxies for this application.
+     *
+     * @var array<int, string>|string|null
+     */
+    protected $proxies;
+
+    /**
+     * The headers that should be used to detect proxies.
+     *
+     * @var int
+     */
+    protected $headers =
+        Request::HEADER_X_FORWARDED_FOR |
+        Request::HEADER_X_FORWARDED_HOST |
+        Request::HEADER_X_FORWARDED_PORT |
+        Request::HEADER_X_FORWARDED_PROTO |
+        Request::HEADER_X_FORWARDED_AWS_ELB;
+}

+ 17 - 0
app/Http/Middleware/VerifyCsrfToken.php

@@ -0,0 +1,17 @@
+<?php
+
+namespace App\Http\Middleware;
+
+use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as Middleware;
+
+class VerifyCsrfToken extends Middleware
+{
+    /**
+     * The URIs that should be excluded from CSRF verification.
+     *
+     * @var array<int, string>
+     */
+    protected $except = [
+        //
+    ];
+}

+ 109 - 0
app/Jobs/ReportDy.php

@@ -0,0 +1,109 @@
+<?php
+
+namespace App\Jobs;
+
+use App\Consts\ErrorConst;
+use App\Libs\Utils;
+use GuzzleHttp\Client;
+use Illuminate\Bus\Queueable;
+use Illuminate\Contracts\Queue\ShouldBeUnique;
+use Illuminate\Contracts\Queue\ShouldQueue;
+use Illuminate\Foundation\Bus\Dispatchable;
+use Illuminate\Queue\InteractsWithQueue;
+use Illuminate\Queue\SerializesModels;
+use Illuminate\Support\Facades\DB;
+use Throwable;
+
+class ReportDy implements ShouldQueue
+{
+    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
+
+    /**
+     * 任务可尝试次数
+     *
+     * @var int
+     */
+    public $tries = 5;          // 重试次数
+    public $timeout = 120;      // 超时秒数
+    public $backoff = 3;        // 重试任务前等待的秒数
+    public $params = [];
+    public $url = 'https://analytics.oceanengine.com/api/v2/conversion';    // 上报url
+
+    /**
+     * Create a new job instance.
+     *
+     * @param $params
+     */
+    public function __construct($params)
+    {
+        $this->params = $params;
+        $this->onQueue('{ReportDy}');
+    }
+
+    /**
+     * Execute the job.
+     *
+     * @return void
+     */
+    public function handle()
+    {
+        dLog('reportDy')->info('开始执行上报抖音队列', $this->params);
+        $clickid = getProp($this->params, 'clickid');
+        $event_type = getProp($this->params, 'event_type');
+        if (!$clickid || !$event_type) {
+            dLog('reportDy')->info('抖音上报参数错误', $this->params);
+            return ;
+        }
+        $trade_no = '';
+        if (isset($this->params['trade_no'])) {
+            $trade_no = $this->params['trade_no'];
+            unset($this->params['trade_no']);
+        }
+
+        try {
+            $client = new Client(['verify' => false]);
+            $response = $client->post($this->url, ['json' => $this->params]);
+            $response_json = $response->getBody()->getContents();
+            $result = json_decode($response_json, true);
+
+            $update_data = [
+                'event_type'        => $event_type,
+                'callback_result'   => isset($result['message']) ? $result['message'] : '回参有误',
+                'callback_response' => $response_json,
+                'updated_at'        => date('Y-m-d H:i:s')
+            ];
+            if ($trade_no) $update_data['trade_no'] = $trade_no;
+
+            DB::beginTransaction();
+
+            // 写入抖音广告主投放回调日志表
+            $boolen = DB::table('dy_report_logs')->where('clickid', $clickid)->where('event_type', '')->update($update_data);
+            if (!$boolen) {
+                DB::rollBack();
+                dLog('reportDy')->info('写入抖音广告主投放回调日志表失败: ', $update_data);
+                Utils::throwError(ErrorConst::REPORT_FAILED);
+            }
+
+            // 如果是充值事件则更新派单级别回传上报总数
+            if ($event_type == 'active_pay') {
+                $send_order_id = DB::table('dy_report_logs')->where('clickid', $clickid)->value('send_order_id');
+                $boolen1 = DB::table('send_orders')->where('id', $send_order_id)->increment('report_post_num');
+                if (!$boolen1) {
+                    DB::rollBack();
+                    dLog('reportDy')->info('派单表更新回传上报总数失败: ', $this->params);
+                    Utils::throwError(ErrorConst::REPORT_FAILED);
+                }
+            }
+            DB::commit();
+
+            dLog('reportDy')->info('上报结果: ', $result);
+        }catch (\Exception $e) {
+            dLog('reportDy')->info('上报异常: ', ['error'=>$e->getMessage()]);
+        }
+    }
+
+    public function failed(Throwable $exception)
+    {
+        dLog('reportDy')->info('上报抖音队列失败', ['error'=>$exception->getMessage()]);
+    }
+}

+ 41 - 0
app/Libs/AliOSS.php

@@ -0,0 +1,41 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: tandunzhao
+ * Date: 2018/1/19
+ * Time: 下午12:58
+ */
+
+namespace App\Libs;
+
+use Illuminate\Support\Facades\Log;
+use OSS\Core\OssException;
+use OSS\OssClient;
+
+class AliOSS
+{
+
+    public static function uploadImg($ossPath, $filePath)
+    {
+        $ossImgBackData = self::ossObject()->uploadFile(env('OSS_BUCKET', 'zhuishuyun'), $ossPath, $filePath);
+        return $ossImgBackData['oss-request-url'];
+    }
+
+
+    /**
+     * OSS文件操作
+     */
+    public static function ossObject()
+    {
+        $accessKeyId     = env('OSS_ACCESS_ID');
+        $accessKeySecret = env('OSS_ACCESS_KEY');
+        $endpoint        = env('OSS_END_POINT');
+        try {
+            $ossClient = new OssClient($accessKeyId, $accessKeySecret, $endpoint);
+        } catch (OssException $e) {
+            Log::error($e->getMessage() . "      " . date("y-m-d H:i:s" . "\n"));
+            return null;
+        }
+        return $ossClient;
+    }
+}

+ 86 - 0
app/Libs/AliSMS.php

@@ -0,0 +1,86 @@
+<?php
+
+namespace App\Libs;
+
+use Exception;
+use AlibabaCloud\SDK\Dysmsapi\V20170525\Dysmsapi;
+use Darabonba\OpenApi\Models\Config;
+use AlibabaCloud\SDK\Dysmsapi\V20170525\Models\SendSmsRequest;
+use AlibabaCloud\Tea\Utils\Utils\RuntimeOptions;
+use AlibabaCloud\SDK\Dysmsapi\V20170525\Models\QuerySmsTemplateListRequest;
+use AlibabaCloud\SDK\Dysmsapi\V20170525\Models\SendSmsResponse;
+use AlibabaCloud\SDK\Dysmsapi\V20170525\Models\QuerySmsTemplateListResponse;
+
+
+class AliSMS
+{
+
+    /**
+     * 使用AK&SK初始化账号Client
+     *
+     * @return Dysmsapi Client
+     */
+    public static function createClient(): Dysmsapi
+    {
+        $config = new Config([
+            // 必填,您的 AccessKey ID
+            "accessKeyId"     => env('SMS_AccessKeyId'),
+            // 必填,您的 AccessKey Secret
+            "accessKeySecret" => env('SMS_AccessKeySecret')
+        ]);
+
+        // Endpoint 请参考 https://api.aliyun.com/product/Dysmsapi
+        $config->endpoint = "dysmsapi.aliyuncs.com";
+        return new Dysmsapi($config);
+    }
+
+    /**
+     * 发送短信
+     * https://next.api.aliyun.com/api/Dysmsapi/2017-05-25/SendSms
+     *
+     * @param $phone
+     * @param $templateCode
+     * @param $param
+     * @param $sign
+     * @return SendSmsResponse|void
+     */
+    public static function sendSms($phone, $templateCode, $param, $sign)
+    {
+        $client = self::createClient();
+
+        // 组装请求参数
+        $sendSmsRequest                = new SendSmsRequest([]);
+        $sendSmsRequest->phoneNumbers  = $phone;
+        $sendSmsRequest->templateCode  = $templateCode;
+        $sendSmsRequest->templateParam = json_encode($param, JSON_UNESCAPED_UNICODE);
+        $sendSmsRequest->signName      = $sign;
+
+        $runtime = new RuntimeOptions([]);
+
+        try {
+            // 复制代码运行请自行打印 API 的返回值
+            return $client->sendSmsWithOptions($sendSmsRequest, $runtime);
+        } catch (Exception $error) {
+            dLog('exception')->info('sendSms', [$error->getMessage(), $error->getCode()]);
+        }
+    }
+
+    /**
+     * 查询短信模板
+     * https://next.api.aliyun.com/api/Dysmsapi/2017-05-25/QuerySmsTemplateList
+     *
+     * @return QuerySmsTemplateListResponse|void
+     */
+    public static function querySmsTemplateList()
+    {
+        $client                      = self::createClient();
+        $querySmsTemplateListRequest = new QuerySmsTemplateListRequest([]);
+        $runtime                     = new RuntimeOptions([]);
+        try {
+            // 复制代码运行请自行打印 API 的返回值
+            return $client->querySmsTemplateListWithOptions($querySmsTemplateListRequest, $runtime);
+        } catch (Exception $error) {
+            dLog('exception')->info('querySmsTemplateList', [$error->getMessage(), $error->getCode()]);
+        }
+    }
+}

+ 185 - 0
app/Libs/ApiResponse.php

@@ -0,0 +1,185 @@
+<?php
+
+
+namespace App\Libs;
+
+use Symfony\Component\HttpFoundation\Response as FoundationResponse;
+use Response;
+
+trait ApiResponse
+{
+    /**
+     * @var int
+     */
+    protected $statusCode = FoundationResponse::HTTP_OK;
+
+    /**
+     * @return mixed
+     */
+    public function getStatusCode()
+    {
+        return $this->statusCode;
+    }
+
+    /**
+     * @param $statusCode
+     * @return $this
+     */
+    public function setStatusCode($statusCode, $httpCode = null)
+    {
+        $httpCode         = $httpCode ?? $statusCode;
+        $this->statusCode = $statusCode;
+        return $this;
+    }
+
+    /**
+     * @param       $data
+     * @param array $header
+     * @return mixed
+     */
+    public function respond($data, $header = [])
+    {
+        return Response::json($data, $this->getStatusCode(), $header, JSON_UNESCAPED_UNICODE);
+    }
+
+    /**
+     * @param       $status
+     * @param array $data
+     * @param null  $code
+     * @return mixed
+     */
+    public function status($status, $data, $code = null)
+    {
+        if ($code) {
+            $this->setStatusCode($code);
+        }
+        $res = [
+            'msg'  => $status,
+            'code' => 0,
+            'data' => $data
+        ];
+
+        return $this->respond($res);
+    }
+
+    /**
+     * @param        $message
+     * @param int    $code
+     * @param string $status
+     * @return mixed
+     */
+    /*
+     * 格式
+     * data:
+     *  code:422
+     *  message:xxx
+     *  status:'error'
+     */
+    public function failed($message, $code = FoundationResponse::HTTP_BAD_REQUEST, $status = 'error')
+    {
+
+        return $this->setStatusCode($code)->message($message, $status);
+    }
+
+    /**
+     * @param        $message
+     * @param string $status
+     * @return mixed
+     */
+    public function message($message, $status = 'success')
+    {
+
+        return $this->status($status, [
+            'message' => $message
+        ]);
+    }
+
+    /**
+     * @param string $message
+     * @return mixed
+     */
+    public function internalError($message = 'Internal Error!')
+    {
+
+        return $this->failed($message, FoundationResponse::HTTP_INTERNAL_SERVER_ERROR);
+    }
+
+    /**
+     * @param string $message
+     * @return mixed
+     */
+    public function created($message = 'created')
+    {
+        return $this->setStatusCode(FoundationResponse::HTTP_CREATED)
+            ->message($message);
+
+    }
+
+    /**
+     * @param $data
+     * @param $callFunc
+     * @return mixed
+     */
+    public function success($data, $callFunc = [])
+    {
+        if ($callFunc) {
+            return $this->status('', call_user_func($callFunc, $data));
+        }
+
+        return $this->status('', $data);
+    }
+
+    /**
+     * @param $data
+     * @param $callFunc
+     * @return mixed
+     */
+    public function paginate($data, $callFunc = [])
+    {
+        $result = [
+            'meta' => getMeta($data),
+            'list' => $data->items()
+        ];
+
+        if ($callFunc) {
+            $result['list'] = call_user_func($callFunc, $data->items());
+        }
+
+        return $this->status('', $result);
+    }
+
+    /**
+     * @param $errorData
+     * @param $data
+     * @return mixed
+     */
+    public function error($errorData, $data = '')
+    {
+        // 分解错误码、错误信息
+        $arr  = explode(':', (string)$errorData);
+        $code = (int)$arr[0];
+        $msg  = (string)$arr[1];
+
+        $res = [
+            'msg'  => $msg,
+            'code' => $code,
+            'data' => $data
+        ];
+
+        return $this->respond($res);
+    }
+
+    public function appResponse($res)
+    {
+        return $this->respond($res);
+    }
+
+    /**
+     * @param string $message
+     * @return mixed
+     */
+    public function notFond($message = 'Not Fond!')
+    {
+        return $this->failed($message, Foundationresponse::HTTP_NOT_FOUND);
+    }
+}

+ 60 - 0
app/Libs/BatchUpdateTrait.php

@@ -0,0 +1,60 @@
+<?php
+
+namespace App\Libs;
+
+use Illuminate\Support\Facades\DB;
+
+trait BatchUpdateTrait
+{
+    /**
+     * @param string $table
+     * @param array  $list_data
+     * @param string  $index
+     * @param int    $chunk_size
+     *
+     * @return int
+     */
+    public function batchUpdateDb(string $table, array $list_data, string $index = 'id', int $chunk_size = 500): int
+    {
+        if (count($list_data) < 1 || $chunk_size < 1) {
+            return 0;
+        }
+
+        $chunk_list = array_chunk($list_data, $chunk_size);
+        $count = 0;
+        foreach ($chunk_list as $list_item) {
+            $first_row = current($list_item);
+            $update_col = array_keys($first_row);
+            // 默认以id为条件更新,如果没有ID则以第一个字段为条件
+            $reference_col = isset($first_row[$index]) ? $index : current($update_col);
+            unset($update_col[0]);
+            // 拼接sql语句
+            $update_sql = 'UPDATE ' . $table . ' SET ';
+            $sets = [];
+            $bindings = [];
+            foreach ($update_col as $u_col) {
+                $set_sql = '`' . $u_col . '` = CASE ';
+                foreach ($list_item as $item) {
+                    $set_sql .= 'WHEN `' . $reference_col . '` = ? THEN ';
+                    $bindings[] = $item[$reference_col];
+                    if ($item[$u_col] instanceof \Illuminate\Database\Query\Expression) {
+                        $set_sql .= $item[$u_col]->getValue() . ' ';
+                    } else {
+                        $set_sql .= '? ';
+                        $bindings[] = $item[$u_col];
+                    }
+                }
+                $set_sql .= 'ELSE `' . $u_col . '` END ';
+                $sets[] = $set_sql;
+            }
+            $update_sql .= implode(', ', $sets);
+            $where_in = collect($list_item)->pluck($reference_col)->values()->all();
+            $bindings = array_merge($bindings, $where_in);
+            $where_in = rtrim(str_repeat('?,', count($where_in)), ',');
+            $update_sql = rtrim($update_sql, ', ') . ' WHERE `' . $reference_col . '` IN (' . $where_in . ')';
+            //
+            $count += DB::update($update_sql, $bindings);
+        }
+        return $count;
+    }
+}

File diff suppressed because it is too large
+ 1592 - 0
app/Libs/Helpers.php


+ 195 - 0
app/Libs/OSS.php

@@ -0,0 +1,195 @@
+<?php
+
+namespace App\Libs;
+
+use DateTime;
+use Exception;
+use JohnLui\AliyunOSS;
+
+class OSS
+{
+
+    /* 城市名称:
+     *
+     *  经典网络下可选:杭州、上海、青岛、北京、张家口、深圳、香港、硅谷、弗吉尼亚、新加坡、悉尼、日本、法兰克福、迪拜
+     *  VPC 网络下可选:杭州、上海、青岛、北京、张家口、深圳、硅谷、弗吉尼亚、新加坡、悉尼、日本、法兰克福、迪拜
+     */
+    private $city = '杭州';
+
+    // 经典网络 or VPC
+    private $networkType = '经典网络';
+
+    private $AccessKeyId     = 'LTAI975hEK1kFkQb';
+    private $AccessKeySecret = 'hdnZFm8roqdCaZNovc4Ygbt4G7dBqt';
+
+
+    private $ossClient;
+
+    /**
+     * 私有初始化 API,非 API,不用关注
+     *
+     * @param boolean 是否使用内网
+     */
+    public function __construct($isInternal = false)
+    {
+        if ($this->networkType == 'VPC' && !$isInternal) {
+            throw new Exception("VPC 网络下不提供外网上传、下载等功能");
+        }
+        $this->ossClient = AliyunOSS::boot(
+            $this->city,
+            $this->networkType,
+            $isInternal,
+            $this->AccessKeyId,
+            $this->AccessKeySecret
+        );
+    }
+
+
+    /**
+     * 使用外网上传文件
+     *
+     * @param string bucket名称
+     * @param string 上传之后的 OSS object 名称
+     * @param string 上传文件路径
+     * @return boolean 上传是否成功
+     */
+    public static function publicUpload($bucketName, $ossKey, $filePath, $options = [])
+    {
+        $oss = new OSS();
+        $oss->ossClient->setBucket($bucketName);
+        return $oss->ossClient->uploadFile($ossKey, $filePath, $options);
+    }
+
+    /**
+     * 使用阿里云内网上传文件
+     *
+     * @param string bucket名称
+     * @param string 上传之后的 OSS object 名称
+     * @param string 上传文件路径
+     * @return boolean 上传是否成功
+     */
+    public static function privateUpload($bucketName, $ossKey, $filePath, $options = [])
+    {
+        $oss = new OSS(true);
+        $oss->ossClient->setBucket($bucketName);
+        return $oss->ossClient->uploadFile($ossKey, $filePath, $options);
+    }
+
+
+    /**
+     * 使用外网直接上传变量内容
+     *
+     * @param string bucket名称
+     * @param string 上传之后的 OSS object 名称
+     * @param string 上传的变量
+     * @return boolean 上传是否成功
+     */
+    public static function publicUploadContent($bucketName, $ossKey, $content, $options = [])
+    {
+        $oss = new OSS();
+        $oss->ossClient->setBucket($bucketName);
+        return $oss->ossClient->uploadContent($ossKey, $content, $options);
+    }
+
+    /**
+     * 使用阿里云内网直接上传变量内容
+     *
+     * @param string bucket名称
+     * @param string 上传之后的 OSS object 名称
+     * @param string 上传的变量
+     * @return boolean 上传是否成功
+     */
+    public static function privateUploadContent($bucketName, $ossKey, $content, $options = [])
+    {
+        $oss = new OSS(true);
+        $oss->ossClient->setBucket($bucketName);
+        return $oss->ossClient->uploadContent($ossKey, $content, $options);
+    }
+
+
+    /**
+     * 使用外网删除文件
+     *
+     * @param string bucket名称
+     * @param string 目标 OSS object 名称
+     * @return boolean 删除是否成功
+     */
+    public static function publicDeleteObject($bucketName, $ossKey)
+    {
+        $oss = new OSS();
+        $oss->ossClient->setBucket($bucketName);
+        return $oss->ossClient->deleteObject($bucketName, $ossKey);
+    }
+
+    /**
+     * 使用阿里云内网删除文件
+     *
+     * @param string bucket名称
+     * @param string 目标 OSS object 名称
+     * @return boolean 删除是否成功
+     */
+    public static function privateDeleteObject($bucketName, $ossKey)
+    {
+        $oss = new OSS(true);
+        $oss->ossClient->setBucket($bucketName);
+        return $oss->ossClient->deleteObject($bucketName, $ossKey);
+    }
+
+
+    /**
+     * -------------------------------------------------
+     *
+     *
+     *  下面不再分公网内网出 API,也不注释了,大家自行体会吧。。。
+     *
+     *
+     * -------------------------------------------------
+     */
+
+    public function copyObject($sourceBuckt, $sourceKey, $destBucket, $destKey)
+    {
+        $oss = new OSS();
+        return $oss->ossClient->copyObject($sourceBuckt, $sourceKey, $destBucket, $destKey);
+    }
+
+    public function moveObject($sourceBuckt, $sourceKey, $destBucket, $destKey)
+    {
+        $oss = new OSS();
+        return $oss->ossClient->moveObject($sourceBuckt, $sourceKey, $destBucket, $destKey);
+    }
+
+    // 获取公开文件的 URL
+    public static function getPublicObjectURL($bucketName, $ossKey)
+    {
+        $oss = new OSS();
+        $oss->ossClient->setBucket($bucketName);
+        return $oss->ossClient->getPublicUrl($ossKey);
+    }
+
+    // 获取私有文件的URL,并设定过期时间,如 \DateTime('+1 day')
+    public static function getPrivateObjectURLWithExpireTime($bucketName, $ossKey, DateTime $expire_time)
+    {
+        $oss = new OSS();
+        $oss->ossClient->setBucket($bucketName);
+        return $oss->ossClient->getUrl($ossKey, $expire_time);
+    }
+
+    public static function createBucket($bucketName)
+    {
+        $oss = new OSS();
+        return $oss->ossClient->createBucket($bucketName);
+    }
+
+    public static function getAllObjectKey($bucketName)
+    {
+        $oss = new OSS();
+        return $oss->ossClient->getAllObjectKey($bucketName);
+    }
+
+    public static function getObjectMeta($bucketName, $ossKey)
+    {
+        $oss = new OSS();
+        return $oss->ossClient->getObjectMeta($bucketName, $ossKey);
+    }
+
+}

+ 183 - 0
app/Libs/Pays/AliPaySdk/aop/AlipayConfig.php

@@ -0,0 +1,183 @@
+
+<?php
+class AlipayConfig {
+    /**
+     * 网关地址
+     * 线上:https://openapi.alipay.com/gateway.do 
+     * 沙箱:https://openapi.alipaydev.com/gateway.do
+     */
+    private $serverUrl;
+
+    /**
+     * 开放平台上创建的应用的ID
+     */
+    private $appId;
+
+    /**
+     * 报文格式,推荐:json
+     */
+    private $format = "json";
+
+    /**
+     * 字符串编码,推荐:utf-8
+     */
+    private $charset = "utf-8";
+
+    /**
+     * 签名算法类型,推荐:RSA2
+     */
+    private $signType = "RSA2";
+
+    /**
+     * 商户私钥
+     */
+    private $privateKey;
+
+    /**
+     * 支付宝公钥字符串(公钥模式下设置,证书模式下无需设置)
+     */
+    private $alipayPublicKey;
+
+    /**
+     * 商户应用公钥证书路径(证书模式下设置,公钥模式下无需设置)
+     */
+    private $appCertPath;
+
+    /**
+     * 支付宝公钥证书路径(证书模式下设置,公钥模式下无需设置)
+     */
+    private $alipayPublicCertPath;
+
+    /**
+     * 支付宝根证书路径(证书模式下设置,公钥模式下无需设置)
+     */
+    private $rootCertPath;
+
+    /**
+     * 指定商户公钥应用证书内容字符串,该字段与appCertPath只需指定一个,优先以该字段的值为准(证书模式下设置,公钥模式下无需设置)
+     */
+    private $appCertContent;
+
+    /**
+     * 指定支付宝公钥证书内容字符串,该字段与alipayPublicCertPath只需指定一个,优先以该字段的值为准(证书模式下设置,公钥模式下无需设置)
+     */
+    private $alipayPublicCertContent;
+
+    /**
+     * 指定根证书内容字符串,该字段与rootCertPath只需指定一个,优先以该字段的值为准(证书模式下设置,公钥模式下无需设置)
+     */
+    private $rootCertContent;
+
+    /**
+     * 敏感信息对称加密算法类型,推荐:AES
+     */
+    private $encryptType = "AES";
+
+    /**
+     * 敏感信息对称加密算法密钥
+     */
+    private $encryptKey;
+
+
+    public function getServerUrl() {
+        return $this->serverUrl;
+    }
+
+    public function setServerUrl($serverUrl) {
+        $this->serverUrl = $serverUrl;
+    }
+    public function getAppId(){
+        return $this->appId;
+    }
+    public function setAppId($appId){
+        $this->appId = $appId;
+    }
+    public function getFormat(){
+        return $this->format;
+    }
+    public function setFormat($format){
+        $this->format = $format;
+    }
+    public function getCharset() {
+        return $this->charset;
+    }
+
+    public function setCharset($charset) {
+        $this->charset = $charset;
+    }
+
+    public function getSignType() {
+        return $this->signType;
+    }
+
+    public function setSignType($signType) {
+        $this->signType = $signType;
+    }
+    public function getEncryptKey() {
+        return $this->encryptKey;
+    }
+
+    public function setEncryptKey($encryptKey) {
+       $this->encryptKey = $encryptKey;
+    }
+    public function getEncryptType() {
+        return $this->encryptType;
+    }
+    public function setEncryptType($encryptType) {
+        $this->encryptType = $encryptType;
+    }
+    public function getPrivateKey() {
+        return $this->privateKey;
+    }
+
+    public function setPrivateKey($privateKey) {
+        $this->privateKey = $privateKey;
+    }
+    public function getAlipayPublicKey() {
+        return $this->alipayPublicKey;
+    }
+    public function setAlipayPublicKey($alipayPublicKey) {
+        $this->alipayPublicKey = $alipayPublicKey;
+    }
+    public function getAppCertPath() {
+        return $this->appCertPath;
+    }
+
+    public function setAppCertPath($appCertPath) {
+        $this->appCertPath = $appCertPath;
+    }
+
+    public function getAlipayPublicCertPath() {
+        return $this->alipayPublicCertPath;
+    }
+    public function setAlipayPublicCertPath($alipayPublicCertPath) {
+        $this->alipayPublicCertPath = $alipayPublicCertPath;
+    }
+
+    public function getRootCertPath() {
+        return $this->rootCertPath;
+    }
+    public function setRootCertPath($rootCertPath) {
+        $this->rootCertPath = $rootCertPath;
+    }
+    public function getAppCertContent() {
+        return $this->appCertContent;
+    }
+    public function setAppCertContent($appCertContent) {
+        $this->appCertContent = $appCertContent;
+    }
+    public function getAlipayPublicCertContent() {
+        return $this->alipayPublicCertContent;
+    }
+    public function setAlipayPublicCertContent($alipayPublicCertContent) {
+        $this->alipayPublicCertContent = $alipayPublicCertContent;
+    }
+    public function getRootCertContent() {
+        return $this->rootCertContent;
+    }
+
+    public function setRootCertContent($rootCertContent) {
+        $this->rootCertContent = $rootCertContent;
+    }
+ 
+}

+ 188 - 0
app/Libs/Pays/AliPaySdk/aop/AlipayMobilePublicMultiMediaClient.php

@@ -0,0 +1,188 @@
+<?php
+
+/**
+ * 多媒体文件客户端
+ * @author yikai.hu
+ * @version $Id: AlipayMobilePublicMultiMediaClient.php, v 0.1 Aug 15, 2014 10:19:01 AM yikai.hu Exp $
+ */
+
+include("AlipayMobilePublicMultiMediaExecute.php");
+
+class AlipayMobilePublicMultiMediaClient
+{
+    private $DEFAULT_CHARSET = 'UTF-8';
+    private $METHOD_POST = "POST";
+    private $METHOD_GET = "GET";
+    private $SIGN = 'sign'; //get name
+
+    private $timeout = 10;// 超时时间
+    private $serverUrl;
+    private $appId;
+    private $privateKey;
+    private $prodCode;
+    private $format = 'json'; //todo
+    private $sign_type = 'RSA'; //todo
+
+    private $charset;
+    private $apiVersion = "1.0";
+    private $apiMethodName = "alipay.mobile.public.multimedia.download";
+    private $media_id = "L21pZnMvVDFQV3hYWGJKWFhYYUNucHJYP3Q9YW13ZiZ4c2lnPTU0MzRhYjg1ZTZjNWJmZTMxZGJiNjIzNDdjMzFkNzkw575";
+    //此处写死的,实际开发中,请传入
+
+    private $connectTimeout = 3000;
+    private $readTimeout = 15000;
+
+    function __construct($serverUrl = '', $appId = '', $partner_private_key = '', $format = '', $charset = 'GBK')
+    {
+        $this->serverUrl = $serverUrl;
+        $this->appId = $appId;
+        $this->privateKey = $partner_private_key;
+        $this->format = $format;
+        $this->charset = $charset;
+    }
+
+    /**
+     * getContents 获取网址内容
+     * @param $request
+     * @return text | bin
+     */
+    public function getContents()
+    {
+        $datas = array(
+            "app_id" => $this->appId,
+            "method" => $this->METHOD_POST,
+            "sign_type" => $this->sign_type,
+            "version" => $this->apiVersion,
+            "timestamp" => date('Y-m-d H:i:s'),//yyyy-MM-dd HH:mm:ss
+            "biz_content" => '{"mediaId":"' . $this->media_id . '"}',
+            "charset" => $this->charset
+        );
+
+        //要提交的数据
+        $data_sign = $this->buildGetUrl($datas);
+
+        $post_data = $data_sign;
+        //初始化 curl
+        $ch = curl_init();
+        //设置目标服务器
+        curl_setopt($ch, CURLOPT_URL, $this->serverUrl);
+        curl_setopt($ch, CURLOPT_HEADER, TRUE);
+        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
+        //超时时间
+        curl_setopt($ch, CURLOPT_TIMEOUT, $this->timeout);
+
+        if ($this->METHOD_POST == 'POST') {
+            // post数据
+            curl_setopt($ch, CURLOPT_POST, 1);
+            // post的变量
+            curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data);
+        }
+
+
+        $output = curl_exec($ch);
+        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
+        curl_close($ch);
+
+        echo $output;
+
+        $datas = explode("\r\n\r\n", $output, 2);
+        $header = $datas[0];
+
+        if ($httpCode == '200') {
+            $body = $datas[1];
+        } else {
+            $body = '';
+
+        }
+
+        return $this->execute($header, $body, $httpCode);
+    }
+
+    /**
+     *
+     * @param $request
+     * @return text | bin
+     */
+    public function execute($header = '', $body = '', $httpCode = '')
+    {
+        $exe = new AlipayMobilePublicMultiMediaExecute($header, $body, $httpCode);
+        return $exe;
+    }
+
+    public function buildGetUrl($query = array())
+    {
+        if (!is_array($query)) {
+            //exit;
+        }
+        //排序参数,
+        $data = $this->buildQuery($query);
+
+        // 私钥密码
+        $passphrase = '';
+        $key_width = 64;
+
+        //私钥
+        $privateKey = $this->privateKey;
+        $p_key = array();
+        //如果私钥是 1行
+        if (!stripos($privateKey, "\n")) {
+            $i = 0;
+            while ($key_str = substr($privateKey, $i * $key_width, $key_width)) {
+                $p_key[] = $key_str;
+                $i++;
+            }
+        } else {
+            //echo '一行?';
+        }
+        $privateKey = "-----BEGIN RSA PRIVATE KEY-----\n" . implode("\n", $p_key);
+        $privateKey = $privateKey . "\n-----END RSA PRIVATE KEY-----";
+
+        //私钥
+        $private_id = openssl_pkey_get_private($privateKey, $passphrase);
+
+        // 签名
+        $signature = '';
+
+        if ("RSA2" == $this->sign_type) {
+
+            openssl_sign($data, $signature, $private_id, OPENSSL_ALGO_SHA256);
+        } else {
+
+            openssl_sign($data, $signature, $private_id, OPENSSL_ALGO_SHA1);
+        }
+
+        openssl_free_key($private_id);
+
+        //加密后的内容通常含有特殊字符,需要编码转换下
+        $signature = base64_encode($signature);
+
+        $signature = urlencode($signature);
+
+        //$signature = 'XjUN6YM1Mc9HXebKMv7GTLy7gmyhktyOgKk2/Jf+cz4DtP6udkzTdpkjW2j/Z4ZSD7xD6CNYI1Spz4yS93HPT0a5X9LgFWYY8SaADqe+ArXg+FBSiTwUz49SE//Xd9+LEiIRsSFkbpkuiGoO6mqJmB7vXjlD5lx6qCM3nb41wb8=';
+
+        $out = $data . '&' . $this->SIGN . '=' . $signature;
+
+        return $out;
+    }
+
+    /*
+     * 查询参数排序 a-z
+     * */
+    public function buildQuery($query)
+    {
+        if (!$query) {
+            return null;
+        }
+        //将要 参数 排序
+        ksort($query);
+
+        //重新组装参数
+        $params = array();
+        foreach ($query as $key => $value) {
+            $params[] = $key . '=' . $value;
+        }
+        $data = implode('&', $params);
+        return $data;
+    }
+
+}

+ 115 - 0
app/Libs/Pays/AliPaySdk/aop/AlipayMobilePublicMultiMediaExecute.php

@@ -0,0 +1,115 @@
+<?php
+
+/**
+ * 多媒体文件客户端
+ * @author yuanwai.wang
+ * @version $Id: AlipayMobilePublicMultiMediaExecute.php, v 0.1 Aug 15, 2014 10:19:01 AM yuanwai.wang Exp $
+ */
+
+//namespace alipay\api ;
+
+
+class AlipayMobilePublicMultiMediaExecute
+{
+
+    private $code = 200;
+    private $msg = '';
+    private $body = '';
+    private $params = '';
+
+    private $fileSuffix = array(
+        "image/jpeg" => 'jpg', //+
+        "text/plain" => 'text'
+    );
+
+    /*
+     * @$header : 头部
+     * */
+    function __construct($header, $body, $httpCode)
+    {
+        $this->code = $httpCode;
+        $this->msg = '';
+        $this->params = $header;
+        $this->body = $body;
+    }
+
+    /**
+     *
+     * @return text | bin
+     */
+    public function getCode()
+    {
+        return $this->code;
+    }
+
+    /**
+     *
+     * @return text | bin
+     */
+    public function getMsg()
+    {
+        return $this->msg;
+    }
+
+    /**
+     *
+     * @return text | bin
+     */
+    public function getType()
+    {
+        $subject = $this->params;
+        $pattern = '/Content\-Type:([^;]+)/';
+        preg_match($pattern, $subject, $matches);
+        if ($matches) {
+            $type = $matches[1];
+        } else {
+            $type = 'application/download';
+        }
+
+        return str_replace(' ', '', $type);
+    }
+
+    /**
+     *
+     * @return text | bin
+     */
+    public function getContentLength()
+    {
+        $subject = $this->params;
+        $pattern = '/Content-Length:\s*([^\n]+)/';
+        preg_match($pattern, $subject, $matches);
+        return (int)(isset($matches[1]) ? $matches[1] : '');
+    }
+
+
+    public function getFileSuffix($fileType)
+    {
+        $type = isset($this->fileSuffix[$fileType]) ? $this->fileSuffix[$fileType] : 'text/plain';
+        if (!$type) {
+            $type = 'json';
+        }
+        return $type;
+    }
+
+
+    /**
+     *
+     * @return text | bin
+     */
+    public function getBody()
+    {
+        //header('Content-type: image/jpeg');
+        return $this->body;
+    }
+
+    /**
+     * 获取参数
+     * @return text | bin
+     */
+    public function getParams()
+    {
+        return $this->params;
+    }
+
+
+}

File diff suppressed because it is too large
+ 1303 - 0
app/Libs/Pays/AliPaySdk/aop/AopCertClient.php


+ 527 - 0
app/Libs/Pays/AliPaySdk/aop/AopCertification.php

@@ -0,0 +1,527 @@
+<?php
+
+/**
+ * 验证支付宝公钥证书是否可信
+ * @param $alipayCert 支付宝公钥证书
+ * @param $rootCert 支付宝根证书
+ */
+function isTrusted($alipayCert, $rootCert)
+{
+    $alipayCerts = readPemCertChain($alipayCert);
+    $rootCerts = readPemCertChain($rootCert);
+    if (verifyCertChain($alipayCerts, $rootCerts)) {
+        return verifySignature($alipayCert, $rootCert);
+    } else {
+        return false;
+    }
+
+}
+
+function verifySignature($alipayCert, $rootCert)
+{
+    $alipayCertArray = explode("-----END CERTIFICATE-----", $alipayCert);
+    $rootCertArray = explode("-----END CERTIFICATE-----", $rootCert);
+    $length = count($rootCertArray) - 1;
+    $checkSign = isCertSigner($alipayCertArray[0] . "-----END CERTIFICATE-----", $alipayCertArray[1] . "-----END CERTIFICATE-----");
+    if (!$checkSign) {
+        $checkSign = isCertSigner($alipayCertArray[1] . "-----END CERTIFICATE-----", $alipayCertArray[0] . "-----END CERTIFICATE-----");
+        if ($checkSign) {
+            $issuer = openssl_x509_parse($alipayCertArray[0] . "-----END CERTIFICATE-----")['issuer'];
+            for ($i = 0; $i < $length; $i++) {
+                $subject = openssl_x509_parse($rootCertArray[$i] . "-----END CERTIFICATE-----")['subject'];
+                if ($issuer == $subject) {
+                    isCertSigner($alipayCertArray[0] . "-----END CERTIFICATE-----", $rootCertArray[$i] . $rootCertArray);
+                    return $checkSign;
+                }
+            }
+        } else {
+            return $checkSign;
+        }
+    } else {
+        $issuer = openssl_x509_parse($alipayCertArray[1] . "-----END CERTIFICATE-----")['issuer'];
+        for ($i = 0; $i < $length; $i++) {
+            $subject = openssl_x509_parse($rootCertArray[$i] . "-----END CERTIFICATE-----")['subject'];
+            if ($issuer == $subject) {
+                $checkSign = isCertSigner($alipayCertArray[1] . "-----END CERTIFICATE-----", $rootCertArray[$i] . "-----END CERTIFICATE-----");
+                return $checkSign;
+            }
+        }
+        return $checkSign;
+    }
+}
+
+function readPemCertChain($cert)
+{
+    $array = explode("-----END CERTIFICATE-----", $cert);
+    $certs[] = null;
+    for ($i = 0; $i < count($array) - 1; $i++) {
+        $certs[$i] = openssl_x509_parse($array[$i] . "-----END CERTIFICATE-----");
+    }
+    return $certs;
+}
+
+function verifyCert($prev, $rootCerts)
+{
+    $nowTime = time();
+    if ($nowTime < $prev['validFrom_time_t']) {
+        echo "证书未激活";
+        return false;
+    }
+    if ($nowTime > $prev['validTo_time_t']) {
+        echo "证书已经过期";
+        return false;
+    }
+    $subjectMap = null;
+    for ($i = 0; $i < count($rootCerts); $i++) {
+        $subjectDN = array2string($rootCerts[$i]['subject']);
+        $subjectMap[$subjectDN] = $rootCerts[$i];
+    }
+    $issuerDN = array2string(($prev['issuer']));
+    if (!array_key_exists($issuerDN, $subjectMap)) {
+        echo "证书链验证失败";
+        return false;
+    }
+    return true;
+}
+
+/**
+ * 验证证书链是否是信任证书库中证书签发的
+ * @param $alipayCerts 目标验证证书列表
+ * @param $rootCerts 可信根证书列表
+ */
+function verifyCertChain($alipayCerts, $rootCerts)
+{
+    $sorted = sortByDn($alipayCerts);
+    if (!$sorted) {
+        echo "证书链验证失败:不是完整的证书链";
+        return false;
+    }
+    //先验证第一个证书是不是信任库中证书签发的
+    $prev = $alipayCerts[0];
+    $firstOK = verifyCert($prev, $rootCerts);
+    $length = count($alipayCerts);
+    if (!$firstOK || $length == 1) {
+        return $firstOK;
+    }
+
+    $nowTime = time();
+    //验证证书链
+    for ($i = 1; $i < $length; $i++) {
+        $cert = $alipayCerts[$i];
+        if ($nowTime < $cert['validFrom_time_t']) {
+            echo "证书未激活";
+            return false;
+        }
+        if ($nowTime > $cert['validTo_time_t']) {
+            echo "证书已经过期";
+            return false;
+        }
+    }
+    return true;
+}
+
+/**
+ * 将证书链按照完整的签发顺序进行排序,排序后证书链为:[issuerA, subjectA]-[issuerA, subjectB]-[issuerB, subjectC]-[issuerC, subjectD]...
+ * @param $certs 证书链
+ */
+function sortByDn(&$certs)
+{
+    //是否包含自签名证书
+    $hasSelfSignedCert = false;
+    $subjectMap = null;
+    $issuerMap = null;
+    for ($i = 0; $i < count($certs); $i++) {
+        if (isSelfSigned($certs[$i])) {
+            if ($hasSelfSignedCert) {
+                return false;
+            }
+            $hasSelfSignedCert = true;
+        }
+        $subjectDN = array2string($certs[$i]['subject']);
+        $issuerDN = array2string(($certs[$i]['issuer']));
+        $subjectMap[$subjectDN] = $certs[$i];
+        $issuerMap[$issuerDN] = $certs[$i];
+    }
+    $certChain = null;
+    addressingUp($subjectMap, $certChain, $certs[0]);
+    addressingDown($issuerMap, $certChain, $certs[0]);
+
+    //说明证书链不完整
+    if (count($certs) != count($certChain)) {
+        return false;
+    }
+    //将证书链复制到原先的数据
+    for ($i = 0; $i < count($certs); $i++) {
+        $certs[$i] = $certChain[count($certs) - $i - 1];
+    }
+    return true;
+}
+
+/**
+ * 验证证书是否是自签发的
+ * @param $cert 目标证书
+ */
+function isSelfSigned($cert)
+{
+    $subjectDN = array2string($cert['subject']);
+    $issuerDN = array2string($cert['issuer']);
+    return ($subjectDN == $issuerDN);
+}
+
+
+function array2string($array)
+{
+    $string = [];
+    if ($array && is_array($array)) {
+        foreach ($array as $key => $value) {
+            $string[] = $key . '=' . $value;
+        }
+    }
+    return implode(',', $string);
+}
+
+/**
+ * 向上构造证书链
+ * @param $subjectMap 主题和证书的映射
+ * @param $certChain 证书链
+ * @param $current 当前需要插入证书链的证书,include
+ */
+function addressingUp($subjectMap, &$certChain, $current)
+{
+    $certChain[] = $current;
+    if (isSelfSigned($current)) {
+        return;
+    }
+    $issuerDN = array2string($current['issuer']);
+
+    if (!array_key_exists($issuerDN, $subjectMap)) {
+        return;
+    }
+    addressingUp($subjectMap, $certChain, $subjectMap[$issuerDN]);
+}
+
+/**
+ * 向下构造证书链
+ * @param $issuerMap 签发者和证书的映射
+ * @param $certChain 证书链
+ * @param $current 当前需要插入证书链的证书,exclude
+ */
+function addressingDown($issuerMap, &$certChain, $current)
+{
+    $subjectDN = array2string($current['subject']);
+    if (!array_key_exists($subjectDN, $issuerMap)) {
+        return $certChain;
+    }
+    $certChain[] = $issuerMap[$subjectDN];
+    addressingDown($issuerMap, $certChain, $issuerMap[$subjectDN]);
+}
+
+
+/**
+ * Extract signature from der encoded cert.
+ * Expects x509 der encoded certificate consisting of a section container
+ * containing 2 sections and a bitstream.  The bitstream contains the
+ * original encrypted signature, encrypted by the public key of the issuing
+ * signer.
+ * @param string $der
+ * @return string on success
+ * @return bool false on failures
+ */
+function extractSignature($der = false)
+{
+    if (strlen($der) < 5) {
+        return false;
+    }
+    // skip container sequence
+    $der = substr($der, 4);
+    // now burn through two sequences and the return the final bitstream
+    while (strlen($der) > 1) {
+        $class = ord($der[0]);
+        $classHex = dechex($class);
+        switch ($class) {
+            // BITSTREAM
+            case 0x03:
+                $len = ord($der[1]);
+                $bytes = 0;
+                if ($len & 0x80) {
+                    $bytes = $len & 0x0f;
+                    $len = 0;
+                    for ($i = 0; $i < $bytes; $i++) {
+                        $len = ($len << 8) | ord($der[$i + 2]);
+                    }
+                }
+                return substr($der, 3 + $bytes, $len);
+                break;
+            // SEQUENCE
+            case 0x30:
+                $len = ord($der[1]);
+                $bytes = 0;
+                if ($len & 0x80) {
+                    $bytes = $len & 0x0f;
+                    $len = 0;
+                    for ($i = 0; $i < $bytes; $i++) {
+                        $len = ($len << 8) | ord($der[$i + 2]);
+                    }
+                }
+                $contents = substr($der, 2 + $bytes, $len);
+                $der = substr($der, 2 + $bytes + $len);
+                break;
+            default:
+                return false;
+                break;
+        }
+    }
+    return false;
+}
+
+/**
+ * Get signature algorithm oid from der encoded signature data.
+ * Expects decrypted signature data from a certificate in der format.
+ * This ASN1 data should contain the following structure:
+ * SEQUENCE
+ *    SEQUENCE
+ *       OID    (signature algorithm)
+ *       NULL
+ * OCTET STRING (signature hash)
+ * @return bool false on failures
+ * @return string oid
+ */
+function getSignatureAlgorithmOid($der = null)
+{
+    // Validate this is the der we need...
+    if (!is_string($der) or strlen($der) < 5) {
+        return false;
+    }
+    $bit_seq1 = 0;
+    $bit_seq2 = 2;
+    $bit_oid = 4;
+    if (ord($der[$bit_seq1]) !== 0x30) {
+        die('Invalid DER passed to getSignatureAlgorithmOid()');
+    }
+    if (ord($der[$bit_seq2]) !== 0x30) {
+        die('Invalid DER passed to getSignatureAlgorithmOid()');
+    }
+    if (ord($der[$bit_oid]) !== 0x06) {
+        die('Invalid DER passed to getSignatureAlgorithmOid');
+    }
+    // strip out what we don't need and get the oid
+    $der = substr($der, $bit_oid);
+    // Get the oid
+    $len = ord($der[1]);
+    $bytes = 0;
+    if ($len & 0x80) {
+        $bytes = $len & 0x0f;
+        $len = 0;
+        for ($i = 0; $i < $bytes; $i++) {
+            $len = ($len << 8) | ord($der[$i + 2]);
+        }
+    }
+    $oid_data = substr($der, 2 + $bytes, $len);
+    // Unpack the OID
+    $oid = floor(ord($oid_data[0]) / 40);
+    $oid .= '.' . ord($oid_data[0]) % 40;
+    $value = 0;
+    $i = 1;
+    while ($i < strlen($oid_data)) {
+        $value = $value << 7;
+        $value = $value | (ord($oid_data[$i]) & 0x7f);
+        if (!(ord($oid_data[$i]) & 0x80)) {
+            $oid .= '.' . $value;
+            $value = 0;
+        }
+        $i++;
+    }
+    return $oid;
+}
+
+/**
+ * Get signature hash from der encoded signature data.
+ * Expects decrypted signature data from a certificate in der format.
+ * This ASN1 data should contain the following structure:
+ * SEQUENCE
+ *    SEQUENCE
+ *       OID    (signature algorithm)
+ *       NULL
+ * OCTET STRING (signature hash)
+ * @return bool false on failures
+ * @return string hash
+ */
+function getSignatureHash($der = null)
+{
+    // Validate this is the der we need...
+    if (!is_string($der) or strlen($der) < 5) {
+        return false;
+    }
+    if (ord($der[0]) !== 0x30) {
+        die('Invalid DER passed to getSignatureHash()');
+    }
+    // strip out the container sequence
+    $der = substr($der, 2);
+    if (ord($der[0]) !== 0x30) {
+        die('Invalid DER passed to getSignatureHash()');
+    }
+    // Get the length of the first sequence so we can strip it out.
+    $len = ord($der[1]);
+    $bytes = 0;
+    if ($len & 0x80) {
+        $bytes = $len & 0x0f;
+        $len = 0;
+        for ($i = 0; $i < $bytes; $i++) {
+            $len = ($len << 8) | ord($der[$i + 2]);
+        }
+    }
+    $der = substr($der, 2 + $bytes + $len);
+    // Now we should have an octet string
+    if (ord($der[0]) !== 0x04) {
+        die('Invalid DER passed to getSignatureHash()');
+    }
+    $len = ord($der[1]);
+    $bytes = 0;
+    if ($len & 0x80) {
+        $bytes = $len & 0x0f;
+        $len = 0;
+        for ($i = 0; $i < $bytes; $i++) {
+            $len = ($len << 8) | ord($der[$i + 2]);
+        }
+    }
+    return bin2hex(substr($der, 2 + $bytes, $len));
+}
+
+/**
+ * Determine if one cert was used to sign another
+ * Note that more than one CA cert can give a positive result, some certs
+ * re-issue signing certs after having only changed the expiration dates.
+ * @param string $cert - PEM encoded cert
+ * @param string $caCert - PEM encoded cert that possibly signed $cert
+ * @return bool
+ */
+function isCertSigner($certPem = null, $caCertPem = null)
+{
+    if (!function_exists('openssl_pkey_get_public')) {
+        die('Need the openssl_pkey_get_public() function.');
+    }
+    if (!function_exists('openssl_public_decrypt')) {
+        die('Need the openssl_public_decrypt() function.');
+    }
+    if (!function_exists('hash')) {
+        die('Need the php hash() function.');
+    }
+    if (empty($certPem) or empty($caCertPem)) {
+        return false;
+    }
+    // Convert the cert to der for feeding to extractSignature.
+    $certDer = pemToDer($certPem);
+    if (!is_string($certDer)) {
+        die('invalid certPem');
+    }
+    // Grab the encrypted signature from the der encoded cert.
+    $encryptedSig = extractSignature($certDer);
+    if (!is_string($encryptedSig)) {
+        die('Failed to extract encrypted signature from certPem.');
+    }
+    // Extract the public key from the ca cert, which is what has
+    // been used to encrypt the signature in the cert.
+    $pubKey = openssl_pkey_get_public($caCertPem);
+    if ($pubKey === false) {
+        die('Failed to extract the public key from the ca cert.');
+    }
+    // Attempt to decrypt the encrypted signature using the CA's public
+    // key, returning the decrypted signature in $decryptedSig.  If
+    // it can't be decrypted, this ca was not used to sign it for sure...
+    $rc = openssl_public_decrypt($encryptedSig, $decryptedSig, $pubKey);
+    if ($rc === false) {
+        return false;
+    }
+    // We now have the decrypted signature, which is der encoded
+    // asn1 data containing the signature algorithm and signature hash.
+    // Now we need what was originally hashed by the issuer, which is
+    // the original DER encoded certificate without the issuer and
+    // signature information.
+    $origCert = stripSignerAsn($certDer);
+    if ($origCert === false) {
+        die('Failed to extract unsigned cert.');
+    }
+    // Get the oid of the signature hash algorithm, which is required
+    // to generate our own hash of the original cert.  This hash is
+    // what will be compared to the issuers hash.
+    $oid = getSignatureAlgorithmOid($decryptedSig);
+    if ($oid === false) {
+        die('Failed to determine the signature algorithm.');
+    }
+    switch ($oid) {
+        case '1.2.840.113549.2.2':
+            $algo = 'md2';
+            break;
+        case '1.2.840.113549.2.4':
+            $algo = 'md4';
+            break;
+        case '1.2.840.113549.2.5':
+            $algo = 'md5';
+            break;
+        case '1.3.14.3.2.18':
+            $algo = 'sha';
+            break;
+        case '1.3.14.3.2.26':
+            $algo = 'sha1';
+            break;
+        case '2.16.840.1.101.3.4.2.1':
+            $algo = 'sha256';
+            break;
+        case '2.16.840.1.101.3.4.2.2':
+            $algo = 'sha384';
+            break;
+        case '2.16.840.1.101.3.4.2.3':
+            $algo = 'sha512';
+            break;
+        default:
+            die('Unknown signature hash algorithm oid: ' . $oid);
+            break;
+    }
+    // Get the issuer generated hash from the decrypted signature.
+    $decryptedHash = getSignatureHash($decryptedSig);
+    // Ok, hash the original unsigned cert with the same algorithm
+    // and if it matches $decryptedHash we have a winner.
+    $certHash = hash($algo, $origCert);
+    return ($decryptedHash === $certHash);
+}
+
+/**
+ * Convert pem encoded certificate to DER encoding
+ * @return string $derEncoded on success
+ * @return bool false on failures
+ */
+function pemToDer($pem = null)
+{
+    if (!is_string($pem)) {
+        return false;
+    }
+    $cert_split = preg_split('/(-----((BEGIN)|(END)) CERTIFICATE-----)/', $pem);
+    if (!isset($cert_split[1])) {
+        return false;
+    }
+    return base64_decode($cert_split[1]);
+}
+
+/**
+ * Obtain der cert with issuer and signature sections stripped.
+ * @param string $der - der encoded certificate
+ * @return string $der on success
+ * @return bool false on failures.
+ */
+function stripSignerAsn($der = null)
+{
+    if (!is_string($der) or strlen($der) < 8) {
+        return false;
+    }
+    $bit = 4;
+    $len = ord($der[($bit + 1)]);
+    $bytes = 0;
+    if ($len & 0x80) {
+        $bytes = $len & 0x0f;
+        $len = 0;
+        for ($i = 0; $i < $bytes; $i++) {
+            $len = ($len << 8) | ord($der[$bit + $i + 2]);
+        }
+    }
+    return substr($der, 4, $len + 4);
+}

File diff suppressed because it is too large
+ 1334 - 0
app/Libs/Pays/AliPaySdk/aop/AopClient.php


+ 78 - 0
app/Libs/Pays/AliPaySdk/aop/AopEncrypt.php

@@ -0,0 +1,78 @@
+<?php
+/**
+ *   加密工具类
+ *
+ * User: jiehua
+ * Date: 16/3/30
+ * Time: 下午3:25
+ */
+
+
+/**
+ * 加密方法
+ * @param string $str
+ * @return string
+ */
+function encrypt_new($str, $screct_key)
+{
+    //AES, 128 模式加密数据 CBC
+    $screct_key = base64_decode($screct_key);
+    $str = trim($str);
+    $str = addPKCS7Padding($str);
+
+    //设置全0的IV
+
+    $iv = str_repeat("\0", 16);
+    $encrypt_str = openssl_encrypt($str, 'aes-128-cbc', $screct_key, OPENSSL_NO_PADDING, $iv);
+    return base64_encode($encrypt_str);
+}
+
+/**
+ * 解密方法
+ * @param string $str
+ * @return string
+ */
+function decrypt_new($str, $screct_key)
+{
+    //AES, 128 模式加密数据 CBC
+    $str = base64_decode($str);
+    $screct_key = base64_decode($screct_key);
+
+    //设置全0的IV
+    $iv = str_repeat("\0", 16);
+    $decrypt_str = openssl_decrypt($str, 'aes-128-cbc', $screct_key, OPENSSL_NO_PADDING, $iv);
+    $decrypt_str = stripPKSC7Padding($decrypt_str);
+    return $decrypt_str;
+}
+
+/**
+ * 填充算法
+ * @param string $source
+ * @return string
+ */
+function addPKCS7Padding($source)
+{
+    $source = trim($source);
+    $block = 16;
+
+    $pad = $block - (strlen($source) % $block);
+    if ($pad <= $block) {
+        $char = chr($pad);
+        $source .= str_repeat($char, $pad);
+    }
+    return $source;
+}
+
+/**
+ * 移去填充算法
+ * @param string $source
+ * @return string
+ */
+function stripPKSC7Padding($source)
+{
+    $char = substr($source, -1);
+    $num = ord($char);
+    if ($num == 62) return $source;
+    $source = substr($source, 0, -$num);
+    return $source;
+}

+ 18 - 0
app/Libs/Pays/AliPaySdk/aop/EncryptParseItem.php

@@ -0,0 +1,18 @@
+<?php
+/**
+ *  TODO 补充说明
+ *
+ * User: jiehua
+ * Date: 16/3/30
+ * Time: 下午8:55
+ */
+
+class EncryptParseItem
+{
+    public $startIndex;
+
+    public $endIndex;
+
+    public $encryptContent;
+
+} 

+ 16 - 0
app/Libs/Pays/AliPaySdk/aop/EncryptResponseData.php

@@ -0,0 +1,16 @@
+<?php
+/**
+ *  TODO 补充说明
+ *
+ * User: jiehua
+ * Date: 16/3/30
+ * Time: 下午8:51
+ */
+
+class EncryptResponseData
+{
+    public $realContent;
+
+    public $returnContent;
+
+} 

+ 0 - 0
app/Libs/Pays/AliPaySdk/aop/NewAopEncrypt.php


Some files were not shown because too many files changed in this diff