Просмотр исходного кода

【程序目录】更新优惠券可以领取多张,可以设置多个分类,增加飞鹅云小票打印,优化定时任务

吴昊天 3 лет назад
Родитель
Сommit
bc6134c6ad
40 измененных файлов с 1482 добавлено и 188 удалено
  1. 8 1
      crmeb/app/adminapi/controller/v1/marketing/StoreCouponIssue.php
  2. 9 3
      crmeb/app/adminapi/controller/v1/order/StoreOrder.php
  3. 92 0
      crmeb/app/adminapi/controller/v1/system/SystemTimer.php
  4. 2 4
      crmeb/app/adminapi/controller/v1/user/UserLevel.php
  5. 15 0
      crmeb/app/adminapi/route/system.php
  6. 1 1
      crmeb/app/api/controller/v1/store/StoreCouponsController.php
  7. 2 1
      crmeb/app/api/controller/v1/store/StoreProductController.php
  8. 6 2
      crmeb/app/dao/activity/coupon/StoreCouponIssueDao.php
  9. 11 5
      crmeb/app/dao/product/product/StoreCategoryDao.php
  10. 6 0
      crmeb/app/dao/product/product/StoreProductDao.php
  11. 18 0
      crmeb/app/dao/system/timer/SystemTimerDao.php
  12. 1 3
      crmeb/app/event.php
  13. 192 0
      crmeb/app/listener/timer/SystemTimer.php
  14. 1 1
      crmeb/app/model/activity/coupon/StoreCouponIssue.php
  15. 23 0
      crmeb/app/model/system/timer/SystemTimer.php
  16. 66 53
      crmeb/app/services/activity/coupon/StoreCouponIssueServices.php
  17. 5 1
      crmeb/app/services/activity/coupon/StoreCouponIssueUserServices.php
  18. 4 15
      crmeb/app/services/activity/coupon/StoreCouponUserServices.php
  19. 22 11
      crmeb/app/services/message/NoticeService.php
  20. 0 6
      crmeb/app/services/order/StoreCartServices.php
  21. 15 6
      crmeb/app/services/order/StoreOrderComputedServices.php
  22. 4 3
      crmeb/app/services/order/StoreOrderCreateServices.php
  23. 27 10
      crmeb/app/services/order/StoreOrderServices.php
  24. 187 0
      crmeb/app/services/system/timer/SystemTimerServices.php
  25. 15 17
      crmeb/app/services/user/UserLevelServices.php
  26. 5 29
      crmeb/crmeb/command/Timer.php
  27. 1 1
      crmeb/crmeb/services/pay/Pay.php
  28. 22 2
      crmeb/crmeb/services/printer/AccessToken.php
  29. 161 0
      crmeb/crmeb/services/printer/storage/FeiEYun.php
  30. 53 6
      crmeb/public/install/crmeb.sql
  31. 1 0
      crmeb/vendor/composer/autoload_psr4.php
  32. 5 0
      crmeb/vendor/composer/autoload_static.php
  33. 59 0
      crmeb/vendor/composer/installed.json
  34. 15 6
      crmeb/vendor/composer/installed.php
  35. 1 1
      crmeb/vendor/services.php
  36. 29 0
      crmeb/vendor/workerman/crontab/README.md
  37. 34 0
      crmeb/vendor/workerman/crontab/composer.json
  38. 16 0
      crmeb/vendor/workerman/crontab/example/test.php
  39. 177 0
      crmeb/vendor/workerman/crontab/src/Crontab.php
  40. 171 0
      crmeb/vendor/workerman/crontab/src/Parser.php

+ 8 - 1
crmeb/app/adminapi/controller/v1/marketing/StoreCouponIssue.php

@@ -67,10 +67,11 @@ class StoreCouponIssue extends AuthController
             ['is_permanent', 0],
             ['total_count', 0],
             ['product_id', ''],
-            ['category_id', 0],
+            ['category_id', []],
             ['type', 0],
             ['sort', 0],
             ['status', 0],
+            ['receive_limit', 1],
         ]);
         $res = $this->services->saveCoupon($data);
         if ($res) return app('json')->success(100000);
@@ -110,6 +111,12 @@ class StoreCouponIssue extends AuthController
                 ];
             }
         }
+        if ($info['category_id'] != '') {
+            $info['category_id'] = explode(',', $info['category_id']);
+            foreach ($info['category_id'] as &$category_id) {
+                $category_id = (int)$category_id;
+            }
+        }
         return app('json')->success($info);
     }
 

+ 9 - 3
crmeb/app/adminapi/controller/v1/order/StoreOrder.php

@@ -479,9 +479,15 @@ class StoreOrder extends AuthController
 
         $orderInfo = $this->services->tidyOrder($orderInfo->toArray(), true, true);
         //核算优惠金额
-        $vipTruePrice = array_column($orderInfo['cartInfo'], 'vip_sum_truePrice');
-        $vipTruePrice = round(array_sum($vipTruePrice), 2);
-        $orderInfo['vip_true_price'] = $vipTruePrice ?: 0;
+        $vipTruePrice = $levelPrice = $memberPrice = 0;
+        foreach ($orderInfo['cartInfo'] as $cart) {
+            $vipTruePrice = bcadd((string)$vipTruePrice, (string)$cart['vip_sum_truePrice'], 2);
+            if ($cart['price_type'] == 'member') $memberPrice = bcadd((string)$memberPrice, (string)$cart['vip_sum_truePrice'], 2);
+            if ($cart['price_type'] == 'level') $levelPrice = bcadd((string)$levelPrice, (string)$cart['vip_sum_truePrice'], 2);
+        }
+        $orderInfo['vip_true_price'] = $vipTruePrice;
+        $orderInfo['levelPrice'] = $levelPrice;
+        $orderInfo['memberPrice'] = $memberPrice;
         $orderInfo['total_price'] = bcadd($orderInfo['total_price'], $orderInfo['vip_true_price'], 2);
         if ($orderInfo['store_id'] && $orderInfo['shipping_type'] == 2) {
             /** @var  $storeServices */

+ 92 - 0
crmeb/app/adminapi/controller/v1/system/SystemTimer.php

@@ -0,0 +1,92 @@
+<?php
+
+namespace app\adminapi\controller\v1\system;
+
+use app\adminapi\controller\AuthController;
+use app\services\system\timer\SystemTimerServices;
+use think\facade\App;
+
+class SystemTimer extends AuthController
+{
+    public function __construct(App $app, SystemTimerServices $services)
+    {
+        parent::__construct($app);
+        $this->services = $services;
+    }
+
+    /**
+     * 获取定时任务列表
+     * @return mixed
+     */
+    public function getTimerList()
+    {
+        $where = ['is_del' => 0];
+        return app('json')->success($this->services->getTimerList($where));
+    }
+
+    /**
+     * 获取定时任务详情
+     * @param $id
+     * @return mixed
+     */
+    public function getTimerInfo($id)
+    {
+        return app('json')->success($this->services->getTimerInfo($id));
+    }
+
+    /**
+     * 获取定时任务类型
+     * @return mixed
+     */
+    public function getMarkList()
+    {
+        return app('json')->success($this->services->getMarkList());
+    }
+
+    /**
+     * 保存定时任务
+     * @return mixed
+     */
+    public function saveTimer()
+    {
+        $data = $this->request->postMore([
+            ['id', 0],
+            ['name', ''],
+            ['mark', ''],
+            ['content', ''],
+            ['type', 0],
+            ['is_open', 0],
+            ['week', 0],
+            ['day', 0],
+            ['hour', 0],
+            ['minute', 0],
+            ['second', 0],
+        ]);
+        $this->services->saveTimer($data);
+        return app('json')->success(100000);
+    }
+
+    /**
+     * 删除定时任务
+     * @param $id
+     * @return mixed
+     */
+    public function delTimer($id)
+    {
+        $this->services->delTimer($id);
+        return app('json')->success(100002);
+    }
+
+    /**
+     * 设置定时任务状态
+     * @param $id
+     * @param $is_open
+     * @return mixed
+     */
+    public function setTimerStatus($id, $is_open)
+    {
+        $this->services->setTimerStatus($id, $is_open);
+        return app('json')->success(100014);
+    }
+
+}

+ 2 - 4
crmeb/app/adminapi/controller/v1/user/UserLevel.php

@@ -63,18 +63,16 @@ class UserLevel extends AuthController
             ['icon', ''],
             ['image', ''],
             ['is_show', ''],
-            ['explain', ''],
             ['exp_num', 0]
         ]);
         if ($data['valid_date'] == 0) $data['is_forever'] = 1;//有效时间为0的时候就是永久
         if (!$data['name']) return app('json')->fail(400324);
         if (!$data['grade']) return app('json')->fail(400325);
-        if (!$data['explain']) return app('json')->fail(400326);
         if (!$data['icon']) return app('json')->fail(400327);
         if (!$data['image']) return app('json')->fail(400328);
         if (!$data['exp_num']) return app('json')->fail(400329);
-
-        return app('json')->success($this->services->save((int)$data['id'], $data));
+        $this->services->save((int)$data['id'], $data);
+        return app('json')->success(100000);
     }
 
     /*

+ 15 - 0
crmeb/app/adminapi/route/system.php

@@ -96,6 +96,21 @@ Route::group('system', function () {
     Route::get('upgrade_export/:id/:type', 'UpgradeController/export')->option(['real_name' => '导出备份']);
     //文件管理登录
     Route::post('file/login', 'v1.system.SystemFile/login')->option(['real_name' => '文件管理登录']);
+
+    /** 定时任务 */
+    //定时任务列表
+    Route::get('timer/list', 'v1.system.SystemTimer/getTimerList')->option(['real_name' => '定时任务列表']);
+    //定时任务类型
+    Route::get('timer/mark', 'v1.system.SystemTimer/getMarkList')->option(['real_name' => '定时任务类型']);
+    //定时任务详情
+    Route::get('timer/info/:id', 'v1.system.SystemTimer/getTimerInfo')->option(['real_name' => '定时任务详情']);
+    //定时任务添加编辑
+    Route::post('timer/save', 'v1.system.SystemTimer/saveTimer')->option(['real_name' => '定时任务添加编辑']);
+    //删除定时任务
+    Route::delete('timer/del/:id', 'v1.system.SystemTimer/delTimer')->option(['real_name' => '删除定时任务']);
+    //定时任务是否开启开关
+    Route::get('timer/set_open/:id/:is_open', 'v1.system.SystemTimer/setTimerStatus')->option(['real_name' => '定时任务是否开启开关']);
+
 })->middleware([
     \app\http\middleware\AllowOriginMiddleware::class,
     \app\adminapi\middleware\AdminAuthTokenMiddleware::class,

+ 1 - 1
crmeb/app/api/controller/v1/store/StoreCouponsController.php

@@ -60,7 +60,7 @@ class StoreCouponsController
 
         /** @var StoreCouponIssueServices $couponIssueService */
         $couponIssueService = app()->make(StoreCouponIssueServices::class);
-        $couponIssueService->issueUserCoupon($couponId, $request->user());
+        $couponIssueService->issueUserCoupon($couponId, $request->user(), true);
         return app('json')->success(410319);
     }
 

+ 2 - 1
crmeb/app/api/controller/v1/store/StoreProductController.php

@@ -55,7 +55,8 @@ class StoreProductController
             [['type', 0], 0],
             ['ids', ''],
             ['selectId', ''],
-            ['productId', '']
+            ['productId', ''],
+            ['coupon_category_id', '']
         ]);
         if ($where['selectId'] && (!$where['sid'] || !$where['cid'])) {
             if ($services->value(['id' => $where['selectId']], 'pid')) {

+ 6 - 2
crmeb/app/dao/activity/coupon/StoreCouponIssueDao.php

@@ -101,7 +101,9 @@ class StoreCouponIssueDao extends BaseDao
             }])
             ->where('type', $type)
             ->when($type == 1, function ($query) use ($typeId) {
-                if ($typeId) $query->where('category_id', 'in', $typeId);
+                if ($typeId) $query->where('id', 'in', function ($query) use ($typeId) {
+                    $query->name('store_coupon_product')->whereIn('category_id', $typeId)->field(['coupon_id'])->select();
+                })->whereOr('category_id', 'in', $typeId);
             })
             ->when($type == 2, function ($query) use ($typeId) {
                 if ($typeId) $query->whereFindinSet('product_id', $typeId);
@@ -138,7 +140,9 @@ class StoreCouponIssueDao extends BaseDao
                 $query->where('uid', $uid);
             }])->where(function ($query) use ($product_id, $cate_ids) {
                 if ($product_id != 0 && $cate_ids != []) {
-                    $query->whereFindinSet('product_id', $product_id)->whereOr('category_id', 'in', $cate_ids)->whereOr('type', 0);
+                    $query->whereFindinSet('product_id', $product_id)->whereOr('id', 'in', function ($query) use ($cate_ids) {
+                        $query->name('store_coupon_product')->whereIn('category_id', $cate_ids)->field(['coupon_id'])->select();
+                    })->whereOr('category_id', 'in', $cate_ids)->whereOr('type', 0);
                 }
             })->when($limit > 0, function ($query) use ($limit) {
                 $query->limit($limit);

+ 11 - 5
crmeb/app/dao/product/product/StoreCategoryDao.php

@@ -164,18 +164,24 @@ class StoreCategoryDao extends BaseDao
 
     /**
      * 通过分类id 获取(自己以及下级)的所有分类
-     * @param int $id
+     * @param $id
      * @param string $field
      * @return array
      * @throws \think\db\exception\DataNotFoundException
      * @throws \think\db\exception\DbException
      * @throws \think\db\exception\ModelNotFoundException
      */
-    public function getAllById(int $id, string $field = 'id')
+    public function getAllById($id, string $field = 'id')
     {
-        return $this->getModel()->where(function ($query) use ($id) {
-            $query->where('id', $id)->whereOr('pid', $id);
-        })->where('is_show', 1)->field($field)->select()->toArray();
+        if (is_array($id)) {
+            return $this->getModel()->where(function ($query) use ($id) {
+                $query->whereIn('id', $id)->whereOr('pid', 'in', $id);
+            })->where('is_show', 1)->field($field)->select()->toArray();
+        } else {
+            return $this->getModel()->where(function ($query) use ($id) {
+                $query->where('id', $id)->whereOr('pid', $id);
+            })->where('is_show', 1)->field($field)->select()->toArray();
+        }
     }
 
     /**

+ 6 - 0
crmeb/app/dao/product/product/StoreProductDao.php

@@ -124,6 +124,12 @@ class StoreProductDao extends BaseDao
                     $query->name('store_category')->where('pid', $where['cid'])->field('id')->select();
                 })->field('product_id')->select();
             });
+        })->when(isset($where['coupon_category_id']) && $where['coupon_category_id'] != '', function ($query) use ($where) {
+            $query->whereIn('id', function ($query) use ($where) {
+                $query->name('store_product_cate')->whereIn('cate_id', function ($query) use ($where) {
+                    $query->name('store_category')->whereIn('pid', $where['coupon_category_id'])->field('id')->select();
+                })->whereOr('cate_id', 'in', $where['coupon_category_id'])->field('product_id')->select();
+            });
         })->when(isset($where['ids']) && $where['ids'], function ($query) use ($where) {
             if ((isset($where['priceOrder']) && $where['priceOrder'] != '') || (isset($where['salesOrder']) && $where['salesOrder'] != '')) {
                 $query->whereIn('id', $where['ids']);

+ 18 - 0
crmeb/app/dao/system/timer/SystemTimerDao.php

@@ -0,0 +1,18 @@
+<?php
+
+namespace app\dao\system\timer;
+
+use app\dao\BaseDao;
+use app\model\system\timer\SystemTimer;
+
+class SystemTimerDao extends BaseDao
+{
+    /**
+     * 设置模型
+     * @return string
+     */
+    protected function setModel(): string
+    {
+        return SystemTimer::class;
+    }
+}

+ 1 - 3
crmeb/app/event.php

@@ -39,10 +39,8 @@ return [
         'user.userVisit' => [\app\listener\user\UserVisit::class], //用户访问事件
         'notice.notice' => [\app\listener\notice\Notice::class], //通知->消息事件
         'pay.notify' => [\app\listener\pay\Notify::class],//支付异步回调
+        'SystemTimer' => [\app\listener\timer\SystemTimer::class],//定时任务事件
     ],
-    'subscribe' => [
-        \app\subscribes\TaskSubscribe::class,//定时任务事件订阅类
-    ]
 ];
 
 

+ 192 - 0
crmeb/app/listener/timer/SystemTimer.php

@@ -0,0 +1,192 @@
+<?php
+
+namespace app\listener\timer;
+
+use app\services\activity\combination\StorePinkServices;
+use app\services\activity\live\LiveGoodsServices;
+use app\services\activity\live\LiveRoomServices;
+use app\services\agent\AgentManageServices;
+use app\services\order\StoreOrderServices;
+use app\services\order\StoreOrderTakeServices;
+use app\services\product\product\StoreProductServices;
+use app\services\system\attachment\SystemAttachmentServices;
+use app\services\system\timer\SystemTimerServices;
+use crmeb\interfaces\ListenerInterface;
+use think\facade\Log;
+use Workerman\Crontab\Crontab;
+
+class SystemTimer implements ListenerInterface
+{
+    public function handle($event): void
+    {
+
+        new Crontab('*/6 * * * * *', function () {
+            file_put_contents(runtime_path() . '.timer', time());
+        });
+
+        /** @var SystemTimerServices $systemTimerServices */
+        $systemTimerServices = app()->make(SystemTimerServices::class);
+        $list = $systemTimerServices->selectList(['is_del' => 0, 'is_open' => 1])->toArray();
+        foreach ($list as &$item) {
+            //获取定时任务时间字符串
+            $timeStr = $this->getTimerStr($item);
+            Log::error('mark:'.$item['mark']);
+            Log::error($timeStr);
+
+            if ($item['mark'] == 'order_cancel') {
+                new Crontab($timeStr, function () {
+                    Log::error('每隔30秒执行一次自动取消订单 '.date('Y-m-d H:i:s'));
+                    //未支付自动取消订单
+                    try {
+                        /** @var StoreOrderServices $orderServices */
+                        $orderServices = app()->make(StoreOrderServices::class);
+                        $orderServices->orderUnpaidCancel();
+                    } catch (\Throwable $e) {
+                        Log::error('自动取消订单失败,失败原因:' . $e->getMessage());
+                    }
+                });
+            }
+
+            if ($item['mark'] == 'pink_expiration') {
+                new Crontab($timeStr, function () {
+                    Log::error('每隔1分钟执行一次拼团到期订单处理 '.date('Y-m-d H:i:s'));
+                    //拼团到期订单处理
+                    try {
+                        /** @var StorePinkServices $storePinkServices */
+                        $storePinkServices = app()->make(StorePinkServices::class);
+                        $storePinkServices->statusPink();
+                    } catch (\Throwable $e) {
+                        Log::error('拼团到期订单处理失败,失败原因:' . $e->getMessage());
+                    }
+                });
+            }
+
+            if ($item['mark'] == 'agent_unbind') {
+                new Crontab($timeStr, function () {
+                    Log::error('每隔1分钟执行一次自动解除上级绑定 '.date('Y-m-d H:i:s'));
+                    //自动解绑上级绑定
+                    try {
+                        /** @var AgentManageServices $agentManage */
+                        $agentManage = app()->make(AgentManageServices::class);
+                        $agentManage->removeSpread();
+                    } catch (\Throwable $e) {
+                        Log::error('自动解除上级绑定失败,失败原因:' . $e->getMessage());
+                    }
+                });
+            }
+
+            if ($item['mark'] == 'live_product_status') {
+                new Crontab($timeStr, function () {
+                    Log::error('每隔3分钟执行一次更新直播商品状态 '.date('Y-m-d H:i:s'));
+                    //更新直播商品状态
+                    try {
+                        /** @var LiveGoodsServices $liveGoods */
+                        $liveGoods = app()->make(LiveGoodsServices::class);
+                        $liveGoods->syncGoodStatus();
+                    } catch (\Throwable $e) {
+                        Log::error('更新直播商品状态失败,失败原因:' . $e->getMessage());
+                    }
+                });
+            }
+
+            if ($item['mark'] == 'live_room_status') {
+                new Crontab($timeStr, function () {
+                    Log::error('每隔3分钟执行一次更新直播间状态 '.date('Y-m-d H:i:s'));
+                    //更新直播间状态
+                    try {
+                        /** @var LiveRoomServices $liveRoom */
+                        $liveRoom = app()->make(LiveRoomServices::class);
+                        $liveRoom->syncRoomStatus();
+                    } catch (\Throwable $e) {
+                        Log::error('更新直播间状态失败,失败原因:' . $e->getMessage());
+                    }
+                });
+            }
+
+            if ($item['mark'] == 'take_delivery') {
+                new Crontab($timeStr, function () {
+                    Log::error('每隔5分钟执行一次自动收货 '.date('Y-m-d H:i:s'));
+                    //自动收货
+                    try {
+                        /** @var StoreOrderTakeServices $services */
+                        $services = app()->make(StoreOrderTakeServices::class);
+                        $services->autoTakeOrder();
+                    } catch (\Throwable $e) {
+                        Log::error('自动收货失败,失败原因:' . $e->getMessage());
+                    }
+                });
+            }
+
+            if ($item['mark'] == 'advance_off') {
+                new Crontab($timeStr, function () {
+                    Log::error('每隔5分钟执行一次查询预售到期商品自动下架 '.date('Y-m-d H:i:s'));
+                    //查询预售到期商品自动下架
+                    try {
+                        /** @var StoreProductServices $product */
+                        $product = app()->make(StoreProductServices::class);
+                        $product->downAdvance();
+                    } catch (\Throwable $e) {
+                        Log::error('预售到期商品自动下架失败,失败原因:' . $e->getMessage());
+                    }
+                });
+            }
+
+            if ($item['mark'] == 'product_replay') {
+                new Crontab($timeStr, function () {
+                    Log::error('每隔5分钟执行一次自动好评 '.date('Y-m-d H:i:s'));
+                    //自动好评
+                    try {
+                        /** @var StoreOrderServices $orderServices */
+                        $orderServices = app()->make(StoreOrderServices::class);
+                        $orderServices->autoComment();
+                    } catch (\Throwable $e) {
+                        Log::error('自动好评失败,失败原因:' . $e->getMessage());
+                    }
+                });
+            }
+
+            if ($item['mark'] == 'product_replay') {
+                new Crontab($timeStr, function () {
+                    Log::error('每天0时30分0秒执行一次清除昨日海报 '.date('Y-m-d H:i:s'));
+                    //清除昨日海报
+                    try {
+                        /** @var SystemAttachmentServices $attach */
+                        $attach = app()->make(SystemAttachmentServices::class);
+                        $attach->emptyYesterdayAttachment();
+                    } catch (\Throwable $e) {
+                        Log::error('清除昨日海报失败,失败原因:' . $e->getMessage());
+                    }
+                });
+            }
+        }
+    }
+
+    public function getTimerStr($data): string
+    {
+        $timeStr = '';
+        switch ($data['type']) {
+            case 1:
+                $timeStr = '*/' . $data['second'] . ' * * * * *';
+                break;
+            case 2:
+                $timeStr = '0 */' . $data['minute'] . ' * * * *';
+                break;
+            case 3:
+                $timeStr = '0 0 */' . $data['hour'] . ' * * *';
+                break;
+            case 4:
+                $timeStr = '0 0 0 */' . $data['day'] . ' * *';
+                break;
+            case 5:
+                $timeStr = $data['second'] . ' ' . $data['minute'] . ' ' . $data['hour'] . ' * * *';
+                break;
+            case 6:
+                $timeStr = $data['second'] . ' ' . $data['minute'] . ' ' . $data['hour'] . ' * * ' . ($data['week'] == 7 ? 0 : $data['week']);
+                break;
+            case 7:
+                $timeStr = $data['second'] . ' ' . $data['minute'] . ' ' . $data['hour'] . ' ' . $data['day'] . ' * *';
+                break;
+        }
+        return $timeStr;
+    }
+}

+ 1 - 1
crmeb/app/model/activity/coupon/StoreCouponIssue.php

@@ -42,7 +42,7 @@ class StoreCouponIssue extends BaseModel
      */
     public function used()
     {
-        return $this->hasOne(StoreCouponIssueUser::class, 'issue_coupon_id', 'id')->field('issue_coupon_id');
+        return $this->hasMany(StoreCouponIssueUser::class, 'issue_coupon_id', 'id');
     }
 
     /**

+ 23 - 0
crmeb/app/model/system/timer/SystemTimer.php

@@ -0,0 +1,23 @@
+<?php
+
+namespace app\model\system\timer;
+
+use crmeb\basic\BaseModel;
+use crmeb\traits\ModelTrait;
+
+class SystemTimer extends BaseModel
+{
+    use ModelTrait;
+
+    /**
+     * 数据表主键
+     * @var string
+     */
+    protected $pk = 'id';
+
+    /**
+     * 模型名称
+     * @var string
+     */
+    protected $name = 'system_timer';
+}

+ 66 - 53
crmeb/app/services/activity/coupon/StoreCouponIssueServices.php

@@ -68,7 +68,8 @@ class StoreCouponIssueServices extends BaseServices
         return compact('list', 'count');
     }
 
-    /**获取会员优惠券列表
+    /**
+     * 获取会员优惠券列表
      * @param array $where
      * @return array
      * @throws \think\db\exception\DataNotFoundException
@@ -117,17 +118,25 @@ class StoreCouponIssueServices extends BaseServices
         $data['end_time'] = strtotime((string)$data['end_time']);
         $data['title'] = $data['coupon_title'];
         $data['remain_count'] = $data['total_count'];
+        $data['category_id'] = implode(',', $data['category_id']);
         if ($data['receive_type'] == 2 || $data['receive_type'] == 3) {
             $data['is_permanent'] = 1;
             $data['total_count'] = 0;
         }
         $data['add_time'] = time();
         $res = $this->dao->save($data);
-        if ($data['product_id'] !== '' && $res) {
-            $productIds = explode(',', $data['product_id']);
+        if (($data['product_id'] !== '' || $data['category_id'] !== '') && $res) {
             $couponData = [];
-            foreach ($productIds as $product_id) {
-                $couponData[] = ['product_id' => $product_id, 'coupon_id' => $res->id];
+            if ($data['product_id'] !== '') {
+                $productIds = explode(',', $data['product_id']);
+                foreach ($productIds as $product_id) {
+                    $couponData[] = ['product_id' => $product_id, 'coupon_id' => $res->id];
+                }
+            } elseif ($data['category_id'] !== '') {
+                $categoryIds = explode(',', $data['category_id']);
+                foreach ($categoryIds as $category_id) {
+                    $couponData[] = ['category_id' => $category_id, 'coupon_id' => $res->id];
+                }
             }
             /** @var StoreCouponProductServices $storeCouponProductService */
             $storeCouponProductService = app()->make(StoreCouponProductServices::class);
@@ -237,30 +246,27 @@ class StoreCouponIssueServices extends BaseServices
             $ids = array_column($couponList, 'id');
             /** @var StoreCouponIssueUserServices $issueUser */
             $issueUser = app()->make(StoreCouponIssueUserServices::class);
-            $userCouponIds = $issueUser->getColumn([['uid', '=', $uid], ['issue_coupon_id', 'in', $ids]], 'issue_coupon_id') ?? [];
             foreach ($couponList as $item) {
-                if (!$userCouponIds || !in_array($item['id'], $userCouponIds)) {
-                    $data['cid'] = $item['id'];
-                    $data['uid'] = $uid;
-                    $data['coupon_title'] = $item['title'];
-                    $data['coupon_price'] = $item['coupon_price'];
-                    $data['use_min_price'] = $item['use_min_price'];
-                    if ($item['coupon_time']) {
-                        $data['add_time'] = $time;
-                        $data['end_time'] = $data['add_time'] + $item['coupon_time'] * 86400;
-                    } else {
-                        $data['add_time'] = $item['start_use_time'];
-                        $data['end_time'] = $item['end_use_time'];
-                    }
-                    $data['type'] = 'get';
-                    $issue['uid'] = $uid;
-                    $issue['issue_coupon_id'] = $item['id'];
-                    $issue['add_time'] = $time;
-                    $issueUserData[] = $issue;
-                    $couponData[] = $data;
-                    unset($data);
-                    unset($issue);
+                $data['cid'] = $item['id'];
+                $data['uid'] = $uid;
+                $data['coupon_title'] = $item['title'];
+                $data['coupon_price'] = $item['coupon_price'];
+                $data['use_min_price'] = $item['use_min_price'];
+                if ($item['coupon_time']) {
+                    $data['add_time'] = $time;
+                    $data['end_time'] = $data['add_time'] + $item['coupon_time'] * 86400;
+                } else {
+                    $data['add_time'] = $item['start_use_time'];
+                    $data['end_time'] = $item['end_use_time'];
                 }
+                $data['type'] = 'get';
+                $issue['uid'] = $uid;
+                $issue['issue_coupon_id'] = $item['id'];
+                $issue['add_time'] = $time;
+                $issueUserData[] = $issue;
+                $couponData[] = $data;
+                unset($data);
+                unset($issue);
             }
             if ($couponData) {
                 /** @var StoreCouponUserServices $storeCouponUser */
@@ -326,10 +332,10 @@ class StoreCouponIssueServices extends BaseServices
         foreach ($list as &$v) {
             $v['coupon_price'] = floatval($v['coupon_price']);
             $v['use_min_price'] = floatval($v['use_min_price']);
-            $v['is_use'] = $uid && isset($v['used']);
+            $v['is_use'] = count($v['used']);
             if ($v['end_use_time']) {
                 $v['start_use_time'] = date('Y/m/d', $v['start_use_time']);
-                $v['end_use_time'] = $v['end_use_time'] ? date('Y/m/d', $v['end_use_time']) : date('Y/m/d', time() + 86400);
+                $v['end_use_time'] = date('Y/m/d', $v['end_use_time']);
             }
             if ($v['start_time']) {
                 $v['start_time'] = date('Y/m/d', $v['start_time']);
@@ -341,7 +347,16 @@ class StoreCouponIssueServices extends BaseServices
         return $data;
     }
 
-    public function issueUserCoupon($id, $user)
+    /**
+     * 领取优惠券
+     * @param $id
+     * @param $user
+     * @param bool $is_receive
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\DbException
+     * @throws \think\db\exception\ModelNotFoundException
+     */
+    public function issueUserCoupon($id, $user, bool $is_receive = false)
     {
         $issueCouponInfo = $this->dao->getInfo((int)$id);
         $uid = $user->uid;
@@ -354,9 +369,14 @@ class StoreCouponIssueServices extends BaseServices
         }
         /** @var StoreCouponIssueUserServices $issueUserService */
         $issueUserService = app()->make(StoreCouponIssueUserServices::class);
+        if ($is_receive) {
+            $alreadyReceived = $issueUserService->count(['uid' => $uid, 'issue_coupon_id' => $id]);
+            if ($alreadyReceived >= $issueCouponInfo['receive_limit']) {
+                throw new ApiException(400518);
+            }
+        }
         /** @var StoreCouponUserServices $couponUserService */
         $couponUserService = app()->make(StoreCouponUserServices::class);
-        if ($issueUserService->getOne(['uid' => $uid, 'issue_coupon_id' => $id])) throw new ApiException(400517);
         if ($issueCouponInfo->remain_count <= 0 && !$issueCouponInfo->is_permanent) throw new ApiException(400518);
         $this->transaction(function () use ($issueUserService, $uid, $id, $couponUserService, $issueCouponInfo) {
             $issueUserService->save(['uid' => $uid, 'issue_coupon_id' => $id, 'add_time' => time()]);
@@ -432,29 +452,24 @@ class StoreCouponIssueServices extends BaseServices
         $storeCouponUser = app()->make(StoreCouponUserServices::class);
         /** @var StoreCouponIssueUserServices $storeCouponIssueUser */
         $storeCouponIssueUser = app()->make(StoreCouponIssueUserServices::class);
-        $uids = $storeCouponIssueUser->getColumn(['issue_coupon_id' => $coupon['id']], 'uid');
         foreach ($user as $k => $v) {
-            if (in_array($v, $uids)) {
-                continue;
+            $data[$k]['cid'] = $coupon['id'];
+            $data[$k]['uid'] = $v;
+            $data[$k]['coupon_title'] = $coupon['title'];
+            $data[$k]['coupon_price'] = $coupon['coupon_price'];
+            $data[$k]['use_min_price'] = $coupon['use_min_price'];
+            $data[$k]['add_time'] = time();
+            if ($coupon['coupon_time']) {
+                $data[$k]['start_time'] = $data[$k]['add_time'];
+                $data[$k]['end_time'] = $data[$k]['add_time'] + $coupon['coupon_time'] * 86400;
             } else {
-                $data[$k]['cid'] = $coupon['id'];
-                $data[$k]['uid'] = $v;
-                $data[$k]['coupon_title'] = $coupon['title'];
-                $data[$k]['coupon_price'] = $coupon['coupon_price'];
-                $data[$k]['use_min_price'] = $coupon['use_min_price'];
-                $data[$k]['add_time'] = time();
-                if ($coupon['coupon_time']) {
-                    $data[$k]['start_time'] = $data[$k]['add_time'];
-                    $data[$k]['end_time'] = $data[$k]['add_time'] + $coupon['coupon_time'] * 86400;
-                } else {
-                    $data[$k]['start_time'] = $coupon['start_use_time'];
-                    $data[$k]['end_time'] = $coupon['end_use_time'];
-                }
-                $data[$k]['type'] = 'send';
-                $issueData[$k]['uid'] = $v;
-                $issueData[$k]['issue_coupon_id'] = $coupon['id'];
-                $issueData[$k]['add_time'] = time();
+                $data[$k]['start_time'] = $coupon['start_use_time'];
+                $data[$k]['end_time'] = $coupon['end_use_time'];
             }
+            $data[$k]['type'] = 'send';
+            $issueData[$k]['uid'] = $v;
+            $issueData[$k]['issue_coupon_id'] = $coupon['id'];
+            $issueData[$k]['add_time'] = time();
         }
         if (!empty($data)) {
             if (!$storeCouponUser->saveAll($data)) {
@@ -464,8 +479,6 @@ class StoreCouponIssueServices extends BaseServices
                 throw new AdminException(100031);
             }
             return true;
-        } else {
-            throw new AdminException(400519);
         }
     }
 

+ 5 - 1
crmeb/app/services/activity/coupon/StoreCouponIssueUserServices.php

@@ -8,7 +8,7 @@
 // +----------------------------------------------------------------------
 // | Author: CRMEB Team <admin@crmeb.com>
 // +----------------------------------------------------------------------
-declare (strict_types = 1);
+declare (strict_types=1);
 
 namespace app\services\activity\coupon;
 
@@ -42,6 +42,10 @@ class StoreCouponIssueUserServices extends BaseServices
     {
         [$page, $limit] = $this->getPageValue();
         $list = $this->dao->getList($where, $page, $limit);
+        $site_url = sys_config('site_url');
+        foreach ($list as &$item) {
+            $item['avatar'] = strpos($item['avatar'], 'http') === false ? ($site_url . $item['avatar']) : $item['avatar'];
+        }
         $count = $this->dao->count($where);
         return compact('list', 'count');
     }

+ 4 - 15
crmeb/app/services/activity/coupon/StoreCouponUserServices.php

@@ -145,9 +145,10 @@ class StoreCouponUserServices extends BaseServices
                     case 1://品类券
                         /** @var StoreCategoryServices $storeCategoryServices */
                         $storeCategoryServices = app()->make(StoreCategoryServices::class);
-                        $cateGorys = $storeCategoryServices->getAllById((int)$coupon['category_id']);
-                        if ($cateGorys) {
-                            $cateIds = array_column($cateGorys, 'id');
+                        $coupon_category = explode(',', $coupon['category_id']);
+                        $category_ids = $storeCategoryServices->getAllById($coupon_category);
+                        if ($category_ids) {
+                            $cateIds = array_column($category_ids, 'id');
                             foreach ($cartInfo as $cart) {
                                 if (isset($cart['productInfo']['cate_id']) && array_intersect(explode(',', $cart['productInfo']['cate_id']), $cateIds)) {
                                     $price += bcmul((string)$cart['truePrice'], (string)$cart['cart_num'], 2);
@@ -302,18 +303,6 @@ class StoreCouponUserServices extends BaseServices
         }
         [$page, $limit] = $this->getPageValue();
         $list = $this->dao->getCouponListByOrder($where, 'status ASC,add_time DESC', $page, $limit);
-        /** @var StoreCategoryServices $categoryServices */
-        $categoryServices = app()->make(StoreCategoryServices::class);
-        $category = $categoryServices->getColumn([], 'pid,cate_name', 'id');
-        foreach ($list as &$item) {
-            if ($item['category_id']) {
-                $item['category_type'] = $category[$item['category_id']]['pid'] == 0 ? 1 : 2;
-                $item['category_name'] = $category[$item['category_id']]['cate_name'];
-            } else {
-                $item['category_type'] = '';
-                $item['category_name'] = '';
-            }
-        }
         return $list ? $this->tidyCouponList($list) : [];
     }
 

+ 22 - 11
crmeb/app/services/message/NoticeService.php

@@ -83,21 +83,32 @@ class NoticeService extends BaseServices
         if (!$product) {
             throw new AdminException(400463);
         }
-        $configdata = [
-            'clientId' => sys_config('printing_client_id', ''),
-            'apiKey' => sys_config('printing_api_key', ''),
-            'partner' => sys_config('develop_id', ''),
-            'terminal' => sys_config('terminal_number', '')
-        ];
         $switch = (bool)sys_config('pay_success_printing_switch');
         if (!$switch) {
             throw new AdminException(400464);
         }
-        if (!$configdata['clientId'] || !$configdata['apiKey'] || !$configdata['partner'] || !$configdata['terminal']) {
-            throw new AdminException(400465);
+        if (sys_config('print_type', 1) == 1) {
+            $name = 'yi_lian_yun';
+            $configData = [
+                'clientId' => sys_config('printing_client_id', ''),
+                'apiKey' => sys_config('printing_api_key', ''),
+                'partner' => sys_config('develop_id', ''),
+                'terminal' => sys_config('terminal_number', '')
+            ];
+            if (!$configData['clientId'] || !$configData['apiKey'] || !$configData['partner'] || !$configData['terminal']) {
+                throw new AdminException(400465);
+            }
+        } else {
+            $name = 'fei_e_yun';
+            $configData = [
+                'feyUser' => sys_config('fey_user', ''),
+                'feyUkey' => sys_config('fey_ukey', ''),
+                'feySn' => sys_config('fey_sn', '')
+            ];
+            if (!$configData['feyUser'] || !$configData['feyUkey'] || !$configData['feySn']) {
+                throw new AdminException(400465);
+            }
         }
-        PrintJob::dispatch('doJob', ['yi_lian_yun', $configdata, $order, $product]);
+        PrintJob::dispatch('doJob', [$name, $configData, $order, $product]);
     }
-
-
 }

+ 0 - 6
crmeb/app/services/order/StoreCartServices.php

@@ -16,7 +16,6 @@ use app\services\activity\advance\StoreAdvanceServices;
 use app\services\BaseServices;
 use app\dao\order\StoreCartDao;
 use app\services\activity\coupon\StoreCouponIssueServices;
-use app\services\activity\coupon\StoreCouponIssueUserServices;
 use app\services\shipping\ShippingTemplatesNoDeliveryServices;
 use app\services\system\SystemUserLevelServices;
 use app\services\user\member\MemberCardServices;
@@ -172,11 +171,6 @@ class StoreCartServices extends BaseServices
                     if (!$issueCoupon->getCount(['id' => $attrInfo['coupon_id'], 'status' => 1, 'is_del' => 0])) {
                         throw new ApiException(410234);
                     }
-                    /** @var StoreCouponIssueUserServices $issueUserCoupon */
-                    $issueUserCoupon = app()->make(StoreCouponIssueUserServices::class);
-                    if ($issueUserCoupon->getCount(['uid' => $uid, 'issue_coupon_id' => $attrInfo['coupon_id']])) {
-                        throw new ApiException(410235);
-                    }
                 }
                 $stockNum = $this->dao->value(['product_id' => $productId, 'product_attr_unique' => $unique, 'uid' => $uid, 'status' => 1], 'cart_num') ?: 0;
                 if ($nowStock < ($cartNum + $stockNum)) {

+ 15 - 6
crmeb/app/services/order/StoreOrderComputedServices.php

@@ -175,9 +175,10 @@ class StoreOrderComputedServices extends BaseServices
                 case 1://品类券
                     /** @var StoreCategoryServices $storeCategoryServices */
                     $storeCategoryServices = app()->make(StoreCategoryServices::class);
-                    $cateGorys = $storeCategoryServices->getAllById((int)$couponInfo['category_id']);
-                    if ($cateGorys) {
-                        $cateIds = array_column($cateGorys, 'id');
+                    $coupon_category = explode(',', $couponInfo['category_id']);
+                    $category_ids = $storeCategoryServices->getAllById($coupon_category);
+                    if ($category_ids) {
+                        $cateIds = array_column($category_ids, 'id');
                         foreach ($cartInfo as $cart) {
                             if (isset($cart['productInfo']['cate_id']) && array_intersect(explode(',', $cart['productInfo']['cate_id']), $cateIds)) {
                                 $price = bcadd($price, bcmul((string)$cart['truePrice'], (string)$cart['cart_num'], 2), 2);
@@ -318,7 +319,9 @@ class StoreOrderComputedServices extends BaseServices
         $sumPrice = $this->getOrderSumPrice($cartInfo, 'sum_price');//获取订单原总金额
         $totalPrice = $this->getOrderSumPrice($cartInfo, 'truePrice');//获取订单svip、用户等级优惠之后总金额
         $costPrice = $this->getOrderSumPrice($cartInfo, 'costPrice');//获取订单成本价
-        $vipPrice = $this->getOrderSumPrice($cartInfo, 'vip_truePrice');//获取订单会员优惠金额
+        $vipPrice = $this->getOrderSumPrice($cartInfo, 'vip_truePrice');//获取订单等级和付费会员总优惠金额
+        $levelPrice = $this->getOrderSumPrice($cartInfo, 'level');//获取会员等级优惠
+        $memberPrice = $this->getOrderSumPrice($cartInfo, 'member');//获取付费会员优惠
 
         // 判断商品包邮和固定运费
         foreach ($cartInfo as $key => &$item) {
@@ -513,7 +516,7 @@ class StoreOrderComputedServices extends BaseServices
                 $storePostage = $storePostage;
             }
         }
-        return compact('storePostage', 'storeFreePostage', 'isStoreFreePostage', 'sumPrice', 'totalPrice', 'costPrice', 'vipPrice', 'storePostageDiscount', 'cartInfo');
+        return compact('storePostage', 'storeFreePostage', 'isStoreFreePostage', 'sumPrice', 'totalPrice', 'costPrice', 'vipPrice', 'storePostageDiscount', 'cartInfo', 'levelPrice', 'memberPrice');
     }
 
     /**
@@ -529,7 +532,13 @@ class StoreOrderComputedServices extends BaseServices
         foreach ($cartInfo as $cart) {
             if (isset($cart['cart_info'])) $cart = $cart['cart_info'];
             if ($is_unit) {
-                $SumPrice = bcadd($SumPrice, bcmul($cart['cart_num'], $cart[$key], 2), 2);
+                if ($key == 'level' || $key == 'member') {
+                    if ($cart['price_type'] == $key) {
+                        $SumPrice = bcadd($SumPrice, bcmul($cart['cart_num'], $cart['vip_truePrice'], 2), 2);
+                    }
+                } else {
+                    $SumPrice = bcadd($SumPrice, bcmul($cart['cart_num'], $cart[$key], 2), 2);
+                }
             } else {
                 $SumPrice = bcadd($SumPrice, $cart[$key], 2);
             }

+ 4 - 3
crmeb/app/services/order/StoreOrderCreateServices.php

@@ -650,9 +650,10 @@ class StoreOrderCreateServices extends BaseServices
                     case 1://品类券
                         /** @var StoreCategoryServices $storeCategoryServices */
                         $storeCategoryServices = app()->make(StoreCategoryServices::class);
-                        $cateGorys = $storeCategoryServices->getAllById((int)$couponInfo['category_id']);
-                        if ($cateGorys) {
-                            $cateIds = array_column($cateGorys, 'id');
+                        $coupon_category = explode(',', $couponInfo['category_id']);
+                        $category_ids = $storeCategoryServices->getAllById($coupon_category);
+                        if ($category_ids) {
+                            $cateIds = array_column($category_ids, 'id');
                             foreach ($cartInfo as $cart) {
                                 if (isset($cart['productInfo']['cate_id']) && array_intersect(explode(',', $cart['productInfo']['cate_id']), $cateIds)) {
                                     $total_price = bcadd((string)$total_price, (string)bcmul((string)$cart['truePrice'], (string)$cart['cart_num'], 4), 2);

+ 27 - 10
crmeb/app/services/order/StoreOrderServices.php

@@ -1454,16 +1454,29 @@ HTML;
         if (!$product) {
             throw new AdminException(400533);
         }
-        $data = [
-            'clientId' => sys_config('printing_client_id', ''),
-            'apiKey' => sys_config('printing_api_key', ''),
-            'partner' => sys_config('develop_id', ''),
-            'terminal' => sys_config('terminal_number', '')
-        ];
-        if (!$data['clientId'] || !$data['apiKey'] || !$data['partner'] || !$data['terminal']) {
-            throw new AdminException(400099);
+        if (sys_config('print_type', 1) == 1) {
+            $name = 'yi_lian_yun';
+            $configData = [
+                'clientId' => sys_config('printing_client_id', ''),
+                'apiKey' => sys_config('printing_api_key', ''),
+                'partner' => sys_config('develop_id', ''),
+                'terminal' => sys_config('terminal_number', '')
+            ];
+            if (!$configData['clientId'] || !$configData['apiKey'] || !$configData['partner'] || !$configData['terminal']) {
+                throw new AdminException(400465);
+            }
+        } else {
+            $name = 'fei_e_yun';
+            $configData = [
+                'feyUser' => sys_config('fey_user', ''),
+                'feyUkey' => sys_config('fey_ukey', ''),
+                'feySn' => sys_config('fey_sn', '')
+            ];
+            if (!$configData['feyUser'] || !$configData['feyUkey'] || !$configData['feySn']) {
+                throw new AdminException(400465);
+            }
         }
-        $printer = new Printer('yi_lian_yun', $data);
+        $printer = new Printer($name, $configData);
         $res = $printer->setPrinterContent([
             'name' => sys_config('site_name'),
             'orderInfo' => is_object($order) ? $order->toArray() : $order,
@@ -2334,9 +2347,11 @@ HTML;
         $order['ali_pay_status'] = (bool)sys_config('ali_pay_status');//支付包支付 1 开启 0 关闭
         $order['friend_pay_status'] = (int)sys_config('friend_pay_status') ?? 0;//好友代付 1 开启 0 关闭
         $orderData = $this->tidyOrder($order, true, true);
-        $vipTruePrice = 0;
+        $vipTruePrice = $memberPrice = $levelPrice = 0;
         foreach ($orderData['cartInfo'] ?? [] as $key => $cart) {
             $vipTruePrice = bcadd((string)$vipTruePrice, (string)$cart['vip_sum_truePrice'], 2);
+            if ($cart['price_type'] == 'member') $memberPrice = bcadd((string)$memberPrice, (string)$cart['vip_sum_truePrice'], 2);
+            if ($cart['price_type'] == 'level') $levelPrice = bcadd((string)$levelPrice, (string)$cart['vip_sum_truePrice'], 2);
             if (isset($splitNum[$cart['id']])) {
                 $orderData['cartInfo'][$key]['cart_num'] = $cart['cart_num'] - $splitNum[$cart['id']];
                 if ($orderData['cartInfo'][$key]['cart_num'] == 0) unset($orderData['cartInfo'][$key]);
@@ -2344,6 +2359,8 @@ HTML;
         }
         $orderData['cartInfo'] = array_merge($orderData['cartInfo']);
         $orderData['vip_true_price'] = $vipTruePrice;
+        $orderData['levelPrice'] = $levelPrice;
+        $orderData['memberPrice'] = $memberPrice;
         $economize = $services->get(['order_id' => $order['order_id']], ['postage_price', 'member_price']);
         if ($economize) {
             $orderData['postage_price'] = $economize['postage_price'];

+ 187 - 0
crmeb/app/services/system/timer/SystemTimerServices.php

@@ -0,0 +1,187 @@
+<?php
+
+namespace app\services\system\timer;
+
+use app\dao\system\timer\SystemTimerDao;
+use app\services\BaseServices;
+use crmeb\exceptions\AdminException;
+
+class SystemTimerServices extends BaseServices
+{
+    /**
+     * 定时任务类型
+     * @var string[]
+     */
+    private $markList = [
+        'order_cancel' => '未支付自动取消订单',
+        'pink_expiration' => '拼团到期订单处理',
+        'agent_unbind' => '到期自动解绑上级',
+        'live_product_status' => '自动更新直播商品状态',
+        'live_room_status' => '自动更新直播间状态',
+        'take_delivery' => '订单自动收货',
+        'advance_off' => '预售商品到期自动下架',
+        'product_replay' => '订单商品自动好评',
+        'clear_poster' => '清除昨日海报',
+    ];
+
+    public function __construct(SystemTimerDao $dao)
+    {
+        $this->dao = $dao;
+    }
+
+    /**
+     * 定时任务列表
+     * @param array $where
+     * @return array
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\DbException
+     * @throws \think\db\exception\ModelNotFoundException
+     */
+    public function getTimerList(array $where = [])
+    {
+        [$page, $limit] = $this->getPageValue();
+        $list = $this->dao->selectList($where, '*', $page, $limit, 'id desc');
+        foreach ($list as &$item) {
+            $item['next_execution_time'] = date('Y-m-d H:i:s', $item['next_execution_time']);
+            $item['last_execution_time'] = $item['last_execution_time'] != 0 ? date('Y-m-d H:i:s', $item['last_execution_time']) : '暂未执行';
+        }
+        $count = $this->dao->count($where);
+        return compact('list', 'count');
+    }
+
+    /**
+     * 定时任务详情
+     * @param $id
+     * @return array
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\DbException
+     * @throws \think\db\exception\ModelNotFoundException
+     */
+    public function getTimerInfo($id)
+    {
+        $info = $this->dao->get($id);
+        if (!$info) throw new AdminException(100026);
+        return $info->toArray();
+    }
+
+    /**
+     * 定时任务类型
+     * @return string[]
+     */
+    public function getMarkList(): array
+    {
+        return $this->markList;
+    }
+
+    /**
+     * 保存定时任务
+     * @param array $data
+     * @return bool
+     */
+    public function saveTimer(array $data = [])
+    {
+        if (!$data['id'] && $this->dao->getCount(['mark' => $data['mark'], 'is_del' => 0])) {
+            throw new AdminException('该定时任务已存在,请勿重复添加');
+        }
+        $data['name'] = $this->markList[$data['mark']];
+        $data['add_time'] = time();
+        if (!$data['id']) {
+            unset($data['id']);
+            $res = $this->dao->save($data);
+        } else {
+            $res = $this->dao->update(['id' => $data['id']], $data);
+        }
+        if (!$res) throw new AdminException(100006);
+        return true;
+    }
+
+    /**
+     * 删除定时任务
+     * @param $id
+     * @return bool
+     */
+    public function delTimer($id)
+    {
+        $res = $this->dao->update(['id' => $id], ['is_del' => 1]);
+        if (!$res) throw new AdminException(100008);
+        return true;
+    }
+
+    /**
+     * 设置定时任务状态
+     * @param $id
+     * @param $is_open
+     * @return bool
+     */
+    public function setTimerStatus($id, $is_open)
+    {
+        $res = $this->dao->update(['id' => $id], ['is_open' => $is_open]);
+        if (!$res) throw new AdminException(100014);
+        return true;
+    }
+
+    /**
+     * 计算定时任务下次执行时间(弃用)
+     * @param $data
+     * @param int $time
+     * @return false|float|int|mixed
+     */
+    public function getTimerCycleTime($data, $time = 0)
+    {
+        if (!$time) $time = time();
+        switch ($data['type']) {
+            case 1: // 每隔几秒
+                $cycle_time = $time + $data['second'];
+                break;
+            case 2: // 每隔几分
+                $cycle_time = $time + ($data['minute'] * 60);
+                break;
+            case 3: // 每隔几时
+                $cycle_time = $time + ($data['hour'] * 3600);
+                break;
+            case 4: // 每隔几日
+                $cycle_time = $time + ($data['day'] * 86400);
+                break;
+            case 5: // 每日几时几分几秒
+                $cycle_time = strtotime(date('Y-m-d ' . $data['hour'] . ':' . $data['minute'] . ':' . $data['second'], time()));
+                if ($time >= $cycle_time) {
+                    $cycle_time = $cycle_time + 86400;
+                }
+                break;
+            case 6: // 每周周几几时几分几秒
+                $todayStart = strtotime(date('Y-m-d 00:00:00', time()));
+                $w = date("w");
+                if ($w > $data['week']) {
+                    $cycle_time = $todayStart + ((7 - $w + $data['week']) * 86400) + ($data['hour'] * 3600) + ($data['minute'] * 60) + $data['second'];
+                } else if ($w == $data['week']) {
+                    $cycle_time = $todayStart + ($data['hour'] * 3600) + ($data['minute'] * 60) + $data['second'];
+                    if ($time >= $cycle_time) {
+                        $cycle_time = $cycle_time + (7 * 86400);
+                    }
+                } else {
+                    $cycle_time = $todayStart + (($data['week'] - $w) * 86400) + ($data['hour'] * 3600) + ($data['minute'] * 60) + $data['second'];
+                }
+                break;
+            case 7: // 每月几日几时几分几秒
+                $d = date("d");
+                $firstDate = date('Y-m-01', time());
+                $maxDay = date('d', strtotime("$firstDate + 1 month -1 day"));
+                $todayStart = strtotime(date('Y-m-d 00:00:00', time()));
+                if ($d > $data['day']) {
+                    $cycle_time = $todayStart + (($maxDay - $d + $data['day']) * 86400) + ($data['hour'] * 3600) + ($data['minute'] * 60) + $data['second'];
+                } elseif ($d == $data['day']) {
+                    $cycle_time = $todayStart + ($data['hour'] * 3600) + ($data['minute'] * 60) + $data['second'];
+                    if ($time >= $cycle_time) {
+                        $cycle_time = $cycle_time + (($maxDay - $d + $data['day']) * 86400) + ($data['hour'] * 3600) + ($data['minute'] * 60) + $data['second'];
+                    }
+                } else {
+                    $cycle_time = $todayStart + (($data['day'] - $d) * 86400) + ($data['hour'] * 3600) + ($data['minute'] * 60) + $data['second'];
+                }
+                break;
+            default:
+                $cycle_time = 0;
+                break;
+        }
+        return $cycle_time;
+    }
+}

+ 15 - 17
crmeb/app/services/user/UserLevelServices.php

@@ -8,7 +8,7 @@
 // +----------------------------------------------------------------------
 // | Author: CRMEB Team <admin@crmeb.com>
 // +----------------------------------------------------------------------
-declare (strict_types = 1);
+declare (strict_types=1);
 
 namespace app\services\user;
 
@@ -206,10 +206,10 @@ class UserLevelServices extends BaseServices
     {
 
         if ($id) {
-            $vipinfo = app()->make(SystemUserLevelServices::class)->getlevel($id);
-            $vipinfo->image = set_file_url($vipinfo->image);
-            $vipinfo->icon = set_file_url($vipinfo->icon);
-            if (!$vipinfo) {
+            $vipInfo = app()->make(SystemUserLevelServices::class)->getlevel($id);
+            $vipInfo->image = set_file_url($vipInfo->image);
+            $vipInfo->icon = set_file_url($vipInfo->icon);
+            if (!$vipInfo) {
                 throw new AdminException(100026);
             }
             $field[] = Form::hidden('id', $id);
@@ -217,15 +217,13 @@ class UserLevelServices extends BaseServices
         } else {
             $msg = '添加用户等级';
         }
-        $field[] = Form::input('name', '等级名称', isset($vipinfo) ? $vipinfo->name : '')->col(24)->required();
-//        $field[] = Form::number('valid_date', '有效时间(天)', isset($vipinfo) ? $vipinfo->valid_date : 0)->min(0)->col(12);
-        $field[] = Form::number('grade', '等级', isset($vipinfo) ? $vipinfo->grade : 0)->min(0)->precision(0)->col(8)->required();
-        $field[] = Form::number('discount', '享受折扣', isset($vipinfo) ? $vipinfo->discount : 100)->min(0)->max(100)->col(8)->placeholder('输入折扣数100,代表原价,90代表9折')->required();
-        $field[] = Form::number('exp_num', '解锁需经验值达到', isset($vipinfo) ? $vipinfo->exp_num : 0)->min(0)->precision(0)->col(8)->required();
-        $field[] = Form::frameImage('icon', '图标', Url::buildUrl('admin/widget.images/index', array('fodder' => 'icon')), isset($vipinfo) ? $vipinfo->icon : '')->icon('ios-add')->width('950px')->height('505px')->modal(['footer-hide' => true]);
-        $field[] = Form::frameImage('image', '用户等级背景', Url::buildUrl('admin/widget.images/index', array('fodder' => 'image')), isset($vipinfo) ? $vipinfo->image : '')->icon('ios-add')->width('950px')->height('505px')->modal(['footer-hide' => true]);
-        $field[] = Form::radio('is_show', '是否显示', isset($vipinfo) ? $vipinfo->is_show : 0)->options([['label' => '显示', 'value' => 1], ['label' => '隐藏', 'value' => 0]])->col(24);
-        $field[] = Form::textarea('explain', '等级说明', isset($vipinfo) ? $vipinfo->explain : '')->required();
+        $field[] = Form::input('name', '等级名称', isset($vipInfo) ? $vipInfo->name : '')->col(24)->required();
+        $field[] = Form::number('grade', '等级', isset($vipInfo) ? $vipInfo->grade : 0)->min(0)->precision(0)->col(8)->required();
+        $field[] = Form::number('discount', '享受折扣', isset($vipInfo) ? $vipInfo->discount : 100)->min(0)->max(100)->col(8)->placeholder('输入折扣数100,代表原价,90代表9折')->required();
+        $field[] = Form::number('exp_num', '解锁需经验值达到', isset($vipInfo) ? $vipInfo->exp_num : 0)->min(0)->precision(0)->col(8)->required();
+        $field[] = Form::frameImage('icon', '图标', Url::buildUrl('admin/widget.images/index', array('fodder' => 'icon')), isset($vipInfo) ? $vipInfo->icon : '')->icon('ios-add')->width('950px')->height('505px')->modal(['footer-hide' => true]);
+        $field[] = Form::frameImage('image', '用户等级背景', Url::buildUrl('admin/widget.images/index', array('fodder' => 'image')), isset($vipInfo) ? $vipInfo->image : '')->icon('ios-add')->width('950px')->height('505px')->modal(['footer-hide' => true]);
+        $field[] = Form::radio('is_show', '是否显示', isset($vipInfo) ? $vipInfo->is_show : 0)->options([['label' => '显示', 'value' => 1], ['label' => '隐藏', 'value' => 0]])->col(24);
         return create_form($msg, $field, Url::buildUrl('/user/user_level'), 'POST');
     }
 
@@ -260,7 +258,7 @@ class UserLevelServices extends BaseServices
             if (!$systemUserLevel->update($id, $data)) {
                 throw new AdminException(100007);
             }
-            return '修改成功';
+            return true;
         } else {
             if ($levelOne || $levelThree) {
                 throw new AdminException(400675);
@@ -273,7 +271,7 @@ class UserLevelServices extends BaseServices
             if (!$systemUserLevel->save($data)) {
                 throw new AdminException(100022);
             }
-            return '添加成功';
+            return true;
         }
     }
 
@@ -291,7 +289,7 @@ class UserLevelServices extends BaseServices
             if (!$systemUserLevel->update($id, ['is_del' => 1]))
                 throw new AdminException(100008);
         }
-        return '删除成功';
+        return 100002;
     }
 
     /**

+ 5 - 29
crmeb/crmeb/command/Timer.php

@@ -27,7 +27,7 @@ class Timer extends Command
     /**
      * @var int|float
      */
-    protected $interval = 2;
+    protected $interval = 1;
 
     protected function configure()
     {
@@ -59,35 +59,11 @@ class Timer extends Command
         $this->init($input, $output);
         Worker::$pidFile = app()->getRootPath().'runtime/timer.pid';
         $task = new Worker();
+        date_default_timezone_set('PRC');
         $task->count = 1;
-        event('Task_6');
-        $task->onWorkerStart = [$this, 'start'];
+        $task->onWorkerStart = function () {
+            event('SystemTimer');
+        };
         $task->runAll();
     }
-
-    public function stop()
-    {
-        \Workerman\Lib\Timer::del($this->timer);
-    }
-
-    public function start()
-    {
-        $last = time();
-        $task = [6 => $last, 10 => $last, 30 => $last, 60 => $last, 180 => $last, 300 => $last];
-        $this->timer = \Workerman\Lib\Timer::add($this->interval, function () use (&$task) {
-            try {
-                $now = time();
-                event('Task_2');
-                foreach ($task as $sec => $time) {
-                    if ($now - $time >= $sec) {
-                        event('Task_' . $sec);
-                        $task[$sec] = $now;
-                    }
-                }
-            } catch (\Throwable $e) {
-            }
-        });
-    }
-
-
 }

+ 1 - 1
crmeb/crmeb/services/pay/Pay.php

@@ -20,7 +20,7 @@ use think\facade\Config;
 
 /**
  * 第三方支付
- * Class Pay
+ * Class AllinPay
  * @package crmeb\services\pay
  * @mixin AliPay
  * @mixin WechatPay

+ 22 - 2
crmeb/crmeb/services/printer/AccessToken.php

@@ -14,7 +14,6 @@ namespace crmeb\services\printer;
 use app\services\other\CacheServices;
 use crmeb\exceptions\AdminException;
 use crmeb\services\HttpService;
-use think\facade\Config;
 use think\helper\Str;
 
 /**
@@ -72,12 +71,33 @@ class AccessToken extends HttpService
      */
     protected $apiKey;
 
+    /**
+     * 飞鹅云SN
+     * @var string
+     */
+    protected $feySn;
+
+    /**
+     * 飞鹅云UYEK
+     * @var string
+     */
+    protected $feyUkey;
+
+    /**
+     * 飞鹅云USER
+     * @var string
+     */
+    protected $feyUser;
+
     public function __construct(array $config = [], string $name, string $configFile)
     {
         $this->clientId = $config['clientId'] ?? null;
         $this->apiKey = $config['apiKey'] ?? null;
         $this->partner = $config['partner'] ?? null;
         $this->machineCode = $config['terminal'] ?? null;
+        $this->feyUser = $config['feyUser'] ?? null;
+        $this->feyUkey = $config['feyUkey'] ?? null;
+        $this->feySn = $config['feySn'] ?? null;
         $this->name = $name;
         $this->configFile = $configFile;
     }
@@ -150,7 +170,7 @@ class AccessToken extends HttpService
      */
     public function __get($name)
     {
-        if (in_array($name, ['clientId', 'apiKey', 'accessToken', 'partner', 'terminal', 'machineCode'])) {
+        if (in_array($name, ['clientId', 'apiKey', 'accessToken', 'partner', 'terminal', 'machineCode', 'feyUser', 'feyUkey', 'feySn'])) {
             return $this->{$name};
         }
     }

+ 161 - 0
crmeb/crmeb/services/printer/storage/FeiEYun.php

@@ -0,0 +1,161 @@
+<?php
+
+namespace crmeb\services\printer\storage;
+
+use crmeb\services\printer\BasePrinter;
+
+class FeiEYun extends BasePrinter
+{
+
+    /**
+     * 初始化
+     * @param array $config
+     * @return mixed|void
+     */
+    protected function initialize(array $config)
+    {
+
+    }
+
+    /**
+     * 开始打印
+     * @return bool|mixed|string
+     * @throws \Exception
+     */
+    public function startPrinter()
+    {
+        if (!$this->printerContent) {
+            return $this->setError('Missing print');
+        }
+        $time = time();
+        $request = $this->accessToken->postRequest('http://api.feieyun.cn/Api/Open/', [
+            'user' => $this->accessToken->feyUser,
+            'stime' => $time,
+            'sig' => sha1($this->accessToken->feyUser . $this->accessToken->feyUkey . $time),
+            'apiname' => 'Open_printMsg',
+            'sn' => $this->accessToken->feySn,
+            'content' => $this->printerContent,
+            'times' => 1
+        ]);
+        $res = json_decode($request, true);
+        if ($res['msg'] == 'ok') {
+            return $res;
+        } else {
+            return $this->setError($res['msg']);
+        }
+    }
+
+    /**
+     * 设置打印内容
+     * @param array $config
+     * @return YiLianYun
+     */
+    public function setPrinterContent(array $config): self
+    {
+        $printTime = date('Y-m-d H:i:s', time());
+        $product = $config['product'];
+        $orderInfo = $config['orderInfo'];
+        $orderTime = date('Y-m-d H:i:s', $orderInfo['pay_time']);
+        $this->printerContent = '<CB>**' . $config['name'] . '**</CB><BR>';
+        $this->printerContent .= '--------------------------------<BR>';
+        $this->printerContent .= '订单编号:' . $orderInfo['order_id'] . '<BR>';
+        $this->printerContent .= '打印时间: ' . $printTime . '<BR>';
+        $this->printerContent .= '付款时间: ' . $orderTime . '<BR>';
+        $this->printerContent .= '姓   名: ' . $orderInfo['real_name'] . '<BR>';
+        $this->printerContent .= '电   话: ' . $orderInfo['user_phone'] . '<BR>';
+        $this->printerContent .= '地   址: ' . $orderInfo['user_address'] . '<BR>';
+        $this->printerContent .= '赠送积分: ' . $orderInfo['gain_integral'] . '<BR>';
+        $this->printerContent .= '订单备注:' . $orderInfo['mark'] . '<BR>';
+        $this->printerContent .= '**************商品**************<BR>';
+        $this->printerContent .= '名称           单价  数量 金额<BR>';
+        $this->printerContent .= '--------------------------------<BR>';
+        foreach ($product as $item) {
+            $name = $item['productInfo']['store_name'];
+            $price = $item['truePrice'];
+            $num = $item['cart_num'];
+            $prices = bcmul((string)$item['cart_num'], (string)$item['truePrice'], 2);
+            $kw3 = '';
+            $kw1 = '';
+            $kw2 = '';
+            $kw4 = '';
+            $str = $name;
+            $blankNum = 14;//名称控制为14个字节
+            $lan = mb_strlen($str, 'utf-8');
+            $m = 0;
+            $j = 1;
+            $blankNum++;
+            $result = array();
+            if (strlen($price) < 6) {
+                $k1 = 6 - strlen($price);
+                for ($q = 0; $q < $k1; $q++) {
+                    $kw1 .= ' ';
+                }
+                $price = $price . $kw1;
+            }
+            if (strlen($num) < 3) {
+                $k2 = 3 - strlen($num);
+                for ($q = 0; $q < $k2; $q++) {
+                    $kw2 .= ' ';
+                }
+                $num = $num . $kw2;
+            }
+            if (strlen($prices) < 6) {
+                $k3 = 6 - strlen($prices);
+                for ($q = 0; $q < $k3; $q++) {
+                    $kw4 .= ' ';
+                }
+                $prices = $prices . $kw4;
+            }
+            for ($i = 0; $i < $lan; $i++) {
+                $new = mb_substr($str, $m, $j, 'utf-8');
+                $j++;
+                if (mb_strwidth($new, 'utf-8') < $blankNum) {
+                    if ($m + $j > $lan) {
+                        $m = $m + $j;
+                        $tail = $new;
+                        $lenght = iconv("UTF-8", "GBK//IGNORE", $new);
+                        $k = 14 - strlen($lenght);
+                        for ($q = 0; $q < $k; $q++) {
+                            $kw3 .= ' ';
+                        }
+                        if ($m == $j) {
+                            $tail .= $kw3 . ' ' . $price . ' ' . $num . ' ' . $prices;
+                        } else {
+                            $tail .= $kw3 . '<BR>';
+                        }
+                        break;
+                    } else {
+                        $next_new = mb_substr($str, $m, $j, 'utf-8');
+                        if (mb_strwidth($next_new, 'utf-8') < $blankNum) continue;
+                        else {
+                            $m = $i + 1;
+                            $result[] = $new;
+                            $j = 1;
+                        }
+                    }
+                }
+            }
+            $head = '';
+            foreach ($result as $key => $value) {
+                if ($key < 1) {
+                    $v_lenght = iconv("UTF-8", "GBK//IGNORE", $value);
+                    $v_lenght = strlen($v_lenght);
+                    if ($v_lenght == 13) $value = $value . " ";
+                    $head .= $value . ' ' . $price . ' ' . $num . ' ' . $prices;
+                } else {
+                    $head .= $value . '<BR>';
+                }
+            }
+            $this->printerContent .= $head . $tail;
+            unset($price);
+        }
+        $this->printerContent .= '--------------------------------<BR>';
+        $this->printerContent .= '合计:' . number_format($orderInfo['total_price'], 1) . '元<BR>';
+        $this->printerContent .= '邮费:' . number_format($orderInfo['pay_postage'], 1) . '元<BR>';
+        $this->printerContent .= '优惠:' . number_format($orderInfo['coupon_price'], 1) . '元<BR>';
+        $this->printerContent .= '抵扣:' . number_format($orderInfo['deduction_price'], 1) . '元<BR>';
+        $this->printerContent .= '实际支付:' . number_format($orderInfo['pay_price'], 1) . '元<BR>';
+        $this->printerContent .= '<QR>' . $config['url'] . '</QR>';//把解析后的二维码生成的字符串用标签套上即可自动生成二维码
+        return $this;
+    }
+}

+ 53 - 6
crmeb/public/install/crmeb.sql

@@ -27857,6 +27857,7 @@ CREATE TABLE IF NOT EXISTS `eb_store_coupon_issue` (
   `end_time` int(10) NOT NULL DEFAULT '0' COMMENT '优惠券领取结束时间',
   `total_count` int(10) NOT NULL DEFAULT '0' COMMENT '优惠券领取数量',
   `remain_count` int(10) NOT NULL DEFAULT '0' COMMENT '优惠券剩余领取数量',
+  `receive_limit` int(10) NOT NULL DEFAULT '0' COMMENT '每个人个领取的优惠券数量',
   `is_permanent` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否无限张数',
   `status` tinyint(1) NOT NULL DEFAULT '1' COMMENT '1 正常 0 未开启 -1 已无效',
   `is_give_subscribe` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否首次关注赠送 0-否(默认) 1-是',
@@ -27901,8 +27902,7 @@ INSERT INTO `eb_store_coupon_issue` (`id`, `cid`, `coupon_title`, `start_time`,
 CREATE TABLE IF NOT EXISTS `eb_store_coupon_issue_user` (
   `uid` int(10) NOT NULL DEFAULT '0' COMMENT '领取优惠券用户ID',
   `issue_coupon_id` int(10) NOT NULL DEFAULT '0' COMMENT '优惠券前台领取ID',
-  `add_time` int(10) NOT NULL DEFAULT '0' COMMENT '领取时间',
-  UNIQUE KEY `uid` (`uid`,`issue_coupon_id`) USING BTREE
+  `add_time` int(10) NOT NULL DEFAULT '0' COMMENT '领取时间'
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='优惠券前台用户领取记录表';
 
 -- --------------------------------------------------------
@@ -27914,6 +27914,7 @@ CREATE TABLE IF NOT EXISTS `eb_store_coupon_issue_user` (
 CREATE TABLE IF NOT EXISTS `eb_store_coupon_product` (
   `coupon_id` int(11) NOT NULL DEFAULT '0' COMMENT '优惠卷模板id',
   `product_id` int(11) NOT NULL DEFAULT '0' COMMENT '商品id',
+  `category_id` int(11) NOT NULL DEFAULT '0' COMMENT '分类id',
   KEY `coupon_id` (`coupon_id`)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='优惠卷模板关联列表';
 
@@ -33177,7 +33178,7 @@ INSERT INTO `eb_system_config` (`id`, `menu_name`, `type`, `input_type`, `config
 (383, 'tengxun_appid', 'text', 'input', 82, '', 0, '', 0, 0, '\"\"', '腾讯云APPID', '腾讯云APPID', 0, 1),
 (384, 'extract_type', 'checkbox', 'input', 74, '0=>银行卡\n1=>微信\n2=>支付宝', 1, '', 0, 0, '[\"0\",\"1\",\"2\"]', '提现方式', '提现方式', 0, 1),
 (385, 'integral_frozen', 'text', 'input', 11, '', 1, '', 100, 0, '\"0\"', '积分冻结(天)', '积分冻结(天)', 0, 1),
-(386, 'print_type', 'radio', 'input', 86, '1=>易联云', 1, '', 0, 0, '\"1\"', '平台选择', '平台选择', 0, 1),
+(386, 'print_type', 'radio', 'input', 86, '1=>易联云\n2=>飞鹅云', 1, '', 0, 0, '1', '平台选择', '平台选择', 0, 1),
 (387, 'config_export_type', 'radio', 'input', 93, '1=>一号通', 1, '', 0, 0, '\"1\"', '电子面单类型', '电子面单类型', 0, 1),
 (388, 'customer_corpId', 'text', 'input', 69, '', 1, '', 100, 0, '\"\"', '企业ID', '小程序需要跳转企业微信客服的话需要配置此项', 0, 1),
 (389, 'create_wechat_user', 'radio', 'input', 2, '1=>开启\r\n0=>关闭', 1, '', 0, 0, '0', '关注公众号是否生成用户', '关注公众号是否生成用户', 0, 1),
@@ -33203,7 +33204,11 @@ INSERT INTO `eb_system_config` (`id`, `menu_name`, `type`, `input_type`, `config
 (416, 'reward_money', 'text', 'number', 105, '', 1, '', 0, 0, '\"0\"', '赠送余额(元)', '新用户奖励金额,必须大于等于0,0为不赠送', 0, 1),
 (417, 'reward_integral', 'text', 'number', 105, '', 1, '', 0, 0, '\"0\"', '赠送积分', '新用户奖励积分,必须大于等于0,0为不赠送', 0, 1),
 (418, 'hs_accesskey', 'text', 'input', 106, '', 1, '', 0, 0, '\"AKLTMzkzZTEzNjg3OTg2NDViM2IwNmFlYzhmNzE4MmI4YmI\"', '火山翻译AccessKey', '机器翻译仅支持火山翻译', 1, 1),
-(419, 'hs_secretkey', 'text', 'input', 106, '', 1, '', 0, 0, '\"TVRneU16STFOVFV4WVRkbE5ERTJaV0pqWm1aaU1UaGlNVFppWldZeE1HUQ==\"', '火山翻译SecretKey', '机器翻译仅支持火山翻译', 0, 1);
+(419, 'hs_secretkey', 'text', 'input', 106, '', 1, '', 0, 0, '\"TVRneU16STFOVFV4WVRkbE5ERTJaV0pqWm1aaU1UaGlNVFppWldZeE1HUQ==\"', '火山翻译SecretKey', '机器翻译仅支持火山翻译', 0, 1),
+(420, 'fey_user', 'text', 'input', 107, '', 1, '', 0, 0, '\"\"', '飞鹅云USER', '飞鹅云后台注册账号', 10, 1),
+(421, 'fey_ukey', 'text', 'input', 107, '', 1, '', 0, 0, '\"\"', '飞鹅云UYEK', '飞鹅云后台注册账号后生成的UKEY 【备注:这不是填打印机的KEY】', 7, 1),
+(422, 'fey_sn', 'text', 'input', 107, '', 1, '', 100, 0, '\"\"', '飞鹅云SN', '打印机标签上的编号,必须要在管理后台里添加打印机或调用API接口添加之后,才能调用API', 0, 1);
+
 -- --------------------------------------------------------
 
 --
@@ -33284,7 +33289,8 @@ INSERT INTO `eb_system_config_tab` (`id`, `pid`, `title`, `eng_title`, `status`,
 (103, 102, '基础配置', 'out_basic', 1, 0, '', 3, 0),
 (104, 102, '推送配置', 'out_push', 1, 0, '', 3, 0),
 (105, 100, '新用户设置', 'new_user_setting', 1, 0, '', 0, 0),
-(106, 5, '机器翻译配置', 'online_translation', 1, 0, '', 0, 0);
+(106, 5, '机器翻译配置', 'online_translation', 1, 0, '', 0, 0),
+(107, 21, '飞鹅云配置', 'fey_config', 1, 0, '', 0, 0);
 
 -- --------------------------------------------------------
 
@@ -34331,7 +34337,8 @@ INSERT INTO `eb_system_menus` (`id`, `pid`, `icon`, `menu_name`, `module`, `cont
 (1071, 56, '', '文件管理', 'admin', '', '', '', '', '[]', 0, 1, 0, 1, '/admin/system/maintain/system_file/opendir', '25/56', 1, '', 0, 'system-maintain-system-file', 0),
 (1072, 1064, '', '接口文档', 'admin', '', '', '', '', '[]', 0, 1, 0, 1, '/admin/setting/system_out_interface/index', '56/1064', 1, '', 0, 'setting-system-out-interface-index', 0),
 (1073, 25, '', '数据维护', 'admin', '', '', '', '', '[]', 7, 1, 0, 1, 'system/database/index', '25', 1, '', 0, 'system-database-index', 0),
-(1075, 731, '', '会员配置', 'admin', '', '', '', '', '[]', 6, 1, 0, 1, '/admin/marketing/member/system_config/3/67', '27/731', 1, '', 0, 'marketing-member-system_config', 0);
+(1075, 731, '', '会员配置', 'admin', '', '', '', '', '[]', 6, 1, 0, 1, '/admin/marketing/member/system_config/3/67', '27/731', 1, '', 0, 'marketing-member-system_config', 0),
+(1076, 56, '', '定时任务', 'admin', '', '', '', '', '[]', 0, 1, 0, 1, '/admin/system/crontab', '25/56', 1, '', 0, 'system-crontab-index', 0);
 
 -- --------------------------------------------------------
 
@@ -34530,6 +34537,46 @@ CREATE TABLE IF NOT EXISTS `eb_system_store_staff` (
 
 -- --------------------------------------------------------
 
+--
+-- 表的结构 `eb_system_timer`
+--
+
+CREATE TABLE IF NOT EXISTS `eb_system_timer` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `name` varchar(255) NOT NULL DEFAULT '' COMMENT '定时器名称',
+  `mark` varchar(255) NOT NULL DEFAULT '' COMMENT '标签',
+  `content` varchar(255) NOT NULL DEFAULT '' COMMENT '说明',
+  `type` tinyint(1) NOT NULL DEFAULT '0' COMMENT '周期状态 1=每隔多少秒 2=每隔多少分钟 3=每隔多少小时 4=每隔多少天 5=每天几点执行 6=每周周几几点执行 7=每月几号几点执行',
+  `week` int(11) NOT NULL DEFAULT '0' COMMENT '周',
+  `day` int(11) NOT NULL DEFAULT '0' COMMENT '日',
+  `hour` int(11) NOT NULL DEFAULT '0' COMMENT '时',
+  `minute` int(11) NOT NULL DEFAULT '0' COMMENT '分',
+  `second` int(11) NOT NULL DEFAULT '0' COMMENT '秒',
+  `last_execution_time` int(11) NOT NULL DEFAULT '0' COMMENT '上次执行时间',
+  `next_execution_time` int(11) NOT NULL DEFAULT '0' COMMENT '下次执行时间',
+  `add_time` int(11) NOT NULL DEFAULT '0' COMMENT '添加时间',
+  `is_del` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否删除',
+  `is_open` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否开启',
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8 COMMENT='定时器';
+
+--
+-- 转存表中的数据 `eb_system_timer`
+--
+
+INSERT INTO `eb_system_timer` (`id`, `name`, `mark`, `content`, `type`, `week`, `day`, `hour`, `minute`, `second`, `last_execution_time`, `next_execution_time`, `add_time`, `is_del`, `is_open`) VALUES
+(1, '未支付自动取消订单', 'order_cancel', '每隔30秒执行自动取消到期未支付的订单', 1, 1, 1, 1, 30, 30, 0, 1670642407, 1670642377, 0, 1),
+(2, '拼团到期订单处理', 'pink_expiration', '每隔1分钟拼团到期之后的操作', 2, 1, 1, 1, 1, 0, 0, 1670642487, 1670642427, 0, 1),
+(3, '到期自动解绑上级', 'agent_unbind', '每隔1分钟执行到期的绑定关系的解除', 2, 1, 1, 1, 1, 0, 0, 1670642534, 1670642474, 0, 1),
+(4, '自动更新直播商品状态', 'live_product_status', '每隔3分钟执行更新直播商品状态', 2, 1, 1, 1, 3, 0, 0, 1670642694, 1670642514, 0, 1),
+(5, '自动更新直播间状态', 'live_room_status', '每隔3分钟执行更新直播间状态', 2, 1, 1, 1, 3, 0, 0, 1670642709, 1670642529, 0, 1),
+(6, '订单自动收货', 'take_delivery', '每隔5分钟执行订单到期自动收货', 2, 1, 1, 1, 5, 0, 0, 1670642891, 1670642591, 0, 1),
+(7, '预售商品到期自动下架', 'advance_off', '每隔5分钟执行预售商品到期下架', 2, 1, 1, 1, 5, 0, 0, 1670642913, 1670642613, 0, 1),
+(8, '订单商品自动好评', 'product_replay', '每隔5分钟执行订单到期商品好评', 2, 1, 1, 1, 5, 0, 0, 1670642933, 1670642633, 0, 1),
+(9, '清除昨日海报', 'clear_poster', '每天0时30分0秒执行一次清除昨日海报', 5, 1, 1, 0, 30, 0, 0, 1670862600, 1670815378, 0, 1);
+
+-- --------------------------------------------------------
+
 --
 -- 表的结构 `eb_system_user_level`
 --

+ 1 - 0
crmeb/vendor/composer/autoload_psr4.php

@@ -16,6 +16,7 @@ return array(
     'crmeb\\' => array($baseDir . '/crmeb'),
     'app\\' => array($baseDir . '/app'),
     'ZipStream\\' => array($vendorDir . '/maennchen/zipstream-php/src'),
+    'Workerman\\Crontab\\' => array($vendorDir . '/workerman/crontab/src'),
     'Workerman\\' => array($vendorDir . '/workerman/workerman'),
     'Volc\\' => array($vendorDir . '/volcengine/volc-sdk-php/src'),
     'Test\\' => array($vendorDir . '/volcengine/volc-sdk-php/tests'),

+ 5 - 0
crmeb/vendor/composer/autoload_static.php

@@ -119,6 +119,7 @@ class ComposerStaticInitf16474ac994ccc25392f403933800b79
         ),
         'W' => 
         array (
+            'Workerman\\Crontab\\' => 18,
             'Workerman\\' => 10,
         ),
         'V' => 
@@ -291,6 +292,10 @@ class ComposerStaticInitf16474ac994ccc25392f403933800b79
         array (
             0 => __DIR__ . '/..' . '/maennchen/zipstream-php/src',
         ),
+        'Workerman\\Crontab\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/workerman/crontab/src',
+        ),
         'Workerman\\' => 
         array (
             0 => __DIR__ . '/..' . '/workerman/workerman',

+ 59 - 0
crmeb/vendor/composer/installed.json

@@ -5358,6 +5358,65 @@
             "homepage": "http://www.workerman.net",
             "install-path": "../workerman/channel"
         },
+        {
+            "name": "workerman/crontab",
+            "version": "v1.0.2",
+            "version_normalized": "1.0.2.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/walkor/crontab.git",
+                "reference": "28106241415049ee340a8a7cd9b640165240a2fa"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/walkor/crontab/zipball/28106241415049ee340a8a7cd9b640165240a2fa",
+                "reference": "28106241415049ee340a8a7cd9b640165240a2fa",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=7.0",
+                "workerman/workerman": ">=3.5.0"
+            },
+            "time": "2021-10-06T14:18:14+00:00",
+            "type": "library",
+            "installation-source": "dist",
+            "autoload": {
+                "psr-4": {
+                    "Workerman\\Crontab\\": "./src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "walkor",
+                    "email": "walkor@workerman.net",
+                    "homepage": "http://www.workerman.net",
+                    "role": "Developer"
+                }
+            ],
+            "description": "A crontab written in PHP based on workerman",
+            "homepage": "http://www.workerman.net",
+            "keywords": [
+                "crontab"
+            ],
+            "support": {
+                "email": "walkor@workerman.net",
+                "forum": "http://wenda.workerman.net/",
+                "issues": "https://github.com/walkor/workerman/issues",
+                "source": "https://github.com/walkor/crontab",
+                "wiki": "http://doc.workerman.net/"
+            },
+            "install-path": "../workerman/crontab"
+        },
         {
             "name": "workerman/workerman",
             "version": "v3.5.19",

+ 15 - 6
crmeb/vendor/composer/installed.php

@@ -1,9 +1,9 @@
 <?php return array(
     'root' => array(
         'name' => 'topthink/think',
-        'pretty_version' => '4.6.0.x-dev',
-        'version' => '4.6.0.9999999-dev',
-        'reference' => '24e1593a45135b3f38cacaa456385e38a370f0e6',
+        'pretty_version' => '4.7.0.x-dev',
+        'version' => '4.7.0.9999999-dev',
+        'reference' => 'a788475ebfcfc447b5f262ea3f803a6fd9b89a8d',
         'type' => 'project',
         'install_path' => __DIR__ . '/../../',
         'aliases' => array(),
@@ -827,9 +827,9 @@
             'dev_requirement' => false,
         ),
         'topthink/think' => array(
-            'pretty_version' => '4.6.0.x-dev',
-            'version' => '4.6.0.9999999-dev',
-            'reference' => '24e1593a45135b3f38cacaa456385e38a370f0e6',
+            'pretty_version' => '4.7.0.x-dev',
+            'version' => '4.7.0.9999999-dev',
+            'reference' => 'a788475ebfcfc447b5f262ea3f803a6fd9b89a8d',
             'type' => 'project',
             'install_path' => __DIR__ . '/../../',
             'aliases' => array(),
@@ -934,6 +934,15 @@
             'aliases' => array(),
             'dev_requirement' => false,
         ),
+        'workerman/crontab' => array(
+            'pretty_version' => 'v1.0.2',
+            'version' => '1.0.2.0',
+            'reference' => '28106241415049ee340a8a7cd9b640165240a2fa',
+            'type' => 'library',
+            'install_path' => __DIR__ . '/../workerman/crontab',
+            'aliases' => array(),
+            'dev_requirement' => false,
+        ),
         'workerman/workerman' => array(
             'pretty_version' => 'v3.5.19',
             'version' => '3.5.19.0',

+ 1 - 1
crmeb/vendor/services.php

@@ -1,5 +1,5 @@
 <?php 
-// This file is automatically generated at:2022-11-17 19:08:04
+// This file is automatically generated at:2022-12-10 14:44:41
 declare (strict_types = 1);
 return array (
   0 => 'think\\captcha\\CaptchaService',

+ 29 - 0
crmeb/vendor/workerman/crontab/README.md

@@ -0,0 +1,29 @@
+# Crontab
+A crontab with precision in seconds written in PHP based on [workerman](https://github.com/walkor/workerman).
+
+# Install
+```
+composer require workerman/crontab
+```
+
+# Usage
+start.php
+```php
+<?php
+use Workerman\Worker;
+require __DIR__ . '/../vendor/autoload.php';
+
+use Workerman\Crontab\Crontab;
+$worker = new Worker();
+
+$worker->onWorkerStart = function () {
+    // Execute the function in the first second of every minute.
+    new Crontab('1 * * * * *', function(){
+        echo date('Y-m-d H:i:s')."\n";
+    });
+};
+
+Worker::runAll();
+```
+
+Run with commands `php start.php start` or php `start.php start -d`

+ 34 - 0
crmeb/vendor/workerman/crontab/composer.json

@@ -0,0 +1,34 @@
+{
+    "name": "workerman/crontab",
+    "type": "library",
+    "keywords": [
+        "crontab"
+    ],
+    "homepage": "http://www.workerman.net",
+    "license": "MIT",
+    "description": "A crontab written in PHP based on workerman",
+    "authors": [
+        {
+            "name": "walkor",
+            "email": "walkor@workerman.net",
+            "homepage": "http://www.workerman.net",
+            "role": "Developer"
+        }
+    ],
+    "support": {
+        "email": "walkor@workerman.net",
+        "issues": "https://github.com/walkor/workerman/issues",
+        "forum": "http://wenda.workerman.net/",
+        "wiki": "http://doc.workerman.net/",
+        "source": "https://github.com/walkor/crontab"
+    },
+    "require": {
+        "php": ">=7.0",
+        "workerman/workerman": ">=3.5.0"
+    },
+    "autoload": {
+        "psr-4": {
+            "Workerman\\Crontab\\": "./src"
+        }
+    }
+}

+ 16 - 0
crmeb/vendor/workerman/crontab/example/test.php

@@ -0,0 +1,16 @@
+<?php
+use Workerman\Worker;
+require __DIR__ . '/../vendor/autoload.php';
+
+use Workerman\Crontab\Crontab;
+
+$worker = new Worker();
+
+$worker->onWorkerStart = function () {
+    // Execute the function in the first second of every minute.
+    new Crontab('1 * * * * *', function(){
+        echo date('Y-m-d H:i:s')."\n";
+    });
+};
+
+Worker::runAll();

+ 177 - 0
crmeb/vendor/workerman/crontab/src/Crontab.php

@@ -0,0 +1,177 @@
+<?php
+/**
+ * This file is part of workerman.
+ *
+ * Licensed under The MIT License
+ * For full copyright and license information, please see the MIT-LICENSE.txt
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @author    walkor<walkor@workerman.net>
+ * @copyright walkor<walkor@workerman.net>
+ * @link      http://www.workerman.net/
+ * @license   http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+namespace Workerman\Crontab;
+use Workerman\Lib\Timer;
+
+/**
+ * Class Crontab
+ * @package Workerman\Crontab
+ */
+class Crontab
+{
+    /**
+     * @var string
+     */
+    protected $_rule;
+
+    /**
+     * @var callable
+     */
+    protected $_callback;
+
+    /**
+     * @var string
+     */
+    protected $_name;
+
+    /**
+     * @var int
+     */
+    protected $_id;
+
+    /**
+     * @var array
+     */
+    protected static $_instances = [];
+
+    /**
+     * Crontab constructor.
+     * @param $rule
+     * @param $callback
+     * @param null $name
+     */
+    public function __construct($rule, $callback, $name = null)
+    {
+        $this->_rule = $rule;
+        $this->_callback = $callback;
+        $this->_name = $name;
+        $this->_id = static::createId();
+        static::$_instances[$this->_id] = $this;
+        static::tryInit();
+    }
+
+    /**
+     * @return string
+     */
+    public function getRule()
+    {
+        return $this->_rule;
+    }
+
+    /**
+     * @return callable
+     */
+    public function getCallback()
+    {
+        return $this->_callback;
+    }
+
+    /**
+     * @return string
+     */
+    public function getName()
+    {
+        return $this->_name;
+    }
+
+    /**
+     * @return int
+     */
+    public function getId()
+    {
+        return $this->_id;
+    }
+
+    /**
+     * @return bool
+     */
+    public function destroy()
+    {
+        return static::remove($this->_id);
+    }
+
+    /**
+     * @return array
+     */
+    public static function getAll()
+    {
+        return static::$_instances;
+    }
+
+    /**
+     * @param $id
+     * @return bool
+     */
+    public static function remove($id)
+    {
+        if ($id instanceof Crontab) {
+            $id = $id->getId();
+        }
+        if (!isset(static::$_instances[$id])) {
+            return false;
+        }
+        unset(static::$_instances[$id]);
+        return true;
+    }
+
+    /**
+     * @return int
+     */
+    protected static function createId()
+    {
+        static $id = 0;
+        return ++$id;
+    }
+
+    /**
+     * tryInit
+     */
+    protected static function tryInit()
+    {
+        static $inited = false;
+        if ($inited) {
+            return;
+        }
+        $inited = true;
+        $parser = new Parser();
+        $callback = function () use ($parser, &$callback) {
+            foreach (static::$_instances as $crontab) {
+                $rule = $crontab->getRule();
+                $cb = $crontab->getCallback();
+                if (!$cb || !$rule) {
+                    continue;
+                }
+                $times = $parser->parse($rule);
+                $now = time();
+                foreach ($times as $time) {
+                    $t = $time-$now;
+                    if ($t <= 0) {
+                        $t = 0.000001;
+                    }
+                    Timer::add($t, $cb, null, false);
+                }
+            }
+            Timer::add(60 - time()%60, $callback, null, false);
+        };
+
+        $next_time = time()%60;
+        if ($next_time == 0) {
+            $next_time = 0.00001;
+        } else {
+            $next_time = 60 - $next_time;
+        }
+        Timer::add($next_time, $callback, null, false);
+    }
+
+}

+ 171 - 0
crmeb/vendor/workerman/crontab/src/Parser.php

@@ -0,0 +1,171 @@
+<?php
+/**
+ * @author:  Jan Konieczny <jkonieczny@gmail.com>, group@hyperf.io
+ * @license: http://www.gnu.org/licenses/
+ * @license: https://github.com/hyperf/hyperf/blob/master/LICENSE
+ *
+ *  This is a simple script to parse crontab syntax to get the execution time
+ *
+ *  Eg.:   $timestamp = Crontab::parse('12 * * * 1-5');
+ *
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * Provides basic cron syntax parsing functionality
+ *
+ * @author:  Jan Konieczny <jkonieczny@gmail.com>, group@hyperf.io
+ * @license: http://www.gnu.org/licenses/
+ * @license  https://github.com/hyperf/hyperf/blob/master/LICENSE
+ */
+
+namespace Workerman\Crontab;
+
+/**
+ * Class Parser
+ * @package Workerman\Crontab
+ */
+class Parser
+{
+    /**
+     *  Finds next execution time(stamp) parsin crontab syntax.
+     *
+     * @param string $crontab_string :
+     *   0    1    2    3    4    5
+     *   *    *    *    *    *    *
+     *   -    -    -    -    -    -
+     *   |    |    |    |    |    |
+     *   |    |    |    |    |    +----- day of week (0 - 6) (Sunday=0)
+     *   |    |    |    |    +----- month (1 - 12)
+     *   |    |    |    +------- day of month (1 - 31)
+     *   |    |    +--------- hour (0 - 23)
+     *   |    +----------- min (0 - 59)
+     *   +------------- sec (0-59)
+     *
+     * @param null|int $start_time
+     * @throws \InvalidArgumentException
+     * @return int[]
+     */
+    public function parse($crontab_string, $start_time = null)
+    {
+        if (! $this->isValid($crontab_string)) {
+            throw new \InvalidArgumentException('Invalid cron string: ' . $crontab_string);
+        }
+        $start_time = $start_time ? $start_time : time();
+        $date = $this->parseDate($crontab_string);
+        if (in_array((int) date('i', $start_time), $date['minutes'])
+            && in_array((int) date('G', $start_time), $date['hours'])
+            && in_array((int) date('j', $start_time), $date['day'])
+            && in_array((int) date('w', $start_time), $date['week'])
+            && in_array((int) date('n', $start_time), $date['month'])
+        ) {
+            $result = [];
+            foreach ($date['second'] as $second) {
+                $result[] = $start_time + $second;
+            }
+            return $result;
+        }
+        return [];
+    }
+
+    public function isValid(string $crontab_string): bool
+    {
+        if (! preg_match('/^((\*(\/[0-9]+)?)|[0-9\-\,\/]+)\s+((\*(\/[0-9]+)?)|[0-9\-\,\/]+)\s+((\*(\/[0-9]+)?)|[0-9\-\,\/]+)\s+((\*(\/[0-9]+)?)|[0-9\-\,\/]+)\s+((\*(\/[0-9]+)?)|[0-9\-\,\/]+)\s+((\*(\/[0-9]+)?)|[0-9\-\,\/]+)$/i', trim($crontab_string))) {
+            if (! preg_match('/^((\*(\/[0-9]+)?)|[0-9\-\,\/]+)\s+((\*(\/[0-9]+)?)|[0-9\-\,\/]+)\s+((\*(\/[0-9]+)?)|[0-9\-\,\/]+)\s+((\*(\/[0-9]+)?)|[0-9\-\,\/]+)\s+((\*(\/[0-9]+)?)|[0-9\-\,\/]+)$/i', trim($crontab_string))) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Parse each segment of crontab string.
+     */
+    protected function parseSegment(string $string, int $min, int $max, int $start = null)
+    {
+        if ($start === null || $start < $min) {
+            $start = $min;
+        }
+        $result = [];
+        if ($string === '*') {
+            for ($i = $start; $i <= $max; ++$i) {
+                $result[] = $i;
+            }
+        } elseif (strpos($string, ',') !== false) {
+            $exploded = explode(',', $string);
+            foreach ($exploded as $value) {
+                if (strpos($value, '/') !== false || strpos($string, '-') !== false) {
+                    $result = array_merge($result, $this->parseSegment($value, $min, $max, $start));
+                    continue;
+                }
+                if (trim($value) === '' || ! $this->between((int) $value, (int) ($min > $start ? $min : $start), (int) $max)) {
+                    continue;
+                }
+                $result[] = (int) $value;
+            }
+        } elseif (strpos($string, '/') !== false) {
+            $exploded = explode('/', $string);
+            if (strpos($exploded[0], '-') !== false) {
+                [$nMin, $nMax] = explode('-', $exploded[0]);
+                $nMin > $min && $min = (int) $nMin;
+                $nMax < $max && $max = (int) $nMax;
+            }
+            $start < $min && $start = $min;
+            for ($i = $start; $i <= $max;) {
+                $result[] = $i;
+                $i += $exploded[1];
+            }
+        } elseif (strpos($string, '-') !== false) {
+            $result = array_merge($result, $this->parseSegment($string . '/1', $min, $max, $start));
+        } elseif ($this->between((int) $string, $min > $start ? $min : $start, $max)) {
+            $result[] = (int) $string;
+        }
+        return $result;
+    }
+
+    /**
+     * Determire if the $value is between in $min and $max ?
+     */
+    private function between(int $value, int $min, int $max): bool
+    {
+        return $value >= $min && $value <= $max;
+    }
+
+
+    private function parseDate(string $crontab_string): array
+    {
+        $cron = preg_split('/[\\s]+/i', trim($crontab_string));
+        if (count($cron) == 6) {
+            $date = [
+                'second'  => $this->parseSegment($cron[0], 0, 59),
+                'minutes' => $this->parseSegment($cron[1], 0, 59),
+                'hours'   => $this->parseSegment($cron[2], 0, 23),
+                'day'     => $this->parseSegment($cron[3], 1, 31),
+                'month'   => $this->parseSegment($cron[4], 1, 12),
+                'week'    => $this->parseSegment($cron[5], 0, 6),
+            ];
+        } else {
+            $date = [
+                'second'  => [1 => 0],
+                'minutes' => $this->parseSegment($cron[0], 0, 59),
+                'hours'   => $this->parseSegment($cron[1], 0, 23),
+                'day'     => $this->parseSegment($cron[2], 1, 31),
+                'month'   => $this->parseSegment($cron[3], 1, 12),
+                'week'    => $this->parseSegment($cron[4], 0, 6),
+            ];
+        }
+        return $date;
+    }
+}